What is Dynamic Blocks in Terraform: A Step-by-Step Guide
The Dynamic Block in Terraform is one of the most useful options for making your code dynamic. Previously, we could use dynamic conditions like ‘for_each’ and ‘if’ conditions at the resource block level. However, with the dynamic block, we can also apply the same logic to resource sub-blocks or nested blocks, such as ‘Identity’ in an Azure SQL Server resource block.
The dynamic block is very useful in a couple of scenarios for creating error-free, dynamic code that can iterate based on the variable values you are passing, such as:
Scenario 1:
Let’s assume there is a scenario where you want to enable the system-managed identity for a resource, such as Azure SQL Server, only if the variable ‘managed_identity_enable’ is set to true. Otherwise, it should not enable the managed identity. Let’s see how you can achieve this.
First, let’s understand the resource block for Azure SQL Server:
This is the basic Terraform resource block for creating an Azure SQL Server.
resource "azurerm_resource_group" "sqlrg" {
name = "sql-rg"
location = "westeurope"
}
resource "azurerm_mssql_server" "sqlserver" {
name = "sqlserver5474"
resource_group_name = azurerm_resource_group.sqlrg.name
location = azurerm_resource_group.sqlrg.location
version = "12.0"
administrator_login = "missadministrator"
administrator_login_password = "thisIsKat11"
}
Now, let’s say we need to enable the managed identity. In that case, we will add the ‘Identity’ nested block as shown below:
resource "azurerm_resource_group" "sqlrg" {
name = "sql-rg"
location = "westeurope"
}
resource "azurerm_mssql_server" "sqlserver" {
name = "sqlserver5474"
resource_group_name = azurerm_resource_group.sqlrg.name
location = azurerm_resource_group.sqlrg.location
version = "12.0"
administrator_login = "missadministrator"
administrator_login_password = "thisIsKat11"
identity {
type = "SystemAssigned"
}
}
Now, I would like to make this ‘Identity’ block dynamic based on my requirements, enabling it only when the variable ‘managed_identity_enable’ is set to true. Let’s see how we can achieve this using a Dynamic Block.
First, let’s declare a variable named ‘managed_identity_enable’ with the type ‘boolean’.
variable "managed_identity_enable" {
type = bool
description = "Set to true for creating a managed identity in the Azure SQL Server. Default is false."
}
Let’s configure the Dynamic block for ‘Identity’:
resource "azurerm_mssql_server" "sqlserver" {
name = "sqlserver5474"
resource_group_name = azurerm_resource_group.sqlrg.name
location = azurerm_resource_group.sqlrg.location
version = "12.0"
administrator_login = "missadministrator"
administrator_login_password = "thisIsKat11"
dynamic "identity" {
for_each = var.managed_identity_enable == true ? [1] : [ ]
content {
type = "SystemAssigned"
}
}
}
Now, let me explain the logic of using the dynamic block:
- First you need to replace the “identity” with dynamic “identity” to let Terraform knows that this will be used dynamically.
- Next, configure the conditional expression. Let me break down the one we’ve used.:
for_each = var.managed_identity_enable == true ? [1] : [ ]
This conditional expression is a combination of ‘for_each’ loop with ‘if’ condition.
if_condition — “var.managed_identity_enable == true ? [1] : [ ]”
‘?’ operator signifies that the condition is true, and it returns ‘for_each = [1]’ (a single element).
‘:’ operator represents the else case, which means the condition is false, and it returns ‘for_each = []’ (an empty list).
‘for_each’ — Then, it executes the dynamic block for each element in the list. If ‘for_each’ is set to [1], it will execute the dynamic block; if it’s an empty list, it will skip the block.
Now, Let’s see dynamic block in action:
If var.managed_identity_enable is false, then-
If var.managed_identity_enable is true (we didn’t make any other changes in the code), then-
In the example above, you could see how the nested block dynamically works based on the value of the ‘managed_identity_enable’ variable without requiring any other changes in the code.
Scenario 2:
Another scenario, and one of my favorites, there are some Azure Resource Blocks in Terraform that have nested blocks that can be defined multiple times, such as the ‘subnet’ nested block in the ‘Virtual Network’ resource block.
In this type of scenario, we can use Dynamic blocks with a ‘for_each’ loop to extract the values for Nested Blocks from a variable of ‘map’ type, thus avoiding the need for repetitive and complex code.
Let’s explore how to use Dynamic blocks in this scenario:
First, declare a variable named ‘network_subnets’ with the type ‘map’ and a default value for the subnet (we are assigning a default value here just to keep it simple; in an actual scenario, you would typically use a terraform.tfvars file or pass the values through a pipeline.)
variable "network_subnets" {
description = "Map of subscriptions."
type = map(string)
default = {
subnet1 = "10.0.1.0/24"
subnet2 = "10.0.2.0/24"
subnet3 = "10.0.3.0/24"
}
}
Now, let’s define the logic for the Dynamic block in the Virtual Network resource block.
variable "network_subnets" {
description = "Map of subscriptions."
type = map(string)
default = {
subnet1 = "10.0.1.0/24"
subnet2 = "10.0.2.0/24"
subnet3 = "10.0.3.0/24"
}
}
resource "azurerm_resource_group" "vnetrg" {
name = "vnet-rg"
location = "westeurope"
}
resource "azurerm_virtual_network" "vnet" {
name = "example-network"
location = azurerm_resource_group.vnetrg.location
resource_group_name = azurerm_resource_group.vnetrg.name
address_space = ["10.0.0.0/16"]
dynamic "subnet" {
for_each = var.network_subnets
content {
name = subnet.key
address_prefix = subnet.value
}
}
}
This code will create a new Virtual Network with 3 subnets defined in the ‘network_subnets’ variable.
Let me explain the logic of using the dynamic block in this scenario:
- In this Dynamic Block, only the ‘for_each’ condition is used, and we are passing a map variable with key-value pairs.
- Since we can define subnet nested blocks multiple times within the Virtual Network block, the ‘for_each’ loop here accomplishes the same based on the values provided in ‘network_subnets.’
- It takes the first value, for example, ‘subnet1 = “10.0.1.0/24,” and passes it through the content block, where we specify that the Subnet name should be the Key and the Address Prefix should be the value from the map variable. It does the same for the second, third, and subsequent values, using them as Subnet names and Address Prefixes.
If you have experience working with ‘for_each’ loops, you’ll notice a slight difference here. Typically, when using a ‘for_each’ loop, we assign values as ‘each.key’ and ‘each.value.’ However, in the dynamic block, we need to replace ‘each’ with the name of the block, which, in this case, is ‘subnet.’
Let’s see this in action:
Using the example provided, it will create three subnets: ‘subnet1,’ ‘subnet2,’ and ‘subnet3.’
Now, let’s add a couple more values to the ‘network_subnets’ variable without making any other changes to the code.
Let’s see the impact of this change now.
Now, it will dynamically create the 5 subnets defined in the ‘network_subnets’ variable.
Scenario 3
Continuing from Scenario 2, let’s assume you need to pass security group values as well in the subnet nested block. However, in the variable, you can only define key-value pairs. How can you pass three or more values using a Dynamic block in the nested block? Something like this:
Then, let’s see how you can pass these values through a map and dynamic block
Firstly, now the network_subnets variable will not be the map of string but map of map of string (I know it sounds weird but this is what it is :D), like below.
First, note that the ‘network_subnets’ variable will no longer be a map of strings but a map of maps of strings (I know it sounds weird but this is what it is :D), let’s have a look:
variable "network_subnets" {
description = "Map of subscriptions."
type = map(map(string))
default = {
subnet1 = {
address_prefix = "10.0.1.0/24"
security_group = "/subscriptions/xxxxx/resourceGroups/vnetrg/providers/Microsoft.Network/networkSecurityGroups/subnet1-nsg"
}
subnet2 = {
address_prefix = "10.0.2.0/24"
security_group = "/subscriptions/xxxxx/resourceGroups/vnetrg/providers/Microsoft.Network/networkSecurityGroups/subnet2-nsg"
}
subnet3 = {
address_prefix = "10.0.3.0/24"
security_group = "/subscriptions/xxxxx/resourceGroups/vnetrg/providers/Microsoft.Network/networkSecurityGroups/subnet3-nsg"
}
}
}
Let’s see how it will work with Dynamic block now.
variable "network_subnets" {
description = "Map of subscriptions."
type = map(map(string))
default = {
subnet1 = {
address_prefix = "10.0.1.0/24"
security_group = "/subscriptions/xxxxx/resourceGroups/vnetrg/providers/Microsoft.Network/networkSecurityGroups/subnet1-nsg"
}
subnet2 = {
address_prefix = "10.0.2.0/24"
security_group = "/subscriptions/xxxxx/resourceGroups/vnetrg/providers/Microsoft.Network/networkSecurityGroups/subnet2-nsg"
}
subnet3 = {
address_prefix = "10.0.3.0/24"
security_group = "/subscriptions/xxxxx/resourceGroups/vnetrg/providers/Microsoft.Network/networkSecurityGroups/subnet3-nsg"
}
}
}
resource "azurerm_resource_group" "vnetrg" {
name = "vnet-rg"
location = "westeurope"
}
resource "azurerm_virtual_network" "vnet" {
name = "example-network"
location = azurerm_resource_group.vnetrg.location
resource_group_name = azurerm_resource_group.vnetrg.name
address_space = ["10.0.0.0/16"]
dynamic "subnet" {
for_each = var.network_subnets
content {
name = subnet.key
address_prefix = subnet.value["address_prefix"]
security_group = subnet.value["security_group"]
}
}
}
The dynamic block will be the same as in Scenario 2, with the only difference being the way values are passed. In this approach, you can specify the keys for the subnet’s value block in the ‘network_subnets’ variable, such as ‘address_prefix’ and ‘security_group.’
The output will look like this:
That’s all about the Dynamic block and its various use cases.
Hope it clears all of your doubts and now you understand the Dynamic block and it’s logic better.
I hope this has clarified any doubts you may have had, and now you have a better understanding of the Dynamic block and its logic.