From 3ee1cc7ab3b926e798522709828b2ce73226dd32 Mon Sep 17 00:00:00 2001 From: Brandon Tucker Date: Sat, 30 May 2026 11:38:55 -0600 Subject: [PATCH] Enable remaining PSC config options in modules/cloudsql-instance (#3982) * Enable remaining PSC config options * Windows slashes * Updates from review; slim down locals; obsolete psc_allowed_consumer_projects * Windows slashes * tfvars; examples * Copy-paste bug * Proper list check * Updates from Automated PR Review * Slahes * null psc fix; regenerate custom test inventory * fmt * Correct fixtures; remove deletion_policy from instance resources * Fix test * Remove deletio policy --------- Co-authored-by: Julio Castillo --- modules/cloudsql-instance/README.md | 60 +++++++++--- modules/cloudsql-instance/main.tf | 63 ++++++++++--- modules/cloudsql-instance/variables.tf | 19 +++- tests/fixtures/cloudsql-instance.tf | 4 +- .../modules/cloudsql_instance/context.tfvars | 4 +- .../cloudsql_instance/examples/custom.yaml | 93 +++++++++++++++++-- .../cloudsql_instance/examples/psc-auto.yaml | 78 ++++++++++++++++ 7 files changed, 287 insertions(+), 34 deletions(-) create mode 100644 tests/modules/cloudsql_instance/examples/psc-auto.yaml diff --git a/modules/cloudsql-instance/README.md b/modules/cloudsql-instance/README.md index 17d23186b..660a9972f 100644 --- a/modules/cloudsql-instance/README.md +++ b/modules/cloudsql-instance/README.md @@ -13,6 +13,7 @@ Note that this module assumes that some options are the same for both the primar - [Custom flags, databases and users](#custom-flags-databases-and-users) - [CMEK encryption](#cmek-encryption) - [Instance with PSC enabled](#instance-with-psc-enabled) + - [Instance with PSC auto connections](#instance-with-psc-auto-connections) - [Enable public IP](#enable-public-ip) - [Query Insights](#query-insights) - [Maintenance Config](#maintenance-config) @@ -246,7 +247,9 @@ module "db" { project_id = var.project_id network_config = { connectivity = { - psc_allowed_consumer_projects = [var.project_id] + psc_config = { + allowed_consumer_projects = [var.project_id] + } } } prefix = "myprefix" @@ -262,6 +265,39 @@ module "db" { # tftest modules=1 resources=1 inventory=psc.yaml e2e ``` +### Instance with PSC auto connections + +```hcl +module "db" { + source = "./fabric/modules/cloudsql-instance" + project_id = var.project_id + context = { + networks = { myvpc = "https://www.googleapis.com/compute/v1/projects/xxx/global/networks/aaa" } + } + network_config = { + connectivity = { + psc_config = { + allowed_consumer_projects = [var.project_id] + psc_auto_connections = [{ + consumer_network = "$networks:myvpc" + consumer_service_project_id = var.project_id + }] + } + } + } + prefix = "myprefix" + name = "db" + region = var.region + availability_type = "REGIONAL" + database_version = "POSTGRES_13" + tier = "db-g1-small" + + gcp_deletion_protection = false + terraform_deletion_protection = false +} +# tftest modules=1 resources=1 inventory=psc-auto.yaml e2e +``` + ### Enable public IP Use `public_ipv4` to create instances with a public IP. @@ -417,9 +453,9 @@ module "db" { | [database_version](variables.tf#L96) | Database type and version to create. | string | ✓ | | | [name](variables.tf#L213) | Name of primary instance. | string | ✓ | | | [network_config](variables.tf#L218) | Network configuration for the instance. Only one between private_network and psc_config can be used. | object({…}) | ✓ | | -| [project_id](variables.tf#L261) | The ID of the project where this instances will be created. | string | ✓ | | -| [region](variables.tf#L266) | Region of the primary instance. | string | ✓ | | -| [tier](variables.tf#L318) | The machine type to use for the instances. | string | ✓ | | +| [project_id](variables.tf#L278) | The ID of the project where this instances will be created. | string | ✓ | | +| [region](variables.tf#L283) | Region of the primary instance. | string | ✓ | | +| [tier](variables.tf#L335) | The machine type to use for the instances. | string | ✓ | | | [activation_policy](variables.tf#L17) | This variable specifies when the instance should be active. Can be either ALWAYS, NEVER or ON_DEMAND. Default is ALWAYS. | string | | "ALWAYS" | | [availability_type](variables.tf#L28) | Availability type for the primary replica. Either `ZONAL` or `REGIONAL`. | string | | "ZONAL" | | [backup_configuration](variables.tf#L34) | Backup settings for primary instance. Set to null to leave existing GCP backup settings unmanaged. When set, all fields are managed by Terraform including disabling backups when enabled=false. | object({…}) | | null | @@ -440,14 +476,14 @@ module "db" { | [labels](variables.tf#L162) | Labels to be attached to all instances. | map(string) | | null | | [maintenance_config](variables.tf#L168) | Set maintenance window configuration and maintenance deny period (up to 90 days). Date format: 'yyyy-mm-dd'. | object({…}) | | {} | | [managed_connection_pooling_config](variables.tf#L203) | Configuration for Managed Connection Pooling. NOTE: This feature is only available for PostgreSQL on Enterprise Plus edition instances. | object({…}) | | {} | -| [password_validation_policy](variables.tf#L237) | Password validation policy configuration for instances. | object({…}) | | null | -| [prefix](variables.tf#L251) | Optional prefix used to generate instance names. | string | | null | -| [replicas](variables.tf#L271) | Map of NAME=> {REGION, KMS_KEY, AVAILABILITY_TYPE} for additional read replicas. Set to null to disable replica creation. | map(object({…})) | | {} | -| [root_password](variables.tf#L282) | Root password of the Cloud SQL instance, or flag to create a random password. Required for MS SQL Server. | object({…}) | | {} | -| [ssl](variables.tf#L296) | Setting to enable SSL, set config and certificates. | object({…}) | | {} | -| [terraform_deletion_protection](variables.tf#L311) | Prevent terraform from deleting instances. | bool | | true | -| [time_zone](variables.tf#L323) | The time_zone to be used by the database engine (supported only for SQL Server), in SQL Server timezone format. | string | | null | -| [users](variables.tf#L329) | Map of users to create in the primary instance (and replicated to other replicas). For MySQL, anything after the first `@` (if present) will be used as the user's host. Set PASSWORD to null if you want to get an autogenerated password. The user types available are: 'BUILT_IN', 'CLOUD_IAM_USER' or 'CLOUD_IAM_SERVICE_ACCOUNT'. | map(object({…})) | | {} | +| [password_validation_policy](variables.tf#L254) | Password validation policy configuration for instances. | object({…}) | | null | +| [prefix](variables.tf#L268) | Optional prefix used to generate instance names. | string | | null | +| [replicas](variables.tf#L288) | Map of NAME=> {REGION, KMS_KEY, AVAILABILITY_TYPE} for additional read replicas. Set to null to disable replica creation. | map(object({…})) | | {} | +| [root_password](variables.tf#L299) | Root password of the Cloud SQL instance, or flag to create a random password. Required for MS SQL Server. | object({…}) | | {} | +| [ssl](variables.tf#L313) | Setting to enable SSL, set config and certificates. | object({…}) | | {} | +| [terraform_deletion_protection](variables.tf#L328) | Prevent terraform from deleting instances. | bool | | true | +| [time_zone](variables.tf#L340) | The time_zone to be used by the database engine (supported only for SQL Server), in SQL Server timezone format. | string | | null | +| [users](variables.tf#L346) | Map of users to create in the primary instance (and replicated to other replicas). For MySQL, anything after the first `@` (if present) will be used as the user's host. Set PASSWORD to null if you want to get an autogenerated password. The user types available are: 'BUILT_IN', 'CLOUD_IAM_USER' or 'CLOUD_IAM_SERVICE_ACCOUNT'. | map(object({…})) | | {} | ## Outputs diff --git a/modules/cloudsql-instance/main.tf b/modules/cloudsql-instance/main.tf index d1fc9d9f3..5df33cd29 100644 --- a/modules/cloudsql-instance/main.tf +++ b/modules/cloudsql-instance/main.tf @@ -43,6 +43,29 @@ locals { project_id = lookup(local.ctx.project_ids, var.project_id, var.project_id) region = lookup(local.ctx.locations, var.region, var.region) + ### PSC locals + psc_allowed_consumer_projects = ( + try(var.network_config.connectivity.psc_config.allowed_consumer_projects, null) == null + ? null + : [ + for p in try(var.network_config.connectivity.psc_config.allowed_consumer_projects, []) : + lookup(local.ctx.project_ids, p, p) + ] + ) + psc_auto_connections = [ + for c in coalesce( + try(var.network_config.connectivity.psc_config.psc_auto_connections, []), + [] + ) : { + consumer_network = lookup(local.ctx.networks, c.consumer_network, c.consumer_network) + consumer_service_project_id = ( + c.consumer_service_project_id == null + ? null + : lookup(local.ctx.project_ids, c.consumer_service_project_id, c.consumer_service_project_id) + ) + } + ] + users = { for k, v in var.users : k => local.is_mysql @@ -113,16 +136,22 @@ resource "google_sql_database_instance" "primary" { } dynamic "psc_config" { for_each = ( - var.network_config.connectivity.psc_allowed_consumer_projects != null + try(var.network_config.connectivity.psc_config, null) != null ? [""] : [] ) content { - psc_enabled = true - allowed_consumer_projects = [ - for p in var.network_config.connectivity.psc_allowed_consumer_projects : - lookup(local.ctx.project_ids, p, p) - ] + psc_enabled = true + allowed_consumer_projects = local.psc_allowed_consumer_projects + network_attachment_uri = try(var.network_config.connectivity.psc_config.network_attachment_uri, null) + + dynamic "psc_auto_connections" { + for_each = local.psc_auto_connections + content { + consumer_network = psc_auto_connections.value.consumer_network + consumer_service_project_id = psc_auto_connections.value.consumer_service_project_id + } + } } } } @@ -314,16 +343,26 @@ resource "google_sql_database_instance" "replicas" { } dynamic "psc_config" { for_each = ( - var.network_config.connectivity.psc_allowed_consumer_projects != null + try(var.network_config.connectivity.psc_config, null) != null ? [""] : [] ) content { - psc_enabled = true - allowed_consumer_projects = [ - for p in var.network_config.connectivity.psc_allowed_consumer_projects : - lookup(local.ctx.project_ids, p, p) - ] + psc_enabled = true + allowed_consumer_projects = local.psc_allowed_consumer_projects + network_attachment_uri = try(var.network_config.connectivity.psc_config.network_attachment_uri, null) + + dynamic "psc_auto_connections" { + for_each = ( + try(var.network_config.connectivity.psc_config.psc_auto_connections, null) != null + ? [""] + : [] + ) + content { + consumer_network = local.psc_consumer_network + consumer_service_project_id = local.psc_consumer_service_project_id + } + } } } } diff --git a/modules/cloudsql-instance/variables.tf b/modules/cloudsql-instance/variables.tf index 82e6257a9..cb5008599 100644 --- a/modules/cloudsql-instance/variables.tf +++ b/modules/cloudsql-instance/variables.tf @@ -228,10 +228,27 @@ variable "network_config" { replica = optional(string) })) })) - psc_allowed_consumer_projects = optional(list(string)) + psc_allowed_consumer_projects = optional(list(string)) # OBSOLETE. See validation below. + psc_config = optional(object({ + allowed_consumer_projects = optional(list(string)) + network_attachment_uri = optional(string) + psc_auto_connections = optional(list(object({ + consumer_network = string + consumer_service_project_id = optional(string) + }))) + })) enable_private_path_for_services = optional(bool, false) }) }) + validation { + condition = ( + try(var.network_config.connectivity, null) == null ? true : ( + var.network_config.connectivity.psc_allowed_consumer_projects == null || + length(var.network_config.connectivity.psc_allowed_consumer_projects) == 0 + ) + ) + error_message = "network_config.connectivity.psc_allowed_consumer_projects is obsolete. Use network_config.connectivity.psc_config.allowed_consumer_projects instead." + } } variable "password_validation_policy" { diff --git a/tests/fixtures/cloudsql-instance.tf b/tests/fixtures/cloudsql-instance.tf index 6fed8d6ad..b8baf23a1 100644 --- a/tests/fixtures/cloudsql-instance.tf +++ b/tests/fixtures/cloudsql-instance.tf @@ -19,7 +19,9 @@ module "cloudsql-instance" { project_id = var.project_id network_config = { connectivity = { - psc_allowed_consumer_projects = [var.project_id] + psc_config = { + allowed_consumer_projects = [var.project_id] + } } } ## define a consumer project with an endpoint within the project diff --git a/tests/modules/cloudsql_instance/context.tfvars b/tests/modules/cloudsql_instance/context.tfvars index b0b115231..f2a80c232 100644 --- a/tests/modules/cloudsql_instance/context.tfvars +++ b/tests/modules/cloudsql_instance/context.tfvars @@ -11,10 +11,12 @@ database_version = "POSTGRES_13" tier = "db-g1-small" network_config = { connectivity = { - psc_allowed_consumer_projects = ["$project_ids:myprj"] psa_config = { private_network = "$networks:myvpc" } + psc_config = { + allowed_consumer_projects = ["$project_ids:myprj"] + } } } encryption_key_name = "$kms_keys:mykey" diff --git a/tests/modules/cloudsql_instance/examples/custom.yaml b/tests/modules/cloudsql_instance/examples/custom.yaml index 5d028b89c..e57dd18af 100644 --- a/tests/modules/cloudsql_instance/examples/custom.yaml +++ b/tests/modules/cloudsql_instance/examples/custom.yaml @@ -1,10 +1,10 @@ -# Copyright 2023 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, @@ -14,36 +14,100 @@ values: module.db.google_sql_database.databases["departments"]: - deletion_policy: DELETE instance: db name: departments project: project-id + timeouts: null module.db.google_sql_database.databases["people"]: - deletion_policy: DELETE instance: db name: people project: project-id - module.db.google_sql_database_instance.primary: {} + timeouts: null + module.db.google_sql_database_instance.primary: + backupdr_backup: null + clone: [] + database_version: MYSQL_8_0 + deletion_protection: false + final_backup_description: null + name: db + point_in_time_restore_context: [] + project: project-id + region: europe-west8 + restore_backup_context: [] + root_password: null + root_password_wo: null + root_password_wo_version: null + settings: + - activation_policy: ALWAYS + active_directory_config: [] + advanced_machine_features: [] + auto_upgrade_enabled: null + availability_type: ZONAL + collation: null + database_flags: + - name: cloudsql_iam_authentication + value: 'on' + - name: disconnect_on_expired_password + value: 'on' + deletion_protection_enabled: false + deny_maintenance_period: [] + disk_autoresize: true + disk_autoresize_limit: 0 + disk_type: PD_SSD + edition: ENTERPRISE + enable_dataplex_integration: null + enable_google_ml_integration: null + entraid_config: [] + final_backup_config: [] + ip_configuration: + - allocated_ip_range: null + authorized_networks: [] + custom_subject_alternative_names: null + enable_private_path_for_google_cloud_services: false + ipv4_enabled: false + private_network: https://www.googleapis.com/compute/v1/projects/xxx/global/networks/aaa + psc_config: [] + server_ca_pool: null + server_certificate_rotation_mode: null + maintenance_window: [] + password_validation_policy: [] + pricing_plan: PER_USE + retain_backups_on_delete: null + sql_server_audit_config: [] + tier: db-g1-small + time_zone: null + timeouts: null module.db.google_sql_user.users["fixture-service-account@project-id.iam.gserviceaccount.com"]: + database_roles: null instance: db name: fixture-service-account@project-id.iam.gserviceaccount.com password: null password_policy: [] + password_wo: null + password_wo_version: null project: project-id + timeouts: null type: CLOUD_IAM_SERVICE_ACCOUNT module.db.google_sql_user.users["user1"]: - deletion_policy: null + database_roles: null instance: db name: user1 password_policy: [] + password_wo: null + password_wo_version: null project: project-id + timeouts: null type: null module.db.google_sql_user.users["user2"]: - deletion_policy: null + database_roles: null instance: db name: user2 + password: mypassword password_policy: [] + password_wo: null + password_wo_version: null project: project-id + timeouts: null type: null module.db.random_password.passwords["user1"]: keepers: null @@ -58,9 +122,24 @@ values: override_special: null special: true upper: true + module.iam-service-account.google_service_account.service_account[0]: + account_id: fixture-service-account + create_ignore_already_exists: null + description: null + disabled: false + display_name: Terraform-managed. + email: fixture-service-account@project-id.iam.gserviceaccount.com + member: serviceAccount:fixture-service-account@project-id.iam.gserviceaccount.com + project: project-id + timeouts: null counts: + google_service_account: 1 google_sql_database: 2 google_sql_database_instance: 1 google_sql_user: 3 modules: 2 + random_password: 1 + resources: 8 + +outputs: {} diff --git a/tests/modules/cloudsql_instance/examples/psc-auto.yaml b/tests/modules/cloudsql_instance/examples/psc-auto.yaml new file mode 100644 index 000000000..0f18a6f12 --- /dev/null +++ b/tests/modules/cloudsql_instance/examples/psc-auto.yaml @@ -0,0 +1,78 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.db.google_sql_database_instance.primary: + backupdr_backup: null + clone: [] + database_version: POSTGRES_13 + final_backup_description: null + name: myprefix-db + point_in_time_restore_context: [] + project: project-id + region: europe-west8 + restore_backup_context: [] + root_password: null + root_password_wo: null + root_password_wo_version: null + settings: + - activation_policy: ALWAYS + active_directory_config: [] + advanced_machine_features: [] + auto_upgrade_enabled: null + availability_type: REGIONAL + collation: null + database_flags: [] + deletion_protection_enabled: false + deny_maintenance_period: [] + disk_autoresize: true + disk_autoresize_limit: 0 + disk_type: PD_SSD + edition: ENTERPRISE + enable_dataplex_integration: null + enable_google_ml_integration: null + entraid_config: [] + final_backup_config: [] + ip_configuration: + - allocated_ip_range: null + authorized_networks: [] + custom_subject_alternative_names: null + enable_private_path_for_google_cloud_services: false + ipv4_enabled: false + private_network: null + psc_config: + - allowed_consumer_projects: + - project-id + network_attachment_uri: '' + psc_auto_connections: + - consumer_network: https://www.googleapis.com/compute/v1/projects/xxx/global/networks/aaa + consumer_service_project_id: project-id + psc_enabled: true + server_ca_pool: null + server_certificate_rotation_mode: null + maintenance_window: [] + password_validation_policy: [] + pricing_plan: PER_USE + retain_backups_on_delete: null + sql_server_audit_config: [] + tier: db-g1-small + time_zone: null + timeouts: null + +counts: + google_sql_database_instance: 1 + modules: 1 + resources: 1 + +outputs: {}