⤴️

Github Actions:inputsの共通化

2024/07/14に公開

Github Actions、便利で好きです。
でもドキュメントの分類が理解できておらず、ちょっとすぐに全容が把握できないことがあります。

今回はworkflowを外部からkickしたりするときに使える、inputsの共通化をトライしたのでメモしておきます。

workflowのinputsとは

今回触るのはjobやstepのinputsではなく、workflowのinputsです。
inputsという名前で入力を設定できるtriggerは、現状二つだけです。

  • workflow_dispatch
    • 主に手動実行用
    • API経由でも実行できる。
    • 有効なトークンが必要
      • PAT(personal access token)など
  • workflow_call
    • 再利用可能なworkflow
    • jobs.<job_id>.uses句で指定する。
    • stepのusesでは使えない。
    • リポジトリ跨ぎも可能
      • private repoは同一org/enpのprivate限定
        • 要設定

1. workflow_dispath.inputs

Actions画面で手動実行可能なUIを追加するためのトリガーです。
https://docs.github.com/en/actions/using-workflows/manually-running-a-workflow

inputsに一番細やかな指定でき、型指定・チェックも可能です。
API経由でもある程度validationされているみたいです。

注意点

  • 一つのworkflowに10個まで
  • 入力の最大ペイロードは 65,535 文字。
  • 型はboolean, choice, number, environment, stringの5つ。
  • choice型やenvironment型はアクセス時には文字列

また、ややこしいことに、workflow_dispatchトリガーではinputsのアクセス方法がふたつあり、若干の違いがあるようです。

  • ${{ github.events.inputs.xxx }}
    • 昔からある。
  • ${{ inputs.xxx }}
    • 後で追加された
    • booleanが文字列に変換されない

基本的にinputs.xxx経由でアクセスする方が良いと思います。

設定ファイル

on:
  workflow_dispatch:
    inputs:
      logLevel:
        description: 'Log level'
        required: true
        default: 'warning'
        type: choice
        options:
        - info
        - warning
        - debug
      tags:
        description: 'Test scenario tags'
        required: false
        type: boolean
      environment:
        description: 'Environment to run tests against'
        type: environment
        required: true

jobs:
  log-the-inputs:
    runs-on: ubuntu-latest
    steps:
      - name: access by env
        run: |
          echo "Log level: $LEVEL"
          echo "Tags: $TAGS"
          echo "Environment: $ENVIRONMENT"
        env:
          LEVEL: ${{ inputs.logLevel }}
          TAGS: ${{ inputs.tags }}
          ENVIRONMENT: ${{ inputs.environment }}
      - name: access by inputs
        if: inputs.tags # boolはbool
        run: |
          echo "Log level: ${{ inputs.logLevel }}"
          echo "Tags: ${{ inputs.tags }}"
          echo "Environment: ${{ inputs.environment }}"
      - name: access by github.event.input
        if: github.event.inputs.tags == 'true' # boolも文字列
        run: |
          echo "Log level: ${{ github.event.inputs.logLevel }}"
          echo "Tags: ${{ github.event.inputs.tags }}"
          echo "Environment: ${{ github.event.inputs.environment }}"

トリガー方法

GUI

https://github.com/wasnot/gh-actions-test/actions/workflows/manual.yml のような、workflowのURLにアクセスすると、GUI経由で実行できます。
ブランチの指定が可能です。

typeに応じてUIが変更されます。

API

curlでリクエストすることも可能です。

curl -L \
  -X POST \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer <YOUR-TOKEN>" \
  -H "X-GitHub-Api-Version: 2022-11-28" \
  https://api.github.com/repos/OWNER/REPO/actions/workflows/WORKFLOW_ID/dispatches \
  -d '{"ref":"main","inputs":{"logLevel":"info","tags":true,"environment":"production"}}'
workflow

github-scriptのactionを使うことで、他のworkflowからリクエストすることもできます。

jobs:
  kick_workflow:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.TOKEN }}
          script: |
            await github.rest.actions.createWorkflowDispatch({
              owner: 'OWNER',
              repo: 'REPO',
              workflow_id: 'WORKFLOW_ID',
              ref: 'main',
              inputs: {
                logLevel: 'warning',
                tags: true,
                environment: "production",
              },
            })

