蟹の缶詰のイラスト

GitHub Actions で再利用可能なワークフローを作成する

GitHub Actions で CI/CD 環境を構築する際に、同じような処理を複数のファイルで記述するようなことがよくあります。この記事では、GitHub Actions で再利用可能なワークフローを作成する方法と、実際に再利用可能なワークフローを作成する手順を紹介します。

GitHub Actions を使って CI/CD 環境を構築する際に、同じような処理を複数のファイルで記述するようなことがよくあります。例えばデプロイを行うワークフローでは、staging 環境と production 環境とで異なるタイミングでデプロイを行う必要があるため、それぞれのワークフローを別々のファイルに記述することでしょう。このような場合記述される処理は環境に依存する部分以外の多くの部分は共通していることが多いです。

ワークフローの再利用を使用することで、処理の共通を回避し容易なワークフローの管理が可能になります。また適切な設計がされたワークフローを使い回すことでベストプラクティスに従ったワークフローを簡単に導入できます。Organizations で共通のワークフローを使い回すことで、組織全体でベストプラクティスを共有できるでしょう。

この記事では、GitHub Actions で再利用可能なワークフローを作成する方法と、実際に再利用可能なワークフローを作成する手順を紹介します。

再利用可能なワークフローを作成する

再利用可能なワークフローは通常のワークフローと同様に .github/workflows ディレクトリに配置します。再利用可能なワークフローとして他のワークフローから呼び出されるようにするためには、on の値に workflow_call を指定します。

.github/workflows/wf-reusable-example.yaml
name: Reusable Workflow Example
 
on:
  workflow_call:

Tip

GitHub Actions では .github/workflows ディレクトリのサブディレクトリは許可されていません。そのため、再利用可能なワークフローとその読み出し元のワークフローを区別するためにファイルの名前に接頭辞をつけることをお勧めします。この記事では再利用可能なワークフローのファイル名に wf- を、読み出し元のワークフローのファイル名に on- を付けることにします。

ワークフローの一連のステップは通常のワークフローと同様に jobs キーの下に記述します。ここでは簡単に echo コマンドを実行するジョブを定義します。

.github/workflows/wf-reusable-example.yaml
name: Reusable Workflow Example
 
on:
  workflow_call:
 
jobs:
  echo-job:
    runs-on: ubuntu-latest
    steps:
      - name: Echo Hello
        run: echo "Hello"

再利用可能なワークフローを呼び出す

再利用可能なワークフローを呼び出すには、jobs キーの下に uses キーを使用します。ワークフローファイルを参照する際には以下のいずれかの構文を使用します。

  • 同じリポジトリ内のワークフローファイルを参照する場合:./.github/workflows/{filename}
  • 他のリポジトリ内のワークフローファイルを参照する場合:{owner}/{repo}/.github/workflows/{filename}@{ref}

他のレポジトリを参照する際に指定する {ref} は SHA、リリースタグ、またはブランチ名を指定できます。同じレポジトリのファイルを参照する場合には、呼び出し元のワークフローと同じコミットから取得されます。

それでは先ほど作成した wf-reusable-example.yaml を呼び出すワークフローを作成してみましょう。ここでは Issue が作成された際に呼ばれるワークフローを作成します。

.github/workflows/on-issue-created.yaml
name: On Issue Created
 
on:
  issues:
    types: [opened]
 
jobs:
  call-workflow:
    uses: ./.github/workflows/wf-reusable-example.yaml

ワークフローファイルを作成したら実際に Issue を作成して試してみましょう。「Issues」タブから「New issue」ボタンをクリックして Issue を作成します。

Issue を作成した後に Actions タブを開くと、On Issue Created ワークフローが実行されていることが確認できます。

実行されたワークフローをクリックしてログを確認すると、確かに Reusable Workflow Example ワークフロー内で定義した echo-job が実行されていることが確認できます。

再利用可能なワークフローにパラメータを渡す

例えばデプロイ環境ごとに異なる設定を渡すような場合、再利用可能なワークフローを呼び出す際にパラメータを渡すことでワークフローを柔軟に使い回すことができます。再利用可能なワークフローにわたすパラメータは on.workflow_call.inputs キーに定義します。

.github/workflows/wf-reusable-example.yaml
name: Reusable Workflow Example
 
on:
  workflow_call:
    inputs:
      environment:
        description: 'デプロイ先の環境'
        required: true
        type: string
      timeout:
        description: 'デプロイがタイムアウトされるまでの時間(ミリ秒)'
        required: false
        default: 10000
        type: number

inputs の下のキー名がパラメータの名前に対応します。パラメータには以下のプロパティを指定できます。

  • description:パラメータの説明
  • required:パラメータが必須かどうか
  • default:パラメータのデフォルト値。指定されていない場合 false(真偽値)、0(数値)、''(文字列)がデフォルト値として使用される
  • type:パラメータの型。stringnumberboolean のいずれかを指定できる

Tip

