Paper2 Blog

ともに、かける

GitHub ActionsのmatrixでfromJSON関数を使って動的な設定をする

GitHub ActionsのfromJSON関数でobjectをmapやdictionaryのような連想配列として扱い動的な設定を実現する方法を紹介します。

要約

  • matrix strategiesreusable workflowで動的に設定を変えたい場合がある。
  • ワークフロー内でJSONを定義し、fromJSON関数を利用することで動的な設定を実現できる。
  • ifを利用するより処理の見通しが良くなることが多い。

使いたくなる時の例

matrix strategiesで値によって動的に設定を変えたい場合があります。

例えば複数のマイクロサービスのマイグレーションを実行するときに接続先のDBだけが異なる場合を考えます。まず思いつくのは ifを利用した以下のコードではないでしょうか。

jobs:
  example_matrix:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        service-name: ['service-a', 'service-b', 'service-c']
    steps:
      - name: set up db instance for service-a
        if: matrix.service-name == 'service-a'
        run: echo "DB_INSTANCE=db-a:1111" >> $GITHUB_ENV
      - name: set up db instance for service-b
        if: matrix.service-name == 'service-b'
        run: echo "DB_INSTANCE=db-b:2222" >> $GITHUB_ENV
      - name: set up db instance for service-c
        if: matrix.service-name == 'service-c'
        run: echo "DB_INSTANCE=db-c:3333" >> $GITHUB_ENV
      - name: migration
        run: echo "run migration --db-instance=$DB_INSTANCE"

ifにはfailure関数などを記載することも多いため、すぐに可読性が落ちます。またstepをたくさん作る事になり、処理の見通しが悪くなります。

そのような観点でもifを使わない動的な設定をしたくなります。

動的な設定の実現方法

ワークフロー内でJSONを定義し、fromJSON関数を利用することで動的な設定を実現できます。 上記と同じ処理を書き換えると以下のようになります。

jobs:
  example_matrix:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        service-name: ['service-a', 'service-b', 'service-c']
    env:
      DB_INSTANCES_JSON: |
        {
          "service-a": "db-a:1111",
          "service-b": "db-b:2222",
          "service-c": "db-c:3333"
        }
    steps:
      - name: migration
        env: 
          DB_INSTANCE: ${{ fromJSON(env.DB_INSTANCES_JSON)[matrix.service-name] }}
        run: echo "run migration --db-instance=$DB_INSTANCE"

余分なifやstepがなくなり、処理の流れもわかりやすくなったのではないでしょうか。

実際のワークフローは多くのstepが記述されるため、できるだけシンプルにしておいた方が良いと思います。

より複雑な設定

以下のようにfromJSON関数はネストしたり、boolean、integerなどを利用できるので色々なことができます。*1

jobs:
  example_matrix:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        service-name: ['service-a', 'service-b', 'service-c']
    env:
      DB_INSTANCES_JSON: |
        {
          # 設定をネストできる
          "service-a": {  
            "instance": "db-a:1111",  
            #  booleanを扱うことも可能
            "pre-migration-job": true,
            #  numberを扱うことも可能
            "timeout-minutes": 30
          },
          "service-b": {
            "instance": "db-b:2222",
            "pre-migration-job": false,
            "timeout-minutes": 10
          },
          "service-c": {
            "instance": "db-c:3333",
            "pre-migration-job": false,
            "timeout-minutes": 10
          }
        }
    steps:
      - name: pre-migration-job
        # pre-migration-jobがtrueのservcie-aのみ実行される
        if: ${{ fromJSON(env.DB_INSTANCES_JSON)[matrix.service-name]['pre-migration-job'] }}
        run: echo "run pre-migration-job"
      - name: migration
        # 各マイクロサービスのmigration時間に合わせてtimeoutを設定できる
        timeout-minutes: ${{ fromJSON(env.DB_INSTANCES_JSON)[matrix.service-name]['timeout-minutes'] }}
        env: 
          DB_INSTANCE: ${{ fromJSON(env.DB_INSTANCES_JSON)[matrix.service-name]['instance'] }}
        run: echo "run migration --db-instance=$DB_INSTANCE"

ただし、やりすぎると熟練のJSON職人じゃないとシンタックスエラーで苦しむので注意が必要です。

(小ネタ) fromJsonでもfromJSONでも動く

公式ドキュメントではfromJSONですが、fromJsonでも動きます。fromJsonで書くとうちのアーキテクトに怒られますww

まとめ

matrix strategiesやreusable workflowで動的に設定を変えたい場合があります。その様な際にワークフロー内でJSONを定義し、fromJSON関数を利用することで動的な設定を実現できる方法を紹介しました。

サンプルコードは以下リポジトリにもあります。

github.com

*1:コードとしては動かなくなりますが、コメントで解説を入れています。