Skip to main content

Preconditions & Postconditions

Custom Conditions (Terraform 1.2+) — assertion ที่ตรวจสอบ assumption ใน config — ป้องกัน bug ที่ output แต่ค่าผิด

Custom Conditions มีอะไรบ้าง?

3 แบบหลัก:

Blockอยู่ในตรวจตอน
validationvariableinput validation
preconditionresource, data, outputก่อน apply
postconditionresource, data, outputหลัง apply

Output Precondition

output "instance_public_ip" {
value = aws_instance.web.public_ip

precondition {
condition = aws_instance.web.public_ip != ""
error_message = "Instance has no public IP. Did you set associate_public_ip_address?"
}
}

ถ้า condition fail → Terraform error ก่อน return output

ตัวอย่าง: Ensure Output ไม่ Empty

output "alb_dns_name" {
value = aws_alb.main.dns_name

precondition {
condition = aws_alb.main.dns_name != ""
error_message = "ALB DNS name is empty. Check ALB creation status."
}
}

Resource Preconditions

resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"

lifecycle {
precondition {
condition = data.aws_ami.ubuntu.architecture == "x86_64"
error_message = "AMI must be x86_64 architecture for t2.micro."
}

precondition {
condition = var.environment != "prod" || var.instance_type != "t2.micro"
error_message = "Production must use t3.large or above."
}
}
}

Data Source Postconditions

data "aws_ami" "latest_ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical

filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}

lifecycle {
postcondition {
condition = self.architecture == "x86_64"
error_message = "Found AMI is not x86_64."
}

postcondition {
condition = timecmp(self.creation_date, "2024-01-01") > 0
error_message = "AMI is too old (created before 2024-01-01)."
}
}
}

เปรียบเทียบ: validation vs precondition vs postcondition

validation (variable block)

ตรวจ input ก่อนเริ่ม plan

variable "instance_type" {
validation {
condition = startswith(var.instance_type, "t3")
error_message = "Only t3 family allowed."
}
}

precondition (resource/output)

ตรวจ before resource is read/created — ใช้ assumption check

resource "aws_instance" "web" {
lifecycle {
precondition {
condition = data.aws_subnet.this.availability_zone == "ap-southeast-1a"
error_message = "Must deploy in ap-southeast-1a."
}
}
}

postcondition (resource/data)

ตรวจ after resource is created/read — ใช้ verify result

data "aws_db_instance" "prod" {
db_instance_identifier = "prod-db"

lifecycle {
postcondition {
condition = self.engine == "postgres"
error_message = "Expected postgres engine."
}
}
}

ตัวอย่าง: Real-World Patterns

Pattern 1: Validate AMI Architecture

data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]

filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-*"]
}

lifecycle {
postcondition {
condition = self.architecture == var.expected_arch
error_message = "AMI architecture mismatch. Expected ${var.expected_arch}, got ${self.architecture}."
}
}
}

Pattern 2: Ensure VPC Has Sufficient Subnets

data "aws_vpc" "selected" {
id = var.vpc_id
}

data "aws_subnets" "private" {
filter {
name = "vpc-id"
values = [data.aws_vpc.selected.id]
}
filter {
name = "tag:Tier"
values = ["Private"]
}

lifecycle {
postcondition {
condition = length(self.ids) >= 2
error_message = "VPC must have at least 2 private subnets across AZs."
}
}
}

Pattern 3: Validate Pre-Deploy Conditions

resource "aws_lambda_function" "api" {
function_name = "api"
role = aws_iam_role.lambda.arn
handler = "index.handler"
runtime = "nodejs20.x"
s3_bucket = aws_s3_bucket.code.id
s3_key = "lambda/api.zip"

lifecycle {
precondition {
condition = aws_iam_role_policy_attachment.lambda_basic.role == aws_iam_role.lambda.name
error_message = "Lambda role must have basic execution policy attached."
}

precondition {
condition = data.aws_s3_object.lambda_code.content_length > 0
error_message = "Lambda code zip is empty."
}
}
}

Pattern 4: Output Verification

output "api_endpoint" {
value = "https://${aws_apigatewayv2_api.main.api_endpoint}"

precondition {
condition = aws_apigatewayv2_stage.main.deployment_id != ""
error_message = "API Gateway stage not deployed yet."
}
}

self ใน Postcondition

ใน postcondition ใช้ self.<attr> อ้างอิง resource ตัวเอง (จะใช้ aws_instance.web.attr ไม่ได้ เพราะวงกลม):

resource "aws_instance" "web" {
ami = "ami-12345"
instance_type = "t2.micro"

lifecycle {
postcondition {
condition = self.public_ip != "" # ← self
error_message = "Instance has no public IP."
}
}
}

สรุป

TypeWhereWhenUse Case
validationvariableinputcheck input values
preconditionresource/data/outputก่อน read/createcheck assumption
postconditionresource/dataหลัง read/createverify result

Best Practices:

  • ใช้ validation บน variable input
  • ใช้ precondition ใน output → ป้องกัน return ค่าว่าง/ผิด
  • ใช้ postcondition ใน data source → verify ดึงข้อมูลถูก
  • Error message ชัดเจน + actionable

ต่อไป → Section 7: Format & Validate