Terraform Code Style และ fmt: การ Format Code ให้เป็นมาตรฐาน

Code style ที่สม่ำเสมอเป็นเรื่องสำคัญในทีม — ช่วยให้ review code เร็วขึ้น diff สะอาด และลดเวลาถกเถียงเรื่อง format HCL มีเครื่องมือ built-in terraform fmt ที่ช่วยจัด format อัตโนมัติตาม convention อย่างเป็นทางการ

บทความนี้จะสอนการใช้ terraform fmt, แนวทาง code style ที่ HashiCorp แนะนำ, และวิธี integrate เข้ากับ git hook / CI เพื่อให้ทุกคนในทีม commit code ที่ format ถูกต้อง

คำสั่ง fmt คืออะไร

terraform fmt เป็น sub-command ที่ช่วย reformat ไฟล์ .tf และ .tfvars ให้ตรงตาม canonical style — จัด indent, alignment ของ =, และ whitespace ให้เป็นมาตรฐานเดียว

# format ไฟล์ใน directory ปัจจุบัน
terraform fmt

# format recursive ลงไปใน subfolder
terraform fmt -recursive

# ดูว่าไฟล์ไหน format ไม่ถูก (ไม่แก้จริง — เหมาะกับ CI)
terraform fmt -check

# ดู diff ของการเปลี่ยนแปลง
terraform fmt -diff

# format จาก stdin
echo 'resource "foo" "bar" { x=1 }' | terraform fmt -

สิ่งที่ fmt จัดให้

  • Indent 2 spaces (ไม่ใช่ tab)
  • Align = ของ argument ภายใน block เดียวกัน
  • Blank line ระหว่าง block
  • ลบ trailing whitespace
  • แก้ quotes ที่ไม่จำเป็น (เช่น "var.x"var.x ใน interpolation)

ตัวอย่าง Before / After

# Before
resource "aws_instance" "web" {
ami=var.ami_id
  instance_type   = var.instance_type
tags={Name="web-server"}
}

# After fmt
resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = var.instance_type
  tags          = { Name = "web-server" }
}

Style Guide ที่แนะนำ

นอกจาก fmt ที่แก้ให้อัตโนมัติ ยังมีแนวทางที่ต้องเขียนเองให้สม่ำเสมอ

1. จัดเรียง Argument ภายใน Block

ให้ปฏิบัติตามลำดับ: required argument ก่อน, optional argument ตาม, meta-argument (depends_on, count, for_each, lifecycle) ไว้ท้ายสุด

resource "aws_instance" "web" {
  # Required
  ami           = var.ami_id
  instance_type = var.instance_type

  # Optional
  key_name               = aws_key_pair.deploy.key_name
  vpc_security_group_ids = [aws_security_group.web.id]
  subnet_id              = aws_subnet.public.id

  tags = {
    Name        = "web-server"
    Environment = var.environment
  }

  # Meta-arguments (ท้ายสุด)
  depends_on = [aws_internet_gateway.main]

  lifecycle {
    create_before_destroy = true
  }
}

2. เขียน Comment เมื่อจำเป็น

  • ใช้ # สำหรับ comment บรรทัดเดียว (แนะนำกว่า //)
  • ใช้ /* ... */ สำหรับ comment หลายบรรทัด
  • อธิบาย “ทำไม” ไม่ใช่ “ทำอะไร” — code บอกว่าทำอะไรอยู่แล้ว
# ใช้ t3.small เพราะ load ยังน้อย เพิ่มทีหลังถ้า traffic โต
resource "aws_instance" "web" {
  instance_type = "t3.small"
}

3. เรียง Variable และ Output ตามลำดับที่สมเหตุสมผล

  • Group ตาม category (network, compute, database)
  • Required variable (ไม่มี default) มาก่อน optional
  • เรียง alphabetical ภายใน group ก็ได้ถ้าไม่มี logical order

4. ใช้ Whitespace ให้เหมาะสม

  • Blank line คั่นระหว่าง resource block
  • Blank line คั่นระหว่าง group ของ argument (required / optional / meta)
  • ไม่มี blank line เกิน 1 บรรทัดติดกัน

