GitHub ActionsのComposite Action (複合ステップアクション) は便利なのですが、制約や歯がゆいことが多く悩ましいです。
では何が難しいと感じているのか、その対処をどうしているのかメモしておきます。
概要
- Composite Actionはrunのみ使える。usesは使えないからあきらめて
- Composite Actionはrun.ifが使えないのでbash ifで分岐しよう
- Composite Actionでスクリプト使うならコンテナ実行時にパス狂うから気を付けて
- Compoiste Actionの全runステップはGrouping log linesを使おう、絶対だ
Composite Actions とは
GitHub Actionsは、Jobで実際にやる処理1つ1つをstepとして記述できます。
このstepでrun:を使っていろいろな処理を書いたりuses:を使ってアクションを呼び出したりしていることでしょう。
さて、プロジェクトでいろいろなworkflowを用意していくと、似通ったrun stepを記述していてまとめ上げたくならないでしょうか。 TypeScriptやDockerアクションにするというわけではなく、単純にrun stepのYAMLを分離して呼び出すことで共通化したい。
こんな時に便利なのがComposite Actionです。
https://docs.github.com/en/actions/creating-actions/creating-a-composite-run-steps-action
Composite Actions の利用例
例えば、次のようにjobA, jobB, jobCそれぞれでdotnet build / publishをしているときに、このdotnet処理を別のYAMLに記述して呼び出せれば便利、みたいな感じです。(これを分離するのに価値があるかはおいておいて、まとめ上げられるというのに注目)
jobs: jobA: runs-on: ubuntu-latest steps: - run: dotnet restore - run: dotnet build -c Debug - run: dotnet publish -c Debug - run: nanika yaru jobB: runs-on: ubuntu-latest steps: - run: dotnet restore - run: dotnet build -c Debug - run: dotnet publish -c Debug - run: betsu no nanika yaru jobC: runs-on: ubuntu-latest steps: - run: dotnet restore - run: dotnet build -c Release - run: dotnet publish -c Release - run: tondemo naikoto yaru
Composite Actionsを使うようにしてみましょう。
やることは単純です。ローカルActionとして、.github/actions/dotnet_build/actions.yamlを定義して、dotnet buildの記述を移します。
外から実行に値を受けるなら、inputsで指定するのはworkflow_dispatchなどと同じで一貫性が取れています。
name: .NET Build description: | .NET Build inputs: build-config: description: "dotnet build config. Debug|Release" default: "Debug" required: false runs: using: "composite" steps: - run: dotnet restore shell: bash - run: dotnet build -c ${{ inputs.build-config }} shell: bash - run: dotnet publish -c ${{ inputs.build-config }} shell: bash
あとは、元のworkflowで呼び出すだけです。簡単ですね。
jobs: jobA: runs-on: ubuntu-latest steps: - name: .NET Build uses: ./.github/actions/dotnet_build - run: nanika yaru jobB: runs-on: ubuntu-latest steps: - name: .NET Build uses: ./.github/actions/dotnet_build - run: betsu no nanika yaru jobC: runs-on: ubuntu-latest steps: - name: .NET Build uses: ./.github/actions/dotnet_build with: build-config: Release - run: tondemo naikoto yaru
Composite Action 利用時の注意
一見すると簡単で便利、最高って感じですが、Composite Actionsは微妙に歯がゆいことがいくつかあります。 ということで、使うときはこれだけ気を付けておくといいです。(順次改善されて行ってほしい)
1. 使えるのはrun:のみ (制約->改善済み)
感想: uses: 使えるようになってほしいけど無理そう
2021/8/26 に uses が使えるようになりました。
GitHub Actions: Reduce duplication with action composition | GitHub Changelog
Composite Actionで使えるのは、 run:のみでuses:は使えません。
そのため、外部Actionsの呼び出しや別のcomposite actionの呼び出しができません。
これが地味につらいところです。 たいがいはusesをいくつか使っているので、結果そのジョブを丸っとComposite Actionに移して実行するというのはたいがいできません。
ほぼ毎回、runs:部分をより分けてどれをcomposite actionにするか検討することになるでしょう。
ただ分離したいだけなのに、というわけにはいかないのです。
2. run.ifは使えない (制約)
感想: これはできるようになっていいのでは
run stepは、実行するかどうかを決定するifコンディションがありますが、Composite Actionのrunでif: <expression>は使えません。
このため、元のrunがifを使っていた場合、run:処理の中でbash ifを使って分岐することになったりします。なるほどねー。
# これはだめ - if: ${{ env.HOGE == 'hoge' }} run: do something shell: bash # こうなる - run: | if [[ "${{ env.HOGE }}" == "hoge" ]]; then do something fi shell: bash
if分岐を多用していると地味にめんどくさいので、ちまちまbash ifにするかshell scriptに処理を書いてまとめたりします。
3. container で実行するとgithub.action_pathパスが狂う (歯がゆい)
感想: 地味に罠なのでなおして~
Composite Actionsの今のパスはgithub.action_pathでとれます。このため、Composite Actionで使うスクリプトは同じパスに置いておく、とかできます。
${{ github.action_path }}/prepare_env.sh
しかしコンテナで実行するときは狂うので、仕方ないので${{ job.container.id }}でコンテナ環境か判定して、${{ github.workspace }}と${{ github.action_path }}で修正してあげましょう。
これやらずパス参照で書けばいいやと考えると、actionsのフォルダ名を変えるたびに毎回YAMLを修正しないと行けなくてつらいので。
やっておくのオススメです。
4. 1ステップで実行されるのでログの区切りがつかない (歯がゆい)
感想: すべての Composite Cction でやらないとつらいので大変めんどくさい
Composite Actionsは、端的に言うと呼び出し側の1 stepで呼び出したrunがすべて実行されます。 つまり、1 stepログに、呼び出したすべての処理の標準出力がでるので、どの処理がどの出力か区別がつきません。
このため、Grouping log linesを使って処理ごとにログ出力をグループ化しましょう。絶対やりましょう。
::group::{title}
::endgroup::
https://docs.github.com/en/actions/reference/workflow-commands-for-GitHub-actions#grouping-log-lines
先ほどのサンプルはやってませんね、ダメな奴です。 アレに適用して次のようにすると、dotnet restore / dotnet build / dotnet publishがそれぞれグループ化されます。(こうなると、nameもつけたくなるのでつけてます)
name: .NET Build description: | .NET Build inputs: build-config: description: "dotnet build config. Debug|Release" default: "Debug" required: false runs: using: "composite" steps: - name: restore packages run: | ::group::Restore packages dotnet restore ::endgroup:: shell: bash - name: build run: | ::group::Build dotnet build -c ${{ inputs.build-config }} ::endgroup:: shell: bash - name: publish binaries run: | ::group::Publish binaries dotnet publish -c ${{ inputs.build-config }} ::endgroup:: shell: bash
個人的には、nameで自動的にグループ化してほしい気もありますが、ユーザーの好きなようにコントロールさせるために何もしていない気もします。
まとめ
Composite Actionsは素朴でいいのですが実際使うときはアレってなるので、これらだけ注意すると便利です。
GitHub Actionsに本当に欲しいのは、Template機能な気もするけどローカルアクションは便利なのでいいものです。 公開されているGitHub ActionsをGHEで使うときにGitHubとConnectせずにローカルへ展開できます。
だいたいのことはGitHub Actionsでできるようになりましたが、パイプライン的な観点がないので、今後はそっちがどうなるのか気になりますね。