Search code examples
amazon-web-servicesnestedterraformnested-loopsnested-lists

How to create a nested for-loop over zipmaps and lists in terraform resources


I've 2 zipmaps over which I would like to loop via a nested for-loop, i.e. an inner and an outer loop.

In Python that would be very easy, for instance:

for i in list_1:
  for j in list_2:
    # do sth.
    print(i, j)

1) Definition of my zipmaps

zipmap 1: "masked_cidr_blocks_zipmap"

dms_ip_addresses = data.dns_a_record_set.input_dms.addrs
cidr_blocks      = [for idx in range(length(dms_ip_addresses)) : join("", [dms_ip_addresses[idx], "/16"])]
masked_cidr_blocks_zipmap = zipmap(range(length(cidr_blocks)),
[for idx in range(length(cidr_blocks)) : cidrsubnet(cidr_blocks[idx], 0, 0)])

According to the zipmap docs, zipmaps are structured like zipmap(keyslist, valueslist), e.g. (invented values):

> zipmap(["0", "1"], ["199.99.0.0/16", "199.97.0.0/16"])
{
  "0" = "199.99.0.0/16",
  "1" = "199.97.0.0/16",
}

zipmap 2: "network_acls"

network_acls = zipmap(range(length(data.aws_network_acls.main.ids)),
data.aws_network_acls.main.ids)

An example in this case could be (invented values):

> zipmap(["0", "1"], ["acl-328ufjf2j3923rjf22", "acl-23489289jf23rf232r"])
    {
      "0" = "acl-328ufjf2j3923rjf22",
      "1" = "acl-23489289jf23rf232r",
    }

2) Intent to create nested for-loop

Please note that the following is pseudo-code which does not work like that in terraform (unfortunately):

resource "aws_network_acl_rule" "dms_control_port" {
for_each = local.network_acls  # outer for-loop
  for_each = local.masked_cidr_blocks_zipmap  # inner for-loop

  network_acl_id = each_outer.value
  rule_number    = 415 + each_inner.key
  egress         = false
  protocol       = "tcp"
  rule_action    = "allow"
  cidr_block     = each_inner.value
  from_port      = 443
  to_port        = 443
}

How can I achieve this nested looping behavior within my resource "aws_network_acl_rule" "dms_control_port" to populate different parameters with either the outer or inner loop-value?


Solution

  • You are simulating the double for_each by flatting your datasets. So for your example:

    locals {
      network_acls =     {
          "0" = "acl-328ufjf2j3923rjf22",
          "1" = "acl-23489289jf23rf232r",
        }
        
      masked_cidr_blocks_zipmap =  {
          "0" = "199.99.0.0/16",
          "1" = "199.97.0.0/16",
        }
      
      # flatten data structure
      acls_cidr = merge([
              for ni,acl in local.network_acls:
               {
                 for maskedi,cidr in local.masked_cidr_blocks_zipmap: 
                 "${ni}-${maskedi}" => {
                       "ni" = ni
                       "maskedi" = maskedi  
                       "acl" = acl
                       "cidr" = cidr                   
                   }
               }    
            ]...) 
    }
    

    gives:

    {
      "0-0" = {
        "acl" = "acl-328ufjf2j3923rjf22"
        "cidr" = "199.99.0.0/16"
        "maskedi" = "0"
        "ni" = "0"
      }
      "0-1" = {
        "acl" = "acl-328ufjf2j3923rjf22"
        "cidr" = "199.97.0.0/16"
        "maskedi" = "1"
        "ni" = "0"
      }
      "1-0" = {
        "acl" = "acl-23489289jf23rf232r"
        "cidr" = "199.99.0.0/16"
        "maskedi" = "0"
        "ni" = "1"
      }
      "1-1" = {
        "acl" = "acl-23489289jf23rf232r"
        "cidr" = "199.97.0.0/16"
        "maskedi" = "1"
        "ni" = "1"
      }
    }
    

    Then you just use a single for_each (example only below - I don't which variable is which in your setup):

    resource "aws_network_acl_rule" "dms_control_port" {
      
      for_each = local.acls_cidr
    
      network_acl_id = each.value.ni
      rule_number    = 415 + each.value.maskedi
      egress         = false
      protocol       = "tcp"
      rule_action    = "allow"
      cidr_block     = each.value.cidr
      from_port      = 443
      to_port        = 443
    }