Dave Michaels
Dave Michaels

Reputation: 937

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.

Upvotes: 3

Views: 11140

Answers (2)

Ondřej Trojan
Ondřej Trojan

Reputation: 2229

Moreover, if you want to avoid flatten function and for loop local variable, you can access the object in the map as:

resource "aws_route53_record" "mx_records" {
  for_each = var.mx
  zone_id  = aws_route53_zone.zone.zone_id
  name     = each.key
  type     = "MX"
  ttl      = each.value["ttl"]
  records = each.value["records"]
}

Upvotes: 0

Marcin
Marcin

Reputation: 238299

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
}

Upvotes: 3

Related Questions