3
votes

I am trying to build multiple vnets in Azure using Terraform 0.12+ and its new for_each and running into some trouble. I was hoping that the new capabilities would allow me to create a generic network module that takes in a complex variable but I perhaps have reached its limit or am just not thinking it through correctly.. Essentially I my variable is built like

variable "networks" {
  type = list(object({ name = string, newbits = number, netnum = number, subnets = list(object({ name = string, newbits = number, netnum = number}))}))
}

You can see that its an array of networks with a subarray of the subnets for that network. Doing it this way would make it easy to document the network without the extra lines of the terraform resource requirements so our network team can easily adjust/expand without needing to worry about knowing the HCL.

I can perform the necessary functions of building the multiple vnet resources using count and its index, but I would like to use the for_each as it allows for indexing off of the key rather than a count which could change over time (requiring redeployment which we cannot do).

network object

networks = [
    {
        # x.x.1.0/24
        name        = "DMZ",
        newbits     = "8",
        netnum      = "1",
        subnets      = [
            {
                # x.x.1.0/25
                name        = "DMZ"
                newbits     = "9",
                netnum      = "2"
            }
        ]
    },
    {
        # x.x.33.0/24
        name        = "Intermediary"
        newbits     = "8",
        netnum      = "33",
        subnets          = [
            {
                # x.x.33.0/25
                name        = "subnet1"
                newbits     = "9",
                netnum      = "66"
            },
            {
                # x.x.33.128/25
                name        = "subnet2"
                newbits     = "9",
                netnum      = "67"
            }
        ]
    }
]

I have tried and successfully built the vnets with a for_each by changing the object into a map and then using the each.key and each.value (doing a cidrsubnet for the each.value) but the problem lies in making the subnets.

locals {
    vnets = {
        for vnet in var.networks:
        vnet.name => cidrsubnet(var.root_cidr, vnet.newbits, vnet.netnum)
    }
}

Since the map does not include those subnets it I am just banging my head against the wall. Does anyone have any suggestions? Or am I really making this overly complex when I dont need to be?

The resource creation that works, but no subnets

resource "azurerm_virtual_network" "vnets" {
  for_each            = local.vnets
  name                = each.key
  address_space       = [each.value]
  location            = azurerm_resource_group.network.location
  resource_group_name = azurerm_resource_group.network.name
}

I was hoping I could use a dynamic block and perhaps filtering it to match the each.key inside of the network resource. I also tried doing it with its own subnet resource after but just cant figure it out.

This is the whole resource I was hoping would work

resource "azurerm_virtual_network" "vnets" {
  for_each            = local.vnets
  name                = "99999-Nucleus-${each.key}"
  address_space       = [each.value]
  location            = azurerm_resource_group.network.location
  resource_group_name = azurerm_resource_group.network.name

  dynamic "subnet" {
      for_each = [for vnet in var.networks:
      [for s in vnet.subnets: {
      name   = s.name
      prefix = cidrsubnet(var.root_cidr, s.newbits, s.netnum)
    }] if var.networks.name == each.key]

    content {
      name           = subnet.value.name
      address_prefix = subnet.value.prefix
    }
  }
}
1

1 Answers

4
votes

Constructing this intermediate local.vnets map here is making this problem a little harder to solve, because it's throwing away all of the other information in those objects and thus making it hard to use that other information inside the resource "azurerm_virtual_network" "vnets" block.

Instead, if we use repetition over the original var.networks value then we can have the subnets list available directly from inside each.value:

resource "azurerm_virtual_network" "vnets" {
  for_each = { for n in var.networks : n.name => n }

  location            = azurerm_resource_group.network.location
  resource_group_name = azurerm_resource_group.network.name

  name = "99999-Nucleus-${each.key}"
  address_space = [cidrsubnet(var.root_cidr, each.value.newbits, each.value.netnum)]

  dynamic "subnet" {
    for_each = each.value.subnets
    content {
      name           = subnet.value.name
      address_prefix = cidrsubnet(var.root_cidr, subnet.value.newbits, subnet.value.netnum)
    }
  }
}