DEV Community

Cover image for Day 10— Terraform Conditional Expressions, Dynamic Blocks and Splat Expressions
Anil KUMAR
Anil KUMAR

Posted on

Day 10— Terraform Conditional Expressions, Dynamic Blocks and Splat Expressions

Today marks the Day 10 of 30 Days of Terraform challenge and in this blog, we will deep dive into the Terraform Conditional Expressions, Dynamic Blocks and Splat Expressions.

Terraform Expressions are something that helps us to avoid writing the code again and again. You can think this of a replacement or similar to functions in programming language. There are mainly 3 types of Expressions in Terrform such as Conditional Expressions, Dynamic Blocks and Splat Expressions

Conditional Expressions:

Conditional Expressions as the name indicates evaluates a condition and returns one of two values based on whether the condition is true or false.

Syntax

condition ? true_value : false_value
Enter fullscreen mode Exit fullscreen mode

In the above conditional expression:
If condition is true, returns true_value
If condition is false, returns false_value

Use Cases:

✅ Choose instance types based on environment (dev vs prod)
✅ Enable/disable monitoring based on configuration
✅ Select different AMIs based on region
✅ Set different resource counts for environments
✅ Apply environment-specific tags

Benefits:

✅ Single configuration for multiple environments
✅ Reduces code duplication
✅ Makes environment differences explicit
✅ Simplifies configuration management
✅ Easy to understand and maintain

When to use:

✅ Environment-specific configurations
✅ Feature flags (enable/disable features)
✅ Conditional resource creation
✅ Region-specific settings
✅ Cost optimization (smaller resources in dev)

When NOT to use:

❌ Complex logic with many conditions (use locals instead)
❌ When separate environment files are clearer
❌ When all environments should be identical

resource "aws_instance" "conditional_example" {
  ami           = "ami-28765345876"
  instance_type = var.environment == "prod" ? "t3.large" : "t2.micro"

  tags = {
    Name = "conditional-instance-${var.environment}"
  }
}
Enter fullscreen mode Exit fullscreen mode
terraform plan
data.aws_ami.amazon_linux: Reading...
data.aws_ami.amazon_linux: Read complete after 1s [id=ami-02610f36df0c59544]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated
with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.conditional_example will be created
  + resource "aws_instance" "conditional_example" {
      + ami                                  = "ami-02610f36df0c59544"
      + arn                                  = (known after apply)
      + associate_public_ip_address          = (known after apply)
      + availability_zone                    = (known after apply)
      + disable_api_stop                     = (known after apply)
      + disable_api_termination              = (known after apply)
      + ebs_optimized                        = (known after apply)
      + enable_primary_ipv6                  = (known after apply)
      + force_destroy                        = false
      + get_password_data                    = false
      + host_id                              = (known after apply)
      + host_resource_group_arn              = (known after apply)
      + iam_instance_profile                 = (known after apply)
      + id                                   = (known after apply)
      + instance_initiated_shutdown_behavior = (known after apply)
      + instance_lifecycle                   = (known after apply)
      + instance_state                       = (known after apply)
      + instance_type                        = "t2.micro"
Enter fullscreen mode Exit fullscreen mode

In the above code block, we can see if the environment is set to prod, t3.large will be selected for instance_type else t2.micro will be selected. As we have not defined anything, t2.micro is selected above.

2. Dynamic Block:

Dynamic Blocks generates multiple nested blocks within a resource based on a collection (list or map). Eliminates the need to repeat similar block configurations. In simple words, it helps us to write the nested block with multiple values.

Syntax:

dynamic "block_name" {
    for_each = var.collection
    content {
        # Block configuration using each.key and each.value
    }
}
Enter fullscreen mode Exit fullscreen mode

In the above block,
for_each iterates over a list or map
content defines what each block should contain
Access values using block_name.value or block_name.key

Use Cases:

✅ Security group ingress/egress rules
✅ Multiple EBS volumes on EC2 instances
✅ IAM policy statements
✅ Load balancer listeners
✅ Route table routes
✅ Any repeating nested block structure

Benefits:

✅ Eliminates repetitive code
✅ Easy to add/remove items
✅ Configuration through variables
✅ Cleaner, more maintainable code
✅ Supports complex data structures

When to use:

✅ Multiple similar nested blocks
✅ Variable number of configurations
✅ Security group rules
✅ Inline policies
✅ Any repeating block pattern

When NOT to use:

❌ Single or few static blocks (overhead not worth it)
❌ When it makes code harder to read
❌ For top-level resources (use count or for_each instead)

resource "aws_security_group" "dynamic_sg" {
  name        = "dynamic-sg-${var.environment}"
  description = "Security group with dynamic rules"

  # Dynamic block creates multiple ingress rules from a list
  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      from_port   = ingress.value.from_port
      to_port     = ingress.value.to_port
      protocol    = ingress.value.protocol
      cidr_blocks = ingress.value.cidr_blocks
      description = ingress.value.description
    }
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "dynamic-sg-${var.environment}"
  }
}

