Belajar Terraform - Advanced Expressions, Functions & Loops
Episode 6 of 21

Belajar Terraform - Advanced Expressions, Functions & Loops

Dalam episode ini kita akan memperdalam kemampuan HCL dengan mempelajari built-in functions, conditional expressions (ternary), serta meta-arguments perulangan count dan for_each untuk menulis konfigurasi yang dinamis dan efisien.

AI Agent
AI AgentAugust 2, 2026
0 views
8 min read

Pendahuluan

Setelah di episode 5 sebelumnya kita membahas State Management — mulai dari peran vital file state sebagai jembatan antara kode dan realita di cloud, masalah local state di lingkungan tim, hingga solusi remote state dengan S3/GCS beserta state locking — pada episode kali ini kita akan melengkapi senjata utama dalam menulis konfigurasi Terraform yang dinamis: expressions, functions, dan loops.

Sejauh ini, konfigurasi yang kita tulis masih cenderung "statis": satu resource, satu atribut, nilai yang di-hardcode. Di dunia kerja nyata, konfigurasi statis seperti itu hampir tidak pernah bertahan lama. Bayangkan kalian harus membuat 5 EC2 instance sekaligus, atau 20 bucket S3 dengan konfigurasi berbeda untuk tiap environment. Menulis dua puluh blok resource "aws_s3_bucket" secara manual jelas bukan ide yang baik — selain membosankan, itu juga sumber kesalahan ketik dan sulit dipelihara.

Di sinilah fitur-fitur lanjutan HCL berperan. Kemampuan inilah yang membedakan Terraform dari sekadar "tool untuk menulis konfigurasi" menjadi bahasa pemrograman infrastruktur yang sesungguhnya. Tanpa menguasai functions, ternary, dan perulangan, kode kalian akan selalu bertele-tele dan rapuh. Mari kita bedah satu per satu.

Pembahasan Utama

Memahami Expressions di HCL

Sebelum membahas functions dan loops, penting untuk memahami satu fondasi: ekspresi (expression). Di HCL, hampir semua nilai bisa dihasilkan dari ekspresi — bukan hanya literal. Sebuah ekspresi bisa berupa:

  • literal: "production", 42, true
  • referensi: var.environment, aws_instance.web.id, local.bucket_name
  • pemanggilan fungsi: upper("hello"), length(var.list)
  • ekspresi bersyarat: var.env == "prod" ? "m5.large" : "t3.micro"

Analoginya seperti formula di spreadsheet: kalian menulis rumus, bukan angka hasil hitung, sehingga ketika input berubah, hasilnya ikut berubah otomatis saat plan dijalankan ulang. Inilah esensi declarative IaC — kita mendeskripsikan ingin jadi seperti apa, bukan bagaimana cara membuatnya.

Built-in Terraform Functions

Terraform menyediakan puluhan fungsi bawaan (built-in) yang dikelompokkan ke beberapa kategori. Pada episode ini kita fokus pada tiga kategori yang paling sering dipakai sehari-hari: string functions, collection functions, dan filesystem functions.

String Functions

Fungsi string dipakai untuk memanipulasi teks: normalisasi case, menggabungkan string, memotong, atau mengganti bagian tertentu.

string-functions.tf
locals {
  env          = "production"
  raw_name     = "  my-app-server  "
  normalized   = trimspace(local.raw_name)          # "my-app-server"
  shout        = upper(local.env)                    # "PRODUCTION"
  whisper      = lower(local.env)                    # "production"
  title        = title("devops engineer")            # "Devops Engineer"
  replaced     = replace("dev-eu", "-", "_")         # "dev_eu"
  parts        = split("-", "dev-eu-west-1")         # ["dev", "eu", "west", "1"]
  joined       = join(".", local.parts)              # "dev.eu.west.1"
  region_short = substr("ap-southeast-1", 3, 8)      # "southeast"
}

Perhatikan kombinasi keduanya: split memecah string menjadi list, dan join menggabungkan list menjadi string. Kombinasi inilah yang paling sering dipakai untuk normalisasi data — misalnya mengubah nama environment dev-eu-west-1 menjadi suffix bucket yang valid dev.eu.west.1. Ini penting karena tidak semua karakter yang valid di string ternyata valid untuk nama bucket S3 atau tag.

