In today’s blog post, we will discuss expressions in terraform. Terraform supports conditional, for, and splat expressions which help you to add conditional logic (if else conditions), loop through data using for expression, retrieve list items using splat expressions.
Conditional Expressions
A conditional expression in Terraform allows you to select one of two values based on the evaluation of a boolean condition. This is similar to the ternary operator found in many programming languages.
Syntax:
condition ? true_val : false_val
condition
: Any expression that evaluates to a boolean (true
orfalse
).true_val
: The value returned if thecondition
istrue
.false_val
: The value returned if thecondition
isfalse
.
Explanation: If the condition
is true, the result of the expression is true_val
. If the condition
is false, the result is false_val
. Terraform attempts automatic type conversions if true_val
and false_val
are of different types.
Example 1: Defining a Default Value
variable "instance_type" {
description = "Type of the EC2 instance."
type = string
default = ""
}
resource "aws_instance" "example" {
ami = "ami-0abcdef1234567890"
instance_type = var.instance_type == "" ? "t2.micro" : var.instance_type
tags = {
Name = "MyInstance"
}
}
Explanation of the code: In this example, if the instance_type
variable is an empty string, the aws_instance
will be created with t2.micro
. Otherwise, it will use the value provided in var.instance_type
.
Example 2: Conditionally Setting a Tag
variable "environment" {
description = "Deployment environment (e.g., 'prod', 'dev')."
type = string
}
resource "aws_s3_bucket" "my_bucket" {
bucket = "my-unique-bucket-${var.environment}"
tags = {
Environment = var.environment == "prod" ? "Production" : "Development"
ManagedBy = "Terraform"
}
}
Explanation of the code: Here, the Environment
tag for the S3 bucket is set to “Production” if var.environment
is “prod”, otherwise it’s set to “Development”.
Common Use Cases:
- Setting default values for variables or attributes.
- Conditionally enabling or disabling features or resources (
count
argument). - Selecting resource attributes based on environment (e.g., development vs. production).
- Dynamically adjusting resource configurations based on input.
For Expressions
For
expressions in Terraform allow you to construct complex type values (lists, sets, tuples, or objects/maps) by transforming elements from another complex type value. This is powerful for data manipulation and dynamic configuration generation.
Syntax:
# For list/set/tuple comprehension
[for element in collection : expression]
# For object/map comprehension
{for key, value in collection : new_key => new_value}
List Comprehension
List comprehension creates a new tuple (which behaves like a list) by iterating over each element of a given collection and applying an expression to it.
Example 1: Transforming a List of Strings
variable "instance_names" {
description = "A list of instance names."
type = list(string)
default = ["webserver", "appserver", "dbserver"]
}
output "uppercase_instance_names" {
value = [for name in var.instance_names : upper(name)]
}
Explanation of the code: This for
expression iterates through var.instance_names
and transforms each name to uppercase, resulting in an output like ["WEBSERVER", "APPSERVER", "DBSERVER"]
.
Example 2: Filtering Elements from a List
variable "all_users" {
type = list(string)
default = ["alice", "bob", "charlie-admin", "diana-admin"]
}
output "admin_users" {
value = [for user in var.all_users : user if contains(user, "-admin")]
}
Explanation of the code: This example filters the all_users
list, including only those names that contain “-admin”, producing ["charlie-admin", "diana-admin"]
. The if
clause acts as a filter.
Map Comprehension
Map comprehension creates a new object (which behaves like a map) by iterating over a collection and constructing key-value pairs from each element.
Example 1: Creating a Map from a List
variable "users" {
description = "A list of user IDs."
type = list(string)
default = ["user123", "admin456", "guest789"]
}
output "user_roles" {
value = { for user in var.users : user => "read-only" }
}
Explanation of the code: This for
expression transforms the users
list into a map where each user ID is a key, and its corresponding value is “read-only”. The output would resemble:
{
"user123" = "read-only"
"admin456" = "read-only"
"guest789" = "read-only"
}
Example 2: Re-keying and Transforming a Map
locals {
instance_specs = {
"dev-web" = { ami = "ami-dev-1", type = "t2.micro" }
"prod-app" = { ami = "ami-prod-1", type = "t3.medium" }
}
}
output "instance_ami_map" {
value = { for name, spec in local.instance_specs : name => spec.ami }
}
Explanation of the code: This for
expression iterates over instance_specs
and creates a new map where the keys are the instance names and the values are their respective AMIs, resulting in:
{
"dev-web" = "ami-dev-1"
"prod-app" = "ami-prod-1"
}
Splat Expressions
Splat expressions provide a way for iterating over a list or set of complex objects and extracting a specific attribute from each of them. They are a shorthand for a common for
expression pattern.
Important Note: Splat expressions apply only to lists, sets, and tuples. For maps or objects, you must use for
expressions. Also, resources that use the for_each
argument will appear as a map of objects, so splat expressions cannot be used directly with them.
List Splat ([*]
)
The [*]
symbol iterates over all elements of the list given to its left and accesses the attribute name given on its right from each element. If a value is non-null, it transforms it into a single-element tuple. If the value is null, it returns an empty tuple.
Syntax:
list_of_objects[*].attribute_name
Example 1: Extracting IDs from a List of Objects
locals {
ec2_instances = [
{ id = "i-0a1b2c3d4e5f6a7b8", name = "web-01" },
{ id = "i-0c9d8e7f6a5b4c3d2", name = "app-01" },
{ id = "i-0g5h4i3j2k1l0m9n8", name = "db-01" },
]
}
output "instance_ids" {
value = local.ec2_instances[*].id
}
Explanation of the code: This splat expression extracts the id
attribute from each object in the ec2_instances
list, producing a list of IDs: ["i-0a1b2c3d4e5f6a7b8", "i-0c9d8e7f6a5b4c3d2", "i-0g5h4i3j2k1l0m9n8"]
.
Example 2: Accessing Nested Attributes
locals {
vpcs = [
{ name = "main", cidr = "10.0.0.0/16", tags = { env = "prod" } },
{ name = "dev", cidr = "10.1.0.0/16", tags = { env = "dev" } },
]
}
output "vpc_envs" {
value = local.vpcs[*].tags.env
}
Explanation of the code: This demonstrates accessing a nested attribute (env
within tags
) for each VPC, resulting in ["prod", "dev"]
.
Example 3: Extracting from Data Source Results
data "aws_security_groups" "web_sgs" {
tags = {
Purpose = "WebAccess"
}
}
output "web_sg_ids" {
value = data.aws_security_groups.web_sgs.ids[*]
}
Explanation of the code: This example uses a data source to fetch security groups tagged “WebAccess” and then uses a splat expression to easily extract a list of their IDs.
Attribute Splat (Legacy .*
)
Earlier versions of Terraform had a slightly different version of splat expressions using the .*
sequence. With the attribute-only splat expression, only the attribute lookups apply to each element of the input, and index operations are applied to the result of the iteration. While still supported for backward compatibility, [*]
is generally preferred for clarity.
Example (Legacy):
# Equivalent to local.vpcs[*].tags.env in modern splat
output "vpc_envs_legacy" {
value = local.vpcs.*.tags.env
}
Practical Use Cases
Expressions are fundamental to nearly every advanced Terraform configuration. Here are a few examples of how they can be combined:
Conditionally Create Resources: Use a conditional expression to decide whether to create a resource or module based on a boolean variable, leveraging the count
argument.
variable "enable_logging" {
description = "Whether to enable S3 logging bucket."
type = bool
default = true
}
resource "aws_s3_bucket" "log_bucket" {
count = var.enable_logging ? 1 : 0
bucket = "my-app-logs-${terraform.workspace}"
acl = "log-delivery-write"
}
Explanation: The log_bucket
will only be created if var.enable_logging
is true
.
Dynamically Generate Security Group Rules: Use for
expressions to create multiple security group ingress rules from a list of CIDR blocks, often combined with for_each
for resource iteration.
variable "allowed_web_cidrs" {
type = list(string)
default = ["192.168.1.0/24", "10.0.0.0/8"]
}
resource "aws_security_group" "web_sg" {
name = "web-access-sg"
description = "Allow web traffic"
vpc_id = "vpc-0abcdef1234567890"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = var.allowed_web_cidrs # This is a direct list assignment
}
# Creating individual rules with for_each for more granular control
dynamic "ingress" {
for_each = var.allowed_web_cidrs
content {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [ingress.value]
}
}
}
Explanation: The first ingress
block directly takes a list of CIDRs. The second dynamic "ingress"
block uses a for_each
loop with var.allowed_web_cidrs
to create separate HTTPS rules for each CIDR, demonstrating how for
can build blocks.
Extracting and Transforming Information from Data Sources: Use splat expressions to easily get specific attributes from a list of data source results, then potentially transform them with for
expressions.
data "aws_instances" "web_servers" {
instance_tags = {
Role = "webserver"
}
}
output "web_server_private_ips" {
value = data.aws_instances.web_servers.private_ips[*] # Splat to get list of IPs
}
output "web_server_id_to_ip_map" {
value = { for i, ip in data.aws_instances.web_servers.private_ips : data.aws_instances.web_servers.ids[i] => ip }
}
Explanation: web_server_private_ips
uses splat to get a list of all private IPs. web_server_id_to_ip_map
combines for
and data
source outputs to create a map of instance IDs to their private IPs.
Managing Multiple Environments with Conditionals and Maps: Using conditional logic to select values from a map based on the current workspace or variable.
variable "env_configs" {
type = map(object({
instance_count = number
vpc_cidr = string
}))
default = {
"dev" = {
instance_count = 1
vpc_cidr = "10.0.0.0/16"
},
"prod" = {
instance_count = 3
vpc_cidr = "10.10.0.0/16"
}
}
}
resource "aws_vpc" "app_vpc" {
cidr_block = var.env_configs[terraform.workspace].vpc_cidr
}
resource "aws_instance" "app_server" {
count = var.env_configs[terraform.workspace].instance_count
ami = "ami-0abcdef1234567890"
instance_type = terraform.workspace == "prod" ? "m5.large" : "t2.micro"
}
Explanation: This setup dynamically configures vpc_cidr
and instance_count
based on the current Terraform workspace by looking up values in the env_configs
map. It also conditionally sets the instance_type
for the app_server
based on the workspace.
Best Practices for Mastering Expressions
To write clear, maintainable, and effective Terraform configurations using expressions, consider these best practices:
- Readability First: While expressions can be concise, prioritize readability. Break down complex expressions into smaller, more manageable parts using local values. Overly long or nested expressions can be difficult to debug.
- Leverage Locals: For expressions used multiple times or those that are particularly complex, define them as
locals
. This improves reusability, makes your code DRY (Do not Repeat Yourself), and enhances readability by giving meaningful names to intermediate computations. - Type Consistency: Be mindful of the data types involved in your expressions. While Terraform performs some automatic conversions, explicit type conversions (
tolist()
,tomap()
,tostring()
,tonumber()
,toset()
) can prevent unexpected errors and make your intent clearer. - Validation Rules: Combine expressions with validation rules in input variables or outputs to ensure that the values provided to your modules are within expected bounds or formats, providing immediate feedback to users.
- Thorough Testing: Expressions can sometimes lead to subtle bugs, especially when dealing with complex logic or data transformations. Always test your configurations thoroughly, especially when using complex expressions, to ensure they produce the desired outcome across various scenarios. Use
terraform console
for quick testing of expressions. - Start Simple: If you are new to expressions, begin with basic conditional and
for
expressions before attempting more intricate scenarios or splat expressions. Gradually build complexity as your understanding grows.
Conclusion
Expressions are the backbone of dynamic and powerful Terraform configurations. By mastering conditional expressions for decision-making, for
expressions for data transformation, and splat expressions for concise attribute extraction, you unlock the full potential of Terraform. These tools enable you to write more flexible, scalable, and maintainable infrastructure as code, adapting to various environments and requirements with ease.
Related Items
- string https://ckdbtech.com/mastering-strings-in-terraform/
- lists https://ckdbtech.com/mastering-lists-in-terraform/
- maps https://ckdbtech.com/mastering-maps-in-terraform/
- sets https://ckdbtech.com/mastering-sets-in-terraform/
- operators https://ckdbtech.com/mastering-operators-in-terraform/
Author

Experienced Cloud & DevOps Engineer with hands-on experience in AWS, GCP, Terraform, Ansible, ELK, Docker, Git, GitLab, Python, PowerShell, Shell, and theoretical knowledge on Azure, Kubernetes & Jenkins. In my free time, I write blogs on ckdbtech.com