23

When I had a single hosted zone it was easy for me to create the zone and then create the NS records for the zone in the delegating account by referencing the hosted zone by name.

Now I need to create multiple hosted zones and pass the nameserver records back to the parent account and I am not sure if its possible (or how if it is) to reference multiple resources. Reading this probably doesn't make a whole lot of sense, so the code below is as far as I have got.

I now have a for_each loop which will loop over a list of strings and create a hosted zone for each string, and I want to then create corresponding NS records in another account, notice that I am using a separate provider provider = aws.management_account to connect to the management account and this works fine for a single hosted zone.

I do not know how to reference the hosted zones, is there some syntax for this or is my approach wrong?

resource "aws_route53_zone" "public_hosted_zone" {
  for_each = local.aws_zones
  name     = "${each.value}.${var.domain}"
}

resource "aws_route53_record" "ns_records" {
  for_each        = local.aws_zones
  provider        = aws.management_account
  allow_overwrite = true
  name            = "${each.value}.${var.domain}"
  ttl             = 30
  type            = "NS"
  zone_id         = data.aws_ssm_parameter.public_hosted_zone_id.value

  records = [
    aws_route53_zone.public_hosted_zone.name_servers[0], # Here is my old code which works for a single hosted zone but I cannot work out how to reference multiples created above
    aws_route53_zone.public_hosted_zone.name_servers[1],
    aws_route53_zone.public_hosted_zone.name_servers[2],
    aws_route53_zone.public_hosted_zone.name_servers[3]
  ]
}
berimbolo
  • 3,319
  • 8
  • 43
  • 78
  • 1
    `records = aws_route53_zone.public_hosted_zone.name_servers` should work in TF >= 0.12 – jordanm Aug 28 '20 at 22:07
  • A very slight adjustment (failed first build) `records = aws_route53_zone.public_hosted_zone[each.key].name_servers ` Can you tell me how this works? When I view the plan output terraform is creating the hosted zones first and then the route53 records, how does it tie the two up and use the `each.key` from the route53 record `for_each` to properly reference the resources created in the `aws_route53_zone`? – berimbolo Aug 28 '20 at 22:56
  • 1
    What is the form of `local.aws_zones`? – Marcin Aug 28 '20 at 23:47
  • It looks like this `["dev", "test", "qa"]` – berimbolo Aug 28 '20 at 23:52
  • 1
    What are you trying to achieve here? It looks like you're trying to set NS records for a zone to the name servers that already exist for the zone. Firstly, this isn't how you set name servers for a route53 zone and secondly you don't actually need to do anything here as you'd see if you looked at your zone records after creating the zone. – ydaetskcoR Aug 29 '20 at 06:34
  • Actually what I am doing is passing the nameservers to the delegating account so I do need to do this. Sure the name server records exist in the account they were created in.... hence I am able to reference them as per my example above, I dont need to look to realise this... also I can see them for a single domain in my management account hosted zone, I am using a separate provider entry for the management account to pass those back as this account owns the domain, for DNS delegation. I have updated the question as it wasnt clear enough, although this is now answered and the answer works. – berimbolo Aug 29 '20 at 11:21

2 Answers2

23

Since your local.aws_zones is set ["dev", "test", "qa"], your aws_route53_zone.public_hosted_zone will be a map with keys "dev", "test", "qa".

Therefore, to use it in your aws_route53_record, you can try:

resource "aws_route53_record" "ns_records" {
  for_each        = local.aws_zones

  # other attributes

  records = aws_route53_zone.public_hosted_zone[each.key].name_servers
}
Marcin
  • 215,873
  • 14
  • 235
  • 294
8

In this case it seems like your intent is to say "there is one zone per entry in local.aws_zones and then one NS recordset per zone".

In situations like that, a concise way to write that down is to use one resource as the for_each of the next, like this:

resource "aws_route53_zone" "public_hosted_zone" {
  for_each = local.aws_zones

  name = "${each.value}.${var.domain}"
}

resource "aws_route53_record" "ns_records" {
  provider = aws.management_account
  for_each = aws_route53_zone.public_hosted_zone

  allow_overwrite = true
  name            = each.value.name
  ttl             = 30
  type            = "NS"
  zone_id         = data.aws_ssm_parameter.public_hosted_zone_id.value
  records         = each.value.name_servers
}

By using for_each inside resource "aws_route53_zone" "public_hosted_zone" you make aws_route53_zone.public_hosted_zone be a map from zone keys to zone objects, which is itself compatible with the expectations of for_each and so you can pass that map downstream to another resource.

An important advantage of this approach is that each.value in the resource "aws_route53_record" "ns_records" block is the corresponding zone object directly, and so we can write each.value.name to get the derived name argument from the zone without having to duplicate that expression again, and we can write each.value.name_servers to get the exported set of nameservers for each of the zones. That then avoids writing more complex expressions to look up zones by each.key, because Terraform has already done the necessary lookup to populate each.value.

Martin Atkins
  • 62,420
  • 8
  • 120
  • 138
  • This is a great tip, I was not aware that you could use a created set of resources to drive the `for_each` of another set of resources and so I loop over the original map all the time (`local.aws_zones`) – berimbolo Sep 01 '20 at 10:26