Back to: AWS-Basics-Advanced
0
A Terraform for expression works on:
✔ list
✔ set
✔ tuple
✔ map
✔ object
✔ map(object)
✔ list(object)
You can loop through any collection type.
1. for loop on LIST (EC2 AMIs)
Variable
Loop Example
Output
✅ 2. for loop on SET (Security groups)
Variable
Loop
Output
✅ 3. for loop on MAP (NLB Target Groups)
Variable
Loop
Output
✅ 4. for loop on OBJECT (Single EC2 config)
Variable
Loop over object attributes
Output
✅ 5. for loop on LIST(OBJECT) (ALB listener rules)
Variable
Loop
Output
✅ 6. for loop on MAP(OBJECT) (Multiple EC2 Instances)
Variable
Loop
Output
✅ 7. for loop on TUPLE (mixed values example)
Variable
Loop
Output
✅ 8. for loop on LIST of VPC Endpoints
Variable
Loop
Output
🎯 Final Summary — You Can Use for Loop On:
| Type | Example |
|---|---|
| list | AMIs |
| set | SGs |
| map | NLB target groups |
| object | EC2 config |
| list(object) | ALB listeners |
| map(object) | EC2 instances |
| tuple | mixed values |
| list(string) | VPC endpoints |
1. contains() — Check if a list has a value
✔ What it does:
Checks whether a given item exists inside a list.
✔ Syntax:
contains(list, value)
✔ Example:
contains([“t2.micro”, “t3.micro”], “t3.micro”) # true
contains([“443”, “8443”], 80) # false
✔ Use in validation:
validation {
condition = contains([“dev”, “stage”, “prod”], var.env)
error_message = “Invalid environment.”
}
⭐ 2. can() — Prevent errors (checks if Terraform can evaluate an expression)
✔ What it does:
Returns true if Terraform can safely evaluate the expression without error.
✔ Useful when:
Some values may be null
Types may not match
Value might not exist
You want validations to be safe
✔ Example:
❌ If we do this:
var.port > 0
If port = “abc” → Terraform crashes.
✔ Using can():
can(var.port > 0)
Returns false instead of crashing.
✔ Used in validation:
validation {
condition = alltrue([
for rule in var.alb_ingress_rules :
can(rule.port) && rule.port >= 1 && rule.port <= 65535
])
}
This ensures:
rule.port is a number
It does NOT crash Terraform
⭐ 3. lookup() — Safely retrieve values from a map
✔ What it does:
Extracts a value from a map without failing if the key doesn’t exist.
✔ Syntax:
lookup(map, key, default)
✔ Example:
lookup({dev = “t3.micro”, prod = “m5.large”}, “dev”, “notfound”)
# result: “t3.micro”
lookup({dev = “t3.micro”}, “stage”, “default-type”)
# result: “default-type”
✔ Use inside validations:
Sometimes used to avoid key errors:
lookup(var.allowed_types, var.env, []) # list fallback
⭐ 4. regex() — Pattern matching
✔ What it does:
Matches a string using regular expression.
✔ Syntax:
regex(pattern, string)
✔ Example:
Validate EC2 instance family:
regex(“^t3\\.”, var.instance_type) # true for t3.micro, t3.large
✔ Used in validations:
validation {
condition = can(regex(“^ami-[0-9a-f]+$”, var.ami_id))
error_message = “Invalid AMI ID”
}
⭐ 5. alltrue() — All conditions must be true
✔ What it does:
If every element in a list is true, returns true.
✔ Example:
alltrue([true, true, true]) # true
alltrue([true, false, true]) # false
✔ Used in list validations:
validation {
condition = alltrue([
for p in var.alb_ports : contains([443, 8443], p)
])
}
⭐ 6. anytrue() — At least one must be true
✔ What it does:
Checks whether one or more elements are true.
✔ Example:
anytrue([false, false, true]) # true
anytrue([false, false]) # false
⭐ 7. startswith() & endswith()
Very simple string checks.
✔ Examples:
startswith(“prod-app”, “prod”) # true
endswith(“file.txt”, “.txt”) # true
✔ Use case: Validate naming conventions
validation {
condition = startswith(var.bucket_name, “myorg-“)
error_message = “Bucket names must start with myorg-“
}
⭐ 8. length() — Count items
✔ Example:
length([“a”,”b”,”c”]) # 3
length(“hello”) # 5
✔ In validation:
Check list is not empty:
length(var.subnets) > 0
⭐ 9. for Expressions inside validations
Terraform allows loops inside validations.
✔ Example:
[for p in var.ports : p if p > 1024]
You can count invalid values:
length([
for p in var.ports : p
if !(contains([443,8443], p))
]) == 0
This ensures all ports are valid.
⭐ 10. toset() / tolist()
Used for type conversion.
✔ Example:
toset([“a”,”b”,”b”]) # {“a”,”b”} — duplicates removed
tolist({“a”,”b”}) # [“a”,”b”] — converts set → list
Useful when validating unique values.
🚀 Summary Table
Function Simple Meaning Best Used For
contains() Check if item exists allow-list validations
can() Prevent Terraform crash type safety validations
lookup() Safe map access environment → values
regex() Pattern match AMI ID, naming rules
alltrue() All checks must be true list validation
anytrue() At least one true OR logic
startswith() / endswith() Prefix/suffix checks naming standards
length() Count elements non-empty lists
for expressions Loop inside validation complex rules
1. Environment Variable With Default + Validation
✔ Default is dev
✔ Only dev, stage, prod allowed
🚀 2. EC2 Instance Variable With Default + Validation (per environment)
Allowed instance types per env:
-
dev: t3.micro, t3.small
-
stage: t3.medium, t3.large
-
prod: m5.large, m5.xlarge
What this does:
✔ Default EC2 instance type = t3.micro
✔ Automatically valid for default environment = dev
✔ If environment = prod, user MUST choose m5.large or m5.xlarge
✔ Prevents invalid instance types in prod
🚀 3. ALB Security Group Ingress Ports — Default + Validation
Only 443 and 8443 are allowed.
What this does:
✔ Default port = 443 (industry standard)
✔ If user adds 80, Terraform will throw an error
🚀 4. Optional: Local Block (Allowed Instance Matrix)
(Makes code even cleaner)
Then replace the long validation condition with:
Much cleaner. 💎
⭐ Final Version (All Variables Together)
🎯 You Now Have:
✅ Default values
✅ Complete environment-based EC2 validation
✅ Complete ALB port validation
✅ Cloud-ready variable structure
1. Advanced Validation — EC2 Instance Types by Environment
We now validate:
Only dev can use small instance types
Stage has medium
Prod has large
Also enforce instance type family (t3, m5, c6i, etc.)
Default value included
Variables
variable “env” {
description = “Environment name”
type = string
default = “dev”
validation {
condition = contains([“dev”, “stage”, “prod”], var.env)
error_message = “Environment must be one of: dev, stage, prod.”
}
}
EC2 Instance Type with Advanced Validation
variable “ec2_instance_type” {
description = “EC2 instance type per environment”
type = string
default = “t3.micro”
validation {
condition = (
(
var.env == “dev” &&
contains([“t2.micro”, “t3.micro”, “t3.small”], var.ec2_instance_type)
)
||
(
var.env == “stage” &&
contains([“t3.medium”, “t3.large”, “m5.large”], var.ec2_instance_type)
)
||
(
var.env == “prod” &&
contains([“m5.large”, “m5.xlarge”, “c6i.large”, “c6i.xlarge”], var.ec2_instance_type)
)
)
error_message = “Invalid EC2 type for environment.
DEV allowed: t2.micro, t3.micro, t3.small
STAGE allowed: t3.medium, t3.large, m5.large
PROD allowed: m5.large, m5.xlarge, c6i.large, c6i.xlarge.”
}
}
✅ 2. Advanced Validation — ALB SG Allowed Ports
Now we’ll enforce:
Only 443, 8443 allowed for ALB
Validate list of ports
Default ports included
Ensure numeric ranges and type safety with can()
Variable
variable “alb_allowed_ports” {
description = “Security group ports for ALB ingress”
type = list(number)
default = [443, 8443]
validation {
condition = length([
for p in var.alb_allowed_ports : p if !(contains([443, 8443], p))
]) == 0
error_message = “ALB can only allow ports 443 and 8443.”
}
}
✔ This ensures every element is valid
✔ Invalid ports instantly fail
🔥 Advanced: Port Range + Protocol Validation
A more complex version checking:
Only valid TCP ports
Must be within range
Must be integers
variable “alb_ingress_rules” {
description = “ALB ingress rules with port and protocol”
type = list(object({
port = number
protocol = string
}))
default = [
{ port = 443, protocol = “tcp” },
{ port = 8443, protocol = “tcp” }
]
validation {
condition = alltrue([
for rule in var.alb_ingress_rules :
(
can(rule.port)
&& rule.port >= 1
&& rule.port <= 65535
&& contains([443, 8443], rule.port)
&& lower(rule.protocol) == “tcp”
)
])
error_message = “Each ALB ingress rule must use port 443 or 8443 and protocol must be TCP.”
}
}
✅ 3. Advanced Validation — VPC Endpoint Types
Validate:
Interface endpoints only allowed for: s3, logs, secretsmanager, sqs
Gateway endpoints only for s3 & dynamodb
Variable
variable “vpc_endpoint_service” {
description = “AWS service for VPC endpoint”
type = string
default = “s3”
validation {
condition = (
(var.vpc_endpoint_service == “s3” || var.vpc_endpoint_service == “dynamodb”)
||
contains([
“logs”,
“secretsmanager”,
“sqs”,
“kms”,
“ec2messages”
], var.vpc_endpoint_service)
)
error_message = “Invalid VPC endpoint service. Gateway allowed: s3, dynamodb.
Interface allowed: logs, secretsmanager, sqs, kms, ec2messages.”
}
}
🔥 Advanced Cross-Validation: Endpoint Type + Service
E.g.,
if type = gateway → only s3 or dynamodb
if type = interface → others allowed
variable “vpc_endpoint_type” {
type = string
default = “Interface”
validation {
condition = contains([“Gateway”, “Interface”], var.vpc_endpoint_type)
error_message = “vpc_endpoint_type must be Gateway or Interface.”
}
}
variable “vpc_endpoint_service” {
type = string
default = “s3”
validation {
condition = (
(var.vpc_endpoint_type == “Gateway” &&
contains([“s3”, “dynamodb”], var.vpc_endpoint_service))
||
(var.vpc_endpoint_type == “Interface” &&
contains([“logs”, “secretsmanager”, “sqs”, “kms”], var.vpc_endpoint_service))
)
error_message = “Service does not match endpoint type.”
}
}
✅ 4. Advanced Validation — EFS & EBS Configuration
We enforce:
EFS throughput must be one of valid options
EBS volume size must be between 8–16384
EBS type must match allowed family
EBS validation
variable “ebs_volume” {
type = object({
size = number
type = string
})
default = {
size = 20
type = “gp3”
}
validation {
condition = (
var.ebs_volume.size >= 8 &&
var.ebs_volume.size <= 16384 &&
contains([“gp2”, “gp3”, “io1”, “sc1”, “st1”], var.ebs_volume.type)
)
error_message = “EBS size must be 8–16384 GiB and type must be one of gp2,gp3,io1,sc1,st1.”
}
}
EFS Validation
variable “efs_settings” {
type = object({
performance_mode = string
throughput_mode = string
})
default = {
performance_mode = “generalPurpose”
throughput_mode = “bursting”
}
validation {
condition = (
contains([“generalPurpose”, “maxIO”], var.efs_settings.performance_mode) &&
contains([“bursting”, “provisioned”, “elastic”], var.efs_settings.throughput_mode)
)
error_message = “Invalid EFS configuration. Supported performance: generalPurpose, maxIO.
Throughput: bursting, provisioned, elastic.”
}
}
🟦 5. OUTPUTS + LOCALS
Now we expose validated values.
Locals Example
locals {
selected_instance = var.ec2_instance_type
alb_ports = var.alb_allowed_ports
endpoint_service_name = var.vpc_endpoint_service
}
Outputs
output “instance_type” {
value = local.selected_instance
}
output “alb_ports” {
value = local.alb_ports
}
output “endpoint_service” {
value = local.endpoint_service_name
}