Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The "for_each" value depends on resource attributes that cannot be determined until apply #59

Closed
goetzc opened this issue Oct 4, 2021 · 39 comments · Fixed by #72
Closed

Comments

@goetzc
Copy link

goetzc commented Oct 4, 2021

Description

Hello. When using the latest module version and running the plan to create a record based on the value of a CloudFront distribution created using a for_each:

  source  = "terraform-aws-modules/route53/aws//modules/records"
  version = "2.3.0"zone_id = local.dns_zone_id
  depends_on = [module.cloudfront]

  records = [
    {
      name = ""
      type = "A"
      alias = {
        name    = module.cloudfront.web.cloudfront_distribution_domain_name
        zone_id = module.cloudfront.web.cloudfront_distribution_hosted_zone_id
      }
    }
  ]

The plan fails with the following error when running locally or on Terraform Cloud:

Configuring remote state backend...
Initializing Terraform configuration...
╷
│ Error: Invalid for_each argument
│
│   on .terraform/modules/records/modules/records/main.tf line 32, in resource "aws_route53_record" "this":32:   for_each = var.create && (var.zone_id != null || var.zone_name != null) ? local.recordsets : tomap({})
│     ├────────────────
│     │ local.recordsets will be known only after apply
│     │ var.create is true
│     │ var.zone_id is "ZLHLLCNNHTTH8"
│     │ var.zone_name is null
│
│ 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.

Versions

Terraform v1.0.8
on linux_amd64
+ provider registry.terraform.io/hashicorp/aws v3.61.0

Reproduction

Steps to reproduce the behavior:

  • Are you using workspaces?: Yes
  • Have you cleared the local cache: Yes and this error is even shows in TF Cloud.

Code Snippet to Reproduce

Additionally to the above code, this is the extra relevant part:

main.tf

module "cloudfront" {
  source = "./modules/aws-cloudfront"

  for_each = {
    for k, v in var.projects : k => v
    if v.cdn
  }

  …

varaibles.tf

variable "projects" {
  type        = map(map(string))
  default = {
    "web" = {
      name = "",
      cdn  = true,
    },
  }
}

Expected behavior

The plan should end successfully.

Actual behavior

The error is shown in the "plan" state and thus the apply is impossible.

Terminal Output Screenshot(s)

image

Workaround

On local backend, the -target argument can be used to first plan and apply the dependencies. On Terraform Cloud there is the TF_CLI_ARGS_plan environment variable, which is supported since version 0.13, e.g.:
TF_CLI_ARGS_plan="-target=module.cloudfront"

Additional context

When first creating the AWS CloudFront dependency, then the records can be planned correctly, as the resources now exists. But running per parts is not ideal.

The error is about "local.recordsets will be known only after apply". I wonder if it is possible to have this refactor to avoid the for_each issue, or if this is a deeper rooted Terraform limitation?

recordsets = {
for rs in local.records :
join(" ", compact(["${rs.name} ${rs.type}", lookup(rs, "set_identifier", "")])) => merge(rs, {
records = jsonencode(try(rs.records, null))
})
}

Thanks.

@antonbabenko
Copy link
Member

This is, unfortunately, the valid behavior of Terraform still.

You even gave Terraform a hint by using depends_on inside of the module block but it does not help. I don't know any better workaround for this issue but I would like to figure it out and implement it.

@dmytro-dorofeiev
Copy link

I can also confirm, I have the same issue after upgrade from 1.11.0 version to 2.3.0.

main.tf

module "records" {
  source  = "terraform-aws-modules/route53/aws//modules/records"
  version = "~> 2.0"

  zone_name = keys(module.zones.route53_zone_zone_id)[0]

  records = [
    {
      name = "admin-ui"
      type = "A"
      alias = {
        name    =  module.alb.dns_name
        zone_id = module.alb.zone_id
      }
      allow_overwrite = true
    },
    {
      name = "customer-ui"
      type = "A"
      alias = {
        name    = module.alb.dns_name 
        zone_id = module.alb.zone_id
      }
      allow_overwrite = true
    },
  ]

