コミットメッセージに特定の文字列が含まれていれば、ある処理を行うを実現する

## はじめに

この記事では、CircleCI で「コミットメッセージに特定の文字列が含まれていれば、ある処理を行いたい」場合の実現方法について紹介します。結論としては、`circleci-agent step halt` コマンドを利用することで比較的シンプルに実現できます。

今回は、CircleCI 2.1 で動作確認を行っています。

## 実現したかったこと

これまで、iOS プロジェクトにおいて以下のルールでワークフローを定義していました。

- master 以外のブランチは、`Unit test` を実行する
- master ブランチは、`Unit test` に加えて、`UI test` と `App Store Connect へのデプロイ` を実行する

これに加えて、新たに以下のルールを追加しました。

- master 以外のブランチは、**コミットメッセージに「kick-ui-test」が含まれていれば、** `Unit test` のあとに `UI test` を実行する

## 定義したワークフローの全体像

|![masterブランチのWorkflow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66682/6c2a563e-1ed4-e099-f680-220344755a52.png)|
| :--------------------------------: |
| 図: master ブランチ以外の Workflow |

|![masterブランチ以外のWorkflow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66682/d129031c-9cd5-cb99-d25c-0ea93762f583.png)|
| :----------------------------: |
| 図: master ブランチの Workflow |

## yaml の全体像

※ 各ジョブ内で行っている処理（呼び出している commands）については省略して例示します。

```yaml
version: 2.1

jobs:
  unit_test:
    <<: *defaults
    steps:
      - unit-test

  run_ui_test_as_needed:
    <<: *defaults
    steps:
      - checkout
      - run:
          name: Decide whether to run ui-test, depending on the content of the commit message.
          command: |
            COMMIT_MESSAGE=$(git log -1 HEAD --pretty=format:%s)
            TRIGGER_MATCH_PATTERN="^.*kick-ui-test.*$"
            if [[ ${COMMIT_MESSAGE} =~ ${TRIGGER_MATCH_PATTERN} ]]; then
              echo "Continue to run ui_test, as the commit message contains kick-ui-test."
            else
              echo "Since the commit message does not include kick-ui-test, this job will be successfully terminated."
              circleci-agent step halt
            fi
      - ui_test

  ui_test:
    <<: *defaults
    steps:
      - ui_test

  deploy_appstore:
    <<: *defaults
    steps:
      - deploy

workflows:
  build-workflow:
    jobs:
      - unit_test
      - run_ui_test_as_needed:
          requires:
            - unit_test
          filters:
            branches:
              ignore: 
                - master
      - ui_test:
          filters:
            branches:
              only:
                - master
      - deploy_appstore:
          requires:
            - unit_test
            - ui_test
          filters:
            branches:
              only:
               - master
```



## `run_ui_test_as_needed` ジョブについて

このジョブで 「**コミットメッセージに「kick-ui-test」が含まれていれば、** `Unit test` のあとに `UI test` を実行する」という制御を実現しています。

```yaml
run_ui_test_as_needed:
  <<: *defaults
  steps:
    - checkout
    - run:
        name: Decide whether to run ui-test, depending on the content of the commit message.
        command: |
          COMMIT_MESSAGE=$(git log -1 HEAD --pretty=format:%s)
          TRIGGER_MATCH_PATTERN="^.*kick-ui-test.*$"
          if [[ ${COMMIT_MESSAGE} =~ ${TRIGGER_MATCH_PATTERN} ]]; then
            echo "Continue to run ui_test, as the commit message contains kick-ui-test."
          else
            echo "Since the commit message does not include kick-ui-test, this job will be successfully terminated."
            circleci-agent step halt
          fi
    - ui_test
```

主に shell script で以下の処理を行っています。

1. 直近のコミットメッセージを取得
2. 正規表現で、特定したい文字列（今回であれば `kick-ui-test`）を定義
3. `1.` で取得したコミットメッセージに特定文字列が含まれているかを判定
   1. もし、含まれていれば何もしないことを `echo` するだけで、次のステップへ進む
   2. もし、含まれていなければその時点でジョブを正常終了する
4. `ui_test` を実行する



3.2 で、ジョブを正常終了させる際には、`circleci-agent step halt ` コマンドを利用しています。このコマンドは、ジョブを失敗させずに終了させることができるもので、ジョブを条件付きで実行する必要がある今回のようなケースに便利です。

[Ending a Job from within a  step | CircleCI](https://circleci.com/docs/2.0/configuration-reference/#ending-a-job-from-within-a-step)

## さいごに

今回は、master ブランチ以外（機能の実装途中など）でも手軽に `ui_test` を実行したいケースがあり、`run_ui_test_as_needed` job を追加しました。

CircleCI 2.0 から[次のジョブを続行する前に手動による承認操作を待つ制御](https://circleci.com/docs/ja/2.0/workflows/#%E6%89%BF%E8%AA%8D%E5%BE%8C%E3%81%AB%E5%87%A6%E7%90%86%E3%82%92%E7%B6%9A%E8%A1%8C%E3%81%99%E3%82%8B-workflow-%E3%81%AE%E4%BE%8B)が可能になりましたが、仕組み上、手動で承認する手間が発生するため、今回のようにコミットする時点で特定ジョブの実行を制御できると便利だと感じました。

## 参考

- [Ending a Job from within a  step | CircleCI]([Configuring CircleCI - CircleCI](https://circleci.com/docs/2.0/configuration-reference/#ending-a-job-from-within-a-step))