上記のプロパティはすべて省略可能ですが、他の人が理解しやすいようにできる限り詳細に使用用途を記述することは良い習慣です。これらのプロパティはドキュメントとしての役割を果たします。もし新たなワークフローを作成する際にこれらのプロパティを必ず指定されるようにしてもらいたい場合には、actionlint のようなツールを使うとよいでしょう。actionlint はファイルの静的解析により構文ミスやベストプラクティスに反する箇所を検出できます。

定義されたパラメータはワークフロー内で ${{ inputs.パラメータ名 }} の形式で参照できます。

.github/workflows/wf-reusable-example.yaml
name: Reusable Workflow Example
 
on:
  workflow_call:
    inputs:
      environment:
        description: 'デプロイ先の環境'
        required: true
        type: string
      timeout:
        description: 'デプロイがタイムアウトされるまでの時間(ミリ秒)'
        required: false
        default: 10000
        type: number
 
jobs:
  echo-job:
    runs-on: ubuntu-latest
    env:
      ENVIRONMENT: ${{ inputs.environment }}]
      # toJSON は数値を文字列に変換するために使用
      TIMEOUT: ${{ toJSON(inputs.timeout) }}
    steps:
      - name: Echo Environment
        run: echo "Deploy to $ENVIRONMENT"
      - name: Echo Timeout
        run: echo "Timeout $TIMEOUT"

Warning

セキュリティ上の利用から inputs の値を直接参照するのではなく、一度環境変数に設定することが推奨されています。メモリに保存される値として使用されるため、コードのインジェクション防止につながります https://docs.github.com/ja/enterprise-cloud@latest/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable

再利用可能なワークフローを呼び出す際にパラメータを渡すには、with キーを使用します。

.github/workflows/on-issue-created.yaml
name: On Issue Created
 
on:
  issues:
    types: [opened]
 
jobs:
  call-workflow:
    uses: ./.github/workflows/wf-reusable-example.yaml
    with:
      environment: 'staging'

ファイルを変更してコミットしたら、もう一度 Issue を作成してワークフローが実行されることを確認しましょう。

$ENVIRONMENT には with で指定した値が、$TIMEOUT にはデフォルト値が設定されていることが確認できます。

シークレットの値を再利用可能なワークフローに渡す

データベースの接続情報や API キーなどの機密情報を GitHub Actions で扱う場合、シークレットを使用します。シークレットは Organization もしくはレポジトリ単位で設定できます。シークレットに設定した値はログに出力される際にマスキングされるという特徴があります。

通常シークレットの値を GitHub Actions から参照する際には ${{ secrets.シークレット名 }} の形式で参照します。しかし、再利用可能なワークフローの中で扱う場合にはたとえ同じレポジトリないであっても直接参照することはできません。workflow_call.secrets キーを使用して明示的にシークレットを渡す必要があるのです。

一度シークレットの値を参照する実験をしてみましょう。再利用可能なワークフロー内で ${{ secrets.SUPERSECRET }} の値を参照しようと試みています。

.github/workflows/wf-echo-secrets.yaml
name: echo secrets
 
on:
  workflow_call:
 
jobs:
  echo-job:
    runs-on: ubuntu-latest
    steps:
      - name: Echo super secret
        # ここでは実験のために echo コマンドを使ってシークレットの値を出力していますが、
        # 実際にはシークレットの値はログに出力されるべきではありません
        run: echo ${{ secrets.SUPERSECRET }}

先ほど作成したワークフローと同じように、Issue が作成された場合に呼び出されるようにします。

.github/workflows/on-issue-created.yaml
name: On Issue Created
 
on:
  issues:
    types: [opened]
 
jobs:
  call-workflow2:
    uses: ./.github/workflows/wf-echo-secrets.yaml

ワークフローを実行する前に、シークレットを設定する必要があります。GitHub のレポジトリページから「Settings」をクリックし、「Secrets and Variables > Actions」を選択します。この画面で「Repository secrets」の「New repository secret」ボタンをクリックすると、シークレットを設定する画面が表示されます。ここで SUPERSECRET という名前のシークレットを任意の値で設定しましょう。

シークレットの作成が完了したら、SUPERSECRET のが一覧に表示されていることが確認できます。

シークレットの設定が完了したら Issue を作成してワークフローが実行されることを確認しましょう。前述した通り再利用可能なワークフローではシークレットの値を直接参照できないため、ログには何も出力されないはずです。

実験が終わったので、正しくシークレットの値を参照できるようにワークフローを修正しましょう。wf-echo-secrets.yamlworkflow_call.secrets キーを追加します。

.github/workflows/wf-echo-secrets.yaml
name: echo secrets
 
on:
  workflow_call:
    secrets:
      SUPERSECRET:
        required: true
 
jobs:
  echo-job:
    runs-on: ubuntu-latest
    steps:
      - name: Echo super secret
        # ここでは実験のために echo コマンドを使ってシークレットの値を出力していますが、
        # 実際にはシークレットの値はログに出力されるべきではありません
        run: echo ${{ secrets.SUPERSECRET }}

secrets キーの下にシークレット名をキーとして設定します。required プロパティを true に設定することで、シークレットが必須であることを示しています。シークレットの値は変わらず ${{ secrets.SUPERSECRET }} の形式で参照できます。

