1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 16:06:56 +00:00

Merge pull request #430 from nirmata/158_array_validation

158 array validation
This commit is contained in:
shuting 2019-11-05 11:39:05 -08:00 committed by GitHub
commit bdb677abf6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 455 additions and 199 deletions

View file

@ -53,16 +53,16 @@ For conditional anchors, the child element is considered to be part of the "if"
````yaml
pattern:
metadata:
labels:
allow-docker: true
spec:
metadata:
labels:
allow-docker: true
(volumes):
(hostPath):
path: "/var/run/docker.sock"
````
This reads as "If a hostPath volume exists and the path it equals /var/run/docker.sock, then a label "allow-docker" must be specified with a value of true."
This reads as "If a hostPath volume exists and the path equals /var/run/docker.sock, then a label "allow-docker" must be specified with a value of true."
For equality anchors, a child element is considered to be part of the "then" clause. Consider this pattern:

View file

@ -31,7 +31,7 @@ func processOverlay(rule kyverno.Rule, resource unstructured.Unstructured) (resp
patches, err := processOverlayPatches(resource.UnstructuredContent(), rule.Mutation.Overlay)
// resource does not satisfy the overlay pattern, we dont apply this rule
if err != nil && strings.Contains(err.Error(), "Conditions are not met") {
glog.V(4).Infof("Resource %s/%s/%s does not meet the conditions in the rule %s with overlay pattern %s", resource.GetKind(), resource.GetNamespace(), resource.GetName(), rule.Name, rule.Mutation.Overlay)
glog.Errorf("Resource %s/%s/%s does not meet the conditions in the rule %s with overlay pattern %s", resource.GetKind(), resource.GetNamespace(), resource.GetName(), rule.Name, rule.Mutation.Overlay)
//TODO: send zero response and not consider this as applied?
return RuleResponse{}, resource
}
@ -69,15 +69,17 @@ func processOverlay(rule kyverno.Rule, resource unstructured.Unstructured) (resp
// rule application succesfuly
response.Success = true
response.Message = fmt.Sprintf("succesfully process overlay")
response.Message = fmt.Sprintf("successfully processed overlay")
response.Patches = patches
// apply the patches to the resource
return response, patchedResource
}
func processOverlayPatches(resource, overlay interface{}) ([][]byte, error) {
if !meetConditions(resource, overlay) {
return nil, errors.New("Conditions are not met")
if path, err := meetConditions(resource, overlay); err != nil {
glog.V(4).Infof("Mutate rule: failed to validate condition at %s, err: %v", path, err)
return nil, fmt.Errorf("Conditions are not met at %s, %v", path, err)
}
return mutateResourceWithOverlay(resource, overlay)

View file

@ -1,16 +1,23 @@
package engine
import (
"fmt"
"reflect"
"strconv"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/engine/anchor"
)
func meetConditions(resource, overlay interface{}) bool {
func meetConditions(resource, overlay interface{}) (string, error) {
return checkConditions(resource, overlay, "/")
}
// resource and overlay should be the same type
func checkConditions(resource, overlay interface{}, path string) (string, error) {
// overlay has no anchor, return true
if !hasNestedAnchors(overlay) {
return true
return "", nil
}
// resource item exists but has different type
@ -18,157 +25,181 @@ func meetConditions(resource, overlay interface{}) bool {
// conditon never be true in this case
if reflect.TypeOf(resource) != reflect.TypeOf(overlay) {
if hasNestedAnchors(overlay) {
glog.V(3).Infof("Found anchor on different types of element: overlay %T, resource %T\nSkip processing overlay.", overlay, resource)
return false
glog.V(4).Infof("Found anchor on different types of element at path %s: overlay %T, resource %T", path, overlay, resource)
return path, fmt.Errorf("Found anchor on different types of element at path %s: overlay %T %v, resource %T %v", path, overlay, overlay, resource, resource)
}
return true
return "", nil
}
return checkConditions(resource, overlay)
}
func checkConditions(resource, overlay interface{}) bool {
switch typedOverlay := overlay.(type) {
case map[string]interface{}:
typedResource := resource.(map[string]interface{})
return checkConditionOnMap(typedResource, typedOverlay)
return checkConditionOnMap(typedResource, typedOverlay, path)
case []interface{}:
typedResource := resource.([]interface{})
return checkConditionOnArray(typedResource, typedOverlay)
return checkConditionOnArray(typedResource, typedOverlay, path)
default:
return true
// anchor on non map/array is invalid:
// - anchor defined on values
glog.Warningln("Found invalid conditional anchor: anchor defined on values")
return "", nil
}
}
func checkConditionOnMap(resourceMap, overlayMap map[string]interface{}) bool {
anchors := getAnchorsFromMap(overlayMap)
if len(anchors) > 0 {
if !isConditionMetOnMap(resourceMap, anchors) {
return false
}
return true
func checkConditionOnMap(resourceMap, overlayMap map[string]interface{}, path string) (string, error) {
anchors, overlayWithoutAnchor := getAnchorAndElementsFromMap(overlayMap)
// validate resource with conditions
if newPath, err := validateConditionAnchorMap(resourceMap, anchors, path); err != nil {
return newPath, err
}
for key, value := range overlayMap {
resourcePart, ok := resourceMap[key]
if ok && !anchor.IsAddingAnchor(key) {
if !meetConditions(resourcePart, value) {
return false
}
}
// traverse overlay pattern to further validate conditions
if newPath, err := validateNonAnchorOverlayMap(resourceMap, overlayWithoutAnchor, path); err != nil {
return newPath, err
}
// key does not exist or isAddingAnchor
return true
// empty overlayMap
return "", nil
}
func checkConditionOnArray(resource, overlay []interface{}) bool {
if 0 == len(resource) {
return false
}
func checkConditionOnArray(resource, overlay []interface{}, path string) (string, error) {
if 0 == len(overlay) {
return true
glog.Infof("Mutate overlay pattern is empty, path %s", path)
return "", nil
}
if reflect.TypeOf(resource[0]) != reflect.TypeOf(overlay[0]) {
glog.Warningf("Overlay array and resource array have elements of different types: %T and %T", overlay[0], resource[0])
return false
glog.V(4).Infof("Overlay array and resource array have elements of different types: %T and %T", overlay[0], resource[0])
return path, fmt.Errorf("Overlay array and resource array have elements of different types: %T and %T", overlay[0], resource[0])
}
return checkConditionsOnArrayOfSameTypes(resource, overlay)
return checkConditionsOnArrayOfSameTypes(resource, overlay, path)
}
func checkConditionsOnArrayOfSameTypes(resource, overlay []interface{}) bool {
switch overlay[0].(type) {
case map[string]interface{}:
return checkConditionsOnArrayOfMaps(resource, overlay)
default:
glog.Warningf("Anchors not supported in overlay of array type %T\n", overlay[0])
return false
}
}
func checkConditionsOnArrayOfMaps(resource, overlay []interface{}) bool {
for _, overlayElement := range overlay {
typedOverlay := overlayElement.(map[string]interface{})
anchors, overlayWithoutAnchor := getElementsFromMap(typedOverlay)
if len(anchors) > 0 {
if !validAnchorMap(anchors) {
return false
}
if !isConditionMet(resource, anchors) {
return false
}
}
for key, val := range overlayWithoutAnchor {
if hasNestedAnchors(val) {
for _, resourceElement := range resource {
typedResource := resourceElement.(map[string]interface{})
if resourcePart, ok := typedResource[key]; ok {
if !meetConditions(resourcePart, val) {
return false
}
}
}
}
}
}
return true
}
func validAnchorMap(anchors map[string]interface{}) bool {
for _, val := range anchors {
switch val.(type) {
case map[string]interface{}, []interface{}:
glog.Warning("Maps and arrays as patterns are not supported")
return false
}
}
return true
}
func isConditionMet(resource []interface{}, anchors map[string]interface{}) bool {
for _, resourceElement := range resource {
typedResource := resourceElement.(map[string]interface{})
for key, pattern := range anchors {
key = key[1 : len(key)-1]
value, ok := typedResource[key]
if !ok {
continue
}
if len(resource) == 1 {
if !ValidateValueWithPattern(value, pattern) {
return false
}
} else {
ValidateValueWithPattern(value, pattern)
return true
}
}
}
return true
}
func isConditionMetOnMap(resource, anchors map[string]interface{}) bool {
for key, pattern := range anchors {
key = key[1 : len(key)-1]
value, ok := resource[key]
if !ok {
func validateConditionAnchorMap(resourceMap, anchors map[string]interface{}, path string) (string, error) {
for key, overlayValue := range anchors {
// skip if key does not have condition anchor
if !anchor.IsConditionAnchor(key) {
continue
}
if !ValidateValueWithPattern(value, pattern) {
return false
// validate condition anchor map
noAnchorKey := removeAnchor(key)
curPath := path + noAnchorKey + "/"
if resourceValue, ok := resourceMap[noAnchorKey]; ok {
// compare entire resourceValue block
// return immediately on err since condition fails on this block
if newPath, err := compareOverlay(resourceValue, overlayValue, curPath); err != nil {
return newPath, err
}
} else {
// noAnchorKey doesn't exist in resource
return curPath, fmt.Errorf("resource field %s is not present", noAnchorKey)
}
}
return true
return "", nil
}
// compareOverlay compare values in anchormap and resourcemap
// i.e. check if B1 == B2
// overlay - (A): B1
// resource - A: B2
func compareOverlay(resource, overlay interface{}, path string) (string, error) {
if reflect.TypeOf(resource) != reflect.TypeOf(overlay) {
glog.Errorf("Found anchor on different types of element: overlay %T, resource %T\nSkip processing overlay.", overlay, resource)
return path, fmt.Errorf("")
}
switch typedOverlay := overlay.(type) {
case map[string]interface{}:
typedResource := resource.(map[string]interface{})
for key, overlayVal := range typedOverlay {
noAnchorKey := removeAnchor(key)
curPath := path + noAnchorKey + "/"
resourceVal, ok := typedResource[noAnchorKey]
if !ok {
return curPath, fmt.Errorf("Field %s is not present", noAnchorKey)
}
if newPath, err := compareOverlay(resourceVal, overlayVal, curPath); err != nil {
return newPath, err
}
}
case []interface{}:
typedResource := resource.([]interface{})
for _, overlayElement := range typedOverlay {
for _, resourceElement := range typedResource {
if newPath, err := compareOverlay(resourceElement, overlayElement, path); err != nil {
return newPath, err
}
}
}
case string, float64, int, int64, bool, nil:
if !ValidateValueWithPattern(resource, overlay) {
glog.V(4).Infof("Mutate rule: failed validating value %v with overlay %v", resource, overlay)
return path, fmt.Errorf("failed validating value %v with overlay %v", resource, overlay)
}
default:
return path, fmt.Errorf("overlay has unknown type %T, value %v", overlay, overlay)
}
return "", nil
}
// validateNonAnchorOverlayMap validate anchor condition in overlay block without anchor
func validateNonAnchorOverlayMap(resourceMap, overlayWithoutAnchor map[string]interface{}, path string) (string, error) {
// validate resource map (anchors could exist in resource)
for key, overlayValue := range overlayWithoutAnchor {
curPath := path + key + "/"
resourceValue, ok := resourceMap[key]
if !ok {
// policy: "(image)": "*:latest",
// "imagePullPolicy": "IfNotPresent",
// resource: "(image)": "*:latest",
// the above case should be allowed
continue
}
if newPath, err := checkConditions(resourceValue, overlayValue, curPath); err != nil {
return newPath, err
}
}
return "", nil
}
func checkConditionsOnArrayOfSameTypes(resource, overlay []interface{}, path string) (string, error) {
switch overlay[0].(type) {
case map[string]interface{}:
return checkConditionsOnArrayOfMaps(resource, overlay, path)
default:
for i, overlayElement := range overlay {
curPath := path + strconv.Itoa(i) + "/"
path, err := checkConditions(resource[i], overlayElement, curPath)
if err != nil {
return path, err
}
}
}
return "", nil
}
func checkConditionsOnArrayOfMaps(resource, overlay []interface{}, path string) (string, error) {
var newPath string
var err error
for i, overlayElement := range overlay {
for _, resourceMap := range resource {
curPath := path + strconv.Itoa(i) + "/"
newPath, err = checkConditionOnMap(resourceMap.(map[string]interface{}), overlayElement.(map[string]interface{}), curPath)
// when resource has multiple same blocks of the overlay block
// return true if there is one resource block meet the overlay pattern
// reference: TestMeetConditions_AtleastOneExist
if err == nil {
return "", nil
}
}
}
// report last error
return newPath, err
}

View file

@ -2,6 +2,7 @@ package engine
import (
"encoding/json"
"strings"
"testing"
"gotest.tools/assert"
@ -26,11 +27,11 @@ func TestMeetConditions_NoAnchor(t *testing.T) {
json.Unmarshal(overlayRaw, &overlay)
res := meetConditions(nil, overlay)
assert.Assert(t, res)
_, err := meetConditions(nil, overlay)
assert.Assert(t, err == nil)
}
func TestMeetConditions_invalidConditionalAnchor(t *testing.T) {
func TestMeetConditions_conditionalAnchorOnMap(t *testing.T) {
resourceRaw := []byte(`
{
"apiVersion":"v1",
@ -79,8 +80,8 @@ func TestMeetConditions_invalidConditionalAnchor(t *testing.T) {
json.Unmarshal(resourceRaw, &resource)
json.Unmarshal(overlayRaw, &overlay)
res := meetConditions(resource, overlay)
assert.Assert(t, !res)
_, err := meetConditions(resource, overlay)
assert.Assert(t, err != nil)
overlayRaw = []byte(`
{
@ -89,8 +90,8 @@ func TestMeetConditions_invalidConditionalAnchor(t *testing.T) {
"ports":[
{
"name":"secure-connection",
"port":444,
"protocol":"UDP"
"port":443,
"(protocol)":"TCP"
}
]
}
@ -99,28 +100,24 @@ func TestMeetConditions_invalidConditionalAnchor(t *testing.T) {
json.Unmarshal(overlayRaw, &overlay)
res = meetConditions(resource, overlay)
assert.Assert(t, !res)
_, err = meetConditions(resource, overlay)
assert.NilError(t, err)
}
func TestMeetConditions_DifferentTypes(t *testing.T) {
resourceRaw := []byte(`
{
"apiVersion":"v1",
"kind":"Endpoints",
"metadata":{
"name":"test-endpoint",
{
"apiVersion": "v1",
"kind": "Endpoints",
"metadata": {
"name": "test-endpoint"
},
"subsets":[
{
"addresses":[
{
"ip":"192.168.10.171"
}
],
"subsets": {
"addresses": {
"ip": "192.168.10.171"
}
]
}`)
}
}`)
overlayRaw := []byte(`
{
@ -142,8 +139,8 @@ func TestMeetConditions_DifferentTypes(t *testing.T) {
json.Unmarshal(overlayRaw, &overlay)
// anchor exist
res := meetConditions(resource, overlay)
assert.Assert(t, !res)
_, err := meetConditions(resource, overlay)
assert.Assert(t, strings.Contains(err.Error(), "Found anchor on different types of element at path /subsets/"))
}
func TestMeetConditions_anchosInSameObject(t *testing.T) {
@ -195,9 +192,8 @@ func TestMeetConditions_anchosInSameObject(t *testing.T) {
json.Unmarshal(resourceRaw, &resource)
json.Unmarshal(overlayRaw, &overlay)
// no anchor
res := meetConditions(resource, overlay)
assert.Assert(t, !res)
_, err := meetConditions(resource, overlay)
assert.Error(t, err, "failed validating value 443 with overlay 444")
}
func TestMeetConditions_anchorOnPeer(t *testing.T) {
@ -254,8 +250,8 @@ func TestMeetConditions_anchorOnPeer(t *testing.T) {
json.Unmarshal(resourceRaw, &resource)
json.Unmarshal(overlayRaw, &overlay)
res := meetConditions(resource, overlay)
assert.Assert(t, res)
_, err := meetConditions(resource, overlay)
assert.NilError(t, err)
}
func TestMeetConditions_anchorsOnMetaAndSpec(t *testing.T) {
@ -331,8 +327,8 @@ func TestMeetConditions_anchorsOnMetaAndSpec(t *testing.T) {
json.Unmarshal(resourceRaw, &resource)
json.Unmarshal(overlayRaw, &overlay)
res := meetConditions(resource, overlay)
assert.Assert(t, res)
_, err := meetConditions(resource, overlay)
assert.NilError(t, err)
}
var resourceRawAnchorOnPeers = []byte(`{
@ -412,8 +408,8 @@ func TestMeetConditions_anchorsOnPeer_single(t *testing.T) {
json.Unmarshal(resourceRawAnchorOnPeers, &resource)
json.Unmarshal(overlayRaw, &overlay)
res := meetConditions(resource, overlay)
assert.Assert(t, res)
_, err := meetConditions(resource, overlay)
assert.NilError(t, err)
}
func TestMeetConditions_anchorsOnPeer_two(t *testing.T) {
@ -425,7 +421,7 @@ func TestMeetConditions_anchorsOnPeer_two(t *testing.T) {
{
"(image)": "*/nginx-unprivileged",
"securityContext": {
"(runAsNonRoot)": true,
"(runAsNonRoot)": false,
"allowPrivilegeEscalation": false
},
"env": [
@ -446,8 +442,8 @@ func TestMeetConditions_anchorsOnPeer_two(t *testing.T) {
json.Unmarshal(resourceRawAnchorOnPeers, &resource)
json.Unmarshal(overlayRaw, &overlay)
res := meetConditions(resource, overlay)
assert.Assert(t, res)
_, err := meetConditions(resource, overlay)
assert.Error(t, err, "failed validating value true with overlay false")
overlayRaw = []byte(`{
"spec": {
@ -475,8 +471,8 @@ func TestMeetConditions_anchorsOnPeer_two(t *testing.T) {
json.Unmarshal(overlayRaw, &overlay)
res = meetConditions(resource, overlay)
assert.Assert(t, res)
_, err = meetConditions(resource, overlay)
assert.NilError(t, err)
overlayRaw = []byte(`{
"spec": {
@ -504,8 +500,8 @@ func TestMeetConditions_anchorsOnPeer_two(t *testing.T) {
json.Unmarshal(overlayRaw, &overlay)
res = meetConditions(resource, overlay)
assert.Assert(t, res)
_, err = meetConditions(resource, overlay)
assert.NilError(t, err)
}
func TestMeetConditions_anchorsOnPeer_multiple(t *testing.T) {
@ -538,8 +534,8 @@ func TestMeetConditions_anchorsOnPeer_multiple(t *testing.T) {
json.Unmarshal(resourceRawAnchorOnPeers, &resource)
json.Unmarshal(overlayRaw, &overlay)
res := meetConditions(resource, overlay)
assert.Assert(t, res)
_, err := meetConditions(resource, overlay)
assert.NilError(t, err)
overlayRaw = []byte(`{
"spec": {
@ -567,8 +563,8 @@ func TestMeetConditions_anchorsOnPeer_multiple(t *testing.T) {
json.Unmarshal(overlayRaw, &overlay)
res = meetConditions(resource, overlay)
assert.Assert(t, res)
_, err = meetConditions(resource, overlay)
assert.NilError(t, err)
overlayRaw = []byte(`{
"spec": {
@ -581,10 +577,10 @@ func TestMeetConditions_anchorsOnPeer_multiple(t *testing.T) {
"runAsNonRoot": true,
"(allowPrivilegeEscalation)": false
},
"env": [
"(env)": [
{
"(name)": "ENV_KEY",
"(value)": "ENV_VALUE"
"name": "ENV_KEY",
"value": "ENV_VALUE1"
}
]
}
@ -596,7 +592,66 @@ func TestMeetConditions_anchorsOnPeer_multiple(t *testing.T) {
json.Unmarshal(overlayRaw, &overlay)
res = meetConditions(resource, overlay)
assert.Assert(t, res)
_, err = meetConditions(resource, overlay)
assert.Error(t, err, "failed validating value ENV_VALUE with overlay ENV_VALUE1")
}
func TestMeetConditions_AtleastOneExist(t *testing.T) {
overlayRaw := []byte(`
{
"metadata": {
"annotations": {
"+(cluster-autoscaler.kubernetes.io/safe-to-evict)": true
}
},
"spec": {
"volumes": [
{
"(emptyDir)": {}
}
]
}
}`)
// validate when resource has multiple same blocks
resourceRaw := []byte(`
{
"spec": {
"containers": [
{
"image": "k8s.gcr.io/test-webserver",
"name": "test-container",
"volumeMounts": [
{
"mountPath": "/cache",
"name": "cache-volume"
}
]
}
],
"volumes": [
{
"name": "cache-volume1",
"emptyDir": 1
},
{
"name": "cache-volume2",
"emptyDir": 2
},
{
"name": "cache-volume3",
"emptyDir": {}
}
]
}
}`)
var resource, overlay interface{}
json.Unmarshal(resourceRaw, &resource)
json.Unmarshal(overlayRaw, &overlay)
path, err := meetConditions(resource, overlay)
assert.NilError(t, err)
assert.Assert(t, len(path) == 0)
}

View file

@ -494,7 +494,7 @@ func TestProcessOverlayPatches_ImagePullPolicy(t *testing.T) {
json.Unmarshal(overlayRaw, &overlay)
patches, err = processOverlayPatches(resource, overlay)
assert.Error(t, err, "Conditions are not met")
assert.Error(t, err, "Conditions are not met at /spec/template/metadata/labels/app/, failed validating value nginx with overlay nginx1")
assert.Assert(t, len(patches) == 0)
}
@ -590,10 +590,10 @@ func TestProcessOverlayPatches_AddingAnchorInsideListElement(t *testing.T) {
"imagePullPolicy":"Always"
},
{
"image":"debian:10"
"image":"debian:latest"
},
{
"image":"ubuntu:18.04",
"image":"ubuntu:latest",
"imagePullPolicy":"Always"
}
]
@ -647,10 +647,11 @@ func TestProcessOverlayPatches_AddingAnchorInsideListElement(t *testing.T) {
"imagePullPolicy":"Always"
},
{
"image":"debian:10"
"image":"debian:latest",
"imagePullPolicy":"IfNotPresent"
},
{
"image":"ubuntu:18.04",
"image":"ubuntu:latest",
"imagePullPolicy":"Always"
}
]
@ -806,7 +807,7 @@ func TestProcessOverlayPatches_anchorOnPeer(t *testing.T) {
json.Unmarshal(overlayRaw, &overlay)
patches, err = processOverlayPatches(resource, overlay)
assert.Error(t, err, "Conditions are not met")
assert.Error(t, err, "Conditions are not met at /subsets/0/ports/0/port/, failed validating value 443 with overlay 444")
assert.Assert(t, len(patches) == 0)
}
@ -946,3 +947,96 @@ func TestProcessOverlayPatches_insertWithCondition(t *testing.T) {
compareJSONAsMap(t, expectedResult, doc)
}
func TestProcessOverlayPatches_InsertIfNotPresentWithConditions(t *testing.T) {
overlayRaw := []byte(`
{
"metadata": {
"annotations": {
"+(cluster-autoscaler.kubernetes.io/safe-to-evict)": true
}
},
"spec": {
"volumes": [
{
"(emptyDir)": {}
}
]
}
}`)
resourceRaw := []byte(`
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "pod-with-emptydir"
},
"spec": {
"containers": [
{
"image": "k8s.gcr.io/test-webserver",
"name": "test-container",
"volumeMounts": [
{
"mountPath": "/cache",
"name": "cache-volume"
}
]
}
],
"volumes": [
{
"name": "cache-volume",
"emptyDir": {}
}
]
}
}`)
var resource, overlay interface{}
json.Unmarshal(resourceRaw, &resource)
json.Unmarshal(overlayRaw, &overlay)
patches, err := processOverlayPatches(resource, overlay)
assert.NilError(t, err)
assert.Assert(t, len(patches) != 0)
doc, err := ApplyPatches(resourceRaw, patches)
assert.NilError(t, err)
expectedResult := []byte(`
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "pod-with-emptydir",
"annotations": {
"cluster-autoscaler.kubernetes.io/safe-to-evict": true
}
},
"spec": {
"containers": [
{
"image": "k8s.gcr.io/test-webserver",
"name": "test-container",
"volumeMounts": [
{
"mountPath": "/cache",
"name": "cache-volume"
}
]
}
],
"volumes": [
{
"name": "cache-volume",
"emptyDir": {}
}
]
}
}`)
compareJSONAsMap(t, expectedResult, doc)
}

View file

@ -210,12 +210,12 @@ func ParseNamespaceFromObject(bytes []byte) string {
return ""
}
// Validation
// getAnchorsFromMap gets the conditional anchor map
func getAnchorsFromMap(anchorsMap map[string]interface{}) map[string]interface{} {
result := make(map[string]interface{})
for key, value := range anchorsMap {
if anchor.IsConditionAnchor(key) || anchor.IsExistanceAnchor(key) {
if anchor.IsConditionAnchor(key) {
result[key] = value
}
}
@ -223,12 +223,12 @@ func getAnchorsFromMap(anchorsMap map[string]interface{}) map[string]interface{}
return result
}
// Mutation
func getElementsFromMap(anchorsMap map[string]interface{}) (map[string]interface{}, map[string]interface{}) {
// getAnchorAndElementsFromMap gets the condition anchor map and resource map without anchor
func getAnchorAndElementsFromMap(anchorsMap map[string]interface{}) (map[string]interface{}, map[string]interface{}) {
anchors := make(map[string]interface{})
elementsWithoutanchor := make(map[string]interface{})
for key, value := range anchorsMap {
if anchor.IsConditionAnchor(key) || anchor.IsExistanceAnchor(key) {
if anchor.IsConditionAnchor(key) {
anchors[key] = value
} else if !anchor.IsAddingAnchor(key) {
elementsWithoutanchor[key] = value

View file

@ -283,9 +283,8 @@ func compareResourceSpec(t *testing.T, resource engine.ResourceSpec, expectedRes
func compareRules(t *testing.T, rule engine.RuleResponse, expectedRule engine.RuleResponse) {
// name
if rule.Name != expectedRule.Name {
t.Errorf("rule name: expected %s, recieved %s", expectedRule.Name, rule.Name)
t.Errorf("rule name: expected %s, recieved %+v", expectedRule.Name, rule.Name)
// as the rule names dont match no need to compare the rest of the information
return
}
// type
if rule.Type != expectedRule.Type {
@ -443,11 +442,11 @@ func loadPolicy(t *testing.T, path string) *kyverno.ClusterPolicy {
}
func testScenario(t *testing.T, path string) {
//load scenario
scenario, err := loadScenario(t, path)
if err != nil {
t.Error(err)
return
}
runScenario(t, scenario)
}

View file

@ -126,3 +126,7 @@ func Test_validate_disallow_docker_sock_mount(t *testing.T) {
func Test_validate_disallow_helm_tiller(t *testing.T) {
testScenario(t, "test/scenarios/samples/best_practices/scenario_validate_disallow_helm_tiller.yaml")
}
func Test_mutate_add_safe_to_evict_annotation(t *testing.T) {
testScenario(t, "test/scenarios/samples/best_practices/scenario_mutate_safe-to-evict.yaml")
}

View file

@ -0,0 +1,22 @@
apiVersion: "kyverno.io/v1alpha1"
kind: "ClusterPolicy"
metadata:
name: "annotate-emptyDir"
annotations:
policies.kyverno.io/category: AutoScaling
policies.kyverno.io/description:
spec:
rules:
- name: "add-safe-to-evict-annotation"
match:
resources:
kinds:
- "Pod"
mutate:
overlay:
metadata:
annotations:
+(cluster-autoscaler.kubernetes.io/safe-to-evict): true
spec:
volumes:
- (emptyDir): {}

View file

@ -0,0 +1,16 @@
apiVersion: v1
kind: Pod
metadata:
name: pod-with-emptydir
annotations:
cluster-autoscaler.kubernetes.io/safe-to-evict: true
spec:
containers:
- image: k8s.gcr.io/test-webserver
name: test-container
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}

View file

@ -0,0 +1,14 @@
apiVersion: v1
kind: Pod
metadata:
name: pod-with-emptydir
spec:
containers:
- image: k8s.gcr.io/test-webserver
name: test-container
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}

View file

@ -1,4 +1,4 @@
# file path relative to project root
# file path is relative to project root
input:
policy: test/policy/mutate/policy_mutate_validate_qos.yaml
resource: test/resources/resource_mutate_validate_qos.yaml
@ -16,7 +16,7 @@ expected:
- name: add-memory-limit
type: Mutation
success: true
message: succesfully process overlay
message: successfully processed overlay
validation:
policyresponse:
policy: policy-qos

View file

@ -0,0 +1,19 @@
# file path is relative to project root
input:
policy: samples/best_practices/add_safe-to-evict_annotation.yaml
resource: test/resources/pod-with-emptydir.yaml
expected:
mutation:
patchedresource: test/output/pod-with-emptydir.yaml
policyresponse:
policy: annotate-emptyDir
resource:
kind: Pod
apiVersion: v1
namespace: ''
name: pod-with-emptydir
rules:
- name: add-safe-to-evict-annotation
type: Mutation
success: true
message: "successfully processed overlay"