Terraform中for_each用数据块作键源的跨环境报错问题排查
我编写了一段Terraform代码,用于将指定AD组中的用户添加至Databricks工作区,其中ad_group_names变量为AD组名称列表。
Terraform代码
# 确保名称正确且组存在于AD中 data "azuread_groups" "this" { display_names = var.ad_group_names } # 获取所有AD组详情 data "azuread_group" "this" { for_each = toset(data.azuread_groups.this.display_names) display_name = each.key } # 获取所有组中的用户 data "azuread_users" "this" { object_ids = flatten([for group in data.azuread_group.this : [group.members]]) } # 添加所有唯一用户至Databricks resource "databricks_user" "this" { for_each = { for user in distinct(data.azuread_users.this.users) : lower(user.mail) => user } user_name = lower(each.value.mail) external_id = each.value.object_id display_name = each.value.display_name databricks_sql_access = true workspace_access = true }
运行错误信息
Error: Invalid for_each argument │ │ on modules/module-databricks-ad-sync/main.tf line 43, in resource "databricks_user" "this": │ 43: for_each = { for user in distinct(data.azuread_users.this.users) : lower(user.mail) => user } │ ├──────────────── │ │ data.azuread_users.this.users is a list of object, known only after apply │ │ The "for_each" map includes keys derived from resource attributes that │ cannot be determined until apply, and so Terraform cannot determine the │ full set of keys that will identify the instances of this resource. │ │ When working with unknown values in for_each, it's better to define the map │ keys statically in your configuration and place apply-time results only in │ the map values. │ │ Alternatively, you could use the -target planning option to first apply │ only the resources that the for_each value depends on, and then apply a │ second time to fully converge. ╵
疑问
我了解Terraform文档要求for_each的映射键必须为已知值,但这段代码在已部署环境中可正常运行并添加新用户,新环境却报错。两个环境的服务主体、AD组、工具版本均一致(仅旧环境初始部署版本略有差异)。请问为何出现此差异,如何让代码在新环境正常运行?
编辑:
我已通过编写Python脚本查询Azure AD并生成包含所需组和用户的JSON文件,再将该文件提供给Terraform以实现需求,以此规避该问题。
新旧环境差异原因
旧环境能正常运行,是因为初始部署后,Azure AD相关数据源已被获取并缓存到Terraform状态文件中,后续运行时可依赖已有状态确定for_each的键。而新环境从零部署时,Terraform在规划阶段无法提前获取data.azuread_users.this.users的具体值——这些数据要到执行阶段才能从Azure AD拉取,导致for_each的键变成未知值,触发报错。
即便工具版本一致,旧环境初始部署的版本差异可能导致:该旧版本的Terraform或Azure AD Provider对未知值的校验更宽松,未严格限制for_each键的已知性;或者旧环境状态文件中留存的历史用户数据,让后续运行无需重新计算未知键。
修复方案(除脚本规避外)
方案1:分阶段部署
按照错误提示,先用-target参数优先部署所有数据源,让Terraform获取AD用户数据并写入状态:
terraform apply -target=data.azuread_groups.this -target=data.azuread_group.this -target=data.azuread_users.this
完成后再执行完整的terraform apply,此时for_each的键已为已知值,可正常运行。
方案2:调整数据源逻辑,提前确定键的唯一性
改用用户的object_id作为for_each的键——object_id来自AD组的members字段,而azuread_group数据源在规划阶段就能获取到已知的members列表,避免依赖users列表中的邮件字段:
# 提取所有唯一的用户ID locals { user_object_ids = distinct(flatten([for group in data.azuread_group.this : group.members])) } # 按用户ID逐个获取详情 data "azuread_user" "this" { for_each = toset(local.user_object_ids) object_id = each.key } # 添加用户至Databricks resource "databricks_user" "this" { for_each = data.azuread_user.this user_name = lower(each.value.mail) external_id = each.key display_name = each.value.display_name databricks_sql_access = true workspace_access = true }
这种方式下,for_each的键是规划阶段就能确定的用户object_id,Terraform可明确实例数量,不会触发未知值错误。
方案3:复用旧环境状态数据(限用户重叠场景)
如果新旧环境用户重叠度高,可通过terraform_remote_state引用旧环境状态中的用户ID列表,作为新环境for_each的初始键。但该方案灵活性较低,仅适用于特定场景。
内容的提问来源于stack exchange,提问作者Michał Wesołowski

