mirror of
https://github.com/kyverno/kyverno.git
synced 2025-04-09 10:42:22 +00:00
variable substitution
This commit is contained in:
parent
12edc56613
commit
7c9bc6fecf
10 changed files with 490 additions and 139 deletions
|
@ -6,12 +6,11 @@ import (
|
|||
|
||||
"github.com/golang/glog"
|
||||
"github.com/nirmata/kyverno/pkg/engine/anchor"
|
||||
"github.com/nirmata/kyverno/pkg/engine/context"
|
||||
)
|
||||
|
||||
//ValidationHandler for element processes
|
||||
type ValidationHandler interface {
|
||||
Handle(ctx context.EvalInterface, resourceMap map[string]interface{}, originPattern interface{}) (string, error)
|
||||
Handle(resourceMap map[string]interface{}, originPattern interface{}) (string, error)
|
||||
}
|
||||
|
||||
//CreateElementHandler factory to process elements
|
||||
|
@ -47,7 +46,7 @@ type NegationHandler struct {
|
|||
}
|
||||
|
||||
//Handle process negation handler
|
||||
func (nh NegationHandler) Handle(ctx context.EvalInterface, resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
|
||||
func (nh NegationHandler) Handle(resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
|
||||
anchorKey := removeAnchor(nh.anchor)
|
||||
currentPath := nh.path + anchorKey + "/"
|
||||
// if anchor is present in the resource then fail
|
||||
|
@ -76,13 +75,13 @@ type EqualityHandler struct {
|
|||
}
|
||||
|
||||
//Handle processed condition anchor
|
||||
func (eh EqualityHandler) Handle(ctx context.EvalInterface, resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
|
||||
func (eh EqualityHandler) Handle(resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
|
||||
anchorKey := removeAnchor(eh.anchor)
|
||||
currentPath := eh.path + anchorKey + "/"
|
||||
// check if anchor is present in resource
|
||||
if value, ok := resourceMap[anchorKey]; ok {
|
||||
// validate the values of the pattern
|
||||
returnPath, err := validateResourceElement(ctx, value, eh.pattern, originPattern, currentPath)
|
||||
returnPath, err := validateResourceElement(value, eh.pattern, originPattern, currentPath)
|
||||
if err != nil {
|
||||
return returnPath, err
|
||||
}
|
||||
|
@ -108,14 +107,14 @@ type DefaultHandler struct {
|
|||
}
|
||||
|
||||
//Handle process non anchor element
|
||||
func (dh DefaultHandler) Handle(ctx context.EvalInterface, resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
|
||||
func (dh DefaultHandler) Handle(resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
|
||||
currentPath := dh.path + dh.element + "/"
|
||||
if dh.pattern == "*" && resourceMap[dh.element] != nil {
|
||||
return "", nil
|
||||
} else if dh.pattern == "*" && resourceMap[dh.element] == nil {
|
||||
return dh.path, fmt.Errorf("Validation rule failed at %s, Field %s is not present", dh.path, dh.element)
|
||||
} else {
|
||||
path, err := validateResourceElement(ctx, resourceMap[dh.element], dh.pattern, originPattern, currentPath)
|
||||
path, err := validateResourceElement(resourceMap[dh.element], dh.pattern, originPattern, currentPath)
|
||||
if err != nil {
|
||||
return path, err
|
||||
}
|
||||
|
@ -140,13 +139,13 @@ type ConditionAnchorHandler struct {
|
|||
}
|
||||
|
||||
//Handle processed condition anchor
|
||||
func (ch ConditionAnchorHandler) Handle(ctx context.EvalInterface, resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
|
||||
func (ch ConditionAnchorHandler) Handle(resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
|
||||
anchorKey := removeAnchor(ch.anchor)
|
||||
currentPath := ch.path + anchorKey + "/"
|
||||
// check if anchor is present in resource
|
||||
if value, ok := resourceMap[anchorKey]; ok {
|
||||
// validate the values of the pattern
|
||||
returnPath, err := validateResourceElement(ctx, value, ch.pattern, originPattern, currentPath)
|
||||
returnPath, err := validateResourceElement(value, ch.pattern, originPattern, currentPath)
|
||||
if err != nil {
|
||||
return returnPath, err
|
||||
}
|
||||
|
@ -173,7 +172,7 @@ type ExistanceHandler struct {
|
|||
}
|
||||
|
||||
//Handle processes the existence anchor handler
|
||||
func (eh ExistanceHandler) Handle(ctx context.EvalInterface, resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
|
||||
func (eh ExistanceHandler) Handle(resourceMap map[string]interface{}, originPattern interface{}) (string, error) {
|
||||
// skip is used by existance anchor to not process further if condition is not satisfied
|
||||
anchorKey := removeAnchor(eh.anchor)
|
||||
currentPath := eh.path + anchorKey + "/"
|
||||
|
@ -192,7 +191,7 @@ func (eh ExistanceHandler) Handle(ctx context.EvalInterface, resourceMap map[str
|
|||
if !ok {
|
||||
return currentPath, fmt.Errorf("Invalid pattern type %T: Pattern has to be of type map to compare against items in resource", eh.pattern)
|
||||
}
|
||||
return validateExistenceListResource(ctx, typedResource, typedPatternMap, originPattern, currentPath)
|
||||
return validateExistenceListResource(typedResource, typedPatternMap, originPattern, currentPath)
|
||||
default:
|
||||
glog.Error("Invalid type: Existance ^ () anchor can be used only on list/array type resource")
|
||||
return currentPath, fmt.Errorf("Invalid resource type %T: Existance ^ () anchor can be used only on list/array type resource", value)
|
||||
|
@ -201,12 +200,12 @@ func (eh ExistanceHandler) Handle(ctx context.EvalInterface, resourceMap map[str
|
|||
return "", nil
|
||||
}
|
||||
|
||||
func validateExistenceListResource(ctx context.EvalInterface, resourceList []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string) (string, error) {
|
||||
func validateExistenceListResource(resourceList []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string) (string, error) {
|
||||
// the idea is atleast on the elements in the array should satisfy the pattern
|
||||
// if non satisfy then throw an error
|
||||
for i, resourceElement := range resourceList {
|
||||
currentPath := path + strconv.Itoa(i) + "/"
|
||||
_, err := validateResourceElement(ctx, resourceElement, patternMap, originPattern, currentPath)
|
||||
_, err := validateResourceElement(resourceElement, patternMap, originPattern, currentPath)
|
||||
if err == nil {
|
||||
// condition is satisfied, dont check further
|
||||
glog.V(4).Infof("Existence check satisfied at path %s, for pattern %v", currentPath, patternMap)
|
||||
|
|
51
pkg/engine/operator/operator.go
Normal file
51
pkg/engine/operator/operator.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
package operator
|
||||
|
||||
// Operator is string alias that represents selection operators enum
|
||||
type Operator string
|
||||
|
||||
const (
|
||||
// Equal stands for ==
|
||||
Equal Operator = ""
|
||||
// MoreEqual stands for >=
|
||||
MoreEqual Operator = ">="
|
||||
// LessEqual stands for <=
|
||||
LessEqual Operator = "<="
|
||||
// NotEqual stands for !
|
||||
NotEqual Operator = "!"
|
||||
// More stands for >
|
||||
More Operator = ">"
|
||||
// Less stands for <
|
||||
Less Operator = "<"
|
||||
)
|
||||
|
||||
const relativePrefix Operator = "./"
|
||||
const ReferenceSign Operator = "$()"
|
||||
|
||||
// getOperatorFromStringPattern parses opeartor from pattern
|
||||
func GetOperatorFromStringPattern(pattern string) Operator {
|
||||
if len(pattern) < 2 {
|
||||
return Equal
|
||||
}
|
||||
|
||||
if pattern[:len(MoreEqual)] == string(MoreEqual) {
|
||||
return MoreEqual
|
||||
}
|
||||
|
||||
if pattern[:len(LessEqual)] == string(LessEqual) {
|
||||
return LessEqual
|
||||
}
|
||||
|
||||
if pattern[:len(More)] == string(More) {
|
||||
return More
|
||||
}
|
||||
|
||||
if pattern[:len(Less)] == string(Less) {
|
||||
return Less
|
||||
}
|
||||
|
||||
if pattern[:len(NotEqual)] == string(NotEqual) {
|
||||
return NotEqual
|
||||
}
|
||||
|
||||
return Equal
|
||||
}
|
17
pkg/engine/operator/operator_test.go
Normal file
17
pkg/engine/operator/operator_test.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package operator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
func TestGetOperatorFromStringPattern_OneChar(t *testing.T) {
|
||||
assert.Equal(t, GetOperatorFromStringPattern("f"), Equal)
|
||||
}
|
||||
|
||||
func TestGetOperatorFromStringPattern_EmptyString(t *testing.T) {
|
||||
assert.Equal(t, GetOperatorFromStringPattern(""), Equal)
|
||||
}
|
||||
|
||||
func TestGetOperatorFromStringPattern_OnlyOperator(t *testing.T) {
|
||||
assert.Equal(t, GetOperatorFromStringPattern(">="), MoreEqual)
|
||||
}
|
|
@ -9,29 +9,9 @@ import (
|
|||
"github.com/golang/glog"
|
||||
|
||||
"github.com/minio/minio/pkg/wildcard"
|
||||
"github.com/nirmata/kyverno/pkg/engine/operator"
|
||||
)
|
||||
|
||||
// Operator is string alias that represents selection operators enum
|
||||
type Operator string
|
||||
|
||||
const (
|
||||
// Equal stands for ==
|
||||
Equal Operator = ""
|
||||
// MoreEqual stands for >=
|
||||
MoreEqual Operator = ">="
|
||||
// LessEqual stands for <=
|
||||
LessEqual Operator = "<="
|
||||
// NotEqual stands for !
|
||||
NotEqual Operator = "!"
|
||||
// More stands for >
|
||||
More Operator = ">"
|
||||
// Less stands for <
|
||||
Less Operator = "<"
|
||||
)
|
||||
|
||||
const relativePrefix Operator = "./"
|
||||
const referenceSign Operator = "$()"
|
||||
|
||||
// ValidateValueWithPattern validates value with operators and wildcards
|
||||
func ValidateValueWithPattern(value, pattern interface{}) bool {
|
||||
switch typedPattern := pattern.(type) {
|
||||
|
@ -179,7 +159,7 @@ func validateValueWithStringPatterns(value interface{}, pattern string) bool {
|
|||
// Handler for single pattern value during validation process
|
||||
// Detects if pattern has a number
|
||||
func validateValueWithStringPattern(value interface{}, pattern string) bool {
|
||||
operator := getOperatorFromStringPattern(pattern)
|
||||
operator := operator.GetOperatorFromStringPattern(pattern)
|
||||
pattern = pattern[len(operator):]
|
||||
number, str := getNumberAndStringPartsFromPattern(pattern)
|
||||
|
||||
|
@ -191,8 +171,8 @@ func validateValueWithStringPattern(value interface{}, pattern string) bool {
|
|||
}
|
||||
|
||||
// Handler for string values
|
||||
func validateString(value interface{}, pattern string, operator Operator) bool {
|
||||
if NotEqual == operator || Equal == operator {
|
||||
func validateString(value interface{}, pattern string, operatorVariable operator.Operator) bool {
|
||||
if operator.NotEqual == operatorVariable || operator.Equal == operatorVariable {
|
||||
strValue, ok := value.(string)
|
||||
if !ok {
|
||||
glog.Warningf("Expected string, found %T\n", value)
|
||||
|
@ -201,7 +181,7 @@ func validateString(value interface{}, pattern string, operator Operator) bool {
|
|||
|
||||
wildcardResult := wildcard.Match(pattern, strValue)
|
||||
|
||||
if NotEqual == operator {
|
||||
if operator.NotEqual == operatorVariable {
|
||||
return !wildcardResult
|
||||
}
|
||||
|
||||
|
@ -213,7 +193,7 @@ func validateString(value interface{}, pattern string, operator Operator) bool {
|
|||
}
|
||||
|
||||
// validateNumberWithStr applies wildcard to suffix and operator to numerical part
|
||||
func validateNumberWithStr(value interface{}, patternNumber, patternStr string, operator Operator) bool {
|
||||
func validateNumberWithStr(value interface{}, patternNumber, patternStr string, operator operator.Operator) bool {
|
||||
// pattern has suffix
|
||||
if "" != patternStr {
|
||||
typedValue, ok := value.(string)
|
||||
|
@ -235,7 +215,7 @@ func validateNumberWithStr(value interface{}, patternNumber, patternStr string,
|
|||
}
|
||||
|
||||
// validateNumber compares two numbers with operator
|
||||
func validateNumber(value, pattern interface{}, operator Operator) bool {
|
||||
func validateNumber(value, pattern interface{}, operatorVariable operator.Operator) bool {
|
||||
floatPattern, err := convertToFloat(pattern)
|
||||
if err != nil {
|
||||
return false
|
||||
|
@ -246,53 +226,24 @@ func validateNumber(value, pattern interface{}, operator Operator) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
switch operator {
|
||||
case Equal:
|
||||
switch operatorVariable {
|
||||
case operator.Equal:
|
||||
return floatValue == floatPattern
|
||||
case NotEqual:
|
||||
case operator.NotEqual:
|
||||
return floatValue != floatPattern
|
||||
case More:
|
||||
case operator.More:
|
||||
return floatValue > floatPattern
|
||||
case MoreEqual:
|
||||
case operator.MoreEqual:
|
||||
return floatValue >= floatPattern
|
||||
case Less:
|
||||
case operator.Less:
|
||||
return floatValue < floatPattern
|
||||
case LessEqual:
|
||||
case operator.LessEqual:
|
||||
return floatValue <= floatPattern
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// getOperatorFromStringPattern parses opeartor from pattern
|
||||
func getOperatorFromStringPattern(pattern string) Operator {
|
||||
if len(pattern) < 2 {
|
||||
return Equal
|
||||
}
|
||||
|
||||
if pattern[:len(MoreEqual)] == string(MoreEqual) {
|
||||
return MoreEqual
|
||||
}
|
||||
|
||||
if pattern[:len(LessEqual)] == string(LessEqual) {
|
||||
return LessEqual
|
||||
}
|
||||
|
||||
if pattern[:len(More)] == string(More) {
|
||||
return More
|
||||
}
|
||||
|
||||
if pattern[:len(Less)] == string(Less) {
|
||||
return Less
|
||||
}
|
||||
|
||||
if pattern[:len(NotEqual)] == string(NotEqual) {
|
||||
return NotEqual
|
||||
}
|
||||
|
||||
return Equal
|
||||
}
|
||||
|
||||
// detects numerical and string parts in pattern and returns them
|
||||
func getNumberAndStringPartsFromPattern(pattern string) (number, str string) {
|
||||
regexpStr := `^(\d*(\.\d+)?)(.*)`
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/nirmata/kyverno/pkg/engine/operator"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
|
@ -243,41 +244,13 @@ func TestGetNumberAndStringPartsFromPattern_Empty(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestValidateNumber_EqualTwoFloats(t *testing.T) {
|
||||
assert.Assert(t, validateNumber(7.0, 7.000, Equal))
|
||||
assert.Assert(t, validateNumber(7.0, 7.000, operator.Equal))
|
||||
}
|
||||
|
||||
func TestValidateNumber_LessFloatAndInt(t *testing.T) {
|
||||
assert.Assert(t, validateNumber(7, 7.00001, Less))
|
||||
assert.Assert(t, validateNumber(7, 7.00001, NotEqual))
|
||||
assert.Assert(t, validateNumber(7, 7.00001, operator.Less))
|
||||
assert.Assert(t, validateNumber(7, 7.00001, operator.NotEqual))
|
||||
|
||||
assert.Assert(t, !validateNumber(7, 7.0000, NotEqual))
|
||||
assert.Assert(t, !validateNumber(6, 6.000000001, More))
|
||||
}
|
||||
|
||||
func TestValidateNumberWithStr_Equal(t *testing.T) {
|
||||
assert.Assert(t, validateNumberWithStr("1024Gi", "1024", "Gi", Equal))
|
||||
}
|
||||
|
||||
func TestValidateNumberWithStr_More(t *testing.T) {
|
||||
assert.Assert(t, !validateNumberWithStr("512Gi", "1024", "Gi", More))
|
||||
}
|
||||
|
||||
func TestValidateNumberWithStr_MoreAndWildCard(t *testing.T) {
|
||||
assert.Assert(t, validateNumberWithStr("2048Gi", "1024", "G?", More))
|
||||
}
|
||||
|
||||
func TestValidateNumberWithStr_NoStr(t *testing.T) {
|
||||
assert.Assert(t, validateNumberWithStr(2048, "1024", "", More))
|
||||
}
|
||||
|
||||
func TestGetOperatorFromStringPattern_OneChar(t *testing.T) {
|
||||
assert.Equal(t, getOperatorFromStringPattern("f"), Equal)
|
||||
}
|
||||
|
||||
func TestGetOperatorFromStringPattern_EmptyString(t *testing.T) {
|
||||
assert.Equal(t, getOperatorFromStringPattern(""), Equal)
|
||||
}
|
||||
|
||||
func TestGetOperatorFromStringPattern_OnlyOperator(t *testing.T) {
|
||||
assert.Equal(t, getOperatorFromStringPattern(">="), MoreEqual)
|
||||
assert.Assert(t, !validateNumber(7, 7.0000, operator.NotEqual))
|
||||
assert.Assert(t, !validateNumber(6, 6.000000001, operator.More))
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/nirmata/kyverno/pkg/engine/anchor"
|
||||
"github.com/nirmata/kyverno/pkg/utils"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"github.com/nirmata/kyverno/pkg/engine/operator"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
@ -280,7 +280,7 @@ func getRawKeyIfWrappedWithAttributes(str string) string {
|
|||
}
|
||||
|
||||
func isStringIsReference(str string) bool {
|
||||
if len(str) < len(referenceSign) {
|
||||
if len(str) < len(operator.ReferenceSign) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ import (
|
|||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
||||
"github.com/nirmata/kyverno/pkg/engine/anchor"
|
||||
"github.com/nirmata/kyverno/pkg/engine/context"
|
||||
"github.com/nirmata/kyverno/pkg/engine/operator"
|
||||
"github.com/nirmata/kyverno/pkg/engine/variables"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
|
@ -227,13 +229,17 @@ func validatePatterns(ctx context.EvalInterface, resource unstructured.Unstructu
|
|||
// It assumes that validation is started from root, so "/" is passed
|
||||
//TODO: for failure, we return the path at which it failed along with error
|
||||
func validateResourceWithPattern(ctx context.EvalInterface, resource, pattern interface{}) (string, error) {
|
||||
return validateResourceElement(ctx, resource, pattern, pattern, "/")
|
||||
// first pass we substitute all the JMESPATH substitution for the variable
|
||||
// variable: {{<JMESPATH>}}
|
||||
// if a JMESPATH fails, we dont return error but variable is substitured with nil and error log
|
||||
pattern = variables.SubstituteVariables(ctx, pattern)
|
||||
return validateResourceElement(resource, pattern, pattern, "/")
|
||||
}
|
||||
|
||||
// validateResourceElement detects the element type (map, array, nil, string, int, bool, float)
|
||||
// and calls corresponding handler
|
||||
// Pattern tree and resource tree can have different structure. In this case validation fails
|
||||
func validateResourceElement(ctx context.EvalInterface, resourceElement, patternElement, originPattern interface{}, path string) (string, error) {
|
||||
func validateResourceElement(resourceElement, patternElement, originPattern interface{}, path string) (string, error) {
|
||||
var err error
|
||||
switch typedPatternElement := patternElement.(type) {
|
||||
// map
|
||||
|
@ -244,7 +250,7 @@ func validateResourceElement(ctx context.EvalInterface, resourceElement, pattern
|
|||
return path, fmt.Errorf("Pattern and resource have different structures. Path: %s. Expected %T, found %T", path, patternElement, resourceElement)
|
||||
}
|
||||
|
||||
return validateMap(ctx, typedResourceElement, typedPatternElement, originPattern, path)
|
||||
return validateMap(typedResourceElement, typedPatternElement, originPattern, path)
|
||||
// array
|
||||
case []interface{}:
|
||||
typedResourceElement, ok := resourceElement.([]interface{})
|
||||
|
@ -253,7 +259,7 @@ func validateResourceElement(ctx context.EvalInterface, resourceElement, pattern
|
|||
return path, fmt.Errorf("Validation rule Failed at path %s, resource does not satisfy the expected overlay pattern", path)
|
||||
}
|
||||
|
||||
return validateArray(ctx, typedResourceElement, typedPatternElement, originPattern, path)
|
||||
return validateArray(typedResourceElement, typedPatternElement, originPattern, path)
|
||||
// elementary values
|
||||
case string, float64, int, int64, bool, nil:
|
||||
/*Analyze pattern */
|
||||
|
@ -278,7 +284,7 @@ func validateResourceElement(ctx context.EvalInterface, resourceElement, pattern
|
|||
|
||||
// If validateResourceElement detects map element inside resource and pattern trees, it goes to validateMap
|
||||
// For each element of the map we must detect the type again, so we pass these elements to validateResourceElement
|
||||
func validateMap(ctx context.EvalInterface, resourceMap, patternMap map[string]interface{}, origPattern interface{}, path string) (string, error) {
|
||||
func validateMap(resourceMap, patternMap map[string]interface{}, origPattern interface{}, path string) (string, error) {
|
||||
// check if there is anchor in pattern
|
||||
// Phase 1 : Evaluate all the anchors
|
||||
// Phase 2 : Evaluate non-anchors
|
||||
|
@ -291,7 +297,7 @@ func validateMap(ctx context.EvalInterface, resourceMap, patternMap map[string]i
|
|||
// - Existance
|
||||
// - Equality
|
||||
handler := CreateElementHandler(key, patternElement, path)
|
||||
handlerPath, err := handler.Handle(ctx, resourceMap, origPattern)
|
||||
handlerPath, err := handler.Handle(resourceMap, origPattern)
|
||||
// if there are resource values at same level, then anchor acts as conditional instead of a strict check
|
||||
// but if there are non then its a if then check
|
||||
if err != nil {
|
||||
|
@ -307,7 +313,7 @@ func validateMap(ctx context.EvalInterface, resourceMap, patternMap map[string]i
|
|||
for key, resourceElement := range resources {
|
||||
// get handler for resources in the pattern
|
||||
handler := CreateElementHandler(key, resourceElement, path)
|
||||
handlerPath, err := handler.Handle(ctx, resourceMap, origPattern)
|
||||
handlerPath, err := handler.Handle(resourceMap, origPattern)
|
||||
if err != nil {
|
||||
return handlerPath, err
|
||||
}
|
||||
|
@ -315,7 +321,7 @@ func validateMap(ctx context.EvalInterface, resourceMap, patternMap map[string]i
|
|||
return "", nil
|
||||
}
|
||||
|
||||
func validateArray(ctx context.EvalInterface, resourceArray, patternArray []interface{}, originPattern interface{}, path string) (string, error) {
|
||||
func validateArray(resourceArray, patternArray []interface{}, originPattern interface{}, path string) (string, error) {
|
||||
|
||||
if 0 == len(patternArray) {
|
||||
return path, fmt.Errorf("Pattern Array empty")
|
||||
|
@ -325,7 +331,7 @@ func validateArray(ctx context.EvalInterface, resourceArray, patternArray []inte
|
|||
case map[string]interface{}:
|
||||
// This is special case, because maps in arrays can have anchors that must be
|
||||
// processed with the special way affecting the entire array
|
||||
path, err := validateArrayOfMaps(ctx, resourceArray, typedPatternElement, originPattern, path)
|
||||
path, err := validateArrayOfMaps(resourceArray, typedPatternElement, originPattern, path)
|
||||
if err != nil {
|
||||
return path, err
|
||||
}
|
||||
|
@ -333,7 +339,7 @@ func validateArray(ctx context.EvalInterface, resourceArray, patternArray []inte
|
|||
// In all other cases - detect type and handle each array element with validateResourceElement
|
||||
for i, patternElement := range patternArray {
|
||||
currentPath := path + strconv.Itoa(i) + "/"
|
||||
path, err := validateResourceElement(ctx, resourceArray[i], patternElement, originPattern, currentPath)
|
||||
path, err := validateResourceElement(resourceArray[i], patternElement, originPattern, currentPath)
|
||||
if err != nil {
|
||||
return path, err
|
||||
}
|
||||
|
@ -348,13 +354,17 @@ func actualizePattern(origPattern interface{}, referencePattern, absolutePath st
|
|||
|
||||
referencePattern = strings.Trim(referencePattern, "$()")
|
||||
|
||||
operator := getOperatorFromStringPattern(referencePattern)
|
||||
referencePattern = referencePattern[len(operator):]
|
||||
operatorVariable := operator.GetOperatorFromStringPattern(referencePattern)
|
||||
referencePattern = referencePattern[len(operatorVariable):]
|
||||
|
||||
if len(referencePattern) == 0 {
|
||||
return nil, errors.New("Expected path. Found empty reference")
|
||||
}
|
||||
|
||||
// Check for variables
|
||||
// substitute it from Context
|
||||
// remove abosolute path
|
||||
// {{ }}
|
||||
// value :=
|
||||
actualPath := FormAbsolutePath(referencePattern, absolutePath)
|
||||
|
||||
valFromReference, err := getValueFromReference(origPattern, actualPath)
|
||||
|
@ -362,15 +372,15 @@ func actualizePattern(origPattern interface{}, referencePattern, absolutePath st
|
|||
return err, nil
|
||||
}
|
||||
//TODO validate this
|
||||
if operator == Equal { //if operator does not exist return raw value
|
||||
if operatorVariable == operator.Equal { //if operator does not exist return raw value
|
||||
return valFromReference, nil
|
||||
}
|
||||
|
||||
foundValue, err = valFromReferenceToString(valFromReference, string(operator))
|
||||
foundValue, err = valFromReferenceToString(valFromReference, string(operatorVariable))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(operator) + foundValue.(string), nil
|
||||
return string(operatorVariable) + foundValue.(string), nil
|
||||
}
|
||||
|
||||
//Parse value to string
|
||||
|
@ -456,12 +466,12 @@ func getValueFromPattern(patternMap map[string]interface{}, keys []string, curre
|
|||
|
||||
// validateArrayOfMaps gets anchors from pattern array map element, applies anchors logic
|
||||
// and then validates each map due to the pattern
|
||||
func validateArrayOfMaps(ctx context.EvalInterface, resourceMapArray []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string) (string, error) {
|
||||
func validateArrayOfMaps(resourceMapArray []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string) (string, error) {
|
||||
for i, resourceElement := range resourceMapArray {
|
||||
// check the types of resource element
|
||||
// expect it to be map, but can be anything ?:(
|
||||
currentPath := path + strconv.Itoa(i) + "/"
|
||||
returnpath, err := validateResourceElement(ctx, resourceElement, patternMap, originPattern, currentPath)
|
||||
returnpath, err := validateResourceElement(resourceElement, patternMap, originPattern, currentPath)
|
||||
if err != nil {
|
||||
return returnpath, err
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"testing"
|
||||
|
||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
||||
"github.com/nirmata/kyverno/pkg/engine/context"
|
||||
"github.com/nirmata/kyverno/pkg/engine/operator"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
|
@ -13,8 +15,8 @@ func TestValidateString_AsteriskTest(t *testing.T) {
|
|||
value := "anything"
|
||||
empty := ""
|
||||
|
||||
assert.Assert(t, validateString(value, pattern, Equal))
|
||||
assert.Assert(t, validateString(empty, pattern, Equal))
|
||||
assert.Assert(t, validateString(value, pattern, operator.Equal))
|
||||
assert.Assert(t, validateString(empty, pattern, operator.Equal))
|
||||
}
|
||||
|
||||
func TestValidateString_LeftAsteriskTest(t *testing.T) {
|
||||
|
@ -22,32 +24,32 @@ func TestValidateString_LeftAsteriskTest(t *testing.T) {
|
|||
value := "leftright"
|
||||
right := "right"
|
||||
|
||||
assert.Assert(t, validateString(value, pattern, Equal))
|
||||
assert.Assert(t, validateString(right, pattern, Equal))
|
||||
assert.Assert(t, validateString(value, pattern, operator.Equal))
|
||||
assert.Assert(t, validateString(right, pattern, operator.Equal))
|
||||
|
||||
value = "leftmiddle"
|
||||
middle := "middle"
|
||||
|
||||
assert.Assert(t, !validateString(value, pattern, Equal))
|
||||
assert.Assert(t, !validateString(middle, pattern, Equal))
|
||||
assert.Assert(t, !validateString(value, pattern, operator.Equal))
|
||||
assert.Assert(t, !validateString(middle, pattern, operator.Equal))
|
||||
}
|
||||
|
||||
func TestValidateString_MiddleAsteriskTest(t *testing.T) {
|
||||
pattern := "ab*ba"
|
||||
value := "abbeba"
|
||||
assert.Assert(t, validateString(value, pattern, Equal))
|
||||
assert.Assert(t, validateString(value, pattern, operator.Equal))
|
||||
|
||||
value = "abbca"
|
||||
assert.Assert(t, !validateString(value, pattern, Equal))
|
||||
assert.Assert(t, !validateString(value, pattern, operator.Equal))
|
||||
}
|
||||
|
||||
func TestValidateString_QuestionMark(t *testing.T) {
|
||||
pattern := "ab?ba"
|
||||
value := "abbba"
|
||||
assert.Assert(t, validateString(value, pattern, Equal))
|
||||
assert.Assert(t, validateString(value, pattern, operator.Equal))
|
||||
|
||||
value = "abbbba"
|
||||
assert.Assert(t, !validateString(value, pattern, Equal))
|
||||
assert.Assert(t, !validateString(value, pattern, operator.Equal))
|
||||
}
|
||||
|
||||
func TestSkipArrayObject_OneAnchor(t *testing.T) {
|
||||
|
@ -977,6 +979,37 @@ func TestValidateMap_CorrectRelativePathInConfig(t *testing.T) {
|
|||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func Test_Variable(t *testing.T) {
|
||||
resourceRaw := []byte(`
|
||||
{
|
||||
"metadata": {
|
||||
"name": "temp",
|
||||
"namespace": "n1"
|
||||
},
|
||||
"spec": {
|
||||
"namespace": "n1",
|
||||
"name": "temp1"
|
||||
}
|
||||
} `)
|
||||
patternMap := []byte(`
|
||||
{
|
||||
"spec": {
|
||||
"name": "$(temp)"
|
||||
}
|
||||
}
|
||||
`)
|
||||
var pattern, resource interface{}
|
||||
json.Unmarshal(patternMap, &pattern)
|
||||
json.Unmarshal(resourceRaw, &resource)
|
||||
|
||||
// context
|
||||
ctx := context.NewContext()
|
||||
ctx.Add("resource", resourceRaw)
|
||||
path, err := validateResourceWithPattern(ctx, resource, pattern)
|
||||
assert.Equal(t, path, "")
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func TestValidateMap_RelativePathDoesNotExists(t *testing.T) {
|
||||
rawPattern := []byte(`{
|
||||
"spec":{
|
||||
|
|
89
pkg/engine/variables/variables.go
Normal file
89
pkg/engine/variables/variables.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
package variables
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/nirmata/kyverno/pkg/engine/context"
|
||||
"github.com/nirmata/kyverno/pkg/engine/operator"
|
||||
)
|
||||
|
||||
func SubstituteVariables(ctx context.EvalInterface, pattern interface{}) interface{} {
|
||||
// var err error
|
||||
switch typedPattern := pattern.(type) {
|
||||
case map[string]interface{}:
|
||||
return substituteMap(ctx, typedPattern)
|
||||
case []interface{}:
|
||||
return substituteArray(ctx, typedPattern)
|
||||
case string:
|
||||
// variable substitution is for strings
|
||||
return substituteValue(ctx, typedPattern)
|
||||
default:
|
||||
return pattern
|
||||
}
|
||||
}
|
||||
|
||||
func substituteMap(ctx context.EvalInterface, patternMap map[string]interface{}) map[string]interface{} {
|
||||
for key, patternElement := range patternMap {
|
||||
value := SubstituteVariables(ctx, patternElement)
|
||||
patternMap[key] = value
|
||||
}
|
||||
return patternMap
|
||||
}
|
||||
|
||||
func substituteArray(ctx context.EvalInterface, patternList []interface{}) []interface{} {
|
||||
for idx, patternElement := range patternList {
|
||||
value := SubstituteVariables(ctx, patternElement)
|
||||
patternList[idx] = value
|
||||
}
|
||||
return patternList
|
||||
}
|
||||
func substituteValue(ctx context.EvalInterface, valuePattern string) interface{} {
|
||||
// patterns supported
|
||||
// - operator + string
|
||||
// operator + variable
|
||||
operatorVariable := getOperator(valuePattern)
|
||||
variable := valuePattern[len(operatorVariable):]
|
||||
// substitute variable with value
|
||||
value := getValueQuery(ctx, variable)
|
||||
if operatorVariable == "" {
|
||||
// default or operator.Equal
|
||||
// equal + string variable
|
||||
// object variable
|
||||
return value
|
||||
}
|
||||
// operator + string variable
|
||||
switch value.(type) {
|
||||
case string:
|
||||
return string(operatorVariable) + value.(string)
|
||||
default:
|
||||
glog.V(4).Info("cannot user operator with object variables")
|
||||
var emptyInterface interface{}
|
||||
return emptyInterface
|
||||
}
|
||||
}
|
||||
func getValueQuery(ctx context.EvalInterface, valuePattern string) interface{} {
|
||||
var emptyInterface interface{}
|
||||
// extract variable {{<variable>}}
|
||||
variableRegex := regexp.MustCompile("^{{(.*)}}$")
|
||||
groups := variableRegex.FindStringSubmatch(valuePattern)
|
||||
if len(groups) < 2 {
|
||||
return valuePattern
|
||||
}
|
||||
searchPath := groups[1]
|
||||
// search for the path in ctx
|
||||
variable, err := ctx.Query(searchPath)
|
||||
if err != nil {
|
||||
glog.V(4).Infof("variable substituion failed for query %s: %v", searchPath, err)
|
||||
return emptyInterface
|
||||
}
|
||||
return variable
|
||||
}
|
||||
|
||||
func getOperator(pattern string) string {
|
||||
operatorVariable := operator.GetOperatorFromStringPattern(pattern)
|
||||
if operatorVariable == operator.Equal {
|
||||
return ""
|
||||
}
|
||||
return string(operatorVariable)
|
||||
}
|
228
pkg/engine/variables/variables_test.go
Normal file
228
pkg/engine/variables/variables_test.go
Normal file
|
@ -0,0 +1,228 @@
|
|||
package variables
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/nirmata/kyverno/pkg/engine/context"
|
||||
)
|
||||
|
||||
func Test_variableSubstitutionValue(t *testing.T) {
|
||||
|
||||
resourceRaw := []byte(`
|
||||
{
|
||||
"metadata": {
|
||||
"name": "temp",
|
||||
"namespace": "n1"
|
||||
},
|
||||
"spec": {
|
||||
"namespace": "n1",
|
||||
"name": "temp1"
|
||||
}
|
||||
}
|
||||
`)
|
||||
patternMap := []byte(`
|
||||
{
|
||||
"spec": {
|
||||
"name": "{{resource.metadata.name}}"
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
var pattern, resource interface{}
|
||||
json.Unmarshal(patternMap, &pattern)
|
||||
json.Unmarshal(resourceRaw, &resource)
|
||||
|
||||
// context
|
||||
ctx := context.NewContext()
|
||||
ctx.Add("resource", resourceRaw)
|
||||
value := SubstituteVariables(ctx, pattern)
|
||||
t.Log(value)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
func Test_variableSubstitutionValueOperatorNotEqual(t *testing.T) {
|
||||
|
||||
resourceRaw := []byte(`
|
||||
{
|
||||
"metadata": {
|
||||
"name": "temp",
|
||||
"namespace": "n1"
|
||||
},
|
||||
"spec": {
|
||||
"namespace": "n1",
|
||||
"name": "temp1"
|
||||
}
|
||||
}
|
||||
`)
|
||||
patternMap := []byte(`
|
||||
{
|
||||
"spec": {
|
||||
"name": "!{{resource.metadata.name}}"
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
var pattern, resource interface{}
|
||||
json.Unmarshal(patternMap, &pattern)
|
||||
json.Unmarshal(resourceRaw, &resource)
|
||||
|
||||
// context
|
||||
ctx := context.NewContext()
|
||||
ctx.Add("resource", resourceRaw)
|
||||
value := SubstituteVariables(ctx, pattern)
|
||||
t.Log(value)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
func Test_variableSubstitutionValueFail(t *testing.T) {
|
||||
|
||||
resourceRaw := []byte(`
|
||||
{
|
||||
"metadata": {
|
||||
"name": "temp",
|
||||
"namespace": "n1"
|
||||
},
|
||||
"spec": {
|
||||
"namespace": "n1",
|
||||
"name": "temp1"
|
||||
}
|
||||
}
|
||||
`)
|
||||
patternMap := []byte(`
|
||||
{
|
||||
"spec": {
|
||||
"name": "{{resource.metadata.name1}}"
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
var pattern, resource interface{}
|
||||
json.Unmarshal(patternMap, &pattern)
|
||||
json.Unmarshal(resourceRaw, &resource)
|
||||
|
||||
// context
|
||||
ctx := context.NewContext()
|
||||
ctx.Add("resource", resourceRaw)
|
||||
value := SubstituteVariables(ctx, pattern)
|
||||
t.Log(value)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
func Test_variableSubstitutionObject(t *testing.T) {
|
||||
resourceRaw := []byte(`
|
||||
{
|
||||
"metadata": {
|
||||
"name": "temp",
|
||||
"namespace": "n1"
|
||||
},
|
||||
"spec": {
|
||||
"namespace": "n1",
|
||||
"variable": {
|
||||
"var1": "temp1",
|
||||
"var2": "temp2",
|
||||
"varNested": {
|
||||
"var1": "temp1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
patternMap := []byte(`
|
||||
{
|
||||
"spec": {
|
||||
"variable": "{{resource.spec.variable}}"
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
var pattern, resource interface{}
|
||||
json.Unmarshal(patternMap, &pattern)
|
||||
json.Unmarshal(resourceRaw, &resource)
|
||||
|
||||
// context
|
||||
ctx := context.NewContext()
|
||||
ctx.Add("resource", resourceRaw)
|
||||
value := SubstituteVariables(ctx, pattern)
|
||||
t.Log(value)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
func Test_variableSubstitutionObjectOperatorNotEqualFail(t *testing.T) {
|
||||
resourceRaw := []byte(`
|
||||
{
|
||||
"metadata": {
|
||||
"name": "temp",
|
||||
"namespace": "n1"
|
||||
},
|
||||
"spec": {
|
||||
"namespace": "n1",
|
||||
"variable": {
|
||||
"var1": "temp1",
|
||||
"var2": "temp2",
|
||||
"varNested": {
|
||||
"var1": "temp1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
patternMap := []byte(`
|
||||
{
|
||||
"spec": {
|
||||
"variable": "!{{resource.spec.variable}}"
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
var pattern, resource interface{}
|
||||
json.Unmarshal(patternMap, &pattern)
|
||||
json.Unmarshal(resourceRaw, &resource)
|
||||
|
||||
// context
|
||||
ctx := context.NewContext()
|
||||
ctx.Add("resource", resourceRaw)
|
||||
value := SubstituteVariables(ctx, pattern)
|
||||
t.Log(value)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
func Test_variableSubstitutionMultipleObject(t *testing.T) {
|
||||
resourceRaw := []byte(`
|
||||
{
|
||||
"metadata": {
|
||||
"name": "temp",
|
||||
"namespace": "n1"
|
||||
},
|
||||
"spec": {
|
||||
"namespace": "n1",
|
||||
"variable": {
|
||||
"var1": "temp1",
|
||||
"var2": "temp2",
|
||||
"varNested": {
|
||||
"var1": "temp1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
patternMap := []byte(`
|
||||
{
|
||||
"spec": {
|
||||
"var": "{{resource.spec.variable.varNested.var1}}",
|
||||
"variable": "{{resource.spec.variable}}"
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
var pattern, resource interface{}
|
||||
json.Unmarshal(patternMap, &pattern)
|
||||
json.Unmarshal(resourceRaw, &resource)
|
||||
|
||||
// context
|
||||
ctx := context.NewContext()
|
||||
ctx.Add("resource", resourceRaw)
|
||||
value := SubstituteVariables(ctx, pattern)
|
||||
t.Log(value)
|
||||
t.Fail()
|
||||
}
|
Loading…
Add table
Reference in a new issue