💡

Github Actionsによるpull-requestでのCIをいい感じにSlackに通知する

8 min read

結論

以下のコードでpull-requestへのリンクとChecksへのリンクを含むメッセージをSlackに投稿し、さらにメンションもできます。

- name: Get author slack user_id
  if: always()
  run: |
    EMAIL=$(git log -1 --pretty=format:%ae HEAD)
    curl -s "https://slack.com/api/users.lookupByEmail?token=${TOKEN}&email=${EMAIL}" \
      | jq -r .user.id \
      | xargs -I{} echo "SLACK_USER={}" >> $GITHUB_ENV
  env:
    TOKEN: ${{ secrets.SLACK_BOT_OAUTH_TOKEN }}
- name: Slack
  uses: 8398a7/action-slack@v3
  if: always()
  # Fail時だけ通知したい場合は if: failure()
  with:
    status: custom
    custom_payload: |
      {
        text: "<@${{ env.SLACK_USER }}>",
        attachments: [{
          color: '${{ job.status }}' === 'success' ? 'good' : '${{ job.status }}' === 'failure' ? 'danger' : 'warning',
          title: "${{ github.repository }} #${{ github.event.number}}",
          title_link: "${{ github.event.pull_request.html_url }}",
          text: "${{ github.event.pull_request.title }} #${{ github.event.number }}",
          fields: [{
            title: "Status",
            value: "${{ job.status }}",
            short: true
          }, {
            title: "Log",
            value: `<${process.env.GITHUB_SERVER_URL}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Checks URL>`,
            short: true
          }]
        }]
      }
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

pull-requestでのCI結果をSlack通知するときの問題

Github ActionsでSlack通知するActionsは既に色々存在しています。軽く調べた感じでは8398a7/action-slackが有望そうだと思いました。

https://zenn.dev/shonansurvivors/articles/fab832f7bfa8cebac24f

まずは実験として表示できる情報をすべて表示させてみたのですが、pull_requestのトリガーの場合は以下のような通知になり、残念ながらあまり有益な情報が表示できていないと感じました。

個人的に感じる問題点と要望としては、

  • pull-requestのタイトルが無いのでどれだか分かりにくい(一応refs/pull/107/mergeから番号だけは判別可能だが)
  • トリガーされたpull-requestへのリンクが無い
  • pull-requestで自動的に作られるマージコミット[1]のmessageなので情報量がゼロ
  • Slackで自分にメンションしてほしい

自分と同じような要望を感じている人がどれだけいるのか分からないですが、なるべく最小の労力でこれらを実現することができたので解説していきたいと思います。

通知内容をcustom_payloadでゴリゴリにカスタムする

8398a7/action-slackにはSlack通知の中身を完全にカスタムする方法も用意されており、ドキュメントにその方法の解説も存在します。

ドキュメントによると status: custom にした場合は custom_payload の中身が使われるようです。この custom_payload の形式はSlackのAPIにそのまま渡されるものであり、つまりSlackのAPIが許す形式を守っていればいくらでも好きなSlack通知にカスタムできるということです。

さらにサンプルコードを見るに、この custom_payload の中はjsとして評価されるようなのでちょっとした条件分岐や環境変数へのアクセスもできそうです。ということで、Github Actions側で提供されている変数とにらめっこして試行錯誤した結果、以下のようにすることで後述するSlackメンション以外を実現できました。

- name: Slack
  uses: 8398a7/action-slack@v3
  if: always()
  # Fail時だけ通知したい場合は if: failure()
  with:
    status: custom
    custom_payload: 
      {
        attachments: [{
          color: '${{ job.status }}' === 'success' ? 'good' : '${{ job.status }}' === 'failure' ? 'danger' : 'warning',
          title: "${{ github.repository }} #${{ github.event.number}}",
          title_link: "${{ github.event.pull_request.html_url }}",
          text: "${{ github.event.pull_request.title }} #${{ github.event.number }}",
          fields: [{
            title: "Status",
            value: "${{ job.status }}",
            short: true
          }, {
            title: "Log",
            value: `<${process.env.GITHUB_SERVER_URL}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Checks URL>`,
            short: true
          }]
        }]
      }
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

それぞれの変数を解説します

説明
job.status ジョブの終了ステータス。ここはサンプルのまま
github.repository {ORG}/{REPO} の文字列
github.event.number pull-requestトリガーの場合はpull-reqの番号が取得できる
github.event.pull_request.html_url pull-requestのリンク
github.event.pull_request.title pull-requestのタイトル
process.env.GITHUB_SERVER_URL 通常は https://github.com の文字列。Enterprise Serverで利用する場合を考慮してこの環境変数を利用
github.run_id Github Actionsを実行する毎に増えていくid

より詳しくは以下の公式ドキュメントも参照してみてください

https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions

https://docs.github.com/en/actions/reference/environment-variables

Slackのメンション

個人的にはそもそもpull-requestでのCIはメンションなどをせずとも待つことができるように長くとも5分以内に終わるLintや簡単なテストだけを行うことが理想だと考えていますが、特に業務でのiOS/Android/UnityアプリのようにどうしてもCIに時間がかかるケースも存在します。
そのような現場ではCIが終了してメンションが来るまでのスキマ時間に他の作業ができるので意外と重宝されたりします。

Slackでのメンションを実現するには意外と手数がかかります。

0. SlackとGithubの登録メルアドが一致している

