Loop Through List of Maps/Objects with Terraform

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:



Leave a comment