Skip to content

Auto-collect roles from service_accounts without requiring empty iam_policy entries #6

@lukwam

Description

@lukwam

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions