Skip to content

Commit

Permalink
feat: support Cloud SQL\'s new field psc_auto_connections. (GoogleC…
Browse files Browse the repository at this point in the history
…loudPlatform#12236)

Signed-off-by: Julia Zou <[email protected]>
  • Loading branch information
yyzou-bot authored Nov 12, 2024
1 parent 8179902 commit 1514358
Show file tree
Hide file tree
Showing 3 changed files with 267 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.`,
},
},
},
},
Expand Down Expand Up @@ -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{})),
}
}

Expand Down Expand Up @@ -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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -944,21 +944,78 @@ 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) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(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",
Expand All @@ -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, "", "")),
},
},
})
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 1514358

Please sign in to comment.