Github Actions - 構成要素の概要と要点

# 内容

GitHub Actionsを構成するコンポーネントの概要と要点について記載する

## イベント

[ワークフローをトリガーするイベント](https://docs.github.com/ja/actions/using-workflows/events-that-trigger-workflows)

:::note info
各イベント説明に記載されている`GITHUB_REF`、`GITHUB_SHA`は、ブランチ名、コミットIDで、  
workflowはそのソースコードの状態で実行される  
[`actions/checkout`](https://github.com/actions/checkout)でソースコードをcheckoutする際にデフォルトではこのブランチ名、コミットIDが使用される（パラメータrefで変更可能）
:::

:::note info
Noteに「このイベントは、ワークフローファイルがデフォルト ブランチにある場合にのみワークフローの実行をトリガーします。」とあるイベントは、  
デフォルトブランチにworkflowの実装がマージされたあとでしか実行・動作確認できないため注意
:::

### [pull_request](https://docs.github.com/ja/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request)

PRの各種操作をトリガーにworkflowを起動する  
以下のようにブランチやファイルパスでイベントのフィルターも可能  

```yaml
on:
  pull_request:
    types:
      - opened
      - synchronize
    branches:
      - 'releases/**'
    paths:
      - '**.js'
```

使用頻度の高いtypesとしては`opened`, `closed`, `synchronize`等、
PRのマージ時にworkflowを実行する場合は以下のように`github.event.pull_request.merged`と合わせて判定する

```yaml
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
```

types`opened`, `synchronize`でPR発行時、PRに対してコミットpush時にテストworkflowを実行、  
types`closed`でPRマージ時に実環境にデプロイする等の使い分けが考えられる  
`synchronize`を入れるとコミット頻度が高い場合や、workflowが重い場合にActionsの実行時間が増加して料金への影響もあるため要検討  

:::note info
pull_requestイベントは`GITHUB_REF`、`GITHUB_SHA`が特殊で、  
PRマージブランチ（PRがマージされた後の状態のソースコード）になる  
pull requestのheadブランチへの最後のコミットのコミットIDを取得する場合は、  
代わりに`github.event.pull_request.head.sha`を使用する
:::

「このイベントは、ワークフローファイルがデフォルト ブランチにある場合にのみワークフローの実行をトリガーします。」の記載がないため、  
pull_requestトリガーのworkflowを新規追加したPRを発行すると、そのPRに対してworkflowがトリガーされる

### [workflow_dispatch](https://docs.github.com/ja/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_dispatch)

workflowを手動実行する際に使用する  
「このイベントは、ワークフローファイルがデフォルト ブランチにある場合にのみワークフローの実行をトリガーします。」の記載があるが、  
デフォルトブランチにdispatch workflowがあり、他ブランチでそのworkflowを変更、pushした場合は、  
以下のように他ブランチを選択して実行することで変更が反映された状態で実行することができる  

![2024-12-29-11-18-24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/f34f9531-3259-1954-1dd2-51f971f5529e.png)

### [workflow_call](https://docs.github.com/ja/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_call)

他のworkflowからworkflowを呼び出す際に使用する  
他イベントとは用途が違って、workflowの共通化や、冗長性の解消等に使用する  
[reusable-workflows](#reusable-workflows)で後述  

## workflows

workflowのトリガーとなるイベントとjob,stepから構成される  

```yaml
name: Run CI
on: [push, pull_request]

jobs:
  normal_ci:
    runs-on: ubuntu-latest
    steps:
      - name: Run normal CI
        run: echo "Running normal CI"

  pull_request_ci:
    runs-on: ubuntu-latest
    if: ${{ github.event_name == 'pull_request' }}  # イベント名を参照
    steps:
      - name: Run PR CI
        run: echo "Running PR only CI"
```

上記`github.event_name`のようにワークフローの実行、変数、ランナーの環境、ジョブ、ステップに関する情報には[コンテキスト](https://docs.github.com/ja/actions/learn-github-actions/contexts)を使ってアクセスする  

### github コンテキストのevent

`github.event`にはGitHubのWebhookペイロードが含まれる（例：[pull_request](https://docs.github.com/ja/webhooks/webhook-events-and-payloads#pull_request)）

:::note info
`github.event`は、トリガーのイベントによって内容が異なる  
そのため、共通処理のreusable-workflowやcomposite-action内で`github.event.pull_request.head.sha`のようなイベント依存の値を参照すると、  
`pull_request`以外のイベントでは参照できないため注意が必要
:::

### env

以下のように各階層で環境変数envを定義することができる（[変数に情報を格納する](https://docs.github.com/ja/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#defining-environment-variables-for-a-single-workflow)抜粋）

```yaml
name: Greeting on variable day

on:
  workflow_dispatch

env:  # workflowレベルの環境変数
  DAY_OF_WEEK: Monday

jobs:
  greeting_job:
    runs-on: ubuntu-latest
    env:  # jobレベルの環境変数
      Greeting: Hello
    steps:
      - name: "Say Hello Mona it's Monday"
        run: echo "$Greeting $First_Name. Today is $DAY_OF_WEEK!"
        env:  # stepレベルの環境変数
          First_Name: Mona
```

ただし、workflow_callイベントの場合はトップ階層のenvが使用できない（issues: https://github.com/actions/runner/issues/2372）

```yaml
on:
  workflow_call:
env:
  env_1: 123
jobs:
  job_1:
    runs-on: ubuntu-latest
    env:
      env_2: ${{ env.env_1 }}  # ERROR (Unrecognized named-value: 'env', Available expression contexts: github, inputs, vars, needs, strategy, matrix, secrets)
    steps:
      - run: echo "hello"
```

解決方法として、inputsのdefaultで代用することができる

```yaml
on:
  workflow_call:
    inputs:
      param_1:
        required: false  # 呼び出し元から指定されない前提
        type: string
        default: 123
jobs:
  job_1:
    runs-on: ubuntu-latest
    env:
      env_2: ${{ inputs.param_1 }}
    steps:
      - run: echo "hello"
```

### reusable-workflows

[reusable-workflows](https://docs.github.com/ja/actions/using-workflows/reusing-workflows)は`workflow_call`イベントによって実行される  
以下、reusable-workflows`test_1.yml`workflowは`uses`で呼び出す例  
`secrets: inherit`を指定することで、organization、repository、environmentレベルのシークレットを継承できる  
ただし、`github.token`、`secrets.GITHUB_TOKEN`へのアクセス権は自動的に呼び出し先workflowに付与される（`GITHUB_TOKEN`については[permissions](#permissions)参照）  

```yaml: ./.github/workflows/test_1.yml
name: 適当なreusable-workflows
on:
  workflow_call:
jobs:
  job_1:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
```

```yaml
jobs:
  test_1:
    secrets: inherit # reusable-workflowsへのシークレットの継承
    uses: ./.github/workflows/test_1.yml  # reusable-workflows呼び出し
```

`workflow_dispatch`, `workflow_call`は、workflowに対するパラメータ(inputs)が指定できる  
指定できるinputsのtypeはそれぞれ異なり、  
`workflow_dispatch`でinputsに指定できるtype([on.workflow_dispatch.inputs.<input_id>.type](https://docs.github.com/ja/enterprise-cloud@latest/actions/writing-workflows/workflow-syntax-for-github-actions#onworkflow_dispatchinputsinput_idtype))は、boolean、choice、number、environmentまたはstring  
`workflow_call`でinputsに指定できるtype([on.workflow_call.inputs.<input_id>.type](https://docs.github.com/ja/enterprise-cloud@latest/actions/writing-workflows/workflow-syntax-for-github-actions#onworkflow_callinputsinput_idtype))は、boolean、number、または string
また、workflowの出力(outputs)も指定できる  
stepの出力をworkflowの出力にする場合、以下のようにsteps → jobs → workflowの順で出力を連携する必要がある

```yaml
name: Test output

on:
  workflow_call:
    inputs:
      param_1:
        required: true
        type: string
    outputs:
      workflow_output:  # jobsのoutputをworkflowのoutputに連携
        value: ${{ jobs.job_1.outputs.job_output }}

jobs:
  job_1:
    runs-on: ubuntu-latest
    outputs:
      job_output: ${{ steps.output_step.outputs.output_1 }}  # stepsのoutputをjobsのoutputに連携
    steps:
      - uses: actions/checkout@v4
      - id: output_step
        run: |
          output="hello"
          echo "output_1=${output}" >> $GITHUB_OUTPUT
```

呼び出し元では以下のようにinputsを指定、outputsを参照する

```yaml
jobs:
  test_1:
    secrets: inherit
    uses: ./.github/workflows/test_1.yml
    with:
      param_1: "test"  # reusable-workflowsのinputs指定
  test_2:
    runs-on: ubuntu-latest
    env:
      test_1_output: ${{ jobs.test_1.outputs.workflow_output }}  # reusable-workflowsのoutputsを参照
    steps:
      - run: |
          echo $test_1_output
```

## job

jobには以下のように`runs-on`, `permissions`, `environment`, `needs`, `env`, `steps`等を指定できる  

```yaml
jobs:
  test_1:
    ...

  test_2:
    name: Test 2
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: write
      pull-requests: write
    environment:
      name: dev
    needs: [test_1]
    env:
      env_1: "abc"
    steps:
      - uses: actions/checkout@v4
      - name: Build Python
        id: build_python
        ...
```

:::note info
各Jobはそれぞれ異なる環境のため、  
Jobでcheckoutしたソースコード、インストールしたツール、作成したファイル等は他Jobに引き継がれない  
そのため、Jobを分けすぎると無駄に環境構築処理が複数回発生し、処理時間の増加につながる  
cache([actions/cache](https://github.com/actions/cache))や、artifact([actions/upload-artifact](https://github.com/actions/upload-artifact)) を使うことでJob間でファイルを共有することができる  
cache、artifactについては[GitHub Actions - cacheとartifact](https://qiita.com/takmot/items/15496d6c57190ee9ff56)参照
:::

### runs-on

`runs-on`にはランナー（jobの実行に使用するimage）を指定する  
`ubuntu-latest`や`windows-latest`等、基本的には[標準のGitHubでホストされたランナー](https://docs.github.com/ja/actions/writing-workflows/choosing-where-your-workflow-runs/choosing-the-runner-for-a-job#%E3%83%91%E3%83%96%E3%83%AA%E3%83%83%E3%82%AF-%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%E3%81%AE%E6%A8%99%E6%BA%96%E3%81%AE-github-%E3%81%A7%E3%83%9B%E3%82%B9%E3%83%88%E3%81%95%E3%82%8C%E3%81%9F%E3%83%A9%E3%83%B3%E3%83%8A%E3%83%BC)にある中から選択する  
各ランナーのリンクから、該当のランナーにインストールされているツールの情報が確認できる（例: [`ubuntu-latest`](https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md)）  
ユーザが管理するセルフホステッドランナーを使用することもできる（[GitHub Actions - セルフホステッド ランナー（AWS CodeBuild）](https://qiita.com/takmot/items/b1436adb08a7a44b05c1)にて使用方法を記載）  

### permissions

GitHubの操作を行うActionに使用される`GITHUB_TOKEN`の権限の変更に使用する  
[permissions](https://docs.github.com/ja/actions/writing-workflows/workflow-syntax-for-github-actions#permissions)にある中から選択する  
githubコンテキストのtoken`github.token`と`GITHUB_TOKEN`は同じものである  
[`actions/checkout`](https://github.com/actions/checkout)もGitHub操作であるため、デフォルトで`GITHUB_TOKEN`を使用している（パラメータ`token`で変更可能）  
GitHub CLI等でGitHub操作をする場合は、以下のように環境変数で`GITHUB_TOKEN`を渡す必要がある（[GITHUB_TOKEN シークレットについて](https://docs.github.com/ja/actions/security-for-github-actions/security-guides/automatic-token-authentication#example-1-passing-the-github_token-as-an-input)抜粋）  

```yaml
name: Open new issue
on: workflow_dispatch

jobs:
  open-issue:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      issues: write
    steps:
      - run: |
          gh issue --repo ${{ github.repository }} \
            create --title "Issue title" --body "Issue body"
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

### environment

[GitHub Actions - environment と Deployment protection rules](https://qiita.com/takmot/items/f101b5f17c7f048afd9e)参照

### needs

workflow内に複数のJobが存在する場合、既定では全Jobは並列実行される  
Jobの実行順番を制御するためには[`needs`](https://docs.github.com/ja/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idneeds)を使用してJobの依存関係を指定する  
`needs`に指定されたJobが成功した場合のみJobが実行される

```yaml
jobs:
  job1:
  job2:
    needs: job1
  job3:
    needs: [job1, job2]
```

`needs`に指定したJobの成功/失敗問わずJobを実行したい場合は以下のように`always()`を使用する

```yaml
jobs:
  job1:
  job2:
    needs: job1
  job3:
    if: ${{ always() }}
    needs: [job1, job2]
```

`needs`に指定したJobの出力([jobs.<job_id>.outputs](https://docs.github.com/ja/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idoutputs))は以下のようにアクセスできる  

```yaml
jobs:
  job1:
    runs-on: ubuntu-latest
    # Map a step output to a job output
    outputs:
      output1: ${{ steps.step1.outputs.test }}
      output2: ${{ steps.step2.outputs.test }}
    steps:
      - id: step1
        run: echo "test=hello" >> "$GITHUB_OUTPUT"
      - id: step2
        run: echo "test=world" >> "$GITHUB_OUTPUT"
  job2:
    runs-on: ubuntu-latest
    needs: job1
    steps:
      - env:
          OUTPUT1: ${{needs.job1.outputs.output1}}
          OUTPUT2: ${{needs.job1.outputs.output2}}
        run: echo "$OUTPUT1 $OUTPUT2"
```

## step

ステップでは、コマンドを実行する、設定タスクを実行する、  
あるいは[`actions/checkout`](https://github.com/actions/checkout)のような公開されたアクションを実行することができる

### composite-action

複数stepからなる処理を共通化したい場合等に使用する  
実装したcomposite-actionは、`.github/actions/{任意の名前}/action.yml`に格納しておく  
以下、AWS認証を行うcomposite-actionと実行例

```yaml: .github/actions/aws-credential/action.yml
name: AWS Credentials Setting
description: Configure AWS credentials

inputs:
  region:
    description: AWS region
    required: true
  account_id:
    description: AWS account
    required: true
  role_name:
    description: Role to assume
    required: true
runs:
  using: "composite"  # composite-action指定
  steps:
    - uses: aws-actions/configure-aws-credentials@v4
      with:
        aws-region: ${{ inputs.region }}
        role-to-assume: arn:aws:iam::${{ inputs.account_id }}:role/${{ inputs.role_name }}
        role-duration-seconds: 3600
```

```yaml
jobs:
  aws_:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
    environment:
      name: dev
    steps:
      - uses: actions/checkout@v4  # composite-action実行前にcheckoutが必須
      - uses: ./.github/actions/aws-credential
        with:
          account_id: ${{ vars.ACCOUNT_ID }}
          region: ${{ vars.REGION }}
          role_name: ${{ secrets.ASSUME_ROLE_NAME }}
```

:::note info
composite-action実行前にcheckoutが必須となる  
:::

:::note info
composite-action内ではsecretsが参照できない、そのためsecretsを使用する場合はinputsで渡す必要がある  
:::

:::note info
composite-action内ではコマンド実行（`run`）を行う際に、`shell: bash`のようにshell指定が必須となる
:::