mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
2289720ba0
* add support for shallow substitution Signed-off-by: Jim Bugwadia <jim@nirmata.com> * linter issue Signed-off-by: Jim Bugwadia <jim@nirmata.com> * exclude EphemeralReport and ClusterEphemeralReport Signed-off-by: Jim Bugwadia <jim@nirmata.com> * update codegen Signed-off-by: Jim Bugwadia <jim@nirmata.com> --------- Signed-off-by: Jim Bugwadia <jim@nirmata.com> Co-authored-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
1260 lines
29 KiB
Go
1260 lines
29 KiB
Go
package variables
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/go-logr/logr"
|
|
v1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
|
"github.com/kyverno/kyverno/pkg/engine/context"
|
|
ju "github.com/kyverno/kyverno/pkg/engine/jsonutils"
|
|
"gotest.tools/assert"
|
|
)
|
|
|
|
func Test_subVars_success(t *testing.T) {
|
|
patternMap := []byte(`
|
|
{
|
|
"kind": "{{request.object.metadata.name}}",
|
|
"name": "ns-owner-{{request.object.metadata.name}}",
|
|
"data": {
|
|
"rules": [
|
|
{
|
|
"apiGroups": [
|
|
"{{request.object.metadata.name}}"
|
|
],
|
|
"resources": [
|
|
"namespaces"
|
|
],
|
|
"verbs": [
|
|
"*"
|
|
],
|
|
"resourceNames": [
|
|
"{{request.object.metadata.name}}"
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
resourceRaw := []byte(`
|
|
{
|
|
"metadata": {
|
|
"name": "temp",
|
|
"namespace": "n1"
|
|
},
|
|
"spec": {
|
|
"namespace": "n1",
|
|
"name": "temp1"
|
|
}
|
|
}
|
|
`)
|
|
|
|
var pattern, resource interface{}
|
|
var err error
|
|
err = json.Unmarshal(patternMap, &pattern)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
err = json.Unmarshal(resourceRaw, &resource)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
// context
|
|
ctx := context.NewContext(jp)
|
|
err = context.AddResource(ctx, resourceRaw)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
if _, err := SubstituteAll(logr.Discard(), ctx, pattern); err != nil {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
|
|
func Test_subVars_failed(t *testing.T) {
|
|
patternMap := []byte(`
|
|
{
|
|
"kind": "{{request.object.metadata.name1}}",
|
|
"name": "ns-owner-{{request.object.metadata.name}}",
|
|
"data": {
|
|
"rules": [
|
|
{
|
|
"apiGroups": [
|
|
"{{request.object.metadata.name}}"
|
|
],
|
|
"resources": [
|
|
"namespaces"
|
|
],
|
|
"verbs": [
|
|
"*"
|
|
],
|
|
"resourceNames": [
|
|
"{{request.object.metadata.name1}}"
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`)
|
|
|
|
resourceRaw := []byte(`
|
|
{
|
|
"metadata": {
|
|
"name": "temp",
|
|
"namespace": "n1"
|
|
},
|
|
"spec": {
|
|
"namespace": "n1",
|
|
"name": "temp1"
|
|
}
|
|
}
|
|
`)
|
|
|
|
var pattern, resource interface{}
|
|
var err error
|
|
err = json.Unmarshal(patternMap, &pattern)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
err = json.Unmarshal(resourceRaw, &resource)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
// context
|
|
ctx := context.NewContext(jp)
|
|
err = context.AddResource(ctx, resourceRaw)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
if _, err := SubstituteAll(logr.Discard(), ctx, pattern); err == nil {
|
|
t.Error("error is expected")
|
|
}
|
|
}
|
|
|
|
func Test_subVars_with_JMESPath_At(t *testing.T) {
|
|
patternMap := []byte(`{
|
|
"mutate": {
|
|
"overlay": {
|
|
"spec": {
|
|
"kind": "{{@}}",
|
|
"data": {
|
|
"rules": [
|
|
{
|
|
"apiGroups": [
|
|
"{{request.object.metadata.name}}"
|
|
],
|
|
"resources": [
|
|
"namespaces"
|
|
],
|
|
"verbs": [
|
|
"*"
|
|
],
|
|
"resourceNames": [
|
|
"{{request.object.metadata.name}}"
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}`)
|
|
|
|
resourceRaw := []byte(`
|
|
{
|
|
"metadata": {
|
|
"name": "temp",
|
|
"namespace": "n1"
|
|
},
|
|
"spec": {
|
|
"kind": "foo",
|
|
"namespace": "n1",
|
|
"name": "temp1"
|
|
}
|
|
}
|
|
`)
|
|
|
|
expectedRaw := []byte(`{
|
|
"mutate":{
|
|
"overlay":{
|
|
"spec":{
|
|
"data":{
|
|
"rules":[
|
|
{
|
|
"apiGroups":[
|
|
"temp"
|
|
],
|
|
"resourceNames":[
|
|
"temp"
|
|
],
|
|
"resources":[
|
|
"namespaces"
|
|
],
|
|
"verbs":[
|
|
"*"
|
|
]
|
|
}
|
|
]
|
|
},
|
|
"kind":"foo"
|
|
}
|
|
}
|
|
}
|
|
}`)
|
|
|
|
var err error
|
|
var pattern, resource interface{}
|
|
err = json.Unmarshal(patternMap, &pattern)
|
|
assert.NilError(t, err)
|
|
err = json.Unmarshal(resourceRaw, &resource)
|
|
assert.NilError(t, err)
|
|
// context
|
|
ctx := context.NewContext(jp)
|
|
err = context.AddResource(ctx, resourceRaw)
|
|
assert.NilError(t, err)
|
|
|
|
output, err := SubstituteAll(logr.Discard(), ctx, pattern)
|
|
assert.NilError(t, err)
|
|
out, err := json.Marshal(output)
|
|
assert.NilError(t, err)
|
|
assert.Equal(t, string(out), compact(t, expectedRaw))
|
|
}
|
|
|
|
func Test_subVars_withRegexMatch(t *testing.T) {
|
|
patternMap := []byte(`{
|
|
"mutate": {
|
|
"overlay": {
|
|
"spec": {
|
|
"port": "{{ regex_match('(443)', '{{@}}') }}",
|
|
"name": "ns-owner-{{request.object.metadata.name}}"
|
|
}
|
|
}
|
|
}
|
|
}`)
|
|
|
|
resourceRaw := []byte(`
|
|
{
|
|
"metadata": {
|
|
"name": "temp",
|
|
"namespace": "n1"
|
|
},
|
|
"spec": {
|
|
"port": "443",
|
|
"namespace": "n1",
|
|
"name": "temp1"
|
|
}
|
|
}`)
|
|
|
|
expectedRaw := []byte(`{
|
|
"mutate":{
|
|
"overlay":{
|
|
"spec":{
|
|
"name":"ns-owner-temp",
|
|
"port":true
|
|
}
|
|
}
|
|
}
|
|
}`)
|
|
|
|
var err error
|
|
|
|
var pattern, resource interface{}
|
|
err = json.Unmarshal(patternMap, &pattern)
|
|
assert.NilError(t, err)
|
|
err = json.Unmarshal(resourceRaw, &resource)
|
|
assert.NilError(t, err)
|
|
// context
|
|
ctx := context.NewContext(jp)
|
|
err = context.AddResource(ctx, resourceRaw)
|
|
assert.NilError(t, err)
|
|
|
|
output, err := SubstituteAll(logr.Discard(), ctx, pattern)
|
|
assert.NilError(t, err)
|
|
out, err := json.Marshal(output)
|
|
assert.NilError(t, err)
|
|
assert.Equal(t, string(out), compact(t, expectedRaw))
|
|
}
|
|
|
|
func Test_subVars_withMerge(t *testing.T) {
|
|
patternMap := []byte(`{"map": "{{ merge(` + "`{\\\"a\\\": 1}`, `{\\\"b\\\": 1}`" + `)}}"}`)
|
|
|
|
resourceRaw := []byte(`{}`)
|
|
|
|
expectedRaw := []byte(`{"map": {"a":1,"b":1}}`)
|
|
|
|
var err error
|
|
|
|
var pattern, resource interface{}
|
|
err = json.Unmarshal(patternMap, &pattern)
|
|
assert.NilError(t, err)
|
|
err = json.Unmarshal(resourceRaw, &resource)
|
|
assert.NilError(t, err)
|
|
// context
|
|
ctx := context.NewContext(jp)
|
|
err = context.AddResource(ctx, resourceRaw)
|
|
assert.NilError(t, err)
|
|
|
|
output, err := SubstituteAll(logr.Discard(), ctx, pattern)
|
|
assert.NilError(t, err)
|
|
out, err := json.Marshal(output)
|
|
assert.NilError(t, err)
|
|
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) {
|
|
patternMap := []byte(`{
|
|
"mutate": {
|
|
"overlay": {
|
|
"spec": {
|
|
"port": "{{ regex_replace_all_literal('.*', '{{@}}', '1313') }}",
|
|
"name": "ns-owner-{{request.object.metadata.name}}"
|
|
}
|
|
}
|
|
}
|
|
}`)
|
|
|
|
resourceRaw := []byte(`{
|
|
"metadata": {
|
|
"name": "temp",
|
|
"namespace": "n1"
|
|
},
|
|
"spec": {
|
|
"port": "43123",
|
|
"namespace": "n1",
|
|
"name": "temp1"
|
|
}
|
|
}`)
|
|
expected := []byte(`{"mutate":{"overlay":{"spec":{"name":"ns-owner-temp","port":"1313"}}}}`)
|
|
|
|
var pattern, resource interface{}
|
|
var err error
|
|
err = json.Unmarshal(patternMap, &pattern)
|
|
assert.NilError(t, err)
|
|
err = json.Unmarshal(resourceRaw, &resource)
|
|
assert.NilError(t, err)
|
|
// context
|
|
ctx := context.NewContext(jp)
|
|
err = context.AddResource(ctx, resourceRaw)
|
|
assert.NilError(t, err)
|
|
|
|
output, err := SubstituteAll(logr.Discard(), ctx, pattern)
|
|
assert.NilError(t, err)
|
|
out, err := json.Marshal(output)
|
|
assert.NilError(t, err)
|
|
assert.Equal(t, string(out), string(expected))
|
|
}
|
|
|
|
func Test_ReplacingPathWhenDeleting(t *testing.T) {
|
|
patternRaw := []byte(`"{{request.object.metadata.annotations.target}}"`)
|
|
|
|
resourceRaw := []byte(`
|
|
{
|
|
"request": {
|
|
"operation": "DELETE",
|
|
"object": {
|
|
"metadata": {
|
|
"name": "curr",
|
|
"namespace": "ns",
|
|
"annotations": {
|
|
"target": "foo"
|
|
}
|
|
}
|
|
},
|
|
"oldObject": {
|
|
"metadata": {
|
|
"name": "old",
|
|
"annotations": {
|
|
"target": "bar"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
|
|
var pattern interface{}
|
|
var err error
|
|
err = json.Unmarshal(patternRaw, &pattern)
|
|
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)
|
|
assert.NilError(t, err)
|
|
|
|
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}}}}"`)
|
|
|
|
resourceRaw := []byte(`
|
|
{
|
|
"request":{
|
|
"operation":"DELETE",
|
|
"oldObject":{
|
|
"metadata":{
|
|
"name":"current",
|
|
"namespace":"ns",
|
|
"annotations":{
|
|
"target":"nested_target",
|
|
"targetnew":"target"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}`)
|
|
|
|
var pattern interface{}
|
|
var err error
|
|
err = json.Unmarshal(patternRaw, &pattern)
|
|
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)
|
|
|
|
assert.Equal(t, fmt.Sprintf("%v", pattern), "nested_target")
|
|
}
|
|
|
|
var resourceRaw = []byte(`
|
|
{
|
|
"metadata": {
|
|
"name": "temp",
|
|
"namespace": "n1",
|
|
"annotations": {
|
|
"test": "name"
|
|
}
|
|
},
|
|
"spec": {
|
|
"namespace": "n1",
|
|
"name": "temp1"
|
|
}
|
|
}
|
|
`)
|
|
|
|
func Test_SubstituteSuccess(t *testing.T) {
|
|
ctx := context.NewContext(jp)
|
|
assert.Assert(t, context.AddResource(ctx, resourceRaw))
|
|
|
|
var pattern interface{}
|
|
patternRaw := []byte(`"{{request.object.metadata.annotations.test}}"`)
|
|
assert.Assert(t, json.Unmarshal(patternRaw, &pattern))
|
|
|
|
action := substituteVariablesIfAny(logr.Discard(), ctx, DefaultVariableResolver)
|
|
results, err := action(&ju.ActionData{
|
|
Document: nil,
|
|
Element: string(patternRaw),
|
|
Path: "/",
|
|
})
|
|
if err != nil {
|
|
t.Errorf("substitution failed: %v", err.Error())
|
|
return
|
|
}
|
|
|
|
if results.(string) != `"name"` {
|
|
t.Errorf("expected %s received %v", "name", results)
|
|
}
|
|
}
|
|
|
|
func Test_SubstituteRecursiveErrors(t *testing.T) {
|
|
ctx := context.NewContext(jp)
|
|
assert.Assert(t, context.AddResource(ctx, resourceRaw))
|
|
|
|
var pattern interface{}
|
|
patternRaw := []byte(`"{{request.object.metadata.{{request.object.metadata.annotations.test2}}}}"`)
|
|
assert.Assert(t, json.Unmarshal(patternRaw, &pattern))
|
|
|
|
action := substituteVariablesIfAny(logr.Discard(), ctx, DefaultVariableResolver)
|
|
results, err := action(&ju.ActionData{
|
|
Document: nil,
|
|
Element: string(patternRaw),
|
|
Path: "/",
|
|
})
|
|
|
|
if err == nil {
|
|
t.Errorf("expected error but received: %v", results)
|
|
}
|
|
|
|
patternRaw = []byte(`"{{request.object.metadata2.{{request.object.metadata.annotations.test}}}}"`)
|
|
assert.Assert(t, json.Unmarshal(patternRaw, &pattern))
|
|
|
|
action = substituteVariablesIfAny(logr.Discard(), ctx, DefaultVariableResolver)
|
|
results, err = action(&ju.ActionData{
|
|
Document: nil,
|
|
Element: string(patternRaw),
|
|
Path: "/",
|
|
})
|
|
|
|
if err == nil {
|
|
t.Errorf("expected error but received: %v", results)
|
|
}
|
|
}
|
|
|
|
func Test_SubstituteRecursive(t *testing.T) {
|
|
ctx := context.NewContext(jp)
|
|
assert.Assert(t, context.AddResource(ctx, resourceRaw))
|
|
|
|
var pattern interface{}
|
|
patternRaw := []byte(`"{{request.object.metadata.{{request.object.metadata.annotations.test}}}}"`)
|
|
assert.Assert(t, json.Unmarshal(patternRaw, &pattern))
|
|
|
|
action := substituteVariablesIfAny(logr.Discard(), ctx, DefaultVariableResolver)
|
|
results, err := action(&ju.ActionData{
|
|
Document: nil,
|
|
Element: string(patternRaw),
|
|
Path: "/",
|
|
})
|
|
if err != nil {
|
|
t.Errorf("substitution failed: %v", err.Error())
|
|
return
|
|
}
|
|
|
|
if results.(string) != `"temp"` {
|
|
t.Errorf("expected %s received %v", "temp", results)
|
|
}
|
|
}
|
|
|
|
func Test_SubstituteShallow(t *testing.T) {
|
|
ctx := context.NewContext(jp)
|
|
data := map[string]interface{}{
|
|
"variableWithVariables": "{{ DO_NOT_SUBSTITUTE_ME {{OR_ME}} }}",
|
|
"foo": "bar",
|
|
"foo2": "bar2",
|
|
"variablesNested": "{{foo2}}",
|
|
}
|
|
|
|
assert.NilError(t, context.AddJSONObject(ctx, data))
|
|
|
|
patternRaw := []byte(`"{{- variableWithVariables }} {{foo}} {{variablesNested}}"`)
|
|
action := substituteVariablesIfAny(logr.Discard(), ctx, DefaultVariableResolver)
|
|
results, err := action(&ju.ActionData{
|
|
Document: nil,
|
|
Element: string(patternRaw),
|
|
Path: "/",
|
|
})
|
|
|
|
assert.NilError(t, err)
|
|
assert.Equal(t, results.(string), "\"{{ DO_NOT_SUBSTITUTE_ME {{OR_ME}} }} bar bar2\"")
|
|
|
|
patternRaw = []byte(`"{{foo}} {{- variableWithVariables }} {{variablesNested}}"`)
|
|
action = substituteVariablesIfAny(logr.Discard(), ctx, DefaultVariableResolver)
|
|
results, err = action(&ju.ActionData{
|
|
Document: nil,
|
|
Element: string(patternRaw),
|
|
Path: "/",
|
|
})
|
|
|
|
assert.NilError(t, err)
|
|
assert.Equal(t, results.(string), "\"bar {{ DO_NOT_SUBSTITUTE_ME {{OR_ME}} }} bar2\"")
|
|
|
|
patternRaw = []byte(`"{{- variableWithVariables {{foo}} {{variablesNested}} }}"`)
|
|
action = substituteVariablesIfAny(logr.Discard(), ctx, DefaultVariableResolver)
|
|
_, err = action(&ju.ActionData{
|
|
Document: nil,
|
|
Element: string(patternRaw),
|
|
Path: "/",
|
|
})
|
|
|
|
assert.ErrorContains(t, err, "failed to resolve variableWithVariables bar bar2")
|
|
}
|
|
|
|
func Test_policyContextValidation(t *testing.T) {
|
|
policyContext := []byte(`
|
|
{
|
|
"context": [
|
|
{
|
|
"name": "myconfigmap",
|
|
"apiCall": {
|
|
"urlPath": "/api/v1/namespaces/{{ request.namespace }}/configmaps/generate-pod"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`)
|
|
|
|
var contextMap interface{}
|
|
err := json.Unmarshal(policyContext, &contextMap)
|
|
assert.NilError(t, err)
|
|
|
|
ctx := context.NewMockContext(nil, "request.object")
|
|
|
|
_, err = SubstituteAll(logr.Discard(), ctx, contextMap)
|
|
assert.Assert(t, err != nil, err)
|
|
}
|
|
|
|
func Test_variableSubstitution_array(t *testing.T) {
|
|
configmapRaw := []byte(`
|
|
{
|
|
"animals": {
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": {
|
|
"name": "animals",
|
|
"namespace": "default"
|
|
},
|
|
"data": {
|
|
"animals": "snake\nbear\ncat\ndog"
|
|
}
|
|
}
|
|
}`)
|
|
|
|
ruleRaw := []byte(`
|
|
{
|
|
"name": "validate-role-annotation",
|
|
"context": [
|
|
{
|
|
"name": "animals",
|
|
"configMap": {
|
|
"name": "animals",
|
|
"namespace": "default"
|
|
}
|
|
}
|
|
],
|
|
"match": {
|
|
"resources": {
|
|
"kinds": [
|
|
"Deployment"
|
|
]
|
|
}
|
|
},
|
|
"validate": {
|
|
"message": "The animal {{ request.object.metadata.labels.animal }} is not in the allowed list of animals: {{ animals.data.animals }}.",
|
|
"deny": {
|
|
"conditions": [
|
|
{
|
|
"key": "{{ request.object.metadata.labels.animal }}",
|
|
"operator": "NotIn",
|
|
"value": "{{ animals.data.animals }}"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}`)
|
|
|
|
resourceRaw := []byte(`
|
|
{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "Deployment",
|
|
"metadata": {
|
|
"name": "busybox",
|
|
"labels": {
|
|
"app": "busybox",
|
|
"color": "red",
|
|
"animal": "cow",
|
|
"food": "pizza",
|
|
"car": "jeep",
|
|
"env": "qa"
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
|
|
var rule v1.Rule
|
|
err := json.Unmarshal(ruleRaw, &rule)
|
|
assert.NilError(t, err)
|
|
|
|
ctxMap, err := unmarshalToMap(configmapRaw)
|
|
assert.NilError(t, err)
|
|
|
|
ctx := context.NewContextFromRaw(jp, ctxMap)
|
|
context.AddResource(ctx, resourceRaw)
|
|
|
|
vars, err := SubstituteAllInRule(logr.Discard(), ctx, rule)
|
|
assert.NilError(t, err)
|
|
|
|
assert.DeepEqual(t, vars.Validation.Message, "The animal cow is not in the allowed list of animals: snake\nbear\ncat\ndog.")
|
|
}
|
|
|
|
var variableObject = []byte(`
|
|
{
|
|
"complex_object_array": [
|
|
"value1",
|
|
"value2",
|
|
"value3"
|
|
],
|
|
"complex_object_map": {
|
|
"key1": "value1",
|
|
"key2": "value2",
|
|
"key3": "value3"
|
|
},
|
|
"simple_object_bool": false,
|
|
"simple_object_int": 5,
|
|
"simple_object_float": -5.5,
|
|
"simple_object_string": "example",
|
|
"simple_object_null": null
|
|
}
|
|
`)
|
|
|
|
func Test_SubstituteNull(t *testing.T) {
|
|
patternRaw := []byte(`
|
|
{
|
|
"spec": {
|
|
"content": "{{ request.object.simple_object_null }}"
|
|
}
|
|
}
|
|
`)
|
|
|
|
var err error
|
|
var pattern, resource map[string]interface{}
|
|
err = json.Unmarshal(patternRaw, &pattern)
|
|
assert.NilError(t, err)
|
|
|
|
err = json.Unmarshal(variableObject, &resource)
|
|
assert.NilError(t, err)
|
|
|
|
ctx := context.NewContext(jp)
|
|
context.AddResource(ctx, variableObject)
|
|
|
|
resolved, err := SubstituteAll(logr.Discard(), ctx, pattern)
|
|
assert.NilError(t, err)
|
|
|
|
content := resolved.(map[string]interface{})["spec"].(map[string]interface{})["content"]
|
|
var expected interface{}
|
|
|
|
assert.DeepEqual(t, expected, content)
|
|
}
|
|
|
|
func Test_SubstituteNullInString(t *testing.T) {
|
|
patternRaw := []byte(`
|
|
{
|
|
"spec": {
|
|
"content": "content = {{ request.object.simple_object_null }}"
|
|
}
|
|
}
|
|
`)
|
|
|
|
var err error
|
|
var pattern, resource map[string]interface{}
|
|
err = json.Unmarshal(patternRaw, &pattern)
|
|
assert.NilError(t, err)
|
|
|
|
err = json.Unmarshal(variableObject, &resource)
|
|
assert.NilError(t, err)
|
|
|
|
ctx := context.NewContext(jp)
|
|
context.AddResource(ctx, variableObject)
|
|
|
|
resolved, err := SubstituteAll(logr.Discard(), ctx, pattern)
|
|
assert.NilError(t, err)
|
|
|
|
content := resolved.(map[string]interface{})["spec"].(map[string]interface{})["content"]
|
|
expected := "content = null"
|
|
|
|
assert.DeepEqual(t, expected, content)
|
|
}
|
|
|
|
func Test_SubstituteArray(t *testing.T) {
|
|
patternRaw := []byte(`
|
|
{
|
|
"spec": {
|
|
"content": "{{ request.object.complex_object_array }}"
|
|
}
|
|
}
|
|
`)
|
|
|
|
var err error
|
|
var pattern, resource map[string]interface{}
|
|
err = json.Unmarshal(patternRaw, &pattern)
|
|
assert.NilError(t, err)
|
|
|
|
err = json.Unmarshal(variableObject, &resource)
|
|
assert.NilError(t, err)
|
|
|
|
ctx := context.NewContext(jp)
|
|
context.AddResource(ctx, variableObject)
|
|
|
|
resolved, err := SubstituteAll(logr.Discard(), ctx, pattern)
|
|
assert.NilError(t, err)
|
|
|
|
content := resolved.(map[string]interface{})["spec"].(map[string]interface{})["content"]
|
|
expected := resource["complex_object_array"]
|
|
|
|
assert.DeepEqual(t, expected, content)
|
|
}
|
|
|
|
func Test_SubstituteArrayInString(t *testing.T) {
|
|
patternRaw := []byte(`
|
|
{
|
|
"spec": {
|
|
"content": "content is {{ request.object.complex_object_map }}"
|
|
}
|
|
}
|
|
`)
|
|
|
|
var err error
|
|
var pattern, resource map[string]interface{}
|
|
err = json.Unmarshal(patternRaw, &pattern)
|
|
assert.NilError(t, err)
|
|
|
|
err = json.Unmarshal(variableObject, &resource)
|
|
assert.NilError(t, err)
|
|
|
|
ctx := context.NewContext(jp)
|
|
context.AddResource(ctx, variableObject)
|
|
|
|
resolved, err := SubstituteAll(logr.Discard(), ctx, pattern)
|
|
assert.NilError(t, err)
|
|
|
|
content := resolved.(map[string]interface{})["spec"].(map[string]interface{})["content"]
|
|
expected := `content is {"key1":"value1","key2":"value2","key3":"value3"}`
|
|
|
|
assert.DeepEqual(t, expected, content)
|
|
}
|
|
|
|
func Test_SubstituteInt(t *testing.T) {
|
|
patternRaw := []byte(`
|
|
{
|
|
"spec": {
|
|
"content": "{{ request.object.simple_object_int }}"
|
|
}
|
|
}
|
|
`)
|
|
|
|
var err error
|
|
var pattern, resource map[string]interface{}
|
|
err = json.Unmarshal(patternRaw, &pattern)
|
|
assert.NilError(t, err)
|
|
|
|
err = json.Unmarshal(variableObject, &resource)
|
|
assert.NilError(t, err)
|
|
|
|
ctx := context.NewContext(jp)
|
|
context.AddResource(ctx, variableObject)
|
|
|
|
resolved, err := SubstituteAll(logr.Discard(), ctx, pattern)
|
|
assert.NilError(t, err)
|
|
|
|
content := resolved.(map[string]interface{})["spec"].(map[string]interface{})["content"]
|
|
expected := resource["simple_object_int"]
|
|
|
|
assert.DeepEqual(t, expected, content)
|
|
}
|
|
|
|
func Test_SubstituteIntInString(t *testing.T) {
|
|
patternRaw := []byte(`
|
|
{
|
|
"spec": {
|
|
"content": "content = {{ request.object.simple_object_int }}"
|
|
}
|
|
}
|
|
`)
|
|
|
|
var err error
|
|
var pattern, resource map[string]interface{}
|
|
err = json.Unmarshal(patternRaw, &pattern)
|
|
assert.NilError(t, err)
|
|
|
|
err = json.Unmarshal(variableObject, &resource)
|
|
assert.NilError(t, err)
|
|
|
|
ctx := context.NewContext(jp)
|
|
context.AddResource(ctx, variableObject)
|
|
|
|
resolved, err := SubstituteAll(logr.Discard(), ctx, pattern)
|
|
assert.NilError(t, err)
|
|
|
|
content := resolved.(map[string]interface{})["spec"].(map[string]interface{})["content"]
|
|
expected := "content = 5"
|
|
|
|
assert.DeepEqual(t, expected, content)
|
|
}
|
|
|
|
func Test_SubstituteBool(t *testing.T) {
|
|
patternRaw := []byte(`
|
|
{
|
|
"spec": {
|
|
"content": "{{ request.object.simple_object_bool }}"
|
|
}
|
|
}
|
|
`)
|
|
|
|
var err error
|
|
var pattern, resource map[string]interface{}
|
|
err = json.Unmarshal(patternRaw, &pattern)
|
|
assert.NilError(t, err)
|
|
|
|
err = json.Unmarshal(variableObject, &resource)
|
|
assert.NilError(t, err)
|
|
|
|
ctx := context.NewContext(jp)
|
|
context.AddResource(ctx, variableObject)
|
|
|
|
resolved, err := SubstituteAll(logr.Discard(), ctx, pattern)
|
|
assert.NilError(t, err)
|
|
|
|
content := resolved.(map[string]interface{})["spec"].(map[string]interface{})["content"]
|
|
expected := false
|
|
|
|
assert.DeepEqual(t, expected, content)
|
|
}
|
|
|
|
func Test_SubstituteBoolInString(t *testing.T) {
|
|
patternRaw := []byte(`
|
|
{
|
|
"spec": {
|
|
"content": "content = {{ request.object.simple_object_bool }}"
|
|
}
|
|
}
|
|
`)
|
|
|
|
var err error
|
|
var pattern, resource map[string]interface{}
|
|
err = json.Unmarshal(patternRaw, &pattern)
|
|
assert.NilError(t, err)
|
|
|
|
err = json.Unmarshal(variableObject, &resource)
|
|
assert.NilError(t, err)
|
|
|
|
ctx := context.NewContext(jp)
|
|
context.AddResource(ctx, variableObject)
|
|
|
|
resolved, err := SubstituteAll(logr.Discard(), ctx, pattern)
|
|
assert.NilError(t, err)
|
|
|
|
content := resolved.(map[string]interface{})["spec"].(map[string]interface{})["content"]
|
|
expected := "content = false"
|
|
|
|
assert.DeepEqual(t, expected, content)
|
|
}
|
|
|
|
func Test_SubstituteString(t *testing.T) {
|
|
patternRaw := []byte(`
|
|
{
|
|
"spec": {
|
|
"content": "{{ request.object.simple_object_string }}"
|
|
}
|
|
}
|
|
`)
|
|
|
|
var err error
|
|
var pattern, resource map[string]interface{}
|
|
err = json.Unmarshal(patternRaw, &pattern)
|
|
assert.NilError(t, err)
|
|
|
|
err = json.Unmarshal(variableObject, &resource)
|
|
assert.NilError(t, err)
|
|
|
|
ctx := context.NewContext(jp)
|
|
context.AddResource(ctx, variableObject)
|
|
|
|
resolved, err := SubstituteAll(logr.Discard(), ctx, pattern)
|
|
assert.NilError(t, err)
|
|
|
|
content := resolved.(map[string]interface{})["spec"].(map[string]interface{})["content"]
|
|
expected := "example"
|
|
|
|
assert.DeepEqual(t, expected, content)
|
|
}
|
|
|
|
func Test_SubstituteStringInString(t *testing.T) {
|
|
patternRaw := []byte(`
|
|
{
|
|
"spec": {
|
|
"content": "content = {{ request.object.simple_object_string }}"
|
|
}
|
|
}
|
|
`)
|
|
|
|
var err error
|
|
var pattern, resource map[string]interface{}
|
|
err = json.Unmarshal(patternRaw, &pattern)
|
|
assert.NilError(t, err)
|
|
|
|
err = json.Unmarshal(variableObject, &resource)
|
|
assert.NilError(t, err)
|
|
|
|
ctx := context.NewContext(jp)
|
|
context.AddResource(ctx, variableObject)
|
|
|
|
resolved, err := SubstituteAll(logr.Discard(), ctx, pattern)
|
|
assert.NilError(t, err)
|
|
|
|
content := resolved.(map[string]interface{})["spec"].(map[string]interface{})["content"]
|
|
expected := "content = example"
|
|
|
|
assert.DeepEqual(t, expected, content)
|
|
}
|
|
|
|
func Test_ReferenceSubstitution(t *testing.T) {
|
|
jsonRaw := []byte(`
|
|
{
|
|
"metadata": {
|
|
"name": "temp",
|
|
"namespace": "n1",
|
|
"annotations": {
|
|
"test": "$(../../../../spec/namespace)"
|
|
}
|
|
},
|
|
"(spec)": {
|
|
"namespace": "n1",
|
|
"name": "temp1"
|
|
}
|
|
}`)
|
|
|
|
expectedJSON := []byte(`
|
|
{
|
|
"metadata": {
|
|
"name": "temp",
|
|
"namespace": "n1",
|
|
"annotations": {
|
|
"test": "n1"
|
|
}
|
|
},
|
|
"(spec)": {
|
|
"namespace": "n1",
|
|
"name": "temp1"
|
|
}
|
|
}`)
|
|
|
|
var document interface{}
|
|
err := json.Unmarshal(jsonRaw, &document)
|
|
assert.NilError(t, err)
|
|
|
|
var expectedDocument interface{}
|
|
err = json.Unmarshal(expectedJSON, &expectedDocument)
|
|
assert.NilError(t, err)
|
|
|
|
ctx := context.NewContext(jp)
|
|
err = context.AddResource(ctx, jsonRaw)
|
|
assert.NilError(t, err)
|
|
|
|
actualDocument, err := SubstituteAll(logr.Discard(), ctx, document)
|
|
assert.NilError(t, err)
|
|
|
|
assert.DeepEqual(t, expectedDocument, actualDocument)
|
|
}
|
|
|
|
func TestFormAbsolutePath_RelativePathExists(t *testing.T) {
|
|
absolutePath := "/spec/containers/0/resources/requests/memory"
|
|
referencePath := "./../../limits/memory"
|
|
expectedString := "/spec/containers/0/resources/limits/memory"
|
|
|
|
result := formAbsolutePath(referencePath, absolutePath)
|
|
|
|
assert.Assert(t, result == expectedString)
|
|
}
|
|
|
|
func TestFormAbsolutePath_RelativePathWithBackToTopInTheBeginning(t *testing.T) {
|
|
absolutePath := "/spec/containers/0/resources/requests/memory"
|
|
referencePath := "../../limits/memory"
|
|
expectedString := "/spec/containers/0/resources/limits/memory"
|
|
|
|
result := formAbsolutePath(referencePath, absolutePath)
|
|
|
|
assert.Assert(t, result == expectedString)
|
|
}
|
|
|
|
func TestFormAbsolutePath_AbsolutePathExists(t *testing.T) {
|
|
absolutePath := "/spec/containers/0/resources/requests/memory"
|
|
referencePath := "/spec/containers/0/resources/limits/memory"
|
|
|
|
result := formAbsolutePath(referencePath, absolutePath)
|
|
|
|
assert.Assert(t, result == referencePath)
|
|
}
|
|
|
|
func TestFormAbsolutePath_EmptyPath(t *testing.T) {
|
|
absolutePath := "/spec/containers/0/resources/requests/memory"
|
|
referencePath := ""
|
|
|
|
result := formAbsolutePath(referencePath, absolutePath)
|
|
|
|
assert.Assert(t, result == absolutePath)
|
|
}
|
|
|
|
func TestActualizePattern_GivenRelativePathThatExists(t *testing.T) {
|
|
absolutePath := "/spec/containers/0/resources/requests/memory"
|
|
referencePath := "$(<=./../../limits/memory)"
|
|
|
|
rawPattern := []byte(`{
|
|
"spec":{
|
|
"containers":[
|
|
{
|
|
"name":"*",
|
|
"resources":{
|
|
"requests":{
|
|
"memory":"$(<=./../../limits/memory)"
|
|
},
|
|
"limits":{
|
|
"memory":"2048Mi"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}`)
|
|
|
|
resolvedReference := "<=2048Mi"
|
|
|
|
var pattern interface{}
|
|
assert.NilError(t, json.Unmarshal(rawPattern, &pattern))
|
|
|
|
// pattern, err := actualizePattern(log.Log, pattern, referencePath, absolutePath)
|
|
|
|
pattern, err := resolveReference(pattern, referencePath, absolutePath)
|
|
|
|
assert.NilError(t, err)
|
|
assert.DeepEqual(t, resolvedReference, pattern)
|
|
}
|
|
|
|
func TestFindAndShiftReferences_PositiveCase(t *testing.T) {
|
|
message := "Message with $(./../../pattern/spec/containers/0/image) reference inside. Or maybe even two $(./../../pattern/spec/containers/0/image), but they are same."
|
|
expectedMessage := strings.Replace(message, "$(./../../pattern/spec/containers/0/image)", "$(./../../pattern/spec/jobTemplate/spec/containers/0/image)", -1)
|
|
actualMessage := FindAndShiftReferences(logr.Discard(), message, "spec/jobTemplate", "pattern")
|
|
|
|
assert.Equal(t, expectedMessage, actualMessage)
|
|
}
|
|
|
|
func TestFindAndShiftReferences_AnyPatternPositiveCase(t *testing.T) {
|
|
message := "Message with $(./../../anyPattern/0/spec/containers/0/image)."
|
|
expectedMessage := strings.Replace(message, "$(./../../anyPattern/0/spec/containers/0/image)", "$(./../../anyPattern/0/spec/jobTemplate/spec/containers/0/image)", -1)
|
|
actualMessage := FindAndShiftReferences(logr.Discard(), message, "spec/jobTemplate", "anyPattern")
|
|
|
|
assert.Equal(t, expectedMessage, actualMessage)
|
|
}
|
|
|
|
func Test_EscpReferenceSubstitution(t *testing.T) {
|
|
jsonRaw := []byte(`
|
|
{
|
|
"metadata": {
|
|
"name": "temp",
|
|
"namespace": "n1",
|
|
"annotations": {
|
|
"test1": "$(../../../../spec/namespace)",
|
|
"test2": "\\$(ENV_VAR)",
|
|
"test3": "\\${ENV_VAR}",
|
|
"test4": "\\\\\\${ENV_VAR}",
|
|
"test5": "\\$(NODE_NAME)/postgres/\\$(POD_NAME)"
|
|
}
|
|
},
|
|
"(spec)": {
|
|
"namespace": "n1",
|
|
"name": "temp1"
|
|
}
|
|
}`)
|
|
|
|
expectedJSON := []byte(`
|
|
{
|
|
"metadata": {
|
|
"name": "temp",
|
|
"namespace": "n1",
|
|
"annotations": {
|
|
"test1": "n1",
|
|
"test2": "$(ENV_VAR)",
|
|
"test3": "\\${ENV_VAR}",
|
|
"test4": "\\\\\\${ENV_VAR}",
|
|
"test5": "$(NODE_NAME)/postgres/$(POD_NAME)"
|
|
}
|
|
},
|
|
"(spec)": {
|
|
"namespace": "n1",
|
|
"name": "temp1"
|
|
}
|
|
}`)
|
|
|
|
var document interface{}
|
|
err := json.Unmarshal(jsonRaw, &document)
|
|
assert.NilError(t, err)
|
|
|
|
var expectedDocument interface{}
|
|
err = json.Unmarshal(expectedJSON, &expectedDocument)
|
|
assert.NilError(t, err)
|
|
|
|
ctx := context.NewContext(jp)
|
|
err = context.AddResource(ctx, jsonRaw)
|
|
assert.NilError(t, err)
|
|
|
|
actualDocument, err := SubstituteAll(logr.Discard(), ctx, document)
|
|
assert.NilError(t, err)
|
|
|
|
assert.DeepEqual(t, expectedDocument, actualDocument)
|
|
}
|
|
|
|
func Test_ReplacingEscpNestedVariableWhenDeleting(t *testing.T) {
|
|
patternRaw := []byte(`"\\{{request.object.metadata.annotations.{{request.object.metadata.annotations.targetnew}}}}"`)
|
|
|
|
resourceRaw := []byte(`
|
|
{
|
|
"request":{
|
|
"operation":"DELETE",
|
|
"oldObject":{
|
|
"metadata":{
|
|
"name":"current",
|
|
"namespace":"ns",
|
|
"annotations":{
|
|
"target":"nested_target",
|
|
"targetnew":"target"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}`)
|
|
|
|
var pattern interface{}
|
|
var err error
|
|
err = json.Unmarshal(patternRaw, &pattern)
|
|
if err != nil {
|
|
t.Error(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)
|
|
assert.NilError(t, err)
|
|
|
|
assert.Equal(t, fmt.Sprintf("%v", pattern), "{{request.object.metadata.annotations.target}}")
|
|
}
|
|
|
|
func Test_ReplaceAllVars(t *testing.T) {
|
|
result := ReplaceAllVars("{{ foo }}", func(s string) string { return "test" })
|
|
assert.Equal(t, result, "test")
|
|
|
|
result = ReplaceAllVars("\"{{ foo }}\"", func(s string) string { return "test" })
|
|
assert.Equal(t, result, "\"test\"")
|
|
|
|
result = ReplaceAllVars("/s/{{elementIndex}}/r", func(s string) string { return "test" })
|
|
assert.Equal(t, result, "/s/test/r")
|
|
|
|
result = ReplaceAllVars("{{ foo }} {{foo}} {{foo}}", func(s string) string { return "test" })
|
|
assert.Equal(t, result, "test test test")
|
|
|
|
result = ReplaceAllVars("{{ foo }} \\{{foo}} {{foo}}", func(s string) string { return "test" })
|
|
assert.Equal(t, result, "test \\{{foo}} test")
|
|
|
|
result = ReplaceAllVars("{{ foo {{foo}} }}", func(s string) string { return "test" })
|
|
assert.Equal(t, result, "{{ foo test }}")
|
|
}
|