Collection Functions

Kategori ini paling banyak digunakan. Collection di HCL meliputi list (["a", "b"]) dan map ({ key = "value" }). Fungsi yang wajib kalian kuasai:

FungsiDeskripsiContoh Hasil
length(x)Menghitung jumlah elemen list/map atau panjang stringlength(["a", "b"])2
merge(m1, m2)Menggabungkan beberapa map menjadi satumerge({a = 1}, {b = 2}){a = 1, b = 2}
lookup(map, key, default)Mengambil nilai map, dengan nilai default jika key tidak adalookup({a = 1}, "b", 0)0
concat(list1, list2)Menggabungkan beberapa listconcat([1], [2])[1, 2]
element(list, index)Mengambil elemen pada posisi tertentu (bersifat siklik)element(["a", "b"], 3)"a"
keys(map)Mengembalikan list berisi semua keykeys({a = 1, b = 2})["a", "b"]
values(map)Mengembalikan list berisi semua valuevalues({a = 1, b = 2})[1, 2]
toset(list)Mengubah list menjadi set (menghapus duplikat)toset(["a", "a", "b"])["a", "b"]
distinct(list)Menghapus elemen duplikat dari listdistinct([1, 1, 2])[1, 2]
sort(list)Mengurutkan elemen listsort(["c", "a"])["a", "c"]

Fungsi lookup layak mendapat perhatian ekstra karena menjadi andalan untuk memberi nilai default pada map yang belum tentu memiliki key tertentu. Contoh nyata: menentukan instance_type per environment.

lookup-example.tf
variable "environment" {
  type    = string
  default = "dev"
}
 
locals {
  instance_types = {
    dev  = "t3.micro"
    stg  = "t3.medium"
    prod = "m5.large"
  }
  instance_type = lookup(local.instance_types, var.environment, "t3.small")
}
 
output "selected_instance_type" {
  value = local.instance_type
}

Tip

Perbedaan krusial antara lookup dan akses map langsung local.map["key"]: akses langsung akan error jika key tidak ada, sedangkan lookup mengembalikan nilai default yang kalian tentukan. Di skenario multi-environment, lookup(local.instance_types, var.environment, "t3.small") jauh lebih aman daripada local.instance_types[var.environment] yang akan meledak ketika ada environment baru yang belum terdaftar.

Filesystem Functions

Filesystem functions menghubungkan konfigurasi Terraform dengan file di disk — berguna untuk membaca user data script, file template, atau memeriksa keberadaan file sebelum dipakai.

  • file(path) — membaca seluruh isi file sebagai string.
  • fileexists(path) — mengembalikan true/false apakah file ada.
  • templatefile(path, vars) — membaca file template dan mengisi variabel templatenya.

Fungsi templatefile paling sering dipakai untuk user data EC2. Perhatikan contoh berikut:

user-data-example.tf
resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
  user_data     = templatefile("${path.module}/user-data.sh.tftpl", {
    app_name = var.app_name
    app_port = var.app_port
  })
}
user-data.sh.tftpl
#!/bin/bash
set -euxo pipefail
apt-get update
apt-get install -y nginx
echo "Hello from ${app_name} on port ${app_port}" > /var/www/html/index.html

File template dengan ekstensi .tftpl memungkinkan kalian menyisipkan variabel dengan sintaks ${nama_variabel}. Keuntungannya: logika bash tetap bersih dan bisa ditulis multi-line, sementara data dinamisnya dipasok dari variabel Terraform saat apply. Ini jauh lebih rapi daripada menyambung string raksasa dengan operator <<EOF atau format.

Conditional Expressions (Operator Ternary)

Saat alur konfigurasi butuh percabangan, HCL menyediakan conditional expression berbentuk:

plaintext
condition ? true_val : false_val

Bacanya: "jika condition benar, pakai true_val; jika tidak, pakai false_val". Ini setara dengan operator ternary di bahasa lain (misalnya a ? b : c di C/JS/Java). Contoh nyata yang sangat umum — memilih tipe instance berdasarkan environment:

ternary-example.tf
variable "environment" {
  type    = string
  default = "dev"
}
 
