Terraform首次运行创建AWS账号及SSO分配for_each报错修复方案
问题场景
需要在首次执行Terraform工作流时,一次性完成AWS组织账号创建与对应SSO账号分配配置,避免plan阶段因无法预判动态值触发for_each参数无效错误,报错信息为:
Invalid for_each argument local will be known only after apply
已尝试以下方案但均未解决问题:
- 将
aws_organizations_account资源与aws_ssoadmin_account_assignment资源拆分到完全独立的Terraform模块 - 在相关资源、模块间添加
depends_on显式依赖声明
当前使用Terraform版本为v1.2.4,报错截图如下:
涉及的原有配置代码:
子模块main.tf原有代码
resource "aws_organizations_account" "account" { name = var.aws_account_name email = "${var.aws_account_name}@gmail.com" tags = { Name = var.aws_account_name } parent_id = var.aws_org_folder_id } data "aws_identitystore_group" "this" { for_each = local.group_list identity_store_id = local.identity_store_id filter { attribute_path = "DisplayName" attribute_value = each.key } } data "aws_identitystore_user" "this" { for_each = local.user_list identity_store_id = local.identity_store_id filter { attribute_path = "UserName" attribute_value = each.key } } data "aws_ssoadmin_instances" "this" {} locals { assignment_map = { for a in var.account_assignments : format("%v-%v-%v-%v", aws_organizations_account.account.id, substr(a.principal_type, 0, 1), a.principal_name, a.permission_set_name) => a } identity_store_id = tolist(data.aws_ssoadmin_instances.this.identity_store_ids)[0] sso_instance_arn = tolist(data.aws_ssoadmin_instances.this.arns)[0] group_list = toset([for mapping in var.account_assignments : mapping.principal_name if mapping.principal_type == "GROUP"]) user_list = toset([for mapping in var.account_assignments : mapping.principal_name if mapping.principal_type == "USER"]) } resource "aws_ssoadmin_account_assignment" "this" { for_each = local.assignment_map instance_arn = local.sso_instance_arn permission_set_arn = each.value.permission_set_arn principal_id = each.value.principal_type == "GROUP" ? data.aws_identitystore_group.this[each.value.principal_name].id : data.aws_identitystore_user.this[each.value.principal_name].id principal_type = each.value.principal_type target_id = aws_organizations_account.account.id target_type = "AWS_ACCOUNT" }
根模块main.tf原有代码
module "sso_account_assignments" { source = "./modules/aws" account_assignments = [ { permission_set_arn = "arn:aws:sso:::permissionSet/ssoins-0000000000000000/ps-31d20e5987f0ce66", permission_set_name = "ReadOnlyAccess", principal_type = "GROUP", principal_name = "Administrators" }, { permission_set_arn = "arn:aws:sso:::permissionSet/ssoins-0000000000000000/ps-955c264e8f20fea3", permission_set_name = "ReadOnlyAccess", principal_type = "GROUP", principal_name = "Developers" }, { permission_set_arn = "arn:aws:sso:::permissionSet/ssoins-0000000000000000/ps-31d20e5987f0ce66", permission_set_name = "ReadOnlyAccess", principal_type = "GROUP", principal_name = "Developers" }, ] }
问题根因
Terraform对for_each参数有硬性要求:plan阶段必须能确定for_each传入集合的所有键值,才能计算出需要管理的资源数量和地址。
原有配置中local.assignment_map的键拼接了aws_organizations_account.account.id,这个值是AWS账号创建完成后才会返回的动态值,首次执行plan阶段完全未知,直接导致for_each校验失败。depends_on和模块拆分只能控制资源的执行先后顺序,无法让plan阶段提前拿到apply后才会生成的值,因此无法解决该问题。
最简最佳实践解决方案
不需要拆分模块、不需要加显式依赖,只需要修改local.assignment_map的键生成规则,全部使用plan阶段即可确定的静态字段作为map键,移除键中对动态账号ID的引用即可。
修改后的locals块代码如下:
locals { # 键仅使用入参中plan阶段就已知的静态字段,不再引用需apply后生成的账号ID assignment_map = { for a in var.account_assignments : format("%v-%v-%v", substr(a.principal_type, 0, 1), a.principal_name, a.permission_set_name) => a } identity_store_id = tolist(data.aws_ssoadmin_instances.this.identity_store_ids)[0] sso_instance_arn = tolist(data.aws_ssoadmin_instances.this.arns)[0] group_list = toset([for mapping in var.account_assignments : mapping.principal_name if mapping.principal_type == "GROUP"]) user_list = toset([for mapping in var.account_assignments : mapping.principal_name if mapping.principal_type == "USER"]) }
方案说明
- Terraform仅要求
for_each的键集合在plan阶段已知,资源参数中引用动态值是完全允许的。修改后aws_ssoadmin_account_assignment资源里的target_id依然引用aws_organizations_account.account.id,Terraform会自动处理依赖关系:等组织账号创建完成拿到真实ID后,再执行SSO分配资源的创建,无需手动声明depends_on。 - 修改后首次执行
terraform plan即可正常生成执行计划,terraform apply可以一次性完成账号创建和SSO分配全流程,不需要分步执行。 - 如果后续扩展为批量创建多个组织账号,只需要把每个账号自定义的静态唯一标识(比如账号名称、业务编码)作为
for_each键的一部分即可,永远不要将apply后才生成的动态资源ID作为for_each的键。
内容的提问来源于stack exchange,提问作者James
相关产品推荐
相关产品推荐