2. workflow_call.inputs

次に、再利用可能なworkflowを使用するときにwith句で渡すことができるinputsについてです。
https://docs.github.com/en/actions/using-workflows/reusing-workflows#nesting-reusable-workflows

inputsに型指定ができ、チェックもされます。
ただし使用できる型数はworkflow_dispatchよりも少ないです。

注意点

  • 型指定は必須で、指定できる型はboolean, number, stringの3つ。
  • デフォルトのパラメータが設定されていない場合、入力のデフォルト値は、ブール値の場合は false、数値の場合は 0、文字列の場合は "" です。
  • コンテキスト変数として参照できるので、 コンテキストにアクセスできないところでは使用できない
  • 呼び出し元のワークフローが、呼び出されたワークフローで指定されていない入力を渡すと、エラーが発生します。

inputsのアクセス方法はinputs.xxxのみです。

設定ファイル

on:
  workflow_call:
    inputs:
      logLevel:
        description: "Log level"
        required: true
        default: "warning"
        type: string
      tags:
        description: "Test scenario tags"
        required: false
        type: boolean
      environment:
        description: "Environment to run tests against"
        type: string
        required: true

jobs:
  log-the-inputs:
    runs-on: ubuntu-latest
    steps:
      - name: access by env
        run: |
          echo "Log level: $LEVEL"
          echo "Tags: $TAGS"
          echo "Environment: $ENVIRONMENT"
        env:
          LEVEL: ${{ inputs.logLevel }}
          TAGS: ${{ inputs.tags }}
          ENVIRONMENT: ${{ inputs.environment }}
      - name: access by inputs
        if: inputs.tags # boolはbool
        run: |
          echo "Log level: ${{ inputs.logLevel }}"
          echo "Tags: ${{ inputs.tags }}"
          echo "Environment: ${{ inputs.environment }}"
      - name: access by github.event.input # これは使えない
        run: |
          echo "Log level: ${{ github.event.inputs.logLevel }}"
          echo "Tags: ${{ github.event.inputs.tags }}"
          echo "Environment: ${{ github.event.inputs.environment }}"

トリガー方法

workflowファイルからjobs.<job_id>.uses句で指定して使用します。
jobs.<job_id>.steps[*].usesでは使えません。

on:
  workflow_dispatch:

jobs:
  kick_reusable:
    uses: ./.github/workflows/reusable.yml
    with:
      logLevel: debug
      tags: true
      environment: development

番外編: repository_dispatch

workflow_dispatchと同じようにAPIでトリガーできるイベントに、repository_dispatchがあります。
違いはいろいろありますが、下記記事が参考になります。

https://gkzz.dev/posts/repository-dispatch-vs-workflow-dispatch-in-github-actions/

inputs変数は使えませんが、API経由でpayloadを受け取ることで、似たような役割を果たすことができます。
ただし、変数定義ができないので、型チェックもされません。
なので、そこを気をつければ問題ないかと思います。
受け取った変数は、jsonがオブジェクトにマッピングされ、コンテキスト変数${{github.event.client_payload}}として展開されるので、便利ではあります。

自分はworkflow_dispatchとの違いをみてこちらの使い所がわからなかったので、実運用では使っていません。
もしかしたらハマりポイントが他にもあるかもしれないです。

設定ファイル

on:
  repository_dispatch:
    types: [test_trigger]

jobs:
  log-the-inputs:
    runs-on: ubuntu-latest
    steps:
      - name: access by env
        run: |
          echo "Log level: $LEVEL"
          echo "Tags: $TAGS"
          echo "Environment: $ENVIRONMENT"
        env:
          LEVEL: ${{ github.event.client_payload.logLevel }}
          TAGS: ${{ github.event.client_payload.tags }}
          ENVIRONMENT: ${{ github.event.client_payload.environment }}
      - name: access by inputs # これは使えない
        if: inputs.tags
        run: |
          echo "Log level: ${{ inputs.logLevel }}"
          echo "Tags: ${{ inputs.tags }}"
          echo "Environment: ${{ inputs.environment }}"
      - name: access by github.event.client_payload
        if: github.event.client_payload.tags # boolはbool
        run: |
          echo "Log level: ${{ github.event.client_payload.logLevel }}"
          echo "Tags: ${{ github.event.client_payload.tags }}"
          echo "Environment: ${{ github.event.client_payload.environment }}"

