mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-06 07:57:07 +00:00
473 lines
10 KiB
Go
473 lines
10 KiB
Go
package variables
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/kyverno/kyverno/pkg/engine/context"
|
|
ju "github.com/kyverno/kyverno/pkg/engine/json-utils"
|
|
"gotest.tools/assert"
|
|
"sigs.k8s.io/controller-runtime/pkg/log"
|
|
)
|
|
|
|
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()
|
|
err = ctx.AddResource(resourceRaw)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
if _, err := SubstituteAll(log.Log, 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()
|
|
err = ctx.AddResource(resourceRaw)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
if _, err := SubstituteAll(log.Log, ctx, pattern); err == nil {
|
|
t.Error("error is expected")
|
|
}
|
|
}
|
|
|
|
func Test_ReplacingPathWhenDeleting(t *testing.T) {
|
|
patternRaw := []byte(`"{{request.object.metadata.annotations.target}}"`)
|
|
|
|
var 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)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
ctx := context.NewContext()
|
|
err = ctx.AddJSON(resourceRaw)
|
|
assert.NilError(t, err)
|
|
|
|
pattern, err = SubstituteVars(log.Log, ctx, pattern)
|
|
assert.NilError(t, err)
|
|
|
|
assert.Equal(t, fmt.Sprintf("%v", pattern), "bar")
|
|
}
|
|
|
|
func Test_ReplacingNestedVariableWhenDeleting(t *testing.T) {
|
|
patternRaw := []byte(`"{{request.object.metadata.annotations.{{request.object.metadata.annotations.targetnew}}}}"`)
|
|
|
|
var 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)
|
|
}
|
|
ctx := context.NewContext()
|
|
err = ctx.AddJSON(resourceRaw)
|
|
assert.NilError(t, err)
|
|
|
|
pattern, err = SubstituteVars(log.Log, 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()
|
|
assert.Assert(t, ctx.AddResource(resourceRaw))
|
|
|
|
var pattern interface{}
|
|
patternRaw := []byte(`"{{request.object.metadata.annotations.test}}"`)
|
|
assert.Assert(t, json.Unmarshal(patternRaw, &pattern))
|
|
|
|
action := substituteVariablesIfAny(log.Log, ctx)
|
|
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()
|
|
assert.Assert(t, ctx.AddResource(resourceRaw))
|
|
|
|
var pattern interface{}
|
|
patternRaw := []byte(`"{{request.object.metadata.{{request.object.metadata.annotations.test2}}}}"`)
|
|
assert.Assert(t, json.Unmarshal(patternRaw, &pattern))
|
|
|
|
action := substituteVariablesIfAny(log.Log, ctx)
|
|
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(log.Log, ctx)
|
|
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()
|
|
assert.Assert(t, ctx.AddResource(resourceRaw))
|
|
|
|
var pattern interface{}
|
|
patternRaw := []byte(`"{{request.object.metadata.{{request.object.metadata.annotations.test}}}}"`)
|
|
assert.Assert(t, json.Unmarshal(patternRaw, &pattern))
|
|
|
|
action := substituteVariablesIfAny(log.Log, ctx)
|
|
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_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.NewContext("request.object")
|
|
|
|
_, err = SubstituteAll(log.Log, ctx, contextMap)
|
|
assert.Assert(t, err != nil, err)
|
|
}
|
|
|
|
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()
|
|
err = ctx.AddResource(jsonRaw)
|
|
assert.NilError(t, err)
|
|
|
|
actualDocument, err := SubstituteAll(log.Log, 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_RelativePathWithBackToTopInTheBegining(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(log.Log, 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(log.Log, 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(log.Log, message, "spec/jobTemplate", "anyPattern")
|
|
|
|
assert.Equal(t, expectedMessage, actualMessage)
|
|
}
|