Skip to content

Commit fc9a556

Browse files
authored
Add field-level metadata generation for mmv1 resources (#12792)
1 parent b062674 commit fc9a556

File tree

6 files changed

+453
-0
lines changed

6 files changed

+453
-0
lines changed

docs/content/reference/metadata.md

+8
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,11 @@ The version of the API used for this resource e.g., "v2".
3030
### `api_resource_type_kind`
3131

3232
The API "resource type kind" used for this resource e.g., "Function".
33+
34+
### `fields`
35+
36+
The list of fields used by this resource. Each field can contain the following attributes:
37+
38+
- `field`: The name of the field in Terraform, including the path e.g., "build_config.source.storage_source.bucket"
39+
- `api_field`: The name of the field in the API, including the path e.g., "build_config.source.storage_source.bucket". Defaults to the value of `field`.
40+
- `provider_only`: If true, the field is only present in the provider. This primarily applies for virtual fields and url-only parameters. When set to true, `api_field` should be left empty, as it will be ignored. Default: `false`.

mmv1/api/resource.go

+28
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,12 @@ func (r Resource) UserParameters() []*Type {
500500
})
501501
}
502502

503+
func (r Resource) UserVirtualFields() []*Type {
504+
return google.Reject(r.VirtualFields, func(p *Type) bool {
505+
return p.Exclude
506+
})
507+
}
508+
503509
func (r Resource) ServiceVersion() string {
504510
if r.CaiBaseUrl != "" {
505511
return extractVersionFromBaseUrl(r.CaiBaseUrl)
@@ -615,6 +621,28 @@ func (r Resource) RootProperties() []*Type {
615621
return props
616622
}
617623

624+
// Returns a sorted list of all "leaf" properties, meaning properties that have
625+
// no children.
626+
func (r Resource) LeafProperties() []*Type {
627+
types := r.AllNestedProperties(google.Concat(r.RootProperties(), r.UserVirtualFields()))
628+
629+
// Remove types that have children, because we only want "leaf" fields
630+
types = slices.DeleteFunc(types, func(t *Type) bool {
631+
nestedProperties := t.NestedProperties()
632+
return len(nestedProperties) > 0
633+
})
634+
635+
// Sort types by lineage
636+
slices.SortFunc(types, func(a, b *Type) int {
637+
if a.MetadataLineage() < b.MetadataLineage() {
638+
return -1
639+
}
640+
return 1
641+
})
642+
643+
return types
644+
}
645+
618646
// Return the product-level async object, or the resource-specific one
619647
// if one exists.
620648
func (r Resource) GetAsync() *Async {

mmv1/api/resource_test.go

+117
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,120 @@ func TestResourceServiceVersion(t *testing.T) {
198198
})
199199
}
200200
}
201+
202+
func TestLeafProperties(t *testing.T) {
203+
t.Parallel()
204+
205+
cases := []struct {
206+
description string
207+
obj Resource
208+
expected Type
209+
}{
210+
{
211+
description: "non-nested type",
212+
obj: Resource{
213+
BaseUrl: "test",
214+
Properties: []*Type{
215+
{
216+
Name: "basic",
217+
Type: "String",
218+
},
219+
},
220+
},
221+
expected: Type{
222+
Name: "basic",
223+
},
224+
},
225+
{
226+
description: "nested type",
227+
obj: Resource{
228+
BaseUrl: "test",
229+
Properties: []*Type{
230+
{
231+
Name: "root",
232+
Type: "NestedObject",
233+
Properties: []*Type{
234+
{
235+
Name: "foo",
236+
Type: "NestedObject",
237+
Properties: []*Type{
238+
{
239+
Name: "bars",
240+
Type: "Array",
241+
ItemType: &Type{
242+
Type: "NestedObject",
243+
Properties: []*Type{
244+
{
245+
Name: "fooBar",
246+
Type: "String",
247+
},
248+
},
249+
},
250+
},
251+
},
252+
},
253+
},
254+
},
255+
},
256+
},
257+
expected: Type{
258+
Name: "fooBar",
259+
},
260+
},
261+
{
262+
description: "nested virtual",
263+
obj: Resource{
264+
BaseUrl: "test",
265+
VirtualFields: []*Type{
266+
{
267+
Name: "root",
268+
Type: "NestedObject",
269+
Properties: []*Type{
270+
{
271+
Name: "foo",
272+
Type: "String",
273+
},
274+
},
275+
},
276+
},
277+
},
278+
expected: Type{
279+
Name: "foo",
280+
},
281+
},
282+
{
283+
description: "nested param",
284+
obj: Resource{
285+
BaseUrl: "test",
286+
Parameters: []*Type{
287+
{
288+
Name: "root",
289+
Type: "NestedObject",
290+
Properties: []*Type{
291+
{
292+
Name: "foo",
293+
Type: "String",
294+
},
295+
},
296+
},
297+
},
298+
},
299+
expected: Type{
300+
Name: "foo",
301+
},
302+
},
303+
}
304+
305+
for _, tc := range cases {
306+
tc := tc
307+
308+
t.Run(tc.description, func(t *testing.T) {
309+
t.Parallel()
310+
311+
tc.obj.SetDefault(nil)
312+
if got, want := tc.obj.LeafProperties(), tc.expected; got[0].Name != want.Name {
313+
t.Errorf("expected %q to be %q", got[0].Name, want.Name)
314+
}
315+
})
316+
}
317+
}

