diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5ab336693..3c7bac81f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,15 +7,22 @@ All notable changes to this project will be documented in this file.
### BREAKING CHANGES
+- `modules/project-factory`: the `factories_config` attribute has been removed from project defaults and overrides. [[#3440](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3440)]
- `modules/gke-hub`: Unified cluster configuration. The module now uses a single `clusters` variable to configure both cluster registration and feature enablement. [[#3332](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3332)]
- `all modules`: Minimum supported Terraform version bumped 1.12.2 [[#3332](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3332)]
- `all modules`: Minimum supported OpenTofu version bumped 1.10.0 [[#3332](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3332)]
- `modules/project-factory`: the format for automation service account names has changed. [[#3345](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3345)]
+- [[#3428](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3428)] gitignore update ([juliocc](https://github.com/juliocc))
- [[#3361](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3361)] Use pre-commit managed Python environment for pre-commit checks ([wiktorn](https://github.com/wiktorn))
### FAST
+- [[#3440](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3440)] Support resource-level factories config in project factory module and FAST stages ([ludoo](https://github.com/ludoo))
+- [[#3439](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3439)] compatiblity fix: Github CICD templates Terraform version bump to 1.12.2 ([ysolt](https://github.com/ysolt))
+- [[#3432](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3432)] Revert "Added audience to workflow local in 0-org-setup" ([ludoo](https://github.com/ludoo))
+- [[#3418](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3418)] Added audience to workflow local in 0-org-setup ([kovagoadam](https://github.com/kovagoadam))
+- [[#3427](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3427)] Add missing project number variable to outputs in 0-org-setup stage ([norbert-loderer](https://github.com/norbert-loderer))
- [[#3332](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3332)] Update gke-hub module to use new Policy Controller API ([juliocc](https://github.com/juliocc))
- [[#3400](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3400)] Remove unavailable service from VPC-SC stage services list ([ludoo](https://github.com/ludoo))
- [[#3385](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3385)] fix: expose missing audiences variable for gitlab workflow file ([vvision](https://github.com/vvision))
@@ -34,6 +41,10 @@ All notable changes to this project will be documented in this file.
### MODULES
+- [[#3440](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3440)] Support resource-level factories config in project factory module and FAST stages ([ludoo](https://github.com/ludoo))
+- [[#3436](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3436)] Add service agent outputs to folder and organization ([juliocc](https://github.com/juliocc))
+- [[#3423](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3423)] bigquery-connection module ([lcaggio](https://github.com/lcaggio))
+- [[#3425](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3425)] bigquery-dataset: fix issues ([rosmo](https://github.com/rosmo))
- [[#3424](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3424)] Support CIDR range sets in firewall modules context ([ludoo](https://github.com/ludoo))
- [[#3421](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3421)] Adds network_id to net-vpc outputs ([sruffilli](https://github.com/sruffilli))
- [[#3420](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3420)] Add support for context to net-vpn-ha module ([ludoo](https://github.com/ludoo))
@@ -65,6 +76,7 @@ All notable changes to this project will be documented in this file.
### TOOLS
+- [[#3436](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3436)] Add service agent outputs to folder and organization ([juliocc](https://github.com/juliocc))
- [[#3407](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3407)] remove tf version from matrix, to keep workflow names stable across upgrades ([wiktorn](https://github.com/wiktorn))
- [[#3332](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3332)] Update gke-hub module to use new Policy Controller API ([juliocc](https://github.com/juliocc))
- [[#3404](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3404)] Add tests for service agents iam_emails ([wiktorn](https://github.com/wiktorn))
diff --git a/fast/stages/0-org-setup/assets/workflow-github.yaml b/fast/stages/0-org-setup/assets/workflow-github.yaml
index 1009cb149..465fd29b8 100644
--- a/fast/stages/0-org-setup/assets/workflow-github.yaml
+++ b/fast/stages/0-org-setup/assets/workflow-github.yaml
@@ -30,7 +30,7 @@ env:
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
TF_PROVIDERS_FILE: ${tf_providers_files.apply}
TF_PROVIDERS_FILE_PLAN: ${tf_providers_files.plan}
- TF_VERSION: 1.11.4
+ TF_VERSION: 1.12.2
jobs:
fast-pr:
diff --git a/fast/stages/0-org-setup/output-files.tf b/fast/stages/0-org-setup/output-files.tf
index c3d9f1ba5..8d138162c 100644
--- a/fast/stages/0-org-setup/output-files.tf
+++ b/fast/stages/0-org-setup/output-files.tf
@@ -68,6 +68,7 @@ locals {
iam_principals = local.iam_principals
logging = {
writer_identities = module.organization-iam.0.sink_writer_identities
+ project_number = module.factory.project_numbers["log-0"]
}
project_ids = merge(
local.ctx.project_ids,
diff --git a/fast/stages/0-org-setup/schemas/folder.schema.json b/fast/stages/0-org-setup/schemas/folder.schema.json
deleted file mode 100644
index 0ca0be056..000000000
--- a/fast/stages/0-org-setup/schemas/folder.schema.json
+++ /dev/null
@@ -1,423 +0,0 @@
-{
- "$schema": "http://json-schema.org/draft-07/schema#",
- "title": "Folder",
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "automation": {
- "type": "object",
- "additionalProperties": false,
- "required": [
- "project"
- ],
- "properties": {
- "prefix": {
- "type": "string"
- },
- "project": {
- "type": "string"
- },
- "bucket": {
- "$ref": "#/$defs/bucket"
- },
- "service_accounts": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z0-9-]+$": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "description": {
- "type": "string"
- },
- "iam": {
- "$ref": "#/$defs/iam"
- },
- "iam_bindings": {
- "$ref": "#/$defs/iam_bindings"
- },
- "iam_bindings_additive": {
- "$ref": "#/$defs/iam_bindings_additive"
- },
- "iam_billing_roles": {
- "$ref": "#/$defs/iam_billing_roles"
- },
- "iam_folder_roles": {
- "$ref": "#/$defs/iam_folder_roles"
- },
- "iam_organization_roles": {
- "$ref": "#/$defs/iam_organization_roles"
- },
- "iam_project_roles": {
- "$ref": "#/$defs/iam_project_roles"
- },
- "iam_sa_roles": {
- "$ref": "#/$defs/iam_sa_roles"
- },
- "iam_storage_roles": {
- "$ref": "#/$defs/iam_storage_roles"
- }
- }
- }
- }
- }
- }
- },
- "iam": {
- "$ref": "#/$defs/iam"
- },
- "iam_bindings": {
- "$ref": "#/$defs/iam_bindings"
- },
- "iam_bindings_additive": {
- "$ref": "#/$defs/iam_bindings_additive"
- },
- "iam_by_principals": {
- "$ref": "#/$defs/iam_by_principals"
- },
- "name": {
- "type": "string"
- },
- "org_policies": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z]+\\.": {
- "type": "object",
- "properties": {
- "inherit_from_parent": {
- "type": "boolean"
- },
- "reset": {
- "type": "boolean"
- },
- "rules": {
- "type": "array",
- "items": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "allow": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "all": {
- "type": "boolean"
- },
- "values": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "deny": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "all": {
- "type": "boolean"
- },
- "values": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "enforce": {
- "type": "boolean"
- },
- "condition": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "description": {
- "type": "string"
- },
- "expression": {
- "type": "string"
- },
- "location": {
- "type": "string"
- },
- "title": {
- "type": "string"
- }
- }
- }
- }
- }
- }
- }
- }
- }
- },
- "parent": {
- "type": "string",
- "pattern": "^(?:folders/[0-9]+|organizations/[0-9]+|\\$folder_ids:[a-z0-9_-]+)$"
- },
- "tag_bindings": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z0-9_-]+$": {
- "type": "string"
- }
- }
- }
- },
- "$defs": {
- "bucket": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "name": {
- "type": "string"
- },
- "description": {
- "type": "string"
- },
- "iam": {
- "$ref": "#/$defs/iam"
- },
- "iam_bindings": {
- "$ref": "#/$defs/iam_bindings"
- },
- "iam_bindings_additive": {
- "$ref": "#/$defs/iam_bindings_additive"
- },
- "force_destroy": {
- "type": "boolean"
- },
- "labels": {
- "type": "object",
- "additionalProperties": {
- "type": "string"
- }
- },
- "location": {
- "type": "string"
- },
- "managed_folders": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-zA-Z0-9][a-zA-Z0-9_/-]+$": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "force_destroy": {
- "type": "boolean"
- },
- "iam": {
- "$ref": "#/$defs/iam"
- },
- "iam_bindings": {
- "$ref": "#/$defs/iam_bindings"
- },
- "iam_bindings_additive": {
- "$ref": "#/$defs/iam_bindings_additive"
- }
- }
- }
- }
- },
- "prefix": {
- "type": "string"
- },
- "storage_class": {
- "type": "string"
- },
- "uniform_bucket_level_access": {
- "type": "boolean"
- },
- "versioning": {
- "type": "boolean"
- }
- }
- },
- "iam": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^(?:roles/|\\$custom_roles:)": {
- "type": "array",
- "items": {
- "type": "string",
- "pattern": "^(?:domain:|group:|serviceAccount:|user:|principal:|principalSet:|\\$iam_principals:)"
- }
- }
- }
- },
- "iam_bindings": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z0-9_-]+$": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "members": {
- "type": "array",
- "items": {
- "type": "string",
- "pattern": "^(?:domain:|group:|serviceAccount:|user:|principal:|principalSet:|\\$iam_principals:)"
- }
- },
- "role": {
- "type": "string",
- "pattern": "^(?:roles/|\\$custom_roles:)"
- },
- "condition": {
- "type": "object",
- "additionalProperties": false,
- "required": [
- "expression",
- "title"
- ],
- "properties": {
- "expression": {
- "type": "string"
- },
- "title": {
- "type": "string"
- },
- "description": {
- "type": "string"
- }
- }
- }
- }
- }
- }
- },
- "iam_bindings_additive": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z0-9_-]+$": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "member": {
- "type": "string",
- "pattern": "^(?:domain:|group:|serviceAccount:|user:|principal:|principalSet:|\\$iam_principals:)"
- },
- "role": {
- "type": "string",
- "pattern": "^(?:roles/|\\$custom_roles:)"
- },
- "condition": {
- "type": "object",
- "additionalProperties": false,
- "required": [
- "expression",
- "title"
- ],
- "properties": {
- "expression": {
- "type": "string"
- },
- "title": {
- "type": "string"
- },
- "description": {
- "type": "string"
- }
- }
- }
- }
- }
- }
- },
- "iam_by_principals": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^(?:domain:|group:|serviceAccount:|user:|principal:|principalSet:|\\$iam_principals:)": {
- "type": "array",
- "items": {
- "type": "string",
- "pattern": "^(?:roles/|\\$custom_roles:)"
- }
- }
- }
- },
- "iam_billing_roles": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z0-9-]+$": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "iam_folder_roles": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z0-9-]+$": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "iam_organization_roles": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z0-9-]+$": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "iam_project_roles": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z0-9-]+$": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "iam_sa_roles": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z0-9-]+$": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "iam_storage_roles": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z0-9-]+$": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- }
- }
-}
diff --git a/fast/stages/0-org-setup/schemas/folder.schema.json b/fast/stages/0-org-setup/schemas/folder.schema.json
new file mode 120000
index 000000000..d58a2759b
--- /dev/null
+++ b/fast/stages/0-org-setup/schemas/folder.schema.json
@@ -0,0 +1 @@
+../../../../modules/project-factory/schemas/folder.schema.json
\ No newline at end of file
diff --git a/fast/stages/0-org-setup/schemas/folder.schema.md b/fast/stages/0-org-setup/schemas/folder.schema.md
deleted file mode 100644
index cb340e2d2..000000000
--- a/fast/stages/0-org-setup/schemas/folder.schema.md
+++ /dev/null
@@ -1,149 +0,0 @@
-# Folder
-
-
-
-## Properties
-
-*additional properties: false*
-
-- **automation**: *object*
-
*additional properties: false*
- - **prefix**: *string*
- - ⁺**project**: *string*
- - **bucket**: *reference([bucket](#refs-bucket))*
- - **service_accounts**: *object*
-
*additional properties: false*
- - **`^[a-z0-9-]+$`**: *object*
-
*additional properties: false*
- - **description**: *string*
- - **iam**: *reference([iam](#refs-iam))*
- - **iam_bindings**: *reference([iam_bindings](#refs-iam_bindings))*
- - **iam_bindings_additive**: *reference([iam_bindings_additive](#refs-iam_bindings_additive))*
- - **iam_billing_roles**: *reference([iam_billing_roles](#refs-iam_billing_roles))*
- - **iam_folder_roles**: *reference([iam_folder_roles](#refs-iam_folder_roles))*
- - **iam_organization_roles**: *reference([iam_organization_roles](#refs-iam_organization_roles))*
- - **iam_project_roles**: *reference([iam_project_roles](#refs-iam_project_roles))*
- - **iam_sa_roles**: *reference([iam_sa_roles](#refs-iam_sa_roles))*
- - **iam_storage_roles**: *reference([iam_storage_roles](#refs-iam_storage_roles))*
-- **iam**: *reference([iam](#refs-iam))*
-- **iam_bindings**: *reference([iam_bindings](#refs-iam_bindings))*
-- **iam_bindings_additive**: *reference([iam_bindings_additive](#refs-iam_bindings_additive))*
-- **iam_by_principals**: *reference([iam_by_principals](#refs-iam_by_principals))*
-- **name**: *string*
-- **org_policies**: *object*
-
*additional properties: false*
- - **`^[a-z]+\.`**: *object*
- - **inherit_from_parent**: *boolean*
- - **reset**: *boolean*
- - **rules**: *array*
- - items: *object*
-
*additional properties: false*
- - **allow**: *object*
-
*additional properties: false*
- - **all**: *boolean*
- - **values**: *array*
- - items: *string*
- - **deny**: *object*
-
*additional properties: false*
- - **all**: *boolean*
- - **values**: *array*
- - items: *string*
- - **enforce**: *boolean*
- - **condition**: *object*
-
*additional properties: false*
- - **description**: *string*
- - **expression**: *string*
- - **location**: *string*
- - **title**: *string*
-- **parent**: *string*
-
*pattern: ^(?:folders/[0-9]+|organizations/[0-9]+|\$folder_ids:[a-z0-9_-]+)$*
-- **tag_bindings**: *object*
-
*additional properties: false*
- - **`^[a-z0-9_-]+$`**: *string*
-
-## Definitions
-
-- **bucket**: *object*
-
*additional properties: false*
- - **name**: *string*
- - **description**: *string*
- - **iam**: *reference([iam](#refs-iam))*
- - **iam_bindings**: *reference([iam_bindings](#refs-iam_bindings))*
- - **iam_bindings_additive**: *reference([iam_bindings_additive](#refs-iam_bindings_additive))*
- - **force_destroy**: *boolean*
- - **labels**: *object*
- *additional properties: String*
- - **location**: *string*
- - **managed_folders**: *object*
-
*additional properties: false*
- - **`^[a-zA-Z0-9][a-zA-Z0-9_/-]+$`**: *object*
-
*additional properties: false*
- - **force_destroy**: *boolean*
- - **iam**: *reference([iam](#refs-iam))*
- - **iam_bindings**: *reference([iam_bindings](#refs-iam_bindings))*
- - **iam_bindings_additive**: *reference([iam_bindings_additive](#refs-iam_bindings_additive))*
- - **prefix**: *string*
- - **storage_class**: *string*
- - **uniform_bucket_level_access**: *boolean*
- - **versioning**: *boolean*
-- **iam**: *object*
-
*additional properties: false*
- - **`^(?:roles/|\$custom_roles:)`**: *array*
- - items: *string*
-
*pattern: ^(?:domain:|group:|serviceAccount:|user:|principal:|principalSet:|\$iam_principals:)*
-- **iam_bindings**: *object*
-
*additional properties: false*
- - **`^[a-z0-9_-]+$`**: *object*
-
*additional properties: false*
- - **members**: *array*
- - items: *string*
-
*pattern: ^(?:domain:|group:|serviceAccount:|user:|principal:|principalSet:|\$iam_principals:)*
- - **role**: *string*
-
*pattern: ^(?:roles/|\$custom_roles:)*
- - **condition**: *object*
-
*additional properties: false*
- - ⁺**expression**: *string*
- - ⁺**title**: *string*
- - **description**: *string*
-- **iam_bindings_additive**: *object*
-
*additional properties: false*
- - **`^[a-z0-9_-]+$`**: *object*
-
*additional properties: false*
- - **member**: *string*
-
*pattern: ^(?:domain:|group:|serviceAccount:|user:|principal:|principalSet:|\$iam_principals:)*
- - **role**: *string*
-
*pattern: ^(?:roles/|\$custom_roles:)*
- - **condition**: *object*
-
*additional properties: false*
- - ⁺**expression**: *string*
- - ⁺**title**: *string*
- - **description**: *string*
-- **iam_by_principals**: *object*
-
*additional properties: false*
- - **`^(?:domain:|group:|serviceAccount:|user:|principal:|principalSet:|\$iam_principals:)`**: *array*
- - items: *string*
-
*pattern: ^(?:roles/|\$custom_roles:)*
-- **iam_billing_roles**: *object*
-
*additional properties: false*
- - **`^[a-z0-9-]+$`**: *array*
- - items: *string*
-- **iam_folder_roles**: *object*
-
*additional properties: false*
- - **`^[a-z0-9-]+$`**: *array*
- - items: *string*
-- **iam_organization_roles**: *object*
-
*additional properties: false*
- - **`^[a-z0-9-]+$`**: *array*
- - items: *string*
-- **iam_project_roles**: *object*
-
*additional properties: false*
- - **`^[a-z0-9-]+$`**: *array*
- - items: *string*
-- **iam_sa_roles**: *object*
-
*additional properties: false*
- - **`^[a-z0-9-]+$`**: *array*
- - items: *string*
-- **iam_storage_roles**: *object*
-
*additional properties: false*
- - **`^[a-z0-9-]+$`**: *array*
- - items: *string*
diff --git a/fast/stages/0-org-setup/schemas/folder.schema.md b/fast/stages/0-org-setup/schemas/folder.schema.md
new file mode 120000
index 000000000..acf5b4bda
--- /dev/null
+++ b/fast/stages/0-org-setup/schemas/folder.schema.md
@@ -0,0 +1 @@
+../../../../modules/project-factory/schemas/folder.schema.md
\ No newline at end of file
diff --git a/fast/stages/0-org-setup/schemas/project.schema.json b/fast/stages/0-org-setup/schemas/project.schema.json
deleted file mode 100644
index bf454c8f8..000000000
--- a/fast/stages/0-org-setup/schemas/project.schema.json
+++ /dev/null
@@ -1,861 +0,0 @@
-{
- "$schema": "http://json-schema.org/draft-07/schema#",
- "title": "Project",
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "automation": {
- "type": "object",
- "additionalProperties": false,
- "required": [
- "project"
- ],
- "properties": {
- "prefix": {
- "type": "string"
- },
- "project": {
- "type": "string"
- },
- "bucket": {
- "$ref": "#/$defs/bucket"
- },
- "service_accounts": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z0-9-]+$": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "description": {
- "type": "string"
- },
- "iam": {
- "$ref": "#/$defs/iam"
- },
- "iam_bindings": {
- "$ref": "#/$defs/iam_bindings"
- },
- "iam_bindings_additive": {
- "$ref": "#/$defs/iam_bindings_additive"
- },
- "iam_billing_roles": {
- "$ref": "#/$defs/iam_billing_roles"
- },
- "iam_folder_roles": {
- "$ref": "#/$defs/iam_folder_roles"
- },
- "iam_organization_roles": {
- "$ref": "#/$defs/iam_organization_roles"
- },
- "iam_project_roles": {
- "$ref": "#/$defs/iam_project_roles"
- },
- "iam_sa_roles": {
- "$ref": "#/$defs/iam_sa_roles"
- },
- "iam_storage_roles": {
- "$ref": "#/$defs/iam_storage_roles"
- }
- }
- }
- }
- }
- }
- },
- "billing_account": {
- "type": "string"
- },
- "billing_budgets": {
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- "buckets": {
- "$ref": "#/$defs/buckets"
- },
- "contacts": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z0-9_-]+$": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "deletion_policy": {
- "type": "string",
- "enum": [
- "PREVENT",
- "DELETE",
- "ABANDON"
- ]
- },
- "iam": {
- "$ref": "#/$defs/iam"
- },
- "iam_bindings": {
- "$ref": "#/$defs/iam_bindings"
- },
- "iam_bindings_additive": {
- "$ref": "#/$defs/iam_bindings_additive"
- },
- "iam_by_principals": {
- "$ref": "#/$defs/iam_by_principals"
- },
- "labels": {
- "type": "object"
- },
- "log_buckets": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z0-9-]+$": {
- "$ref": "#/$defs/log_bucket"
- }
- }
- },
- "metric_scopes": {
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- "name": {
- "type": "string"
- },
- "org_policies": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z]+\\.": {
- "type": "object",
- "properties": {
- "inherit_from_parent": {
- "type": "boolean"
- },
- "reset": {
- "type": "boolean"
- },
- "rules": {
- "type": "array",
- "items": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "allow": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "all": {
- "type": "boolean"
- },
- "values": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "deny": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "all": {
- "type": "boolean"
- },
- "values": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "enforce": {
- "type": "boolean"
- },
- "condition": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "description": {
- "type": "string"
- },
- "expression": {
- "type": "string"
- },
- "location": {
- "type": "string"
- },
- "title": {
- "type": "string"
- }
- }
- }
- }
- }
- }
- }
- }
- }
- },
- "quotas": {
- "title": "Quotas",
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-zA-Z0-9_-]+$": {
- "type": "object",
- "additionalProperties": false,
- "required": [
- "service",
- "quota_id",
- "preferred_value"
- ],
- "properties": {
- "service": {
- "type": "string"
- },
- "quota_id": {
- "type": "string"
- },
- "preferred_value": {
- "type": "number"
- },
- "dimensions": {
- "type": "object",
- "additionalProperties": {
- "type": "string"
- }
- },
- "justification": {
- "type": "string"
- },
- "contact_email": {
- "type": "string"
- },
- "annotations": {
- "type": "object",
- "additionalProperties": {
- "type": "string"
- }
- },
- "ignore_safety_checks": {
- "type": "string",
- "enum": [
- "QUOTA_DECREASE_BELOW_USAGE",
- "QUOTA_DECREASE_PERCENTAGE_TOO_HIGH",
- "QUOTA_SAFETY_CHECK_UNSPECIFIED"
- ]
- }
- }
- }
- }
- },
- "parent": {
- "type": "string"
- },
- "prefix": {
- "type": "string"
- },
- "project_reuse": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "use_data_source": {
- "type": "boolean"
- },
- "attributes": {
- "type": "object",
- "required": [
- "name",
- "number"
- ],
- "properties": {
- "name": {
- "type": "string"
- },
- "number": {
- "type": "number"
- },
- "services_enabled": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- }
- }
- },
- "project_template": {
- "type": "string"
- },
- "service_accounts": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z0-9-]+$": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "display_name": {
- "type": "string"
- },
- "iam": {
- "$ref": "#/$defs/iam"
- },
- "iam_self_roles": {
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- "iam_project_roles": {
- "$ref": "#/$defs/iam_project_roles"
- },
- "iam_sa_roles": {
- "$ref": "#/$defs/iam_sa_roles"
- }
- }
- }
- }
- },
- "service_encryption_key_ids": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z-]+\\.googleapis\\.com$": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "services": {
- "type": "array",
- "items": {
- "type": "string",
- "pattern": "^[a-z-]+\\.googleapis\\.com$"
- }
- },
- "shared_vpc_host_config": {
- "type": "object",
- "additionalProperties": false,
- "required": [
- "enabled"
- ],
- "properties": {
- "enabled": {
- "type": "boolean"
- },
- "service_projects": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "shared_vpc_service_config": {
- "type": "object",
- "additionalProperties": false,
- "required": [
- "host_project"
- ],
- "properties": {
- "host_project": {
- "type": "string"
- },
- "iam_bindings_additive": {
- "$ref": "#/$defs/iam_bindings_additive"
- },
- "network_users": {
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- "service_agent_iam": {
- "type": "object",
- "additionalItems": false,
- "patternProperties": {
- "^[a-z0-9_-]+$": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "service_agent_subnet_iam": {
- "type": "object",
- "additionalItems": false,
- "patternProperties": {
- "^[a-z0-9_-]+$": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "service_iam_grants": {
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- "network_subnet_users": {
- "type": "object",
- "additionalItems": false,
- "patternProperties": {
- "^[a-z0-9_-]+$": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- }
- }
- },
- "tag_bindings": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z0-9_-]+$": {
- "type": "string"
- }
- }
- },
- "tags": {
- "type": "object",
- "additionalProperties": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "description": {
- "type": "string"
- },
- "iam": {
- "$ref": "#/$defs/iam"
- },
- "iam_bindings": {
- "$ref": "#/$defs/iam_bindings"
- },
- "iam_bindings_additive": {
- "$ref": "#/$defs/iam_bindings_additive"
- },
- "id": {
- "type": "string"
- },
- "values": {
- "type": "object",
- "additionalProperties": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "description": {
- "type": "string"
- },
- "iam": {
- "$ref": "#/$defs/iam"
- },
- "iam_bindings": {
- "$ref": "#/$defs/iam_bindings"
- },
- "iam_bindings_additive": {
- "$ref": "#/$defs/iam_bindings_additive"
- },
- "id": {
- "type": "string"
- }
- }
- }
- }
- }
- }
- },
- "universe": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "prefix": {
- "type": "string"
- },
- "forced_jit_service_identities": {
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- "unavailable_services": {
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- "unavailable_service_identities": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "vpc_sc": {
- "type": "object",
- "additionalItems": false,
- "required": [
- "perimeter_name"
- ],
- "properties": {
- "perimeter_name": {
- "type": "string"
- },
- "is_dry_run": {
- "type": "boolean"
- }
- }
- },
- "datasets": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z0-9_]+$": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "friendly_name": {
- "type": "string"
- },
- "location": {
- "type": "string"
- }
- }
- }
- }
- }
- },
- "$defs": {
- "bucket": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "name": {
- "type": "string"
- },
- "create": {
- "type": "boolean",
- "default": true
- },
- "description": {
- "type": "string"
- },
- "iam": {
- "$ref": "#/$defs/iam"
- },
- "iam_bindings": {
- "$ref": "#/$defs/iam_bindings"
- },
- "iam_bindings_additive": {
- "$ref": "#/$defs/iam_bindings_additive"
- },
- "force_destroy": {
- "type": "boolean"
- },
- "labels": {
- "type": "object",
- "additionalProperties": {
- "type": "string"
- }
- },
- "location": {
- "type": "string"
- },
- "managed_folders": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-zA-Z0-9][a-zA-Z0-9_/-]+$": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "force_destroy": {
- "type": "boolean"
- },
- "iam": {
- "$ref": "#/$defs/iam"
- },
- "iam_bindings": {
- "$ref": "#/$defs/iam_bindings"
- },
- "iam_bindings_additive": {
- "$ref": "#/$defs/iam_bindings_additive"
- }
- }
- }
- }
- },
- "prefix": {
- "type": "string"
- },
- "storage_class": {
- "type": "string"
- },
- "uniform_bucket_level_access": {
- "type": "boolean"
- },
- "versioning": {
- "type": "boolean"
- },
- "retention_policy": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "retention_period": {
- "type": "number"
- },
- "is_locked": {
- "type": "boolean"
- }
- }
- },
- "enable_object_retention": {
- "type": "boolean"
- }
- }
- },
- "buckets": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z0-9-]+$": {
- "$ref": "#/$defs/bucket"
- }
- }
- },
- "iam": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^(?:roles/|\\$custom_roles:)": {
- "type": "array",
- "items": {
- "type": "string",
- "pattern": "^(?:domain:|group:|serviceAccount:|user:|principal:|principalSet:||\\$iam_principals:[a-z0-9_-]+)"
- }
- }
- }
- },
- "iam_bindings": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z0-9_-]+$": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "members": {
- "type": "array",
- "items": {
- "type": "string",
- "pattern": "^(?:domain:|group:|serviceAccount:|user:|principal:|principalSet:|\\$iam_principals:[a-z0-9_-]+)"
- }
- },
- "role": {
- "type": "string",
- "pattern": "^(?:roles/|\\$custom_roles:)"
- },
- "condition": {
- "type": "object",
- "additionalProperties": false,
- "required": [
- "expression",
- "title"
- ],
- "properties": {
- "expression": {
- "type": "string"
- },
- "title": {
- "type": "string"
- },
- "description": {
- "type": "string"
- }
- }
- }
- }
- }
- }
- },
- "iam_bindings_additive": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z0-9_-]+$": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "member": {
- "type": "string",
- "pattern": "^(?:domain:|group:|serviceAccount:|user:|principal:|principalSet:|\\$iam_principals:[a-z0-9_-]+)"
- },
- "role": {
- "type": "string",
- "pattern": "^(?:roles/|\\$custom_roles:)"
- },
- "condition": {
- "type": "object",
- "additionalProperties": false,
- "required": [
- "expression",
- "title"
- ],
- "properties": {
- "expression": {
- "type": "string"
- },
- "title": {
- "type": "string"
- },
- "description": {
- "type": "string"
- }
- }
- }
- }
- }
- }
- },
- "iam_by_principals": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^(?:domain:|group:|serviceAccount:|user:|principal:|principalSet:|\\$iam_principals:[a-z0-9_-]+)": {
- "type": "array",
- "items": {
- "type": "string",
- "pattern": "^(?:roles/|\\$custom_roles:)"
- }
- }
- }
- },
- "iam_billing_roles": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z0-9-]+$": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "iam_folder_roles": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z0-9-]+$": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "iam_organization_roles": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z0-9-]+$": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "iam_project_roles": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^(?:[a-z0-9-]|\\$project_ids:[a-z0-9_-])+$": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "iam_sa_roles": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^(?:\\$service_account_ids:|projects/)": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "iam_storage_roles": {
- "type": "object",
- "additionalProperties": false,
- "patternProperties": {
- "^[a-z0-9-]+$": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "log_bucket": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "description": {
- "type": "string"
- },
- "kms_key_name": {
- "type": "string"
- },
- "location": {
- "type": "string"
- },
- "log_analytics": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "enable": {
- "type": "boolean",
- "default": false
- },
- "dataset_link_id": {
- "type": "string"
- },
- "description": {
- "type": "string"
- }
- }
- },
- "retention": {
- "type": "number"
- }
- }
- }
- }
-}
diff --git a/fast/stages/0-org-setup/schemas/project.schema.json b/fast/stages/0-org-setup/schemas/project.schema.json
new file mode 120000
index 000000000..11f161f17
--- /dev/null
+++ b/fast/stages/0-org-setup/schemas/project.schema.json
@@ -0,0 +1 @@
+../../../../modules/project-factory/schemas/project.schema.json
\ No newline at end of file
diff --git a/fast/stages/0-org-setup/schemas/project.schema.md b/fast/stages/0-org-setup/schemas/project.schema.md
deleted file mode 100644
index 6915ddb0b..000000000
--- a/fast/stages/0-org-setup/schemas/project.schema.md
+++ /dev/null
@@ -1,257 +0,0 @@
-# Project
-
-
-
-## Properties
-
-*additional properties: false*
-
-- **automation**: *object*
-
*additional properties: false*
- - **prefix**: *string*
- - ⁺**project**: *string*
- - **bucket**: *reference([bucket](#refs-bucket))*
- - **service_accounts**: *object*
-
*additional properties: false*
- - **`^[a-z0-9-]+$`**: *object*
-
*additional properties: false*
- - **description**: *string*
- - **iam**: *reference([iam](#refs-iam))*
- - **iam_bindings**: *reference([iam_bindings](#refs-iam_bindings))*
- - **iam_bindings_additive**: *reference([iam_bindings_additive](#refs-iam_bindings_additive))*
- - **iam_billing_roles**: *reference([iam_billing_roles](#refs-iam_billing_roles))*
- - **iam_folder_roles**: *reference([iam_folder_roles](#refs-iam_folder_roles))*
- - **iam_organization_roles**: *reference([iam_organization_roles](#refs-iam_organization_roles))*
- - **iam_project_roles**: *reference([iam_project_roles](#refs-iam_project_roles))*
- - **iam_sa_roles**: *reference([iam_sa_roles](#refs-iam_sa_roles))*
- - **iam_storage_roles**: *reference([iam_storage_roles](#refs-iam_storage_roles))*
-- **billing_account**: *string*
-- **billing_budgets**: *array*
- - items: *string*
-- **buckets**: *reference([buckets](#refs-buckets))*
-- **contacts**: *object*
-
*additional properties: false*
- - **`^[a-z0-9_-]+$`**: *array*
- - items: *string*
-- **deletion_policy**: *string*
-
*enum: ['PREVENT', 'DELETE', 'ABANDON']*
-- **iam**: *reference([iam](#refs-iam))*
-- **iam_bindings**: *reference([iam_bindings](#refs-iam_bindings))*
-- **iam_bindings_additive**: *reference([iam_bindings_additive](#refs-iam_bindings_additive))*
-- **iam_by_principals**: *reference([iam_by_principals](#refs-iam_by_principals))*
-- **labels**: *object*
-- **log_buckets**: *object*
-
*additional properties: false*
- - **`^[a-z0-9-]+$`**: *reference([log_bucket](#refs-log_bucket))*
-- **metric_scopes**: *array*
- - items: *string*
-- **name**: *string*
-- **org_policies**: *object*
-
*additional properties: false*
- - **`^[a-z]+\.`**: *object*
- - **inherit_from_parent**: *boolean*
- - **reset**: *boolean*
- - **rules**: *array*
- - items: *object*
-
*additional properties: false*
- - **allow**: *object*
-
*additional properties: false*
- - **all**: *boolean*
- - **values**: *array*
- - items: *string*
- - **deny**: *object*
-
*additional properties: false*
- - **all**: *boolean*
- - **values**: *array*
- - items: *string*
- - **enforce**: *boolean*
- - **condition**: *object*
-
*additional properties: false*
- - **description**: *string*
- - **expression**: *string*
- - **location**: *string*
- - **title**: *string*
-- **quotas**: *object*
-
*additional properties: false*
- - **`^[a-zA-Z0-9_-]+$`**: *object*
-
*additional properties: false*
- - ⁺**service**: *string*
- - ⁺**quota_id**: *string*
- - ⁺**preferred_value**: *number*
- - **dimensions**: *object*
- *additional properties: String*
- - **justification**: *string*
- - **contact_email**: *string*
- - **annotations**: *object*
- *additional properties: String*
- - **ignore_safety_checks**: *string*
-
*enum: ['QUOTA_DECREASE_BELOW_USAGE', 'QUOTA_DECREASE_PERCENTAGE_TOO_HIGH', 'QUOTA_SAFETY_CHECK_UNSPECIFIED']*
-- **parent**: *string*
-- **prefix**: *string*
-- **project_reuse**: *object*
-
*additional properties: false*
- - **use_data_source**: *boolean*
- - **attributes**: *object*
- - ⁺**name**: *string*
- - ⁺**number**: *number*
- - **services_enabled**: *array*
- - items: *string*
-- **service_accounts**: *object*
-
*additional properties: false*
- - **`^[a-z0-9-]+$`**: *object*
-
*additional properties: false*
- - **display_name**: *string*
- - **iam**: *reference([iam](#refs-iam))*
- - **iam_self_roles**: *array*
- - items: *string*
- - **iam_project_roles**: *reference([iam_project_roles](#refs-iam_project_roles))*
- - **iam_sa_roles**: *reference([iam_sa_roles](#refs-iam_sa_roles))*
-- **service_encryption_key_ids**: *object*
-
*additional properties: false*
- - **`^[a-z-]+\.googleapis\.com$`**: *array*
- - items: *string*
-- **services**: *array*
- - items: *string*
-
*pattern: ^[a-z-]+\.googleapis\.com$*
-- **shared_vpc_host_config**: *object*
-
*additional properties: false*
- - ⁺**enabled**: *boolean*
- - **service_projects**: *array*
- - items: *string*
-- **shared_vpc_service_config**: *object*
-
*additional properties: false*
- - ⁺**host_project**: *string*
- - **iam_bindings_additive**: *reference([iam_bindings_additive](#refs-iam_bindings_additive))*
- - **network_users**: *array*
- - items: *string*
- - **service_agent_iam**: *object*
- - **`^[a-z0-9_-]+$`**: *array*
- - items: *string*
- - **service_agent_subnet_iam**: *object*
- - **`^[a-z0-9_-]+$`**: *array*
- - items: *string*
- - **service_iam_grants**: *array*
- - items: *string*
- - **network_subnet_users**: *object*
- - **`^[a-z0-9_-]+$`**: *array*
- - items: *string*
-- **tag_bindings**: *object*
-
*additional properties: false*
- - **`^[a-z0-9_-]+$`**: *string*
-- **tags**: *object*
- *additional properties: Object*
-- **vpc_sc**: *object*
- - ⁺**perimeter_name**: *string*
- - **is_dry_run**: *boolean*
-- **datasets**: *object*
-
*additional properties: false*
- - **`^[a-z0-9_]+$`**: *object*
-
*additional properties: false*
- - **friendly_name**: *string*
- - **location**: *string*
-
-## Definitions
-
-- **bucket**: *object*
-
*additional properties: false*
- - **name**: *string*
- - **description**: *string*
- - **iam**: *reference([iam](#refs-iam))*
- - **iam_bindings**: *reference([iam_bindings](#refs-iam_bindings))*
- - **iam_bindings_additive**: *reference([iam_bindings_additive](#refs-iam_bindings_additive))*
- - **force_destroy**: *boolean*
- - **labels**: *object*
- *additional properties: String*
- - **location**: *string*
- - **managed_folders**: *object*
-
*additional properties: false*
- - **`^[a-zA-Z0-9][a-zA-Z0-9_/-]+$`**: *object*
-
*additional properties: false*
- - **force_destroy**: *boolean*
- - **iam**: *reference([iam](#refs-iam))*
- - **iam_bindings**: *reference([iam_bindings](#refs-iam_bindings))*
- - **iam_bindings_additive**: *reference([iam_bindings_additive](#refs-iam_bindings_additive))*
- - **prefix**: *string*
- - **storage_class**: *string*
- - **uniform_bucket_level_access**: *boolean*
- - **versioning**: *boolean*
- - **retention_policy**: *object*
-
*additional properties: false*
- - **retention_period**: *number*
- - **is_locked**: *boolean*
- - **enable_object_retention**: *boolean*
-- **buckets**: *object*
-
*additional properties: false*
- - **`^[a-z0-9-]+$`**: *reference([bucket](#refs-bucket))*
-- **iam**: *object*
-
*additional properties: false*
- - **`^(?:roles/|\$custom_roles:)`**: *array*
- - items: *string*
-
*pattern: ^(?:domain:|group:|serviceAccount:|user:|principal:|principalSet:||\$iam_principals:[a-z0-9_-]+)*
-- **iam_bindings**: *object*
-
*additional properties: false*
- - **`^[a-z0-9_-]+$`**: *object*
-
*additional properties: false*
- - **members**: *array*
- - items: *string*
-
*pattern: ^(?:domain:|group:|serviceAccount:|user:|principal:|principalSet:|\$iam_principals:[a-z0-9_-]+)*
- - **role**: *string*
-
*pattern: ^(?:roles/|\$custom_roles:)*
- - **condition**: *object*
-
*additional properties: false*
- - ⁺**expression**: *string*
- - ⁺**title**: *string*
- - **description**: *string*
-- **iam_bindings_additive**: *object*
-
*additional properties: false*
- - **`^[a-z0-9_-]+$`**: *object*
-
*additional properties: false*
- - **member**: *string*
-
*pattern: ^(?:domain:|group:|serviceAccount:|user:|principal:|principalSet:|\$iam_principals:[a-z0-9_-]+)*
- - **role**: *string*
-
*pattern: ^(?:roles/|\$custom_roles:)*
- - **condition**: *object*
-
*additional properties: false*
- - ⁺**expression**: *string*
- - ⁺**title**: *string*
- - **description**: *string*
-- **iam_by_principals**: *object*
-
*additional properties: false*
- - **`^(?:domain:|group:|serviceAccount:|user:|principal:|principalSet:|\$iam_principals:[a-z0-9_-]+)`**: *array*
- - items: *string*
-
*pattern: ^(?:roles/|\$custom_roles:)*
-- **iam_billing_roles**: *object*
-
*additional properties: false*
- - **`^[a-z0-9-]+$`**: *array*
- - items: *string*
-- **iam_folder_roles**: *object*
-
*additional properties: false*
- - **`^[a-z0-9-]+$`**: *array*
- - items: *string*
-- **iam_organization_roles**: *object*
-
*additional properties: false*
- - **`^[a-z0-9-]+$`**: *array*
- - items: *string*
-- **iam_project_roles**: *object*
-
*additional properties: false*
- - **`^(?:[a-z0-9-]|\$project_ids:[a-z0-9_-])+$`**: *array*
- - items: *string*
-- **iam_sa_roles**: *object*
-
*additional properties: false*
- - **`^(?:\$service_account_ids:|projects/)`**: *array*
- - items: *string*
-- **iam_storage_roles**: *object*
-
*additional properties: false*
- - **`^[a-z0-9-]+$`**: *array*
- - items: *string*
-- **log_bucket**: *object*
-
*additional properties: false*
- - **description**: *string*
- - **kms_key_name**: *string*
- - **location**: *string*
- - **log_analytics**: *object*
-
*additional properties: false*
- - **enable**: *boolean*
- - **dataset_link_id**: *string*
- - **description**: *string*
- - **retention**: *number*
diff --git a/fast/stages/0-org-setup/schemas/project.schema.md b/fast/stages/0-org-setup/schemas/project.schema.md
new file mode 120000
index 000000000..1b04336aa
--- /dev/null
+++ b/fast/stages/0-org-setup/schemas/project.schema.md
@@ -0,0 +1 @@
+../../../../modules/project-factory/schemas/project.schema.md
\ No newline at end of file
diff --git a/modules/apigee/recipe-apigee-swp/README.md b/modules/apigee/recipe-apigee-swp/README.md
index 121d54532..c246e5df9 100644
--- a/modules/apigee/recipe-apigee-swp/README.md
+++ b/modules/apigee/recipe-apigee-swp/README.md
@@ -54,4 +54,4 @@ module "recipe_apigee_swp" {
subnet_proxy_only_ip_cidr_range = "10.16.2.0/24"
}
}
-# tftest modules=10 resources=43
+# tftest modules=10 resources=44
diff --git a/modules/folder/README.md b/modules/folder/README.md
index 8798cb33d..a644e9af2 100644
--- a/modules/folder/README.md
+++ b/modules/folder/README.md
@@ -473,6 +473,7 @@ module "folder" {
| [organization-policies.tf](./organization-policies.tf) | Folder-level organization policies. | google_org_policy_policy |
| [outputs.tf](./outputs.tf) | Module outputs. | |
| [scc-sha-custom-modules.tf](./scc-sha-custom-modules.tf) | Folder-level Custom modules with Security Health Analytics. | google_scc_management_folder_security_health_analytics_custom_module |
+| [service-agents.tf](./service-agents.tf) | Service agents supporting resources. | |
| [tags.tf](./tags.tf) | None | google_tags_tag_binding |
| [variables-iam.tf](./variables-iam.tf) | None | |
| [variables-logging.tf](./variables-logging.tf) | None | |
@@ -517,5 +518,6 @@ module "folder" {
| [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. | |
-| [sink_writer_identities](outputs.tf#L57) | Writer identities created for each sink. | |
+| [service_agents](outputs.tf#L57) | Identities of all folder-level service agents. | |
+| [sink_writer_identities](outputs.tf#L62) | Writer identities created for each sink. | |
diff --git a/modules/folder/main.tf b/modules/folder/main.tf
index 75d099846..cb788fd32 100644
--- a/modules/folder/main.tf
+++ b/modules/folder/main.tf
@@ -30,8 +30,9 @@ locals {
)
: format("folders/%s", try(google_assured_workloads_workload.folder[0].resources[0].resource_id, ""))
)
+ folder_number = split("/", local.folder_id)[1]
aw_parent = (
- # Assured Workload only accepls folder as a parent and uses organization as a parent when no value provided.
+ # Assured Workload only accepts folder as a parent and uses organization as a parent when no value provided.
var.parent == null
? null
: (
diff --git a/modules/folder/outputs.tf b/modules/folder/outputs.tf
index 58693e74f..1346077d4 100644
--- a/modules/folder/outputs.tf
+++ b/modules/folder/outputs.tf
@@ -1,5 +1,5 @@
/**
- * Copyright 2022 Google LLC
+ * 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.
@@ -54,6 +54,11 @@ output "scc_custom_sha_modules_ids" {
value = { for k, v in google_scc_management_folder_security_health_analytics_custom_module.scc_folder_custom_module : k => v.id }
}
+output "service_agents" {
+ description = "Identities of all folder-level service agents."
+ value = local.service_agents
+}
+
output "sink_writer_identities" {
description = "Writer identities created for each sink."
value = {
diff --git a/modules/folder/service-agents.tf b/modules/folder/service-agents.tf
new file mode 100644
index 000000000..a26207e50
--- /dev/null
+++ b/modules/folder/service-agents.tf
@@ -0,0 +1,33 @@
+/**
+ * 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.
+ */
+
+# tfdoc:file:description Service agents supporting resources.
+
+locals {
+ _sa_raw = yamldecode(file("${path.module}/service-agents.yaml"))
+ service_agents = {
+ for agent in local._sa_raw :
+ agent.name => {
+ create_command = (
+ "gcloud beta services identity create --service=${agent.api} --folder=${local.folder_number}"
+ )
+ display_name = agent.display_name
+ identity = templatestring(agent.identity, {
+ folder_number = local.folder_number
+ })
+ }
+ }
+}
diff --git a/modules/folder/service-agents.yaml b/modules/folder/service-agents.yaml
new file mode 100644
index 000000000..e1e2da4aa
--- /dev/null
+++ b/modules/folder/service-agents.yaml
@@ -0,0 +1,85 @@
+# 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
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# 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.
+
+- name: accessapproval
+ display_name: Access Approval Service Agent
+ api: accessapproval.googleapis.com
+ identity: service-f${folder_number}@gcp-sa-accessapproval.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: assuredworkloads
+ display_name: Assured Workloads Service Agent
+ api: assuredworkloads.googleapis.com
+ identity: service-folder-${folder_number}@gcp-sa-assuredworkloads.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: audit-manager
+ display_name: Audit Manager Service Agent
+ api: auditmanager.googleapis.com
+ identity: service-folder-${folder_number}@gcp-sa-audit-manager.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: cloudcontrolspartner
+ display_name: Cloud Controls Partner Service Agent
+ api: cloudcontrolspartner.googleapis.com
+ identity: service-folder-${folder_number}@gcp-sa-cloudcontrolspartner.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: logging
+ display_name: Cloud Logging Service Agent
+ api: logging.googleapis.com
+ identity: service-folder-${folder_number}@gcp-sa-logging.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: observability
+ display_name: Cloud Observability Service Account
+ api: observability.googleapis.com
+ identity: service-folder-${folder_number}@gcp-sa-observability.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: osconfig-rollout
+ display_name: Google Cloud OS Config Rollout Service Agent
+ api: osconfig.googleapis.com
+ identity: service-folder-${folder_number}@gcp-sa-osconfig-rollout.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: osconfig
+ display_name: Google Cloud OS Config Service Agent
+ api: osconfig.googleapis.com
+ identity: service-folder-${folder_number}@gcp-sa-osconfig.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: pam
+ display_name: Privileged Access Manager Service Agent
+ api: privilegedaccessmanager.googleapis.com
+ identity: service-folder-${folder_number}@gcp-sa-pam.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: progrollout
+ display_name: Progressive Rollout Service Agent
+ api: progressiverollout.googleapis.com
+ identity: service-folder-${folder_number}@gcp-sa-progrollout.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+
diff --git a/modules/organization/README.md b/modules/organization/README.md
index 2ad352edb..45bbf8796 100644
--- a/modules/organization/README.md
+++ b/modules/organization/README.md
@@ -655,6 +655,7 @@ values:
| [organization-policies.tf](./organization-policies.tf) | Organization-level organization policies. | google_org_policy_policy |
| [outputs.tf](./outputs.tf) | Module outputs. | |
| [scc-sha-custom-modules.tf](./scc-sha-custom-modules.tf) | Organization-level Custom modules with Security Health Analytics. | google_scc_management_organization_security_health_analytics_custom_module |
+| [service-agents.tf](./service-agents.tf) | Service agents supporting resources. | |
| [tags.tf](./tags.tf) | Manages GCP Secure Tags, keys, values, and IAM. | google_tags_tag_binding · google_tags_tag_key · google_tags_tag_key_iam_binding · google_tags_tag_key_iam_member · google_tags_tag_value · google_tags_tag_value_iam_binding · google_tags_tag_value_iam_member |
| [variables-iam.tf](./variables-iam.tf) | None | |
| [variables-logging.tf](./variables-logging.tf) | None | |
@@ -703,7 +704,8 @@ values:
| [organization_id](outputs.tf#L69) | Organization id dependent on module resources. | |
| [organization_policies_ids](outputs.tf#L86) | Map of ORGANIZATION_POLICIES => ID in the organization. | |
| [scc_custom_sha_modules_ids](outputs.tf#L91) | Map of SCC CUSTOM SHA MODULES => ID in the organization. | |
-| [sink_writer_identities](outputs.tf#L96) | Writer identities created for each sink. | |
-| [tag_keys](outputs.tf#L104) | Tag key resources. | |
-| [tag_values](outputs.tf#L113) | Tag value resources. | |
+| [service_agents](outputs.tf#L96) | Identities of all organization-level service agents. | |
+| [sink_writer_identities](outputs.tf#L101) | Writer identities created for each sink. | |
+| [tag_keys](outputs.tf#L109) | Tag key resources. | |
+| [tag_values](outputs.tf#L118) | Tag value resources. | |
diff --git a/modules/organization/outputs.tf b/modules/organization/outputs.tf
index 43c8062eb..7f5e61abe 100644
--- a/modules/organization/outputs.tf
+++ b/modules/organization/outputs.tf
@@ -1,5 +1,5 @@
/**
- * Copyright 2024 Google LLC
+ * 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.
@@ -93,6 +93,11 @@ output "scc_custom_sha_modules_ids" {
value = { for k, v in google_scc_management_organization_security_health_analytics_custom_module.scc_organization_custom_module : k => v.id }
}
+output "service_agents" {
+ description = "Identities of all organization-level service agents."
+ value = local.service_agents
+}
+
output "sink_writer_identities" {
description = "Writer identities created for each sink."
value = {
diff --git a/modules/organization/service-agents.tf b/modules/organization/service-agents.tf
new file mode 100644
index 000000000..eca1c65df
--- /dev/null
+++ b/modules/organization/service-agents.tf
@@ -0,0 +1,33 @@
+/**
+ * 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.
+ */
+
+# tfdoc:file:description Service agents supporting resources.
+
+locals {
+ _sa_raw = yamldecode(file("${path.module}/service-agents.yaml"))
+ service_agents = {
+ for agent in local._sa_raw :
+ agent.name => {
+ create_command = (
+ "gcloud beta services identity create --service=${agent.api} --organization=${local.organization_id_numeric}"
+ )
+ display_name = agent.display_name
+ identity = templatestring(agent.identity, {
+ organization_number = local.organization_id_numeric
+ })
+ }
+ }
+}
diff --git a/modules/organization/service-agents.yaml b/modules/organization/service-agents.yaml
new file mode 100644
index 000000000..c6572248d
--- /dev/null
+++ b/modules/organization/service-agents.yaml
@@ -0,0 +1,190 @@
+# 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
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# 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.
+
+- name: accessapproval
+ display_name: Access Approval Service Agent
+ api: accessapproval.googleapis.com
+ identity: service-o${organization_number}@gcp-sa-accessapproval.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: assuredoss
+ display_name: Assured OSS Service Agent
+ api: assuredoss.googleapis.com
+ identity: service-org-${organization_number}@gcp-sa-assuredoss.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: asm-hpsa
+ display_name: Attack Surface Management Service Agent
+ api: securitycenter.googleapis.com
+ identity: service-org-${organization_number}@gcp-sa-asm-hpsa.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: audit-manager
+ display_name: Audit Manager Service Agent
+ api: auditmanager.googleapis.com
+ identity: service-org-${organization_number}@gcp-sa-audit-manager.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: chronicle-soar
+ display_name: Chronicle Soar Service Agent
+ api: chronicle.googleapis.com
+ identity: service-org-${organization_number}@gcp-sa-chronicle-soar.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: effectivepolicy
+ display_name: Cloud Asset Effective Policy Service Agent
+ api: cloudasset.googleapis.com
+ identity: service-org-${organization_number}@gcp-sa-effectivepolicy.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: othercloudcfg
+ display_name: Cloud Asset Other Cloud Config Service Agent
+ api: cloudasset.googleapis.com
+ identity: service-org-${organization_number}@gcp-sa-othercloudcfg.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: cloudkms
+ display_name: Cloud KMS Organization Service Agent
+ api: cloudkms.googleapis.com
+ identity: service-org-${organization_number}@gcp-sa-cloudkms.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: logging
+ display_name: Cloud Logging Service Agent
+ api: logging.googleapis.com
+ identity: service-org-${organization_number}@gcp-sa-logging.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: nss-hpsa
+ display_name: Cloud Notebook Security Scanner Service Agent
+ api: notebooksecurityscanner.googleapis.com
+ identity: service-org-${organization_number}@gcp-sa-nss-hpsa.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: observability
+ display_name: Cloud Observability Service Account
+ api: observability.googleapis.com
+ identity: service-org-${organization_number}@gcp-sa-observability.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: cloudresourcemanager
+ display_name: Cloud Resource Manager Service Agent
+ api: cloudresourcemanager.googleapis.com
+ identity: service-org-${organization_number}@gcp-sa-cloudresourcemanager.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: riskmanager
+ display_name: Cloud Risk Manager Service Agent
+ api: dlp.googleapis.com
+ identity: organizations-${organization_number}@gcp-sa-riskmanager.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: scc-bulk-export
+ display_name: Cloud Security Command Center Bulk Export Service Account
+ api: securitycenter.googleapis.com
+ identity: service-org-${organization_number}@gcp-sa-scc-bulk-export.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: scc-notification
+ display_name: Cloud Security Command Center Notification Service Account
+ api: securitycenter.googleapis.com
+ identity: service-org-${organization_number}@gcp-sa-scc-notification.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: security-center-api
+ display_name: Cloud Security Command Center Service Agent
+ api: securitycenter.googleapis.com
+ identity: service-org-${organization_number}@security-center-api.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: ktd-hpsa
+ display_name: Container Threat Detection Service Agent
+ api: containerthreatdetection.googleapis.com
+ identity: service-org-${organization_number}@gcp-sa-ktd-hpsa.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: dataplex-cmek
+ display_name: Dataplex Cmek Service Agent
+ api: dataplex.googleapis.com
+ identity: service-org-${organization_number}@gcp-sa-dataplex-cmek.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: dataplex
+ display_name: Dataplex Service Agent
+ api: dataplex.googleapis.com
+ identity: service-org-${organization_number}@gcp-sa-dataplex.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: osconfig-rollout
+ display_name: Google Cloud OS Config Rollout Service Agent
+ api: osconfig.googleapis.com
+ identity: service-org-${organization_number}@gcp-sa-osconfig-rollout.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: osconfig
+ display_name: Google Cloud OS Config Service Agent
+ api: osconfig.googleapis.com
+ identity: service-org-${organization_number}@gcp-sa-osconfig.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: v1-remediator
+ display_name: Policy Remediator Service Agent (prod)
+ api: policyremediator.googleapis.com
+ identity: service-org-${organization_number}@gcp-sa-v1-remediator.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: pam
+ display_name: Privileged Access Manager Service Agent
+ api: privilegedaccessmanager.googleapis.com
+ identity: service-org-${organization_number}@gcp-sa-pam.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: progrollout
+ display_name: Progressive Rollout Service Agent
+ api: progressiverollout.googleapis.com
+ identity: service-org-${organization_number}@gcp-sa-progrollout.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+- name: sccspanner
+ display_name: SCC CMEK Spanner Service Agent (PROD)
+ api: securitycenter.googleapis.com
+ identity: service-org-${organization_number}@gcp-sa-sccspanner.iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+
diff --git a/modules/project-factory/README.md b/modules/project-factory/README.md
index 906db469f..cca3410f8 100644
--- a/modules/project-factory/README.md
+++ b/modules/project-factory/README.md
@@ -462,7 +462,7 @@ module "project-factory" {
}
}
}
-# tftest files=t0,0,1,2,3,4,5,6,7,8,9 inventory=example.yaml
+# tftest files=t0,0,1,2,3,4,5,6,7,8,9,10 inventory=example.yaml
```
A project template for GKE projects:
@@ -510,6 +510,8 @@ parent: folders/5678901234
```yaml
name: App 0
+factories_config:
+ org_policies: data/factories/org-policies
# tftest-file id=3 path=data/hierarchy/team-a/app-0/.config.yaml schema=folder.schema.json
```
@@ -520,10 +522,12 @@ tag_bindings:
# tftest-file id=4 path=data/hierarchy/team-b/app-0/.config.yaml schema=folder.schema.json
```
-One project defined within the folder hierarchy:
+One project defined within the folder hierarchy, using a lower level factory for org polcies:
```yaml
billing_account: 012345-67890A-BCDEF0
+factories_config:
+ org_policies: data/factories/org-policies
services:
- container.googleapis.com
- storage.googleapis.com
@@ -682,6 +686,15 @@ service_accounts:
# tftest-file id=9 path=data/projects/dev-tb-app0-1.yaml schema=project.schema.json
```
+Lower-level factory example.
+
+```yaml
+compute.disableSerialPortAccess:
+ rules:
+ - enforce: false
+# tftest-file id=10 path=data/factories/org-policies/compute.yaml
+```
+
## Files
@@ -708,11 +721,11 @@ service_accounts:
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [factories_config](variables.tf#L181) | Path to folder with YAML resource description data files. | object({…}) | ✓ | |
+| [factories_config](variables.tf#L170) | Path to folder with YAML resource description data files. | object({…}) | ✓ | |
| [context](variables.tf#L17) | Context-specific interpolations. | object({…}) | | {} |
-| [data_defaults](variables.tf#L36) | Optional default values used when corresponding project or folder data from files are missing. | object({…}) | | {} |
-| [data_merges](variables.tf#L112) | 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#L131) | Optional values that override corresponding data from files. Takes precedence over file data and `data_defaults`. | object({…}) | | {} |
+| [data_defaults](variables.tf#L36) | Optional default values used when corresponding project or folder data from files are missing. | object({…}) | | {} |
+| [data_merges](variables.tf#L107) | 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#L126) | 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({…})) | | {} |
diff --git a/modules/project-factory/folders.tf b/modules/project-factory/folders.tf
index 8d57ab85b..8ec80f424 100644
--- a/modules/project-factory/folders.tf
+++ b/modules/project-factory/folders.tf
@@ -58,6 +58,7 @@ module "folder-1" {
}
parent = coalesce(each.value.parent, "$folder_ids:default")
name = each.value.name
+ factories_config = lookup(each.value, "factories_config", {})
org_policies = lookup(each.value, "org_policies", {})
tag_bindings = lookup(each.value, "tag_bindings", {})
logging_data_access = lookup(each.value, "logging_data_access", {})
@@ -89,6 +90,7 @@ module "folder-2" {
each.value.parent, "$folder_ids:${each.value.parent_key}"
)
name = each.value.name
+ factories_config = lookup(each.value, "factories_config", {})
org_policies = lookup(each.value, "org_policies", {})
tag_bindings = lookup(each.value, "tag_bindings", {})
logging_data_access = lookup(each.value, "logging_data_access", {})
@@ -128,6 +130,7 @@ module "folder-3" {
each.value.parent, "$folder_ids:${each.value.parent_key}"
)
name = each.value.name
+ factories_config = lookup(each.value, "factories_config", {})
org_policies = lookup(each.value, "org_policies", {})
tag_bindings = lookup(each.value, "tag_bindings", {})
logging_data_access = lookup(each.value, "logging_data_access", {})
diff --git a/modules/project-factory/projects-defaults.tf b/modules/project-factory/projects-defaults.tf
index a12f0756f..5e7ff14f0 100644
--- a/modules/project-factory/projects-defaults.tf
+++ b/modules/project-factory/projects-defaults.tf
@@ -25,6 +25,103 @@ locals {
defaults = try(var.data_defaults, {})
overrides = try(var.data_overrides, {})
}
+ data_defaults = {
+ defaults = merge(
+ {
+ billing_account = null
+ contacts = {}
+ deletion_policy = null
+ labels = {}
+ metric_scopes = []
+ parent = null
+ prefix = null
+ project_reuse = merge(
+ {
+ use_data_source = true
+ attributes = null
+ },
+ try(local._data_defaults.defaults.project_reuse, {
+ use_data_source = true
+ attributes = null
+ }
+ )
+ )
+ service_encryption_key_ids = {}
+ services = []
+ shared_vpc_service_config = merge(
+ {
+ host_project = null
+ iam_bindings_additive = {}
+ network_users = []
+ service_agent_iam = {}
+ service_agent_subnet_iam = {}
+ service_iam_grants = []
+ network_subnet_users = {}
+ },
+ try(local._data_defaults.defaults.shared_vpc_service_config, {
+ host_project = null
+ iam_bindings_additive = {}
+ network_users = []
+ service_agent_iam = {}
+ service_agent_subnet_iam = {}
+ service_iam_grants = []
+ network_subnet_users = {}
+ }
+ )
+ )
+ storage_location = null
+ tag_bindings = {}
+ service_accounts = {}
+ universe = null
+ vpc_sc = merge(
+ {
+ perimeter_name = null
+ is_dry_run = false
+ },
+ try(local._data_defaults.defaults.vpc_sc, {
+ perimeter_name = null
+ is_dry_run = false
+ }
+ )
+ )
+ logging_data_access = {}
+ bigquery_location = null
+ },
+ try(
+ local._data_defaults.defaults, {}
+ )
+ )
+ # data_overrides default to null's, to mark that they should not override
+ overrides = merge({
+ billing_account = null
+ contacts = null
+ deletion_policy = null
+ parent = null
+ prefix = null
+ service_encryption_key_ids = null
+ storage_location = null
+ tag_bindings = null
+ services = null
+ service_accounts = null
+ universe = null
+ vpc_sc = try(
+ merge(
+ {
+ perimeter_name = null
+ is_dry_run = false
+ },
+ local._data_defaults.overrides.vpc_sc
+ ),
+ null
+ )
+ logging_data_access = null
+ bigquery_location = null
+ },
+ try(
+ local._data_defaults.overrides, {}
+ )
+ )
+ }
_projects_output = {
# Semantics of the merges are:
# - if data_overrides. is not null, use this value
@@ -49,37 +146,7 @@ locals {
try(v.contacts, null),
local.data_defaults.defaults.contacts
)
- factories_config = { # type: object
- custom_roles = try( # type: string
- coalesce(
- local.data_defaults.overrides.factories_config.custom_roles,
- try(v.factories_config.custom_roles, null),
- local.data_defaults.defaults.factories_config.custom_roles
- ),
- null
- )
- observability = try( # type: string
- coalesce(
- local.data_defaults.overrides.factories_config.observability,
- try(v.factories_config.observability, null),
- local.data_defaults.defaults.factories_config.observability
- ),
- null)
- org_policies = try( # type: string
- coalesce(
- local.data_defaults.overrides.factories_config.org_policies,
- try(v.factories_config.org_policies, null),
- local.data_defaults.defaults.factories_config.org_policies
- ),
- null)
- quotas = try( # type: string
- coalesce(
- local.data_defaults.overrides.factories_config.quotas,
- try(v.factories_config.quotas, null),
- local.data_defaults.defaults.factories_config.quotas
- ),
- null)
- }
+ factories_config = try(v.factories_config, {})
iam = try(v.iam, {}) # type: map(list(string))
iam_bindings = try(v.iam_bindings, {}) # type: map(object({...}))
iam_bindings_additive = try(v.iam_bindings_additive, {}) # type: map(object({...}))
@@ -226,21 +293,6 @@ locals {
billing_account = null
contacts = {}
deletion_policy = null
- factories_config = merge(
- {
- custom_roles = null
- observability = null
- org_policies = null
- quotas = null
- },
- try(local._data_defaults.defaults.factories_config, {
- custom_roles = null
- observability = null
- org_policies = null
- quotas = null
- }
- )
- )
labels = {}
locations = {
bigquery = try(local._data_defaults.defaults.locations.bigquery, null)
@@ -297,21 +349,6 @@ locals {
billing_account = null
contacts = null
deletion_policy = null
- factories_config = merge(
- {
- custom_roles = null
- observability = null
- org_policies = null
- quotas = null
- },
- try(local._data_defaults.overrides.factories_config, {
- custom_roles = null
- observability = null
- org_policies = null
- quotas = null
- }
- )
- )
locations = {
bigquery = try(local._data_defaults.overrides.locations.bigquery, null)
logging = try(local._data_defaults.overrides.locations.logging, null)
diff --git a/modules/project-factory/projects.tf b/modules/project-factory/projects.tf
index 4486ce1da..71b521db4 100644
--- a/modules/project-factory/projects.tf
+++ b/modules/project-factory/projects.tf
@@ -73,12 +73,7 @@ module "projects" {
})
default_service_account = try(each.value.default_service_account, "keep")
descriptive_name = try(each.value.descriptive_name, null)
- factories_config = {
- custom_roles = each.value.factories_config.custom_roles
- observability = each.value.factories_config.observability
- org_policies = each.value.factories_config.org_policies
- quotas = each.value.factories_config.quotas
- }
+ factories_config = each.value.factories_config
labels = merge(
each.value.labels, var.data_merges.labels
)
diff --git a/modules/project-factory/schemas/folder.schema.json b/modules/project-factory/schemas/folder.schema.json
index 1125a8a97..fd266920f 100644
--- a/modules/project-factory/schemas/folder.schema.json
+++ b/modules/project-factory/schemas/folder.schema.json
@@ -64,6 +64,18 @@
}
}
},
+ "factories_config": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "org_policies": {
+ "type": "string"
+ },
+ "scc_sha_custom_modules": {
+ "type": "string"
+ }
+ }
+ },
"iam": {
"$ref": "#/$defs/iam"
},
@@ -157,7 +169,8 @@
}
},
"parent": {
- "type": "string"
+ "type": "string",
+ "pattern": "^(?:folders/[0-9]+|organizations/[0-9]+|\\$folder_ids:[a-z0-9_-]+)$"
},
"tag_bindings": {
"type": "object",
@@ -269,7 +282,7 @@
},
"role": {
"type": "string",
- "pattern": "^roles/"
+ "pattern": "^(?:roles/|\\$custom_roles:)"
},
"condition": {
"type": "object",
@@ -308,7 +321,7 @@
},
"role": {
"type": "string",
- "pattern": "^roles/"
+ "pattern": "^(?:roles/|\\$custom_roles:)"
},
"condition": {
"type": "object",
diff --git a/modules/project-factory/schemas/folder.schema.md b/modules/project-factory/schemas/folder.schema.md
index 4c5fac144..b175e3421 100644
--- a/modules/project-factory/schemas/folder.schema.md
+++ b/modules/project-factory/schemas/folder.schema.md
@@ -25,6 +25,10 @@
- **iam_project_roles**: *reference([iam_project_roles](#refs-iam_project_roles))*
- **iam_sa_roles**: *reference([iam_sa_roles](#refs-iam_sa_roles))*
- **iam_storage_roles**: *reference([iam_storage_roles](#refs-iam_storage_roles))*
+- **factories_config**: *object*
+
*additional properties: false*
+ - **org_policies**: *string*
+ - **scc_sha_custom_modules**: *string*
- **iam**: *reference([iam](#refs-iam))*
- **iam_bindings**: *reference([iam_bindings](#refs-iam_bindings))*
- **iam_bindings_additive**: *reference([iam_bindings_additive](#refs-iam_bindings_additive))*
@@ -99,7 +103,7 @@
- items: *string*
*pattern: ^(?:domain:|group:|serviceAccount:|user:|principal:|principalSet:|\$iam_principals:)*
- **role**: *string*
-
*pattern: ^roles/*
+
*pattern: ^(?:roles/|\$custom_roles:)*
- **condition**: *object*
*additional properties: false*
- ⁺**expression**: *string*
@@ -112,7 +116,7 @@
- **member**: *string*
*pattern: ^(?:domain:|group:|serviceAccount:|user:|principal:|principalSet:|\$iam_principals:)*
- **role**: *string*
-
*pattern: ^roles/*
+
*pattern: ^(?:roles/|\$custom_roles:)*
- **condition**: *object*
*additional properties: false*
- ⁺**expression**: *string*
diff --git a/modules/project-factory/schemas/project.schema.json b/modules/project-factory/schemas/project.schema.json
index baf129a1e..0eedab517 100644
--- a/modules/project-factory/schemas/project.schema.json
+++ b/modules/project-factory/schemas/project.schema.json
@@ -114,6 +114,30 @@
"ABANDON"
]
},
+ "factories_config": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "custom_roles": {
+ "type": "string"
+ },
+ "observability": {
+ "type": "string"
+ },
+ "org_policies": {
+ "type": "string"
+ },
+ "quotas": {
+ "type": "string"
+ },
+ "scc_sha_custom_modules": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "string"
+ }
+ }
+ },
"iam": {
"$ref": "#/$defs/iam"
},
diff --git a/modules/project-factory/schemas/project.schema.md b/modules/project-factory/schemas/project.schema.md
index c013c7d02..6c908d654 100644
--- a/modules/project-factory/schemas/project.schema.md
+++ b/modules/project-factory/schemas/project.schema.md
@@ -35,6 +35,14 @@
- items: *string*
- **deletion_policy**: *string*
*enum: ['PREVENT', 'DELETE', 'ABANDON']*
+- **factories_config**: *object*
+
*additional properties: false*
+ - **custom_roles**: *string*
+ - **observability**: *string*
+ - **org_policies**: *string*
+ - **quotas**: *string*
+ - **scc_sha_custom_modules**: *string*
+ - **tags**: *string*
- **iam**: *reference([iam](#refs-iam))*
- **iam_bindings**: *reference([iam_bindings](#refs-iam_bindings))*
- **iam_bindings_additive**: *reference([iam_bindings_additive](#refs-iam_bindings_additive))*
@@ -96,6 +104,7 @@
- ⁺**number**: *number*
- **services_enabled**: *array*
- items: *string*
+- **project_template**: *string*
- **service_accounts**: *object*
*additional properties: false*
- **`^[a-z0-9-]+$`**: *object*
@@ -143,6 +152,12 @@
- **universe**: *object*
*additional properties: false*
- **prefix**: *string*
+ - **forced_jit_service_identities**: *array*
+ - items: *string*
+ - **unavailable_services**: *array*
+ - items: *string*
+ - **unavailable_service_identities**: *array*
+ - items: *string*
- **vpc_sc**: *object*
- ⁺**perimeter_name**: *string*
- **is_dry_run**: *boolean*
@@ -158,6 +173,7 @@
- **bucket**: *object*
*additional properties: false*
- **name**: *string*
+ - **create**: *boolean*
- **description**: *string*
- **iam**: *reference([iam](#refs-iam))*
- **iam_bindings**: *reference([iam_bindings](#refs-iam_bindings))*
diff --git a/modules/project-factory/variables.tf b/modules/project-factory/variables.tf
index 2e33347b3..d380247d0 100644
--- a/modules/project-factory/variables.tf
+++ b/modules/project-factory/variables.tf
@@ -42,18 +42,13 @@ variable "data_defaults" {
}), {})
contacts = optional(map(list(string)), {})
deletion_policy = optional(string)
- factories_config = optional(object({
- custom_roles = optional(string)
- observability = optional(string)
- org_policies = optional(string)
- quotas = optional(string)
- }), {})
- labels = optional(map(string), {})
+ labels = optional(map(string), {})
locations = optional(object({
bigquery = optional(string)
logging = optional(string)
storage = optional(string)
}), {})
+ labels = optional(map(string), {})
logging_data_access = optional(map(object({
ADMIN_READ = optional(object({ exempted_members = optional(list(string)) })),
DATA_READ = optional(object({ exempted_members = optional(list(string)) })),
@@ -138,12 +133,6 @@ variable "data_overrides" {
}), {})
contacts = optional(map(list(string)))
deletion_policy = optional(string)
- factories_config = optional(object({
- custom_roles = optional(string)
- observability = optional(string)
- org_policies = optional(string)
- quotas = optional(string)
- }), {})
locations = optional(object({
bigquery = optional(string)
logging = optional(string)
diff --git a/modules/project/service-agents.yaml b/modules/project/service-agents.yaml
index 08967a456..1d2fea21e 100644
--- a/modules/project/service-agents.yaml
+++ b/modules/project/service-agents.yaml
@@ -103,6 +103,13 @@
role: roles/meshdataplane.serviceAgent
is_primary: false
aliases: []
+- name: accessapproval
+ display_name: Access Approval Service Agent
+ api: accessapproval.googleapis.com
+ identity: service-p${project_number}@gcp-sa-accessapproval.${universe_domain}iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
- name: adsdatahub
display_name: Ads Data Hub Service Account
api: adsdatahub.googleapis.com
@@ -218,7 +225,7 @@
- name: gae-api-prod
display_name: App Engine Flexible Environment Service Agent
api: appengineflex.googleapis.com
- identity: service-${project_number}@gae-api-prod.google.com.${universe_domain}iam.gserviceaccount.com
+ identity: service-${project_number}@gae-api-prod.${universe_domain}iam.gserviceaccount.com
role: roles/appengineflex.serviceAgent
is_primary: true
aliases:
@@ -293,13 +300,6 @@
role: roles/backupdr.serviceAgent
is_primary: true
aliases: []
-- name: backupdr-pr
- display_name: Backup and DR Vault Service Agent
- api: backupdr.googleapis.com
- identity: vault-${project_number}-IDENTIFIER@gcp-sa-backupdr-pr.${universe_domain}iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- name: gkebackup
display_name: Backup for GKE Service Account
api: gkebackup.googleapis.com
@@ -329,11 +329,11 @@
is_primary: false
aliases:
- bq
-- name: biglakerestcatalog
- display_name: BigLake Iceberg Rest Catalog API Service Agent
- api: biglake.googleapis.com
- identity: blirc-${project_number}-IDENTIFIER@gcp-sa-biglakerestcatalog.${universe_domain}iam.gserviceaccount.com
- role: null
+- name: connectedsheets
+ display_name: BigQuery Connected Sheets Service Agent
+ api: bigquery.googleapis.com
+ identity: service-${project_number}@gcp-sa-connectedsheets.${universe_domain}iam.gserviceaccount.com
+ role: roles/bigquery.connectedSheetsServiceAgent
is_primary: false
aliases: []
- name: bigqueryconnection
@@ -371,13 +371,6 @@
role: null
is_primary: false
aliases: []
-- name: bigquery-consp
- display_name: BigQuery Spark Connection Delegate Service Agent
- api: bigqueryconnection.googleapis.com
- identity: bqcx-${project_number}-IDENTIFIER@gcp-sa-bigquery-consp.${universe_domain}iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- name: bigqueryspark
display_name: BigQuery Spark Service Agent
api: bigquery.googleapis.com
@@ -399,13 +392,6 @@
role: roles/blockchainnodeengine.serviceAgent
is_primary: true
aliases: []
-- name: bundles
- display_name: Bundles Service Agent
- api: integrations.googleapis.com
- identity: b${project_number}-IDENTIFIER@gcp-sa-bundles.${universe_domain}iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- name: chronicle-sv
display_name: Chronicle Security Validation Service Account
api: chronicle.googleapis.com
@@ -639,11 +625,18 @@
role: null
is_primary: true
aliases: []
+- name: nss-hpsa
+ display_name: Cloud Notebook Security Scanner Service Agent
+ api: notebooksecurityscanner.googleapis.com
+ identity: service-${project_number}@gcp-sa-nss-hpsa.${universe_domain}iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
- name: observability
display_name: Cloud Observability Service Account
api: observability.googleapis.com
identity: service-${project_number}@gcp-sa-observability.${universe_domain}iam.gserviceaccount.com
- role: null
+ role: roles/observability.serviceAgent
is_primary: true
aliases: []
- name: cloudoptim
@@ -850,6 +843,13 @@
role: roles/containerthreatdetection.serviceAgent
is_primary: true
aliases: []
+- name: ktd-hpsa
+ display_name: Container Threat Detection Service Agent
+ api: containerthreatdetection.googleapis.com
+ identity: service-${project_number}@gcp-sa-ktd-hpsa.${universe_domain}iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
- name: cloud-cw
display_name: Content Warehouse Service Account
api: contentwarehouse.googleapis.com
@@ -1096,6 +1096,13 @@
is_primary: true
aliases:
- fleet
+- name: geminicodeassistmp
+ display_name: Gemini Code Assist Management Service Agent
+ api: geminicodeassistmanagement.googleapis.com
+ identity: service-${project_number}@gcp-sa-geminicodeassistmp.${universe_domain}iam.gserviceaccount.com
+ role: roles/geminicodeassistmanagement.serviceAgent
+ is_primary: true
+ aliases: []
- name: cloudaicompanion
display_name: Gemini for Google Cloud Service Agent
api: cloudaicompanion.googleapis.com
@@ -1145,7 +1152,7 @@
- name: cloud-ml
display_name: Google Cloud ML Engine Service Agent
api: ml.googleapis.com
- identity: service-${project_number}@cloud-ml.google.com.${universe_domain}iam.gserviceaccount.com
+ identity: service-${project_number}@cloud-ml.${universe_domain}iam.gserviceaccount.com
role: roles/ml.serviceAgent
is_primary: true
aliases: []
@@ -1156,6 +1163,13 @@
role: null
is_primary: true
aliases: []
+- name: ns-authz
+ display_name: Google Cloud Network Security Authz Service Account
+ api: networksecurity.googleapis.com
+ identity: service-${project_number}@gcp-sa-ns-authz.${universe_domain}iam.gserviceaccount.com
+ role: roles/networksecurity.authzServiceAgent
+ is_primary: false
+ aliases: []
- name: osconfig-rollout
display_name: Google Cloud OS Config Rollout Service Agent
api: osconfig.googleapis.com
@@ -1383,7 +1397,7 @@
display_name: On-Demand Scanning Service Account
api: ondemandscanning.googleapis.com
identity: service-${project_number}@gcp-sa-ondemandscanning.${universe_domain}iam.gserviceaccount.com
- role: roles/ondemandscanning.serviceAgent
+ role: null
is_primary: true
aliases: []
- name: oci
@@ -1414,6 +1428,13 @@
role: null
is_primary: true
aliases: []
+- name: pam
+ display_name: Privileged Access Manager Service Agent
+ api: privilegedaccessmanager.googleapis.com
+ identity: service-${project_number}@gcp-sa-pam.${universe_domain}iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
- name: progrollout
display_name: Progressive Rollout Service Agent
api: progressiverollout.googleapis.com
@@ -1673,6 +1694,13 @@
role: roles/aiplatform.tuningServiceAgent
is_primary: false
aliases: []
+- name: vertex-telemetry
+ display_name: Vertex AI Telemetry Service Agent
+ api: aiplatform.googleapis.com
+ identity: service-${project_number}@gcp-sa-vertex-telemetry.${universe_domain}iam.gserviceaccount.com
+ role: roles/aiplatform.telemetryServiceAgent
+ is_primary: false
+ aliases: []
- name: vertex-agent
display_name: Vertex Agent Service Agent
api: aiplatform.googleapis.com
diff --git a/tests/fast/stages/s0_org_setup/not-simple.yaml b/tests/fast/stages/s0_org_setup/not-simple.yaml
index 78f2f91be..3c9cec91a 100644
--- a/tests/fast/stages/s0_org_setup/not-simple.yaml
+++ b/tests/fast/stages/s0_org_setup/not-simple.yaml
@@ -2781,7 +2781,7 @@ counts:
google_organization_iam_custom_role: 9
google_project: 3
google_project_iam_binding: 16
- google_project_iam_member: 15
+ google_project_iam_member: 18
google_project_service: 33
google_project_service_identity: 9
google_service_account: 16
diff --git a/tests/fast/stages/s2_networking_a_simple/ncc.yaml b/tests/fast/stages/s2_networking_a_simple/ncc.yaml
index 09d4db45c..59af96b43 100644
--- a/tests/fast/stages/s2_networking_a_simple/ncc.yaml
+++ b/tests/fast/stages/s2_networking_a_simple/ncc.yaml
@@ -1,4 +1,4 @@
-# Copyright 2024 Google LLC
+# 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.
@@ -36,10 +36,10 @@ counts:
google_network_connectivity_spoke: 2
google_project: 3
google_project_iam_binding: 2
- google_project_iam_member: 22
+ google_project_iam_member: 24
google_project_service: 28
google_project_service_identity: 22
google_storage_bucket_object: 2
google_tags_tag_binding: 3
modules: 23
- resources: 189
+ resources: 191
diff --git a/tests/fast/stages/s2_networking_a_simple/simple.yaml b/tests/fast/stages/s2_networking_a_simple/simple.yaml
index add5be060..3f3b4c8ce 100644
--- a/tests/fast/stages/s2_networking_a_simple/simple.yaml
+++ b/tests/fast/stages/s2_networking_a_simple/simple.yaml
@@ -1,4 +1,4 @@
-# Copyright 2024 Google LLC
+# 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.
@@ -40,11 +40,11 @@ counts:
google_monitoring_monitored_project: 2
google_project: 3
google_project_iam_binding: 2
- google_project_iam_member: 22
+ google_project_iam_member: 24
google_project_service: 28
google_project_service_identity: 22
google_storage_bucket_object: 2
google_tags_tag_binding: 3
modules: 28
random_id: 3
- resources: 206
+ resources: 208
diff --git a/tests/fast/stages/s2_networking_a_simple/vpn.yaml b/tests/fast/stages/s2_networking_a_simple/vpn.yaml
index 8443a904d..4b711fb60 100644
--- a/tests/fast/stages/s2_networking_a_simple/vpn.yaml
+++ b/tests/fast/stages/s2_networking_a_simple/vpn.yaml
@@ -1,4 +1,4 @@
-# Copyright 2024 Google LLC
+# 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.
@@ -38,11 +38,11 @@ counts:
google_monitoring_monitored_project: 2
google_project: 3
google_project_iam_binding: 2
- google_project_iam_member: 22
+ google_project_iam_member: 24
google_project_service: 28
google_project_service_identity: 22
google_storage_bucket_object: 2
google_tags_tag_binding: 3
modules: 30
random_id: 17
- resources: 253
+ resources: 255
diff --git a/tests/fast/stages/s2_networking_b_nva/ncc-ra.yaml b/tests/fast/stages/s2_networking_b_nva/ncc-ra.yaml
index e5560b5b1..28c84e930 100644
--- a/tests/fast/stages/s2_networking_b_nva/ncc-ra.yaml
+++ b/tests/fast/stages/s2_networking_b_nva/ncc-ra.yaml
@@ -1,4 +1,4 @@
-# Copyright 2024 Google LLC
+# 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.
@@ -43,11 +43,11 @@ counts:
google_network_connectivity_spoke: 4
google_project: 3
google_project_iam_binding: 2
- google_project_iam_member: 22
+ google_project_iam_member: 24
google_project_service: 28
google_project_service_identity: 22
google_storage_bucket_object: 2
google_tags_tag_binding: 3
modules: 38
random_id: 6
- resources: 273
+ resources: 275
diff --git a/tests/fast/stages/s2_networking_b_nva/regional.yaml b/tests/fast/stages/s2_networking_b_nva/regional.yaml
index cee53e755..5e08d85a3 100644
--- a/tests/fast/stages/s2_networking_b_nva/regional.yaml
+++ b/tests/fast/stages/s2_networking_b_nva/regional.yaml
@@ -1,4 +1,4 @@
-# Copyright 2024 Google LLC
+# 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.
@@ -45,11 +45,11 @@ counts:
google_monitoring_monitored_project: 2
google_project: 3
google_project_iam_binding: 2
- google_project_iam_member: 22
+ google_project_iam_member: 24
google_project_service: 28
google_project_service_identity: 22
google_storage_bucket_object: 2
google_tags_tag_binding: 3
modules: 46
random_id: 6
- resources: 283
+ resources: 285
diff --git a/tests/fast/stages/s2_networking_b_nva/simple.yaml b/tests/fast/stages/s2_networking_b_nva/simple.yaml
index b2c876a2b..777d7c8ac 100644
--- a/tests/fast/stages/s2_networking_b_nva/simple.yaml
+++ b/tests/fast/stages/s2_networking_b_nva/simple.yaml
@@ -1,4 +1,4 @@
-# Copyright 2024 Google LLC
+# 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.
@@ -45,11 +45,11 @@ counts:
google_monitoring_monitored_project: 2
google_project: 3
google_project_iam_binding: 2
- google_project_iam_member: 22
+ google_project_iam_member: 24
google_project_service: 28
google_project_service_identity: 22
google_storage_bucket_object: 2
google_tags_tag_binding: 3
modules: 42
random_id: 6
- resources: 259
+ resources: 261
diff --git a/tests/fast/stages/s2_networking_c_separate_envs/simple.yaml b/tests/fast/stages/s2_networking_c_separate_envs/simple.yaml
index 9b87e211d..89a29d649 100644
--- a/tests/fast/stages/s2_networking_c_separate_envs/simple.yaml
+++ b/tests/fast/stages/s2_networking_c_separate_envs/simple.yaml
@@ -1,4 +1,4 @@
-# Copyright 2024 Google LLC
+# 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.
@@ -38,11 +38,11 @@ counts:
google_monitoring_dashboard: 6
google_project: 2
google_project_iam_binding: 2
- google_project_iam_member: 18
+ google_project_iam_member: 20
google_project_service: 22
google_project_service_identity: 18
google_storage_bucket_object: 2
google_tags_tag_binding: 2
modules: 23
random_id: 6
- resources: 231
+ resources: 233
diff --git a/tests/fast/stages/s3_data_platform_dev/simple.yaml b/tests/fast/stages/s3_data_platform_dev/simple.yaml
index 7d0fc4e16..f5f9c4074 100644
--- a/tests/fast/stages/s3_data_platform_dev/simple.yaml
+++ b/tests/fast/stages/s3_data_platform_dev/simple.yaml
@@ -1,4 +1,4 @@
-# Copyright 2024 Google LLC
+# 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.
@@ -24,7 +24,7 @@ counts:
google_folder_iam_binding: 3
google_project: 3
google_project_iam_binding: 23
- google_project_iam_member: 12
+ google_project_iam_member: 15
google_project_service: 18
google_project_service_identity: 6
google_service_account: 6
@@ -37,4 +37,4 @@ counts:
google_tags_tag_key: 1
google_tags_tag_value: 1
modules: 19
- resources: 109
+ resources: 112
diff --git a/tests/modules/cloud_run_v2/examples/service-vpc-access-connector-create-sharedvpc.yaml b/tests/modules/cloud_run_v2/examples/service-vpc-access-connector-create-sharedvpc.yaml
index 16efec979..77920dad3 100644
--- a/tests/modules/cloud_run_v2/examples/service-vpc-access-connector-create-sharedvpc.yaml
+++ b/tests/modules/cloud_run_v2/examples/service-vpc-access-connector-create-sharedvpc.yaml
@@ -53,6 +53,6 @@ counts:
google_cloud_run_v2_service: 1
google_vpc_access_connector: 1
modules: 4
- resources: 59
+ resources: 60
outputs: {}
diff --git a/tests/modules/net_vpc_factory/ncc.yaml b/tests/modules/net_vpc_factory/ncc.yaml
index 30a070167..4b45eb9e2 100644
--- a/tests/modules/net_vpc_factory/ncc.yaml
+++ b/tests/modules/net_vpc_factory/ncc.yaml
@@ -1,4 +1,4 @@
-# Copyright 2023 Google LLC
+# 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.
@@ -32,9 +32,9 @@ counts:
google_network_connectivity_hub: 1
google_network_connectivity_spoke: 3
google_project: 3
- google_project_iam_member: 21
+ google_project_iam_member: 24
google_project_service: 27
google_project_service_identity: 21
modules: 17
random_id: 3
- resources: 136
+ resources: 139
diff --git a/tests/modules/net_vpc_factory/only_projects.yaml b/tests/modules/net_vpc_factory/only_projects.yaml
index e67e4ff6d..bbe208a05 100644
--- a/tests/modules/net_vpc_factory/only_projects.yaml
+++ b/tests/modules/net_vpc_factory/only_projects.yaml
@@ -1,4 +1,4 @@
-# Copyright 2023 Google LLC
+# 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.
@@ -14,8 +14,8 @@
counts:
google_project: 3
- google_project_iam_member: 21
+ google_project_iam_member: 24
google_project_service: 27
google_project_service_identity: 21
modules: 3
- resources: 72
+ resources: 75
diff --git a/tests/modules/net_vpc_factory/peering.yaml b/tests/modules/net_vpc_factory/peering.yaml
index fd734ffcd..cd34a4f83 100644
--- a/tests/modules/net_vpc_factory/peering.yaml
+++ b/tests/modules/net_vpc_factory/peering.yaml
@@ -1,4 +1,4 @@
-# Copyright 2023 Google LLC
+# 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.
@@ -30,9 +30,9 @@ counts:
google_dns_policy: 4
google_dns_record_set: 1
google_project: 3
- google_project_iam_member: 21
+ google_project_iam_member: 24
google_project_service: 27
google_project_service_identity: 21
modules: 18
random_id: 3
- resources: 139
+ resources: 142
diff --git a/tests/modules/net_vpc_factory/separate_envs.yaml b/tests/modules/net_vpc_factory/separate_envs.yaml
index f3b1399a0..221d45505 100644
--- a/tests/modules/net_vpc_factory/separate_envs.yaml
+++ b/tests/modules/net_vpc_factory/separate_envs.yaml
@@ -1,4 +1,4 @@
-# Copyright 2023 Google LLC
+# 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.
@@ -27,9 +27,9 @@ counts:
google_compute_vpn_tunnel: 2
google_dns_policy: 2
google_project: 3
- google_project_iam_member: 21
+ google_project_iam_member: 24
google_project_service: 27
google_project_service_identity: 21
modules: 11
random_id: 4
- resources: 114
+ resources: 117
diff --git a/tests/modules/net_vpc_factory/vpn.yaml b/tests/modules/net_vpc_factory/vpn.yaml
index 7598121b7..c8d8b574e 100644
--- a/tests/modules/net_vpc_factory/vpn.yaml
+++ b/tests/modules/net_vpc_factory/vpn.yaml
@@ -1,4 +1,4 @@
-# Copyright 2023 Google LLC
+# 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.
@@ -29,9 +29,9 @@ counts:
google_dns_policy: 4
google_dns_record_set: 1
google_project: 3
- google_project_iam_member: 21
+ google_project_iam_member: 24
google_project_service: 27
google_project_service_identity: 21
modules: 22
random_id: 15
- resources: 175
+ resources: 178
diff --git a/tests/modules/project/examples/data.yaml b/tests/modules/project/examples/data.yaml
index 065a56aa0..1962876d1 100644
--- a/tests/modules/project/examples/data.yaml
+++ b/tests/modules/project/examples/data.yaml
@@ -516,7 +516,7 @@ counts:
google_project: 2
google_project_iam_audit_config: 2
google_project_iam_binding: 7
- google_project_iam_member: 14
+ google_project_iam_member: 15
google_project_service: 7
google_project_service_identity: 3
google_pubsub_topic: 1
@@ -525,7 +525,7 @@ counts:
google_storage_bucket_iam_member: 1
google_storage_project_service_account: 1
modules: 8
- resources: 63
+ resources: 64
outputs: {}
diff --git a/tests/modules/project_factory/examples/example.yaml b/tests/modules/project_factory/examples/example.yaml
index a69cf903d..2c487a7e4 100644
--- a/tests/modules/project_factory/examples/example.yaml
+++ b/tests/modules/project_factory/examples/example.yaml
@@ -145,6 +145,19 @@ values:
display_name: App 0
tags: null
timeouts: null
+ module.project-factory.module.folder-2["team-a/app-0"].google_org_policy_policy.default["compute.disableSerialPortAccess"]:
+ dry_run_spec: []
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: 'FALSE'
+ parameters: null
+ values: []
+ timeouts: null
module.project-factory.module.folder-2["team-b/app-0"].google_folder.folder[0]:
deletion_protection: false
display_name: App 0
@@ -438,6 +451,21 @@ values:
- ALL
parent: projects/test-pf-teams-iac-0
timeouts: null
+ module.project-factory.module.projects["teams-iac-0"].google_org_policy_policy.default["compute.disableSerialPortAccess"]:
+ dry_run_spec: []
+ name: projects/test-pf-teams-iac-0/policies/compute.disableSerialPortAccess
+ parent: projects/test-pf-teams-iac-0
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: 'FALSE'
+ parameters: null
+ values: []
+ timeouts: null
module.project-factory.module.projects["teams-iac-0"].google_project.project[0]:
auto_create_network: false
billing_account: 012345-67890A-BCDEF0
@@ -587,6 +615,7 @@ counts:
google_folder_iam_binding: 1
google_kms_crypto_key_iam_member: 2
google_monitoring_notification_channel: 1
+ google_org_policy_policy: 2
google_project: 4
google_project_iam_binding: 6
google_project_iam_member: 21
@@ -602,5 +631,5 @@ counts:
google_tags_tag_value: 2
google_tags_tag_value_iam_binding: 1
modules: 23
- resources: 86
+ resources: 88
terraform_data: 1
diff --git a/tools/build_service_agents.py b/tools/build_service_agents.py
index 55b4ff573..eddab41f6 100755
--- a/tools/build_service_agents.py
+++ b/tools/build_service_agents.py
@@ -58,6 +58,13 @@ IGNORED_AGENTS = [
'c-PROJECT_NUMBER-IDENTIFIER@gcp-sa-alloydb.iam.gserviceaccount.com'
]
+AGENT_NAME_OVERRIDE = {
+ # special case for Cloud Build that has two service agents:
+ # - %s@cloudbuild.gserviceaccount.com
+ # - service-%s@gcp-sa-cloudbuild.iam.gserviceaccount.com
+ 'PROJECT_NUMBER@cloudbuild.gserviceaccount.com': 'cloudbuild-sa',
+}
+
E2E_SERVICES = [
"alloydb.googleapis.com",
"analyticshub.googleapis.com",
@@ -112,7 +119,13 @@ class Agent:
@click.command()
@click.option('--e2e', is_flag=True, default=False)
-def main(e2e=False):
+@click.option('--organization', 'mode', flag_value='organization',
+ default=False, help='Extract organization-level service agents')
+@click.option('--folder', 'mode', flag_value='folder', default=False,
+ help='Extract folder-level service agents')
+@click.option('--project', 'mode', flag_value='project', default=False,
+ help='Extract project-level service agents')
+def main(mode, e2e=False):
page = requests.get(SERVICE_AGENTS_URL).content
soup = BeautifulSoup(page, 'html.parser')
agents = []
@@ -120,32 +133,58 @@ def main(e2e=False):
agent_text = content.get_text()
col1, col2 = content.find_all('td')
- # skip agents with more than one identity
+ # Extract all identities from col1 (could be in a single or multiple in a
)
+ identities = []
if col1.find('ul'):
+ # Multiple identities in a list
+ for li in col1.find_all('li'):
+ identities.append(li.get_text().strip())
+ elif col1.find('p'):
+ # Single identity
+ identities.append(col1.p.get_text().strip())
+
+ # Filter identities based on mode and find the matching one
+ identity = None
+ for id_candidate in identities:
+ if mode == 'project' and 'PROJECT_NUMBER' in id_candidate:
+ identity = id_candidate
+ break
+ elif mode == 'organization' and 'ORGANIZATION_NUMBER' in id_candidate:
+ identity = id_candidate
+ break
+ elif mode == 'folder' and 'FOLDER_NUMBER' in id_candidate:
+ identity = id_candidate
+ break
+ # Skip if no matching identity found for this mode
+ if not identity:
continue
- identity = col1.p.get_text()
- if identity in IGNORED_AGENTS:
+ if identity in IGNORED_AGENTS or '-IDENTIFIER' in identity:
continue
- # skip agents that are not contained in a project
- if 'PROJECT_NUMBER' not in identity:
- continue
-
- # special case for Cloud Build that has two service agents:
- # - %s@cloudbuild.gserviceaccount.com
- # - service-%s@gcp-sa-cloudbuild.iam.gserviceaccount.com
- if identity == 'PROJECT_NUMBER@cloudbuild.gserviceaccount.com':
- name = "cloudbuild-sa" # Cloud Build Service Account
+ if identity in AGENT_NAME_OVERRIDE:
+ name = AGENT_NAME_OVERRIDE[identity]
else:
# most service agents have the format
- # service-PROJECT_NUMBER@gcp-sa-SERVICE_NAME.iam.gserviceaccount.com.
+ # service-PROJECT_NUMBER@gcp-sa-SERVICE_NAME.iam.gserviceaccount.com
+ # or service-ORGANIZATION_NUMBER@gcp-sa-SERVICE_NAME.iam.gserviceaccount.com
+ # or service-FOLDER_NUMBER@gcp-sa-SERVICE_NAME.iam.gserviceaccount.com
# We keep the SERVICE_NAME part as the agent's name
name = identity.split('@')[1].split('.')[0]
name = name.removeprefix('gcp-sa-')
- identity = identity.replace('PROJECT_NUMBER', '${project_number}')
- identity = identity.replace('.iam.gserviceaccount.',
- '.${universe_domain}iam.gserviceaccount.')
+
+ # Replace identifiers based on mode
+ if mode == 'project':
+ identity = identity.replace('PROJECT_NUMBER', '${project_number}')
+ identity = identity.replace('.iam.gserviceaccount.',
+ '.${universe_domain}iam.gserviceaccount.')
+ elif mode == 'organization':
+ identity = identity.replace('ORGANIZATION_NUMBER',
+ '${organization_number}')
+ # Skip universe domain replacement for organization agents
+ elif mode == 'folder':
+ identity = identity.replace('FOLDER_NUMBER', '${folder_number}')
+ # Skip universe domain replacement for folder agents
if name == 'monitoring':
# monitoring is deprecated in favor of monitoring-notification.
@@ -163,7 +202,7 @@ def main(e2e=False):
aliases=ALIASES.get(name, []),
)
- if agent.name == 'cloudservices':
+ if mode == 'project' and agent.name == 'cloudservices':
# cloudservices role is granted automatically, we don't want to manage it
agent.role = None
@@ -175,6 +214,10 @@ def main(e2e=False):
aliases = set(chain.from_iterable(agent.aliases for agent in agents))
assert aliases.isdisjoint(names)
+ # ensure there are no aliases for folders or organization service agents
+ # mode \in [O, F] => empty(aliases)
+ assert mode not in ['organization', 'folder'] or len(aliases) == 0
+
if not e2e:
# take the header from the first lines of this file
header = open(__file__).readlines()[2:15]