Skip to content

Commit 500ed84

Browse files
Make IterFields take in aliases (#3207)
* update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * Update common/reflect_resource.go --------- Co-authored-by: Miles Yucht <[email protected]>
1 parent a644c6a commit 500ed84

File tree

3 files changed

+233
-61
lines changed

3 files changed

+233
-61
lines changed

clusters/resource_cluster.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,15 @@ func ZoneDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
5858
return false
5959
}
6060

61-
type ClusterResourceProvider struct{}
62-
63-
func (ClusterResourceProvider) UnderlyingType() compute.ClusterSpec {
64-
return compute.ClusterSpec{}
61+
type ClusterSpec struct {
62+
compute.ClusterSpec
6563
}
6664

67-
func (ClusterResourceProvider) Aliases() map[string]string {
65+
func (ClusterSpec) Aliases() map[string]string {
6866
return map[string]string{"cluster_mount_infos": "cluster_mount_info"}
6967
}
7068

71-
func (ClusterResourceProvider) CustomizeSchema(s map[string]*schema.Schema) map[string]*schema.Schema {
69+
func (ClusterSpec) CustomizeSchema(s map[string]*schema.Schema) map[string]*schema.Schema {
7270
common.CustomizeSchemaPath(s, "cluster_source").SetReadOnly()
7371
common.CustomizeSchemaPath(s, "enable_elastic_disk").SetComputed()
7472
common.CustomizeSchemaPath(s, "enable_local_disk_encryption").SetComputed()
@@ -171,7 +169,7 @@ func (ClusterResourceProvider) CustomizeSchema(s map[string]*schema.Schema) map[
171169
}
172170

173171
func resourceClusterSchema() map[string]*schema.Schema {
174-
return common.ResourceProviderStructToSchema[compute.ClusterSpec](ClusterResourceProvider{})
172+
return common.StructToSchema(ClusterSpec{}, nil)
175173
}
176174

177175
func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {

common/reflect_resource.go

Lines changed: 84 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,17 @@ var kindMap = map[reflect.Kind]string{
3838
reflect.UnsafePointer: "UnsafePointer",
3939
}
4040

41-
// Generic interface for resource provider struct. Using CustomizeSchema and Aliases functions to keep track of additional information
41+
// Generic interface for ResourceProvider. Using CustomizeSchema and Aliases functions to keep track of additional information
4242
// on top of the generated go-sdk struct. This is used to replace manually maintained structs with `tf` tags.
43-
type ResourceProviderStruct[T any] interface {
44-
UnderlyingType() T
43+
type ResourceProvider interface {
4544
Aliases() map[string]string
4645
CustomizeSchema(map[string]*schema.Schema) map[string]*schema.Schema
4746
}
4847

49-
// Takes in a ResourceProviderStruct and converts that into a map from string to schema.
50-
func ResourceProviderStructToSchema[T any](v ResourceProviderStruct[T]) map[string]*schema.Schema {
51-
underlyingType := v.UnderlyingType()
52-
rv := reflect.ValueOf(underlyingType)
53-
scm := typeToSchema(rv, []string{}, v.Aliases())
48+
// Takes in a ResourceProvider and converts that into a map from string to schema.
49+
func resourceProviderStructToSchema(v ResourceProvider) map[string]*schema.Schema {
50+
rv := reflect.ValueOf(v)
51+
scm := typeToSchema(rv, v.Aliases())
5452
scm = v.CustomizeSchema(scm)
5553
return scm
5654
}
@@ -63,15 +61,18 @@ func reflectKind(k reflect.Kind) string {
6361
return n
6462
}
6563

66-
func chooseFieldNameWithAliases(typeField reflect.StructField, fieldNamePath []string, aliases map[string]string) string {
64+
func chooseFieldNameWithAliases(typeField reflect.StructField, aliases map[string]string) string {
65+
// If nothing in the aliases map, return the field name from plain chooseFieldName method.
66+
if len(aliases) == 0 {
67+
return chooseFieldName(typeField)
68+
}
69+
6770
jsonFieldName := getJsonFieldName(typeField)
6871
if jsonFieldName == "-" {
6972
return "-"
7073
}
7174

72-
aliasKey := strings.Join(append(fieldNamePath, jsonFieldName), ".")
73-
74-
if value, ok := aliases[aliasKey]; ok {
75+
if value, ok := aliases[jsonFieldName]; ok {
7576
return value
7677
}
7778
return jsonFieldName
@@ -117,8 +118,15 @@ func MustSchemaPath(s map[string]*schema.Schema, path ...string) *schema.Schema
117118

118119
// StructToSchema makes schema from a struct type & applies customizations from callback given
119120
func StructToSchema(v any, customize func(map[string]*schema.Schema) map[string]*schema.Schema) map[string]*schema.Schema {
121+
// If the input 'v' is an instance of ResourceProvider, call resourceProviderStructToSchema instead.
122+
if rp, ok := v.(ResourceProvider); ok {
123+
if customize != nil {
124+
panic("customize should be nil if the input implements the ResourceProvider interface; use CustomizeSchema of ResourceProvider instead")
125+
}
126+
return resourceProviderStructToSchema(rp)
127+
}
120128
rv := reflect.ValueOf(v)
121-
scm := typeToSchema(rv, []string{}, map[string]string{})
129+
scm := typeToSchema(rv, map[string]string{})
122130
if customize != nil {
123131
scm = customize(scm)
124132
}
@@ -274,7 +282,7 @@ func listAllFields(v reflect.Value) []field {
274282
return fields
275283
}
276284

277-
func typeToSchema(v reflect.Value, path []string, aliases map[string]string) map[string]*schema.Schema {
285+
func typeToSchema(v reflect.Value, aliases map[string]string) map[string]*schema.Schema {
278286
scm := map[string]*schema.Schema{}
279287
rk := v.Kind()
280288
if rk == reflect.Ptr {
@@ -289,12 +297,8 @@ func typeToSchema(v reflect.Value, path []string, aliases map[string]string) map
289297
typeField := field.sf
290298
tfTag := typeField.Tag.Get("tf")
291299

292-
var fieldName string
293-
if len(aliases) == 0 {
294-
fieldName = chooseFieldName(typeField)
295-
} else {
296-
fieldName = chooseFieldNameWithAliases(typeField, path, aliases)
297-
}
300+
fieldName := chooseFieldNameWithAliases(typeField, aliases)
301+
unwrappedAliases := unwrapAliasesMap(fieldName, aliases)
298302
if fieldName == "-" {
299303
continue
300304
}
@@ -357,7 +361,7 @@ func typeToSchema(v reflect.Value, path []string, aliases map[string]string) map
357361
scm[fieldName].Type = schema.TypeList
358362
elem := typeField.Type.Elem()
359363
sv := reflect.New(elem).Elem()
360-
nestedSchema := typeToSchema(sv, append(path, fieldName), aliases)
364+
nestedSchema := typeToSchema(sv, unwrappedAliases)
361365
if strings.Contains(tfTag, "suppress_diff") {
362366
scm[fieldName].DiffSuppressFunc = diffSuppressor(scm[fieldName])
363367
for _, v := range nestedSchema {
@@ -375,7 +379,7 @@ func typeToSchema(v reflect.Value, path []string, aliases map[string]string) map
375379
elem := typeField.Type // changed from ptr
376380
sv := reflect.New(elem) // changed from ptr
377381

378-
nestedSchema := typeToSchema(sv, append(path, fieldName), aliases)
382+
nestedSchema := typeToSchema(sv, unwrappedAliases)
379383
if strings.Contains(tfTag, "suppress_diff") {
380384
scm[fieldName].DiffSuppressFunc = diffSuppressor(scm[fieldName])
381385
for _, v := range nestedSchema {
@@ -405,7 +409,7 @@ func typeToSchema(v reflect.Value, path []string, aliases map[string]string) map
405409
case reflect.Struct:
406410
sv := reflect.New(elem).Elem()
407411
scm[fieldName].Elem = &schema.Resource{
408-
Schema: typeToSchema(sv, append(path, fieldName), aliases),
412+
Schema: typeToSchema(sv, unwrappedAliases),
409413
}
410414
}
411415
default:
@@ -424,7 +428,7 @@ func IsRequestEmpty(v any) (bool, error) {
424428
return false, fmt.Errorf("value of Struct is expected, but got %s: %#v", reflectKind(rv.Kind()), rv)
425429
}
426430
var isNotEmpty bool
427-
err := iterFields(rv, []string{}, StructToSchema(v, nil), func(fieldSchema *schema.Schema, path []string, valueField *reflect.Value) error {
431+
err := iterFields(rv, []string{}, StructToSchema(v, nil), map[string]string{}, func(fieldSchema *schema.Schema, path []string, valueField *reflect.Value) error {
428432
if isNotEmpty {
429433
return nil
430434
}
@@ -450,7 +454,29 @@ func isGoSdk(v reflect.Value) bool {
450454
return false
451455
}
452456

453-
func iterFields(rv reflect.Value, path []string, s map[string]*schema.Schema,
457+
// Unwraps aliases map given a fieldname. Should be called everytime we recursively call iterFields.
458+
//
459+
// NOTE: If the target field has an alias, we expect `fieldname` argument to be the alias.
460+
// For example
461+
//
462+
// fieldName = "cluster"
463+
// aliases = {"cluster.clusterName": "name", "libraries": "library"}
464+
// would return: {"clusterName": "name"}
465+
func unwrapAliasesMap(fieldName string, aliases map[string]string) map[string]string {
466+
result := make(map[string]string)
467+
prefix := fieldName + "."
468+
for key, value := range aliases {
469+
// Only keep the keys that have the prefix.
470+
if strings.HasPrefix(key, prefix) && key != prefix {
471+
result[key] = value
472+
}
473+
}
474+
return result
475+
}
476+
477+
// Iterate through each field of the given reflect.Value object and execute a callback function with the corresponding
478+
// terraform schema object as the input.
479+
func iterFields(rv reflect.Value, path []string, s map[string]*schema.Schema, aliases map[string]string,
454480
cb func(fieldSchema *schema.Schema, path []string, valueField *reflect.Value) error) error {
455481
rk := rv.Kind()
456482
if rk != reflect.Struct {
@@ -463,7 +489,7 @@ func iterFields(rv reflect.Value, path []string, s map[string]*schema.Schema,
463489
fields := listAllFields(rv)
464490
for _, field := range fields {
465491
typeField := field.sf
466-
fieldName := chooseFieldName(typeField)
492+
fieldName := chooseFieldNameWithAliases(typeField, aliases)
467493
if fieldName == "-" {
468494
continue
469495
}
@@ -489,7 +515,7 @@ func iterFields(rv reflect.Value, path []string, s map[string]*schema.Schema,
489515
return nil
490516
}
491517

492-
func collectionToMaps(v any, s *schema.Schema) ([]any, error) {
518+
func collectionToMaps(v any, s *schema.Schema, aliases map[string]string) ([]any, error) {
493519
resultList := []any{}
494520
if sl, ok := v.([]string); ok {
495521
// most likely list of parameters to job task
@@ -521,14 +547,15 @@ func collectionToMaps(v any, s *schema.Schema) ([]any, error) {
521547
}
522548
v = v.Elem()
523549
}
524-
err := iterFields(v, []string{}, r.Schema, func(fieldSchema *schema.Schema,
550+
err := iterFields(v, []string{}, r.Schema, aliases, func(fieldSchema *schema.Schema,
525551
path []string, valueField *reflect.Value) error {
526552
fieldName := path[len(path)-1]
553+
newAliases := unwrapAliasesMap(fieldName, aliases)
527554
fieldValue := valueField.Interface()
528555
fieldPath := strings.Join(path, ".")
529556
switch fieldSchema.Type {
530557
case schema.TypeList, schema.TypeSet:
531-
nv, err := collectionToMaps(fieldValue, fieldSchema)
558+
nv, err := collectionToMaps(fieldValue, fieldSchema, newAliases)
532559
if err != nil {
533560
return fmt.Errorf("%s: %v", path, err)
534561
}
@@ -570,11 +597,12 @@ func isValueNilOrEmpty(valueField *reflect.Value, fieldPath string) bool {
570597

571598
// StructToData reads result using schema onto resource data
572599
func StructToData(result any, s map[string]*schema.Schema, d *schema.ResourceData) error {
600+
aliases := getAliasesMapFromStruct(result)
573601
v := reflect.ValueOf(result)
574602
if v.Kind() == reflect.Ptr {
575603
v = v.Elem()
576604
}
577-
return iterFields(v, []string{}, s, func(
605+
return iterFields(v, []string{}, s, aliases, func(
578606
fieldSchema *schema.Schema, path []string, valueField *reflect.Value) error {
579607
fieldValue := valueField.Interface()
580608
if fieldValue == nil {
@@ -599,7 +627,7 @@ func StructToData(result any, s map[string]*schema.Schema, d *schema.ResourceDat
599627
// validation, so we don't to it twice
600628
return d.Set(fieldPath, fieldValue)
601629
}
602-
nv, err := collectionToMaps(fieldValue, fieldSchema)
630+
nv, err := collectionToMaps(fieldValue, fieldSchema, aliases)
603631
if err != nil {
604632
return fmt.Errorf("%s: %v", fieldPath, err)
605633
}
@@ -633,36 +661,50 @@ func DiffToStructPointer(d attributeGetter, scm map[string]*schema.Schema, resul
633661
panic(fmt.Errorf("pointer is expected, but got %s: %#v", reflectKind(rk), result))
634662
}
635663
rv = rv.Elem()
636-
err := readReflectValueFromData([]string{}, d, rv, scm)
664+
aliases := getAliasesMapFromStruct(result)
665+
err := readReflectValueFromData([]string{}, d, rv, scm, aliases)
637666
if err != nil {
638667
panic(err)
639668
}
640669
}
641670

642671
// DataToStructPointer reads resource data with given schema onto result pointer. Panics.
643672
func DataToStructPointer(d *schema.ResourceData, scm map[string]*schema.Schema, result any) {
673+
aliases := getAliasesMapFromStruct(result)
644674
rv := reflect.ValueOf(result)
645675
rk := rv.Kind()
646676
if rk != reflect.Ptr {
647677
panic(fmt.Errorf("pointer is expected, but got %s: %#v", reflectKind(rk), result))
648678
}
649679
rv = rv.Elem()
650-
err := readReflectValueFromData([]string{}, d, rv, scm)
680+
err := readReflectValueFromData([]string{}, d, rv, scm, aliases)
651681
if err != nil {
652682
panic(err)
653683
}
654684
}
655685

656686
// DataToReflectValue reads reflect value from data
657687
func DataToReflectValue(d *schema.ResourceData, s map[string]*schema.Schema, rv reflect.Value) error {
658-
return readReflectValueFromData([]string{}, d, rv, s)
688+
// TODO: Pass in the right aliases map.
689+
return readReflectValueFromData([]string{}, d, rv, s, map[string]string{})
690+
}
691+
692+
// Get the aliases map from the given struct if it is an instance of ResourceProvider.
693+
// NOTE: This does not return aliases defined on `tf` tags.
694+
func getAliasesMapFromStruct(s any) map[string]string {
695+
if v, ok := s.(ResourceProvider); ok {
696+
return v.Aliases()
697+
}
698+
return map[string]string{}
659699
}
660700

661701
func readReflectValueFromData(path []string, d attributeGetter,
662-
rv reflect.Value, s map[string]*schema.Schema) error {
663-
return iterFields(rv, path, s, func(fieldSchema *schema.Schema,
702+
rv reflect.Value, s map[string]*schema.Schema, aliases map[string]string) error {
703+
return iterFields(rv, path, s, aliases, func(fieldSchema *schema.Schema,
664704
path []string, valueField *reflect.Value) error {
665705
fieldPath := strings.Join(path, ".")
706+
fieldName := path[len(path)-1]
707+
newAliases := unwrapAliasesMap(fieldName, aliases)
666708
raw, ok := d.GetOk(fieldPath)
667709
if !ok {
668710
return nil
@@ -699,13 +741,13 @@ func readReflectValueFromData(path []string, d attributeGetter,
699741
rawSet := raw.(*schema.Set)
700742
rawList := rawSet.List()
701743
return readListFromData(path, d, rawList, valueField,
702-
fieldSchema, func(i int) string {
744+
fieldSchema, newAliases, func(i int) string {
703745
return strconv.Itoa(rawSet.F(rawList[i]))
704746
})
705747
case schema.TypeList:
706748
// here we rely on Terraform SDK to perform validation, so we don't to it twice
707749
rawList := raw.([]any)
708-
return readListFromData(path, d, rawList, valueField, fieldSchema, strconv.Itoa)
750+
return readListFromData(path, d, rawList, valueField, fieldSchema, newAliases, strconv.Itoa)
709751
default:
710752
return fmt.Errorf("%s[%v] unsupported field type", fieldPath, raw)
711753
}
@@ -758,7 +800,7 @@ func primitiveReflectValueFromInterface(rk reflect.Kind,
758800
}
759801

760802
func readListFromData(path []string, d attributeGetter,
761-
rawList []any, valueField *reflect.Value, fieldSchema *schema.Schema,
803+
rawList []any, valueField *reflect.Value, fieldSchema *schema.Schema, aliases map[string]string,
762804
offsetConverter func(i int) string) error {
763805
if len(rawList) == 0 {
764806
return nil
@@ -772,7 +814,7 @@ func readListFromData(path []string, d attributeGetter,
772814
// here we rely on Terraform SDK to perform validation, so we don't to it twice
773815
nestedResource := fieldSchema.Elem.(*schema.Resource)
774816
nestedPath := append(path, offsetConverter(0))
775-
return readReflectValueFromData(nestedPath, d, ve, nestedResource.Schema)
817+
return readReflectValueFromData(nestedPath, d, ve, nestedResource.Schema, aliases)
776818
case reflect.Struct:
777819
// code path for setting the struct value is different from pointer value
778820
// in a single way: we set the field only after readReflectValueFromData
@@ -781,7 +823,7 @@ func readListFromData(path []string, d attributeGetter,
781823
ve := vstruct.Elem()
782824
nestedResource := fieldSchema.Elem.(*schema.Resource)
783825
nestedPath := append(path, offsetConverter(0))
784-
err := readReflectValueFromData(nestedPath, d, ve, nestedResource.Schema)
826+
err := readReflectValueFromData(nestedPath, d, ve, nestedResource.Schema, aliases)
785827
if err != nil {
786828
return err
787829
}
@@ -800,7 +842,7 @@ func readListFromData(path []string, d attributeGetter,
800842
nestedPath := append(path, offsetConverter(i))
801843
vpointer := reflect.New(valueField.Type().Elem())
802844
ve := vpointer.Elem()
803-
err := readReflectValueFromData(nestedPath, d, ve, nestedResource.Schema)
845+
err := readReflectValueFromData(nestedPath, d, ve, nestedResource.Schema, aliases)
804846
if err != nil {
805847
return err
806848
}

0 commit comments

Comments
 (0)