CircleCIの承認ジョブの承認者を限定したい

## はじめに

この記事は[CircleCI Advent Calendar 2020](https://qiita.com/advent-calendar/2020/circleci)の24日目の記事です。

CircleCIでカスタマーサクセスをしている鈴木と申します。

CircleCIには、ワークフローを一時停止し、[手動承認のジョブ](https://circleci.com/docs/ja/2.0/workflows/#%E6%89%8B%E5%8B%95%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%E3%83%AF%E3%83%BC%E3%82%AF%E3%83%95%E3%83%AD%E3%83%BC)を経て、ワークフローを再開する機能があります。この承認ジョブの機能を利用することで、他のチームや責任者が承認したうえで本番環境へのデプロイを行うといったことが可能です。
承認ジョブの承認を実行するApproveボタンはCircleCIのUIで以下のように表示されます。

![cci-advent-20201224-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11243/8c1e4b65-78db-94c6-767d-3149a2e3953a.png)

この機能には**そのリポジトリのWrite権限を持っているCircleCIユーザーなら誰でもこのApproveボタンを押すことができる**という大きな注意点があります。
誰にでも承認できてしまうのでは利用にふさわしくないという場面もあるでしょうが、承認ボタンを特定のユーザーにのみ表示させる方法はありません。そこで、**特定のユーザーが承認した場合はワークフローの処理が継続され、それ以外のユーザーが承認した場合には処理が失敗する**という制御によってこの問題を回避します。この方法ではGitHub OrganizationのTeamsと、CircleCIの[コンテキスト（Context）](https://circleci.com/docs/ja/2.0/contexts/#section=projects)という機能を利用します。
※ *そのため、BitbucketでCircleCIを利用している場合には適用できません。(T T)*

GitHub上で公開している[サンプルのリポジトリ](https://github.com/CircleCI-Demo-Suzuki/restricted-approval-job)とともに以下の説明をご参考ください。
 

## GitHub OrganizationでTeamsを設定する
[GitHub OrganizationでTeamsを作成する方法](https://docs.github.com/ja/free-pro-team@latest/github/setting-up-and-managing-organizations-and-teams/creating-a-team)の説明は割愛します。今回のサンプルでは`Admin`というTeamを作成し、そこに承認者としてふさわしいメンバーを登録しています。

![cci-advent-20201224-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11243/9d496523-b744-8092-e01b-74375baf0099.png)



## コンテキストを設定する
CircleCIのコンテキストは、Organizartion内の複数のプロジェクト（リポジトリ）で共通して利用する環境変数を登録する機能です。コンテキストの設定画面はCircleCIのサイドメニューにあるOrganization Settingsをクリックすると表示できます。
今回のサンプルでは以下の画面のように`approvers`という名前のコンテキストを作成しました。

![cci-advent-20201224-04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11243/dde410a0-d31d-f289-965a-9e456830ab92.png)
コンテキストの一覧画面にSecurityという項目が見えますが、作成したばかりのapproversには`All Members`と表示されています。これはOrganizartion内のCirlceCIユーザーであれば誰でもこのコンテキストを利用することができることを意味します。（のちほど消しましょう。）

リンクになっているコンテキスト名をクリックすると以下の画面に遷移します。
![cci-advent-20201224-05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11243/c13afc71-0952-b468-cbf7-95dadc2a511b.png)
↑この画面では、Security Groupの設定と環境変数(Environment Variables)の登録を行うことができます。今回のサンプルでは環境変数は必要ないので空のまま進め、Security Groupの設定を見ていきます。

Add Security Groupのボタンをクリックすると以下の画面が表示されます。
![cci-advent-20201224-08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11243/4f4bbd20-03c7-cf43-4b92-228f7c466fd1.png)

GitHub OrganizationのTeam名が一覧に表示されるので、ふさわしいグループを選択します。今回のサンプルでは事前に設定したAdminというチームを選択します。このようにSecurity Groupを設定したコンテキストを**制限付きコンテキスト(restricted context)**と呼んでいます。
なお、**初期状態で選択されているAll Membersというグループを削除する**ことを忘れないでください。All Membersが含まれたままでは承認者を限定できません。


## config.ymlを記述する

それではconfig.ymlの記述を見ていきます。
サンプルリポジトリの.circleci/config.ymlでは以下のように書いています。

```yaml:config.yml

version: 2.1

jobs:
  build:
    docker:
      - image: cimg/node:lts
    steps:
      - run:
          command: echo "Hello!" 

  deploy:
    docker:
      - image: cimg/node:lts
    steps:
      - run: 
          name: Deployment
          command: echo "Success!" 

workflows:
  build-hold-deploy:
    jobs:
      - build
      - hold:
          type: approval # 承認ジョブを表示。
          requires:
            - build
      - deploy:
          context: approvers # ここでコンテキストを参照。そのセキュリティグループに属するユーザーが実行可能。
          requires:
            - hold
```

このサンプルの`build-hold-deploy`というワークフローでは以下の3つのジョブを実行します。
`build`ジョブ : 承認前に実行されるジョブです。 
`hold`ジョブ : 承認ジョブとなります。**type: approval** により承認用のUIを呼び出します。
`deploy`ジョブ  : 承認後に実行されるジョブです。ここで呼び出したコンテキストのSecurity Groupを参照します。そのSecurity Group内のメンバーがこのジョブを実行できます。

Security Groupに指定したGitHub OrgaizationのAdminチームのメンバーが実行したビルドの結果は以下のように表示されます。
![cci-advent-20201224-09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11243/f8b69a65-e4e5-95bd-c02f-98f0b59faf28.png)



一方、Adminチーム外のメンバーが実行したビルドの結果は以下のようにdeployジョブが失敗し、ビルドのステータスは**Unauthrized**と表示されます。
![cci-advent-20201224-10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11243/98ee98a4-94f3-20f5-44ef-46bba1ea2db0.png)

## その他の承認ジョブ利用例
ここでは詳細に触れませんが、承認ジョブに関する他の利用例を紹介します。

##### APIによるApprove
2020年は[承認ジョブをApproveするAPI](https://circleci.com/docs/api/v2/#operation/approvePendingApprovalJobById "CircleCI API - Approve a job")を公開しました。承認者がCircleCIのUIにアクセスしてApproveボタンを押すのは面倒でしょうという方はぜひこちらをお試しください。

##### 多段階承認
制限付きコンテキストと承認ジョブを増やすことで、複数の承認者による承認フローを実現できます。以下のような二段階承認のワークフローのサンプルリポジトリは[こちら](https://github.com/CircleCI-Demo-Suzuki/restricted-approval-job/tree/multiple_approvals "CircleCIのワークフローで多段階承認を行う")です。
![cci-advent-20201224-11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11243/f3d39691-cb77-290c-f32b-8cb38d70b504.png)



## さいごに
承認ジョブとSecurity Groupを設定した制限付きコンテキストを組み合わせることで、特定のユーザーが承認した場合はワークフローの処理が継続され、それ以外のユーザーが承認した場合には処理が失敗するという制御を行いました。

一点、注意事項があります。GitHub OrganizationでOwner権限をもつユーザーはコンテキストのSecurity Groupに含まれていなくても承認を進めることができるのでご留意ください。

さて、2020年を振り返りますと、2019年に比べてCircleCIに触れる機会を増やすことができました。色々と要因はあるのですが、『[CirlceCI実践入門](https://gihyo.jp/book/2020/978-4-297-11411-4 "CirlceCI実践入門 - 技術評論社")』の出版が大きかったと思います。『CirlceCI実践入門』は本当に良書でわかりやすく、非技術職の私がCircleCIに参加したときにこの本があったらどんなに楽だったかと思います。

2021年は[CircleCIコミュニティのもくもく会](https://circleci.connpass.com/)の時間を使ってセットアップした[個人ブログ](https://yurify.me/)が塩漬けになっているので、少しずつ日常の記録を残してみようかなと思っています。

これからもCircleCIをよろしくお願いします！

### 追記
ついでながら、承認ジョブに関するFAQを付記しておきます。

Q. 承認ジョブで承認したユーザーはPerformanceプランのActiveユーザーとしてカウントされますか？
A. はい。カウントされます。

Q. 承認ジョブで承認を待っている間もCircleCIのクレジットを消費しますか？
A. いいえ、承認を待っているHoldステータスの間はクレジットを消費しません。