在 Terraform 中使用 for each 和 count together 的解决方法是什么?

What is the workaround of using for each and count together in Terraform?

我有两个条件需要满足:

  1. 根据 env 授予用户对特定 project-id 的权限。例如:my-project-{env} (env: stg/prd)
  2. 我想遍历变量,而不是为每个用户写下重复的 resource

示例:

variable some_ext_users {
  type = map(any)
  default = {
    user_1 = { email_id = "user_1@gmail.com" }
    user_2 = { email_id = "user_2@gmail.com" }
  }
}

为了避免对每个用户(假设 100++ 用户)重复资源,我决定将它们列在 variable 中,如上所述。

那么,我想给这些用户分配GCS权限,例如:

resource "google_storage_bucket_iam_member" "user_email_access" {
  for_each = var.some_ext_users
  count    = var.env == "stg" ? 1 : 0
  provider = google-beta
  bucket   = "my-bucketttt"
  role     = "roles/storage.objectViewer"
  member   = "user:${each.value.email_id}"
}

我得到的错误很清楚:

Error: Invalid combination of "count" and "for_each" on ../../../modules/my-tf.tf line 54, in resource "google_storage_bucket_iam_member" "user_email_access": 54:
for_each = var.some_ext_users The "count" and "for_each" meta-arguments are mutually-exclusive, only one should be used to be explicit about the number of resources to be created.

我的问题是,如果 countfor_each 不能一起使用,为了满足上述要求,有什么解决方法?

您可以根据环境控制用户列表,而不是试图控制资源。所以,像这样:

resource "google_storage_bucket_iam_member" "user_email_access" {
  for_each = var.env == "stg" ? var.some_ext_users : {}
  provider = google-beta
  bucket   = "my-bucketttt"
  role     = "roles/storage.objectViewer"
  member   = "user:${each.value.email_id}"
}

for_each 的规则是为它分配一个映射,每个实例都有一个要声明的元素,因此在这里考虑您的要求的最佳方式是您需要编写一个表达式来生成当您的条件不成立时,具有零元素的地图。

在 Terraform 中投影和过滤集合的常用方法是 for expressions, and indeed we can use a for expression with an if clause 有条件地过滤掉不需要的元素,在这种特殊情况下将是 all 元素:

resource "google_storage_bucket_iam_member" "user_email_access" {
  for_each = {
    for name, user in var.some_ext_users : name => user
    if var.env == "stg"
  }

  # ...
}

另一种可能的构造方法是将环境关键字作为数据结构的一部分,这会将所有信息保存在一个位置,并可能允许您拥有适用于多个环境的条目一次:

variable "some_ext_users" {
  type = map(object({
    email_id     = string
    environments = set(string)
  }))
  default = {
    user_1 = {
      email_id     = "user_1@gmail.com"
      environments = ["stg"]
    }
    user_2 = {
      email_id     = "user_2@gmail.com"
      environments = ["stg", "prd"]
    }
  }
}

resource "google_storage_bucket_iam_member" "user_email_access" {
  for_each = {
    for name, user in var.some_ext_users : name => user
    if contains(user.environments, var.env)
  }

  # ...
}

这是我上面链接的“过滤元素”文档中示例的变体,它使用 is_admin 标志来为管理员用户和非管理员用户声明不同的资源。在这种情况下,请注意 if 子句引用 for 表达式中声明的符号,这意味着我们现在可以为映射的每个元素获得不同的结果,而第一个示例要么保留所有元素或没有元素。