😊

Reusable workflows / Composite action を用いて GitHub Actions の処理を再利用する

2024/02/16に公開

自前で書いた GitHub Actions の処理を再利用化する方法として、おおまかに以下の2通りの方法があります。

GitHub Actions の yaml ファイルは放っておくと肥大化し、可読性も保守性も落ちてしまうため、平和な空間を保つためにも適度なサイズに保つ努力、適切なモジュールの分解と再利用化が欠かせません。
というような事例に今年はそれなりに遭遇したため、記事としてまとめてみました。

Reusable workflows とは?

https://docs.github.com/ja/actions/using-workflows/reusing-workflows

言葉の通り、再利用可能な workflows です。
GitHub Actions の定義を各リポジトリごとに記載していくと冗長な記載が散在しコピペ天国になってしまうため、共通的な定義については切り出して外だしすることが望まれます。そのための仕組みが reusable workflows になります。

いくつかの仕様・環境の変遷を経て、現在(2022年12月以降)は 別の private repository に置かれている Actions を呼び出すことができる ようになっています。このドキュメントはその状況を前提に記載をします。

Reusable workflows の設定

repository

Reusable workflows で使用する GitHub Actions ファイルを設置するリポジトリについては、以下の設定を行う必要があります。
こちらを行わないと、Actions の呼び出しが行えません

GitHub Actions の Access 設定を Accessible from repositories in the ~ に変更する

https://github.com/(organization)/(repository)/settings/actions

上記のURL経由でアクセスできる設定画面から設定が可能です。

Access の設定はデフォルトでは Not accessible となっているので、こちらを Accessible from repositories in the 'xxxxxx' organization に変更します

image.png

Github Actions ファイル

Actions ファイルも Reusable workflows の仕様にあわせて記述する必要があります。

  • .github/workflows 配下に設置する必要がある
    • サブディレクトリの作成も基本的に不可
  • 呼び出しの定義として on: workflow_call: の定義を必ずつける必要がある
on:
  workflow_call:

記述例

ここでは、GitHub Actions の Lint ツールである actionlint を実行する Reusable wofkflows をサンプルとして掲載します。

on:
  workflow_call:
    inputs:
      os:
        default: ubuntu-latest
        required: false
        type: string

jobs:
  actionlint:
    runs-on: ${{ inputs.os }}
    steps:
      - uses: actions/checkout@v4
      - name: Run actionlint
        shell: bash
        run: |
          bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
          ./actionlint -color

inputs で指定された OS 環境(デフォルトでは ubuntu-latest)の上で rhysd/actionlint を実行する、という例になっています。
上述の通り Reusable workflows として使用するためには on: workflow_callの指定が必要なので、そのような記述をしています。

Reusable workflows の呼び出し

記述例

上記の actionslint 向けの Reusable workflows が (organization)/github-actions/.github/workflows/actionlint.yml に置かれている、とした場合、以下のような形で呼び出すことができます。

name: Actionlint
on:
  pull_request:

jobs:
  actionlint:
    uses: (organization)/github-actions/.github/workflows/actionlint.yml@main

uses には、以下のようなフォーマットで呼び出す Actions ファイルを指定します

(organization)/(repository)/.github/workflows/(reusable workflows file)@(branch name)

reusable workflows に値を渡す場合は、 with や secrets などを指定して渡します。

こんな形で、色々な箇所で実施される共通処理を Reusable workflows として切り出すことが可能です。
Reusable workflows は以前から有った機能ですが、2023年(2022年12月〜)のトピックとしては 「別の Private Repository に置かれている workflow も呼び出すことができる」 ようになったことが利便性の向上という意味で大きな点かなと思います。

Composite action とは?

https://docs.github.com/ja/actions/creating-actions/creating-a-composite-action

直訳すると「複合アクション」となります。複数の step からなる処理を切り出して、再利用可能な形にした Action です。

Reusable workflows と同様、 別の private repository に置かれている Actions を呼び出すことができる ようになっています。このドキュメントはその状況を前提に記載をします。

なお、Reusable workflows と Composite action の違いは以下のようにまとめられると思います。

  • Reusable workflows: workflow の処理を切り出して再利用化できる。
  • Composite action: 複数の step の処理を切り出して再利用化できる。

Composite action の設定

Github Actions ファイル

Actions ファイルも Composite action の仕様にあわせて記述する必要があります。

  • 呼び出しの定義として runs: using:composite の定義を必ずつける必要がある
runs:
  using: composite
  • Reusable workflow と異なり、Composite action は所定のディレクトリを作成し、その中に単一もしくは複数の Action 定義 yaml を設置する形になる。
  • Composite action へ値を渡す時は Composite action 側で inputs を定義する。
inputs:
  some_value:
    required: true

conventions (organization specific)

これは私の組織の固有の事情ですが、対象となる Composite action のファイルは .github/composite 配下に設置するようにしています。

その理由は、現時点で actionlint が Composite action 形式をサポートしておらず、 actionlint に読み込ませるとエラーとなってしまうためです。

https://github.com/rhysd/actionlint/issues/46

.github/workflows については actionlint の対象としたいため、その回避策として上記のような運用をしています。

記述例

terraform の環境を初期化して、plan を実行する Composite action を例に挙げます。

name: terraform-plan
description: terraform-plan
inputs:
  terraform_version:
    required: true
runs:
  using: composite
  steps:
    - name: setup terraform
      uses: hashicorp/setup-terraform@v2
      with:
        terraform_version: ${{ inputs.terraform_version }}

    - name: terraform fmt
      id: fmt
      shell: bash
      run: terraform fmt -check

    - name: terraform init
      id: init
      shell: bash
      run: terraform init

    - name: terraform validate
      id: validate
      shell: bash
      run: terraform validate -no-color

    - name: terraform plan
      id: plan
      shell: bash
      run: terraform plan -no-color

terraform をインストールした上で、以下のコマンドを実行しています。

  • terraform fmt
  • terraform init
  • terraform validate
  • terraform plan

Composite action の呼び出し

記述例

上記の terraform 関連の Composite Action を例にすると、呼び出し側では以下のような記述で呼び出しを行うことができます

      - name: terraform plan
        uses: (organization)/github-actions/.github/composite/terraform-plan@main
        with:
          terraform_version: 1.6.5

uses に以下のようなフォーマットで呼び出す Actions ファイルを指定します。

(organization)/(repository)/.github/composite/(composite action dir)@(branch name)

大きな注意点として、Composite action に値を渡す場合は、 withを指定して渡します。
理由としては secrets は現時点では Composite action ではサポートされていないため、値渡しは with のみで実現可能なためです。

https://github.com/orgs/community/discussions/34212

現時点では、社内ではそこまでセキュアな値をやり取りする要件に遭遇してはいませんが、上記の仕様を嫌って Composite Action を採用しない、という選択肢も実際のところはありえるかなと感じます。

Discussion