Avoid tag binding permadiffs for project-factory service accounts (#4006)

* Avoid tag binding permadiffs for service accounts created by project-factory

* Regenerate schema docs
This commit is contained in:
Julio Castillo
2026-06-01 06:33:53 +02:00
committed by GitHub
parent d8d66583f8
commit 982717188d
25 changed files with 151 additions and 19 deletions

View File

@@ -165,6 +165,9 @@
},
"iam_storage_roles": {
"$ref": "#/$defs/iam_storage_roles"
},
"tag_bindings": {
"$ref": "#/$defs/tag_bindings"
}
}
}

View File

@@ -54,6 +54,7 @@
- **iam_project_roles**: *reference([iam_project_roles](#refs-iam_project_roles))*
- **iam_sa_roles**: *reference([iam_sa_roles](#refs-iam_sa_roles))*
- **iam_storage_roles**: *reference([iam_storage_roles](#refs-iam_storage_roles))*
- **tag_bindings**: *reference([tag_bindings](#refs-tag_bindings))*
- **autokey_config**: *object*
<br>*additional properties: false*
- **project**: *string*

View File

@@ -146,6 +146,9 @@
},
"iam_storage_roles": {
"$ref": "#/$defs/iam_storage_roles"
},
"tag_bindings": {
"$ref": "#/$defs/tag_bindings"
}
}
}

View File

@@ -49,6 +49,7 @@
- **iam_project_roles**: *reference([iam_project_roles](#refs-iam_project_roles))*
- **iam_sa_roles**: *reference([iam_sa_roles](#refs-iam_sa_roles))*
- **iam_storage_roles**: *reference([iam_storage_roles](#refs-iam_storage_roles))*
- **tag_bindings**: *reference([tag_bindings](#refs-tag_bindings))*
- **billing_account**: *string*
- **billing_budgets**: *array*
- items: *string*

View File

@@ -165,6 +165,9 @@
},
"iam_storage_roles": {
"$ref": "#/$defs/iam_storage_roles"
},
"tag_bindings": {
"$ref": "#/$defs/tag_bindings"
}
}
}

View File

@@ -54,6 +54,7 @@
- **iam_project_roles**: *reference([iam_project_roles](#refs-iam_project_roles))*
- **iam_sa_roles**: *reference([iam_sa_roles](#refs-iam_sa_roles))*
- **iam_storage_roles**: *reference([iam_storage_roles](#refs-iam_storage_roles))*
- **tag_bindings**: *reference([tag_bindings](#refs-tag_bindings))*
- **autokey_config**: *object*
<br>*additional properties: false*
- **project**: *string*

View File

@@ -146,6 +146,9 @@
},
"iam_storage_roles": {
"$ref": "#/$defs/iam_storage_roles"
},
"tag_bindings": {
"$ref": "#/$defs/tag_bindings"
}
}
}

View File

@@ -49,6 +49,7 @@
- **iam_project_roles**: *reference([iam_project_roles](#refs-iam_project_roles))*
- **iam_sa_roles**: *reference([iam_sa_roles](#refs-iam_sa_roles))*
- **iam_storage_roles**: *reference([iam_storage_roles](#refs-iam_storage_roles))*
- **tag_bindings**: *reference([tag_bindings](#refs-tag_bindings))*
- **billing_account**: *string*
- **billing_budgets**: *array*
- items: *string*

View File

@@ -165,6 +165,9 @@
},
"iam_storage_roles": {
"$ref": "#/$defs/iam_storage_roles"
},
"tag_bindings": {
"$ref": "#/$defs/tag_bindings"
}
}
}

View File

@@ -54,6 +54,7 @@
- **iam_project_roles**: *reference([iam_project_roles](#refs-iam_project_roles))*
- **iam_sa_roles**: *reference([iam_sa_roles](#refs-iam_sa_roles))*
- **iam_storage_roles**: *reference([iam_storage_roles](#refs-iam_storage_roles))*
- **tag_bindings**: *reference([tag_bindings](#refs-tag_bindings))*
- **autokey_config**: *object*
<br>*additional properties: false*
- **project**: *string*

View File

@@ -146,6 +146,9 @@
},
"iam_storage_roles": {
"$ref": "#/$defs/iam_storage_roles"
},
"tag_bindings": {
"$ref": "#/$defs/tag_bindings"
}
}
}

View File

@@ -49,6 +49,7 @@
- **iam_project_roles**: *reference([iam_project_roles](#refs-iam_project_roles))*
- **iam_sa_roles**: *reference([iam_sa_roles](#refs-iam_sa_roles))*
- **iam_storage_roles**: *reference([iam_storage_roles](#refs-iam_storage_roles))*
- **tag_bindings**: *reference([tag_bindings](#refs-tag_bindings))*
- **billing_account**: *string*
- **billing_budgets**: *array*
- items: *string*

View File

@@ -165,6 +165,9 @@
},
"iam_storage_roles": {
"$ref": "#/$defs/iam_storage_roles"
},
"tag_bindings": {
"$ref": "#/$defs/tag_bindings"
}
}
}

View File

@@ -54,6 +54,7 @@
- **iam_project_roles**: *reference([iam_project_roles](#refs-iam_project_roles))*
- **iam_sa_roles**: *reference([iam_sa_roles](#refs-iam_sa_roles))*
- **iam_storage_roles**: *reference([iam_storage_roles](#refs-iam_storage_roles))*
- **tag_bindings**: *reference([tag_bindings](#refs-tag_bindings))*
- **autokey_config**: *object*
<br>*additional properties: false*
- **project**: *string*

View File

@@ -146,6 +146,9 @@
},
"iam_storage_roles": {
"$ref": "#/$defs/iam_storage_roles"
},
"tag_bindings": {
"$ref": "#/$defs/tag_bindings"
}
}
}

View File

@@ -49,6 +49,7 @@
- **iam_project_roles**: *reference([iam_project_roles](#refs-iam_project_roles))*
- **iam_sa_roles**: *reference([iam_sa_roles](#refs-iam_sa_roles))*
- **iam_storage_roles**: *reference([iam_storage_roles](#refs-iam_storage_roles))*
- **tag_bindings**: *reference([tag_bindings](#refs-tag_bindings))*
- **billing_account**: *string*
- **billing_budgets**: *array*
- items: *string*

View File

@@ -101,6 +101,6 @@ resource "google_service_account" "service_account" {
resource "google_tags_tag_binding" "binding" {
for_each = local.tag_bindings
parent = "//iam.googleapis.com/projects/${coalesce(var.project_number, var.project_id)}/serviceAccounts/${local.service_account.unique_id}"
parent = "//iam.googleapis.com/projects/${coalesce(var.project_number, local.project_id)}/serviceAccounts/${local.service_account.unique_id}"
tag_value = templatestring(local._tag_bindings[each.key], var.context.tag_vars)
}

View File

@@ -968,7 +968,7 @@ module "project-factory" {
basepath = "data"
}
}
# tftest modules=7 resources=31 files=test-0,test-1,test-2 inventory=test-1.yaml
# tftest modules=10 resources=36 files=test-0,test-1,test-2 inventory=test-1.yaml
```
```yaml
@@ -995,6 +995,16 @@ tags:
roles/resourcemanager.tagUser:
- $iam_principals:tag-test
- $iam_principals:service_accounts/test-1/tag-test
service_accounts:
tag-test:
tag_bindings:
project-level: $tag_values:test-0/context/project-factory
automation:
project: test-0
service_accounts:
auto-tag-test:
tag_bindings:
project-level: $tag_values:test-0/context/project-factory
# tftest-file id=test-0 path=data/projects/test-0.yaml
```

View File

@@ -126,9 +126,18 @@ module "automation-bucket" {
}
module "automation-service-accounts" {
source = "../iam-service-account"
for_each = local.automation_sas
project_id = each.value.automation_project
source = "../iam-service-account"
for_each = local.automation_sas
project_id = each.value.automation_project
project_number = (
each.value.automation_project == null
? null
: lookup(
local.ctx_project_numbers,
trimprefix(each.value.automation_project, "$project_ids:"),
null
)
)
prefix = each.value.prefix
name = each.value.name
description = lookup(each.value, "description", null)
@@ -143,6 +152,11 @@ module "automation-service-accounts" {
local.ctx.iam_principals,
local.projects_sas_iam_emails
)
tag_vars = {
projects = merge(try(local.ctx.tag_vars.projects, {}), local.tag_vars_projects)
organization = try(local.ctx.tag_vars.organization, {})
}
tag_values = local.ctx_tag_values
})
iam = lookup(each.value, "iam", {})
iam_bindings = lookup(each.value, "iam_bindings", {})
@@ -154,6 +168,7 @@ module "automation-service-accounts" {
# iam_sa_roles = lookup(each.value, "iam_sa_roles", {})
# we don't interpolate buckets here as we can't use a dynamic key
iam_storage_roles = lookup(each.value, "iam_storage_roles", {})
tag_bindings = lookup(each.value, "tag_bindings", {})
}
module "automation-service-accounts-iam" {

View File

@@ -80,10 +80,11 @@ module "service-accounts" {
for k in local.projects_service_accounts :
"${k.project_key}/${k.name}" => k
}
project_id = module.projects[each.value.project_key].project_id
name = each.value.name
description = each.value.description
display_name = each.value.display_name
project_id = module.projects[each.value.project_key].project_id
project_number = module.projects[each.value.project_key].number
name = each.value.name
description = each.value.description
display_name = each.value.display_name
context = merge(local.ctx, {
tag_vars = {
projects = merge(try(local.ctx.tag_vars.projects, {}), local.tag_vars_projects)

View File

@@ -165,6 +165,9 @@
},
"iam_storage_roles": {
"$ref": "#/$defs/iam_storage_roles"
},
"tag_bindings": {
"$ref": "#/$defs/tag_bindings"
}
}
}

View File

@@ -54,6 +54,7 @@
- **iam_project_roles**: *reference([iam_project_roles](#refs-iam_project_roles))*
- **iam_sa_roles**: *reference([iam_sa_roles](#refs-iam_sa_roles))*
- **iam_storage_roles**: *reference([iam_storage_roles](#refs-iam_storage_roles))*
- **tag_bindings**: *reference([tag_bindings](#refs-tag_bindings))*
- **autokey_config**: *object*
<br>*additional properties: false*
- **project**: *string*

View File

@@ -146,6 +146,9 @@
},
"iam_storage_roles": {
"$ref": "#/$defs/iam_storage_roles"
},
"tag_bindings": {
"$ref": "#/$defs/tag_bindings"
}
}
}

View File

@@ -49,6 +49,7 @@
- **iam_project_roles**: *reference([iam_project_roles](#refs-iam_project_roles))*
- **iam_sa_roles**: *reference([iam_sa_roles](#refs-iam_sa_roles))*
- **iam_storage_roles**: *reference([iam_storage_roles](#refs-iam_storage_roles))*
- **tag_bindings**: *reference([tag_bindings](#refs-tag_bindings))*
- **billing_account**: *string*
- **billing_budgets**: *array*
- items: *string*

View File

@@ -1,10 +1,10 @@
# Copyright 2025 Google LLC
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,6 +13,47 @@
# limitations under the License.
values:
module.project-factory.module.automation-bucket["test-0/automation/tf-state"].google_storage_bucket.bucket[0]:
autoclass: []
cors: []
custom_placement_config: []
default_event_based_hold: null
deletion_policy: DELETE
effective_labels:
goog-terraform-provisioned: 'true'
enable_object_retention: null
encryption: []
force_destroy: false
hierarchical_namespace: []
ip_filter: []
labels: null
lifecycle_rule: []
location: EU
logging: []
name: foo-test-0-tf-state
project: test-0
requester_pays: null
retention_policy: []
storage_class: STANDARD
terraform_labels:
goog-terraform-provisioned: 'true'
timeouts: null
uniform_bucket_level_access: true
versioning:
- enabled: false
? module.project-factory.module.automation-service-accounts["test-0/automation/auto-tag-test"].google_service_account.service_account[0]
: account_id: test-0-auto-tag-test
create_ignore_already_exists: null
deletion_policy: DELETE
description: null
disabled: false
display_name: Service account auto-tag-test for test-0.
email: test-0-auto-tag-test@test-0.iam.gserviceaccount.com
member: serviceAccount:test-0-auto-tag-test@test-0.iam.gserviceaccount.com
project: test-0
timeouts: null
? module.project-factory.module.automation-service-accounts["test-0/automation/auto-tag-test"].google_tags_tag_binding.binding["project-level"]
: timeouts: null
module.project-factory.module.projects-iam["test-0"].google_project_iam_member.bindings["test_context"]:
condition:
- description: null
@@ -31,8 +72,6 @@ values:
tag_value: tagValues/1234567890
timeouts: null
module.project-factory.module.projects-iam["test-1"].google_tags_tag_binding.binding["project-level"]:
# tag_value is undefined at plan time as it depends on the tag
# tag_value: $tag_values:test-0/context/project-factory
timeouts: null
module.project-factory.module.projects["test-0"].google_project.project[0]:
auto_create_network: false
@@ -65,24 +104,28 @@ values:
project: foo-test-0
role: roles/container.defaultNodeServiceAgent
module.project-factory.module.projects["test-0"].google_project_service.project_services["compute.googleapis.com"]:
deletion_policy: DELETE
disable_dependent_services: false
disable_on_destroy: false
project: foo-test-0
service: compute.googleapis.com
timeouts: null
? module.project-factory.module.projects["test-0"].google_project_service.project_services["contactcenteraiplatform.googleapis.com"]
: disable_dependent_services: false
: deletion_policy: DELETE
disable_dependent_services: false
disable_on_destroy: false
project: foo-test-0
service: contactcenteraiplatform.googleapis.com
timeouts: null
module.project-factory.module.projects["test-0"].google_project_service.project_services["container.googleapis.com"]:
deletion_policy: DELETE
disable_dependent_services: false
disable_on_destroy: false
project: foo-test-0
service: container.googleapis.com
timeouts: null
module.project-factory.module.projects["test-0"].google_project_service.project_services["iam.googleapis.com"]:
deletion_policy: DELETE
disable_dependent_services: false
disable_on_destroy: false
project: foo-test-0
@@ -131,18 +174,21 @@ values:
project: test-1
role: roles/compute.serviceAgent
module.project-factory.module.projects["test-1"].google_project_service.project_services["compute.googleapis.com"]:
deletion_policy: DELETE
disable_dependent_services: false
disable_on_destroy: false
project: test-1
service: compute.googleapis.com
timeouts: null
? module.project-factory.module.projects["test-1"].google_project_service.project_services["contactcenteraiplatform.googleapis.com"]
: disable_dependent_services: false
: deletion_policy: DELETE
disable_dependent_services: false
disable_on_destroy: false
project: test-1
service: contactcenteraiplatform.googleapis.com
timeouts: null
module.project-factory.module.projects["test-1"].google_project_service.project_services["iam.googleapis.com"]:
deletion_policy: DELETE
disable_dependent_services: false
disable_on_destroy: false
project: test-1
@@ -178,26 +224,43 @@ values:
project: bar-test-2
role: roles/compute.serviceAgent
module.project-factory.module.projects["test-2"].google_project_service.project_services["compute.googleapis.com"]:
deletion_policy: DELETE
disable_dependent_services: false
disable_on_destroy: false
project: bar-test-2
service: compute.googleapis.com
timeouts: null
module.project-factory.module.projects["test-2"].google_project_service.project_services["iam.googleapis.com"]:
deletion_policy: DELETE
disable_dependent_services: false
disable_on_destroy: false
project: bar-test-2
service: iam.googleapis.com
timeouts: null
module.project-factory.module.projects["test-2"].google_project_service.project_services["storage.googleapis.com"]:
deletion_policy: DELETE
disable_dependent_services: false
disable_on_destroy: false
project: bar-test-2
service: storage.googleapis.com
timeouts: null
module.project-factory.module.service-accounts["test-0/tag-test"].google_service_account.service_account[0]:
account_id: tag-test
create_ignore_already_exists: null
deletion_policy: DELETE
description: null
disabled: false
display_name: Terraform-managed.
email: tag-test@foo-test-0.iam.gserviceaccount.com
member: serviceAccount:tag-test@foo-test-0.iam.gserviceaccount.com
project: foo-test-0
timeouts: null
module.project-factory.module.service-accounts["test-0/tag-test"].google_tags_tag_binding.binding["project-level"]:
timeouts: null
module.project-factory.module.service-accounts["test-1/tag-test"].google_service_account.service_account[0]:
account_id: tag-test
create_ignore_already_exists: null
deletion_policy: DELETE
description: null
disabled: false
display_name: Terraform-managed.
@@ -219,12 +282,15 @@ counts:
google_project_iam_member: 6
google_project_service: 10
google_project_service_identity: 3
google_service_account: 1
google_service_account: 3
google_storage_bucket: 1
google_storage_project_service_account: 1
google_tags_tag_binding: 2
google_tags_tag_binding: 4
google_tags_tag_key: 1
google_tags_tag_value: 1
google_tags_tag_value_iam_binding: 1
modules: 7
resources: 31
modules: 10
resources: 36
terraform_data: 2
outputs: {}