@@ -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
119120func 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
572599func 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.
643672func 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
657687func 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
661701func 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
760802func 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