Github Actionsで複数jobの結果を確認する
モノレポで開発していたらGithub Actionsのworkflow.ymlの数が多くなってしまいました。それだけなら良いのですが、それぞれでslack通知をしている(または、これからやりたい)とチャンネルが通知で埋め尽くされて煩わしくなりそうだったので、jobとして1つのworkflowにまとめつつ、複数のjob結果を処理する方法を考えました。
結果確認用jobを作る
Github acitonsは複数のjobを1つのymlに設定できますが、今回の目的のためには最後に実行されるjobが以下の条件を満たすようにする必要があります。
- 前のjobの結果に関わらず必ず実行される
- 前のjobの結果を受け取れる
- 前のjobの独自のoutputも受け取れる
前のjobの結果に関わらず必ず実行させる
まず、「前のjob」を定義するにはneeds
を使ってjob名を指定します。ただし、それだけだと前のjobが1つでも失敗するとそのjobはskipされてしまいます。「前のjobの結果に関わらず常に実行させる」を指定するにはif
にalways()
を設定します。
check-result:
needs: [job1, job2, job3, job4]
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
- name: step
run: (省略)
前のjobの結果を受け取る
GithubにはContextsという機能があり、さまざまな情報を実行時に取得できます。
このなかで、前のjobの情報がneedsというContextに設定されます。formatはシンプルでjob名(job_id)、result, outputの3つです。これらがneedsに指定した全てのjobについて設定されます。
{
"job1": {
"result": "success",
"outputs": {}
},
"job2": {
"result": "success",
"outputs": {}
},
"job3": {
"result": "failure",
"outputs": {}
},
"job4": {
"result": "skipped",
"outputs": {}
}
}
あとはこれをjqや何らかの方法で処理して通知に使える値に変換するれば良いだけです。例えば「job毎にjob名 + スペース + result
にして\n
で改行」というフォーマットにしたい場合は
echo '${{ toJSON(needs) }}' | jq 'to_entries | map(.key + " " + .value.result) | join("\n")'
をrunに指定すると、以下のような文字列が取得できます。
前のjobの独自のoutputも受け取る
needs Contextにあるoutputにはそれぞれのjobからの独自の値を設定できます。ただし、jobのoutputにset-output
コマンドの結果を使うとneeds Contextに設定されません。(set-output
が非推奨になったのも関係しているんでしょうか?)
推奨されているGITHUB_ENV
を使う必要があります。
Actionsのサマリーページにもこういったwarningが出るようになりました。
GITHUB_ENVでjob outputsに設定する
上記のchangelogの先にある推奨方法にあるとおり、
echo "{env名}={値}" >> $GITHUB_ENV
を使います。さらにjob outputsでenv.
を使ってoutputs
を設定します。
job2:
needs: setup
runs-on: ubuntu-latest
outputs:
output_via_command: ${{ steps.step.outputs.command_result }} # 別jobに渡されない
output_via_env: ${{ env.ENV_RESULT }} # 別jobに渡される
steps:
- name: step
run: |
echo "do something2"
echo "::set-output name=command_result::1234"
echo "ENV_RESULT=5678" >> $GITHUB_ENV
上記ymlではわざと非推奨のset-output
を使ってjob outputoutput_via_command
を設定していますが、この方法だと別jonのneeds Contextに値が渡されません。実際にGithub actionで実行した際のtoJSON(needs)
の値は以下です。
{
"job1": {
"result": "success",
"outputs": {}
},
"job2": {
"result": "success",
"outputs": {
"output_via_env": "5678"
}
},
"job3": {
"result": "failure",
"outputs": {}
},
"job4": {
"result": "skipped",
"outputs": {}
}
}
GITHUB_ENV
経由で渡したoutputは無事にneeds Contextに設定されています。ここまでくればあとはresultと同様にjqやスクリプトで必要な処理に組み込むことができます。
ソースコード
以上で
- 前のjobの結果に関わらず必ず実行される
- 前のjobの結果を受け取れる
- 前のjobの独自のoutputも受け取れる
という条件を満たす最後のjobを作成することができるようになりました。
最後に、ここまでで説明したymlを以下に記載します。また、このymlをGithub actionで実行した結果もリンクを載せておきます。job2には前述の通り意図的に非推奨のset-output
コマンドを使っている箇所があるのでご注意ください。
get-previous-job-status.yml
on:
push:
jobs:
setup:
runs-on: ubuntu-latest
steps:
- name: step
run: |
echo "setup something"
echo "${{ toJSON(github) }}"
job1:
needs: setup
runs-on: ubuntu-latest
steps:
- name: step
run: |
echo "do something1"
job2:
needs: setup
runs-on: ubuntu-latest
outputs:
output_via_command: ${{ steps.step.outputs.command_result }}
output_via_env: ${{ env.ENV_RESULT }}
steps:
- name: step
run: |
echo "do something2"
echo "::set-output name=command_result::1234"
echo "ENV_RESULT=5678" >> $GITHUB_ENV
job3:
needs: setup
runs-on: ubuntu-latest
steps:
- name: step
run: |
echo "do something3"
exit 1
job4:
needs: setup
if: false
runs-on: ubuntu-latest
steps:
- name: step
run: |
echo "skip always"
check-result:
needs: [job1, job2, job3, job4]
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
- name: step
run: |
echo "check something"
echo '${{ toJSON(needs) }}'
echo '${{ toJSON(needs) }}' | jq 'to_entries | map(.key + " " + .value.result) | join("\n")'
Discussion