Skip to content

Commit

Permalink
Supporting regex in config path (#636)
Browse files Browse the repository at this point in the history
* started supporting regex in config path

Signed-off-by: sethiyash <[email protected]>

* copying with calling apply recursively

Signed-off-by: sethiyash <[email protected]>

* refactoring

Signed-off-by: sethiyash <[email protected]>

* fixing linting issues

Signed-off-by: sethiyash <[email protected]>

* stop overwriting object in copy mod and made changes in remove mod

Signed-off-by: sethiyash <[email protected]>

* tweaked the logic to resolve issues

Signed-off-by: sethiyash <[email protected]>

* added e2e testcase with regex in config

Signed-off-by: sethiyash <[email protected]>

* made suggested changes on e2e testcases

Signed-off-by: sethiyash <[email protected]>

* regex as seprate path part

Signed-off-by: sethiyash <[email protected]>

* renamed regexObj to regex

Signed-off-by: sethiyash <[email protected]>

* fixing testcase

Signed-off-by: sethiyash <[email protected]>

* added nil check Signed-off-by: Yash Sethiya  <[email protected]>

Signed-off-by: sethiyash <[email protected]>

* refactored logic to support regex with indexes

Signed-off-by: sethiyash <[email protected]>

* adopted some nits

Signed-off-by: sethiyash <[email protected]>

* Added comment in e2e testcases

Signed-off-by: sethiyash <[email protected]>

---------

Signed-off-by: sethiyash <[email protected]>
  • Loading branch information
sethiyash authored Sep 5, 2023
1 parent 0d3d1df commit ec961f8
Show file tree
Hide file tree
Showing 6 changed files with 407 additions and 16 deletions.
105 changes: 89 additions & 16 deletions pkg/kapp/resources/mod_field_copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package resources

import (
"fmt"
"regexp"
)

type FieldCopyModSource string
Expand All @@ -30,29 +31,37 @@ func (t FieldCopyMod) IsResourceMatching(res Resource) bool {
}

func (t FieldCopyMod) ApplyFromMultiple(res Resource, srcs map[FieldCopyModSource]Resource) error {
// Make a copy of resource, to avoid modifications
// that may be done even in case when there is nothing to copy
updatedRes := res.DeepCopy()

updated, err := t.apply(updatedRes.unstructured().Object, t.Path, Path{}, srcs)
if err != nil {
return fmt.Errorf("FieldCopyMod for path '%s' on resource '%s': %s", t.Path.AsString(), res.Description(), err)
}

if updated {
res.setUnstructured(updatedRes.unstructured())
for _, src := range t.Sources {
source, found := srcs[src]
if !found {
continue
}
// Make a copy of resource, to avoid modifications
// that may be done even in case when there is nothing to copy
updatedRes := res.DeepCopy()
updated, err := t.apply(updatedRes.unstructured().Object, source.unstructured().Object, t.Path, Path{}, srcs)
if err != nil {
return fmt.Errorf("FieldCopyMod for path '%s' on resource '%s': %s", t.Path.AsString(), res.Description(), err)
}
if updated {
res.setUnstructured(updatedRes.unstructured())
}
}

return nil
}

func (t FieldCopyMod) apply(obj interface{}, path Path, fullPath Path, srcs map[FieldCopyModSource]Resource) (bool, error) {
func (t FieldCopyMod) apply(obj interface{}, srcObj interface{}, path Path, fullPath Path, srcs map[FieldCopyModSource]Resource) (bool, error) {
for i, part := range path {
isLast := len(path) == i+1
fullPath = append(fullPath, part)

switch {
case part.MapKey != nil:
srcTypedObj, ok := srcObj.(map[string]interface{})
if !ok {
return false, fmt.Errorf("Unexpected non-map found: %T", srcObj)
}
typedObj, ok := obj.(map[string]interface{})
if !ok {
return false, fmt.Errorf("Unexpected non-map found: %T", obj)
Expand All @@ -62,13 +71,21 @@ func (t FieldCopyMod) apply(obj interface{}, path Path, fullPath Path, srcs map[
return t.copyIntoMap(typedObj, fullPath, srcs)
}

var found bool
var (
found bool
srcObjFound bool
)
srcObj, srcObjFound = srcTypedObj[*part.MapKey]
if !srcObjFound || srcObj == nil {
return false, nil
}

obj, found = typedObj[*part.MapKey]
// TODO check strictness?
if !found || obj == nil {
// create empty maps if there are no downstream array indexes;
// if there are, we cannot make them anyway, so just exit
if path.ContainsNonMapKeys() {
if path.ContainsArrayIndex() {
return false, nil
}
obj = map[string]interface{}{}
Expand All @@ -87,6 +104,11 @@ func (t FieldCopyMod) apply(obj interface{}, path Path, fullPath Path, srcs map[
return false, fmt.Errorf("Unexpected non-array found: %T", obj)
}

srcTypedObj, ok := srcObj.([]interface{})
if !ok {
return false, fmt.Errorf("Unexpected non-array found: %T", srcObj)
}

var anyUpdated bool

for objI, obj := range typedObj {
Expand All @@ -95,7 +117,11 @@ func (t FieldCopyMod) apply(obj interface{}, path Path, fullPath Path, srcs map[
newFullPath := append([]*PathPart{}, fullPath...)
newFullPath[len(newFullPath)-1] = &PathPart{ArrayIndex: &PathPartArrayIndex{Index: &objI}}

updated, err := t.apply(obj, path[i+1:], newFullPath, srcs)
var srcTypeObj map[string]interface{}
if objI < len(srcTypedObj) {
srcTypeObj = srcTypedObj[objI].(map[string]interface{})
}
updated, err := t.apply(obj, srcTypeObj, path[i+1:], newFullPath, srcs)
if err != nil {
return false, err
}
Expand All @@ -112,9 +138,15 @@ func (t FieldCopyMod) apply(obj interface{}, path Path, fullPath Path, srcs map[
return false, fmt.Errorf("Unexpected non-array found: %T", obj)
}

srcTypedObj, ok := srcObj.([]interface{})
if !ok {
return false, fmt.Errorf("Unexpected non-array found: %T", srcObj)
}

if *part.ArrayIndex.Index < len(typedObj) {
obj = typedObj[*part.ArrayIndex.Index]
return t.apply(obj, path[i+1:], fullPath, srcs)
srcObj = srcTypedObj[*part.ArrayIndex.Index]
return t.apply(obj, srcObj, path[i+1:], fullPath, srcs)
}

return false, nil // index not found, nothing to append to
Expand All @@ -123,6 +155,29 @@ func (t FieldCopyMod) apply(obj interface{}, path Path, fullPath Path, srcs map[
panic(fmt.Sprintf("Unknown array index: %#v", part.ArrayIndex))
}

case part.Regex != nil:
if part.Regex.Regex == nil {
panic("Regex should be non nil")
}
matchedKeys, err := matchRegexWithSrcObj(*part.Regex.Regex, srcObj)
if err != nil {
return false, err
}
var anyUpdated bool
for _, key := range matchedKeys {
newPath := append(Path{&PathPart{MapKey: &key}}, path[i+1:]...)
newFullPath := fullPath[:len(fullPath)-1]
updated, err := t.apply(obj, srcObj, newPath, newFullPath, srcs)
if err != nil {
return false, err
}
if updated {
anyUpdated = true
}
}

return anyUpdated, nil

default:
panic(fmt.Sprintf("Unexpected path part: %#v", part))
}
Expand Down Expand Up @@ -203,3 +258,21 @@ func (t FieldCopyMod) obtainValue(obj interface{}, path Path) (interface{}, bool

return obj, true, nil
}

func matchRegexWithSrcObj(regexString string, srcObj interface{}) ([]string, error) {
var matchedKeys []string
regex, err := regexp.Compile(regexString)
if err != nil {
return matchedKeys, err
}
srcTypedObj, ok := srcObj.(map[string]interface{})
if !ok && srcTypedObj != nil {
return matchedKeys, fmt.Errorf("Unexpected non-map found: %T", srcObj)
}
for key := range srcTypedObj {
if regex.MatchString(key) {
matchedKeys = append(matchedKeys, key)
}
}
return matchedKeys, nil
}
17 changes: 17 additions & 0 deletions pkg/kapp/resources/mod_field_remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,23 @@ func (t FieldRemoveMod) apply(obj interface{}, path Path) error {
default:
panic(fmt.Sprintf("Unknown array index: %#v", part.ArrayIndex))
}
case part.Regex != nil:
if part.Regex.Regex == nil {
panic("Regex should be non nil")
}
matchedKeys, err := matchRegexWithSrcObj(*part.Regex.Regex, obj)
if err != nil {
return err
}
for _, key := range matchedKeys {
newPath := append(Path{&PathPart{MapKey: &key}}, path[i+1:]...)
err := t.apply(obj, newPath)
if err != nil {
return err
}
}

return nil

default:
panic(fmt.Sprintf("Unexpected path part: %#v", part))
Expand Down
3 changes: 3 additions & 0 deletions pkg/kapp/resources/mod_object_ref_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ func (t ObjectRefSetMod) apply(obj interface{}, path Path) error {
panic(fmt.Sprintf("Unknown array index: %#v", part.ArrayIndex))
}

case part.Regex != nil:
panic("Regex in path part is only supported for rebaseRules.")

default:
panic(fmt.Sprintf("Unexpected path part: %#v", part))
}
Expand Down
19 changes: 19 additions & 0 deletions pkg/kapp/resources/mod_path.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Path []*PathPart

type PathPart struct {
MapKey *string
Regex *PathPartRegex
ArrayIndex *PathPartArrayIndex
}

Expand All @@ -32,6 +33,10 @@ type PathPartArrayIndex struct {
All *bool `json:"allIndexes"`
}

type PathPartRegex struct {
Regex *string `json:"regex"`
}

func NewPathFromStrings(strs []string) Path {
var path Path
for _, str := range strs {
Expand Down Expand Up @@ -83,6 +88,15 @@ func (p Path) ContainsNonMapKeys() bool {
return false
}

func (p Path) ContainsArrayIndex() bool {
for _, part := range p {
if part.ArrayIndex != nil {
return true
}
}
return false
}

func NewPathPartFromString(str string) *PathPart {
return &PathPart{MapKey: &str}
}
Expand All @@ -104,6 +118,8 @@ func (p *PathPart) AsString() string {
return fmt.Sprintf("%d", *p.ArrayIndex.Index)
case p.ArrayIndex != nil && p.ArrayIndex.All != nil:
return "(all)"
case p.Regex != nil && p.Regex.Regex != nil:
return *p.Regex.Regex
default:
panic("Unknown path part")
}
Expand All @@ -112,10 +128,13 @@ func (p *PathPart) AsString() string {
func (p *PathPart) UnmarshalJSON(data []byte) error {
var str string
var idx PathPartArrayIndex
var regx PathPartRegex

switch {
case json.Unmarshal(data, &str) == nil:
p.MapKey = &str
case json.Unmarshal(data, &regx) == nil && regx.Regex != nil:
p.Regex = &regx
case json.Unmarshal(data, &idx) == nil:
p.ArrayIndex = &idx
default:
Expand Down
3 changes: 3 additions & 0 deletions pkg/kapp/resources/mod_string_map_append.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ func (t StringMapAppendMod) apply(obj interface{}, path Path) error {
panic(fmt.Sprintf("Unknown array index: %#v", part.ArrayIndex))
}

case part.Regex != nil:
panic("Regex in path part is only supported for rebaseRules.")

default:
panic(fmt.Sprintf("Unexpected path part: %#v", part))
}
Expand Down
Loading

0 comments on commit ec961f8

Please sign in to comment.