Problem
Currently, when using the service_accounts roles feature, you must also define empty arrays in iam_policy for those roles:
iam_policy = {
"roles/logging.logWriter" = [] # Required even though it's empty
"roles/datastore.user" = [] # Required even though it's empty
}
service_accounts = {
api-service = {
roles = [
"roles/logging.logWriter",
"roles/datastore.user",
]
}
}
This is because locals.tf only loops through roles present in var.iam_policy.
Solution
Update locals.tf to collect roles from BOTH iam_policy AND service_accounts:
locals {
sa_bindings = transpose({
for account, config in var.service_accounts : account => coalesce(config.roles, [])
})
# Collect all unique roles from BOTH sources
all_roles = distinct(concat(
keys(var.iam_policy == null ? {} : var.iam_policy),
keys(local.sa_bindings)
))
# Process ALL roles
iam_policy = {
for role in local.all_roles : role => sort(distinct(concat(
[for member in try(var.iam_policy[role], []) : replace(replace(member, "PROJECT_NUMBER", local.number), "PROJECT_ID", local.id)],
[for member in try(local.sa_bindings[role], []) : "serviceAccount:${member}@${local.id}.iam.gserviceaccount.com"]
)))
}
}
Benefits
- No need for empty array placeholders
- Cleaner configuration
- More intuitive behavior
Problem
Currently, when using the
service_accountsroles feature, you must also define empty arrays iniam_policyfor those roles:This is because
locals.tfonly loops through roles present invar.iam_policy.Solution
Update
locals.tfto collect roles from BOTHiam_policyANDservice_accounts:Benefits