I have an aws_route
resource where I want to update all routing tables from list with new routes. Here is the relevant code:
resource "aws_route" "private_route" {
for_each = data.aws_route_tables.private_route_tables.ids
route_table_id = each.key
destination_cidr_block = var.tgw_destination_cidr_block
transit_gateway_id = module.tgw.ec2_transit_gateway_id
}
Above code works fine when there is one CIDR defined in variable tgw_destination_cidr_block
, as aws_route
requires string type for destination_cidr_block
attribute.
tgw_destination_cidr_block = "10.255.160.0/20"
Now I want to add multiple CIDR ranges in this variable, and to iterate through it.
tgw_destination_cidr_block = ["10.255.160.0/20", "10.255.176.0/22"]
in order to create two new routes in each routing table. So, I guess I need list of string in the variable and to iterate through list of CIDR ranges.
Any idea how to do this?
CodePudding user response:
When thinking about for_each
expressions I always start by thinking through what might end the sentence "declare one instance for each of ...", and in this case it seems like that sentence would be:
Declare one instance for each pair of private route table and destination CIDR block.
This tells us then that we need a collection where each element represents one of those pairs.
The usual way to find all of the combinations of multiple sets of items is the setproduct
function, and its documentation has a section Finding combinations for for_each
that shows a worked example of declaring a systematic set of subnets for a number of different base networks.
We can adapt that example to your problem as follows. The example in the docs already works it through in small steps, so I'm going to merge all of the local value expressions into a single one here for simplicity's sake:
locals {
route_table_routes = [
for pair in setproduct(data.aws_route_tables.private_route_tables.ids, var.tgw_destination_cidr_blocks) : {
route_table_id = pair[0]
destination_cidr_block = pair[1]
}
]
}
resource "aws_route" "private_route" {
for_each = {
for rtr in local.route_table_routes : "${rtr.route_table_id}:${rtr.destination_cidr_block}" => rtr
}
route_table_id = each.value.route_table_id
destination_cidr_block = each.value.destination_cidr_block
transit_gateway_id = module.tgw.ec2_transit_gateway_id
}
The above will declare aws_route.private_route
instances whose keys are a combination of route table ID and destination CIDR block.
Note that it can be problematic to use ids assigned by the remote system as part of your instance keys in Terraform, because Terraform needs those instance keys to be fully known throughout the whole plan and apply process. However, in your case you are retrieving those route table ids using a data source and so they will be known during planning as long as your data resource configuration itself can be fully known during planning.
Usually I'd recommend having the same configuration be responsible for declaring both the route tables and these routes, and thus be able to give each of the route tables a static key known entirely from configuration (not decided by the remote system at all), but that sort of design isn't always practical in a decomposed system, and so what you're doing here can be okay as long as you take care to ensure that you apply changes to your separate Terraform configurations in the correct order so that the changes will cascade in the way you're intending, even though Terraform can't see the whole dependency structure and therefore can't guarantee the ordering itself.