Search code examples
terraformterraform-provider-azure

How to loop through nested object maps in terraform?


I'd like the resource below to ingest a variable/local containing nested object maps.

Resource:

resource "azurerm_postgresql_flexible_server_firewall_rule" "pgsql_fw_rules" {
  for_each = ???
  server_id        = module.pgsql[each.key].id
  name             = ???
  start_ip_address = ???
  end_ip_address   = ???
}

Variable/local:

  + output1 = {
      + aa1 = {
          + azure_services = {
              + end_ip   = "0.0.0.0"
              + start_ip = "0.0.0.0"
            }
          + rule2          = {
              + end_ip   = "10.255.255.255"
              + start_ip = "10.0.0.1"
            }
          + ruleabc        = {
              + end_ip   = "1.1.1.2"
              + start_ip = "1.1.1.1"
            }
          + tf_user        = {
              + end_ip   = "127.0.0.2"
              + start_ip = "127.0.0.1"
            }
        }
      + aa2 = {
          + azure_services = {
              + end_ip   = "0.0.0.0"
              + start_ip = "0.0.0.0"
            }
          + rulexyz        = {
              + end_ip   = "1.1.1.2"
              + start_ip = "1.1.1.1"
            }
          + tf_user        = {
              + end_ip   = "127.0.0.2"
              + start_ip = "127.0.0.1"
            }
        }
    }

As you can see from the resource above, I'm unsure how to access each object map's contents and this is what I would like some help with.

Here's a few restrictions though:

  • the each.key statement inside the resource must be the customer's name (aa1, aa2, etc.) - the first map's key
  • I cannot change the original variable/local named output1, so the issue must be either resolved from within the resource for_each (preferably) or with the creation of another local

I assume that:

  • a for_each with a nested for could do trick, although the logic to build it escapes me
  • there could be an easy (magical :-/) way to refer to those inner map strings, e.g. each.[*].value.start_ip

Any ideas?


Solution

  • To make things easier you can:

    1. Flatten all the rules
    2. Create a map with a custom key such as ${server_name}-${rule_name}
    3. Finally, iterate over the map for_each in the resource you want to manage

    Working example:

    locals {
      output1 = {
        aa1 = {
          azure_services = {
            end_ip   = "0.0.0.0"
            start_ip = "0.0.0.0"
          }
          rule2 = {
            end_ip   = "10.255.255.255"
            start_ip = "10.0.0.1"
          }
          ruleabc = {
            end_ip   = "1.1.1.2"
            start_ip = "1.1.1.1"
          }
          tf_user = {
            end_ip   = "127.0.0.2"
            start_ip = "127.0.0.1"
          }
        }
        aa2 = {
          azure_services = {
            end_ip   = "0.0.0.0"
            start_ip = "0.0.0.0"
          }
          rulexyz = {
            end_ip   = "1.1.1.2"
            start_ip = "1.1.1.1"
          }
          tf_user = {
            end_ip   = "127.0.0.2"
            start_ip = "127.0.0.1"
          }
        }
      }
    
      flattened_rules = flatten([
        for server_name, rules in local.output1 : [
          for rule_name, details in rules : {
            server_name = server_name
            rule_name   = rule_name
            start_ip    = details.start_ip
            end_ip      = details.end_ip
          }
        ]
      ])
    
      # Create a map with key as "${server_name}-${rule_name}" and value as the rule details
      rules_map = {
        for rule in local.flattened_rules : "${rule.server_name}-${rule.rule_name}" => rule
      }
    }
    
    resource "null_resource" "example" {
      for_each = local.rules_map
    
      triggers = {
        server_name = each.value.server_name
        rule_name   = each.value.rule_name
        start_ip    = each.value.start_ip
        end_ip      = each.value.end_ip
      }
    }
    

    Running terraform plan:

    
    Terraform used the selected providers to generate the following execution
    plan. Resource actions are indicated with the following symbols:
      + create
    
    Terraform will perform the following actions:
    
      # null_resource.example["aa1-azure_services"] will be created
      + resource "null_resource" "example" {
          + id       = (known after apply)
          + triggers = {
              + "end_ip"      = "0.0.0.0"
              + "rule_name"   = "azure_services"
              + "server_name" = "aa1"
              + "start_ip"    = "0.0.0.0"
            }
        }
    
      # null_resource.example["aa1-rule2"] will be created
      + resource "null_resource" "example" {
          + id       = (known after apply)
          + triggers = {
              + "end_ip"      = "10.255.255.255"
              + "rule_name"   = "rule2"
              + "server_name" = "aa1"
              + "start_ip"    = "10.0.0.1"
            }
        }
    
      # null_resource.example["aa1-ruleabc"] will be created
      + resource "null_resource" "example" {
          + id       = (known after apply)
          + triggers = {
              + "end_ip"      = "1.1.1.2"
              + "rule_name"   = "ruleabc"
              + "server_name" = "aa1"
              + "start_ip"    = "1.1.1.1"
            }
        }
    
      # null_resource.example["aa1-tf_user"] will be created
      + resource "null_resource" "example" {
          + id       = (known after apply)
          + triggers = {
              + "end_ip"      = "127.0.0.2"
              + "rule_name"   = "tf_user"
              + "server_name" = "aa1"
              + "start_ip"    = "127.0.0.1"
            }
        }
    
      # null_resource.example["aa2-azure_services"] will be created
      + resource "null_resource" "example" {
          + id       = (known after apply)
          + triggers = {
              + "end_ip"      = "0.0.0.0"
              + "rule_name"   = "azure_services"
              + "server_name" = "aa2"
              + "start_ip"    = "0.0.0.0"
            }
        }
    
      # null_resource.example["aa2-rulexyz"] will be created
      + resource "null_resource" "example" {
          + id       = (known after apply)
          + triggers = {
              + "end_ip"      = "1.1.1.2"
              + "rule_name"   = "rulexyz"
              + "server_name" = "aa2"
              + "start_ip"    = "1.1.1.1"
            }
        }
    
      # null_resource.example["aa2-tf_user"] will be created
      + resource "null_resource" "example" {
          + id       = (known after apply)
          + triggers = {
              + "end_ip"      = "127.0.0.2"
              + "rule_name"   = "tf_user"
              + "server_name" = "aa2"
              + "start_ip"    = "127.0.0.1"
            }
        }
    
    Plan: 7 to add, 0 to change, 0 to destroy.