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 →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact