Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Supporting regex in config path #636

Merged
merged 15 commits into from
Sep 5, 2023
Merged
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())
}
}

sethiyash marked this conversation as resolved.
Show resolved Hide resolved
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