Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The "for_each" value depends on resource attributes that cannot be determined (Terraform)

Tags:

terraform

I have a terraform configuration which needs to:

  1. Create a lambda
  2. Invoke the lambda
  3. Iterate on the lambda's json result which returns an array and create a CloudWatch event rule per entry in the array

The relevant code looks like:

Create lambda code...

data "aws_lambda_invocation" "run_lambda" {
  function_name = "${aws_lambda_function.deployed_lambda.function_name}"

  input = <<JSON
  {}
  JSON
  depends_on = [aws_lambda_function.deployed_lambda]
}

resource "aws_cloudwatch_event_rule" "aws_my_cloudwatch_rule" {
  for_each = {for record in jsondecode(data.aws_lambda_invocation.run_lambda.result).entities : record.entityName => record} 
    name = "${each.value.entityName}-event"
    description = "Cloudwatch rule for ${each.value.entityName}"
    schedule_expression = "cron(${each.value.cronExpression})"
}

The problem is that when I run it, I get:

Error: Invalid for_each argument

  on lambda.tf line 131, in resource "aws_cloudwatch_event_rule" "aws_my_cloudwatch_rule":
 131:   for_each = {for record in jsondecode(data.aws_lambda_invocation.aws_lambda_invocation.result).entities : record.entityName => record}

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.

I've read a bunch of posts on the problem but couldn't find a workaround.
The problem is that Terraform needs to know the size of the array returned by the lambda in the planning phase before the lambda was created.
What is the best approach to solving such a task?
Since it is run as part of a CI/CD pipeline I prefer a solution that doesn't include the "-target" flag.

like image 768
Avner Levy Avatar asked Sep 06 '20 21:09

Avner Levy


People also ask

When working with unknown values in For_each it's better to use a map value where the keys are defined statically?

│ │ When working with unknown values in for_each, it's better to use a map value where the keys are defined statically in your configuration and where only the values contain apply-time results.

What is the difference between Count and For_each in Terraform?

If the resources you are provisioning are identical or nearly identical, then count is a safe bet. However if elements of the resources change between the different instances, then for_each is the way to go.

How do you use depends on Terraform?

Use the depends_on meta-argument to handle hidden resource or module dependencies that Terraform cannot automatically infer. You only need to explicitly specify a dependency when a resource or module relies on another resource's behavior but does not access any of that resource's data in its arguments.

What is Count Index Terraform?

When count is set, Terraform distinguishes between the block itself and the multiple resource or module instances associated with it. Instances are identified by an index number, starting with 0 . <TYPE>. <NAME> or module.


2 Answers

If you want to solve this in pure terraform, the workarounds are at the moment to either split your deployment into multiple stacks/phases (e.g. first deploy a stack with the lambda, and then the second stack that is using the lambda as a data source) or as you already found out, partially deploy your stack using -target and then deploy the full stack. (Be sure to remove the depends_on in this case as it will defer reading the data source to the apply phase all the time.)

Another option is to use a tool like terragrunt that solves the partial apply issue by deploying a set of terraform modules in the right order if all dependencies between those modules are defined. with terragrunt you can deploy everything in a single run of e.g. terragrunt apply-all. The Downside is you still won't get a nice preview of changes in your CI to be reviewed by your peers.

I would suggest splitting this into two phases as you might actually want to review both before applying the final changes. Else you might end up with a setup where a broken lambda results in destroying all your existing cloudwatch rules unnoticed by you or your team.

like image 64
mariux Avatar answered Nov 16 '22 00:11

mariux


One possibility is to reconsider for_each and use count instead, if appropriate. for_each has some major limitations. I ran into something similar (seems like a major bug to me, but they say it is a feature) Consider I am deploying three vms, and want to bind them to a load balancer:

resource "aws_instance" "xxx-IIS-004" {

  ami               = var.ami["Windows Server 2019"]
  instance_type     = var.depoy_lowcost ? var.default_instance_type : "m5.2xlarge"
  count             = "3"
  ...

When I try to use for_each, I get The “for_each” value depends on resource attributes that cannot be determined... or Tuple error.

Fails:

resource "aws_elb_attachment" "attachments_004" {
  depends_on = [ aws_instance.xxx-IIS-004 ]
  elb        = data.aws_elb.loadBalancer.id
  for_each   = aws_instance.xxx-IIS-004[*]
  instance   = each.value.id
}

Works*

locals {
  att_004 = join("_", aws_instance.xxx-IIS-004[*].id )
}

resource "aws_elb_attachment" "attachments_004" {
  depends_on = [ aws_instance.xxx-IIS-004 ]
  elb        = data.aws_elb.loadBalancer.id
  count      = length( aws_instance.xxx-IIS-004 )
  instance   = split("_", local.att_004)[count.index]
}
like image 30
jlo-gmail Avatar answered Nov 15 '22 23:11

jlo-gmail