mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-05 15:37:19 +00:00
optimize JSON context processing using in-memory maps (#8322)
* optimize JSON context processing using in memory maps Signed-off-by: Jim Bugwadia <jim@nirmata.com> * fix excessive logs Signed-off-by: Jim Bugwadia <jim@nirmata.com> * fix mutate resource diff Signed-off-by: Jim Bugwadia <jim@nirmata.com> * uncomment tests Signed-off-by: Jim Bugwadia <jim@nirmata.com> * copy resource, as it can be modified Signed-off-by: Jim Bugwadia <jim@nirmata.com> * clear prior resource to prevent mutating original Signed-off-by: Jim Bugwadia <jim@nirmata.com> * linter fix Signed-off-by: Jim Bugwadia <jim@nirmata.com> * fix ImageInfo to unstructured conversion Signed-off-by: Jim Bugwadia <jim@nirmata.com> * fix custom image extractors Signed-off-by: Jim Bugwadia <jim@nirmata.com> * do not update mutated resource in JSON context Signed-off-by: Jim Bugwadia <jim@nirmata.com> * address review comments Signed-off-by: Jim Bugwadia <jim@nirmata.com> --------- Signed-off-by: Jim Bugwadia <jim@nirmata.com> Signed-off-by: shuting <shuting@nirmata.com> Co-authored-by: Vishal Choudhary <sendtovishalchoudhary@gmail.com> Co-authored-by: shuting <shuting@nirmata.com>
This commit is contained in:
parent
a3279329eb
commit
46f02a8ba7
22 changed files with 488 additions and 174 deletions
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
|
@ -90,7 +90,7 @@
|
|||
"cwd": "${workspaceFolder}",
|
||||
"args": [
|
||||
"test",
|
||||
"./test/cli/",
|
||||
"test/cli"
|
||||
],
|
||||
}
|
||||
]
|
||||
|
|
|
@ -2,23 +2,28 @@ package context
|
|||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch/v5"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
|
||||
"github.com/kyverno/kyverno/pkg/config"
|
||||
"github.com/kyverno/kyverno/pkg/engine/jmespath"
|
||||
"github.com/kyverno/kyverno/pkg/engine/jsonutils"
|
||||
"github.com/kyverno/kyverno/pkg/logging"
|
||||
apiutils "github.com/kyverno/kyverno/pkg/utils/api"
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
var logger = logging.WithName("context")
|
||||
var (
|
||||
logger = logging.WithName("context")
|
||||
json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
ReservedKeys = regexp.MustCompile(`request|serviceAccountName|serviceAccountNamespace|element|elementIndex|@|images|image|([a-z_0-9]+\()[^{}]`)
|
||||
)
|
||||
|
||||
// EvalInterface is used to query and inspect context data
|
||||
// TODO: move to contextapi to prevent circular dependencies
|
||||
|
@ -26,6 +31,9 @@ type EvalInterface interface {
|
|||
// Query accepts a JMESPath expression and returns matching data
|
||||
Query(query string) (interface{}, error)
|
||||
|
||||
// Operation returns the admission operation i.e. "request.operation"
|
||||
QueryOperation() string
|
||||
|
||||
// HasChanged accepts a JMESPath expression and compares matching data in the
|
||||
// request.object and request.oldObject context fields. If the data has changed
|
||||
// it return `true`. If the data has not changed it returns false. If either
|
||||
|
@ -100,50 +108,70 @@ type Interface interface {
|
|||
|
||||
EvalInterface
|
||||
|
||||
// AddJSON merges the json with context
|
||||
addJSON(dataRaw []byte) error
|
||||
// AddJSON merges the json map with context
|
||||
addJSON(dataMap map[string]interface{}) error
|
||||
}
|
||||
|
||||
// Context stores the data resources as JSON
|
||||
type context struct {
|
||||
jp jmespath.Interface
|
||||
mutex sync.RWMutex
|
||||
jsonRaw []byte
|
||||
jsonRawCheckpoints [][]byte
|
||||
jsonRaw map[string]interface{}
|
||||
jsonRawCheckpoints []map[string]interface{}
|
||||
images map[string]map[string]apiutils.ImageInfo
|
||||
operation kyvernov1.AdmissionOperation
|
||||
deferred DeferredLoaders
|
||||
}
|
||||
|
||||
// NewContext returns a new context
|
||||
func NewContext(jp jmespath.Interface) Interface {
|
||||
return NewContextFromRaw(jp, []byte(`{}`))
|
||||
return NewContextFromRaw(jp, map[string]interface{}{})
|
||||
}
|
||||
|
||||
// NewContextFromRaw returns a new context initialized with raw data
|
||||
func NewContextFromRaw(jp jmespath.Interface, raw []byte) Interface {
|
||||
func NewContextFromRaw(jp jmespath.Interface, raw map[string]interface{}) Interface {
|
||||
return &context{
|
||||
jp: jp,
|
||||
jsonRaw: raw,
|
||||
jsonRawCheckpoints: make([][]byte, 0),
|
||||
jsonRawCheckpoints: make([]map[string]interface{}, 0),
|
||||
deferred: NewDeferredLoaders(),
|
||||
}
|
||||
}
|
||||
|
||||
// addJSON merges json data
|
||||
func (ctx *context) addJSON(dataRaw []byte) error {
|
||||
ctx.mutex.Lock()
|
||||
defer ctx.mutex.Unlock()
|
||||
json, err := jsonpatch.MergeMergePatches(ctx.jsonRaw, dataRaw)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to merge JSON data: %w", err)
|
||||
}
|
||||
ctx.jsonRaw = json
|
||||
func (ctx *context) addJSON(dataMap map[string]interface{}) error {
|
||||
mergeMaps(dataMap, ctx.jsonRaw)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx *context) QueryOperation() string {
|
||||
if ctx.operation != "" {
|
||||
return string(ctx.operation)
|
||||
}
|
||||
|
||||
if requestMap, val := ctx.jsonRaw["request"].(map[string]interface{}); val {
|
||||
if op, val := requestMap["operation"].(string); val {
|
||||
return op
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// AddRequest adds an admission request to context
|
||||
func (ctx *context) AddRequest(request admissionv1.AdmissionRequest) error {
|
||||
return addToContext(ctx, request, "request")
|
||||
// an AdmissionRequest needs to be marshaled / unmarshaled as
|
||||
// JSON to properly convert types of runtime.RawExtension
|
||||
mapObj, err := jsonutils.DocumentToUntyped(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := addToContext(ctx, mapObj, "request"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.operation = kyvernov1.AdmissionOperation(request.Operation)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx *context) AddVariable(key string, value interface{}) error {
|
||||
|
@ -181,31 +209,39 @@ func (ctx *context) ReplaceContextEntry(name string, dataRaw []byte) error {
|
|||
|
||||
// AddResource data at path: request.object
|
||||
func (ctx *context) AddResource(data map[string]interface{}) error {
|
||||
clearLeafValue(ctx.jsonRaw, "request", "object")
|
||||
return addToContext(ctx, data, "request", "object")
|
||||
}
|
||||
|
||||
// AddOldResource data at path: request.oldObject
|
||||
func (ctx *context) AddOldResource(data map[string]interface{}) error {
|
||||
clearLeafValue(ctx.jsonRaw, "request", "oldObject")
|
||||
return addToContext(ctx, data, "request", "oldObject")
|
||||
}
|
||||
|
||||
// AddTargetResource adds data at path: target
|
||||
func (ctx *context) SetTargetResource(data map[string]interface{}) error {
|
||||
if err := addToContext(ctx, nil, "target"); err != nil {
|
||||
logger.Error(err, "unable to replace target resource")
|
||||
return err
|
||||
}
|
||||
clearLeafValue(ctx.jsonRaw, "target")
|
||||
return addToContext(ctx, data, "target")
|
||||
}
|
||||
|
||||
// AddOperation data at path: request.operation
|
||||
func (ctx *context) AddOperation(data string) error {
|
||||
return addToContext(ctx, data, "request", "operation")
|
||||
if err := addToContext(ctx, data, "request", "operation"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.operation = kyvernov1.AdmissionOperation(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddUserInfo adds userInfo at path request.userInfo
|
||||
func (ctx *context) AddUserInfo(userRequestInfo kyvernov1beta1.RequestInfo) error {
|
||||
return addToContext(ctx, userRequestInfo, "request")
|
||||
if data, err := toUnstructured(&userRequestInfo); err == nil {
|
||||
return addToContext(ctx, data, "request")
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// AddServiceAccount removes prefix 'system:serviceaccount:' and namespace, then loads only SA name and SA namespace
|
||||
|
@ -225,33 +261,14 @@ func (ctx *context) AddServiceAccount(userName string) error {
|
|||
saName = groups[1]
|
||||
saNamespace = groups[0]
|
||||
}
|
||||
saNameObj := struct {
|
||||
SA string `json:"serviceAccountName"`
|
||||
}{
|
||||
SA: saName,
|
||||
data := map[string]interface{}{
|
||||
"serviceAccountName": saName,
|
||||
"serviceAccountNamespace": saNamespace,
|
||||
}
|
||||
saNameRaw, err := json.Marshal(saNameObj)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to marshal the SA")
|
||||
return err
|
||||
}
|
||||
if err := ctx.addJSON(saNameRaw); err != nil {
|
||||
if err := ctx.addJSON(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
saNsObj := struct {
|
||||
SA string `json:"serviceAccountNamespace"`
|
||||
}{
|
||||
SA: saNamespace,
|
||||
}
|
||||
saNsRaw, err := json.Marshal(saNsObj)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to marshal the SA namespace")
|
||||
return err
|
||||
}
|
||||
if err := ctx.addJSON(saNsRaw); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.V(4).Info("Adding service account", "service account name", saName, "service account namespace", saNamespace)
|
||||
return nil
|
||||
}
|
||||
|
@ -267,8 +284,8 @@ func (ctx *context) AddElement(data interface{}, index, nesting int) error {
|
|||
data = map[string]interface{}{
|
||||
"element": data,
|
||||
nestedElement: data,
|
||||
"elementIndex": index,
|
||||
nestedElementIndex: index,
|
||||
"elementIndex": int64(index),
|
||||
nestedElementIndex: int64(index),
|
||||
}
|
||||
return addToContext(ctx, data)
|
||||
}
|
||||
|
@ -291,13 +308,45 @@ func (ctx *context) AddImageInfos(resource *unstructured.Unstructured, cfg confi
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.addImageInfos(images)
|
||||
}
|
||||
|
||||
func (ctx *context) addImageInfos(images map[string]map[string]apiutils.ImageInfo) error {
|
||||
if len(images) == 0 {
|
||||
return nil
|
||||
}
|
||||
ctx.images = images
|
||||
utm, err := convertImagesToUnstructured(images)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logging.V(4).Info("updated image info", "images", images)
|
||||
return addToContext(ctx, images, "images")
|
||||
logging.V(4).Info("updated image info", "images", utm)
|
||||
return addToContext(ctx, utm, "images")
|
||||
}
|
||||
|
||||
func convertImagesToUnstructured(images map[string]map[string]apiutils.ImageInfo) (map[string]interface{}, error) {
|
||||
results := map[string]interface{}{}
|
||||
for containerType, v := range images {
|
||||
imgMap := map[string]interface{}{}
|
||||
for containerName := range v {
|
||||
imageInfo := v[containerName]
|
||||
img, err := toUnstructured(&imageInfo.ImageInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pointer interface{} = imageInfo.Pointer
|
||||
img["jsonPointer"] = pointer
|
||||
|
||||
imgMap[containerName] = img
|
||||
}
|
||||
|
||||
results[containerType] = imgMap
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (ctx *context) GenerateCustomImageInfo(resource *unstructured.Unstructured, imageExtractorConfigs kyvernov1.ImageExtractorConfigs, cfg config.Configuration) (map[string]map[string]apiutils.ImageInfo, error) {
|
||||
|
@ -306,12 +355,11 @@ func (ctx *context) GenerateCustomImageInfo(resource *unstructured.Unstructured,
|
|||
return nil, fmt.Errorf("failed to extract images: %w", err)
|
||||
}
|
||||
|
||||
if len(images) == 0 {
|
||||
logger.V(4).Info("no images found", "extractor", imageExtractorConfigs)
|
||||
return nil, nil
|
||||
if err := ctx.addImageInfos(images); err != nil {
|
||||
return nil, fmt.Errorf("failed to add images to context: %w", err)
|
||||
}
|
||||
|
||||
return images, addToContext(ctx, images, "images")
|
||||
return images, nil
|
||||
}
|
||||
|
||||
func (ctx *context) ImageInfo() map[string]map[string]apiutils.ImageInfo {
|
||||
|
@ -321,13 +369,23 @@ func (ctx *context) ImageInfo() map[string]map[string]apiutils.ImageInfo {
|
|||
// Checkpoint creates a copy of the current internal state and
|
||||
// pushes it into a stack of stored states.
|
||||
func (ctx *context) Checkpoint() {
|
||||
ctx.mutex.Lock()
|
||||
defer ctx.mutex.Unlock()
|
||||
jsonRawCheckpoint := make([]byte, len(ctx.jsonRaw))
|
||||
copy(jsonRawCheckpoint, ctx.jsonRaw)
|
||||
jsonRawCheckpoint := ctx.copyContext(ctx.jsonRaw)
|
||||
ctx.jsonRawCheckpoints = append(ctx.jsonRawCheckpoints, jsonRawCheckpoint)
|
||||
}
|
||||
|
||||
func (ctx *context) copyContext(in map[string]interface{}) map[string]interface{} {
|
||||
out := make(map[string]interface{}, len(in))
|
||||
for k, v := range in {
|
||||
if ReservedKeys.MatchString(k) {
|
||||
out[k] = v
|
||||
} else {
|
||||
out[k] = runtime.DeepCopyJSONValue(v)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// Restore sets the internal state to the last checkpoint, and removes the checkpoint.
|
||||
func (ctx *context) Restore() {
|
||||
ctx.reset(true)
|
||||
|
@ -344,20 +402,19 @@ func (ctx *context) reset(restore bool) {
|
|||
}
|
||||
}
|
||||
|
||||
func (ctx *context) resetCheckpoint(removeCheckpoint bool) bool {
|
||||
ctx.mutex.Lock()
|
||||
defer ctx.mutex.Unlock()
|
||||
|
||||
func (ctx *context) resetCheckpoint(restore bool) bool {
|
||||
if len(ctx.jsonRawCheckpoints) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
n := len(ctx.jsonRawCheckpoints) - 1
|
||||
jsonRawCheckpoint := ctx.jsonRawCheckpoints[n]
|
||||
ctx.jsonRaw = make([]byte, len(jsonRawCheckpoint))
|
||||
copy(ctx.jsonRaw, jsonRawCheckpoint)
|
||||
if removeCheckpoint {
|
||||
|
||||
if restore {
|
||||
ctx.jsonRawCheckpoints = ctx.jsonRawCheckpoints[:n]
|
||||
ctx.jsonRaw = jsonRawCheckpoint
|
||||
} else {
|
||||
ctx.jsonRaw = ctx.copyContext(jsonRawCheckpoint)
|
||||
}
|
||||
|
||||
return true
|
||||
|
|
|
@ -95,8 +95,8 @@ func TestDeferredLoaderMismatch(t *testing.T) {
|
|||
func newContext() *context {
|
||||
return &context{
|
||||
jp: jp,
|
||||
jsonRaw: []byte(`{}`),
|
||||
jsonRawCheckpoints: make([][]byte, 0),
|
||||
jsonRaw: make(map[string]interface{}),
|
||||
jsonRawCheckpoints: make([]map[string]interface{}, 0),
|
||||
deferred: NewDeferredLoaders(),
|
||||
}
|
||||
}
|
||||
|
@ -290,7 +290,7 @@ func TestDeferredCheckpointRestore(t *testing.T) {
|
|||
|
||||
func TestDeferredForloop(t *testing.T) {
|
||||
ctx := newContext()
|
||||
addDeferred(ctx, "value", -1)
|
||||
addDeferred(ctx, "value", float64(-1))
|
||||
|
||||
ctx.Checkpoint()
|
||||
for i := 0; i < 5; i++ {
|
||||
|
@ -299,7 +299,7 @@ func TestDeferredForloop(t *testing.T) {
|
|||
assert.Equal(t, float64(i-1), val)
|
||||
|
||||
ctx.Reset()
|
||||
mock, _ := addDeferred(ctx, "value", i)
|
||||
mock, _ := addDeferred(ctx, "value", float64(i))
|
||||
val, err = ctx.Query("value")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, float64(i), val)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
|
@ -25,13 +24,7 @@ func (ctx *context) Query(query string) (interface{}, error) {
|
|||
return nil, fmt.Errorf("incorrect query %s: %v", query, err)
|
||||
}
|
||||
// search
|
||||
ctx.mutex.RLock()
|
||||
defer ctx.mutex.RUnlock()
|
||||
var data interface{}
|
||||
if err := json.Unmarshal(ctx.jsonRaw, &data); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal context: %w", err)
|
||||
}
|
||||
result, err := queryPath.Search(data)
|
||||
result, err := queryPath.Search(ctx.jsonRaw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("JMESPath query failed: %w", err)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package context
|
|||
import (
|
||||
"testing"
|
||||
|
||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
)
|
||||
|
@ -65,3 +66,23 @@ func createTestContext(obj, oldObj string) Interface {
|
|||
ctx.AddRequest(request)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func TestQueryOperation(t *testing.T) {
|
||||
ctx := createTestContext(`{"a": {"b": 1, "c": 2}, "d": 3}`, `{"a": {"b": 2, "c": 2}, "d": 4}`)
|
||||
assert.Equal(t, ctx.QueryOperation(), "UPDATE")
|
||||
request := admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Delete,
|
||||
}
|
||||
|
||||
err := ctx.AddRequest(request)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, ctx.QueryOperation(), "DELETE")
|
||||
|
||||
err = ctx.AddOperation(string(kyvernov1.Connect))
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, ctx.QueryOperation(), "CONNECT")
|
||||
|
||||
err = ctx.AddRequest(admissionv1.AdmissionRequest{})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, ctx.QueryOperation(), "")
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
|
||||
"github.com/kyverno/kyverno/pkg/engine/jmespath"
|
||||
"github.com/kyverno/kyverno/pkg/engine/jsonutils"
|
||||
"github.com/kyverno/kyverno/pkg/engine/variables"
|
||||
)
|
||||
|
||||
|
@ -58,7 +59,7 @@ func (vl *variableLoader) loadVariable() (err error) {
|
|||
|
||||
var defaultValue interface{} = nil
|
||||
if entry.Variable.Default != nil {
|
||||
value, err := variables.DocumentToUntyped(entry.Variable.Default)
|
||||
value, err := jsonutils.DocumentToUntyped(entry.Variable.Default)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid default for variable %s", entry.Name)
|
||||
}
|
||||
|
@ -71,7 +72,7 @@ func (vl *variableLoader) loadVariable() (err error) {
|
|||
|
||||
var output interface{} = defaultValue
|
||||
if entry.Variable.Value != nil {
|
||||
value, _ := variables.DocumentToUntyped(entry.Variable.Value)
|
||||
value, _ := jsonutils.DocumentToUntyped(entry.Variable.Value)
|
||||
variable, err := variables.SubstituteAll(logger, ctx, value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to substitute variables in context entry %s %s: %v", entry.Name, entry.Variable.Value, err)
|
||||
|
|
|
@ -64,6 +64,16 @@ func (ctx *MockContext) Query(query string) (interface{}, error) {
|
|||
}
|
||||
}
|
||||
|
||||
func (ctx *MockContext) QueryOperation() string {
|
||||
if op, err := ctx.Query("request.operation"); err != nil {
|
||||
if op != nil {
|
||||
return op.(string)
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (ctx *MockContext) isVariableDefined(variable string) bool {
|
||||
for _, pattern := range ctx.getVariables() {
|
||||
if wildcard.Match(pattern, variable) {
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// AddJSONObject merges json data
|
||||
func AddJSONObject(ctx Interface, data interface{}) error {
|
||||
jsonBytes, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.addJSON(jsonBytes)
|
||||
func AddJSONObject(ctx Interface, data map[string]interface{}) error {
|
||||
return ctx.addJSON(data)
|
||||
}
|
||||
|
||||
func AddResource(ctx Interface, dataRaw []byte) error {
|
||||
|
@ -32,19 +30,83 @@ func AddOldResource(ctx Interface, dataRaw []byte) error {
|
|||
}
|
||||
|
||||
func addToContext(ctx *context, data interface{}, tags ...string) error {
|
||||
dataRaw, err := json.Marshal(push(data, tags...))
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to marshal the resource")
|
||||
if v, err := convertStructs(data); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
dataRaw := push(v, tags...)
|
||||
return ctx.addJSON(dataRaw)
|
||||
}
|
||||
}
|
||||
|
||||
func push(data interface{}, tags ...string) interface{} {
|
||||
func clearLeafValue(data map[string]interface{}, tags ...string) bool {
|
||||
if len(tags) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := 0; i < len(tags); i++ {
|
||||
k := tags[i]
|
||||
if i == len(tags)-1 {
|
||||
delete(data, k)
|
||||
return true
|
||||
}
|
||||
|
||||
if nextMap, ok := data[k].(map[string]interface{}); ok {
|
||||
data = nextMap
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// convertStructs converts structs, and pointers-to-structs, to map[string]interface{}
|
||||
func convertStructs(value interface{}) (interface{}, error) {
|
||||
if value != nil {
|
||||
v := reflect.ValueOf(value)
|
||||
if v.Kind() == reflect.Struct {
|
||||
return toUnstructured(value)
|
||||
}
|
||||
|
||||
if v.Kind() == reflect.Ptr {
|
||||
ptrVal := v.Elem()
|
||||
if ptrVal.Kind() == reflect.Struct {
|
||||
return toUnstructured(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func push(data interface{}, tags ...string) map[string]interface{} {
|
||||
for i := len(tags) - 1; i >= 0; i-- {
|
||||
data = map[string]interface{}{
|
||||
tags[i]: data,
|
||||
}
|
||||
}
|
||||
return data
|
||||
|
||||
return data.(map[string]interface{})
|
||||
}
|
||||
|
||||
// mergeMaps merges srcMap entries into destMap
|
||||
func mergeMaps(srcMap, destMap map[string]interface{}) {
|
||||
for k, v := range srcMap {
|
||||
if nextSrcMap, ok := v.(map[string]interface{}); ok {
|
||||
if nextDestMap, ok := destMap[k].(map[string]interface{}); ok {
|
||||
mergeMaps(nextSrcMap, nextDestMap)
|
||||
} else {
|
||||
destMap[k] = nextSrcMap
|
||||
}
|
||||
} else {
|
||||
destMap[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// toUnstructured converts a struct with JSON tags to a map[string]interface{}
|
||||
func toUnstructured(typedStruct interface{}) (map[string]interface{}, error) {
|
||||
converter := runtime.DefaultUnstructuredConverter
|
||||
u, err := converter.ToUnstructured(typedStruct)
|
||||
return u, err
|
||||
}
|
||||
|
|
144
pkg/engine/context/utils_test.go
Normal file
144
pkg/engine/context/utils_test.go
Normal file
|
@ -0,0 +1,144 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMergeMaps(t *testing.T) {
|
||||
map1 := map[string]interface{}{
|
||||
"strVal": "bar1",
|
||||
"strVal2": "bar2",
|
||||
"intVal": 2,
|
||||
"arrayVal": []string{"1", "2", "3"},
|
||||
"mapVal": map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
"mapVal2": map[string]interface{}{
|
||||
"foo2": map[string]interface{}{
|
||||
"foo3": 3,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
map2 := map[string]interface{}{
|
||||
"strVal": "bar2",
|
||||
"intVal": 3,
|
||||
"intVal2": 3,
|
||||
"arrayVal": []string{"1", "2", "3", "4"},
|
||||
"mapVal": map[string]interface{}{
|
||||
"foo1": "bar1",
|
||||
"foo2": "bar2",
|
||||
},
|
||||
}
|
||||
|
||||
mergeMaps(map1, map2)
|
||||
|
||||
assert.Equal(t, "bar1", map2["strVal"])
|
||||
assert.Equal(t, "bar2", map2["strVal2"])
|
||||
assert.Equal(t, 2, map2["intVal"])
|
||||
assert.Equal(t, 3, map2["intVal2"])
|
||||
assert.Equal(t, []string{"1", "2", "3"}, map2["arrayVal"])
|
||||
assert.Equal(t, map[string]interface{}{"foo": "bar", "foo1": "bar1", "foo2": "bar2"}, map2["mapVal"])
|
||||
assert.Equal(t, map1["mapVal2"], map2["mapVal2"])
|
||||
|
||||
requestObj := map[string]interface{}{
|
||||
"request": map[string]interface{}{
|
||||
"object": map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ctxMap := map[string]interface{}{}
|
||||
mergeMaps(requestObj, ctxMap)
|
||||
|
||||
r := ctxMap["request"].(map[string]interface{})
|
||||
o := r["object"].(map[string]interface{})
|
||||
assert.Equal(t, o["foo"], "bar")
|
||||
|
||||
requestObj2 := map[string]interface{}{
|
||||
"request": map[string]interface{}{
|
||||
"object": map[string]interface{}{
|
||||
"foo": "bar2",
|
||||
"foo2": "bar2",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mergeMaps(requestObj2, ctxMap)
|
||||
r2 := ctxMap["request"].(map[string]interface{})
|
||||
o2 := r2["object"].(map[string]interface{})
|
||||
assert.Equal(t, "bar2", o2["foo"])
|
||||
assert.Equal(t, "bar2", o2["foo2"])
|
||||
|
||||
request3 := map[string]interface{}{
|
||||
"request": map[string]interface{}{
|
||||
"userInfo": "user1",
|
||||
},
|
||||
}
|
||||
|
||||
mergeMaps(request3, ctxMap)
|
||||
r3 := ctxMap["request"].(map[string]interface{})
|
||||
o3 := r3["object"].(map[string]interface{})
|
||||
assert.NotNil(t, o3)
|
||||
assert.Equal(t, "bar2", o2["foo"])
|
||||
assert.Equal(t, "bar2", o2["foo2"])
|
||||
assert.Equal(t, "user1", r3["userInfo"])
|
||||
}
|
||||
|
||||
func TestStructToUntypedMap(t *testing.T) {
|
||||
type SampleStuct struct {
|
||||
Name string `json:"name"`
|
||||
ID int32 `json:"identifier"`
|
||||
}
|
||||
|
||||
sample := &SampleStuct{
|
||||
Name: "user1",
|
||||
ID: 12345,
|
||||
}
|
||||
|
||||
result, err := toUnstructured(sample)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, "user1", result["name"])
|
||||
assert.Equal(t, int64(12345), result["identifier"])
|
||||
}
|
||||
|
||||
func TestClearLeaf(t *testing.T) {
|
||||
request := map[string]interface{}{
|
||||
"request": map[string]interface{}{
|
||||
"object": map[string]interface{}{
|
||||
"key1": "val1",
|
||||
"key2": "val2",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result := clearLeafValue(request, "request", "object", "key1")
|
||||
assert.True(t, result)
|
||||
|
||||
r := request["request"].(map[string]interface{})
|
||||
o := r["object"].(map[string]interface{})
|
||||
_, exists := o["key1"]
|
||||
assert.Equal(t, false, exists)
|
||||
|
||||
_, exists = o["key2"]
|
||||
assert.Equal(t, true, exists)
|
||||
|
||||
result = clearLeafValue(request, "request", "object", "key3")
|
||||
assert.True(t, result)
|
||||
|
||||
_, exists = o["key3"]
|
||||
assert.Equal(t, false, exists)
|
||||
|
||||
result = clearLeafValue(request, "request", "object-bad", "key3")
|
||||
assert.Equal(t, false, result)
|
||||
|
||||
result = clearLeafValue(request, "request", "object")
|
||||
assert.True(t, result)
|
||||
|
||||
_, exists = r["object"]
|
||||
assert.Equal(t, false, exists)
|
||||
}
|
|
@ -66,6 +66,7 @@ func (f *forEachMutator) mutateElements(ctx context.Context, foreach kyvernov1.F
|
|||
defer f.policyContext.JSONContext().Restore()
|
||||
|
||||
patchedResource := f.resource
|
||||
|
||||
reverse := false
|
||||
// if it's a patch strategic merge, reverse by default
|
||||
if foreach.RawPatchStrategicMerge != nil {
|
||||
|
|
22
pkg/engine/jsonutils/convert.go
Normal file
22
pkg/engine/jsonutils/convert.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
package jsonutils
|
||||
|
||||
import jsoniter "github.com/json-iterator/go"
|
||||
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
|
||||
// DocumentToUntyped converts a typed object to JSON data
|
||||
// i.e. string, []interface{}, map[string]interface{}
|
||||
func DocumentToUntyped(doc interface{}) (interface{}, error) {
|
||||
jsonDoc, err := json.Marshal(doc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var untyped interface{}
|
||||
err = json.Unmarshal(jsonDoc, &untyped)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return untyped, nil
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package jsonutils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/assert"
|
||||
|
|
|
@ -47,7 +47,9 @@ func Mutate(rule *kyvernov1.Rule, ctx context.Interface, resource unstructured.U
|
|||
if patcher == nil {
|
||||
return NewErrorResponse("empty mutate rule", nil)
|
||||
}
|
||||
resourceBytes, err := resource.MarshalJSON()
|
||||
|
||||
patchedResource := resource.DeepCopy()
|
||||
resourceBytes, err := patchedResource.MarshalJSON()
|
||||
if err != nil {
|
||||
return NewErrorResponse("failed to marshal resource", err)
|
||||
}
|
||||
|
@ -58,19 +60,15 @@ func Mutate(rule *kyvernov1.Rule, ctx context.Interface, resource unstructured.U
|
|||
if strings.TrimSpace(string(resourceBytes)) == strings.TrimSpace(string(patchedBytes)) {
|
||||
return NewResponse(engineapi.RuleStatusSkip, resource, "no patches applied")
|
||||
}
|
||||
if err := resource.UnmarshalJSON(patchedBytes); err != nil {
|
||||
if err := patchedResource.UnmarshalJSON(patchedBytes); err != nil {
|
||||
return NewErrorResponse("failed to unmarshal patched resource", err)
|
||||
}
|
||||
if rule.IsMutateExisting() {
|
||||
if err := ctx.SetTargetResource(resource.Object); err != nil {
|
||||
return NewErrorResponse("failed to update patched resource in the JSON context", err)
|
||||
}
|
||||
} else {
|
||||
if err := ctx.AddResource(resource.Object); err != nil {
|
||||
return NewErrorResponse("failed to update patched resource in the JSON context", err)
|
||||
if err := ctx.SetTargetResource(patchedResource.Object); err != nil {
|
||||
return NewErrorResponse("failed to update patched target resource in the JSON context", err)
|
||||
}
|
||||
}
|
||||
return NewResponse(engineapi.RuleStatusPass, resource, "resource patched")
|
||||
return NewResponse(engineapi.RuleStatusPass, *patchedResource, "resource patched")
|
||||
}
|
||||
|
||||
func ForEach(name string, foreach kyvernov1.ForEachMutation, policyContext engineapi.PolicyContext, resource unstructured.Unstructured, element interface{}, logger logr.Logger) *Response {
|
||||
|
@ -83,7 +81,9 @@ func ForEach(name string, foreach kyvernov1.ForEachMutation, policyContext engin
|
|||
if patcher == nil {
|
||||
return NewErrorResponse("empty mutate rule", nil)
|
||||
}
|
||||
resourceBytes, err := resource.MarshalJSON()
|
||||
|
||||
patchedResource := resource.DeepCopy()
|
||||
resourceBytes, err := patchedResource.MarshalJSON()
|
||||
if err != nil {
|
||||
return NewErrorResponse("failed to marshal resource", err)
|
||||
}
|
||||
|
@ -94,13 +94,11 @@ func ForEach(name string, foreach kyvernov1.ForEachMutation, policyContext engin
|
|||
if strings.TrimSpace(string(resourceBytes)) == strings.TrimSpace(string(patchedBytes)) {
|
||||
return NewResponse(engineapi.RuleStatusSkip, resource, "no patches applied")
|
||||
}
|
||||
if err := resource.UnmarshalJSON(patchedBytes); err != nil {
|
||||
if err := patchedResource.UnmarshalJSON(patchedBytes); err != nil {
|
||||
return NewErrorResponse("failed to unmarshal patched resource", err)
|
||||
} else if err := ctx.AddResource(resource.Object); err != nil {
|
||||
return NewErrorResponse("failed to update patched resource in the JSON context", err)
|
||||
} else {
|
||||
return NewResponse(engineapi.RuleStatusPass, resource, "resource patched")
|
||||
}
|
||||
|
||||
return NewResponse(engineapi.RuleStatusPass, *patchedResource, "resource patched")
|
||||
}
|
||||
|
||||
func substituteAllInForEach(fe kyvernov1.ForEachMutation, ctx context.Interface, logger logr.Logger) (*kyvernov1.ForEachMutation, error) {
|
||||
|
|
|
@ -193,6 +193,7 @@ func NewPolicyContext(
|
|||
configuration config.Configuration,
|
||||
) (*PolicyContext, error) {
|
||||
enginectx := enginectx.NewContext(jp)
|
||||
|
||||
if operation != kyvernov1.Delete {
|
||||
if err := enginectx.AddResource(resource.Object); err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
|
||||
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
||||
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
|
||||
"github.com/kyverno/kyverno/pkg/engine/variables"
|
||||
"github.com/kyverno/kyverno/pkg/engine/jsonutils"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
|
@ -31,7 +31,7 @@ func InvertedElement(elements []interface{}) {
|
|||
}
|
||||
|
||||
func AddElementToContext(ctx engineapi.PolicyContext, element interface{}, index, nesting int, elementScope *bool) error {
|
||||
data, err := variables.DocumentToUntyped(element)
|
||||
data, err := jsonutils.DocumentToUntyped(element)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -14,8 +14,16 @@ import (
|
|||
)
|
||||
|
||||
func IsDeleteRequest(ctx engineapi.PolicyContext) bool {
|
||||
if ctx == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if op := ctx.Operation(); string(op) != "" {
|
||||
return op == kyvernov1.Delete
|
||||
}
|
||||
|
||||
// if the NewResource is empty, the request is a DELETE
|
||||
newResource := ctx.NewResource()
|
||||
// if the OldResource is not empty, and the NewResource is empty, the request is a DELETE
|
||||
return IsEmptyUnstructured(&newResource)
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
|
||||
"github.com/kyverno/kyverno/pkg/config"
|
||||
|
@ -53,7 +52,7 @@ func testValidate(
|
|||
func newPolicyContext(
|
||||
t *testing.T,
|
||||
resource unstructured.Unstructured,
|
||||
operation kyverno.AdmissionOperation,
|
||||
operation kyvernov1.AdmissionOperation,
|
||||
admissionInfo *kyvernov1beta1.RequestInfo,
|
||||
) *PolicyContext {
|
||||
t.Helper()
|
||||
|
@ -2145,7 +2144,7 @@ func executeTest(t *testing.T, test testCase) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pc := newPolicyContext(t, newR, kyverno.AdmissionOperation(request.Operation), &userInfo).
|
||||
pc := newPolicyContext(t, newR, kyvernov1.AdmissionOperation(request.Operation), &userInfo).
|
||||
WithPolicy(&policy).
|
||||
WithOldResource(oldR)
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package variables
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
package variables
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
gojmespath "github.com/kyverno/go-jmespath"
|
||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/engine/anchor"
|
||||
|
@ -19,6 +19,8 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/utils/jsonpointer"
|
||||
)
|
||||
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
|
||||
// ReplaceAllVars replaces all variables with the value defined in the replacement function
|
||||
// This is used to avoid validation errors
|
||||
func ReplaceAllVars(src string, repl func(string) string) string {
|
||||
|
@ -58,7 +60,7 @@ func SubstituteAll(log logr.Logger, ctx context.EvalInterface, document interfac
|
|||
}
|
||||
|
||||
func SubstituteAllInPreconditions(log logr.Logger, ctx context.EvalInterface, document interface{}) (interface{}, error) {
|
||||
untypedDoc, err := DocumentToUntyped(document)
|
||||
untypedDoc, err := jsonUtils.DocumentToUntyped(document)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -66,7 +68,7 @@ func SubstituteAllInPreconditions(log logr.Logger, ctx context.EvalInterface, do
|
|||
}
|
||||
|
||||
func SubstituteAllInType[T any](log logr.Logger, ctx context.EvalInterface, t *T) (*T, error) {
|
||||
untyped, err := DocumentToUntyped(t)
|
||||
untyped, err := jsonUtils.DocumentToUntyped(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -99,23 +101,6 @@ func SubstituteAllInRule(log logr.Logger, ctx context.EvalInterface, rule kyvern
|
|||
return *result, nil
|
||||
}
|
||||
|
||||
// DocumentToUntyped converts a typed object to JSON data i.e.
|
||||
// string, []interface{}, map[string]interface{}
|
||||
func DocumentToUntyped(doc interface{}) (interface{}, error) {
|
||||
jsonDoc, err := json.Marshal(doc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var untyped interface{}
|
||||
err = json.Unmarshal(jsonDoc, &untyped)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return untyped, nil
|
||||
}
|
||||
|
||||
func untypedToTyped[T any](untyped interface{}) (*T, error) {
|
||||
jsonRule, err := json.Marshal(untyped)
|
||||
if err != nil {
|
||||
|
@ -184,7 +169,7 @@ func substituteAll(log logr.Logger, ctx context.EvalInterface, document interfac
|
|||
func SubstituteAllForceMutate(log logr.Logger, ctx context.Interface, typedRule kyvernov1.Rule) (_ kyvernov1.Rule, err error) {
|
||||
var rule interface{}
|
||||
|
||||
rule, err = DocumentToUntyped(typedRule)
|
||||
rule, err = jsonUtils.DocumentToUntyped(typedRule)
|
||||
if err != nil {
|
||||
return kyvernov1.Rule{}, err
|
||||
}
|
||||
|
@ -407,10 +392,11 @@ func isDeleteRequest(ctx context.EvalInterface) bool {
|
|||
if ctx == nil {
|
||||
return false
|
||||
}
|
||||
operation, err := ctx.Query("request.operation")
|
||||
if err == nil && operation == "DELETE" {
|
||||
return true
|
||||
|
||||
if op := ctx.QueryOperation(); op != "" {
|
||||
return op == "DELETE"
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package variables
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -208,11 +206,6 @@ func Test_subVars_with_JMESPath_At(t *testing.T) {
|
|||
}`)
|
||||
|
||||
var err error
|
||||
|
||||
expected := new(bytes.Buffer)
|
||||
err = json.Compact(expected, expectedRaw)
|
||||
assert.NilError(t, err)
|
||||
|
||||
var pattern, resource interface{}
|
||||
err = json.Unmarshal(patternMap, &pattern)
|
||||
assert.NilError(t, err)
|
||||
|
@ -227,7 +220,7 @@ func Test_subVars_with_JMESPath_At(t *testing.T) {
|
|||
assert.NilError(t, err)
|
||||
out, err := json.Marshal(output)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, string(out), expected.String())
|
||||
assert.Equal(t, string(out), compact(t, expectedRaw))
|
||||
}
|
||||
|
||||
func Test_subVars_withRegexMatch(t *testing.T) {
|
||||
|
@ -268,10 +261,6 @@ func Test_subVars_withRegexMatch(t *testing.T) {
|
|||
|
||||
var err error
|
||||
|
||||
expected := new(bytes.Buffer)
|
||||
err = json.Compact(expected, expectedRaw)
|
||||
assert.NilError(t, err)
|
||||
|
||||
var pattern, resource interface{}
|
||||
err = json.Unmarshal(patternMap, &pattern)
|
||||
assert.NilError(t, err)
|
||||
|
@ -286,7 +275,7 @@ func Test_subVars_withRegexMatch(t *testing.T) {
|
|||
assert.NilError(t, err)
|
||||
out, err := json.Marshal(output)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, string(out), expected.String())
|
||||
assert.Equal(t, string(out), compact(t, expectedRaw))
|
||||
}
|
||||
|
||||
func Test_subVars_withMerge(t *testing.T) {
|
||||
|
@ -298,10 +287,6 @@ func Test_subVars_withMerge(t *testing.T) {
|
|||
|
||||
var err error
|
||||
|
||||
expected := new(bytes.Buffer)
|
||||
err = json.Compact(expected, expectedRaw)
|
||||
assert.NilError(t, err)
|
||||
|
||||
var pattern, resource interface{}
|
||||
err = json.Unmarshal(patternMap, &pattern)
|
||||
assert.NilError(t, err)
|
||||
|
@ -316,7 +301,17 @@ func Test_subVars_withMerge(t *testing.T) {
|
|||
assert.NilError(t, err)
|
||||
out, err := json.Marshal(output)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, string(out), expected.String())
|
||||
assert.Equal(t, string(out), compact(t, expectedRaw))
|
||||
}
|
||||
|
||||
func compact(t *testing.T, in []byte) string {
|
||||
var tmp map[string]interface{}
|
||||
err := json.Unmarshal(in, &tmp)
|
||||
assert.NilError(t, err)
|
||||
|
||||
out, err := json.Marshal(tmp)
|
||||
assert.NilError(t, err)
|
||||
return string(out)
|
||||
}
|
||||
|
||||
func Test_subVars_withRegexReplaceAll(t *testing.T) {
|
||||
|
@ -393,10 +388,12 @@ func Test_ReplacingPathWhenDeleting(t *testing.T) {
|
|||
var pattern interface{}
|
||||
var err error
|
||||
err = json.Unmarshal(patternRaw, &pattern)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
ctx := context.NewContextFromRaw(jp, resourceRaw)
|
||||
assert.NilError(t, err)
|
||||
|
||||
ctxMap, err := unmarshalToMap(resourceRaw)
|
||||
assert.NilError(t, err)
|
||||
|
||||
ctx := context.NewContextFromRaw(jp, ctxMap)
|
||||
assert.NilError(t, err)
|
||||
|
||||
pattern, err = SubstituteAll(logr.Discard(), ctx, pattern)
|
||||
|
@ -405,6 +402,15 @@ func Test_ReplacingPathWhenDeleting(t *testing.T) {
|
|||
assert.Equal(t, fmt.Sprintf("%v", pattern), "bar")
|
||||
}
|
||||
|
||||
func unmarshalToMap(jsonBytes []byte) (map[string]interface{}, error) {
|
||||
var data map[string]interface{}
|
||||
if err := json.Unmarshal(jsonBytes, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func Test_ReplacingNestedVariableWhenDeleting(t *testing.T) {
|
||||
patternRaw := []byte(`"{{request.object.metadata.annotations.{{request.object.metadata.annotations.targetnew}}}}"`)
|
||||
|
||||
|
@ -428,12 +434,12 @@ func Test_ReplacingNestedVariableWhenDeleting(t *testing.T) {
|
|||
var pattern interface{}
|
||||
var err error
|
||||
err = json.Unmarshal(patternRaw, &pattern)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
ctx := context.NewContextFromRaw(jp, resourceRaw)
|
||||
assert.NilError(t, err)
|
||||
|
||||
ctxMap, err := unmarshalToMap(resourceRaw)
|
||||
assert.NilError(t, err)
|
||||
|
||||
ctx := context.NewContextFromRaw(jp, ctxMap)
|
||||
pattern, err = SubstituteAll(logr.Discard(), ctx, pattern)
|
||||
assert.NilError(t, err)
|
||||
|
||||
|
@ -633,7 +639,10 @@ func Test_variableSubstitution_array(t *testing.T) {
|
|||
err := json.Unmarshal(ruleRaw, &rule)
|
||||
assert.NilError(t, err)
|
||||
|
||||
ctx := context.NewContextFromRaw(jp, configmapRaw)
|
||||
ctxMap, err := unmarshalToMap(configmapRaw)
|
||||
assert.NilError(t, err)
|
||||
|
||||
ctx := context.NewContextFromRaw(jp, ctxMap)
|
||||
context.AddResource(ctx, resourceRaw)
|
||||
|
||||
vars, err := SubstituteAllInRule(logr.Discard(), ctx, rule)
|
||||
|
@ -1173,7 +1182,11 @@ func Test_ReplacingEscpNestedVariableWhenDeleting(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
ctx := context.NewContextFromRaw(jp, resourceRaw)
|
||||
|
||||
ctxMap, err := unmarshalToMap(resourceRaw)
|
||||
assert.NilError(t, err)
|
||||
|
||||
ctx := context.NewContextFromRaw(jp, ctxMap)
|
||||
assert.NilError(t, err)
|
||||
|
||||
pattern, err = SubstituteAll(logr.Discard(), ctx, pattern)
|
||||
|
|
|
@ -34,7 +34,7 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
allowedVariables = regexp.MustCompile(`request\.|serviceAccountName|serviceAccountNamespace|element|elementIndex|@|images|images\.|image\.|([a-z_0-9]+\()[^{}]`)
|
||||
allowedVariables = enginecontext.ReservedKeys
|
||||
allowedVariablesBackground = regexp.MustCompile(`request\.|element|elementIndex|@|images|images\.|image\.|([a-z_0-9]+\()[^{}]`)
|
||||
allowedVariablesInTarget = regexp.MustCompile(`request\.|serviceAccountName|serviceAccountNamespace|element|elementIndex|@|images|images\.|image\.|target\.|([a-z_0-9]+\()[^{}]`)
|
||||
allowedVariablesBackgroundInTarget = regexp.MustCompile(`request\.|element|elementIndex|@|images|images\.|image\.|target\.|([a-z_0-9]+\()[^{}]`)
|
||||
|
|
|
@ -80,5 +80,5 @@ spec:
|
|||
mutateDigest: false
|
||||
required: true
|
||||
useCache: true
|
||||
verifyDigest: true
|
||||
verifyDigest: false
|
||||
validationFailureAction: Enforce
|
||||
|
|
Loading…
Add table
Reference in a new issue