GitHub Actions で terraform plan の結果をPRに添付する方法

こんにちは、SRE所属の @ishii1648 です。
この記事は、All About Group（株式会社オールアバウト） Advent Calendar 2022 19日目の記事です。

## はじめに
現在オールアバウトでは Terraform のCI化を進めています。CI の流れとしてはPRの作成・更新をトリガーにざっくり以下のような動作順序になっています。

1. 変更対象のディレクトリを検知
2. terraform のセットアップ
3. terraform init
3. terraform plan
3. terraform validate
3. terraform plan の結果を PR に添付

割とよくある構成だと思いますが、一つ躓くポイントがあるのでその点について解決策を共有できればと思います。
かなりニッチな内容にはなりますが、興味のある方、同じ悩みをお持ちの方の一助になれば幸いです。

## 躓きポイントについて

terraform validate を利用する場合（conftestを利用する場合も同じです）、terraformのセットアップで以下のようにオプションを設定する必要があります。

```yaml
- name: Setup terraform
  uses: hashicorp/setup-terraform@v2
    with:
      terraform_version: 1.x.x
      terraform_wrapper: false
```

注目して頂きたいのは `terraform_wrapper` というオプションでデフォルトが true なのですが false に設定してやらないと validate が正常動作しません。

しかしこの設定にすると terraform が標準出力できなくなるためplan結果を PR に添付するのが難しくなります。
例えば validate を省略するようなCIの作りの場合以下のようなコードでplan結果を PR に添付できます。

```yaml
- name: Setup terraform
  uses: hashicorp/setup-terraform@v2
  with:
    terraform_version: 1.2.0
- name: Terraform init & plan
  id: plan
  run: |
    terraform init
    terraform plan -no-color
  working-directory: ${{ matrix.dir }}
  continue-on-error: true
- name: Notify
  if: steps.plan.outputs.exitcode == 2
  uses: actions/github-script@v3.0.0
  with:
    github-token: ${{ secrets.github_token }}
    script: |
      const output = `## \`${{ matrix.dir }}\`
      <details><summary>Show Plan</summary>

      \`\`\`terraform\n${{ steps.plan.outputs.stdout }}\`\`\`

      </details>`;

      github.issues.createComment({
        issue_number: context.issue.number,
        owner: context.repo.owner,
        repo: context.repo.repo,
        body: output
      })
```

terraform plan した結果を次のジョブで標準出力（steps.plan.outputs.stdout）として受け取って通知しています。
ここで validate の処理を追加しようとすると上述の通り標準出力が使えなくなるため困り果ててしまいます。

## 解消方法

結論以下のようなコードで validate を入れつつ結果を PR に添付できるようになります。

```yaml
- name: Setup terraform
  uses: hashicorp/setup-terraform@v2
  with:
    terraform_version: 1.2.0
    terraform_wrapper: false
- name: Terraform init & plan
  id: plan
  run: |
    terraform init
    terraform plan -no-color -out=tfplan
  working-directory: ${{ matrix.dir }}
  continue-on-error: true
- name: Terraform show
  id: show
  run: |
    terraform show -no-color tfplan > tfplan.txt
    terraform show -no-color -json tfplan > tfplan.json
    cat tfplan.txt
  working-directory: ${{ matrix.dir }}
- name: Terraform validate
  id: validate
  run: |
    cp ${GITHUB_WORKSPACE}/policies/constraints/* ${GITHUB_WORKSPACE}/policy-library/policies/constraints
    gcloud beta terraform vet tfplan.json --policy-library=${GITHUB_WORKSPACE}/policy-library --format=json
- name: Notify
  uses: actions/github-script@v3.0.0
  with:
    github-token: ${{ secrets.github_token }}
    script: |
      const output = `## \`${{ matrix.dir }}\`
      <details><summary>Show Plan</summary>

      \`\`\`terraform\n${{ steps.show.outputs.stdout }}\`\`\`

      </details>`;

      github.issues.createComment({
        issue_number: context.issue.number,
        owner: context.repo.owner,
        repo: context.repo.repo,
        body: output
      })
```

ポイントは terraform plan 実行時にoutオプションを設定する所です。outオプションはplanの実行結果をバイナリ形式でファイル保存してくれます。ここは標準出力を利用していないので `terraform_wrapper` が無効化されていても有効に機能します。

バイナリ形式で保存されたplan結果は terraform show を使ってテキスト化できるのでその内容を通知として扱うことができます。

## 最後に

如何だったでしょうか？

かなりニッチな内容だったかとは思いますが、TerraformをCI化しようとすればvalidate等の静的チェックも併せて入れるのは一般的なことかと思います。私はこのやり方を思いつくまでに1ヶ月以上かかってしまったので同じ悩みの方がいればぜひ参考にして頂けると幸いです。

以上、All About Group（株式会社オールアバウト） Advent Calendar 2022 19日目でした。