Integrate กับ Git Pre-commit Hook

Pre-commit hook จะรัน fmt อัตโนมัติก่อน commit — ป้องกัน commit code ที่ format ไม่ถูก

# .git/hooks/pre-commit (ไฟล์ใน local repo)
#!/bin/bash
set -e

CHANGED_TF=$(git diff --cached --name-only --diff-filter=ACM | grep '\.tf$' || true)

if [ -n "$CHANGED_TF" ]; then
  terraform fmt -check $CHANGED_TF
  if [ $? -ne 0 ]; then
    echo "ERROR: HCL files not formatted. Run 'fmt' command."
    exit 1
  fi
fi

วิธีที่ดีกว่าคือใช้ pre-commit framework ซึ่ง share config ได้ทั้งทีม:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/antonbabenko/pre-commit-terraform
    rev: v1.86.0
    hooks:
      - id: terraform_fmt
      - id: terraform_validate
      - id: terraform_tflint
pip install pre-commit
pre-commit install
# ตอนนี้ทุก commit จะรัน fmt + validate + tflint อัตโนมัติ

Integrate ใน CI Pipeline

ใน CI ให้รัน fmt -check -recursive เพื่อ fail build ถ้ามีไฟล์ที่ format ไม่ถูก — ป้องกัน code ที่ lint ไม่ผ่าน merge เข้า main branch

# GitHub Actions ตัวอย่าง
name: Terraform Lint
on: [push, pull_request]

jobs:
  fmt:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.6.0
      - name: Check formatting
        run: terraform fmt -check -recursive -diff

คำสั่ง validate

คู่กับ fmt ควรใช้ validate ตรวจ syntax และ internal consistency ของ config

# ต้อง init ก่อนรัน validate
terraform init -backend=false
terraform validate

# ตัวอย่าง error ที่ validate จับได้:
# - reference variable ที่ไม่มี declaration
# - argument ที่ไม่มีใน schema ของ resource
# - type mismatch

IDE Integration

  • VS Code — extension HashiCorp Terraform รัน fmt on save ได้
  • JetBrains (IntelliJ/GoLand) — plugin “HashiCorp Terraform and HCL” รองรับ format + syntax highlight
  • Vim / Neovim — plugin vim-terraform ทำ autofmt on save

ตั้งค่า VS Code auto-format

// .vscode/settings.json
{
  "[terraform]": {
    "editor.defaultFormatter": "hashicorp.terraform",
    "editor.formatOnSave": true,
    "editor.formatOnSaveMode": "file"
  }
}

Best Practices

  • รัน terraform fmt -recursive ก่อน commit ทุกครั้ง
  • ติดตั้ง pre-commit hook ในทีมให้ทุกคน — ใช้ pre-commit framework จะ share config ง่าย
  • เพิ่ม terraform fmt -check -recursive ใน CI เพื่อ enforce ระดับ PR
  • ใช้ terraform validate คู่กับ fmt ตรวจ syntax error
  • ตั้ง IDE ให้ format on save เพื่อไม่ต้องจำรัน fmt เอง
  • เรียง argument ภายใน block: required → optional → meta-argument
  • เขียน comment เฉพาะเมื่ออธิบาย “ทำไม” ไม่ใช่ “ทำอะไร”
  • commit .pre-commit-config.yaml และ .vscode/settings.json (ไม่ใส่ใน .gitignore) เพื่อ share config ทีม

สรุป

Code style ที่สม่ำเสมอเริ่มจากการใช้ fmt เป็นเครื่องมือพื้นฐาน — integrate กับ pre-commit hook และ CI เพื่อให้ทุกคนในทีม commit code ที่ format ถูกต้อง ลด overhead ของการ review code เรื่อง cosmetic และหันไปโฟกัสกับ logic ที่สำคัญกว่า ในบทความถัดไปจะพูดถึง validate และ tflint สำหรับการ lint code ลึกกว่าแค่ format — ตรวจ best practice และ bug ที่ syntactically ถูกต้องแต่ผิด pattern