OpenTofu: A Beginners Guide to a Terraform Fork Including Migration From Terraform
OpenTofu Functions and Conditional Expressions
Dynamic Blocks and Splat Expressions
In this guide, we’ll explore how to streamline repetitive Terraform configurations in OpenTofu using dynamic blocks and splat expressions. You’ll learn to replace verbose nested blocks with a DRY, scalable approach, and extract attributes efficiently from generated resources.
Looping with count
and for_each
Traditionally, you can create multiple resources by using the count
or for_each
arguments:
resource "aws_instance" "backend" {
ami = var.ami
instance_type = var.instance_type
count = length(var.backend_servers)
tags = {
Name = var.backend_servers[count.index]
}
}
variable "ami" {
default = "ami-06178cf087598769c"
}
variable "instance_type" {
default = "m5.large"
}
variable "backend_servers" {
type = list(string)
default = ["server1", "server2"]
}
Here, two EC2 instances (server1
and server2
) are instantiated by leveraging count
.
Building a VPC, Subnet, and Security Group
Let’s set up:
- A new VPC
- A private subnet
- A security group allowing SSH (port 22) and HTTP (port 8080)
A VPC provides an isolated network (10.0.0.0/16
), and the subnet uses 10.0.2.0/24
. The security group acts as a virtual firewall.
First, declare the VPC and subnet:
resource "aws_vpc" "backend_vpc" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "backend-vpc"
}
}
resource "aws_subnet" "private_subnet" {
vpc_id = aws_vpc.backend_vpc.id
cidr_block = "10.0.2.0/24"
tags = {
Name = "private-subnet"
}
}
Next, define a security group with two hard-coded ingress
blocks:
resource "aws_security_group" "backend_sg" {
name = "backend-sg"
vpc_id = aws_vpc.backend_vpc.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
Adding more ports would require additional nested ingress
blocks, quickly becoming repetitive.
Simplifying with Dynamic Blocks
With a dynamic block, you can loop over a list of ports and generate as many ingress
entries as needed.
Declare an input variable for ports:
variable "ingress_ports" {
type = list(number)
default = [22, 8080]
}
Replace the static blocks with one dynamic block:
resource "aws_security_group" "backend_sg" {
name = "backend-sg"
vpc_id = aws_vpc.backend_vpc.id
dynamic "ingress" {
for_each = var.ingress_ports
content {
from_port = ingress.value
to_port = ingress.value
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
}
Custom Iterator Name
You can rename the default iterator (ingress
) to anything meaningful.
Example:
dynamic "ingress" {
iterator = port
for_each = var.ingress_ports
content {
from_port = port.value
to_port = port.value
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
Splat Expressions
After generating multiple ingress rules, you might want to output all to_port
values at once. Use a splat expression:
output "to_ports" {
value = aws_security_group.backend_sg.ingress[*].to_port
}
Splat Expression Caveat
Be aware that splat expressions return a list. If your security group has no ingress
rules, you’ll get an empty list rather than a single value.
Compare Approaches
Approach | Description | Pros |
---|---|---|
Static Blocks | Individual ingress blocks for each port | Simple for few ports |
Dynamic Blocks | One block looping over var.ingress_ports | DRY, maintainable |
Splat Expressions | Extracts list of attributes from resources | Concise outputs |
Apply and Inspect
Execute your plan:
$ tofu apply --auto-approve
aws_vpc.backend_vpc: Creating...
aws_vpc.backend_vpc: Creation complete after 0s [id=vpc-593470c0]
aws_subnet.private_subnet: Creating...
aws_security_group.backend_sg: Creating...
aws_subnet.private_subnet: Creation complete after 1s [id=subnet-fdd6b762]
aws_security_group.backend_sg: Creation complete after 1s [id=sg-a5aa3b711157d4a2b]
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Retrieve the generated ports:
$ tofu output
to_ports = [
22,
8080,
]
By leveraging dynamic blocks and splat expressions, your OpenTofu configurations become more expressive, concise, and easier to maintain.
References
Watch Video
Watch video content