Search code examples
amazon-web-servicesforeachterraformaws-certificate-manager

Terraform nested for_each aws_acm_certificate domain_validation_options


I am using Terraform v0.13.5. If I create a single AWS certificate resource for a domain registered in AWS, I can also successfully create a Route53 DNS validation record using:

resource "aws_acm_certificate" "api" {
  domain_name       = "api.example.com"
  validation_method = "DNS"

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_route53_record" "api_validation" {
  for_each = {
    for dvo in aws_acm_certificate.api.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }
  allow_overwrite = true
  name            = each.value.name
  records         = [each.value.record]
  ttl             = 60
  type            = each.value.type
  zone_id         = data.aws_route53_zone.example.zone_id # already exists
}

However I want to create multiple certificates using a for_each with map variable. I have defined the aws_acm_certificate resources using:

variable "sub_domains" {
  type = map
  default = {
    "api"      = "api"
    "api_test" = "api.test"
  }
}

resource "aws_acm_certificate" "certs" {
  for_each          = var.sub_domains
  domain_name       = "${each.value}.example.com"
  validation_method = "DNS"

  lifecycle {
    create_before_destroy = true
  }
}

I cannot work out how to refer to the dynamic certificates created when defining the validation records. The following snippet now does not work:

resource "aws_route53_record" "api_validation" {
  for_each = {
    for dvo in aws_acm_certificate.certs.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }
  allow_overwrite = true
  name            = each.value.name
  records         = [each.value.record]
  ttl             = 60
  type            = each.value.type
  zone_id         = data.aws_route53_zone.myzone.zone_id # already exists
}

Terraform complains with the following error:

Because aws_acm_certificate.certs has "for_each" set, its attributes must be
accessed on specific instances.

So how do I get the domain_validation_options for each dynamically created certificate?


Solution

  • A couple ways you can handle this.

    1. It looks like you are using the same high level domain. In this case it's example.com. You should consider using the subject_alternative_names option in the aws_acm_certificate resource. This would request just one cert with multiple SANs, and the logic you have there from the provider doc would create the validation record sets as needed.

    2. If you're looking to create unique individual certs, I suggest a modular approach. If simply looking at certs being validated through DNS, you can take all of the code you have there (plus the aws_acm_certificate_validation resource not shown) and package it up into a module in its own folder. You can then call that module with something like this:

    module "acm_certs" {
      for_each = var.sub_domains
    
      source = "../modules/acm_certificate/"
    
      certificate_domain_name   = "${each.value}.example.com"
      validation_domain_name    = "example.com"
    }
    

    Notice we can use for_each to call modules in Terraform v0.13+, giving us more flexibility in accomplishing what you are asking, but without introducing complexity in the re-usable code itself.

    Each variable you see there are inputs to the module. If you need a more detailed explanation of what I mean by modules, just reply and I'll dive a bit deeper.