mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
fb90d0935d
Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
132 lines
3.1 KiB
Go
132 lines
3.1 KiB
Go
package jsonutils
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
|
|
datautils "github.com/kyverno/kyverno/pkg/utils/data"
|
|
)
|
|
|
|
// ActionData represents data available for action on current element
|
|
type ActionData struct {
|
|
Document interface{}
|
|
Element interface{}
|
|
Path string
|
|
}
|
|
|
|
// Action encapsulates the logic that must be performed for each
|
|
// JSON element
|
|
type Action func(data *ActionData) (interface{}, error)
|
|
|
|
// OnlyForLeafsAndKeys is an action modifier - apply action only for leafs and map keys
|
|
func OnlyForLeafsAndKeys(action Action) Action {
|
|
return func(data *ActionData) (interface{}, error) {
|
|
switch typed := data.Element.(type) {
|
|
case map[string]interface{}, []interface{}: // skip arrays and maps
|
|
return data.Element, nil
|
|
case Key:
|
|
data.Element = typed.Key
|
|
return action(data)
|
|
default: // leaf detected
|
|
return action(data)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Traversal is a type that encapsulates JSON traversal algorithm
|
|
// It traverses entire JSON structure applying some logic to its elements
|
|
type Traversal struct {
|
|
document interface{}
|
|
action Action
|
|
}
|
|
|
|
// NewTraversal creates JSON Traversal object
|
|
func NewTraversal(document interface{}, action Action) *Traversal {
|
|
return &Traversal{
|
|
document,
|
|
action,
|
|
}
|
|
}
|
|
|
|
// Key type is needed for traversal to specify the key
|
|
type Key struct {
|
|
Key string
|
|
}
|
|
|
|
// TraverseJSON performs a traverse of JSON document and applying
|
|
// action for each JSON element
|
|
func (t *Traversal) TraverseJSON() (interface{}, error) {
|
|
return t.traverseJSON(t.document, "")
|
|
}
|
|
|
|
func (t *Traversal) traverseJSON(element interface{}, path string) (interface{}, error) {
|
|
// perform an action
|
|
element, err := t.action(&ActionData{t.document, element, path})
|
|
if err != nil {
|
|
return element, err
|
|
}
|
|
|
|
// traverse further
|
|
switch typed := element.(type) {
|
|
case map[string]interface{}:
|
|
return t.traverseObject(datautils.CopyMap(typed), path)
|
|
|
|
case []interface{}:
|
|
return t.traverseList(slices.Clone(typed), path)
|
|
|
|
case []map[string]interface{}:
|
|
return t.traverseList(datautils.CopySliceOfMaps(typed), path)
|
|
|
|
case Key:
|
|
return typed.Key, nil
|
|
|
|
default:
|
|
return element, nil
|
|
}
|
|
}
|
|
|
|
func (t *Traversal) traverseObject(object map[string]interface{}, path string) (map[string]interface{}, error) {
|
|
for key, element := range object {
|
|
newKey, err := t.traverseJSON(Key{key}, path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var newKeyStr string
|
|
if newKey == nil {
|
|
newKeyStr = key
|
|
} else {
|
|
var ok bool
|
|
if newKeyStr, ok = newKey.(string); !ok {
|
|
return nil, fmt.Errorf("expected string after substituting variables in key \"%s\"", key)
|
|
}
|
|
}
|
|
|
|
value, err := t.traverseJSON(element, path+"/"+strings.ReplaceAll(key, "/", `\/`))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if newKeyStr != key {
|
|
// key was renamed after var substitution
|
|
// delete old key
|
|
object[newKeyStr] = value
|
|
delete(object, key)
|
|
} else {
|
|
object[key] = value
|
|
}
|
|
}
|
|
return object, nil
|
|
}
|
|
|
|
func (t *Traversal) traverseList(list []interface{}, path string) ([]interface{}, error) {
|
|
for idx, element := range list {
|
|
value, err := t.traverseJSON(element, path+"/"+strconv.Itoa(idx))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
list[idx] = value
|
|
}
|
|
return list, nil
|
|
}
|