TFLAB21-precondition, postcondition and check Blocks

precondition, postcondition and check Blocks

🔧 Terraform Core ⭐ Intermediate precondition postcondition check block assert Custom Validation

Scenario

You want to catch errors at three different levels: before a resource is created (bad assumptions), after it's created (unexpected cloud behavior), and as an ongoing health check on every apply. Terraform provides three mechanisms for this — each with different failure behavior.

Your Objectives
  • Add a precondition inside a resource's lifecycle block that validates an assumption before the resource is created.
  • Add a postcondition using self that checks a property after the resource is created.
  • Add a standalone check block with an assert that runs after every apply — failures produce warnings, not errors.
  • Observe the different failure behavior of each mechanism.

Additional Context

| Mechanism | When | Failure Impact |
| precondition | Before resource apply | Blocks apply (error) |
| postcondition | After resource apply | Blocks apply (error) |
| check block | After every apply | Warning only (apply succeeds) |

Preconditions validate assumptions ("I expect this data source to return an x86_64 AMI"). Postconditions validate guarantees ("I expect this instance to have a public IP"). Check blocks monitor ongoing health ("I expect this endpoint to return HTTP 200").

main.tf

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    aws = { source = "hashicorp/aws", version = "~> 5.0" }
  }
}

provider "aws" { region = "us-east-1" }

variable "environment" {
  type    = string
  default = "dev"
}

variable "expected_region" {
  type    = string
  default = "us-east-1"
}

data "aws_region" "current" {}
data "aws_caller_identity" "current" {}

resource "aws_s3_bucket" "app" {
  bucket = "checks-demo-${var.environment}-${data.aws_caller_identity.current.account_id}"

  tags = {
    Environment = var.environment
    ManagedBy   = "terraform"
  }

  lifecycle {
    # ─── PRECONDITION: checked BEFORE the resource is created ───
    # Validates an assumption about the infrastructure state.
    precondition {
      condition     = data.aws_region.current.name == var.expected_region
      error_message = "This config must be deployed to ${var.expected_region}, but the provider is configured for ${data.aws_region.current.name}."
    }

    # ─── POSTCONDITION: checked AFTER the resource is created ───
    # Validates that the resource was created with expected properties.
    # 'self' refers to the resource that was just created.
    postcondition {
      condition     = self.bucket_regional_domain_name != ""
      error_message = "Bucket was created but regional domain name is empty — unexpected."
    }
  }
}

# ─── CHECK BLOCK: runs after EVERY apply, failures are WARNINGS ───
# Terraform 1.5+. Does not block the apply.
check "bucket_has_tags" {
  assert {
    condition     = length(aws_s3_bucket.app.tags) > 0
    error_message = "Warning: The app bucket has no tags. All buckets should be tagged."
  }
}

check "environment_is_valid" {
  assert {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Warning: Environment '${var.environment}' is not a standard value."
  }
}

outputs.tf

output "bucket_name"    { value = aws_s3_bucket.app.id }
output "bucket_domain"  { value = aws_s3_bucket.app.bucket_regional_domain_name }
output "current_region" { value = data.aws_region.current.name }

Workflow Commands

terraform init && terraform apply

# Test precondition failure — wrong expected region
terraform plan -var="expected_region=eu-west-1"
# Error: This config must be deployed to eu-west-1, but the provider is configured for us-east-1.

# Test check warning — non-standard environment
terraform apply -var="environment=testing"
# Warning: Environment 'testing' is not a standard value.
# (apply still succeeds — checks produce warnings, not errors)

terraform destroy
✓ Precondition Failure Example
│ Error: Resource precondition failed
│
│   on main.tf line 34, in resource "aws_s3_bucket" "app":
│   34:       condition = data.aws_region.current.name == var.expected_region
│
│ This config must be deployed to eu-west-1, but the provider
│ is configured for us-east-1.