From fe905880466a7425fbdbc27bae6ae1185d9cbe3d Mon Sep 17 00:00:00 2001 From: Zhenhua Li Date: Tue, 22 Aug 2023 14:11:40 -0700 Subject: [PATCH] Support provider default labels for DCL resources --- .../resource_clouddeploy_target_test.go | 278 ++++++++++++++++++ ...rce_dataproc_workflow_template_test.go.erb | 4 +- tpgtools/property.go | 38 ++- tpgtools/resource.go | 14 +- tpgtools/templates/resource.go.tmpl | 19 +- tpgtools/templates/serialization.go.tmpl | 2 +- 6 files changed, 346 insertions(+), 9 deletions(-) create mode 100644 mmv1/third_party/terraform/services/clouddeploy/resource_clouddeploy_target_test.go diff --git a/mmv1/third_party/terraform/services/clouddeploy/resource_clouddeploy_target_test.go b/mmv1/third_party/terraform/services/clouddeploy/resource_clouddeploy_target_test.go new file mode 100644 index 000000000000..e8393b2a09ba --- /dev/null +++ b/mmv1/third_party/terraform/services/clouddeploy/resource_clouddeploy_target_test.go @@ -0,0 +1,278 @@ +package clouddeploy_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/envvar" +) + +func TestAccClouddeployTarget_withProviderDefaultLabels(t *testing.T) { + // The test failed if VCR testing is enabled, because the cached provider config is used. + // Any changes in the provider default labels will not be applied. + acctest.SkipIfVcr(t) + t.Parallel() + + context := map[string]interface{}{ + "project_name": envvar.GetTestProjectFromEnv(), + "region": envvar.GetTestRegionFromEnv(), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckClouddeployTargetDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccClouddeployTarget_withProviderDefaultLabels(context), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "labels.%", "2"), + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "labels.my_first_label", "example-label-1"), + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "labels.my_second_label", "example-label-2"), + + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "terraform_labels.%", "3"), + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "terraform_labels.my_first_label", "example-label-1"), + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "terraform_labels.my_second_label", "example-label-2"), + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "terraform_labels.default_key1", "default_value1"), + + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "effective_labels.%", "3"), + ), + }, + { + ResourceName: "google_clouddeploy_target.primary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "terraform_labels", "annotations"}, + }, + { + Config: testAccClouddeployTarget_resourceLabelsOverridesProviderDefaultLabels(context), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "labels.%", "3"), + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "labels.my_first_label", "example-label-1"), + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "labels.my_second_label", "example-label-2"), + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "labels.default_key1", "value1"), + + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "terraform_labels.%", "3"), + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "terraform_labels.my_first_label", "example-label-1"), + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "terraform_labels.my_second_label", "example-label-2"), + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "terraform_labels.default_key1", "value1"), + + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "effective_labels.%", "3"), + ), + }, + { + ResourceName: "google_clouddeploy_target.primary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "terraform_labels", "annotations"}, + }, + { + Config: testAccClouddeployTarget_moveResourceLabelToProviderDefaultLabels(context), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "labels.%", "2"), + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "labels.my_first_label", "example-label-1"), + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "labels.default_key1", "value1"), + + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "terraform_labels.%", "3"), + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "terraform_labels.my_first_label", "example-label-1"), + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "terraform_labels.my_second_label", "example-label-2"), + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "terraform_labels.default_key1", "value1"), + + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "effective_labels.%", "3"), + ), + }, + { + ResourceName: "google_clouddeploy_target.primary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "terraform_labels", "annotations"}, + }, + { + Config: testAccClouddeployTarget_resourceLabelsOverridesProviderDefaultLabels(context), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "labels.%", "3"), + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "labels.my_first_label", "example-label-1"), + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "labels.my_second_label", "example-label-2"), + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "labels.default_key1", "value1"), + + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "terraform_labels.%", "3"), + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "terraform_labels.my_first_label", "example-label-1"), + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "terraform_labels.my_second_label", "example-label-2"), + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "terraform_labels.default_key1", "value1"), + + resource.TestCheckResourceAttr("google_clouddeploy_target.primary", "effective_labels.%", "3"), + ), + }, + { + ResourceName: "google_clouddeploy_target.primary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "terraform_labels", "annotations"}, + }, + { + Config: testAccClouddeployTarget_withoutLabels(context), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckNoResourceAttr("google_clouddeploy_target.primary", "labels.%"), + resource.TestCheckNoResourceAttr("google_clouddeploy_target.primary", "terraform_labels.%"), + resource.TestCheckNoResourceAttr("google_clouddeploy_target.primary", "effective_labels.%"), + ), + }, + { + ResourceName: "google_clouddeploy_target.primary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "terraform_labels", "annotations"}, + }, + }, + }) +} + +func testAccClouddeployTarget_withProviderDefaultLabels(context map[string]interface{}) string { + return acctest.Nprintf(` +provider "google" { + default_labels = { + default_key1 = "default_value1" + } +} + +resource "google_clouddeploy_target" "primary" { + location = "%{region}" + name = "tf-test-target%{random_suffix}" + + deploy_parameters = { + deployParameterKey = "deployParameterValue" + } + + description = "basic description" + + gke { + cluster = "projects/%{project_name}/locations/%{region}/clusters/example-cluster-name" + } + + project = "%{project_name}" + require_approval = false + + annotations = { + my_first_annotation = "example-annotation-1" + + my_second_annotation = "example-annotation-2" + } + + labels = { + my_first_label = "example-label-1" + my_second_label = "example-label-2" + } +} +`, context) +} + +func testAccClouddeployTarget_resourceLabelsOverridesProviderDefaultLabels(context map[string]interface{}) string { + return acctest.Nprintf(` +provider "google" { + default_labels = { + default_key1 = "default_value1" + } +} + +resource "google_clouddeploy_target" "primary" { + location = "%{region}" + name = "tf-test-target%{random_suffix}" + + deploy_parameters = { + deployParameterKey = "deployParameterValue" + } + + description = "basic description" + + gke { + cluster = "projects/%{project_name}/locations/%{region}/clusters/example-cluster-name" + } + + project = "%{project_name}" + require_approval = false + + annotations = { + my_first_annotation = "example-annotation-1" + + my_second_annotation = "example-annotation-2" + } + + labels = { + my_first_label = "example-label-1" + my_second_label = "example-label-2" + default_key1 = "value1" + } +} +`, context) +} + +func testAccClouddeployTarget_moveResourceLabelToProviderDefaultLabels(context map[string]interface{}) string { + return acctest.Nprintf(` +provider "google" { + default_labels = { + default_key1 = "default_value1" + my_second_label = "example-label-2" + } +} + +resource "google_clouddeploy_target" "primary" { + location = "%{region}" + name = "tf-test-target%{random_suffix}" + + deploy_parameters = { + deployParameterKey = "deployParameterValue" + } + + description = "basic description" + + gke { + cluster = "projects/%{project_name}/locations/%{region}/clusters/example-cluster-name" + } + + project = "%{project_name}" + require_approval = false + + annotations = { + my_first_annotation = "example-annotation-1" + + my_second_annotation = "example-annotation-2" + } + + labels = { + my_first_label = "example-label-1" + default_key1 = "value1" + } +} +`, context) +} + +func testAccClouddeployTarget_withoutLabels(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_clouddeploy_target" "primary" { + location = "%{region}" + name = "tf-test-target%{random_suffix}" + + deploy_parameters = { + deployParameterKey = "deployParameterValue" + } + + description = "basic description" + + gke { + cluster = "projects/%{project_name}/locations/%{region}/clusters/example-cluster-name" + } + + project = "%{project_name}" + require_approval = false + + annotations = { + my_first_annotation = "example-annotation-1" + + my_second_annotation = "example-annotation-2" + } +} +`, context) +} diff --git a/mmv1/third_party/terraform/services/dataproc/resource_dataproc_workflow_template_test.go.erb b/mmv1/third_party/terraform/services/dataproc/resource_dataproc_workflow_template_test.go.erb index aa3dc64a6223..21988d0cb673 100644 --- a/mmv1/third_party/terraform/services/dataproc/resource_dataproc_workflow_template_test.go.erb +++ b/mmv1/third_party/terraform/services/dataproc/resource_dataproc_workflow_template_test.go.erb @@ -38,9 +38,9 @@ func TestAccDataprocWorkflowTemplate_basic(t *testing.T) { ImportState: true, ImportStateVerify: true, // The "labels" field in the state are decided by the configuration. - // During importing, as the configuration is unavailableafter, the "labels" field in the state will be empty. + // During importing, as the configuration is unavailable, the "labels" field in the state will be empty. // So add the "labels" to the ImportStateVerifyIgnore list. - ImportStateVerifyIgnore: []string{"labels"}, + ImportStateVerifyIgnore: []string{"labels", "terraform_labels"}, ResourceName: "google_dataproc_workflow_template.template", }, }, diff --git a/tpgtools/property.go b/tpgtools/property.go index 0292c3124857..8b490cb83f4b 100644 --- a/tpgtools/property.go +++ b/tpgtools/property.go @@ -452,6 +452,10 @@ func (p Property) IsResourceAnnotations() bool { return p.Name() == "annotations" && p.parent == nil } +func (p Property) ShouldShowUpInSamples() bool { + return (p.Settable && p.Name() != "effective_labels" && p.Name() != "effective_annotations") || p.IsResourceLabels() || p.IsResourceAnnotations() +} + // collapsedProperties returns the input list of properties with nested objects collapsed if needed. func collapsedProperties(props []Property) (collapsed []Property) { for _, v := range props { @@ -897,7 +901,15 @@ func createPropertiesFromSchema(schema *openapi.Schema, typeFetcher *TypeFetcher note := "**Note**: This field is non-authoritative, and will only manage the labels present in your configuration. " + "Please refer to the field `effective_labels` for all of the labels present on the resource." p.Description = fmt.Sprintf("%s\n\n%s", p.Description, note) + p.Settable = false + p.StateGetter = nil + p.ForceNew = false + props = append(props, build_effective_labels_field(p, resource, parent)) + + if p.IsResourceLabels() { + props = append(props, build_terraform_labels_field(p, resource, parent)) + } } props = append(props, p) @@ -929,7 +941,7 @@ func build_effective_labels_field(p Property, resource *Resource, parent *Proper description := fmt.Sprintf("All of %s (key/value pairs) present on the resource in GCP, including the %s configured through Terraform, other clients and services.", p.title, p.title) stateSetter := fmt.Sprintf("d.Set(%q, res.%s)", title, p.PackageName) - return Property{ + effectiveLabels := Property{ title: title, Type: p.Type, Description: description, @@ -937,6 +949,30 @@ func build_effective_labels_field(p Property, resource *Resource, parent *Proper parent: parent, Optional: false, Computed: true, + ForceNew: p.ForceNew, // Add ForceNew property if labels field has it + PackageName: p.PackageName, + Settable: true, + StateSetter: &stateSetter, + } + + stateGetter := effectiveLabels.DefaultStateGetter() + effectiveLabels.StateGetter = &stateGetter + return effectiveLabels +} + +func build_terraform_labels_field(p Property, resource *Resource, parent *Property) Property { + title := fmt.Sprintf("terraform_%s", p.title) + description := fmt.Sprintf("The combination of %s configured directly on the resource and default %s configured on the provider.", p.title, p.title) + stateSetter := fmt.Sprintf("d.Set(%q, flatten%sTerraform%s(res.%s, d))", title, p.resource.PathType(), p.PackagePath(), p.PackageName) + + return Property{ + title: title, + Type: p.Type, + Description: description, + resource: resource, + parent: parent, + Computed: true, + PackageName: p.PackageName, StateSetter: &stateSetter, } } diff --git a/tpgtools/resource.go b/tpgtools/resource.go index 1e6f6e7a1498..26c94420ef45 100644 --- a/tpgtools/resource.go +++ b/tpgtools/resource.go @@ -586,6 +586,14 @@ func createResource(schema *openapi.Schema, info *openapi.Info, typeFetcher *Typ res.CustomizeDiff = cdiff.Functions } + if res.HasLabels() { + res.CustomizeDiff = append(res.CustomizeDiff, "tpgresource.SetLabelsDiff") + } + + if res.HasAnnotations() { + res.CustomizeDiff = append(res.CustomizeDiff, "tpgresource.SetAnnotationsDiff") + } + // ListFields if parameters, ok := typeFetcher.doc.Paths["list"]; ok { for _, param := range parameters.Parameters { @@ -835,7 +843,7 @@ func (r *Resource) loadHandWrittenSamples() []Sample { // During importing, as the configuration is unavailableafter, the "labels" and "annotations" fields in the state will be empty. // So add the "labels" and the "annotations" fields to the ImportStateVerifyIgnore list. if r.HasLabels() { - sample.IgnoreRead = append(sample.IgnoreRead, "labels") + sample.IgnoreRead = append(sample.IgnoreRead, "labels", "terraform_labels") } if r.HasAnnotations() { @@ -930,10 +938,10 @@ func (r *Resource) loadDCLSamples() []Sample { sample.TestSlug = RenderedString(sampleNameToTitleCase(*sample.Name).titlecase()) // The "labels" and "annotations" fields in the state are decided by the configuration. - // During importing, as the configuration is unavailableafter, the "labels" and "annotations" fields in the state will be empty. + // During importing, as the configuration is unavailable, the "labels" and "annotations" fields in the state will be empty. // So add the "labels" and the "annotations" fields to the ImportStateVerifyIgnore list. if r.HasLabels() { - sample.IgnoreRead = append(sample.IgnoreRead, "labels") + sample.IgnoreRead = append(sample.IgnoreRead, "labels", "terraform_labels") } if r.HasAnnotations() { diff --git a/tpgtools/templates/resource.go.tmpl b/tpgtools/templates/resource.go.tmpl index a3f10f2db10b..db1eeb4d6aea 100644 --- a/tpgtools/templates/resource.go.tmpl +++ b/tpgtools/templates/resource.go.tmpl @@ -723,7 +723,22 @@ func flatten{{$.PathType}}Labels(v map[string]string, d *schema.ResourceData) in transformed := make(map[string]interface{}) if l, ok := d.Get("labels").(map[string]interface{}); ok { for k, _ := range l { - transformed[k] = l[k] + transformed[k] = v[k] + } + } + + return transformed +} + +func flatten{{$.PathType}}TerraformLabels(v map[string]string, d *schema.ResourceData) interface{} { + if v == nil { + return nil + } + + transformed := make(map[string]interface{}) + if l, ok := d.Get("terraform_labels").(map[string]interface{}); ok { + for k, _ := range l { + transformed[k] = v[k] } } @@ -740,7 +755,7 @@ func flatten{{$.PathType}}Annotations(v map[string]string, d *schema.ResourceDat transformed := make(map[string]interface{}) if l, ok := d.Get("annotations").(map[string]interface{}); ok { for k, _ := range l { - transformed[k] = l[k] + transformed[k] = v[k] } } diff --git a/tpgtools/templates/serialization.go.tmpl b/tpgtools/templates/serialization.go.tmpl index 7936e1a0119c..faba0a38cdfc 100644 --- a/tpgtools/templates/serialization.go.tmpl +++ b/tpgtools/templates/serialization.go.tmpl @@ -126,7 +126,7 @@ func ConvertSampleJSONToHCL(product DCLPackageName, resource miscellaneousNameSn func {{ $res.TitleCaseFullName }}{{$version.SerializationSuffix}}AsHCL(r {{$res.Package}}{{$version.SerializationSuffix}}.{{$res.DCLStructName}}, hasGAEquivalent bool) (string, error) { outputConfig := "resource \"{{$res.TerraformName}}\" \"output\" {\n" {{- range $field := $res.Properties}} - {{- if $field.Settable }} + {{- if $field.ShouldShowUpInSamples }} {{- if eq $field.Type.String "TypeString" "TypeInt" "TypeBool" "TypeFloat" }} {{- if $field.Type.IsDateTime }} if !r.{{$field.PackageName}}.IsZero() {