I am trying to create a nested loop to create Firewall rules in GCP. I have a locals
variable that looks something like this:
local.firewall_definitions
{
"10.0.0.0/8" = [
{
"port" = "80"
"protocol" = "tcp"
},
{
"port" = "443"
"protocol" = "tcp"
},
{
"port" = "9000"
"protocol" = "udp"
},
]
"99.99.99.99/32" = [
{
"port" = "30000"
"protocol" = "tcp"
},
{
"port" = "1822"
"protocol" = "tcp"
},
{
"port" = "1823"
"protocol" = "udp"
},
]
}
What I am essentially trying to do, is to loop through this variable in 2 ways:
- Create a new
google_compute_firewall
rule once for every different CIDR in this list (sometimes it may be 1, sometimes there could be 10) - Within each CIDR, loop through the variable for every port & protocol to create each
allow
rule
My main.tf
looks like this:
resource "google_compute_firewall" "firewalls" {
for_each = local.firewall_definitions
name = var.name
network = var.network
project = var.project
target_tags = var.targets
source_ranges = var.ranges
dynamic "allow" {
for_each = local.firewall_definitions[*]
content {
protocol = each.value[*].protocol
ports = each.value[*].port
}
}
}
The issue is (I think) that I am trying to loop through using splat, but I get errors like
each.value is tuple with 3 elements. Inappropriate value for attribute "protocol": string required.
I think this is because each.value[*].protocol
currently equates to
"tcp",
"tcp",
"udp",
Whereas Terraform expects only a single protocol in string
form, not a tuple
of 3 elements.
Does anyone have any ideas about the correct way to achieve a nested loop?
CodePudding user response:
This is not actually a nested loop because the key is ignored in the temporary iterator variable in the lambda scope, and as such the structure could be flattened completely. The first problem with the implementation is that the for_each
in the dynamic block is iterating on the entire local.firewall_definitions
. In the resource scope, for each iteration on local.firewall_definitions
the key
will be the unused ip address, and the value
is the list(object(string))
assigned to it. We can update the dynamic block to iterate on the list
value (dynamic blocks are allowed to iterate on the list
type with the for_each
meta-argument unlike resource
):
dynamic "allow" {
for_each = each.value
content {
protocol = each.value[*].protocol
ports = each.value[*].port
}
}
The second problem with the implementation is that the value assigned to the for_each
meta-argument in the dynamic block is being ignored completely in the resource. Now that the dynamic block is iterating on the list
of object
with the port
and protocol
for the specific ip address key
, we need to access the protocol and port for each object
in the iterated list
:
dynamic "allow" {
for_each = each.value
content {
protocol = allow.value.protocol
ports = allow.value.port
}
}
and the desired result is achieved. Documentation for the for_each meta-argument and dynamic blocks can be viewed for further information.