I'm trying to create a map in locals where a key is created dynamically based on the value of another variable, and can't find a functional way of doing this in Terraform.
The map contains subnet configuration parameters, in the format:
<subnet-name> = {
subnet_id = <subnet-id>
nsg_id = <nsg-id>
rtbl_id = <rtbl-id>
}
And I'm creating it like this:
snet_mappings = {
for key in keys(module.subnet.snet-ids) : key => {
snet_id = module.subnet.snet-ids[key],
nsg_id = module.nsg.nsg-ids[var.snet_config[key]["nsg"]],
rtbl_id = module.route-table.rtbl-ids[var.snet_config[key]["route_table"]]
}
}
With the following variable acting as the lookup for the NSG/Route Table to add from the ones created:
snet_config = {
"testsub" = {
"address_space" = ["10.10.0.0/24"]
"nsg" = "nsg-001"
"route_table" = "rtbl-001"
}
}
This works perfectly so long as I define an NSG and a Route Table every time in the snet_config
map. It doesn't work for a case like this however where I don't want the Route Table to be set, so I leave it blank:
snet_config = {
"testsub" = {
"address_space" = ["10.10.0.0/24"]
"nsg" = "nsg-001"
"route_table" = ""
}
}
This is because it tries to lookup the Route Table ID with an empty string, which therefore fails as it doesn't match a key in the route-table.rtbl-ids
map. In the instance where route table isn't set, I actually want my map to look like this:
<subnet-name> = {
subnet_id = <subnet-id>
nsg_id = <nsg-id>
}
i.e. the rtbl_id
key isn't set at all. Is it possible to conditionally create keys? I've tried playing around with Terraform conditionals but can't find a working solution.
If you are optionally specifying the key, then you can use the merge
, keys
, and contains
functions with a ternary to accomplish this:
snet_mappings = {
for key in keys(module.subnet.snet-ids) : key => merge(
{
snet_id = module.subnet.snet-ids[key],
nsg_id = module.nsg.nsg-ids[var.snet_config[key]["nsg"]],
},
contains(keys(var.snet_config[key]), "route_table") ? { rtbl_id = module.route-table.rtbl-ids[var.snet_config[key]["route_table"]] } : {}
)
}
alternatively with can
:
snet_mappings = {
for key in keys(module.subnet.snet-ids) : key => merge(
{
snet_id = module.subnet.snet-ids[key],
nsg_id = module.nsg.nsg-ids[var.snet_config[key]["nsg"]],
},
can(module.route-table.rtbl-ids[var.snet_config[key]["route_table"]]) ? { rtbl_id = module.route-table.rtbl-ids[var.snet_config[key]["route_table"]] } : {}
)
}
The null coealescing functionality with coalesce
cannot be used here as it pertains to an optional key.
If instead you specify an empty string for route_table
when unused and always specify it as a key, then you would conditional off the length of the string:
snet_mappings = {
for key in keys(module.subnet.snet-ids) : key => merge(
{
snet_id = module.subnet.snet-ids[key],
nsg_id = module.nsg.nsg-ids[var.snet_config[key]["nsg"]],
},
len(module.route-table.rtbl-ids[var.snet_config[key]["route_table"]]) > 0 ? { rtbl_id = module.route-table.rtbl-ids[var.snet_config[key]["route_table"]] } : {}
)
}
In all three situations the merge
function ensure the extra key-value pair only exists in the returned map based on the conditional, and therefore this satisfies the desired functionality.