mmv1/api/type.go

+49
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,39 @@ func (t Type) Lineage() string {
399399
return fmt.Sprintf("%s.%s", t.ParentMetadata.Lineage(), google.Underscore(t.Name))
400400
}
401401

402+
// Returns a dot notation path to where the field is nested within the parent
403+
// object. eg: parent.meta.label.foo
404+
// This format is intended for resource metadata, to be used for connecting a Terraform
405+
// type with a corresponding API type.
406+
func (t Type) MetadataLineage() string {
407+
if t.ParentMetadata == nil {
408+
return google.Underscore(t.Name)
409+
}
410+
411+
// Skip arrays because otherwise the array name will be included twice
412+
if t.ParentMetadata.IsA("Array") {
413+
return t.ParentMetadata.MetadataLineage()
414+
}
415+
416+
return fmt.Sprintf("%s.%s", t.ParentMetadata.MetadataLineage(), google.Underscore(t.Name))
417+
}
418+
419+
// Returns a dot notation path to where the field is nested within the parent
420+
// object. eg: parent.meta.label.foo
421+
// This format is intended for to represent an API type.
422+
func (t Type) MetadataApiLineage() string {
423+
apiName := t.ApiName
424+
if t.ParentMetadata == nil {
425+
return google.Underscore(apiName)
426+
}
427+
428+
if t.ParentMetadata.IsA("Array") {
429+
return t.ParentMetadata.MetadataApiLineage()
430+
}
431+
432+
return fmt.Sprintf("%s.%s", t.ParentMetadata.MetadataApiLineage(), google.Underscore(apiName))
433+
}
434+
402435
// Returns the lineage in snake case
403436
func (t Type) LineageAsSnakeCase() string {
404437
if t.ParentMetadata == nil {
@@ -1065,6 +1098,22 @@ func (t *Type) IsForceNew() bool {
10651098
!(parent.FlattenObject && t.IsA("KeyValueLabels"))))))
10661099
}
10671100

1101+
// Returns true if the type does not correspond to an API type
1102+
func (t *Type) ProviderOnly() bool {
1103+
// These are special case fields created by the generator which have no API counterpart
1104+
if t.IsA("KeyValueEffectiveLabels") || t.IsA("KeyValueTerraformLabels") {
1105+
return true
1106+
}
1107+
1108+
if t.UrlParamOnly || t.ClientSide {
1109+
return true
1110+
}
1111+
1112+
// The type is provider-only if any of its ancestors are provider-only (it is inherited)
1113+
parent := t.Parent()
1114+
return parent != nil && parent.ProviderOnly()
1115+
}
1116+
10681117
// Returns an updated path for a given Terraform field path (e.g.
10691118
// 'a_field', 'parent_field.0.child_name'). Returns nil if the property
10701119
// is not included in the resource's properties and removes keys that have

0 commit comments

Comments
 (0)