I am trying to create a terraform module for aws_route_table creation, here is an example of this resource definition:
resource "aws_route_table" "example" {
vpc_id = aws_vpc.example.id
route {
cidr_block = "10.0.1.0/24"
gateway_id = aws_internet_gateway.example.id
}
route {
ipv6_cidr_block = "::/0"
egress_only_gateway_id = aws_egress_only_internet_gateway.example.id
}
tags = {
Name = "example"
}
}
I am trying to make it more dynamic by using dynamic blocks. The problem is that I always have to define the keys in content block
resource "aws_route_table" "example" {
...
dynamic "route" {
for_each = var.route
content {
cidr_block = route.value.cidr_block
gateway_id = route.value.gateway_id
}
}
...
}
So in this case, I will need to write two dynamic blocks, one for the content with cidr_block and gateway_id and one for the content with ipv6_cidr_block and egress_only_gateway_id.
Is there any way to do this without defining keys explicitly. Something like this:
dynamic "route" {
for_each = var.route
content {
var.route.map
}
}
Yes, you can create route dynamically, because block route acts as Attributes as Blocks. So you can do (example)
# define all your routes in a variable (example content)
variable "routes" {
default = [
{
cidr_block = "0.0.0.0/0"
gateway_id = "igw-0377483faa64bf010"
},
{
cidr_block = "172.31.0.0/20"
instance_id = "i-043fc97db72ad1b59"
}
]
}
# need to provide default values (null) for all possibilities
# in route
locals {
routes_helper = [
for route in var.routes: merge({
carrier_gateway_id = null
destination_prefix_list_id = null
egress_only_gateway_id = null
ipv6_cidr_block = null
local_gateway_id = null
nat_gateway_id = null
network_interface_id = null
transit_gateway_id = null
vpc_endpoint_id = null
instance_id = null
gateway_id = null
vpc_peering_connection_id = null
}, route)
]
}
resource "aws_route_table" "example" {
vpc_id = aws_vpc.example.id
# route can be attribute, instead of blocks
route = local.routes_helper
tags = {
Name = "example"
}
}
Docs do not recommend to use that in general, but I think route is a good example where this would be acceptable.
The general answer for a block type that doesn't employ the "Attributes as Blocks" backward-compatibility shim Marcin mentioned is to rely on the fact that for Terraform provider arguments there is no difference in meaning between omitting an argument or setting it explicitly to null.
That means that you can write an expression to decide whether to populate a particular argument or not, by making it return an explicit null in the appropriate cases. For example:
dynamic "route" {
for_each = var.route
content {
cidr_block = try(route.value.cidr_block, null)
gateway_id = try(route.value.gateway_id, null)
ipv6_cidr_block = try(route.value.ipv6_cidr_block, null)
# ...
}
}
A nested block like this is more like a fixed data structure than a collection, so there isn't any syntax to construct it dynamically.
With that said, in this particular case route is, as Marcin noted, actually really just an attribute of an object type, and Terraform Core is allowing to use it with block syntax as a concession to backward compatibility with older Terraform versions. For that reason, there's not really any significant difference between these two approaches in practice, but the "Attributes as Blocks" mechanism is only for certain situations where providers were design to assume Terraform v0.11 validation bugs and so the conditional null approach I described above is the more general answer, which should work for normal nested blocks too.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With