GitHub Actions - workflow_dispatch の結果も status checks に適切に反映したい
概要
本記事は、GitHub Actions Advent Calendar 2023 の 18日目の記事です。
GitHub の Pull Request にある status checks (commit status)[1] ですが、REST API を使って値が設定できることを最近知りました。
また、 workflow_call (他のワークフローから呼び出すことを許可するトリガー) を指定し、呼び出した場合、 commit status は適切に作成、更新されるようです。
「では、workflow_call を使わずに別のワークフローを呼び出した場合[2]にも、上記の API をたたけば、適切に commit status を作成、更新することは可能なんじゃないか?」
と思いつき、実際にやったらできた🥰、という記事になります。
以下では、workflow_dispatch を使って別のワークフローを呼び出し、適切に commit status を作成、更新する例を紹介します。
シーケンス図
今回作成したサンプルは、以下の流れとなっています
- PR 作成によって、main.yaml がトリガーされる
- main.yaml の中で 本体の処理 always_success が実行される
- always_success の commit status (status checks) が更新される
- always_success の commit status (status checks) が表示される
- main.yaml の中で sub.yaml を workflow_dispatch でトリガーさせる
- sub.yaml の中で sub_routine が実行される
- main.yaml の中で sub.yaml の commit staus を sub (🏃pending) として新規作成する
- sub の commit status (status checks) が表示される
- トリガーされた sub.yaml の本体の処理 sub_routine が完了した後、 commit status を sub_routine の結果に応じて[3]更新し、完了する
- sub の commit status (status checks) が表示される
概ねシーケンス図の通りですが要点をまとめると、
- main 側で sub workflow を起動
- main 側で sub workflow の status を新規作成 (pending)
- sub 側で sub workflow の status を更新 (success/failure)
となります。
結果
作成したサンプルの PR status checks は以下です。
sub workflow -- called by main
とあるのが、今回作成した commit status になります。
- 比較のため workflow_call を使ったジョブ
main workflow / call_sub_by_call_workflow
も載せています。 やはり workflow_call では、job 名、トリガーイベント、処理時間まで自動で追加されるし、summary にはジョブの結果まであるので、とても見やすいですね 🫠
躓いた点
以下では、ソースコードを交えながら躓いた点を説明します。
呼び出し側
permissions - 適切な権限設定
actions のデフォルト権限は読み取りのみとなっています。なので、適切な権限を把握し、最小権限を付与してあげる必要があります。
今回必要となる権限は、ワークフローの実行と status の作成です。
よって、以下のように設定すると良いです。
permissions:
actions: write
statuses: write
権限設定に関するドキュメントは以下を参照してください。
Create a workflow dispatch event
You must authenticate using an access token with the repo scope to use this endpoint. GitHub Apps must have the actions:write permission to use this endpoint.
Repository permissions for "Commit statuses"
余談
上記の権限設定をした状態で job のコンテキストをダンプすると、最低限の情報しか出力されていないことが確認できました🥰
steps:
- name: Dump job context
env:
JOB_CONTEXT: ${{ toJson(job) }}
run: |
echo "$JOB_CONTEXT"
{
"status": "success"
}
actions/checkout - デフォルトの状態と必要な設定
Pull Request イベントでトリガーされたワークフローにおいて、actions/checkout で checkout されるデフォルトの状態(HEAD)は、refs/pull/x/merge
となっています。つまりマージコミット上にいます。
# actions/checkout した後で以下を実行する
$ git log --oneline --graph --decorate --all
* 0a1c82e (HEAD, pull/1/merge) Merge 28806442437eecc9b99ef9995fb6ec787afb489a into 91cc053f09bd8c552742126cd2834775ec590c5d
|\
| * 2880644 (grafted) Add main and sub workflows for status synchronization
* 91cc053 (grafted) init
一方で、今回 commit status を付与したい commit はマージコミット(0a1c82e
)ではなく、 push された feat ブランチの最新コミット[4] (2880644
)です。
actions/checkout にて ref を feat ブランチに指定することで、 commi status を付与したいコミットハッシュを取得できるはずです。
actions/checkout にて ref を指定しないで解決する方法
ちなみに、 ref を変えずに欲しいコミットハッシュを得る方法もあります。
マージコミット上で、git rev-parse HEAD^2
として、二番目の親ブランチのコミットハッシュを取得することが可能です。
# current ref is merge commit
# HEAD^2 means the second parent of HEAD
# https://git-scm.com/docs/git-rev-parse#Documentation/git-rev-parse.txt-emltrevgtltngtemegemHEADv1510em
latest_main_run_commit_hash=$(git rev-parse HEAD^2)
と思いきや、以下のエラーで怒られてしまいます。
fatal: ambiguous argument 'HEAD^2': unknown revision or path not in the working tree.
これは actions/checkout の fetch-depth がデフォルト 1、つまりマージコミット分だけ歴史を取得しているためです。
よって、2 以上の値を設定してあげると、feat ブランチの最新コミット(2880644
)を取得することができます。
- uses: actions/checkout@v4
with:
fetch-depth: 2
# In actions/checkout, fetch-depth: 2 is required
# if not, HEAD^2 is not available
# --> fatal: ambiguous argument 'HEAD^2': unknown revision or path not in the working tree.
呼び出される側
呼び出される側については、特に躓くことなく実装できた[5]ため、省略します 🙃
気になる方は、以下を参照してください👍
まとめ
本記事では、workflow_dispatch を使って別のワークフローを呼び出し、適切に commit status を作成、更新する例を紹介しました。
本記事作成にあたり、シーケンス図を書いたことで、sub_routine が起動しなかったら状態がずっとpending になってまずいなー🧐、という点に気づけました。
その他、ツッコミ、ご指摘有りましたら、コメントしていただければ幸いです 🙌
参考記事
使用したアクション
git rev parse 関連
permission 関連
mermaid 関連
-
本記事では、status checksと commit status の使い分けが怪しい点があります🙇 ドキュメントを見るに、status checks は PR の文脈で使用され、commit status は REST API の文脈で使用されるという点が異なっていると理解しました。 ↩︎
-
そんな状況ある?となりますが、元の PR の状態に影響を与えるワークフローを呼び出す状況などが考えられます。 ↩︎
-
今回は常に成功するように設定しています。 ↩︎
-
話が複雑になるのを避けるため、本記事では concurrency をワークフロー単位で設定することで、最新コミットでしかワークフローが実行されないことを前提としています。 ↩︎
-
概ね、GitHub Copilot ✈️ さんに補完してもらいました✌️ ↩︎
Discussion