In todays blog, I am going to show you how to use the for_each argument in Terraform to loop over list of maps and list of objects!
List of Maps
First things first, we need to set the correct variable type, when working with a variable type of list(map(any)), all values within each map must be the same type or be able to be converted to the same type by Terraform, whether this be a string, list(string) or other.
Below is a code snippet consisting of a variable with some default values:
variable "nsg_rules" {
description = "list of maps consisting of nsg rules"
type = list(map(any))
default = [
{
access = "Deny"
destination_address_prefix = "VirtualNetwork"
destination_port_range = "80"
direction = "Inbound"
name = "DenyHTTPInbound"
priority = 100
protocol = "*"
source_address_prefix = "AzureLoadBalancer"
source_port_range = "*"
},
{
access = "Deny"
destination_address_prefix = "VirtualNetwork"
destination_port_range = "22"
direction = "Inbound"
name = "DenySSHInbound"
priority = 200
protocol = "*"
source_address_prefix = "VirtualNetwork"
source_port_range = "*"
}
]
}
To be able to loop through the list of maps, we need to give each map a key value for Terraform to reference, we need to use the for expression wrapped in curly brackets to achieve this, this converts our value into a map of maps which allows the for_each arguement to do its magic!
Now for a working example… we are going to give each maps key the same value as the name input, so for example, the first map above would be given a key of “DenyHTTPInbound”:
resource "azurerm_network_security_rule" "nsg_rules" {
for_each = { for rule in var.nsg_rules : rule.name => rule }
access = each.value.access
destination_address_prefix = each.value.destination_address_prefix
destination_port_range = each.value.destination_port_range
direction = each.value.direction
name = each.value.name
network_security_group_name = azurerm_network_security_group.nsg.name
priority = each.value.priority
protocol = each.value.protocol
resource_group_name = azurerm_resource_group.rg.name
source_address_prefix = each.value.source_address_prefix
source_port_range = each.value.source_port_range
}
The above for expression would produce the following output for Terraform to loop over:
Now lets run Terraform Plan! We can see the following is produced:
List of Objects
Now lets do the same with a list of objects, we again need to set the correct variable type list(object({}), when working with this variable type, the values in each map can be different, so as you can see below, my maps values contain both string and list(string):
variable "nsg_rules" {
description = "list of maps consisting of nsg rules"
type = list(object({
access = string
destination_address_prefixes = list(string)
destination_port_ranges = list(string)
direction = string
name = string
priority = number
protocol = string
source_address_prefixes = list(string)
source_port_range = string
}))
default = [
{
access = "Deny"
destination_address_prefixes = ["10.10.1.0/24", "10.10.2.0/24"]
destination_port_ranges = ["80"]
direction = "Inbound"
name = "DenyHTTPInbound"
priority = 100
protocol = "*"
source_address_prefixes = ["10.0.0.0/24"]
source_port_range = "*"
},
{
access = "Deny"
destination_address_prefixes = ["10.10.10.0/24", "10.10.11.0/24"]
destination_port_ranges = ["22"]
direction = "Inbound"
name = "DenySSHInbound"
priority = 200
protocol = "*"
source_address_prefixes = ["10.0.0.0/24"]
source_port_range = "*"
}
]
}
Again… we need to repeat the above and use the for expression wrapped in curly brackets to convert our value into a map of maps:
resource "azurerm_network_security_rule" "nsg_rules" {
for_each = { for rule in var.nsg_rules : rule.name => rule }
access = each.value.access
destination_address_prefixes = each.value.destination_address_prefixes
destination_port_ranges = each.value.destination_port_ranges
direction = each.value.direction
name = each.value.name
network_security_group_name = azurerm_network_security_group.nsg.name
priority = each.value.priority
protocol = each.value.protocol
resource_group_name = azurerm_resource_group.rg.name
source_address_prefixes = each.value.source_address_prefixes
source_port_range = each.value.source_port_range
}
The above for expression would produce the following output for Terraform to loop over:
Now for Terraform Plan, as you can see, our different value types are still intact: