Variable Validation: Catching Bad Input at Plan Time
Scenario
You are publishing a Terraform module for other teams to consume. Users keep passing invalid values — environment names with uppercase letters, bucket names with underscores (which S3 doesn't allow), and retention periods of zero days. Each mistake results in a cryptic AWS API error many seconds into the apply.
You want to add validation rules to your variables so bad input is rejected immediately at plan time — with a clear, human-readable error message — before any API call is made.
- Add a validation block to an environment variable that only allows dev, staging, or prod using contains().
- Add a validation to a bucket_name variable that enforces lowercase-alphanumeric-and-hyphens only using can(regex()).
- Add two validations to a retention_days variable: minimum 1 and maximum 365.
- Add a validation to a cidr_block variable using can(cidrhost()) to check valid CIDR notation.
- Test each validation by passing invalid values and observing the error messages.
Additional Context
Validation blocks run during terraform plan — before any cloud API calls. The condition must be an expression that evaluates to true or false. The error_message is shown when the condition is false.
Important constraint: a validation condition can only reference its own variable (var.name). It cannot reference other variables, resources, data sources, or locals. This is by design — validations run before the dependency graph is resolved.
The can() function is the key to defensive validation. It wraps an expression and returns true if it succeeds, or false if it throws an error — without crashing the plan.
variables.tf
# variables.tf
variable "aws_region" {
type = string
default = "us-east-1"
}
# Validation with contains() — allowed values list
variable "environment" {
description = "Deployment environment."
type = string
default = "dev"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be one of: dev, staging, prod."
}
}
# Validation with can(regex()) — pattern matching
variable "bucket_name" {
description = "S3 bucket name. Must be lowercase alphanumeric and hyphens only."
type = string
default = "my-valid-bucket"
validation {
condition = can(regex("^[a-z0-9][a-z0-9-]*[a-z0-9]$", var.bucket_name))
error_message = "Bucket name must contain only lowercase letters, numbers, and hyphens. Cannot start or end with a hyphen."
}
validation {
condition = length(var.bucket_name) >= 3 && length(var.bucket_name) <= 63
error_message = "Bucket name must be between 3 and 63 characters."
}
}
# Multiple validations — min and max range
variable "retention_days" {
description = "Object retention period in days."
type = number
default = 30
validation {
condition = var.retention_days >= 1
error_message = "Retention period must be at least 1 day."
}
validation {
condition = var.retention_days <= 365
error_message = "Retention period cannot exceed 365 days."
}
}
# Validation with can(cidrhost()) — CIDR format check
variable "allowed_cidr" {
description = "CIDR block for access control."
type = string
default = "10.0.0.0/16"
validation {
condition = can(cidrhost(var.allowed_cidr, 0))
error_message = "Must be a valid IPv4 CIDR notation (e.g., 10.0.0.0/16)."
}
}main.tf
# main.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
}
provider "aws" { region = var.aws_region }
resource "aws_s3_bucket" "validated" {
bucket = "${var.bucket_name}-${var.environment}"
tags = {
Environment = var.environment
RetentionDays = tostring(var.retention_days)
AllowedCIDR = var.allowed_cidr
ManagedBy = "terraform"
}
}Workflow Commands — Testing Validations
terraform init
# Valid input — should succeed
terraform plan
# Invalid environment → immediate error
terraform plan -var="environment=production"
# Error: Environment must be one of: dev, staging, prod.
# Invalid bucket name (uppercase) → immediate error
terraform plan -var="bucket_name=My_Bucket"
# Error: Bucket name must contain only lowercase letters, numbers, and hyphens.
# Invalid retention (zero) → immediate error
terraform plan -var="retention_days=0"
# Error: Retention period must be at least 1 day.
# Invalid CIDR → immediate error
terraform plan -var="allowed_cidr=not-a-cidr"
# Error: Must be a valid IPv4 CIDR notation.
# All valid — apply
terraform apply
terraform destroy│ Error: Invalid value for variable
│
│ on variables.tf line 12:
│ 12: variable "environment" {
│
│ Environment must be one of: dev, staging, prod.
│
│ This was checked by the validation rule at variables.tf:17,3-13.
