diff --git a/pkg/engine/mutate/mutation_test.go b/pkg/engine/mutate/mutation_test.go index 15ebad63d1..4ccfe513ba 100644 --- a/pkg/engine/mutate/mutation_test.go +++ b/pkg/engine/mutate/mutation_test.go @@ -2,6 +2,7 @@ package mutate import ( "encoding/json" + "os" "testing" "github.com/go-logr/logr" @@ -10,12 +11,20 @@ import ( engineapi "github.com/kyverno/kyverno/pkg/engine/api" "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/jmespath" - kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" - "gotest.tools/assert" + "github.com/stretchr/testify/require" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/yaml" ) +func loadYaml(t *testing.T, file string) []byte { + bytes, err := os.ReadFile(file) + require.NoError(t, err) + yaml, err := yaml.YAMLToJSON(bytes) + require.NoError(t, err) + return yaml +} + // jsonPatch is used to build test patches type jsonPatch struct { Path string `json:"path,omitempty" yaml:"path,omitempty"` @@ -23,53 +32,30 @@ type jsonPatch struct { Value apiextensions.JSON `json:"value,omitempty" yaml:"value,omitempty"` } -const endpointsDocument string = `{ - "kind": "Endpoints", - "apiVersion": "v1", - "metadata": { - "name": "my-endpoint-service", - "labels": { - "originalLabel": "isHere" - } - }, - "subsets": [ - { - "addresses": [ - { - "ip": "1.2.3.4" - } - ], - "ports": [ - { - "port": 9376 - } - ] - } - ] -}` - func applyPatches(rule *types.Rule, resource unstructured.Unstructured) (*engineapi.RuleResponse, unstructured.Unstructured) { mutateResp := Mutate(rule, context.NewContext(jmespath.New(config.NewDefaultConfiguration(false))), resource, logr.Discard()) if mutateResp.Status != engineapi.RuleStatusPass { return engineapi.NewRuleResponse("", engineapi.Mutation, mutateResp.Message, mutateResp.Status), resource } - return engineapi.RulePass( - "", - engineapi.Mutation, - mutateResp.Message, - ).WithPatches(mutateResp.Patches...), mutateResp.PatchedResource + return engineapi.RulePass("", engineapi.Mutation, mutateResp.Message), mutateResp.PatchedResource } func TestProcessPatches_EmptyPatches(t *testing.T) { - emptyRule := &types.Rule{Name: "emptyRule"} - resourceUnstructured, err := kubeutils.BytesToUnstructured([]byte(endpointsDocument)) - if err != nil { - t.Error(err) - } + // load resource + bytes := loadYaml(t, "testdata/endpoints.yaml") + var resource unstructured.Unstructured + require.NoError(t, resource.UnmarshalJSON(bytes)) - rr, _ := applyPatches(emptyRule, *resourceUnstructured) - assert.Equal(t, rr.Status(), engineapi.RuleStatusError) - assert.Assert(t, len(rr.DeprecatedPatches()) == 0) + // use rule + rule := types.Rule{Name: "emptyRule"} + + // apply patches + rr, patched := applyPatches(&rule, resource) + + // assert + require.NotNil(t, rr) + require.Equal(t, engineapi.RuleStatusError, rr.Status()) + require.Equal(t, resource, patched) } func makeAddIsMutatedLabelPatch() jsonPatch { @@ -100,105 +86,150 @@ func makeRuleWithPatches(t *testing.T, patches []jsonPatch) *types.Rule { } func TestProcessPatches_EmptyDocument(t *testing.T) { + // load resource + var resource unstructured.Unstructured + + // use rule rule := makeRuleWithPatch(t, makeAddIsMutatedLabelPatch()) - rr, _ := applyPatches(rule, unstructured.Unstructured{}) - assert.Equal(t, rr.Status(), engineapi.RuleStatusError) - assert.Assert(t, len(rr.DeprecatedPatches()) == 0) + + // apply patches + rr, patched := applyPatches(rule, resource) + + // assert + require.Equal(t, engineapi.RuleStatusError, rr.Status()) + require.Equal(t, resource, patched) } func TestProcessPatches_AllEmpty(t *testing.T) { - emptyRule := &types.Rule{} - rr, _ := applyPatches(emptyRule, unstructured.Unstructured{}) - assert.Equal(t, rr.Status(), engineapi.RuleStatusError) - assert.Assert(t, len(rr.DeprecatedPatches()) == 0) + // load resource + var resource unstructured.Unstructured + + // use rule + rule := types.Rule{} + + // apply patches + rr, patched := applyPatches(&rule, resource) + + // assert + require.Equal(t, engineapi.RuleStatusError, rr.Status()) + require.Equal(t, resource, patched) } func TestProcessPatches_AddPathDoesntExist(t *testing.T) { + // load resource + bytes := loadYaml(t, "testdata/endpoints.yaml") + var resource unstructured.Unstructured + require.NoError(t, resource.UnmarshalJSON(bytes)) + + // use rule patch := makeAddIsMutatedLabelPatch() patch.Path = "/metadata/additional/is-mutated" rule := makeRuleWithPatch(t, patch) - resourceUnstructured, err := kubeutils.BytesToUnstructured([]byte(endpointsDocument)) - if err != nil { - t.Error(err) - } - rr, _ := applyPatches(rule, *resourceUnstructured) - assert.Equal(t, rr.Status(), engineapi.RuleStatusSkip) - assert.Assert(t, len(rr.DeprecatedPatches()) == 0) + + // apply patches + rr, patched := applyPatches(rule, resource) + + // assert + require.Equal(t, engineapi.RuleStatusSkip, rr.Status()) + require.Equal(t, resource, patched) } func TestProcessPatches_RemovePathDoesntExist(t *testing.T) { + // load resource + bytes := loadYaml(t, "testdata/endpoints.yaml") + var resource unstructured.Unstructured + require.NoError(t, resource.UnmarshalJSON(bytes)) + + // use rule patch := jsonPatch{Path: "/metadata/labels/is-mutated", Operation: "remove"} rule := makeRuleWithPatch(t, patch) - resourceUnstructured, err := kubeutils.BytesToUnstructured([]byte(endpointsDocument)) - if err != nil { - t.Error(err) - } - rr, _ := applyPatches(rule, *resourceUnstructured) - assert.Equal(t, rr.Status(), engineapi.RuleStatusSkip) - assert.Assert(t, len(rr.DeprecatedPatches()) == 0) + + // apply patches + rr, patched := applyPatches(rule, resource) + + // assert + require.Equal(t, engineapi.RuleStatusSkip, rr.Status()) + require.Equal(t, resource, patched) } func TestProcessPatches_AddAndRemovePathsDontExist_EmptyResult(t *testing.T) { + // load resource + bytes := loadYaml(t, "testdata/endpoints.yaml") + var resource unstructured.Unstructured + require.NoError(t, resource.UnmarshalJSON(bytes)) + + // use rule patch1 := jsonPatch{Path: "/metadata/labels/is-mutated", Operation: "remove"} patch2 := jsonPatch{Path: "/spec/labels/label3", Operation: "add", Value: "label3Value"} rule := makeRuleWithPatches(t, []jsonPatch{patch1, patch2}) - resourceUnstructured, err := kubeutils.BytesToUnstructured([]byte(endpointsDocument)) - if err != nil { - t.Error(err) - } - rr, _ := applyPatches(rule, *resourceUnstructured) - assert.Equal(t, rr.Status(), engineapi.RuleStatusPass) - assert.Equal(t, len(rr.DeprecatedPatches()), 1) + + // apply patches + rr, patched := applyPatches(rule, resource) + + // assert + require.Equal(t, engineapi.RuleStatusPass, rr.Status()) + require.NotEqual(t, patched.UnstructuredContent(), resource.UnstructuredContent()) + unstructured.SetNestedField(resource.UnstructuredContent(), "label3Value", "spec", "labels", "label3") + require.Equal(t, resource, patched) } func TestProcessPatches_AddAndRemovePathsDontExist_ContinueOnError_NotEmptyResult(t *testing.T) { + // load resource + bytes := loadYaml(t, "testdata/endpoints.yaml") + var resource unstructured.Unstructured + require.NoError(t, resource.UnmarshalJSON(bytes)) + + // use rule patch1 := jsonPatch{Path: "/metadata/labels/is-mutated", Operation: "remove"} patch2 := jsonPatch{Path: "/spec/labels/label2", Operation: "remove", Value: "label2Value"} patch3 := jsonPatch{Path: "/metadata/labels/label3", Operation: "add", Value: "label3Value"} rule := makeRuleWithPatches(t, []jsonPatch{patch1, patch2, patch3}) - resourceUnstructured, err := kubeutils.BytesToUnstructured([]byte(endpointsDocument)) - if err != nil { - t.Error(err) - } - rr, _ := applyPatches(rule, *resourceUnstructured) - assert.Equal(t, rr.Status(), engineapi.RuleStatusPass) - assert.Assert(t, len(rr.DeprecatedPatches()) != 0) - assertEqStringAndData(t, `{"path":"/metadata/labels/label3","op":"add","value":"label3Value"}`, []byte(rr.DeprecatedPatches()[0].Json())) + // apply patches + rr, patched := applyPatches(rule, resource) + + // assert + require.Equal(t, engineapi.RuleStatusPass, rr.Status()) + require.NotEqual(t, patched.UnstructuredContent(), resource.UnstructuredContent()) + unstructured.SetNestedField(resource.UnstructuredContent(), "label3Value", "metadata", "labels", "label3") + require.Equal(t, resource, patched) } func TestProcessPatches_RemovePathDoesntExist_EmptyResult(t *testing.T) { + // load resource + bytes := loadYaml(t, "testdata/endpoints.yaml") + var resource unstructured.Unstructured + require.NoError(t, resource.UnmarshalJSON(bytes)) + + // use rule patch := jsonPatch{Path: "/metadata/labels/is-mutated", Operation: "remove"} rule := makeRuleWithPatch(t, patch) - resourceUnstructured, err := kubeutils.BytesToUnstructured([]byte(endpointsDocument)) - if err != nil { - t.Error(err) - } - rr, _ := applyPatches(rule, *resourceUnstructured) - assert.Equal(t, rr.Status(), engineapi.RuleStatusSkip) - assert.Assert(t, len(rr.DeprecatedPatches()) == 0) + + // apply patches + rr, patched := applyPatches(rule, resource) + + // assert + require.Equal(t, engineapi.RuleStatusSkip, rr.Status()) + require.Equal(t, resource, patched) } func TestProcessPatches_RemovePathDoesntExist_NotEmptyResult(t *testing.T) { + // load resource + bytes := loadYaml(t, "testdata/endpoints.yaml") + var resource unstructured.Unstructured + require.NoError(t, resource.UnmarshalJSON(bytes)) + + // use rule patch1 := jsonPatch{Path: "/metadata/labels/is-mutated", Operation: "remove"} patch2 := jsonPatch{Path: "/metadata/labels/label2", Operation: "add", Value: "label2Value"} rule := makeRuleWithPatches(t, []jsonPatch{patch1, patch2}) - resourceUnstructured, err := kubeutils.BytesToUnstructured([]byte(endpointsDocument)) - if err != nil { - t.Error(err) - } - rr, _ := applyPatches(rule, *resourceUnstructured) - assert.Equal(t, rr.Status(), engineapi.RuleStatusPass) - assert.Assert(t, len(rr.DeprecatedPatches()) == 1) - assertEqStringAndData(t, `{"path":"/metadata/labels/label2","op":"add","value":"label2Value"}`, []byte(rr.DeprecatedPatches()[0].Json())) -} - -func assertEqStringAndData(t *testing.T, str string, data []byte) { - var p1 jsonPatch - json.Unmarshal([]byte(str), &p1) - - var p2 jsonPatch - json.Unmarshal([]byte(data), &p2) - - assert.Equal(t, p1, p2) + + // apply patches + rr, patched := applyPatches(rule, resource) + + // assert + require.Equal(t, engineapi.RuleStatusPass, rr.Status()) + require.NotEqual(t, patched.UnstructuredContent(), resource.UnstructuredContent()) + unstructured.SetNestedField(resource.UnstructuredContent(), "label2Value", "metadata", "labels", "label2") + require.Equal(t, resource, patched) } diff --git a/pkg/engine/mutate/testdata/endpoints.yaml b/pkg/engine/mutate/testdata/endpoints.yaml new file mode 100644 index 0000000000..92b1b21c98 --- /dev/null +++ b/pkg/engine/mutate/testdata/endpoints.yaml @@ -0,0 +1,11 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: my-endpoint-service + labels: + originalLabel: isHere +subsets: +- addresses: + - ip: 1.2.3.4 + ports: + - port: 9376