1.この記事の立ち位置

自分がいつも調べていること、忘れがちな Tips や小ネタを列挙していく。そのため、網羅性は重視しない。

というのも、なにか調べていていろいろ読み漁った挙げ句、1周回って行き着くところは GitHub Actions の公式ドキュメントであり、たとえば Workflow の書き方は以下のページをよく開いている。

Workflow syntax for GitHub Actions - GitHub Docs

それでも、公式ドキュメントで参照したい箇所を引っ張るための用語を知るまでに苦労することが往々にあり、この記事が、公式ドキュメントで参照したい箇所を導くための助けとなればと思い、書いていく。

2.Step と Job と Workflowの違いアレコレ

2-1.Step と Job と Workflow の違いの一行まとめ

Step < Job < Workflow

2-2.Step と Job と Workflow の違いの三行まとめ

  • Step はGitHub Actions Workflow の最小単位であり、runuses で指定するもの
  • Job はコンテナや VM のことを指し、runs-on で指定するものであり、ひとつないしは複数の Step を抱えている
  • Workflow はひとつ以上の Job を束ねている

これらについて端的にまとめた図が公式ドキュメントで掲載されているのでそちらもぜひ。

Understanding GitHub Actions - GitHub Docs

2-3.Step と Job と Workflow それぞれの分け方・分割の基準

  • Step を分けるときは、runusesname が異なるとき
    • 以下のように書けば、ひとつの Step に複数のコマンドを始めとする処理を書くことができる
  - name: Install Dependencies
    run: |
      npm ci
      npm run build      
  - name: Run test
    run: npm run test

3.Step の実行制御や Step の参照方法

以下のような成功するときもあれば失敗するときもある、不安定な Step があるとしよう。そして、このような不安定な Step の結果に応じて後続の Step を実行するかしないか決めたいとする。

    steps:
# 略
      - name: Generate 0 or 1
        id: generate_number
        run:  echo "::set-output name=random_number::$(($RANDOM % 2))"
      - name: Pass or fail # 不安定な Step
        run: >
          if [[ ${{ steps.generate_number.outputs.random_number }} == 0 ]]; then 
          exit 0; else exit 1; fi          
# 以下に実行したい Step が続く
      - run: echo "Hello, World!"

上記のような特に何もしていない場合、「Pass or fial」で失敗した(exit 1 となった)際には、後続の “Hello, World!” を出力する Step は実行されない。

3-1.後続の Step をお手軽に実行するか決めたい

後続の Step をお手軽に実行するか決めたい場合、真っ先に挙げられるのが if:always(), if:success(), if:failure() だろう。

※ なお、各 step では if:success() が初期値として扱われているために、コケた step の後続の step の処理はおこなわれない こととなっている。

A default status check of success() is applied unless you include one of these functions.

参考:Expressions - GitHub Docs

※ したがって、以下のように 成否を無視してよい step の直後の stepif:always() さえ書いておけば、後続の全ての step は実行されることとなる。もちろん、if:always() を適用した step がコケた場合、それ以降の step は実行されない。

    steps:
# 略
      - name: Generate 0 or 1
        id: generate_number
        run:  echo "::set-output name=random_number::$(($RANDOM % 2))"
      - name: Pass or fail # 不安定な Step
        run: >
          if [[ ${{ steps.generate_number.outputs.random_number }} == 0 ]]; then
          exit 0; else exit 1; fi          
# 以下に実行したい Step が続く
      - run: echo "Hello, World!"
        if: always()
        #if: success() 直前の「Pass or fail」が成功したときのみ実行。なお、success()は初期値。
        #if: failure() 直前の「Pass or fail」が失敗したときのみ実行

参考:Actions の実行ログ

3-2.Step の出力結果に応じて決めたい

Step の出力結果に応じて、後続の Step を実行するか決めたいときもある。そういうときは steps.<step_id>.outputs.<output_name> がおすすめ!

if:always(), if:success(), if:failure() では直前の成否でしか実行制御できないが、steps.<step_id>.outputs.<output_name>同じ Job 内の任意の Step の成否 で実行制御ができる。

    steps:
