Allow skipping data source in service account module (#3450)

* test implementation

* wip

* service account reuse

* fix fast stage test

* revert cicd changes

* remove unused dep

* add comment on extra condition
This commit is contained in:
Ludovico Magnocavallo
2025-10-22 13:04:00 +02:00
committed by GitHub
parent 7b272da6b6
commit 7ea9612b07
8 changed files with 133 additions and 39 deletions

View File

@@ -138,8 +138,8 @@ module "service-account-with-tags" {
| [iam_storage_roles](variables-iam.tf#L103) | Storage roles granted to this service account, by bucket name. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [prefix](variables.tf#L60) | Prefix applied to service account names. | <code>string</code> | | <code>null</code> |
| [project_number](variables.tf#L75) | Project number of var.project_id. Set this to avoid permadiffs when creating tag bindings. | <code>string</code> | | <code>null</code> |
| [service_account_create](variables.tf#L81) | Create service account. When set to false, uses a data source to reference an existing service account. | <code>bool</code> | | <code>true</code> |
| [tag_bindings](variables.tf#L88) | Tag bindings for this service accounts, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [service_account_reuse](variables.tf#L81) | Reuse existing service account if not null. Data source can be forced disabled if tag bindings are not used, or unique id is set. | <code title="object&#40;&#123;&#10; use_data_source &#61; optional&#40;bool, true&#41;&#10; attributes &#61; optional&#40;object&#40;&#123;&#10; unique_id &#61; string&#10; &#125;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [tag_bindings](variables.tf#L92) | Tag bindings for this service accounts, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
## Outputs
@@ -150,4 +150,5 @@ module "service-account-with-tags" {
| [id](outputs.tf#L33) | Fully qualified service account id. | |
| [name](outputs.tf#L41) | Service account name. | |
| [service_account](outputs.tf#L49) | Service account resource. | |
| [unique_id](outputs.tf#L54) | Fully qualified service account id. | |
<!-- END TFDOC -->

View File

@@ -37,20 +37,30 @@ locals {
"projects/${local.project_id}/serviceAccounts/${local.static_email}"
)
service_account = (
var.service_account_create
? try(google_service_account.service_account[0], null)
: try(data.google_service_account.service_account[0], null)
local.use_data_source
? try(data.google_service_account.service_account[0], null)
: try(google_service_account.service_account[0], null)
)
# universe-related locals
universe = try(regex("^([^:]*):[a-z]", local.project_id)[0], "")
universe = try(regex("^([^:]*):[a-z]", local.project_id)[0], "")
use_data_source = (
try(var.service_account_reuse.use_data_source, null) == true
)
project_id_no_universe = element(split(":", local.project_id), 1)
sa_domain = join(".", compact([
local.project_id_no_universe, local.universe
]))
# the condition here avoids referring to attributes not known at plan time
tag_bindings = (
var.service_account_reuse != null ||
try(var.service_account_reuse.attributes.unique_id, null) != null
? {}
: var.tag_bindings
)
}
data "google_service_account" "service_account" {
count = var.service_account_create ? 0 : 1
count = local.use_data_source ? 1 : 0
project = (
strcontains(local.project_id, ":")
? join(".", reverse(split(":", local.project_id)))
@@ -60,7 +70,7 @@ data "google_service_account" "service_account" {
}
resource "google_service_account" "service_account" {
count = var.service_account_create ? 1 : 0
count = local.use_data_source ? 0 : 1
project = local.project_id
account_id = "${local.prefix}${local.name}"
display_name = var.display_name
@@ -69,7 +79,7 @@ resource "google_service_account" "service_account" {
}
resource "google_tags_tag_binding" "binding" {
for_each = var.tag_bindings
for_each = local.tag_bindings
parent = "//iam.googleapis.com/projects/${coalesce(var.project_number, var.project_id)}/serviceAccounts/${local.service_account.unique_id}"
tag_value = lookup(local.ctx.tag_values, each.value, each.value)
}

View File

@@ -50,3 +50,8 @@ output "service_account" {
description = "Service account resource."
value = local.service_account
}
output "unique_id" {
description = "Fully qualified service account id."
value = local.service_account.unique_id
}

View File

@@ -35,8 +35,8 @@ variable "create_ignore_already_exists" {
type = bool
default = null
validation {
condition = !(var.create_ignore_already_exists == true && var.service_account_create == false)
error_message = "Cannot set create_ignore_already_exists when service_account_create is false."
condition = !(var.create_ignore_already_exists == true && var.service_account_reuse == null)
error_message = "Cannot set create_ignore_already_exists when service_account_reuse is null."
}
}
@@ -78,11 +78,15 @@ variable "project_number" {
default = null
}
variable "service_account_create" {
description = "Create service account. When set to false, uses a data source to reference an existing service account."
type = bool
default = true
nullable = false
variable "service_account_reuse" {
description = "Reuse existing service account if not null. Data source can be forced disabled if tag bindings are not used, or unique id is set."
type = object({
use_data_source = optional(bool, true)
attributes = optional(object({
unique_id = string
}))
})
default = null
}
variable "tag_bindings" {

View File

@@ -156,8 +156,13 @@ module "automation-service-accounts-iam" {
project_id = (
module.automation-service-accounts[each.key].service_account.project
)
name = module.automation-service-accounts[each.key].name
service_account_create = false
name = module.automation-service-accounts[each.key].name
service_account_reuse = {
use_data_source = false
attributes = {
unique_id = module.automation-service-accounts[each.key].unique_id
}
}
context = merge(local.ctx, {
service_account_ids = local.project_sas_ids
})

View File

@@ -84,8 +84,13 @@ module "service_accounts-iam" {
project_id = (
module.service-accounts[each.key].service_account.project
)
name = each.value.name
service_account_create = false
name = each.value.name
service_account_reuse = {
use_data_source = false
attributes = {
unique_id = module.service-accounts[each.key].unique_id
}
}
context = merge(local.ctx, {
project_ids = local.ctx_project_ids
iam_principals = merge(

View File

@@ -530,12 +530,14 @@ values:
members:
- serviceAccount:iac-org-ro@ft0-prod-iac-core-0.iam.gserviceaccount.com
role: organizations/1234567890/roles/storageViewer
timeouts: null
? module.factory.module.buckets["iac-0/iac-org-state"].google_storage_bucket_iam_binding.authoritative["roles/storage.admin"]
: bucket: ft0-prod-iac-core-0-iac-org-state
condition: []
members:
- serviceAccount:iac-org-rw@ft0-prod-iac-core-0.iam.gserviceaccount.com
role: roles/storage.admin
timeouts: null
module.factory.module.buckets["iac-0/iac-outputs"].google_storage_bucket.bucket[0]:
autoclass: []
cors: []
@@ -576,6 +578,7 @@ values:
- serviceAccount:iac-security-ro@ft0-prod-iac-core-0.iam.gserviceaccount.com
- serviceAccount:iac-vpcsc-ro@ft0-prod-iac-core-0.iam.gserviceaccount.com
role: organizations/1234567890/roles/storageViewer
timeouts: null
module.factory.module.buckets["iac-0/iac-outputs"].google_storage_bucket_iam_binding.authoritative["roles/storage.admin"]:
bucket: ft0-prod-iac-core-0-iac-outputs
condition: []
@@ -587,6 +590,7 @@ values:
- serviceAccount:iac-security-rw@ft0-prod-iac-core-0.iam.gserviceaccount.com
- serviceAccount:iac-vpcsc-rw@ft0-prod-iac-core-0.iam.gserviceaccount.com
role: roles/storage.admin
timeouts: null
module.factory.module.buckets["iac-0/iac-stage-state"].google_storage_bucket.bucket[0]:
autoclass: []
cors: []
@@ -1630,8 +1634,13 @@ values:
member: serviceAccount:iac-vpcsc-rw@ft0-prod-iac-core-0.iam.gserviceaccount.com
project: ft0-prod-iac-core-0
timeouts: null
module.factory.module.service_accounts-iam["iac-0/iac-org-cicd-ro"].data.google_service_account.service_account[0]:
module.factory.module.service_accounts-iam["iac-0/iac-org-cicd-ro"].google_service_account.service_account[0]:
account_id: iac-org-cicd-ro
create_ignore_already_exists: null
description: null
disabled: false
display_name: Terraform-managed.
timeouts: null
? module.factory.module.service_accounts-iam["iac-0/iac-org-cicd-ro"].google_service_account_iam_member.additive["$service_account_ids:iac-0/iac-org-ro-roles/iam.serviceAccountTokenCreator"]
: condition: []
role: roles/iam.serviceAccountTokenCreator
@@ -1640,8 +1649,13 @@ values:
: condition: []
role: roles/iam.workloadIdentityUser
service_account_id: projects/ft0-prod-iac-core-0/serviceAccounts/iac-org-ro@ft0-prod-iac-core-0.iam.gserviceaccount.com
module.factory.module.service_accounts-iam["iac-0/iac-org-cicd-rw"].data.google_service_account.service_account[0]:
module.factory.module.service_accounts-iam["iac-0/iac-org-cicd-rw"].google_service_account.service_account[0]:
account_id: iac-org-cicd-rw
create_ignore_already_exists: null
description: null
disabled: false
display_name: Terraform-managed.
timeouts: null
? module.factory.module.service_accounts-iam["iac-0/iac-org-cicd-rw"].google_service_account_iam_member.additive["$service_account_ids:iac-0/iac-org-rw-roles/iam.serviceAccountTokenCreator"]
: condition: []
role: roles/iam.serviceAccountTokenCreator
@@ -2395,6 +2409,12 @@ values:
- group:fabric-fast-owners@google.com
org_id: '1234567890'
role: roles/compute.osLoginExternalUser
module.organization-iam[0].google_organization_iam_binding.authoritative["roles/compute.viewer"]:
condition: []
members:
- serviceAccount:iac-networking-ro@ft0-prod-iac-core-0.iam.gserviceaccount.com
org_id: '1234567890'
role: roles/compute.viewer
module.organization-iam[0].google_organization_iam_binding.authoritative["roles/compute.xpnAdmin"]:
condition: []
members:
@@ -2402,12 +2422,6 @@ values:
- serviceAccount:iac-networking-rw@ft0-prod-iac-core-0.iam.gserviceaccount.com
org_id: '1234567890'
role: roles/compute.xpnAdmin
module.organization-iam[0].google_organization_iam_binding.authoritative["roles/compute.viewer"]:
condition: []
members:
- serviceAccount:iac-networking-ro@ft0-prod-iac-core-0.iam.gserviceaccount.com
org_id: '1234567890'
role: roles/compute.viewer
module.organization-iam[0].google_organization_iam_binding.authoritative["roles/essentialcontacts.admin"]:
condition: []
members:
@@ -2623,6 +2637,66 @@ values:
role_id: networkFirewallPoliciesAdmin
stage: GA
title: Custom role networkFirewallPoliciesAdmin
module.organization[0].google_organization_iam_custom_role.roles["ngfw_enterprise_admin"]:
description: Terraform-managed.
org_id: '1234567890'
permissions:
- networksecurity.firewallEndpoints.create
- networksecurity.firewallEndpoints.delete
- networksecurity.firewallEndpoints.get
- networksecurity.firewallEndpoints.list
- networksecurity.firewallEndpoints.update
- networksecurity.firewallEndpoints.use
- networksecurity.locations.get
- networksecurity.locations.list
- networksecurity.operations.cancel
- networksecurity.operations.delete
- networksecurity.operations.get
- networksecurity.operations.list
- networksecurity.securityProfileGroups.create
- networksecurity.securityProfileGroups.delete
- networksecurity.securityProfileGroups.get
- networksecurity.securityProfileGroups.list
- networksecurity.securityProfileGroups.update
- networksecurity.securityProfileGroups.use
- networksecurity.securityProfiles.create
- networksecurity.securityProfiles.delete
- networksecurity.securityProfiles.get
- networksecurity.securityProfiles.list
- networksecurity.securityProfiles.update
- networksecurity.securityProfiles.use
- networksecurity.tlsInspectionPolicies.create
- networksecurity.tlsInspectionPolicies.delete
- networksecurity.tlsInspectionPolicies.get
- networksecurity.tlsInspectionPolicies.list
- networksecurity.tlsInspectionPolicies.update
- networksecurity.tlsInspectionPolicies.use
role_id: ngfwEnterpriseAdmin
stage: GA
title: Custom role ngfwEnterpriseAdmin
module.organization[0].google_organization_iam_custom_role.roles["ngfw_enterprise_viewer"]:
description: Terraform-managed.
org_id: '1234567890'
permissions:
- networksecurity.firewallEndpoints.get
- networksecurity.firewallEndpoints.list
- networksecurity.firewallEndpoints.use
- networksecurity.locations.get
- networksecurity.locations.list
- networksecurity.operations.get
- networksecurity.operations.list
- networksecurity.securityProfileGroups.get
- networksecurity.securityProfileGroups.list
- networksecurity.securityProfileGroups.use
- networksecurity.securityProfiles.get
- networksecurity.securityProfiles.list
- networksecurity.securityProfiles.use
- networksecurity.tlsInspectionPolicies.get
- networksecurity.tlsInspectionPolicies.list
- networksecurity.tlsInspectionPolicies.use
role_id: ngfwEnterpriseViewer
stage: GA
title: Custom role ngfwEnterpriseViewer
module.organization[0].google_organization_iam_custom_role.roles["organization_admin_viewer"]:
description: Terraform-managed.
org_id: '1234567890'
@@ -2764,6 +2838,7 @@ values:
input: null
output: null
triggers_replace: null
counts:
google_bigquery_dataset: 1
google_bigquery_default_service_account: 2

View File

@@ -273,9 +273,6 @@ values:
- serviceAccount:app-0-be@test-pf-dev-tb-app0-1.iam.gserviceaccount.com
project: test-pf-dev-tb-app0-1
role: roles/run.developer
module.project-factory.module.projects["dev-ta-app0-be"].data.google_storage_project_service_account.gcs_sa[0]:
project: test-pf-dev-ta-app0-be
user_project: null
module.project-factory.module.projects["dev-ta-app0-be"].google_essential_contacts_contact.contact["admin@example.org"]:
email: admin@example.org
language_tag: en
@@ -405,9 +402,6 @@ values:
module.project-factory.module.projects["dev-tb-app0-0"].google_project_service_identity.default["run.googleapis.com"]:
project: test-pf-dev-tb-app0-0
service: run.googleapis.com
module.project-factory.module.projects["dev-tb-app0-1"].data.google_storage_project_service_account.gcs_sa[0]:
project: test-pf-dev-tb-app0-1
user_project: null
module.project-factory.module.projects["dev-tb-app0-1"].google_essential_contacts_contact.contact["admin@example.org"]:
email: admin@example.org
language_tag: en
@@ -462,9 +456,6 @@ values:
? module.project-factory.module.projects["dev-tb-app0-1"].google_project_service_identity.default["container.googleapis.com"]
: project: test-pf-dev-tb-app0-1
service: container.googleapis.com
module.project-factory.module.projects["teams-iac-0"].data.google_storage_project_service_account.gcs_sa[0]:
project: test-pf-teams-iac-0
user_project: null
module.project-factory.module.projects["teams-iac-0"].google_essential_contacts_contact.contact["admin@example.org"]:
email: admin@example.org
language_tag: en
@@ -604,8 +595,6 @@ values:
email: app-0-be@test-pf-dev-tb-app0-1.iam.gserviceaccount.com
member: serviceAccount:app-0-be@test-pf-dev-tb-app0-1.iam.gserviceaccount.com
project: test-pf-dev-tb-app0-1
? module.project-factory.module.service_accounts-iam["dev-tb-app0-0/vm-default"].data.google_service_account.service_account[0]
: account_id: vm-default
? module.project-factory.module.service_accounts-iam["dev-tb-app0-0/vm-default"].google_service_account_iam_binding.authoritative["roles/iam.serviceAccountTokenCreator"]
: condition: []
members: