From 46cbbf9568a87a72c40b92c9adbdab4ade375896 Mon Sep 17 00:00:00 2001 From: belyshevdenis Date: Thu, 20 Jun 2019 18:21:55 +0300 Subject: [PATCH] Implemented validation across same yaml --- .directory | 6 + pkg/engine/anchor.go | 18 +- pkg/engine/pattern.go | 3 + pkg/engine/utils.go | 22 ++ pkg/engine/validation.go | 153 ++++++++- pkg/engine/validation_test.go | 563 +++++++++++++++++++++++++++++++++- 6 files changed, 732 insertions(+), 33 deletions(-) create mode 100644 .directory diff --git a/.directory b/.directory new file mode 100644 index 0000000000..ac8b873ef4 --- /dev/null +++ b/.directory @@ -0,0 +1,6 @@ +[Dolphin] +Timestamp=2019,6,20,15,39,37 +Version=3 + +[Settings] +HiddenFilesShown=true diff --git a/pkg/engine/anchor.go b/pkg/engine/anchor.go index d10c4e2759..a511f9a659 100644 --- a/pkg/engine/anchor.go +++ b/pkg/engine/anchor.go @@ -23,7 +23,7 @@ func CreateAnchorHandler(anchor string, pattern interface{}, path string) Valida // 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 + Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) result.RuleApplicationResult } // NoAnchorValidationHandler just calls validateMap @@ -41,7 +41,7 @@ func NewNoAnchorValidationHandler(path string) ValidationAnchorHandler { } // Handle performs validation in context of NoAnchorValidationHandler -func (navh *NoAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}) result.RuleApplicationResult { +func (navh *NoAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) result.RuleApplicationResult { handlingResult := result.NewRuleApplicationResult("") for i, resourceElement := range resourcePart { @@ -53,7 +53,7 @@ func (navh *NoAnchorValidationHandler) Handle(resourcePart []interface{}, patter return handlingResult } - res := validateMap(typedResourceElement, patternPart, currentPath) + res := validateMap(typedResourceElement, patternPart, originPattern, currentPath) handlingResult.MergeWith(&res) } @@ -81,8 +81,8 @@ func NewConditionAnchorValidationHandler(anchor string, pattern interface{}, pat } // 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) +func (cavh *ConditionAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) result.RuleApplicationResult { + _, handlingResult := handleConditionCases(resourcePart, patternPart, cavh.anchor, cavh.pattern, cavh.path, originPattern) return handlingResult } @@ -110,8 +110,8 @@ func NewExistanceAnchorValidationHandler(anchor string, pattern interface{}, pat } // Handle performs validation in context of ExistanceAnchorValidationHandler -func (eavh *ExistanceAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}) result.RuleApplicationResult { - anchoredEntries, handlingResult := handleConditionCases(resourcePart, patternPart, eavh.anchor, eavh.pattern, eavh.path) +func (eavh *ExistanceAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) result.RuleApplicationResult { + anchoredEntries, handlingResult := handleConditionCases(resourcePart, patternPart, eavh.anchor, eavh.pattern, eavh.path, originPattern) if 0 == anchoredEntries { handlingResult.FailWithMessagef("Existance anchor %s used, but no suitable entries were found", eavh.anchor) @@ -134,7 +134,7 @@ func checkForAnchorCondition(anchor string, pattern interface{}, resourceMap map // both () and ^() are checking conditions and have a lot of similar logic // the only difference is that ^() requires existace of one element // anchoredEntries var counts this occurences. -func handleConditionCases(resourcePart []interface{}, patternPart map[string]interface{}, anchor string, pattern interface{}, path string) (int, result.RuleApplicationResult) { +func handleConditionCases(resourcePart []interface{}, patternPart map[string]interface{}, anchor string, pattern interface{}, path string, originPattern interface{}) (int, result.RuleApplicationResult) { handlingResult := result.NewRuleApplicationResult("") anchoredEntries := 0 @@ -152,7 +152,7 @@ func handleConditionCases(resourcePart []interface{}, patternPart map[string]int } anchoredEntries++ - res := validateMap(typedResourceElement, patternPart, currentPath) + res := validateMap(typedResourceElement, patternPart, originPattern, currentPath) handlingResult.MergeWith(&res) } diff --git a/pkg/engine/pattern.go b/pkg/engine/pattern.go index 7efa9f4640..8327206b1c 100644 --- a/pkg/engine/pattern.go +++ b/pkg/engine/pattern.go @@ -28,6 +28,9 @@ const ( 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) { diff --git a/pkg/engine/utils.go b/pkg/engine/utils.go index c41d693d00..e4987e20a3 100644 --- a/pkg/engine/utils.go +++ b/pkg/engine/utils.go @@ -148,6 +148,28 @@ func isConditionAnchor(str string) bool { return (str[0] == '(' && str[len(str)-1] == ')') } +func getRawKeyIfWrappedWithAttributes(str string) string { + if len(str) < 2 { + return str + } + + if str[0] == '(' && str[len(str)-1] == ')' { + return str[1 : len(str)-1] + } else if (str[0] == '$' || str[0] == '^' || str[0] == '+') && (str[1] == '(' && str[len(str)-1] == ')') { + return str[2 : len(str)-1] + } else { + return str + } +} + +func isStringIsReference(str string) bool { + if len(str) < len(referenceSign) { + return false + } + + return str[0] == '$' && str[1] == '(' && str[len(str)-1] == ')' +} + func isExistanceAnchor(str string) bool { left := "^(" right := ")" diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index db01189ea4..09f5a487d4 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -2,7 +2,11 @@ package engine import ( "encoding/json" + "fmt" + "path/filepath" + "reflect" "strconv" + "strings" kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1" "github.com/nirmata/kyverno/pkg/result" @@ -48,13 +52,13 @@ func Validate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVers // validateResourceWithPattern is a start of element-by-element validation process // It assumes that validation is started from root, so "/" is passed func validateResourceWithPattern(resource, pattern interface{}) result.RuleApplicationResult { - return validateResourceElement(resource, 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(resourceElement, patternElement interface{}, path string) result.RuleApplicationResult { +func validateResourceElement(resourceElement, patternElement, originPattern interface{}, path string) result.RuleApplicationResult { res := result.NewRuleApplicationResult("") // TODO: Move similar message templates to message package @@ -67,7 +71,7 @@ func validateResourceElement(resourceElement, patternElement interface{}, path s return res } - return validateMap(typedResourceElement, typedPatternElement, path) + return validateMap(typedResourceElement, typedPatternElement, originPattern, path) // array case []interface{}: typedResourceElement, ok := resourceElement.([]interface{}) @@ -76,9 +80,18 @@ func validateResourceElement(resourceElement, patternElement interface{}, path s return res } - return validateArray(typedResourceElement, typedPatternElement, path) + return validateArray(typedResourceElement, typedPatternElement, originPattern, path) // elementary values case string, float64, int, int64, bool, nil: + /*Analyze pattern */ + if checkedPattern := reflect.ValueOf(patternElement); checkedPattern.Kind() == reflect.String { + if isStringIsReference(checkedPattern.String()) { //check for $ anchor + patternElement, res = actualizePattern(originPattern, checkedPattern.String(), path) + if result.Failed == res.Reason { + return res + } + } + } if !ValidateValueWithPattern(resourceElement, patternElement) { res.FailWithMessagef("Failed to validate value %v with pattern %v. Path: %s", resourceElement, patternElement, path) } @@ -92,7 +105,7 @@ func validateResourceElement(resourceElement, patternElement interface{}, path s // 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(resourceMap, patternMap map[string]interface{}, path string) result.RuleApplicationResult { +func validateMap(resourceMap, patternMap map[string]interface{}, origPattern interface{}, path string) result.RuleApplicationResult { res := result.NewRuleApplicationResult("") for key, patternElement := range patternMap { @@ -104,7 +117,7 @@ func validateMap(resourceMap, patternMap map[string]interface{}, path string) re } else if patternElement == "*" && resourceMap[key] == nil { res.FailWithMessagef("Field %s is not present", key) } else { - elementResult := validateResourceElement(resourceMap[key], patternElement, path+key+"/") + elementResult := validateResourceElement(resourceMap[key], patternElement, origPattern, path+key+"/") res.MergeWith(&elementResult) } } @@ -112,10 +125,7 @@ func validateMap(resourceMap, patternMap map[string]interface{}, path string) re return res } -// If validateResourceElement detects array element inside resource and pattern trees, it goes to validateArray -// Unlike the validateMap, we should check the array elements type on-site, because in case of maps, we should -// get anchors and check each array element with it. -func validateArray(resourceArray, patternArray []interface{}, path string) result.RuleApplicationResult { +func validateArray(resourceArray, patternArray []interface{}, originPattern interface{}, path string) result.RuleApplicationResult { res := result.NewRuleApplicationResult("") if 0 == len(patternArray) { @@ -126,13 +136,13 @@ func validateArray(resourceArray, patternArray []interface{}, path string) resul 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 - arrayResult := validateArrayOfMaps(resourceArray, typedPatternElement, path) + arrayResult := validateArrayOfMaps(resourceArray, typedPatternElement, originPattern, path) res.MergeWith(&arrayResult) default: // In all other cases - detect type and handle each array element with validateResourceElement for i, patternElement := range patternArray { currentPath := path + strconv.Itoa(i) + "/" - elementResult := validateResourceElement(resourceArray[i], patternElement, currentPath) + elementResult := validateResourceElement(resourceArray[i], patternElement, originPattern, currentPath) res.MergeWith(&elementResult) } } @@ -140,11 +150,126 @@ func validateArray(resourceArray, patternArray []interface{}, path string) resul return res } +func actualizePattern(origPattern interface{}, referencePattern, absolutePath string) (interface{}, result.RuleApplicationResult) { + res := result.NewRuleApplicationResult("") + var foundValue interface{} + + referencePattern = strings.Trim(referencePattern, "$()") + + operator := getOperatorFromStringPattern(referencePattern) + referencePattern = referencePattern[len(operator):] + + if len(referencePattern) == 0 { + res.FailWithMessagef("Expected path. Found empty reference") + return nil, res + } + + actualPath := FormAbsolutePath(referencePattern, absolutePath) + + valFromReference, res := getValueFromReference(origPattern, actualPath) + + if result.Failed == res.Reason { + return nil, res + } + + if operator == Equal { //if operator does not exist return raw value + return valFromReference, res + } + + foundValue, res = valFromReferenceToString(valFromReference, string(operator)) + + return string(operator) + foundValue.(string), res +} + +//Parse value to string +func valFromReferenceToString(value interface{}, operator string) (string, result.RuleApplicationResult) { + res := result.NewRuleApplicationResult("") + + switch typed := value.(type) { + case string: + return typed, res + case int, int64: + return fmt.Sprintf("%d", value), res + case float64: + return fmt.Sprintf("%f", value), res + default: + res.FailWithMessagef("Incorrect expression. Operator %s does not match with value: %v", operator, value) + return "", res + } +} + +func FormAbsolutePath(referencePath, absolutePath string) string { + if filepath.IsAbs(referencePath) { + return referencePath + } + + return filepath.Join(absolutePath, referencePath) +} + +//Prepares original pattern, path to value, and call traverse function +func getValueFromReference(origPattern interface{}, reference string) (interface{}, result.RuleApplicationResult) { + originalPatternMap := origPattern.(map[string]interface{}) + reference = reference[1:len(reference)] + statements := strings.Split(reference, "/") + + return getValueFromPattern(originalPatternMap, statements, 0) +} + +func getValueFromPattern(patternMap map[string]interface{}, keys []string, currentKeyIndex int) (interface{}, result.RuleApplicationResult) { + res := result.NewRuleApplicationResult("") + + for key, pattern := range patternMap { + rawKey := getRawKeyIfWrappedWithAttributes(key) + + if rawKey == keys[len(keys)-1] && currentKeyIndex == len(keys)-1 { + return pattern, res + } else if rawKey != keys[currentKeyIndex] && currentKeyIndex != len(keys)-1 { + continue + } + + switch typedPattern := pattern.(type) { + case []interface{}: + if keys[currentKeyIndex] == rawKey { + for i, value := range typedPattern { + resourceMap, ok := value.(map[string]interface{}) + if !ok { + res.FailWithMessagef("Pattern and resource have different structures. Expected %T, found %T", pattern, value) + return nil, res + } + if keys[currentKeyIndex+1] == strconv.Itoa(i) { + return getValueFromPattern(resourceMap, keys, currentKeyIndex+2) + } + res.FailWithMessagef("Reference to non-existent place in the document") + } + } + res.FailWithMessagef("Reference to non-existent place in the document") + case map[string]interface{}: + if keys[currentKeyIndex] == rawKey { + return getValueFromPattern(typedPattern, keys, currentKeyIndex+1) + } + res.FailWithMessagef("Reference to non-existent place in the document") + case string, float64, int, int64, bool, nil: + continue + } + } + + path := "" + + /*for i := len(keys) - 1; i >= 0; i-- { + path = keys[i] + path + "/" + }*/ + for _, elem := range keys { + path = "/" + elem + path + } + res.FailWithMessagef("No value found for specified reference: %s", path) + return nil, res +} + // 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 { +func validateArrayOfMaps(resourceMapArray []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string) result.RuleApplicationResult { anchor, pattern := getAnchorFromMap(patternMap) handler := CreateAnchorHandler(anchor, pattern, path) - return handler.Handle(resourceMapArray, patternMap) + return handler.Handle(resourceMapArray, patternMap, originPattern) } diff --git a/pkg/engine/validation_test.go b/pkg/engine/validation_test.go index 127e736565..155e46aed1 100644 --- a/pkg/engine/validation_test.go +++ b/pkg/engine/validation_test.go @@ -289,7 +289,7 @@ func TestValidateMap(t *testing.T) { json.Unmarshal(rawPattern, &pattern) json.Unmarshal(rawMap, &resource) - res := validateMap(resource, pattern, "/") + res := validateMap(resource, pattern, pattern, "/") assert.NilError(t, res.ToError()) } @@ -384,7 +384,7 @@ func TestValidateMap_AsteriskForInt(t *testing.T) { json.Unmarshal(rawPattern, &pattern) json.Unmarshal(rawMap, &resource) - res := validateMap(resource, pattern, "/") + res := validateMap(resource, pattern, pattern, "/") assert.NilError(t, res.ToError()) } @@ -476,7 +476,7 @@ func TestValidateMap_AsteriskForMap(t *testing.T) { json.Unmarshal(rawPattern, &pattern) json.Unmarshal(rawMap, &resource) - res := validateMap(resource, pattern, "/") + res := validateMap(resource, pattern, pattern, "/") assert.NilError(t, res.ToError()) } @@ -563,7 +563,7 @@ func TestValidateMap_AsteriskForArray(t *testing.T) { json.Unmarshal(rawPattern, &pattern) json.Unmarshal(rawMap, &resource) - res := validateMap(resource, pattern, "/") + res := validateMap(resource, pattern, pattern, "/") assert.NilError(t, res.ToError()) } @@ -653,7 +653,7 @@ func TestValidateMap_AsteriskFieldIsMissing(t *testing.T) { json.Unmarshal(rawPattern, &pattern) json.Unmarshal(rawMap, &resource) - res := validateMap(resource, pattern, "/") + res := validateMap(resource, pattern, pattern, "/") assert.Assert(t, res.ToError() != nil) } @@ -743,7 +743,7 @@ func TestValidateMap_livenessProbeIsNull(t *testing.T) { json.Unmarshal(rawPattern, &pattern) json.Unmarshal(rawMap, &resource) - res := validateMap(resource, pattern, "/") + res := validateMap(resource, pattern, pattern, "/") assert.NilError(t, res.ToError()) } @@ -832,7 +832,7 @@ func TestValidateMap_livenessProbeIsMissing(t *testing.T) { json.Unmarshal(rawPattern, &pattern) json.Unmarshal(rawMap, &resource) - res := validateMap(resource, pattern, "/") + res := validateMap(resource, pattern, pattern, "/") assert.NilError(t, res.ToError()) } @@ -873,7 +873,7 @@ func TestValidateMapElement_TwoElementsInArrayOnePass(t *testing.T) { json.Unmarshal(rawPattern, &pattern) json.Unmarshal(rawMap, &resource) - res := validateResourceElement(resource, pattern, "/") + res := validateResourceElement(resource, pattern, pattern, "/") assert.NilError(t, res.ToError()) } @@ -905,10 +905,553 @@ func TestValidateMapElement_OneElementInArrayPass(t *testing.T) { json.Unmarshal(rawPattern, &pattern) json.Unmarshal(rawMap, &resource) - res := validateResourceElement(resource, pattern, "/") + res := validateResourceElement(resource, pattern, pattern, "/") assert.NilError(t, res.ToError()) } +func TestValidateMap_CorrectRelativePathInConfig(t *testing.T) { + rawPattern := []byte(`{ + "spec":{ + "containers":[ + { + "name":"*", + "resources":{ + "requests":{ + "memory":"$(<=./../../limits/memory)" + }, + "limits":{ + "memory":"2048Mi" + } + } + } + ] + } + }`) + + rawMap := []byte(`{ + "apiVersion":"apps/v1", + "kind":"Deployment", + "metadata":{ + "name":"nginx-deployment", + "labels":{ + "app":"nginx" + } + }, + "spec":{ + "containers":[ + { + "name":"nirmata", + "resources":{ + "requests":{ + "memory":"1024Mi" + }, + "limits":{ + "memory":"2048Mi" + } + } + } + ] + } + }`) + + var pattern, resource interface{} + json.Unmarshal(rawPattern, &pattern) + json.Unmarshal(rawMap, &resource) + + res := validateResourceElement(resource, pattern, pattern, "/") + assert.NilError(t, res.ToError()) +} + +func TestValidateMap_RelativePathDoesNotExists(t *testing.T) { + rawPattern := []byte(`{ + "spec":{ + "containers":[ + { + "name":"*", + "resources":{ + "requests":{ + "memory":"$(./../somekey/somekey2/memory)" + }, + "limits":{ + "memory":"2048Mi" + } + } + } + ] + } + }`) + + rawMap := []byte(`{ + "apiVersion":"apps/v1", + "kind":"Deployment", + "metadata":{ + "name":"nginx-deployment", + "labels":{ + "app":"nginx" + } + }, + "spec":{ + "containers":[ + { + "name":"nirmata", + "resources":{ + "requests":{ + "memory":"1024Mi" + }, + "limits":{ + "memory":"2048Mi" + } + } + } + ] + } + }`) + + var pattern, resource interface{} + json.Unmarshal(rawPattern, &pattern) + json.Unmarshal(rawMap, &resource) + + res := validateResourceElement(resource, pattern, pattern, "/") + assert.Assert(t, res.ToError() != nil) +} + +func TestValidateMap_OnlyAnchorsInPath(t *testing.T) { + rawPattern := []byte(`{ + "spec":{ + "containers":[ + { + "name":"*", + "resources":{ + "requests":{ + "memory":"$()" + }, + "limits":{ + "memory":"2048Mi" + } + } + } + ] + } + }`) + + rawMap := []byte(`{ + "apiVersion":"apps/v1", + "kind":"Deployment", + "metadata":{ + "name":"nginx-deployment", + "labels":{ + "app":"nginx" + } + }, + "spec":{ + "containers":[ + { + "name":"nirmata", + "resources":{ + "requests":{ + "memory":"1024Mi" + }, + "limits":{ + "memory":"2048Mi" + } + } + } + ] + } + }`) + + var pattern, resource interface{} + json.Unmarshal(rawPattern, &pattern) + json.Unmarshal(rawMap, &resource) + + res := validateResourceElement(resource, pattern, pattern, "/") + assert.Assert(t, res.ToError() != nil) +} + +func TestValidateMap_MalformedReferenceOnlyDolarMark(t *testing.T) { + rawPattern := []byte(`{ + "spec":{ + "containers":[ + { + "name":"*", + "resources":{ + "requests":{ + "memory":"$" + }, + "limits":{ + "memory":"2048Mi" + } + } + } + ] + } + }`) + + rawMap := []byte(`{ + "apiVersion":"apps/v1", + "kind":"Deployment", + "metadata":{ + "name":"nginx-deployment", + "labels":{ + "app":"nginx" + } + }, + "spec":{ + "containers":[ + { + "name":"nirmata", + "resources":{ + "requests":{ + "memory":"1024Mi" + }, + "limits":{ + "memory":"2048Mi" + } + } + } + ] + } + }`) + + var pattern, resource interface{} + json.Unmarshal(rawPattern, &pattern) + json.Unmarshal(rawMap, &resource) + + res := validateResourceElement(resource, pattern, pattern, "/") + assert.Assert(t, res.ToError() != nil) +} + +func TestValidateMap_RelativePathWithParentheses(t *testing.T) { + rawPattern := []byte(`{ + "spec":{ + "containers":[ + { + "name":"*", + "resources":{ + "requests":{ + "memory":"$(<=./../../lim(its/mem)ory)" + }, + "lim(its":{ + "mem)ory":"2048Mi" + } + } + } + ] + } + }`) + + rawMap := []byte(`{ + "apiVersion":"apps/v1", + "kind":"Deployment", + "metadata":{ + "name":"nginx-deployment", + "labels":{ + "app":"nginx" + } + }, + "spec":{ + "containers":[ + { + "name":"nirmata", + "resources":{ + "requests":{ + "memory":"1024Mi" + }, + "lim(its":{ + "mem)ory":"2048Mi" + } + } + } + ] + } + }`) + + var pattern, resource interface{} + json.Unmarshal(rawPattern, &pattern) + json.Unmarshal(rawMap, &resource) + + res := validateResourceElement(resource, pattern, pattern, "/") + assert.NilError(t, res.ToError()) +} + +func TestValidateMap_MalformedPath(t *testing.T) { + rawPattern := []byte(`{ + "spec":{ + "containers":[ + { + "name":"*", + "resources":{ + "requests":{ + "memory":"$(>2048)" + }, + "limits":{ + "memory":"2048Mi" + } + } + } + ] + } + }`) + + rawMap := []byte(`{ + "apiVersion":"apps/v1", + "kind":"Deployment", + "metadata":{ + "name":"nginx-deployment", + "labels":{ + "app":"nginx" + } + }, + "spec":{ + "containers":[ + { + "name":"nirmata", + "resources":{ + "requests":{ + "memory":"1024Mi" + }, + "limits":{ + "memory":"2048Mi" + } + } + } + ] + } + }`) + + var pattern, resource interface{} + json.Unmarshal(rawPattern, &pattern) + json.Unmarshal(rawMap, &resource) + + res := validateResourceElement(resource, pattern, pattern, "/") + assert.Assert(t, res.ToError() != nil) +} + +func TestValidateMap_AbosolutePathExists(t *testing.T) { + rawPattern := []byte(`{ + "spec":{ + "containers":[ + { + "name":"*", + "resources":{ + "requests":{ + "memory":"$(<=/spec/containers/0/resources/limits/memory)" + }, + "limits":{ + "memory":"2048Mi" + } + } + } + ] + } + }`) + + rawMap := []byte(`{ + "apiVersion":"apps/v1", + "kind":"Deployment", + "metadata":{ + "name":"nginx-deployment", + "labels":{ + "app":"nginx" + } + }, + "spec":{ + "containers":[ + { + "name":"nirmata", + "resources":{ + "requests":{ + "memory":"1024Mi" + }, + "limits":{ + "memory":"2048Mi" + } + } + } + ] + } + }`) + + var pattern, resource interface{} + json.Unmarshal(rawPattern, &pattern) + json.Unmarshal(rawMap, &resource) + + res := validateResourceElement(resource, pattern, pattern, "/") + assert.Assert(t, res.ToError() == nil) +} + +func TestValidateMap_AbsolutePathToMetadata(t *testing.T) { + rawPattern := []byte(`{ + "metadata":{ + "labels":{ + "app":"nirmata*" + } + }, + "spec":{ + "containers":[ + { + "(name)":"$(/metadata/labels/app)", + "image":"nirmata.io*" + } + ] + } + }`) + + rawMap := []byte(`{ + "apiVersion":"apps/v1", + "kind":"Deployment", + "metadata":{ + "labels":{ + "app":"nirmata*" + } + }, + "spec":{ + "containers":[ + { + "resources":{ + "requests":{ + "memory":"1024Mi" + }, + "limits":{ + "memory":"2048Mi" + } + }, + "name":"nirmata" + } + ] + } + }`) + + var pattern, resource interface{} + json.Unmarshal(rawPattern, &pattern) + json.Unmarshal(rawMap, &resource) + + res := validateResourceElement(resource, pattern, pattern, "/") + assert.Assert(t, res.ToError() == nil) +} + +func TestValidateMap_AbosolutePathDoesNotExists(t *testing.T) { + rawPattern := []byte(`{ + "spec":{ + "containers":[ + { + "name":"*", + "resources":{ + "requests":{ + "memory":"$(<=/some/memory)" + }, + "limits":{ + "memory":"2048Mi" + } + } + } + ] + } + }`) + + rawMap := []byte(`{ + "apiVersion":"apps/v1", + "kind":"Deployment", + "metadata":{ + "name":"nginx-deployment", + "labels":{ + "app":"nginx" + } + }, + "spec":{ + "containers":[ + { + "name":"nirmata", + "resources":{ + "requests":{ + "memory":"1024Mi" + }, + "limits":{ + "memory":"2048Mi" + } + } + } + ] + } + }`) + + var pattern, resource interface{} + json.Unmarshal(rawPattern, &pattern) + json.Unmarshal(rawMap, &resource) + + res := validateResourceElement(resource, pattern, pattern, "/") + assert.Assert(t, res.ToError() != nil) +} + +func TestActualizePattern_GivenRelativePathThatExists(t *testing.T) { + absolutePath := "/spec/containers/0/resources/requests/memory" + referencePath := "$(<=./../../limits/memory)" + + rawPattern := []byte(`{ + "spec":{ + "containers":[ + { + "name":"*", + "resources":{ + "requests":{ + "memory":"$(<=./../../limits/memory)" + }, + "limits":{ + "memory":"2048Mi" + } + } + } + ] + } + }`) + + var pattern interface{} + + json.Unmarshal(rawPattern, &pattern) + + pattern, res := actualizePattern(pattern, referencePath, absolutePath) + + assert.Assert(t, res.ToError() == nil) +} + +func TestFormAbsolutePath_RelativePathExists(t *testing.T) { + absolutePath := "/spec/containers/0/resources/requests/memory" + referencePath := "./../../limits/memory" + expectedString := "/spec/containers/0/resources/limits/memory" + + result := FormAbsolutePath(referencePath, absolutePath) + + assert.Assert(t, result == expectedString) +} + +func TestFormAbsolutePath_RelativePathWithBackToTopInTheBegining(t *testing.T) { + absolutePath := "/spec/containers/0/resources/requests/memory" + referencePath := "../../limits/memory" + expectedString := "/spec/containers/0/resources/limits/memory" + + result := FormAbsolutePath(referencePath, absolutePath) + + assert.Assert(t, result == expectedString) +} + +func TestFormAbsolutePath_AbsolutePathExists(t *testing.T) { + absolutePath := "/spec/containers/0/resources/requests/memory" + referencePath := "/spec/containers/0/resources/limits/memory" + + result := FormAbsolutePath(referencePath, absolutePath) + + assert.Assert(t, result == referencePath) +} + +func TestFormAbsolutePath_EmptyPath(t *testing.T) { + absolutePath := "/spec/containers/0/resources/requests/memory" + referencePath := "" + + result := FormAbsolutePath(referencePath, absolutePath) + + assert.Assert(t, result == absolutePath) +} + func TestValidateMapElement_OneElementInArrayNotPass(t *testing.T) { rawPattern := []byte(`[ { @@ -937,7 +1480,7 @@ func TestValidateMapElement_OneElementInArrayNotPass(t *testing.T) { json.Unmarshal(rawPattern, &pattern) json.Unmarshal(rawMap, &resource) - res := validateResourceElement(resource, pattern, "/") + res := validateResourceElement(resource, pattern, pattern, "/") assert.Assert(t, res.ToError() != nil) }