Reputation: 731
I have two conditions need to be fulfilled:
project-id
based on env
. For example: my-project-{env}
(env: stg/prd)resource
for each user.Example:
variable some_ext_users {
type = map(any)
default = {
user_1 = { email_id = "[email protected]" }
user_2 = { email_id = "[email protected]" }
}
}
To avoid repetitive resource made on each user (imagine 100++ users), I decided to list them in variable
as written above.
Then, I'd like to assign these user GCS permission, e.g:
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}"
}
The error I'm getting is clear :
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.
My question is, what is the workaround in order to satisfy the requirements above if count
and for_each
can't be used together?
Upvotes: 0
Views: 814
Reputation: 74169
The rule for for_each
is to assign it a map that has one element per instance you want to declare, so the best way to think about your requirement here is that you need to write an expression that produces a map with zero elements when your condition doesn't hold.
The usual way to project and filter collections in Terraform is for
expressions, and indeed we can use a for
expression with an if
clause to conditionally filter out unwanted elements, which in this particular case will be all of the elements:
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"
}
# ...
}
Another possible way to structure this would be to include the environment keywords as part of the data structure, which would keep all of the information in one spot and potentially allow you to have entries that apply to more than one environment at once:
variable "some_ext_users" {
type = map(object({
email_id = string
environments = set(string)
}))
default = {
user_1 = {
email_id = "[email protected]"
environments = ["stg"]
}
user_2 = {
email_id = "[email protected]"
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)
}
# ...
}
This is a variation of the example in the "Filtering Elements" documentation I linked above, which uses an is_admin
flag in order to declare different resources for admin users vs. non-admin users. In this case, notice that the if
clause refers to the symbols declared in the for
expression, which means we can now get a different result for each element of the map, whereas the first example either kept all elements or no elements.
Upvotes: 1
Reputation: 8595
You could control the user list according to the environment, rather than trying to control the resource. So, something like this:
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}"
}
Upvotes: 1