GitHub Actionsのstepsの重複を減らすためのComposite run steps action

GitHub Actionsを使っていて、並列目的などでジョブを細かく分けたときに依存関係の設定などで記述が重複する箇所が結構発生し、それらの重複をComposite run steps action （Composite actionなどとも表記されるようです）という機能を使って軽減したのでその際の備忘録です。

# 発生していた問題とやったこと

少し前にPythonの自作ライブラリのLintやテスト・デプロイなどのGitHub Actionsのジョブで依存関係の設定のキャッシュ機能やジョブ分割による並列実行などを対応しました。多くのジョブを走らせても処理時間などは良い感じに気にならない形となりました。

一方でジョブの分割によって依存関係の設定など（Pythonの設定やPythonライブラリのパッケージ設定など）の記述が各ジョブ間で重複している箇所がsteps内で目立ちました。

DRY原則的にもこの辺は重複が多いと結構気になりますし変更する際などに変更漏れなどが発生しそうであまり良くありません。

そこでComposite run steps actionという機能を使ってstepsの重複部分の記述を統一しました。

# Composite run steps actionとはどんな機能なのか

- steps内の一部の処理群を別のYAMLファイルに記述することができます。
- 元々のワークフローの各ジョブのsteps内の任意の位置でその別ファイルのYAMLのstepsの処理を呼び出すことができます。
- 別ファイル側で実行されたstepsの処理結果は呼び出し元にも反映されます。つまり依存関係などの設定を別ファイルで設定した際には呼び出し元のジョブ側でもその後その依存関係のものを利用することができます。
- 別ファイルのstepsへは呼び出し元から引数のように任意のパラメーターを渡すことができます。
- この記事を書いている時点では別ファイル側ではifが使えなかったり一部追加の設定などが必要になったりといった制限などがあります。

# 必要な対応内容の概略

※それぞれ後の節で詳しく触れます。

- 切り出したいstepsの処理を記述した別ファイルのYAMLファイルを設置します。
- そちらの別ファイルに受け付けるパラメーターの設定やstepsなどを記述します。
- 呼び出し元のジョブの任意のstepsの箇所でその別ファイルを指定して処理を呼び出します。

# 別ファイルのYAMLの設置方法

設置する別ファイルにはフォルダ名やファイル名にルールがあるので注意します。

まずは通常のワークフローのYAMLが設置されているリポジトリの`.github`フォルダ以下にactionsというフォルダを追加します。そちらのフォルダ内に切り出したい処理の名前を付けたフォルダを更に追加します。

今回は依存関係などの設定を行うためのstepsを切り出したので`setup_py_dependencies`という名前のフォルダにしました。`<リポジトリパス>/.github/actions/setup_py_dependencies/`といったようなフォルダ構成になっています。

今後別のstepsの処理を追加で切り出したい場合は`setup_py_dependencies`フォルダと同じ階層に別の名前のフォルダを追加します。

追加した`setup_py_dependencies`フォルダ以下に`action.yml`というファイルを追加します。拡張子は`.yaml`でも認識してくれるようです。ただしファイル名は別のものにしてしまうと認識してくれなさそう？なので注意します。結果的に今回対応したファイルのパスは`<リポジトリパス>/.github/actions/setup_py_dependencies/action.yml`となっています。

# 切り出したaction.ymlファイルで最低限必要な記述

最低限name, description, runsが必要になります。descriptionなどは省略するとVS Code上で引っかかっていたので基本的には書く形のようです。また、runs内には`using: composite`という記述が必要になります。後はruns内にstepsを書いていけば対応ができます。

```yaml:setup_py_dependencies/action.yml
name: Setup the Python dependencies
description: This composite run steps require the dependencies cache before running.
runs:
  using: composite
  steps:
    - name: Set the Python version
      ...
```

# 渡すパラメーターの設定

呼び出し元からなんらか任意のパラメーターを切り出したYAMLの処理に渡したい場合には`inputs`の設定を行う必要があります。以下のようなフォーマットで書きます。

```yaml
inputs:
  <パラメーター名>:
    description: <パラメーターの説明>
    required: <パラメーターを必須とするかどうかの真偽値>
```

