1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-04-09 10:42:22 +00:00

variable substitution

This commit is contained in:
shivkumar dudhani 2019-12-12 10:19:45 -08:00
parent 12edc56613
commit 7c9bc6fecf
10 changed files with 490 additions and 139 deletions

View file

@ -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)

View 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
}

View 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)
}

View file

@ -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+)?)(.*)`

View file

@ -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))
}

View file

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

View file

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

View file

@ -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":{

View 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)
}

View 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()
}