CircleCIで特定ブランチで動くワークフローをさらに分岐させたい

## はじめに
CircleCIのworkflowを使ってjobを実行する場合に、ブランチでフィルターをかけて実行するjobを決めていました。例えばリリース用のブランチへマージされた場合のみデプロイに関するjobを実行するといった使い方です。

開発をしている中でブランチよりももう少し細かい条件で分岐でjobを分けたいという要望があり、その時行った方法について紹介します。

## やりたいこと
remoteのブランチへのcommitをトリガーにブランチによって以下のようなworkflowが別れている場合に

| ブランチ |    job  |
|:-------:|----------:|
| master以外| test-job   |
| master   | test-job,deploy-job |


masterブランチへのcommitをトリガーに実行されるworkflowを二つに分けて特定の条件の場合にテストをskipするようにしたい。

| ブランチ | job  |
|:-------:|----------:|
| master以外| test-job   |
| master   | test-job,deploy-job |
| master（特定の条件を満たす）   | deploy-job |



## 前提
前提としてはworkflowを利用する

簡単のため以下のようなシンプルな`config.yml`があった場合で説明していきます。

```yml:.circleci/config.yml
version: 2.1 # executorsのみ2.1の機能を使っていますが、それ以外は2.0の機能です
executors:
  default:
    working_directory: ~/sample
    docker:
      - image: hoge/hoge:latest
jobs:
  test-job:
    executor:
      name: default
    steps:
      - run:
          name: test_step
          command: echo 'execute test job'
  deploy-job:
    executor:
      name: default
    steps:
      - checkout
      - run:
          name: deploy_step
          command: echo 'execute deploy job'

workflows:
  test-and-deploy:
    jobs:
      - test-job
      - deploy-job:
          requires:
            - test-job
          filters:
            branches:
              only:
                - master

```


## 採用した方法
単純な方法ですが、commit logに特定の文字列が入っている場合にテストの実行をskipするようにしました。
例えば`skip_test`のような文字列が入っている場合に実行をする。場合は`config.yml`を以下のように修正します。

```yml:.circleci/config.yml
version: 2.1
executors:
  default:
    working_directory: ~/sample
    docker:
      - image: hoge/hoge:latest
jobs:
  test-job:
    executor:
      name: default
    steps:
      - checkout
      - run:
          name: test_step
          command: | # commandの内容を修正
            if [ -z "`git log -1 --oneline | grep skip_test`" ]; then
              echo 'execute test job'
            else
              echo "skip test job"
            fi
  deploy-job:
    executor:
      name: default
    steps:
      - run:
          name: deploy_step
          command: echo 'execute deploy job'

workflows:
  test-and-deploy:
    jobs:
      - test-job
      - deploy-job:
          requires:
            - test-job
          filters:
            branches:
              only:
                - master

```

シンプルな方法はですが微妙な点もあります。jobの中でテストを実行するかどうかを判断しているため、テストの実行自体はスキップできますが、その前処理（例でいうとexecutorの中の処理や、checkoutの処理）は実行されるためそのそのオーバーヘッドは必ずかかってしまいます。

<img width="562" alt="スクリーンショット 2019-03-16 23.19.32.png" src="https://qiita-image-store.s3.amazonaws.com/0/132008/54414f4f-6cf2-4c12-605d-11b63d6f088b.png">
<img width="771" alt="スクリーンショット 2019-03-17 0.41.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/132008/77db131c-00c2-3627-16bf-049a11c4a3e9.png">


## 採用しなかった方法
gitのtagを使って実行するjobにfilterをかけることもできるため、tagでの制御も検討しましたが以下のような理由で採用しませんでした。

- 今の開発チームではtagを使った運用をしていない
- テストをスキップするためにタグを使い分けるのもタグの使い方として微妙
- branchでfilterをかける場合と比較してfilterが複雑になってくる

以下はtagを使った制御の例になります。

| 発火条件 | job  |
|:-------:|----------:|
| master以外のブランチへのコミット | test-job   |
| skiptestの文字列を含まないtagの作成 | test-job,deploy-job |
| skiptestの文字列を含むtagの作成 | deploy-job |


```yml:.circleci/config.yml
# workflowsのみ抜粋
workflows:
  test:
    jobs:
      - test-job:
          filters:
            branches:
              ignore: master # masterブランチでどのjobを実行するかはtagで制御するためignore
  test-and-deploy:
    jobs:
      - test-job:
          filters:
            tags:
              only: /^(?!.*skiptest).*$/ # skiptestを含まないタグの場合のみ発火
            branches:
              ignore: /.*/ # branchへのcommitで発火しないようにignore
      - deploy-job:
          requires:
            - test-job
          filters:
            tags:
              only: /^(?!.*skiptest).*$/ # skiptestを含まないタグの場合のみ発火
            branches:
              ignore: /.*/ # branchへのcommitで発火しないようにignore
  only-deploy:
    jobs:
      - deploy-job:
          filters:
            tags:
              only: /^.*skiptest.*$/ # skiptestを含むタグの場合のみ発火
            branches:
              ignore: /.*/ # branchへのcommitで発火しないようにignore

```

この例ではworkflow自体がシンプルなのでそこまで複雑ではないですが、workflow自体が複雑になってくるとfilterまわりが複雑になってくるなという印象です。

## まとめ
CircleCIのworkflowを使って特定ブランチで実行されるjobをさらに分けて実行する方法を紹介しました。採用した方法では不要なオーバーヘッドがかかっているので、jobレベルで分けれてもっとシンプルにできる方法がないかなあと思うところです。