Jaga kualitas dan keamanan kode IaC dengan pipeline quality gate: terraform fmt & validate, linter tflint, security scanner Checkov dan Trivy, hingga automated testing infrastruktur sungguhan menggunakan Terratest berbasis Go.

Setelah di episode 10 sebelumnya kita membahas Workspaces vs Directory-Based Multi-Environment — bagaimana mengatur dev, staging, dan prod secara terisolasi — pada episode kali ini kita membahas hal yang menentukan apakah kode kalian layak masuk ke prod atau tidak: quality gate.
Bayangkan ada tiga developer yang menulis kode Terraform dengan gaya masing-masing: satu tidak pernah merapikan indentasi, satu lupa menutup braces sehingga apply gagal di tengah malam, satu lagi tanpa sengaja membuat S3 bucket publik yang datanya bisa dibaca semua orang. Masing-masing menganggap kodenya "jalan" karena terraform apply berhasil di laptopnya. Tapi di skala tim, kode seperti ini adalah bom waktu — dan tidak ada satu pun yang bisa dideteksi oleh terraform plan biasa.
Kabar baiknya, ekosistem Terraform dewasa ini punya rangkaian tools yang saling melengkapi untuk menangkap masalah ini sebelum menyentuh cloud:
terraform fmt dan terraform validate untuk basis sanitasi kode.tflint untuk aturan spesifik provider, plus Checkov dan Trivy untuk keamanan & compliance.Di episode ini kita akan menjalankan semuanya satu per satu, memahami outputnya, lalu menyusunnya menjadi alur gate yang bisa dipasang di pipeline CI/CD (yang akan kita bangun utuh di episode 13). Inilah perbedaan antara tim yang "berharap kode benar" dan tim yang "membuktikan kode benar".
terraform fmt — Merapikan Kode Secara OtomatisTidak peduli seberapa baik engineer-nya, manusia tidak konsisten dalam spasi dan indentasi. terraform fmt menulis ulang format kode kalian ke satu standar baku HashiCorp: indentasi 2 spasi, satu baris kosong antara blocks, alokasi = yang sejajar, dan seterusnya.
Perhatikan contoh main.tf yang berantakan ini — masih valid secara sintaks, tapi sulit dibaca:
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
tags = {
Name="web-server"
}
}Jalankan terraform fmt untuk merapikannya:
terraform fmt main.tf
terraform fmt -recursiveterraform fmt -check
terraform fmt -recursive -check -diffHasil setelah diformat — bandingkan dengan versi sebelumnya:
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
tags = {
Name = "web-server"
}
}Dengan terraform fmt -check, perintah ini berperilaku seperti linter: mengembalikan exit code non-zero jika ada file yang belum rapi — sempurna untuk dipakai sebagai gate di CI. Untuk melihat persisnya apa yang diubah, gunakan -diff:
--- a/main.tf
+++ b/main.tf
@@ -1,8 +1,8 @@
resource "aws_instance" "web" {
- ami = data.aws_ami.ubuntu.id
+ ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"Untuk menandai perubahan secara inline di editor/PR, format diff juga bisa direpresentasikan dengan marker [!code --] (baris dihapus) dan [!code ++] (baris ditambah):
resource "aws_instance" "web" {
resource "aws_instance" "nginx" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
}Note
Perhatikan bahwa terraform fmt hanya menata format — ia tidak akan memperbaiki kesalahan logika atau atribut yang salah. fmt adalah perapian layout, bukan penguji kebenaran. Anggap ia seperti prettier/gofmt di dunia JavaScript/Go.
terraform validate — Validasi Sintaks & Konsistensi Internalfmt merapikan, validate memeriksa kesehatan. terraform validate memverifikasi bahwa konfigurasi secara sintaks valid, semua referensi terdefinisi (variabel ada, atribut resource yang dirujuk memang ada), dan argument yang dipakai valid menurut skema provider.
terraform validateSuccess! The configuration is valid.Jika ada kesalahan, misalnya merujuk variabel yang tidak dideklarasikan atau menulis atribut yang tidak dikenal provider, validate akan menunjuk lokasinya dengan presisi:
Error: Reference to undeclared input variable
on main.tf line 7, in resource "aws_instance" "web":
7: instance_type = var.instance_type_x
An input variable with the name "instance_type_x" has not been declared.Important
terraform validate tidak membaca nilai .tfvars (karena ia tidak butuh nilai konkret, hanya tipe). Namun ia membutuhkan provider terinstal — jadi jalankan terraform init terlebih dahulu. Ingat urutan emasnya: fmt → validate → plan. fmt memastikan rapi, validate memastikan valid, plan memastikan aman.
terraform validate hanya memeriksa struktur umum. Ia tidak tahu bahwa t3.microx bukan instance type yang valid di AWS, atau bahwa region ap-southeast-1x tidak ada. Di sinilah tflint masuk — linter yang memahami aturan spesifik provider, menangkap error, anti-pattern, dan unused declaration yang luput dari validasi standar.
Instalasi (dua cara paling umum):
brew install tflintSebelum tflint bisa memeriksa aturan provider, kita perlu mengunduh rule plugin-nya dengan tflint --init, lalu jalankan:
tflint --init
tflint --recursiveContoh output — perhatikan bahwa tflint menemukan kesalahan yang tidak akan ditemukan terraform validate:
4 issue(s) found:
Error: [Fixable] "t3.microx" is an invalid instance type (aws_instance_invalid_type)
on main.tf line 5:
5: instance_type = "t3.microx"
Warning: [Fixable] Missing default value for variable (terraform_default_required_provider_version)
on variables.tf line 1:
1: variable "region" {}
Warning: Duplicate tag key "Name" (aws_resource_missing_tags)
on main.tf line 10:
10: tags = {
11: Name = "web"
12: name = "WEB"
13: }Banyak issue tflint bahkan bisa diperbaiki otomatis dengan tflint --fix. Yang penting, tflint juga jalan di mode recursive sehingga bisa memeriksa seluruh modul dalam project — persis yang kita butuhkan setelah mempelajari modularitas di episode 9.
Tip
tflint mengunduh rule provider dari .tflint.hcl. Di project enterprise, file ini biasanya dikomit ke repo dengan daftar rule tambahan dari komunitas seperti terraform-unused-declarations dan terraform-required-version, sehingga setiap anggota tim dan CI menjalankan standar yang sama persis.
fmt, validate, dan tflint memastikan kode benar secara teknis. Tapi "benar secara teknis" tidak berarti "aman". Scanner keamanan menangkap miskonfigurasi seperti S3 bucket publik, encryption yang mati, atau IAM policy yang terlalu permisif — hal-hal yang secara sintaks valid tapi merupakan pelanggaran standar keamanan (CIS Benchmarks, SOC2, PCI-DSS).
Checkov adalah scanner open-source dari Bridgecrew (kini bagian dari Prisma Cloud) yang memeriksa Terraform, CloudFormation, Kubernetes, dan banyak lagi dengan ratusan policy bawaan.
pip install checkovcheckov -d .Contoh output — perhatikan kode policy CKV_AWS_* yang unik untuk setiap check:
_ _
___| |__ ___ ___ |_ _ __
/ __| '_ \ / _ \/ _ \ | | '_ \
| (__| | | | __/ __/ |_| | | | |
\___|_| |_|\___|\___| (_|_| |_|_|
By bridgecrew.io | version: 3.2.5
terraform scan results:
Passed checks: 14, Failed checks: 2, Skipped checks: 0
Check: CKV_AWS_126: "Ensure that S3 bucket has versioning enabled"
FAILED for resource: aws_s3_bucket.data
File: /s3.tf:1-8
Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-policy-index/ckv-aws-126
Check: CKV_AWS_115: "Ensure that no IAM policy with EBS privileges is attached"
FAILED for resource: aws_iam_policy.policy
File: /iam.tf:9-20
Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-policy-index/ckv-aws-115Checkov menampilkan file dan baris persis dari pelanggaran, plus link guide untuk perbaikannya. Ia juga bisa diarahkan untuk hanya memeriksa severity tertentu atau melewati check yang sudah disetujui dengan --skip-check.
Trivy adalah scanner universal dari Aqua Security. Untuk IaC, perintah trivy config menganalisis misconfigurasi (dengan engine yang sama seperti Checkov) sekaligus mendeteksi hardcoded secrets di file konfigurasi — kombinasi yang sangat praktis karena sekaligus menangkap masalah state-file rahasia yang akan kita bahas di episode 15.
sudo apt-get install wget apt-transport-https gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee -a /etc/apt/sources.list.d/trivy.list
sudo apt-get update && sudo apt-get install trivytrivy config .Contoh output — Trivy mengelompokkan hasil per file dengan ID AVD-AWS-* dan severity:
main.tf (terraform)
===================
Tests: 8 (SUCCESS: 6, FAILED: 2, WARNING: 0)
Failures: 2
AVD-AWS-0086 (HIGH): S3 bucket does not have server-side encryption enabled
════════════════════════════════════════════════════════════════════════
S3 buckets should be encrypted with encryption keys. By default, S3...
See https://avd.aquasec.com/misconfig/avd-aws-0086
────────────────────────────────────────────────────────────────────────
main.tf:3-10
────────────────────────────────────────────────────────────────────────Warning
Scanner keamanan tidak mengenal konteks. Sebuah S3 bucket yang sengaja dibuat publik untuk static website akan selalu dilaporkan sebagai pelanggaran. Karena itu di pipeline CI, jadikan hasil scan sebagai gate yang bisa di-approve secara eksplisit — misalnya dengan whitelist check + alasan, bukan asal melewati semua temuan. Konteks harus dicatat, bukan disembunyikan.
| Tool | Kategori | Apa yang Dicek | Ekstensi |
|---|---|---|---|
terraform fmt | Formatting | Kerapian & konsistensi layout HCL | HCL |
terraform validate | Validasi | Sintaks, referensi, dan skema provider | HCL |
tflint | Linting | Aturan spesifik provider & best practice (mis. region/instance type valid) | HCL |
| Checkov | Security scan | Misconfigurasi keamanan & compliance (CIS, SOC2, dsb.) | HCL & IaC lain |
| Trivy | Security scan | Misconfigurasi + hardcoded secrets (multi format) | HCL & IaC lain |
| Terratest | Automated testing | Apply + assert infrastruktur nyata | Go |
Semua tool di atas bersifat statis — mereka membaca kode, tidak menjalankannya. Tidak ada yang bisa menjamin bahwa VPC yang kalian konfigurasi benar-benar punya dua subnet publik yang bisa diakses internet. Untuk itu ada Terratest: library Go buatan Gruntwork yang menjalankan terraform apply sungguhan, memeriksa hasilnya, lalu menghancurkan semuanya.
Ini membuat Terratest menjadi tulang punggung integration test infrastruktur: kode yang sama dipakai untuk memvalidasi modul kita di examples/ sekaligus menjaga agar perubahan modul tidak merusak perilakunya.
Test ditulis sebagai fungsi Go biasa, memakai helper terraform dari Terratest. Perhatikan struktur wajibnya: definisikan options, pastikan destroy selalu dijalankan lewat defer, lalu InitAndApply, assert output, dan selesai:
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
func TestVPCModule(t *testing.T) {
t.Parallel()
options := &terraform.Options{
TerraformDir: "../examples/vpc",
Vars: map[string]interface{}{
"name": "terratest-vpc",
"cidr": "10.0.0.0/16",
},
}
// Jaminan utama: apa pun yang terjadi (termasuk assertion gagal),
// infrastruktur tetap dihancurkan setelah test selesai.
defer terraform.Destroy(t, options)
terraform.InitAndApply(t, options)
vpcID := terraform.Output(t, options, "vpc_id")
assert.NotEmpty(t, vpcID)
assert.Contains(t, vpcID, "vpc-")
subnetIDs := terraform.OutputList(t, options, "public_subnet_ids")
assert.Len(t, subnetIDs, 2)
}Poin-poin kunci yang wajib dipahami:
defer terraform.Destroy(t, options) — dijalankan paling akhir, selalu, bahkan saat test gagal. Tanpa ini, test yang gagal akan meninggalkan infrastruktur berjalan dan menagih biaya terus-menerus.terraform.InitAndApply — satu helper yang menjalankan init lalu apply -auto-approve, dan otomatis gagal (fail) jika ada error.terraform.Output / OutputList — membaca nilai output modul kita (dari episode 9) dan membandingkannya dengan ekspektasi via testify/assert.t.Parallel() — test antar modul yang berbeda bisa dijalankan paralel untuk mempercepat, karena setiap test punya state-nya sendiri.Terratest adalah project Go biasa, jadi butuh inisialisasi module dan install dependency:
go mod init github.com/company/terraform-modules
go get github.com/gruntwork-io/terratest/modules/terraform@latest
go test ./test/ -v -timeout 30m=== RUN TestVPCModule
TestVPCModule 2026-08-02T10:00:00+07:00 retry.go:99: Running terraform [init ...]
TestVPCModule 2026-08-02T10:00:01+07:00 retry.go:99: Running terraform [apply -auto-approve ...]
TestVPCModule 2026-08-02T10:00:45+07:00 retry.go:99: Running terraform [destroy -auto-approve ...]
--- PASS: TestVPCModule (47.34s)
PASS
ok github.com/company/terraform-modules 47.344sCaution
Terratest benar-benar membuat infrastruktur di cloud — berpotensi menimbulkan biaya dan menggunakan kredensial akun sungguhan. Jangan pernah menjalankan test suite Terratest terhadap environment prod, dan pastikan setiap test dijalankan dengan defer terraform.Destroy serta timeout yang cukup (-timeout 30m). Idealnya Terratest dijalankan di CI dengan akun terisolasi, memakai modul examples/ yang memang didesain untuk dihancurkan.
Setelah punya rangkaian tool, cara paling efektif memastikan kualitas di setiap developer adalah memasangnya sebagai pre-commit hooks — script yang otomatis dijalankan sebelum git commit. Dengan begitu, masalah tertangkap sebelum kode meninggalkan laptop. Paket paling populer untuk ini adalah pre-commit-terraform:
repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.92.0
hooks:
- id: terraform_fmt
- id: terraform_validate
- id: terraform_tflint
- id: checkov
- id: terraform_trivypip install pre-commit
pre-commit installDengan konfigurasi di atas, setiap git commit otomatis menjalankan fmt, validate, tflint, checkov, dan trivy — dan memblokir commit jika ada yang gagal. Urutan gate yang ideal untuk project produksi nantinya adalah: pre-commit di laptop → pipeline CI saat PR (fmt, validate, tflint, scan, plan) → approval → apply. Alur penuh ini akan kita rakit bersama di episode 13.
1. terraform validate sebelum terraform init
Error: Missing required provider atau Module not installed akan muncul karena validate butuh skema provider. Selalu init dulu.
2. Menganggap terraform fmt sudah cukup untuk "kode aman"
fmt hanya merapikan layout. Ia tidak menangkap instance type salah, bucket publik, maupun IAM permisif. Kumpulkan fmt + validate + tflint + scanner secara berurutan, bukan salah satunya.
3. Menambahkan scanner tanpa mengerti outputnya
Menjalankan Checkov/Trivy lalu mengabaikan semua temuan (--skip-check .*) sama saja dengan tidak menjalankan. Pahami tiap temuan, buat whitelist dengan alasan, dan pertahankan jumlah fail = 0 di pipeline.
4. Test Terratest tanpa defer Destroy
Ini penyebab paling umum "biaya cloud misterius" dari tim yang baru belajar testing. Selalu pakai defer terraform.Destroy(t, options) tepat setelah options dibuat, dan set -timeout agar test yang hang tidak berjalan selamanya.
5. Menjalankan Terratest terhadap environment prod
Terratest apply tanpa pandang bulu. Pisahkan akun/workspace test dari produksi, dan jangan pernah mengarahkan TerraformDir ke direktori environment prod.
6. Tidak menjadikan kualitas sebagai gate di CI
Kalau tools hanya dipakai manual di laptop, hasilnya tidak konsisten antar orang. Pasang gate di pipeline (dan pre-commit) sehingga standar dijalankan otomatis — konsistensi itulah yang membuat tim cepat, bukan individunya.
Pada episode 11 ini kalian telah membangun fondasi quality gate untuk kode Terraform: merapikan dan memvalidasi dengan terraform fmt -recursive dan terraform validate; menangkap error spesifik provider dengan tflint; memindai miskonfigurasi keamanan dan compliance dengan Checkov dan Trivy; menulis integration test sungguhan dengan Terratest — apply, assert, dan destroy infrastruktur asli lewat test Go; serta mengotomatiskan semuanya lewat pre-commit hooks.
Poin kunci yang perlu kalian bawa pulang:
fmt → validate → tflint → security scan → plan.defer terraform.Destroy adalah pengaman wajib setiap test Terratest.Kalian sekarang punya kode yang rapi, aman, dan teruji. Tapi ada satu masalah klasik yang tersisa: semua kualitas itu bisa runtuh jika struktur projectnya berantakan — khususnya duplikasi konfigurasi antar environment yang masih kita tulis manual di episode 10. Bagaimana menjaga kode tetap DRY di tengah puluhan environment?
Di episode 12 selanjutnya kita akan membahas Advanced Architecture dengan Terragrunt — thin wrapper buatan Gruntwork untuk menghilangkan duplikasi backend, provider, dan input antar environment, lengkap dengan fitur include, dependency, dan inputs. Pastikan tetap semangat!