Invalid for_each argument when trying to create additive bindings
joe-a-t opened this issue · comments
Overview
If you try to create a project, service account, and service account binding at the same time, terraform errors out if the binding mode is additive but not if the binding mode is authoritative.
My suspicion is that this is because authoritative
uses known values for the keys in the for_each
but additive
uses the member
in the keys, which includes the service account's email and is not known until the service account has been created. If the helper could be refactored so that an unknown value (member
) is not in the keys, I suspect additive would no longer cause this error.
Sample Configuration
resource "google_project" "main" {
name = var.project_id
project_id = var.project_id
org_id = var.org_id
folder_id = var.folder_id
billing_account = var.billing_account
auto_create_network = false
}
module "google_test_sa" {
source = "git@github.com:terraform-google-modules/terraform-google-service-accounts"
project_id = google_project.main.project_id
names = ["google-test-sa"]
display_name = "google-test-sa"
}
module "impersonate_service_account_iam_bindings" {
source = "git@github.com:terraform-google-modules/terraform-google-iam//modules/service_accounts_iam"
service_accounts = [
module.google_test_sa.service_account.email,
]
project = google_project.main.project_id
mode = "additive"
bindings = {
"roles/storage.objectAdmin" = [
"serviceAccount:${module.google_test_sa.service_account.email}",
]
}
}
Error message
Error: Invalid for_each argument
on .terraform/modules/impersonate_service_account_iam_bindings/modules/service_accounts_iam/main.tf line 50, in resource "google_service_account_iam_member" "service_account_iam_additive":
50: for_each = module.helper.set_additive
The "for_each" value depends on resource attributes that cannot be determined
until apply, so Terraform cannot predict how many instances will be created.
To work around this, use the -target argument to first apply only the
resources that the for_each depends on.
Unfortunately there's no way to make the keys deterministic with this set up, I recommend using the google_service_account_iam_member
resource directly or using authoritative mode.
@morgante what about changing the schema of bindings
so that it can take both a name for the entity (which could be a known value) and the email? Then https://github.com/terraform-google-modules/terraform-google-iam/blob/master/modules/helper/main.tf#L85 could use that name instead of the member
when creating keys_additive
.
@morgante are you talking about https://github.com/terraform-google-modules/terraform-google-iam/blob/master/modules/helper/main.tf#L96? It already has both member and title so I'm not sure how it would work if you want to do a conditional with multiple members but if you're able to refactor that to solve this issue and can make description and expression optional, that would be much appreciated.
It already has both member and title so I'm not sure how it would work if you want to do a conditional with multiple members
Good point, but do you need multiple members for your use case?
It might be possible for us to special case the scenario where there's only 1 member, but otherwise I still think the best recommendation is to just use google_service_account_iam_member
directly.
Yes, we need multiple members. What about a change like:
advanced_bindings = [
{
role = "roles/storage.admin"
title = "expires_after_2019_12_31"
description = "Expiring at midnight of 2019-12-31"
expression = "request.time < timestamp(\"2020-01-01T00:00:00Z\")"
members = {
service_account = "serviceAccount:${var.sa_email}",
group = "group:${var.group_email}",
user = "user:${var.user_email}",
}
}
]
then the key in members (a known value) is used as ${member}
in https://github.com/terraform-google-modules/terraform-google-iam/blob/master/modules/helper/main.tf#L96. It does add more info you need to put in the advanced bindings but it would solve this issue.
And then also making title/description/expression optional
It seems like what you're trying to accomplish would best be done using the project_roles
parameter on the service accounts module.
project_roles
is possible. However, there is some awkwardness seeing what permissions are if you are creating a resource and a service account then trying to add permissions to both the new service account and existing group/service accounts/etc. Instead of having a single bindings location to determine what the permissions that are added in this directory are like this would provide,
module "google_test_sa" {
source = "git@github.com:terraform-google-modules/terraform-google-service-accounts"
project_id = google_project.main.project_id
names = ["google-test-sa"]
display_name = "google-test-sa"
}
module "impersonate_service_account_iam_bindings" {
source = "git@github.com:terraform-google-modules/terraform-google-iam//modules/service_accounts_iam"
service_accounts = [
module.google_test_sa.service_account.email,
]
project = google_project.main.project_id
mode = "additive"
bindings = {
"roles/storage.objectAdmin" = merge(
{
google-test-sa = "serviceAccount:${module.google_test_sa.service_account.email}"
},
var.storage_object_admins_map
)
}
}
you have to do this where the permission is added both in the service account creation and separately for the other objects.
module "google_test_sa" {
source = "git@github.com:terraform-google-modules/terraform-google-service-accounts"
project_id = google_project.main.project_id
names = ["google-test-sa"]
display_name = "google-test-sa"
project_roles = ["${google_project.main.project_id}=>roles/storage.objectAdmin"]
}
module "impersonate_service_account_iam_bindings" {
source = "git@github.com:terraform-google-modules/terraform-google-iam//modules/service_accounts_iam"
service_accounts = [
module.google_test_sa.service_account.email,
]
project = google_project.main.project_id
mode = "additive"
bindings = {
"roles/storage.objectAdmin" = var.storage_object_admins_list
}
}
That dynamic of having multiple scattershot places where the same permissions are added within a single directory is harder for users to follow. I know the ability to add the same permission in multiple places is the benefit of additive, but we would like to only have to have separate bindings if we are trying to add permissions in a different directory/statefile
Another option is targeted applies (terraform apply -target=module. google_test_sa
).
We're probably not going to change this in the module though as there are multiple different alternatives that can accommodate your use case without complicating the module interface unnecessarily.
This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days