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
CodePudding user response:
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
CodePudding user response:
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, 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, env, {})
name = each.key
address_prefixes = each.value
virtual_network_name = var.hub_vnet_name
resource_group_name = var.resource_group_name
}