0

Context: This is continuation of what I was doing at below post.

what is correct way of reference to value in object/map type value in terraform

Objective: Trying to create subnets in loop using for_each in terraform

My terraform.tfvars.json: (only I have mentioned variable realated to my problem I am facing)

 "subnets" : {
        "Dev" :
        [  
        {"gw_snet":{
          "name"                 : "GatewaySubnet",
          "address_prefixes"     : ["10.1.1.0/24"]
        },
        "dns-snet" : {
          "name"                 : "InboundDNSSubnet",
          "address_prefixes"     : ["10.1.2.0/24"]
        },
        "common_snet" : {
          "name"                 : "Common",
          "address_prefixes"     : ["10.1.3.0/24"]
        },
        "clientdata_snet" : {
          "name"                 : "ClientDataSubnet",
          "address_prefixes"     : ["10.1.4.0/20"]
        }}
        ],
        "Stage" :
        [  
        {"gw_snet":{
          "name"                 : "GatewaySubnet",
          "address_prefixes"     : ["10.2.1.0/24"]
        },
        "dns-snet" : {
          "name"                 : "InboundDNSSubnet",
          "address_prefixes"     : ["10.2.2.0/24"]
        },
        "common_snet" : {
          "name"                 : "Common",
          "address_prefixes"     : ["10.2.3.0/24"]
        },
        "clientdata_snet" : {
          "name"                 : "ClientDataSubnet",
          "address_prefixes"     : ["10.2.4.0/20"]
        }}
        ],
        "Prod" :
        [  
        {"gw_snet":{
          "name"                 : "GatewaySubnet",
          "address_prefixes"     : ["10.3.1.0/24"]
        },
        "dns-snet" : {
          "name"                 : "InboundDNSSubnet",
          "address_prefixes"     : ["10.3.2.0/24"]
        },
        "common_snet" : {
          "name"                 : "Common",
          "address_prefixes"     : ["10.3.3.0/24"]
        },
        "clientdata_snet" : {
          "name"                 : "ClientDataSubnet",
          "address_prefixes"     : ["10.3.4.0/20"]
        }}
        ]
      }  

My vnet creation code:

resource "azurerm_virtual_network" "vnet" {
  name                = var.hub_vnet_name
  location            = azurerm_resource_group.rg[0].location
  resource_group_name = azurerm_resource_group.rg[0].name
  for_each = {for k,v in var.vnet_address_space: k=>v if k == "Dev"}
  address_space       =  each.value
  dns_servers         = var.dns_servers
  tags     = {
    environment = "${var.env}"
    costcentre = "14500"
  }
  dynamic "ddos_protection_plan" {
    for_each = local.if_ddos_enabled

    content {
      id     = azurerm_network_ddos_protection_plan.ddos[0].id
      enable = false
    }
  }
}

I am trying to create subnets with for_each like below

  resource "azurerm_subnet" "mysubnet" {
  for_each = {for k,v in var.subnets: k=>v if k == "Dev"}
  name                 = each.value.name
  address_prefixes     = [each.value.address_prefixes]
  virtual_network_name = var.hub_vnet_name
  resource_group_name  = var.resource_group_name
}

Error I get:

No errors in my terraform plan, its not creating vnet also as my plan is not validated.

Is my subnets variable definition ok ?

I guess the below is not working at all.. correct way of accessing this nested value ?

  name                 = each.value.name
  address_prefixes     = [each.value.address_prefixes]

Please help me to identify issue

11
  • 1
    So you only want Dev subnets to be created? Commented Aug 14, 2022 at 16:18
  • 1
    Ok, so you know this for_each = {for k,v in var.subnets: k=>v if k == "Dev"} means it will create subnets for Dev only? Commented Aug 14, 2022 at 16:34
  • 1
    Ah, well, that's an important information. Commented Aug 14, 2022 at 16:40
  • 1
    Not really sure if the one with providing variable value will work with for_each as it can only work with values that are known, but I think I might have an answer for you. Commented Aug 14, 2022 at 17:09
  • 1
    for_each can for with computed values, but not in the key. Commented Aug 14, 2022 at 19:59

2 Answers 2

1

I think this is seeming much more complex than it really is. What you seek, I believe, is the lookup function. Just lookup your var.env in the map. Your current data structure doesn't make much sense. I show it here as locals with just few enough to show the structure.

locals {
  subnets = {
    "Dev" = [
      {
        "some_name_a" = {
          name             = "SomeOtherNameA",
          address_prefixes = ["10.1.1.0/24"]
        },
        "some_name_b" = {
          name             = "SomeOtherNameB",
          address_prefixes = ["10.1.2.0/24"]
        }
      }
    ],
    "Stage" = [
      {
        "some_name_a" = {
          name             = "SomeOtherNameA",
          address_prefixes = ["10.1.1.0/24"]
        },
        "some_name_b" = {
          name             = "SomeOtherNameB",
          address_prefixes = ["10.1.2.0/24"]
        }
      }
    ]
  }
}

