ข้ามไปยังเนื้อหา

Expressions, Functions, and Conditionals

Terraform configuration ไม่ได้มีแค่ค่า static — built-in function แปลงข้อมูล, conditional expression เลือกระหว่างสองค่า, และ dynamic block สร้าง nested block จำนวนไม่คงที่ ทั้งหมดนี้ evaluate ตอน plan ก่อนที่จะไปเรียก GCP API เลยด้วยซ้ำ

Terraform มี built-in function ชุดตายตัว — เราสร้าง function เองไม่ได้ — แบ่งหมวดคร่าว ๆ ตามชนิดข้อมูลที่ทำงานด้วย มีไม่กี่ตัวที่เจอบ่อยมากใน GCP configuration:

locals {
# String functions
bucket_suffix = join("-", ["assets", "prod", "us"])
path_parts = split("/", "projects/my-project/zones/us-central1-a")
log_message = format("instance %s in zone %s", "web-1", "us-central1-a")
# Collection functions
tier_lookup = lookup({ dev = "db-f1-micro", prod = "db-custom-2-7680" }, "dev", "db-f1-micro")
merged_labels = merge(
{ managed_by = "terraform" },
{ environment = "prod" },
)
has_ssh = contains([22, 443, 8080], 22)
# Networking function: carve a /24 out of a /16 VPC range
subnet_a = cidrsubnet("10.0.0.0/16", 8, 0)
subnet_b = cidrsubnet("10.0.0.0/16", 8, 1)
}

join กับ split เป็นด้านตรงข้ามกัน — ตัวหนึ่งเปลี่ยน list ให้เป็น string ที่มี delimiter คั่น อีกตัวเปลี่ยน string ที่มี delimiter กลับเป็น list lookup อ่าน map ด้วย key พร้อม fallback ที่เป็น argument ตัวที่สามชัดเจน ทำให้ key ที่หายไปไม่ทำให้เกิด error merge รวมหลาย map เข้าเป็นก้อนเดียว โดย argument ที่มาทีหลัง override key จาก argument ก่อนหน้า contains เช็คว่ามีสมาชิกอยู่ใน list ไหมและคืนค่า boolean ธรรมดา cidrsubnet(prefix, newbits, netnum) คือตัวที่ควรจำให้แม่นสำหรับงาน networking บน GCP function นี้รับ CIDR block ตั้งต้น ขยาย prefix length ไปอีก newbits บิต แล้วคืน subnet ตัวที่ netnum ของขนาดใหม่นั้น — เหมาะเจาะพอดีสำหรับตัด 10.0.0.0/16 ของ VPC network ให้เป็น /24 หลายชุดโดยไม่ต้องคำนวณ address เองด้วยมือ

ก่อนจะเอา function หรือ expression ที่ยังไม่คุ้นมือไปใส่ใน resource argument จริง terraform console เปิด interactive REPL ที่ evaluate expression กับ configuration และ state ปัจจุบันได้เลย โดยไม่ต้อง plan หรือ apply อะไร

Terminal window
terraform console
> cidrsubnet("10.0.0.0/16", 8, 1)
"10.0.1.0/24"
> join("-", ["assets", "prod", "us"])
"assets-prod-us"
> var.environment
"dev"

นี่คือวิธีที่เร็วที่สุดในการตอบคำถาม “expression นี้ evaluate ออกมาเป็นอะไรกันแน่” — พิมพ์เข้าไป อ่านผลลัพธ์ แล้วค่อยเอา expression ที่ใช้ได้จริงไปวางในไฟล์ .tf คำสั่งนี้เป็น read-only กับ infrastructure จริง terraform console อ่าน variable, local, และ state ที่มีอยู่ได้ แต่สร้าง เปลี่ยน หรือทำลายอะไรไม่ได้เลย

conditional expression มีรูปร่าง condition ? true_val : false_val และ evaluate ออกมาเป็นค่าใดค่าหนึ่งในสองฝั่งตามว่า condition เป็น true หรือ false

variable "environment" {
type = string
description = "Deployment environment name"
default = "dev"
}
locals {
machine_type = var.environment == "prod" ? "e2-standard-4" : "e2-medium"
}
resource "google_compute_instance" "web" {
name = "web-server"
machine_type = local.machine_type
zone = "us-central1-a"
boot_disk {
initialize_params {
image = "debian-cloud/debian-12"
}
}
network_interface {
network = "default"
access_config {}
}
}

สามส่วนคือ condition (expression ที่ต้อง evaluate ออกมาเป็น boolean), true_val (ใช้เมื่อ condition เป็น true), และ false_val (ใช้เมื่อ condition เป็น false) ในตัวอย่างนี้ local.machine_type จะได้ "e2-standard-4" ก็ต่อเมื่อ var.environment เท่ากับ "prod" เป๊ะ ๆ และตกไปที่ "e2-medium" สำหรับค่าอื่นทั้งหมด รวมถึง "dev" กับ "staging"

count หรือ for_each บน resource สร้าง resource ทั้งก้อนจำนวนไม่คงที่ ซึ่งบทถัดไปจะพูดถึง dynamic block แก้ปัญหาที่คล้ายกันแต่ต่างกันคือ สร้าง nested block ที่ซ้ำกันจำนวนไม่คงที่ภายใน resource เดียว

variable "allowed_ports" {
type = list(number)
description = "TCP ports allowed inbound on the firewall rule"
default = [22, 80, 443]
}
resource "google_compute_firewall" "allow_multiple" {
name = "allow-multiple-ports"
network = "default"
dynamic "allow" {
for_each = var.allowed_ports
content {
protocol = "tcp"
ports = [allow.value]
}
}
}

dynamic "allow" block วนลูปผ่าน var.allowed_ports และสร้าง nested block allow { ... } หนึ่งอันต่อหนึ่ง element ภายใน google_compute_firewall.allow_multiple resource เดียว ภายใน content allow.value อ้างถึง element ปัจจุบัน — ชื่อ iterator variable default เป็นชื่อ label ของ dynamic block เอง คือ allow ใช้ dynamic เมื่อสิ่งที่ซ้ำคือ nested block ภายใน resource เดียว ใช้ count หรือ for_each บนตัว resource เมื่อสิ่งที่ซ้ำคือทั้ง resource

flowchart LR
  a["var.environment"] --> c{"environment == prod ?"}
  c -->|true| t["e2-standard-4"]
  c -->|false| f["e2-medium"]
  t --> m["local.machine_type"]
  f --> m
  m --> r["google_compute_instance.web.machine_type"]
A conditional expression and a function call resolve to one final value used in a resource argument
terraform console ใช้ทำอะไร
conditional expression มีสามส่วนอะไรบ้าง
ควรใช้ dynamic block แทน count หรือ for_each บน resource เมื่อไหร่
cidrsubnet(prefix, newbits, netnum) ทำงานอย่างไร