1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-31 03:45:17 +00:00

mutate: support anchor on map/array

This commit is contained in:
Shuting Zhao 2019-10-31 20:38:24 -07:00
parent 6fa53a2304
commit ef8bf695b1
4 changed files with 107 additions and 98 deletions

View file

@ -18,7 +18,7 @@ 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)
glog.Errorf("Found anchor on different types of element: overlay %T, %v, resource %T, %v\nSkip processing overlay.", overlay, overlay, resource, resource)
return false
}
return true
@ -27,6 +27,7 @@ func meetConditions(resource, overlay interface{}) bool {
return checkConditions(resource, overlay)
}
// resource and overlay should be the same type
func checkConditions(resource, overlay interface{}) bool {
switch typedOverlay := overlay.(type) {
case map[string]interface{}:
@ -36,30 +37,108 @@ func checkConditions(resource, overlay interface{}) bool {
typedResource := resource.([]interface{})
return checkConditionOnArray(typedResource, typedOverlay)
default:
// anchor on non map/array is invalid:
// - anchor defined on values
return true
}
}
func checkConditionOnMap(resourceMap, overlayMap map[string]interface{}) bool {
anchors := getAnchorsFromMap(overlayMap)
if len(anchors) > 0 {
if !isConditionMetOnMap(resourceMap, anchors) {
return false
}
return true
// compareOverlay compare values in anchormap and resourcemap
// i.e. check if B1 == B2
// overlay - (A): B1
// resource - A: B2
func compareOverlay(resource, overlay interface{}) bool {
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 false
}
for key, value := range overlayMap {
resourcePart, ok := resourceMap[key]
if ok && !anchor.IsAddingAnchor(key) {
if !meetConditions(resourcePart, value) {
switch typedOverlay := overlay.(type) {
case map[string]interface{}:
typedResource := resource.(map[string]interface{})
for key, overlayVal := range typedOverlay {
noAnchorKey := removeAnchor(key)
resourceVal, ok := typedResource[noAnchorKey]
if !ok {
return false
}
if !compareOverlay(resourceVal, overlayVal) {
return false
}
}
case []interface{}:
typedResource := resource.([]interface{})
for _, overlayElement := range typedOverlay {
for _, resourceElement := range typedResource {
if !compareOverlay(resourceElement, overlayElement) {
return false
}
}
}
case string, float64, int, int64, bool, nil:
if !ValidateValueWithPattern(resource, overlay) {
glog.Errorf("Mutate rule failed validating value %v with overlay %v", resource, overlay)
return false
}
default:
glog.Errorf("Mutate overlay has unknown type %T, value %v", overlay, overlay)
return false
}
// key does not exist or isAddingAnchor
return true
}
func checkConditionOnMap(resourceMap, overlayMap map[string]interface{}) bool {
anchors, overlayWithoutAnchor := getAnchorAndElementsFromMap(overlayMap)
if !validateConditionAnchorMap(resourceMap, anchors) {
return false
}
if !validateNonAnchorOverlayMap(resourceMap, overlayWithoutAnchor) {
return false
}
// empty overlayMap
return true
}
func validateConditionAnchorMap(resourceMap, anchors map[string]interface{}) bool {
for key, overlayValue := range anchors {
// skip if key does not have condition anchor
if !anchor.IsConditionAnchor(key) {
continue
}
// validate condition anchor map
noAnchorKey := removeAnchor(key)
if resourceValue, ok := resourceMap[noAnchorKey]; ok {
if !compareOverlay(resourceValue, overlayValue) {
return false
}
} else {
// noAnchorKey doesn't exist in resource
return false
}
}
return true
}
func validateNonAnchorOverlayMap(resourceMap, overlayWithoutAnchor map[string]interface{}) bool {
// validate resource map (anchors could exist in resource)
for key, overlayValue := range overlayWithoutAnchor {
resourceValue, ok := resourceMap[key]
if !ok {
// policy: "(image)": "*:latest",
// "imagePullPolicy": "IfNotPresent",
// resource: "(image)": "*:latest",
// the above case should be allowed
continue
}
if !meetConditions(resourceValue, overlayValue) {
return false
}
}
return true
}
@ -85,6 +164,7 @@ func checkConditionsOnArrayOfSameTypes(resource, overlay []interface{}) bool {
case map[string]interface{}:
return checkConditionsOnArrayOfMaps(resource, overlay)
default:
// TODO: array of array?
glog.Warningf("Anchors not supported in overlay of array type %T\n", overlay[0])
return false
}
@ -92,83 +172,11 @@ func checkConditionsOnArrayOfSameTypes(resource, overlay []interface{}) bool {
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) {
for _, resourceMap := range resource {
if !checkConditionOnMap(resourceMap.(map[string]interface{}), overlayElement.(map[string]interface{})) {
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 {
continue
}
if !ValidateValueWithPattern(value, pattern) {
return false
}
}
return true
}

View file

@ -30,7 +30,7 @@ func TestMeetConditions_NoAnchor(t *testing.T) {
assert.Assert(t, res)
}
func TestMeetConditions_invalidConditionalAnchor(t *testing.T) {
func TestMeetConditions_conditionalAnchorOnMap(t *testing.T) {
resourceRaw := []byte(`
{
"apiVersion":"v1",
@ -89,8 +89,8 @@ func TestMeetConditions_invalidConditionalAnchor(t *testing.T) {
"ports":[
{
"name":"secure-connection",
"port":444,
"protocol":"UDP"
"port":443,
"protocol":"TCP"
}
]
}
@ -100,7 +100,7 @@ func TestMeetConditions_invalidConditionalAnchor(t *testing.T) {
json.Unmarshal(overlayRaw, &overlay)
res = meetConditions(resource, overlay)
assert.Assert(t, !res)
assert.Assert(t, res)
}
func TestMeetConditions_DifferentTypes(t *testing.T) {

View file

@ -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"
}
]

View file

@ -223,8 +223,8 @@ 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 {