Open11

[TIL] 14. GitHub Actions

wakakawakaka

各ジョブごとに仮想マシンが用意される

Github hosted runnerは実行環境として、エフェメラルな仮想マシンを提供する。1つのジョブにつき1つの仮想マシンが割り当てられる(ジョブごとにruns-onを指定することもスッキリ理解できる)。

次のワークフロー例には、Run-npm-on-Ubuntu および Run-PSScriptAnalyzer-on-Windows という名前のついた 2 つのジョブがあります。 このワークフローがトリガーされると、GitHub ではジョブごとに新しい仮想マシンをプロビジョニングします。

  • Run-npm-on-Ubuntu という名前のジョブは Linux VM で実行されます。これは、ジョブの runs-on: で ubuntu-latest が指定されているためです。
  • Run-PSScriptAnalyzer-on-Windows という名前のジョブは Windows VM で実行されます。これは、ジョブの runs-on: で windows-latest が指定されているためです。

https://docs.github.com/ja/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners

wakakawakaka

各ステップごとに別のシェルプロセスが作成される

以下に示すように、個々のステップごとにシェルプロセスが立ち上がる(ステップごとに起動シェルを変更することが可能なこともスッキリ理解できる)。

name: STEP process check
on: push
jobs:
  process-check:
    runs-on: ubuntu-latest
    steps:
      - run: ps
      - run: ps     # ステップごとに異なるbashプロセスが立ち上がるため出力が異なる

------------------ result ------------------
run ps
  PID TTY          TIME CMD
 ~~~~~省略~~~~~
 1791 ?        00:00:00 bash
 1792 ?        00:00:00 ps

run ps
  PID TTY          TIME CMD
 ~~~~~省略~~~~~
 1793 ?        00:00:00 bash
 1794 ?        00:00:00 ps
wakakawakaka

actions/checkout とは?

リモートリポジトリの作業対象ブランチを GitHub Hosted Runner 内に複製する操作である。内部的には git fetch + git checkout を実行している。デフォルトではワークフローを実行したブランチをcheckoutしてくるが、refパラメータを利用することで別のブランチをcheckoutすることも可能。

https://github.com/actions/checkout?tab=readme-ov-file#checkout-a-different-branch

wakakawakaka

中間環境変数を使おう!

GitHub Actionsではコンテキストという事前定義されたオブジェクトから値を取得可能。

コンテキストによっては特殊文字が含まれるため、シェルコマンドの実行時に意図しない影響を与える可能性がある。そのため、コンテキストは基本的に直接シェルコマンドに埋め込むことはせず、中間環境変数にコンテキストを渡すことで実装する。

環境変数としてOS上で定義した値をシェルコマンドで使用する」という流れも理解しやすい。

書籍 Code 3.4 (P40)
name: Intermediate environment variables
on: push
jobs:
  print:
    runs-on: ubuntu-latest
    env:
      ACTOR: ${{ github.actor }} # コンテキストの値を中間環境変数へセット
    steps:
      - run: echo "${ACTOR}"     # 中間環境変数経由でコンテキストのプロパティを参照
wakakawakaka

ステップ間(別プロセスのシェル間)のデータ共有

各ステップは別プロセスであるため、export TEST = testのように環境変数を定義しただけではステップ間では値を共有できない。仮想マシン(ジョブ)のファイルに書き出して参照する必要がある

そこで、GITHUB_OUTPUT環境変数を利用してステップ間で値を受け渡す。GITHUB_OUTPUT環境変数はGithub Hosted Runner(仮想マシン)に事前に定義されている環境変数であり、以下に示すような特殊なファイルパスが格納されている。

GITHUB_OUTPUT環境変数の内容
steps:
  - name : CHECK ENVIRONMENT VARIABLES
    run : printenv | grep GITHUB_OUTPUT
  
------------------ result ------------------

GITHUB_OUTPUT=/home/runner/work/_temp/_runner_file_commands/set_output_xxx-xxx-xxx-xxx

受け渡し側ステップでGITHUB_OUTPUT環境変数(に格納された特殊ファイル)へキーバリュー形式の文字列を書き出し、受け取り側ステップでstepsコンテキストを参照することで、ステップ間での値の受け渡しが可能となっている。steps.[step_id].outputs.KEYで書き出した値を参照できる。

書籍 Code 3.18 (P54)
name: GITHUB_OUTPUT
on: push
jobs:
  share:
    runs-on: ubuntu-latest
    steps:
      - id: source                                     # ステップIDを設定
        run: echo "result=Hello" >> "${GITHUB_OUTPUT}" # GITHUB_OUTPUTへ書き出し
      - env:
          RESULT: ${{ steps.source.outputs.result }}   # stepsコンテキストから参照
        run: echo "${RESULT}"
wakakawakaka

ジョブ間のデータ共有

ジョブ間のデータ共有にはoutputsキーを利用する。
受け渡し側ジョブで渡したい値をoutputsキーに指定する。受け取り側ジョブではneedsキーでジョブの依存関係を定義した後に、needsコンテキストを経由してデータを参照する。

