Timee Product Team Blog

タイミー開発者ブログ

YAML Programming in GitHub Actions

こんにちは。2022年11月に株式会社タイミーに入社した sinsoku です。

最近はGitHub ActionsのYAMLを書く機会が多く、YAMLも複雑化してきました。
しかし、日常的にYAMLを触っている職人以外にはパッと読めないことも多いので、社内の方々が読めるようにGitHub ActionsのYAMLの書き方をまとめたいと思います。

目次

三項演算子

GitHub Actions には三項演算子がないため、代わりに論理演算子を使います。

- steps:
  - run: echo "${{ (github.ref == 'refs/heads/main' && 'production') || 'staging' }}"

参考: Expressions

環境変数(env)

環境変数を使いたい場合は env で定義します。

env:
  TIMEE_CEO: ryo
  TIMEE_CTO: kameike

jobs:
  job-env:
    runs-on: ubuntu-latest
    steps:
      - run: echo $TIMEE_CEO

      # この実装だと置換後の文字列 `echo kameike` を実行する
      - run: echo ${{ env.TIMEE_CTO }}

ただし、 env で env の値を指定するとエラーになるケースがあるので注意してください。

env:
  DEPLOY_ENV: ${{ (github.ref == 'refs/heads/main' && 'production') || 'staging' }}
  # Unrecognized named-value: 'env'. Located at position 1 within expression: env.DEPLOY_ENV == 'production'
  IS_PROD: ${{ env.DEPLOY_ENV == 'production' }}

jobs.<job_id>.env でも同様のエラーが出ます。

jobs:
  job-error:
    runs-on: ubuntu-latest

    # Unrecognized named-value: 'env'. Located at position 1 within expression: env.DEPLOY_ENV == 'production'
    IS_PROD: ${{ env.DEPLOY_ENV == 'production' }}

jobs.<job_id>.steps[*].env であればエラーになりませんが、同じ階層の env の値は参照できません。

env:
  TIMEE_CTO: kameike

jobs:
  job-env:
    runs-on: ubuntu-latest

  steps:
    # この実装だと `echo "true, foo, -bar"` を実行する
    - run: echo "${{ env.IS_KAMEIKE }}, ${{ env.FOO }}, ${{ env.BAR }}"
      env:
        IS_KAMEIKE: ${{ env.TIMEE_CTO == 'kameike' }}
        FOO: foo
        BAR: ${{ env.FOO }}-bar

参考: Workflow syntax for GitHub Actions

変数(outputs)

汎用的な名前の環境変数を定義すると、何かのCLIコマンドに影響する可能性があります。
これを避けるために、outputs で変数を定義することもできます。

steps:
  - id: var
    run: |
      echo "x=foo" >> "$GITHUB_OUTPUT"
      echo "y=bar" >> "$GITHUB_OUTPUT"

  # この実装だと `echo "foo, bar"` を実行する
  - run: echo "${{ steps.var.outputs.x }}, ${{ steps.var.outputs.y }}"

outputs は env と違い、 bash の処理結果を変数に定義することができます。

steps:
  - uses: actions/checkout@v3

  - id: var
    run: |
      echo "terraform-version=`cat .terraform-version`" >> "$GITHUB_OUTPUT"

  # この実装だと `echo "1.4.4"` を実行する
  - run: echo "${{ steps.var.outputs.terraform-version }}"

参考: Defining outputs for jobs

関数(workflow_call)

ワークフローの一部を別のファイルに定義し、関数のように呼び出すことができます。

# .github/workflows/_say.yml
name: say

on:
  workflow_call:
    inputs:
      name:
        required: true
        type: string

jobs:
  hello:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Hello, ${{ inputs.name }}."

  bye:
    needs: hello
    runs-on: ubuntu-latest
    steps:
      - run: echo "Bye, ${{ inputs.name }}."

使い方は以下の通りです。

jobs:
  job-say:
    uses: ./.github/workflows/_say.yml
    with:
      name: kameike

参考: Reusing workflows

関数 + 配列(dynamic matrix)

workflows_call の入力には真偽値、数字、文字列しか使えません。

The value of this parameter is a string specifying the data type of the input. This must be one of: boolean, number, or string.
引用: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onworkflow_callinputsinput_idtype

しかし、少し工夫することで配列を扱うことができます。

# .github/workflows/_say_multi.yml
name: say_multi

on:
  workflow_call:
    inputs:
      names:
        required: true
        type: string

jobs:
  matrix:
    runs-on: ubuntu-latest
    outputs:
      names: ${{ steps.set-matrix.outputs.names }}
    steps:
      - id: set-matrix
        run: |
          names=`echo '${{ inputs.names }}' | jq -csR 'split("\\n") | map(select(. != ""))'`
          echo "names=$names" >> $GITHUB_OUTPUT

  hello:
    needs: matrix
    runs-on: ubuntu-latest
    strategy:
      matrix:
        name: ${{ fromJSON(needs.matrix.outputs.names) }}
    steps:
      - run: echo "Hello, ${{ matrix.name }}."

使い方は以下の通りです。

job-say-multi:
  uses: ./.github/workflows/_say_multi.yml
  with:
    names: |
      ryo
      kameike

GitHub CLIの活用

GitHub APIを使うことで、チェックアウトせずにファイル名を取得することができます。

job-gh-matrix:
  runs-on: ubuntu-latest
  outputs:
    files: ${{ steps.set-matrix.outputs.files }}
  steps:
    - id: set-matrix
      run: echo "files=`gh api '/repos/{owner}/{repo}/contents/.github/workflows?ref=${{ github.sha }}' --jq '[.[].name]'`" >> $GITHUB_OUTPUT
      env:
        GH_REPO: ${{ github.repository }}
        GH_TOKEN: ${{ github.token }}

job-gh-echo:
  needs: job-gh-matrix
  runs-on: ubuntu-latest
  strategy:
    matrix:
      file: ${{ fromJSON(needs.job-gh-matrix.outputs.files) }}
  steps:
    - run: echo "${{ matrix.file }}"

画像のようにファイル名の一覧で並列にJobを実行できます。

まとめ

YAMLむずかしいですね。

この記事が読んだ方の参考になれば幸いです。
また、弊社のGitHub Actionsやデプロイフローについて気になることがあれば、フッターの採用ページのURLから面談の申し込みをどうぞ!