この記事は Akatsuki Advent Calendar 2022 の7日目の記事です。
昨日は g_garden さんの GAS で 「slack に雑談お題投稿君」を作る でした。リモートワーク下で雑談を自然に作り出すことの難しさは日々感じます。技術で解決できるといいですね!
こんばんは。4月から長期インターンをしている otofune です。
先日担当するプロジェクトで、運用されている git のブランチを複数から1つに、リリースのトリガーを環境ごとに用意されたブランチが更新された時からタグが打たれた時に変更しました。
当然リリースするためには自動テストが通過していることを確認したいですが、再度実行すると効率がよくありません。
そこで、GitHub からコミットの status checks を取得することで代用しました。
TL;DR
サンプルはこちら https://github.com/otofune/actions-graphql-status-checks-example
- コミットの status checks は REST API ではまとめて取得できず、GraphQL API を使うほうが適切
- GraphQL API の StatusCheckRollup を取得するとよい。ただし Commit から取得する必要があり、癖がある
- GitHub Actions から GraphQL API を使うために actions/github-script が使える
- 例えば Actions などの status checks を変更する環境から参照するときは、自分自身をうまく除外してやらないといけない
status checks とはなにか、取得する方法
GitHub 上のコミットに保留、合格、不合格の状態をつける機能を総称して status checks と呼ぶようです。 コミットの横にあるチェックマークのことだと思えばよさそうです。
歴史的経緯によりこれを実現する機能には Statuses と、それより新しい Checks (2018/5/7 リリース) の2種類があります。
GitHub の UI 上ではうまくまとめて表示されていますが、調べた限りでは REST API ではまとめて取ることができず、Statuses API と Checks API を個別に取る必要があります。
もし片方のみが使われているのであればどちらかを REST API で取得すればよいですが、該当プロジェクトでは CircleCI と GitHub Actions を併用していました。
GitHub Actions は Checks API を使い、CircleCI はデフォルトで Statuses API を使います。
また Statuses はいまいちよくわからない応答を返します。List commit statuses for a reference では編集履歴のようなものが返され、Get the combined status for a specific reference を使用しないと求めたような結果になりません。
GraphQL API には StatusCheckRollup または Status という型があり、これらを使えばまとめて取得することができます。 ただし Union となっており、Statuses と Checks を意識してコードを書く必要はあります。
今回は GraphQL を使う方法を選ぶことにしました。
REST API の Statuses API の挙動がよくわからないこと、当初 Checks / Statuses をまとめた結論が取得できることも優位点であると考えて採用しました。実際には GitHub Actions から取得するために結論は常に pending
となってしまうため自前での計算が必要でした (後述)。
GitHub Actions で status checks を取得する
GraphQL API を使う
GitHub API へのリクエストが JavaScript で書ける actions/github-script
が GraphQL にも対応しています。
github
A pre-authenticated octokit/rest.js client with pagination plugins https://github.com/actions/github-script
README の先頭にはこのように書いてありますが、graphql の example が載っているほか、ソースコード を見ると github
object は @actions/github
由来です。
github.graphql
から octokit/graphql.js が使えます。
どうやって status checks を取得するか - Commit
を取得するには
StatusCheckRollup
にしろ Status
にしろ、Commit
から辿るしかありません。
かといって Query の一覧を見ても直接 Commit
を取る方法はありません。
実は Repository
の object フィールドから取得する必要があります。
repository(name: $repo, owner: $owner) { object(expression: $expression) { __typename ... on Commit { (snip) } } }
この object は nullable で、かつ Commit 専用 ではなく git の object を取得するためのものです。
必ずしも Commit が返ってくるとは限らない点に注意が必要です。Object として扱われるものはたくさんあります。(GitObject interface 参照)
しっかり null チェックをした上で __typename
を見てエラーハンドリングしましょう。
また、権限が必要であることも忘れてはいけません。checks:read
と statuses:read
を permission に与えるとよいです。
自分自身の Job を除外し、結論を計算する
Checks の内、permalink を比べると除外できます。
permalink に入っているリンクは /:owner/:repo/actions/runs/:run_id/jobs/:unknown_id
のような形式になっており run_id は context から取得できます。
なお一時期は /:owner/:repo/actions/runs/:job_id?check_suite_focus=true
という URL が入っており、Job ID は与えられていないため別途 octokit.rest.actions.listJobsForWorkflowRunAttempt
なりを使って取得する必要がありました。
いまでも API などから Job ID を取得してやると古い UI を表示することができるはずです。
除外したのち、うまく結論を計算するとよいです。
GitHub は次のような順番で判定しているっぽいと推測しています。
- 失敗している checks / statuses があれば結論を失敗にする
- 保留が1つでもあれば結論を保留にする
- すべて成功なら結論を成功にする
これで取得できました 🎉🎉🎉
詳しくは example の .github/workflows/graphql.js に書いてあります。
困った点
object の取得ミス
当初 git rev-parse ${{ github.ref }}
を expression として渡していました。
タグを契機に起動するため、もし annotated tag だと tag の object id が取得され、Tag が取得されてしまっていました。
そもそも ${{ github.sha }}
を使うか、git rev-parse ${{ github.ref }}^{commit}
のように取得するオブジェクトを明記してやるとよいです。
nullable の検査漏れ・デバッグ
言わずもがなですが。
せっかく GraphQL API を使っているので型の自動生成ができればよかったのですが、actions/github-script
で使うには大仰ではと思い、TypeScript ではなく JavaScript で書いたところかなり苦しめられました。
GitHub Actions 上でなにが起こったか追跡するのは困難です。とにかくログになんでも出力しておくと幸せになれます。
まとめ
GitHub Actions からある commit がテストに通過しているかを GitHub GraphQL API を使って取得し、活用できる状態にしました。振り返れば REST API で適当に作ってしまったほうがよかったのかもしれません。
最後まで読んで頂きありがとうございました。
明日の Akatsuki Games Advent Calendar 2022 は kazuma.ito さんの「UEでワールド構築のなにか」です!
最後に、アカツキでは一緒に働くエンジニアを募集しています。
カジュアル面談もやっていますので、気軽にご応募ください。
https://hrmos.co/pages/aktsk/jobs?category=1220948640529788928