In this article, you’ll get to learn how to use dynamic block in Terraform (by HashiCorp). When using Terraform you need to have a thorough understanding of a variety of capabilities that help us make our infrastructure as code more dynamic. With that in mind, we will discover how to create a configuration that includes repeating building pieces for a certain setup.
If you’ve used Terraform Count before, you are aware of its limits. If you want to know visit the following link as we have discussed before in this article ⇒ How to use Terraform count and for_each. Now, let’s get into our subject.
Terraform Dynamic
As we previously discussed, a FOR expression and the Count keyword might be used to compare the Terraform Dynamic.
Compared to all Terraform expressions, dynamic blocks are less frequently used and less widely known, but they are also more potent. They were developed to assist us with a few issues related to dynamically building nested configuration blocks in Terraform.
Prerequisites
Use of Dynamic Block in Terraform?
Only when repeated configuration blocks are supported and other blocks are supported may dynamic blocks be used (surprisingly, they are not that common). But there are times when Terraform Dynamic blocks come in handy, such as when we’re setting rules or tags for a security group.
In situations where we need to repeat a nested block given a List, Set, or Map, the Dynamic block is helpful.
Dynamic Block in Terraform Syntax
Syntax:
dynamic "example_config" {
for_each = VARIABLE_NAME # any of => set | map | list
content = {
key = example_config.value
}
}
Here, the example_config is the name for the variable that will contain the value of each “iteration”. And the VARIABLE_NAME could be a Set, Map, or List to iterate over.
What will be produced after each repetition will be inside the content block. Use example_config.key and example_config.value from the content block to get the key and value of the current item from the iteration in the VARIABLE_NAME.
List and Map
In using for_each with a list, keep in mind that the key is the index and the value is the item in the list. The key and value will be one of the key-value pairs in the map if we use the map with for_each, which is different.
As a result, we may build inline blocks using Terraform Dynamic from the following Syntax. For now, let’s examine some actual instances and discuss how to use them.
Example: Dynamic Block in Terraform
For example, we need to configure some tags for an EC2 instance. And we would like to make it dynamically according to our VARIABLE_NAME.
data "aws_ami" "example_instance" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-22.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"] # Canonical
}
resource "aws_instance" "cloudyfox_server" {
ami = data.aws_ami.example_instance.id
instance_type = "t2.micro"
tags = {
Project = "Hello Cloudyfox-ian!"
}
dynamic "tag" {
for_each = var.append_custom_tags
content {
key = tag.key
value = tag.value
}
}
}
So, when we run the terraform plan, the following output will be seen:
Terraform will perform the following actions:
# aws_instance.server will be updated in-place
~ resource "aws_instance" "cloudyfox_server" {
tag {
key = "Project"
value = "Hello Cloudyfox-ian!"
}
+ tag {
+ key = "Environment"
+ value = "Production"
}
+ tag {
+ key = "Terraform"
+ value = "true"
}
}
Plan: 0 to add, 1 to change, 0 to destroy.
If the append_custom_tags is empty in our example above, Terraform will add only one tag, “Project = Hello Cloudyfox-ian!”.
Combine for_each with the “for” expression
Inside the Terraform Dynamic Block, we can combine for_each with for.
dynamic "tag" {
for_each = {
for key, value in var.append_custom_tags:
key => lower(value)
if key != "Project"
}
content {
key = tag.key
value = tag.value
}
}
The nested for expression loops over append_custom_tags transforms each value to lowercase and, at the same time, evaluates a conditional in the for expression to skip the key equal Project. But, you can implement any logic here to meet your requirements.
Let’s another example where we need to create a kubernetes_deployment and give all environment variables (var.env_vars) using a single variable (list).
resource "kubernetes_deployment" "cloudyfox_server" {
metadata {
name = "Terraform Tutorial - How to use Terraform Dynamic"
labels = {
test = "Cloudyfox"
}
}
spec {
replicas = 2
selector {
match_labels = {
test = "Cloudyfox"
}
}
template {
metadata {
labels = {
test = "Cloudyfox"
}
}
spec {
container {
image = "nginx:1.21.6"
name = "example"
dynamic "env" {
for_each = var.env_vars
content {
name = env.key
value = env.value
}
}
resources {
limits = {
cpu = "0.5"
memory = "512Mi"
}
requests = {
cpu = "250m"
memory = "50Mi"
}
}
liveness_probe {
http_get {
path = "/"
port = 443
http_header {
name = "X-Custom-Header"
value = "Awesome Example!"
}
}
}
}
}
}
}
}
Classical Example: Security Group
Let’s see how we define Security without any FOR:
resource "aws_security_group" "demo_sg" {
name = "sample-sg"
ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 21
to_port = 21
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
It looks like we have too many lines, right? However, the example above turns really simple once we know how to implement the same code using Dynamic block in Terraform.
variable "sg_ports" {
type = list(number)
description = "list of ingress ports"
default = [8080, 80,21, 22, 443]
}
resource "aws_security_group" "dynamicsg" {
name = "dynamic-sg"
description = "Ingress for Vault"
dynamic "ingress" {
for_each = var.sg_ports
iterator = port
content {
from_port = port.value
to_port = port.value
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
dynamic "egress" {
for_each = var.sg_ports
content {
from_port = egress.value
to_port = egress.value
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
}
Looks better now!
When not using Dynamic block in Terraform
We can solve a number of problems with the use of dynamic blocks. Do not, however, use them excessively as this will make our code harder to understand.
Additionally, only arguments from the same resource type, data source, provider, or provisioner that are defined can be created by a dynamic block. Because Terraform must process blocks like “lifecycle” and “provisioner” before it is ready to evaluate expressions, it is impossible to create meta-argument blocks like these.
That’s it!
Conclusion
In this article, we went through how infrastructure configurations are performed dynamically using dynamic block in Terraform.
Also checkout: Important and useful tips and cheatsheets on Terraform CLI.
Thank you!