見出し画像

CircleCI から GitHub Apps 経由で Pull Request にコメントを投げる

こんにちは、iwanaga です。
サムネイルは note creator の方のを借りました。感謝。

今回は、表題の件のソースコードを垂れ流します。
すごい初歩的なことだからか、ユースケースがレアだからか、あまり情報がなかったので。

最終形

最終的な図はこんな感じ。
terraform plan の結果を毎回 CI ページを確認するのは面倒なので、自動で PR にコメントがついてほしい。
CI ユーザ用の GitHub クレデンシャルを用意するのはバッドプラクティスなので、GitHub Apps を使いたい。

GitHub Apps を作る

この辺を参考に作成してください。
Homepage URL は必須なので、リポジトリへのリンクとかにしておきます。
Identifying and authorizing users / Post installation / Webhook は何も設定しなくて OK。作成完了したら、アカウントにインストールする。
App Id を確認し、Private keys を生成してダウンロードしておく。
こんな感じ。

Permission
private key
App ID

Private Key を CircleCI の環境変数に登録

CircleCI の環境変数は一行しか登録できないので、エンコードして登録し、CI で使用するときにデコードして使います。

# このコマンドで出力された値を GITHUB_APPS_PEM_BASE64 として CircleCI に登録する
$ > cat path/to/private-key.pem | base64

tfnotify config を書く

今回は terraform plan の結果を PR にコメントしたいので tfnotify を使います。

ci: circleci
notifier:
  github:
    token: $GITHUB_APPS_TOKEN # これ大事
    repository:
      owner: "iwanaga" # github account name
      name: "repository-name" # github repository name
terraform:
  plan:
    template: |
      {{ .Title }} <sup>[CI link!!]( {{ .Link }} )</sup>
      {{ .Message }}
      {{if .Result}}
      <pre><code>{{ .Result }}
      </pre></code>
      {{end}}
      <details><summary>Details (Click me)</summary>

      <pre><code>{{ .Body }}
      </pre></code></details>

CI の実装

version: 2.1

anchors:
  working_directory: &working_directory
    working_directory: ~/repo
  resource_class: &resource_class
    resource_class: medium

jobs:
  github-apps-token:
    <<: *resource_class
    <<: *working_directory
    docker:
      - image: cimg/base:2021.04
    steps:
      - run:
          name: commands
          command: |
            header=$(echo -n '{"alg":"RS256","typ":"JWT"}' | base64 -w 0)
            now=$(date "+%s")
            iat=$((${now} - 60))
            exp=$((${now} + (10 * 60)))
            github_app_id="000000" # App ID
            payload=$(echo -n "{\"iat\":${iat},\"exp\":${exp},\"iss\":${github_app_id}}" | base64 -w 0)
            echo $GITHUB_APPS_PEM_BASE64 | base64 --decode > ./githubapps
            unsigned_token="${header}.${payload}"
            signed_token=$(echo -n $(echo -n "${unsigned_token}" | openssl dgst -binary -sha256 -sign "./githubapps" | base64))
            rm ./githubapps
            jwt="${unsigned_token}.${signed_token}"
            user_name="iwanaga" # githubアカウント
            installation_id=$(
              curl -s -X GET \
                -H "Authorization: Bearer ${jwt}" \
                -H "Accept: application/vnd.github.v3+json" \
                "https://api.github.com/app/installations" \
              | jq -r ".[] | select(.account.login == \"${user_name}\" and .account.type == \"User\") | .id"
            ) # account_type が Organization の場合は $user_name は OrganizationName にする
            echo $(
              curl -s -X POST \
                -H "Authorization: Bearer ${jwt}" \
                -H "Accept: application/vnd.github.v3+json" \
                "https://api.github.com/app/installations/${installation_id}/access_tokens" \
              | jq -r ".token"
            ) > ./githubapps_token
      - persist_to_workspace:
          root: .
          paths:
            - ./githubapps_token

  plan:
    <<: *resource_class
    <<: *working_directory
    docker:
      - image: docker.mirror.hashicorp.services/hashicorp/terraform:light
    steps:
      - checkout
      - restore_cache:
          keys:
            - bin-tfnotify
      - run:
          name: intall tfnotify
          command: |
            if ! type tfnotify >/dev/null 2>&1; then
              apk update && apk add curl
              curl -fL -o tfnotify.tar.gz https://github.com/mercari/tfnotify/releases/download/v0.7.5/tfnotify_linux_amd64.tar.gz
              tar -C /usr/bin -xzf ./tfnotify.tar.gz
            fi
      - attach_workspace:
          at: /githubapps
      - run:
          name: terraform init & plan
          command: |
            terraform init -input=false
            terraform plan | GITHUB_APPS_TOKEN=$(cat /githubapps/githubapps_token) tfnotify --config tfnotify.yml plan
      - save_cache:
          key: bin-tfnotify
          paths:
            - /usr/bin/tfnotify

workflows:
  test_build_deploy:
    jobs:
      - github-apps-token
      - plan:
          requires:
            - github-apps-token

時限トークンの生成し、ファイルに保持

# これ以前のコードは時限トークンリクエストのための jwt を作成するためのコード、コメント部分以外はそのまま使えるはず
# 他 Job で使いたいのでファイルに出力して、persist_to_workspace します
echo $(
  curl -s -X POST \
    -H "Authorization: Bearer ${jwt}" \
    -H "Accept: application/vnd.github.v3+json" \
    "https://api.github.com/app/installations/${installation_id}/access_tokens" \
  | jq -r ".token"
) > ./githubapps_token

生成したトークンを使用して tfnotify に渡す

- attach_workspace:
    at: /githubapps # アタッチして
- run:
    name: terraform init & plan
    command: |
      terraform init -input=false
      # 環境変数にセットして tfnotify から参照できるようにする
      terraform plan | GITHUB_APPS_TOKEN=$(cat /githubapps/githubapps_token) tfnotify --config tfnotify.yml plan

まとめ

githubapps_token というファイルに書き出される時限トークンは、当然 tfnotify に限らず他の用途にも使えます。
CircleCI で GitHub API を使うようなケースがどれほどあるのか不明ですが、ご参考になれば幸いです。

関係ないですが、CircleCI の AWS OIDC 対応はいつになったら実装されるんですかね。遅れまくっているようですが…

この記事が気に入ったらサポートをしてみませんか?