diff --git a/.changelog/10291.txt b/.changelog/10291.txt new file mode 100644 index 0000000000..42b910df15 --- /dev/null +++ b/.changelog/10291.txt @@ -0,0 +1,3 @@ +```release-note:none + +``` \ No newline at end of file diff --git a/go.mod b/go.mod index 840767522a..35bf8537b5 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.21 require ( cloud.google.com/go/bigtable v1.19.0 - github.com/GoogleCloudPlatform/declarative-resource-client-library v1.63.0 + github.com/GoogleCloudPlatform/declarative-resource-client-library v1.64.0 github.com/apparentlymart/go-cidr v1.1.0 github.com/davecgh/go-spew v1.1.1 github.com/dnaeon/go-vcr v1.0.1 diff --git a/go.sum b/go.sum index b5c2715612..1672ae714e 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,8 @@ cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDn dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/GoogleCloudPlatform/declarative-resource-client-library v1.63.0 h1:eSOBYPZVnU2fZul9sAJFGLVCgv6stNVKkmsogKF7UeY= -github.com/GoogleCloudPlatform/declarative-resource-client-library v1.63.0/go.mod h1:pL2Qt5HT+x6xrTd806oMiM3awW6kNIXB/iiuClz6m6k= +github.com/GoogleCloudPlatform/declarative-resource-client-library v1.64.0 h1:QA90iKudX8ijAW795f/jVbo0oEo7VoevwxLCNyi2qRc= +github.com/GoogleCloudPlatform/declarative-resource-client-library v1.64.0/go.mod h1:pL2Qt5HT+x6xrTd806oMiM3awW6kNIXB/iiuClz6m6k= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/ProtonMail/go-crypto v1.1.0-alpha.0 h1:nHGfwXmFvJrSR9xu8qL7BkO4DqTHXE9N5vPhgY2I+j0= diff --git a/google-beta/provider/provider_mmv1_resources.go b/google-beta/provider/provider_mmv1_resources.go index 53ae075f8f..0375965624 100644 --- a/google-beta/provider/provider_mmv1_resources.go +++ b/google-beta/provider/provider_mmv1_resources.go @@ -155,6 +155,7 @@ var handwrittenDatasources = map[string]*schema.Resource{ "google_artifact_registry_repository": artifactregistry.DataSourceArtifactRegistryRepository(), "google_apphub_discovered_workload": apphub.DataSourceApphubDiscoveredWorkload(), "google_app_engine_default_service_account": appengine.DataSourceGoogleAppEngineDefaultServiceAccount(), + "google_apphub_application": apphub.DataSourceGoogleApphubApplication(), "google_apphub_discovered_service": apphub.DataSourceApphubDiscoveredService(), "google_backup_dr_management_server": backupdr.DataSourceGoogleCloudBackupDRService(), "google_beyondcorp_app_connection": beyondcorp.DataSourceGoogleBeyondcorpAppConnection(), diff --git a/google-beta/services/apphub/data_source_apphub_application.go b/google-beta/services/apphub/data_source_apphub_application.go new file mode 100644 index 0000000000..625ca0e72a --- /dev/null +++ b/google-beta/services/apphub/data_source_apphub_application.go @@ -0,0 +1,47 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 +package apphub + +import ( + "fmt" + + "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func DataSourceGoogleApphubApplication() *schema.Resource { + dsSchema := tpgresource.DatasourceSchemaFromResourceSchema(ResourceApphubApplication().Schema) + tpgresource.AddRequiredFieldsToSchema(dsSchema, "project") + tpgresource.AddRequiredFieldsToSchema(dsSchema, "application_id") + tpgresource.AddRequiredFieldsToSchema(dsSchema, "location") + + return &schema.Resource{ + Read: dataSourceGoogleApphubApplicationRead, + Schema: dsSchema, + } +} + +func dataSourceGoogleApphubApplicationRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + + id, err := tpgresource.ReplaceVars(d, config, "projects/{{project}}/locations/{{location}}/applications/{{application_id}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + err = resourceApphubApplicationRead(d, meta) + if err != nil { + return err + } + + if err := tpgresource.SetDataSourceLabels(d); err != nil { + return err + } + + if d.Id() == "" { + return fmt.Errorf("%s not found", id) + } + return nil +} diff --git a/google-beta/services/apphub/data_source_apphub_application_test.go b/google-beta/services/apphub/data_source_apphub_application_test.go new file mode 100644 index 0000000000..3a4469ed06 --- /dev/null +++ b/google-beta/services/apphub/data_source_apphub_application_test.go @@ -0,0 +1,72 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 +package apphub_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-google-beta/google-beta/acctest" +) + +func TestDataSourceApphubApplication_basic(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckApphubApplicationDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testDataSourceApphubApplication_basic(context), + Check: resource.ComposeTestCheckFunc( + acctest.CheckDataSourceStateMatchesResourceState("data.google_apphub_application.example_data", "google_apphub_application.example"), + ), + }, + }, + }) +} + +func testDataSourceApphubApplication_basic(context map[string]interface{}) string { + return acctest.Nprintf(` + +data "google_apphub_application" "example_data" { + project = google_apphub_application.example.project + application_id = google_apphub_application.example.application_id + location = google_apphub_application.example.location +} + +resource "google_apphub_application" "example" { + location = "us-central1" + application_id = "tf-test-example-application%{random_suffix}" + display_name = "Application Full New%{random_suffix}" + scope { + type = "REGIONAL" + } + attributes { + environment { + type = "STAGING" + } + criticality { + type = "MISSION_CRITICAL" + } + business_owners { + display_name = "Alice%{random_suffix}" + email = "alice@google.com%{random_suffix}" + } + developer_owners { + display_name = "Bob%{random_suffix}" + email = "bob@google.com%{random_suffix}" + } + operator_owners { + display_name = "Charlie%{random_suffix}" + email = "charlie@google.com%{random_suffix}" + } + } +} +`, context) +} diff --git a/google-beta/services/container/resource_container_cluster_test.go b/google-beta/services/container/resource_container_cluster_test.go index 0f479a7d1a..0baab7066d 100644 --- a/google-beta/services/container/resource_container_cluster_test.go +++ b/google-beta/services/container/resource_container_cluster_test.go @@ -856,7 +856,7 @@ func TestAccContainerCluster_withInvalidReleaseChannel(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccContainerCluster_withReleaseChannelEnabled(clusterName, "CANARY", networkName, subnetworkName), - ExpectError: regexp.MustCompile(`expected release_channel\.0\.channel to be one of \[UNSPECIFIED RAPID REGULAR STABLE\], got CANARY`), + ExpectError: regexp.MustCompile(`expected release_channel\.0\.channel to be one of \["?UNSPECIFIED"? "?RAPID"? "?REGULAR"? "?STABLE"?\], got CANARY`), }, }, }) @@ -3336,7 +3336,7 @@ func TestAccContainerCluster_withInvalidAutoscalingProfile(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccContainerCluster_withAutoscalingProfile(clusterName, "AS_CHEAP_AS_POSSIBLE", networkName, subnetworkName), - ExpectError: regexp.MustCompile(`expected cluster_autoscaling\.0\.autoscaling_profile to be one of \[BALANCED OPTIMIZE_UTILIZATION\], got AS_CHEAP_AS_POSSIBLE`), + ExpectError: regexp.MustCompile(`expected cluster_autoscaling\.0\.autoscaling_profile to be one of \["?BALANCED"? "?OPTIMIZE_UTILIZATION"?\], got AS_CHEAP_AS_POSSIBLE`), }, }, }) diff --git a/google-beta/services/container/resource_container_node_pool_test.go b/google-beta/services/container/resource_container_node_pool_test.go index a60a760509..bf6f343d39 100644 --- a/google-beta/services/container/resource_container_node_pool_test.go +++ b/google-beta/services/container/resource_container_node_pool_test.go @@ -573,7 +573,7 @@ func TestAccContainerNodePool_withInvalidKubeletCpuManagerPolicy(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccContainerNodePool_withKubeletConfig(cluster, np, "dontexist", "100us", networkName, subnetworkName, true, 1024), - ExpectError: regexp.MustCompile(`.*to be one of \[static none \].*`), + ExpectError: regexp.MustCompile(`.*to be one of \["?static"? "?none"? "?"?\].*`), }, }, }) diff --git a/google-beta/services/firebaseappcheck/resource_firebase_app_check_service_config_test.go b/google-beta/services/firebaseappcheck/resource_firebase_app_check_service_config_test.go index d73d282231..493eafe6f3 100644 --- a/google-beta/services/firebaseappcheck/resource_firebase_app_check_service_config_test.go +++ b/google-beta/services/firebaseappcheck/resource_firebase_app_check_service_config_test.go @@ -26,8 +26,7 @@ func TestAccFirebaseAppCheckServiceConfig_firebaseAppCheckServiceConfigUpdate(t ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), CheckDestroy: testAccCheckFirebaseAppCheckServiceConfigDestroyProducer(t), ExternalProviders: map[string]resource.ExternalProvider{ - "random": {}, - "time": {}, + "time": {}, }, Steps: []resource.TestStep{ { @@ -86,6 +85,9 @@ resource "google_project_service" "database" { project = google_project.default.project_id service = "firebasedatabase.googleapis.com" disable_on_destroy = false + depends_on = [ + google_project_service.firebase, + ] } resource "google_project_service" "appcheck" { @@ -93,6 +95,9 @@ resource "google_project_service" "appcheck" { project = google_project.default.project_id service = "firebaseappcheck.googleapis.com" disable_on_destroy = false + depends_on = [ + google_project_service.database, + ] } resource "google_firebase_project" "default" { @@ -100,8 +105,6 @@ resource "google_firebase_project" "default" { project = google_project.default.project_id depends_on = [ - google_project_service.firebase, - google_project_service.database, google_project_service.appcheck, ] } diff --git a/google-beta/services/integrations/resource_integrations_client.go b/google-beta/services/integrations/resource_integrations_client.go index c9d4b903b4..d93b4217d5 100644 --- a/google-beta/services/integrations/resource_integrations_client.go +++ b/google-beta/services/integrations/resource_integrations_client.go @@ -251,11 +251,54 @@ func resourceIntegrationsClientRead(d *schema.ResourceData, meta interface{}) er } func resourceIntegrationsClientDelete(d *schema.ResourceData, meta interface{}) error { - log.Printf("[WARNING] Integrations Client resources"+ - " cannot be deleted from Google Cloud. The resource %s will be removed from Terraform"+ - " state, but will still be present on Google Cloud.", d.Id()) - d.SetId("") + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for Client: %s", err) + } + billingProject = project + + lockName, err := tpgresource.ReplaceVars(d, config, "Client/{{location}}") + if err != nil { + return err + } + transport_tpg.MutexStore.Lock(lockName) + defer transport_tpg.MutexStore.Unlock(lockName) + + url, err := tpgresource.ReplaceVars(d, config, "{{IntegrationsBasePath}}projects/{{project}}/locations/{{location}}/clients:deprovision") + if err != nil { + return err + } + + var obj map[string]interface{} + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + log.Printf("[DEBUG] Deleting Client %q", d.Id()) + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "POST", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutDelete), + }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, "Client") + } + log.Printf("[DEBUG] Finished deleting Client %q: %#v", d.Id(), res) return nil } diff --git a/google-beta/services/integrations/resource_integrations_client_generated_test.go b/google-beta/services/integrations/resource_integrations_client_generated_test.go index c876f36a44..9b968743c6 100644 --- a/google-beta/services/integrations/resource_integrations_client_generated_test.go +++ b/google-beta/services/integrations/resource_integrations_client_generated_test.go @@ -18,11 +18,16 @@ package integrations_test import ( + "fmt" + "strings" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-google-beta/google-beta/acctest" + "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport" ) func TestAccIntegrationsClient_integrationsClientBasicExample(t *testing.T) { @@ -35,6 +40,7 @@ func TestAccIntegrationsClient_integrationsClientBasicExample(t *testing.T) { acctest.VcrTest(t, resource.TestCase{ PreCheck: func() { acctest.AccTestPreCheck(t) }, ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckIntegrationsClientDestroyProducer(t), Steps: []resource.TestStep{ { Config: testAccIntegrationsClient_integrationsClientBasicExample(context), @@ -68,6 +74,7 @@ func TestAccIntegrationsClient_integrationsClientAdvanceExample(t *testing.T) { acctest.VcrTest(t, resource.TestCase{ PreCheck: func() { acctest.AccTestPreCheck(t) }, ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckIntegrationsClientDestroyProducer(t), Steps: []resource.TestStep{ { Config: testAccIntegrationsClient_integrationsClientAdvanceExample(context), @@ -120,3 +127,42 @@ resource "google_integrations_client" "example" { } `, context) } + +func testAccCheckIntegrationsClientDestroyProducer(t *testing.T) func(s *terraform.State) error { + return func(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_integrations_client" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := acctest.GoogleProviderConfig(t) + + url, err := tpgresource.ReplaceVarsForTest(config, rs, "{{IntegrationsBasePath}}projects/{{project}}/locations/{{location}}/clients") + if err != nil { + return err + } + + billingProject := "" + + if config.BillingProject != "" { + billingProject = config.BillingProject + } + + _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: billingProject, + RawURL: url, + UserAgent: config.UserAgent, + }) + if err == nil { + return fmt.Errorf("IntegrationsClient still exists at %s", url) + } + } + + return nil + } +} diff --git a/google-beta/services/integrations/resource_integrations_client_sweeper.go b/google-beta/services/integrations/resource_integrations_client_sweeper.go new file mode 100644 index 0000000000..9598839ae9 --- /dev/null +++ b/google-beta/services/integrations/resource_integrations_client_sweeper.go @@ -0,0 +1,139 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package integrations + +import ( + "context" + "log" + "strings" + "testing" + + "github.com/hashicorp/terraform-provider-google-beta/google-beta/envvar" + "github.com/hashicorp/terraform-provider-google-beta/google-beta/sweeper" + "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport" +) + +func init() { + sweeper.AddTestSweepers("IntegrationsClient", testSweepIntegrationsClient) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepIntegrationsClient(region string) error { + resourceName := "IntegrationsClient" + log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName) + + config, err := sweeper.SharedConfigForRegion(region) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error getting shared config for region: %s", err) + return err + } + + err = config.LoadAndValidate(context.Background()) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error loading: %s", err) + return err + } + + t := &testing.T{} + billingId := envvar.GetTestBillingAccountFromEnv(t) + + // Setup variables to replace in list template + d := &tpgresource.ResourceDataMock{ + FieldsInSchema: map[string]interface{}{ + "project": config.Project, + "region": region, + "location": region, + "zone": "-", + "billing_account": billingId, + }, + } + + listTemplate := strings.Split("https://integrations.googleapis.com/v1/projects/{{project}}/locations/{{location}}/clients", "?")[0] + listUrl, err := tpgresource.ReplaceVars(d, config, listTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err) + return nil + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: config.Project, + RawURL: listUrl, + UserAgent: config.UserAgent, + }) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) + return nil + } + + resourceList, ok := res["clients"] + if !ok { + log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") + return nil + } + + rl := resourceList.([]interface{}) + + log.Printf("[INFO][SWEEPER_LOG] Found %d items in %s list response.", len(rl), resourceName) + // Keep count of items that aren't sweepable for logging. + nonPrefixCount := 0 + for _, ri := range rl { + obj := ri.(map[string]interface{}) + if obj["name"] == nil { + log.Printf("[INFO][SWEEPER_LOG] %s resource name was nil", resourceName) + return nil + } + + name := tpgresource.GetResourceNameFromSelfLink(obj["name"].(string)) + // Skip resources that shouldn't be sweeped + if !sweeper.IsSweepableTestResource(name) { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://integrations.googleapis.com/v1/projects/{{project}}/locations/{{location}}/clients:deprovision" + deleteUrl, err := tpgresource.ReplaceVars(d, config, deleteTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing delete url: %s", err) + return nil + } + deleteUrl = deleteUrl + name + + // Don't wait on operations as we may have a lot to delete + _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "DELETE", + Project: config.Project, + RawURL: deleteUrl, + UserAgent: config.UserAgent, + }) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error deleting for url %s : %s", deleteUrl, err) + } else { + log.Printf("[INFO][SWEEPER_LOG] Sent delete request for %s resource: %s", resourceName, name) + } + } + + if nonPrefixCount > 0 { + log.Printf("[INFO][SWEEPER_LOG] %d items were non-sweepable and skipped.", nonPrefixCount) + } + + return nil +} diff --git a/website/docs/d/apphub_application.html.markdown b/website/docs/d/apphub_application.html.markdown new file mode 100644 index 0000000000..3fd866b7f0 --- /dev/null +++ b/website/docs/d/apphub_application.html.markdown @@ -0,0 +1,26 @@ +--- +subcategory: "App Hub" +description: |- + Application is a functional grouping of Services and Workloads that helps achieve a desired end-to-end business functionality. +--- + +# google\_apphub\_application + +Application is a functional grouping of Services and Workloads that helps achieve a desired end-to-end business functionality. Services and Workloads are owned by the Application. + + +## Example Usage + + +```hcl +data "google_apphub_application" "application" { + project = "project-id" + application_id = "application" + location = "location" +} +``` + +## Argument Reference + +See [google_resource_application](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/apphub_application#argument-reference) resource for details of the available attributes. +