So each environment section is a list of length one of an object with a key per some network name you don't need housing an object that actually defines your configuration. What you need is much more simple.

locals {
  subnets = {
    "Dev" = [
      {
        name             = "SomeOtherNameA",
        address_prefixes = ["10.1.1.0/24"]
      },
      {
        name             = "SomeOtherNameB",
        address_prefixes = ["10.1.2.0/24"]
      }
    ],
    "Stage" = [
      {
        name             = "SomeOtherNameA",
        address_prefixes = ["10.1.1.0/24"]
      },
      {
        name             = "SomeOtherNameB",
        address_prefixes = ["10.1.2.0/24"]
      }
    ]
  }
}

In this case you can use:

resource "azurerm_subnet" "mysubnet" {
  for_each = { for v in lookup(var.subnets, var.env, []) : v.name => v.address_prefixes }

  name                 = each.key
  address_prefixes     = each.value
  virtual_network_name = var.hub_vnet_name
  resource_group_name  = var.resource_group_name
}

Or even more simple, given your data:

locals {
  subnets_simple = {
    "Dev" = {
      "SomeOtherNameA" = ["10.1.1.0/24"]
      "SomeOtherNameB" = ["10.1.2.0/24"]
    },
    "Stage" = {
      "SomeOtherNameA" = ["10.1.1.0/24"]
      "SomeOtherNameB" = ["10.1.2.0/24"]
    },
  }
}

In this case, you should be able to simply use:

resource "azurerm_subnet" "mysubnet" {
  for_each = lookup(var.subnets, var.env, {})

  name                 = each.key
  address_prefixes     = each.value
  virtual_network_name = var.hub_vnet_name
  resource_group_name  = var.resource_group_name
}
Sign up to request clarification or add additional context in comments.

7 Comments

I tried last solution, got this ` for_each = lookup(var.subnets, env, {}) │ │ A reference to a resource type must be followed by at least one attribute access, specifying the resource name.` any how thanks for guidance ╵
I'm guessing it should probably be for_each = lookup(var.subnets, var.env, {}).
No worries, otherwise it's a great answer, much simpler than mine. :)
Both answers work , I should have option to make both answers correct. Sad that I dont have option
Most of us really just want to make the community better. I can only speak for me, but I really don't mind either way, but I appreciate your comment.
|
1

The closest solution to which I got (based on your input) is this:

locals {
   net_subnets = merge([
    for env, network in var.subnets : {
      for k, v in network[0] :
      "${k}-${v.name}" => {
        subnet_name      = v.name
        address_prefixes = v.address_prefixes
      } if env == "Dev"
  }]...)
}

Here merge built-in function [1] and expanding function argument [2] are used. This will result in the output:

> local.net_subnets
{
  "clientdata_snet-ClientDataSubnet" = {
    "address_prefixes" = [
      "10.1.4.0/20",
    ]
    "subnet_name" = "ClientDataSubnet"
  }
  "common_snet-Common" = {
    "address_prefixes" = [
      "10.1.3.0/24",
    ]
    "subnet_name" = "Common"
  }
  "dns-snet-InboundDNSSubnet" = {
    "address_prefixes" = [
      "10.1.2.0/24",
    ]
    "subnet_name" = "InboundDNSSubnet"
  }
  "gw_snet-GatewaySubnet" = {
    "address_prefixes" = [
      "10.1.1.0/24",
    ]
    "subnet_name" = "GatewaySubnet"
  }
}

That means you can do for_each on that variable value, where the keys will be a combination of the *-snet key and name from the var.subnets variable. Then, the resource code block should look like:

resource "azurerm_subnet" "mysubnet" {
  for_each             = local.net_subnets
  name                 = each.value.subnet_name
  address_prefixes     = each.value.address_prefixes
  virtual_network_name = var.hub_vnet_name
  resource_group_name  = var.resource_group_name
}

In order to avoid the issue with pre-computed values required for for_each, it might be the best to use the locals block and just use the same logic for the pipeline, i.e., instead of using if env == "Dev", just use if env == var.env. Or alternatively define three local variables for each of the environments.


[1] https://www.terraform.io/language/functions/merge

[2] https://www.terraform.io/language/expressions/function-calls#expanding-function-arguments

4 Comments

your logic of locals working, but now I am getting issue at below virtual_network_name = var.hub_vnet_name it said vnet not found then I tried with ` virtual_network_name = azurerm_virtual_network.vnet.name` then got │ Because azurerm_virtual_network.vnet has "for_each" set, its attributes must be accessed on specific instances. .
That is a topic for another question.
No Problem will draft one hmm
I have created another post as you suggested stackoverflow.com/questions/73360687/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.