呼び出し元のワークフローでは、with キーと同様に secrets キーを使用してシークレットを渡します。

.github/workflows/on-issue-created.yaml
name: On Issue Created
 
on:
  issues:
    types: [opened]
 
jobs:
  call-workflow2:
    uses: ./.github/workflows/wf-echo-secrets.yaml
    with:
      environment: 'staging'
    secrets:
      SUPERSECRET: ${{ secrets.SUPERSECRET }}

個別のキーを指定する代わりに、inherit キーワードを使用して暗黙的にすべてのシークレットを渡すこともできます。ただし、予期せぬシークレットを渡してしまうことを防ぐため、常に明示的にシークレットを渡すことをお勧めします。また明示的にシークレットを渡す方法では、呼び出し対象のワークフローで指定されていないシークレットが呼び出し元ワークフローによって渡されると、エラーが発生するという利点もあります。

jobs:
  call-workflow2:
    uses: ./.github/workflows/wf-echo-secrets.yaml
    secrets: inherit

ファイルを修正したら再度ワークフローを実行してみましょう。SUPERSECRET の値がマスキングして表示されることが確認できます。

再利用可能なワークフローの出力を読み取る

再利用可能なワークフローで生成されるデータを呼び出し元から参照したいような場合もあります。例えばデプロイが成功したかどうかで後続の処理を分岐させたい場合などです。再利用可能なワークフローで出力を返すには、workflow_call.outputs キーを使用します。outputs キーの下に出力の名前をキーとして設定します。input キーと同様に description プロパティで出力される値の説明を記述できます。

下記のワークフローでは、output-job というジョブを定義し、このジョブで出力された値を workflow_call.outputs キーに引き渡しています。各ジョブで出力される値は $GITHUB_OUTPUT という特別な環境変数に {key}={value} の形式で設定します。

.github/workflows/wf-output-example.yaml
name: Output Example
 
on:
  workflow_call:
    outputs:
      build:
        description: 'ビルドの成否'
        value: ${{ jobs.output-job.outputs.build }}
      test:
        description: 'テストの成否'
        value: ${{ jobs.output-job.outputs.test }}
jobs:
  output-job:
    runs-on: ubuntu-latest
    steps:
      - name: Step 1
        id: step1
        run: echo "build=succeeded" >> $GITHUB_OUTPUT
      - name: Step 2
        id: step2
        run: echo "test=failure" >> $GITHUB_OUTPUT
    outputs:
      build: ${{ steps.step1.outputs.build }}
      test: ${{ steps.step2.outputs.test }}

ここでは buildtest という 2 つの出力を定義しています。呼び出し元のワークフローから参照する場合には、同じワークフロー内の job を参照するのと同じように ${{ steps.{job_id}.outputs.{key} }} の形式で参照します。

.github/workflows/on-issue-created.yaml
name: On Issue Created
 
on:
  issues:
    types: [opened]
 
jobs:
  call-workflow3:
    uses: ./.github/workflows/wf-output-example.yaml
 
  check-output:
    runs-on: ubuntu-latest
    # needs で call-workflow3 のジョブが完了するまで待つ
    needs: call-workflow3
    steps:
      - name: Check build output
        run: echo ${{ needs.call-workflow3.outputs.build }}
      - name: Check test output
        run: echo ${{ needs.call-workflow3.outputs.test }}

実際にワークフローを十個すうると、check-output ジョブで buildtest の出力が表示されることが確認できます。

まとめ

  • 再利用可能なワークフローを作成するには、on の値に workflow_call を指定する
  • 再利用可能なワークフローを呼び出すには、uses キーを使用する。ファイルを参照する際には以下のいずれかの構文を使用する
    • 同じリポジトリ内のワークフローファイルを参照する場合:./.github/workflows/{filename}
    • 他のリポジトリ内のワークフローファイルを参照する場合:{owner}/{repo}/.github/workflows/{filename}@{ref}
  • 再利用可能なワークフローにパラメータを渡すには、on.workflow_call.inputs キーを使用する。呼び出し元のワークフローで with キーを使用してパラメータを渡す
  • 再利用可能なワークフローにシークレットを渡すには、on.workflow_call.secrets キーを使用する。呼び出し元のワークフローで secrets キーを使用して明示的にシークレットを渡すか、inherit キーワードを使用して暗黙的にすべてのシークレットを渡す
  • 再利用可能なワークフローで出力を返すには、on.workflow_call.outputs キーを使用する。出力された値は呼び出し元のワークフローで ${{ needs.{job_id}.outputs.{key} }} の形式で参照する

参考

記事の理解度チェック

以下の問題に答えて、記事の理解を深めましょう。

再利用可能なワークフローを作成するためにはどのキーを使用するか?

  • `on.workflow_call`

    正解!

  • `on.workflow_dispatch`

    もう一度考えてみましょう

    workflow_dispatch イベントは手動でワークフローをトリガーするためのイベントです。

  • `reusable-workflow`

    もう一度考えてみましょう

  • `module`

    もう一度考えてみましょう


Contributors

> GitHub で修正を提案する
この記事をシェアする
はてなブックマークに追加

関連記事