Search code examples
for-loopforeachterraformamazon-route53

Iterating through map of objects in terraform with route 53 records


I am trying to figure out how to read from additional values in Terraform using for / for_each using Terraform 0.12.26

dns.tfvars

mx = {
  "mywebsite.org." = {
    ttl = "3600"
    records = [
      "home.mywebsite.org.",
      "faq.mywebsite.org."
    ]
  }
  "myotherwebsite.org." = {
    ttl = "3600"
    records = [
      "home.myotherwebsite.org."
    ]
  }
}

variables.tf

variable "mx" {
  type = map(object({
    ttl     = string
    records = set(string)
  }))
}

mx.tf

locals {
  mx_records = flatten([
    for mx_key, mx in var.mx : [
      for record in mx.records : {
        mx_key = mx_key
        record = record
        ttl    = mx.ttl
    }]
  ])
}

resource "aws_route53_record" "mx_records" {
  for_each = { for mx in local.mx_records : mx.mx_key => mx... }
  zone_id  = aws_route53_zone.zone.zone_id
  name     = each.key
  type     = "MX"
  ttl      = each.value.ttl

  records = [
    each.value.record
  ]
}

In mx.tf, I can comment out the second value, faq.mywebsite.org, and the code works perfectly. I cannot figure out how to set up my for loop and for each statements to get it to "loop" through the second value. The first error I had received stated below:

Error: Duplicate object key

  on mx.tf line 13, in resource "aws_route53_record" "mx_records":
  13:   for_each = { for mx in local.mx_records : mx.mx_key => mx }
    |----------------
    | mx.mx_key is "mywebsite.org."

Two different items produced the key "mywebsite.org." in this 'for'
expression. If duplicates are expected, use the ellipsis (...) after the value
expression to enable grouping by key.

To my understanding, I do not have two duplicate values helping to form the key so I should not have to use the ellipsis, but I tried using the ellipsis anyway to see if it would apply properly. After adding on the ellipsis after the value expression, I got this error:

Error: Unsupported attribute

  on mx.tf line 20, in resource "aws_route53_record" "mx_records":
  20:     each.value.record
    |----------------
    | each.value is tuple with 2 elements

This value does not have any attributes.

Any advice on this issue would be appreciated.

UPDATE

Error: [ERR]: Error building changeset: InvalidChangeBatch: [Tried to create resource record set [name='mywebsiteorg.', type='MX'] but it already exists]
    status code: 400, request id: dadd6490-efac-47ac-be5d-ab8dad0f4a6c

It's trying to create the record, but it already created because of the first record in the list.


Solution

  • I think you could just construct a map of your objects with key being the index of mx_records list (note the idx being the index):

    resource "aws_route53_record" "mx_records" {
    
      for_each = { for idx, mx in local.mx_records : idx => mx }
    
      zone_id  = aws_route53_zone.zone.zone_id
    
      name     = each.value.mx_key
      type     = "MX"
      ttl      = each.value.ttl
    
      records = [
        each.value.record
      ]
    }
    

    The above for_each expressions changes your local.mx_records from list(objects) to map(objects), where the map key is idx, and the value is the original object.

    Update:

    I verified in Route53 and you can't duplicate codes. Thus may try using orginal mx variable:

    resource "aws_route53_record" "mx_records" {
    
      for_each = { for idx, mx in var.mx : idx => mx }
    
      zone_id  = aws_route53_zone.zone.zone_id
    
      name     = each.key
      type     = "MX"
      ttl      = each.value.ttl
    
      records = each.value.records
    }