title: Terraform for_each for maps inside map
body: <p>I am wondering if it would be possible to iterate over a structure like this using a combination of for_each/for </p>
  
  <pre><code>module "network" {
    source    = "../../"
    role                = "test"
    resource_group_name = xyz
    location            = "centralus"
    environment         = "test-1"
    tags = {
      role = "unit-test"
    }
    subnets = {
      subnet-1 = {
        name              = "subnet-a"
        cidr              = "10.0.1.0/24",
        nsg = {
          httpallowinbound = {
            name = "junk"
          }
        }
      }
    }
  }
  </code></pre>
  
  <p>I can do a for_each on the outer structure to obtain values and assign them to respective fields but cannot do for the nested structure above. Appreciate any feed back so far. What I have so far: </p>
  
  <pre><code>resource "azurerm_subnet" "this" {
    for_each             = var.subnets
    name                 = lookup(each.value, "name")
    resource_group_name  = var.resource_group_name
    virtual_network_name = azurerm_virtual_network.this.name
    address_prefix       = lookup(each.value, "cidr"
  }
  </code></pre>
  
  <p>Out of ideas for this piece </p>
  
  <pre><code>resource "azurerm_network_security_group" "this" {
     Map NSGs to Subnets 
  }
  </code></pre>
  
  <p>----------------------Update-1---------------------------------------------</p>
  
  <p>Based on finding so far, the flatten function is supposed to provide this mechanism as described <a href="https://www.terraform.io/docs/configuration/functions/flatten.html" rel="nofollow noreferrer">here</a> </p>
  
  <p>However, I am unable to get the example working as well:</p>
  
  <p>Main.tf</p>
  
  <pre><code>variable "networks" {
    type = map(object({
      cidr_block = string
      subnets = map(object({
        cidr_block = string
      })) # this is missing in the document
    })) # this is missing in the document
  }
  
  locals {
    # flatten ensures that this local value is a flat list of objects, rather
    # than a list of lists of objects.
    network_subnets = flatten([
      for network_key, network in var.networks : [
        for subnet_key, subnet in network.subnets : {
          network_key = network_key
          subnet_key  = subnet_key
          #network_id  = aws_vpc.example[network_key].id
          cidr_block  = subnet.cidr_block
        }
      ]
    ])
  }
  
  resource "null_resource" "dynamic_self" {
    for_each = {
      for subnet in local.network_subnets : "${subnet.network_key}.${subnet.subnet_key}" =&gt; subnet
    }
  
    provisioner "local-exec" {
      command = "echo ${local.network_subnets}"
    }
  }
  </code></pre>
  
  <p>*.tfvars</p>
  
  <pre><code>networks = {
      network-1 = {
        cidr_block = "10.0.0.0/16"
        subnets = {
          subnet-1 = {
            cidr_block = "10.0.0.0/24"
          }
        }
      }
  }
  </code></pre>
  
  <p>This fails with error: <em>Error: Invalid template interpolation value: Cannot include the given value in a string template: string required.</em></p>
  
  <p>--------------------Update-2-----------------------------------------</p>
  
  <p>Looks like flatten is the right function for this use case and I have moved quite farther down the lane but the last stage is still not working. What I have now: </p>
  
  <p><strong>Values.tfvars</strong></p>
  
  <pre><code>  subnets = {
      inbound-subnet = {
        name              = "subnet-a"
        cidr              = "10.0.1.0/24",
        nsgs = {
          nsg1 = {
              httpallowinbound = {
                name                       = "httpallow"
                priority                   = 100
                direction                  = "Inbound"
                access                     = "Allow"
                protocol                   = "Tcp"
                source_port_range          = "*"
                destination_port_range     = "*"
                source_address_prefix      = "*"
                destination_address_prefix = "*"
              },
              httpdenyinbound = {
                name                       = "httpdeny"
                priority                   = 101
                direction                  = "Inbound"
                access                     = "Deny"
                protocol                   = "Tcp"
                source_port_range          = "*"
                destination_port_range     = "*"
                source_address_prefix      = "*"
                destination_address_prefix = "*"
             },
          },
        }
      }
    }
  }
  </code></pre>
  
  <p><strong>locals</strong></p>
  
  <pre><code>nsgs = flatten([
      for subnets_name, subnets_values in var.subnets : [
        for nsg_name, nsg_values in subnets_values.nsgs : [
          for rule_name, rule_values in nsg_values: {
            nsg_name                   = nsg_name
            rule                       = rule_name
            name                       = rule_values.name
            priority                   = rule_values.priority
            direction                  = rule_values.direction
            access                     = rule_values.access
            protocol                   = rule_values.protocol
            source_port_range          = rule_values.source_port_range
            destination_port_range     = rule_values.destination_port_range
            source_address_prefix      = rule_values.source_address_prefix
            destination_address_prefix = rule_values.destination_address_prefix
         }
        ]
      ]
    ])
  }
  </code></pre>
  
  <p><strong>main.tf</strong></p>
  
  <pre><code>resource "azurerm_subnet" "this" {
    for_each             = var.subnets
    name                 = lookup(each.value, "name")
    resource_group_name  = var.resource_group_name
    virtual_network_name = azurerm_virtual_network.this.name
    address_prefix       = lookup(each.value, "cidr")
  }
  
  
  resource "azurerm_network_security_group" "this" {
    for_each = {
        for sg in local.nsg_name:
          "${sg.nsg_name}" =&gt; sg
    }
    name                 = each.value.nsg_name
    location             = var.location
    resource_group_name  = var.resource_group_name
  }
  
  
  resource "azurerm_network_security_rule" "this" {
    for_each = {
        for sg in local.nsgs:
          //"${sg.rule}.${sg.name}" =&gt; sg
          sg.nsg_name =&gt; sg...
    }
    name                        = each.value.name
    priority                    = each.value.priority
    direction                   = each.value.direction
    access                      = each.value.access
    protocol                    = each.value.protocol
    source_port_range           = each.value.source_port_range
    destination_port_range      = each.value.destination_port_range
    source_address_prefix       = each.value.source_address_prefix
    destination_address_prefix  = each.value.destination_address_prefix
    resource_group_name         = var.resource_group_name
    network_security_group_name = azurerm_network_security_group.this[tostring(each.key)]
  }
  </code></pre>
  
  <p>But I keep getting this error:</p>
  
  <p>Error: Incorrect attribute value type</p>
  
  <p>on ../../vnet.tf line 55, in resource "azurerm_network_security_rule" "this":
    55:   network_security_group_name = azurerm_network_security_group.this[tostring(each.value.nsg_name)]
      |----------------</p>
  
  <pre><code>| azurerm_network_security_group.this is object with 1 attribute "nsg1"
  | each.value.nsg_name is "nsg1"
  </code></pre>
  
  <p>Inappropriate value for attribute "network_security_group_name": string
  required.</p>
  
  <pre><code>Error: Incorrect attribute value type
  
    on ../../vnet.tf line 55, in resource "azurerm_network_security_rule" "this":
    55:   network_security_group_name = azurerm_network_security_group.this[tostring(each.value.nsg_name)]
      |----------------
      | azurerm_network_security_group.this is object with 1 attribute "nsg1"
      | each.value.nsg_name is "nsg1"
  </code></pre>
  
  <p>I am sure I am missing something tiny because it looks it is doable. Any pointers would really help</p>