例えば`python-version`という名前のパラメーターを必須の設定で設定したい場合には以下のように書きます。

```yaml:setup_py_dependencies/action.yml
name: Setup the Python dependencies
description: This composite run steps require the dependencies cache before running.
inputs:
  python-version:
    description: Python version to use.
    required: true
...
```

こうすることで、steps内で`${{ inputs.<パラメーター名> }}`と記述することで渡されたパラメーターを参照することができます。先ほどの`python-version`というパラメーターの場合は`${{ inputs.python-version }}`といったようになります。

記述例 :

```yaml:setup_py_dependencies/action.yml
name: Setup the Python dependencies
description: This composite run steps require the dependencies cache before running.
inputs:
  python-version:
    description: Python version to use.
    required: true
runs:
  using: composite
  steps:
    - name: Set the Python version
      uses: actions/setup-python@v2
      with:
        python-version: ${{ inputs.python-version }}
...
```

# 呼び出し元のsteps内で追加したYAMLの内容を呼び出す方法

呼び出し元となるワークフローの任意のsteps内で追加した別ファイルのYAMLのものを呼び出したい場合には`uses`に`./.github/actions/<対象の処理のフォルダ名>`と指定します。今回は`setup_py_dependencies`というフォルダを追加しているので`uses: ./.github/actions/setup_py_dependencies`といった記述になります。action.ymlといったファイル名部分は不要です。また、先頭に`./`の記述が無いと動かなかったりする？ようなので注意してください。

記述例 :

```yaml
...
  RunFlake8:
    needs: CreateCache
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: Set the Python version environment variable
        run: echo "python-version=3.6.15" >> $GITHUB_ENV
      - name: Checkout
        uses: actions/checkout@v2
      - name: Setup the Python dependencies
        uses: ./.github/actions/setup_py_dependencies
      - name: Set the flake8 alias
        run: alias flake8=/opt/hostedtoolcache/Python/${{ env.python-version }}/x64/bin/flake8
      - name: Run the flake8 command
        run: python run_flake8.py
...
```

# パラメーターを指定して呼び出しを行う

呼び出しの際にパラメーターを指定したい場合には`with`を使って以下のように書きます。

```yaml
...
        with:
          <パラメーター名>: <渡すパラメーター>
...
```

今回は`python-version`というパラメーターを必須として切り出したYAMLファイル側で設定しているのでそちらを指定します。

記述例 :

```yaml
...
      - name: Setup the Python dependencies
        uses: ./.github/actions/setup_py_dependencies
        with:
          python-version: ${{ env.python-version }}
...
```

# runを使うときには追加でshellの指定が必要

注意点となりますが、切り出したYAML内でのrunでのコマンドの実行には`shell`の設定が必要なようです。`shell: bash`といったような記述を追加しておく必要があります。

記述例 :

```yaml
    - name: Set the site package path to the environment variables
      run: python -c "import site; print(f'site-packages-path={site.getsitepackages()[0]}')" >> $GITHUB_ENV
      shell: bash
```

記述が無いとVS Code上などでエラーが表示されます。試していないですがそのままGitHub上に反映しても弾かれるかもしれません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/1df2ebca-8087-dd18-6a96-be449c28fa03.png)

# 今回の記事の内容を反映したYAML

参考用に今回のものを反映したYAMLファイルのリンクを貼っておきます（日々更新しているため記事内容と最新のYAMLファイルの差分などはご容赦ください）。

メインのワークフローのYAML:

https://github.com/simon-ritchie/apysc/blob/main/.github/workflows/deploy_to_pypi.yml

Composite run steps actionとして別ファイルに切り出したYAML:

https://github.com/simon-ritchie/apysc/blob/main/.github/actions/setup_py_dependencies/action.yml

# 参考サイトまとめ

https://blog.n-z.jp/blog/2020-08-21-github-actions-composite-run-steps-as-local-action.html

https://zenn.dev/tmrekk/articles/5fef57be891040

https://dev.classmethod.jp/articles/composite-run-step-with-private-repos/