Skip to content

chore: Enhance Terraform CI/CD workflow with artifact management #53

chore: Enhance Terraform CI/CD workflow with artifact management

chore: Enhance Terraform CI/CD workflow with artifact management #53

Workflow file for this run

# TerraformのGitOpsパイプライン
# - PR作成時: フォーマットチェック・検証・プラン(PRにコメント)
# - mainマージ時: プラン → dev自動apply → prod手動承認apply
name: Terraform CI
# トリガー条件
on:
# main/developブランチへのpush時、またはこれらのブランチへのPR作成時に実行
push:
branches:
- main
- develop
paths:
# Terraform関連ファイルの変更時のみ実行
- "terraform/environments/**"
- "terraform/modules/**"
- ".github/workflows/**"
pull_request:
branches:
- main
- develop
paths:
- "terraform/environments/**"
- "terraform/modules/**"
# 環境変数(全ジョブで共有)
env:
AWS_REGION: ap-northeast-1 # 東京リージョン
TF_VERSION: 1.5.0 # 使用するTerraformバージョン
jobs:
# ========================================
# Job 1: Terraformコードのフォーマットチェック
# ========================================
# 目的: コードスタイルの統一性を確保
# 実行内容: `terraform fmt -check -recursive` でフォーマット違反を検出
terraform-fmt:
name: Terraform Format Check
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: ${{ env.TF_VERSION }}
# 再帰的にすべてのTerraformファイルのフォーマットをチェック
# フォーマット違反があればエラーで終了(修正はしない)
- name: Terraform Format Check
run: terraform fmt -check -recursive
# ========================================
# Job 2: Terraformコードの文法・設定検証
# ========================================
# 目的: Terraformコードの構文エラーや設定ミスを早期発見
# 実行内容: dev/prod両環境で terraform validate を実行
# 注意: バックエンドなしで初期化(-backend=false)し、ダミー変数で検証
terraform-validate:
name: Terraform Validate
runs-on: ubuntu-latest
strategy:
# dev と prod の両方の環境で並行実行
matrix:
environment: [dev, prod]
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: ${{ env.TF_VERSION }}
# バックエンド(S3ステート)なしで初期化
# 検証だけならステート不要なので高速化のため無効化
- name: Terraform Init
working-directory: terraform/environments/${{ matrix.environment }}
run: terraform init -backend=false
# 環境ごとのドメイン名を設定(検証用のダミー値)
- name: Set environment variables for validation
id: set-validate-vars
run: |
if [ "${{ matrix.environment }}" == "dev" ]; then
echo "domain_name=dev.note-app.kanare.dev" >> $GITHUB_OUTPUT
echo "api_domain_name=api-dev.note-app.kanare.dev" >> $GITHUB_OUTPUT
else
echo "domain_name=note-app.kanare.dev" >> $GITHUB_OUTPUT
echo "api_domain_name=api.note-app.kanare.dev" >> $GITHUB_OUTPUT
fi
# Terraform設定の構文と整合性を検証
# 必須変数にはダミー値を設定してバリデーションを通す
- name: Terraform Validate
working-directory: terraform/environments/${{ matrix.environment }}
env:
# 必須変数(検証用ダミー値)
TF_VAR_env: ${{ matrix.environment }}
TF_VAR_domain_name: ${{ steps.set-validate-vars.outputs.domain_name }}
TF_VAR_api_domain_name: ${{ steps.set-validate-vars.outputs.api_domain_name }}
# Cloudflare設定(オプション変数にダミー値)
TF_VAR_enable_cloudflare_dns: "false"
TF_VAR_cloudflare_api_token: "dummy"
TF_VAR_cloudflare_zone_id: "dummy"
run: terraform validate
# ========================================
# Job 3: Terraformプラン(インフラ変更の計画)
# ========================================
# 目的: 実際のAWSリソースと比較し、どのような変更が発生するかプレビュー
# 実行タイミング: PR作成時、またはmainブランチへのpush時
# 依存関係: フォーマットチェックと検証が成功した場合のみ実行
# 注意: planを実行するだけで、applyは手動で行う必要がある
terraform-plan:
name: Terraform Plan
runs-on: ubuntu-latest
needs: [terraform-fmt, terraform-validate] # 前のジョブが成功した場合のみ実行
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main'
permissions:
contents: read # コードの読み取り権限
pull-requests: write # PRにコメントを書き込む権限
strategy:
matrix:
environment: [dev, prod] # dev/prod両環境で並行実行
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: ${{ env.TF_VERSION }}
# AWS認証情報を設定(S3ステートへのアクセスとプラン実行に必要)
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
# S3バックエンドとプロバイダーを初期化
- name: Terraform Init
working-directory: terraform/environments/${{ matrix.environment }}
run: terraform init
# 環境ごとのドメイン名を設定
- name: Set environment variables
id: set-env-vars
run: |
if [ "${{ matrix.environment }}" == "dev" ]; then
echo "domain_name=dev.note-app.kanare.dev" >> $GITHUB_OUTPUT
echo "api_domain_name=api-dev.note-app.kanare.dev" >> $GITHUB_OUTPUT
else
echo "domain_name=note-app.kanare.dev" >> $GITHUB_OUTPUT
echo "api_domain_name=api.note-app.kanare.dev" >> $GITHUB_OUTPUT
fi
# インフラ変更計画を作成(実行はしない)
# plan_output.txtに結果を保存してPRコメントで共有
- name: Terraform Plan
id: plan
working-directory: terraform/environments/${{ matrix.environment }}
continue-on-error: true # planが失敗してもPRコメントを表示できるように継続
env:
# 必須変数(実際の値を使用)
TF_VAR_env: ${{ matrix.environment }}
TF_VAR_domain_name: ${{ steps.set-env-vars.outputs.domain_name }}
TF_VAR_api_domain_name: ${{ steps.set-env-vars.outputs.api_domain_name }}
# Cloudflare設定(オプション:DNS自動管理を有効にする場合のみ設定)
TF_VAR_enable_cloudflare_dns: ${{ secrets.ENABLE_CLOUDFLARE_DNS || 'false' }}
TF_VAR_cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN || '' }}
TF_VAR_cloudflare_zone_id: ${{ secrets.CLOUDFLARE_ZONE_ID || '' }}
run: |
terraform plan -no-color -out=tfplan 2>&1 | tee plan_output.txt
echo "exitcode=${PIPESTATUS[0]}" >> $GITHUB_OUTPUT
# plan ジョブで作成した tfplan を apply ジョブに引き渡すためアーティファクトとして保存
# main push 時のみアップロード(PR では apply は実行しない)
- name: Upload plan artifact
uses: actions/upload-artifact@v3
if: steps.plan.outcome == 'success' && github.ref == 'refs/heads/main' && github.event_name == 'push'
with:
name: tfplan-${{ matrix.environment }}
path: terraform/environments/${{ matrix.environment }}/tfplan
retention-days: 1
# PR作成時にプラン結果をコメントとして追加
# レビュアーがインフラ変更内容を確認できるようにする
- name: Comment PR
uses: actions/github-script@v6
if: github.event_name == 'pull_request'
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
const planOutput = fs.readFileSync('terraform/environments/${{ matrix.environment }}/plan_output.txt', 'utf8');
// GitHub PRコメントの文字数制限対策(65KB以下に制限)
const truncatedPlan = planOutput.length > 65000 ? planOutput.substring(0, 65000) + '\n\n... (truncated)' : planOutput;
const output = `#### Terraform Plan - \`${{ matrix.environment }}\` environment
<details><summary>Show Plan</summary>
\`\`\`terraform
${truncatedPlan}
\`\`\`
</details>
*Environment: \`${{ matrix.environment }}\`, Pusher: @${{ github.actor }}, Workflow: \`${{ github.workflow }}\`*`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})
# ========================================
# Job 4: Terraform Apply(dev環境)
# ========================================
# 目的: mainマージ時にdev環境へ自動適用
# 実行タイミング: mainへのpush時のみ(PRでは実行しない)
# 依存関係: fmt・validate・plan(dev+prod両方)が成功した場合のみ実行
terraform-apply-dev:
name: Terraform Apply (dev)
runs-on: ubuntu-latest
needs: [terraform-plan]
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: ${{ env.TF_VERSION }}
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Terraform Init
working-directory: terraform/environments/dev
run: terraform init
# plan ジョブで保存した tfplan をダウンロード(PR で表示した内容と同一の変更を適用)
- name: Download plan artifact
uses: actions/download-artifact@v3
with:
name: tfplan-dev
path: terraform/environments/dev
- name: Terraform Apply
working-directory: terraform/environments/dev
run: terraform apply tfplan
# ========================================
# Job 5: Terraform Apply(prod環境)
# ========================================
# 目的: dev適用成功後、手動承認を経てprod環境へ適用
# 実行タイミング: mainへのpush時、apply-dev成功後
# 承認: GitHub Environment "production" の Required reviewers による手動承認が必要
# 事前準備: GitHub Settings → Environments → "production" → Required reviewers を設定
terraform-apply-prod:
name: Terraform Apply (prod)
runs-on: ubuntu-latest
needs: [terraform-apply-dev]
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
environment: production
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: ${{ env.TF_VERSION }}
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Terraform Init
working-directory: terraform/environments/prod
run: terraform init
# plan ジョブで保存した tfplan をダウンロード(PR で表示した内容と同一の変更を適用)
- name: Download plan artifact
uses: actions/download-artifact@v3
with:
name: tfplan-prod
path: terraform/environments/prod
- name: Terraform Apply
working-directory: terraform/environments/prod
run: terraform apply tfplan