diff --git a/.changelog/9765.txt b/.changelog/9765.txt new file mode 100644 index 00000000000..2256a09d95f --- /dev/null +++ b/.changelog/9765.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +service: added field `deletion_policy` to `google_service_networking_connection` +``` diff --git a/google/services/servicenetworking/resource_service_networking_connection.go b/google/services/servicenetworking/resource_service_networking_connection.go index 7346d7e08fd..f88cd60d257 100644 --- a/google/services/servicenetworking/resource_service_networking_connection.go +++ b/google/services/servicenetworking/resource_service_networking_connection.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/errwrap" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "google.golang.org/api/servicenetworking/v1" ) @@ -60,6 +61,12 @@ func ResourceServiceNetworkingConnection() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, Description: `Named IP address range(s) of PEERING type reserved for this service provider. Note that invoking this method with a different range when connection is already established will not reallocate already provisioned service producer subnetworks.`, }, + "deletion_policy": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"ABANDON", ""}, false), + Description: `When set to ABANDON, terraform will abandon management of the resource instead of deleting it. Prevents terraform apply failures with CloudSQL. Note: The resource will still exist.`, + }, "peering": { Type: schema.TypeString, Computed: true, @@ -250,6 +257,12 @@ func resourceServiceNetworkingConnectionUpdate(d *schema.ResourceData, meta inte func resourceServiceNetworkingConnectionDelete(d *schema.ResourceData, meta interface{}) error { config := meta.(*transport_tpg.Config) + + if deletionPolicy := d.Get("deletion_policy"); deletionPolicy == "ABANDON" { + log.Printf("[WARN] The service networking connection has been abandoned") + return nil + } + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) if err != nil { return err diff --git a/google/services/servicenetworking/resource_service_networking_connection_test.go b/google/services/servicenetworking/resource_service_networking_connection_test.go index ef78f92bb0b..70d9ac4237c 100644 --- a/google/services/servicenetworking/resource_service_networking_connection_test.go +++ b/google/services/servicenetworking/resource_service_networking_connection_test.go @@ -38,6 +38,32 @@ func TestAccServiceNetworkingConnection_create(t *testing.T) { }) } +func TestAccServiceNetworkingConnection_abandon(t *testing.T) { + t.Parallel() + + network := fmt.Sprintf("tf-test-service-networking-connection-abandon-%s", acctest.RandString(t, 10)) + addr := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) + service := "servicenetworking.googleapis.com" + org_id := envvar.GetTestOrgFromEnv(t) + billing_account := envvar.GetTestBillingAccountFromEnv(t) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testServiceNetworkingConnectionDestroyAbandon(t, service, network), + Steps: []resource.TestStep{ + { + Config: testAccServiceNetworkingConnectionToBeAbandoned(network, addr, "servicenetworking.googleapis.com", org_id, billing_account), + }, + { + ResourceName: "google_service_networking_connection.foobar", + ImportState: true, + ImportStateVerify: false, + }, + }, + }) +} + func TestAccServiceNetworkingConnection_update(t *testing.T) { t.Parallel() @@ -98,6 +124,30 @@ func testServiceNetworkingConnectionDestroy(t *testing.T, parent, network string } } +func testServiceNetworkingConnectionDestroyAbandon(t *testing.T, parent, network string) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := acctest.GoogleProviderConfig(t) + parentService := "services/" + parent + networkName := fmt.Sprintf("projects/%s/global/networks/%s", envvar.GetTestProjectFromEnv(), network) + listCall := config.NewServiceNetworkingClient(config.UserAgent).Services.Connections.List(parentService).Network(networkName) + if config.UserProjectOverride { + listCall.Header().Add("X-Goog-User-Project", envvar.GetTestProjectFromEnv()) + } + response, err := listCall.Do() + if err != nil { + return err + } + + for _, c := range response.Connections { + if c.Network == networkName { + return fmt.Errorf("Found %s which should have been destroyed.", networkName) + } + } + + return nil + } +} + func testAccServiceNetworkingConnection(networkName, addressRangeName, serviceName, org_id, billing_account string) string { return fmt.Sprintf(` resource "google_project" "project" { @@ -134,3 +184,41 @@ resource "google_service_networking_connection" "foobar" { } `, addressRangeName, addressRangeName, org_id, billing_account, networkName, addressRangeName, serviceName) } + +func testAccServiceNetworkingConnectionToBeAbandoned(networkName, addressRangeName, serviceName, org_id, billing_account string) string { + return fmt.Sprintf(` +resource "google_project" "project" { + project_id = "%s" + name = "%s" + org_id = "%s" + billing_account = "%s" +} + +resource "google_project_service" "servicenetworking" { + project = google_project.project.project_id + service = "servicenetworking.googleapis.com" +} + +resource "google_compute_network" "servicenet" { + name = "%s" + depends_on = [google_project_service.servicenetworking] +} + +resource "google_compute_global_address" "foobar" { + name = "%s" + purpose = "VPC_PEERING" + address_type = "INTERNAL" + prefix_length = 16 + network = google_compute_network.servicenet.self_link + depends_on = [google_project_service.servicenetworking] +} + +resource "google_service_networking_connection" "foobar" { + network = google_compute_network.servicenet.self_link + service = "%s" + reserved_peering_ranges = [google_compute_global_address.foobar.name] + depends_on = [google_project_service.servicenetworking] + deletion_policy = "ABANDON" +} +`, addressRangeName, addressRangeName, org_id, billing_account, networkName, addressRangeName, serviceName) +} diff --git a/website/docs/r/service_networking_connection.html.markdown b/website/docs/r/service_networking_connection.html.markdown index 0c9abcbce60..632c7d4ee8a 100644 --- a/website/docs/r/service_networking_connection.html.markdown +++ b/website/docs/r/service_networking_connection.html.markdown @@ -59,6 +59,8 @@ The following arguments are supported: this service provider. Note that invoking this method with a different range when connection is already established will not reallocate already provisioned service producer subnetworks. +* `deletion_policy` - (Optional) The deletion policy for the service networking connection. Setting to ABANDON allows the resource to be abandoned rather than deleted. This will enable a successful terraform destroy when destroying CloudSQL instances. Use with care as it can lead to dangling resources. + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are exported: