Search code examples
amazon-web-servicesterraformssl-certificatecloudflare

Terraform: Iterating list for AWS Certificate validation with Cloudflare DNS


I have a map in a tfvars file that contains, Cloudflare zone id, site address, and zone (domain), I am wanting to iterate through that map, generating an ACM certificate, with a certificate validation DNS record being created in Cloudflare. My map looks like this;

my_domains = {
  example1 = {
    cloudflare_zone_id = "00000000000000000000000000001"
    address            = "dev.example1.com"
    domain             = "example1.com"
  }
  example2 = {
    cloudflare_zone_id = "0000000000000000000000000000002"
    address            = "dev.example2.com"
    domain             = "example2.com"
  }
  example3 = {
    cloudflare_zone_id = "0000000000000000000000000000003"
    address            = "dev.library.example3.com"
    domain             = "example3.com"
  }
}

I then have the following code for the certificate creation and validation:

resource "aws_acm_certificate" "my_certs" {
  for_each          = var.my_domains
  domain_name       = each.value.address
  validation_method = "DNS"

  subject_alternative_names = [
    "*.${each.value.address}"
  ]

  lifecycle {
    create_before_destroy = true
  }
}
resource "cloudflare_zone" "my_zone" {
  for_each = var.my_domains
  zone     = each.value.domain
  type     = "full"
}

resource "cloudflare_record" "my_certificate_validation" {
  for_each = {
    for dvo in aws_acm_certificate.my_certs.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  zone_id = cloudflare_zone.my_zone.id
  name    = each.value.name
  value   = trimsuffix(each.value.record, ".")
  type    = each.value.type
  ttl     = 1
  proxied = false
}

When I run a plan, I get the following errors:

Error: Missing resource instance key

on cfcertvalidation.tf line 23, in resource "cloudflare_record" "my_certificate_validation": 23: for dvo in aws_acm_certificate.my_certs.domain_validation_options : dvo.domain_name => {

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

For example, to correlate with indices of a referring resource, use: aws_acm_certificate.my_certs[each.key]

Error: Missing resource instance key

on cfcertvalidation.tf line 30, in resource "cloudflare_record" "my_certificate_validation": 30: zone_id = cloudflare_zone.my_zone.id

Because cloudflare_zone.cdt has "for_each" set, its attributes must be accessed on specific instances.

For example, to correlate with indices of a referring resource, use: cloudflare_zone.my_zone[each.key]

Note: I added the cloudflare_zone resource rther than using the zone id already in the map as a way to simplify things in troubleshooting.

I am sure the answer is in the suggestion for using a [each.key], but I'm not sure how to implement it.

Any assistance would be greatly appreciated.


Solution

  • I have changed the map somewhat for my solution, so for completeness I have included the changed map here:

    variable "my_domains" {
      type = map(any)
      default = {
        example1 = {
          cf_zone_id         = "0000000000000000000000000000"
          address            = "example1.com"
          zone               = "example1.com"
      }
      example2 = {
        cf_zone_id         = "0000000000000000000000000000"
        address            = "example2.com"
        zone               = "example2.com"
      }
      example3 = {
        cf_zone_id         = "0000000000000000000000000000"
        address            = "library.example3.com"
        zone               = "example3.com"
      }
     }
    }
    

    What follows is the working solution, we start out by creating a local variable of type list, looping through the my_domains map to get the cert validation records we need. That then gets converted into a map, which is then used by the cloudflare_record resource to create the relevant DNS entries.

    resource "aws_acm_certificate" "my_certs" {
      for_each                  = var.my_domains
      domain_name               = "${var.env_url_prefix}${var.my_domains[each.key] ["address"]}"
      validation_method         = "DNS"
      subject_alternative_names = ["*.${var.env_url_prefix}${var.my_domains[each.key]["address"]}"]
      lifecycle {
      create_before_destroy = true
     }
    }
    
    locals {
       validation = [
         for certs in keys(var.my_domains) : {
            for dvo in aws_acm_certificate.my_certs[certs].domain_validation_options : dvo.domain_name => {
                name    = dvo.resource_record_name
                value   = trimsuffix(dvo.resource_record_value, ".")
                type    = dvo.resource_record_type
                zone_id = var.my_domains[certs]["cf_zone_id"] # Get the zone id
              }
          }
        ]
     # transform the list into a map
        validation_map = { for item in local.validation : keys(item)[0] => values(item)[0]
        }
    }
    
    
    resource "cloudflare_record" "my_cert_validations" {
       for_each = local.validation_map
    
       zone_id = local.validation_map[each.key]["zone_id"]
       name    = local.validation_map[each.key]["name"]
       value   = local.validation_map[each.key]["value"]
       type    = local.validation_map[each.key]["type"]
       ttl     = 1
       proxied = false #important otherwise validation will fail
    }