まず前提条件として、同じ個人はSlackとGithubで同じメルアドを利用していることが必要です。これはGithubのユーザーとSlackのユーザーを紐付けるためのキーとしてメルアドを利用するためです。

1. pull-request authorのメルアドをゲットする

いきなりですが実はこれがなかなか難しいです。Github Actionsのpull_requestトリガーでは github.event.pull_requestwebhookの中身を参照することが可能ですが、ここにはGithubのユーザーidは存在しますがメルアドは含まれていません。

GithubのAPIに問い合わせればユーザーidからメルアドをゲットすることも可能だと思いますが、今回はもっとシンプルにGithubに関係なく git log でコミットに含まれるauthorがpull-requestの作者であるという仮定をしてこのメルアドを使用しています。(gitとGithubに詳しい人向けの解説[2]

EMAIL=$(git log -1 --pretty=format:%ae HEAD)

2. authorのメルアドからSlackのuser idをゲットする

API経由の投稿でメンションする場合、単に @user_name という文字列ではメンションすることはできず <@user_id> という特殊なエスケープと、普段メンションに使っているユーザー名とは別のSlack内部のuser idが必要です。

Slack内部のuser_idを取得するにはいくつか方法がありますが、動的に取得する場合には例えばusers.lookupByEmailのAPIを使います。先ほどのauthorのメルアドをこのAPIに渡すことで同ユーザーのSlack内部のuser idをゲットすることが可能です。

curl -s "https://slack.com/api/users.lookupByEmail?token=${TOKEN}&email=${EMAIL}" \
| jq -r .user.id

このSlackのAPIを呼び出すこと自体はcurlで簡単なのですが、人によってはSlackトークンの取得が少々難しいかもしれません。既にSlackのappsやbotを作ったことがある人ならいつものトークンで大丈夫だと思います。lookupByEmailのドキュメントによると users:read.email の権限があればOKなようです。

Slackのappsやbotを作ったことが無い方、あるいは大昔のhubotの時代以来SlackのAPIを触っていない方は残念ながら自分も解説できるほど詳しくはありませんので、各自で調べてみてください・・・。一応自分はここのトークンの種類別解説によるとBot tokensを使っています。

3. steps間でSlackのuser id変数を引き継ぐ

SlackのAPIを呼び出すには run stepでcurlを実行する必要がありますが、Slack通知は8398a7/action-slackのstepで行うため次のstepに変数を引き継ぐ必要があります。

これもいくつか方法があると思いますが、自分はジョブ全体から参照できる $GITHUB_ENV に追加することで次のstepで参照可能にする方法が好みです。この方法はドキュメントでも結構奥底に隠れているので知らなかった人のためにリンクを貼っておきます

https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable
curl -s "https://slack.com/api/users.lookupByEmail?token=${TOKEN}&email=${EMAIL}" \
| jq -r .user.id \
| xargs -I{} echo "SLACK_USER={}" >> $GITHUB_ENV # <= ここで環境変数に追加している

4. 8398a7/action-slackのcustom_payloadに自前でメンション用の文字列を埋め込む

8398a7/actions-slackには元々 mention というパラメータにSlackのuser idの入れることでメンションしてくれる機能が存在するのですが、 custom_payload を使う場合はこの機能が使えません。コードを読んだところ、 status: custom にした場合は自動的に生成されるメンション用の文字列よりも custom_payload の中身が優先されてしまうのでメンションされないようでした。

というわけで、仕方がないので3で環境変数に入れた SLACK_USER とSlack用のエスケープを組み合わせて <@${{ env.SLACK_USER }}> という文字列を生成し、これを custom_payload に追加します。

これらをすべて組み合わせたコードが冒頭に貼ったYAMLになります。実際に実行したときのSlack通知はこのようになります。

最低限の情報しか表示していませんが、最初の方に自分が欲しかった以下の要素は含まれているので今のところ満足しています。

  • pull-requestのauthorにメンション
  • リポジトリ名(モザイクかけてしまいましたが)とpull-requestのタイトル
  • CI結果ログへのリンク(Checks URLと書いてあるリンクです)

まとめ

Github ActionsからのSlack通知をpull-requestのCI用にゴリゴリにカスタムする方法の紹介でした。最初はこんなの既に誰かが作ってるだろうと思って調べていたのですが、あまり良さげなものが見つけられなかったのと、8398a7/action-slackの使い方を工夫するぐらいで実現できそうだったのでやってみました。自分と同様にpull-requestに特化した通知が欲しい人には役に立つかもしれません。

ちなみに独自のActionsとしてjsで作り直してリリースするべきか若干悩んでいるのですが、curlとjqならほとんどの環境に存在しているでしょうし、それほど複雑でもないのでしらばくはコピペ運用でもいいかなぁと思ってます。

脚注
  1. Githubのこの挙動についてはこのあたりを参考:https://qiita.com/sonots/items/16df445132e704198e3e ↩︎

  2. actions/checkoutのデフォルトではdepth=1でgit cloneされ、pull_requestトリガーの場合はマージコミットが先頭のコミットになるので git log -1 HEAD だとそのコミットを参照することになりauthor情報はどうなっているの?と思われるかもしれません。自分が試した限りではこのコミットのauthorは自分自身になっていたので問題ありませんでした。ただ、forkからのpull-requestなどのケースは試していないのでもしかしたら変になるケースはあるかもしれません。 ↩︎

Discussion

ログインするとコメントできます