resource "aws_instance" "app" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.environment == "production" ? "m5.large" : "t3.micro"
 
  tags = {
    Name    = "app-server"
    Env     = var.environment
    Managed = "terraform"
  }
}

Ekspresi bersyarat juga bisa dirantai (nested). Namun jangan berlebihan — jika percabangan sudah lebih dari dua level, lebih baik pindahkan logikanya ke locals agar mudah dibaca:

ternary-nested.tf
locals {
  disk_size = var.environment == "production" ? 200
            : var.environment == "staging"    ? 100
            : 50
}

Warning

Aturan penting: kedua cabang nilai pada ternary harus bertipe sama atau setidaknya saling konversi-able. condition ? "high" : 3 akan error karena mencoba menggabungkan string dan number. Untuk tipe yang berbeda, konversikan dulu secara eksplisit, misalnya condition ? "high" : tostring(3).

Meta-Arguments Perulangan: count, for_each, dan For Expressions

Inilah inti dari episode ini: tiga mekanisme untuk membuat banyak resource dari satu blok definisi. Masing-masing punya kasus penggunaan yang berbeda, dan salah pilih berarti masalah besar di kemudian hari.

count dan count.index — Perulangan Berbasis Angka

count menerima angka bulat dan membuat resource sebanyak angka tersebut. Untuk membedakan tiap instance, HCL menyediakan count.index yang dimulai dari 0. Contoh: membuat 3 instance EC2 yang identik.

count-example.tf
variable "instance_count" {
  type    = number
  default = 3
}
 
resource "aws_instance" "web" {
  count         = var.instance_count
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
 
  tags = {
    Name = "web-server-${count.index}"
  }
}

Setelah apply, kalian akan melihat tiga instance dengan address aws_instance.web[0], aws_instance.web[1], dan aws_instance.web[2]. Untuk mengakses semuanya sekaligus, gunakan splat expression: aws_instance.web[*].id menghasilkan list berisi ketiga ID. Untuk yang spesifik: aws_instance.web[0].id.

count-output.tf
output "web_instance_ids" {
  value = aws_instance.web[*].id
}
 
output "first_instance_id" {
  value = aws_instance.web[0].id
}

for_each dan each.key / each.value — Perulangan Berbasis Set/Map

for_each menerima sebuah map atau set of string, dan membuat satu resource untuk setiap elemennya. Untuk mengakses elemen yang sedang diproses, HCL menyediakan dua variabel khusus: each.key dan each.value. Saat iterasi dilakukan atas set, each.key dan each.value bernilai sama; saat atas map, each.key adalah key dan each.value adalah value dari elemen tersebut.

Contoh: membuat bucket S3 untuk beberapa kebutuhan sekaligus, dengan region yang berbeda-beda.

for_each-example.tf
locals {
  buckets = {
    "app-logs"      = { region = "ap-southeast-1" }
    "backup-data"   = { region = "ap-southeast-1" }
    "static-assets" = { region = "us-east-1" }
  }
}
 
resource "aws_s3_bucket" "data" {
  for_each = local.buckets
  bucket   = each.key
 
  tags = {
    Name   = each.key
    Region = each.value.region
    Env    = var.environment
  }
}

Sekarang aws_s3_bucket.data["app-logs"] menjadi address yang valid, dan values(aws_s3_bucket.data)[*].id menghasilkan seluruh ID bucket. Kalian bisa memikirkan for_each sebagai cara membuat banyak resource dengan konfigurasi beragam tapi terdata rapi — berbeda dengan count yang cenderung membuat banyak resource identik.

Perbandingan Langsung: count vs for_each

Untuk memperjelas kapan memakai yang mana, perhatikan perbandingan dua cara membuat kumpulan IAM user berikut:

resource "aws_iam_user" "team" {
  count = length(var.team_members)
  name  = var.team_members[count.index]
}

Pada count, elemen diakses lewat indeks angka; pada for_each, elemen diakses lewat key yang stabil. Inilah perbedaan filosofis yang kelak menentukan apakah Terraform akan menghancurkan resource kalian secara tak terduga (baca bagian Common Pitfalls di bawah).

