Terraform创建新AWS账号时for_each参数依赖问题求助
解决Terraform创建新账号时CloudFormation StackSet实例的for_each错误
问题背景
通过本地变量映射AWS账号ID与账号类型,传递给子模块创建SSM补丁管理器的CloudFormation StackSet资源。现有账号配置运行正常,但通过module.accounts创建新账号时,执行terraform plan出现for_each参数无效错误,原因是新账号的ID要到apply阶段才能确定,导致Terraform无法提前预知for_each的键集合。
根模块本地变量与子模块调用代码
locals { org_sub_accounts_map = zipmap(module.accounts.account_id, module.accounts.tags_all.*.AccountType) org_sub_accounts = [for k, v in local.org_sub_accounts_map : { id = k type = v } ] } module "ssm_patch_manager" { source = "../../../../modules/aws/cloudformation/stacksets" accounts = local.org_sub_accounts account_exception_list = var.account_exception_list regions = var.region_list stackset_name = "SSM-PatchManager" template = "ssm_patch_manager" parameters = var.patch_manager_default_params parameter_overrides = var.patch_manager_params_overrides stackset_admin_role_arn = module.stackset_admin_role.role_arn depends_on = [module.accounts] }
local.org_sub_accounts示例:
org_sub_accounts = [ { "id" = "111111111111" "type" = "Dev" }, { "id" = "222222222222" "type" = "Prod" }, { "id" = "33333333333" "type" = "Dev" } ]
报错信息
Error: Invalid for_each argument on ../../../../modules/aws/cloudformation/stacksets/main.tf line 25, in resource "aws_cloudformation_stack_set_instance" "stack": 25: for_each = { 26: for stack_instance in local.instance_data : "${stack_instance.account}.${stack_instance.region}" => stack_instance if contains(var.account_exception_list, stack_instance.account) == false 27: } ├──────────────── │ local.instance_data will be known only after apply │ var.account_exception_list is list of string with 1 element 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.
子模块关键代码
locals { instance_data = flatten([ for account in var.accounts : [ for region in var.regions : { account = account.id type = try(length(account.type), 0) > 0 ? account.type : "default" # 兼容旧版输入,未传递type的情况 region = region } ] ]) } resource "aws_cloudformation_stack_set_instance" "stack" { for_each = { for stack_instance in local.instance_data : "${stack_instance.account}.${stack_instance.region}" => stack_instance if contains(var.account_exception_list, stack_instance.account) == false } account_id = each.value.account region = each.value.region parameter_overrides = lookup(var.parameter_overrides, each.value.type, null) # 根据子账号的AccountType标签使用不同参数 stack_set_name = aws_cloudformation_stack_set.stackset.name }
解决方案(无需大幅修改现有结构)
方案1:分阶段执行(官方临时 workaround)
先单独创建账号资源,让Terraform确定账号ID后再执行完整部署:
- 执行
terraform apply -target=module.accounts,优先创建新账号,此时账号ID会被写入状态文件 - 再执行
terraform plan和terraform apply,此时for_each能获取到已知的账号ID,不会再触发报错
方案2:用可预测标识生成for_each键(持久化解决)
如果不想每次分阶段执行,可以调整子模块的for_each键生成逻辑,用plan阶段就能确定的账号标识(比如预先定义的账号名称)代替账号ID:
- 根模块传递账号名称(假设
module.accounts输出account_names与account_id一一对应):locals { org_sub_accounts = [for idx, id in module.accounts.account_id : { id = id name = module.accounts.account_names[idx] type = module.accounts.tags_all[idx].AccountType } ] } - 子模块修改
instance_data和for_each:locals { instance_data = flatten([ for account in var.accounts : [ for region in var.regions : { account_id = account.id account_name = account.name type = try(length(account.type), 0) > 0 ? account.type : "default" region = region } ] ]) } resource "aws_cloudformation_stack_set_instance" "stack" { # 用账号名称+区域作为for_each键,名称在plan阶段即可确定 for_each = { for stack_instance in local.instance_data : "${stack_instance.account_name}.${stack_instance.region}" => stack_instance if contains(var.account_exception_list, stack_instance.account_id) == false } account_id = each.value.account_id region = each.value.region parameter_overrides = lookup(var.parameter_overrides, each.value.type, null) stack_set_name = aws_cloudformation_stack_set.stackset.name }
方案3:用terraform_data延迟资源创建(Terraform 1.4+适用)
通过terraform_data作为依赖屏障,确保账号创建完成后再处理StackSet实例:
resource "terraform_data" "wait_for_accounts" { input = var.accounts } resource "aws_cloudformation_stack_set_instance" "stack" { for_each = { for stack_instance in local.instance_data : "${stack_instance.account}.${stack_instance.region}" => stack_instance if contains(var.account_exception_list, stack_instance.account) == false } account_id = each.value.account region = each.value.region parameter_overrides = lookup(var.parameter_overrides, each.value.type, null) stack_set_name = aws_cloudformation_stack_set.stackset.name depends_on = [terraform_data.wait_for_accounts] }
注:此方案仅在部分场景下有效,核心仍需确保for_each的键可提前预测。
内容的提问来源于stack exchange,提问作者hlesnt395
相关产品推荐
相关产品推荐

