2026-01-21
GitHub Actions で terraform plan を自動実行して結果を PR にコメントとして表示する
GitHub Actions を使って PR 作成時に terraform plan を自動実行し、その結果を PR コメントとして表示するワークフローを作成しました。追加コミット時のコメント更新やトグル表示による見やすさの工夫についても紹介します。
概要
Terraform でインフラを管理している場合、コードの変更による PR を作成する際には、コードの差分だけでなく terraform plan の結果もあわせてレビューしたい場面が多くあります。しかし、手元で plan を実行してその結果を PR に貼り付けるのは面倒です。そこで、GitHub Actions を使って plan の自動実行と結果の表示を自動化しました。
前提
今回のワークフローは以下の環境を前提としています。
- クラウドプロバイダー: AWS
- tfstate のバックエンド: Amazon S3
- AWS 認証方式: OIDC (OpenID Connect)
Terraform で AWS のリソースを管理しており、tfstate ファイルは Amazon S3 に保存しています。GitHub Actions から AWS へのアクセスには、アクセスキーを使わず OIDC による認証を採用しています。OIDC を使うことで、長期的なクレデンシャルを GitHub Secrets に保存する必要がなくなり、セキュリティが向上します。
OIDC 認証を利用するためには、事前に AWS 側で IAM Identity Provider と IAM Role を作成しておく必要があります。IAM Role には terraform plan の実行に必要な権限(対象リソースへの読み取り権限、S3 バックエンドへのアクセス権限など)を付与します。
ワークフローの特徴
今回作成したワークフローには以下のような特徴があります。
1. 追加コミット時は既存のコメントを更新
PR 作成後に追加のコミットが発生した場合、新たにコメントを追加するのではなく、既存の plan 結果コメントを更新する形にしています。これにより、PR のコメント欄が plan 結果で溢れることを防ぎ、常に最新の plan 結果だけが表示されるようになります。
2. トグル表示でスペースを節約
plan 結果は HTML の <details> タグを使ってトグル表示にしています。これにより、PR のコメント欄を圧迫せず、必要なときだけ展開して確認できるようになっています。
3. シンタックスハイライトの適用
plan 結果のコードブロックには hcl の言語指定をすることで、GitHub 上でシンタックスハイライトが適用されるようにしています。これにより、リソースの追加・変更・削除が視覚的に分かりやすくなります。
ワークフローの全体像
作成したワークフローの全体は以下の通りです。
name: Terraform Plan (infra)
on:
pull_request:
paths:
- "infra/**"
- ".github/workflows/infra-plan.yml"
workflow_dispatch:
jobs:
plan:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
pull-requests: write
env:
TF_IN_AUTOMATION: "true"
steps:
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
with:
ref: ${{ github.head_ref }}
fetch-depth: 0
- name: Setup Terraform
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3
- name: Write tfvars
working-directory: infra/environments/prd
run: |
cat > terraform.auto.tfvars <<'EOF'
hosted_zone_id = "${{ secrets.TF_VARS_HOSTED_ZONE_ID }}"
github_actions_oidc_provider_arn = "${{ secrets.TF_VARS_GHA_OIDC_PROVIDER_ARN }}"
EOF
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5
with:
role-to-assume: ${{ vars.TF_PLAN_ROLE_ARN }}
aws-region: ap-northeast-1
role-session-name: tf-plan-${{ github.run_id }}
- name: Terraform init
working-directory: infra/environments/prd
run: terraform init -input=false
- name: Terraform plan
id: plan
continue-on-error: true
working-directory: infra/environments/prd
run: |
set -o pipefail
terraform plan -input=false -no-color -var-file=terraform.auto.tfvars -out=tfplan 2>&1 | tee plan.log
echo "exitcode=${PIPESTATUS[0]}" >> "$GITHUB_OUTPUT"
- name: Terraform show
if: always()
working-directory: infra/environments/prd
run: |
if [ -f tfplan ]; then
terraform show -no-color tfplan > plan.txt
fi
- name: Prepare plan comment
if: always()
id: comment
working-directory: infra/environments/prd
run: |
{
echo "## Terraform Plan (prd)"
echo
echo "- Commit: [\`${{ github.event.pull_request.head.sha }}\`](https://github.com/${{ github.repository }}/commit/${{ github.event.pull_request.head.sha }})"
echo
if [ "${{ steps.plan.outputs.exitcode }}" != "0" ]; then
echo "**Plan failed (exit code ${{ steps.plan.outputs.exitcode }})**"
else
echo "**Plan succeeded**"
fi
echo
echo "<details><summary>Plan output</summary>"
echo
echo '```hcl'
if [ -f plan.txt ]; then
cat plan.txt
else
cat plan.log
fi
echo '```'
echo
echo "</details>"
} > plan-comment.md
echo "body_path=infra/environments/prd/plan-comment.md" >> "$GITHUB_OUTPUT"
- name: Find existing plan comment
if: always()
id: find-comment
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: "github-actions[bot]"
body-includes: "Terraform Plan (prd)"
- name: Comment on PR
if: always()
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5
with:
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.pull_request.number }}
comment-id: ${{ steps.find-comment.outputs.comment-id }}
body-path: infra/environments/prd/plan-comment.md
- name: Fail if plan errored
if: steps.plan.outputs.exitcode != '0'
run: exit 1
ワークフローの解説
各ステップについて詳しく解説します。
トリガーの設定
on:
pull_request:
paths:
- "infra/**"
- ".github/workflows/infra-plan.yml"
workflow_dispatch:
infra/ ディレクトリ配下のファイルまたはワークフローファイル自体が変更された場合にのみ実行されるようにしています。これにより、Terraform に関係のない変更で不要な plan が実行されることを防いでいます。また、workflow_dispatch を追加することで、手動での実行も可能にしています。
必要な権限の設定
permissions:
id-token: write
contents: read
pull-requests: write
このワークフローでは以下の 3 つの権限が必要です。
id-token: write: AWS への OIDC 認証に必要contents: read: リポジトリのコードを読み取るために必要pull-requests: write: PR にコメントを書き込むために必要
AWS 認証の設定
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5
with:
role-to-assume: ${{ vars.TF_PLAN_ROLE_ARN }}
aws-region: ap-northeast-1
role-session-name: tf-plan-${{ github.run_id }}
AWS への認証には aws-actions/configure-aws-credentials アクションを使用しています。role-to-assume に plan 実行用の IAM Role の ARN を指定することで、OIDC を使った認証が行われます。
IAM Role の ARN は GitHub リポジトリの Variables(vars.TF_PLAN_ROLE_ARN)に設定しています。Secrets ではなく Variables を使っているのは、Role ARN 自体は機密情報ではないためです。一方、tfvars に含まれる値(今回でいうと TF_VARS_HOSTED_ZONE_ID など)は Secrets に設定しています。
role-session-name には tf-plan-${{ github.run_id }} を指定しており、CloudTrail などで確認する際にどのワークフロー実行からのアクセスかを識別しやすくしています。
Terraform plan の実行
- name: Terraform plan
id: plan
continue-on-error: true
working-directory: infra/environments/prd
run: |
set -o pipefail
terraform plan -input=false -no-color -var-file=terraform.auto.tfvars -out=tfplan 2>&1 | tee plan.log
echo "exitcode=${PIPESTATUS[0]}" >> "$GITHUB_OUTPUT"
continue-on-error: true を設定することで、plan が失敗してもワークフロー全体が中断されないようにしています。これにより、plan が失敗した場合でもコメントを投稿できます。また、set -o pipefail と ${PIPESTATUS[0]} を使って tee コマンドを経由しても正確な終了コードを取得しています。
plan 結果の整形
- name: Terraform show
if: always()
working-directory: infra/environments/prd
run: |
if [ -f tfplan ]; then
terraform show -no-color tfplan > plan.txt
fi
terraform plan の出力には進捗表示などのノイズが含まれるため、terraform show を使って plan ファイルから整形された出力を取得しています。これにより、PR コメントに表示される plan 結果がより読みやすくなります。
コメントの作成
- name: Prepare plan comment
if: always()
id: comment
working-directory: infra/environments/prd
run: |
{
echo "## Terraform Plan (prd)"
echo
echo "- Commit: [\`${{ github.event.pull_request.head.sha }}\`](https://github.com/${{ github.repository }}/commit/${{ github.event.pull_request.head.sha }})"
echo
if [ "${{ steps.plan.outputs.exitcode }}" != "0" ]; then
echo "**Plan failed (exit code ${{ steps.plan.outputs.exitcode }})**"
else
echo "**Plan succeeded**"
fi
echo
echo "<details><summary>Plan output</summary>"
echo
echo '```hcl'
if [ -f plan.txt ]; then
cat plan.txt
else
cat plan.log
fi
echo '```'
echo
echo "</details>"
} > plan-comment.md
コメントには以下の情報を含めています。
- 環境名: 今回は
prd環境の plan 結果であることを明示 - コミットハッシュ: どのコミットに対する plan 結果かをリンク付きで表示
- plan の成否: 成功・失敗を分かりやすく表示
- plan 結果:
<details>タグでトグル表示、hclでシンタックスハイライト
terraform show の出力がある場合はそちらを使い、ない場合(plan が途中で失敗した場合など)は terraform plan の出力を使うようにしています。
既存コメントの検索と更新
- name: Find existing plan comment
if: always()
id: find-comment
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: "github-actions[bot]"
body-includes: "Terraform Plan (prd)"
- name: Comment on PR
if: always()
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5
with:
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.pull_request.number }}
comment-id: ${{ steps.find-comment.outputs.comment-id }}
body-path: infra/environments/prd/plan-comment.md
peter-evans/find-comment アクションを使って、既存の plan 結果コメントを検索しています。検索条件として、コメントの投稿者が github-actions[bot] であること、およびコメント本文に Terraform Plan (prd) が含まれていることを指定しています。
既存のコメントが見つかった場合は comment-id にその ID が設定され、peter-evans/create-or-update-comment アクションがそのコメントを更新します。見つからなかった場合は新規にコメントを作成します。
ワークフローの最終結果
- name: Fail if plan errored
if: steps.plan.outputs.exitcode != '0'
run: exit 1
plan が失敗した場合でもコメントを投稿するために continue-on-error: true を設定していますが、最終的にはワークフロー自体を失敗させたいため、最後に終了コードをチェックして必要に応じて失敗させています。
実際の表示例
ワークフロー実行後、PR には以下のようなコメントが投稿されます。

plan 結果がトグル表示されているため、コメント欄がコンパクトに保たれます。
まとめ
GitHub Actions を使って terraform plan を自動実行し、結果を PR コメントとして表示するワークフローを作成しました。
工夫した点として、追加コミット時には既存のコメントを更新する形にすることで PR のコメント欄を整理し、トグル表示でスペースを節約しつつ、hcl のシンタックスハイライトで見やすさを向上させています。
このようなワークフローを導入することで、Terraform の変更を含む PR のレビューがより効率的になります。コードの差分だけでなく、実際のインフラへの影響を plan 結果として確認できるため、レビュー時の見落としを防ぐことができます。