@@ -2,6 +2,7 @@ package resourcecollector
22
33import (
44 "fmt"
5+ "regexp"
56 "strings"
67
78 stork_api "github.com/libopenstorage/stork/pkg/apis/stork/v1alpha1"
@@ -57,7 +58,7 @@ func TransformResources(
5758 value := getNewValueForPath (path .Value , string (path .Type ))
5859 if path .Type == stork_api .KeyPairResourceType {
5960 updateMap := value .(map [string ]string )
60- err := unstructured . SetNestedStringMap (content , updateMap , strings . Split ( path .Path , "." ) ... )
61+ err := SetNestedStringMap (content , updateMap , path .Path )
6162 if err != nil {
6263 logrus .Errorf ("Unable to apply patch path %s on resource kind: %s/,%s/%s, err: %v" , path , patch .Kind , patch .Namespace , patch .Name , err )
6364 return err
@@ -69,21 +70,21 @@ func TransformResources(
6970 return err
7071 }
7172 } else {
72- err := unstructured . SetNestedField (content , value , strings . Split ( path .Path , "." ) ... )
73+ err := SetNestedField (content , value , path .Path )
7374 if err != nil {
7475 logrus .Errorf ("Unable to perform operation %s on path %s on resource kind: %s/,%s/%s, err: %v" , path .Operation , path , patch .Kind , patch .Namespace , patch .Name , err )
7576 return err
7677 }
7778 }
7879
7980 case stork_api .DeleteResourcePath :
80- unstructured . RemoveNestedField (content , strings .Split (path .Path , "." )... )
81+ RemoveNestedField (content , strings .Split (path .Path , "." )... )
8182 logrus .Debugf ("Removed patch path %s on resource kind: %s/,%s/%s" , path , patch .Kind , patch .Namespace , patch .Name )
8283
8384 case stork_api .ModifyResourcePathValue :
8485 var value interface {}
8586 if path .Type == stork_api .KeyPairResourceType {
86- currMap , _ , err := unstructured . NestedMap (content , strings .Split (path .Path , "." )... )
87+ currMap , _ , err := NestedMap (content , strings .Split (path .Path , "." )... )
8788 if err != nil || len (currMap ) == 0 {
8889 return fmt .Errorf ("unable to find spec path, err: %v" , err )
8990 }
@@ -97,7 +98,7 @@ func TransformResources(
9798 }
9899 value = currMap
99100 } else if path .Type == stork_api .SliceResourceType {
100- currList , _ , err := unstructured . NestedSlice (content , strings .Split (path .Path , "." )... )
101+ currList , _ , err := NestedSlice (content , strings .Split (path .Path , "." )... )
101102 if err != nil {
102103 return fmt .Errorf ("unable to find spec path, err: %v" , err )
103104 }
@@ -109,7 +110,7 @@ func TransformResources(
109110 } else {
110111 value = path .Value
111112 }
112- err := unstructured . SetNestedField (content , value , strings . Split ( path .Path , "." ) ... )
113+ err := SetNestedField (content , value , path .Path )
113114 if err != nil {
114115 logrus .Errorf ("Unable to perform operation %s on path %s on resource kind: %s/,%s/%s, err: %v" , path .Operation , path , patch .Kind , patch .Namespace , patch .Name , err )
115116 return err
@@ -157,3 +158,222 @@ func getNewValueForPath(oldVal, valType string) interface{} {
157158 }
158159 return updatedValue
159160}
161+
162+ var pathRegexpWithanArray = regexp .MustCompile (`^.+\[[0-9]+\](\.[a-zA-Z_/][a-zA-Z0-9_/]*)+$` )
163+
164+ func jsonPath (fields []string ) string {
165+ return "." + strings .Join (fields , "." )
166+ }
167+
168+ // NestedSlice is wrapper around unstructured.NestedSlice function
169+ // if the path doesn't consists of an index the call is transferred to unstructured.NestedSlice
170+ // else it uses the same logic but includes changes to support the array index
171+ func NestedSlice (obj map [string ]interface {}, fields ... string ) ([]interface {}, bool , error ) {
172+ if ! pathRegexpWithanArray .MatchString (strings .Join (fields , "." )) {
173+ return unstructured .NestedSlice (obj , fields ... )
174+ }
175+
176+ val , found , err := NestedFieldNoCopy (obj , fields ... )
177+ if ! found || err != nil {
178+ return nil , found , err
179+ }
180+ _ , ok := val .([]interface {})
181+ if ! ok {
182+ return nil , false , fmt .Errorf ("%v accessor error: %v is of the type %T, expected []interface{}" , jsonPath (fields ), val , val )
183+ }
184+ return runtime .DeepCopyJSONValue (val ).([]interface {}), true , nil
185+ }
186+
187+ // NestedMap is wrapper around unstructured.NestedMap function
188+ // if the path doesn't consists of an index the call is transferred to unstructured.NestedMap
189+ // else it uses the same logic but includes changes to support the array index
190+ func NestedMap (obj map [string ]interface {}, fields ... string ) (map [string ]interface {}, bool , error ) {
191+ if ! pathRegexpWithanArray .MatchString (strings .Join (fields , "." )) {
192+ return unstructured .NestedMap (obj , fields ... )
193+ }
194+
195+ m , found , err := nestedMapNoCopy (obj , fields ... )
196+ if ! found || err != nil {
197+ return nil , found , err
198+ }
199+ return runtime .DeepCopyJSON (m ), true , nil
200+ }
201+
202+ func nestedMapNoCopy (obj map [string ]interface {}, fields ... string ) (map [string ]interface {}, bool , error ) {
203+ val , found , err := NestedFieldNoCopy (obj , fields ... )
204+ if ! found || err != nil {
205+ return nil , found , err
206+ }
207+ m , ok := val .(map [string ]interface {})
208+ if ! ok {
209+ return nil , false , fmt .Errorf ("%v accessor error: %v is of the type %T, expected map[string]interface{}" , jsonPath (fields ), val , val )
210+ }
211+ return m , true , nil
212+ }
213+
214+ func NestedFieldNoCopy (obj map [string ]interface {}, fields ... string ) (interface {}, bool , error ) {
215+ var val interface {} = obj
216+
217+ for i , field := range fields {
218+ if val == nil {
219+ return nil , false , nil
220+ }
221+ if m , ok := val .(map [string ]interface {}); ok {
222+ var err error
223+ val , ok , err = getValueFromMapKey (m , field )
224+ if ! ok || err != nil {
225+ return nil , false , err
226+ }
227+ } else {
228+ return nil , false , fmt .Errorf ("%v accessor error: %v is of the type %T, expected map[string]interface{}" , jsonPath (fields [:i + 1 ]), val , val )
229+ }
230+ }
231+ return val , true , nil
232+ }
233+
234+ // SetNestedStringSlice is wrapper around unstructured.SetNestedStringSlice function
235+ // if the path doesn't consists of an index the call is transferred to unstructured.SetNestedStringSlice
236+ // else it uses the same logic but includes changes to support the array index
237+ func SetNestedStringSlice (obj map [string ]interface {}, value []string , path string ) error {
238+ if ! pathRegexpWithanArray .MatchString (path ) {
239+ return unstructured .SetNestedStringSlice (obj , value , strings .Split (path , "." )... )
240+ }
241+
242+ m := make ([]interface {}, 0 , len (value )) // convert []string into []interface{}
243+ for _ , v := range value {
244+ m = append (m , v )
245+ }
246+ return setNestedFieldNoCopy (obj , m , strings .Split (path , "." )... )
247+ }
248+
249+ // SetNestedStringMap is wrapper around unstructured.SetNestedStringMap function
250+ // if the path doesn't consists of an index the call is transferred to unstructured.SetNestedStringMap
251+ // else it uses the same logic but includes changes to support the array index
252+ func SetNestedStringMap (obj map [string ]interface {}, value map [string ]string , path string ) error {
253+ if ! pathRegexpWithanArray .MatchString (path ) {
254+ return unstructured .SetNestedStringMap (obj , value , strings .Split (path , "." )... )
255+ }
256+ m := make (map [string ]interface {}, len (value )) // convert map[string]string into map[string]interface{}
257+ for k , v := range value {
258+ m [k ] = v
259+ }
260+ return setNestedFieldNoCopy (obj , m , strings .Split (path , "." )... )
261+ }
262+
263+ // SetNestedField is wrapper around unstructured.SetNestedField function
264+ // if the path doesn't consists of an index the call is transferred to unstructured.SetNestedField
265+ // else it uses the same logic but includes changes to support the array index
266+ func SetNestedField (obj map [string ]interface {}, value interface {}, path string ) error {
267+ if ! pathRegexpWithanArray .MatchString (path ) {
268+ return unstructured .SetNestedField (obj , value , strings .Split (path , "." )... )
269+ }
270+ return setNestedFieldNoCopy (obj , runtime .DeepCopyJSONValue (value ), strings .Split (path , "." )... )
271+ }
272+
273+ func setNestedFieldNoCopy (obj map [string ]interface {}, value interface {}, fields ... string ) error {
274+ m := obj
275+
276+ for index , field := range fields [:len (fields )- 1 ] {
277+ if val , ok , err := getValueFromMapKey (m , field ); err != nil {
278+ return err
279+ } else if ok {
280+ if valMap , ok := val .(map [string ]interface {}); ok {
281+ m = valMap
282+ } else {
283+ return fmt .Errorf ("value cannot be set because %v is not a map[string]interface{}" , jsonPath (fields [:index + 1 ]))
284+ }
285+ } else {
286+ newVal := make (map [string ]interface {})
287+ if err := setMapKeyWithValue (m , newVal , field ); err != nil {
288+ return err
289+ }
290+ m = newVal
291+ }
292+ }
293+ m [fields [len (fields )- 1 ]] = value
294+ return nil
295+ }
296+
297+ // RemoveNestedField is wrapper around unstructured.RemoveNestedField function
298+ // if the path doesn't consists of an index the call is transferred to unstructured.RemoveNestedField
299+ // else it uses the same logic but includes changes to support the array index
300+ func RemoveNestedField (obj map [string ]interface {}, fields ... string ) error {
301+ if ! pathRegexpWithanArray .MatchString (strings .Join (fields , "." )) {
302+ unstructured .RemoveNestedField (obj , fields ... )
303+ return nil
304+ }
305+ m := obj
306+ for _ , field := range fields [:len (fields )- 1 ] {
307+ if val , ok , err := getValueFromMapKey (m , field ); err != nil {
308+ return err
309+ } else if ok {
310+ if valMap , ok := val .(map [string ]interface {}); ok {
311+ m = valMap
312+ } else {
313+ return nil
314+ }
315+ } else {
316+ return nil
317+ }
318+ }
319+ delete (m , fields [len (fields )- 1 ])
320+ return nil
321+ }
322+
323+ var indexDelimeter = func (c rune ) bool {
324+ return c == '[' || c == ']'
325+ }
326+
327+ // setMapKeyWithValue is to assign the value to the map m with key field, value newVal. here the field may even contain the array index
328+ func setMapKeyWithValue (m , newVal map [string ]interface {}, field string ) error {
329+ // check if an array index exists in the field
330+ parts := strings .FieldsFunc (field , indexDelimeter )
331+ if len (parts ) != 2 {
332+ m [field ] = newVal
333+ return nil
334+ }
335+
336+ // if the parts[0] is not an array send an error
337+ arr := m [parts [0 ]]
338+ value , ok := arr .([]interface {})
339+ if ! ok {
340+ return fmt .Errorf ("value cannot be set because %v is not a []interface{}" , arr )
341+ }
342+
343+ // append the newVal to the existing array
344+ value = append (value , newVal )
345+ m [parts [0 ]] = value
346+ return nil
347+ }
348+
349+ // getValueFromMapKey is to retrive the value for the map m with key field. here the field may even contain the array index
350+ func getValueFromMapKey (m map [string ]interface {}, field string ) (interface {}, bool , error ) {
351+ // check if an array index exists in the field
352+ parts := strings .FieldsFunc (field , indexDelimeter )
353+ if len (parts ) != 2 {
354+ value , ok := m [field ]
355+ return value , ok , nil
356+ }
357+
358+ // if the parts[0] is not an array send an error
359+ arr := m [parts [0 ]]
360+ value , ok := arr .([]interface {})
361+ if ! ok {
362+ return nil , false , fmt .Errorf ("value cannot be set because %v is not a []interface{}" , arr )
363+ }
364+
365+ // Convert the array index to int
366+ var arrIndex int
367+ _ , err := fmt .Sscanf (parts [1 ], "%d" , & arrIndex )
368+ if err != nil {
369+ return nil , false , err
370+ }
371+
372+ // send the approriate array object
373+ if arrIndex < len (value ) {
374+ return value [arrIndex ], true , nil
375+ } else if arrIndex > len (value ) {
376+ return nil , false , fmt .Errorf ("value cannot be set because index %d is out of range in array %v with length %d" , arrIndex , arr , len (value ))
377+ }
378+ return nil , false , nil
379+ }
0 commit comments