# 略
      - name: Generate 0 or 1
        id: generate_number
        run:  echo "::set-output name=random_number::$(($RANDOM % 2))"
        
      - run: echo "OK"
        if: ${{ steps.generate_number.outputs.random_number == 0 }}

      - run: echo "Failed"
        if: ${{ steps.generate_number.outputs.random_number == 1 }}

参考:Actions の実行ログ

3-3.不安定な Step の成否問わず後続の Step を処理したい

ここまでは、if:always()outputs など「後続の Step 」で実行制御するようにしてきた。

steps:
# 略
      - name: Pass or fail # 不安定な Step
        run: >
          if [[ ${{ steps.generate_number.outputs.random_number }} == 0 ]]; then
          exit 0; else exit 1; fi          

# 以下に実行したい Step が続く
      - run: echo "Alice"
        if: always()
      - run: echo "Bob"
      - run: echo "Charlie"

別案として、不安定な stepcontinue-on-error: true を指定しても実行した Step の成否問わず、後続の Step を処理することができる。 こちらのほうが失敗を許容する step をハッキリコード上で表現できると思うので、continue-on-error: true を推したい。

    steps:
# 略
      - name: Generate 0 or 1
        id: generate_number
        run:  echo "::set-output name=random_number::$(($RANDOM % 2))"
      - name: Pass or fail # 不安定な Step
        continue-on-error: true
        run: >
          if [[ ${{ steps.generate_number.outputs.random_number }} == 0 ]]; then
          exit 0; else exit 1; fi          

  # 以下に実行したい Step が続く
      - run: echo "Alice"
      - run: echo "Bob"
      - run: echo "Charlie"

参考:Actions の実行ログ

3-4.不安定な Step の後続の Step で不安定な Step の成否に応じて条件分岐させたい

さて、continue-on-error: true を指定すると指定された Step は失敗していたとしても後続の Step は一様に処理されることになる。これはこれでいいのだが、continue-on-error: true が指定された Step の本来の成否に応じて、後続の Step から実行するものを決めることはできるのだろうか?

GitHub Actions の steps.<step_id>.outcome を使うとそれができる

    steps:
# 略
      - name: Generate 0 or 1
        id: generate_number
        run:  echo "::set-output name=random_number::$(($RANDOM % 2))"
      - name: Pass or fail # 不安定な Step
        id: generate_number_outputs
        continue-on-error: true
        run: |
          if [[ ${{ steps.generate_number.outputs.random_number }} == 0 ]]; then
          exit 0; else exit 1; fi          

      - name: "steps.generate_number_outputs.outcome の値確認"
        run: echo "${{ steps.generate_number_outputs.outcome }}"

      - name: "success のときだけ"
        if: ${{ steps.generate_number_outputs.outcome == 'success' }}
        run: echo "OK"

      - name: "failure のときだけ"
        if: ${{ steps.generate_number_outputs.outcome == 'failure' }}
        run:  echo "Failed"

参考:Actions の実行ログ

3-5.steps.<step_id>.outcome とsteps.<step_id>.conclusion の違い

3-5-1.ポイント

continue-on-error:true の適用前と後、どちらのstepの実行結果を知りたいか?

  • steps.<step_id>.outcomecontinue-on-error:true の適用前の Step の実行結果。
    • continue-on-error: true を適用している Step の本来の実行結果
  • steps.<step_id>.conclusion は適用後の Step の実行結果。
  • またどちらも最終的な結果は success となるので、後続の Step の実行が妨げられることはない。

参考:Contexts - GitHub Docs

3-5-2.steps.<step_id>.outcome とsteps.<step_id>.conclusion の違いに着目したサンプルコード

    steps:
# 略
      - name: Generate 0 or 1
        id: generate_number
        run:  echo "::set-output name=random_number::$(($RANDOM % 2))"
      - name: Pass or fail # 不安定な Step
        id: generate_number_outputs
        continue-on-error: true
        run: |
          if [[ ${{ steps.generate_number.outputs.random_number }} == 0 ]]; then
          exit 0; else exit 1; fi          

      - name: "steps.generate_number_outputs.outcome の値確認"
        run: echo "${{ steps.generate_number_outputs.outcome }}"

      - name: "steps.generate_number_outputs.conclusion の値確認"
        run: echo "${{ steps.generate_number_outputs.conclusion }}"

      - name: "success のときだけ"
        if: ${{ steps.generate_number_outputs.outcome == 'success' }}
        run: echo "OK"

      - name: "failure のときだけ"
        if: ${{ steps.generate_number_outputs.outcome == 'failure' }}
        run:  echo "Failed"

