Search code examples
terraformdigital-ocean

How to Use a Non-unique Key in Terraform Resource Loop to Create Unique Resources


I'd like to create every type of DNS record for a domain using a generic module, and so be able to call it with something like:

module "example_com_dns" {
  source = "[PATH_TO_MODULES]/modules/dns"

  domain  = "example.com"

  a_records = {
    "@"    = [SOME IP]
    "www"  = [SOME IP]
    "home" = [SOME IP]
  }

  txt_records = {
    "@"                    = "txt-foobar1"
    "@"                    = "txt-foobar2"
    "mail._domainkey.self" = "foobar"
  }

  mx_entries = {
     "10" = "mail.someprovider.com"
     "20" = "mail2.someprovider.com"
  }

  cname_records {
    "cname-foo" = "cname-bar
  }
}

I have something that works fine for A, CNAME , and MX records, but TXT has an edge case which I need to work around. My module has resource blocks for each type of record, which run through loops. I'll just paste the TXT one, but they're all the same:

resource "digitalocean_record" "this_txt_record" {
  for_each = var.txt_records

  domain = var.domain
  type   = "TXT"
  name   = each.key
  value  = each.value
}

This all works fine, except for the fact that since there are 2 records with "@" for their key, it results in only the last one being created (in my example above, this being "txt-foobar2"):


...

  # module.example_com.digitalocean_record.this_txt_record["@"] will be created
  + resource "digitalocean_record" "this_txt_record" {
      + domain = "example.com"
      + fqdn   = (known after apply)
      + id     = (known after apply)
      + name   = "@"
      + ttl    = (known after apply)
      + type   = "TXT"
      + value  = "txt-foobar2"
    }

I'd like for it to create both "txt-foobar1" and "txt-foobar2", even given non-unique keys in the map.

Perhaps this is the wrong way and I just need to figure out a clever loop for for parsing this structure instead?:

  txt_records = [
    { "@" = "foo" },
    { "@" = "bar"},
    { "mail._domainkey.self" = "foobar"}
  ]

If so, I'm currently failing there too :)


Solution

  • Alternative way to already given one is to use the following:

     variable "txt_records" {
      default = {
           "@" = ["foo", "bar"],
           "mail._domainkey.self" = ["foobar"]
           }
     }
    

    Then you can flatten the txt_records using:

    locals {
    
      txt_records_flat = merge([
          for key, values in var.txt_records:
            {for value in values: 
               "${key}-${value}" => {"record_name" = key, "record_value" = value}
            }
      ]...)
    }
    

    which results in local.txt_records_flat of:

    {
      "@-bar" = {
        "record_name" = "@"
        "record_value" = "bar"
      }
      "@-foo" = {
        "record_name" = "@"
        "record_value" = "foo"
      }
      "mail._domainkey.self-foobar" = {
        "record_name" = "mail._domainkey.self"
        "record_value" = "foobar"
      }
    }
    
    

    Then you use it:

    resource "digitalocean_record" "this_txt_record" {
      for_each = local.txt_records_flat
    
      domain = var.domain
      type   = "TXT"
      name   = each.value.record_name
      value  = each.value.record_value
    }