CI Pipeline #
Menjalankan terraform plan secara lokal sebelum push adalah kebiasaan yang baik, tapi tidak cukup. Kamu mungkin lupa, rekan tim yang lain mungkin tidak tahu kamu sudah plan, dan tidak ada yang bisa memverifikasi bahwa plan yang kamu jalankan sama dengan yang akan di-apply. CI pipeline menyelesaikan semua ini — setiap perubahan pada konfigurasi Terraform secara otomatis divalidasi, di-plan, dan hasilnya bisa dilihat oleh semua orang sebelum keputusan apply dibuat.
Apa yang Harus Dilakukan CI Pipeline Terraform #
CI pipeline untuk Terraform bukan hanya “jalankan terraform plan dan lihat apakah error”. Ada tahapan yang lebih lengkap yang membuat pipeline benar-benar berguna.
TAHAPAN CI PIPELINE TERRAFORM YANG BAIK:
1. CHECKOUT
Clone repository, pastikan semua file tersedia
2. SETUP
Install Terraform versi yang benar (dari .terraform-version atau versi pinned)
3. VALIDATE (cepat, tanpa akses cloud)
terraform fmt -check ← Cek format konsisten
terraform validate ← Cek sintaks dan referensi
4. LINT (opsional tapi direkomendasikan)
tflint ← Detect best practice violations
checkov atau trivy ← Security scan
5. PLAN (butuh akses cloud)
terraform init
terraform plan -out=tfplan
Simpan tfplan sebagai artifact
6. TAMPILKAN PLAN (untuk review)
terraform show -no-color tfplan > plan.txt
Post plan ke PR sebagai comment
SETELAH REVIEW & APPROVAL:
7. APPLY (di pipeline terpisah, dipicu manual atau auto-merge)
terraform apply tfplan
GitHub Actions: Pipeline Lengkap #
# .github/workflows/terraform.yml
name: Terraform CI
on:
pull_request:
branches: [main]
paths:
- 'infrastructure/**' # Hanya trigger jika ada perubahan di direktori ini
push:
branches: [main]
paths:
- 'infrastructure/**'
env:
TF_VERSION: "1.7.0"
WORKING_DIR: "infrastructure/environments/production"
jobs:
validate:
name: Validate
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TF_VERSION }}
- name: Terraform Format Check
run: terraform fmt -check -recursive
working-directory: ${{ env.WORKING_DIR }}
- name: Terraform Init (untuk validate)
run: terraform init -backend=false
working-directory: ${{ env.WORKING_DIR }}
- name: Terraform Validate
run: terraform validate
working-directory: ${{ env.WORKING_DIR }}
plan:
name: Plan
runs-on: ubuntu-latest
needs: validate # Jalankan setelah validate berhasil
if: github.event_name == 'pull_request'
permissions:
contents: read
pull-requests: write # Izin untuk post comment ke PR
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TF_VERSION }}
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/TerraformCIRole
aws-region: ap-southeast-1
- name: Terraform Init
run: terraform init
working-directory: ${{ env.WORKING_DIR }}
- name: Terraform Plan
id: plan
run: |
terraform plan \
-var-file="terraform.tfvars" \
-out=tfplan \
-no-color \
2>&1 | tee plan_output.txt
working-directory: ${{ env.WORKING_DIR }}
continue-on-error: true # Lanjutkan meski plan error, agar bisa post comment
- name: Upload Plan Artifact
uses: actions/upload-artifact@v4
with:
name: tfplan-${{ github.sha }}
path: |
${{ env.WORKING_DIR }}/tfplan
${{ env.WORKING_DIR }}/plan_output.txt
retention-days: 5 # Simpan 5 hari — cukup untuk review dan apply
- name: Post Plan to PR
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const plan = fs.readFileSync('${{ env.WORKING_DIR }}/plan_output.txt', 'utf8');
const maxLength = 65000; // GitHub PR comment limit
const truncated = plan.length > maxLength
? plan.substring(0, maxLength) + '\n\n... (output truncated)'
: plan;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Terraform Plan\n\`\`\`\n${truncated}\n\`\`\``
});
- name: Check Plan Status
if: steps.plan.outcome == 'failure'
run: exit 1 # Fail pipeline jika plan gagal
GitLab CI: Pipeline Lengkap #
# .gitlab-ci.yml
variables:
TF_VERSION: "1.7.0"
WORKING_DIR: "infrastructure/environments/production"
stages:
- validate
- plan
- apply
.terraform_base:
image: hashicorp/terraform:$TF_VERSION
before_script:
- cd $WORKING_DIR
- terraform init
validate:
stage: validate
image: hashicorp/terraform:$TF_VERSION
before_script:
- cd $WORKING_DIR
- terraform init -backend=false
script:
- terraform fmt -check -recursive
- terraform validate
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
plan:
extends: .terraform_base
stage: plan
script:
- terraform plan -out=tfplan -no-color | tee plan_output.txt
- terraform show -no-color tfplan > plan_readable.txt
artifacts:
paths:
- $WORKING_DIR/tfplan
- $WORKING_DIR/plan_output.txt
- $WORKING_DIR/plan_readable.txt
expire_in: 5 days
reports:
# Tampilkan plan di MR sidebar GitLab (jika didukung)
terraform: $WORKING_DIR/plan_readable.txt
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
apply:
extends: .terraform_base
stage: apply
script:
- terraform apply tfplan
dependencies:
- plan # Ambil artifact tfplan dari job plan
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: manual # Butuh klik manual di GitLab UI
environment:
name: production
Mengelola Credentials di CI #
CI pipeline butuh credentials untuk bisa menjalankan terraform plan dan apply. Cara yang benar adalah menggunakan OIDC (OpenID Connect) — pipeline mendapatkan temporary credentials tanpa perlu menyimpan secret permanen.
# GitHub Actions + AWS OIDC (cara yang benar)
# Tidak perlu AWS_ACCESS_KEY_ID atau AWS_SECRET_ACCESS_KEY di secrets
- name: Configure AWS Credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/GitHubActionsRole
aws-region: ap-southeast-1
# GitHub Actions secara otomatis menyediakan OIDC token
# AWS mengverifikasi token ini dan memberikan temporary credentials
# IAM Role Trust Policy yang mengizinkan GitHub Actions:
# {
# "Effect": "Allow",
# "Principal": {
# "Federated": "arn:aws:iam::123456789:oidc-provider/token.actions.githubusercontent.com"
# },
# "Action": "sts:AssumeRoleWithWebIdentity",
# "Condition": {
# "StringEquals": {
# "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
# "token.actions.githubusercontent.com:sub":
# "repo:myorg/myrepo:ref:refs/heads/main"
# }
# }
# }
Ringkasan #
- Pipeline CI Terraform terdiri dari beberapa tahapan: format check, validate, plan, tampilkan plan ke PR, lalu apply terpisah setelah approval.
- Simpan plan artifact (
-out=tfplan) agar apply menggunakan persis plan yang sudah di-review — bukan plan baru yang mungkin berbeda.- Post plan ke PR sebagai comment — semua orang di tim bisa melihat apa yang akan berubah sebelum merge.
- Gunakan OIDC untuk credentials di CI/CD — tidak perlu menyimpan AWS access key sebagai secret permanen, lebih aman dan mudah dirotasi.
- Pisahkan validate dan plan — validate tanpa akses cloud (lebih cepat), plan dengan akses cloud (tapi lebih lambat).
- Apply dipicu secara terpisah setelah approval, bukan otomatis setelah plan — ini memastikan ada human review sebelum infrastruktur berubah.
← Sebelumnya: State Sharing Berikutnya: Plan Approval Strategy →