参考:Actions の実行ログ 参考:Contexts - GitHub Docs


4.Job の実行制御や Job をまたいだ Step の参照方法

4-1.Job をまたいで Step の実行を制御したり出力結果を参照したい

いよいよ、Job をまたいで Step の実行を制御したり出力結果を参照するという、これもまたありがちなケースを取り上げたい。

結論としては、Step の実行結果を、別の Job で参照するにはは jobs.<job_id>.outputs を使うと出来る。

このとき <job_id> は 参照する側の Jobneeds: <参照したい Step を抱える job_id> を指定する必要がある。

4-1-1.サンプルコード

以下のサンプルコードをみてほしい。Step の実行結果を参照したい Job は random-number-output であり、その Step が実行される Jobrandomly-failing-job とする。

jobs:
  randomly-failing-job:
    runs-on: ubuntu-latest
    outputs:
    steps:
# 略    
      - name: Generate 0 or 1
        id: generate_number
        run:  echo "::set-output name=random_number::$(($RANDOM % 2))"
      - name: Pass or fail # 不安定な Step
        id: generate_number_outputs
        continue-on-error: true
        run: |
          if [[ ${{ steps.generate_number.outputs.random_number }} == 0 ]]; then
          exit 0; else exit 1; fi          


  random-number-output:
    runs-on: ubuntu-latest
    needs: randomly-failing-job
    steps:
      - name: Echo zero
        if: ${{ needs.randomly-failing-job.outputs.random-number-output == 0}}
        run: |
          echo "AWESOME!!"
          echo ${{ needs.randomly-failing-job.outputs.random-number-output }}          

      - name: Echo one
        if: ${{ needs.randomly-failing-job.outputs.random-number-output == 1}}
        run: |
          echo "One more time!"
          echo ${{ needs.randomly-failing-job.outputs.random-number-output }}          

参考:Actions の実行ログ

4-1-2.Step の実行結果を別の Job で参照するための条件

Step の実行結果を別の Job で参照するための条件は、参照したい Step を含む Job が完走するまで待機することである。

このような「ある Job の完走が終わるまで、別の Job の実行はせず、待機させる」ためにうってつけなのが、needs:<参照したい job_id> となっている。


X.他

X-1.Workflow Dispatch の実行場所

以下の Workflows の画面から on: workflow_dispatchWorkflow Dispatch をトリガーとして使うように指定しているワークフローを選択すると、実行するブランチが表示される。

https://github.com/<アカウント名>/<リポジトリ名>/actions

Image from Gyazo

注意点

注意点は、Manually running a workflow - GitHub Docs で書いてあるとおり、そのリポジトリのデフォルトブランチ(mainなど)で Workflow Dispatch をトリガーとして使うようにする必要があるということ。

To run a workflow manually, the workflow must be configured to run on the workflow_dispatch event. To trigger the workflow_dispatch event, your workflow must be in the default branch.

つまり、デフォルトブランチの Workflow に以下のように書かれていなければ、workflow_dispatch をトリガーとして Workflow を実行することが出来ないということだ。

on:
  workflow_dispatch:

X-2.Workflow のログはどこにある?

Actions タブからログを見たい WorkflowJobStep を順番に選択する。Step を選択する際には、> をクリックする必要がある。

Image from Gyazo

X-3. run: に書くコマンドが長いので改行したい

バックスラッシュで改行を入れるとエラーになってしまう。

        run: 
          if [[ ${{ steps.generate_number.outputs.random_number }} == 0 ]]; then \
          exit 0; else exit 1; fi

> を使えばok! >Folded Style という名前があることを書きながら調べて知った、、。

        run: >
          if [[ ${{ steps.generate_number.outputs.random_number }} == 0 ]]; then
          exit 0; else exit 1; fi          

参考:813-folded-style | YAML Ain’t Markup Language (YAML™) revision 1.2.2

X-4.ひとつの run: に複数のコマンドを書きたい

こんどはひとつの run: に複数のコマンドを書きたい場合だ。

こちらは、| であり、Literal Style と言うらしい。

  - name: Install Dependencies
    run: |
      npm ci
      npm run build      

