@@ -77,13 +77,44 @@ func MustSchemaPath(s map[string]*schema.Schema, path ...string) *schema.Schema
7777// StructToSchema makes schema from a struct type & applies customizations from callback given
7878func StructToSchema (v any , customize func (map [string ]* schema.Schema ) map [string ]* schema.Schema ) map [string ]* schema.Schema {
7979 rv := reflect .ValueOf (v )
80- scm := typeToSchema (rv , rv . Type (), []string {})
80+ scm := typeToSchema (rv , []string {})
8181 if customize != nil {
8282 scm = customize (scm )
8383 }
8484 return scm
8585}
8686
87+ // SetSuppressDiff adds diff suppression to a schema. This is necessary for non-computed
88+ // fields for which the platform returns a value, but the user has not configured any value.
89+ // For example: the REST API returns `{"tags": {}}` for a resource with no tags.
90+ func SetSuppressDiff (v * schema.Schema ) {
91+ v .DiffSuppressFunc = diffSuppressor (fmt .Sprintf ("%v" , v .Type .Zero ()))
92+ }
93+
94+ // SetDefault sets the default value for a schema.
95+ func SetDefault (v * schema.Schema , value any ) {
96+ v .Default = value
97+ v .Optional = true
98+ v .Required = false
99+ }
100+
101+ // SetReadOnly sets the schema to be read-only (i.e. computed, non-optional).
102+ // This should be used for fields that are not user-configurable but are returned
103+ // by the platform.
104+ func SetReadOnly (v * schema.Schema ) {
105+ v .Optional = false
106+ v .Required = false
107+ v .MaxItems = 0
108+ v .Computed = true
109+ }
110+
111+ // SetRequired sets the schema to be required.
112+ func SetRequired (v * schema.Schema ) {
113+ v .Optional = false
114+ v .Required = true
115+ v .Computed = false
116+ }
117+
87118func isOptional (typeField reflect.StructField ) bool {
88119 if strings .Contains (typeField .Tag .Get ("json" ), "omitempty" ) {
89120 return true
@@ -175,26 +206,52 @@ func diffSuppressor(zero string) func(k, old, new string, d *schema.ResourceData
175206 log .Printf ("[DEBUG] Suppressing diff for %v: platform=%#v config=%#v" , k , old , new )
176207 return true
177208 }
209+ if strings .HasSuffix (k , ".#" ) && new == "0" && old != "0" {
210+ field := strings .TrimSuffix (k , ".#" )
211+ log .Printf ("[DEBUG] Suppressing diff for list or set %v: no value configured but platform returned some value (likely {})" , field )
212+ return true
213+ }
178214 return false
179215 }
180216}
181217
218+ type field struct {
219+ sf reflect.StructField
220+ v reflect.Value
221+ }
222+
223+ func listAllFields (v reflect.Value ) []field {
224+ t := v .Type ()
225+ fields := make ([]field , 0 , v .NumField ())
226+ for i := 0 ; i < v .NumField (); i ++ {
227+ f := t .Field (i )
228+ if f .Anonymous {
229+ fields = append (fields , listAllFields (v .Field (i ))... )
230+ } else {
231+ fields = append (fields , field {
232+ sf : f ,
233+ v : v .Field (i ),
234+ })
235+ }
236+ }
237+ return fields
238+ }
239+
182240// typeToSchema converts struct into terraform schema. `path` is used for block suppressions
183241// special path element `"0"` is used to denote either arrays or sets of elements
184- func typeToSchema (v reflect.Value , t reflect. Type , path []string ) map [string ]* schema.Schema {
242+ func typeToSchema (v reflect.Value , path []string ) map [string ]* schema.Schema {
185243 scm := map [string ]* schema.Schema {}
186244 rk := v .Kind ()
187245 if rk == reflect .Ptr {
188246 v = v .Elem ()
189- t = v .Type ()
190247 rk = v .Kind ()
191248 }
192249 if rk != reflect .Struct {
193250 panic (fmt .Errorf ("Schema value of Struct is expected, but got %s: %#v" , reflectKind (rk ), v ))
194251 }
195- for i := 0 ; i < v . NumField (); i ++ {
196- typeField := t . Field ( i )
197-
252+ fields := listAllFields ( v )
253+ for _ , field := range fields {
254+ typeField := field . sf
198255 tfTag := typeField .Tag .Get ("tf" )
199256
200257 fieldName := chooseFieldName (typeField )
@@ -260,7 +317,7 @@ func typeToSchema(v reflect.Value, t reflect.Type, path []string) map[string]*sc
260317 scm [fieldName ].Type = schema .TypeList
261318 elem := typeField .Type .Elem ()
262319 sv := reflect .New (elem ).Elem ()
263- nestedSchema := typeToSchema (sv , elem , append (path , fieldName , "0" ))
320+ nestedSchema := typeToSchema (sv , append (path , fieldName , "0" ))
264321 if strings .Contains (tfTag , "suppress_diff" ) {
265322 blockCount := strings .Join (append (path , fieldName , "#" ), "." )
266323 scm [fieldName ].DiffSuppressFunc = makeEmptyBlockSuppressFunc (blockCount )
@@ -279,7 +336,7 @@ func typeToSchema(v reflect.Value, t reflect.Type, path []string) map[string]*sc
279336 elem := typeField .Type // changed from ptr
280337 sv := reflect .New (elem ) // changed from ptr
281338
282- nestedSchema := typeToSchema (sv , elem , append (path , fieldName , "0" ))
339+ nestedSchema := typeToSchema (sv , append (path , fieldName , "0" ))
283340 if strings .Contains (tfTag , "suppress_diff" ) {
284341 blockCount := strings .Join (append (path , fieldName , "#" ), "." )
285342 scm [fieldName ].DiffSuppressFunc = makeEmptyBlockSuppressFunc (blockCount )
@@ -310,7 +367,7 @@ func typeToSchema(v reflect.Value, t reflect.Type, path []string) map[string]*sc
310367 case reflect .Struct :
311368 sv := reflect .New (elem ).Elem ()
312369 scm [fieldName ].Elem = & schema.Resource {
313- Schema : typeToSchema (sv , elem , append (path , fieldName , "0" )),
370+ Schema : typeToSchema (sv , append (path , fieldName , "0" )),
314371 }
315372 }
316373 default :
@@ -341,6 +398,20 @@ func IsRequestEmpty(v any) (bool, error) {
341398 return ! isNotEmpty , err
342399}
343400
401+ // isGoSdk returns true if the struct is from databricks-sdk-go or embeds a struct from databricks-sdk-go.
402+ func isGoSdk (v reflect.Value ) bool {
403+ if strings .Contains (v .Type ().PkgPath (), "databricks-sdk-go" ) {
404+ return true
405+ }
406+ for i := 0 ; i < v .NumField (); i ++ {
407+ f := v .Type ().Field (i )
408+ if f .Anonymous && isGoSdk (v .Field (i )) {
409+ return true
410+ }
411+ }
412+ return false
413+ }
414+
344415func iterFields (rv reflect.Value , path []string , s map [string ]* schema.Schema ,
345416 cb func (fieldSchema * schema.Schema , path []string , valueField * reflect.Value ) error ) error {
346417 rk := rv .Kind ()
@@ -350,9 +421,10 @@ func iterFields(rv reflect.Value, path []string, s map[string]*schema.Schema,
350421 if ! rv .IsValid () {
351422 return fmt .Errorf ("%s: got invalid reflect value %#v" , path , rv )
352423 }
353- isGoSDK := strings .Contains (rv .Type ().PkgPath (), "databricks-sdk-go" )
354- for i := 0 ; i < rv .NumField (); i ++ {
355- typeField := rv .Type ().Field (i )
424+ isGoSDK := isGoSdk (rv )
425+ fields := listAllFields (rv )
426+ for _ , field := range fields {
427+ typeField := field .sf
356428 fieldName := chooseFieldName (typeField )
357429 if fieldName == "-" {
358430 continue
@@ -370,7 +442,7 @@ func iterFields(rv reflect.Value, path []string, s map[string]*schema.Schema,
370442 if fieldSchema .Optional && defaultEmpty && ! omitEmpty {
371443 return fmt .Errorf ("inconsistency: %s is optional, default is empty, but has no omitempty" , fieldName )
372444 }
373- valueField := rv . Field ( i )
445+ valueField := field . v
374446 err := cb (fieldSchema , append (path , fieldName ), & valueField )
375447 if err != nil {
376448 return fmt .Errorf ("%s: %s" , fieldName , err )
@@ -393,13 +465,15 @@ func collectionToMaps(v any, s *schema.Schema) ([]any, error) {
393465 return nil , fmt .Errorf ("not resource" )
394466 }
395467 var allItems []reflect.Value
396- if s . MaxItems == 1 {
397- allItems = append ( allItems , reflect . ValueOf ( v ) )
398- } else {
399- vs := reflect . ValueOf ( v )
400- for i := 0 ; i < vs .Len (); i ++ {
401- allItems = append (allItems , vs .Index (i ))
468+ rv := reflect . ValueOf ( v )
469+ rvType := rv . Type (). Kind ( )
470+ isList := rvType == reflect . Array || rvType == reflect . Slice
471+ if isList {
472+ for i := 0 ; i < rv .Len (); i ++ {
473+ allItems = append (allItems , rv .Index (i ))
402474 }
475+ } else {
476+ allItems = append (allItems , rv )
403477 }
404478 for _ , v := range allItems {
405479 data := map [string ]any {}
0 commit comments