diff --git a/mmv1/third_party/terraform/tests/resource_cloudbuild_worker_pool_test.go.erb b/mmv1/third_party/terraform/tests/resource_cloudbuild_worker_pool_test.go.erb index 8dc25191686e..acfef3e1978b 100644 --- a/mmv1/third_party/terraform/tests/resource_cloudbuild_worker_pool_test.go.erb +++ b/mmv1/third_party/terraform/tests/resource_cloudbuild_worker_pool_test.go.erb @@ -41,6 +41,7 @@ func TestAccCloudbuildWorkerPool_basic(t *testing.T) { { ImportState: true, ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"annotations"}, ResourceName: "google_cloudbuild_worker_pool.pool", }, { @@ -79,6 +80,11 @@ resource "google_cloudbuild_worker_pool" "pool" { machine_type = "e2-standard-4" no_external_ip = false } + + annotations = { + env = "foo" + default_expiration_ms = 3600000 + } } `, context) } diff --git a/mmv1/third_party/terraform/tests/resource_dataproc_workflow_template_test.go.erb b/mmv1/third_party/terraform/tests/resource_dataproc_workflow_template_test.go.erb index a277266a01a4..495e7d8e8f73 100644 --- a/mmv1/third_party/terraform/tests/resource_dataproc_workflow_template_test.go.erb +++ b/mmv1/third_party/terraform/tests/resource_dataproc_workflow_template_test.go.erb @@ -37,6 +37,10 @@ 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. + // So add the "labels" to the ImportStateVerifyIgnore list. + ImportStateVerifyIgnore: []string{"labels"}, ResourceName: "google_dataproc_workflow_template.template", }, }, @@ -124,6 +128,11 @@ resource "google_dataproc_workflow_template" "template" { query_file_uri = "someuri" } } + + labels = { + env = "foo" + somekey = "somevalue" + } } `, context) } diff --git a/tpgtools/overrides/dataplex/samples/asset/basic_asset.tf.tmpl b/tpgtools/overrides/dataplex/samples/asset/basic_asset.tf.tmpl index 07c9141bb937..d3f18d1990dd 100644 --- a/tpgtools/overrides/dataplex/samples/asset/basic_asset.tf.tmpl +++ b/tpgtools/overrides/dataplex/samples/asset/basic_asset.tf.tmpl @@ -52,6 +52,12 @@ resource "google_dataplex_asset" "primary" { name = "projects/{{project}}/buckets/{{bucket}}" type = "STORAGE_BUCKET" } + + labels = { + env = "foo" + my-asset = "exists" + } + project = "{{project}}" depends_on = [ diff --git a/tpgtools/property.go b/tpgtools/property.go index 0ee26f2af299..0292c3124857 100644 --- a/tpgtools/property.go +++ b/tpgtools/property.go @@ -311,6 +311,10 @@ func (p Property) DefaultStateSetter() string { case SchemaTypeFloat: fallthrough case SchemaTypeMap: + if p.IsResourceLabels() || p.IsResourceAnnotations() { + return fmt.Sprintf("d.Set(%q, flatten%s%s(res.%s, d))", p.Name(), p.resource.PathType(), p.PackagePath(), p.PackageName) + } + return fmt.Sprintf("d.Set(%q, res.%s)", p.Name(), p.PackageName) case SchemaTypeList, SchemaTypeSet: if p.typ.Items != nil && ((p.typ.Items.Type == "string" && len(p.typ.Items.Enum) == 0) || p.typ.Items.Type == "integer") { @@ -440,6 +444,14 @@ func (p Property) Objects() (props []Property) { return props } +func (p Property) IsResourceLabels() bool { + return p.Name() == "labels" && p.parent == nil +} + +func (p Property) IsResourceAnnotations() bool { + return p.Name() == "annotations" && p.parent == nil +} + // collapsedProperties returns the input list of properties with nested objects collapsed if needed. func collapsedProperties(props []Property) (collapsed []Property) { for _, v := range props { @@ -878,6 +890,16 @@ func createPropertiesFromSchema(schema *openapi.Schema, typeFetcher *TypeFetcher resource.ReusedTypes = resource.RegisterReusedType(p) } + // Add the "effective_labels" property when the current property is top level "labels" or + // add the "effective_annotations" property when the current property is top level "annotations" + + if p.IsResourceLabels() || p.IsResourceAnnotations() { + 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) + props = append(props, build_effective_labels_field(p, resource, parent)) + } + props = append(props, p) } @@ -901,3 +923,20 @@ func createPropertiesFromSchema(schema *openapi.Schema, typeFetcher *TypeFetcher return props, nil } + +func build_effective_labels_field(p Property, resource *Resource, parent *Property) Property { + title := fmt.Sprintf("effective_%s", p.title) + 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{ + title: title, + Type: p.Type, + Description: description, + resource: resource, + parent: parent, + Optional: false, + Computed: true, + StateSetter: &stateSetter, + } +} diff --git a/tpgtools/resource.go b/tpgtools/resource.go index b65c2061f9f9..1e6f6e7a1498 100644 --- a/tpgtools/resource.go +++ b/tpgtools/resource.go @@ -381,6 +381,26 @@ func (r Resource) IDFunction() string { return "tpgresource.ReplaceVarsForId" } +// Check if the resource has the lables field for the resource +func (r Resource) HasLabels() bool { + for _, p := range r.Properties { + if p.IsResourceLabels() { + return true + } + } + return false +} + +// Check if the resource has the annotations field for the resource +func (r Resource) HasAnnotations() bool { + for _, p := range r.Properties { + if p.IsResourceAnnotations() { + return true + } + } + return false +} + // ResourceInput is a Resource along with additional generation metadata. type ResourceInput struct { Resource @@ -810,6 +830,18 @@ func (r *Resource) loadHandWrittenSamples() []Sample { sample.Name = &sampleName } sample.TestSlug = RenderedString(snakeToTitleCase(miscellaneousNameSnakeCase(sampleName)).titlecase() + "HandWritten") + + // 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. + // So add the "labels" and the "annotations" fields to the ImportStateVerifyIgnore list. + if r.HasLabels() { + sample.IgnoreRead = append(sample.IgnoreRead, "labels") + } + + if r.HasAnnotations() { + sample.IgnoreRead = append(sample.IgnoreRead, "annotations") + } + samples = append(samples, sample) } @@ -896,6 +928,18 @@ func (r *Resource) loadDCLSamples() []Sample { } sample.DependencyList = dependencies 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. + // So add the "labels" and the "annotations" fields to the ImportStateVerifyIgnore list. + if r.HasLabels() { + sample.IgnoreRead = append(sample.IgnoreRead, "labels") + } + + if r.HasAnnotations() { + sample.IgnoreRead = append(sample.IgnoreRead, "annotations") + } + samples = append(samples, sample) } diff --git a/tpgtools/templates/resource.go.tmpl b/tpgtools/templates/resource.go.tmpl index 532dd4c4c75b..a3f10f2db10b 100644 --- a/tpgtools/templates/resource.go.tmpl +++ b/tpgtools/templates/resource.go.tmpl @@ -714,6 +714,40 @@ func flatten{{$.PathType}}{{$v.PackagePath}}(obj *{{$.Package}}.{{$v.ObjectType} } {{ end -}} +{{ if $.HasLabels }} +func flatten{{$.PathType}}Labels(v map[string]string, d *schema.ResourceData) interface{} { + if v == nil { + return nil + } + + transformed := make(map[string]interface{}) + if l, ok := d.Get("labels").(map[string]interface{}); ok { + for k, _ := range l { + transformed[k] = l[k] + } + } + + return transformed +} +{{ end }} + +{{ if $.HasAnnotations }} +func flatten{{$.PathType}}Annotations(v map[string]string, d *schema.ResourceData) interface{} { + if v == nil { + return nil + } + + transformed := make(map[string]interface{}) + if l, ok := d.Get("annotations").(map[string]interface{}); ok { + for k, _ := range l { + transformed[k] = l[k] + } + } + + return transformed +} +{{ end }} + {{ range $v := .EnumArrays -}} func flatten{{$.PathType}}{{$v.PackagePath}}Array(obj []{{$.Package}}.{{$v.ObjectType}}Enum) interface{} { if obj == nil {