Looping through a Map to create route53 zone and records

Current Terraform Version

Terraform v0.12.3
+ provider.aws v2.18.0

Use-case

Populate route53 records for a reverse DNS zone

I have an external data source which accepts one or more subnet CIDR’s and returns json data as a map of reverse DNS zone name and the ip-addresses associated to the zone

The data

{"0.0.10.in-addr.arpa" = ["16,10.0.0.16,0", "17,10.0.0.17,1",]}

With the example data I have, I am able to create route53 zones but I am stumped as to how to setup count, name, records and zone_id values for the route53 records.

Attempted Solution

locals {
  reverse_zone_data = {"0.0.10.in-addr.arpa" = ["16,10.0.0.16,0", "17,10.0.0.17,1",]}
}

output "reverse_zone_record_count" {
  value = [for zone in local.reverse_zone_data:
	zone
  ]
}


resource "aws_route53_zone" "reverse_zone" {
  count = length(keys(local.reverse_zone_data))
  name    = keys(local.reverse_zone_data)[count.index]
}

resource "aws_route53_record" "reverse_zone_record" {
  count = length(local.reverse_zone_data[aws_route53_zone.reverse_zone[count.index].name])
  name       = ?
  type       = "PTR"
  records    = [?]
  zone_id    = aws_route53_zone.reverse_zone.*.id
  ttl        = 300
  depends_on = [aws_route53_zone.reverse_zone]
}

Based on the data I have, I should be able to create 1 route53 zones and 2 records in the zone. Any pointers welcome.

The name for the zone record will be obtained from the 1st element of the comma separated item. e.g. 16,10.0.0.16,0 and the record part will be obtained from the 3rd element from the same data to be concatenated with a FQDN.

Hi @jeffery,

A key issue with this situation is that count works with zero-based incrementing integers as instance identifiers, which is not compatible with a map that uses string keys. The first step here then is to translate the map into a list so that each element is assigned an index. That requires deciding on a suitable ordering for the items in that list, but if you don’t have a strong need for any particular ordering you can just accept the default lexical ordering of keys done by a for expression:

locals {
  reverse_zone_data = {"0.0.10.in-addr.arpa" = ["16,10.0.0.16,0", "17,10.0.0.17,1",]}
  reverse_zone_data_list = [for k, v in local.reverse_zone_data : {
    name    = k
    records = v
  }]
}
[
  {
    "name" = "0.0.10.in-addr.arpa"
    "records" = [
      "16,10.0.0.16,0",
      "17,10.0.0.17,1",
    ]
  },
]

You can then use reverse_zone_data_list with count. For example:

resource "aws_route53_zone" "reverse_zone" {
  count = length(local.reverse_zone_data_list)
  name  = local.reverse_zone_data_list[count.index]
}

The aws_route53_record resource is more complicated because you want to create these per-zone-per-record. The same general solution applies though: we need to flatten this down into a single list that we can count over. We can again use for expressions for this, but they’ll be a little more complicated this time:

locals {
  reverse_zone_data = {"0.0.10.in-addr.arpa" = ["16,10.0.0.16,0", "17,10.0.0.17,1",]}
  reverse_zone_data_list = [for k, v in local.reverse_zone_data : {
    name    = k
    records = v
  }]
  reverse_zone_data_records = flatten([for i, v in local.reverse_zone_data_list : [
    for record in v.records : {
      zone_index = i
      # Maybe you'd like to also split the string into separate
      # attributes here, if that's helpful to the expressions
      # in the aws_route53_record block. I'm just going to use
      # it directly because the meaning of those segments isn't
      # clear to me from your description.
      record = record
    }
  ]))
}
[
  {
    "record" = "16,10.0.0.16,0"
    "zone_index" = 0
  },
  {
    "record" = "17,10.0.0.17,1"
    "zone_index" = 0
  },
]

Now you can use this flattened list with count:

resource "aws_route53_record" "reverse_zone_record" {
  count = length(local.reverse_zone_data_records)
  # Not sure exactly how you wanted to decode that
  # comma-separated string, so this is just using it
  # directly for example even though that result is
  # nonsensical...
  name       = local.reverse_zone_data_records[count.index].record 
  type       = "PTR"
  records    = ...
  zone_id    = aws_route53_zone.reverse_zone[local.reverse_zone_data_records[count.index].zone_index].id
  ttl        = 300
}

Hi @apparentlymart,

Thanks very much for this solution. It very much works for me.

I didn’t know you can obtain the index of the record in a loop as per for i, v in local.reverse_zone_data_list : statement. Can you please point me to this syntax in the documentation?

Appreciate the time and effort you have put into this response.

Regards,
Jeff