Skip to content

Commit

Permalink
Update Transform resources to accept array index in the path
Browse files Browse the repository at this point in the history
  • Loading branch information
sgajawada-px committed Oct 5, 2023
1 parent 29f495e commit 01a8742
Showing 1 changed file with 127 additions and 14 deletions.
141 changes: 127 additions & 14 deletions pkg/resourcecollector/resourcetransformation.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package resourcecollector

import (
"fmt"
"regexp"
"strconv"
"strings"

stork_api "github.com/libopenstorage/stork/pkg/apis/stork/v1alpha1"
Expand Down Expand Up @@ -54,20 +56,18 @@ func TransformResources(
for _, path := range patch.Specs.Paths {
switch path.Operation {
case stork_api.AddResourcePath:
value := getNewValueForPath(path.Value, string(path.Type))
value, err := getNewValueForPath(path.Value, path.Type)
if err != nil {
logrus.Errorf("Unable to parse the Value for the type %s specified, path %s on resource kind: %s/,%s/%s, err: %v", path.Type, path, patch.Kind, patch.Namespace, patch.Name, err)
return err
}
if path.Type == stork_api.KeyPairResourceType {
updateMap := value.(map[string]string)
err := unstructured.SetNestedStringMap(content, updateMap, strings.Split(path.Path, ".")...)
if err != nil {
logrus.Errorf("Unable to apply patch path %s on resource kind: %s/,%s/%s, err: %v", path, patch.Kind, patch.Namespace, patch.Name, err)
return err
}
} else if path.Type == stork_api.SliceResourceType {
err := unstructured.SetNestedField(content, value, strings.Split(path.Path, ".")...)
if err != nil {
logrus.Errorf("Unable to apply patch path %s on resource kind: %s/,%s/%s, err: %v", path, patch.Kind, patch.Namespace, patch.Name, err)
return err
}
} else {
err := unstructured.SetNestedField(content, value, strings.Split(path.Path, ".")...)
if err != nil {
Expand Down Expand Up @@ -107,7 +107,12 @@ func TransformResources(
}
value = currList
} else {
value = path.Value
var err error
value, err = getNewValueForPath(path.Value, path.Type)
if err != nil {
logrus.Errorf("Unable to parse the Value for the type %s specified, path %s on resource kind: %s/,%s/%s, err: %v", path.Type, path, patch.Kind, patch.Namespace, patch.Name, err)
return err
}
}
err := unstructured.SetNestedField(content, value, strings.Split(path.Path, ".")...)
if err != nil {
Expand Down Expand Up @@ -137,23 +142,131 @@ func TransformResources(
return nil
}

func getNewValueForPath(oldVal, valType string) interface{} {
func getNewValueForPath(oldVal string, valType stork_api.ResourceTransformationValueType) (interface{}, error) {
var updatedValue interface{}
if valType == string(stork_api.KeyPairResourceType) {
var err error

switch valType {
case stork_api.KeyPairResourceType:
// here we can accept map[string]interface{} because inside it is getting changed
newVal := make(map[string]string)
mapList := strings.Split(oldVal, ",")
for _, val := range mapList {
keyPair := strings.Split(val, ":")
newVal[keyPair[0]] = keyPair[1]
}
updatedValue = newVal
} else if valType == string(stork_api.SliceResourceType) {
case stork_api.SliceResourceType:
newVal := []string{}
arrList := strings.Split(oldVal, ",")
newVal = append(newVal, arrList...)
updatedValue = newVal
} else {
updatedValue = oldVal
case stork_api.IntResourceType:
updatedValue, err = strconv.ParseInt(oldVal, 10, 64)
case stork_api.BoolResourceType:
updatedValue, err = strconv.ParseBool(oldVal)
}
return updatedValue
return updatedValue, err
}

func jsonPath(fields []string) string {
return "." + strings.Join(fields, ".")
}

// what if the path ends with an array ?
// what if the array index is negative ?
// as we are accepting array [ ,] needs validation on the path
var pathRegexpWithanArray = regexp.MustCompile(`^.+\[[0-9]+\](\.[a-zA-Z_/][a-zA-Z0-9_/]*)+$`)

func RemoveNestedField(obj map[string]interface{}, fields ...string) {
m := obj
for _, field := range fields[:len(fields)-1] {
if val, ok := abc(m, field); ok {
if valMap, ok := val.(map[string]interface{}); ok {
m = valMap
}
} else {
return
}
}
delete(m, fields[len(fields)-1])
}

func SetNestedStringMap(obj map[string]interface{}, value map[string]string, path string) error {
if !pathRegexpWithanArray.MatchString(path) {
return unstructured.SetNestedStringMap(obj, value, strings.Split(path, ".")...)
}
m := make(map[string]interface{}, len(value)) // convert map[string]string into map[string]interface{}
for k, v := range value {
m[k] = v
}
return setNestedFieldNoCopy(obj, m, strings.Split(path, ".")...)
}

func SetNestedField(obj map[string]interface{}, value interface{}, path string) error {
if !pathRegexpWithanArray.MatchString(path) {
return unstructured.SetNestedField(obj, value, strings.Split(path, ".")...)
}
return setNestedFieldNoCopy(obj, runtime.DeepCopyJSONValue(value), strings.Split(path, ".")...)
}

func setNestedFieldNoCopy(obj map[string]interface{}, value interface{}, fields ...string) error {
m := obj

for i, field := range fields[:len(fields)-1] {
if val, ok := abc(m, field); ok {
if valMap, ok := val.(map[string]interface{}); ok {
m = valMap
} else {
return fmt.Errorf("value cannot be set because %v is not a map[string]interface{}", jsonPath(fields[:i+1]))
}
} else {
newVal := make(map[string]interface{})
m[field] = newVal
m = newVal
}
}
m[fields[len(fields)-1]] = value
return nil
}

func abc(m map[string]interface{}, field string) (interface{}, bool) {
//regexp.MustCompile("")
f := func(c rune) bool {
return c == '[' || c == ']'
}
parts := strings.FieldsFunc(field, f)
if len(parts) != 2 {
value, ok := m[field]
return value, ok
}
arr := m[parts[0]]
value, ok := arr.([]interface{})
if !ok {
return m[parts[0]], true
}
var index int
fmt.Sscanf(parts[1], "%d", &index)
if index < len(value) {
return value[index], true
} else if index == len(value) {
// try creating another func use def - it may create even during RemoveNestedField
value = append(value, make(map[string]interface{}))
return value[index], true
}
logrus.Errorf("index [%s] is beyound the array: %s with length %d", parts[1], parts[0], len(value))
return nil, false
}

// func def(m map[string]interface{}, field string) map[string]interface{} {
// newVal := make(map[string]interface{})

// f := func(c rune) bool {
// return c == '[' || c == ']'
// }
// parts := strings.FieldsFunc(field, f)
// if len(parts) != 2 {
// m[field] = newVal
// return m
// }
// }

0 comments on commit 01a8742

Please sign in to comment.