diff --git a/CHANGELOG.md b/CHANGELOG.md index d05dbb75f..0b8f649f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,17 +7,24 @@ All notable changes to this project will be documented in this file. ### BREAKING CHANGES -- `modules/secret-manager`: the module interface has changed and been brought up to date with our current modules' shared interfaces; please test and refactor appropriately before using it in existing installations. This new version is **incompatible with OpenTofu** as it lacks support for write-once attributes. [[#3315](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3315)] +- `modules/net-lb-app-int`: resource names for new-style HTTP and HTTP proxies have changed. If you upgrade the module, please rename those resources in state via `terraform state mv`. [[#3320](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3320)] +- `modules/net-vpc`: the values of `secondary_ip_ranges` changed from a string to an object. [[#3318](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3318)] +- `modules/secret-manager`: the module interface has changed and been brought up to date with our current modules' shared interfaces; please test and refactor appropriately before using it in existing installations. This new version is **incompatible with OpenTofu** as it lacks support for write-only attributes. [[#3315](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3315)] - `modules/secure-source-manager-instance`: Changed interface to declare private instances. [[#3310](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3310)] ### FAST +- [[#3318](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3318)] Add support for VPC internal ranges to `modules/net-vpc` ([juliocc](https://github.com/juliocc)) +- [[#3317](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3317)] Add support for project templates to project factory module ([ludoo](https://github.com/ludoo)) - [[#3315](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3315)] Refactor secret manager module ([ludoo](https://github.com/ludoo)) - [[#3305](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3305)] Improve context support in vpc-sc module and stage / new FAST stages small fixes ([ludoo](https://github.com/ludoo)) ### MODULES +- [[#3320](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3320)] Add http_keep_alive_timeout_sec to application load balancers ([ludoo](https://github.com/ludoo)) +- [[#3318](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3318)] Add support for VPC internal ranges to `modules/net-vpc` ([juliocc](https://github.com/juliocc)) +- [[#3317](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3317)] Add support for project templates to project factory module ([ludoo](https://github.com/ludoo)) - [[#3316](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3316)] Add support for upgrade notification filters to GKE cluster modules ([ludoo](https://github.com/ludoo)) - [[#3315](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3315)] Refactor secret manager module ([ludoo](https://github.com/ludoo)) - [[#3313](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3313)] Add support for startup script to compute-vm module ([ludoo](https://github.com/ludoo)) @@ -27,6 +34,10 @@ All notable changes to this project will be documented in this file. - [[#3307](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3307)] Add support for context in kms module ([ludoo](https://github.com/ludoo)) - [[#3305](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3305)] Improve context support in vpc-sc module and stage / new FAST stages small fixes ([ludoo](https://github.com/ludoo)) +### TOOLS + +- [[#3319](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/3319)] Allow skipping selected tests with tofu ([ludoo](https://github.com/ludoo)) + ## [44.1.0] - 2025-09-06 ### BREAKING CHANGES diff --git a/fast/stages/0-org-setup/README.md b/fast/stages/0-org-setup/README.md index 24d64bd21..8f89e84b1 100644 --- a/fast/stages/0-org-setup/README.md +++ b/fast/stages/0-org-setup/README.md @@ -647,8 +647,8 @@ Define values for the `var.environments` variable in a tfvars file. |---|---|:---:|:---:|:---:| | [bootstrap_user](variables.tf#L17) | Email of the nominal user running this stage for the first time. | string | | null | | [context](variables.tf#L23) | Context-specific interpolations. | object({…}) | | {} | -| [factories_config](variables.tf#L43) | Configuration for the resource factories or external data. | object({…}) | | {} | -| [org_policies_imports](variables.tf#L57) | List of org policies to import. These need to also be defined in data files. | list(string) | | [] | +| [factories_config](variables.tf#L43) | Configuration for the resource factories or external data. | object({…}) | | {} | +| [org_policies_imports](variables.tf#L58) | List of org policies to import. These need to also be defined in data files. | list(string) | | [] | ## Outputs diff --git a/fast/stages/0-org-setup/factory.tf b/fast/stages/0-org-setup/factory.tf index fb80d5bc0..39b2884ff 100644 --- a/fast/stages/0-org-setup/factory.tf +++ b/fast/stages/0-org-setup/factory.tf @@ -52,7 +52,8 @@ module "factory" { ) }) factories_config = { - folders = var.factories_config.folders - projects = var.factories_config.projects + folders = var.factories_config.folders + project_templates = var.factories_config.project_templates + projects = var.factories_config.projects } } diff --git a/fast/stages/0-org-setup/schemas/defaults.schema.json b/fast/stages/0-org-setup/schemas/defaults.schema.json index e13a9d757..ea0af8c6d 100644 --- a/fast/stages/0-org-setup/schemas/defaults.schema.json +++ b/fast/stages/0-org-setup/schemas/defaults.schema.json @@ -114,6 +114,48 @@ } } }, + "logging_data_access": { + "type": "object", + "default": {}, + "additionalProperties": { + "type": "object", + "properties": { + "ADMIN_READ": { + "type": "object", + "properties": { + "exempted_members": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "DATA_READ": { + "type": "object", + "properties": { + "exempted_members": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "DATA_WRITE": { + "type": "object", + "properties": { + "exempted_members": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, "metric_scopes": { "type": "array", "default": [], @@ -268,6 +310,24 @@ } } }, + "universe": { + "type": "object", + "additionalProperties": false, + "required": [ + "prefix" + ], + "properties": { + "prefix": { + "type": "string" + }, + "unavailable_service_identities": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "vpc_sc": { "type": "object", "properties": { @@ -282,6 +342,45 @@ "required": [ "perimeter_name" ] + } + } + }, + "overrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "billing_account": { + "type": "string" + }, + "bucket": { + "type": "object", + "additionalProperties": false, + "properties": { + "force_destroy": { + "type": "boolean" + } + } + }, + "contacts": { + "type": "object", + "default": {}, + "additionalProperties": false, + "patternProperties": { + "^[a-z0-9_-]+$": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "deletion_policy": { + "type": "string", + "enum": [ + "PREVENT", + "DELETE", + "ABANDON" + ] }, "logging_data_access": { "type": "object", @@ -324,45 +423,6 @@ } } } - } - } - }, - "overrides": { - "type": "object", - "additionalProperties": false, - "properties": { - "billing_account": { - "type": "string" - }, - "bucket": { - "type": "object", - "additionalProperties": false, - "properties": { - "force_destroy": { - "type": "boolean" - } - } - }, - "contacts": { - "type": "object", - "default": {}, - "additionalProperties": false, - "patternProperties": { - "^[a-z0-9_-]+$": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "deletion_policy": { - "type": "string", - "enum": [ - "PREVENT", - "DELETE", - "ABANDON" - ] }, "parent": { "type": "string" @@ -412,6 +472,24 @@ } } }, + "universe": { + "type": "object", + "additionalProperties": false, + "required": [ + "prefix" + ], + "properties": { + "prefix": { + "type": "string" + }, + "unavailable_service_identities": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "vpc_sc": { "type": "object", "properties": { @@ -426,48 +504,6 @@ "required": [ "perimeter_name" ] - }, - "logging_data_access": { - "type": "object", - "default": {}, - "additionalProperties": { - "type": "object", - "properties": { - "ADMIN_READ": { - "type": "object", - "properties": { - "exempted_members": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "DATA_READ": { - "type": "object", - "properties": { - "exempted_members": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "DATA_WRITE": { - "type": "object", - "properties": { - "exempted_members": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - } - } } } } diff --git a/fast/stages/0-org-setup/schemas/project.schema.json b/fast/stages/0-org-setup/schemas/project.schema.json index b41bba991..d0df5f57a 100644 --- a/fast/stages/0-org-setup/schemas/project.schema.json +++ b/fast/stages/0-org-setup/schemas/project.schema.json @@ -295,6 +295,9 @@ } } }, + "project_template": { + "type": "string" + }, "service_accounts": { "type": "object", "additionalProperties": false, @@ -482,6 +485,27 @@ } } }, + "universe": { + "type": "object", + "additionalProperties": false, + "properties": { + "prefix": { + "type": "string", + "unavailable_services": { + "type": "array", + "items": { + "type": "string" + } + }, + "unavailable_service_identities": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, "vpc_sc": { "type": "object", "additionalItems": false, diff --git a/fast/stages/0-org-setup/variables.tf b/fast/stages/0-org-setup/variables.tf index 1b4fabf92..8bb429b4f 100644 --- a/fast/stages/0-org-setup/variables.tf +++ b/fast/stages/0-org-setup/variables.tf @@ -43,12 +43,13 @@ variable "context" { variable "factories_config" { description = "Configuration for the resource factories or external data." type = object({ - billing_accounts = optional(string, "data/billing-accounts") - cicd = optional(string) - defaults = optional(string, "data/defaults.yaml") - folders = optional(string, "data/folders") - organization = optional(string, "data/organization") - projects = optional(string, "data/projects") + billing_accounts = optional(string, "data/billing-accounts") + cicd = optional(string) + defaults = optional(string, "data/defaults.yaml") + folders = optional(string, "data/folders") + organization = optional(string, "data/organization") + project_templates = optional(string, "data/templates") + projects = optional(string, "data/projects") }) nullable = false default = {} diff --git a/fast/stages/2-networking-a-simple/data/subnets/dev/dev-gke-nodes.yaml b/fast/stages/2-networking-a-simple/data/subnets/dev/dev-gke-nodes.yaml index afc7122b0..338e17ab0 100644 --- a/fast/stages/2-networking-a-simple/data/subnets/dev/dev-gke-nodes.yaml +++ b/fast/stages/2-networking-a-simple/data/subnets/dev/dev-gke-nodes.yaml @@ -7,5 +7,7 @@ region: $regions:primary description: Default subnet for prod gke nodes ip_cidr_range: 10.68.1.0/24 secondary_ip_ranges: - pods: 100.68.0.0/16 - services: 100.71.1.0/24 + pods: + ip_cidr_range: 100.68.0.0/16 + services: + ip_cidr_range: 100.71.1.0/24 diff --git a/fast/stages/2-networking-b-nva/data/subnets/dev/dev-dataplatform.yaml b/fast/stages/2-networking-b-nva/data/subnets/dev/dev-dataplatform.yaml index dccb62d09..76ace5082 100644 --- a/fast/stages/2-networking-b-nva/data/subnets/dev/dev-dataplatform.yaml +++ b/fast/stages/2-networking-b-nva/data/subnets/dev/dev-dataplatform.yaml @@ -7,5 +7,7 @@ region: $regions:primary description: Default subnet for dev Data Platform ip_cidr_range: 10.68.2.0/24 secondary_ip_ranges: - pods: 100.69.0.0/16 - services: 100.71.2.0/24 + pods: + ip_cidr_range: 100.69.0.0/16 + services: + ip_cidr_range: 100.71.2.0/24 diff --git a/fast/stages/2-networking-b-nva/data/subnets/dev/dev-gke-nodes.yaml b/fast/stages/2-networking-b-nva/data/subnets/dev/dev-gke-nodes.yaml index afc7122b0..338e17ab0 100644 --- a/fast/stages/2-networking-b-nva/data/subnets/dev/dev-gke-nodes.yaml +++ b/fast/stages/2-networking-b-nva/data/subnets/dev/dev-gke-nodes.yaml @@ -7,5 +7,7 @@ region: $regions:primary description: Default subnet for prod gke nodes ip_cidr_range: 10.68.1.0/24 secondary_ip_ranges: - pods: 100.68.0.0/16 - services: 100.71.1.0/24 + pods: + ip_cidr_range: 100.68.0.0/16 + services: + ip_cidr_range: 100.71.1.0/24 diff --git a/fast/stages/2-networking-c-separate-envs/data/subnets/dev/dev-dataplatform.yaml b/fast/stages/2-networking-c-separate-envs/data/subnets/dev/dev-dataplatform.yaml index dccb62d09..76ace5082 100644 --- a/fast/stages/2-networking-c-separate-envs/data/subnets/dev/dev-dataplatform.yaml +++ b/fast/stages/2-networking-c-separate-envs/data/subnets/dev/dev-dataplatform.yaml @@ -7,5 +7,7 @@ region: $regions:primary description: Default subnet for dev Data Platform ip_cidr_range: 10.68.2.0/24 secondary_ip_ranges: - pods: 100.69.0.0/16 - services: 100.71.2.0/24 + pods: + ip_cidr_range: 100.69.0.0/16 + services: + ip_cidr_range: 100.71.2.0/24 diff --git a/fast/stages/2-networking-c-separate-envs/data/subnets/dev/dev-gke-nodes.yaml b/fast/stages/2-networking-c-separate-envs/data/subnets/dev/dev-gke-nodes.yaml index afc7122b0..338e17ab0 100644 --- a/fast/stages/2-networking-c-separate-envs/data/subnets/dev/dev-gke-nodes.yaml +++ b/fast/stages/2-networking-c-separate-envs/data/subnets/dev/dev-gke-nodes.yaml @@ -7,5 +7,7 @@ region: $regions:primary description: Default subnet for prod gke nodes ip_cidr_range: 10.68.1.0/24 secondary_ip_ranges: - pods: 100.68.0.0/16 - services: 100.71.1.0/24 + pods: + ip_cidr_range: 100.68.0.0/16 + services: + ip_cidr_range: 100.71.1.0/24 diff --git a/modules/apigee/README.md b/modules/apigee/README.md index d4a1d30fb..7ffa6b09a 100644 --- a/modules/apigee/README.md +++ b/modules/apigee/README.md @@ -240,6 +240,8 @@ module "apigee" { ### New instance (VPC Peering Provisioning Mode) +Access logging is optional, shown here as an example. + ```hcl module "apigee" { source = "./fabric/modules/apigee" @@ -248,10 +250,13 @@ module "apigee" { europe-west1 = { runtime_ip_cidr_range = "10.0.4.0/22" troubleshooting_ip_cidr_range = "10.1.1.0/28" + access_logging = { + filter = "statusCode >= 200 && statusCode < 300" + } } } } -# tftest modules=1 resources=1 +# tftest modules=1 resources=1 inventory=access-logging.yaml ``` ### New instance (Non VPC Peering Provisioning Mode) @@ -383,14 +388,14 @@ module "apigee" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [project_id](variables.tf#L144) | Project ID. | string | ✓ | | +| [project_id](variables.tf#L148) | Project ID. | string | ✓ | | | [addons_config](variables.tf#L17) | Addons configuration. | object({…}) | | null | | [dns_zones](variables.tf#L29) | DNS zones. | map(object({…})) | | {} | | [endpoint_attachments](variables.tf#L41) | Endpoint attachments. | map(object({…})) | | {} | | [envgroups](variables.tf#L51) | Environment groups (NAME => [HOSTNAMES]). | map(list(string)) | | {} | | [environments](variables.tf#L58) | Environments. | map(object({…})) | | {} | -| [instances](variables.tf#L86) | Instances ([REGION] => [INSTANCE]). | map(object({…})) | | {} | -| [organization](variables.tf#L112) | Apigee organization. If set to null the organization must already exist. | object({…}) | | null | +| [instances](variables.tf#L86) | Instances ([REGION] => [INSTANCE]). | map(object({…})) | | {} | +| [organization](variables.tf#L116) | Apigee organization. If set to null the organization must already exist. | object({…}) | | null | ## Outputs diff --git a/modules/apigee/main.tf b/modules/apigee/main.tf index 130314797..225bc5918 100644 --- a/modules/apigee/main.tf +++ b/modules/apigee/main.tf @@ -103,6 +103,13 @@ resource "google_apigee_instance" "instances" { ) disk_encryption_key_name = each.value.disk_encryption_key consumer_accept_list = each.value.consumer_accept_list + dynamic "access_logging_config" { + for_each = each.value.access_logging == null ? [] : [""] + content { + enabled = each.value.access_logging.enabled + filter = each.value.access_logging.filter + } + } } resource "google_apigee_nat_address" "apigee_nat" { diff --git a/modules/apigee/variables.tf b/modules/apigee/variables.tf index b450b47f5..9acb05492 100644 --- a/modules/apigee/variables.tf +++ b/modules/apigee/variables.tf @@ -96,6 +96,10 @@ variable "instances" { name = optional(string) runtime_ip_cidr_range = optional(string) troubleshooting_ip_cidr_range = optional(string) + access_logging = optional(object({ + enabled = optional(bool, true) + filter = optional(string) + })) })) validation { condition = alltrue([ diff --git a/modules/gke-hub/README.md b/modules/gke-hub/README.md index 8c703a4ed..3bf643b48 100644 --- a/modules/gke-hub/README.md +++ b/modules/gke-hub/README.md @@ -39,8 +39,8 @@ module "vpc" { name = "cluster-1" region = "europe-west1" secondary_ip_range = { - pods = "10.1.0.0/16" - services = "10.2.0.0/24" + pods = { ip_cidr_range = "10.1.0.0/16" } + services = { ip_cidr_range = "10.2.0.0/24" } } }] } @@ -142,8 +142,8 @@ module "vpc" { name = "subnet-cluster-1" region = "europe-west1" secondary_ip_ranges = { - pods = "10.1.0.0/16" - services = "10.2.0.0/24" + pods = { ip_cidr_range = "10.1.0.0/16" } + services = { ip_cidr_range = "10.2.0.0/24" } } }, { @@ -151,8 +151,8 @@ module "vpc" { name = "subnet-cluster-2" region = "europe-west4" secondary_ip_ranges = { - pods = "10.3.0.0/16" - services = "10.4.0.0/24" + pods = { ip_cidr_range = "10.3.0.0/16" } + services = { ip_cidr_range = "10.4.0.0/24" } } }, { diff --git a/modules/net-lb-app-ext/README.md b/modules/net-lb-app-ext/README.md index 9a511143b..592e53127 100644 --- a/modules/net-lb-app-ext/README.md +++ b/modules/net-lb-app-ext/README.md @@ -1062,21 +1062,22 @@ After provisioning this change, and verifying that the new certificate is provis | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L111) | Load balancer name. | string | ✓ | | -| [project_id](variables.tf#L208) | Project id. | string | ✓ | | +| [name](variables.tf#L123) | Load balancer name. | string | ✓ | | +| [project_id](variables.tf#L220) | Project id. | string | ✓ | | | [backend_buckets_config](variables.tf#L17) | Backend buckets configuration. | map(object({…})) | | {} | | [backend_service_configs](variables-backend-service.tf#L19) | Backend service level configuration. | map(object({…})) })) | | {} | | [description](variables.tf#L51) | Optional description used for resources. | string | | "Terraform managed." | | [forwarding_rules_config](variables.tf#L57) | The optional forwarding rules configuration. | map(object({…})) | | {…} | | [group_configs](variables.tf#L78) | Optional unmanaged groups to create. Can be referenced in backends via key or outputs. | map(object({…})) | | {} | | [health_check_configs](variables-health-check.tf#L19) | Optional auto-created health check configurations, use the output self-link to set it in the auto healing policy. Refer to examples for usage. | map(object({…})) | | {…} | -| [https_proxy_config](variables.tf#L90) | HTTPS proxy connfiguration. | object({…}) | | {} | -| [labels](variables.tf#L105) | Labels set on resources. | map(string) | | {} | -| [neg_configs](variables.tf#L116) | Optional network endpoint groups to create. Can be referenced in backends via key or outputs. | map(object({…})) | | {} | -| [protocol](variables.tf#L213) | Protocol supported by this load balancer. | string | | "HTTP" | -| [ssl_certificates](variables.tf#L226) | SSL target proxy certificates (only if protocol is HTTPS) for existing, custom, and managed certificates. | object({…}) | | {} | +| [http_proxy_config](variables.tf#L90) | HTTP proxy configuration. Only used for non-classic load balancers. | object({…}) | | {} | +| [https_proxy_config](variables.tf#L101) | HTTPS proxy connfiguration. | object({…}) | | {} | +| [labels](variables.tf#L117) | Labels set on resources. | map(string) | | {} | +| [neg_configs](variables.tf#L128) | Optional network endpoint groups to create. Can be referenced in backends via key or outputs. | map(object({…})) | | {} | +| [protocol](variables.tf#L225) | Protocol supported by this load balancer. | string | | "HTTP" | +| [ssl_certificates](variables.tf#L238) | SSL target proxy certificates (only if protocol is HTTPS) for existing, custom, and managed certificates. | object({…}) | | {} | | [urlmap_config](variables-urlmap.tf#L19) | The URL map configuration. | object({…}) | | {…} | -| [use_classic_version](variables.tf#L244) | Use classic Global Load Balancer. | bool | | true | +| [use_classic_version](variables.tf#L256) | Use classic Global Load Balancer. | bool | | true | ## Outputs diff --git a/modules/net-lb-app-ext/main.tf b/modules/net-lb-app-ext/main.tf index 621d40f4f..4b617f161 100644 --- a/modules/net-lb-app-ext/main.tf +++ b/modules/net-lb-app-ext/main.tf @@ -21,8 +21,16 @@ locals { } fwd_rule_target = ( var.protocol == "HTTPS" - ? google_compute_target_https_proxy.default[0].id - : google_compute_target_http_proxy.default[0].id + ? ( + var.use_classic_version + ? google_compute_target_https_proxy.default[0].id + : google_compute_target_https_proxy.new[0].id + ) + : ( + var.use_classic_version + ? google_compute_target_http_proxy.default[0].id + : google_compute_target_http_proxy.new[0].id + ) ) proxy_ssl_certificates = concat( coalesce(var.ssl_certificates.certificate_ids, []), @@ -83,15 +91,19 @@ resource "google_compute_managed_ssl_certificate" "default" { # proxies resource "google_compute_target_http_proxy" "default" { - count = var.protocol == "HTTPS" ? 0 : 1 + count = ( + var.protocol == "HTTP" && var.use_classic_version ? 1 : 0 + ) project = var.project_id - name = var.name - description = var.description + name = coalesce(var.https_proxy_config.name, var.name) + description = var.http_proxy_config.description url_map = google_compute_url_map.default.id } resource "google_compute_target_https_proxy" "default" { - count = var.protocol == "HTTPS" ? 1 : 0 + count = ( + var.protocol == "HTTPS" && var.use_classic_version ? 1 : 0 + ) project = var.project_id name = coalesce(var.https_proxy_config.name, var.name) description = var.https_proxy_config.description @@ -103,3 +115,30 @@ resource "google_compute_target_https_proxy" "default" { url_map = google_compute_url_map.default.id server_tls_policy = var.https_proxy_config.mtls_policy } + +resource "google_compute_target_http_proxy" "new" { + count = ( + var.protocol == "HTTP" && !var.use_classic_version ? 1 : 0 + ) + project = var.project_id + name = coalesce(var.https_proxy_config.name, var.name) + description = var.http_proxy_config.description + url_map = google_compute_url_map.default.id +} + +resource "google_compute_target_https_proxy" "new" { + count = ( + var.protocol == "HTTPS" && !var.use_classic_version ? 1 : 0 + ) + project = var.project_id + name = coalesce(var.https_proxy_config.name, var.name) + description = var.https_proxy_config.description + certificate_map = var.https_proxy_config.certificate_map + certificate_manager_certificates = var.https_proxy_config.certificate_manager_certificates + http_keep_alive_timeout_sec = var.https_proxy_config.http_keepalive_timeout + quic_override = var.https_proxy_config.quic_override + ssl_certificates = length(local.proxy_ssl_certificates) == 0 ? null : local.proxy_ssl_certificates + ssl_policy = var.https_proxy_config.ssl_policy + url_map = google_compute_url_map.default.id + server_tls_policy = var.https_proxy_config.mtls_policy +} diff --git a/modules/net-lb-app-ext/variables.tf b/modules/net-lb-app-ext/variables.tf index 31ac6a1f8..2f22cb08c 100644 --- a/modules/net-lb-app-ext/variables.tf +++ b/modules/net-lb-app-ext/variables.tf @@ -87,6 +87,17 @@ variable "group_configs" { nullable = false } +variable "http_proxy_config" { + description = "HTTP proxy configuration. Only used for non-classic load balancers." + type = object({ + name = optional(string) + description = optional(string, "Terraform managed.") + http_keepalive_timeout = optional(string) + }) + default = {} + nullable = false +} + variable "https_proxy_config" { description = "HTTPS proxy connfiguration." type = object({ @@ -94,6 +105,7 @@ variable "https_proxy_config" { description = optional(string, "Terraform managed.") certificate_manager_certificates = optional(list(string)) certificate_map = optional(string) + http_keepalive_timeout = optional(string) quic_override = optional(string) ssl_policy = optional(string) mtls_policy = optional(string) # id of the mTLS policy to use for the target proxy. diff --git a/modules/net-lb-app-int-cross-region/README.md b/modules/net-lb-app-int-cross-region/README.md index 415efebca..a1e4a8766 100644 --- a/modules/net-lb-app-int-cross-region/README.md +++ b/modules/net-lb-app-int-cross-region/README.md @@ -751,20 +751,21 @@ When deploying changes to load balancer configuration please refer to [net-lb-ap | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L60) | Load balancer name. | string | ✓ | | -| [project_id](variables.tf#L141) | Project id. | string | ✓ | | -| [vpc_config](variables.tf#L168) | VPC-level configuration. | object({…}) | ✓ | | +| [name](variables.tf#L72) | Load balancer name. | string | ✓ | | +| [project_id](variables.tf#L153) | Project id. | string | ✓ | | +| [vpc_config](variables.tf#L180) | VPC-level configuration. | object({…}) | ✓ | | | [addresses](variables.tf#L17) | Optional IP address used for the forwarding rule. | map(string) | | null | | [backend_service_configs](variables-backend-service.tf#L19) | Backend service level configuration. | map(object({…})) | | {} | | [description](variables.tf#L23) | Optional description used for resources. | string | | "Terraform managed." | | [group_configs](variables.tf#L29) | Optional unmanaged groups to create. Can be referenced in backends via key or outputs. | map(object({…})) | | {} | | [health_check_configs](variables-health-check.tf#L19) | Optional auto-created health check configurations, use the output self-link to set it in the auto healing policy. Refer to examples for usage. | map(object({…})) | | {…} | -| [https_proxy_config](variables.tf#L41) | HTTPS proxy configuration. | object({…}) | | {} | -| [labels](variables.tf#L54) | Labels set on resources. | map(string) | | {} | -| [neg_configs](variables.tf#L65) | Optional network endpoint groups to create. Can be referenced in backends via key or outputs. | map(object({…})) | | {} | -| [ports](variables.tf#L131) | Optional ports for HTTP load balancer. | list(string) | | null | -| [protocol](variables.tf#L146) | Protocol supported by this load balancer. | string | | "HTTP" | -| [service_directory_registration](variables.tf#L159) | Service directory namespace and service used to register this load balancer. | object({…}) | | null | +| [http_proxy_config](variables.tf#L41) | HTTP proxy configuration. | object({…}) | | {} | +| [https_proxy_config](variables.tf#L52) | HTTPS proxy configuration. | object({…}) | | {} | +| [labels](variables.tf#L66) | Labels set on resources. | map(string) | | {} | +| [neg_configs](variables.tf#L77) | Optional network endpoint groups to create. Can be referenced in backends via key or outputs. | map(object({…})) | | {} | +| [ports](variables.tf#L143) | Optional ports for HTTP load balancer. | list(string) | | null | +| [protocol](variables.tf#L158) | Protocol supported by this load balancer. | string | | "HTTP" | +| [service_directory_registration](variables.tf#L171) | Service directory namespace and service used to register this load balancer. | object({…}) | | null | | [urlmap_config](variables-urlmap.tf#L19) | The URL map configuration. | object({…}) | | {…} | ## Outputs diff --git a/modules/net-lb-app-int-cross-region/main.tf b/modules/net-lb-app-int-cross-region/main.tf index c43f190b2..ec584d876 100644 --- a/modules/net-lb-app-int-cross-region/main.tf +++ b/modules/net-lb-app-int-cross-region/main.tf @@ -82,8 +82,8 @@ resource "google_compute_global_forwarding_rule" "forwarding_rules" { resource "google_compute_target_http_proxy" "default" { count = var.protocol == "HTTPS" ? 0 : 1 project = var.project_id - name = var.name - description = var.description + name = coalesce(var.https_proxy_config.name, var.name) + description = var.http_proxy_config.description url_map = google_compute_url_map.default.id } @@ -93,6 +93,7 @@ resource "google_compute_target_https_proxy" "default" { name = coalesce(var.https_proxy_config.name, var.name) description = var.https_proxy_config.description certificate_manager_certificates = var.https_proxy_config.certificate_manager_certificates + http_keep_alive_timeout_sec = var.https_proxy_config.http_keepalive_timeout quic_override = var.https_proxy_config.quic_override ssl_policy = var.https_proxy_config.ssl_policy url_map = google_compute_url_map.default.id diff --git a/modules/net-lb-app-int-cross-region/variables.tf b/modules/net-lb-app-int-cross-region/variables.tf index 74398f316..ac9471858 100644 --- a/modules/net-lb-app-int-cross-region/variables.tf +++ b/modules/net-lb-app-int-cross-region/variables.tf @@ -38,12 +38,24 @@ variable "group_configs" { nullable = false } +variable "http_proxy_config" { + description = "HTTP proxy configuration." + type = object({ + name = optional(string) + description = optional(string, "Terraform managed.") + http_keepalive_timeout = optional(string) + }) + default = {} + nullable = false +} + variable "https_proxy_config" { description = "HTTPS proxy configuration." type = object({ name = optional(string) description = optional(string, "Terraform managed.") certificate_manager_certificates = optional(list(string), []) + http_keepalive_timeout = optional(string) quic_override = optional(string) ssl_policy = optional(string) }) diff --git a/modules/net-vpc/README.md b/modules/net-vpc/README.md index 59fd876fb..8309ec6c4 100644 --- a/modules/net-vpc/README.md +++ b/modules/net-vpc/README.md @@ -24,6 +24,10 @@ This module allows creation and management of VPC networks including subnetworks - [Allow Firewall Policy to be evaluated before Firewall Rules](#allow-firewall-policy-to-be-evaluated-before-firewall-rules) - [IPv6](#ipv6) - [IPv6-Only and IP Collections](#ipv6-only-and-ip-collections) + - [Internal Ranges](#internal-ranges) + - [Basic Internal Range Configuration](#basic-internal-range-configuration) + - [Subnets with Internal Ranges](#subnets-with-internal-ranges) + - [Internal Range Factory](#internal-range-factory) - [Variables](#variables) - [Outputs](#outputs) @@ -41,8 +45,8 @@ module "vpc" { name = "production" region = "europe-west1" secondary_ip_ranges = { - pods = "172.16.0.0/20" - services = "192.168.0.0/24" + pods = { ip_cidr_range = "172.16.0.0/20" } + services = { ip_cidr_range = "192.168.0.0/24" } } }, { @@ -83,8 +87,8 @@ module "vpc" { region = "europe-west1" ip_cidr_range = "10.0.2.0/24" secondary_ip_ranges = { - a = "192.168.0.0/24" - b = "192.168.1.0/24" + a = { ip_cidr_range = "192.168.0.0/24" } + b = { ip_cidr_range = "192.168.1.0/24" } } }, # enable flow logs @@ -221,8 +225,8 @@ module "vpc-host" { name = "subnet-1" region = "europe-west1" secondary_ip_ranges = { - pods = "172.16.0.0/20" - services = "192.168.0.0/24" + pods = { ip_cidr_range = "172.16.0.0/20" } + services = { ip_cidr_range = "192.168.0.0/24" } } iam = { "roles/compute.networkUser" = [ @@ -522,7 +526,8 @@ iam: - serviceAccount:fbz@prj.iam.gserviceaccount.com - user:foobar@example.com secondary_ip_ranges: # map of secondary ip ranges - secondary-range-a: 192.168.0.0/24 + secondary-range-a: + ip_cidr_range: 192.168.0.0/24 flow_logs_config: # enable, set to empty map to use defaults aggregation_interval: "INTERVAL_5_SEC" flow_sampling: 0.5 @@ -666,8 +671,8 @@ module "vpc" { name = "production" region = "europe-west1" secondary_ip_ranges = { - pods = "172.16.0.0/20" - services = "192.168.0.0/24" + pods = { ip_cidr_range = "172.16.0.0/20" } + services = { ip_cidr_range = "192.168.0.0/24" } } }, { @@ -716,12 +721,7 @@ module "vpc" { ### IPv6-Only and IP Collections -An IPv6-only subnetwork can be specified by setting `ipv6_only` to `true` and -setting `ip_cidr_range` to `null`. An IP Collection may be specified with -`ip_collection` and a -[reference](https://cloud.google.com/compute/docs/reference/rest/v1/subnetworks/insert) -to a collection source, like a PublicDelegatedPrefix (PDP) for BYOIPv6. The PDP -must be a sub-PDP in `EXTERNAL_IPV6_SUBNETWORK_CREATION` mode. +An IPv6-only subnetwork can be specified by setting `ipv6_only` to `true` and setting `ip_cidr_range` to `null`. An IP Collection may be specified with `ip_collection` and a [reference](https://cloud.google.com/compute/docs/reference/rest/v1/subnetworks/insert) to a collection source, like a PublicDelegatedPrefix (PDP) for BYOIPv6. The PDP must be a sub-PDP in `EXTERNAL_IPV6_SUBNETWORK_CREATION` mode. ```hcl module "vpc" { @@ -754,36 +754,177 @@ module "vpc" { } # tftest modules=1 resources=6 inventory=ipv6_only.yaml ``` + +### Internal Ranges + +Google Cloud [Internal Ranges](https://cloud.google.com/vpc/docs/create-use-internal-ranges) provide advanced IPAM (IP Address Management) capabilities for VPC networks. Internal ranges represent private address ranges with specific behavioral characteristics such as usage and peering behavior. The module supports creating internal ranges directly or through factory configurations, and integrating them with subnet creation. + +#### Basic Internal Range Configuration + +```hcl +module "vpc" { + source = "./fabric/modules/net-vpc" + project_id = var.project_id + name = "my-network" + internal_ranges = [ + { + name = "range1" + usage = "FOR_VPC" + peering = "FOR_SELF" + ip_cidr_range = "10.0.0.0/16" + }, + { + name = "range2" + usage = "FOR_VPC" + peering = "FOR_SELF" + prefix_length = 24 + target_cidr_range = ["10.1.0.0/16"] + description = "Auto-allocated secondary range" + } + ] +} +# tftest inventory=internal-ranges.yaml +``` + +#### Subnets with Internal Ranges + +Subnets can reference internal ranges instead of specifying explicit CIDR ranges, enabling centralized IP management: + +```hcl +module "vpc" { + source = "./fabric/modules/net-vpc" + project_id = var.project_id + name = "my-network" + internal_ranges = [ + { + name = "subnet-range" + usage = "FOR_VPC" + peering = "FOR_SELF" + ip_cidr_range = "10.0.1.0/24" + }, + { + name = "pods-range" + usage = "FOR_VPC" + peering = "FOR_SELF" + ip_cidr_range = "10.1.0.0/16" + }, + { + name = "services-range" + usage = "FOR_VPC" + peering = "FOR_SELF" + prefix_length = 20 + target_cidr_range = ["10.2.0.0/16"] + } + ] + subnets = [ + { + name = "production" + region = "europe-west1" + reserved_internal_range = "subnet-range" + secondary_ip_ranges = { + pods = { + reserved_internal_range = "pods-range" + } + services = { + reserved_internal_range = "services-range" + } + # Mixed configuration: some ranges use internal ranges, others use CIDR + traditional = { + ip_cidr_range = "192.168.0.0/24" + } + } + } + ] +} +# tftest inventory=subnets-internal-ranges.yaml +``` + +#### Internal Range Factory + +Internal ranges can be defined using YAML factory files, similar to the subnet factory: + +```hcl +module "vpc" { + source = "./fabric/modules/net-vpc" + project_id = var.project_id + name = "my-network" + factories_config = { + internal_ranges_folder = "config/internal-ranges" + subnets_folder = "config/subnets" + } +} +# tftest files=subnet,subnet-range,pods-range,services-range inventory=subnets-internal-ranges.yaml +``` + +```yaml +usage: FOR_VPC +peering: FOR_SELF +ip_cidr_range: "10.0.1.0/24" +# tftest-file id=subnet-range path=config/internal-ranges/subnet-range.yaml schema=internal-range.schema.json +``` + +```yaml +usage: FOR_VPC +peering: FOR_SELF +ip_cidr_range: "10.1.0.0/16" + +# tftest-file id=pods-range path=config/internal-ranges/pods-range.yaml schema=internal-range.schema.json +``` + +```yaml +usage: FOR_VPC +peering: FOR_SELF +prefix_length: 20 +target_cidr_range: + - "10.2.0.0/16" + +# tftest-file id=services-range path=config/internal-ranges/services-range.yaml schema=internal-range.schema.json +``` + +```yaml +region: europe-west1 +reserved_internal_range: subnet-range +secondary_ip_ranges: + pods: + reserved_internal_range: pods-range + services: + reserved_internal_range: services-range + traditional: + ip_cidr_range: "192.168.0.0/24" + +# tftest-file id=subnet path=config/subnets/production.yaml schema=subnet.schema.json +``` ## Variables | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L106) | The name of the network being created. | string | ✓ | | -| [project_id](variables.tf#L183) | The ID of the project where this VPC will be created. | string | ✓ | | +| [name](variables.tf#L171) | The name of the network being created. | string | ✓ | | +| [project_id](variables.tf#L248) | The ID of the project where this VPC will be created. | string | ✓ | | | [auto_create_subnetworks](variables.tf#L17) | Set to true to create an auto mode subnet, defaults to custom mode. | bool | | false | | [context](variables.tf#L23) | Context-specific interpolations. | object({…}) | | {} | | [create_googleapis_routes](variables.tf#L32) | Toggle creation of googleapis private/restricted routes. Disabled when vpc creation is turned off, or when set to null. | object({…}) | | {} | | [delete_default_routes_on_create](variables.tf#L45) | Set to true to delete the default routes at creation time. | bool | | false | | [description](variables.tf#L51) | An optional description of this resource (triggers recreation on change). | string | | "Terraform-managed." | | [dns_policy](variables.tf#L57) | DNS policy setup for the VPC. | object({…}) | | null | -| [factories_config](variables.tf#L70) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} | -| [firewall_policy_enforcement_order](variables.tf#L78) | Order that Firewall Rules and Firewall Policies are evaluated. Can be either 'BEFORE_CLASSIC_FIREWALL' or 'AFTER_CLASSIC_FIREWALL'. | string | | "AFTER_CLASSIC_FIREWALL" | -| [ipv6_config](variables.tf#L90) | Optional IPv6 configuration for this network. | object({…}) | | {} | -| [mtu](variables.tf#L100) | Maximum Transmission Unit in bytes. The minimum value for this field is 1460 (the default) and the maximum value is 1500 bytes. | number | | null | -| [network_attachments](variables.tf#L111) | PSC network attachments, names as keys. | map(object({…})) | | {} | -| [peering_config](variables.tf#L124) | VPC peering configuration. | object({…}) | | null | -| [policy_based_routes](variables.tf#L135) | Policy based routes, keyed by name. | map(object({…})) | | {} | -| [psa_configs](variables.tf#L188) | The Private Service Access configuration. | list(object({…})) | | [] | -| [routes](variables.tf#L219) | Network routes, keyed by name. | map(object({…})) | | {} | -| [routing_mode](variables.tf#L240) | The network routing mode (default 'GLOBAL'). | string | | "GLOBAL" | -| [shared_vpc_host](variables.tf#L250) | Enable shared VPC for this project. | bool | | false | -| [shared_vpc_service_projects](variables.tf#L256) | Shared VPC service projects to register with this host. | list(string) | | [] | -| [subnets](variables.tf#L262) | Subnet configuration. | list(object({…})) | | [] | -| [subnets_private_nat](variables.tf#L311) | List of private NAT subnets. | list(object({…})) | | [] | -| [subnets_proxy_only](variables.tf#L323) | List of proxy-only subnets for Regional HTTPS or Internal HTTPS load balancers. Note: Only one proxy-only subnet for each VPC network in each region can be active. | list(object({…})) | | [] | -| [subnets_psc](variables.tf#L357) | List of subnets for Private Service Connect service producers. | list(object({…})) | | [] | -| [vpc_reuse](variables.tf#L389) | Reuse existing VPC if not null. If the network_id number is not passed in, a data source is used. | object({…}) | | null | +| [factories_config](variables.tf#L70) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} | +| [firewall_policy_enforcement_order](variables.tf#L79) | Order that Firewall Rules and Firewall Policies are evaluated. Can be either 'BEFORE_CLASSIC_FIREWALL' or 'AFTER_CLASSIC_FIREWALL'. | string | | "AFTER_CLASSIC_FIREWALL" | +| [internal_ranges](variables.tf#L91) | Internal range configuration for IPAM operations within the VPC network. | list(object({…})) | | [] | +| [ipv6_config](variables.tf#L155) | Optional IPv6 configuration for this network. | object({…}) | | {} | +| [mtu](variables.tf#L165) | Maximum Transmission Unit in bytes. The minimum value for this field is 1460 (the default) and the maximum value is 1500 bytes. | number | | null | +| [network_attachments](variables.tf#L176) | PSC network attachments, names as keys. | map(object({…})) | | {} | +| [peering_config](variables.tf#L189) | VPC peering configuration. | object({…}) | | null | +| [policy_based_routes](variables.tf#L200) | Policy based routes, keyed by name. | map(object({…})) | | {} | +| [psa_configs](variables.tf#L253) | The Private Service Access configuration. | list(object({…})) | | [] | +| [routes](variables.tf#L284) | Network routes, keyed by name. | map(object({…})) | | {} | +| [routing_mode](variables.tf#L305) | The network routing mode (default 'GLOBAL'). | string | | "GLOBAL" | +| [shared_vpc_host](variables.tf#L315) | Enable shared VPC for this project. | bool | | false | +| [shared_vpc_service_projects](variables.tf#L321) | Shared VPC service projects to register with this host. | list(string) | | [] | +| [subnets](variables.tf#L327) | Subnet configuration. | list(object({…})) | | [] | +| [subnets_private_nat](variables.tf#L407) | List of private NAT subnets. | list(object({…})) | | [] | +| [subnets_proxy_only](variables.tf#L419) | List of proxy-only subnets for Regional HTTPS or Internal HTTPS load balancers. Note: Only one proxy-only subnet for each VPC network in each region can be active. | list(object({…})) | | [] | +| [subnets_psc](variables.tf#L453) | List of subnets for Private Service Connect service producers. | list(object({…})) | | [] | +| [vpc_reuse](variables.tf#L485) | Reuse existing VPC if not null. If the network_id number is not passed in, a data source is used. | object({…}) | | null | ## Outputs @@ -791,19 +932,22 @@ module "vpc" { |---|---|:---:| | [id](outputs.tf#L17) | Fully qualified network id. | | | [internal_ipv6_range](outputs.tf#L29) | ULA range. | | -| [name](outputs.tf#L34) | Network name. | | -| [network](outputs.tf#L46) | Network resource. | | -| [network_attachment_ids](outputs.tf#L58) | IDs of network attachments. | | -| [project_id](outputs.tf#L66) | Project ID containing the network. Use this when you need to create resources *after* the VPC is fully set up (e.g. subnets created, shared VPC service projects attached, Private Service Networking configured). | | -| [self_link](outputs.tf#L79) | Network self link. | | -| [subnet_ids](outputs.tf#L91) | Map of subnet IDs keyed by name. | | -| [subnet_ips](outputs.tf#L100) | Map of subnet address ranges keyed by name. | | -| [subnet_ipv6_external_prefixes](outputs.tf#L107) | Map of subnet external IPv6 prefixes keyed by name. | | -| [subnet_regions](outputs.tf#L115) | Map of subnet regions keyed by name. | | -| [subnet_secondary_ranges](outputs.tf#L122) | Map of subnet secondary ranges keyed by name. | | -| [subnet_self_links](outputs.tf#L133) | Map of subnet self links keyed by name. | | -| [subnets](outputs.tf#L142) | Subnet resources. | | -| [subnets_private_nat](outputs.tf#L151) | Private NAT subnet resources. | | -| [subnets_proxy_only](outputs.tf#L156) | L7 ILB or L7 Regional LB subnet resources. | | -| [subnets_psc](outputs.tf#L161) | Private Service Connect subnet resources. | | +| [internal_range_ids](outputs.tf#L34) | Map of internal range IDs keyed by name. | | +| [internal_range_ip_cidr_ranges](outputs.tf#L39) | Map of internal range IP CIDR ranges keyed by name. | | +| [internal_ranges](outputs.tf#L46) | Internal range resources. | | +| [name](outputs.tf#L51) | Network name. | | +| [network](outputs.tf#L63) | Network resource. | | +| [network_attachment_ids](outputs.tf#L75) | IDs of network attachments. | | +| [project_id](outputs.tf#L83) | Project ID containing the network. Use this when you need to create resources *after* the VPC is fully set up (e.g. subnets created, shared VPC service projects attached, Private Service Networking configured). | | +| [self_link](outputs.tf#L96) | Network self link. | | +| [subnet_ids](outputs.tf#L108) | Map of subnet IDs keyed by name. | | +| [subnet_ips](outputs.tf#L117) | Map of subnet address ranges keyed by name. | | +| [subnet_ipv6_external_prefixes](outputs.tf#L124) | Map of subnet external IPv6 prefixes keyed by name. | | +| [subnet_regions](outputs.tf#L132) | Map of subnet regions keyed by name. | | +| [subnet_secondary_ranges](outputs.tf#L139) | Map of subnet secondary ranges keyed by name. | | +| [subnet_self_links](outputs.tf#L150) | Map of subnet self links keyed by name. | | +| [subnets](outputs.tf#L159) | Subnet resources. | | +| [subnets_private_nat](outputs.tf#L168) | Private NAT subnet resources. | | +| [subnets_proxy_only](outputs.tf#L173) | L7 ILB or L7 Regional LB subnet resources. | | +| [subnets_psc](outputs.tf#L178) | Private Service Connect subnet resources. | | diff --git a/modules/net-vpc/internal-ranges.tf b/modules/net-vpc/internal-ranges.tf new file mode 100644 index 000000000..2304919d4 --- /dev/null +++ b/modules/net-vpc/internal-ranges.tf @@ -0,0 +1,100 @@ +/** + * Copyright 2024 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 Internal range resources. + +locals { + _internal_ranges_factory_data_raw = { + for f in try(fileset(local._internal_ranges_factory_path, "**/*.yaml"), []) : + trimsuffix(basename(f), ".yaml") => yamldecode(file("${local._internal_ranges_factory_path}/${f}")) + } + _internal_ranges_factory_data = { + for k, v in local._internal_ranges_factory_data_raw : k => merge(v, { + name = try(v.name, k) + }) + } + _internal_ranges_factory_path = try(pathexpand(var.factories_config.internal_ranges_folder), null) + _factory_internal_ranges = { + for k, v in local._internal_ranges_factory_data : + try(v.name, k) => { + description = try(v.description, null) + ip_cidr_range = try(v.ip_cidr_range, null) + labels = try(v.labels, {}) + name = try(v.name, k) + network = try(v.network, local.network.id) + usage = v.usage + peering = v.peering + prefix_length = try(v.prefix_length, null) + target_cidr_range = try(v.target_cidr_range, null) + exclude_cidr_ranges = try(v.exclude_cidr_ranges, null) + overlaps = try(v.overlaps, null) + immutable = try(v.immutable, null) + allocation_options = !can(v.allocation_options) ? null : { + allocation_strategy = try(v.allocation_options.allocation_strategy, null) + first_available_ranges_lookup_size = try(v.allocation_options.first_available_ranges_lookup_size, null) + } + migration = !can(v.migration) ? null : { + source = v.migration.source + target = v.migration.target + } + } + } + + internal_ranges = merge( + { for r in var.internal_ranges : r.name => r }, + local._factory_internal_ranges + ) + + internal_ranges_ids = { + for k, v in google_network_connectivity_internal_range.internal_range : + k => v.id + } +} + +resource "google_network_connectivity_internal_range" "internal_range" { + provider = google-beta + for_each = local.internal_ranges + project = var.project_id + name = each.value.name + network = local.network.id + + description = each.value.description + ip_cidr_range = each.value.ip_cidr_range + labels = each.value.labels + usage = each.value.usage + peering = each.value.peering + prefix_length = each.value.prefix_length + target_cidr_range = each.value.target_cidr_range + exclude_cidr_ranges = each.value.exclude_cidr_ranges + overlaps = each.value.overlaps + immutable = each.value.immutable + + dynamic "allocation_options" { + for_each = each.value.allocation_options != null ? [""] : [] + content { + allocation_strategy = each.value.allocation_options.allocation_strategy + first_available_ranges_lookup_size = each.value.allocation_options.first_available_ranges_lookup_size + } + } + + dynamic "migration" { + for_each = each.value.migration != null ? [""] : [] + content { + source = each.value.migration.source + target = each.value.migration.target + } + } +} diff --git a/modules/net-vpc/outputs.tf b/modules/net-vpc/outputs.tf index 8184e96d4..1e162ffb7 100644 --- a/modules/net-vpc/outputs.tf +++ b/modules/net-vpc/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. @@ -31,6 +31,23 @@ output "internal_ipv6_range" { value = try(google_compute_network.network[0].internal_ipv6_range, null) } +output "internal_range_ids" { + description = "Map of internal range IDs keyed by name." + value = { for k, v in google_network_connectivity_internal_range.internal_range : k => v.id } +} + +output "internal_range_ip_cidr_ranges" { + description = "Map of internal range IP CIDR ranges keyed by name." + value = { + for k, v in google_network_connectivity_internal_range.internal_range : k => v.ip_cidr_range + } +} + +output "internal_ranges" { + description = "Internal range resources." + value = { for k, v in google_network_connectivity_internal_range.internal_range : k => v } +} + output "name" { description = "Network name." value = local.network.name diff --git a/modules/net-vpc/schemas/internal-range.schema.json b/modules/net-vpc/schemas/internal-range.schema.json new file mode 100644 index 000000000..02f8eb938 --- /dev/null +++ b/modules/net-vpc/schemas/internal-range.schema.json @@ -0,0 +1,110 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InternalRange", + "type": "object", + "additionalProperties": false, + "required": [ + "usage", + "peering" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the internal range. If not provided, the filename will be used." + }, + "description": { + "type": "string", + "description": "An optional description of this internal range." + }, + "labels": { + "type": "object", + "description": "User-defined labels.", + "additionalProperties": { + "type": "string" + } + }, + "ip_cidr_range": { + "type": "string", + "description": "The IP range that this internal range defines. Note: IPv6 ranges are limited to usage=EXTERNAL_TO_VPC and peering=FOR_SELF. For IPv6 ranges this field is compulsory." + }, + "usage": { + "type": "string", + "enum": ["FOR_VPC", "EXTERNAL_TO_VPC", "FOR_MIGRATION"], + "description": "The type of usage set for this InternalRange." + }, + "peering": { + "type": "string", + "enum": ["FOR_SELF", "FOR_PEER", "NOT_SHARED"], + "description": "The type of peering set for this internal range." + }, + "prefix_length": { + "type": "integer", + "minimum": 1, + "maximum": 32, + "description": "An alternate to ip_cidr_range. Can be set when trying to create a reservation that automatically finds a free range of the given size." + }, + "target_cidr_range": { + "type": "array", + "description": "Optional. Can be set to narrow down or pick a different address space while searching for a free range.", + "items": { + "type": "string" + } + }, + "exclude_cidr_ranges": { + "type": "array", + "description": "Optional. List of IP CIDR ranges to be excluded. Only IPv4 CIDR ranges are supported.", + "items": { + "type": "string" + } + }, + "allocation_options": { + "type": "object", + "description": "Options for automatically allocating a free range with a size given by prefixLength.", + "additionalProperties": false, + "properties": { + "allocation_strategy": { + "type": "string", + "enum": ["RANDOM", "FIRST_AVAILABLE", "RANDOM_FIRST_N_AVAILABLE", "FIRST_SMALLEST_FITTING"], + "description": "Sets the strategy used to automatically find a free range of a size given by prefixLength." + }, + "first_available_ranges_lookup_size": { + "type": "integer", + "minimum": 1, + "description": "Must be set when allocation_strategy is RANDOM_FIRST_N_AVAILABLE, otherwise must remain unset." + } + } + }, + "overlaps": { + "type": "array", + "description": "Optional. Types of resources that are allowed to overlap with the current internal range.", + "items": { + "type": "string", + "enum": ["OVERLAP_ROUTE_RANGE", "OVERLAP_EXISTING_SUBNET_RANGE"] + } + }, + "migration": { + "type": "object", + "description": "Specification for migration with source and target resource names.", + "additionalProperties": false, + "required": ["source", "target"], + "properties": { + "source": { + "type": "string", + "description": "Resource path as an URI of the source resource, for example a subnet." + }, + "target": { + "type": "string", + "description": "Resource path of the target resource. The target project can be different." + } + } + }, + "immutable": { + "type": "boolean", + "description": "Immutable ranges cannot have their fields modified, except for labels and description." + } + }, + "anyOf": [ + {"required": ["ip_cidr_range"]}, + {"required": ["prefix_length"]} + ] +} diff --git a/modules/net-vpc/schemas/subnet.schema.json b/modules/net-vpc/schemas/subnet.schema.json index 48f8093c6..6e1095692 100644 --- a/modules/net-vpc/schemas/subnet.schema.json +++ b/modules/net-vpc/schemas/subnet.schema.json @@ -4,9 +4,21 @@ "type": "object", "additionalProperties": false, "required": [ - "ip_cidr_range", "region" ], + "anyOf": [ + {"required": ["ip_cidr_range"]}, + {"required": ["reserved_internal_range"]}, + {"required": ["ip_collection"]}, + { + "allOf": [ + {"not": {"required": ["ip_cidr_range"]}}, + {"not": {"required": ["reserved_internal_range"]}}, + {"not": {"required": ["ip_collection"]}}, + {"properties": {"ipv6": {"properties": {"ipv6_only": {"const": true}}}}, "required": ["ipv6"]} + ] + } + ], "properties": { "active": { "type": "boolean" @@ -50,6 +62,10 @@ "ip_cidr_range": { "type": "string" }, + "reserved_internal_range": { + "type": "string", + "description": "Name of the internal range to use for this subnet. Mutually exclusive with ip_cidr_range and ip_collection." + }, "ipv6": { "type": "object", "additionalProperties": false, @@ -80,7 +96,30 @@ "secondary_ip_ranges": { "type": "object", "additionalProperties": { - "type": "string" + "oneOf": [ + { + "type": "string", + "description": "IP CIDR range for backward compatibility" + }, + { + "type": "object", + "additionalProperties": false, + "anyOf": [ + {"required": ["ip_cidr_range"]}, + {"required": ["reserved_internal_range"]} + ], + "properties": { + "ip_cidr_range": { + "type": "string", + "description": "IP CIDR range for this secondary range" + }, + "reserved_internal_range": { + "type": "string", + "description": "Name of the internal range to use for this secondary range" + } + } + } + ] } }, "iam": { @@ -189,4 +228,4 @@ } } } -} \ No newline at end of file +} diff --git a/modules/net-vpc/subnets.tf b/modules/net-vpc/subnets.tf index 06f103003..5d3ff70f0 100644 --- a/modules/net-vpc/subnets.tf +++ b/modules/net-vpc/subnets.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. @@ -34,6 +34,7 @@ locals { description = try(v.description, null) enable_private_access = try(v.enable_private_access, true) allow_subnet_cidr_routes_overlap = try(v.allow_subnet_cidr_routes_overlap, null) + reserved_internal_range = try(v.reserved_internal_range, null) flow_logs_config = can(v.flow_logs_config) ? { aggregation_interval = try(v.flow_logs_config.aggregation_interval, null) filter_expression = try(v.flow_logs_config.filter_expression, null) @@ -42,16 +43,22 @@ locals { metadata_fields = try(v.flow_logs_config.metadata_fields, null) } : null global = try(v.global, false) - ip_cidr_range = v.ip_cidr_range + ip_cidr_range = try(v.ip_cidr_range, null) ipv6 = !can(v.ipv6) ? null : { access_type = try(v.ipv6.access_type, "INTERNAL") ipv6_only = try(v.ipv6.ipv6_only, false) } - ip_collection = try(v.ip_collection, null) - name = try(v.name, k) - region = v.region_computed - secondary_ip_ranges = try(v.secondary_ip_ranges, null) - iam = try(v.iam, {}) + ip_collection = try(v.ip_collection, null) + name = try(v.name, k) + region = v.region_computed + secondary_ip_ranges = !can(v.secondary_ip_ranges) ? null : { + for k2, v2 in v.secondary_ip_ranges : + k2 => { + ip_cidr_range = try(v2.ip_cidr_range, null) + reserved_internal_range = try(v2.reserved_internal_range, null) + } + } + iam = try(v.iam, {}) iam_bindings = !can(v.iam_bindings) ? {} : { for k2, v2 in v.iam_bindings : k2 => { @@ -151,6 +158,11 @@ resource "google_compute_subnetwork" "subnetwork" { region = each.value.region ip_cidr_range = try(each.value.ipv6.ipv6_only, false) ? null : each.value.ip_cidr_range allow_subnet_cidr_routes_overlap = each.value.allow_subnet_cidr_routes_overlap + reserved_internal_range = ( + each.value.reserved_internal_range != null + ? "networkconnectivity.googleapis.com/${try(local.internal_ranges_ids[each.value.reserved_internal_range], each.value.reserved_internal_range)}" + : null + ) description = ( # Set description to an empty string (eg "") to create subnet without a description. each.value.description == null @@ -178,7 +190,12 @@ resource "google_compute_subnetwork" "subnetwork" { for_each = each.value.secondary_ip_ranges == null ? {} : each.value.secondary_ip_ranges content { range_name = secondary_ip_range.key - ip_cidr_range = secondary_ip_range.value + ip_cidr_range = try(secondary_ip_range.value.ip_cidr_range, secondary_ip_range.value) + reserved_internal_range = ( + try(secondary_ip_range.value.reserved_internal_range, null) != null + ? "networkconnectivity.googleapis.com/${try(local.internal_ranges_ids[secondary_ip_range.value.reserved_internal_range], secondary_ip_range.value.reserved_internal_range)}" + : null + ) } } dynamic "log_config" { diff --git a/modules/net-vpc/variables.tf b/modules/net-vpc/variables.tf index 4b44c6ac1..646a75212 100644 --- a/modules/net-vpc/variables.tf +++ b/modules/net-vpc/variables.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. @@ -70,7 +70,8 @@ variable "dns_policy" { variable "factories_config" { description = "Paths to data files and folders that enable factory functionality." type = object({ - subnets_folder = optional(string) + subnets_folder = optional(string) + internal_ranges_folder = optional(string) }) default = {} } @@ -87,6 +88,70 @@ variable "firewall_policy_enforcement_order" { } } +variable "internal_ranges" { + description = "Internal range configuration for IPAM operations within the VPC network." + type = list(object({ + name = string + description = optional(string) + ip_cidr_range = optional(string) + labels = optional(map(string), {}) + usage = string + peering = string + prefix_length = optional(number) + target_cidr_range = optional(list(string)) + exclude_cidr_ranges = optional(list(string)) + overlaps = optional(list(string)) + immutable = optional(bool) + allocation_options = optional(object({ + allocation_strategy = optional(string) + first_available_ranges_lookup_size = optional(number) + })) + migration = optional(object({ + source = string + target = string + })) + })) + default = [] + nullable = false + validation { + condition = alltrue([ + for r in var.internal_ranges : + contains(["FOR_VPC", "EXTERNAL_TO_VPC", "FOR_MIGRATION"], r.usage) + ]) + error_message = "Usage must be one of: FOR_VPC, EXTERNAL_TO_VPC, FOR_MIGRATION." + } + validation { + condition = alltrue([ + for r in var.internal_ranges : + contains(["FOR_SELF", "FOR_PEER", "NOT_SHARED"], r.peering) + ]) + error_message = "Peering must be one of: FOR_SELF, FOR_PEER, NOT_SHARED." + } + validation { + condition = alltrue([ + for r in var.internal_ranges : ( + r.allocation_options == null || + try(r.allocation_options.allocation_strategy, null) == null || + contains( + ["RANDOM", "FIRST_AVAILABLE", "RANDOM_FIRST_N_AVAILABLE", "FIRST_SMALLEST_FITTING"], + try(r.allocation_options.allocation_strategy, "") + ) + ) + ]) + error_message = "Allocation strategy must be one of: RANDOM, FIRST_AVAILABLE, RANDOM_FIRST_N_AVAILABLE, FIRST_SMALLEST_FITTING." + } + validation { + condition = alltrue([ + for r in var.internal_ranges : + r.overlaps == null || alltrue([ + for overlap in coalesce(r.overlaps, []) : + contains(["OVERLAP_ROUTE_RANGE", "OVERLAP_EXISTING_SUBNET_RANGE"], overlap) + ]) + ]) + error_message = "Overlaps must contain only: OVERLAP_ROUTE_RANGE, OVERLAP_EXISTING_SUBNET_RANGE." + } +} + variable "ipv6_config" { description = "Optional IPv6 configuration for this network." type = object({ @@ -263,11 +328,12 @@ variable "subnets" { description = "Subnet configuration." type = list(object({ name = string - ip_cidr_range = string + ip_cidr_range = optional(string) region = string description = optional(string) enable_private_access = optional(bool, true) allow_subnet_cidr_routes_overlap = optional(bool, null) + reserved_internal_range = optional(string) flow_logs_config = optional(object({ aggregation_interval = optional(string) filter_expression = optional(string) @@ -282,9 +348,12 @@ variable "subnets" { # enable_private_access = optional(string) ipv6_only = optional(bool, false) })) - ip_collection = optional(string, null) - secondary_ip_ranges = optional(map(string)) - iam = optional(map(list(string)), {}) + ip_collection = optional(string, null) + secondary_ip_ranges = optional(map(object({ + ip_cidr_range = optional(string) + reserved_internal_range = optional(string) + }))) + iam = optional(map(list(string)), {}) iam_bindings = optional(map(object({ role = string members = list(string) @@ -306,6 +375,33 @@ variable "subnets" { })) default = [] nullable = false + validation { + condition = alltrue([ + for s in var.subnets : + ( + length([ + for field in [s.ip_cidr_range, s.reserved_internal_range, s.ip_collection] : + field if field != null + ]) == 1 + ) || ( + length([ + for field in [s.ip_cidr_range, s.reserved_internal_range, s.ip_collection] : + field if field != null + ]) == 0 && try(s.ipv6.ipv6_only, false) == true + ) + ]) + error_message = "Each subnet must specify exactly one of ip_cidr_range, reserved_internal_range, or ip_collection, or all three can be null for IPv6-only subnets (ipv6.ipv6_only = true)." + } + validation { + condition = alltrue([ + for s in var.subnets : + s.secondary_ip_ranges == null || alltrue([ + for range_name, range_config in coalesce(s.secondary_ip_ranges, {}) : + (range_config.ip_cidr_range != null) != (range_config.reserved_internal_range != null) + ]) + ]) + error_message = "Each secondary IP range must specify either ip_cidr_range or reserved_internal_range, but not both." + } } variable "subnets_private_nat" { diff --git a/modules/project-factory/README.md b/modules/project-factory/README.md index 7a276ad1e..b33547d31 100644 --- a/modules/project-factory/README.md +++ b/modules/project-factory/README.md @@ -29,6 +29,7 @@ The code is meant to be executed by a high level service account with powerful p - [Folder hierarchy](#folder-hierarchy) - [Projects](#projects) - [Factory-wide project defaults, merges, optionals](#factory-wide-project-defaults-merges-optionals) + - [Project templates](#project-templates) - [Service accounts and buckets](#service-accounts-and-buckets) - [Automation project and resources](#automation-project-and-resources) - [Billing budgets](#billing-budgets) @@ -74,6 +75,16 @@ In addition to the YAML-based project configurations, the factory accepts three Some examples on where to use each of the three sets are [provided below](#example). +### Project templates + +Project templates are project definitions that can be "inherited" and extended in YAML-based project configurations. Templates are YAML files which use the same schema as a project, but which don't directly trigger project creation by themselves. + +When referenced in a project configuration file, a template attributes are used as the initial project definition, over which the project's own attributes are merged. The merge is shallow, so any attribute which is defined in the project configuration will take precedence and completely override the template's own definition. + +For example, declaring `iam` or `org_policies` in the template and then doing the same in the project file will result in those two attributes in the template being ignored. + +The set of available templates is defined via a dedicated path in the `factories_config` file, and then a template can be referenced from a project definition via the `project_template` YAML attribute. + ### Service accounts and buckets Service accounts and GCS buckets can be managed as part of each project's YAML configuration. This allows creation of default service accounts used for GCE instances, in firewall rules, or for application-level credentials without resorting to a separate Terraform configuration. @@ -343,6 +354,7 @@ module "project-factory" { gcp-devops = "group:gcp-devops@example.org" } tag_values = { + "context/gke" = "tagValues/654321" "org-policies/drs-allow-all" = "tagValues/123456" } vpc_host_projects = { @@ -376,8 +388,9 @@ module "project-factory" { billing_account_id = var.billing_account_id data = "data/budgets" } - folders = "data/hierarchy" - projects = "data/projects" + folders = "data/hierarchy" + project_templates = "data/templates" + projects = "data/projects" } notification_channels = { billing-default = { @@ -389,7 +402,24 @@ module "project-factory" { } } } -# tftest files=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 inventory=example.yaml +``` + +A project template for GKE projects: + +```yaml +services: + - compute.googleapis.com + - container.googleapis.com + - storage.googleapis.com +service_encryption_key_ids: + storage.googleapis.com: + - projects/kms-central-prj/locations/europe-west3/keyRings/my-keyring/cryptoKeys/europe3-gce + compute.googleapis.com: + - $kms_keys:compute-prod-ew1 +tag_bindings: + context: $tag_values:context/gke +# tftest-file id=t0 path=data/templates/container/base.yaml schema=project.schema.json ``` A simple hierarchy of folders: @@ -443,20 +473,14 @@ services: More traditional project definitions via the project factory data: ```yaml +# inherit template attributes +project_template: container/base +# define project attributes (potentially overriding template) billing_account: 012345-67890A-BCDEF0 labels: app: app-0 team: team-a parent: $folder_ids:team-a/app-0 -service_encryption_key_ids: - storage.googleapis.com: - - projects/kms-central-prj/locations/europe-west3/keyRings/my-keyring/cryptoKeys/europe3-gce - compute.googleapis.com: - - $kms_keys:compute-prod-ew1 -services: - - compute.googleapis.com - - container.googleapis.com - - storage.googleapis.com iam_by_principals: $iam_principals:service_accounts/dev-ta-app0-be/app-0-be: - roles/storage.objectViewer @@ -623,7 +647,7 @@ service_accounts: | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [factories_config](variables.tf#L173) | Path to folder with YAML resource description data files. | object({…}) | ✓ | | +| [factories_config](variables.tf#L173) | 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#L108) | Optional values that will be merged with corresponding data from files. Combines with `data_defaults`, file data, and `data_overrides`. | object({…}) | | {} | diff --git a/modules/project-factory/projects.tf b/modules/project-factory/projects.tf index ac7fbab6b..98f91b761 100644 --- a/modules/project-factory/projects.tf +++ b/modules/project-factory/projects.tf @@ -27,7 +27,9 @@ locals { } _projects_input = { for k, v in merge(local._folder_projects_raw, local._projects_raw) : - basename(k) => v + basename(k) => merge( + try(local._templates_raw[v.project_template], {}), v + ) } _projects_path = try( pathexpand(var.factories_config.projects), null @@ -36,6 +38,13 @@ locals { for f in try(fileset(local._projects_path, "**/*.yaml"), []) : trimsuffix(f, ".yaml") => yamldecode(file("${local._projects_path}/${f}")) } + _templates_path = try( + pathexpand(var.factories_config.project_templates), null + ) + _templates_raw = { + for f in try(fileset(local._templates_path, "**/*.yaml"), []) : + trimsuffix(f, ".yaml") => yamldecode(file("${local._templates_path}/${f}")) + } ctx_project_ids = merge(local.ctx.project_ids, local.project_ids) project_ids = { for k, v in module.projects : k => v.project_id diff --git a/modules/project-factory/schemas/project.schema.json b/modules/project-factory/schemas/project.schema.json index 626dfcc2a..d0df5f57a 100644 --- a/modules/project-factory/schemas/project.schema.json +++ b/modules/project-factory/schemas/project.schema.json @@ -295,6 +295,9 @@ } } }, + "project_template": { + "type": "string" + }, "service_accounts": { "type": "object", "additionalProperties": false, diff --git a/modules/project-factory/variables.tf b/modules/project-factory/variables.tf index 176d33492..7afdaf1f8 100644 --- a/modules/project-factory/variables.tf +++ b/modules/project-factory/variables.tf @@ -173,8 +173,9 @@ variable "data_overrides" { variable "factories_config" { description = "Path to folder with YAML resource description data files." type = object({ - folders = optional(string) - projects = optional(string) + folders = optional(string) + project_templates = optional(string) + projects = optional(string) budgets = optional(object({ billing_account_id = string data = string diff --git a/tests/collectors.py b/tests/collectors.py index 66ac5c54e..08e30a3ea 100644 --- a/tests/collectors.py +++ b/tests/collectors.py @@ -21,6 +21,7 @@ See FabricTestFile for details on the file structure. import fnmatch import json +import os import re from pathlib import Path @@ -61,7 +62,6 @@ class FabricTestFile(pytest.File): will be taken from the file test-name.yaml """ - try: raw = yaml.safe_load(self.path.open()) module = raw.pop('module') @@ -72,6 +72,8 @@ class FabricTestFile(pytest.File): common = raw.pop('common_tfvars', []) for test_name, spec in raw.get('tests', {}).items(): spec = {} if spec is None else spec + if spec.get('skip_tofu') and os.environ.get('TERRAFORM') == 'tofu': + continue extra_dirs = spec.get('extra_dirs') extra_files = spec.get('extra_files') inventories = spec.get('inventory', [f'{test_name}.yaml']) diff --git a/tests/fixtures/shared-vpc.tf b/tests/fixtures/shared-vpc.tf index fcab1b9a7..2fc18972d 100644 --- a/tests/fixtures/shared-vpc.tf +++ b/tests/fixtures/shared-vpc.tf @@ -1,5 +1,5 @@ /** - * 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. @@ -70,8 +70,8 @@ module "net-vpc-host" { name = "fixture-subnet-24" region = var.region secondary_ip_ranges = { - pods = "172.16.0.0/20" - services = "192.168.0.0/24" + pods = { ip_cidr_range = "172.16.0.0/20" } + services = { ip_cidr_range = "192.168.0.0/24" } } }, { @@ -79,8 +79,8 @@ module "net-vpc-host" { name = "fixture-subnet-28" region = var.region secondary_ip_ranges = { - pods = "172.16.16.0/20" - services = "192.168.1.0/24" + pods = { ip_cidr_range = "172.16.16.0/20" } + services = { ip_cidr_range = "192.168.1.0/24" } } } diff --git a/tests/modules/apigee/examples/access-logging.yaml b/tests/modules/apigee/examples/access-logging.yaml new file mode 100644 index 000000000..b54f811cd --- /dev/null +++ b/tests/modules/apigee/examples/access-logging.yaml @@ -0,0 +1,32 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.apigee.google_apigee_instance.instances["europe-west1"]: + access_logging_config: + - enabled: true + filter: statusCode >= 200 && statusCode < 300 + description: Terraform-managed + disk_encryption_key_name: null + display_name: null + ip_range: 10.0.4.0/22,10.1.1.0/28 + location: europe-west1 + name: instance-europe-west1 + org_id: organizations/my-project + timeouts: null + +counts: + google_apigee_instance: 1 + modules: 1 + resources: 1 diff --git a/tests/modules/net_lb_app_ext/examples/classic-vs-non-classic.yaml b/tests/modules/net_lb_app_ext/examples/classic-vs-non-classic.yaml index 6029352c0..7a5110c44 100644 --- a/tests/modules/net_lb_app_ext/examples/classic-vs-non-classic.yaml +++ b/tests/modules/net_lb_app_ext/examples/classic-vs-non-classic.yaml @@ -71,7 +71,7 @@ values: timeout_sec: 5 timeouts: null unhealthy_threshold: 2 - module.glb-0.google_compute_target_http_proxy.default[0]: + module.glb-0.google_compute_target_http_proxy.new[0]: description: Terraform managed. http_keep_alive_timeout_sec: null name: glb-test-0 diff --git a/tests/modules/net_lb_app_ext/examples/http-https-redirect.yaml b/tests/modules/net_lb_app_ext/examples/http-https-redirect.yaml index f4a4b65aa..9db14c35b 100644 --- a/tests/modules/net_lb_app_ext/examples/http-https-redirect.yaml +++ b/tests/modules/net_lb_app_ext/examples/http-https-redirect.yaml @@ -16,17 +16,195 @@ values: module.addresses.google_compute_global_address.global["glb-test-0"]: address_type: null description: Terraform managed. + effective_labels: + goog-terraform-provisioned: 'true' ip_version: null + labels: null name: glb-test-0 network: null project: project-id purpose: null + terraform_labels: + goog-terraform-provisioned: 'true' timeouts: null + module.compute-vm-group-b.google_compute_instance.default[0]: + advanced_machine_features: [] + allow_stopping_for_update: true + attached_disk: [] + boot_disk: + - auto_delete: true + disk_encryption_key_raw: null + disk_encryption_key_rsa: null + disk_encryption_service_account: null + force_attach: null + initialize_params: + - enable_confidential_compute: null + image: cos-cloud/cos-stable + resource_manager_tags: null + size: 10 + source_image_encryption_key: [] + source_snapshot_encryption_key: [] + storage_pool: null + type: pd-balanced + interface: null + mode: READ_WRITE + can_ip_forward: false + deletion_protection: false + description: Managed by the compute-vm Terraform module. + desired_status: null + effective_labels: + goog-terraform-provisioned: 'true' + enable_display: false + hostname: null + instance_encryption_key: [] + key_revocation_action_type: null + labels: null + machine_type: f1-micro + metadata: null + metadata_startup_script: null + name: my-ig-b + network_interface: + - access_config: [] + alias_ip_range: [] + ipv6_access_config: [] + network: projects/xxx/global/networks/aaa + nic_type: null + queue_count: null + security_policy: null + subnetwork: subnet_self_link + network_performance_config: [] + params: [] + partner_metadata: null + project: project-id + resource_policies: null + scheduling: + - automatic_restart: true + availability_domain: null + graceful_shutdown: [] + host_error_timeout_seconds: null + instance_termination_action: null + local_ssd_recovery_timeout: [] + maintenance_interval: null + max_run_duration: [] + min_node_cpus: null + node_affinities: [] + on_host_maintenance: MIGRATE + on_instance_stop_action: [] + preemptible: false + provisioning_model: STANDARD + termination_time: null + scratch_disk: [] + service_account: + - scopes: + - https://www.googleapis.com/auth/devstorage.read_only + - https://www.googleapis.com/auth/logging.write + - https://www.googleapis.com/auth/monitoring.write + shielded_instance_config: [] + tags: null + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + zone: europe-west8-b + module.compute-vm-group-b.google_compute_instance_group.unmanaged[0]: + description: Managed by the compute-vm Terraform module. + name: my-ig-b + named_port: [] + network: projects/xxx/global/networks/aaa + project: project-id + timeouts: null + zone: europe-west8-b + module.compute-vm-group-c.google_compute_instance.default[0]: + advanced_machine_features: [] + allow_stopping_for_update: true + attached_disk: [] + boot_disk: + - auto_delete: true + disk_encryption_key_raw: null + disk_encryption_key_rsa: null + disk_encryption_service_account: null + force_attach: null + initialize_params: + - enable_confidential_compute: null + image: cos-cloud/cos-stable + resource_manager_tags: null + size: 10 + source_image_encryption_key: [] + source_snapshot_encryption_key: [] + storage_pool: null + type: pd-balanced + interface: null + mode: READ_WRITE + can_ip_forward: false + deletion_protection: false + description: Managed by the compute-vm Terraform module. + desired_status: null + effective_labels: + goog-terraform-provisioned: 'true' + enable_display: false + hostname: null + instance_encryption_key: [] + key_revocation_action_type: null + labels: null + machine_type: f1-micro + metadata: null + metadata_startup_script: null + name: my-ig-c + network_interface: + - access_config: [] + alias_ip_range: [] + ipv6_access_config: [] + network: projects/xxx/global/networks/aaa + nic_type: null + queue_count: null + security_policy: null + subnetwork: subnet_self_link + network_performance_config: [] + params: [] + partner_metadata: null + project: project-id + resource_policies: null + scheduling: + - automatic_restart: true + availability_domain: null + graceful_shutdown: [] + host_error_timeout_seconds: null + instance_termination_action: null + local_ssd_recovery_timeout: [] + maintenance_interval: null + max_run_duration: [] + min_node_cpus: null + node_affinities: [] + on_host_maintenance: MIGRATE + on_instance_stop_action: [] + preemptible: false + provisioning_model: STANDARD + termination_time: null + scratch_disk: [] + service_account: + - scopes: + - https://www.googleapis.com/auth/devstorage.read_only + - https://www.googleapis.com/auth/logging.write + - https://www.googleapis.com/auth/monitoring.write + shielded_instance_config: [] + tags: null + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + zone: europe-west8-c + module.compute-vm-group-c.google_compute_instance_group.unmanaged[0]: + description: Managed by the compute-vm Terraform module. + name: my-ig-c + named_port: [] + network: projects/xxx/global/networks/aaa + project: project-id + timeouts: null + zone: europe-west8-c module.glb-test-0-redirect.google_compute_global_forwarding_rule.default[""]: allow_psc_global_access: null description: Terraform managed. + external_managed_backend_bucket_migration_state: null + external_managed_backend_bucket_migration_testing_percentage: null ip_protocol: TCP - ip_version: __missing__ labels: null load_balancing_scheme: EXTERNAL metadata_filters: [] @@ -43,6 +221,7 @@ values: project: project-id timeouts: null module.glb-test-0-redirect.google_compute_url_map.default: + default_custom_error_response_policy: [] default_route_action: [] default_service: null default_url_redirect: @@ -66,27 +245,39 @@ values: compression_mode: null connection_draining_timeout_sec: 300 consistent_hash: [] + custom_metrics: [] custom_request_headers: null custom_response_headers: null description: Terraform managed. + dynamic_forwarding: [] edge_security_policy: null enable_cdn: null + external_managed_migration_state: null + external_managed_migration_testing_percentage: null + ip_address_selection_policy: null load_balancing_scheme: EXTERNAL_MANAGED locality_lb_policies: [] locality_lb_policy: null + max_stream_duration: [] name: glb-test-0-default + network_pass_through_lb_traffic_policy: [] outlier_detection: [] + params: [] port_name: http project: project-id protocol: HTTP security_policy: null security_settings: [] + service_lb_policy: null + strong_session_affinity_cookie: [] timeouts: null + tls_settings: [] module.glb-test-0.google_compute_global_forwarding_rule.default[""]: allow_psc_global_access: null description: Terraform managed. + external_managed_backend_bucket_migration_state: null + external_managed_backend_bucket_migration_testing_percentage: null ip_protocol: TCP - ip_version: __missing__ labels: null load_balancing_scheme: EXTERNAL_MANAGED metadata_filters: [] @@ -100,6 +291,7 @@ values: check_interval_sec: 5 description: Terraform managed. grpc_health_check: [] + grpc_tls_health_check: [] healthy_threshold: 2 http2_health_check: [] http_health_check: @@ -113,6 +305,7 @@ values: https_health_check: [] name: glb-test-0-default project: project-id + source_regions: null ssl_health_check: [] tcp_health_check: [] timeout_sec: 5 @@ -127,7 +320,7 @@ values: project: project-id timeouts: null type: MANAGED - module.glb-test-0.google_compute_target_https_proxy.default[0]: + module.glb-test-0.google_compute_target_https_proxy.new[0]: certificate_manager_certificates: null certificate_map: null description: Terraform managed. @@ -139,6 +332,7 @@ values: ssl_policy: null timeouts: null module.glb-test-0.google_compute_url_map.default: + default_custom_error_response_policy: [] default_route_action: [] default_url_redirect: [] description: Terraform managed. diff --git a/tests/modules/net_lb_app_ext/examples/serverless-neg.yaml b/tests/modules/net_lb_app_ext/examples/serverless-neg.yaml index 0575e1ad0..fe56e41ac 100644 --- a/tests/modules/net_lb_app_ext/examples/serverless-neg.yaml +++ b/tests/modules/net_lb_app_ext/examples/serverless-neg.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. @@ -62,7 +62,6 @@ values: url_mask: null description: Terraform managed. name: glb-test-0-neg-0 - network: null network_endpoint_type: SERVERLESS project: project-id psc_target_service: null diff --git a/tests/modules/net_vpc/examples/internal-ranges.yaml b/tests/modules/net_vpc/examples/internal-ranges.yaml new file mode 100644 index 000000000..98f9038bc --- /dev/null +++ b/tests/modules/net_vpc/examples/internal-ranges.yaml @@ -0,0 +1,76 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.vpc.google_compute_network.network[0]: + auto_create_subnetworks: false + delete_default_routes_on_create: false + description: Terraform-managed. + enable_ula_internal_ipv6: null + name: my-network + network_firewall_policy_enforcement_order: AFTER_CLASSIC_FIREWALL + network_profile: null + params: [] + project: project-id + routing_mode: GLOBAL + timeouts: null + module.vpc.google_network_connectivity_internal_range.internal_range["range1"]: + allocation_options: [] + description: null + effective_labels: + goog-terraform-provisioned: 'true' + exclude_cidr_ranges: null + immutable: null + ip_cidr_range: 10.0.0.0/16 + labels: null + migration: [] + name: range1 + overlaps: null + peering: FOR_SELF + prefix_length: null + project: project-id + target_cidr_range: null + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + usage: FOR_VPC + module.vpc.google_network_connectivity_internal_range.internal_range["range2"]: + allocation_options: [] + description: Auto-allocated secondary range + effective_labels: + goog-terraform-provisioned: 'true' + exclude_cidr_ranges: null + immutable: null + labels: null + migration: [] + name: range2 + overlaps: null + peering: FOR_SELF + prefix_length: 24 + project: project-id + target_cidr_range: + - 10.1.0.0/16 + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + usage: FOR_VPC + +counts: + google_compute_network: 1 + google_compute_route: 3 + google_network_connectivity_internal_range: 2 + modules: 1 + resources: 6 + +outputs: {} diff --git a/tests/modules/net_vpc/examples/subnets-internal-ranges.yaml b/tests/modules/net_vpc/examples/subnets-internal-ranges.yaml new file mode 100644 index 000000000..9edb090fa --- /dev/null +++ b/tests/modules/net_vpc/examples/subnets-internal-ranges.yaml @@ -0,0 +1,117 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.vpc.google_compute_network.network[0]: + auto_create_subnetworks: false + delete_default_routes_on_create: false + description: Terraform-managed. + enable_ula_internal_ipv6: null + name: my-network + network_firewall_policy_enforcement_order: AFTER_CLASSIC_FIREWALL + network_profile: null + params: [] + project: project-id + routing_mode: GLOBAL + timeouts: null + module.vpc.google_compute_subnetwork.subnetwork["europe-west1/production"]: + description: Terraform-managed. + ip_collection: null + ipv6_access_type: null + log_config: [] + name: production + network: my-network + params: [] + private_ip_google_access: true + project: project-id + region: europe-west1 + role: null + secondary_ip_range: + - range_name: pods + - range_name: services + - ip_cidr_range: 192.168.0.0/24 + range_name: traditional + reserved_internal_range: null + send_secondary_ip_range_if_empty: true + timeouts: null + module.vpc.google_network_connectivity_internal_range.internal_range["pods-range"]: + allocation_options: [] + description: null + effective_labels: + goog-terraform-provisioned: 'true' + exclude_cidr_ranges: null + immutable: null + ip_cidr_range: 10.1.0.0/16 + labels: null + migration: [] + name: pods-range + overlaps: null + peering: FOR_SELF + prefix_length: null + project: project-id + target_cidr_range: null + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + usage: FOR_VPC + module.vpc.google_network_connectivity_internal_range.internal_range["services-range"]: + allocation_options: [] + description: null + effective_labels: + goog-terraform-provisioned: 'true' + exclude_cidr_ranges: null + immutable: null + labels: null + migration: [] + name: services-range + overlaps: null + peering: FOR_SELF + prefix_length: 20 + project: project-id + target_cidr_range: + - 10.2.0.0/16 + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + usage: FOR_VPC + module.vpc.google_network_connectivity_internal_range.internal_range["subnet-range"]: + allocation_options: [] + description: null + effective_labels: + goog-terraform-provisioned: 'true' + exclude_cidr_ranges: null + immutable: null + ip_cidr_range: 10.0.1.0/24 + labels: null + migration: [] + name: subnet-range + overlaps: null + peering: FOR_SELF + prefix_length: null + project: project-id + target_cidr_range: null + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + usage: FOR_VPC + +counts: + google_compute_network: 1 + google_compute_route: 3 + google_compute_subnetwork: 1 + google_network_connectivity_internal_range: 3 + modules: 1 + resources: 8 + +outputs: {} diff --git a/tests/modules/project_factory/examples/example.yaml b/tests/modules/project_factory/examples/example.yaml index f79567cad..4af807cd3 100644 --- a/tests/modules/project_factory/examples/example.yaml +++ b/tests/modules/project_factory/examples/example.yaml @@ -289,6 +289,9 @@ values: : project: test-pf-dev-ta-app0-be service: container.googleapis.com timeouts: null + module.project-factory.module.projects["dev-ta-app0-be"].google_tags_tag_binding.binding["context"]: + tag_value: tagValues/654321 + timeouts: null module.project-factory.module.projects["dev-ta-app0-be"].google_tags_tag_key.default["my-tag-key-1"]: description: Managed by the Terraform project-factory module. parent: projects/test-pf-dev-ta-app0-be @@ -594,10 +597,10 @@ counts: google_storage_bucket: 1 google_storage_bucket_iam_binding: 2 google_storage_project_service_account: 4 - google_tags_tag_binding: 1 + google_tags_tag_binding: 2 google_tags_tag_key: 1 google_tags_tag_value: 2 google_tags_tag_value_iam_binding: 1 modules: 23 - resources: 85 + resources: 86 terraform_data: 1 diff --git a/tests/modules/secret_manager/context.tfvars b/tests/modules/secret_manager/context.tfvars new file mode 100644 index 000000000..efe175755 --- /dev/null +++ b/tests/modules/secret_manager/context.tfvars @@ -0,0 +1,99 @@ +context = { + condition_vars = { + organization = { + id = 1234567890 + } + } + custom_roles = { + myrole_one = "organizations/366118655033/roles/myRoleOne" + myrole_two = "organizations/366118655033/roles/myRoleTwo" + } + kms_keys = { + compute-prod-ew1 = "projects/kms-central-prj/locations/europe-west1/keyRings/my-keyring/cryptoKeys/ew1-compute" + } + iam_principals = { + mygroup = "group:test-group@example.com" + mysa = "serviceAccount:test@test-project.iam.gserviceaccount.com" + myuser = "user:test-user@example.com" + } + locations = { + ew1 = "europe-west1" + } + project_ids = { + vpc-host = "test-vpc-host" + } + tag_keys = { + test = "tagKeys/1234567890" + } + tag_values = { + "test/one" = "tagValues/1234567890" + } +} +project_id = "test-0" +secrets = { + test-global = { + kms_key = "$kms_keys:compute-prod-ew1" + iam = { + "$custom_roles:myrole_one" = [ + "$iam_principals:myuser" + ] + "roles/viewer" = [ + "$iam_principals:mysa", + ] + } + iam_bindings = { + myrole_two = { + role = "$custom_roles:myrole_two" + members = [ + "$iam_principals:mysa" + ] + condition = { + title = "Test" + expression = "resource.matchTag('$${organization.id}/environment', 'development')" + } + } + } + iam_bindings_additive = { + myrole_two = { + role = "$custom_roles:myrole_two" + member = "$iam_principals:myuser" + } + } + tag_bindings = { + foo = "$tag_values:test/one" + } + } + test-regional = { + location = "$locations:ew1" + kms_key = "$kms_keys:compute-prod-ew1" + iam = { + "$custom_roles:myrole_one" = [ + "$iam_principals:myuser" + ] + "roles/viewer" = [ + "$iam_principals:mysa", + ] + } + iam_bindings = { + myrole_two = { + role = "$custom_roles:myrole_two" + members = [ + "$iam_principals:mysa" + ] + condition = { + title = "Test" + expression = "resource.matchTag('$${organization.id}/environment', 'development')" + } + } + } + iam_bindings_additive = { + myrole_two = { + role = "$custom_roles:myrole_two" + member = "$iam_principals:myuser" + } + } + tag_bindings = { + foo = "$tag_values:test/one" + } + } +} diff --git a/tests/modules/secret_manager/context.yaml b/tests/modules/secret_manager/context.yaml new file mode 100644 index 000000000..ef6af4e62 --- /dev/null +++ b/tests/modules/secret_manager/context.yaml @@ -0,0 +1,125 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + google_secret_manager_regional_secret.default["test-regional"]: + annotations: null + customer_managed_encryption: + - kms_key_name: projects/kms-central-prj/locations/europe-west1/keyRings/my-keyring/cryptoKeys/ew1-compute + deletion_protection: false + effective_labels: + goog-terraform-provisioned: 'true' + labels: null + location: europe-west1 + project: test-0 + rotation: [] + secret_id: test-regional + tags: null + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + topics: [] + ttl: null + version_aliases: null + version_destroy_ttl: null + google_secret_manager_regional_secret_iam_binding.authoritative["test-regional.$custom_roles:myrole_one"]: + condition: [] + location: europe-west1 + members: + - user:test-user@example.com + role: organizations/366118655033/roles/myRoleOne + google_secret_manager_regional_secret_iam_binding.authoritative["test-regional.roles/viewer"]: + condition: [] + location: europe-west1 + members: + - serviceAccount:test@test-project.iam.gserviceaccount.com + role: roles/viewer + google_secret_manager_regional_secret_iam_binding.bindings["test-regional-myrole_two"]: + condition: + - description: null + expression: resource.matchTag('1234567890/environment', 'development') + title: Test + location: europe-west1 + members: + - serviceAccount:test@test-project.iam.gserviceaccount.com + role: organizations/366118655033/roles/myRoleTwo + google_secret_manager_regional_secret_iam_member.members["test-regional-myrole_two"]: + condition: [] + location: europe-west1 + member: user:test-user@example.com + role: organizations/366118655033/roles/myRoleTwo + google_secret_manager_secret.default["test-global"]: + annotations: null + deletion_protection: false + effective_labels: + goog-terraform-provisioned: 'true' + labels: null + project: test-0 + replication: + - auto: + - customer_managed_encryption: + - kms_key_name: projects/kms-central-prj/locations/europe-west1/keyRings/my-keyring/cryptoKeys/ew1-compute + user_managed: [] + rotation: [] + secret_id: test-global + tags: null + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + topics: [] + ttl: null + version_aliases: null + version_destroy_ttl: null + google_secret_manager_secret_iam_binding.authoritative["test-global.$custom_roles:myrole_one"]: + condition: [] + members: + - user:test-user@example.com + role: organizations/366118655033/roles/myRoleOne + google_secret_manager_secret_iam_binding.authoritative["test-global.roles/viewer"]: + condition: [] + members: + - serviceAccount:test@test-project.iam.gserviceaccount.com + role: roles/viewer + google_secret_manager_secret_iam_binding.bindings["test-global-myrole_two"]: + condition: + - description: null + expression: resource.matchTag('1234567890/environment', 'development') + title: Test + members: + - serviceAccount:test@test-project.iam.gserviceaccount.com + role: organizations/366118655033/roles/myRoleTwo + google_secret_manager_secret_iam_member.members["test-global-myrole_two"]: + condition: [] + member: user:test-user@example.com + role: organizations/366118655033/roles/myRoleTwo + google_tags_location_tag_binding.binding["test-regional/foo"]: + location: europe-west1 + parent: //secretmanager.googleapis.com/projects/test-0/locations/europe-west1/secrets/test-regional + tag_value: tagValues/1234567890 + timeouts: null + google_tags_tag_binding.binding["test-global/foo"]: + parent: //secretmanager.googleapis.com/projects/test-0/secrets/test-global + tag_value: tagValues/1234567890 + timeouts: null +counts: + google_secret_manager_regional_secret: 1 + google_secret_manager_regional_secret_iam_binding: 3 + google_secret_manager_regional_secret_iam_member: 1 + google_secret_manager_secret: 1 + google_secret_manager_secret_iam_binding: 3 + google_secret_manager_secret_iam_member: 1 + google_tags_location_tag_binding: 1 + google_tags_tag_binding: 1 + modules: 0 + resources: 12 diff --git a/tests/modules/secret_manager/tftest.yaml b/tests/modules/secret_manager/tftest.yaml new file mode 100644 index 000000000..37846c04a --- /dev/null +++ b/tests/modules/secret_manager/tftest.yaml @@ -0,0 +1,19 @@ +# 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. + +module: modules/secret-manager + +tests: + context: + skip_tofu: True