From 151435867d85bc67cc73d32365733313abf5b2de Mon Sep 17 00:00:00 2001 From: yyzou-bot Date: Tue, 12 Nov 2024 09:36:22 -0800 Subject: [PATCH] feat: support Cloud SQL\'s new field `psc_auto_connections`. (#12236) Signed-off-by: Julia Zou --- .../resource_sql_database_instance.go.tmpl | 53 +++++ .../resource_sql_database_instance_test.go | 187 +++++++++++++++++- .../r/sql_database_instance.html.markdown | 34 ++++ 3 files changed, 267 insertions(+), 7 deletions(-) diff --git a/mmv1/third_party/terraform/services/sql/resource_sql_database_instance.go.tmpl b/mmv1/third_party/terraform/services/sql/resource_sql_database_instance.go.tmpl index 88e812273422..1ffe1aed81d5 100644 --- a/mmv1/third_party/terraform/services/sql/resource_sql_database_instance.go.tmpl +++ b/mmv1/third_party/terraform/services/sql/resource_sql_database_instance.go.tmpl @@ -489,6 +489,25 @@ is set to true. Defaults to ZONAL.`, Set: schema.HashString, Description: `List of consumer projects that are allow-listed for PSC connections to this instance. This instance can be connected to with PSC from any network in these projects. Each consumer project in this list may be represented by a project number (numeric) or by a project id (alphanumeric).`, }, + "psc_auto_connections": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "consumer_service_project_id": { + Type: schema.TypeString, + Optional: true, + Description: `The project ID of consumer service project of this consumer endpoint.`, + }, + "consumer_network": { + Type: schema.TypeString, + Required: true, + Description: `The consumer network of this consumer endpoint. This must be a resource path that includes both the host project and the network name. The consumer host project of this network might be different from the consumer service project.`, + }, + }, + }, + Description: `A comma-separated list of networks or a comma-separated list of network-project pairs. Each project in this list is represented by a project number (numeric) or by a project ID (alphanumeric). This allows Private Service Connect connections to be created automatically for the specified networks.`, + }, }, }, }, @@ -1437,12 +1456,30 @@ func expandIpConfiguration(configured []interface{}, databaseVersion string) *sq } } +func expandPscAutoConnectionConfig(configured []interface{}) []*sqladmin.PscAutoConnectionConfig { + pscAutoConnections:= make([]*sqladmin.PscAutoConnectionConfig, 0, len(configured)) + + for _, _flag := range configured { + if _flag == nil { + continue + } + _entry := _flag.(map[string]interface{}) + + pscAutoConnections = append(pscAutoConnections, &sqladmin.PscAutoConnectionConfig { + ConsumerNetwork: _entry["consumer_network"].(string), + ConsumerProject: _entry["consumer_service_project_id"].(string), + }) + } + return pscAutoConnections +} + func expandPscConfig(configured []interface{}) *sqladmin.PscConfig { for _, _pscConfig := range configured { _entry := _pscConfig.(map[string]interface{}) return &sqladmin.PscConfig{ PscEnabled: _entry["psc_enabled"].(bool), AllowedConsumerProjects: tpgresource.ConvertStringArr(_entry["allowed_consumer_projects"].(*schema.Set).List()), + PscAutoConnections: expandPscAutoConnectionConfig(_entry["psc_auto_connections"].([]interface{})), } } @@ -2342,10 +2379,26 @@ func flattenIpConfiguration(ipConfiguration *sqladmin.IpConfiguration, d *schema return []map[string]interface{}{data} } +func flattenPscAutoConnections(pscAutoConnections []*sqladmin.PscAutoConnectionConfig) []map[string]interface{} { + flags := make([]map[string]interface{}, 0, len(pscAutoConnections)) + + for _, flag := range pscAutoConnections { + data := map[string]interface{}{ + "consumer_network": flag.ConsumerNetwork, + "consumer_service_project_id": flag.ConsumerProject, + } + + flags = append(flags, data) + } + + return flags +} + func flattenPscConfigs(pscConfig *sqladmin.PscConfig) interface{} { data := map[string]interface{}{ "psc_enabled": pscConfig.PscEnabled, "allowed_consumer_projects": schema.NewSet(schema.HashString, tpgresource.ConvertStringArrToInterface(pscConfig.AllowedConsumerProjects)), + "psc_auto_connections": flattenPscAutoConnections(pscConfig.PscAutoConnections), } return []map[string]interface{}{data} diff --git a/mmv1/third_party/terraform/services/sql/resource_sql_database_instance_test.go b/mmv1/third_party/terraform/services/sql/resource_sql_database_instance_test.go index 53d975ec08fa..b1abff5d5f7f 100644 --- a/mmv1/third_party/terraform/services/sql/resource_sql_database_instance_test.go +++ b/mmv1/third_party/terraform/services/sql/resource_sql_database_instance_test.go @@ -944,13 +944,69 @@ func TestAccSqlDatabaseInstance_withPSCEnabled_thenAddAllowedConsumerProjects_th }) } -func TestAccSqlDatabaseInstance_basicInstance_thenPSCEnabled(t *testing.T) { +func TestAccSqlDatabaseInstance_withPSCEnabled_withoutPscAutoConnections(t *testing.T) { t.Parallel() instanceName := "tf-test-" + acctest.RandString(t, 10) - projectId := "psctestproject" + acctest.RandString(t, 10) - orgId := envvar.GetTestOrgFromEnv(t) - billingAccount := envvar.GetTestBillingAccountFromEnv(t) + projectId := envvar.GetTestProjectFromEnv() + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccSqlDatabaseInstanceDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccSqlDatabaseInstance_withPSCEnabled_withoutPscAutoConnections(instanceName), + Check: resource.ComposeTestCheckFunc(verifyPscAutoConnectionsOperation("google_sql_database_instance.instance", true, true, false, "", "")), + }, + { + ResourceName: "google_sql_database_instance.instance", + ImportState: true, + ImportStateVerify: true, + ImportStateIdPrefix: fmt.Sprintf("%s/", projectId), + ImportStateVerifyIgnore: []string{"deletion_protection"}, + }, + }, + }) +} + +func TestAccSqlDatabaseInstance_withPSCEnabled_withPscAutoConnections(t *testing.T) { + t.Parallel() + + testId := "test-psc-auto-con" + acctest.RandString(t, 10) + instanceName := "tf-test-" + acctest.RandString(t, 10) + projectId := envvar.GetTestProjectFromEnv() + networkName := acctest.BootstrapSharedTestNetwork(t, testId) + network_short_link_name := fmt.Sprintf("projects/%s/global/networks/%s", projectId, networkName) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccSqlDatabaseInstanceDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccSqlDatabaseInstance_withPSCEnabled_withPscAutoConnections(instanceName, projectId, networkName), + Check: resource.ComposeTestCheckFunc(verifyPscAutoConnectionsOperation("google_sql_database_instance.instance", true, true, true, network_short_link_name, projectId)), + }, + { + ResourceName: "google_sql_database_instance.instance", + ImportState: true, + ImportStateVerify: true, + ImportStateIdPrefix: fmt.Sprintf("%s/", projectId), + ImportStateVerifyIgnore: []string{"deletion_protection"}, + }, + }, + }) +} + +func TestAccSqlDatabaseInstance_withPSCEnabled_thenAddPscAutoConnections_thenRemovePscAutoConnections(t *testing.T) { + t.Parallel() + + testId := "test-psc-auto-con" + acctest.RandString(t, 10) + instanceName := "tf-test-" + acctest.RandString(t, 10) + projectId := envvar.GetTestProjectFromEnv() + networkName := acctest.BootstrapSharedTestNetwork(t, testId) + network_short_link_name := fmt.Sprintf("projects/%s/global/networks/%s", projectId, networkName) acctest.VcrTest(t, resource.TestCase{ PreCheck: func() { acctest.AccTestPreCheck(t) }, @@ -958,7 +1014,8 @@ func TestAccSqlDatabaseInstance_basicInstance_thenPSCEnabled(t *testing.T) { CheckDestroy: testAccSqlDatabaseInstanceDestroyProducer(t), Steps: []resource.TestStep{ { - Config: testAccSqlDatabaseInstance_basicInstanceForPsc(instanceName, projectId, orgId, billingAccount), + Config: testAccSqlDatabaseInstance_withPSCEnabled_withoutPscAutoConnections(instanceName), + Check: resource.ComposeTestCheckFunc(verifyPscAutoConnectionsOperation("google_sql_database_instance.instance", true, true, false, "", "")), }, { ResourceName: "google_sql_database_instance.instance", @@ -968,8 +1025,19 @@ func TestAccSqlDatabaseInstance_basicInstance_thenPSCEnabled(t *testing.T) { ImportStateVerifyIgnore: []string{"deletion_protection"}, }, { - Config: testAccSqlDatabaseInstance_withPSCEnabled_withoutAllowedConsumerProjects(instanceName, projectId, orgId, billingAccount), - ExpectError: regexp.MustCompile("PSC connectivity can not be enabled"), + Config: testAccSqlDatabaseInstance_withPSCEnabled_withPscAutoConnections(instanceName, projectId, networkName), + Check: resource.ComposeTestCheckFunc(verifyPscAutoConnectionsOperation("google_sql_database_instance.instance", true, true, true, network_short_link_name, projectId)), + }, + { + ResourceName: "google_sql_database_instance.instance", + ImportState: true, + ImportStateVerify: true, + ImportStateIdPrefix: fmt.Sprintf("%s/", projectId), + ImportStateVerifyIgnore: []string{"deletion_protection"}, + }, + { + Config: testAccSqlDatabaseInstance_withPSCEnabled_withoutPscAutoConnections(instanceName), + Check: resource.ComposeTestCheckFunc(verifyPscAutoConnectionsOperation("google_sql_database_instance.instance", true, true, false, "", "")), }, }, }) @@ -3569,6 +3637,111 @@ func verifyPscOperation(resourceName string, isPscConfigExpected bool, expectedP } } +func verifyPscAutoConnectionsOperation(resourceName string, isPscConfigExpected bool, expectedPscEnabled bool, isPscAutoConnectionConfigExpected bool, expectedConsumerNetwork string, expectedConsumerProject string) func(*terraform.State) error { + return func(s *terraform.State) error { + resource, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Can't find %s in state", resourceName) + } + + resourceAttributes := resource.Primary.Attributes + _, ok = resourceAttributes["settings.0.ip_configuration.#"] + if !ok { + return fmt.Errorf("settings.0.ip_configuration.# block is not present in state for %s", resourceName) + } + + if isPscConfigExpected { + _, ok := resourceAttributes["settings.0.ip_configuration.0.psc_config.#"] + if !ok { + return fmt.Errorf("settings.0.ip_configuration.0.psc_config property is not present or set in state of %s", resourceName) + } + + pscEnabledStr, ok := resourceAttributes["settings.0.ip_configuration.0.psc_config.0.psc_enabled"] + pscEnabled, err := strconv.ParseBool(pscEnabledStr) + if err != nil || pscEnabled != expectedPscEnabled { + return fmt.Errorf("settings.0.ip_configuration.0.psc_config.0.psc_enabled property value is not set as expected in state of %s, expected %v, actual %v", resourceName, expectedPscEnabled, pscEnabled) + } + + _, ok = resourceAttributes["settings.0.ip_configuration.0.psc_config.0.psc_auto_connections.#"] + if !ok { + return fmt.Errorf("settings.0.ip_configuration.0.psc_config.0.psc_auto_connections property is not present or set in state of %s", resourceName) + } + + if isPscAutoConnectionConfigExpected { + consumerNetwork, ok := resourceAttributes["settings.0.ip_configuration.0.psc_config.0.psc_auto_connections.0.consumer_network"] + if !ok || consumerNetwork != expectedConsumerNetwork { + return fmt.Errorf("settings.0.ip_configuration.0.psc_config.0.psc_auto_connections.0.consumer_network property is not present or set as expected in state of %s", resourceName) + } + + consumerProject, ok := resourceAttributes["settings.0.ip_configuration.0.psc_config.0.psc_auto_connections.0.consumer_service_project_id"] + if !ok || consumerProject != expectedConsumerProject { + return fmt.Errorf("settings.0.ip_configuration.0.psc_config.0.psc_auto_connections.0.consumer_service_project_id property is not present or set as expected in state of %s", resourceName) + } + } + } + + return nil + } +} + +func testAccSqlDatabaseInstance_withPSCEnabled_withoutPscAutoConnections(instanceName string) string { + return fmt.Sprintf(` +resource "google_sql_database_instance" "instance" { + name = "%s" + region = "us-west2" + database_version = "MYSQL_8_0" + deletion_protection = false + settings { + tier = "db-g1-small" + ip_configuration { + psc_config { + psc_enabled = true + } + ipv4_enabled = false + } + backup_configuration { + enabled = true + binary_log_enabled = true + } + availability_type = "REGIONAL" + } +} +`, instanceName) +} + +func testAccSqlDatabaseInstance_withPSCEnabled_withPscAutoConnections(instanceName string, projectId string, networkName string) string { + return fmt.Sprintf(` +data "google_compute_network" "testnetwork" { + name = "%s" +} + +resource "google_sql_database_instance" "instance" { + name = "%s" + region = "us-west2" + database_version = "MYSQL_8_0" + deletion_protection = false + settings { + tier = "db-g1-small" + ip_configuration { + psc_config { + psc_enabled = true + psc_auto_connections { + consumer_network = "projects/%s/global/networks/%s" + consumer_service_project_id = "%s" + } + } + ipv4_enabled = false + } + backup_configuration { + enabled = true + binary_log_enabled = true + } + availability_type = "REGIONAL" + } +} +`, networkName, instanceName, projectId, networkName, projectId) +} + func testAccSqlDatabaseInstance_withPrivateNetwork_withoutAllocatedIpRange(databaseName, networkName string, specifyPrivatePathOption bool, enablePrivatePath bool) string { privatePathOption := "" if specifyPrivatePathOption { diff --git a/mmv1/third_party/terraform/website/docs/r/sql_database_instance.html.markdown b/mmv1/third_party/terraform/website/docs/r/sql_database_instance.html.markdown index 0346b9a2e62c..5c70a7a4c1a2 100644 --- a/mmv1/third_party/terraform/website/docs/r/sql_database_instance.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/sql_database_instance.html.markdown @@ -197,6 +197,34 @@ resource "google_sql_database_instance" "main" { } ``` +### Cloud SQL Instance with PSC auto connections + +```hcl +resource "google_sql_database_instance" "main" { + name = "psc-enabled-main-instance" + database_version = "MYSQL_8_0" + settings { + tier = "db-f1-micro" + ip_configuration { + psc_config { + psc_enabled = true + allowed_consumer_projects = ["allowed-consumer-project-name"] + psc_auto_connections { + consumer_network = "network-name" + consumer_service_project_id = "project-id" + } + } + ipv4_enabled = false + } + backup_configuration { + enabled = true + binary_log_enabled = true + } + availability_type = "REGIONAL" + } +} +``` + ## Argument Reference The following arguments are supported: @@ -404,6 +432,12 @@ The optional `settings.ip_configuration.psc_config` sublist supports: * `allowed_consumer_projects` - (Optional) List of consumer projects that are allow-listed for PSC connections to this instance. This instance can be connected to with PSC from any network in these projects. Each consumer project in this list may be represented by a project number (numeric) or by a project id (alphanumeric). +* The optional `psc_config.psc_auto_connections` subblock - (Optional) A comma-separated list of networks or a comma-separated list of network-project pairs. Each project in this list is represented by a project number (numeric) or by a project ID (alphanumeric). This allows Private Service Connect connections to be created automatically for the specified networks. + +* `consumer_network` - "The consumer network of this consumer endpoint. This must be a resource path that includes both the host project and the network name. For example, `projects/project1/global/networks/network1`. The consumer host project of this network might be different from the consumer service project." + +* `consumer_service_project_id` - (Optional) The project ID of consumer service project of this consumer endpoint. + The optional `settings.location_preference` subblock supports: * `follow_gae_application` - (Optional) A GAE application whose zone to remain