Reputation: 477
I'm currently trying to create aws_route resources leveraging the for_each function and associate these routes with a number of aws_route_table resources that are built using the count function. I was wondering if there was a way for me to still use the for_each function within the aws_route resource and reference the aws_route_table resources.
My end goal is to create and apply ~5 routes to ~7 route tables.
Here is my code for reference:
resource "aws_route_table" "private" {
vpc_id = aws_vpc.vpc.id
count = length(split(",", var.private_subnets))
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = join(",", aws_nat_gateway.nat.*.id)
}
route {
cidr_block = var.stage_vpc_cidr
vpc_peering_connection_id = var.stage_peering_connection_id
}
route {
cidr_block = var.production_vpc_cidr
vpc_peering_connection_id = var.production_peering_connection_id
}
tags = {
Name = "${var.name}-private.${element(split(",", var.azs), count.index)}"
Department = var.tag_department
Lifecycle = var.tag_lifecycle
Account = var.tag_account
Application = var.tag_application
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_route" "vpn-route" {
for_each = toset(var.vpn_subnets)
route_table_id = element(aws_route_table.private.*.id, count.index)
gateway_id = var.vpn_gateway_id
destination_cidr_block = each.value
}
and my variable
variable "vpn_subnets" {
type = list
default = ["192.168.0.0/24","192.168.2.0/24","192.168.4.0/24","192.168.5.0/24","192.168.10.0/24","192.168.253.0/24"]
}
Upvotes: 0
Views: 747
Reputation: 74064
It sounds like your requirement is to have one aws_route
instance for each combination of route table instance and var.vpn_subnets
element.
The operation of finding all of the combinations of elements of two collection is called the Cartesian Product and Terraform has the setproduct
function to calculate that.
locals {
route_table_vpn_subnets = [
for pair in setproduct(aws_route_table.private, var.vpn_subnets) : {
route_table_name = pair[0].tags.Name
route_table_id = pair[0].id
destination_cidr_block = pair[1]
}
]
}
This is using setproduct
in combination with a for
expression to produce a series of objects that described all of the combinations of route table ID and destination CIDR block, like this:
[
{
route_table_name = "example-private.0"
route_table_id = "rtb-12323453"
destination_cidr_block = "192.168.0.0/24"
},
{
route_table_name = "example-private.0"
route_table_id = "rtb-12323453"
destination_cidr_block = "192.168.2.0/24"
},
# ...
{
route_table_name = "example-private.1"
route_table_id = "rtb-abcd"
destination_cidr_block = "192.168.0.0/24"
},
{
route_table_name = "example-private.1"
route_table_id = "rtb-abcd"
destination_cidr_block = "192.168.2.0/24"
},
# ...
]
This data structure now meets two key requirements for for_each
:
There's one element of this collection per instance we want to create.
Each object has a set of attributes that can together serve as a unique identifier built from values decided in the configuration, as opposed to values decided by the remote system.
In this case, that's served by including both route_table_name
and route_table_id
, because the name is chosen by your configuration while the id is chosen by the remote system, and so the id would not be suitable for use as an instance identifier.
To use it in for_each
we just need to do one more step to project it into a map where those unique attribute values are combined into unique string keys:
resource "aws_route" "vpn" {
for_each = {
for obj in local.route_table_vpn_subnets :
"${obj.route_table_name}:${obj.destination_cidr_block}" => obj
}
route_table_id = each.value.route_table_id
gateway_id = var.vpn_gateway_id
destination_cidr_block = each.value.destination_cidr_block
}
The key expression in the for
expression here is telling Terraform to assign these instances addresses like the following, which should meet the requirement of being unique across all instances:
aws_route.vpn["example-private.0:192.168.0.0/24"]
aws_route.vpn["example-private.0:192.168.0.2/24"]
...
aws_route.vpn["example-private.1:192.168.0.0/24"]
aws_route.vpn["example-private.1:192.168.0.2/24"]
...
Note that, because you're using numeric indices for your route tables, if you change the number of elements in the split of var.private_subnets
then that's likely to cause the route tables to get renumbered. Because the number is part of the name and the name is the part of the unique key of each route instance, that'll also cause the route instances to be renumbered, which I mention only because a typical reason for using for_each
is to avoid that sort of renumbering.
There's likely a different design you could follow where you describe your route tables using a collection of objects that meets the two criteria for for_each
I described above, so that the route table instances can also be declared using for_each
with meaningful unique keys, but that'd be a pretty significant change outside of the scope of what you asked and so I won't go into the details here.
Upvotes: 2