📈

Terraformのディレクトリが増えても自動でGitHub Actionsの処理対象にする(動的matrix)

2022/01/07に公開

前提と課題

Terraformではtfstateを適度に分割するのが良いとされています。どの程度の粒度にまで分割するかは様々な考え方があるかと思いますが、例として以下のように分割していたとします。

一方でGitHub Actionsでこれらの各ディレクトリに対して何らかの同一の処理を実行したいとします。

├── envs
│   ├── stg
│   │    │── infra
│   │    │── service_a
│   │    │── ...
│   │    └── service_x
│   └── prd
│        │── infra
│        │── service_a
│        │── ...
│        └── service_x
└── modules
    ├── module_a
    ├── ...
    └── module_x

GitHub Actionsにはbuild matrixという仕組みがあり、これを利用することで同一ジョブを複数ディレクトリに対して実行できます。

例えば、以下のジョブは各ディレクトリでterraform planを実行するジョブとなっています(plan実行にあたって事前に必要なstepなどは割愛しています)。

jobs:
  plan:
    runs-on: ubuntu-20.04

    strategy:
      fail-fast: false
      max-parallel: 1
      matrix:
        TF_DIR: # 以下にディレクトリを記載
	  - envs/stg/infra
	  - envs/stg/service_a
	  - ...
	  - envs/prd/service_x

    env:
      TF_DIR: ${{ matrix.TF_DIR }}

    step:
      //
      - name: terraform plan ${{ env.TF_DIR }}
        run: |
          cd ${TF_DIR}
          terraform plan
      //

ただ、これだとリポジトリ内にtfstateを管理するディレクトリが増えるたびに、GitHub Actionsのワークフローにmatrix.TF_DIRの値をひとつひとつ追記していかなければなりません。

これに対し、matrixを動的に設定することで、ワークフローファイルの修正を不要にします。

準備

まず、matrixを動的に設定するためのジョブを定義します。

jobs:
  set-matrix:
    runs-on: ubuntu-20.04

    env:
      TF_ROOT_DIR: .

    outputs:
      dirs: ${{ steps.find-tfstate-dirs.outputs.dirs }}

    steps:
      - uses: actions/checkout@v2

      - name: Find tfstate dirs
        id: find-tfstate-dirs
        run: |
          dirs=$(find ${TF_ROOT_DIR} -type f -name '*.tf' -exec dirname {} \; | grep -v 'modules\|\.terraform' | sort | uniq | jq -R -s -c 'split("\n")[:-1]')
          echo $dirs # 確認用
          echo "::set-output name=dirs::${dirs}"

findコマンドにより、Terraformコードの存在するディレクトリを洗い出します。また、jqを使うことでディレクトリ一覧を以下の形式に変換しています。これは後ほどのジョブでfromJson()関数を使うために必要な変換となります。

["envs/stg/infra","envs/stg/service_a", ... , "envs/prd/service_x"]

そして、この値はecho "::set-output name=dirs::${dirs}"により、stepのoutputとして設定されます。

さらにstepのoutputは、以下の指定により、jobのoutputとして設定されます。

jobs:
  set-matrix:
    //
    outputs:
      dirs: ${{ steps.find-tfstate-dirs.outputs.dirs }}

ここまでで準備は完了です。

なお、ディレクトリ一覧洗い出しに際し、modules.terraformを名前に含むディレクトリは除外するようにしています(tfstateを管理しているディレクトリとして想定していないため)。

matrixを動的に設定する

後は任意のジョブで、matrixを使います。

jobs:
  set-matrix:
    //

  plan:
    needs: set-matrix

    runs-on: ubuntu-20.04

    strategy:
      fail-fast: false
      max-parallel: 1
      matrix:
        TF_DIR: ${{ fromJson(needs.set-matrix.outputs.dirs) }}

    env:
      TF_DIR: ${{ matrix.TF_DIR }}
      
    steps:
      //
      - name: terraform plan ${{ env.TF_DIR }}
        run: |
          cd ${TF_DIR}
          terraform plan
      //

GitHub Actionsの関数であるfromJson()を以下のように使うことで、前段のジョブset-matrixで生成した["envs/stg/infra","envs/stg/service_a", ... , "envs/prd/service_x"]に基づき、複数のjobが実行されます。

      matrix:
        TF_DIR: ${{ fromJson(needs.set-matrix.outputs.dirs) }}

これで新しく作ったディレクトリをGitHub Actionsのワークフローに追加し忘れた・・・といったことから解放されます💪

参考

https://swfz.hatenablog.com/entry/2021/06/29/195359

https://stackoverflow.com/questions/10234327/convert-bash-ls-output-to-json-array

Discussion