GitHub ActionsでTerragruntとtfcmtを組み合わせてplan結果を見やすくする方法

# やりたいこと
GitHub ActionsでTerragruntを使う際に、plan結果をプルリクエストにコメントする方法を調べていました。
plan結果をそのままコメントすることはできたのですが、もっと見やすくできないかと思い、気になっていたtfcmtと組み合わせてみることにしました。

# Terragruntとは？
Terragruntは、Terraformをより便利にしてくれる薄いラッパーツールです。
設定をDRYに保ち、複数のTerraformモジュールの管理や、リモートステートの管理をやりやすくしてくれます。

Terraformのステートが複数ディレクトリに分かれていて、かつ依存関係があるような場合だと、必須レベルで便利なツールだと思います。
色々と便利な機能があるので、使ったことがない方はぜひ一度試してみてほしいです。

https://terragrunt.gruntwork.io


# tfcmtとは？
>tfcmt は terraform plan, apply の結果を GitHub の Pull Request (以下 PR) にコメントとして通知する CLI ツールです。
態々 CI のログを見にいかなくても PR のページで結果を確認でき、
なおかつ素のログに比べて非常に分かりやすいのが特徴です。

https://zenn.dev/shunsuke_suzuki/articles/improve-terraform-cicd-with-tfcmt

https://suzuki-shunsuke.github.io/tfcmt/

# tfcmtの使い方
`terraform plan`, `terraform apply`を以下のように置き換えます。
```
$ tfcmt plan -- terraform plan [terraform plan の引数...]
```
```
$ tfcmt apply -- terraform apply [terraform apply の引数...]
```
terraformコマンドをそのままterragruntコマンドに置き換えた場合、run-allを使うことができないので、少し工夫する必要があります。

# Terragruntとtfcmtを組み合わせて使う方法
terragruntコマンドのオプションで`--terragrunt-tfpath`を指定します。
このオプションによって、Terragrunt内部で実行されるterraformコマンドの代わりに実行されるバイナリを指定することができます。
以下のようなシェルスクリプトへのパスを指定することで、terraformコマンドの動作をカスタマイズすることができます。

```
terragrunt run-all plan --terragrunt-tfpath $GITHUB_WORKSPACE/.github/scripts/tfwrapper.sh
```

https://terragrunt.gruntwork.io/docs/reference/cli-options/#terragrunt-tfpath

```bash:tfwrapper.sh
#!/bin/bash

set -euo pipefail

# コマンドの種類を取得(例: apply, plan, fmt...)
type=$(echo "$@" |  awk '{print $1}')

# 実行しているディレクトリ名を取得
current_dir=$(pwd | sed 's/.*\///g')

if [ "$type" == "plan" ]; then
    # planのときは-patchオプションを付ける
    # 実行ディレクトリ名をターゲットとして指定
    tfcmt -var "target:${current_dir}" plan -patch -- terraform "$@"
elif [ "$type" == "apply" ]; then
    tfcmt -var "target:${current_dir}" apply -- terraform "$@"
else
    terraform "$@"
fi

```
terraformコマンドの種類で条件分岐させています。
planとapplyの時はtfcmtを実行しますが、planのときは-patchオプションを付けてコメントの数を抑えています。
それ以外のコマンドはそのまま実行します。

https://zenn.dev/shunsuke_suzuki/articles/tfcmt-plan-patch

また、tfcmtでは`-var "target:foo"`のようにすることで、それぞれの結果をラベルのprefixなどで区別できるようになります。
これを利用して実行ディレクトリ名をターゲットとして指定しています。


# サンプルコード