参考:#812-literal-style | YAML Ain’t Markup Language (YAML™) revision 1.2.2

X-5.GITHUB_TOKEN のデフォルトの権限・スコープを確認したい

Permissions for the GITHUB_TOKEN | Automatic token authentication - GitHub Docs の表の Default access(permissive) という列に列挙されている。

あるいは、Actions のログのページから、権限・スコープを確認したい Jobの名前, Set up job , GITHUB_TOKEN Permission の順番にクリックしても確認できる。

Image from Gyazo

X-6.permissions で権限 / scope を指定すると、デフォルトの権限に追加されることになる?

permissions で権限 / scope を指定するとデフォルトの権限、Default access(permissive) を上書きされる。permissions で指定していない権限については metadata のみ Read 権限が付与されることを除いて no access (何も権限が付与されない)となる。

Image from Gyazo

When the permissions key is used, all unspecified permissions are set to no access, with the exception of the metadata scope, which always gets read access.

参考:Permissions for the GITHUB_TOKEN | Automatic token authentication - GitHub Docs

※ そもそも GITHUB_TOKEN とは、ワークフローを実行すると ${{ secrets.GITHUB_TOKEN }} という書式でワークフローのなかで使うことが出来るシークレット。Personal Access Token (PAT) と違い、シークレットの管理をする必要がない。 参考:Automatic token authentication - GitHub Docs

X-7. permissions ってどこに書けばいいの?

特定の job のみ、あるいはグローバル、どちらでも書くことが出来るが、原則としては、permissions は必要な job に「最小限の権限のみ」 払い出すべきだろう。

また、特定の step にのみ権限を払い出すことはできなかった。エラーメッセージは Unexpected value 'permissions' だった。


# 全べての job に適用する必要がなければ、ココに書くことは避けたほうがいい
#permissions:
#  contents: write
#  pull-requests: write

jobs:
  randomly-failing-job:
#略

  random-number-output:
    runs-on: ubuntu-latest
    needs: randomly-failing-job
    # permissions は必要な job に「最小限の権限のみ」払い出すのべき!
    permissions:
      contents: write
      pull-requests: write
    steps:
      - uses: actions/checkout@v3
#略

      - name: Create Pull Request
        id: cpr
        # 特定の step にのみ権限を払い出すことはできなかった
        # Unexpected value 'permissions'
        #permissions:
        #  contents: write
        #  pull-requests: write
        uses: peter-evans/create-pull-request@v4
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          commit-message: "mod: output.txt"
          title: "Generate 0 or 1? ${{ needs.randomly-failing-job.outputs.random-number-output }}"
          body: "Generate 0 or 1? ${{ needs.randomly-failing-job.outputs.random-number-output }}"

払い出す権限の探し方はいろいろあると思うが、一例としては、使う Action の input などの項目を確認することだ。

たとえば、上で書いているサンプルコードで使われている peter-evans/create-pull-request@v4 を例に挙げると、ドキュメントの Action inputstoken に、必要な権限が書かれている。

token

GITHUB_TOKEN (permissions contents: write and pull-requests: write) or a repo scoped Personal Access Token (PAT).

参考:peter-evans/create-pull-request: A GitHub action to create a pull request for changes to your repository in the actions workspace

GITHUB_TOKENPersonal Access Token (PAT) どちらを使うべきか?という点については、シークレットの管理の煩わしさを踏まえると、GITHUB_TOKEN に軍配があがるのでは?と考える。

X-8.公開されている Actions のいいカンジの探し方

awesome actions などでググるとよさそう。以下はその一例。

sdras/awesome-actions: A curated list of awesome actions to use on GitHub

Y.自分のブログから参考になりそうなものをご紹介

Actions 上でマニフェストのイメージタグを更新したい

[addnab/docker-run-action]GitHub Actionsでyqのコンテナイメージを使ってマニフェストを更新する | gkzz.dev

repository_dispatchとworkflow_dispatchの使い分けや書き方の違い

[GitHub Actions]repository_dispatchとworkflow_dispatchの使い分けや書き方の違いをまとめてみた | gkzz.dev

schedule event がうまく起動しない、発火しないときに確認したい cron の書き方

Github Actions の schedule で日時と曜日を指定することができなかったけどなんとかした | gkzz.dev