Rationalize project factory context interpolations for automation service accounts (#2959)

* use different keys for automation service accounts

* inventory

* improve error handling on automation buckets
This commit is contained in:
Ludovico Magnocavallo
2025-03-16 16:40:47 +01:00
committed by GitHub
parent b50e8a16dc
commit 717f89dc00
5 changed files with 135 additions and 61 deletions

View File

@@ -196,8 +196,8 @@ Interpolations leverage contexts from two separate sources: an internal set for
The following table lists the available context interpolations. External contexts are passed in via the `factories_config.contexts` variable. IAM principals are interpolated in all IAM attributes except `iam_by_principal`. First two columns show for which attribute of which resource context is interpolated. `external contexts` column show in which map passed as `var.factories_config.context` key will be looked up.
* Internally created folders creates keys under `${folder_name_1}[/${folder_name_2}/${folder_name_3}]`
* IAM principals are resolved within context of managed project or use `${project}/${service_account}` to refer service account from other projects managed by the same project factory instance.
- Internally created folders creates keys under `${folder_name_1}[/${folder_name_2}/${folder_name_3}]`
- IAM principals are resolved within context of managed project or use `${project}/${service_account}` to refer service account from other projects managed by the same project factory instance.
| resource | attribute | external contexts | internal contexts |
|---------------------|-----------------|---------------------|------------------------------------|
@@ -400,9 +400,9 @@ services:
- storage.googleapis.com
iam:
"roles/owner":
- rw
- automation/rw
"roles/viewer":
- ro
- automation/ro
shared_vpc_host_config:
enabled: true
automation:
@@ -416,12 +416,12 @@ automation:
description: Team B app 0 Terraform state bucket.
iam:
roles/storage.objectCreator:
- rw
- automation/rw
roles/storage.objectViewer:
- gcp-devops
- group:team-b-admins@example.org
- rw
- ro
- automation/rw
- automation/ro
# tftest-file id=7 path=data/projects/dev-tb-app0-0.yaml schema=project.schema.json
```

View File

@@ -35,7 +35,7 @@ locals {
]
])
}
output "foo" { value = local.automation_buckets }
module "automation-bucket" {
source = "../gcs"
for_each = local.automation_buckets
@@ -58,9 +58,20 @@ module "automation-bucket" {
for k, v in lookup(each.value, "iam_bindings", {}) : k => merge(v, {
members = [
for vv in v.members : try(
# rw (infer local project and automation prefix)
module.automation-service-accounts["${each.key}/automation/${vv}"].iam_email,
# automation/rw or sa (infer local project)
module.automation-service-accounts["${each.key}/${vv}"].iam_email,
# project/automation/rw project/sa
var.factories_config.context.iam_principals[vv],
vv
# fully specified principal
vv,
# passthrough + error handling using tonumber until Terraform gets fail/raise function
(
strcontains(vv, ":")
? vv
: tonumber("[Error] Invalid member: '${vv}' in automation bucket '${each.key}'")
)
)
]
})
@@ -94,7 +105,7 @@ module "automation-bucket" {
module "automation-service-accounts" {
source = "../iam-service-account"
for_each = {
for k in local.automation_sa : "${k.project}/${k.name}" => k
for k in local.automation_sa : "${k.project}/automation/${k.name}" => k
}
# we cannot use interpolation here as we would get a cycle
# from the IAM dependency in the outputs of the main project
@@ -107,6 +118,7 @@ module "automation-service-accounts" {
"display_name",
"Service account ${each.value.name} for ${each.value.project}."
)
# TODO: also support short form for service accounts in this project
iam = {
for k, v in lookup(each.value, "iam", {}) : k => [
for vv in v : lookup(

View File

@@ -106,16 +106,22 @@ module "projects-iam" {
iam = {
for k, v in lookup(each.value, "iam", {}) : k => [
for vv in v : try(
# project service accounts
# project service accounts (sa)
module.service-accounts["${each.key}/${vv}"].iam_email,
# automation service account
# automation service account (rw)
local.context.iam_principals["${each.key}/automation/${vv}"],
# automation service account (automation/rw)
local.context.iam_principals["${each.key}/${vv}"],
# other projects service accounts
# other projects service accounts (project/sa)
module.service-accounts[vv].iam_email,
# other automation service account
# other automation service account (project/automation/rw)
local.context.iam_principals[vv],
# passthrough + error handling using tonumber until Terraform gets fail/raise function
strcontains(vv, ":") ? vv : tonumber("[Error] Invalid member: '${vv}' in project '${each.key}'")
(
strcontains(vv, ":")
? vv
: tonumber("[Error] Invalid member: '${vv}' in project '${each.key}'")
)
)
]
}
@@ -123,16 +129,22 @@ module "projects-iam" {
for k, v in lookup(each.value, "iam_bindings", {}) : k => merge(v, {
members = [
for vv in v.members : try(
# project service accounts
# project service accounts (sa)
module.service-accounts["${each.key}/${vv}"].iam_email,
# automation service account
# automation service account (rw)
local.context.iam_principals["${each.key}/automation/${vv}"],
# automation service account (automation/rw)
local.context.iam_principals["${each.key}/${vv}"],
# other projects service accounts
# other projects service accounts (project/sa)
module.service-accounts[vv].iam_email,
# other automation service account
# other automation service account (project/automation/rw)
local.context.iam_principals[vv],
# passthrough + error handling using tonumber until Terraform gets fail/raise function
strcontains(vv, ":") ? vv : tonumber("[Error] Invalid member: '${vv}' in project '${each.key}'")
(
strcontains(vv, ":")
? vv
: tonumber("[Error] Invalid member: '${vv}' in project '${each.key}'")
)
)
]
})
@@ -140,16 +152,22 @@ module "projects-iam" {
iam_bindings_additive = {
for k, v in lookup(each.value, "iam_bindings_additive", {}) : k => merge(v, {
member = try(
# project service accounts
# project service accounts (sa)
module.service-accounts["${each.key}/${v.member}"].iam_email,
# automation service account
# automation service account (rw)
local.context.iam_principals["${each.key}/automation/${v.member}"],
# automation service account (automation/rw)
local.context.iam_principals["${each.key}/${v.member}"],
# other projects service accounts
# other projects service accounts (project/sa)
module.service-accounts[v.member].iam_email,
# other automation service account
# other automation service account (project/automation/rw)
local.context.iam_principals[v.member],
# passthrough + error handling using tonumber until Terraform gets fail/raise function
strcontains(v.member, ":") ? v.member : tonumber("[Error] Invalid member: '${v.member}' in project '${each.key}'")
(
strcontains(v.member, ":")
? v.member
: tonumber("[Error] Invalid member: '${v.member}' in project '${each.key}'")
)
)
})
}
@@ -164,32 +182,38 @@ module "projects-iam" {
try(each.value.shared_vpc_service_config.host_project, null) == null
? null
: merge(each.value.shared_vpc_service_config, {
host_project = lookup(
var.factories_config.context.vpc_host_projects,
each.value.shared_vpc_service_config.host_project,
host_project = try(
var.factories_config.context.vpc_host_projects[each.value.shared_vpc_service_config.host_project],
module.projects[each.value.shared_vpc_service_config.host_project].project_id,
each.value.shared_vpc_service_config.host_project
)
network_users = [
for v in try(each.value.shared_vpc_service_config.network_users, []) :
for vv in try(each.value.shared_vpc_service_config.network_users, []) :
try(
# project service accounts
module.service-accounts["${each.key}/${v}"].iam_email,
# automation service account
local.context.iam_principals["${each.key}/${v}"],
# other projects service accounts
module.service-accounts[v].iam_email,
# other automation service account
local.context.iam_principals[v],
# project service accounts (sa)
module.service-accounts["${each.key}/${vv}"].iam_email,
# automation service account (rw)
local.context.iam_principals["${each.key}/automation/${vv}"],
# automation service account (automation/rw)
local.context.iam_principals["${each.key}/${vv}"],
# other projects service accounts (project/sa)
module.service-accounts[vv].iam_email,
# other automation service account (project/automation/rw)
local.context.iam_principals[vv],
# passthrough + error handling using tonumber until Terraform gets fail/raise function
strcontains(v, ":") ? v : tonumber("[Error] Invalid member: '${v}' in project '${each.key}'")
(
strcontains(vv, ":")
? vv
: tonumber("[Error] Invalid member: '${vv}' in project '${each.key}'")
)
)
]
# TODO: network subnet users
})
)
# add service agents config, so Service Agents can be referred in the IAM grants
service_agents_config = {
grant_default_roles = false # Default roles are granted in module.project
# default roles are granted in module.project
grant_default_roles = false
}
}
@@ -205,16 +229,22 @@ module "buckets" {
iam = {
for k, v in each.value.iam : k => [
for vv in v : try(
# project service accounts
# project service accounts (sa)
module.service-accounts["${each.value.project}/${vv}"].iam_email,
# automation service account
# automation service account (rw)
local.context.iam_principals["${each.value.project}/automation/${vv}"],
# automation service account (automation/rw)
local.context.iam_principals["${each.value.project}/${vv}"],
# other projects service accounts
# other projects service accounts (project/sa)
module.service-accounts[vv].iam_email,
# other automation service account
# other automation service account (project/automation/rw)
local.context.iam_principals[vv],
# passthrough + error handling using tonumber until Terraform gets fail/raise function
strcontains(vv, ":") ? vv : tonumber("[Error] Invalid member: '${vv}' for bucket '${each.key}' in project '${each.value.project}'")
(
strcontains(vv, ":")
? vv
: tonumber("[Error] Invalid member: '${vv}' in project '${each.value.project}'")
)
)
]
}
@@ -222,16 +252,22 @@ module "buckets" {
for k, v in each.value.iam_bindings : k => merge(v, {
members = [
for vv in v.members : try(
# project service accounts
# project service accounts (sa)
module.service-accounts["${each.value.project}/${vv}"].iam_email,
# automation service account
# automation service account (rw)
local.context.iam_principals["${each.value.project}/automation/${vv}"],
# automation service account (automation/rw)
local.context.iam_principals["${each.value.project}/${vv}"],
# other projects service accounts
# other projects service accounts (project/sa)
module.service-accounts[vv].iam_email,
# other automation service account
# other automation service account (project/automation/rw)
local.context.iam_principals[vv],
# passthrough + error handling using tonumber until Terraform gets fail/raise function
strcontains(vv, ":") ? vv : tonumber("[Error] Invalid member: '${vv}' for bucket '${each.key}' in project '${each.value.project}'")
(
strcontains(vv, ":")
? vv
: tonumber("[Error] Invalid member: '${vv}' in project '${each.value.project}'")
)
)
]
})
@@ -239,16 +275,22 @@ module "buckets" {
iam_bindings_additive = {
for k, v in each.value.iam_bindings_additive : k => merge(v, {
member = try(
# project service accounts
# project service accounts (sa)
module.service-accounts["${each.value.project}/${v.member}"].iam_email,
# automation service account
# automation service account (rw)
local.context.iam_principals["${each.value.project}/automation/${v.member}"],
# automation service account (automation/rw)
local.context.iam_principals["${each.value.project}/${v.member}"],
# other projects service accounts
# other projects service accounts (project/sa)
module.service-accounts[v.member].iam_email,
# other automation service account
# other automation service account (project/automation/rw)
local.context.iam_principals[v.member],
# passthrough + error handling using tonumber until Terraform gets fail/raise function
strcontains(v.member, ":") ? v.member : tonumber("[Error] Invalid member: '${v.member}' for bucket '${each.key}' in project '${each.value.project}'")
(
strcontains(v.member, ":")
? v.member
: tonumber("[Error] Invalid member: '${v.member}' in project '${each.value.project}'")
)
)
})
}