Search code examples
azurefor-loopazure-devopsterraformterraform-provider-azure

Terraform Nested For Loop on an Azure NSG Resource


I have been trying to work out how to get this resource:

resource "azurerm_subnet_network_security_group_association" "example" {
  subnet_id                 = azurerm_subnet.example.id
  network_security_group_id = azurerm_network_security_group.example.id
}

To step through my two maps one on NSGS and the other the subnets.

Here is the Subnet Resource Code:

resource "azurerm_subnet" "one_subnet" {
  for_each             = var.subnets
  resource_group_name  = data.azurerm_resource_group.one_rg.name
  virtual_network_name = azurerm_virtual_network.one_vnet.name
  name                 = each.value["name"]
  address_prefixes     = each.value["address_prefixes"]
}

Subnet Variable File:

variable "subnets" {
  type = map(any)
}

Subnet TFVar

subnets = {
  subnet_1 = {
    name             = "virtual-subnet"
    address_prefixes = ["10.13.1.0/24"]
  }
  subnet_2 = {
    name             = "virtual-subnet"
    address_prefixes = ["10.13.2.0/24"]
  }
  subnet_3 = {
    name             = "virtual-subnet"
    address_prefixes = ["10.13.3.0/24"]
  }
}

NSG Code:

resource "azurerm_network_security_group" "one_nsgs" {
  for_each            = var.one_nsgs
  name                = each.value["name"]
  location            = data.azurerm_resource_group.one_rg.location
  resource_group_name = data.azurerm_resource_group.one_rg.name

  security_rule {}
}

NSG Variable File

variable "one_nsgs" {
  type = map(any)
}

NSG Tfvars

one_nsgs = {
  devwebnsg = {
    name = "DevWebNSG"
  }
  devapinsg = {
    name = "DevApiNSG"
  }
  devjobsnsg = {
    name = "DevNSG"
  }
}

I have tried combining two of the variable maps into a nested map in my locals file and then passing that to the binding NSG resource. But what happens is the Binding NSG Resource wants the id of the resources not the names, which only happens through passing the resource block into the NSG bind resource.

I have also tried this on the NSG Binding Resource:

resource "azurerm_subnet_network_security_group_association" "bind_nsg_to_subnet" {
  for_each      = { for entry in local.combined_nsg_and_subnet: "${entry.subnet}.${entry.nsg}" => entry }
  subnet_id                 = each.value.subnet.id
  network_security_group_id = each.value.nsg.id
}

This looks at my Locals file map

 # Nested loop over both lists, and flatten the result.
  combined_nsg_and_subnet = distinct(flatten([
    for subnet in var.subnets["name"] : [
      for nsg in var.one_nsgs["name"] : {
        subnet = subnet
        nsg    = nsg
      }
    ]
  ]))

But the Id of the resource is not passed in this way.


Solution

  • If you really want to combine both azurerm_subnet and azurerm_network_security_group in a local map and use it for ID, you have to do using resource attributes which will have ID, instead of variables.

    For example:

    combined_nsg_and_subnet = flatten([
      for subnet in azurerm_subnet.one_subnet : [
        for nsg in azurerm_network_security_group.one_nsgs : {
          subnet_id = subnet.id
          nsg_id    = nsg.id
        }
      ]
    ])
    
    resource "azurerm_subnet_network_security_group_association" "bind_nsg_to_subnet" {
      for_each = { for entry in local.combined_nsg_and_subnet: "${entry.subnet_id}.${entry.nsg_id}" => entry }
      
      subnet_id                 = each.value.subnet_id
      network_security_group_id = each.value.nsg_id
    }