Terraform Built-in Functions: คู่มือ Function ยอดนิยมสำหรับเขียน HCL

Built-in functions เป็นหนึ่งในเครื่องมือที่ทำให้ HCL มีความยืดหยุ่นสูง ใช้ช่วย transform, คำนวณ, หรือจัดการข้อมูลใน configuration โดยไม่ต้องเขียน custom code ภายนอก ทำให้ config สั้นลงและอ่านง่ายขึ้น

บทความนี้แบ่ง functions ที่ใช้บ่อยออกเป็น 5 กลุ่ม: String, Collection, Numeric, Encoding และ Filesystem พร้อมตัวอย่างการใช้งานจริง

String Functions

upper("hello")                     # "HELLO"
lower("HELLO")                     # "hello"
title("hello world")               # "Hello World"
trimspace("  hello  ")             # "hello"
replace("hello-world", "-", "_")   # "hello_world"
split(",", "a,b,c")                # ["a", "b", "c"]
join("-", ["a", "b", "c"])         # "a-b-c"
format("vm-%03d", 5)               # "vm-005"
substr("terraform", 0, 5)          # "terra"
startswith("terraform.io", "terra") # true

format() ใช้บ่อยมากในการสร้างชื่อ resource ที่มี pattern เช่น web-prod-001, db-staging-mysql-01 โดยไม่ต้อง concat ด้วย +

Collection Functions

length([1, 2, 3])                  # 3
length({a = 1, b = 2})             # 2

concat(["a"], ["b", "c"])          # ["a", "b", "c"]
merge({a = 1}, {b = 2})            # {a = 1, b = 2}

contains(["prod", "staging"], "prod") # true
keys({a = 1, b = 2})               # ["a", "b"]
values({a = 1, b = 2})             # [1, 2]

distinct(["a", "b", "a", "c"])     # ["a", "b", "c"]
sort(["c", "a", "b"])              # ["a", "b", "c"]
reverse([1, 2, 3])                 # [3, 2, 1]

merge() เป็น function ที่พลิกโฉมการจัดการ default + override ใน module — สามารถ merge tag default ของบริษัทเข้ากับ tag เฉพาะของ resource ได้ในบรรทัดเดียว

locals {
  default_tags = {
    Environment = "production"
    Team        = "platform"
    ManagedBy   = "terraform"
  }
}

resource "aws_instance" "web" {
  # ...
  tags = merge(local.default_tags, {
    Name = "web-server-001"
    Role = "frontend"
  })
}

Numeric Functions

max(10, 20, 5)                     # 20
min(10, 20, 5)                     # 5
abs(-7)                            # 7
ceil(4.3)                          # 5
floor(4.7)                         # 4
pow(2, 10)                         # 1024

# ใช้คำนวณ subnet CIDR
cidrsubnet("10.0.0.0/16", 8, 1)    # "10.0.1.0/24"
cidrhost("10.0.1.0/24", 5)         # "10.0.1.5"

cidrsubnet() และ cidrhost() เป็น function ที่ช่วยให้สร้าง VPC/subnet แบบ programmatic ได้ เช่นแบ่ง 10.0.0.0/16 เป็น subnet ย่อย /24 จำนวน 3 ตัวโดยอัตโนมัติ

resource "aws_subnet" "private" {
  count      = 3
  vpc_id     = aws_vpc.main.id
  cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index)
  # → 10.0.0.0/24, 10.0.1.0/24, 10.0.2.0/24
}

Encoding Functions

jsonencode({name = "web", port = 80})
# "{\"name\":\"web\",\"port\":80}"

jsondecode("{\"name\":\"web\"}")
# {name = "web"}

yamlencode({version = 1, services = ["web"]})
# "services:\n- web\nversion: 1\n"

base64encode("hello")              # "aGVsbG8="
base64decode("aGVsbG8=")           # "hello"

ใช้ encoding functions ในการเตรียม user_data ของ EC2 instance, ECS task definition, หรือ Kubernetes ConfigMap/Secret

resource "aws_instance" "web" {
  # ...
  user_data = base64encode(templatefile("init.sh.tpl", {
    app_port = var.app_port
    env      = var.environment
  }))
}

Filesystem Functions

file("${path.module}/init.sh")
# อ่านไฟล์ในโมดูล ส่งคืน string

filebase64("${path.module}/cert.pem")
# อ่านไฟล์เป็น base64 (ใช้กับ binary)

templatefile("${path.module}/config.tpl", {
  db_host = "localhost"
  db_port = 5432
})
# อ่าน template + substitute variables
# template:
#   DB_HOST=${db_host}
#   DB_PORT=${db_port}

fileexists("${path.module}/custom.tf")  # true/false
fileset("${path.module}/scripts", "*.sh") # ["init.sh", "setup.sh"]

templatefile() เหมาะกับการสร้างไฟล์ config ที่มีค่าเปลี่ยนตาม environment เช่น nginx.conf, app config, cloud-init script โดยไม่ต้อง hardcode ค่าใน HCL

Combining Functions

Function แต่ละตัวสามารถ chain ต่อกันได้เพื่อจัดการข้อมูลซับซ้อน ตัวอย่างการ generate hostname จาก tag

locals {
  instances = {
    web  = { role = "Frontend", env = "prod" }
    api  = { role = "Backend",  env = "prod" }
    db   = { role = "Database", env = "prod" }
  }

  # สร้าง hostname: lowercase(role) + "-" + name + "-" + env
  hostnames = {
    for name, cfg in local.instances :
    name => format("%s-%s-%s", lower(cfg.role), name, cfg.env)
  }
  # = { web = "frontend-web-prod", api = "backend-api-prod", db = "database-db-prod" }
}

Best Practices

  • ใช้ terraform console ทดสอบ function call ก่อนใส่ใน config — ช่วยให้เข้าใจพฤติกรรมก่อน apply
  • หลีกเลี่ยง function chain ยาวเกินไป ถ้าซับซ้อนให้แยกเป็น locals หลายตัว
  • Documentation ของ built-in functions อยู่ที่ developer.hashicorp.com/terraform/language/functions — เปิดไว้ตลอดเวลาเขียน config ใหม่
  • อย่าพยายามสร้าง custom function ใน HCL — ถ้าต้องการ logic ซับซ้อน ให้ใช้ external data source หรือ provider เฉพาะ

สรุป

Built-in functions ครอบคลุมงานส่วนใหญ่ที่ต้องทำกับข้อมูลใน configuration ตั้งแต่ string manipulation, collection operation, การคำนวณ, encoding, จนถึงการอ่านไฟล์ การใช้ function ร่วมกับ locals และ for expression จะทำให้ config มีความยืดหยุ่นและบำรุงรักษาง่าย ในบทความถัดไปจะพูดถึง conditional expression และ count/for_each ซึ่งเป็นอีกเครื่องมือสำคัญในการควบคุม resource ตามเงื่อนไข