  depends_on = [module.alb]
}

Receive output:

╷
│ Error: Invalid for_each argument
│ 
│   on .terraform/modules/records/modules/records/main.tf line 32, in resource "aws_route53_record" "this":
│   32:   for_each = var.create && (var.zone_id != null || var.zone_name != null) ? local.recordsets : tomap({})
│     ├────────────────
│     │ local.recordsets will be known only after apply
│     │ var.create is true
│     │ var.zone_id is null
│     │ var.zone_name is "qa.example.com"
│ 
│ 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.

When I replace in 2.3.0 two strings from 1.11.0 it works correctly
https://github.com/terraform-aws-modules/terraform-aws-route53/blob/v1.11.0/modules/records/main.tf#L3
https://github.com/terraform-aws-modules/terraform-aws-route53/blob/v1.11.0/modules/records/main.tf#L22

@rtdean
Copy link

rtdean commented Oct 6, 2021

It's worth noting that the complete example in the examples directory is currently broken with this issue as well.

@mustafa89
Copy link

mustafa89 commented Oct 8, 2021

Also facing this issue.

on .terraform/modules/records/modules/records/main.tf line 32, in resource "aws_route53_record" "this":
│   32:   for_each = var.create && (var.zone_id != null || var.zone_name != null) ? local.recordsets : tomap({})
│     ├────────────────
│     │ local.recordsets will be known only after apply
│     │ var.create is true
│     │ var.zone_id is "<redacted>"
│     │ var.zone_name is null
│
│ 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.

downgrading to 1.11.0 fixed it for me. Worth noting here, I came across this problem not at apply, but when I recreated the instances that the module was referencing.

@whiteley
Copy link

We had success downgrading to 2.0.0 and breakage with 2.1.0 (and all newer versions) suggesting the issue is isolated to def5327.

@goetzc
Copy link
Author

goetzc commented Oct 12, 2021

That is very useful @whiteley!

So looks like jsondecode(var.records) breaks the module?

@antonbabenko
Copy link
Member

Probably, it is related to jsondecode() and def5327 but WHYYY? :) And how to make it work?

I don't know the answer myself. If anyone has a working solution, please open a PR, and let's try to get it to work for terragrunt and terraform users.

@rtdean
Copy link

rtdean commented Oct 12, 2021 via email

@goetzc
Copy link
Author

goetzc commented Oct 15, 2021

I'm not sure if this is a use case for a dynamic block:
https://www.terraform.io/docs/language/expressions/dynamic-blocks.html

@anonymousobject
Copy link

Just dropping in to say I do not believe it is specific to jsondecode. I modified the module to remove any json en/decoding and I still get that error. Also, it seems to be specific to for_each. I further modified the code to use the old count style of iterating aws_route53_record and I encountered no errors. Unfortunately, I did not find a fix to continue using for_each.

@abcfy2
Copy link

abcfy2 commented Dec 3, 2021

Seem issue here. Will this will be fixed?

@gallowaystorm
Copy link

Is this going to be fixed anytime soon?

@antonbabenko
Copy link
Member

I am not sure how to fix this. If anyone has a possibility to look into this, please open a PR where this is implemented correctly for all cases (to begin with, examples/complete should work).

@abcfy2
Copy link

abcfy2 commented Dec 21, 2021

I am not sure how to fix this. If anyone has a possibility to look into this, please open a PR where this is implemented correctly for all cases (to begin with, examples/complete should work).

Maybe we should fallback to 2.0.0 without jsondecode?

@antonbabenko
Copy link
Member

jsondecode (or another workaround) is necessary to avoid multiple calls to the same module (e.g. when using terragrunt).

@sanguis
Copy link

sanguis commented Jan 5, 2022

Confirmed on terragrunt. Removing the jsonencode and reverting to v2.0.0

@flora-five
Copy link
Contributor

I would like to suggest a possible solution and I have prepared a draft PR #64.

When any attribute of a record depends on resources that have to be created, jsonencode makes the entire var.records a value that is not known yet and Terraform cannot use it to make a plan.

