From 9da2d44ee1680743af21a43cdd6a127fef49d6c4 Mon Sep 17 00:00:00 2001 From: Max Goncharenko Date: Wed, 28 Apr 2021 23:12:44 +0300 Subject: [PATCH] Fix #1737: forceMutate does not handle StrategicMerge patchesJson6902 (#1775) * Fix #1737: forceMutate does not handle StrategicMerge patchesJson6902 Signed-off-by: Max Goncharenko * go fmt Signed-off-by: Max Goncharenko * updated PR due to the comments Signed-off-by: Max Goncharenko --- pkg/engine/forceMutate.go | 23 ++++ pkg/engine/forceMutate_test.go | 192 +++++++++++++++++++++++++++++++++ 2 files changed, 215 insertions(+) diff --git a/pkg/engine/forceMutate.go b/pkg/engine/forceMutate.go index 718629824c..ee4bb90295 100644 --- a/pkg/engine/forceMutate.go +++ b/pkg/engine/forceMutate.go @@ -3,6 +3,7 @@ package engine import ( "fmt" + "github.com/ghodss/yaml" kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1" "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/mutate" @@ -86,6 +87,28 @@ func ForceMutate(ctx context.EvalInterface, policy kyverno.ClusterPolicy, resour return unstructured.Unstructured{}, fmt.Errorf(resp.Message) } } + + if rule.Mutation.PatchStrategicMerge != nil { + var resp response.RuleResponse + resp, resource = mutate.ProcessStrategicMergePatch(rule.Name, rule.Mutation.PatchStrategicMerge, resource, logger.WithValues("rule", rule.Name)) + if !resp.Success { + return unstructured.Unstructured{}, fmt.Errorf(resp.Message) + } + } + + if rule.Mutation.PatchesJSON6902 != "" { + var resp response.RuleResponse + jsonPatches, err := yaml.YAMLToJSON([]byte(rule.Mutation.PatchesJSON6902)) + if err != nil { + return unstructured.Unstructured{}, err + } + + resp, resource = mutate.ProcessPatchJSON6902(rule.Name, jsonPatches, resource, logger.WithValues("rule", rule.Name)) + if !resp.Success { + return unstructured.Unstructured{}, fmt.Errorf(resp.Message) + } + } + } return resource, nil diff --git a/pkg/engine/forceMutate_test.go b/pkg/engine/forceMutate_test.go index a948d6129c..38b0a640a9 100644 --- a/pkg/engine/forceMutate_test.go +++ b/pkg/engine/forceMutate_test.go @@ -8,6 +8,7 @@ import ( "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/utils" "gotest.tools/assert" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) var rawPolicy = []byte(` @@ -148,3 +149,194 @@ func Test_ForceMutateSubstituteVarsWithNilContext(t *testing.T) { assert.DeepEqual(t, expectedResource, mutatedResource.UnstructuredContent()) } + +func Test_ForceMutateSubstituteVarsWithPatchStrategicMerge(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "strategic-merge-patch" + }, + "spec": { + "rules": [ + { + "name": "set-image-pull-policy-add-command", + "match": { + "resources": { + "kinds": [ + "Pod" + ] + } + }, + "mutate": { + "patchStrategicMerge": { + "spec": { + "volumes": [ + { + "emptyDir": { + "medium": "Memory" + }, + "name": "cache-volume" + } + ] + } + } + } + } + ] + } + } +`) + + rawResource := []byte(` +{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "check-root-user" + }, + "spec": { + "volumes": [ + { + "name": "cache-volume", + "emptyDir": { } + }, + { + "name": "cache-volume2", + "emptyDir": { + "medium": "Memory" + } + } + ] + } +} +`) + + expectedRawResource := []byte(` + {"apiVersion":"v1","kind":"Pod","metadata":{"name":"check-root-user"},"spec":{"volumes":[{"emptyDir":{"medium":"Memory"},"name":"cache-volume"},{"emptyDir":{"medium":"Memory"},"name":"cache-volume2"}]}} + `) + + var expectedResource interface{} + assert.NilError(t, json.Unmarshal(expectedRawResource, &expectedResource)) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + ctx := context.NewContext() + err = ctx.AddResource(rawResource) + assert.NilError(t, err) + + mutatedResource, err := ForceMutate(ctx, policy, *resourceUnstructured) + assert.NilError(t, err) + + assert.DeepEqual(t, expectedResource, mutatedResource.UnstructuredContent()) +} + +func Test_ForceMutateSubstituteVarsWithPatchesJson6902(t *testing.T) { + rawPolicy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "insert-container" + }, + "spec": { + "rules": [ + { + "name": "insert-container", + "match": { + "resources": { + "kinds": [ + "Pod" + ] + } + }, + "mutate": { + "patchesJson6902": "- op: add\n path: \"/spec/template/spec/containers/0/command/0\"\n value: ls" + } + } + ] + } + } + `) + + rawResource := []byte(` + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "myDeploy" + }, + "spec": { + "replica": 2, + "template": { + "metadata": { + "labels": { + "old-label": "old-value" + } + }, + "spec": { + "containers": [ + { + "command": ["ll", "rm"], + "image": "nginx", + "name": "nginx" + } + ] + } + } + } + } + `) + + rawExpected := []byte(` + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "myDeploy" + }, + "spec": { + "replica": 2, + "template": { + "metadata": { + "labels": { + "old-label": "old-value" + } + }, + "spec": { + "containers": [ + { + "command": ["ls", "ll", "rm"], + "image": "nginx", + "name": "nginx" + } + ] + } + } + } + } + `) + + var expectedResource unstructured.Unstructured + assert.NilError(t, json.Unmarshal(rawExpected, &expectedResource)) + + var policy kyverno.ClusterPolicy + err := json.Unmarshal(rawPolicy, &policy) + assert.NilError(t, err) + + resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) + assert.NilError(t, err) + ctx := context.NewContext() + err = ctx.AddResource(rawResource) + assert.NilError(t, err) + + mutatedResource, err := ForceMutate(ctx, policy, *resourceUnstructured) + assert.NilError(t, err) + + assert.DeepEqual(t, expectedResource.UnstructuredContent(), mutatedResource.UnstructuredContent()) +}