Nick Bull
Nick Bull

Reputation: 9866

Using for_each on property of a counted resource

I have a resource with a varying count (0 or 1 resources).

resource "aws_acm_certificate" "cert" {
  count = local.acm_version_count

  domain_name               = "${local.deployment_version_name}.example.com"
  subject_alternative_names = [
    "*.${local.deployment_version_name}.example.com
  ]

  validation_method = "DNS"
}

And now I want to iterate over a property of the optional resource:

resource "aws_route53_record" "acm_validation" {
  for_each = {
    for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  zone_id = data.aws_route53_zone.r53_zone.zone_id
  name    = each.value.name
  type    = each.value.type
  ttl     = 60

  records = [each.value.record]

  allow_overwrite = true

}

Of course, because aws_acm_certificate.cert is counted, we must access that at an index (i.e., aws_acm_certificate.cert[0] or aws_acm_certificate.cert[count.index]). However, I can't use a count because it is mutually exclusive from for_each, and I cannot access [0] when the optional resource is not created. Ideally I could add a count on the record itself, so that I ignore the aws_route53_record.acm_validation if there is no aws_acm_certificate

I know about toproduct, so I wonder if that would be useful - but I honestly didn't know enough about it to utilize it and couldn't make it work here

How can I iterate over this optional resource so that I can ignore it if the acm is optionally excluded, but still iterate over those domain_validation_options if there is an acm?

Upvotes: 1

Views: 563

Answers (2)

Martin Atkins
Martin Atkins

Reputation: 74219

You can produce a flattened list of all of the "domain validation options" across all of the aws_acm_certificate.cert instances like this:

flatten(aws_acm_certificate.cert[*].domain_validation_options)

When you have zero certificates declared, this would be like calling flatten([]) and so it'll just produce an empty list, whereas when count is nonzero it'll capture all of the declared validation options, because aws_acm_certificate.cert[*].domain_validation_options alone would produce a list of lists of objects.

I think it should work to insert that flatten( ... ) call in the same place where you are currently referring directly to an attribute of a singleton resource, and leave everything else unchanged:

  for_each = {
    for dvo in flatten(aws_acm_certificate.cert[*].domain_validation_options) :
    dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

In most cases I'd suggest thinking of a conditional count as "there can be zero or more instances", even if you know that the maximum count is 1 really, because the language features designed for working with lists of objects are often more compact and concise than what you'd need to write to specifically distinguish between zero or one of some resource. It doesn't always work out like that, but in many cases it's not really important whether there's one or more than one instances, including this situation.

Upvotes: 2

Mark B
Mark B

Reputation: 200562

You need to check if the resource is created, and pass an empty list to for_each if it isn't. I would add a local variable to make this a bit cleaner.

locals {
  dvos = local.acm_version_count == 0 ? [] : aws_acm_certificate.cert[0].domain_validation_options
}

resource "aws_route53_record" "acm_validation" {
  for_each = {
    for dvo in local.dvos : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  ...
}

Upvotes: 1

Related Questions