Skip to content

Commit 95a6d29

Browse files
Adding Support to update DataCatalog TagTemplate fields (#6803) (#4968)
fixes hashicorp/terraform-provider-google#6574 Signed-off-by: Modular Magician <[email protected]> Signed-off-by: Modular Magician <[email protected]>
1 parent 951570a commit 95a6d29

File tree

4 files changed

+367
-12
lines changed

4 files changed

+367
-12
lines changed

.changelog/6803.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
datacatalog: added update support for `fields` in `google_data_catalog_tag_template`
3+
```

google-beta/resource_data_catalog_tag_template.go

Lines changed: 191 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,50 @@ import (
2525
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
2626
)
2727

28+
//Use it to delete TagTemplate Field
29+
func deleteTagTemplateField(d *schema.ResourceData, config *Config, name, billingProject, userAgent string) error {
30+
31+
url_delete, err := replaceVars(d, config, "{{DataCatalogBasePath}}{{name}}/fields/"+name+"?force={{force_delete}}")
32+
if err != nil {
33+
return err
34+
}
35+
var obj map[string]interface{}
36+
res, err := sendRequestWithTimeout(config, "DELETE", billingProject, url_delete, userAgent, obj, d.Timeout(schema.TimeoutDelete))
37+
if err != nil {
38+
return fmt.Errorf("Error deleting TagTemplate Field %v: %s", name, err)
39+
}
40+
41+
log.Printf("[DEBUG] Finished deleting TagTemplate Field %q: %#v", name, res)
42+
return nil
43+
}
44+
45+
//Use it to create TagTemplate Field
46+
func createTagTemplateField(d *schema.ResourceData, config *Config, body map[string]interface{}, name, billingProject, userAgent string) error {
47+
48+
url_create, err := replaceVars(d, config, "{{DataCatalogBasePath}}{{name}}/fields")
49+
if err != nil {
50+
return err
51+
}
52+
53+
url_create, err = addQueryParams(url_create, map[string]string{"tagTemplateFieldId": name})
54+
if err != nil {
55+
return err
56+
}
57+
58+
res_create, err := sendRequestWithTimeout(config, "POST", billingProject, url_create, userAgent, body, d.Timeout(schema.TimeoutCreate))
59+
if err != nil {
60+
return fmt.Errorf("Error creating TagTemplate Field: %s", err)
61+
}
62+
63+
if err != nil {
64+
return fmt.Errorf("Error creating TagTemplate Field %v: %s", name, err)
65+
} else {
66+
log.Printf("[DEBUG] Finished creating TagTemplate Field %v: %#v", name, res_create)
67+
}
68+
69+
return nil
70+
}
71+
2872
func resourceDataCatalogTagTemplate() *schema.Resource {
2973
return &schema.Resource{
3074
Create: resourceDataCatalogTagTemplateCreate,
@@ -46,14 +90,12 @@ func resourceDataCatalogTagTemplate() *schema.Resource {
4690
"fields": {
4791
Type: schema.TypeSet,
4892
Required: true,
49-
ForceNew: true,
50-
Description: `Set of tag template field IDs and the settings for the field. This set is an exhaustive list of the allowed fields. This set must contain at least one field and at most 500 fields.`,
93+
Description: `Set of tag template field IDs and the settings for the field. This set is an exhaustive list of the allowed fields. This set must contain at least one field and at most 500 fields. The change of field_id will be resulting in re-creating of field. The change of primitive_type will be resulting in re-creating of field, however if the field is a required, you cannot update it.`,
5194
Elem: &schema.Resource{
5295
Schema: map[string]*schema.Schema{
5396
"field_id": {
5497
Type: schema.TypeString,
5598
Required: true,
56-
ForceNew: true,
5799
},
58100
"type": {
59101
Type: schema.TypeList,
@@ -86,6 +128,7 @@ Can have up to 500 allowed values.`,
86128
},
87129
"primitive_type": {
88130
Type: schema.TypeString,
131+
Computed: true,
89132
Optional: true,
90133
ValidateFunc: validateEnum([]string{"DOUBLE", "STRING", "BOOL", "TIMESTAMP", ""}),
91134
Description: `Represents primitive types - string, bool etc.
@@ -96,21 +139,25 @@ Can have up to 500 allowed values.`,
96139
},
97140
"description": {
98141
Type: schema.TypeString,
142+
Computed: true,
99143
Optional: true,
100144
Description: `A description for this field.`,
101145
},
102146
"display_name": {
103147
Type: schema.TypeString,
148+
Computed: true,
104149
Optional: true,
105150
Description: `The display name for this field.`,
106151
},
107152
"is_required": {
108153
Type: schema.TypeBool,
154+
Computed: true,
109155
Optional: true,
110156
Description: `Whether this is a required field. Defaults to false.`,
111157
},
112158
"order": {
113159
Type: schema.TypeInt,
160+
Computed: true,
114161
Optional: true,
115162
Description: `The order of this field with respect to other fields in this tag template.
116163
A higher value indicates a more important field. The value can be negative.
@@ -314,6 +361,12 @@ func resourceDataCatalogTagTemplateUpdate(d *schema.ResourceData, meta interface
314361
} else if v, ok := d.GetOkExists("display_name"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, displayNameProp)) {
315362
obj["displayName"] = displayNameProp
316363
}
364+
fieldsProp, err := expandDataCatalogTagTemplateFields(d.Get("fields"), d, config)
365+
if err != nil {
366+
return err
367+
} else if v, ok := d.GetOkExists("fields"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, fieldsProp)) {
368+
obj["fields"] = fieldsProp
369+
}
317370

318371
url, err := replaceVars(d, config, "{{DataCatalogBasePath}}{{name}}")
319372
if err != nil {
@@ -326,26 +379,153 @@ func resourceDataCatalogTagTemplateUpdate(d *schema.ResourceData, meta interface
326379
if d.HasChange("display_name") {
327380
updateMask = append(updateMask, "displayName")
328381
}
382+
329383
// updateMask is a URL parameter but not present in the schema, so replaceVars
330384
// won't set it
331385
url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")})
332386
if err != nil {
333387
return err
334388
}
335389

336-
// err == nil indicates that the billing_project value was found
337-
if bp, err := getBillingProject(d, config); err == nil {
338-
billingProject = bp
390+
if len(updateMask) > 0 {
391+
392+
// err == nil indicates that the billing_project value was found
393+
if bp, err := getBillingProject(d, config); err == nil {
394+
billingProject = bp
395+
}
396+
397+
res, err := sendRequestWithTimeout(config, "PATCH", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutUpdate))
398+
399+
if err != nil {
400+
return fmt.Errorf("Error updating TagTemplate %q: %s", d.Id(), err)
401+
} else {
402+
log.Printf("[DEBUG] Finished updating TagTemplate %q: %#v", d.Id(), res)
403+
}
404+
339405
}
340406

341-
res, err := sendRequestWithTimeout(config, "PATCH", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutUpdate))
407+
// since fields have a separate endpoint,
408+
// we need to handle it manually
342409

343-
if err != nil {
344-
return fmt.Errorf("Error updating TagTemplate %q: %s", d.Id(), err)
345-
} else {
346-
log.Printf("[DEBUG] Finished updating TagTemplate %q: %#v", d.Id(), res)
410+
type FieldChange struct {
411+
Old, New map[string]interface{}
347412
}
348413

414+
o, n := d.GetChange("fields")
415+
vals := make(map[string]*FieldChange)
416+
417+
// this will create a dictionary with the value
418+
// of field_id as the key that will contain the
419+
// maps of old and new values
420+
for _, raw := range o.(*schema.Set).List() {
421+
obj := raw.(map[string]interface{})
422+
k := obj["field_id"].(string)
423+
vals[k] = &FieldChange{Old: obj}
424+
}
425+
426+
for _, raw := range n.(*schema.Set).List() {
427+
obj := raw.(map[string]interface{})
428+
k := obj["field_id"].(string)
429+
if _, ok := vals[k]; !ok {
430+
// if key is not present in the vals,
431+
// then create an empty object to hold the new value
432+
vals[k] = &FieldChange{}
433+
}
434+
vals[k].New = obj
435+
}
436+
437+
// fields schema to create schema.set below
438+
dataCatalogTagTemplateFieldsSchema := &schema.Resource{
439+
Schema: resourceDataCatalogTagTemplate().Schema["fields"].Elem.(*schema.Resource).Schema,
440+
}
441+
442+
for name, change := range vals {
443+
// A few different situations to deal with in here:
444+
// - change.Old is nil: create a new role
445+
// - change.New is nil: remove an existing role
446+
// - both are set: test if New is different than Old and update if so
447+
448+
changeOldSet := schema.NewSet(schema.HashResource(dataCatalogTagTemplateFieldsSchema), []interface{}{})
449+
changeOldSet.Add(change.Old)
450+
var changeOldProp map[string]interface{}
451+
if len(change.Old) != 0 {
452+
changeOldProp, _ = expandDataCatalogTagTemplateFields(changeOldSet, nil, nil)
453+
changeOldProp = changeOldProp[name].(map[string]interface{})
454+
}
455+
456+
changeNewSet := schema.NewSet(schema.HashResource(dataCatalogTagTemplateFieldsSchema), []interface{}{})
457+
changeNewSet.Add(change.New)
458+
var changeNewProp map[string]interface{}
459+
if len(change.New) != 0 {
460+
changeNewProp, _ = expandDataCatalogTagTemplateFields(changeNewSet, nil, nil)
461+
changeNewProp = changeNewProp[name].(map[string]interface{})
462+
}
463+
464+
// if old state is empty, then we have a new field to create
465+
if len(change.Old) == 0 {
466+
err := createTagTemplateField(d, config, changeNewProp, name, billingProject, userAgent)
467+
if err != nil {
468+
return err
469+
}
470+
471+
continue
472+
}
473+
474+
// if new state is empty, then we need to delete the current field
475+
if len(change.New) == 0 {
476+
err := deleteTagTemplateField(d, config, name, billingProject, userAgent)
477+
if err != nil {
478+
return err
479+
}
480+
481+
continue
482+
}
483+
484+
// if we have old and new values, but are not equal, update with the new state
485+
if !reflect.DeepEqual(changeOldProp, changeNewProp) {
486+
url1, err := replaceVars(d, config, "{{DataCatalogBasePath}}{{name}}/fields/"+name)
487+
if err != nil {
488+
return err
489+
}
490+
491+
oldType := changeOldProp["type"].(map[string]interface{})
492+
newType := changeNewProp["type"].(map[string]interface{})
493+
494+
if oldType["primitiveType"] != newType["primitiveType"] {
495+
// As primitiveType can't be changed, it is considered as ForceNew which triggers the deletion of old field and recreation of a new field
496+
// Before that, we need to check that is_required is True for the newType or not, as we don't have support to add new required field in the existing TagTemplate,
497+
// So in such cases, we can simply return the error
498+
499+
// Reason for checking the isRequired in changeNewProp -
500+
// Because this changeNewProp check should be ignored when the user wants to update the primitive type and make it optional rather than keeping it required.
501+
if changeNewProp["isRequired"] != nil && changeNewProp["isRequired"].(bool) {
502+
return fmt.Errorf("Updating the primitive type for a required field on an existing tag template is not supported as TagTemplateField %q is required", name)
503+
}
504+
505+
// delete changeOldProp
506+
err_delete := deleteTagTemplateField(d, config, name, billingProject, userAgent)
507+
if err_delete != nil {
508+
return err_delete
509+
}
510+
511+
// recreate changeNewProp
512+
err_create := createTagTemplateField(d, config, changeNewProp, name, billingProject, userAgent)
513+
if err_create != nil {
514+
return err_create
515+
}
516+
517+
log.Printf("[DEBUG] Finished updating TagTemplate Field %q", name)
518+
return resourceDataCatalogTagTemplateRead(d, meta)
519+
}
520+
521+
res, err := sendRequestWithTimeout(config, "PATCH", billingProject, url1, userAgent, changeNewProp, d.Timeout(schema.TimeoutDelete))
522+
if err != nil {
523+
return fmt.Errorf("Error updating TagTemplate Field %v: %s", name, err)
524+
}
525+
526+
log.Printf("[DEBUG] Finished updating TagTemplate Field %q: %#v", name, res)
527+
}
528+
}
349529
return resourceDataCatalogTagTemplateRead(d, meta)
350530
}
351531

0 commit comments

Comments
 (0)