The idea of the proposed solution is to allow the caller of the records module to pass to it, in another variable, a list with fully-known keys that the module can use for its internal map. This list, with fully-known keys, allows Terraform to build the plan.

@github-actions
Copy link

github-actions bot commented Feb 6, 2022

This issue has been automatically marked as stale because it has been open 30 days
with no activity. Remove stale label or comment or this issue will be closed in 10 days

@github-actions github-actions bot added the stale label Feb 6, 2022
@goetzc
Copy link
Author

goetzc commented Feb 6, 2022

Confirmed on terragrunt. Removing the jsonencode and reverting to v2.0.0

I can confirm that old v2.0.0 works great.

@github-actions github-actions bot removed the stale label Feb 7, 2022
@CumpsD
Copy link

CumpsD commented Feb 19, 2022

Ran into this right now as well, on 2.5.0

@CumpsD
Copy link

CumpsD commented Feb 19, 2022

I reverted this commit on 2.5.0 and the error is gone: def5327

Working code:

locals {
  # terragrunt users have to provide `records` as jsonencode()'d string.
  # See details: https://github.com/gruntwork-io/terragrunt/issues/1211
  // records = try(jsondecode(var.records), var.records)

  # Convert `records` from list to map with unique keys
  #
  # A list of `records` values is jsonencode()'d to a string to solve issue:
  # The true result value has the wrong type:
  # element types must all match for conversion to map.
  # Ref:
  # https://github.com/terraform-aws-modules/terraform-aws-route53/issues/47
  # https://github.com/terraform-aws-modules/terraform-aws-route53/pull/39

  recordsets = {
    for rs in var.records :
    join(" ", compact(["${rs.name} ${rs.type}", lookup(rs, "set_identifier", "")])) => merge(rs, {
      records = jsonencode(try(rs.records, null))
    })
  }
}

@sejimono
Copy link

sejimono commented Mar 5, 2022

I reverted this commit on 2.5.0 and the error is gone: def5327

I can confirm that this issue is present on 2.5.0. CumpsD's suggestion worked for me.

Pure Terraform (no Terragrunt): v1.1.7
AWS Provider: v4.4.0

@github-actions
Copy link

github-actions bot commented Apr 5, 2022

This issue has been automatically marked as stale because it has been open 30 days
with no activity. Remove stale label or comment or this issue will be closed in 10 days

@github-actions github-actions bot added the stale label Apr 5, 2022
@goetzc
Copy link
Author

goetzc commented Apr 7, 2022

I would like to suggest a possible solution and I have prepared a draft PR #64.

When any attribute of a record depends on resources that have to be created, jsonencode makes the entire var.records a value that is not known yet and Terraform cannot use it to make a plan.

The idea of the proposed solution is to allow the caller of the records module to pass to it, in another variable, a list with fully-known keys that the module can use for its internal map. This list, with fully-known keys, allows Terraform to build the plan.

It sounds like a pretty good workaround for the current error.

Not being able to use this Terraform module because of Terragrunt miscompatibility is pretty weird.

@ajuddin
Copy link

ajuddin commented Apr 7, 2022

just trying to understand these two modules(zones and records)can this handle multiple zones and how do you map records to the respective zones.

@github-actions github-actions bot removed the stale label Apr 8, 2022
@jefferson-nagro
Copy link

I reverted this commit on 2.5.0 and the error is gone: def5327

I can confirm that this issue is present on 2.5.0. CumpsD's suggestion worked for me.

Pure Terraform (no Terragrunt): v1.1.7 AWS Provider: v4.4.0

Work same for me.

@air3ijai
Copy link

air3ijai commented Apr 30, 2022

module route53/records - can't create DNS records using zone_id, only by zone_name

module "delegation_records" {
  source  = "terraform-aws-modules/route53/aws//modules/records"
  version = "~> 2.6"