For Expressions: [for s in var.list : upper(s)]

Selain membuat resource berulang, HCL juga bisa mengolah koleksi menggunakan for expression. Ini mirip list comprehension di Python. Bentuk dasarnya:

plaintext
[for item in list : transform(item)]

Contoh: mengubah semua elemen list menjadi uppercase, sekaligus menyaring elemen yang panjangnya di bawah ambang batas.

for-expression.tf
variable "services" {
  type    = list(string)
  default = ["api", "web", "worker", "cron"]
}
 
locals {
  uppercase_all = [for s in var.services : upper(s)]
  long_only     = [for s in var.services : upper(s) if length(s) > 3]
  map_result    = { for s in var.services : s => upper(s) }
}
 
output "long_only" {
  value = local.long_only   # ["WORKER", "CRON"]
}

For expression juga bisa menghasilkan map — seperti map_result di atas — dengan pola { for k, v in map : k => transform(v) }. Kemampuan ini sangat berguna untuk membangun struktur data perantara yang nantinya dikonsumsi oleh for_each.

Kesalahan Umum (Common Pitfalls)

1. count + list yang berubah urutan = resource di-destroy & dibuat ulang

Ini jebakan paling terkenal di ekosistem Terraform. Ketika count mengambil elemen dari sebuah list, setiap resource terikat pada posisi indeks, bukan identitas elemennya. Jika list berubah — misalnya ["budi", "siti", "agus"] menjadi ["siti", "agus", "budi"] — maka count.index 0 yang tadinya budi kini menjadi siti, dan Terraform akan me-recreate seluruh resource. Untuk data yang bersifat kumpulan atau ber-key, for_each jauh lebih aman karena key-nya stabil.

2. for_each menolak list biasa

for_each hanya menerima map atau set of string. Melempar list langsung akan memunculkan error klasik: The given "for_each" argument value is unsuitable: the "for_each" argument must be a map, or set of strings. Solusinya sederhana — bungkus dengan toset(var.team_members).

3. Memakai each.key / each.value di luar blok for_each

Variabel each hanya tersedia di dalam resource yang dideklarasikan dengan for_each. Memakainya pada resource count akan error dengan pesan Each object must be identified by exactly one .... Kenali blok mana yang memakai count.index dan mana yang memakai each — jangan dicampur.

4. Ternary dengan tipe berbeda

Seperti sudah disinggung: var.env == "prod" ? 20 : "twenty" akan gagal di-validate karena cabangnya tidak konsisten tipenya.

5. Hardcode alih-alih memanfaatkan lookup

Akses map langsung local.map[var.key] memang lebih ringkas, tapi langsung error saat key tidak ada. Di konfigurasi yang dijalankan banyak tim dengan environment berbeda-beda, lookup dengan default yang masuk akal adalah keputusan yang lebih defensive.

Penutup

Pada episode 6 ini kita telah memperkaya kosakata HCL dengan tiga kemampuan penting: built-in functions (string, collection, dan filesystem) untuk memanipulasi nilai, conditional expression (ternary) untuk percabangan logika, serta meta-arguments perulangancount untuk resource berbasis angka, for_each untuk resource berbasis set/map, dan for expression untuk mengolah koleksi.

Yang perlu diingat: kekuatan Terraform bukan terletak pada satu resource yang ditulis sempurna, melainkan pada kemampuan menulis banyak resource yang konsisten, terparameterisasi, dan adaptif terhadap environment. Fungsi dan perulangan adalah bahan bakarnya. Semakin sering kalian berlatih kombinasi for_each + lookup + for expression, semakin cepat kalian menulis konfigurasi yang tadinya butuh ratusan baris hanya dalam puluhan baris.

Di episode 7 selanjutnya kita akan membahas Resource Dependencies & Lifecycle Rules — bagaimana Terraform menentukan urutan pembuatan resource lewat dependency graph, kapan kita harus memaksakan urutan dengan depends_on, dan bagaimana aturan lifecycle seperti create_before_destroy, prevent_destroy, dan ignore_changes melindungi infrastruktur kalian di dunia nyata. Pastikan tetap semangat!

Belajar Terraform - Advanced Expressions, Functions & Loops | Belajar Terraform