EnvironmentsのレビューリクエストをSlackで受けとる

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Environmentsの承認が必要になったときにSlackに通知してくれるやつができた <a href="https://t.co/jptj8tHckA">pic.twitter.com/jptj8tHckA</a></p>&mdash; 🥴S.Percentage🐾 (@Pctg_x8) <a href="https://twitter.com/Pctg_x8/status/1634812207262470146?ref_src=twsrc%5Etfw">March 12, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

## これはなに？

GitHub ActionsでEnvironmentsを使ってデプロイが起きるとき、レビューが必要な場合にその旨をSlackに通知してくれるbotを作りました。

Environmentsとはその名のとおり「環境」のことで、いわゆるプロダクション環境やステージング環境といったものを表すことができます。Environmentsには環境ごとのシークレット変数を設定できるほか、その環境をつかうために一定の制約を課すことができます。
この制約のひとつにブランチ保護と同じような「必須レビュアー」の設定があり、これを使うことで自動デプロイの恩恵を得つつ「だれも知らんうちに勝手にプロダクション環境が更新された」といった事故をある程度抑制することができます。

このように便利かつ安全なEnvironmentsですが、レビューリクエストが来ても通知がないので見落としがちという問題があって、とくに個人開発でそれっぽく設定して運用していると「pushしたのに全然反映されない......」→「Approveしないといけないのわすれてたｗ」ということがよくあります(ありました)。
というわけで、通知先はどこでもいいんですがとりあえずSlackに通知(のようなもの)を流すようにしてみた、というのが今回のはなしになります。

## Envrionmentのレビューリクエストを受け取るには

GitHubではリポジトリ単位でWebhookを設定することができ、基本的にはリポジトリ上でおこる全てのイベントをこれで受け取ることができます。
で今回のレビューリクエストも受け取ることができるのかというところですが、残念ながらこれ専用のイベントは存在しません。
なので、今回はかわりにワークフロージョブの`waiting`イベントを受けて処理することにしました。

https://docs.github.com/en/webhooks-and-events/webhooks/webhook-events-and-payloads?actionType=waiting#workflow_job

将来的に役割が変わる可能性はありますが、少なくともいまはしっかり

> A job in a workflow run was created and **is waiting for approvals**.

と書かれているのでひとまずは使えるとおもいます。

### レビュアー情報がない問題

どういうペイロードが送られてくるかは上記ドキュメントを参照してほしいのですが、結構いろいろな情報が送られてきています。一部不足している情報もありますが、例えば表示用に「workflow_runの通し番号がほしい」とかであれば`workflow_job.run_url`が該当の
workflow_runを取得するREST APIのURLになっているので、このURLを叩くことで通し番号を含むworkflow_runにまつわる詳細情報を得ることができます。

ただし、それらの情報をつかっても今回一番に欲しい「レビュアー」の情報は入手できません。これはこまりました
しかたないので、これに限っては別途APIを叩いて取得することになります。

どのEnvrionmentが使われるかはペイロードに入っているので(`deployment.environment`)、そこから該当Environmentの必須レビュアーを取得することができます。
REST APIで取得することもできますが、今回は追加でコミットの情報(これもペイロードにはない)も欲しかったのでリクエストバッチングができるGraphQL APIで取得することにしました。

GitHub GraphQL APIについては詳細は割愛しますが、`https://api.github.com/graphql`に特定の形のJSONデータをPOSTすることで様々なデータをJSON形式で受け取ることができます。[GraphQL Explorer](https://docs.github.com/en/graphql/overview/explorer)というものすごく便利なものがあるので、独自にクエリを作る場合はこれを使って試行錯誤するとよいです。
今回は以下のようなクエリを発行して追加のデータを取得しています。

```graphql
query($repo_owner: String!, $repo_name: String!, $environment_name: String!, $commit_url: URI!) {
    repository(owner: $repo_owner, name: $repo_name) {
        environment(name: $environment_name) {
            protectionRules(first: 20) {
                nodes {
                    reviewers(first: 20) {
                        nodes {
                            __typename
                            ... on User {
                                name
                                login
                            }
                        }
                    }
                }
            }
        }
    }
    commit: resource(url: $commit_url) { ... on Commit { message committer { name } } }
}
```

`environment`の中の`protectionRules`が利用に必要な制約を表しており、その中の`reviewers`が必須レビュアーを表しています。レビュアーとしては実際のユーザのほかチームも設定することができるため、その2つのどちらかのデータが出現します。今回はチームを設定していないので対応は見送っています。

また、GitHub GraphQL APIには任意のリソースをURIの形で指定することができるクエリがあり(名前もまんま`resource`です)、これを使うことで奥深くにあるようなリソースも浅い階層でクエリすることができます。URIは割とよく見る形をしていて、例えばコミットであれば`https://github.com/{owner}/{repo}/commit/{sha}`という形をしており、これはGitHubのWebUI上でコミットハッシュをクリックしたときに見れるページのURLと同一のものです。

これでvariablesに適切な値をわたすことで、以下のようなJSONデータを受け取ることができます。

```json
{
    "data": {
        "repository": {
            "environment": {
                "protectionRules": {
                    "nodes": [
                        {
                            "reviewers": {
                                "nodes": [
                                    {
                                        "__typename": "User"
                                        "name": レビュアーの表示名
                                        "login": レビュアーのユーザID
                                    }, ...
                                ]
                            }
                        }, ...
                    ]
                }
            }
        },
        "commit": {
            "message": コミットメッセージ,
            "committer": {
                "name": コミッターの名前
            }
        }
    }
}
```

GitHub GraphQL APIは、複数のデータを返すクエリはほぼ例外なくページネーションの形になっています。そのため、本来であればしっかりと複数ページある場合のハンドリングをしてあげる必要があります。今回はデータが少ないことがわかっているので1回の取得で終わりにしています。

あとはSlack通知用に表示を整形してpostすればbotとしては完成です。

## おわり

冒頭のスクショを見てもらえればわかるとおもいますが、現時点ではメンションの形になっていません。これはSlackとGitHubのユーザIDが微妙に違うからなのでしょうがないのですが、ちゃんと使えるかたちにするとなればやはりメンションのかたちにできたほうがよいので、そのあたりはそのうち対応していきたいなという気持ちです。
やるとすれば形がちがう以上なんらかのマッピングテーブルを用意するしかないかな......