I have a terraform configuration which needs to:
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.
│ │ 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.
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.
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.
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.
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.
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]
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With