```:ディレクトリ構成
.
├── .github
│   ├── scripts
│   │   └── tfwrapper.sh
│   └── workflows
│       └── plan.yml
└── app
    ├── envs
    │   ├── dev
    │   │   ├── app
    │   │   │   ├── 〇〇.tf
    │   │   │   └── terragrunt.hcl
    │   │   ├── cicd
    │   │   │   ├── 〇〇.tf
    │   │   │   └── terragrunt.hcl
    │   │   ├── db
    │   │   │   ├── 〇〇.tf
    │   │   │   └── terragrunt.hcl
    │   │   └── ...
    │   └── prod
    ├── modules
    │   ├── acm
    │   │   ├── main.tf
    │   │   ├── outputs.tf
    │   │   └── variables.tf
    │   ├── cloudfront
    │   ├── cognito
    │   └── ...
    └── shared
        ├── provider.tf
        ├── variables.tf
        └── version.tf

```

```yaml:.github/workflows/plan.yml
name: Terraform plan
on:
  pull_request:
    branches:
      - main
      - develop
    types:
      - opened
      - synchronize

env:
  AWS_REGION: ap-northeast-1
  PROJECT: sample
  TF_VERSION: 1.3.7
  TG_VERSION: 0.43.0
  TFCMT_VERSION: 4.0.1

permissions:
  id-token: write
  contents: read
  pull-requests: write

jobs:
  plan:
    name: Terraform plan
    runs-on: ubuntu-latest

    steps:
      - name: Set env vars for dev
        if: github.base_ref == 'develop'
        run: |
          echo "ENVIRONMENT=dev" >> $GITHUB_ENV
          echo "AWS_ACCOUNT_ID=123456789012" >> $GITHUB_ENV
          echo "WORK_DIR=app/envs/dev" >> $GITHUB_ENV

      - name: Set env vars for prod
        if: github.base_ref == 'main'
        run: |
          echo "ENVIRONMENT=dev" >> $GITHUB_ENV
          echo "AWS_ACCOUNT_ID=123456789012" >> $GITHUB_ENV
          echo "WORK_DIR=app/envs/prod" >> $GITHUB_ENV

      - name: Checkout
        uses: actions/checkout@v3

      - name: Setup Terraform
        uses: hashicorp/setup-Terraform@v1
        with:
          terraform_version: ${{ env.TF_VERSION }}
          terraform_wrapper: false

      - name: Setup Terragrunt
        run: |
          sudo wget -q -O /bin/terragrunt "https://github.com/gruntwork-io/terragrunt/releases/download/v${TG_VERSION}/terragrunt_linux_amd64"
          sudo chmod +x /bin/terragrunt
          terragrunt --version

      - name: Setup tfcmt
        run: |
          wget "https://github.com/suzuki-shunsuke/tfcmt/releases/download/v${TFCMT_VERSION}/tfcmt_linux_amd64.tar.gz" -O /tmp/tfcmt.tar.gz
          tar xzf /tmp/tfcmt.tar.gz -C /tmp
          mv /tmp/tfcmt /usr/local/bin
          tfcmt --version

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@master
        with:
          role-to-assume: arn:aws:iam::${{ env.AWS_ACCOUNT_ID }}:role/${{ env.PROJECT }}-${{ env.ENVIRONMENT }}-github-actions-terraform-role
          role-session-name: ${{ env.PROJECT }}-${{ env.ENVIRONMENT }}-github-actions-terraform-session
          aws-region: ${{ env.AWS_REGION }}

      - name: Terragrunt init
        working-directory: ${{ env.WORK_DIR }}
        run: terragrunt run-all init

      - name: Check terraform fmt
        working-directory: ${{ env.WORK_DIR }}
        run: terraform fmt -check -recursive

      - name: Terragrunt validate
        working-directory: ${{ env.WORK_DIR }}
        run: terragrunt run-all validate

      - name: Terragrunt plan
        working-directory: ${{ env.WORK_DIR }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          terragrunt run-all plan --terragrunt-tfpath $GITHUB_WORKSPACE/.github/scripts/tfwrapper.sh

```

# 参考

https://qiita.com/hiko1129/items/4f4699a170f69c4582ba?utm_source=pocket_reader