  zone_id = "XXXXXXXXXXXXXXXXXXXXX"
  records = [
    {
      name    = local.zone_prefix
      type    = "NS"
      ttl     = var.ns_ttl
      records = module.zones[0].route53_zone_name_servers[local.zone_name]
    }
  ] 
}
│ Error: Invalid for_each argument
│
│   on .terraform/modules/delegation_records/modules/records/main.tf line 32, in resource "aws_route53_record" "this":
│   32:   for_each = var.create && (var.zone_id != null || var.zone_name != null) ? local.recordsets : tomap({})
│     ├────────────────
│     │ local.recordsets will be known only after apply
│     │ var.create is true
│     │ var.zone_id is "XXXXXXXXXXXXXXXXXXXXX"
│     │ var.zone_name is null
│
│ 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.
╵

@air3ijai
Copy link

air3ijai commented May 1, 2022

Same issue with other resource dependency. It start to work only after resource random_string is created.
Also, it works fine with a regular resource.

Base code

# DNS zone
module "zones" {
  source  = "terraform-aws-modules/route53/aws//modules/zones"
  version = "2.6.0"

  zones = {
    "test.tld" = {
    }
  }
}

# Random prefix
resource "random_string" "test" {
  length  = 5
  special = false
}

Resource - ok

# DNS records - resource
resource "aws_route53_record" "test" {
  zone_id = var.zone_id
  name    = "test-${random_string.test.result}"
  type    = "A"
  records = ["1.1.1.1"]
}

Module - issue

# DNS records - module
module "records" {
  source  = "terraform-aws-modules/route53/aws//modules/records"
  version = "2.6.0"

  zone_name = "test.tld"
  records = [
    {
      name    = "test-${random_string.test.result}"
      type    = "A"
      records = ["1.1.1.1"]
    }
  ]
  depends_on = [
    module.zones,
    random_string.test
  ]
}

Error

╷
│ Error: Invalid for_each argument
│
│   on .terraform/modules/records/modules/records/main.tf line 32, in resource "aws_route53_record" "this":
│   32:   for_each = var.create && (var.zone_id != null || var.zone_name != null) ? local.recordsets : tomap({})
│     ├────────────────
│     │ local.recordsets will be known only after apply
│     │ var.create is true
│     │ var.zone_id is null
│     │ var.zone_name is "test.tld"
│
│ 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.

@maltyxx
Copy link

maltyxx commented May 12, 2022

Same problem I have downgrade to version 2.0.0.

@github-actions
Copy link

This issue has been automatically marked as stale because it has been open 30 days
with no activity. Remove stale label or comment or this issue will be closed in 10 days

@github-actions github-actions bot added the stale label Jun 12, 2022
@vtzan
Copy link

vtzan commented Jun 15, 2022

any update on this please? thanks

@antonbabenko
Copy link
Member

@vtzan Not yet. Hopefully, during this week, it will be improved.

@vtzan
Copy link

vtzan commented Jun 15, 2022

@vtzan Not yet. Hopefully, during this week, it will be improved.

Great news! Thank you @antonbabenko for your reply

@github-actions github-actions bot removed the stale label Jun 16, 2022
@flora-five
Copy link
Contributor

@antonbabenko There was a proposed solution in PR #64, but that PR has been closed automatically due to staleness. Just a gentle reminder about it, in case there isn't a better solution.

@antonbabenko
Copy link
Member

@vtzan I give up (for at least a couple of months). I tried to fix it for several hours more, involving @bryantbiggs, but it is still not working as we need. I opened #72 and added the explanation for anyone willing to give it a try.

@flora-five #64 is rather close, but I am not sure it is going to work with all examples. Also, it would be great if it did not introduce a new unnecessary/verbose variable (record_map_keys).

@antonbabenko
Copy link
Member

This issue has been resolved in version 2.8.1 🎉

@antonbabenko
Copy link
Member

Please give it a try! Big thanks to @flora-five for the final touch that fixed the unfixable :)

@vtzan
Copy link

vtzan commented Jun 19, 2022

Dear @antonbabenko @flora-five this is working great! Thanks alot for the fix.

@github-actions
Copy link

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues. If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 28, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet