You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 06:04:55