Search code examples
azure-devopsterraformterraform-provider-azureinfrastructure-as-code

Terraform - How To Flatten A List With In a List Of Objects


I am trying to set up my DNS zones and records with in those zones and I can't get the structured collection I am looking for to pass into the tf file that will generate all the zones and records. I have a list of DNS objects and each object will have multiple lists of record objects. I am trying to create lists of just records (a, cname, txt, ...). Im trying merges and flattening and I just not sure how to get what I need. I keep trying different patterns with merge and flatten but keep getting errors in different configurations.

Errors:

Error: Invalid 'for' expression. Extra characters after the end of the 'for' expression.
Error: Missing attribute value. Expected an attribute value, introduced by an equals sign ("=").
Error: Invalid 'for' expression. Key expression is required when building an object.
Error: Missing key/value separator. Expected an equals sign ("=") to mark the beginning of the attribute value.

Variable Definition:

variable "dns_target_child_zones" {
  type = list(object({
    name      = string
    a_records = list(object({
      name        = string
      ttl         = number
      ip_address  = string
    }))
    cname_records = list(object({
      name        = string
      ttl         = number
      record      = string
    }))
    mx_records = list(object({
      name        = string
      ttl         = number
      record      = list(object({
        preference  = number
        exchange    = string
      }))
    }))
    txt_records = list(object({
      name        = string
      ttl         = number
      record      = list(object({
        value  = string
      }))
    }))
  }))
}

Locals file:

locals {
  a_records = flatten ([

  ])
  cname_records = flatten ([

  ])
  mx_records = flatten ([

  ])
  txt_records = flatten ([

  ])        
}

The output structure I am trying to produce so I can do something like count = length(locals.a_records)

[
 {
   name                = 
   zone_name           = 
   ttl                 = 
   records             = 
 },
 {
   name                = 
   zone_name           = 
   ttl                 = 
   records             = 
 }
 ...
]

Solution

  • Here's an example of the local object you could create for a_records (note: I included the ip_address attribute but you have records which doesn't appear in the a_records variable object):

      a_records = flatten([
        for v in var.dns_target_child_zones : [
          for record in v.a_records : {
            name       = record.name
            zone_name  = v.name
            ttl        = record.ttl
            ip_address = record.ip_address
          }
        ]
      ])
    

    This will product an output like the following:

      + a_records = [
      + {
          + ip_address = "ip1"
          + name       = "a1"
          + ttl        = 10
          + zone_name  = "dns1"
        },
      + {
          + ip_address = "ip2"
          + name       = "a2"
          + ttl        = 10
          + zone_name  = "dns1"
        },
      ]
    

    I used a default value and an output to test the expected result. Here's my full test, which you can run terraform plan for to show the output

    variable "dns_target_child_zones" {
      type = list(object({
        name = string
        a_records = list(object({
          name       = string
          ttl        = number
          ip_address = string
        }))
        cname_records = list(object({
          name   = string
          ttl    = number
          record = string
        }))
        mx_records = list(object({
          name = string
          ttl  = number
          record = list(object({
            preference = number
            exchange   = string
          }))
        }))
        txt_records = list(object({
          name = string
          ttl  = number
          record = list(object({
            value = string
          }))
        }))
      }))
      default = [{
        name = "dns1",
        a_records = [{
          name       = "a1",
          ttl        = 10
          ip_address = "ip1"
          },
          {
            name       = "a2",
            ttl        = 10
            ip_address = "ip2"
          }
        ],
        cname_records = [{
          name   = "cname1",
          ttl    = 10
          record = "rec1"
        }],
        mx_records = [{
          name = "mx1",
          ttl  = 10,
          record = [{
            preference = 1,
            exchange   = "exchange1"
            }, {
            preference = 2,
            exchange   = "exchange2"
          }]
        }],
        txt_records = [{
          name = "txt1",
          ttl  = 10,
          record = [{
            value = "val1"
          }]
        }]
      }]
    }
    
    
    locals {
        a_records = flatten([
            for v in var.dns_target_child_zones: [
                for record in v.a_records: {
                    name       = record.name
                    zone_name  = v.name
                    ttl        = record.ttl
                    ip_address = record.ip_address
                }
            ]
        ])
    }
    
    output "a_records" {
        value = local.a_records
    }
    

    For further nesting extraction e.g with mx_records then you can add another for loop in your local like the below

      mx_records = flatten([
        for v in var.dns_target_child_zones : [
          for mx in v.mx_records : [
            for r in mx.record : {
              name       = mx.name
              zone_name  = v.name
              ttl        = mx.ttl
              preference = r.preference
              exchange   = r.exchange
            }
          ]
        ]
      ])
    

    The above local outputs this result

      + mx_records = [
          + {
              + exchange   = "exchange1"
              + name       = "mx1"
              + preference = 1
              + ttl        = 10
              + zone_name  = "dns1"
            },
          + {
              + exchange   = "exchange2"
              + name       = "mx1"
              + preference = 2
              + ttl        = 10
              + zone_name  = "dns1"
            },
        ]