Reputation: 167
In order to simplify variable definition, I'm grouping subnets and attributes (like security groups) into a list of dictionaries... which then has a layer of dicts etc.. (or maps/objects)
Here is a sample definition JSON:
"subnets": [
{
"name": "inside",
"prefix": "10.248.0.0/28",
"route_table": true,
"nsg": true,
"security_group_rules": [
{
"name": "AllowAllInbound",
"properties": {
"priority": 100,
"access": "Allow",
"direction": "Inbound",
"protocol": "*",
"sourcePortRange": "*",
"destinationPortRange": "*",
"sourceAddressPrefix": "*",
"destinationAddressPrefix": "*"
}
}
]
}
With this single block, I'd like to be able to run through a loop to build out the subnets, then with a different resource block, build out the security groups. I'm having all sorts of issues being able to iterate over this.
I figured I should do something with locals so I can pull out what I need for each operation
on the subnet side:
resource "azurerm_subnet" "subnet" {
for_each = var.subnets
name = each.value.name
resource_group_name = azurerm_resource_group.hub_network_rg.name
virtual_network_name = azurerm_virtual_network.hub_vnet.name
address_prefixes = [each.value.prefix]
}
Doesn't work due to issues with variable types: I'm defining the 'subnets' variable as:
variable "subnets" {
type = list(object({
name = string
prefix = string
route_table = bool
nsg = bool
security_group_rules = map(list(string))
}))
}
I know TF is loosely typed, but it doesn't want to iterate over the objects since it is encountering other things.
Options: 1.break this out into separate elements - I'll run into this again, just on a smaller scale. 2. Find a way to create s local.subnets map and a local.security_groups map that references the var.subnets I have.
Looking for help on #2.
something like:
locals {
subnets = for k,v in var.subnets: [
{
name = var.subnet.name
prefix = var.subnet.prefix
}
I'm WAY off here, but I think that gets across the issue. My brain goes to Python and list/dict comprehension but I can figure out how to do this with TF. I feel it is right in front of me, I just can't piece together the syntax.
Upvotes: 0
Views: 1957
Reputation: 16805
Terraform for_each
accepts only set of strings or map of strings. Passing a var
of something like a list simply wont work. What I suggest doing is the following:
# Create a JSON like variable for your subnets. Notice, this variable is a list, the type of it is inferred, we don't need to complicate ourselves here
variable "subnets" {
default = [{
"name" : "inside",
"prefix" : "10.248.0.0/28",
"route_table" : true,
"nsg" : true,
"security_group_rules" : [
{
"name" : "AllowAllInbound",
"properties" : {
"priority" : 100,
"access" : "Allow",
"direction" : "Inbound",
"protocol" : "*",
"sourcePortRange" : "*",
"destinationPortRange" : "*",
"sourceAddressPrefix" : "*",
"destinationAddressPrefix" : "*"
}
}
]
}]
}
# Create a local which maps the name of the the subnet to the subnet itself
locals {
subnet_map = { for s in var.subnets: s.name => s }
}
# Create the resources
resource "azurerm_subnet" "subnet" {
for_each = toset([
for s in var.subnets: s.name
]) # Note, we create a set from the subnet names. We can use these names to read out properties from the map created above
name = each.value.name
resource_group_name = azurerm_resource_group.hub_network_rg.name
virtual_network_name = azurerm_virtual_network.hub_vnet.name
address_prefixes = [local.subnet_map[each.value].prefix] # We use the subnet names here
}
Update:
The reason why we need subnet_map
is to have to following mapping:
subnet_map = {
"inside" = {
"name" = "inside"
"nsg" = true
"prefix" = "10.248.0.0/28"
...
},
# you have only one subnet as the input, but if you had more than one the map will contain it
"other_subnet" = {
...
}
}
Essentially, we want to map the name
of each subnet to the subnet itself. The reason why we need this is, as I stated above, for_each
accepts only sets of strings. The value for this toset([for s in var.subnets: s.name])
is a set of subnet names, so set("inside")
, meaning that for_each
will loop through the name of the subnets and create an azurerm_subnet
for each subnet name. Obviously we need more information about the subnet, not only its name. We can get all the information we need from the subnet_map
. This is because we can get an object out of it if we provide the name of the subnet. This is exactly what this statement does: local.subnet_map[each.value].prefix
Upvotes: 1