Home > Back-end >  Terraform - dynamic nested loops
Terraform - dynamic nested loops

Time:11-10

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:

  1. 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)
  2. 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.

  • Related