Skip to content

Commit ef618ef

Browse files
Added tag binding resource (#4638) (#3121)
* Added tag binding resource * Corrected double-quotes at EOL in tag value definitions * Marked project as using google-beta provider * Split tag mutexes again due to not actually being able to share a parent * Set up custom flatten for tag binding name * Use randString suffixes for tag-related shortnames since there was a conflict * Fixed reading of tagbinding instances * Added explicit delete url * Switched to nested_query with idempotent name flattening Signed-off-by: Modular Magician <[email protected]>
1 parent cbc0e8d commit ef618ef

12 files changed

+683
-18
lines changed

.changelog/4638.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:new-resource
2+
`google_tags_tag_binding` (beta)
3+
```

google-beta/provider.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -843,9 +843,9 @@ func Provider() *schema.Provider {
843843
return provider
844844
}
845845

846-
// Generated resources: 219
846+
// Generated resources: 220
847847
// Generated IAM resources: 111
848-
// Total generated resources: 330
848+
// Total generated resources: 331
849849
func ResourceMap() map[string]*schema.Resource {
850850
resourceMap, _ := ResourceMapWithErrors()
851851
return resourceMap
@@ -1181,6 +1181,7 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) {
11811181
"google_storage_hmac_key": resourceStorageHmacKey(),
11821182
"google_tags_tag_key": resourceTagsTagKey(),
11831183
"google_tags_tag_value": resourceTagsTagValue(),
1184+
"google_tags_tag_binding": resourceTagsTagBinding(),
11841185
"google_tpu_node": resourceTPUNode(),
11851186
"google_vpc_access_connector": resourceVPCAccessConnector(),
11861187
"google_workflows_workflow": resourceWorkflowsWorkflow(),

google-beta/resource_dataflow_flex_template_job_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88

99
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
1010
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
11-
"google.golang.org/api/compute/v1"
11+
compute "google.golang.org/api/compute/v1"
1212
)
1313

1414
func TestAccDataflowFlexTemplateJob_basic(t *testing.T) {
Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
// ----------------------------------------------------------------------------
2+
//
3+
// *** AUTO GENERATED CODE *** AUTO GENERATED CODE ***
4+
//
5+
// ----------------------------------------------------------------------------
6+
//
7+
// This file is automatically generated by Magic Modules and manual
8+
// changes will be clobbered when the file is regenerated.
9+
//
10+
// Please read more about how to change this file in
11+
// .github/CONTRIBUTING.md.
12+
//
13+
// ----------------------------------------------------------------------------
14+
15+
package google
16+
17+
import (
18+
"fmt"
19+
"log"
20+
"reflect"
21+
"strings"
22+
"time"
23+
24+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
25+
)
26+
27+
func resourceTagsTagBinding() *schema.Resource {
28+
return &schema.Resource{
29+
Create: resourceTagsTagBindingCreate,
30+
Read: resourceTagsTagBindingRead,
31+
Delete: resourceTagsTagBindingDelete,
32+
33+
Importer: &schema.ResourceImporter{
34+
State: resourceTagsTagBindingImport,
35+
},
36+
37+
Timeouts: &schema.ResourceTimeout{
38+
Create: schema.DefaultTimeout(4 * time.Minute),
39+
Delete: schema.DefaultTimeout(4 * time.Minute),
40+
},
41+
42+
Schema: map[string]*schema.Schema{
43+
"parent": {
44+
Type: schema.TypeString,
45+
Required: true,
46+
ForceNew: true,
47+
Description: `The full resource name of the resource the TagValue is bound to. E.g. //cloudresourcemanager.googleapis.com/projects/123`,
48+
},
49+
"tag_value": {
50+
Type: schema.TypeString,
51+
Required: true,
52+
ForceNew: true,
53+
Description: `The TagValue of the TagBinding. Must be of the form tagValues/456.`,
54+
},
55+
"name": {
56+
Type: schema.TypeString,
57+
Computed: true,
58+
Description: `The generated id for the TagBinding. This is a string of the form: 'tagBindings/{full-resource-name}/{tag-value-name}'`,
59+
},
60+
},
61+
UseJSONNumber: true,
62+
}
63+
}
64+
65+
func resourceTagsTagBindingCreate(d *schema.ResourceData, meta interface{}) error {
66+
config := meta.(*Config)
67+
userAgent, err := generateUserAgentString(d, config.userAgent)
68+
if err != nil {
69+
return err
70+
}
71+
72+
obj := make(map[string]interface{})
73+
parentProp, err := expandNestedTagsTagBindingParent(d.Get("parent"), d, config)
74+
if err != nil {
75+
return err
76+
} else if v, ok := d.GetOkExists("parent"); !isEmptyValue(reflect.ValueOf(parentProp)) && (ok || !reflect.DeepEqual(v, parentProp)) {
77+
obj["parent"] = parentProp
78+
}
79+
tagValueProp, err := expandNestedTagsTagBindingTagValue(d.Get("tag_value"), d, config)
80+
if err != nil {
81+
return err
82+
} else if v, ok := d.GetOkExists("tag_value"); !isEmptyValue(reflect.ValueOf(tagValueProp)) && (ok || !reflect.DeepEqual(v, tagValueProp)) {
83+
obj["tagValue"] = tagValueProp
84+
}
85+
86+
url, err := replaceVars(d, config, "{{TagsBasePath}}tagBindings")
87+
if err != nil {
88+
return err
89+
}
90+
91+
log.Printf("[DEBUG] Creating new TagBinding: %#v", obj)
92+
billingProject := ""
93+
94+
// err == nil indicates that the billing_project value was found
95+
if bp, err := getBillingProject(d, config); err == nil {
96+
billingProject = bp
97+
}
98+
99+
res, err := sendRequestWithTimeout(config, "POST", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutCreate))
100+
if err != nil {
101+
return fmt.Errorf("Error creating TagBinding: %s", err)
102+
}
103+
104+
// Store the ID now
105+
id, err := replaceVars(d, config, "tagBindings/{{name}}")
106+
if err != nil {
107+
return fmt.Errorf("Error constructing id: %s", err)
108+
}
109+
d.SetId(id)
110+
111+
// Use the resource in the operation response to populate
112+
// identity fields and d.Id() before read
113+
var opRes map[string]interface{}
114+
err = tagsOperationWaitTimeWithResponse(
115+
config, res, &opRes, "Creating TagBinding", userAgent,
116+
d.Timeout(schema.TimeoutCreate))
117+
if err != nil {
118+
// The resource didn't actually create
119+
d.SetId("")
120+
return fmt.Errorf("Error waiting to create TagBinding: %s", err)
121+
}
122+
123+
if _, ok := opRes["tagBindings"]; ok {
124+
opRes, err = flattenNestedTagsTagBinding(d, meta, opRes)
125+
if err != nil {
126+
return fmt.Errorf("Error getting nested object from operation response: %s", err)
127+
}
128+
if opRes == nil {
129+
// Object isn't there any more - remove it from the state.
130+
return fmt.Errorf("Error decoding response from operation, could not find nested object")
131+
}
132+
}
133+
if err := d.Set("name", flattenNestedTagsTagBindingName(opRes["name"], d, config)); err != nil {
134+
return err
135+
}
136+
137+
// This may have caused the ID to update - update it if so.
138+
id, err = replaceVars(d, config, "tagBindings/{{name}}")
139+
if err != nil {
140+
return fmt.Errorf("Error constructing id: %s", err)
141+
}
142+
d.SetId(id)
143+
144+
log.Printf("[DEBUG] Finished creating TagBinding %q: %#v", d.Id(), res)
145+
146+
return resourceTagsTagBindingRead(d, meta)
147+
}
148+
149+
func resourceTagsTagBindingRead(d *schema.ResourceData, meta interface{}) error {
150+
config := meta.(*Config)
151+
userAgent, err := generateUserAgentString(d, config.userAgent)
152+
if err != nil {
153+
return err
154+
}
155+
156+
url, err := replaceVars(d, config, "{{TagsBasePath}}tagBindings/?parent={{parent}}&pageSize=300")
157+
if err != nil {
158+
return err
159+
}
160+
161+
billingProject := ""
162+
163+
// err == nil indicates that the billing_project value was found
164+
if bp, err := getBillingProject(d, config); err == nil {
165+
billingProject = bp
166+
}
167+
168+
res, err := sendRequest(config, "GET", billingProject, url, userAgent, nil)
169+
if err != nil {
170+
return handleNotFoundError(err, d, fmt.Sprintf("TagsTagBinding %q", d.Id()))
171+
}
172+
173+
res, err = flattenNestedTagsTagBinding(d, meta, res)
174+
if err != nil {
175+
return err
176+
}
177+
178+
if res == nil {
179+
// Object isn't there any more - remove it from the state.
180+
log.Printf("[DEBUG] Removing TagsTagBinding because it couldn't be matched.")
181+
d.SetId("")
182+
return nil
183+
}
184+
185+
if err := d.Set("name", flattenNestedTagsTagBindingName(res["name"], d, config)); err != nil {
186+
return fmt.Errorf("Error reading TagBinding: %s", err)
187+
}
188+
if err := d.Set("parent", flattenNestedTagsTagBindingParent(res["parent"], d, config)); err != nil {
189+
return fmt.Errorf("Error reading TagBinding: %s", err)
190+
}
191+
if err := d.Set("tag_value", flattenNestedTagsTagBindingTagValue(res["tagValue"], d, config)); err != nil {
192+
return fmt.Errorf("Error reading TagBinding: %s", err)
193+
}
194+
195+
return nil
196+
}
197+
198+
func resourceTagsTagBindingDelete(d *schema.ResourceData, meta interface{}) error {
199+
config := meta.(*Config)
200+
userAgent, err := generateUserAgentString(d, config.userAgent)
201+
if err != nil {
202+
return err
203+
}
204+
205+
billingProject := ""
206+
207+
url, err := replaceVars(d, config, "{{TagsBasePath}}tagBindings/{{name}}")
208+
if err != nil {
209+
return err
210+
}
211+
212+
var obj map[string]interface{}
213+
log.Printf("[DEBUG] Deleting TagBinding %q", d.Id())
214+
215+
// err == nil indicates that the billing_project value was found
216+
if bp, err := getBillingProject(d, config); err == nil {
217+
billingProject = bp
218+
}
219+
220+
res, err := sendRequestWithTimeout(config, "DELETE", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutDelete))
221+
if err != nil {
222+
return handleNotFoundError(err, d, "TagBinding")
223+
}
224+
225+
err = tagsOperationWaitTime(
226+
config, res, "Deleting TagBinding", userAgent,
227+
d.Timeout(schema.TimeoutDelete))
228+
229+
if err != nil {
230+
return err
231+
}
232+
233+
log.Printf("[DEBUG] Finished deleting TagBinding %q: %#v", d.Id(), res)
234+
return nil
235+
}
236+
237+
func resourceTagsTagBindingImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
238+
config := meta.(*Config)
239+
240+
// current import_formats can't import fields with forward slashes in their value
241+
if err := parseImportId([]string{
242+
"tagBindings/(?P<name>.+)",
243+
"(?P<name>.+)",
244+
}, d, config); err != nil {
245+
return nil, err
246+
}
247+
248+
name := d.Get("name").(string)
249+
d.SetId(name)
250+
251+
return []*schema.ResourceData{d}, nil
252+
}
253+
254+
func flattenNestedTagsTagBindingName(v interface{}, d *schema.ResourceData, config *Config) interface{} {
255+
if v == nil {
256+
return v
257+
}
258+
parts := strings.Split(v.(string), "/")
259+
return strings.Join(parts[len(parts)-3:], "/")
260+
}
261+
262+
func flattenNestedTagsTagBindingParent(v interface{}, d *schema.ResourceData, config *Config) interface{} {
263+
return v
264+
}
265+
266+
func flattenNestedTagsTagBindingTagValue(v interface{}, d *schema.ResourceData, config *Config) interface{} {
267+
return v
268+
}
269+
270+
func expandNestedTagsTagBindingParent(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
271+
return v, nil
272+
}
273+
274+
func expandNestedTagsTagBindingTagValue(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
275+
return v, nil
276+
}
277+
278+
func flattenNestedTagsTagBinding(d *schema.ResourceData, meta interface{}, res map[string]interface{}) (map[string]interface{}, error) {
279+
var v interface{}
280+
var ok bool
281+
282+
v, ok = res["tagBindings"]
283+
if !ok || v == nil {
284+
return nil, nil
285+
}
286+
287+
switch v.(type) {
288+
case []interface{}:
289+
break
290+
case map[string]interface{}:
291+
// Construct list out of single nested resource
292+
v = []interface{}{v}
293+
default:
294+
return nil, fmt.Errorf("expected list or map for value tagBindings. Actual value: %v", v)
295+
}
296+
297+
_, item, err := resourceTagsTagBindingFindNestedObjectInList(d, meta, v.([]interface{}))
298+
if err != nil {
299+
return nil, err
300+
}
301+
return item, nil
302+
}
303+
304+
func resourceTagsTagBindingFindNestedObjectInList(d *schema.ResourceData, meta interface{}, items []interface{}) (index int, item map[string]interface{}, err error) {
305+
expectedName := d.Get("name")
306+
expectedFlattenedName := flattenNestedTagsTagBindingName(expectedName, d, meta.(*Config))
307+
308+
// Search list for this resource.
309+
for idx, itemRaw := range items {
310+
if itemRaw == nil {
311+
continue
312+
}
313+
item := itemRaw.(map[string]interface{})
314+
315+
itemName := flattenNestedTagsTagBindingName(item["name"], d, meta.(*Config))
316+
// isEmptyValue check so that if one is nil and the other is "", that's considered a match
317+
if !(isEmptyValue(reflect.ValueOf(itemName)) && isEmptyValue(reflect.ValueOf(expectedFlattenedName))) && !reflect.DeepEqual(itemName, expectedFlattenedName) {
318+
log.Printf("[DEBUG] Skipping item with name= %#v, looking for %#v)", itemName, expectedFlattenedName)
319+
continue
320+
}
321+
log.Printf("[DEBUG] Found item for resource %q: %#v)", d.Id(), item)
322+
return idx, item, nil
323+
}
324+
return -1, nil, nil
325+
}

0 commit comments

Comments
 (0)