package admissionpolicy import ( "encoding/json" "testing" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" "gotest.tools/assert" admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1" ) func Test_MutateResource(t *testing.T) { tests := []struct { name string rawPolicy []byte rawResource []byte expectedRawResource []byte }{ { name: "MAP ApplyConfiguration", rawPolicy: []byte(`{ "apiVersion": "admissionregistration.k8s.io/v1alpha1", "kind": "MutatingAdmissionPolicy", "metadata": { "name": "mutate-policy" }, "spec": { "matchConstraints": { "resourceRules": [ { "apiGroups": [ "" ], "apiVersions": [ "v1" ], "operations": [ "CREATE" ], "resources": [ "configmaps" ] } ] }, "failurePolicy": "Fail", "mutations": [ { "patchType": "ApplyConfiguration", "applyConfiguration": { "expression": "object.metadata.?labels[\"lfx-mentorship\"].hasValue() ? \n Object{} :\n Object{ metadata: Object.metadata{ labels: {\"lfx-mentorship\": \"kyverno\"}}}\n" } } ] } }`), rawResource: []byte(`{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": { "name": "game-demo", "labels": { "app": "game" } }, "data": { "player_initial_lives": "3" } }`), expectedRawResource: []byte(`{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": { "name": "game-demo", "labels": { "app": "game", "lfx-mentorship": "kyverno" } }, "data": { "player_initial_lives": "3" } }`), }, { name: "MAP JSONPatch", rawPolicy: []byte(`{ "apiVersion": "admissionregistration.k8s.io/v1alpha1", "kind": "MutatingAdmissionPolicy", "metadata": { "name": "mutate-policy" }, "spec": { "matchConstraints": { "resourceRules": [ { "apiGroups": [ "discovery.k8s.io" ], "apiVersions": [ "v1" ], "operations": [ "CREATE" ], "resources": [ "endpointslices" ] } ] }, "failurePolicy": "Fail", "reinvocationPolicy": "Never", "mutations": [ { "patchType": "JSONPatch", "jsonPatch": { "expression": "[\n JSONPatch{\n op: \"add\", path: \"/ports\",\n value: object.ports.map(\n p, \n {\n \"name\": p.name,\n \"port\": dyn(p.name.contains(\"secure\") ? 6443 : p.port)\n }\n )\n }\n]\n" } } ] } }`), rawResource: []byte(`{ "apiVersion": "discovery.k8s.io/v1", "kind": "EndpointSlice", "metadata": { "name": "example-abc", "labels": { "kubernetes.io/service-name": "example" } }, "addressType": "IPv4", "ports": [ { "name": "http", "protocol": "TCP", "port": 80 }, { "name": "secure", "protocol": "TCP" } ], "endpoints": [ { "addresses": [ "10.1.2.3" ], "conditions": { "ready": true }, "hostname": "pod-1", "nodeName": "node-1", "zone": "us-west2-a" } ] }`), expectedRawResource: []byte(`{ "apiVersion": "discovery.k8s.io/v1", "kind": "EndpointSlice", "metadata": { "name": "example-abc", "labels": { "kubernetes.io/service-name": "example" } }, "addressType": "IPv4", "ports": [ { "name": "http", "port": 80 }, { "name": "secure", "port": 6443 } ], "endpoints": [ { "addresses": [ "10.1.2.3" ], "conditions": { "ready": true }, "hostname": "pod-1", "nodeName": "node-1", "zone": "us-west2-a" } ] }`), }, { name: "MAP JSONPatch and ApplyConfiguration", rawPolicy: []byte(`{ "apiVersion": "admissionregistration.k8s.io/v1alpha1", "kind": "MutatingAdmissionPolicy", "metadata": { "name": "sample-policy" }, "spec": { "matchConstraints": { "resourceRules": [ { "apiGroups": [ "apps" ], "apiVersions": [ "v1" ], "operations": [ "CREATE" ], "resources": [ "deployments" ] } ] }, "failurePolicy": "Fail", "mutations": [ { "patchType": "ApplyConfiguration", "applyConfiguration": { "expression": "Object{\n spec: Object.spec{\n replicas: object.spec.replicas + 100\n }\n}\n" } }, { "patchType": "JSONPatch", "jsonPatch": { "expression": "[\n JSONPatch{\n op: \"replace\", \n path: \"/spec/replicas\", \n value: object.spec.replicas + 10\n }\n]\n" } } ] } }`), rawResource: []byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "name": "nginx-deployment", "labels": { "app": "nginx" } }, "spec": { "replicas": 3, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "labels": { "app": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx:1.14.2", "ports": [ { "containerPort": 80 } ] } ] } } } }`), expectedRawResource: []byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "name": "nginx-deployment", "labels": { "app": "nginx" } }, "spec": { "replicas": 113, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "labels": { "app": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx:1.14.2", "ports": [ { "containerPort": 80 } ] } ] } } } }`), }, { name: "Two mutations of type ApplyConfigurations", rawPolicy: []byte(`{ "apiVersion": "admissionregistration.k8s.io/v1alpha1", "kind": "MutatingAdmissionPolicy", "metadata": { "name": "sample-policy" }, "spec": { "matchConstraints": { "resourceRules": [ { "apiGroups": [ "apps" ], "apiVersions": [ "v1" ], "operations": [ "CREATE" ], "resources": [ "deployments" ] } ] }, "failurePolicy": "Fail", "mutations": [ { "patchType": "ApplyConfiguration", "applyConfiguration": { "expression": "Object{\n spec: Object.spec{\n replicas: object.spec.replicas + 100\n }\n}\n" } }, { "patchType": "ApplyConfiguration", "applyConfiguration": { "expression": "Object{\n spec: Object.spec{\n replicas: object.spec.replicas + 100\n }\n}\n" } } ] } }`), rawResource: []byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "name": "nginx-deployment", "labels": { "app": "nginx" } }, "spec": { "replicas": 3, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "labels": { "app": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx:1.14.2", "ports": [ { "containerPort": 80 } ] } ] } } } }`), expectedRawResource: []byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "name": "nginx-deployment", "labels": { "app": "nginx" } }, "spec": { "replicas": 203, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "labels": { "app": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx:1.14.2", "ports": [ { "containerPort": 80 } ] } ] } } } }`), }, { name: "Two mutations of type JSONPatch", rawPolicy: []byte(`{ "apiVersion": "admissionregistration.k8s.io/v1alpha1", "kind": "MutatingAdmissionPolicy", "metadata": { "name": "sample-policy" }, "spec": { "matchConstraints": { "resourceRules": [ { "apiGroups": [ "apps" ], "apiVersions": [ "v1" ], "operations": [ "CREATE" ], "resources": [ "deployments" ] } ] }, "failurePolicy": "Fail", "mutations": [ { "patchType": "JSONPatch", "jsonPatch": { "expression": "[\n JSONPatch{\n op: \"replace\", \n path: \"/spec/replicas\", \n value: object.spec.replicas + 10\n }\n]\n" } }, { "patchType": "JSONPatch", "jsonPatch": { "expression": "[\n JSONPatch{\n op: \"replace\", \n path: \"/spec/replicas\", \n value: object.spec.replicas + 10\n }\n]\n" } } ] } }`), rawResource: []byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "name": "nginx-deployment", "labels": { "app": "nginx" } }, "spec": { "replicas": 3, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "labels": { "app": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx:1.14.2", "ports": [ { "containerPort": 80 } ] } ] } } } }`), expectedRawResource: []byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "name": "nginx-deployment", "labels": { "app": "nginx" } }, "spec": { "replicas": 23, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "labels": { "app": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx:1.14.2", "ports": [ { "containerPort": 80 } ] } ] } } } }`), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { expectedResource, err := kubeutils.BytesToUnstructured(tt.expectedRawResource) assert.NilError(t, err) var policy admissionregistrationv1alpha1.MutatingAdmissionPolicy err = json.Unmarshal(tt.rawPolicy, &policy) assert.NilError(t, err) resource, err := kubeutils.BytesToUnstructured(tt.rawResource) assert.NilError(t, err) response, err := mutateResource(policy, *resource) assert.NilError(t, err) assert.DeepEqual(t, expectedResource.Object, response.PatchedResource.Object) }) } }