"for_each"值取决于无法确定的资源属性(地形)



我有一个地形配置,需要:

  1. 创建λ
  2. 调用 lambda
  3. 迭代返回数组的 lambda 的 json 结果,并在数组中的每个条目创建一个 CloudWatch 事件规则

相关代码如下所示:

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})"
}

问题是当我运行它时,我得到:

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.

我已经阅读了一堆关于这个问题的文章,但找不到解决方法.
问题是 Terraform 需要在创建 lambda 之前的规划阶段知道 lambda 返回的数组的大小.
解决此类任务的最佳方法是什么?
由于它是作为 CI/CD 管道的一部分运行的,因此我更喜欢不包含"-target"的解决方案旗。

一种可能性是重新考虑for_each,并在适当的情况下改用计数for_each有一些主要限制。我遇到了类似的事情(对我来说似乎是一个主要错误,但他们说这是一个功能) 假设我正在部署三个 VM,并希望将它们绑定到负载均衡器:

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"
...

当我尝试使用for_each时,我得到"for_each"值取决于无法确定的资源属性... 或元组错误。

失败:

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
}

作品*

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]
}

如果您想在纯地形中解决此问题,目前的解决方法是将您的部署拆分为多个堆栈/阶段(例如,首先使用 lambda 部署堆栈,然后使用 lambda 作为数据源的第二个堆栈)或者您已经发现,使用-target部分部署堆栈,然后部署整个堆栈。(在这种情况下,请务必删除depends_on,因为它会一直将数据源的读取延迟到应用阶段。

另一种选择是使用像 terragrunt 这样的工具,如果定义了这些模块之间的所有依赖关系,则通过以正确的顺序部署一组 terraform 模块来解决部分应用问题。 使用terragrunt,您可以在一次运行中部署所有内容,例如terragrunt apply-all.缺点是你仍然无法很好地预览CI中的更改,以便同行审查。

我建议将其分为两个阶段,因为您可能实际上希望在应用最终更改之前查看这两个阶段。否则,您最终可能会得到一个设置,即损坏的 lambda 会导致您或您的团队在未注意到的情况下破坏所有现有的 cloudwatch 规则。

工作一段时间

resource "aws_lb_target_group_attachment" "attach-in" {
for_each = toset([for x in module.webserver-in.instance : x.id])
target_group_arn  = aws_lb_target_group.tg-in.arn
target_id         = each.value
port             = 443
} 

完美工作

locals {
instance_ids = join("_", [for x in module.webserver.instance : x.id] )
}
resource "aws_lb_target_group_attachment" "attach-in" 
{
count = length(module.webserver-in.instance)
target_group_arn  = aws_lb_target_group.tg-in.arn
target_id         = split("_", local.instance_ids)[count.index]
port             = 443
}   

而不是for_each = toset(var.your_list)

用途for_each = toset(formatlist("%s", range(length(var.your_list))))

然后,您可以将该值用作your_list[each.key]


文档中提到的for_each不支持您尝试执行的操作。但是这里有一个小技巧来实现你想要做的事情:

解释: 由于未知值仅是for_each的限制,因此我们从for_each中删除未知部分。我们使用:

  • length()获取列表中的元素数。
  • range()生成列表,例如[0, 1, 2, 3]
  • formatlist()将列表项从数字转换为字符串。
  • var.your_list[each.key]遍历实际项目。

注意:我正在使用var.your_list,这是一个具有未知值的列表以进行简化,它可以是任何内容,例如在您的情况下来自数据源的结果。

最新更新