Skip to content

Commit aeea9b4

Browse files
committed
PB-4580:
- Change TransformResources resource collector code to accept the array index in the path
1 parent aef5257 commit aeea9b4

File tree

2 files changed

+232
-6
lines changed

2 files changed

+232
-6
lines changed

pkg/migration/controllers/resourcetransformation.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"reflect"
7+
"regexp"
78
"strings"
89

910
"github.com/libopenstorage/stork/drivers/volume"
@@ -30,6 +31,8 @@ import (
3031
"sigs.k8s.io/controller-runtime/pkg/reconcile"
3132
)
3233

34+
var pathRegexp = regexp.MustCompile(`^([a-zA-Z_/][a-zA-Z0-9_/]*(\[[0-9]+\])?\.)*[a-zA-Z_/][a-zA-Z0-9_/]*$`)
35+
3336
const (
3437
// ResourceTransformationControllerName of resource transformation CR handler
3538
ResourceTransformationControllerName = "resource-transformation-controller"
@@ -173,6 +176,9 @@ func (r *ResourceTransformationController) validateSpecPath(transform *stork_api
173176
path.Type == stork_api.KeyPairResourceType) {
174177
return fmt.Errorf("unsupported type for resource %s, path %s, type: %s", kind, path.Path, path.Type)
175178
}
179+
if !pathRegexp.MatchString(path.Path) {
180+
return fmt.Errorf("invalid path for resource %s, path %s, type: %s", kind, path.Path, path.Type)
181+
}
176182
}
177183
}
178184
log.TransformLog(transform).Infof("validated paths ")

pkg/resourcecollector/resourcetransformation.go

Lines changed: 226 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package resourcecollector
22

33
import (
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

Comments
 (0)