mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-31 03:45:17 +00:00
Implemented at least one exists logic
This commit is contained in:
parent
9002a27ddb
commit
9bacfe4363
6 changed files with 256 additions and 58 deletions
160
pkg/engine/anchor.go
Normal file
160
pkg/engine/anchor.go
Normal file
|
@ -0,0 +1,160 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/nirmata/kyverno/pkg/result"
|
||||
)
|
||||
|
||||
// CreateAnchorHandler is a factory that create anchor handlers
|
||||
func CreateAnchorHandler(anchor string, pattern interface{}, path string) ValidationAnchorHandler {
|
||||
switch {
|
||||
case isConditionAnchor(anchor):
|
||||
return NewConditionAnchorValidationHandler(anchor, pattern, path)
|
||||
case isExistanceAnchor(anchor):
|
||||
return NewExistanceAnchorValidationHandler(anchor, pattern, path)
|
||||
default:
|
||||
return NewNoAnchorValidationHandler(path)
|
||||
}
|
||||
}
|
||||
|
||||
// ValidationAnchorHandler is an interface that represents
|
||||
// a family of anchor handlers for array of maps
|
||||
// resourcePart must be an array of dictionaries
|
||||
// patternPart must be a dictionary with anchors
|
||||
type ValidationAnchorHandler interface {
|
||||
Handle(resourcePart []interface{}, patternPart map[string]interface{}) result.RuleApplicationResult
|
||||
}
|
||||
|
||||
// NoAnchorValidationHandler just calls validateMap
|
||||
// because no anchors were found in the pattern map
|
||||
type NoAnchorValidationHandler struct {
|
||||
path string
|
||||
}
|
||||
|
||||
// NewNoAnchorValidationHandler creates new instance of
|
||||
// NoAnchorValidationHandler
|
||||
func NewNoAnchorValidationHandler(path string) ValidationAnchorHandler {
|
||||
return &NoAnchorValidationHandler{
|
||||
path: path,
|
||||
}
|
||||
}
|
||||
|
||||
// Handle performs validation in context of NoAnchorValidationHandler
|
||||
func (navh *NoAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}) result.RuleApplicationResult {
|
||||
handlingResult := result.NewRuleApplicationResult("")
|
||||
|
||||
for i, resourceElement := range resourcePart {
|
||||
currentPath := navh.path + strconv.Itoa(i) + "/"
|
||||
|
||||
typedResourceElement, ok := resourceElement.(map[string]interface{})
|
||||
if !ok {
|
||||
handlingResult.FailWithMessagef("Pattern and resource have different structures. Path: %s. Expected %T, found %T", currentPath, patternPart, resourceElement)
|
||||
return handlingResult
|
||||
}
|
||||
|
||||
res := validateMap(typedResourceElement, patternPart, currentPath)
|
||||
handlingResult.MergeWith(&res)
|
||||
}
|
||||
|
||||
return handlingResult
|
||||
}
|
||||
|
||||
// ConditionAnchorValidationHandler performs
|
||||
// validation only for array elements that
|
||||
// pass condition in the anchor
|
||||
// (key): value
|
||||
type ConditionAnchorValidationHandler struct {
|
||||
anchor string
|
||||
pattern interface{}
|
||||
path string
|
||||
}
|
||||
|
||||
// NewConditionAnchorValidationHandler creates new instance of
|
||||
// NoAnchorValidationHandler
|
||||
func NewConditionAnchorValidationHandler(anchor string, pattern interface{}, path string) ValidationAnchorHandler {
|
||||
return &ConditionAnchorValidationHandler{
|
||||
anchor: anchor,
|
||||
pattern: pattern,
|
||||
path: path,
|
||||
}
|
||||
}
|
||||
|
||||
// Handle performs validation in context of ConditionAnchorValidationHandler
|
||||
func (cavh *ConditionAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}) result.RuleApplicationResult {
|
||||
_, handlingResult := handleConditionCases(resourcePart, patternPart, cavh.anchor, cavh.pattern, cavh.path)
|
||||
|
||||
return handlingResult
|
||||
}
|
||||
|
||||
// ExistanceAnchorValidationHandler performs
|
||||
// validation only for array elements that
|
||||
// pass condition in the anchor
|
||||
// AND requires an existance of at least one
|
||||
// element that passes this condition
|
||||
// ^(key): value
|
||||
type ExistanceAnchorValidationHandler struct {
|
||||
anchor string
|
||||
pattern interface{}
|
||||
path string
|
||||
}
|
||||
|
||||
// NewExistanceAnchorValidationHandler creates new instance of
|
||||
// NoAnchorValidationHandler
|
||||
func NewExistanceAnchorValidationHandler(anchor string, pattern interface{}, path string) ValidationAnchorHandler {
|
||||
return &ExistanceAnchorValidationHandler{
|
||||
anchor: anchor,
|
||||
pattern: pattern,
|
||||
path: path,
|
||||
}
|
||||
}
|
||||
|
||||
// Handle performs validation in context of ExistanceAnchorValidationHandler
|
||||
func (eavh *ExistanceAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}) result.RuleApplicationResult {
|
||||
anchoredEtries, handlingResult := handleConditionCases(resourcePart, patternPart, eavh.anchor, eavh.pattern, eavh.path)
|
||||
|
||||
if 0 == anchoredEtries {
|
||||
handlingResult.FailWithMessagef("Existance anchor %s used, but no suitable entries were found", eavh.anchor)
|
||||
}
|
||||
|
||||
return handlingResult
|
||||
}
|
||||
|
||||
// check if array element fits the anchor
|
||||
func checkForAnchorCondition(anchor string, pattern interface{}, resourceMap map[string]interface{}) bool {
|
||||
anchorKey := removeAnchor(anchor)
|
||||
|
||||
if value, ok := resourceMap[anchorKey]; ok {
|
||||
return ValidateValueWithPattern(value, pattern)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// both () and ^() are checking conditions and have a lot of similar logic
|
||||
// the only difference is that ^() requires existace of one element
|
||||
// anchoredEtries var counts this occurences.
|
||||
func handleConditionCases(resourcePart []interface{}, patternPart map[string]interface{}, anchor string, pattern interface{}, path string) (int, result.RuleApplicationResult) {
|
||||
handlingResult := result.NewRuleApplicationResult("")
|
||||
anchoredEtries := 0
|
||||
|
||||
for i, resourceElement := range resourcePart {
|
||||
currentPath := path + strconv.Itoa(i) + "/"
|
||||
|
||||
typedResourceElement, ok := resourceElement.(map[string]interface{})
|
||||
if !ok {
|
||||
handlingResult.FailWithMessagef("Pattern and resource have different structures. Path: %s. Expected %T, found %T", currentPath, patternPart, resourceElement)
|
||||
break
|
||||
}
|
||||
|
||||
if !checkForAnchorCondition(anchor, pattern, typedResourceElement) {
|
||||
continue
|
||||
}
|
||||
|
||||
anchoredEtries++
|
||||
res := validateMap(typedResourceElement, patternPart, currentPath)
|
||||
handlingResult.MergeWith(&res)
|
||||
}
|
||||
|
||||
return anchoredEtries, handlingResult
|
||||
}
|
|
@ -109,7 +109,7 @@ func applyOverlayToMap(resourceMap, overlayMap map[string]interface{}, path stri
|
|||
for key, value := range overlayMap {
|
||||
// skip anchor element because it has condition, not
|
||||
// the value that must replace resource value
|
||||
if wrappedWithParentheses(key) {
|
||||
if isConditionAnchor(key) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
@ -113,7 +113,7 @@ func getAnchorsFromMap(anchorsMap map[string]interface{}) map[string]interface{}
|
|||
result := make(map[string]interface{})
|
||||
|
||||
for key, value := range anchorsMap {
|
||||
if wrappedWithParentheses(key) {
|
||||
if isConditionAnchor(key) || isExistanceAnchor(key) {
|
||||
result[key] = value
|
||||
}
|
||||
}
|
||||
|
@ -121,6 +121,16 @@ func getAnchorsFromMap(anchorsMap map[string]interface{}) map[string]interface{}
|
|||
return result
|
||||
}
|
||||
|
||||
func getAnchorFromMap(anchorsMap map[string]interface{}) (string, interface{}) {
|
||||
for key, value := range anchorsMap {
|
||||
if isConditionAnchor(key) || isExistanceAnchor(key) {
|
||||
return key, value
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func findKind(kinds []string, kindGVK string) bool {
|
||||
for _, kind := range kinds {
|
||||
if kind == kindGVK {
|
||||
|
@ -130,7 +140,7 @@ func findKind(kinds []string, kindGVK string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func wrappedWithParentheses(str string) bool {
|
||||
func isConditionAnchor(str string) bool {
|
||||
if len(str) < 2 {
|
||||
return false
|
||||
}
|
||||
|
@ -138,6 +148,17 @@ func wrappedWithParentheses(str string) bool {
|
|||
return (str[0] == '(' && str[len(str)-1] == ')')
|
||||
}
|
||||
|
||||
func isExistanceAnchor(str string) bool {
|
||||
left := "^("
|
||||
right := ")"
|
||||
|
||||
if len(str) < len(left)+len(right) {
|
||||
return false
|
||||
}
|
||||
|
||||
return (str[:len(left)] == left && str[len(str)-len(right):] == right)
|
||||
}
|
||||
|
||||
// Checks if array object matches anchors. If not - skip - return true
|
||||
func skipArrayObject(object, anchors map[string]interface{}) bool {
|
||||
for key, pattern := range anchors {
|
||||
|
@ -158,10 +179,14 @@ func skipArrayObject(object, anchors map[string]interface{}) bool {
|
|||
|
||||
// removeAnchor remove special characters around anchored key
|
||||
func removeAnchor(key string) string {
|
||||
if wrappedWithParentheses(key) {
|
||||
if isConditionAnchor(key) {
|
||||
return key[1 : len(key)-1]
|
||||
}
|
||||
|
||||
if isExistanceAnchor(key) {
|
||||
return key[2 : len(key)-1]
|
||||
}
|
||||
|
||||
// TODO: Add logic for other anchors here
|
||||
|
||||
return key
|
||||
|
|
|
@ -331,3 +331,66 @@ func TestResourceMeetsDescription_MatchLabelsAndMatchExpressions(t *testing.T) {
|
|||
|
||||
assert.Assert(t, false == ResourceMeetsDescription(rawResource, resourceDescription, groupVersionKind))
|
||||
}
|
||||
|
||||
func TestWrappedWithParentheses_StringIsWrappedWithParentheses(t *testing.T) {
|
||||
str := "(something)"
|
||||
assert.Assert(t, isConditionAnchor(str))
|
||||
}
|
||||
|
||||
func TestWrappedWithParentheses_StringHasOnlyParentheses(t *testing.T) {
|
||||
str := "()"
|
||||
assert.Assert(t, isConditionAnchor(str))
|
||||
}
|
||||
|
||||
func TestWrappedWithParentheses_StringHasNoParentheses(t *testing.T) {
|
||||
str := "something"
|
||||
assert.Assert(t, !isConditionAnchor(str))
|
||||
}
|
||||
|
||||
func TestWrappedWithParentheses_StringHasLeftParentheses(t *testing.T) {
|
||||
str := "(something"
|
||||
assert.Assert(t, !isConditionAnchor(str))
|
||||
}
|
||||
|
||||
func TestWrappedWithParentheses_StringHasRightParentheses(t *testing.T) {
|
||||
str := "something)"
|
||||
assert.Assert(t, !isConditionAnchor(str))
|
||||
}
|
||||
|
||||
func TestWrappedWithParentheses_StringParenthesesInside(t *testing.T) {
|
||||
str := "so)m(et(hin)g"
|
||||
assert.Assert(t, !isConditionAnchor(str))
|
||||
}
|
||||
|
||||
func TestWrappedWithParentheses_Empty(t *testing.T) {
|
||||
str := ""
|
||||
assert.Assert(t, !isConditionAnchor(str))
|
||||
}
|
||||
|
||||
func TestIsExistanceAnchor_Yes(t *testing.T) {
|
||||
assert.Assert(t, isExistanceAnchor("^(abc)"))
|
||||
}
|
||||
|
||||
func TestIsExistanceAnchor_NoRightBracket(t *testing.T) {
|
||||
assert.Assert(t, !isExistanceAnchor("^(abc"))
|
||||
}
|
||||
|
||||
func TestIsExistanceAnchor_OnlyHat(t *testing.T) {
|
||||
assert.Assert(t, !isExistanceAnchor("^abc"))
|
||||
}
|
||||
|
||||
func TestIsExistanceAnchor_ConditionAnchor(t *testing.T) {
|
||||
assert.Assert(t, !isExistanceAnchor("(abc)"))
|
||||
}
|
||||
|
||||
func TestRemoveAnchor_ConditionAnchor(t *testing.T) {
|
||||
assert.Equal(t, removeAnchor("(abc)"), "abc")
|
||||
}
|
||||
|
||||
func TestRemoveAnchor_ExistanceAnchor(t *testing.T) {
|
||||
assert.Equal(t, removeAnchor("^(abc)"), "abc")
|
||||
}
|
||||
|
||||
func TestRemoveAnchor_EmptyExistanceAnchor(t *testing.T) {
|
||||
assert.Equal(t, removeAnchor("^()"), "")
|
||||
}
|
||||
|
|
|
@ -143,24 +143,9 @@ func validateArray(resourceArray, patternArray []interface{}, path string) resul
|
|||
// validateArrayOfMaps gets anchors from pattern array map element, applies anchors logic
|
||||
// and then validates each map due to the pattern
|
||||
func validateArrayOfMaps(resourceMapArray []interface{}, patternMap map[string]interface{}, path string) result.RuleApplicationResult {
|
||||
res := result.NewRuleApplicationResult("")
|
||||
anchors := getAnchorsFromMap(patternMap)
|
||||
anchor, pattern := getAnchorFromMap(patternMap)
|
||||
delete(patternMap, anchor)
|
||||
|
||||
for i, resourceElement := range resourceMapArray {
|
||||
currentPath := path + strconv.Itoa(i) + "/"
|
||||
typedResourceElement, ok := resourceElement.(map[string]interface{})
|
||||
if !ok {
|
||||
res.FailWithMessagef("Pattern and resource have different structures. Path: %s. Expected %T, found %T", currentPath, patternMap, resourceElement)
|
||||
return res
|
||||
}
|
||||
|
||||
if skipArrayObject(typedResourceElement, anchors) {
|
||||
continue
|
||||
}
|
||||
|
||||
mapValidationResult := validateMap(typedResourceElement, patternMap, currentPath)
|
||||
res.MergeWith(&mapValidationResult)
|
||||
}
|
||||
|
||||
return res
|
||||
handler := CreateAnchorHandler(anchor, pattern, path)
|
||||
return handler.Handle(resourceMapArray, patternMap)
|
||||
}
|
||||
|
|
|
@ -9,41 +9,6 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestWrappedWithParentheses_StringIsWrappedWithParentheses(t *testing.T) {
|
||||
str := "(something)"
|
||||
assert.Assert(t, wrappedWithParentheses(str))
|
||||
}
|
||||
|
||||
func TestWrappedWithParentheses_StringHasOnlyParentheses(t *testing.T) {
|
||||
str := "()"
|
||||
assert.Assert(t, wrappedWithParentheses(str))
|
||||
}
|
||||
|
||||
func TestWrappedWithParentheses_StringHasNoParentheses(t *testing.T) {
|
||||
str := "something"
|
||||
assert.Assert(t, !wrappedWithParentheses(str))
|
||||
}
|
||||
|
||||
func TestWrappedWithParentheses_StringHasLeftParentheses(t *testing.T) {
|
||||
str := "(something"
|
||||
assert.Assert(t, !wrappedWithParentheses(str))
|
||||
}
|
||||
|
||||
func TestWrappedWithParentheses_StringHasRightParentheses(t *testing.T) {
|
||||
str := "something)"
|
||||
assert.Assert(t, !wrappedWithParentheses(str))
|
||||
}
|
||||
|
||||
func TestWrappedWithParentheses_StringParenthesesInside(t *testing.T) {
|
||||
str := "so)m(et(hin)g"
|
||||
assert.Assert(t, !wrappedWithParentheses(str))
|
||||
}
|
||||
|
||||
func TestWrappedWithParentheses_Empty(t *testing.T) {
|
||||
str := ""
|
||||
assert.Assert(t, !wrappedWithParentheses(str))
|
||||
}
|
||||
|
||||
func TestValidateString_AsteriskTest(t *testing.T) {
|
||||
pattern := "*"
|
||||
value := "anything"
|
||||
|
|
Loading…
Add table
Reference in a new issue