Github Actionsによるpull-requestでのCIをいい感じにSlackに通知する
結論
以下のコードで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が有望そうだと思いました。
まずは実験として表示できる情報をすべて表示させてみたのですが、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 |
より詳しくは以下の公式ドキュメントも参照してみてください
Slackのメンション
Slackでのメンションを実現するには意外と手数がかかります。
0. SlackとGithubの登録メルアドが一致している
まず前提条件として、同じ個人はSlackとGithubで同じメルアドを利用していることが必要です。これはGithubのユーザーとSlackのユーザーを紐付けるためのキーとしてメルアドを利用するためです。
1. pull-request authorのメルアドをゲットする
いきなりですが実はこれがなかなか難しいです。Github Actionsのpull_requestトリガーでは github.event.pull_request
でwebhookの中身を参照することが可能ですが、ここには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で参照可能にする方法が好みです。この方法はドキュメントでも結構奥底に隠れているので知らなかった人のためにリンクを貼っておきます
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ならほとんどの環境に存在しているでしょうし、それほど複雑でもないのでしらばくはコピペ運用でもいいかなぁと思ってます。
-
Githubのこの挙動についてはこのあたりを参考:https://qiita.com/sonots/items/16df445132e704198e3e ↩︎
-
actions/checkoutのデフォルトではdepth=1でgit cloneされ、pull_requestトリガーの場合はマージコミットが先頭のコミットになるので
git log -1 HEAD
だとそのコミットを参照することになりauthor情報はどうなっているの?と思われるかもしれません。自分が試した限りではこのコミットのauthorは自分自身になっていたので問題ありませんでした。ただ、forkからのpull-requestなどのケースは試していないのでもしかしたら変になるケースはあるかもしれません。 ↩︎
Discussion