From 7d33becacff4bef3bf73321cd866b460e40aed42 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 5 Feb 2026 12:56:27 +0100 Subject: [PATCH 1/6] Add `asset_search` to folder, project, and organization modules. (#3707) --- modules/folder/README.md | 69 ++++++++++----- modules/folder/assets.tf | 7 ++ modules/folder/outputs.tf | 9 +- modules/folder/variables.tf | 10 +++ modules/organization/README.md | 74 ++++++++++------ modules/organization/assets.tf | 7 ++ modules/organization/outputs.tf | 9 +- modules/organization/variables.tf | 12 ++- modules/project/README.md | 136 ++++++++++++++++++------------ modules/project/assets.tf | 7 ++ modules/project/outputs.tf | 7 ++ modules/project/variables.tf | 10 +++ 12 files changed, 254 insertions(+), 103 deletions(-) diff --git a/modules/folder/README.md b/modules/folder/README.md index 56e1a555a..dc3a39ad0 100644 --- a/modules/folder/README.md +++ b/modules/folder/README.md @@ -19,6 +19,7 @@ This module allows the creation and management of folders, including support for - [Custom Security Health Analytics Modules Factory](#custom-security-health-analytics-modules-factory) - [Security Command Center Mute Configs](#security-command-center-mute-configs) - [Security Command Center Mute Configs Factory](#security-command-center-mute-configs-factory) +- [Cloud Asset Search](#cloud-asset-search) - [Cloud Asset Inventory Feeds](#cloud-asset-inventory-feeds) - [Tags](#tags) - [Files](#files) @@ -617,6 +618,30 @@ muteHighSeverity: type: "DYNAMIC" ``` +## Cloud Asset Search + +The Cloud Asset Search feature allows you to search for resources within the project using the Cloud Asset Inventory API. This is useful for discovering and auditing resources based on asset types and query filters. + +```hcl +module "folder" { + source = "./fabric/modules/folder" + billing_account = var.billing_account_id + id = var.folder_id + folder_create = false + asset_search = { + compute-sas = { + asset_types = ["iam.googleapis.com/ServiceAccount"] + query = "name:compute@developer.gserviceaccount.com" + } + } +} + +output "service_accounts" { + value = module.folder.asset_search_results["copute-sas"] +} +# tftest skip +``` + ## Cloud Asset Inventory Feeds Cloud Asset Inventory feeds allow you to monitor asset changes in real-time by publishing notifications to a Pub/Sub topic. Feeds configured at the folder level will monitor all resources within the folder and its subfolders. @@ -709,43 +734,45 @@ module "folder" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| | [asset_feeds](variables.tf#L18) | Cloud Asset Inventory feeds. | map(object({…})) | | {} | -| [assured_workload_config](variables.tf#L51) | Create AssuredWorkloads folder instead of regular folder when value is provided. Incompatible with folder_create=false. | object({…}) | | null | -| [autokey_config](variables.tf#L113) | Enable autokey support for this folder's children. Project accepts either project id or number. | object({…}) | | null | -| [contacts](variables.tf#L122) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | map(list(string)) | | {} | -| [context](variables.tf#L141) | Context-specific interpolations. | object({…}) | | {} | -| [deletion_protection](variables.tf#L161) | Deletion protection setting for this folder. | bool | | false | -| [factories_config](variables.tf#L167) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} | -| [firewall_policy](variables.tf#L179) | Hierarchical firewall policy to associate to this folder. | object({…}) | | null | -| [folder_create](variables.tf#L188) | Create folder. When set to false, uses id to reference an existing folder. | bool | | true | +| [asset_search](variables.tf#L51) | Cloud Asset Inventory search configurations. | map(object({…})) | | {} | +| [assured_workload_config](variables.tf#L61) | Create AssuredWorkloads folder instead of regular folder when value is provided. Incompatible with folder_create=false. | object({…}) | | null | +| [autokey_config](variables.tf#L123) | Enable autokey support for this folder's children. Project accepts either project id or number. | object({…}) | | null | +| [contacts](variables.tf#L132) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | map(list(string)) | | {} | +| [context](variables.tf#L151) | Context-specific interpolations. | object({…}) | | {} | +| [deletion_protection](variables.tf#L171) | Deletion protection setting for this folder. | bool | | false | +| [factories_config](variables.tf#L177) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} | +| [firewall_policy](variables.tf#L189) | Hierarchical firewall policy to associate to this folder. | object({…}) | | null | +| [folder_create](variables.tf#L198) | Create folder. When set to false, uses id to reference an existing folder. | bool | | true | | [iam](variables-iam.tf#L17) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_bindings](variables-iam.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) | | {} | | [iam_bindings_additive](variables-iam.tf#L39) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | | [iam_by_principals](variables-iam.tf#L61) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam` variable. | map(list(string)) | | {} | | [iam_by_principals_additive](variables-iam.tf#L54) | Additive IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam_bindings_additive` variable. | map(list(string)) | | {} | | [iam_by_principals_conditional](variables-iam.tf#L68) | Authoritative IAM binding in {PRINCIPAL => {roles = [roles], condition = {cond}}} format. Principals need to be statically defined to avoid errors. Condition is required. | map(object({…})) | | {} | -| [id](variables.tf#L198) | Folder ID in case you use folder_create=false. | string | | null | +| [id](variables.tf#L208) | Folder ID in case you use folder_create=false. | string | | null | | [logging_data_access](variables-logging.tf#L17) | Control activation of data access logs. The special 'allServices' key denotes configuration for all services. | map(object({…})) | | {} | | [logging_exclusions](variables-logging.tf#L28) | Logging exclusions for this folder in the form {NAME -> FILTER}. | map(string) | | {} | | [logging_settings](variables-logging.tf#L35) | Default settings for logging resources. | object({…}) | | null | | [logging_sinks](variables-logging.tf#L45) | Logging sinks to create for the folder. | map(object({…})) | | {} | -| [name](variables.tf#L204) | Folder name. | string | | null | -| [org_policies](variables.tf#L210) | Organization policies applied to this folder keyed by policy name. | map(object({…})) | | {} | +| [name](variables.tf#L214) | Folder name. | string | | null | +| [org_policies](variables.tf#L220) | Organization policies applied to this folder keyed by policy name. | map(object({…})) | | {} | | [pam_entitlements](variables-pam.tf#L17) | Privileged Access Manager entitlements for this resource, keyed by entitlement ID. | map(object({…})) | | {} | -| [parent](variables.tf#L238) | Parent in folders/folder_id or organizations/org_id format. | string | | null | +| [parent](variables.tf#L248) | Parent in folders/folder_id or organizations/org_id format. | string | | null | | [scc_mute_configs](variables-scc.tf#L17) | SCC mute configurations keyed by name. | map(object({…})) | | {} | | [scc_sha_custom_modules](variables-scc.tf#L27) | SCC custom modules keyed by module name. | map(object({…})) | | {} | -| [tag_bindings](variables.tf#L252) | Tag bindings for this folder, in key => tag value id format. | map(string) | | null | +| [tag_bindings](variables.tf#L262) | Tag bindings for this folder, in key => tag value id format. | map(string) | | null | ## Outputs | name | description | sensitive | |---|---|:---:| -| [assured_workload](outputs.tf#L17) | Assured Workloads workload resource. | | -| [folder](outputs.tf#L22) | Folder resource. | | -| [id](outputs.tf#L27) | Fully qualified folder id. | | -| [name](outputs.tf#L38) | Folder name. | | -| [organization_policies_ids](outputs.tf#L47) | Map of ORGANIZATION_POLICIES => ID in the folder. | | -| [scc_custom_sha_modules_ids](outputs.tf#L52) | Map of SCC CUSTOM SHA MODULES => ID in the folder. | | -| [service_agents](outputs.tf#L57) | Identities of all folder-level service agents. | | -| [sink_writer_identities](outputs.tf#L62) | Writer identities created for each sink. | | +| [asset_search_results](outputs.tf#L17) | Cloud Asset Inventory search results. | | +| [assured_workload](outputs.tf#L24) | Assured Workloads workload resource. | | +| [folder](outputs.tf#L29) | Folder resource. | | +| [id](outputs.tf#L34) | Fully qualified folder id. | | +| [name](outputs.tf#L45) | Folder name. | | +| [organization_policies_ids](outputs.tf#L54) | Map of ORGANIZATION_POLICIES => ID in the folder. | | +| [scc_custom_sha_modules_ids](outputs.tf#L59) | Map of SCC CUSTOM SHA MODULES => ID in the folder. | | +| [service_agents](outputs.tf#L64) | Identities of all folder-level service agents. | | +| [sink_writer_identities](outputs.tf#L69) | Writer identities created for each sink. | | diff --git a/modules/folder/assets.tf b/modules/folder/assets.tf index 04bd1b929..94a904f8f 100644 --- a/modules/folder/assets.tf +++ b/modules/folder/assets.tf @@ -14,6 +14,13 @@ * limitations under the License. */ +data "google_cloud_asset_search_all_resources" "default" { + for_each = var.asset_search + scope = local.folder_id + asset_types = each.value.asset_types + query = each.value.query +} + resource "google_cloud_asset_folder_feed" "default" { for_each = var.asset_feeds billing_project = lookup( diff --git a/modules/folder/outputs.tf b/modules/folder/outputs.tf index 1346077d4..b1c14d76d 100644 --- a/modules/folder/outputs.tf +++ b/modules/folder/outputs.tf @@ -1,5 +1,5 @@ /** - * 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. @@ -14,6 +14,13 @@ * limitations under the License. */ +output "asset_search_results" { + description = "Cloud Asset Inventory search results." + value = { + for k, v in data.google_cloud_asset_search_all_resources.default : k => v.results + } +} + output "assured_workload" { description = "Assured Workloads workload resource." value = try(google_assured_workloads_workload.folder[0], null) diff --git a/modules/folder/variables.tf b/modules/folder/variables.tf index 0de8855cb..d7f3f75f4 100644 --- a/modules/folder/variables.tf +++ b/modules/folder/variables.tf @@ -48,6 +48,16 @@ variable "asset_feeds" { } } +variable "asset_search" { + description = "Cloud Asset Inventory search configurations." + type = map(object({ + asset_types = list(string) + query = optional(string) + })) + default = {} + nullable = false +} + variable "assured_workload_config" { description = "Create AssuredWorkloads folder instead of regular folder when value is provided. Incompatible with folder_create=false." type = object({ diff --git a/modules/organization/README.md b/modules/organization/README.md index 1b8fd2126..f25a33c0f 100644 --- a/modules/organization/README.md +++ b/modules/organization/README.md @@ -33,6 +33,7 @@ To manage organization policies, the `orgpolicy.googleapis.com` service should b - [Custom Security Health Analytics Modules Factory](#custom-security-health-analytics-modules-factory) - [Security Command Center Mute Configs](#security-command-center-mute-configs) - [Security Command Center Mute Configs Factory](#security-command-center-mute-configs-factory) +- [Cloud Asset Search](#cloud-asset-search) - [Cloud Asset Inventory Feeds](#cloud-asset-inventory-feeds) - [Tags](#tags) - [Tags Factory](#tags-factory) @@ -625,6 +626,27 @@ muteHighSeverity: type: "DYNAMIC" ``` +## Cloud Asset Search + +The Cloud Asset Search feature allows you to search for resources within the organization using the Cloud Asset Inventory API. This is useful for discovering and auditing resources based on asset types and query filters. + +```hcl +module "org" { + source = "./fabric/modules/organization" + organization_id = var.organization_id + asset_search = { + org-policies = { + asset_types = ["orgpolicy.googleapis.com/Policy"] + } + } +} + +output "org_policies" { + value = module.org.asset_search_results["org-policies"] +} +# tftest skip +``` + ## Cloud Asset Inventory Feeds Cloud Asset Inventory feeds allow you to monitor asset changes in real-time by publishing notifications to a Pub/Sub topic. Feeds configured at the organization level will monitor all resources within the organization. @@ -910,13 +932,14 @@ module "org" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [organization_id](variables.tf#L162) | Organization id in organizations/nnnnnn format. | string | ✓ | | +| [organization_id](variables.tf#L172) | Organization id in organizations/nnnnnn format. | string | ✓ | | | [asset_feeds](variables.tf#L18) | Cloud Asset Inventory feeds. | map(object({…})) | | {} | -| [contacts](variables.tf#L51) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | map(list(string)) | | {} | -| [context](variables.tf#L69) | Context-specific interpolations. | object({…}) | | {} | -| [custom_roles](variables.tf#L89) | Map of role name => list of permissions to create in this project. | map(list(string)) | | {} | -| [factories_config](variables.tf#L96) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} | -| [firewall_policy](variables.tf#L111) | Hierarchical firewall policies to associate to the organization. | object({…}) | | null | +| [asset_search](variables.tf#L51) | Cloud Asset Inventory search configurations. | map(object({…})) | | {} | +| [contacts](variables.tf#L61) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | map(list(string)) | | {} | +| [context](variables.tf#L79) | Context-specific interpolations. | object({…}) | | {} | +| [custom_roles](variables.tf#L99) | Map of role name => list of permissions to create in this project. | map(list(string)) | | {} | +| [factories_config](variables.tf#L106) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} | +| [firewall_policy](variables.tf#L121) | Hierarchical firewall policies to associate to the organization. | object({…}) | | null | | [iam](variables-iam.tf#L17) | Authoritative IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_bindings](variables-iam.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) | | {} | | [iam_bindings_additive](variables-iam.tf#L39) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | @@ -928,8 +951,8 @@ module "org" { | [logging_settings](variables-logging.tf#L35) | Default settings for logging resources. | object({…}) | | null | | [logging_sinks](variables-logging.tf#L46) | Logging sinks to create for the organization. | map(object({…})) | | {} | | [network_tags](variables-tags.tf#L17) | Network tags by key name. If `id` is provided, key creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} | -| [org_policies](variables.tf#L120) | Organization policies applied to this organization keyed by policy name. | map(object({…})) | | {} | -| [org_policy_custom_constraints](variables.tf#L148) | Organization policy custom constraints keyed by constraint name. | map(object({…})) | | {} | +| [org_policies](variables.tf#L130) | Organization policies applied to this organization keyed by policy name. | map(object({…})) | | {} | +| [org_policy_custom_constraints](variables.tf#L158) | Organization policy custom constraints keyed by constraint name. | map(object({…})) | | {} | | [pam_entitlements](variables-pam.tf#L17) | Privileged Access Manager entitlements for this resource, keyed by entitlement ID. | map(object({…})) | | {} | | [scc_mute_configs](variables-scc.tf#L17) | SCC mute configurations keyed by name. | map(object({…})) | | {} | | [scc_sha_custom_modules](variables-scc.tf#L28) | SCC custom modules keyed by module name. | map(object({…})) | | {} | @@ -942,21 +965,22 @@ module "org" { | name | description | sensitive | |---|---|:---:| -| [custom_constraint_ids](outputs.tf#L17) | Map of CUSTOM_CONSTRAINTS => ID in the organization. | | -| [custom_role_id](outputs.tf#L22) | Map of custom role IDs created in the organization. | | -| [custom_roles](outputs.tf#L27) | Map of custom roles resources created in the organization. | | -| [id](outputs.tf#L32) | Fully qualified organization id. | | -| [logging_identities](outputs.tf#L50) | Principals used for logging sinks. | | -| [network_tag_keys](outputs.tf#L62) | Tag key resources. | | -| [network_tag_values](outputs.tf#L71) | Tag value resources. | | -| [organization_id](outputs.tf#L81) | Organization id dependent on module resources. | | -| [organization_policies_ids](outputs.tf#L98) | Map of ORGANIZATION_POLICIES => ID in the organization. | | -| [scc_custom_sha_modules_ids](outputs.tf#L103) | Map of SCC CUSTOM SHA MODULES => ID in the organization. | | -| [scc_mute_configs](outputs.tf#L108) | SCC mute configurations. | | -| [service_agents](outputs.tf#L113) | Identities of all organization-level service agents. | | -| [sink_writer_identities](outputs.tf#L118) | Writer identities created for each sink. | | -| [tag_keys](outputs.tf#L126) | Tag key resources. | | -| [tag_values](outputs.tf#L135) | Tag value resources. | | -| [workforce_identity_provider_names](outputs.tf#L143) | Workforce Identity provider names. | | -| [workforce_identity_providers](outputs.tf#L150) | Workforce Identity provider attributes. | | +| [asset_search_results](outputs.tf#L17) | Cloud Asset Inventory search results. | | +| [custom_constraint_ids](outputs.tf#L24) | Map of CUSTOM_CONSTRAINTS => ID in the organization. | | +| [custom_role_id](outputs.tf#L29) | Map of custom role IDs created in the organization. | | +| [custom_roles](outputs.tf#L34) | Map of custom roles resources created in the organization. | | +| [id](outputs.tf#L39) | Fully qualified organization id. | | +| [logging_identities](outputs.tf#L57) | Principals used for logging sinks. | | +| [network_tag_keys](outputs.tf#L69) | Tag key resources. | | +| [network_tag_values](outputs.tf#L78) | Tag value resources. | | +| [organization_id](outputs.tf#L88) | Organization id dependent on module resources. | | +| [organization_policies_ids](outputs.tf#L105) | Map of ORGANIZATION_POLICIES => ID in the organization. | | +| [scc_custom_sha_modules_ids](outputs.tf#L110) | Map of SCC CUSTOM SHA MODULES => ID in the organization. | | +| [scc_mute_configs](outputs.tf#L115) | SCC mute configurations. | | +| [service_agents](outputs.tf#L120) | Identities of all organization-level service agents. | | +| [sink_writer_identities](outputs.tf#L125) | Writer identities created for each sink. | | +| [tag_keys](outputs.tf#L133) | Tag key resources. | | +| [tag_values](outputs.tf#L142) | Tag value resources. | | +| [workforce_identity_provider_names](outputs.tf#L150) | Workforce Identity provider names. | | +| [workforce_identity_providers](outputs.tf#L157) | Workforce Identity provider attributes. | | diff --git a/modules/organization/assets.tf b/modules/organization/assets.tf index 59a650f5e..ea1727e00 100644 --- a/modules/organization/assets.tf +++ b/modules/organization/assets.tf @@ -14,6 +14,13 @@ * limitations under the License. */ +data "google_cloud_asset_search_all_resources" "default" { + for_each = var.asset_search + scope = var.organization_id + asset_types = each.value.asset_types + query = each.value.query +} + resource "google_cloud_asset_organization_feed" "default" { for_each = var.asset_feeds billing_project = lookup( diff --git a/modules/organization/outputs.tf b/modules/organization/outputs.tf index a77041b2e..3eec6ceea 100644 --- a/modules/organization/outputs.tf +++ b/modules/organization/outputs.tf @@ -1,5 +1,5 @@ /** - * 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. @@ -14,6 +14,13 @@ * limitations under the License. */ +output "asset_search_results" { + description = "Cloud Asset Inventory search results." + value = { + for k, v in data.google_cloud_asset_search_all_resources.default : k => v.results + } +} + output "custom_constraint_ids" { description = "Map of CUSTOM_CONSTRAINTS => ID in the organization." value = { for k, v in google_org_policy_custom_constraint.constraint : k => v.id } diff --git a/modules/organization/variables.tf b/modules/organization/variables.tf index ac814cac0..03ce35388 100644 --- a/modules/organization/variables.tf +++ b/modules/organization/variables.tf @@ -1,5 +1,5 @@ /** - * 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. @@ -48,6 +48,16 @@ variable "asset_feeds" { } } +variable "asset_search" { + description = "Cloud Asset Inventory search configurations." + type = map(object({ + asset_types = list(string) + query = optional(string) + })) + default = {} + nullable = false +} + variable "contacts" { description = "List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES." type = map(list(string)) diff --git a/modules/project/README.md b/modules/project/README.md index a28ac5213..ae5ac7b1d 100644 --- a/modules/project/README.md +++ b/modules/project/README.md @@ -39,6 +39,7 @@ This module implements the creation and management of one GCP project including - [Privileged Access Manager (PAM) Entitlements Factory](#privileged-access-manager-pam-entitlements-factory) - [VPC Service Controls](#vpc-service-controls) - [Default compute network tier](#default-compute-network-tier) +- [Cloud Asset Search](#cloud-asset-search) - [Cloud Asset Inventory Feeds](#cloud-asset-inventory-feeds) - [BigQuery Reservations](#bigquery-reservations) - [Project Related Outputs](#project-related-outputs) @@ -1641,6 +1642,31 @@ module "project" { # tftest modules=1 resources=4 ``` +## Cloud Asset Search + +The Cloud Asset Search feature allows you to search for resources within the project using the Cloud Asset Inventory API. This is useful for discovering and auditing resources based on asset types and query filters. + +```hcl +module "project" { + source = "./fabric/modules/project" + billing_account = var.billing_account_id + name = "project" + parent = var.folder_id + prefix = var.prefix + asset_search = { + compute-sas = { + asset_types = ["iam.googleapis.com/ServiceAccount"] + query = "name:compute@developer.gserviceaccount.com" + } + } +} + +output "service_accounts" { + value = module.project.asset_search_results["copute-sas"] +} +# tftest skip +``` + ## Cloud Asset Inventory Feeds Cloud Asset Inventory feeds allow you to monitor asset changes in real-time by publishing notifications to a Pub/Sub topic. Feeds can be configured to monitor specific asset types, filter by conditions, and export different content types. @@ -2258,30 +2284,31 @@ module "project" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L238) | Project name and id suffix. | string | ✓ | | +| [name](variables.tf#L248) | Project name and id suffix. | string | ✓ | | | [alerts](variables-observability.tf#L17) | Monitoring alerts. | map(object({…})) | | {} | | [asset_feeds](variables.tf#L18) | Cloud Asset Inventory feeds. | map(object({…})) | | {} | -| [auto_create_network](variables.tf#L51) | Whether to create the default network for the project. | bool | | false | -| [bigquery_reservations](variables.tf#L57) | BigQuery reservations and assignments. Assignment specified as {JOB_TYPE = ['projects/PROJECT_ID']}. | map(object({…})) | | {} | -| [billing_account](variables.tf#L94) | Billing account id. | string | | null | -| [compute_metadata](variables.tf#L100) | Optional compute metadata key/values. Only usable if compute API has been enabled. | map(string) | | {} | -| [contacts](variables.tf#L107) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | map(list(string)) | | {} | -| [context](variables.tf#L125) | Context-specific interpolations. | object({…}) | | {} | -| [custom_roles](variables.tf#L148) | Map of role name => list of permissions to create in this project. | map(list(string)) | | {} | -| [default_network_tier](variables.tf#L155) | Default compute network tier for the project. | string | | null | -| [default_service_account](variables.tf#L161) | Project default service account setting: can be one of `delete`, `deprivilege`, `disable`, or `keep`. | string | | "keep" | -| [deletion_policy](variables.tf#L174) | Deletion policy setting for this project. | string | | "DELETE" | -| [descriptive_name](variables.tf#L185) | Descriptive project name. Set when name differs from project id. | string | | null | -| [factories_config](variables.tf#L191) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} | +| [asset_search](variables.tf#L51) | Cloud Asset Inventory search configurations. | map(object({…})) | | {} | +| [auto_create_network](variables.tf#L61) | Whether to create the default network for the project. | bool | | false | +| [bigquery_reservations](variables.tf#L67) | BigQuery reservations and assignments. Assignment specified as {JOB_TYPE = ['projects/PROJECT_ID']}. | map(object({…})) | | {} | +| [billing_account](variables.tf#L104) | Billing account id. | string | | null | +| [compute_metadata](variables.tf#L110) | Optional compute metadata key/values. Only usable if compute API has been enabled. | map(string) | | {} | +| [contacts](variables.tf#L117) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | map(list(string)) | | {} | +| [context](variables.tf#L135) | Context-specific interpolations. | object({…}) | | {} | +| [custom_roles](variables.tf#L158) | Map of role name => list of permissions to create in this project. | map(list(string)) | | {} | +| [default_network_tier](variables.tf#L165) | Default compute network tier for the project. | string | | null | +| [default_service_account](variables.tf#L171) | Project default service account setting: can be one of `delete`, `deprivilege`, `disable`, or `keep`. | string | | "keep" | +| [deletion_policy](variables.tf#L184) | Deletion policy setting for this project. | string | | "DELETE" | +| [descriptive_name](variables.tf#L195) | Descriptive project name. Set when name differs from project id. | string | | null | +| [factories_config](variables.tf#L201) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} | | [iam](variables-iam.tf#L17) | Authoritative IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_bindings](variables-iam.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) | | {} | | [iam_bindings_additive](variables-iam.tf#L39) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | | [iam_by_principals](variables-iam.tf#L61) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam` variable. | map(list(string)) | | {} | | [iam_by_principals_additive](variables-iam.tf#L54) | Additive IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam_bindings_additive` variable. | map(list(string)) | | {} | | [iam_by_principals_conditional](variables-iam.tf#L68) | Authoritative IAM binding in {PRINCIPAL => {roles = [roles], condition = {cond}}} format. Principals need to be statically defined to avoid errors. Condition is required. | map(object({…})) | | {} | -| [kms_autokeys](variables.tf#L207) | KMS Autokey key handles. | map(object({…})) | | {} | -| [labels](variables.tf#L225) | Resource labels. | map(string) | | {} | -| [lien_reason](variables.tf#L232) | If non-empty, creates a project lien with this description. | string | | null | +| [kms_autokeys](variables.tf#L217) | KMS Autokey key handles. | map(object({…})) | | {} | +| [labels](variables.tf#L235) | Resource labels. | map(string) | | {} | +| [lien_reason](variables.tf#L242) | If non-empty, creates a project lien with this description. | string | | null | | [log_scopes](variables-observability.tf#L117) | Log scopes under this project. | map(object({…})) | | {} | | [logging_data_access](variables-observability.tf#L127) | Control activation of data access logs. The special 'allServices' key denotes configuration for all services. | map(object({…})) | | {} | | [logging_exclusions](variables-observability.tf#L138) | Logging exclusions for this project in the form {NAME -> FILTER}. | map(string) | | {} | @@ -2290,26 +2317,26 @@ module "project" { | [metric_scopes](variables-observability.tf#L216) | List of projects that will act as metric scopes for this project. | list(string) | | [] | | [network_tags](variables-tags.tf#L17) | Network tags by key name. If `id` is provided, key creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} | | [notification_channels](variables-observability.tf#L223) | Monitoring notification channels. | map(object({…})) | | {} | -| [org_policies](variables.tf#L243) | Organization policies applied to this project keyed by policy name. | map(object({…})) | | {} | +| [org_policies](variables.tf#L253) | Organization policies applied to this project keyed by policy name. | map(object({…})) | | {} | | [pam_entitlements](variables-pam.tf#L17) | Privileged Access Manager entitlements for this resource, keyed by entitlement ID. | map(object({…})) | | {} | -| [parent](variables.tf#L271) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | string | | null | -| [prefix](variables.tf#L285) | Optional prefix used to generate project id and name. | string | | null | -| [project_reuse](variables.tf#L295) | Reuse existing project if not null. If name and number are not passed in, a data source is used. | object({…}) | | null | +| [parent](variables.tf#L281) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | string | | null | +| [prefix](variables.tf#L295) | Optional prefix used to generate project id and name. | string | | null | +| [project_reuse](variables.tf#L305) | Reuse existing project if not null. If name and number are not passed in, a data source is used. | object({…}) | | null | | [quotas](variables-quotas.tf#L17) | Service quota configuration. | map(object({…})) | | {} | | [scc_mute_configs](variables-scc.tf#L17) | SCC mute configurations keyed by name. | map(object({…})) | | {} | | [scc_sha_custom_modules](variables-scc.tf#L28) | SCC custom modules keyed by module name. | map(object({…})) | | {} | -| [service_agents_config](variables.tf#L315) | Automatic service agent configuration options. | object({…}) | | {} | -| [service_config](variables.tf#L326) | Configure service API activation. | object({…}) | | {…} | -| [service_encryption_key_ids](variables.tf#L338) | Service Agents to be granted encryption/decryption permissions over Cloud KMS encryption keys. Format {SERVICE_AGENT => [KEY_ID]}. | map(list(string)) | | {} | -| [services](variables.tf#L345) | Service APIs to enable. | list(string) | | [] | -| [shared_vpc_host_config](variables.tf#L351) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | object({…}) | | null | -| [shared_vpc_service_config](variables.tf#L361) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | object({…}) | | {…} | -| [skip_delete](variables.tf#L398) | Deprecated. Use deletion_policy. | bool | | null | +| [service_agents_config](variables.tf#L325) | Automatic service agent configuration options. | object({…}) | | {} | +| [service_config](variables.tf#L336) | Configure service API activation. | object({…}) | | {…} | +| [service_encryption_key_ids](variables.tf#L348) | Service Agents to be granted encryption/decryption permissions over Cloud KMS encryption keys. Format {SERVICE_AGENT => [KEY_ID]}. | map(list(string)) | | {} | +| [services](variables.tf#L355) | Service APIs to enable. | list(string) | | [] | +| [shared_vpc_host_config](variables.tf#L361) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | object({…}) | | null | +| [shared_vpc_service_config](variables.tf#L371) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | object({…}) | | {…} | +| [skip_delete](variables.tf#L408) | Deprecated. Use deletion_policy. | bool | | null | | [tag_bindings](variables-tags.tf#L89) | Tag bindings for this project, in key => tag value id format. | map(string) | | null | | [tags](variables-tags.tf#L96) | Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} | | [tags_config](variables-tags.tf#L161) | Fine-grained control on tag resource and IAM creation. | object({…}) | | {} | -| [universe](variables.tf#L410) | GCP universe where to deploy the project. The prefix will be prepended to the project id. | object({…}) | | null | -| [vpc_sc](variables.tf#L421) | VPC-SC configuration for the project, use when `ignore_changes` for resources is set in the VPC-SC module. | object({…}) | | null | +| [universe](variables.tf#L420) | GCP universe where to deploy the project. The prefix will be prepended to the project id. | object({…}) | | null | +| [vpc_sc](variables.tf#L431) | VPC-SC configuration for the project, use when `ignore_changes` for resources is set in the VPC-SC module. | object({…}) | | null | | [workload_identity_pools](variables-identity-providers.tf#L17) | Workload Identity Federation pools and providers. | map(object({…})) | | {} | ## Outputs @@ -2317,31 +2344,32 @@ module "project" { | name | description | sensitive | |---|---|:---:| | [alert_ids](outputs.tf#L17) | Monitoring alert IDs. | | -| [bigquery_reservations](outputs.tf#L25) | BigQuery reservations and assignments. | | -| [custom_role_id](outputs.tf#L33) | Map of custom role IDs created in the project. | | -| [custom_roles](outputs.tf#L38) | Map of custom roles resources created in the project. | | -| [default_service_accounts](outputs.tf#L43) | Emails of the default service accounts for this project. | | -| [id](outputs.tf#L48) | Project id. | | -| [kms_autokeys](outputs.tf#L66) | KMS Autokey key ids. | | -| [name](outputs.tf#L73) | Project name. | | -| [network_tag_keys](outputs.tf#L85) | Tag key resources. | | -| [network_tag_values](outputs.tf#L94) | Tag value resources. | | -| [notification_channel_names](outputs.tf#L102) | Notification channel names. | | -| [notification_channels](outputs.tf#L110) | Full notification channel objects. | | -| [number](outputs.tf#L115) | Project number. | | -| [organization_policies_ids](outputs.tf#L130) | Map of ORGANIZATION_POLICIES => ID in the organization. | | -| [project_id](outputs.tf#L137) | Project id. | | -| [quota_configs](outputs.tf#L155) | Quota configurations. | | -| [quotas](outputs.tf#L166) | Quota resources. | | -| [scc_custom_sha_modules_ids](outputs.tf#L171) | Map of SCC CUSTOM SHA MODULES => ID in the project. | | -| [service_agents](outputs.tf#L176) | List of all (active) service agents for this project. | | -| [services](outputs.tf#L185) | Service APIs to enable in the project. | | -| [sink_writer_identities](outputs.tf#L194) | Writer identities created for each sink. | | -| [tag_keys](outputs.tf#L201) | Tag key resources. | | -| [tag_values](outputs.tf#L210) | Tag value resources. | | -| [workload_identity_pool_ids](outputs.tf#L218) | Workload identity provider ids. | | -| [workload_identity_provider_ids](outputs.tf#L225) | Workload identity provider attributes. | | -| [workload_identity_providers](outputs.tf#L233) | Workload identity provider attributes. | | +| [asset_search_results](outputs.tf#L25) | Cloud Asset Inventory search results. | | +| [bigquery_reservations](outputs.tf#L32) | BigQuery reservations and assignments. | | +| [custom_role_id](outputs.tf#L40) | Map of custom role IDs created in the project. | | +| [custom_roles](outputs.tf#L45) | Map of custom roles resources created in the project. | | +| [default_service_accounts](outputs.tf#L50) | Emails of the default service accounts for this project. | | +| [id](outputs.tf#L55) | Project id. | | +| [kms_autokeys](outputs.tf#L73) | KMS Autokey key ids. | | +| [name](outputs.tf#L80) | Project name. | | +| [network_tag_keys](outputs.tf#L92) | Tag key resources. | | +| [network_tag_values](outputs.tf#L101) | Tag value resources. | | +| [notification_channel_names](outputs.tf#L109) | Notification channel names. | | +| [notification_channels](outputs.tf#L117) | Full notification channel objects. | | +| [number](outputs.tf#L122) | Project number. | | +| [organization_policies_ids](outputs.tf#L137) | Map of ORGANIZATION_POLICIES => ID in the organization. | | +| [project_id](outputs.tf#L144) | Project id. | | +| [quota_configs](outputs.tf#L162) | Quota configurations. | | +| [quotas](outputs.tf#L173) | Quota resources. | | +| [scc_custom_sha_modules_ids](outputs.tf#L178) | Map of SCC CUSTOM SHA MODULES => ID in the project. | | +| [service_agents](outputs.tf#L183) | List of all (active) service agents for this project. | | +| [services](outputs.tf#L192) | Service APIs to enable in the project. | | +| [sink_writer_identities](outputs.tf#L201) | Writer identities created for each sink. | | +| [tag_keys](outputs.tf#L208) | Tag key resources. | | +| [tag_values](outputs.tf#L217) | Tag value resources. | | +| [workload_identity_pool_ids](outputs.tf#L225) | Workload identity provider ids. | | +| [workload_identity_provider_ids](outputs.tf#L232) | Workload identity provider attributes. | | +| [workload_identity_providers](outputs.tf#L240) | Workload identity provider attributes. | | ## Fixtures diff --git a/modules/project/assets.tf b/modules/project/assets.tf index 77fac7cab..8d8dfb6dc 100644 --- a/modules/project/assets.tf +++ b/modules/project/assets.tf @@ -14,6 +14,13 @@ * limitations under the License. */ +data "google_cloud_asset_search_all_resources" "default" { + for_each = var.asset_search + scope = "projects/${local.project.project_id}" + asset_types = each.value.asset_types + query = each.value.query +} + resource "google_cloud_asset_project_feed" "default" { for_each = var.asset_feeds project = local.project.project_id diff --git a/modules/project/outputs.tf b/modules/project/outputs.tf index 1fa7b32b8..aae95dd83 100644 --- a/modules/project/outputs.tf +++ b/modules/project/outputs.tf @@ -22,6 +22,13 @@ output "alert_ids" { } } +output "asset_search_results" { + description = "Cloud Asset Inventory search results." + value = { + for k, v in data.google_cloud_asset_search_all_resources.default : k => v.results + } +} + output "bigquery_reservations" { description = "BigQuery reservations and assignments." value = { diff --git a/modules/project/variables.tf b/modules/project/variables.tf index 22471b1ea..8a4d495f3 100644 --- a/modules/project/variables.tf +++ b/modules/project/variables.tf @@ -48,6 +48,16 @@ variable "asset_feeds" { } } +variable "asset_search" { + description = "Cloud Asset Inventory search configurations." + type = map(object({ + asset_types = list(string) + query = optional(string) + })) + default = {} + nullable = false +} + variable "auto_create_network" { description = "Whether to create the default network for the project." type = bool From 97297d6065db987aa8878997d64c8b30d5886228 Mon Sep 17 00:00:00 2001 From: lopezvit Date: Thu, 5 Feb 2026 17:50:43 +0200 Subject: [PATCH 2/6] fix(project-factory): Correctly interpolate IAM principals in tags (#3704) * fix(project-factory): Correctly interpolate IAM principals in tags Moves the processing of `tags` and `tag_bindings` from the `projects` module instance to the `projects-iam` instance. This fixes a bug where IAM principals for automation service accounts, referenced via `$iam_principals:service_accounts/...`, were not being interpolated within `tags` IAM definitions. The `projects` module was called before the automation service account context was available, leading to the literal string being used instead of the service account email. Processing tags in the `projects-iam` module ensures the full context is available for interpolation. Adds new tests for both the `project` and `project-factory` modules to validate the fix. * fix(project-factory): Tag creation is now done in 2 steps. 1st step(projects): Creation of the tags without IAM bindings 2nd step(projects-iam): IAM bindings without creating the tags again That way we are more backwards compatible as tags and tags values are back to be under module.project-factory.module.projects["*"].google_tags_tag_* * fix(modules/project-factory): introduce fix suggested by @ludoo, fix logs * fix(modules/project-factory): fix linting --------- Co-authored-by: Ludovico Magnocavallo --- modules/project-factory/README.md | 48 +++++++++++++++ modules/project-factory/projects.tf | 11 +++- .../project/tags_iam_principals.tfvars | 44 +++++++++++++ .../modules/project/tags_iam_principals.yaml | 44 +++++++++++++ tests/modules/project/tftest.yaml | 1 + .../project_factory/examples/example.yaml | 2 +- .../examples/tags_iam_principals_bug.yaml | 61 +++++++++++++++++++ 7 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 tests/modules/project/tags_iam_principals.tfvars create mode 100644 tests/modules/project/tags_iam_principals.yaml create mode 100644 tests/modules/project_factory/examples/tags_iam_principals_bug.yaml diff --git a/modules/project-factory/README.md b/modules/project-factory/README.md index 91e3df336..b8dc5d151 100644 --- a/modules/project-factory/README.md +++ b/modules/project-factory/README.md @@ -47,6 +47,7 @@ The code is meant to be executed by a high level service account with powerful p - [Variables](#variables) - [Outputs](#outputs) - [Tests](#tests) + - [Tags with $iam_principals interpolation](#tags-with-iam_principals-interpolation) ## Folder hierarchy @@ -927,3 +928,50 @@ services: - storage.googleapis.com # tftest-file id=test-2 path=data/projects/test-2.yaml ``` + +### Tags with $iam_principals interpolation + +This test validates that `$iam_principals:service_accounts/...` interpolation works correctly +within tags IAM definitions when referencing automation service accounts created by the same +project-factory. + +```hcl +module "project-factory" { + source = "./fabric/modules/project-factory" + data_defaults = { + billing_account = "012345-67890A-ABCDEF" + locations = { + storage = "eu" + } + } + data_overrides = { + prefix = "test-pf" + } + factories_config = { + projects = "data/projects" + } +} +# tftest modules=5 resources=9 files=tags-iam-test inventory=tags_iam_principals_bug.yaml +``` + +```yaml +parent: folders/1234567890 +services: + - resourcemanager.googleapis.com +automation: + project: test-pf-teams-iac-0 + service_accounts: + rw: + description: Read/write automation service account. +tags: + allow-key-creation: + description: Allow key creation for automation service account + values: + allow: + description: Allow key creation + iam: + roles/resourcemanager.tagUser: + - $iam_principals:service_accounts/tags-iam-test/automation/rw +# tftest-file id=tags-iam-test path=data/projects/tags-iam-test.yaml +``` + diff --git a/modules/project-factory/projects.tf b/modules/project-factory/projects.tf index 1366b9c8b..c344e1d1e 100644 --- a/modules/project-factory/projects.tf +++ b/modules/project-factory/projects.tf @@ -133,7 +133,10 @@ module "projects" { tag_bindings = merge( each.value.tag_bindings, var.data_merges.tag_bindings ) - tags = each.value.tags + tags = each.value.tags + tags_config = { + ignore_iam = true + } universe = each.value.universe vpc_sc = each.value.vpc_sc workload_identity_pools = each.value.workload_identity_pools @@ -186,5 +189,9 @@ module "projects-iam" { ) shared_vpc_host_config = each.value.shared_vpc_host_config shared_vpc_service_config = each.value.shared_vpc_service_config - universe = each.value.universe + tags = each.value.tags + tags_config = { + force_context_ids = true + } + universe = each.value.universe } diff --git a/tests/modules/project/tags_iam_principals.tfvars b/tests/modules/project/tags_iam_principals.tfvars new file mode 100644 index 000000000..c8fc62937 --- /dev/null +++ b/tests/modules/project/tags_iam_principals.tfvars @@ -0,0 +1,44 @@ +# 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 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Test for $iam_principals interpolation within tags +# This test demonstrates the bug where if a service account created by the same +# project-factory is referenced via $iam_principals:service_accounts/... in tags, +# the interpolation fails because the context doesn't have the key at the time +# tags is processed (tags are processed in module "projects", but iam_principals +# for the project's own service accounts are only added in module "projects-iam"). + +# Case 1: iam_principals key IS present in context - should work +context = { + iam_principals = { + # Simulate service accounts created by project-factory with nested paths + "service_accounts/my-project/automation/rw" = "serviceAccount:my-project-rw@my-project.iam.gserviceaccount.com" + } +} + +tags = { + allow-key-creation = { + description = "Allow key creation for automation service account" + values = { + allow = { + description = "Allow key creation" + iam = { + "roles/resourcemanager.tagUser" = [ + "$iam_principals:service_accounts/my-project/automation/rw" + ] + } + } + } + } +} diff --git a/tests/modules/project/tags_iam_principals.yaml b/tests/modules/project/tags_iam_principals.yaml new file mode 100644 index 000000000..1201a2792 --- /dev/null +++ b/tests/modules/project/tags_iam_principals.yaml @@ -0,0 +1,44 @@ +# 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 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This test verifies that $iam_principals interpolation works correctly +# in tags when using nested paths like service_accounts/project/automation/rw +# +# Expected: The member should resolve to the service account email from context + +values: + google_project.project[0]: + name: my-project + google_tags_tag_key.default["allow-key-creation"]: + parent: projects/my-project + purpose: null + purpose_data: null + short_name: allow-key-creation + timeouts: null + google_tags_tag_value.default["allow-key-creation/allow"]: + short_name: allow + timeouts: null + google_tags_tag_value_iam_binding.default["allow-key-creation/allow:roles/resourcemanager.tagUser"]: + condition: [] + members: + # This is the expected behavior - the interpolation should resolve + # to the actual service account email from context.iam_principals + - serviceAccount:my-project-rw@my-project.iam.gserviceaccount.com + role: roles/resourcemanager.tagUser + +counts: + google_project: 1 + google_tags_tag_key: 1 + google_tags_tag_value: 1 + google_tags_tag_value_iam_binding: 1 diff --git a/tests/modules/project/tftest.yaml b/tests/modules/project/tftest.yaml index 465f71bb1..ed27f6fca 100644 --- a/tests/modules/project/tftest.yaml +++ b/tests/modules/project/tftest.yaml @@ -31,4 +31,5 @@ tests: service_encryption_keys: service_agents: service_agents_universe: + tags_iam_principals: universe: diff --git a/tests/modules/project_factory/examples/example.yaml b/tests/modules/project_factory/examples/example.yaml index 01337b178..815463bb9 100644 --- a/tests/modules/project_factory/examples/example.yaml +++ b/tests/modules/project_factory/examples/example.yaml @@ -438,7 +438,7 @@ values: description: My value 3 short_name: my-value-2 timeouts: null - ? module.project-factory.module.projects["dev-ta-app0-be"].google_tags_tag_value_iam_binding.default["my-tag-key-1/my-value-2:roles/resourcemanager.tagUser"] + ? module.project-factory.module.projects-iam["dev-ta-app0-be"].google_tags_tag_value_iam_binding.default["my-tag-key-1/my-value-2:roles/resourcemanager.tagUser"] : condition: [] members: - user:user@example.com diff --git a/tests/modules/project_factory/examples/tags_iam_principals_bug.yaml b/tests/modules/project_factory/examples/tags_iam_principals_bug.yaml new file mode 100644 index 000000000..ba2280409 --- /dev/null +++ b/tests/modules/project_factory/examples/tags_iam_principals_bug.yaml @@ -0,0 +1,61 @@ +# 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 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Test for $iam_principals interpolation bug in tags +# +# BUG DESCRIPTION: +# When a project uses tags with IAM that references an automation service account +# via $iam_principals:service_accounts/PROJECT/automation/SA, the interpolation +# fails because: +# +# 1. In projects.tf, `module "projects"` (line ~78) processes `tags` but passes +# a context that does NOT include the project's own automation service accounts +# +# 2. In projects.tf, `module "projects-iam"` (line ~141) has the correct context +# with automation service accounts merged, but it does NOT process `tags` +# +# RESULT: The literal string "$iam_principals:service_accounts/..." is used +# as the member instead of being interpolated to the actual service account email. +# +# This test expects the CORRECT behavior (interpolated value). +# Currently, it will FAIL until the bug is fixed. + +values: + module.project-factory.module.projects["tags-iam-test"].google_project.project[0]: + name: test-pf-tags-iam-test + module.project-factory.module.projects["tags-iam-test"].google_tags_tag_key.default["allow-key-creation"]: + description: Allow key creation for automation service account + short_name: allow-key-creation + module.project-factory.module.projects["tags-iam-test"].google_tags_tag_value.default["allow-key-creation/allow"]: + description: Allow key creation + short_name: allow + ? module.project-factory.module.projects-iam["tags-iam-test"].google_tags_tag_value_iam_binding.default["allow-key-creation/allow:roles/resourcemanager.tagUser"] + : condition: [] + members: + # EXPECTED: This should be the interpolated service account email + # BUG: Currently this is the literal "$iam_principals:service_accounts/tags-iam-test/automation/rw" + - serviceAccount:tags-iam-test-rw@test-pf-teams-iac-0.iam.gserviceaccount.com + role: roles/resourcemanager.tagUser + module.project-factory.module.automation-service-accounts["tags-iam-test/automation/rw"].google_service_account.service_account[0]: + account_id: tags-iam-test-rw + email: tags-iam-test-rw@test-pf-teams-iac-0.iam.gserviceaccount.com + project: test-pf-teams-iac-0 + +counts: + google_project: 1 + google_service_account: 1 + google_tags_tag_key: 1 + google_tags_tag_value: 1 + google_tags_tag_value_iam_binding: 1 + modules: 5 From 06da98fac6bc255341c1c6a138d33f7f1ff3b0dd Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Thu, 5 Feb 2026 19:06:34 +0100 Subject: [PATCH 3/6] Fix regression in project factory module context (#3708) * fix regression in pf * regression test --- modules/project-factory/README.md | 21 ++++++++++++++----- modules/project-factory/main.tf | 2 +- modules/project-factory/projects.tf | 4 ++-- .../project_factory/examples/test-1.yaml | 17 +++++++++++++++ 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/modules/project-factory/README.md b/modules/project-factory/README.md index b8dc5d151..daaa9c7a4 100644 --- a/modules/project-factory/README.md +++ b/modules/project-factory/README.md @@ -47,7 +47,6 @@ The code is meant to be executed by a high level service account with powerful p - [Variables](#variables) - [Outputs](#outputs) - [Tests](#tests) - - [Tags with $iam_principals interpolation](#tags-with-iam_principals-interpolation) ## Folder hierarchy @@ -869,6 +868,7 @@ compute.disableSerialPortAccess: | [service_accounts](outputs.tf#L158) | Service account emails. | | | [storage_buckets](outputs.tf#L163) | Bucket names. | | + ## Tests These tests validate fixes to the project factory. @@ -876,6 +876,13 @@ These tests validate fixes to the project factory. ```hcl module "project-factory" { source = "./fabric/modules/project-factory" + context = { + condition_vars = { + organization = { + id = 1234567890 + } + } + } data_defaults = { billing_account = "012345-67890A-ABCDEF" locations = { @@ -895,7 +902,7 @@ module "project-factory" { projects = "data/projects" } } -# tftest modules=4 resources=24 files=test-0,test-1,test-2 inventory=test-1.yaml +# tftest modules=5 resources=25 files=test-0,test-1,test-2 inventory=test-1.yaml ``` ```yaml @@ -905,6 +912,13 @@ services: - iam.googleapis.com - contactcenteraiplatform.googleapis.com - container.googleapis.com +iam_bindings_additive: + test_context: + role: roles/viewer + member: user:user1@example.com + condition: + title: Test context + expression: resource.matchTag('${organization.id}/context', 'project-factory') # tftest-file id=test-0 path=data/projects/test-0.yaml ``` @@ -929,8 +943,6 @@ services: # tftest-file id=test-2 path=data/projects/test-2.yaml ``` -### Tags with $iam_principals interpolation - This test validates that `$iam_principals:service_accounts/...` interpolation works correctly within tags IAM definitions when referencing automation service accounts created by the same project-factory. @@ -974,4 +986,3 @@ tags: - $iam_principals:service_accounts/tags-iam-test/automation/rw # tftest-file id=tags-iam-test path=data/projects/tags-iam-test.yaml ``` - diff --git a/modules/project-factory/main.tf b/modules/project-factory/main.tf index 90d85bf22..55ac766d1 100644 --- a/modules/project-factory/main.tf +++ b/modules/project-factory/main.tf @@ -39,7 +39,7 @@ resource "terraform_data" "defaults_preconditions" { } # precondition { # condition = local.projects_input == null - # error_message = yamlencode(local.budget_project_sets) + # error_message = yamlencode(var.context.condition_vars) # } } } diff --git a/modules/project-factory/projects.tf b/modules/project-factory/projects.tf index c344e1d1e..d3cbaa30e 100644 --- a/modules/project-factory/projects.tf +++ b/modules/project-factory/projects.tf @@ -95,11 +95,11 @@ module "projects" { each.value.contacts, var.data_merges.contacts ) context = merge(local.ctx, { - condition_vars = { + condition_vars = merge(local.ctx.condition_vars, { folder_ids = { for k, v in local.ctx_folder_ids : replace(k, "$folder_ids:", "") => v } - } + }) folder_ids = local.ctx_folder_ids }) default_service_account = try(each.value.default_service_account, "keep") diff --git a/tests/modules/project_factory/examples/test-1.yaml b/tests/modules/project_factory/examples/test-1.yaml index 512cddac6..dc066b09c 100644 --- a/tests/modules/project_factory/examples/test-1.yaml +++ b/tests/modules/project_factory/examples/test-1.yaml @@ -20,3 +20,20 @@ values: project_id: test-1 module.project-factory.module.projects["test-2"].google_project.project[0]: project_id: bar-test-2 + module.project-factory.module.projects-iam["test-0"].google_project_iam_member.bindings["test_context"]: + condition: + - description: null + expression: resource.matchTag('1234567890/context', 'project-factory') + title: Test context + member: user:user1@example.com + project: foo-test-0 + role: roles/viewer +counts: + google_project: 3 + google_project_iam_member: 6 + google_project_service: 10 + google_project_service_identity: 3 + google_storage_project_service_account: 1 + modules: 5 + resources: 25 + terraform_data: 2 From d499dc692887e928ea88993688d402784064ceb0 Mon Sep 17 00:00:00 2001 From: Vannick Trinquier Date: Fri, 6 Feb 2026 14:14:46 +0700 Subject: [PATCH 4/6] Add support for bucket logging configuration in module gcs and project-factory (#3699) --- .../0-org-setup/schemas/project.schema.json | 15 ++++ .../0-org-setup/schemas/project.schema.md | 4 + .../2-networking/schemas/project.schema.json | 15 ++++ .../2-networking/schemas/project.schema.md | 4 + .../schemas/project.schema.json | 15 ++++ .../schemas/project.schema.md | 4 + .../2-security/schemas/project.schema.json | 15 ++++ .../2-security/schemas/project.schema.md | 4 + modules/gcs/README.md | 85 ++++++++++++------- modules/gcs/main.tf | 7 +- modules/gcs/variables.tf | 15 ++-- modules/project-factory/README.md | 26 ++++-- modules/project-factory/automation.tf | 7 +- modules/project-factory/projects-buckets.tf | 9 +- .../schemas/project.schema.json | 15 ++++ .../project-factory/schemas/project.schema.md | 4 + modules/project-factory/variables-projects.tf | 8 ++ modules/project-factory/variables.tf | 1 + .../examples/retention-logging-context.yaml | 28 ++++++ .../project_factory/examples/example.yaml | 66 +++++++++++++- 20 files changed, 294 insertions(+), 53 deletions(-) create mode 100644 tests/modules/gcs/examples/retention-logging-context.yaml diff --git a/fast/stages/0-org-setup/schemas/project.schema.json b/fast/stages/0-org-setup/schemas/project.schema.json index 87866ff86..4881ece7c 100644 --- a/fast/stages/0-org-setup/schemas/project.schema.json +++ b/fast/stages/0-org-setup/schemas/project.schema.json @@ -1121,6 +1121,21 @@ } } }, + "logging_config": { + "type": "object", + "additionalProperties": false, + "properties": { + "log_bucket": { + "type": "string" + }, + "log_object_prefix": { + "type": "string" + } + }, + "required": [ + "log_bucket" + ] + }, "location": { "type": "string" }, diff --git a/fast/stages/0-org-setup/schemas/project.schema.md b/fast/stages/0-org-setup/schemas/project.schema.md index f4b2057e6..5e7f57c9b 100644 --- a/fast/stages/0-org-setup/schemas/project.schema.md +++ b/fast/stages/0-org-setup/schemas/project.schema.md @@ -308,6 +308,10 @@ - **num_newer_versions**: *number* - **with_state**: *string*
*enum: ['LIVE', 'ARCHIVED', 'ANY']* + - **logging_config**: *object* +
*additional properties: false* + - **log_bucket**: *string* + - **log_object_prefix**: *string* - **location**: *string* - **managed_folders**: *object*
*additional properties: false* diff --git a/fast/stages/2-networking/schemas/project.schema.json b/fast/stages/2-networking/schemas/project.schema.json index 87866ff86..4881ece7c 100644 --- a/fast/stages/2-networking/schemas/project.schema.json +++ b/fast/stages/2-networking/schemas/project.schema.json @@ -1121,6 +1121,21 @@ } } }, + "logging_config": { + "type": "object", + "additionalProperties": false, + "properties": { + "log_bucket": { + "type": "string" + }, + "log_object_prefix": { + "type": "string" + } + }, + "required": [ + "log_bucket" + ] + }, "location": { "type": "string" }, diff --git a/fast/stages/2-networking/schemas/project.schema.md b/fast/stages/2-networking/schemas/project.schema.md index f4b2057e6..5e7f57c9b 100644 --- a/fast/stages/2-networking/schemas/project.schema.md +++ b/fast/stages/2-networking/schemas/project.schema.md @@ -308,6 +308,10 @@ - **num_newer_versions**: *number* - **with_state**: *string*
*enum: ['LIVE', 'ARCHIVED', 'ANY']* + - **logging_config**: *object* +
*additional properties: false* + - **log_bucket**: *string* + - **log_object_prefix**: *string* - **location**: *string* - **managed_folders**: *object*
*additional properties: false* diff --git a/fast/stages/2-project-factory/schemas/project.schema.json b/fast/stages/2-project-factory/schemas/project.schema.json index 87866ff86..4881ece7c 100644 --- a/fast/stages/2-project-factory/schemas/project.schema.json +++ b/fast/stages/2-project-factory/schemas/project.schema.json @@ -1121,6 +1121,21 @@ } } }, + "logging_config": { + "type": "object", + "additionalProperties": false, + "properties": { + "log_bucket": { + "type": "string" + }, + "log_object_prefix": { + "type": "string" + } + }, + "required": [ + "log_bucket" + ] + }, "location": { "type": "string" }, diff --git a/fast/stages/2-project-factory/schemas/project.schema.md b/fast/stages/2-project-factory/schemas/project.schema.md index f4b2057e6..5e7f57c9b 100644 --- a/fast/stages/2-project-factory/schemas/project.schema.md +++ b/fast/stages/2-project-factory/schemas/project.schema.md @@ -308,6 +308,10 @@ - **num_newer_versions**: *number* - **with_state**: *string*
*enum: ['LIVE', 'ARCHIVED', 'ANY']* + - **logging_config**: *object* +
*additional properties: false* + - **log_bucket**: *string* + - **log_object_prefix**: *string* - **location**: *string* - **managed_folders**: *object*
*additional properties: false* diff --git a/fast/stages/2-security/schemas/project.schema.json b/fast/stages/2-security/schemas/project.schema.json index 87866ff86..4881ece7c 100644 --- a/fast/stages/2-security/schemas/project.schema.json +++ b/fast/stages/2-security/schemas/project.schema.json @@ -1121,6 +1121,21 @@ } } }, + "logging_config": { + "type": "object", + "additionalProperties": false, + "properties": { + "log_bucket": { + "type": "string" + }, + "log_object_prefix": { + "type": "string" + } + }, + "required": [ + "log_bucket" + ] + }, "location": { "type": "string" }, diff --git a/fast/stages/2-security/schemas/project.schema.md b/fast/stages/2-security/schemas/project.schema.md index f4b2057e6..5e7f57c9b 100644 --- a/fast/stages/2-security/schemas/project.schema.md +++ b/fast/stages/2-security/schemas/project.schema.md @@ -308,6 +308,10 @@ - **num_newer_versions**: *number* - **with_state**: *string*
*enum: ['LIVE', 'ARCHIVED', 'ANY']* + - **logging_config**: *object* +
*additional properties: false* + - **log_bucket**: *string* + - **log_object_prefix**: *string* - **location**: *string* - **managed_folders**: *object*
*additional properties: false* diff --git a/modules/gcs/README.md b/modules/gcs/README.md index ec262d8b7..a2f865b73 100644 --- a/modules/gcs/README.md +++ b/modules/gcs/README.md @@ -125,6 +125,31 @@ module "bucket" { # tftest modules=1 resources=1 inventory=retention-logging.yaml ``` +```hcl +module "bucket" { + source = "./fabric/modules/gcs" + project_id = var.project_id + prefix = var.prefix + name = "my-bucket" + location = "EU" + retention_policy = { + retention_period = 100 + is_locked = true + } + soft_delete_retention = 7776000 + context = { + storage_buckets = { + log-bucket = "log-bucket" + } + } + logging_config = { + log_bucket = "$storage_buckets:log-bucket" + log_object_prefix = null + } +} +# tftest modules=1 inventory=retention-logging-context.yaml +``` + ## Lifecycle rule ```hcl @@ -392,42 +417,42 @@ module "bucket" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L223) | Bucket name suffix. | string | ✓ | | +| [name](variables.tf#L224) | Bucket name suffix. | string | ✓ | | | [autoclass](variables.tf#L17) | Enable autoclass to automatically transition objects to appropriate storage classes based on their access pattern. If set to true, storage_class must be set to STANDARD. Defaults to false. | bool | | null | | [bucket_create](variables.tf#L23) | Create bucket. | bool | | true | -| [context](variables.tf#L30) | Context-specific interpolations. | object({…}) | | {} | -| [cors](variables.tf#L45) | CORS configuration for the bucket. Defaults to null. | object({…}) | | null | -| [custom_placement_config](variables.tf#L56) | The bucket's custom location configuration, which specifies the individual regions that comprise a dual-region bucket. If the bucket is designated as REGIONAL or MULTI_REGIONAL, the parameters are empty. | list(string) | | null | -| [default_event_based_hold](variables.tf#L62) | Enable event based hold to new objects added to specific bucket, defaults to false. | bool | | null | -| [enable_hierarchical_namespace](variables.tf#L68) | Enables hierarchical namespace. | bool | | null | -| [enable_object_retention](variables.tf#L74) | Enables object retention on a storage bucket. | bool | | null | -| [encryption_key](variables.tf#L80) | KMS key that will be used for encryption. | string | | null | -| [force_destroy](variables.tf#L86) | Optional map to set force destroy keyed by name, defaults to false. | bool | | false | +| [context](variables.tf#L30) | Context-specific interpolations. | object({…}) | | {} | +| [cors](variables.tf#L46) | CORS configuration for the bucket. Defaults to null. | object({…}) | | null | +| [custom_placement_config](variables.tf#L57) | The bucket's custom location configuration, which specifies the individual regions that comprise a dual-region bucket. If the bucket is designated as REGIONAL or MULTI_REGIONAL, the parameters are empty. | list(string) | | null | +| [default_event_based_hold](variables.tf#L63) | Enable event based hold to new objects added to specific bucket, defaults to false. | bool | | null | +| [enable_hierarchical_namespace](variables.tf#L69) | Enables hierarchical namespace. | bool | | null | +| [enable_object_retention](variables.tf#L75) | Enables object retention on a storage bucket. | bool | | null | +| [encryption_key](variables.tf#L81) | KMS key that will be used for encryption. | string | | null | +| [force_destroy](variables.tf#L87) | Optional map to set force destroy keyed by name, defaults to false. | bool | | false | | [iam](variables-iam.tf#L17) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_bindings](variables-iam.tf#L23) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) | | {} | | [iam_bindings_additive](variables-iam.tf#L38) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | | [iam_by_principals](variables-iam.tf#L53) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable. | map(list(string)) | | {} | -| [ip_filter](variables.tf#L92) | The bucket's IP filter configuration. | object({…}) | | null | -| [kms_autokeys](variables.tf#L103) | KMS Autokey key handles. If location is not specified the bucket location will be used. Key handle names will be added to the kms_keys context with an `autokeys/` prefix. | map(object({…})) | | {} | -| [labels](variables.tf#L121) | Labels to be attached to all buckets. | map(string) | | {} | -| [lifecycle_rules](variables.tf#L127) | Bucket lifecycle rule. | map(object({…})) | | {} | -| [location](variables.tf#L176) | Bucket location. | string | | null | -| [logging_config](variables.tf#L186) | Bucket logging configuration. | object({…}) | | null | -| [managed_folders](variables.tf#L195) | Managed folders to create within the bucket in {PATH => CONFIG} format. | map(object({…})) | | {} | -| [notification_config](variables.tf#L228) | GCS Notification configuration. | object({…}) | | null | -| [objects_to_upload](variables.tf#L246) | Objects to be uploaded to bucket. | map(object({…})) | | {} | -| [prefix](variables.tf#L272) | Optional prefix used to generate the bucket name. | string | | null | -| [project_id](variables.tf#L282) | Bucket project id. Only required when creating buckets, or notification config topics. | string | | null | -| [public_access_prevention](variables.tf#L301) | Prevents public access to the bucket. | string | | null | -| [requester_pays](variables.tf#L311) | Enables Requester Pays on a storage bucket. | bool | | null | -| [retention_policy](variables.tf#L317) | Bucket retention policy. | object({…}) | | null | -| [rpo](variables.tf#L326) | Bucket recovery point objective. | string | | null | -| [soft_delete_retention](variables.tf#L336) | The duration in seconds that soft-deleted objects in the bucket will be retained and cannot be permanently deleted. Set to 0 to override the default and disable. | number | | null | -| [storage_class](variables.tf#L342) | Bucket storage class. | string | | "STANDARD" | -| [tag_bindings](variables.tf#L352) | Tag bindings for this folder, in key => tag value id format. | map(string) | | {} | -| [uniform_bucket_level_access](variables.tf#L359) | Allow using object ACLs (false) or not (true, this is the recommended behavior) , defaults to true (which is the recommended practice, but not the behavior of storage API). | bool | | true | -| [versioning](variables.tf#L365) | Enable versioning, defaults to false. | bool | | null | -| [website](variables.tf#L371) | Bucket website. | object({…}) | | null | +| [ip_filter](variables.tf#L93) | The bucket's IP filter configuration. | object({…}) | | null | +| [kms_autokeys](variables.tf#L104) | KMS Autokey key handles. If location is not specified the bucket location will be used. Key handle names will be added to the kms_keys context with an `autokeys/` prefix. | map(object({…})) | | {} | +| [labels](variables.tf#L122) | Labels to be attached to all buckets. | map(string) | | {} | +| [lifecycle_rules](variables.tf#L128) | Bucket lifecycle rule. | map(object({…})) | | {} | +| [location](variables.tf#L177) | Bucket location. | string | | null | +| [logging_config](variables.tf#L187) | Bucket logging configuration. | object({…}) | | null | +| [managed_folders](variables.tf#L196) | Managed folders to create within the bucket in {PATH => CONFIG} format. | map(object({…})) | | {} | +| [notification_config](variables.tf#L229) | GCS Notification configuration. | object({…}) | | null | +| [objects_to_upload](variables.tf#L247) | Objects to be uploaded to bucket. | map(object({…})) | | {} | +| [prefix](variables.tf#L273) | Optional prefix used to generate the bucket name. | string | | null | +| [project_id](variables.tf#L283) | Bucket project id. Only required when creating buckets, or notification config topics. | string | | null | +| [public_access_prevention](variables.tf#L302) | Prevents public access to the bucket. | string | | null | +| [requester_pays](variables.tf#L312) | Enables Requester Pays on a storage bucket. | bool | | null | +| [retention_policy](variables.tf#L318) | Bucket retention policy. | object({…}) | | null | +| [rpo](variables.tf#L327) | Bucket recovery point objective. | string | | null | +| [soft_delete_retention](variables.tf#L337) | The duration in seconds that soft-deleted objects in the bucket will be retained and cannot be permanently deleted. Set to 0 to override the default and disable. | number | | null | +| [storage_class](variables.tf#L343) | Bucket storage class. | string | | "STANDARD" | +| [tag_bindings](variables.tf#L353) | Tag bindings for this folder, in key => tag value id format. | map(string) | | {} | +| [uniform_bucket_level_access](variables.tf#L360) | Allow using object ACLs (false) or not (true, this is the recommended behavior) , defaults to true (which is the recommended practice, but not the behavior of storage API). | bool | | true | +| [versioning](variables.tf#L366) | Enable versioning, defaults to false. | bool | | null | +| [website](variables.tf#L372) | Bucket website. | object({…}) | | null | ## Outputs diff --git a/modules/gcs/main.tf b/modules/gcs/main.tf index 44f516ed8..25b5b281e 100644 --- a/modules/gcs/main.tf +++ b/modules/gcs/main.tf @@ -125,7 +125,12 @@ resource "google_storage_bucket" "bucket" { dynamic "logging" { for_each = var.logging_config == null ? [] : [""] content { - log_bucket = var.logging_config.log_bucket + # log_bucket is actually a storage bucket (and not a logging bucket) + log_bucket = var.logging_config.log_bucket == null ? null : lookup( + local.ctx.storage_buckets, + var.logging_config.log_bucket, + var.logging_config.log_bucket + ) log_object_prefix = var.logging_config.log_object_prefix } } diff --git a/modules/gcs/variables.tf b/modules/gcs/variables.tf index b10e47a5d..671dd0f35 100644 --- a/modules/gcs/variables.tf +++ b/modules/gcs/variables.tf @@ -30,13 +30,14 @@ variable "bucket_create" { variable "context" { description = "Context-specific interpolations." type = object({ - condition_vars = optional(map(map(string)), {}) - custom_roles = optional(map(string), {}) - iam_principals = optional(map(string), {}) - kms_keys = optional(map(string), {}) - locations = optional(map(string), {}) - project_ids = optional(map(string), {}) - tag_values = optional(map(string), {}) + condition_vars = optional(map(map(string)), {}) + custom_roles = optional(map(string), {}) + iam_principals = optional(map(string), {}) + kms_keys = optional(map(string), {}) + locations = optional(map(string), {}) + project_ids = optional(map(string), {}) + storage_buckets = optional(map(string), {}) + tag_values = optional(map(string), {}) }) default = {} nullable = false diff --git a/modules/project-factory/README.md b/modules/project-factory/README.md index daaa9c7a4..652589322 100644 --- a/modules/project-factory/README.md +++ b/modules/project-factory/README.md @@ -424,6 +424,9 @@ module "project-factory" { pubsub_topics = { feeds-topic = "projects/my-cai-feeds-project/topics/feed" } + storage_buckets = { + log-bucket = "log-bucket" + } tag_values = { "context/gke" = "tagValues/654321" "org-policies/drs-allow-all" = "tagValues/123456" @@ -666,6 +669,14 @@ shared_vpc_service_config: - $service_agents:container-engine billing_budgets: - $billing_budgets:test-100 +buckets: + app-0-bucket-a: + location: europe-west8 + app-0-bucket-b: + location: europe-west8 + logging_config: + log_bucket: $storage_buckets:log-bucket + log_object_prefix: log-prefix pam_entitlements: project-admins: max_request_duration: 3600s @@ -752,6 +763,9 @@ automation: - group:team-b-admins@example.org - $iam_principals:service_accounts/dev-tb-app0-0/automation/rw - $iam_principals:service_accounts/dev-tb-app0-0/automation/ro + logging_config: + log_bucket: $storage_buckets:log-bucket + log_object_prefix: log-prefix # tftest-file id=7 path=data/projects/dev-tb-app0-0.yaml schema=project.schema.json ``` @@ -841,14 +855,14 @@ compute.disableSerialPortAccess: | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [factories_config](variables.tf#L163) | Path to folder with YAML resource description data files. | object({…}) | ✓ | | -| [context](variables.tf#L17) | Context-specific interpolations. | object({…}) | | {} | -| [data_defaults](variables.tf#L40) | Optional default values used when corresponding project or folder data from files are missing. | object({…}) | | {} | -| [data_merges](variables.tf#L105) | Optional values that will be merged with corresponding data from files. Combines with `data_defaults`, file data, and `data_overrides`. | object({…}) | | {} | -| [data_overrides](variables.tf#L124) | Optional values that override corresponding data from files. Takes precedence over file data and `data_defaults`. | object({…}) | | {} | +| [factories_config](variables.tf#L164) | Path to folder with YAML resource description data files. | object({…}) | ✓ | | +| [context](variables.tf#L17) | Context-specific interpolations. | object({…}) | | {} | +| [data_defaults](variables.tf#L41) | Optional default values used when corresponding project or folder data from files are missing. | object({…}) | | {} | +| [data_merges](variables.tf#L106) | Optional values that will be merged with corresponding data from files. Combines with `data_defaults`, file data, and `data_overrides`. | object({…}) | | {} | +| [data_overrides](variables.tf#L125) | Optional values that override corresponding data from files. Takes precedence over file data and `data_defaults`. | object({…}) | | {} | | [folders](variables-folders.tf#L17) | Folders data merged with factory data. | map(object({…})) | | {} | | [notification_channels](variables-billing.tf#L17) | Notification channels used by budget alerts. | map(object({…})) | | {} | -| [projects](variables-projects.tf#L17) | Projects data merged with factory data. | map(object({…})) | | {} | +| [projects](variables-projects.tf#L17) | Projects data merged with factory data. | map(object({…})) | | {} | ## Outputs diff --git a/modules/project-factory/automation.tf b/modules/project-factory/automation.tf index d0496f966..a0be4a17a 100644 --- a/modules/project-factory/automation.tf +++ b/modules/project-factory/automation.tf @@ -46,6 +46,7 @@ locals { lifecycle_rules = lookup(v.bucket, "lifecycle_rules", {}) retention_policy = lookup(v.bucket, "retention_policy", null) soft_delete_retention = lookup(v.bucket, "soft_delete_retention", null) + logging_config = lookup(v.bucket, "logging_config", null) prefix = try(coalesce( local.data_defaults.overrides.prefix, v.prefix, @@ -95,8 +96,9 @@ module "automation-bucket" { local.data_defaults.defaults.force_destroy, ), null) context = merge(local.ctx, { - project_ids = local.ctx_project_ids - iam_principals = local.ctx_iam_principals + project_ids = local.ctx_project_ids + iam_principals = local.ctx_iam_principals + storage_buckets = local.ctx.storage_buckets }) iam = lookup(each.value, "iam", {}) iam_bindings = lookup(each.value, "iam_bindings", {}) @@ -120,6 +122,7 @@ module "automation-bucket" { ) retention_policy = each.value.retention_policy soft_delete_retention = each.value.soft_delete_retention + logging_config = each.value.logging_config } module "automation-service-accounts" { diff --git a/modules/project-factory/projects-buckets.tf b/modules/project-factory/projects-buckets.tf index 9244bf305..bffb78fd1 100644 --- a/modules/project-factory/projects-buckets.tf +++ b/modules/project-factory/projects-buckets.tf @@ -53,6 +53,7 @@ locals { retention_policy = lookup(opts, "retention_policy", null) soft_delete_retention = lookup(opts, "soft_delete_retention", null) lifecycle_rules = lookup(opts, "lifecycle_rules", {}) + logging_config = lookup(opts, "logging_config", null) enable_object_retention = lookup(opts, "enable_object_retention", null) } ] @@ -77,9 +78,10 @@ module "buckets" { local.automation_sas_iam_emails, lookup(local.self_sas_iam_emails, each.value.project_key, {}) ) - kms_keys = merge(local.ctx.kms_keys, local.kms_keys, local.kms_autokeys) - locations = local.ctx.locations - project_ids = local.ctx_project_ids + kms_keys = merge(local.ctx.kms_keys, local.kms_keys, local.kms_autokeys) + locations = local.ctx.locations + project_ids = local.ctx_project_ids + storage_buckets = local.ctx.storage_buckets }) iam = each.value.iam iam_bindings = each.value.iam_bindings @@ -98,5 +100,6 @@ module "buckets" { versioning = each.value.versioning retention_policy = each.value.retention_policy soft_delete_retention = each.value.soft_delete_retention + logging_config = each.value.logging_config enable_object_retention = each.value.enable_object_retention } diff --git a/modules/project-factory/schemas/project.schema.json b/modules/project-factory/schemas/project.schema.json index 87866ff86..4881ece7c 100644 --- a/modules/project-factory/schemas/project.schema.json +++ b/modules/project-factory/schemas/project.schema.json @@ -1121,6 +1121,21 @@ } } }, + "logging_config": { + "type": "object", + "additionalProperties": false, + "properties": { + "log_bucket": { + "type": "string" + }, + "log_object_prefix": { + "type": "string" + } + }, + "required": [ + "log_bucket" + ] + }, "location": { "type": "string" }, diff --git a/modules/project-factory/schemas/project.schema.md b/modules/project-factory/schemas/project.schema.md index f4b2057e6..5e7f57c9b 100644 --- a/modules/project-factory/schemas/project.schema.md +++ b/modules/project-factory/schemas/project.schema.md @@ -308,6 +308,10 @@ - **num_newer_versions**: *number* - **with_state**: *string*
*enum: ['LIVE', 'ARCHIVED', 'ANY']* + - **logging_config**: *object* +
*additional properties: false* + - **log_bucket**: *string* + - **log_object_prefix**: *string* - **location**: *string* - **managed_folders**: *object*
*additional properties: false* diff --git a/modules/project-factory/variables-projects.tf b/modules/project-factory/variables-projects.tf index d63c7d2e5..88f69a388 100644 --- a/modules/project-factory/variables-projects.tf +++ b/modules/project-factory/variables-projects.tf @@ -105,6 +105,10 @@ variable "projects" { with_state = optional(string) }) })), {}) + logging_config = optional(object({ + log_bucket = string + log_object_prefix = optional(string) + }), null) retention_policy = optional(object({ retention_period = string is_locked = optional(bool) @@ -211,6 +215,10 @@ variable "projects" { with_state = optional(string) }) })), {}) + logging_config = optional(object({ + log_bucket = string + log_object_prefix = optional(string) + }), null) retention_policy = optional(object({ retention_period = string is_locked = optional(bool) diff --git a/modules/project-factory/variables.tf b/modules/project-factory/variables.tf index f9e68249f..b387ff686 100644 --- a/modules/project-factory/variables.tf +++ b/modules/project-factory/variables.tf @@ -29,6 +29,7 @@ variable "context" { project_ids = optional(map(string), {}) project_numbers = optional(map(string), {}) pubsub_topics = optional(map(string), {}) + storage_buckets = optional(map(string), {}) tag_values = optional(map(string), {}) vpc_host_projects = optional(map(string), {}) vpc_sc_perimeters = optional(map(string), {}) diff --git a/tests/modules/gcs/examples/retention-logging-context.yaml b/tests/modules/gcs/examples/retention-logging-context.yaml new file mode 100644 index 000000000..9c869849b --- /dev/null +++ b/tests/modules/gcs/examples/retention-logging-context.yaml @@ -0,0 +1,28 @@ +# Copyright 2025 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 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.bucket.google_storage_bucket.bucket[0]: + logging: + - log_bucket: log-bucket + name: test-my-bucket + project: project-id + soft_delete_policy: + - retention_duration_seconds: 7776000 + retention_policy: + - is_locked: true + retention_period: "100" + +counts: + google_storage_bucket: 1 diff --git a/tests/modules/project_factory/examples/example.yaml b/tests/modules/project_factory/examples/example.yaml index 815463bb9..4165ead62 100644 --- a/tests/modules/project_factory/examples/example.yaml +++ b/tests/modules/project_factory/examples/example.yaml @@ -30,7 +30,9 @@ values: labels: null lifecycle_rule: [] location: EU - logging: [] + logging: + - log_bucket: log-bucket + log_object_prefix: log-prefix name: test-pf-dev-tb-app0-0-tf-state project: test-pf-teams-iac-0 requester_pays: null @@ -119,6 +121,62 @@ values: timeouts: null type: email user_labels: null + module.project-factory.module.buckets["dev-ta-app0-be/app-0-bucket-a"].google_storage_bucket.bucket[0]: + autoclass: [] + cors: [] + custom_placement_config: [] + default_event_based_hold: null + effective_labels: + goog-terraform-provisioned: 'true' + enable_object_retention: null + encryption: [] + force_destroy: false + hierarchical_namespace: [] + ip_filter: [] + labels: null + lifecycle_rule: [] + location: EUROPE-WEST8 + logging: [] + name: test-pf-dev-ta-app0-be-app-0-bucket-a + project: test-pf-dev-ta-app0-be + 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.buckets["dev-ta-app0-be/app-0-bucket-b"].google_storage_bucket.bucket[0]: + autoclass: [] + cors: [] + custom_placement_config: [] + default_event_based_hold: null + effective_labels: + goog-terraform-provisioned: 'true' + enable_object_retention: null + encryption: [] + force_destroy: false + hierarchical_namespace: [] + ip_filter: [] + labels: null + lifecycle_rule: [] + location: EUROPE-WEST8 + logging: + - log_bucket: log-bucket + log_object_prefix: log-prefix + name: test-pf-dev-ta-app0-be-app-0-bucket-b + project: test-pf-dev-ta-app0-be + 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.folder-1-iam["team-a"].google_folder_iam_audit_config.default["storage.googleapis.com"]: audit_log_config: - exempted_members: @@ -872,13 +930,13 @@ counts: google_service_account: 6 google_service_account_iam_binding: 2 google_service_account_iam_member: 2 - google_storage_bucket: 1 + google_storage_bucket: 3 google_storage_bucket_iam_binding: 2 google_storage_project_service_account: 4 google_tags_tag_binding: 2 google_tags_tag_key: 1 google_tags_tag_value: 2 google_tags_tag_value_iam_binding: 1 - modules: 30 - resources: 109 + modules: 32 + resources: 111 terraform_data: 2 From fda89827a286cd4d8e4e7287bddc4ca274fefce6 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Sat, 7 Feb 2026 11:08:25 +0100 Subject: [PATCH 5/6] revert #3704 (#3713) --- modules/project-factory/README.md | 57 ++---- modules/project-factory/projects.tf | 11 +- .../project/tags_iam_principals.tfvars | 44 ---- .../modules/project/tags_iam_principals.yaml | 44 ---- tests/modules/project/tftest.yaml | 1 - .../project_factory/examples/example.yaml | 2 +- .../examples/tags_iam_principals_bug.yaml | 61 ------ .../project_factory/examples/test-1.yaml | 191 +++++++++++++++++- 8 files changed, 198 insertions(+), 213 deletions(-) delete mode 100644 tests/modules/project/tags_iam_principals.tfvars delete mode 100644 tests/modules/project/tags_iam_principals.yaml delete mode 100644 tests/modules/project_factory/examples/tags_iam_principals_bug.yaml diff --git a/modules/project-factory/README.md b/modules/project-factory/README.md index 652589322..c5be4d194 100644 --- a/modules/project-factory/README.md +++ b/modules/project-factory/README.md @@ -916,7 +916,7 @@ module "project-factory" { projects = "data/projects" } } -# tftest modules=5 resources=25 files=test-0,test-1,test-2 inventory=test-1.yaml +# tftest modules=5 resources=29 files=test-0,test-1,test-2 inventory=test-1.yaml ``` ```yaml @@ -933,6 +933,15 @@ iam_bindings_additive: condition: title: Test context expression: resource.matchTag('${organization.id}/context', 'project-factory') +tags: + allow-key-creation: + description: Allow key creation for automation service account + values: + allow: + description: Allow key creation + iam: + roles/resourcemanager.tagUser: + - $iam_principals:service_accounts/tags-iam-test/automation/rw # tftest-file id=test-0 path=data/projects/test-0.yaml ``` @@ -944,6 +953,8 @@ prefix: null services: - iam.googleapis.com - contactcenteraiplatform.googleapis.com +tag_bindings: + test: $tag_values/ # tftest-file id=test-1 path=data/projects/test-1.yaml ``` @@ -956,47 +967,3 @@ services: - storage.googleapis.com # tftest-file id=test-2 path=data/projects/test-2.yaml ``` - -This test validates that `$iam_principals:service_accounts/...` interpolation works correctly -within tags IAM definitions when referencing automation service accounts created by the same -project-factory. - -```hcl -module "project-factory" { - source = "./fabric/modules/project-factory" - data_defaults = { - billing_account = "012345-67890A-ABCDEF" - locations = { - storage = "eu" - } - } - data_overrides = { - prefix = "test-pf" - } - factories_config = { - projects = "data/projects" - } -} -# tftest modules=5 resources=9 files=tags-iam-test inventory=tags_iam_principals_bug.yaml -``` - -```yaml -parent: folders/1234567890 -services: - - resourcemanager.googleapis.com -automation: - project: test-pf-teams-iac-0 - service_accounts: - rw: - description: Read/write automation service account. -tags: - allow-key-creation: - description: Allow key creation for automation service account - values: - allow: - description: Allow key creation - iam: - roles/resourcemanager.tagUser: - - $iam_principals:service_accounts/tags-iam-test/automation/rw -# tftest-file id=tags-iam-test path=data/projects/tags-iam-test.yaml -``` diff --git a/modules/project-factory/projects.tf b/modules/project-factory/projects.tf index d3cbaa30e..3d17037d9 100644 --- a/modules/project-factory/projects.tf +++ b/modules/project-factory/projects.tf @@ -133,10 +133,7 @@ module "projects" { tag_bindings = merge( each.value.tag_bindings, var.data_merges.tag_bindings ) - tags = each.value.tags - tags_config = { - ignore_iam = true - } + tags = each.value.tags universe = each.value.universe vpc_sc = each.value.vpc_sc workload_identity_pools = each.value.workload_identity_pools @@ -189,9 +186,5 @@ module "projects-iam" { ) shared_vpc_host_config = each.value.shared_vpc_host_config shared_vpc_service_config = each.value.shared_vpc_service_config - tags = each.value.tags - tags_config = { - force_context_ids = true - } - universe = each.value.universe + universe = each.value.universe } diff --git a/tests/modules/project/tags_iam_principals.tfvars b/tests/modules/project/tags_iam_principals.tfvars deleted file mode 100644 index c8fc62937..000000000 --- a/tests/modules/project/tags_iam_principals.tfvars +++ /dev/null @@ -1,44 +0,0 @@ -# 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 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Test for $iam_principals interpolation within tags -# This test demonstrates the bug where if a service account created by the same -# project-factory is referenced via $iam_principals:service_accounts/... in tags, -# the interpolation fails because the context doesn't have the key at the time -# tags is processed (tags are processed in module "projects", but iam_principals -# for the project's own service accounts are only added in module "projects-iam"). - -# Case 1: iam_principals key IS present in context - should work -context = { - iam_principals = { - # Simulate service accounts created by project-factory with nested paths - "service_accounts/my-project/automation/rw" = "serviceAccount:my-project-rw@my-project.iam.gserviceaccount.com" - } -} - -tags = { - allow-key-creation = { - description = "Allow key creation for automation service account" - values = { - allow = { - description = "Allow key creation" - iam = { - "roles/resourcemanager.tagUser" = [ - "$iam_principals:service_accounts/my-project/automation/rw" - ] - } - } - } - } -} diff --git a/tests/modules/project/tags_iam_principals.yaml b/tests/modules/project/tags_iam_principals.yaml deleted file mode 100644 index 1201a2792..000000000 --- a/tests/modules/project/tags_iam_principals.yaml +++ /dev/null @@ -1,44 +0,0 @@ -# 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 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This test verifies that $iam_principals interpolation works correctly -# in tags when using nested paths like service_accounts/project/automation/rw -# -# Expected: The member should resolve to the service account email from context - -values: - google_project.project[0]: - name: my-project - google_tags_tag_key.default["allow-key-creation"]: - parent: projects/my-project - purpose: null - purpose_data: null - short_name: allow-key-creation - timeouts: null - google_tags_tag_value.default["allow-key-creation/allow"]: - short_name: allow - timeouts: null - google_tags_tag_value_iam_binding.default["allow-key-creation/allow:roles/resourcemanager.tagUser"]: - condition: [] - members: - # This is the expected behavior - the interpolation should resolve - # to the actual service account email from context.iam_principals - - serviceAccount:my-project-rw@my-project.iam.gserviceaccount.com - role: roles/resourcemanager.tagUser - -counts: - google_project: 1 - google_tags_tag_key: 1 - google_tags_tag_value: 1 - google_tags_tag_value_iam_binding: 1 diff --git a/tests/modules/project/tftest.yaml b/tests/modules/project/tftest.yaml index ed27f6fca..465f71bb1 100644 --- a/tests/modules/project/tftest.yaml +++ b/tests/modules/project/tftest.yaml @@ -31,5 +31,4 @@ tests: service_encryption_keys: service_agents: service_agents_universe: - tags_iam_principals: universe: diff --git a/tests/modules/project_factory/examples/example.yaml b/tests/modules/project_factory/examples/example.yaml index 4165ead62..c4d1d307f 100644 --- a/tests/modules/project_factory/examples/example.yaml +++ b/tests/modules/project_factory/examples/example.yaml @@ -496,7 +496,7 @@ values: description: My value 3 short_name: my-value-2 timeouts: null - ? module.project-factory.module.projects-iam["dev-ta-app0-be"].google_tags_tag_value_iam_binding.default["my-tag-key-1/my-value-2:roles/resourcemanager.tagUser"] + ? module.project-factory.module.projects["dev-ta-app0-be"].google_tags_tag_value_iam_binding.default["my-tag-key-1/my-value-2:roles/resourcemanager.tagUser"] : condition: [] members: - user:user@example.com diff --git a/tests/modules/project_factory/examples/tags_iam_principals_bug.yaml b/tests/modules/project_factory/examples/tags_iam_principals_bug.yaml deleted file mode 100644 index ba2280409..000000000 --- a/tests/modules/project_factory/examples/tags_iam_principals_bug.yaml +++ /dev/null @@ -1,61 +0,0 @@ -# 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 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Test for $iam_principals interpolation bug in tags -# -# BUG DESCRIPTION: -# When a project uses tags with IAM that references an automation service account -# via $iam_principals:service_accounts/PROJECT/automation/SA, the interpolation -# fails because: -# -# 1. In projects.tf, `module "projects"` (line ~78) processes `tags` but passes -# a context that does NOT include the project's own automation service accounts -# -# 2. In projects.tf, `module "projects-iam"` (line ~141) has the correct context -# with automation service accounts merged, but it does NOT process `tags` -# -# RESULT: The literal string "$iam_principals:service_accounts/..." is used -# as the member instead of being interpolated to the actual service account email. -# -# This test expects the CORRECT behavior (interpolated value). -# Currently, it will FAIL until the bug is fixed. - -values: - module.project-factory.module.projects["tags-iam-test"].google_project.project[0]: - name: test-pf-tags-iam-test - module.project-factory.module.projects["tags-iam-test"].google_tags_tag_key.default["allow-key-creation"]: - description: Allow key creation for automation service account - short_name: allow-key-creation - module.project-factory.module.projects["tags-iam-test"].google_tags_tag_value.default["allow-key-creation/allow"]: - description: Allow key creation - short_name: allow - ? module.project-factory.module.projects-iam["tags-iam-test"].google_tags_tag_value_iam_binding.default["allow-key-creation/allow:roles/resourcemanager.tagUser"] - : condition: [] - members: - # EXPECTED: This should be the interpolated service account email - # BUG: Currently this is the literal "$iam_principals:service_accounts/tags-iam-test/automation/rw" - - serviceAccount:tags-iam-test-rw@test-pf-teams-iac-0.iam.gserviceaccount.com - role: roles/resourcemanager.tagUser - module.project-factory.module.automation-service-accounts["tags-iam-test/automation/rw"].google_service_account.service_account[0]: - account_id: tags-iam-test-rw - email: tags-iam-test-rw@test-pf-teams-iac-0.iam.gserviceaccount.com - project: test-pf-teams-iac-0 - -counts: - google_project: 1 - google_service_account: 1 - google_tags_tag_key: 1 - google_tags_tag_value: 1 - google_tags_tag_value_iam_binding: 1 - modules: 5 diff --git a/tests/modules/project_factory/examples/test-1.yaml b/tests/modules/project_factory/examples/test-1.yaml index dc066b09c..ed4ae9fe1 100644 --- a/tests/modules/project_factory/examples/test-1.yaml +++ b/tests/modules/project_factory/examples/test-1.yaml @@ -13,13 +13,6 @@ # limitations under the License. values: - module.project-factory.module.projects["test-0"].google_project.project[0]: - project_id: foo-test-0 - module.project-factory.module.projects["test-1"].google_project.project[0]: - name: Test Project 1 - project_id: test-1 - module.project-factory.module.projects["test-2"].google_project.project[0]: - project_id: bar-test-2 module.project-factory.module.projects-iam["test-0"].google_project_iam_member.bindings["test_context"]: condition: - description: null @@ -28,12 +21,194 @@ values: member: user:user1@example.com project: foo-test-0 role: roles/viewer + module.project-factory.module.projects["test-0"].google_project.project[0]: + auto_create_network: false + billing_account: 012345-67890A-ABCDEF + deletion_policy: DELETE + effective_labels: + goog-terraform-provisioned: 'true' + owner: foo + folder_id: '1234567890' + labels: + owner: foo + name: foo-test-0 + org_id: null + project_id: foo-test-0 + tags: null + terraform_labels: + goog-terraform-provisioned: 'true' + owner: foo + timeouts: null + module.project-factory.module.projects["test-0"].google_project_iam_member.service_agents["compute-system"]: + condition: [] + project: foo-test-0 + role: roles/compute.serviceAgent + module.project-factory.module.projects["test-0"].google_project_iam_member.service_agents["container-engine-robot"]: + condition: [] + project: foo-test-0 + role: roles/container.serviceAgent + module.project-factory.module.projects["test-0"].google_project_iam_member.service_agents["gkenode"]: + condition: [] + project: foo-test-0 + role: roles/container.defaultNodeServiceAgent + module.project-factory.module.projects["test-0"].google_project_service.project_services["compute.googleapis.com"]: + 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 + 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"]: + 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"]: + disable_dependent_services: false + disable_on_destroy: false + project: foo-test-0 + service: iam.googleapis.com + timeouts: null + ? module.project-factory.module.projects["test-0"].google_project_service_identity.default["contactcenteraiplatform.googleapis.com"] + : project: foo-test-0 + service: contactcenteraiplatform.googleapis.com + timeouts: null + module.project-factory.module.projects["test-0"].google_project_service_identity.default["container.googleapis.com"]: + project: foo-test-0 + service: container.googleapis.com + timeouts: null + module.project-factory.module.projects["test-0"].google_tags_tag_key.default["allow-key-creation"]: + allowed_values_regex: null + description: Allow key creation for automation service account + parent: projects/foo-test-0 + purpose: null + purpose_data: null + short_name: allow-key-creation + timeouts: null + module.project-factory.module.projects["test-0"].google_tags_tag_value.default["allow-key-creation/allow"]: + description: Allow key creation + short_name: allow + timeouts: null + ? module.project-factory.module.projects["test-0"].google_tags_tag_value_iam_binding.default["allow-key-creation/allow:roles/resourcemanager.tagUser"] + : condition: [] + members: + - $iam_principals:service_accounts/tags-iam-test/automation/rw + role: roles/resourcemanager.tagUser + module.project-factory.module.projects["test-1"].google_project.project[0]: + auto_create_network: false + billing_account: 012345-67890A-ABCDEF + deletion_policy: DELETE + effective_labels: + goog-terraform-provisioned: 'true' + owner: foo + folder_id: '1234567890' + labels: + owner: foo + name: Test Project 1 + org_id: null + project_id: test-1 + tags: null + terraform_labels: + goog-terraform-provisioned: 'true' + owner: foo + timeouts: null + module.project-factory.module.projects["test-1"].google_project_iam_member.service_agents["compute-system"]: + condition: [] + project: test-1 + role: roles/compute.serviceAgent + module.project-factory.module.projects["test-1"].google_project_service.project_services["compute.googleapis.com"]: + 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 + 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"]: + disable_dependent_services: false + disable_on_destroy: false + project: test-1 + service: iam.googleapis.com + timeouts: null + ? module.project-factory.module.projects["test-1"].google_project_service_identity.default["contactcenteraiplatform.googleapis.com"] + : project: test-1 + service: contactcenteraiplatform.googleapis.com + timeouts: null + module.project-factory.module.projects["test-1"].google_tags_tag_binding.binding["test"]: + tag_value: $tag_values/ + timeouts: null + module.project-factory.module.projects["test-2"].data.google_storage_project_service_account.gcs_sa[0]: + project: bar-test-2 + user_project: null + module.project-factory.module.projects["test-2"].google_project.project[0]: + auto_create_network: false + billing_account: 012345-67890A-ABCDEF + deletion_policy: DELETE + effective_labels: + goog-terraform-provisioned: 'true' + owner: foo + folder_id: '1234567890' + labels: + owner: foo + name: bar-test-2 + org_id: null + project_id: bar-test-2 + tags: null + terraform_labels: + goog-terraform-provisioned: 'true' + owner: foo + timeouts: null + module.project-factory.module.projects["test-2"].google_project_iam_member.service_agents["compute-system"]: + condition: [] + project: bar-test-2 + role: roles/compute.serviceAgent + module.project-factory.module.projects["test-2"].google_project_service.project_services["compute.googleapis.com"]: + 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"]: + 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"]: + disable_dependent_services: false + disable_on_destroy: false + project: bar-test-2 + service: storage.googleapis.com + timeouts: null + module.project-factory.terraform_data.defaults_preconditions: + input: null + output: null + triggers_replace: null + module.project-factory.terraform_data.project-preconditions: + input: null + output: null + triggers_replace: null + counts: google_project: 3 google_project_iam_member: 6 google_project_service: 10 google_project_service_identity: 3 google_storage_project_service_account: 1 + google_tags_tag_binding: 1 + google_tags_tag_key: 1 + google_tags_tag_value: 1 + google_tags_tag_value_iam_binding: 1 modules: 5 - resources: 25 + resources: 29 terraform_data: 2 From 439fc4ad3c2a1545cd43dbee3f847c705feffac4 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Sat, 7 Feb 2026 11:56:10 +0100 Subject: [PATCH 6/6] Remove resman mentions from FAST files (#3709) * wip * nuke resman mentions --- CONTRIBUTING.md | 2 +- fast/addons/2-networking-ngfw/README.md | 2 +- fast/addons/2-networking-swp/README.md | 3 +- fast/addons/2-networking-test/README.md | 2 +- fast/extras/0-cicd-github/README.md | 34 +----- fast/extras/0-cicd-gitlab/README.md | 108 ++---------------- .../0-cicd-gitlab/terraform.tfvars.sample | 42 +------ fast/stages/1-vpcsc/IAM.md | 18 --- fast/stages/3-data-platform-dev/README.md | 8 +- .../3-data-platform-dev/variables-fast.tf | 6 +- fast/stages/3-gcve-dev/README.md | 6 +- fast/stages/3-gcve-dev/variables-fast.tf | 4 +- fast/stages/3-gke-dev/README.md | 4 +- fast/stages/3-gke-dev/variables-fast.tf | 4 +- fast/stages/3-secops-dev/.fast-stage.env | 2 +- 15 files changed, 36 insertions(+), 209 deletions(-) delete mode 100644 fast/stages/1-vpcsc/IAM.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 862072ceb..74ac583d2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -442,7 +442,7 @@ For example, resource management stages only export three map variables: `folder ```hcl variable "folder_ids" { - # tfdoc:variable:source 01-resman + # tfdoc:variable:source 0-org-setup description = "Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created." type = object({ networking = string diff --git a/fast/addons/2-networking-ngfw/README.md b/fast/addons/2-networking-ngfw/README.md index bc534c513..8de37c378 100644 --- a/fast/addons/2-networking-ngfw/README.md +++ b/fast/addons/2-networking-ngfw/README.md @@ -138,7 +138,7 @@ The preconfigured provider file uses impersonation to run with this stage's auto Variables in this stage -- like most other FAST stages -- are broadly divided into three separate sets: - variables which refer to global values for the whole organization (org id, billing account id, prefix, etc.), which are pre-populated via the `0-globals.auto.tfvars.json` file linked or copied above -- variables which refer to resources managed by previous stages, which are prepopulated here via the `0-org-setup.auto.tfvars.json`, `1-resman.auto.tfvars.json` and `2-networking.auto.tfvars.json` files linked or copied above +- variables which refer to resources managed by previous stages, which are prepopulated here via the `0-org-setup.auto.tfvars.json`, `2-networking.auto.tfvars.json` files linked or copied above - and finally variables that optionally control this stage's behaviour and customizations, and can to be set in a custom `terraform.tfvars` file The first two sets are defined in the `variables-fast.tf` file, the latter set in the `variables.tf` file. The full list of variables can be found in the [Variables](#variables) table at the bottom of this document. diff --git a/fast/addons/2-networking-swp/README.md b/fast/addons/2-networking-swp/README.md index 355f7d279..3070a4d28 100644 --- a/fast/addons/2-networking-swp/README.md +++ b/fast/addons/2-networking-swp/README.md @@ -64,7 +64,6 @@ ln -s ~/fast-config/providers/2-networking-swp-providers.tf ./ # input files from other stages ln -s ~/fast-config/tfvars/0-globals.auto.tfvars.json ./ ln -s ~/fast-config/tfvars/0-org-setup.auto.tfvars.json ./ -ln -s ~/fast-config/tfvars/1-resman.auto.tfvars.json ./ ln -s ~/fast-config/tfvars/2-networking.auto.tfvars.json ./ # conventional place for stage tfvars (manually created) @@ -83,7 +82,7 @@ The preconfigured provider file uses impersonation to run with this stage's auto Variables in this stage -- like most other FAST stages -- are broadly divided into three separate sets: - variables which refer to global values for the whole organization (org id, billing account id, prefix, etc.), which are pre-populated via the `0-globals.auto.tfvars.json` file linked or copied above -- variables which refer to resources managed by previous stages, which are prepopulated here via the `0-org-setup.auto.tfvars.json`, `1-resman.auto.tfvars.json` and `2-networking.auto.tfvars.json` files linked or copied above +- variables which refer to resources managed by previous stages, which are prepopulated here via the `0-org-setup.auto.tfvars.json`, `2-networking.auto.tfvars.json` files linked or copied above - and finally variables that optionally control this stage's behaviour and customizations, and can to be set in a custom `terraform.tfvars` file The first two sets are defined in the `variables-fast.tf` file, the latter set in the `variables.tf` file. The full list of variables can be found in the [Variables](#variables) table at the bottom of this document. diff --git a/fast/addons/2-networking-test/README.md b/fast/addons/2-networking-test/README.md index 6217b07ae..b03b21887 100644 --- a/fast/addons/2-networking-test/README.md +++ b/fast/addons/2-networking-test/README.md @@ -64,7 +64,7 @@ The preconfigured provider file uses impersonation to run with this stage's auto Variables in this stage -- like most other FAST stages -- are broadly divided into three separate sets: - variables which refer to global values for the whole organization (org id, billing account id, prefix, etc.), which are pre-populated via the `0-globals.auto.tfvars.json` file linked or copied above -- variables which refer to resources managed by previous stages, which are prepopulated here via the `0-org-setup.auto.tfvars.json`, `1-resman.auto.tfvars.json` and `2-networking.auto.tfvars.json` files linked or copied above +- variables which refer to resources managed by previous stages, which are prepopulated here via the `0-org-setup.auto.tfvars.json`, `2-networking.auto.tfvars.json` files linked or copied above - and finally variables that optionally control this stage's behaviour and customizations, and can to be set in a custom `terraform.tfvars` file The first two sets are defined in the `variables-fast.tf` file, the latter set in the `variables.tf` file. The full list of variables can be found in the [Variables](#variables) table at the bottom of this document. diff --git a/fast/extras/0-cicd-github/README.md b/fast/extras/0-cicd-github/README.md index 3627a1b7d..3e1acd477 100644 --- a/fast/extras/0-cicd-github/README.md +++ b/fast/extras/0-cicd-github/README.md @@ -87,27 +87,15 @@ This is an example that creates repositories for stages 00 and 01, and populates ```tfvars repositories = { - fast_00_bootstrap = { + fast_00_org_setup = { create_options = { - description = "FAST bootstrap." + description = "FAST org setup." features = { issues = true } } populate_from = "../../stages/0-org-setup" } - fast_01_resman = { - create_options = { - description = "FAST resource management." - features = { - issues = true - } - } - populate_from = "../../stages/1-resman" - } - fast_02_networking = { - populate_from = "../../stages/2-networking-peering" - } } # tftest skip ``` @@ -120,9 +108,9 @@ Each repository may contain some sample tfvars and data files that can be used a ```tfvars repositories = { - fast_00_bootstrap = { + fast_00_org_setup = { create_options = { - description = "FAST bootstrap." + description = "FAST org setup." features = { issues = true } @@ -130,20 +118,6 @@ repositories = { populate_from = "../../stages/0-org-setup" populate_samples = true } - fast_01_resman = { - create_options = { - description = "FAST resource management." - features = { - issues = true - } - } - populate_from = "../../stages/1-resman" - populate_samples = true - } - fast_02_networking = { - populate_from = "../../stages/2-networking-peering" - populate_samples = true - } } # tftest skip diff --git a/fast/extras/0-cicd-gitlab/README.md b/fast/extras/0-cicd-gitlab/README.md index cae871fe2..9df02e2d2 100644 --- a/fast/extras/0-cicd-gitlab/README.md +++ b/fast/extras/0-cicd-gitlab/README.md @@ -149,38 +149,16 @@ This is an example that creates repositories for stages 00 and 01 and 02: ```tfvars projects = { - fast_00_bootstrap = { + fast_0_org_setup = { create_options = { - description = "FAST bootstrap." + description = "FAST org setup." features = { issues = true } } group = "org-admins" populate_from = "../../stages/0-org-setup" - workflow_file = "bootstrap-workflow.yaml" - } - fast_01_resman = { - create_options = { - description = "FAST resource management." - features = { - issues = true - } - } - group = "org-admins" - populate_from = "../../stages/1-resman" - workflow_file = "resman-workflow.yaml" - } - fast_02_networking = { - create_options = { - description = "FAST networking management." - features = { - issues = true - } - } - group = "net-admins" - populate_from = "../../stages/2-networking-legacy-a-peering" - workflow_file = "networking-workflow.yaml" + workflow_file = "org-setup-workflow.yaml" } } # tftest skip @@ -203,9 +181,9 @@ attribute to `true`. Here's an updated example: ```tfvars projects = { - fast_00_bootstrap = { + fast_0_org_setup = { create_options = { - description = "FAST bootstrap." + description = "FAST org setup." features = { issues = true } @@ -213,31 +191,7 @@ projects = { group = "org-admins" populate_from = "../../stages/0-org-setup" populate_sample = true - workflow_file = "bootstrap-workflow.yaml" - } - fast_01_resman = { - create_options = { - description = "FAST resource management." - features = { - issues = true - } - } - group = "org-admins" - populate_from = "../../stages/1-resman" - populate_sample = true - workflow_file = "resman-workflow.yaml" - } - fast_02_networking = { - create_options = { - description = "FAST networking management." - features = { - issues = true - } - } - group = "net-admins" - populate_from = "../../stages/2-networking-legacy-a-peering" - populate_sample = true - workflow_file = "networking-workflow.yaml" + workflow_file = "org-setup-workflow.yaml" } } # tftest skip @@ -258,9 +212,9 @@ initialization. ```tfvars projects = { - fast_00_bootstrap = { + fast_0_org_setup = { create_options = { - description = "FAST bootstrap." + description = "FAST org setup." features = { issues = true } @@ -268,31 +222,7 @@ projects = { group = "org-admins" populate_from = "../../stages/0-org-setup" populate_sample = true - workflow_file = "bootstrap-workflow.yaml" - } - fast_01_resman = { - create_options = { - description = "FAST resource management." - features = { - issues = true - } - } - group = "org-admins" - populate_from = "../../stages/1-resman" - populate_sample = true - workflow_file = "resman-workflow.yaml" - } - fast_02_networking = { - create_options = { - description = "FAST networking management." - features = { - issues = true - } - } - group = "net-admins" - populate_from = "../../stages/2-networking-legacy-a-peering" - populate_sample = true - workflow_file = "networking-workflow.yaml" + workflow_file = "org-setup-workflow.yaml" } } @@ -302,16 +232,6 @@ groups = { path = "gcp-org-admins" description = "GCP Organization administrators" } - net-admins = { - name = "gcp-net-admins" - path = "gcp-net-admins" - description = "GCP Network administrators" - } - shared = { - name = "shared" - path = "shared" - description = "Shared repositories" - } } # tftest skip ``` @@ -328,16 +248,6 @@ groups = { path = "gcp-org-admins" description = "GCP Organization administrators" } - net-admins = { - name = "gcp-net-admins" - path = "gcp-net-admins" - description = "GCP Network administrators" - } - shared = { - name = "shared" - path = "shared" - description = "Shared repositories" - } } # tftest skip ``` diff --git a/fast/extras/0-cicd-gitlab/terraform.tfvars.sample b/fast/extras/0-cicd-gitlab/terraform.tfvars.sample index 6f114beb9..367aa75ea 100644 --- a/fast/extras/0-cicd-gitlab/terraform.tfvars.sample +++ b/fast/extras/0-cicd-gitlab/terraform.tfvars.sample @@ -14,41 +14,17 @@ modules_config = { group = "shared" } projects = { - fast_00_bootstrap = { + fast_0_org_setup = { create_options = { - description = "FAST bootstrap." + description = "FAST org setup." features = { issues = true } } group = "org-admins" - populate_from = "../../stages/0-bootstrap" + populate_from = "../../stages/0-org-setup" populate_samples = true - workflow_file = "bootstrap-workflow.yaml" - } - fast_01_resman = { - create_options = { - description = "FAST resource management." - features = { - issues = true - } - } - group = "org-admins" - populate_from = "../../stages/1-resman" - populate_samples = true - workflow_file = "resman-workflow.yaml" - } - fast_02_networking = { - create_options = { - description = "FAST networking management." - features = { - issues = true - } - } - group = "net-admins" - populate_from = "../../stages/2-networking-legacy-a-peering" - populate_samples = true - workflow_file = "networking-workflow.yaml" + workflow_file = "org-setup-workflow.yaml" } } groups = { @@ -57,14 +33,4 @@ groups = { path = "gcp-org-admins" description = "GCP Organization administrators" } - net-admins = { - name = "gcp-net-admins" - path = "gcp-net-admins" - description = "GCP Network administrators" - } - shared = { - name = "shared" - path = "shared" - description = "Shared repositories" - } } \ No newline at end of file diff --git a/fast/stages/1-vpcsc/IAM.md b/fast/stages/1-vpcsc/IAM.md deleted file mode 100644 index e6c31d31e..000000000 --- a/fast/stages/1-vpcsc/IAM.md +++ /dev/null @@ -1,18 +0,0 @@ -# IAM bindings reference - -Legend: + additive, conditional. - -## Project dev-sec-core-0 - -| members | roles | -|---|---| -|dev-resman-dp-0
serviceAccount|[roles/cloudkms.viewer](https://cloud.google.com/iam/docs/understanding-roles#cloudkms.viewer)
[roles/cloudkms.admin](https://cloud.google.com/iam/docs/understanding-roles#cloudkms.admin) +| -|dev-resman-pf-0
serviceAccount|[roles/cloudkms.viewer](https://cloud.google.com/iam/docs/understanding-roles#cloudkms.viewer)
[roles/cloudkms.admin](https://cloud.google.com/iam/docs/understanding-roles#cloudkms.admin) +| -|prod-resman-pf-0
serviceAccount|[roles/cloudkms.viewer](https://cloud.google.com/iam/docs/understanding-roles#cloudkms.viewer)
[roles/cloudkms.admin](https://cloud.google.com/iam/docs/understanding-roles#cloudkms.admin) +| - -## Project prod-sec-core-0 - -| members | roles | -|---|---| -|prod-resman-dp-0
serviceAccount|[roles/cloudkms.viewer](https://cloud.google.com/iam/docs/understanding-roles#cloudkms.viewer)
[roles/cloudkms.admin](https://cloud.google.com/iam/docs/understanding-roles#cloudkms.admin) +| -|prod-resman-pf-0
serviceAccount|[roles/cloudkms.viewer](https://cloud.google.com/iam/docs/understanding-roles#cloudkms.viewer)
[roles/cloudkms.admin](https://cloud.google.com/iam/docs/understanding-roles#cloudkms.admin) +| diff --git a/fast/stages/3-data-platform-dev/README.md b/fast/stages/3-data-platform-dev/README.md index c42de2fa4..fef424ed1 100644 --- a/fast/stages/3-data-platform-dev/README.md +++ b/fast/stages/3-data-platform-dev/README.md @@ -201,7 +201,6 @@ ln -s ~/fast-config/providers/3-data-platform-dev-providers.tf ./ # input files from other stages ln -s ~/fast-config/tfvars/0-globals.auto.tfvars.json ./ ln -s ~/fast-config/tfvars/0-org-setup.auto.tfvars.json ./ -ln -s ~/fast-config/tfvars/1-resman.auto.tfvars.json ./ # conventional location for this stage terraform.tfvars (manually managed) ln -s ~/fast-config/3-data-platform-dev.auto.tfvars ./ @@ -222,7 +221,6 @@ gcloud storage cp gs://xxx-prod-iac-core-outputs-0/providers/3-data-platform-dev # input files from other stages gcloud storage cp gs://xxx-prod-iac-core-outputs-0/tfvars/0-globals.auto.tfvars.json ./ gcloud storage cp gs://xxx-prod-iac-core-outputs-0/tfvars/0-org-setup.auto.tfvars.json ./ -gcloud storage cp gs://xxx-prod-iac-core-outputs-0/tfvars/1-resman.auto.tfvars.json ./ # conventional location for this stage terraform.tfvars (manually managed) gcloud storage cp gs://xxx-prod-iac-core-outputs-0/3-data-platform-dev.auto.tfvars ./ @@ -347,14 +345,14 @@ The following table lists the available substitutions. |---|---|:---:|:---:|:---:|:---:| | [automation](variables-fast.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-org-setup | | [billing_account](variables-fast.tf#L26) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-org-setup | -| [environments](variables-fast.tf#L34) | Environment names. | object({…}) | ✓ | | 1-resman | +| [environments](variables-fast.tf#L34) | Environment names. | object({…}) | ✓ | | 0-org-setup | | [prefix](variables-fast.tf#L69) | Prefix used for resources that need unique names. Use a maximum of 9 chars for organizations, and 11 chars for tenants. | string | ✓ | | 0-org-setup | | [aspect_types](variables.tf#L17) | Aspect templates. Merged with those defined via the factory. | map(object({…})) | | {} | | | [central_project_config](variables.tf#L48) | Configuration for the top-level central project. | object({…}) | | {} | | | [encryption_keys](variables.tf#L90) | Default encryption keys for services, in service => { region => key id } format. Overridable on a per-object basis. | object({…}) | | {} | | | [exposure_config](variables.tf#L101) | Data exposure configuration. | object({…}) | | {} | | | [factories_config](variables.tf#L119) | Configuration for the resource factories. | object({…}) | | {} | | -| [folder_ids](variables-fast.tf#L45) | Folder name => id mappings. | map(string) | | {} | 1-resman | +| [folder_ids](variables-fast.tf#L45) | Folder name => id mappings. | map(string) | | {} | 0-org-setup | | [host_project_ids](variables-fast.tf#L53) | Shared VPC host project name => id mappings. | map(string) | | {} | 2-networking | | [kms_keys](variables-fast.tf#L61) | KMS key ids. | map(string) | | {} | 2-security | | [location](variables.tf#L134) | Default location used when no location is specified. | string | | "europe-west1" | | @@ -363,7 +361,7 @@ The following table lists the available substitutions. | [secure_tags](variables.tf#L147) | Resource manager tags created in the central project. | map(object({…})) | | {} | | | [stage_config](variables.tf#L168) | Stage configuration used to find environment and resource ids, and to generate names. | object({…}) | | {…} | | | [subnet_self_links](variables-fast.tf#L87) | Subnet VPC name => { name => self link } mappings. | map(map(string)) | | {} | 2-networking | -| [tag_values](variables-fast.tf#L95) | FAST-managed resource manager tag values. | map(string) | | {} | 1-resman | +| [tag_values](variables-fast.tf#L95) | FAST-managed resource manager tag values. | map(string) | | {} | 0-org-setup | | [vpc_self_links](variables-fast.tf#L103) | Shared VPC name => self link mappings. | map(string) | | {} | 2-networking | ## Outputs diff --git a/fast/stages/3-data-platform-dev/variables-fast.tf b/fast/stages/3-data-platform-dev/variables-fast.tf index 0f42f325c..82b096909 100644 --- a/fast/stages/3-data-platform-dev/variables-fast.tf +++ b/fast/stages/3-data-platform-dev/variables-fast.tf @@ -32,7 +32,7 @@ variable "billing_account" { } variable "environments" { - # tfdoc:variable:source 1-resman + # tfdoc:variable:source 0-org-setup description = "Environment names." type = object({ dev = object({ @@ -43,7 +43,7 @@ variable "environments" { } variable "folder_ids" { - # tfdoc:variable:source 1-resman + # tfdoc:variable:source 0-org-setup description = "Folder name => id mappings." type = map(string) nullable = false @@ -93,7 +93,7 @@ variable "subnet_self_links" { } variable "tag_values" { - # tfdoc:variable:source 1-resman + # tfdoc:variable:source 0-org-setup description = "FAST-managed resource manager tag values." type = map(string) nullable = false diff --git a/fast/stages/3-gcve-dev/README.md b/fast/stages/3-gcve-dev/README.md index 7d382003e..8f2aebf57 100644 --- a/fast/stages/3-gcve-dev/README.md +++ b/fast/stages/3-gcve-dev/README.md @@ -110,7 +110,6 @@ ln -s ~/fast-config/fast-test-00/providers/3-gcve-dev-providers.tf ./ # input files from other stages ln -s ~/fast-config/fast-test-00/tfvars/0-globals.auto.tfvars.json ./ ln -s ~/fast-config/fast-test-00/tfvars/0-org-setup.auto.tfvars.json ./ -ln -s ~/fast-config/fast-test-00/tfvars/1-resman.auto.tfvars.json ./ ln -s ~/fast-config/fast-test-00/tfvars/2-networking.auto.tfvars.json ./ # conventional place for stage tfvars (manually created) @@ -128,7 +127,6 @@ gcloud storage cp gs://xxx-prod-iac-core-outputs-0/providers/3-gcve-dev-provider # input files from other stages gcloud storage cp gs://xxx-prod-iac-core-outputs-0/tfvars/0-globals.auto.tfvars.json ./ gcloud storage cp gs://xxx-prod-iac-core-outputs-0/tfvars/0-org-setup.auto.tfvars.json ./ -gcloud storage cp gs://xxx-prod-iac-core-outputs-0/tfvars/1-resman.auto.tfvars.json ./ gcloud storage cp gs://xxx-prod-iac-core-outputs-0/tfvars/2-networking.auto.tfvars.json ./ # conventional place for stage tfvars (manually created) @@ -176,9 +174,9 @@ terraform apply |---|---|:---:|:---:|:---:|:---:| | [automation](variables-fast.tf#L19) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-org-setup | | [billing_account](variables-fast.tf#L28) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-org-setup | -| [environments](variables-fast.tf#L36) | Long environment names. | object({…}) | ✓ | | 1-resman | +| [environments](variables-fast.tf#L36) | Long environment names. | object({…}) | ✓ | | 0-org-setup | | [prefix](variables-fast.tf#L53) | Prefix used for resources that need unique names. Use a maximum of 9 chars for organizations, and 11 chars for tenants. | string | ✓ | | 0-org-setup | -| [folder_ids](variables-fast.tf#L46) | Folders used by FAST stages in folders/nnnnnnnnnnn format. | map(string) | | {} | 1-resman | +| [folder_ids](variables-fast.tf#L46) | Folders used by FAST stages in folders/nnnnnnnnnnn format. | map(string) | | {} | 0-org-setup | | [iam](variables.tf#L17) | Project-level authoritative IAM bindings for users and service accounts in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | | [iam_by_principals](variables.tf#L24) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable. | map(list(string)) | | {} | | | [network_peerings](variables.tf#L31) | The network peerings between users' VPCs and the VMware Engine networks. Key is used for the peering name suffix. Network is expanded for FAST defined networks. | map(object({…})) | | {…} | | diff --git a/fast/stages/3-gcve-dev/variables-fast.tf b/fast/stages/3-gcve-dev/variables-fast.tf index e8313def6..88d4366e6 100644 --- a/fast/stages/3-gcve-dev/variables-fast.tf +++ b/fast/stages/3-gcve-dev/variables-fast.tf @@ -34,7 +34,7 @@ variable "billing_account" { } variable "environments" { - # tfdoc:variable:source 1-resman + # tfdoc:variable:source 0-org-setup description = "Long environment names." type = object({ dev = object({ @@ -44,7 +44,7 @@ variable "environments" { } variable "folder_ids" { - # tfdoc:variable:source 1-resman + # tfdoc:variable:source 0-org-setup description = "Folders used by FAST stages in folders/nnnnnnnnnnn format." type = map(string) default = {} diff --git a/fast/stages/3-gke-dev/README.md b/fast/stages/3-gke-dev/README.md index 1a523822b..48a8331f0 100644 --- a/fast/stages/3-gke-dev/README.md +++ b/fast/stages/3-gke-dev/README.md @@ -183,13 +183,13 @@ Clusters can then be configured for fleet registration and one of the config man |---|---|:---:|:---:|:---:|:---:| | [automation](variables-fast.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-org-setup | | [billing_account](variables-fast.tf#L26) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-org-setup | -| [environments](variables-fast.tf#L34) | Long environment names. | object({…}) | ✓ | | 1-resman | +| [environments](variables-fast.tf#L34) | Long environment names. | object({…}) | ✓ | | 0-org-setup | | [prefix](variables-fast.tf#L60) | Prefix used for resources that need unique names. Use a maximum of 9 chars for organizations, and 11 chars for tenants. | string | ✓ | | 0-org-setup | | [clusters](variables.tf#L17) | Clusters configuration. Refer to the gke-cluster module for type details. | map(object({…})) | | {} | | | [deletion_protection](variables.tf#L102) | Prevent Terraform from destroying data resources. | bool | | false | | | [fleet_config](variables-fleet.tf#L19) | Fleet configuration. | object({…}) | | null | | | [fleet_configmanagement_templates](variables-fleet.tf#L35) | Sets of fleet configurations that can be applied to member clusters, in config name => {options} format. | map(object({…})) | | {} | | -| [folder_ids](variables-fast.tf#L44) | Folder name => id mappings. | map(string) | | {} | 1-resman | +| [folder_ids](variables-fast.tf#L44) | Folder name => id mappings. | map(string) | | {} | 0-org-setup | | [host_project_ids](variables-fast.tf#L52) | Shared VPC host project name => id mappings. | map(string) | | {} | 2-networking | | [iam](variables.tf#L109) | Project-level authoritative IAM bindings for users and service accounts in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | | [iam_by_principals](variables.tf#L116) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable. | map(list(string)) | | {} | | diff --git a/fast/stages/3-gke-dev/variables-fast.tf b/fast/stages/3-gke-dev/variables-fast.tf index 4ca85aeae..39f4f3396 100644 --- a/fast/stages/3-gke-dev/variables-fast.tf +++ b/fast/stages/3-gke-dev/variables-fast.tf @@ -32,7 +32,7 @@ variable "billing_account" { } variable "environments" { - # tfdoc:variable:source 1-resman + # tfdoc:variable:source 0-org-setup description = "Long environment names." type = object({ dev = object({ @@ -42,7 +42,7 @@ variable "environments" { } variable "folder_ids" { - # tfdoc:variable:source 1-resman + # tfdoc:variable:source 0-org-setup description = "Folder name => id mappings." type = map(string) nullable = false diff --git a/fast/stages/3-secops-dev/.fast-stage.env b/fast/stages/3-secops-dev/.fast-stage.env index 1f65dce13..eabb4d26f 100644 --- a/fast/stages/3-secops-dev/.fast-stage.env +++ b/fast/stages/3-secops-dev/.fast-stage.env @@ -1,4 +1,4 @@ FAST_STAGE_DESCRIPTION="SecOps (dev)" FAST_STAGE_LEVEL=3 FAST_STAGE_NAME=secops-dev -FAST_STAGE_DEPS="0-globals 0-org-setup 1-resman 2-secops" +FAST_STAGE_DEPS="0-globals 0-org-setup 2-secops"