diff --git a/fast/stages/0-org-setup/README.md b/fast/stages/0-org-setup/README.md
index 0e34b18d3..09433fc8f 100644
--- a/fast/stages/0-org-setup/README.md
+++ b/fast/stages/0-org-setup/README.md
@@ -745,9 +745,9 @@ Define values for the `var.environments` variable in a tfvars file.
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [context](variables.tf#L17) | Context-specific interpolations. | object({…}) | | {} |
-| [factories_config](variables.tf#L37) | Configuration for the resource factories or external data. | object({…}) | | {} |
-| [org_policies_imports](variables.tf#L52) | List of org policies to import. These need to also be defined in data files. | list(string) | | [] |
+| [context](variables.tf#L17) | Context-specific interpolations. | object({…}) | | {} |
+| [factories_config](variables.tf#L38) | Configuration for the resource factories or external data. | object({…}) | | {} |
+| [org_policies_imports](variables.tf#L53) | List of org policies to import. These need to also be defined in data files. | list(string) | | [] |
## Outputs
@@ -757,4 +757,3 @@ Define values for the `var.environments` variable in a tfvars file.
| [projects](outputs.tf#L22) | Attributes for managed projects. | |
| [tfvars](outputs.tf#L27) | Stage tfvars. | ✓ |
-
diff --git a/fast/stages/0-org-setup/datasets/classic/defaults.yaml b/fast/stages/0-org-setup/datasets/classic/defaults.yaml
index 91d030719..2c04275f2 100644
--- a/fast/stages/0-org-setup/datasets/classic/defaults.yaml
+++ b/fast/stages/0-org-setup/datasets/classic/defaults.yaml
@@ -33,6 +33,8 @@ projects:
overrides: {}
context:
# you can populate context variables here for use in YAML replacements
+ email_addresses:
+ gcp-organization-admins: gcp-organization-admins@example.com
iam_principals:
# this is the default group used in bootstrap, initial user must be a member
gcp-organization-admins: group:gcp-organization-admins@example.com
diff --git a/fast/stages/0-org-setup/datasets/classic/organization/.config.yaml b/fast/stages/0-org-setup/datasets/classic/organization/.config.yaml
index 65741b295..89aad3cc4 100644
--- a/fast/stages/0-org-setup/datasets/classic/organization/.config.yaml
+++ b/fast/stages/0-org-setup/datasets/classic/organization/.config.yaml
@@ -18,8 +18,8 @@
id: $defaults:organization/id
contacts:
- default:
- - $iam_principals:gcp-organization-admins
+ $email_addresses:gcp-organization-admins:
+ - ALL
# conditional authoritative IAM bindings
iam_bindings:
# these don't conflict with IAM / IAM by principal
diff --git a/fast/stages/0-org-setup/datasets/hardened/defaults.yaml b/fast/stages/0-org-setup/datasets/hardened/defaults.yaml
index 91d030719..2c04275f2 100644
--- a/fast/stages/0-org-setup/datasets/hardened/defaults.yaml
+++ b/fast/stages/0-org-setup/datasets/hardened/defaults.yaml
@@ -33,6 +33,8 @@ projects:
overrides: {}
context:
# you can populate context variables here for use in YAML replacements
+ email_addresses:
+ gcp-organization-admins: gcp-organization-admins@example.com
iam_principals:
# this is the default group used in bootstrap, initial user must be a member
gcp-organization-admins: group:gcp-organization-admins@example.com
diff --git a/fast/stages/0-org-setup/datasets/hardened/organization/.config.yaml b/fast/stages/0-org-setup/datasets/hardened/organization/.config.yaml
index 6de4656d2..852aa089c 100644
--- a/fast/stages/0-org-setup/datasets/hardened/organization/.config.yaml
+++ b/fast/stages/0-org-setup/datasets/hardened/organization/.config.yaml
@@ -18,8 +18,8 @@
id: $defaults:organization/id
contacts:
- default:
- - $iam_principals:gcp-organization-admins
+ $email_addresses:gcp-organization-admins:
+ - ALL
# conditional authoritative IAM bindings
iam_bindings:
# these don't conflict with IAM / IAM by principal
diff --git a/fast/stages/0-org-setup/schemas/defaults.schema.json b/fast/stages/0-org-setup/schemas/defaults.schema.json
index 9720eb96b..7e869f5d4 100644
--- a/fast/stages/0-org-setup/schemas/defaults.schema.json
+++ b/fast/stages/0-org-setup/schemas/defaults.schema.json
@@ -538,6 +538,11 @@
"type": "string"
}
},
+ "email_addresses": {
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
"folder_ids": {
"type": "object",
"additionalProperties": {
diff --git a/fast/stages/0-org-setup/schemas/folder.schema.json b/fast/stages/0-org-setup/schemas/folder.schema.json
index 778dc0572..076464aca 100644
--- a/fast/stages/0-org-setup/schemas/folder.schema.json
+++ b/fast/stages/0-org-setup/schemas/folder.schema.json
@@ -64,6 +64,27 @@
}
}
},
+ "contacts": {
+ "type": "object",
+ "additionalProperties": false,
+ "patternProperties": {
+ "^(\\S+@\\S+\\.\\S+|\\$email_addresses:\\S+)$": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "ALL",
+ "BILLING",
+ "LEGAL",
+ "SECURITY",
+ "PRODUCT_UPDATES",
+ "SUSPENSION",
+ "TECHNICAL"
+ ]
+ }
+ }
+ }
+ },
"factories_config": {
"type": "object",
"additionalProperties": false,
@@ -552,4 +573,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/fast/stages/0-org-setup/schemas/organization.schema.json b/fast/stages/0-org-setup/schemas/organization.schema.json
index c0afbdeff..349861e87 100644
--- a/fast/stages/0-org-setup/schemas/organization.schema.json
+++ b/fast/stages/0-org-setup/schemas/organization.schema.json
@@ -11,10 +11,19 @@
"type": "object",
"additionalProperties": false,
"patternProperties": {
- "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$": {
+ "^(\\S+@\\S+\\.\\S+|\\$email_addresses:\\S+)$": {
"type": "array",
"items": {
- "type": "string"
+ "type": "string",
+ "enum": [
+ "ALL",
+ "BILLING",
+ "LEGAL",
+ "SECURITY",
+ "PRODUCT_UPDATES",
+ "SUSPENSION",
+ "TECHNICAL"
+ ]
}
}
}
diff --git a/fast/stages/0-org-setup/schemas/project.schema.json b/fast/stages/0-org-setup/schemas/project.schema.json
index 228c10c06..0df37926b 100644
--- a/fast/stages/0-org-setup/schemas/project.schema.json
+++ b/fast/stages/0-org-setup/schemas/project.schema.json
@@ -80,10 +80,19 @@
"type": "object",
"additionalProperties": false,
"patternProperties": {
- "^[a-z0-9_-]+$": {
+ "^(\\S+@\\S+\\.\\S+|\\$email_addresses:\\S+)$": {
"type": "array",
"items": {
- "type": "string"
+ "type": "string",
+ "enum": [
+ "ALL",
+ "BILLING",
+ "LEGAL",
+ "SECURITY",
+ "PRODUCT_UPDATES",
+ "SUSPENSION",
+ "TECHNICAL"
+ ]
}
}
}
@@ -1005,4 +1014,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/fast/stages/0-org-setup/variables.tf b/fast/stages/0-org-setup/variables.tf
index 99b2057ae..450c658f4 100644
--- a/fast/stages/0-org-setup/variables.tf
+++ b/fast/stages/0-org-setup/variables.tf
@@ -18,6 +18,7 @@ variable "context" {
description = "Context-specific interpolations."
type = object({
custom_roles = optional(map(string), {})
+ email_addresses = optional(map(string), {})
folder_ids = optional(map(string), {})
iam_principals = optional(map(string), {})
locations = optional(map(string), {})
diff --git a/fast/stages/0-org-setup/wif-providers.tf b/fast/stages/0-org-setup/wif-providers.tf
index f2b3278b6..76b664b12 100644
--- a/fast/stages/0-org-setup/wif-providers.tf
+++ b/fast/stages/0-org-setup/wif-providers.tf
@@ -52,7 +52,7 @@ resource "google_iam_workload_identity_pool_provider" "default" {
# If users don't provide an issuer_uri, we set the public one for the platform chosen.
issuer_uri = coalesce(
try(each.value.custom_settings.issuer_uri, null),
- try(each.value.custom_settings.okta == null ? null : "https://${each.value.custom_settings.okta.organization_name}/oauth2/${each.value.custom_settings.okta.auth_server_name}", null),
+ try("https://${each.value.custom_settings.okta.organization_name}/oauth2/${each.value.custom_settings.okta.auth_server_name}", null),
try(each.value.issuer_uri, null),
)
# OIDC JWKs in JSON String format. If no value is provided, they key is
diff --git a/fast/stages/2-networking/README.md b/fast/stages/2-networking/README.md
index 72153c266..2079e86dc 100644
--- a/fast/stages/2-networking/README.md
+++ b/fast/stages/2-networking/README.md
@@ -316,9 +316,9 @@ Internally created resources are mapped to context namespaces, and use specific
| [billing_account](variables-fast.tf#L17) | Billing account id. | object({…}) | ✓ | |
| [organization](variables-fast.tf#L49) | Organization details. | object({…}) | ✓ | |
| [prefix](variables-fast.tf#L66) | Prefix used for resources that need unique names. Use a maximum of 9 chars for organizations, and 11 chars for tenants. | string | ✓ | |
-| [context](variables.tf#L17) | Context-specific interpolations. | object({…}) | | {} |
+| [context](variables.tf#L17) | Context-specific interpolations. | object({…}) | | {} |
| [custom_roles](variables-fast.tf#L25) | Custom roles defined at the org level, in key => id format. | map(string) | | {} |
-| [factories_config](variables.tf#L36) | Configuration for the resource factories or external data. | object({…}) | | {} |
+| [factories_config](variables.tf#L37) | Configuration for the resource factories or external data. | object({…}) | | {} |
| [folder_ids](variables-fast.tf#L33) | Folders created in the bootstrap stage. | map(string) | | {} |
| [iam_principals](variables-fast.tf#L41) | IAM-format principals. | map(string) | | {} |
| [perimeters](variables-fast.tf#L58) | Optional VPC-SC perimeter ids. | map(string) | | {} |
@@ -327,7 +327,7 @@ Internally created resources are mapped to context namespaces, and use specific
| [storage_buckets](variables-fast.tf#L92) | Storage buckets created in the bootstrap stage. | map(string) | | {} |
| [tag_keys](variables-fast.tf#L100) | FAST-managed resource manager tag keys. | map(string) | | {} |
| [tag_values](variables-fast.tf#L108) | FAST-managed resource manager tag values. | map(string) | | {} |
-| [universe](variables.tf#L53) | GCP universe where to deploy projects. The prefix will be prepended to the project id. | object({…}) | | null |
+| [universe](variables.tf#L54) | GCP universe where to deploy projects. The prefix will be prepended to the project id. | object({…}) | | null |
## Outputs
diff --git a/fast/stages/2-networking/schemas/defaults.schema.json b/fast/stages/2-networking/schemas/defaults.schema.json
index 89f6400df..4eedc6e68 100644
--- a/fast/stages/2-networking/schemas/defaults.schema.json
+++ b/fast/stages/2-networking/schemas/defaults.schema.json
@@ -549,6 +549,11 @@
"type": "string"
}
},
+ "email_addresses": {
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
"folder_ids": {
"type": "object",
"additionalProperties": {
diff --git a/fast/stages/2-networking/schemas/folder.schema.json b/fast/stages/2-networking/schemas/folder.schema.json
index 778dc0572..076464aca 100644
--- a/fast/stages/2-networking/schemas/folder.schema.json
+++ b/fast/stages/2-networking/schemas/folder.schema.json
@@ -64,6 +64,27 @@
}
}
},
+ "contacts": {
+ "type": "object",
+ "additionalProperties": false,
+ "patternProperties": {
+ "^(\\S+@\\S+\\.\\S+|\\$email_addresses:\\S+)$": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "ALL",
+ "BILLING",
+ "LEGAL",
+ "SECURITY",
+ "PRODUCT_UPDATES",
+ "SUSPENSION",
+ "TECHNICAL"
+ ]
+ }
+ }
+ }
+ },
"factories_config": {
"type": "object",
"additionalProperties": false,
@@ -552,4 +573,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/fast/stages/2-networking/schemas/project.schema.json b/fast/stages/2-networking/schemas/project.schema.json
index 228c10c06..0df37926b 100644
--- a/fast/stages/2-networking/schemas/project.schema.json
+++ b/fast/stages/2-networking/schemas/project.schema.json
@@ -80,10 +80,19 @@
"type": "object",
"additionalProperties": false,
"patternProperties": {
- "^[a-z0-9_-]+$": {
+ "^(\\S+@\\S+\\.\\S+|\\$email_addresses:\\S+)$": {
"type": "array",
"items": {
- "type": "string"
+ "type": "string",
+ "enum": [
+ "ALL",
+ "BILLING",
+ "LEGAL",
+ "SECURITY",
+ "PRODUCT_UPDATES",
+ "SUSPENSION",
+ "TECHNICAL"
+ ]
}
}
}
@@ -1005,4 +1014,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/fast/stages/2-networking/variables.tf b/fast/stages/2-networking/variables.tf
index 47b47f910..1fc182d39 100644
--- a/fast/stages/2-networking/variables.tf
+++ b/fast/stages/2-networking/variables.tf
@@ -19,6 +19,7 @@ variable "context" {
type = object({
cidr_ranges_sets = optional(map(list(string)), {})
custom_roles = optional(map(string), {})
+ email_addresses = optional(map(string), {})
folder_ids = optional(map(string), {})
kms_keys = optional(map(string), {})
iam_principals = optional(map(string), {})
diff --git a/fast/stages/2-project-factory/README.md b/fast/stages/2-project-factory/README.md
index 53953a04a..e1b28cff8 100644
--- a/fast/stages/2-project-factory/README.md
+++ b/fast/stages/2-project-factory/README.md
@@ -481,12 +481,12 @@ Pattern-based files make specific assumptions:
| [automation](variables-fast.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-org-setup |
| [billing_account](variables-fast.tf#L26) | Billing account id. | object({…}) | ✓ | | 0-org-setup |
| [prefix](variables-fast.tf#L82) | Prefix used for resources that need unique names. Use a maximum of 9 chars for organizations, and 11 chars for tenants. | string | ✓ | | 0-org-setup |
-| [context](variables.tf#L17) | Context-specific interpolations. | object({…}) | | {} | |
+| [context](variables.tf#L17) | Context-specific interpolations. | object({…}) | | {} | |
| [custom_roles](variables-fast.tf#L34) | Custom roles defined at the org level, in key => id format. | map(string) | | {} | 0-org-setup |
| [data_defaults](variables-projects.tf#L17) | Optional default values used when corresponding project or folder data from files are missing. | object({…}) | | {} | |
| [data_merges](variables-projects.tf#L93) | Optional values that will be merged with corresponding data from files. Combines with `data_defaults`, file data, and `data_overrides`. | object({…}) | | {} | |
| [data_overrides](variables-projects.tf#L112) | Optional values that override corresponding data from files. Takes precedence over file data and `data_defaults`. | object({…}) | | {} | |
-| [factories_config](variables.tf#L36) | Path to folder with YAML resource description data files. | object({…}) | | {} | |
+| [factories_config](variables.tf#L37) | Path to folder with YAML resource description data files. | object({…}) | | {} | |
| [folder_ids](variables-fast.tf#L42) | Folders created in the bootstrap stage. | map(string) | | {} | 0-org-setup |
| [host_project_ids](variables-fast.tf#L58) | Host project for the shared VPC. | map(string) | | {} | 2-networking |
| [iam_principals](variables-fast.tf#L50) | IAM-format principals. | map(string) | | {} | 0-org-setup |
@@ -494,7 +494,7 @@ Pattern-based files make specific assumptions:
| [perimeters](variables-fast.tf#L74) | Optional VPC-SC perimeter ids. | map(string) | | {} | 1-vpcsc |
| [project_ids](variables-fast.tf#L92) | Projects created in the bootstrap stage. | map(string) | | {} | 0-org-setup |
| [service_accounts](variables-fast.tf#L100) | Service accounts created in the bootstrap stage. | map(string) | | {} | 0-org-setup |
-| [stage_name](variables.tf#L57) | FAST stage name. Used to separate output files across different factories. | string | | "2-project-factory" | |
+| [stage_name](variables.tf#L58) | FAST stage name. Used to separate output files across different factories. | string | | "2-project-factory" | |
| [subnet_self_links](variables-fast.tf#L108) | Shared VPC subnet IDs. | map(map(string)) | | {} | 2-networking |
| [tag_values](variables-fast.tf#L116) | FAST-managed resource manager tag values. | map(string) | | {} | 0-org-setup |
| [universe](variables-fast.tf#L124) | GCP universe where to deploy projects. The prefix will be prepended to the project id. | object({…}) | | null | 0-org-setup |
diff --git a/fast/stages/2-project-factory/schemas/defaults.schema.json b/fast/stages/2-project-factory/schemas/defaults.schema.json
index 29b4ab2d8..d0ef30e99 100644
--- a/fast/stages/2-project-factory/schemas/defaults.schema.json
+++ b/fast/stages/2-project-factory/schemas/defaults.schema.json
@@ -572,6 +572,11 @@
"type": "string"
}
},
+ "email_addresses": {
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
"folder_ids": {
"type": "object",
"additionalProperties": {
diff --git a/fast/stages/2-project-factory/schemas/folder.schema.json b/fast/stages/2-project-factory/schemas/folder.schema.json
deleted file mode 120000
index d58a2759b..000000000
--- a/fast/stages/2-project-factory/schemas/folder.schema.json
+++ /dev/null
@@ -1 +0,0 @@
-../../../../modules/project-factory/schemas/folder.schema.json
\ No newline at end of file
diff --git a/fast/stages/2-project-factory/schemas/folder.schema.json b/fast/stages/2-project-factory/schemas/folder.schema.json
new file mode 100644
index 000000000..076464aca
--- /dev/null
+++ b/fast/stages/2-project-factory/schemas/folder.schema.json
@@ -0,0 +1,576 @@
+{
+ "$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"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "contacts": {
+ "type": "object",
+ "additionalProperties": false,
+ "patternProperties": {
+ "^(\\S+@\\S+\\.\\S+|\\$email_addresses:\\S+)$": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "ALL",
+ "BILLING",
+ "LEGAL",
+ "SECURITY",
+ "PRODUCT_UPDATES",
+ "SUSPENSION",
+ "TECHNICAL"
+ ]
+ }
+ }
+ }
+ },
+ "factories_config": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "org_policies": {
+ "type": "string"
+ },
+ "pam_entitlements": {
+ "type": "string"
+ },
+ "scc_sha_custom_modules": {
+ "type": "string"
+ }
+ }
+ },
+ "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"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "pam_entitlements": {
+ "$ref": "#/$defs/pam_entitlements"
+ },
+ "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"
+ }
+ }
+ }
+ },
+ "pam_entitlements": {
+ "type": "object",
+ "additionalProperties": false,
+ "patternProperties": {
+ "^[a-z][a-z0-9-]{0,61}[a-z0-9]$": {
+ "type": "object",
+ "properties": {
+ "max_request_duration": {
+ "type": "string"
+ },
+ "eligible_users": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "privileged_access": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "role": {
+ "type": "string"
+ },
+ "condition": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "role"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "requester_justification_config": {
+ "type": "object",
+ "properties": {
+ "not_mandatory": {
+ "type": "boolean"
+ },
+ "unstructured": {
+ "type": "boolean"
+ }
+ },
+ "additionalProperties": false
+ },
+ "manual_approvals": {
+ "type": "object",
+ "properties": {
+ "require_approver_justification": {
+ "type": "boolean"
+ },
+ "steps": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "approvers": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "approvals_needed": {
+ "type": "number"
+ },
+ "approver_email_recipients": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "approvers"
+ ],
+ "additionalProperties": false
+ }
+ }
+ },
+ "required": [
+ "require_approver_justification",
+ "steps"
+ ],
+ "additionalProperties": false
+ },
+ "additional_notification_targets": {
+ "type": "object",
+ "properties": {
+ "admin_email_recipients": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "requester_email_recipients": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "required": [
+ "max_request_duration",
+ "eligible_users",
+ "privileged_access"
+ ],
+ "additionalProperties": false
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/fast/stages/2-project-factory/schemas/project.schema.json b/fast/stages/2-project-factory/schemas/project.schema.json
deleted file mode 120000
index 11f161f17..000000000
--- a/fast/stages/2-project-factory/schemas/project.schema.json
+++ /dev/null
@@ -1 +0,0 @@
-../../../../modules/project-factory/schemas/project.schema.json
\ No newline at end of file
diff --git a/fast/stages/2-project-factory/schemas/project.schema.json b/fast/stages/2-project-factory/schemas/project.schema.json
new file mode 100644
index 000000000..0df37926b
--- /dev/null
+++ b/fast/stages/2-project-factory/schemas/project.schema.json
@@ -0,0 +1,1017 @@
+{
+ "$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": {
+ "^(\\S+@\\S+\\.\\S+|\\$email_addresses:\\S+)$": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "ALL",
+ "BILLING",
+ "LEGAL",
+ "SECURITY",
+ "PRODUCT_UPDATES",
+ "SUSPENSION",
+ "TECHNICAL"
+ ]
+ }
+ }
+ }
+ },
+ "datasets": {
+ "type": "object",
+ "additionalProperties": false,
+ "patternProperties": {
+ "^[a-z0-9_]+$": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "friendly_name": {
+ "type": "string"
+ },
+ "location": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ "deletion_policy": {
+ "type": "string",
+ "enum": [
+ "PREVENT",
+ "DELETE",
+ "ABANDON"
+ ]
+ },
+ "factories_config": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "custom_roles": {
+ "type": "string"
+ },
+ "observability": {
+ "type": "string"
+ },
+ "org_policies": {
+ "type": "string"
+ },
+ "org_policies": {
+ "type": "string"
+ },
+ "quotas": {
+ "type": "string"
+ },
+ "scc_sha_custom_modules": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "string"
+ }
+ }
+ },
+ "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"
+ },
+ "iam_by_principals_additive": {
+ "$ref": "#/$defs/iam_by_principals"
+ },
+ "labels": {
+ "type": "object"
+ },
+ "pam_entitlements": {
+ "$ref": "#/$defs/pam_entitlements"
+ },
+ "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"
+ }
+ }
+ }
+ },
+ "$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"
+ }
+ }
+ },
+ "pam_entitlements": {
+ "type": "object",
+ "additionalProperties": false,
+ "patternProperties": {
+ "^[a-z][a-z0-9-]{0,61}[a-z0-9]$": {
+ "type": "object",
+ "properties": {
+ "max_request_duration": {
+ "type": "string"
+ },
+ "eligible_users": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "privileged_access": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "role": {
+ "type": "string"
+ },
+ "condition": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "role"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "requester_justification_config": {
+ "type": "object",
+ "properties": {
+ "not_mandatory": {
+ "type": "boolean"
+ },
+ "unstructured": {
+ "type": "boolean"
+ }
+ },
+ "additionalProperties": false
+ },
+ "manual_approvals": {
+ "type": "object",
+ "properties": {
+ "require_approver_justification": {
+ "type": "boolean"
+ },
+ "steps": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "approvers": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "approvals_needed": {
+ "type": "number"
+ },
+ "approver_email_recipients": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "approvers"
+ ],
+ "additionalProperties": false
+ }
+ }
+ },
+ "required": [
+ "require_approver_justification",
+ "steps"
+ ],
+ "additionalProperties": false
+ },
+ "additional_notification_targets": {
+ "type": "object",
+ "properties": {
+ "admin_email_recipients": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "requester_email_recipients": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "required": [
+ "max_request_duration",
+ "eligible_users",
+ "privileged_access"
+ ],
+ "additionalProperties": false
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/fast/stages/2-project-factory/variables.tf b/fast/stages/2-project-factory/variables.tf
index df0105199..c4aab0cb5 100644
--- a/fast/stages/2-project-factory/variables.tf
+++ b/fast/stages/2-project-factory/variables.tf
@@ -19,6 +19,7 @@ variable "context" {
type = object({
condition_vars = optional(map(map(string)), {})
custom_roles = optional(map(string), {})
+ email_addresses = optional(map(string), {})
folder_ids = optional(map(string), {})
iam_principals = optional(map(string), {})
kms_keys = optional(map(string), {})
diff --git a/fast/stages/2-security/README.md b/fast/stages/2-security/README.md
index ebd1a475e..7b46a0d30 100644
--- a/fast/stages/2-security/README.md
+++ b/fast/stages/2-security/README.md
@@ -183,9 +183,9 @@ A reference Certificate Authority Services (CAS) is also part of this stage, all
|---|---|:---:|:---:|:---:|:---:|
| [billing_account](variables-fast.tf#L17) | Billing account id. | object({…}) | ✓ | | 0-org-setup |
| [prefix](variables-fast.tf#L57) | Prefix used for resources that need unique names. Use a maximum of 9 chars for organizations, and 11 chars for tenants. | string | ✓ | | 0-org-setup |
-| [context](variables.tf#L17) | Context-specific interpolations. | object({…}) | | {} | |
+| [context](variables.tf#L17) | Context-specific interpolations. | object({…}) | | {} | |
| [custom_roles](variables-fast.tf#L25) | Custom roles defined at the org level, in key => id format. | map(string) | | {} | 0-org-setup |
-| [factories_config](variables.tf#L35) | Configuration for the resource factories or external data. | object({…}) | | {} | |
+| [factories_config](variables.tf#L36) | Configuration for the resource factories or external data. | object({…}) | | {} | |
| [folder_ids](variables-fast.tf#L33) | Folders created in the bootstrap stage. | map(string) | | {} | 0-org-setup |
| [iam_principals](variables-fast.tf#L41) | IAM-format principals. | map(string) | | {} | 0-org-setup |
| [perimeters](variables-fast.tf#L49) | Optional VPC-SC perimeter ids. | map(string) | | {} | 1-vpcsc |
diff --git a/fast/stages/2-security/schemas/defaults.schema.json b/fast/stages/2-security/schemas/defaults.schema.json
index 92a0fe849..605e999f2 100644
--- a/fast/stages/2-security/schemas/defaults.schema.json
+++ b/fast/stages/2-security/schemas/defaults.schema.json
@@ -522,6 +522,12 @@
"type": "string"
}
},
+ "email_addresses": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
"folder_ids": {
"type": "object",
"additionalProperties": {
diff --git a/fast/stages/2-security/schemas/folder.schema.json b/fast/stages/2-security/schemas/folder.schema.json
new file mode 100644
index 000000000..076464aca
--- /dev/null
+++ b/fast/stages/2-security/schemas/folder.schema.json
@@ -0,0 +1,576 @@
+{
+ "$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"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "contacts": {
+ "type": "object",
+ "additionalProperties": false,
+ "patternProperties": {
+ "^(\\S+@\\S+\\.\\S+|\\$email_addresses:\\S+)$": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "ALL",
+ "BILLING",
+ "LEGAL",
+ "SECURITY",
+ "PRODUCT_UPDATES",
+ "SUSPENSION",
+ "TECHNICAL"
+ ]
+ }
+ }
+ }
+ },
+ "factories_config": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "org_policies": {
+ "type": "string"
+ },
+ "pam_entitlements": {
+ "type": "string"
+ },
+ "scc_sha_custom_modules": {
+ "type": "string"
+ }
+ }
+ },
+ "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"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "pam_entitlements": {
+ "$ref": "#/$defs/pam_entitlements"
+ },
+ "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"
+ }
+ }
+ }
+ },
+ "pam_entitlements": {
+ "type": "object",
+ "additionalProperties": false,
+ "patternProperties": {
+ "^[a-z][a-z0-9-]{0,61}[a-z0-9]$": {
+ "type": "object",
+ "properties": {
+ "max_request_duration": {
+ "type": "string"
+ },
+ "eligible_users": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "privileged_access": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "role": {
+ "type": "string"
+ },
+ "condition": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "role"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "requester_justification_config": {
+ "type": "object",
+ "properties": {
+ "not_mandatory": {
+ "type": "boolean"
+ },
+ "unstructured": {
+ "type": "boolean"
+ }
+ },
+ "additionalProperties": false
+ },
+ "manual_approvals": {
+ "type": "object",
+ "properties": {
+ "require_approver_justification": {
+ "type": "boolean"
+ },
+ "steps": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "approvers": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "approvals_needed": {
+ "type": "number"
+ },
+ "approver_email_recipients": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "approvers"
+ ],
+ "additionalProperties": false
+ }
+ }
+ },
+ "required": [
+ "require_approver_justification",
+ "steps"
+ ],
+ "additionalProperties": false
+ },
+ "additional_notification_targets": {
+ "type": "object",
+ "properties": {
+ "admin_email_recipients": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "requester_email_recipients": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "required": [
+ "max_request_duration",
+ "eligible_users",
+ "privileged_access"
+ ],
+ "additionalProperties": false
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/fast/stages/2-security/schemas/project.schema.json b/fast/stages/2-security/schemas/project.schema.json
index 228c10c06..0df37926b 100644
--- a/fast/stages/2-security/schemas/project.schema.json
+++ b/fast/stages/2-security/schemas/project.schema.json
@@ -80,10 +80,19 @@
"type": "object",
"additionalProperties": false,
"patternProperties": {
- "^[a-z0-9_-]+$": {
+ "^(\\S+@\\S+\\.\\S+|\\$email_addresses:\\S+)$": {
"type": "array",
"items": {
- "type": "string"
+ "type": "string",
+ "enum": [
+ "ALL",
+ "BILLING",
+ "LEGAL",
+ "SECURITY",
+ "PRODUCT_UPDATES",
+ "SUSPENSION",
+ "TECHNICAL"
+ ]
}
}
}
@@ -1005,4 +1014,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/fast/stages/2-security/variables.tf b/fast/stages/2-security/variables.tf
index cbe98a1a7..d3e290109 100644
--- a/fast/stages/2-security/variables.tf
+++ b/fast/stages/2-security/variables.tf
@@ -18,6 +18,7 @@ variable "context" {
description = "Context-specific interpolations."
type = object({
condition_vars = optional(map(map(string)), {})
+ email_addresses = optional(map(string), {})
custom_roles = optional(map(string), {})
folder_ids = optional(map(string), {})
iam_principals = optional(map(string), {})
diff --git a/modules/folder/README.md b/modules/folder/README.md
index 7a134aab3..d78448df8 100644
--- a/modules/folder/README.md
+++ b/modules/folder/README.md
@@ -541,27 +541,27 @@ module "folder" {
|---|---|:---:|:---:|:---:|
| [assured_workload_config](variables.tf#L17) | Create AssuredWorkloads folder instead of regular folder when value is provided. Incompatible with folder_create=false. | object({…}) | | null |
| [contacts](variables.tf#L70) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | map(list(string)) | | {} |
-| [context](variables.tf#L78) | Context-specific interpolations. | object({…}) | | {} |
-| [deletion_protection](variables.tf#L91) | Deletion protection setting for this folder. | bool | | false |
-| [factories_config](variables.tf#L97) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} |
-| [firewall_policy](variables.tf#L108) | Hierarchical firewall policy to associate to this folder. | object({…}) | | null |
-| [folder_create](variables.tf#L117) | Create folder. When set to false, uses id to reference an existing folder. | bool | | true |
+| [context](variables.tf#L89) | Context-specific interpolations. | object({…}) | | {} |
+| [deletion_protection](variables.tf#L103) | Deletion protection setting for this folder. | bool | | false |
+| [factories_config](variables.tf#L109) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} |
+| [firewall_policy](variables.tf#L120) | Hierarchical firewall policy to associate to this folder. | object({…}) | | null |
+| [folder_create](variables.tf#L129) | Create folder. When set to false, uses id to reference an existing folder. | bool | | true |
| [iam](variables-iam.tf#L17) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} |
| [iam_bindings](variables-iam.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) | | {} |
| [iam_bindings_additive](variables-iam.tf#L39) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} |
| [iam_by_principals](variables-iam.tf#L61) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam` variable. | map(list(string)) | | {} |
| [iam_by_principals_additive](variables-iam.tf#L54) | Additive IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam_bindings_additive` variable. | map(list(string)) | | {} |
-| [id](variables.tf#L127) | Folder ID in case you use folder_create=false. | string | | null |
+| [id](variables.tf#L139) | Folder ID in case you use folder_create=false. | string | | null |
| [logging_data_access](variables-logging.tf#L17) | Control activation of data access logs. The special 'allServices' key denotes configuration for all services. | map(object({…})) | | {} |
| [logging_exclusions](variables-logging.tf#L28) | Logging exclusions for this folder in the form {NAME -> FILTER}. | map(string) | | {} |
| [logging_settings](variables-logging.tf#L35) | Default settings for logging resources. | object({…}) | | null |
| [logging_sinks](variables-logging.tf#L45) | Logging sinks to create for the folder. | map(object({…})) | | {} |
-| [name](variables.tf#L133) | Folder name. | string | | null |
-| [org_policies](variables.tf#L139) | Organization policies applied to this folder keyed by policy name. | map(object({…})) | | {} |
+| [name](variables.tf#L145) | Folder name. | string | | null |
+| [org_policies](variables.tf#L151) | Organization policies applied to this folder keyed by policy name. | map(object({…})) | | {} |
| [pam_entitlements](variables-pam.tf#L17) | Privileged Access Manager entitlements for this resource, keyed by entitlement ID. | map(object({…})) | | {} |
-| [parent](variables.tf#L167) | Parent in folders/folder_id or organizations/org_id format. | string | | null |
+| [parent](variables.tf#L179) | Parent in folders/folder_id or organizations/org_id format. | string | | null |
| [scc_sha_custom_modules](variables-scc.tf#L17) | SCC custom modules keyed by module name. | map(object({…})) | | {} |
-| [tag_bindings](variables.tf#L181) | Tag bindings for this folder, in key => tag value id format. | map(string) | | null |
+| [tag_bindings](variables.tf#L193) | Tag bindings for this folder, in key => tag value id format. | map(string) | | null |
## Outputs
diff --git a/modules/folder/main.tf b/modules/folder/main.tf
index cb788fd32..ecdd45453 100644
--- a/modules/folder/main.tf
+++ b/modules/folder/main.tf
@@ -51,10 +51,12 @@ resource "google_folder" "folder" {
}
resource "google_essential_contacts_contact" "contact" {
- provider = google-beta
- for_each = var.contacts
- parent = local.folder_id
- email = each.key
+ provider = google-beta
+ for_each = var.contacts
+ parent = local.folder_id
+ email = lookup(
+ local.ctx.email_addresses, each.key, each.key
+ )
language_tag = "en"
notification_category_subscriptions = each.value
depends_on = [
diff --git a/modules/folder/variables.tf b/modules/folder/variables.tf
index 7cfc941d8..edb800b0a 100644
--- a/modules/folder/variables.tf
+++ b/modules/folder/variables.tf
@@ -72,17 +72,29 @@ variable "contacts" {
type = map(list(string))
default = {}
nullable = false
+ validation {
+ condition = alltrue(flatten([
+ for k, v in var.contacts : [
+ for vv in v : contains([
+ "ALL", "SUSPENSION", "SECURITY", "TECHNICAL", "BILLING", "LEGAL",
+ "PRODUCT_UPDATES"
+ ], vv)
+ ]
+ ]))
+ error_message = "Invalid contact notification value."
+ }
}
variable "context" {
description = "Context-specific interpolations."
type = object({
- condition_vars = optional(map(map(string)), {})
- custom_roles = optional(map(string), {})
- folder_ids = optional(map(string), {})
- iam_principals = optional(map(string), {})
- tag_values = optional(map(string), {})
+ condition_vars = optional(map(map(string)), {})
+ custom_roles = optional(map(string), {})
+ email_addresses = optional(map(string), {})
+ folder_ids = optional(map(string), {})
+ iam_principals = optional(map(string), {})
+ tag_values = optional(map(string), {})
})
default = {}
nullable = false
diff --git a/modules/organization/README.md b/modules/organization/README.md
index 45af6eecd..3c48e543c 100644
--- a/modules/organization/README.md
+++ b/modules/organization/README.md
@@ -736,12 +736,12 @@ values:
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [organization_id](variables.tf#L115) | Organization id in organizations/nnnnnn format. | string | ✓ | |
+| [organization_id](variables.tf#L127) | Organization id in organizations/nnnnnn format. | string | ✓ | |
| [contacts](variables.tf#L17) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | map(list(string)) | | {} |
-| [context](variables.tf#L24) | Context-specific interpolations. | object({…}) | | {} |
-| [custom_roles](variables.tf#L43) | Map of role name => list of permissions to create in this project. | map(list(string)) | | {} |
-| [factories_config](variables.tf#L50) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} |
-| [firewall_policy](variables.tf#L64) | Hierarchical firewall policies to associate to the organization. | object({…}) | | null |
+| [context](variables.tf#L35) | Context-specific interpolations. | object({…}) | | {} |
+| [custom_roles](variables.tf#L55) | Map of role name => list of permissions to create in this project. | map(list(string)) | | {} |
+| [factories_config](variables.tf#L62) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} |
+| [firewall_policy](variables.tf#L76) | Hierarchical firewall policies to associate to the organization. | object({…}) | | null |
| [iam](variables-iam.tf#L17) | Authoritative IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} |
| [iam_bindings](variables-iam.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) | | {} |
| [iam_bindings_additive](variables-iam.tf#L39) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} |
@@ -752,8 +752,8 @@ values:
| [logging_settings](variables-logging.tf#L35) | Default settings for logging resources. | object({…}) | | null |
| [logging_sinks](variables-logging.tf#L45) | Logging sinks to create for the organization. | map(object({…})) | | {} |
| [network_tags](variables-tags.tf#L17) | Network tags by key name. If `id` is provided, key creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} |
-| [org_policies](variables.tf#L73) | Organization policies applied to this organization keyed by policy name. | map(object({…})) | | {} |
-| [org_policy_custom_constraints](variables.tf#L101) | Organization policy custom constraints keyed by constraint name. | map(object({…})) | | {} |
+| [org_policies](variables.tf#L85) | Organization policies applied to this organization keyed by policy name. | map(object({…})) | | {} |
+| [org_policy_custom_constraints](variables.tf#L113) | Organization policy custom constraints keyed by constraint name. | map(object({…})) | | {} |
| [pam_entitlements](variables-pam.tf#L17) | Privileged Access Manager entitlements for this resource, keyed by entitlement ID. | map(object({…})) | | {} |
| [scc_sha_custom_modules](variables-scc.tf#L17) | SCC custom modules keyed by module name. | map(object({…})) | | {} |
| [tag_bindings](variables-tags.tf#L82) | Tag bindings for this organization, in key => tag value id format. | map(string) | | {} |
diff --git a/modules/organization/main.tf b/modules/organization/main.tf
index 5b8f341d3..a46129be5 100644
--- a/modules/organization/main.tf
+++ b/modules/organization/main.tf
@@ -25,10 +25,12 @@ locals {
}
resource "google_essential_contacts_contact" "contact" {
- provider = google-beta
- for_each = var.contacts
- parent = var.organization_id
- email = each.key
+ provider = google-beta
+ for_each = var.contacts
+ parent = var.organization_id
+ email = lookup(
+ local.ctx.email_addresses, each.key, each.key
+ )
language_tag = "en"
notification_category_subscriptions = each.value
depends_on = [
diff --git a/modules/organization/variables.tf b/modules/organization/variables.tf
index 0c9f13fca..fb4c81f47 100644
--- a/modules/organization/variables.tf
+++ b/modules/organization/variables.tf
@@ -19,6 +19,17 @@ variable "contacts" {
type = map(list(string))
default = {}
nullable = false
+ validation {
+ condition = alltrue(flatten([
+ for k, v in var.contacts : [
+ for vv in v : contains([
+ "ALL", "SUSPENSION", "SECURITY", "TECHNICAL", "BILLING", "LEGAL",
+ "PRODUCT_UPDATES"
+ ], vv)
+ ]
+ ]))
+ error_message = "Invalid contact notification value."
+ }
}
variable "context" {
@@ -27,6 +38,7 @@ variable "context" {
bigquery_datasets = optional(map(string), {})
condition_vars = optional(map(map(string)), {})
custom_roles = optional(map(string), {})
+ email_addresses = optional(map(string), {})
iam_principals = optional(map(string), {})
locations = optional(map(string), {})
log_buckets = optional(map(string), {})
diff --git a/modules/project-factory/README.md b/modules/project-factory/README.md
index a5811dc60..426342d08 100644
--- a/modules/project-factory/README.md
+++ b/modules/project-factory/README.md
@@ -778,11 +778,11 @@ compute.disableSerialPortAccess:
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [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#L37) | 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({…}) | | {} |
+| [factories_config](variables.tf#L171) | Path to folder with YAML resource description data files. | object({…}) | ✓ | |
+| [context](variables.tf#L17) | Context-specific interpolations. | object({…}) | | {} |
+| [data_defaults](variables.tf#L38) | Optional default values used when corresponding project or folder data from files are missing. | object({…}) | | {} |
+| [data_merges](variables.tf#L108) | 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#L127) | 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/schemas/folder.schema.json b/modules/project-factory/schemas/folder.schema.json
index 778dc0572..076464aca 100644
--- a/modules/project-factory/schemas/folder.schema.json
+++ b/modules/project-factory/schemas/folder.schema.json
@@ -64,6 +64,27 @@
}
}
},
+ "contacts": {
+ "type": "object",
+ "additionalProperties": false,
+ "patternProperties": {
+ "^(\\S+@\\S+\\.\\S+|\\$email_addresses:\\S+)$": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "ALL",
+ "BILLING",
+ "LEGAL",
+ "SECURITY",
+ "PRODUCT_UPDATES",
+ "SUSPENSION",
+ "TECHNICAL"
+ ]
+ }
+ }
+ }
+ },
"factories_config": {
"type": "object",
"additionalProperties": false,
@@ -552,4 +573,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/modules/project-factory/schemas/project.schema.json b/modules/project-factory/schemas/project.schema.json
index 228c10c06..0df37926b 100644
--- a/modules/project-factory/schemas/project.schema.json
+++ b/modules/project-factory/schemas/project.schema.json
@@ -80,10 +80,19 @@
"type": "object",
"additionalProperties": false,
"patternProperties": {
- "^[a-z0-9_-]+$": {
+ "^(\\S+@\\S+\\.\\S+|\\$email_addresses:\\S+)$": {
"type": "array",
"items": {
- "type": "string"
+ "type": "string",
+ "enum": [
+ "ALL",
+ "BILLING",
+ "LEGAL",
+ "SECURITY",
+ "PRODUCT_UPDATES",
+ "SUSPENSION",
+ "TECHNICAL"
+ ]
}
}
}
@@ -1005,4 +1014,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/modules/project-factory/variables.tf b/modules/project-factory/variables.tf
index d32bf85ed..9c570dcd6 100644
--- a/modules/project-factory/variables.tf
+++ b/modules/project-factory/variables.tf
@@ -19,6 +19,7 @@ variable "context" {
type = object({
condition_vars = optional(map(map(string)), {})
custom_roles = optional(map(string), {})
+ email_addresses = optional(map(string), {})
folder_ids = optional(map(string), {})
iam_principals = optional(map(string), {})
kms_keys = optional(map(string), {})
diff --git a/modules/project/README.md b/modules/project/README.md
index e3bb4d186..36a204744 100644
--- a/modules/project/README.md
+++ b/modules/project/README.md
@@ -1946,26 +1946,26 @@ alerts:
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [name](variables.tf#L133) | Project name and id suffix. | string | ✓ | |
+| [name](variables.tf#L145) | Project name and id suffix. | string | ✓ | |
| [alerts](variables-observability.tf#L17) | Monitoring alerts. | map(object({…})) | | {} |
| [auto_create_network](variables.tf#L17) | Whether to create the default network for the project. | bool | | false |
| [billing_account](variables.tf#L23) | Billing account id. | string | | null |
| [compute_metadata](variables.tf#L29) | Optional compute metadata key/values. Only usable if compute API has been enabled. | map(string) | | {} |
| [contacts](variables.tf#L36) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | map(list(string)) | | {} |
-| [context](variables.tf#L43) | Context-specific interpolations. | object({…}) | | {} |
-| [custom_roles](variables.tf#L62) | Map of role name => list of permissions to create in this project. | map(list(string)) | | {} |
-| [default_network_tier](variables.tf#L69) | Default compute network tier for the project. | string | | null |
-| [default_service_account](variables.tf#L75) | Project default service account setting: can be one of `delete`, `deprivilege`, `disable`, or `keep`. | string | | "keep" |
-| [deletion_policy](variables.tf#L88) | Deletion policy setting for this project. | string | | "DELETE" |
-| [descriptive_name](variables.tf#L99) | Name of the project name. Used for project name instead of `name` variable. | string | | null |
-| [factories_config](variables.tf#L105) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} |
+| [context](variables.tf#L54) | Context-specific interpolations. | object({…}) | | {} |
+| [custom_roles](variables.tf#L74) | Map of role name => list of permissions to create in this project. | map(list(string)) | | {} |
+| [default_network_tier](variables.tf#L81) | Default compute network tier for the project. | string | | null |
+| [default_service_account](variables.tf#L87) | Project default service account setting: can be one of `delete`, `deprivilege`, `disable`, or `keep`. | string | | "keep" |
+| [deletion_policy](variables.tf#L100) | Deletion policy setting for this project. | string | | "DELETE" |
+| [descriptive_name](variables.tf#L111) | Name of the project name. Used for project name instead of `name` variable. | string | | null |
+| [factories_config](variables.tf#L117) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} |
| [iam](variables-iam.tf#L17) | Authoritative IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} |
| [iam_bindings](variables-iam.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) | | {} |
| [iam_bindings_additive](variables-iam.tf#L39) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} |
| [iam_by_principals](variables-iam.tf#L61) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam` variable. | map(list(string)) | | {} |
| [iam_by_principals_additive](variables-iam.tf#L54) | Additive IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam_bindings_additive` variable. | map(list(string)) | | {} |
-| [labels](variables.tf#L120) | Resource labels. | map(string) | | {} |
-| [lien_reason](variables.tf#L127) | If non-empty, creates a project lien with this description. | string | | null |
+| [labels](variables.tf#L132) | Resource labels. | map(string) | | {} |
+| [lien_reason](variables.tf#L139) | If non-empty, creates a project lien with this description. | string | | null |
| [log_scopes](variables-observability.tf#L117) | Log scopes under this project. | map(object({…})) | | {} |
| [logging_data_access](variables-observability.tf#L127) | Control activation of data access logs. The special 'allServices' key denotes configuration for all services. | map(object({…})) | | {} |
| [logging_exclusions](variables-observability.tf#L138) | Logging exclusions for this project in the form {NAME -> FILTER}. | map(string) | | {} |
@@ -1974,25 +1974,25 @@ alerts:
| [metric_scopes](variables-observability.tf#L216) | List of projects that will act as metric scopes for this project. | list(string) | | [] |
| [network_tags](variables-tags.tf#L17) | Network tags by key name. If `id` is provided, key creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} |
| [notification_channels](variables-observability.tf#L223) | Monitoring notification channels. | map(object({…})) | | {} |
-| [org_policies](variables.tf#L138) | Organization policies applied to this project keyed by policy name. | map(object({…})) | | {} |
+| [org_policies](variables.tf#L150) | Organization policies applied to this project keyed by policy name. | map(object({…})) | | {} |
| [pam_entitlements](variables-pam.tf#L17) | Privileged Access Manager entitlements for this resource, keyed by entitlement ID. | map(object({…})) | | {} |
-| [parent](variables.tf#L166) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | string | | null |
-| [prefix](variables.tf#L180) | Optional prefix used to generate project id and name. | string | | null |
-| [project_reuse](variables.tf#L190) | Reuse existing project if not null. If name and number are not passed in, a data source is used. | object({…}) | | null |
+| [parent](variables.tf#L178) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | string | | null |
+| [prefix](variables.tf#L192) | Optional prefix used to generate project id and name. | string | | null |
+| [project_reuse](variables.tf#L202) | Reuse existing project if not null. If name and number are not passed in, a data source is used. | object({…}) | | null |
| [quotas](variables-quotas.tf#L17) | Service quota configuration. | map(object({…})) | | {} |
| [scc_sha_custom_modules](variables-scc.tf#L17) | SCC custom modules keyed by module name. | map(object({…})) | | {} |
-| [service_agents_config](variables.tf#L210) | Automatic service agent configuration options. | object({…}) | | {} |
-| [service_config](variables.tf#L220) | Configure service API activation. | object({…}) | | {…} |
-| [service_encryption_key_ids](variables.tf#L232) | Service Agents to be granted encryption/decryption permissions over Cloud KMS encryption keys. Format {SERVICE_AGENT => [KEY_ID]}. | map(list(string)) | | {} |
-| [services](variables.tf#L239) | Service APIs to enable. | list(string) | | [] |
-| [shared_vpc_host_config](variables.tf#L245) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | object({…}) | | null |
-| [shared_vpc_service_config](variables.tf#L255) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | object({…}) | | {…} |
-| [skip_delete](variables.tf#L292) | Deprecated. Use deletion_policy. | bool | | null |
+| [service_agents_config](variables.tf#L222) | Automatic service agent configuration options. | object({…}) | | {} |
+| [service_config](variables.tf#L232) | Configure service API activation. | object({…}) | | {…} |
+| [service_encryption_key_ids](variables.tf#L244) | Service Agents to be granted encryption/decryption permissions over Cloud KMS encryption keys. Format {SERVICE_AGENT => [KEY_ID]}. | map(list(string)) | | {} |
+| [services](variables.tf#L251) | Service APIs to enable. | list(string) | | [] |
+| [shared_vpc_host_config](variables.tf#L257) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | object({…}) | | null |
+| [shared_vpc_service_config](variables.tf#L267) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | object({…}) | | {…} |
+| [skip_delete](variables.tf#L304) | Deprecated. Use deletion_policy. | bool | | null |
| [tag_bindings](variables-tags.tf#L82) | Tag bindings for this project, in key => tag value id format. | map(string) | | null |
| [tags](variables-tags.tf#L89) | Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} |
| [tags_config](variables-tags.tf#L154) | Fine-grained control on tag resource and IAM creation. | object({…}) | | {} |
-| [universe](variables.tf#L304) | GCP universe where to deploy the project. The prefix will be prepended to the project id. | object({…}) | | null |
-| [vpc_sc](variables.tf#L315) | VPC-SC configuration for the project, use when `ignore_changes` for resources is set in the VPC-SC module. | object({…}) | | null |
+| [universe](variables.tf#L316) | GCP universe where to deploy the project. The prefix will be prepended to the project id. | object({…}) | | null |
+| [vpc_sc](variables.tf#L327) | VPC-SC configuration for the project, use when `ignore_changes` for resources is set in the VPC-SC module. | object({…}) | | null |
## Outputs
diff --git a/modules/project/main.tf b/modules/project/main.tf
index 3979a14d7..6bb609419 100644
--- a/modules/project/main.tf
+++ b/modules/project/main.tf
@@ -137,10 +137,12 @@ resource "google_resource_manager_lien" "lien" {
}
resource "google_essential_contacts_contact" "contact" {
- provider = google-beta
- for_each = var.contacts
- parent = "projects/${local.project.project_id}"
- email = each.key
+ provider = google-beta
+ for_each = var.contacts
+ parent = "projects/${local.project.project_id}"
+ email = lookup(
+ local.ctx.email_addresses, each.key, each.key
+ )
language_tag = "en"
notification_category_subscriptions = each.value
depends_on = [
diff --git a/modules/project/variables.tf b/modules/project/variables.tf
index 6b771c1c5..49db4969a 100644
--- a/modules/project/variables.tf
+++ b/modules/project/variables.tf
@@ -38,6 +38,17 @@ variable "contacts" {
type = map(list(string))
default = {}
nullable = false
+ validation {
+ condition = alltrue(flatten([
+ for k, v in var.contacts : [
+ for vv in v : contains([
+ "ALL", "SUSPENSION", "SECURITY", "TECHNICAL", "BILLING", "LEGAL",
+ "PRODUCT_UPDATES"
+ ], vv)
+ ]
+ ]))
+ error_message = "Invalid contact notification value."
+ }
}
variable "context" {
@@ -45,6 +56,7 @@ variable "context" {
type = object({
condition_vars = optional(map(map(string)), {})
custom_roles = optional(map(string), {})
+ email_addresses = optional(map(string), {})
folder_ids = optional(map(string), {})
kms_keys = optional(map(string), {})
iam_principals = optional(map(string), {})
diff --git a/tests/modules/folder/context.tfvars b/tests/modules/folder/context.tfvars
index 80ff9e02b..37e984252 100644
--- a/tests/modules/folder/context.tfvars
+++ b/tests/modules/folder/context.tfvars
@@ -10,6 +10,9 @@ context = {
myrole_one = "organizations/366118655033/roles/myRoleOne"
myrole_two = "organizations/366118655033/roles/myRoleTwo"
}
+ email_addresses = {
+ default = "foo@example.com"
+ }
folder_ids = {
default = "organizations/1234567890"
}
@@ -22,6 +25,9 @@ context = {
"test/one" = "tagValues/1234567890"
}
}
+contacts = {
+ "$email_addresses:default" = ["ALL"]
+}
iam = {
"$custom_roles:myrole_one" = [
"$iam_principals:myuser"
diff --git a/tests/modules/folder/context.yaml b/tests/modules/folder/context.yaml
index 49f50e7ff..e85e894be 100644
--- a/tests/modules/folder/context.yaml
+++ b/tests/modules/folder/context.yaml
@@ -13,6 +13,12 @@
# limitations under the License.
values:
+ google_essential_contacts_contact.contact["$email_addresses:default"]:
+ email: foo@example.com
+ language_tag: en
+ notification_category_subscriptions:
+ - ALL
+ timeouts: null
google_folder.folder[0]:
deletion_protection: false
display_name: Test Context
@@ -81,10 +87,11 @@ values:
tag_value: tagValues/1234567890
counts:
+ google_essential_contacts_contact: 1
google_folder: 1
google_folder_iam_binding: 4
google_folder_iam_member: 1
google_privileged_access_manager_entitlement: 1
google_tags_tag_binding: 1
modules: 0
- resources: 8
+ resources: 9
diff --git a/tests/modules/organization/context.tfvars b/tests/modules/organization/context.tfvars
index 993ab9836..42a52a5c5 100644
--- a/tests/modules/organization/context.tfvars
+++ b/tests/modules/organization/context.tfvars
@@ -11,6 +11,9 @@ context = {
myrole_one = "organizations/366118655033/roles/myRoleOne"
myrole_two = "organizations/366118655033/roles/myRoleTwo"
}
+ email_addresses = {
+ default = "foo@example.com"
+ }
iam_principals = {
mygroup = "group:test-group@example.com"
mysa = "serviceAccount:test@test-project.iam.gserviceaccount.com"
@@ -38,6 +41,9 @@ context = {
"test/one" = "tagValues/1234567890"
}
}
+contacts = {
+ "$email_addresses:default" = ["ALL"]
+}
iam = {
"$custom_roles:myrole_one" = [
"$iam_principals:myuser"
diff --git a/tests/modules/organization/context.yaml b/tests/modules/organization/context.yaml
index 6efce634e..99f6de3f8 100644
--- a/tests/modules/organization/context.yaml
+++ b/tests/modules/organization/context.yaml
@@ -18,6 +18,12 @@ values:
dataset_id: logs
project: test-prod-audit-logs-0
role: roles/bigquery.dataEditor
+ google_essential_contacts_contact.contact["$email_addresses:default"]:
+ email: foo@example.com
+ language_tag: en
+ notification_category_subscriptions:
+ - ALL
+ timeouts: null
google_logging_organization_settings.default[0]:
organization: '1234567890'
storage_location: europe-west8
@@ -198,6 +204,7 @@ values:
counts:
google_bigquery_dataset_iam_member: 1
+ google_essential_contacts_contact: 1
google_logging_organization_settings: 1
google_logging_organization_sink: 5
google_organization_iam_binding: 4
@@ -212,4 +219,4 @@ counts:
google_tags_tag_value_iam_binding: 2
google_tags_tag_value_iam_member: 1
modules: 0
- resources: 24
+ resources: 25
diff --git a/tests/modules/project/context.tfvars b/tests/modules/project/context.tfvars
index bafcb3889..80029fa8a 100644
--- a/tests/modules/project/context.tfvars
+++ b/tests/modules/project/context.tfvars
@@ -8,6 +8,9 @@ context = {
myrole_one = "organizations/366118655033/roles/myRoleOne"
myrole_two = "organizations/366118655033/roles/myRoleTwo"
}
+ email_addresses = {
+ default = "foo@example.com"
+ }
folder_ids = {
"test/prod" = "folders/6789012345"
}
@@ -32,6 +35,9 @@ context = {
default = "accessPolicies/888933661165/servicePerimeters/default"
}
}
+contacts = {
+ "$email_addresses:default" = ["ALL"]
+}
parent = "$folder_ids:test/prod"
iam = {
"$custom_roles:myrole_one" = [
diff --git a/tests/modules/project/context.yaml b/tests/modules/project/context.yaml
index a2b4ba97a..030d08ab5 100644
--- a/tests/modules/project/context.yaml
+++ b/tests/modules/project/context.yaml
@@ -19,6 +19,12 @@ values:
deletion_policy: null
host_project: test-vpc-host
service_project: my-project
+ google_essential_contacts_contact.contact["$email_addresses:default"]:
+ email: foo@example.com
+ language_tag: en
+ notification_category_subscriptions:
+ - ALL
+ timeouts: null
google_kms_crypto_key_iam_member.service_agent_cmek["key-0.compute-system"]:
condition: []
crypto_key_id: projects/kms-central-prj/locations/europe-west1/keyRings/my-keyring/cryptoKeys/ew1-compute
@@ -174,6 +180,7 @@ values:
counts:
google_access_context_manager_service_perimeter_resource: 1
google_compute_shared_vpc_service_project: 1
+ google_essential_contacts_contact: 1
google_kms_crypto_key_iam_member: 1
google_privileged_access_manager_entitlement: 1
google_project: 1
@@ -186,4 +193,4 @@ counts:
google_tags_tag_value_iam_binding: 2
google_tags_tag_value_iam_member: 1
modules: 0
- resources: 24
+ resources: 25
diff --git a/tools/duplicate-diff.py b/tools/duplicate-diff.py
index e3847045a..17c9dd1ca 100755
--- a/tools/duplicate-diff.py
+++ b/tools/duplicate-diff.py
@@ -72,9 +72,10 @@ duplicates = [
"modules/net-vpc-firewall/schemas/firewall-rules.schema.json",
],
[
- "fast/stages/2-project-factory/schemas/folder.schema.json",
- "fast/stages/2-networking/schemas/folder.schema.json",
"fast/stages/0-org-setup/schemas/folder.schema.json",
+ "fast/stages/2-networking/schemas/folder.schema.json",
+ "fast/stages/2-project-factory/schemas/folder.schema.json",
+ "fast/stages/2-security/schemas/folder.schema.json",
"modules/project-factory/schemas/folder.schema.json",
],
[