terraform-provider-aws: Can't iterate over certificate_validation_records attributes of aws_apprunner_custom_domain_association resource

Community Note

  • Please vote on this issue by adding a πŸ‘ reaction to the original issue to help the community and maintainers prioritize this request
  • Please do not leave β€œ+1” or other comments that do not add relevant new information or questions, they generate extra noise for issue followers and do not help prioritize the request
  • If you are interested in working on this issue or have submitted a pull request, please leave a comment

Terraform CLI and Terraform AWS Provider Version

on darwin_amd64
+ provider registry.terraform.io/hashicorp/aws v4.3.0

Affected Resource(s)

  • aws_apprunner_custom_domain_association

Terraform Configuration Files


resource "aws_apprunner_custom_domain_association" "main" {
  domain_name          = "${local.domain_name}.${data.aws_route53_zone.main.name}"
  service_arn          = aws_apprunner_service.main.arn
  enable_www_subdomain = true
}

resource "aws_route53_record" "main-www" {
  name           = local.domain_name
  set_identifier = local.domain_name

  type    = "CNAME"
  zone_id = data.aws_route53_zone.main.zone_id
  ttl     = 300
  records = [aws_apprunner_service.main.service_url]

  weighted_routing_policy {
    weight = 90
  }
}

resource "aws_route53_record" "main-cert" {
  for_each = {
    for entry in aws_apprunner_custom_domain_association.main.certificate_validation_records : entry.name => {
      name   = entry.name
      record = entry.value
      type   = entry.type
    }
  }

  allow_overwrite = true
  zone_id         = data.aws_route53_zone.main.zone_id
  type            = each.value.type
  ttl             = 300
  name            = each.key
  records         = [each.value.record]
}

Expected Behavior

The resource aws_route53_record.main-cert should be created properly. We should be able to iterate through aws_apprunner_custom_domain_association.main.certificate_validation_records dynamically.

Actual Behavior

β”‚ Error: Invalid for_each argument
β”‚ 
β”‚   on modules/app_runner/main.tf line 94, in resource "aws_route53_record" "main-cert":
β”‚   94:   for_each = {
β”‚   95:     for entry in aws_apprunner_custom_domain_association.main.certificate_validation_records : entry.name => {
β”‚   96:       name   = entry.name
β”‚   97:       record = entry.value
β”‚   98:       type   = entry.type
β”‚   99:     }
β”‚  100:   }
β”‚     β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚     β”‚ aws_apprunner_custom_domain_association.main.certificate_validation_records is a set of object, known only after apply
β”‚ 
β”‚ The "for_each" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the
β”‚ for_each depends on.

Steps to Reproduce

  1. terraform apply

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 39
  • Comments: 20 (3 by maintainers)

Commits related to this issue

Most upvoted comments

@justinretzolk The documentation does address this. However, I think most of us are boggled about why Terraform has trouble with this. Terraform does its own dependency mapping, and therefore should be able to run the aws_route53_record resource after the aws_apprunner_custom_domain_association resource is created. After that resource is created, the certificate_validation_records values are available for the aws_route53_record resource to use.

This is really not expected behavior. Due to dependency mapping, Terraform infers a promise that these values can be used in the same script. Just like how the endpoint for an aws_db_instance is available for use in the same configuration, so should the certificate_validation_records map be available for use within a script.

As for solutions, for larger configurations, the -target workaround is not even an option. For the other workaround:

When working with unknown values in for_each, it's better to define the map keys statically in your configuration and place apply-time results only in the map values.

There are no examples of how to do this. It’s a convoluted message and I personally can’t make sense of it (especially since the values are available at the time the script runs the resource block…again, due to dependency mapping). It would be great if in the documentation, there would be an example of how to define the map keys statically for use in the same config.

Is that something HashiCorp can provide?

I used the following code successfully @TechplexEngineer. As I understand it, the tolist(...) (instead of direct array access) has Terraform create a runtime dependency as opposed to a statically known one - which is the default for array access, and obviously won’t work because the DNS values aren’t known ahead of time - and allows validation to function correctly. This obviously won’t work in case of multiple validation options, but honestly who has those πŸ˜‰

resource "aws_acm_certificate" "example" {
  domain_name       = var.domain_name
  validation_method = "DNS"

}

resource "aws_acm_certificate_validation" "example" {
  certificate_arn = aws_acm_certificate.example.arn
}

resource "aws_route53_record" "example" {
  zone_id = var.route53_public_zone_id
  ttl     = 60
  type    = "CNAME"
  name    = tolist(aws_acm_certificate.example.domain_validation_options)[0].resource_record_name
  records = [tolist(aws_acm_certificate.example.domain_validation_options)[0].resource_record_value]
}

Full working example:

resource "aws_apprunner_service" "this" {
  . . .
}

resource "aws_apprunner_custom_domain_association" "this" {
  domain_name = "custom-sub-domain.my-domain.com"
  service_arn = aws_apprunner_service.this.arn
}

data "aws_route53_zone" "this" {
  name = "my-domain.com"
}

resource "aws_route53_record" "validation_records_linglinger_1" {
  name     = tolist(aws_apprunner_custom_domain_association.this.certificate_validation_records)[0].name
  type     = tolist(aws_apprunner_custom_domain_association.this.certificate_validation_records)[0].type
  records  = [tolist(aws_apprunner_custom_domain_association.this.certificate_validation_records)[0].value]
  ttl      = 300
  zone_id  = data.aws_route53_zone.this.id
}

resource "aws_route53_record" "validation_records_linglinger_2" {
  name     = tolist(aws_apprunner_custom_domain_association.this.certificate_validation_records)[1].name
  type     = tolist(aws_apprunner_custom_domain_association.this.certificate_validation_records)[1].type
  records  = [tolist(aws_apprunner_custom_domain_association.this.certificate_validation_records)[1].value]
  ttl      = 300
  zone_id  = data.aws_route53_zone.this.id
}

resource "aws_route53_record" "validation_records_linglinger_3" {
  name     = tolist(aws_apprunner_custom_domain_association.this.certificate_validation_records)[2].name
  type     = tolist(aws_apprunner_custom_domain_association.this.certificate_validation_records)[2].type
  records  = [tolist(aws_apprunner_custom_domain_association.this.certificate_validation_records)[2].value]
  ttl      = 300
  zone_id  = data.aws_route53_zone.this.id
}

resource "aws_route53_record" "custom_domain" {
  name    = aws_apprunner_custom_domain_association.this.domain_name
  type    = "CNAME"
  records = [aws_apprunner_service.this.service_url]
  ttl     = 300
  zone_id = data.aws_route53_zone.this.id
}

At the time of writing, AWS provides 3 certificate validation records. The above exmple works with 3 certificate validation records. It also creates the record to send traffic to the app via the custom domain.

The above example enables the following custom domain for the app: custom-sub-domain.my-domain.com

@anilmujagic

That will still result on the same error

The β€œcount” value depends on resource attributes that cannot be determined β”‚ until apply, so Terraform cannot predict how many instances will be β”‚ created. To work around this, use the -target argument to first apply only β”‚ the resources that the count depends on

Hey @vmignot πŸ‘‹ Thank you for following up, and for pointing that out. Looking at the schema for these two resources, I’d think that a similar for_each approach could be used. Perhaps this is due to the aws_acm_certificate resource having Set: defined. I’m going to mark this as a bug so that we can look into it and determine whether this should be possible.

I was able to reproduce this bug. In case it helps, I noticed that if you remove the aws_route53_record.main-cert resource then run apply, then re-add it, it works as expected.