From ac736bbf84d5376c3363b8bfe116ccd1293aa41d Mon Sep 17 00:00:00 2001 From: Maxim Goncharenko Date: Mon, 27 May 2019 14:45:54 +0300 Subject: [PATCH 1/8] Written base for patterns using TDD --- pkg/engine/pattern.go | 106 ++++++++++++++++++ pkg/engine/pattern_test.go | 213 +++++++++++++++++++++++++++++++++++++ 2 files changed, 319 insertions(+) create mode 100644 pkg/engine/pattern.go create mode 100644 pkg/engine/pattern_test.go diff --git a/pkg/engine/pattern.go b/pkg/engine/pattern.go new file mode 100644 index 0000000000..e387a02b04 --- /dev/null +++ b/pkg/engine/pattern.go @@ -0,0 +1,106 @@ +package engine + +import ( + "log" + "math" + "strings" +) + +// ValidateValueWithPattern validates value with operators and wildcards +func ValidateValueWithPattern(value, pattern interface{}) bool { + switch typedPattern := pattern.(type) { + case bool: + typedValue, ok := value.(bool) + if !ok { + log.Printf("Expected bool, found %T", value) + return false + } + return typedPattern == typedValue + case int: + return validateValueWithIntPattern(value, typedPattern) + case float64: + return validateValueWithFloatPattern(value, typedPattern) + case string: + return validateValueWithStringPattern(value, typedPattern) + case map[string]interface{}, []interface{}: + log.Println("Maps and arrays as patterns are not supported") + return false + case nil: + return validateValueWithNilPattern(value) + default: + log.Printf("Unknown type as pattern: %T\n", pattern) + return false + } +} + +func validateValueWithIntPattern(value interface{}, pattern int) bool { + switch typedValue := value.(type) { + case int: + return typedValue == pattern + case float64: + // check that float has no fraction + if typedValue == math.Trunc(typedValue) { + return int(typedValue) == pattern + } + + log.Printf("Expected int, found float: %f\n", typedValue) + return false + default: + log.Printf("Expected int, found: %T\n", value) + return false + } +} + +func validateValueWithFloatPattern(value interface{}, pattern float64) bool { + switch typedValue := value.(type) { + case int: + // check that float has no fraction + if pattern == math.Trunc(pattern) { + return int(pattern) == value + } + + log.Printf("Expected float, found int: %d\n", typedValue) + return false + case float64: + return typedValue == pattern + default: + log.Printf("Expected float, found: %T\n", value) + return false + } +} + +func validateValueWithNilPattern(value interface{}) bool { + switch typed := value.(type) { + case float64: + return typed == 0.0 + case int: + return typed == 0 + case string: + return typed == "" + case bool: + return typed == false + case nil: + return true + case map[string]interface{}, []interface{}: + log.Println("Maps and arrays could not be checked with nil pattern") + return false + default: + log.Printf("Unknown type as value when checking for nil pattern: %T\n", value) + return false + } +} + +func validateValueWithStringPattern(value interface{}, pattern string) bool { + statements := strings.Split(pattern, "|") + for statement := range statements { + if checkSingleStatement(value, statement) { + return true + } + } + + return false +} + +func checkSingleStatement(value, pattern interface{}) bool { + return true +} diff --git a/pkg/engine/pattern_test.go b/pkg/engine/pattern_test.go new file mode 100644 index 0000000000..5e1c8c2b82 --- /dev/null +++ b/pkg/engine/pattern_test.go @@ -0,0 +1,213 @@ +package engine + +import ( + "encoding/json" + "testing" + + "gotest.tools/assert" +) + +func TestValidateValueWithPattern_Bool(t *testing.T) { + assert.Assert(t, ValidateValueWithPattern(true, true)) + assert.Assert(t, !ValidateValueWithPattern(true, false)) + assert.Assert(t, !ValidateValueWithPattern(false, true)) + assert.Assert(t, ValidateValueWithPattern(false, false)) +} + +func TestValidateValueWithPattern_BoolInJson(t *testing.T) { + rawPattern := []byte(` + { + "key": true + } + `) + + rawValue := []byte(` + { + "key": true + } + `) + + var pattern, value map[string]interface{} + err := json.Unmarshal(rawPattern, &pattern) + assert.Assert(t, err) + err = json.Unmarshal(rawValue, &value) + assert.Assert(t, err) + + assert.Assert(t, ValidateValueWithPattern(value["key"], pattern["key"])) +} + +func TestValidateValueWithPattern_NullPatternStringValue(t *testing.T) { + rawPattern := []byte(` + { + "key": null + } + `) + + rawValue := []byte(` + { + "key": "value" + } + `) + + var pattern, value map[string]interface{} + err := json.Unmarshal(rawPattern, &pattern) + assert.Assert(t, err) + err = json.Unmarshal(rawValue, &value) + assert.Assert(t, err) + + assert.Assert(t, !ValidateValueWithPattern(value["key"], pattern["key"])) +} + +func TestValidateValueWithPattern_NullPatternDefaultString(t *testing.T) { + rawPattern := []byte(` + { + "key": null + } + `) + + rawValue := []byte(` + { + "key": "" + } + `) + + var pattern, value map[string]interface{} + err := json.Unmarshal(rawPattern, &pattern) + assert.Assert(t, err) + err = json.Unmarshal(rawValue, &value) + assert.Assert(t, err) + + assert.Assert(t, ValidateValueWithPattern(value["key"], pattern["key"])) +} + +func TestValidateValueWithPattern_NullPatternDefaultFloat(t *testing.T) { + rawPattern := []byte(` + { + "key": null + } + `) + + rawValue := []byte(` + { + "key": 0.0 + } + `) + + var pattern, value map[string]interface{} + err := json.Unmarshal(rawPattern, &pattern) + assert.Assert(t, err) + err = json.Unmarshal(rawValue, &value) + assert.Assert(t, err) + + assert.Assert(t, ValidateValueWithPattern(value["key"], pattern["key"])) +} + +func TestValidateValueWithPattern_NullPatternDefaultInt(t *testing.T) { + rawPattern := []byte(` + { + "key": null + } + `) + + rawValue := []byte(` + { + "key": 0 + } + `) + + var pattern, value map[string]interface{} + err := json.Unmarshal(rawPattern, &pattern) + assert.Assert(t, err) + err = json.Unmarshal(rawValue, &value) + assert.Assert(t, err) + + assert.Assert(t, ValidateValueWithPattern(value["key"], pattern["key"])) +} + +func TestValidateValueWithPattern_NullPatternDefaultBool(t *testing.T) { + rawPattern := []byte(` + { + "key": null + } + `) + + rawValue := []byte(` + { + "key": false + } + `) + + var pattern, value map[string]interface{} + err := json.Unmarshal(rawPattern, &pattern) + assert.Assert(t, err) + err = json.Unmarshal(rawValue, &value) + assert.Assert(t, err) + + assert.Assert(t, ValidateValueWithPattern(value["key"], pattern["key"])) +} + +func TestValidateValueWithPattern_StringsLogicalOr(t *testing.T) { + pattern := "192.168.88.1 | 10.100.11.*" + value := "10.100.11.54" + assert.Assert(t, ValidateValueWithPattern(value, pattern)) +} + +func TestValidateValueWithNilPattern_NullPatternStringValue(t *testing.T) { + assert.Assert(t, !validateValueWithNilPattern("value")) +} + +func TestValidateValueWithNilPattern_NullPatternDefaultString(t *testing.T) { + assert.Assert(t, validateValueWithNilPattern("")) +} + +func TestValidateValueWithNilPattern_NullPatternDefaultFloat(t *testing.T) { + assert.Assert(t, validateValueWithNilPattern(0.0)) +} + +func TestValidateValueWithNilPattern_NullPatternFloat(t *testing.T) { + assert.Assert(t, !validateValueWithNilPattern(0.1)) +} + +func TestValidateValueWithNilPattern_NullPatternDefaultInt(t *testing.T) { + assert.Assert(t, validateValueWithNilPattern(0)) +} + +func TestValidateValueWithNilPattern_NullPatternInt(t *testing.T) { + assert.Assert(t, !validateValueWithNilPattern(1)) +} + +func TestValidateValueWithNilPattern_NullPatternDefaultBool(t *testing.T) { + assert.Assert(t, validateValueWithNilPattern(false)) +} + +func TestValidateValueWithNilPattern_NullPatternTrueBool(t *testing.T) { + assert.Assert(t, !validateValueWithNilPattern(true)) +} + +func TestValidateValueWithFloatPattern_FloatValue(t *testing.T) { + assert.Assert(t, validateValueWithFloatPattern(7.9914, 7.9914)) +} + +func TestValidateValueWithFloatPattern_FloatValueNotPass(t *testing.T) { + assert.Assert(t, !validateValueWithFloatPattern(7.9914, 7.99141)) +} + +func TestValidateValueWithFloatPattern_FloatPatternWithoutFractionIntValue(t *testing.T) { + assert.Assert(t, validateValueWithFloatPattern(7, 7.000000)) +} + +func TestValidateValueWithFloatPattern_FloatPatternWithoutFraction(t *testing.T) { + assert.Assert(t, validateValueWithFloatPattern(7.000000, 7.000000)) +} + +func TestValidateValueWithIntPattern_FloatValueWithoutFraction(t *testing.T) { + assert.Assert(t, validateValueWithFloatPattern(7.000000, 7)) +} + +func TestValidateValueWithIntPattern_FloatValueWitFraction(t *testing.T) { + assert.Assert(t, !validateValueWithFloatPattern(7.000001, 7)) +} + +func TestValidateValueWithIntPattern_NotPass(t *testing.T) { + assert.Assert(t, !validateValueWithFloatPattern(8, 7)) +} From 435a19152a704b3d3c6f2d07eba5928ec94c2d94 Mon Sep 17 00:00:00 2001 From: Denis Belyshev Date: Mon, 27 May 2019 18:07:24 +0300 Subject: [PATCH 2/8] I have finished implementing patterns --- pkg/engine/overlay.go | 2 +- pkg/engine/pattern.go | 206 ++++++++++++++++++++++++++++++++-- pkg/engine/pattern_test.go | 58 ++++++++++ pkg/engine/validation.go | 156 +------------------------ pkg/engine/validation_test.go | 164 --------------------------- 5 files changed, 262 insertions(+), 324 deletions(-) diff --git a/pkg/engine/overlay.go b/pkg/engine/overlay.go index 51e710ff1c..0199c9b63d 100644 --- a/pkg/engine/overlay.go +++ b/pkg/engine/overlay.go @@ -231,7 +231,7 @@ func skipArrayObject(object, anchors map[string]interface{}) bool { return true } - if value != pattern { + if !ValidateValueWithPattern(value, pattern) { return true } } diff --git a/pkg/engine/pattern.go b/pkg/engine/pattern.go index e387a02b04..56929a692d 100644 --- a/pkg/engine/pattern.go +++ b/pkg/engine/pattern.go @@ -1,9 +1,32 @@ package engine import ( + "fmt" "log" "math" + "regexp" + "strconv" "strings" + + "github.com/minio/minio/pkg/wildcard" +) + +// 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 = "<" ) // ValidateValueWithPattern validates value with operators and wildcards @@ -17,30 +40,34 @@ func ValidateValueWithPattern(value, pattern interface{}) bool { } return typedPattern == typedValue case int: + return validateValueWithIntPattern(value, int64(typedPattern)) + case int64: return validateValueWithIntPattern(value, typedPattern) case float64: return validateValueWithFloatPattern(value, typedPattern) case string: - return validateValueWithStringPattern(value, typedPattern) + return validateValueWithStringPatterns(value, typedPattern) + case nil: + return validateValueWithNilPattern(value) case map[string]interface{}, []interface{}: log.Println("Maps and arrays as patterns are not supported") return false - case nil: - return validateValueWithNilPattern(value) default: log.Printf("Unknown type as pattern: %T\n", pattern) return false } } -func validateValueWithIntPattern(value interface{}, pattern int) bool { +func validateValueWithIntPattern(value interface{}, pattern int64) bool { switch typedValue := value.(type) { case int: + return int64(typedValue) == pattern + case int64: return typedValue == pattern case float64: // check that float has no fraction if typedValue == math.Trunc(typedValue) { - return int(typedValue) == pattern + return int64(typedValue) == pattern } log.Printf("Expected int, found float: %f\n", typedValue) @@ -75,6 +102,8 @@ func validateValueWithNilPattern(value interface{}) bool { return typed == 0.0 case int: return typed == 0 + case int64: + return typed == 0 case string: return typed == "" case bool: @@ -90,10 +119,11 @@ func validateValueWithNilPattern(value interface{}) bool { } } -func validateValueWithStringPattern(value interface{}, pattern string) bool { +func validateValueWithStringPatterns(value interface{}, pattern string) bool { statements := strings.Split(pattern, "|") - for statement := range statements { - if checkSingleStatement(value, statement) { + for _, statement := range statements { + statement = strings.Trim(statement, " ") + if validateValueWithStringPattern(value, statement) { return true } } @@ -101,6 +131,164 @@ func validateValueWithStringPattern(value interface{}, pattern string) bool { return false } -func checkSingleStatement(value, pattern interface{}) bool { +func validateValueWithStringPattern(value interface{}, pattern string) bool { + operator := getOperatorFromStringPattern(pattern) + pattern = pattern[len(operator):] + number, str := getNumberAndStringPartsFromPattern(pattern) + + if "" == number { + return validateString(value, str, operator) + } + + validateNumberWithStr(value, number, str, operator) + return true } + +func validateString(value interface{}, pattern string, operator Operator) bool { + if NotEqual == operator || Equal == operator { + strValue, ok := value.(string) + if !ok { + log.Printf("Expected string, found %T\n", value) + return false + } + + wildcardResult := wildcard.Match(pattern, strValue) + + if NotEqual == operator { + return !wildcardResult + } + + return wildcardResult + } + + log.Println("Operators >, >=, <, <= are not applicable to strings") + return false +} + +func validateNumberWithStr(value interface{}, patternNumber, patternStr string, operator Operator) bool { + patternParsedNumber, err := parseNumber(patternNumber) + if err != nil { + return false + } + + if "" != patternStr { + typedValue, ok := value.(string) + if !ok { + log.Printf("Number must have suffix: %s", patternStr) + return false + } + + valueNumber, valueStr := getNumberAndStringPartsFromPattern(typedValue) + if !wildcard.Match(patternStr, valueStr) { + log.Printf("Suffix %s has not passed wildcard check: %s", valueStr, patternStr) + return false + } + + valueParsedNumber, err := parseNumber(valueNumber) + if err != nil { + return false + } + + return validateNumber(valueParsedNumber, patternParsedNumber, operator) + } + + return validateNumber(value, patternParsedNumber, operator) +} + +func validateNumber(value, pattern interface{}, operator Operator) bool { + var floatPattern, floatValue float64 + + switch typed := value.(type) { + case float64: + floatValue = typed + case int64: + floatValue = float64(typed) + case int: + floatValue = float64(typed) + default: + return false + } + + switch typed := pattern.(type) { + case float64: + floatPattern = typed + case int64: + floatPattern = float64(typed) + case int: + floatPattern = float64(typed) + default: + return false + } + + switch operator { + case Equal: + return floatValue == floatPattern + case NotEqual: + return floatValue != floatPattern + case More: + return floatValue > floatPattern + case MoreEqual: + return floatValue >= floatPattern + case Less: + return floatValue < floatPattern + case LessEqual: + return floatValue <= floatPattern + } + + return false +} + +func getOperatorFromStringPattern(pattern string) Operator { + 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 "" +} + +func getNumberAndStringPartsFromPattern(pattern string) (number, str string) { + regexpStr := `^(\d*(\.\d+)?)(.*)` + re := regexp.MustCompile(regexpStr) + matches := re.FindAllStringSubmatch(pattern, -1) + match := matches[0] + return match[1], match[3] +} + +func checkForWildcard(value, pattern string) error { + if !wildcard.Match(pattern, value) { + return fmt.Errorf("wildcard check has failed. Pattern: \"%s\". Value: \"%s\"", pattern, value) + } + + return nil +} + +func parseNumber(number string) (interface{}, error) { + var err error + + if floatValue, err := strconv.ParseFloat(number, 64); err == nil { + return floatValue, nil + } + + if intValue, err := strconv.ParseInt(number, 10, 64); err == nil { + return intValue, nil + } + + return nil, err +} diff --git a/pkg/engine/pattern_test.go b/pkg/engine/pattern_test.go index 5e1c8c2b82..b61c0d2a4e 100644 --- a/pkg/engine/pattern_test.go +++ b/pkg/engine/pattern_test.go @@ -211,3 +211,61 @@ func TestValidateValueWithIntPattern_FloatValueWitFraction(t *testing.T) { func TestValidateValueWithIntPattern_NotPass(t *testing.T) { assert.Assert(t, !validateValueWithFloatPattern(8, 7)) } + +func TestGetNumberAndStringPartsFromPattern_NumberAndString(t *testing.T) { + number, str := getNumberAndStringPartsFromPattern("1024Gi") + assert.Equal(t, number, "1024") + assert.Equal(t, str, "Gi") +} + +func TestGetNumberAndStringPartsFromPattern_OnlyNumber(t *testing.T) { + number, str := getNumberAndStringPartsFromPattern("1024") + assert.Equal(t, number, "1024") + assert.Equal(t, str, "") +} + +func TestGetNumberAndStringPartsFromPattern_OnlyString(t *testing.T) { + number, str := getNumberAndStringPartsFromPattern("Gi") + assert.Equal(t, number, "") + assert.Equal(t, str, "Gi") +} + +func TestGetNumberAndStringPartsFromPattern_StringFirst(t *testing.T) { + number, str := getNumberAndStringPartsFromPattern("Gi1024") + assert.Equal(t, number, "") + assert.Equal(t, str, "Gi1024") +} + +func TestGetNumberAndStringPartsFromPattern_Empty(t *testing.T) { + number, str := getNumberAndStringPartsFromPattern("") + assert.Equal(t, number, "") + assert.Equal(t, str, "") +} + +func TestValidateNumber_EqualTwoFloats(t *testing.T) { + assert.Assert(t, validateNumber(7.0, 7.000, 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.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)) +} diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index f7664aef7f..5809c4d31d 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -4,26 +4,11 @@ import ( "encoding/json" "fmt" "log" - "strconv" - "strings" - - "github.com/minio/minio/pkg/wildcard" kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// Operator is string alias that represents selection operators enum -type Operator string - -const ( - MoreEqual Operator = ">=" - LessEqual Operator = "<=" - NotEqual Operator = "!=" - More Operator = ">" - Less Operator = "<" -) - // TODO: Refactor using State pattern // TODO: Return Events and pass all checks to get all validation errors (not ) @@ -114,8 +99,8 @@ func validateArray(resourcePart, patternPart interface{}) error { } default: for _, value := range resourceArray { - if err := checkSingleValue(value, patternArray[0]); err != nil { - return err + if !ValidateValueWithPattern(value, patternArray[0]) { + return fmt.Errorf("Failed validate %v wit %v", value, patternArray[0]) } } } @@ -139,33 +124,9 @@ func validateMapElement(resourcePart, patternPart interface{}) error { } return validateArray(array, pattern) - case string: - return checkSingleValue(resourcePart, patternPart) - case float64: - switch num := resourcePart.(type) { - case float64: - if num != pattern { - return fmt.Errorf("%f not equal %f", num, pattern) - } - case int64: - if float64(num) != pattern { - return fmt.Errorf("%d not equal %f", num, pattern) - } - default: - return fmt.Errorf("expected %T, found %T", patternPart, resourcePart) - } - case int64: - switch num := resourcePart.(type) { - case float64: - if num != float64(pattern) { - return fmt.Errorf("%f not equal %d", num, pattern) - } - case int64: - if float64(num) != float64(num) { - return fmt.Errorf("%d not equal %d", num, pattern) - } - default: - return fmt.Errorf("expected %T, found %T", patternPart, resourcePart) + case string, float64, int, int64: + if !ValidateValueWithPattern(resourcePart, patternPart) { + return fmt.Errorf("Failed validate %v wit %v", resourcePart, patternPart) } default: return fmt.Errorf("validating error: unknown type in map: %T", patternPart) @@ -183,7 +144,7 @@ func skipValidatingObject(object, anchors map[string]interface{}) bool { return true } - if err := checkSingleValue(value, pattern); err != nil { + if !ValidateValueWithPattern(value, pattern) { return true } } @@ -191,111 +152,6 @@ func skipValidatingObject(object, anchors map[string]interface{}) bool { return false } -func checkSingleValue(value, pattern interface{}) error { - switch typedPattern := pattern.(type) { - case string: - switch typedValue := value.(type) { - case string: - return checkForWildcard(typedValue, typedPattern) - case float64: - return checkForOperator(typedValue, typedPattern) - case int: - return checkForOperator(float64(typedValue), typedPattern) - default: - return fmt.Errorf("expected string or numerical type, found %T, pattern: %s", value, typedPattern) - } - case float64: - num, ok := value.(float64) - if !ok { - return fmt.Errorf("expected float, found %T", value) - } - - if typedPattern != num { - return fmt.Errorf("value %f is not equal to pattern %f", value, typedPattern) - } - case int: - num, ok := value.(int) - if !ok { - return fmt.Errorf("expected int, found %T", value) - } - - if typedPattern != num { - return fmt.Errorf("value %d is not equal to pattern %d", num, typedPattern) - } - default: - return fmt.Errorf("expected pattern (string or numerical type), found %T", pattern) - } - - return nil -} - -func checkForWildcard(value, pattern string) error { - if !wildcard.Match(pattern, value) { - return fmt.Errorf("wildcard check has failed. Pattern: \"%s\". Value: \"%s\"", pattern, value) - } - - return nil -} - -func checkForOperator(value float64, pattern string) error { - operators := strings.Split(pattern, "|") - - for _, operator := range operators { - operator = strings.Replace(operator, " ", "", -1) - - // At least one success - return nil - if checkSingleOperator(value, operator) { - return nil - } - } - - return fmt.Errorf("operator check has failed. Pattern: \"%s\". Value: \"%f\"", pattern, value) -} - -func checkSingleOperator(value float64, pattern string) bool { - if operatorVal, err := strconv.ParseFloat(pattern, 64); err == nil { - return value == operatorVal - } - - if len(pattern) < 2 { - fmt.Printf("Validating error: operator can't have less than 2 characters: %s\n", pattern) - return false - } - - if operatorVal, ok := parseOperator(MoreEqual, pattern); ok { - return value >= operatorVal - } - - if operatorVal, ok := parseOperator(LessEqual, pattern); ok { - return value <= operatorVal - } - - if operatorVal, ok := parseOperator(More, pattern); ok { - return value > operatorVal - } - - if operatorVal, ok := parseOperator(Less, pattern); ok { - return value < operatorVal - } - - if operatorVal, ok := parseOperator(NotEqual, pattern); ok { - return value != operatorVal - } - - fmt.Printf("Validating error: unknown operator: %s\n", pattern) - return false -} - -func parseOperator(operator Operator, pattern string) (float64, bool) { - if pattern[:len(operator)] == string(operator) { - if value, err := strconv.ParseFloat(pattern[len(operator):len(pattern)], 64); err == nil { - return value, true - } - } - - return 0.0, false -} - func wrappedWithParentheses(str string) bool { if len(str) < 2 { return false diff --git a/pkg/engine/validation_test.go b/pkg/engine/validation_test.go index e2c9f54454..78160fae36 100644 --- a/pkg/engine/validation_test.go +++ b/pkg/engine/validation_test.go @@ -86,170 +86,6 @@ func TestCheckForWildcard_QuestionMark(t *testing.T) { assert.Assert(t, checkForWildcard(value, pattern) != nil) } -func TestCheckSingleValue_CheckInt(t *testing.T) { - pattern := 89 - value := 89 - assert.NilError(t, checkSingleValue(value, pattern)) - - value = 202 - assert.Assert(t, checkSingleValue(value, pattern) != nil) -} - -func TestCheckSingleValue_CheckFloat(t *testing.T) { - pattern := 89.9091 - value := 89.9091 - assert.NilError(t, checkSingleValue(value, pattern)) - - value = 89.9092 - assert.Assert(t, checkSingleValue(value, pattern) != nil) -} - -func TestCheckSingleValue_CheckOperatorMoreEqual(t *testing.T) { - pattern := " >= 89 " - value := 89 - assert.NilError(t, checkSingleValue(value, pattern)) - - pattern = ">=10.0001" - floatValue := 89.901 - assert.NilError(t, checkSingleValue(floatValue, pattern)) -} - -func TestCheckSingleValue_CheckOperatorMoreEqualFail(t *testing.T) { - pattern := " >= 90 " - value := 89 - assert.Assert(t, checkSingleValue(value, pattern) != nil) - - pattern = ">=910.0001" - floatValue := 89.901 - assert.Assert(t, checkSingleValue(floatValue, pattern) != nil) -} - -func TestCheckSingleValue_CheckOperatorLessEqual(t *testing.T) { - pattern := " <= 1 " - value := 1 - assert.NilError(t, checkSingleValue(value, pattern)) - - pattern = "<=10.0001" - floatValue := 1.901 - assert.NilError(t, checkSingleValue(floatValue, pattern)) -} - -func TestCheckSingleValue_CheckOperatorLessEqualFail(t *testing.T) { - pattern := " <= 0.1558 " - value := 1 - assert.Assert(t, checkSingleValue(value, pattern) != nil) - - pattern = "<=10.0001" - floatValue := 12.901 - assert.Assert(t, checkSingleValue(floatValue, pattern) != nil) -} - -func TestCheckSingleValue_CheckOperatorMore(t *testing.T) { - pattern := " > 10 " - value := 89 - assert.NilError(t, checkSingleValue(value, pattern)) - - pattern = ">10.0001" - floatValue := 89.901 - assert.NilError(t, checkSingleValue(floatValue, pattern)) -} - -func TestCheckSingleValue_CheckOperatorMoreFail(t *testing.T) { - pattern := " > 89 " - value := 89 - assert.Assert(t, checkSingleValue(value, pattern) != nil) - - pattern = ">910.0001" - floatValue := 89.901 - assert.Assert(t, checkSingleValue(floatValue, pattern) != nil) -} - -func TestCheckSingleValue_CheckOperatorLess(t *testing.T) { - pattern := " < 10 " - value := 9 - assert.NilError(t, checkSingleValue(value, pattern)) - - pattern = "<10.0001" - floatValue := 9.901 - assert.NilError(t, checkSingleValue(floatValue, pattern)) -} - -func TestCheckSingleValue_CheckOperatorLessFail(t *testing.T) { - pattern := " < 10 " - value := 10 - assert.Assert(t, checkSingleValue(value, pattern) != nil) - - pattern = "<10.0001" - floatValue := 19.901 - assert.Assert(t, checkSingleValue(floatValue, pattern) != nil) -} - -func TestCheckSingleValue_CheckOperatorNotEqual(t *testing.T) { - pattern := " != 10 " - value := 9.99999 - assert.NilError(t, checkSingleValue(value, pattern)) - - pattern = "!=10.0001" - floatValue := 10.0000 - assert.NilError(t, checkSingleValue(floatValue, pattern)) -} - -func TestCheckSingleValue_CheckOperatorNotEqualFail(t *testing.T) { - pattern := " != 9.99999 " - value := 9.99999 - assert.Assert(t, checkSingleValue(value, pattern) != nil) - - pattern = "!=10" - floatValue := 10 - assert.Assert(t, checkSingleValue(floatValue, pattern) != nil) -} - -func TestCheckSingleValue_CheckOperatorEqual(t *testing.T) { - pattern := " 10.000001 " - value := 10.000001 - assert.NilError(t, checkSingleValue(value, pattern)) - - pattern = "10.000000" - floatValue := 10 - assert.NilError(t, checkSingleValue(floatValue, pattern)) -} - -func TestCheckSingleValue_CheckOperatorEqualFail(t *testing.T) { - pattern := " 10.000000 " - value := 10.000001 - assert.Assert(t, checkSingleValue(value, pattern) != nil) - - pattern = "10.000001" - floatValue := 10 - assert.Assert(t, checkSingleValue(floatValue, pattern) != nil) -} - -func TestCheckSingleValue_CheckSeveralOperators(t *testing.T) { - pattern := " <-1 | 10.000001 " - value := 10.000001 - assert.NilError(t, checkSingleValue(value, pattern)) - - value = -30 - assert.NilError(t, checkSingleValue(value, pattern)) - - value = 5 - assert.Assert(t, checkSingleValue(value, pattern) != nil) -} - -func TestCheckSingleValue_CheckWildcard(t *testing.T) { - pattern := "nirmata_*" - value := "nirmata_awesome" - assert.NilError(t, checkSingleValue(value, pattern)) - - pattern = "nirmata_*" - value = "spasex_awesome" - assert.Assert(t, checkSingleValue(value, pattern) != nil) - - pattern = "g?t" - value = "git" - assert.NilError(t, checkSingleValue(value, pattern)) -} - func TestSkipArrayObject_OneAnchor(t *testing.T) { rawAnchors := []byte(`{"(name)": "nirmata-*"}`) From a85f646bd4d14aab50796fdf1d6adca6ba3f2de9 Mon Sep 17 00:00:00 2001 From: Denis Belyshev Date: Mon, 27 May 2019 18:11:39 +0300 Subject: [PATCH 3/8] Removed excess Fatal log --- pkg/engine/overlay.go | 2 +- pkg/engine/overlay_test.go | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/engine/overlay.go b/pkg/engine/overlay.go index 0199c9b63d..a69df58b7c 100644 --- a/pkg/engine/overlay.go +++ b/pkg/engine/overlay.go @@ -66,7 +66,7 @@ func applyOverlay(resource, overlay interface{}, path string) ([]PatchBytes, err for key, value := range typedOverlay { if wrappedWithParentheses(key) { - key = key[1 : len(key)-1] + continue } currentPath := path + key + "/" resourcePart, ok := typedResource[key] diff --git a/pkg/engine/overlay_test.go b/pkg/engine/overlay_test.go index 76e8cc5cae..f0d04f5e58 100644 --- a/pkg/engine/overlay_test.go +++ b/pkg/engine/overlay_test.go @@ -2,7 +2,6 @@ package engine import ( "encoding/json" - "log" "testing" jsonpatch "github.com/evanphx/json-patch" @@ -220,7 +219,6 @@ func TestApplyOverlay_TestInsertToArray(t *testing.T) { assert.Assert(t, decoded != nil) patched, err := decoded.Apply(resourceRaw) - log.Fatalf("%s", patched) assert.NilError(t, err) assert.Assert(t, patched != nil) } From 22b942d229d8741a21c590bf9d7b5d6b01996c43 Mon Sep 17 00:00:00 2001 From: Denis Belyshev Date: Mon, 27 May 2019 18:21:23 +0300 Subject: [PATCH 4/8] Added bool support --- pkg/engine/overlay.go | 4 +++- pkg/engine/validation.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/engine/overlay.go b/pkg/engine/overlay.go index a69df58b7c..a5b5042b86 100644 --- a/pkg/engine/overlay.go +++ b/pkg/engine/overlay.go @@ -96,7 +96,7 @@ func applyOverlay(resource, overlay interface{}, path string) ([]PatchBytes, err } appliedPatches = append(appliedPatches, patches...) - case string, float64, int64: + case string, float64, int64, bool: patch, err := replaceSubtree(overlay, path) if err != nil { return nil, err @@ -324,6 +324,8 @@ func prepareJSONValue(overlay interface{}) string { return fmt.Sprintf("%f", typed) case int64: return fmt.Sprintf("%d", typed) + case bool: + return fmt.Sprintf("%t", typed) default: return "" } diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index 5809c4d31d..ab288c467c 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -124,7 +124,7 @@ func validateMapElement(resourcePart, patternPart interface{}) error { } return validateArray(array, pattern) - case string, float64, int, int64: + case string, float64, int, int64, bool: if !ValidateValueWithPattern(resourcePart, patternPart) { return fmt.Errorf("Failed validate %v wit %v", resourcePart, patternPart) } From b42f517093eae57e1decd5b7b9c212767dfbe965 Mon Sep 17 00:00:00 2001 From: Denis Belyshev Date: Tue, 28 May 2019 13:13:57 +0300 Subject: [PATCH 5/8] Fixed crash when checking operator on strings lesser than 2 chars --- pkg/engine/pattern.go | 6 +++++- pkg/engine/pattern_test.go | 12 ++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/pkg/engine/pattern.go b/pkg/engine/pattern.go index 56929a692d..c7d20ba650 100644 --- a/pkg/engine/pattern.go +++ b/pkg/engine/pattern.go @@ -240,6 +240,10 @@ func validateNumber(value, pattern interface{}, operator Operator) bool { } func getOperatorFromStringPattern(pattern string) Operator { + if len(pattern) < 2 { + return Equal + } + if pattern[:len(MoreEqual)] == string(MoreEqual) { return MoreEqual } @@ -260,7 +264,7 @@ func getOperatorFromStringPattern(pattern string) Operator { return NotEqual } - return "" + return Equal } func getNumberAndStringPartsFromPattern(pattern string) (number, str string) { diff --git a/pkg/engine/pattern_test.go b/pkg/engine/pattern_test.go index b61c0d2a4e..2ce21ddde2 100644 --- a/pkg/engine/pattern_test.go +++ b/pkg/engine/pattern_test.go @@ -269,3 +269,15 @@ func TestValidateNumberWithStr_MoreAndWildCard(t *testing.T) { 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) +} From cde2f2b0a027c143387b3d88a9da10a7d075613c Mon Sep 17 00:00:00 2001 From: Denis Belyshev Date: Tue, 28 May 2019 14:07:15 +0300 Subject: [PATCH 6/8] Fixed issue: string patterns always return true --- pkg/engine/pattern.go | 4 +--- pkg/engine/validation_test.go | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/pkg/engine/pattern.go b/pkg/engine/pattern.go index c7d20ba650..18c45b8d9c 100644 --- a/pkg/engine/pattern.go +++ b/pkg/engine/pattern.go @@ -140,9 +140,7 @@ func validateValueWithStringPattern(value interface{}, pattern string) bool { return validateString(value, str, operator) } - validateNumberWithStr(value, number, str, operator) - - return true + return validateNumberWithStr(value, number, str, operator) } func validateString(value interface{}, pattern string, operator Operator) bool { diff --git a/pkg/engine/validation_test.go b/pkg/engine/validation_test.go index 78160fae36..d2d3c2071b 100644 --- a/pkg/engine/validation_test.go +++ b/pkg/engine/validation_test.go @@ -158,6 +158,17 @@ func TestGetAnchorsFromMap_ThereAreNoAnchors(t *testing.T) { assert.Assert(t, len(actualMap) == 0) } +func TestValidateMap(t *testing.T) { + rawPattern := []byte(`{ "spec": { "template": { "spec": { "containers": [ { "name": "?*", "resources": { "requests": { "cpu": "5" } } } ] } } } }`) + rawMap := []byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "name": "nginx-deployment", "labels": { "app": "nginx" } }, "spec": { "replicas": 3, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "labels": { "app": "nginx" } }, "spec": { "securityContext": { "runAsNonRoot": true }, "containers": [ { "name": "nginx", "image": "https://nirmata/nginx:latest", "imagePullPolicy": "Always", "readinessProbe": { "exec": { "command": [ "cat", "/tmp/healthy" ] }, "initialDelaySeconds": 5, "periodSeconds": 10 }, "livenessProbe": { "tcpSocket": { "port": 8080 }, "initialDelaySeconds": 15, "periodSeconds": 11 }, "resources": { "limits": { "memory": "2Gi", "cpu": 8 }, "requests": { "memory": "512Mi", "cpu": 6 } }, "ports": [ { "containerPort": 80 } ] } ] } } } }`) + + var pattern, resource interface{} + json.Unmarshal(rawPattern, &pattern) + json.Unmarshal(rawMap, &resource) + + assert.NilError(t, validateMap(resource, pattern)) +} + func TestValidateMapElement_TwoElementsInArrayOnePass(t *testing.T) { rawPattern := []byte(`[ { "(name)": "nirmata-*", "object": [ { "(key1)": "value*", "key2": "value*" } ] } ]`) rawMap := []byte(`[ { "name": "nirmata-1", "object": [ { "key1": "value1", "key2": "value2" } ] }, { "name": "nirmata-1", "object": [ { "key1": "not_value", "key2": "not_value" } ] } ]`) @@ -192,7 +203,7 @@ func TestValidateMapElement_OneElementInArrayNotPass(t *testing.T) { } func TestValidate_ServiceTest(t *testing.T) { - rawPolicy := []byte(`{ "apiVersion": "kyverno.nirmata.io/v1alpha1", "kind": "Policy", "metadata": { "name": "policy-service" }, "spec": { "rules": [ { "name": "ps1", "resource": { "kind": "Service", "name": "game-service*" }, "mutate": { "patches": [ { "path": "/metadata/labels/isMutated", "op": "add", "value": "true" }, { "path": "/metadata/labels/secretLabel", "op": "replace", "value": "weKnow" }, { "path": "/metadata/labels/originalLabel", "op": "remove" }, { "path": "/spec/selector/app", "op": "replace", "value": "mutedApp" } ] }, "validate": { "message": "This resource is broken", "pattern": { "spec": { "ports": [ { "name": "hs", "protocol": 32 } ] } } } } ] } }`) + rawPolicy := []byte(`{ "apiVersion": "kyverno.nirmata.io/v1alpha1", "kind": "Policy", "metadata": { "name": "policy-service" }, "spec": { "rules": [ { "name": "ps1", "resource": { "kinds": [ "Service" ], "name": "game-service*" }, "mutate": { "patches": [ { "path": "/metadata/labels/isMutated", "op": "add", "value": "true" }, { "path": "/metadata/labels/secretLabel", "op": "replace", "value": "weKnow" }, { "path": "/metadata/labels/originalLabel", "op": "remove" }, { "path": "/spec/selector/app", "op": "replace", "value": "mutedApp" } ] }, "validate": { "message": "This resource is broken", "pattern": { "spec": { "ports": [ { "name": "hs", "protocol": 32 } ] } } } } ] } }`) rawResource := []byte(`{ "kind": "Service", "apiVersion": "v1", "metadata": { "name": "game-service", "labels": { "originalLabel": "isHere", "secretLabel": "thisIsMySecret" } }, "spec": { "selector": { "app": "MyApp" }, "ports": [ { "name": "http", "protocol": "TCP", "port": 80, "targetPort": 9376 } ] } }`) var policy kubepolicy.Policy @@ -206,7 +217,7 @@ func TestValidate_ServiceTest(t *testing.T) { } func TestValidate_MapHasFloats(t *testing.T) { - rawPolicy := []byte(`{ "apiVersion": "kyverno.nirmata.io/v1alpha1", "kind": "Policy", "metadata": { "name": "policy-deployment-changed" }, "spec": { "rules": [ { "name": "First policy v2", "resource": { "kind": "Deployment", "name": "nginx-*" }, "mutate": { "patches": [ { "path": "/metadata/labels/isMutated", "op": "add", "value": "true" }, { "path": "/metadata/labels/app", "op": "replace", "value": "nginx_is_mutated" } ] }, "validate": { "message": "replicas number is wrong", "pattern": { "metadata": { "labels": { "app": "*" } }, "spec": { "replicas": 3 } } } } ] } }`) + rawPolicy := []byte(`{ "apiVersion": "kyverno.nirmata.io/v1alpha1", "kind": "Policy", "metadata": { "name": "policy-deployment-changed" }, "spec": { "rules": [ { "name": "First policy v2", "resource": { "kinds": [ "Deployment" ], "name": "nginx-*" }, "mutate": { "patches": [ { "path": "/metadata/labels/isMutated", "op": "add", "value": "true" }, { "path": "/metadata/labels/app", "op": "replace", "value": "nginx_is_mutated" } ] }, "validate": { "message": "replicas number is wrong", "pattern": { "metadata": { "labels": { "app": "*" } }, "spec": { "replicas": 3 } } } } ] } }`) rawResource := []byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "name": "nginx-deployment", "labels": { "app": "nginx" } }, "spec": { "replicas": 3, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "labels": { "app": "nginx" } }, "spec": { "containers": [ { "name": "nginx", "image": "nginx:1.7.9", "ports": [ { "containerPort": 80 } ] } ] } } } }`) var policy kubepolicy.Policy From f57422ef7988469ef232332f74f1f8a4b2cd22d9 Mon Sep 17 00:00:00 2001 From: Denis Belyshev Date: Tue, 28 May 2019 14:25:29 +0300 Subject: [PATCH 7/8] Fixed issue when number comes from resource as string and does not being validated --- pkg/engine/pattern.go | 21 ++++++++++++++------- pkg/engine/validation.go | 4 ++-- pkg/engine/validation_test.go | 4 ++-- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/pkg/engine/pattern.go b/pkg/engine/pattern.go index 18c45b8d9c..77ff51ffbd 100644 --- a/pkg/engine/pattern.go +++ b/pkg/engine/pattern.go @@ -165,11 +165,6 @@ func validateString(value interface{}, pattern string, operator Operator) bool { } func validateNumberWithStr(value interface{}, patternNumber, patternStr string, operator Operator) bool { - patternParsedNumber, err := parseNumber(patternNumber) - if err != nil { - return false - } - if "" != patternStr { typedValue, ok := value.(string) if !ok { @@ -188,16 +183,22 @@ func validateNumberWithStr(value interface{}, patternNumber, patternStr string, return false } - return validateNumber(valueParsedNumber, patternParsedNumber, operator) + return validateNumber(valueParsedNumber, patternNumber, operator) } - return validateNumber(value, patternParsedNumber, operator) + return validateNumber(value, patternNumber, operator) } func validateNumber(value, pattern interface{}, operator Operator) bool { var floatPattern, floatValue float64 switch typed := value.(type) { + case string: + var err error + floatValue, err = strconv.ParseFloat(typed, 64) + if err != nil { + return false + } case float64: floatValue = typed case int64: @@ -209,6 +210,12 @@ func validateNumber(value, pattern interface{}, operator Operator) bool { } switch typed := pattern.(type) { + case string: + var err error + floatPattern, err = strconv.ParseFloat(typed, 64) + if err != nil { + return false + } case float64: floatPattern = typed case int64: diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index ab288c467c..0ab84c7978 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -100,7 +100,7 @@ func validateArray(resourcePart, patternPart interface{}) error { default: for _, value := range resourceArray { if !ValidateValueWithPattern(value, patternArray[0]) { - return fmt.Errorf("Failed validate %v wit %v", value, patternArray[0]) + return fmt.Errorf("Failed validate %v with %v", value, patternArray[0]) } } } @@ -126,7 +126,7 @@ func validateMapElement(resourcePart, patternPart interface{}) error { return validateArray(array, pattern) case string, float64, int, int64, bool: if !ValidateValueWithPattern(resourcePart, patternPart) { - return fmt.Errorf("Failed validate %v wit %v", resourcePart, patternPart) + return fmt.Errorf("Failed validate %v with %v", resourcePart, patternPart) } default: return fmt.Errorf("validating error: unknown type in map: %T", patternPart) diff --git a/pkg/engine/validation_test.go b/pkg/engine/validation_test.go index d2d3c2071b..09a8bde7cd 100644 --- a/pkg/engine/validation_test.go +++ b/pkg/engine/validation_test.go @@ -159,8 +159,8 @@ func TestGetAnchorsFromMap_ThereAreNoAnchors(t *testing.T) { } func TestValidateMap(t *testing.T) { - rawPattern := []byte(`{ "spec": { "template": { "spec": { "containers": [ { "name": "?*", "resources": { "requests": { "cpu": "5" } } } ] } } } }`) - rawMap := []byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "name": "nginx-deployment", "labels": { "app": "nginx" } }, "spec": { "replicas": 3, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "labels": { "app": "nginx" } }, "spec": { "securityContext": { "runAsNonRoot": true }, "containers": [ { "name": "nginx", "image": "https://nirmata/nginx:latest", "imagePullPolicy": "Always", "readinessProbe": { "exec": { "command": [ "cat", "/tmp/healthy" ] }, "initialDelaySeconds": 5, "periodSeconds": 10 }, "livenessProbe": { "tcpSocket": { "port": 8080 }, "initialDelaySeconds": 15, "periodSeconds": 11 }, "resources": { "limits": { "memory": "2Gi", "cpu": 8 }, "requests": { "memory": "512Mi", "cpu": 6 } }, "ports": [ { "containerPort": 80 } ] } ] } } } }`) + rawPattern := []byte(`{ "spec": { "template": { "spec": { "containers": [ { "name": "?*", "resources": { "requests": { "cpu": "<4|8" } } } ] } } } }`) + rawMap := []byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "name": "nginx-deployment", "labels": { "app": "nginx" } }, "spec": { "replicas": 3, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "labels": { "app": "nginx" } }, "spec": { "securityContext": { "runAsNonRoot": true }, "containers": [ { "name": "nginx", "image": "https://nirmata/nginx:latest", "imagePullPolicy": "Always", "readinessProbe": { "exec": { "command": [ "cat", "/tmp/healthy" ] }, "initialDelaySeconds": 5, "periodSeconds": 10 }, "livenessProbe": { "tcpSocket": { "port": 8080 }, "initialDelaySeconds": 15, "periodSeconds": 11 }, "resources": { "limits": { "memory": "2Gi", "cpu": 8 }, "requests": { "memory": "512Mi", "cpu": "8" } }, "ports": [ { "containerPort": 80 } ] } ] } } } }`) var pattern, resource interface{} json.Unmarshal(rawPattern, &pattern) From 2a24f8dc3b83f7546ed9231366d68c500a680336 Mon Sep 17 00:00:00 2001 From: Denis Belyshev Date: Tue, 28 May 2019 20:30:48 +0100 Subject: [PATCH 8/8] Fixed issue: null corresponds for non-existing field, 0 for ints and empty quotes for strings --- pkg/engine/validation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index 0ab84c7978..3c107e4070 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -124,7 +124,7 @@ func validateMapElement(resourcePart, patternPart interface{}) error { } return validateArray(array, pattern) - case string, float64, int, int64, bool: + case string, float64, int, int64, bool, nil: if !ValidateValueWithPattern(resourcePart, patternPart) { return fmt.Errorf("Failed validate %v with %v", resourcePart, patternPart) }