🎨

Terraform PlanをPRコメントに色付きで表示する

2025/01/23に公開

はじめに

Terraformを使用したインフラ管理では、コードレビューの際にterraform planコマンドでリソースの変更内容を確認することが非常に重要です。GitHub Actionsを用いて自動でterraform planを実行し、その結果をPull Requestのコメントとして投稿する機能を構築することで開発チームの効率を向上させることができます。

本記事では、GitHub Actionsを利用してPRにterraform planの結果を色付きのdiff形式で表示する方法を解説します。この仕組みにより、変更内容を簡単かつ直感的に確認できるようになり、レビュー時の効率が格段に向上します。

完成イメージ

以下のようなコメントがPRに自動で投稿されます。変更点が色付きのdiff形式で表示されるため、確認が容易になるのがポイントです。

サンプル画像

では、この仕組みを構築するための手順を説明していきます。

ワークフローの全体コード

以下は最終的に構築するGitHub Actionsワークフロー全体のコードです。内容を確認しやすいように、後ほど部分ごとに詳しく解説します。また、内容のほとんどはsetup-terraformのREADMEのサンプルをそのまま持ってきております。

name: "Terraform Plan"

on:
  pull_request:
    paths:
      - "**/*.tf" # 対象ファイルをTerraformファイルに制限
jobs:
  terraform-plan:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      # Terraformのセットアップ
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3

      # Terraform Initの実行
      - name: Terraform Init
        run: terraform init

      # Terraform Validateの実行
      - name: Terraform Validate
        id: validate
        run: terraform validate -no-color

      # Terraform Planの実行
      - name: Run terraform plan
        id: plan
        run: terraform plan -no-color

      # Planの結果を整形してハイライト可能なdiff形式に変換
      - name: Reformat Plan
        run: |
          echo '${{ steps.plan.outputs.stdout || steps.plan.outputs.stderr }}' \
          | sed -E 's/^([[:space:]]+)([-+])/\2\1/g' > plan.txt

      # Planの内容を環境変数に入れる
      - name: Put Plan in Env Var
        run: |
          PLAN=$(cat plan.txt)
          echo "PLAN<<EOF" >> $GITHUB_ENV
          echo "$PLAN" >> $GITHUB_ENV
          echo "EOF" >> $GITHUB_ENV
          
      # PRコメントにPlanの結果を投稿
      - name: Read Plan and Post Comment
        uses: actions/github-script@v7
        if: github.event_name == 'pull_request'
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            // 1. Retrieve existing bot comments for the PR
            const { data: comments } = await github.rest.issues.listComments({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
            })
            const botComment = comments.find(comment => {
              return comment.user.type === 'Bot' && comment.body.includes('Terraform Format and Style')
            })

            // 2. Prepare format of the comment
            const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
            #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
            #### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
            <details><summary>Validation Output</summary>

            \`\`\`terraform
            ${{ steps.validate.outputs.stdout }}
            \`\`\`

            </details>

            #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`

            <details><summary>Show Plan</summary>

            \`\`\`diff
            ${{ env.PLAN }}
            \`\`\`

            </details>

            *Pusher: @${{ github.actor }}, Working Directory: \`${{ matrix.directory }}\``;

            // 3. If we have a comment, update it, otherwise create a new one
            if (botComment) {
              await github.rest.issues.updateComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                comment_id: botComment.id,
                body: output
              })
            } else {
              await github.rest.issues.createComment({
                issue_number: context.issue.number,
                owner: context.repo.owner,
                repo: context.repo.repo,
                body: output
              })
            }

解説: ワークフローの各ステップ

  1. terraform planの実行:
    Terraformの設定ファイルを基にPlanを生成します。この際、no-colorオプションを使って余計な装飾を排除し、Planデータをシンプルなテキスト形式で得ます。

    - name: Run terraform plan
      id: plan
      run: terraform plan -no-color
    
  2. Planの結果を整形:

    sedコマンドを使って、リソースの変更に該当する最初の+(追加)および-(削除)が行頭に出るように整形します。これにより、GitHubのdiffコードブロック内で適切に強調されます。

    - name: Reformat Plan
      run: |
        echo '${{ steps.plan.outputs.stdout || steps.plan.outputs.stderr }}' \
        | sed -E 's/^([[:space:]]+)([-+])/\2\1/g' > plan.txt
    

    さらに整形された結果を環境変数に保存し、後続のステップで利用できるようにします。

    - name: Put Plan in Env Var
      run: |
        PLAN=$(cat plan.txt)
        echo "PLAN<<EOF" >> $GITHUB_ENV
        echo "$PLAN" >> $GITHUB_ENV
        echo "EOF" >> $GITHUB_ENV
    
  3. PRにコメントを投稿:
    Terraform Planの結果をPRにコメントとして投稿します。GitHub Actionのgithub-scriptを利用して、既存のBotコメントがある場合は更新し、なければ新規作成するロジックを追加しています。

    # PRコメントにPlanの結果を投稿
    - name: Read Plan and Post Comment
      uses: actions/github-script@v7
      if: github.event_name == 'pull_request'
      with:
        github-token: ${{ secrets.GITHUB_TOKEN }}
        script: |
          // 1. Retrieve existing bot comments for the PR
          const { data: comments } = await github.rest.issues.listComments({
            owner: context.repo.owner,
            repo: context.repo.repo,
            issue_number: context.issue.number,
          })
          const botComment = comments.find(comment => {
            return comment.user.type === 'Bot' && comment.body.includes('Terraform Format and Style')
          })
    
          // 2. Prepare format of the comment
          const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
          #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
          #### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
          <details><summary>Validation Output</summary>
    
          \`\`\`terraform
          ${{ steps.validate.outputs.stdout }}
          \`\`\`
    
          </details>
    
          #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
    
          <details><summary>Show Plan</summary>
    
          \`\`\`diff
          ${{ env.PLAN }}
          \`\`\`
    
          </details>
    
          *Pusher: @${{ github.actor }}, Working Directory: \`${{ matrix.directory }}\``;
    
          // 3. If we have a comment, update it, otherwise create a new one
          if (botComment) {
            await github.rest.issues.updateComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              comment_id: botComment.id,
              body: output
            })
          } else {
            await github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: output
            })
          }
    

まとめ

インフラ変更はプロダクション環境にも大きな影響を与える可能性があるため、適切な確認とレビューのプロセスが重要です。GitHub Actionsを利用してterraform planの結果をPRに自動で投稿し、色付きのdiff形式で可視化することで、レビューが効率的になり、抜け漏れや確認ミスを防ぐことができます。インフラ管理をより安全で効率的に行うための一助となれば幸いです。

Discussion