CompositeRunStepsをinputsとoutputsを使って効率よく設計したい時に陥りがちな点を書いてみた

CompositeRunStepsで効率良い再利用設計を始める時に、多分ぶつかるポイントについて書いてみました。useするActionの仕様についても理解していないと結構ハマります。
2021.08.28

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

あれもこれも出来て便利そうに見えつつも、制約を整理してみると中々大変だった、というのはよくありがちです。

CompositeRunStepsはinputsとoutputsを用いることで関数のような扱いも可能になります。問題はWorkflowと違って制約が多々あることや、Workflowでは触れることの少なかった値の引き渡し等を意識する必要がある点です。

公式ドキュメントを参考しつつ、Composite Run Steps内でinputsにてパラメータを受け取る場合のバリデートや、複数行のパラメータを受け取る場合の渡し方など、試してみてわかったことを書いてみました。

CompositeRunStepsの制約について

ツリーとして、及び親子関係で利用可能なアクションや機能については以下READMEに記載されていますが、把握し難い点は否めません。

上記リンク先で利用可能とされているものと、現在利用できるようになったもの(uses)を実際のツリー構成で記述してみると以下の様になります。

name:
description:
inputs:
outputs:
runs:
  steps:
    - name:
      uses:
      id:
      env:
      shell:
      working-directory:

基本的なツリー構成については理解出来ました。次にinputsから引数を受ける場合の初期値やバリデートの制約について挙げていきます。

inputsで指定可能な初期値

何でも指定できると思いこんでいましたが、そんなことはありませんでした。以下のツリー構成の値は設定できません

  • steps.*
  • env.*

設定した場合は以下の通り。

inputs:
  python-version:
    default: ${{ env.python-version }}

Unrecognized named-value: 'env'. Located at position 1 within expression: env.python-version

inputs:
  python-version:
    default: ${{ steps.versions.outputs.python }}

Unrecognized named-value: 'steps'. Located at position 1 within expression: steps.versions.outputs.python

この辺りが使えない記述を見かけなかった為に「いけるのでは」と思ったものの無理でした。関数の実行(hashFiles)やWorkflow起動時に固定されるツリー等(runner)であれば埋め込み可能です。

CompositeRunSteps内での値バリデート

defaultの精査をするには runs でシェルスクリプトベースの判定に迫られそうですが、シンプルな判定で問題なければActionに頼る方法もあります。

inputs:
  pipenv-cache-key:
    default: ${{ null }}
    required: false
    description: 'pipenv cache key'
run:
  steps
    - uses: haya14busa/action-cond@v1
      id: update-pipenv-cache-key
      with:
        cond: ${{ inputs.pipenv-cache-key == null }}
        if_true: "pipenv-cache-key=${{ runner.os }}-python-${{ env.python-version }}-pipenv-${{ env.pipenv-version }}-airflow-${{ hashFiles('airflow/Pipfile.lock') }}"
        if_false: "${{ inputs.pipenv-cache-key }}"

CompositeRunStepsではif構文が使えない為に条件に応じたStep実行切り分けができないのですが、このActionは指定条件を用いて独自に判定を行っており、値の設定限定ですが分岐が可能です。 runs でのシェルスクリプトデバッグに時間を取られることを防げるわけです。

なお、 if_trueif_false には複数行の指定も可能です。

run:
  steps:
    - uses: haya14busa/action-cond@v1
      id: update-pipenv-restore-keys
      with:
        cond: ${{ inputs.pipenv-restore-keys == null }}
        if_true: |
          ${{ runner.os }}-python-${{ env.python-version }}-pipenv-${{ env.pipenv-version }}-airflow-
        if_false: |
          ${{ inputs.pipenv-restore-keys }}

設定した値は steps から outputs.value 経由で取得します。

  ....
    - name: pipenv-cache
      uses: actions/cache@v2
      id: pipenv-cache
      with:
        path: |
          ${{ inputs.pipenv-cache-path }}
        key: ${{ steps.update-pipenv-cache-key.outputs.value }}
        restore-keys: |
          ${{ steps.update-pipenv-restore-keys.outputs.value }}

CompositeRunSteps内で使ったusesによるoutputs.valueをWorkflow側で受け取る

CompositeRunSteps内のStepsはWorkflow側と共有されていません。ではどうやるか。CompositeRunSteps内でusesを使い、且つoutputsにvalueを設定してWorkflowに戻します。

以下、今回はactions/cacheを例にしてみます。このActionはキャッシュ結果を outputs.cache-hit で返してくれます。ファイルパスは .github/actions/cache_recover/action.yml です。

outputs:
  pipenv-cache-hit:
    description: "pipenv cache hit result"
    value: ${{ steps.pipenv-cache.outputs.cache-hit }}
runs:
  steps:
    - name: pipenv-cache
      uses: actions/cache@v2
      id: pipenv-cache
      with:
        path: |
          ${{ inputs.pipenv-cache-path }}
        key: ${{ steps.update-pipenv-cache-key.outputs.value }}
        restore-keys: |
          ${{ steps.update-pipenv-restore-keys.outputs.value }}

そしてWorkflow側。

    - name: Recover from cache
      id: setup
      uses: ./.github/actions/cache_recover

    - name: Install dependencies
      if: ${{ steps.setup.outputs.pipenv-cache-hit != 'true' }}
      run: |
        pipenv sync --dev

分かってみればシンプルですが、値の返し方については勿論、useしたActionが何を返すのかも把握しておく必要があります。

あとがき

CompositeRunStepsは使い込もうとすればするほど仕様の制約を踏む確率が高く、迂回するための試行錯誤が必要になってきます。また、Actionsの仕様を把握していない状態での着手は作業時間超過に繋がる可能性も高くなります。行き当たりばったりではなく、事前にある程度仕様の読み込みをおすすめします。