Looping over a list in a list

hello.

I know there is a lot of example and article about this but i can’t get it working.

I have a variable like that:

dns = {
  server_ip     = "xxxxxxx"
  domains = [
    {
      domain        = "mydomain1.fr"
      a_records     = [""]
      cname_records = [
        "www"
      ]
    },
    {
      domain        = "mydomain2"
      a_records     = [""]
      cname_records = [
        "www",
        "back"
      ]
    }
  ]
}

I’m trying to iterate the list (dns) to create records into a registrar. How can I iterate over a_records inside the foreach (subdomain need a string, not a list) ?

resource "ovh_domain_zone_record" "a_records" {
  for_each = { for domain in var.dns2.domains : domain.domain => domain }

  zone      = each.key
  subdomain = each.value.a_records[0]
  fieldtype = "A"
  ttl       = 300
  target    = var.dns.server_ip
}

Thanks fr your help :cry:

Hi,

This can be quite tricky to get a good understanding of in Terraform, so I will illustrate one possible solution and then try and explain the ‘clever’ bit.

Your required result is to have an ovh_domain_zone_record resource created for each pair of domain+a_record therefore you need to pass an appropriately formed map to the for_each of this resource block such that it can iterate through and create all of the records across the domains:

locals {
  # Source Local Variable containing a list of domains with a list of A records and CNAME records
  dns = {
    server_ip = "172.168.10.1"
    domains = [
      {
        domain        = "mydomain1.fr"
        a_records     = ["fred", "bob", "alice"]
        cname_records = []
      },
      {
        domain        = "mydomain2.de"
        a_records     = ["janet", "joe", "jill"]
        cname_records = []
      }
    ]
  }
}

locals {
  # local variable that merges the domain and a_record lists to provide a unique key for each domain+a_record pair
  # use this variable as your input to for_each (or copy the expression directly into for_each)
  zone_records = merge(
    [ for domain in local.dns.domains :
      { for a_record in domain.a_records :
        "${domain.domain}_${a_record}" => {
          domain = domain.domain
        a_record = a_record }
      }
    ]
  ...)

}

resource "ovh_domain_zone_record" "a_records" {
  for_each = local.zone_records

  zone      = each.value.domain
  subdomain = each.value.a_record
  fieldtype = "A"
  ttl       = 300
  target    = var.dns.server_ip
}


output "zone_records" {
  value = local.zone_records
}

Explanation

What this is doing is creating a map containing a maps of objects (given the above inputs) where each map item has a ‘composite’ key, composed of the domain and the a_record, and each map value contains an object with a_record and domain properties:

{
  "mydomain1.fr_alice" = {
    "a_record" = "alice"
    "domain" = "mydomain1.fr"
  }
  "mydomain1.fr_bob" = {
    "a_record" = "bob"
    "domain" = "mydomain1.fr"
  }
  "mydomain1.fr_fred" = {
    "a_record" = "fred"
    "domain" = "mydomain1.fr"
  }
  "mydomain2.de_janet" = {
    "a_record" = "janet"
    "domain" = "mydomain2.de"
  }
  "mydomain2.de_jill" = {
    "a_record" = "jill"
    "domain" = "mydomain2.de"
  }
  "mydomain2.de_joe" = {
    "a_record" = "joe"
    "domain" = "mydomain2.de"
  }
}

The clever bit is this:

merge(
    [ for domain in local.dns.domains :
      { for a_record in domain.a_records :
        "${domain.domain}_${a_record}" => {
          domain = domain.domain
        a_record = a_record }
      }
    ]
  ...)
  • merge( - I will describe this separately below
  • [ for domain in local.dns.domains :” - Loop over each item in local.dns.domains creating a list
    • Within this outer loop
      { for a_record in domain.a_records :” - Loop over each item in domain.a_records creating a map
      • Within this inner loop
        "${domain.domain}_${a_record}" => {” Create map entry with composite key containing an object
        • Object contains a domain and a_record property with the appropriate values }
      • } Close the Map
    • ] Close the list

The merge([list]...) takes the output of the above steps which now is structured like this:

[
  {
    "mydomain1.fr_alice" = {
      "a_record" = "alice"
      "domain" = "mydomain1.fr"
    }
    "mydomain1.fr_bob" = {
      "a_record" = "bob"
      "domain" = "mydomain1.fr"
    }
    "mydomain1.fr_fred" = {
      "a_record" = "fred"
      "domain" = "mydomain1.fr"
    }
  },
  {
    "mydomain2.de_janet" = {
      "a_record" = "janet"
      "domain" = "mydomain2.de"
    }
    "mydomain2.de_jill" = {
      "a_record" = "jill"
      "domain" = "mydomain2.de"
    }
    "mydomain2.de_joe" = {
      "a_record" = "joe"
      "domain" = "mydomain2.de"
    }
  },
]

Which is a list(map(object()))

merge()” merges each map into a single map after first expanding...each item in the list into a separate argument for input into the merge function. Which results in a map(map(object())

When using nested for loops and trying to create a flattened data structure from a complex collection that is able to be used as a input for a for_each iteration it is usually better to break the problem down into smaller parts.
You can use the terraform console to experiment but I usually create a separate simple module with a single main.tf file and work through the problem using local variables and outputs until I have got to my desired structure. This can then be ‘ported’ into my main terraform module with a good understanding.

Hope that helps!

Happy Terraforming