From 6a3c7fe4445ca3f801db6a7d19cdd47587fe6a35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Niesiob=C4=99dzki?= Date: Sun, 12 May 2024 12:00:39 +0200 Subject: [PATCH] CloudSQL PSC Endpoints support (#2242) * Add PSC endpoints consumers to net-address * Cloud SQL E2E tests --- modules/cloudsql-instance/README.md | 175 ++++++++++-------- modules/cloudsql-instance/variables.tf | 2 +- modules/net-address/README.md | 25 ++- modules/net-address/main.tf | 42 +---- modules/net-address/psc.tf | 102 ++++++++++ modules/net-address/variables.tf | 26 ++- tests/collectors.py | 11 +- tests/examples/variables.tf | 2 +- .../setup_module/e2e_tests.tfvars.tftpl | 6 + tests/examples_e2e/setup_module/main.tf | 46 ++++- tests/fixtures.py | 11 +- tests/fixtures/cloudsql-instance.tf | 33 ++++ tests/fixtures/cloudsql-kms-iam-grant.tf | 23 +++ .../cloudsql_instance/examples/insights.yaml | 4 +- .../cloudsql_instance/examples/psc.yaml | 38 ++++ .../cloudsql_instance/examples/public-ip.yaml | 25 +-- .../cloudsql_instance/examples/replicas.yaml | 2 +- .../cloudsql_instance/examples/simple.yaml | 40 ++-- .../examples/psc-service-attachment.yaml | 29 +++ 19 files changed, 464 insertions(+), 178 deletions(-) create mode 100644 modules/net-address/psc.tf create mode 100644 tests/fixtures/cloudsql-instance.tf create mode 100644 tests/fixtures/cloudsql-kms-iam-grant.tf create mode 100644 tests/modules/cloudsql_instance/examples/psc.yaml create mode 100644 tests/modules/net_address/examples/psc-service-attachment.yaml diff --git a/modules/cloudsql-instance/README.md b/modules/cloudsql-instance/README.md index c1474cec1..6ecf42a32 100644 --- a/modules/cloudsql-instance/README.md +++ b/modules/cloudsql-instance/README.md @@ -1,4 +1,4 @@ -# Cloud SQL instance with read replicas +# Cloud SQL instance module This module manages the creation of Cloud SQL instances with potential read replicas in other regions. It can also create an initial set of users and databases via the `users` and `databases` parameters. @@ -6,7 +6,24 @@ Note that this module assumes that some options are the same for both the primar *Warning:* if you use the `users` field, you terraform state will contain each user's password in plain text. -## Simple example + +- [Examples](#examples) + - [Simple example](#simple-example) + - [Cross-regional read replica](#cross-regional-read-replica) + - [Custom flags, databases and users](#custom-flags-databases-and-users) + - [CMEK encryption](#cmek-encryption) + - [Instance with PSC enabled](#instance-with-psc-enabled) + - [Enable public IP](#enable-public-ip) + - [Query Insights](#query-insights) + - [Maintenance Config](#maintenance-config) + - [SSL Config](#ssl-config) +- [Variables](#variables) +- [Outputs](#outputs) +- [Fixtures](#fixtures) + + +## Examples +### Simple example This example shows how to setup a project, VPC and a standalone Cloud SQL instance. @@ -14,10 +31,12 @@ This example shows how to setup a project, VPC and a standalone Cloud SQL instan module "project" { source = "./fabric/modules/project" billing_account = var.billing_account_id - parent = var.organization_id - name = "my-db-project" + parent = var.folder_id + name = "db-prj" + prefix = var.prefix services = [ - "servicenetworking.googleapis.com" + "servicenetworking.googleapis.com", + "sqladmin.googleapis.com", ] } @@ -25,9 +44,18 @@ module "vpc" { source = "./fabric/modules/net-vpc" project_id = module.project.project_id name = "my-network" + # need only one - psa_config or subnets_psc psa_configs = [{ - ranges = { cloud-sql = "10.60.0.0/16" } + ranges = { cloud-sql = "10.60.0.0/16" } + deletion_policy = "ABANDON" }] + subnets_psc = [ + { + ip_cidr_range = "10.0.3.0/24" + name = "psc" + region = var.region + } + ] } module "db" { @@ -38,17 +66,20 @@ module "db" { psa_config = { private_network = module.vpc.self_link } + # psc_allowed_consumer_projects = [var.project_id] } } - name = "db" - region = "europe-west1" - database_version = "POSTGRES_13" - tier = "db-g1-small" + name = "db" + region = var.region + database_version = "POSTGRES_13" + tier = "db-g1-small" + gcp_deletion_protection = false + terraform_deletion_protection = false } -# tftest modules=3 resources=11 inventory=simple.yaml +# tftest modules=3 resources=14 inventory=simple.yaml e2e ``` -## Cross-regional read replica +### Cross-regional read replica ```hcl module "db" { @@ -61,21 +92,23 @@ module "db" { } } } - prefix = "myprefix" name = "db" - region = "europe-west1" + prefix = "myprefix" + region = var.region database_version = "POSTGRES_13" tier = "db-g1-small" replicas = { - replica1 = { region = "europe-west3", encryption_key_name = null } - replica2 = { region = "us-central1", encryption_key_name = null } + replica1 = { region = "europe-west3" } + replica2 = { region = "us-central1" } } + gcp_deletion_protection = false + terraform_deletion_protection = false } -# tftest modules=1 resources=3 inventory=replicas.yaml +# tftest modules=1 resources=3 inventory=replicas.yaml e2e ``` -## Custom flags, databases and users +### Custom flags, databases and users ```hcl module "db" { @@ -89,7 +122,7 @@ module "db" { } } name = "db" - region = "europe-west1" + region = var.region database_version = "MYSQL_8_0" tier = "db-g1-small" @@ -112,47 +145,19 @@ module "db" { password = "mypassword" } } + gcp_deletion_protection = false + terraform_deletion_protection = false } -# tftest modules=1 resources=6 inventory=custom.yaml +# tftest modules=1 resources=6 inventory=custom.yaml e2e ``` ### CMEK encryption ```hcl - -module "project" { - source = "./fabric/modules/project" - billing_account = var.billing_account_id - parent = var.organization_id - name = "my-db-project" - services = [ - "servicenetworking.googleapis.com", - "sqladmin.googleapis.com", - ] -} - -module "kms" { - source = "./fabric/modules/kms" - project_id = module.project.project_id - keyring = { - name = "keyring" - location = var.region - } - keys = { - key-sql = { - iam = { - "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [ - "serviceAccount:${module.project.service_accounts.robots.sqladmin}" - ] - } - } - } -} - module "db" { source = "./fabric/modules/cloudsql-instance" - project_id = module.project.project_id - encryption_key_name = module.kms.keys["key-sql"].id + project_id = var.project_id + encryption_key_name = var.kms_key.id network_config = { connectivity = { psa_config = { @@ -160,13 +165,15 @@ module "db" { } } } - name = "db" - region = var.region - database_version = "POSTGRES_13" - tier = "db-g1-small" + name = "db" + region = var.region + database_version = "POSTGRES_13" + tier = "db-g1-small" + gcp_deletion_protection = false + terraform_deletion_protection = false } -# tftest modules=3 resources=10 +# tftest modules=1 resources=2 fixtures=fixtures/cloudsql-kms-iam-grant.tf e2e ``` ### Instance with PSC enabled @@ -177,22 +184,25 @@ module "db" { project_id = var.project_id network_config = { connectivity = { - psc_allowed_consumer_projects = ["my-project-id"] + psc_allowed_consumer_projects = [var.project_id] } } prefix = "myprefix" name = "db" - region = "europe-west1" + 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 +# tftest modules=1 resources=1 inventory=psc.yaml e2e ``` ### Enable public IP -Use `ipv_enabled` to create instances with a public IP. +Use `public_ipv4` to create instances with a public IP. ```hcl module "db" { @@ -206,15 +216,14 @@ module "db" { } } } - name = "db" - region = "europe-west1" - tier = "db-g1-small" - database_version = "MYSQL_8_0" - replicas = { - replica1 = { region = "europe-west3", encryption_key_name = null } - } + name = "db" + region = var.region + tier = "db-g1-small" + database_version = "MYSQL_8_0" + gcp_deletion_protection = false + terraform_deletion_protection = false } -# tftest modules=1 resources=2 inventory=public-ip.yaml +# tftest modules=1 resources=1 inventory=public-ip.yaml e2e ``` ### Query Insights @@ -233,15 +242,17 @@ module "db" { } } name = "db" - region = "europe-west1" + region = var.region database_version = "POSTGRES_13" tier = "db-g1-small" insights_config = { query_string_length = 2048 } + gcp_deletion_protection = false + terraform_deletion_protection = false } -# tftest modules=1 resources=1 inventory=insights.yaml +# tftest modules=1 resources=1 inventory=insights.yaml e2e ``` ### Maintenance Config @@ -260,13 +271,15 @@ module "db" { } } name = "db" - region = "europe-west1" + region = var.region database_version = "POSTGRES_13" tier = "db-g1-small" - maintenance_config = {} + maintenance_config = {} + gcp_deletion_protection = false + terraform_deletion_protection = false } -# tftest modules=1 resources=1 +# tftest modules=1 resources=1 e2e ``` ### SSL Config @@ -285,13 +298,15 @@ module "db" { } } name = "db" - region = "europe-west1" + region = var.region database_version = "POSTGRES_13" tier = "db-g1-small" - ssl = {} + ssl = {} + gcp_deletion_protection = false + terraform_deletion_protection = false } -# tftest modules=1 resources=1 +# tftest modules=1 resources=1 e2e ``` ## Variables @@ -322,7 +337,7 @@ module "db" { | [labels](variables.tf#L140) | Labels to be attached to all instances. | map(string) | | null | | [maintenance_config](variables.tf#L146) | Set maintenance window configuration and maintenance deny period (up to 90 days). Date format: 'yyyy-mm-dd'. | object({…}) | | {} | | [prefix](variables.tf#L207) | Optional prefix used to generate instance names. | string | | null | -| [replicas](variables.tf#L227) | Map of NAME=> {REGION, KMS_KEY} for additional read replicas. Set to null to disable replica creation. | map(object({…})) | | {} | +| [replicas](variables.tf#L227) | Map of NAME=> {REGION, KMS_KEY} for additional read replicas. Set to null to disable replica creation. | map(object({…})) | | {} | | [root_password](variables.tf#L236) | Root password of the Cloud SQL instance. Required for MS SQL Server. | string | | null | | [ssl](variables.tf#L242) | Setting to enable SSL, set config and certificates. | object({…}) | | {} | | [terraform_deletion_protection](variables.tf#L258) | Prevent terraform from deleting instances. | bool | | true | @@ -350,4 +365,8 @@ module "db" { | [self_link](outputs.tf#L114) | Self link of the primary instance. | | | [self_links](outputs.tf#L119) | Self links of all instances. | | | [user_passwords](outputs.tf#L127) | Map of containing the password of all users created through terraform. | ✓ | + +## Fixtures + +- [cloudsql-kms-iam-grant.tf](../../tests/fixtures/cloudsql-kms-iam-grant.tf) diff --git a/modules/cloudsql-instance/variables.tf b/modules/cloudsql-instance/variables.tf index e67dd3979..613e106e7 100644 --- a/modules/cloudsql-instance/variables.tf +++ b/modules/cloudsql-instance/variables.tf @@ -228,7 +228,7 @@ variable "replicas" { description = "Map of NAME=> {REGION, KMS_KEY} for additional read replicas. Set to null to disable replica creation." type = map(object({ region = string - encryption_key_name = string + encryption_key_name = optional(string) })) default = {} } diff --git a/modules/net-address/README.md b/modules/net-address/README.md index 55b84108a..5f5306336 100644 --- a/modules/net-address/README.md +++ b/modules/net-address/README.md @@ -122,6 +122,26 @@ module "addresses" { # tftest modules=1 resources=1 inventory=psc.yaml e2e ``` +To create PSC address targeting a service regional provider use the `service_attachment` property. +```hcl +module "addresses" { + source = "./fabric/modules/net-address" + project_id = var.project_id + psc_addresses = { + cloudsql-one = { + address = "10.0.16.32" + subnet_self_link = var.subnet.self_link + region = var.region + service_attachment = { + psc_service_attachment_link = module.cloudsql-instance.psc_service_attachment_link + } + } + } +} +# tftest modules=2 resources=3 fixtures=fixtures/cloudsql-instance.tf inventory=psc-service-attachment.yaml e2e +``` + + ### IPSec Interconnect addresses ```hcl @@ -176,8 +196,8 @@ module "addresses" { | [internal_addresses](variables.tf#L50) | Map of internal addresses to create, keyed by name. | map(object({…})) | | {} | | [ipsec_interconnect_addresses](variables.tf#L65) | Map of internal addresses used for HPA VPN over Cloud Interconnect. | map(object({…})) | | {} | | [network_attachments](variables.tf#L84) | PSC network attachments, names as keys. | map(object({…})) | | {} | -| [psa_addresses](variables.tf#L102) | Map of internal addresses used for Private Service Access. | map(object({…})) | | {} | -| [psc_addresses](variables.tf#L115) | Map of internal addresses used for Private Service Connect. | map(object({…})) | | {} | +| [psa_addresses](variables.tf#L102) | Map of internal addresses used for Private Service Access. | map(object({…})) | | {} | +| [psc_addresses](variables.tf#L114) | Map of internal addresses used for Private Service Connect. | map(object({…})) | | {} | ## Outputs @@ -193,5 +213,6 @@ module "addresses" { ## Fixtures +- [cloudsql-instance.tf](../../tests/fixtures/cloudsql-instance.tf) - [net-vpc-ipv6.tf](../../tests/fixtures/net-vpc-ipv6.tf) diff --git a/modules/net-address/main.tf b/modules/net-address/main.tf index 20b8b6a16..e7c69413c 100644 --- a/modules/net-address/main.tf +++ b/modules/net-address/main.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * 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. @@ -14,20 +14,6 @@ * limitations under the License. */ -locals { - network_attachments = { - for k, v in var.network_attachments : k => merge(v, { - region = regex("regions/([^/]+)", v.subnet_self_link)[0] - # not using the full self link generates a permadiff - subnet_self_link = ( - startswith(v.subnet_self_link, "https://") - ? v.subnet_self_link - : "https://www.googleapis.com/compute/v1/${v.subnet_self_link}" - ) - }) - } -} - resource "google_compute_global_address" "global" { for_each = var.global_addresses project = var.project_id @@ -66,18 +52,6 @@ resource "google_compute_address" "internal" { subnetwork = each.value.subnetwork } -resource "google_compute_global_address" "psc" { - for_each = var.psc_addresses - project = var.project_id - name = coalesce(each.value.name, each.key) - description = each.value.description - address = try(each.value.address, null) - address_type = "INTERNAL" - network = each.value.network - purpose = "PRIVATE_SERVICE_CONNECT" - # labels = lookup(var.internal_address_labels, each.key, {}) -} - resource "google_compute_global_address" "psa" { for_each = var.psa_addresses project = var.project_id @@ -104,17 +78,3 @@ resource "google_compute_address" "ipsec_interconnect" { purpose = "IPSEC_INTERCONNECT" } -resource "google_compute_network_attachment" "default" { - provider = google-beta - for_each = local.network_attachments - project = var.project_id - region = each.value.region - name = each.key - description = each.value.description - connection_preference = ( - each.value.automatic_connection ? "ACCEPT_AUTOMATIC" : "ACCEPT_MANUAL" - ) - subnetworks = [each.value.subnet_self_link] - producer_accept_lists = each.value.producer_accept_lists - producer_reject_lists = each.value.producer_reject_lists -} diff --git a/modules/net-address/psc.tf b/modules/net-address/psc.tf new file mode 100644 index 000000000..2fbf75788 --- /dev/null +++ b/modules/net-address/psc.tf @@ -0,0 +1,102 @@ +/** + * 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. + */ + +locals { + network_attachments = { + for k, v in var.network_attachments : k => merge(v, { + region = regex("regions/([^/]+)", v.subnet_self_link)[0] + # not using the full self link generates a permadiff + subnet_self_link = ( + startswith(v.subnet_self_link, "https://") + ? v.subnet_self_link + : "https://www.googleapis.com/compute/v1/${v.subnet_self_link}" + ) + }) + } + regional_psc = { + for name, psc in var.psc_addresses : name => psc if psc.region != null + + } + global_psc = { + for name, psc in var.psc_addresses : name => psc if psc.region == null + } +} + +resource "google_compute_network_attachment" "default" { + provider = google-beta + for_each = local.network_attachments + project = var.project_id + region = each.value.region + name = each.key + description = each.value.description + connection_preference = ( + each.value.automatic_connection ? "ACCEPT_AUTOMATIC" : "ACCEPT_MANUAL" + ) + subnetworks = [each.value.subnet_self_link] + producer_accept_lists = each.value.producer_accept_lists + producer_reject_lists = each.value.producer_reject_lists +} + +# global PSC services +resource "google_compute_global_address" "psc" { + for_each = local.global_psc + project = var.project_id + name = coalesce(each.value.name, each.key) + description = each.value.description + address = try(each.value.address, null) + address_type = "INTERNAL" + network = each.value.network + purpose = "PRIVATE_SERVICE_CONNECT" + # labels = lookup(var.internal_address_labels, each.key, {}) +} + +resource "google_compute_global_forwarding_rule" "psc_consumer" { + for_each = { for name, psc in local.global_psc : name => psc if psc.service_attachment != null } + name = coalesce(each.value.name, each.key) + project = var.project_id + subnetwork = each.value.subnet_self_link + ip_address = google_compute_global_address.psc[each.key].self_link + load_balancing_scheme = "" + target = each.value.service_attachment.psc_service_attachment_link +} + +# regional PSC services +resource "google_compute_address" "psc" { + for_each = local.regional_psc + project = var.project_id + name = coalesce(each.value.name, each.key) + address = try(each.value.address, null) + address_type = "INTERNAL" + description = each.value.description + network = each.value.network + # purpose not applicable for regional address + # purpose = "PRIVATE_SERVICE_CONNECT" + region = each.value.region + subnetwork = each.value.subnet_self_link + # labels = lookup(var.internal_address_labels, each.key, {}) +} + +resource "google_compute_forwarding_rule" "psc_consumer" { + for_each = { for name, psc in local.regional_psc : name => psc if psc.service_attachment != null } + name = coalesce(each.value.name, each.key) + project = var.project_id + region = each.value.region + subnetwork = each.value.subnet_self_link + ip_address = google_compute_address.psc[each.key].self_link + load_balancing_scheme = "" + recreate_closed_psc = true + target = each.value.service_attachment.psc_service_attachment_link +} diff --git a/modules/net-address/variables.tf b/modules/net-address/variables.tf index 190bffdde..236c23960 100644 --- a/modules/net-address/variables.tf +++ b/modules/net-address/variables.tf @@ -107,7 +107,6 @@ variable "psa_addresses" { prefix_length = number description = optional(string, "Terraform managed.") name = optional(string) - })) default = {} } @@ -115,10 +114,27 @@ variable "psa_addresses" { variable "psc_addresses" { description = "Map of internal addresses used for Private Service Connect." type = map(object({ - address = string - network = string - description = optional(string, "Terraform managed.") - name = optional(string) + address = string + description = optional(string, "Terraform managed.") + name = optional(string) + network = optional(string) + region = optional(string) + subnet_self_link = optional(string) + service_attachment = optional(object({ # so we can safely check if service_attachemnt != null in for_each + psc_service_attachment_link = string + })) })) default = {} + validation { + condition = alltrue([for key, value in var.psc_addresses : (value.region != null || (value.region == null && value.network != null))]) + error_message = "Provide network if creating global PSC addresses / endpoints." + } + validation { + condition = alltrue([for key, value in var.psc_addresses : (value.region == null || (value.region != null && value.subnet_self_link != null))]) + error_message = "Provide subnet_self_link if creating regional PSC addresses / endpoints." + } + validation { + condition = alltrue([for key, value in var.psc_addresses : !(value.subnet_self_link != null && value.network != null)]) + error_message = "Do not provide network and subnet_self_link at the same time" + } } diff --git a/tests/collectors.py b/tests/collectors.py index 749e192c5..310b8151c 100644 --- a/tests/collectors.py +++ b/tests/collectors.py @@ -87,8 +87,15 @@ class FabricTestItem(pytest.Item): self.extra_files = extra_files def runtest(self): - s = plan_validator(self.module, self.inventory, self.parent.path.parent, - self.tf_var_files, self.extra_files) + try: + summary = plan_validator(self.module, self.inventory, self.parent.path.parent, + self.tf_var_files, self.extra_files) + except AssertionError: + def full_paths(x): + return [(self.parent.path.parent / x ) for x in x] + print(f'Error in inventory file: {" ".join(full_paths(self.inventory))}') + print(f'To regenerate inventory run: python tools/plan_summary.py {self.module} {" ".join(full_paths(self.tf_var_files))}') + raise def reportinfo(self): return self.path, None, self.name diff --git a/tests/examples/variables.tf b/tests/examples/variables.tf index 29dc8d894..d999609b0 100644 --- a/tests/examples/variables.tf +++ b/tests/examples/variables.tf @@ -84,7 +84,7 @@ variable "subnet_psc_1" { name = "subnet_name" region = "subnet_region" cidr = "subnet_cidr" - self_link = "subnet_self_link" + self_link = "https://www.googleapis.com/compute/v1/projects/my-project/regions/europe-west8/subnetworks/subnet" } } diff --git a/tests/examples_e2e/setup_module/e2e_tests.tfvars.tftpl b/tests/examples_e2e/setup_module/e2e_tests.tfvars.tftpl index 5bb6d779e..fd63d450f 100644 --- a/tests/examples_e2e/setup_module/e2e_tests.tfvars.tftpl +++ b/tests/examples_e2e/setup_module/e2e_tests.tfvars.tftpl @@ -37,6 +37,12 @@ subnet = { cidr = "${subnet.ip_cidr_range}" self_link = "${subnet.self_link}" } +subnet_psc_1 = { + name = "${subnet_psc_1.name}" + region = "${subnet_psc_1.region}" + cidr = "${subnet_psc_1.ip_cidr_range}" + self_link = "${subnet_psc_1.self_link}" +} vpc = { name = "${vpc.name}" self_link = "${vpc.self_link}" diff --git a/tests/examples_e2e/setup_module/main.tf b/tests/examples_e2e/setup_module/main.tf index bd48e4fbd..4e1d1931b 100644 --- a/tests/examples_e2e/setup_module/main.tf +++ b/tests/examples_e2e/setup_module/main.tf @@ -15,7 +15,8 @@ locals { prefix = "${var.prefix}-${var.timestamp}${var.suffix}" jit_services = [ - "storage.googleapis.com", # no permissions granted by default + "storage.googleapis.com", # no permissions granted by default + "sqladmin.googleapis.com", # roles/cloudsql.serviceAgent ] services = [ # trimmed down list of services, to be extended as needed @@ -35,6 +36,7 @@ locals { "secretmanager.googleapis.com", "servicenetworking.googleapis.com", "serviceusage.googleapis.com", + "sqladmin.googleapis.com", "stackdriver.googleapis.com", "storage-component.googleapis.com", "storage.googleapis.com", @@ -114,6 +116,36 @@ resource "google_compute_subnetwork" "proxy_only_regional" { role = "ACTIVE" } +resource "google_compute_subnetwork" "psc" { + project = google_project.project.project_id + network = google_compute_network.network.name + name = "psc-regional" + region = var.region + ip_cidr_range = "10.0.19.0/24" + purpose = "PRIVATE_SERVICE_CONNECT" +} + +### PSA ### + +resource "google_compute_global_address" "psa_ranges" { + project = google_project.project.project_id + network = google_compute_network.network.id + name = "psa-range" + purpose = "VPC_PEERING" + address_type = "INTERNAL" + address = "10.0.20.0" + prefix_length = 22 +} + +resource "google_service_networking_connection" "psa_connection" { + network = google_compute_network.network.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.psa_ranges.name] + deletion_policy = "ABANDON" +} + +### END OF PSA + resource "google_service_account" "service_account" { account_id = "e2e-service-account" project = google_project.project.project_id @@ -141,6 +173,12 @@ resource "google_project_service_identity" "jit_si" { depends_on = [google_project_service.project_service] } +resource "google_project_iam_binding" "cloudsql_agent" { + members = ["serviceAccount:service-${google_project.project.number}@gcp-sa-cloud-sql.iam.gserviceaccount.com"] + project = google_project.project.project_id + role = "roles/cloudsql.serviceAgent" + depends_on = [google_project_service_identity.jit_si] +} resource "local_file" "terraform_tfvars" { filename = "e2e_tests.tfvars" @@ -168,6 +206,12 @@ resource "local_file" "terraform_tfvars" { ip_cidr_range = google_compute_subnetwork.subnetwork.ip_cidr_range self_link = google_compute_subnetwork.subnetwork.self_link } + subnet_psc_1 = { + name = google_compute_subnetwork.psc.name + region = google_compute_subnetwork.psc.region + ip_cidr_range = google_compute_subnetwork.psc.ip_cidr_range + self_link = google_compute_subnetwork.psc.self_link + } vpc = { name = google_compute_network.network.name self_link = google_compute_network.network.self_link diff --git a/tests/fixtures.py b/tests/fixtures.py index 50b6d8ad6..1d725135f 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -186,8 +186,13 @@ def plan_validator(module_path, inventory_paths, basedir, tf_var_files=None, # print(yaml.dump({'counts': summary.counts})) if 'values' in inventory: - validate_plan_object(inventory['values'], summary.values, relative_path, - "") + try: + validate_plan_object(inventory['values'], summary.values, relative_path, + "") + except AssertionError: + print(f'\n{path}') + print(yaml.dump({'values': summary.values})) + raise if 'counts' in inventory: try: @@ -199,6 +204,7 @@ def plan_validator(module_path, inventory_paths, basedir, tf_var_files=None, assert plan_count == expected_count, \ f'{relative_path}: count of {type_} resources failed. Got {plan_count}, expected {expected_count}' except AssertionError: + print(f'\n{path}') print(yaml.dump({'counts': summary.counts})) raise @@ -218,6 +224,7 @@ def plan_validator(module_path, inventory_paths, basedir, tf_var_files=None, f'{relative_path}: output {output_name} failed. Got `{plan_output}`, expected `{expected_output}`' except AssertionError: if _buffer: + print(f'\n{path}') print(yaml.dump(_buffer)) raise return summary diff --git a/tests/fixtures/cloudsql-instance.tf b/tests/fixtures/cloudsql-instance.tf new file mode 100644 index 000000000..6fed8d6ad --- /dev/null +++ b/tests/fixtures/cloudsql-instance.tf @@ -0,0 +1,33 @@ +/** + * 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. + */ + +module "cloudsql-instance" { + source = "./fabric/modules/cloudsql-instance" + project_id = var.project_id + network_config = { + connectivity = { + psc_allowed_consumer_projects = [var.project_id] + } + } + ## define a consumer project with an endpoint within the project + name = "db" + region = var.region + availability_type = "REGIONAL" + database_version = "POSTGRES_13" + tier = "db-g1-small" + gcp_deletion_protection = false + terraform_deletion_protection = false +} diff --git a/tests/fixtures/cloudsql-kms-iam-grant.tf b/tests/fixtures/cloudsql-kms-iam-grant.tf new file mode 100644 index 000000000..9ea53a026 --- /dev/null +++ b/tests/fixtures/cloudsql-kms-iam-grant.tf @@ -0,0 +1,23 @@ +/** + * 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. + */ + +resource "google_kms_crypto_key_iam_binding" "encrypt_decrypt" { + crypto_key_id = var.kms_key.id + members = [ + "serviceAccount:service-${var.project_number}@gcp-sa-cloud-sql.iam.gserviceaccount.com" + ] + role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" +} diff --git a/tests/modules/cloudsql_instance/examples/insights.yaml b/tests/modules/cloudsql_instance/examples/insights.yaml index 84a4245dd..a2c46f5b9 100644 --- a/tests/modules/cloudsql_instance/examples/insights.yaml +++ b/tests/modules/cloudsql_instance/examples/insights.yaml @@ -17,11 +17,11 @@ values: database_version: POSTGRES_13 name: db project: project-id - region: europe-west1 + region: europe-west8 settings: - activation_policy: ALWAYS availability_type: ZONAL - deletion_protection_enabled: true + deletion_protection_enabled: false disk_autoresize: true disk_type: PD_SSD insights_config: diff --git a/tests/modules/cloudsql_instance/examples/psc.yaml b/tests/modules/cloudsql_instance/examples/psc.yaml new file mode 100644 index 000000000..722700d7b --- /dev/null +++ b/tests/modules/cloudsql_instance/examples/psc.yaml @@ -0,0 +1,38 @@ +# 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. + +values: + module.db.google_sql_database_instance.primary: + database_version: POSTGRES_13 + deletion_protection: false + name: myprefix-db + project: project-id + region: europe-west8 + settings: + - activation_policy: ALWAYS + availability_type: REGIONAL + deletion_protection_enabled: false + ip_configuration: + - ipv4_enabled: false + private_network: null + psc_config: + - allowed_consumer_projects: + - project-id + psc_enabled: true + tier: db-g1-small + +counts: + google_sql_database_instance: 1 + modules: 1 + resources: 1 diff --git a/tests/modules/cloudsql_instance/examples/public-ip.yaml b/tests/modules/cloudsql_instance/examples/public-ip.yaml index f3ca86d3a..e216ee3b0 100644 --- a/tests/modules/cloudsql_instance/examples/public-ip.yaml +++ b/tests/modules/cloudsql_instance/examples/public-ip.yaml @@ -17,30 +17,11 @@ values: database_version: MYSQL_8_0 name: db project: project-id - region: europe-west1 + region: europe-west8 settings: - activation_policy: ALWAYS availability_type: ZONAL - deletion_protection_enabled: true - disk_autoresize: true - disk_type: PD_SSD - insights_config: [] - ip_configuration: - - allocated_ip_range: null - authorized_networks: [] - ipv4_enabled: true - private_network: projects/xxx/global/networks/aaa - tier: db-g1-small - module.db.google_sql_database_instance.replicas["replica1"]: - database_version: MYSQL_8_0 - master_instance_name: db - name: replica1 - project: project-id - region: europe-west3 - settings: - - activation_policy: ALWAYS - availability_type: ZONAL - deletion_protection_enabled: true + deletion_protection_enabled: false disk_autoresize: true disk_type: PD_SSD insights_config: [] @@ -52,4 +33,4 @@ values: tier: db-g1-small counts: - google_sql_database_instance: 2 + google_sql_database_instance: 1 diff --git a/tests/modules/cloudsql_instance/examples/replicas.yaml b/tests/modules/cloudsql_instance/examples/replicas.yaml index 1ed30f9bc..bb337d445 100644 --- a/tests/modules/cloudsql_instance/examples/replicas.yaml +++ b/tests/modules/cloudsql_instance/examples/replicas.yaml @@ -18,7 +18,7 @@ values: database_version: POSTGRES_13 name: myprefix-db project: project-id - region: europe-west1 + region: europe-west8 module.db.google_sql_database_instance.replicas["replica1"]: clone: [] database_version: POSTGRES_13 diff --git a/tests/modules/cloudsql_instance/examples/simple.yaml b/tests/modules/cloudsql_instance/examples/simple.yaml index 5103c12b4..4221eb288 100644 --- a/tests/modules/cloudsql_instance/examples/simple.yaml +++ b/tests/modules/cloudsql_instance/examples/simple.yaml @@ -16,10 +16,10 @@ values: module.db.google_sql_database_instance.primary: clone: [] database_version: POSTGRES_13 - deletion_protection: true + deletion_protection: false name: db - project: my-db-project - region: europe-west1 + project: test-db-prj + region: europe-west8 restore_backup_context: [] root_password: null settings: @@ -30,7 +30,7 @@ values: collation: null data_cache_config: [] database_flags: [] - deletion_protection_enabled: true + deletion_protection_enabled: false deny_maintenance_period: [] disk_autoresize: true disk_autoresize_limit: 0 @@ -54,25 +54,25 @@ values: module.project.google_project.project[0]: auto_create_network: false billing_account: 123456-123456-123456 - folder_id: null + folder_id: '1122334455' labels: null - name: my-db-project - org_id: '1122334455' - project_id: my-db-project + name: test-db-prj + org_id: null + project_id: test-db-prj skip_delete: false timeouts: null module.project.google_project_iam_member.servicenetworking[0]: condition: [] - project: my-db-project + project: test-db-prj role: roles/servicenetworking.serviceAgent module.project.google_project_service.project_services["servicenetworking.googleapis.com"]: disable_dependent_services: false disable_on_destroy: false - project: my-db-project + project: test-db-prj service: servicenetworking.googleapis.com timeouts: null module.project.google_project_service_identity.servicenetworking[0]: - project: my-db-project + project: test-db-prj service: servicenetworking.googleapis.com timeouts: null module.vpc.google_compute_global_address.psa_ranges["servicenetworking-googleapis-com-cloud-sql"]: @@ -82,7 +82,7 @@ values: ip_version: null name: servicenetworking-googleapis-com-cloud-sql prefix_length: 16 - project: my-db-project + project: test-db-prj purpose: VPC_PEERING timeouts: null module.vpc.google_compute_network.network[0]: @@ -92,14 +92,14 @@ values: enable_ula_internal_ipv6: null name: my-network network_firewall_policy_enforcement_order: AFTER_CLASSIC_FIREWALL - project: my-db-project + project: test-db-prj routing_mode: GLOBAL timeouts: null module.vpc.google_compute_network_peering_routes_config.psa_routes["servicenetworking.googleapis.com"]: export_custom_routes: false import_custom_routes: false network: my-network - project: my-db-project + project: test-db-prj timeouts: null module.vpc.google_compute_route.gateway["private-googleapis"]: description: Terraform-managed. @@ -111,7 +111,7 @@ values: next_hop_instance: null next_hop_vpn_tunnel: null priority: 1000 - project: my-db-project + project: test-db-prj tags: null timeouts: null module.vpc.google_compute_route.gateway["restricted-googleapis"]: @@ -124,11 +124,11 @@ values: next_hop_instance: null next_hop_vpn_tunnel: null priority: 1000 - project: my-db-project + project: test-db-prj tags: null timeouts: null module.vpc.google_service_networking_connection.psa_connection["servicenetworking.googleapis.com"]: - deletion_policy: null + deletion_policy: ABANDON reserved_peering_ranges: - servicenetworking-googleapis-com-cloud-sql service: servicenetworking.googleapis.com @@ -141,11 +141,11 @@ counts: google_compute_route: 2 google_project: 1 google_project_iam_member: 1 - google_project_service: 1 - google_project_service_identity: 1 + google_project_service: 2 + google_project_service_identity: 2 google_service_networking_connection: 1 google_sql_database_instance: 1 modules: 3 - resources: 11 + resources: 14 outputs: {} diff --git a/tests/modules/net_address/examples/psc-service-attachment.yaml b/tests/modules/net_address/examples/psc-service-attachment.yaml new file mode 100644 index 000000000..7c3eaca02 --- /dev/null +++ b/tests/modules/net_address/examples/psc-service-attachment.yaml @@ -0,0 +1,29 @@ +# 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. + +values: + module.addresses.google_compute_forwarding_rule.psc_consumer["cloudsql-one"]: + load_balancing_scheme: '' + name: cloudsql-one + project: project-id + recreate_closed_psc: true + region: europe-west8 + subnetwork: subnet_self_link + module.addresses.google_compute_address.psc["cloudsql-one"]: + address: 10.0.16.32 + address_type: INTERNAL + description: Terraform managed. + name: cloudsql-one + project: project-id + subnetwork: subnet_self_link