書籍 Code 5.9 (P105)
name: Share job data
on: push
jobs:
  before:
    runs-on: ubuntu-latest
    steps:
      - id: generate                                   # ステップのID
        run: echo "result=Hello" >> "${GITHUB_OUTPUT}" # ステップレベルの出力値
    outputs:
      result: ${{ steps.generate.outputs.result }}     # ジョブレベルの出力値
  after:
    runs-on: ubuntu-latest
    needs: [before]                                    # 依存するジョブIDの指定
    steps:
      - env:
          RESULT: ${{ needs.before.outputs.result }}   # 依存ジョブの出力値を参照
        run: echo "${RESULT}"
wakakawakaka

OpenID Connectによる認証

OpenID Connectの仕組みを下図に示す。GitHub OIDC Providerから渡されたJWTトークンの認証は2ステップに分かれている。以下の2工程の認証を経て、一時的な認証情報が与えられる。

  1. 署名認証により、Token生成元がGithub OIDC Providerであることを確認
  2. AssumeRoleConditionを使用したJWTクレーム認証により、アカウント情報やリポジトリ情報が意図したものであることを確認

https://qiita.com/satooshi/items/0c2f5a0e2b64a1d9a4b3

aws-actions/configure-aws-credentialsを利用するだけで、上図1~5の全ての作業を完結することができる(引数で指定したIAM Roleで定義されている権限を有する一時的な認証情報を取得)。

aws-actions/configure-aws-credentials
 - name : CONFIGURE AWS CREDENTIALS
   uses: aws-actions/configure-aws-credentials@v4
   with:
     role-to-assume: ${{ env.ROLE_ARN }}
     role-session-name: ${{ env.SESSION_NAME }}
     aws-region: ap-northeast-1
wakakawakaka

Pull Requestのマージをワークフロー実行トリガーにする

Pull Requestがマージされた際に、CICDワークフローを実行したいケースは多い。だがGithub ActionsではPull Requestがマージされたとき用のイベントは用意されていない
Pull Requestはマージされると自動的にクローズされることを利用して、以下のように設定する。

on:
  pull_request:
    types:
      - closed

jobs:
  if_merged:
    if: github.event.pull_request.merged == true
    runs-on: ubuntu-latest
    steps:
    - run: echo "The PR was merged"

https://docs.github.com/ja/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#running-your-pull_request-workflow-when-a-pull-request-merges

wakakawakaka

Pull Requestのマージは統合先へのPushと同義であるため、pushイベントを利用してプルリクエストのマージ時にワークフローを実行することも可能(但し、直接Pushした場合にもトリガーされることに注意が必要)。

https://qiita.com/okazy/items/7ab46f2c20ec341a2836

wakakawakaka

dorny/paths-filterを用いた「ジョブ単位でのpathフィルター」

on.**.pathsフィルタを用いることで「ワークフロー単位でのpathフィルター」を実装できる。以下の例では、backend_app配下の任意のgoファイルがPushされた場合にワークフローが実行される。

pathsフィルタ
on:
  push:
    paths:
      - 'backend_app/**/*.go'

だが、複雑なワークフローを組む場合には、より詳細にジョブ単位で実行トリガーを制御したい
これを実現するために、dorny/paths-filterを利用する。

# フィルタ結果がBool型で出力される
jobs:
  job_change_check:
    runs-on: ubuntu-latest
    outputs:
      app1: ${{ steps.filter.outputs.app1 }}
      app2: ${{ steps.filter.outputs.app2 }}
    steps:
      - uses: dorny/paths-filter@v3
        id: filter
        with:
          filters: |
            app1:
              - 'src/app1/**'
            app2:
              - 'src/app2/**'
	            
  job_app1:
    needs: job_change_check
    if: ${{ needs.job_change_check.outputs.app1 == 'true' }}
    runs-on: ubuntu-latest
    steps:
      - run: echo "src/app1 folder was changed!!"
	    
  job_app2:
    needs: job_change_check
    if: ${{ needs.job_change_check.outputs.app2 == 'true' }}
    runs-on: ubuntu-latest
    steps:
      - run: echo "src/app2 folder was changed!!"

https://qiita.com/yokawasa/items/6c8ab43200ed4ff09060

wakakawakaka

真価を発揮するのが保護ブランチ設定で特定ジョブの実行をRequiredとしているとき。この場合にon.**.pathsフィルタの条件を満たさずにスキップされると、そのワークフロー内のジョブ(CI処理:ステータスチェック)は成功扱いにならず、マージがブロックされる。dorny/paths-filterなどのジョブ単位でのpathフィルターを使用することで、この問題を回避できる。

保護ブランチでステータスチェックの Required 対象にしている場合、paths の条件を満たさずスキップされると成功扱いにならずマージがブロックされてしまいます。

https://zenn.dev/bigwheel/articles/05accc6323de18
https://zenn.dev/snowcait/articles/0206264b4aa99a