トリガー方法

APIリクエストすることで可能です。
https://docs.github.com/ja/rest/repos/repos?apiVersion=2022-11-28#create-a-repository-dispatch-event

curl -L \
  -X POST \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer <YOUR-TOKEN>" \
  -H "X-GitHub-Api-Version: 2022-11-28" \
  https://api.github.com/repos/OWNER/REPO/dispatches \
  -d '{"event_type":"test_trigger","client_payload":{"logLevel":"debug","tags":true,"environment":"staging"}}'

他のワークフローからhookするなども想定されていそうです。

他のトリガーと併用する

inputsを使えるトリガーのみをworkflowのtriggerに指定している場合は、inputs経由でアクセスすれば注意することはあまりありません。

pushやschedule等、他のtriggerも同じworkflowで併用したい場合は、少し注意が必要です。
なぜなら、他のトリガー経由では、inputs.xxxへのアクセスできず、無指定時には型指定にかかわらず必ず空となるからです。
なので、同一のタスクを他のトリガーでも実行したい場合は、下記のどちらかの対応が必要です。

  • workflowを分けて、トリガーする
    • workflow_callもworkflow_dispatchも可能
    • 同期的にやりたいならworkflow_callがおすすめ
  • inputs.xxxがないときにデフォルト値を使う
    • 使用箇所によってはできないケースもある

workflowを分けるのは簡単なので、ここではデフォルト値を使うケースのみを少し紹介します。

その他のトリガーでデフォルト値を使いたい場合

その他のトリガーの際に、inputs.xxxは型定義が無視されて空オブジェクトが挿入されます。
文字列なら空文字チェックすればいいのですが、数値やbool値だと誤判定になりやすいので、一度文字列に変換すると楽な場合があります。
ここでは環境変数を経由するパターンを紹介します。

環境変数を経由するパターン

例えば、scheduleトリガーとworkflow_dispatchトリガーを使いたい場合、下記のように環境変数を経由すると、scheduleの場合はデフォルト値で、手動実行の場合は実行時のinputsを使うことが可能です。

on:
  schedule:
    - cron:  '0 0 1 * *'
  workflow_call:
    inputs:
      logLevel:
        description: 'Log level'
        required: true
        default: 'warning'
        type: choice
        options:
        - info
        - warning
        - debug
      tags:
        description: 'Test scenario tags'
        required: false
        type: boolean

env:
  # schedule実行時の値を入れるため、envを介する.
  LOG_LEVEL: ${{ github.event_name == 'schedule' && 'info' || inputs.logLevel }}
  TAGS: ${{ github.event_name == 'schedule' && 'false' || inputs.tags }}

jobs:
  test_job:
    runs-on: ubuntu-latest
    steps:
      - name: run if tags is true
        if: ${{ env.TAGS != 'true' }}
        run: echo "$LOG_LEVEL"

この方法の注意点として、inputsは型指定が可能なのに対し、環境変数は文字列しか扱えないので、workflow_dispatchでbooleanが渡ってきた時の処理を考慮する必要があります。
上の例ではif文でtrueという文字列と一致するかを常に確認しています。
空文字を許容している場合は、気をつけてください。
(自分は結構ハマりました。)

また、コンテキスト変数は、jobs.<job_id>.if等では使えなかったりするので、トリガーが異なる時にjobを切り替えるとかはできないことがあります。
なので、おすすめとしては、普通にworkflowを分ける方法がいいと思います。

まとめ

最後に余計な例をつけましたが、なるべく一つのworkflowに複数triggerを設定せず、workflowを分割するのがいいです。
workflow_callを定義すれば、他のworkflowから簡単に実行できます。

どうしても一緒にしたい場合は、環境変数を経由するのがおすすめです。
ただし、型情報が欠損するので、その点だけ注意しておくといいかなと思いました。

以上です。

参考

Discussion