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
101 changes: 84 additions & 17 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,36 @@ 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 {
// Make a copy of resource, to avoid modifications
// that may be done even in case when there is nothing to copy
updatedRes := res.DeepCopy()
sethiyash marked this conversation as resolved.
Show resolved Hide resolved
source, found := srcs[src]
if !found {
continue
}
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 +70,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 +103,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 +116,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 +137,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 +154,24 @@ 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 && part.Regex.Regex != nil:
matchedKeys, err := t.matchRegexWithSrcObj(*part.Regex.Regex, srcObj)
if err != nil {
return false, err
}
allUpdated := true
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 {
allUpdated = false
}
}
return allUpdated, nil
sethiyash marked this conversation as resolved.
Show resolved Hide resolved
default:
panic(fmt.Sprintf("Unexpected path part: %#v", part))
}
Expand Down Expand Up @@ -203,3 +252,21 @@ func (t FieldCopyMod) obtainValue(obj interface{}, path Path) (interface{}, bool

return obj, true, nil
}

func (t FieldCopyMod) matchRegexWithSrcObj(regexString string, srcObj interface{}) ([]string, error) {
sethiyash marked this conversation as resolved.
Show resolved Hide resolved
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
}
33 changes: 32 additions & 1 deletion pkg/kapp/resources/mod_field_remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package resources

import (
"fmt"
"regexp"
)

type FieldRemoveMod struct {
Expand Down Expand Up @@ -78,7 +79,6 @@ func (t FieldRemoveMod) apply(obj interface{}, path Path) error {
return err
}
}

sethiyash marked this conversation as resolved.
Show resolved Hide resolved
return nil // dealt with children, get out

case part.ArrayIndex.Index != nil:
Expand All @@ -96,6 +96,19 @@ func (t FieldRemoveMod) apply(obj interface{}, path Path) error {
default:
panic(fmt.Sprintf("Unknown array index: %#v", part.ArrayIndex))
}
case part.Regex != nil && part.Regex.Regex != nil:
matchedKeys, err := t.obtainMatchingRegexKeys(obj, *part.Regex.Regex)
sethiyash marked this conversation as resolved.
Show resolved Hide resolved
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 All @@ -104,3 +117,21 @@ func (t FieldRemoveMod) apply(obj interface{}, path Path) error {

panic("unreachable")
}

func (t FieldRemoveMod) obtainMatchingRegexKeys(obj interface{}, regexString string) ([]string, error) {
var matchedKeys []string
regex, err := regexp.Compile(regexString)
if err != nil {
return matchedKeys, err
}
typedObj, ok := obj.(map[string]interface{})
if !ok && typedObj != nil {
return matchedKeys, fmt.Errorf("Unexpected non-map found: %T", obj)
}
for key := range typedObj {
if regex.MatchString(key) {
matchedKeys = append(matchedKeys, key)
}
}
return matchedKeys, nil
}
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 && part.Regex.Regex != nil:
panic(fmt.Sprintf("Regex in path part: %#v is only supported for rebase rules.", part))

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 && part.Regex.Regex != nil:
panic(fmt.Sprintf("Regex in path part: %#v is only supported for rebase rules.", part))

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