variable "ingress_rules" {
  description = "List of ingress rules for security group"
  type = list(object({
    from_port   = number
    to_port     = number
    protocol    = string
    cidr_blocks = list(string)
    description = string
  }))
  default = [
    {
      from_port   = 80
      to_port     = 80
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
      description = "HTTP"
    },
    {
      from_port   = 443
      to_port     = 443
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
      description = "HTTPS"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

In the above block, in the variable.tf we have added 2 rules like port 80 and port 443 but in main.tf for ingress security group rule we have used dynamic block which will iterate through those 2 rules and set them, no need to repeat the whole sequence.

 # aws_security_group.dynamic_sg will be created
  + resource "aws_security_group" "dynamic_sg" {
      + arn                    = (known after apply)
      + description            = "Security group with dynamic rules"
      + egress                 = [
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + from_port        = 0
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "-1"
              + security_groups  = []
              + self             = false
              + to_port          = 0
                # (1 unchanged attribute hidden)
            },
        ]
      + id                     = (known after apply)
      + ingress                = [
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = "HTTP"
              + from_port        = 80
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 80
            },
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = "HTTPS"
              + from_port        = 443
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 443
            },
        ]
      + name                   = "dynamic-sg-dev"
      + name_prefix            = (known after apply)
      + owner_id               = (known after apply)
      + region                 = "us-west-1"
      + revoke_rules_on_delete = false
      + tags                   = {
          + "Name" = "dynamic-sg-dev"
        }
      + tags_all               = {
          + "Name" = "dynamic-sg-dev"
        }
      + vpc_id                 = (known after apply)
    }
Enter fullscreen mode Exit fullscreen mode

You could see in the above block that security group is getting created with 2 ingress rules.

3. Splat Expressions:

Splat Expression extracts attribute values from all elements in a list in a single, concise expression. The [*] operator is the splat operator. It is a one-liner which helps us to retrieve multiple values within just single line using star operator.

For example, you have created an EC2 instance, when you used this Splat Expression, it will retrieve all the metadata and information of that EC2 instance like instance_id, private_ip, public_ip, associated vpc, subnet, security_group... From that you can retrieve that specific thing.

Syntax

resource_list[*].attribute_name
Enter fullscreen mode Exit fullscreen mode

How it works:

Takes a list of resources/objects
Extracts specified attribute from each element
Returns a new list with just those values

Use Cases:

✅ Get all instance IDs from multiple EC2 instances
✅ Extract all subnet IDs from a VPC
✅ Collect all security group IDs
✅ Get private IPs from instance list
✅ Extract ARNs for outputs
✅ Gather resource attributes for other resources

resource "aws_instance" "server" {
  count         = 3
  ami           = "ami-0abcd12345"
  instance_type = "t2.micro"

  tags = {
    Name = "server-${count.index}"
  }
}

output "public_ips" {
  value = aws_instance.server[*].public_ip
}
Enter fullscreen mode Exit fullscreen mode

After running terraform plan, you can see public_ip as below, if you have not specifically public_ip, you will be flooded with all that EC2 instance information.

Changes to Outputs:
  + public_ips = [
      + (known after apply),
      + (known after apply),
      + (known after apply),
    ]
Enter fullscreen mode Exit fullscreen mode

Conclusion:

Terraform expressions like Conditional Expressions, Dynamic Blocks, and Splat Expressions are incredibly useful tools for simplifying your infrastructure as code. By leveraging these features, you can write more flexible, reusable, and efficient Terraform configurations. Whether you’re managing complex security groups, deploying resources in multiple environments, or simply retrieving multiple values at once, these expressions can help make your infrastructure code cleaner and more maintainable.

Below is the youtube video for reference:

Top comments (0)