Expressions, Functions, and Conditionals
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”Terraform configuration ไม่ได้มีแค่ค่า static — built-in function แปลงข้อมูล, conditional expression เลือกระหว่างสองค่า, และ dynamic block สร้าง nested block จำนวนไม่คงที่ ทั้งหมดนี้ evaluate ตอน plan ก่อนที่จะไปเรียก GCP API เลยด้วยซ้ำ
Built-in function แยกตามหมวด
หัวข้อที่มีชื่อว่า “Built-in function แยกตามหมวด”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 เองด้วยมือ
terraform console — REPL สำหรับทดลอง expression
หัวข้อที่มีชื่อว่า “terraform console — REPL สำหรับทดลอง expression”ก่อนจะเอา function หรือ expression ที่ยังไม่คุ้นมือไปใส่ใน resource argument จริง terraform console เปิด interactive REPL ที่ evaluate expression กับ configuration และ state ปัจจุบันได้เลย โดยไม่ต้อง plan หรือ apply อะไร
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
หัวข้อที่มีชื่อว่า “Conditional expression”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"
dynamic block — nested block จำนวนไม่คงที่
หัวข้อที่มีชื่อว่า “dynamic block — nested block จำนวนไม่คงที่”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"]