mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-15 17:51:20 +00:00
refactor: engine pattern package and add duration support (#5958)
* refactor: engine pattern package and add duration support Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * fix Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * logger Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
parent
68fb237d25
commit
4475a0adca
9 changed files with 697 additions and 763 deletions
|
@ -6,7 +6,7 @@ import (
|
|||
"strconv"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/kyverno/kyverno/pkg/engine/common"
|
||||
"github.com/kyverno/kyverno/pkg/engine/pattern"
|
||||
"github.com/kyverno/kyverno/pkg/engine/wildcards"
|
||||
"github.com/kyverno/kyverno/pkg/logging"
|
||||
)
|
||||
|
@ -54,7 +54,7 @@ func validateResourceElement(log logr.Logger, resourceElement, patternElement, o
|
|||
return validateArray(log, typedResourceElement, typedPatternElement, originPattern, path)
|
||||
// elementary values
|
||||
case string, float64, int, int64, bool, nil:
|
||||
if !common.ValidateValueWithPattern(log, resourceElement, patternElement) {
|
||||
if !pattern.Validate(log, resourceElement, patternElement) {
|
||||
return path, fmt.Errorf("value '%v' does not match '%v' at path %s", resourceElement, patternElement, path)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// convertNumberToString converts value to string
|
||||
func convertNumberToString(value interface{}) (string, error) {
|
||||
if value == nil {
|
||||
return "0", nil
|
||||
}
|
||||
switch typed := value.(type) {
|
||||
case string:
|
||||
return typed, nil
|
||||
case float64:
|
||||
return fmt.Sprintf("%f", typed), nil
|
||||
case int64:
|
||||
return strconv.FormatInt(typed, 10), nil
|
||||
case int:
|
||||
return strconv.Itoa(typed), nil
|
||||
case nil:
|
||||
return "", fmt.Errorf("got empty string, expect %v", value)
|
||||
default:
|
||||
return "", fmt.Errorf("could not convert %v to string", typed)
|
||||
}
|
||||
}
|
|
@ -1,323 +0,0 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/kyverno/kyverno/pkg/engine/operator"
|
||||
wildcard "github.com/kyverno/kyverno/pkg/utils/wildcard"
|
||||
apiresource "k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
type quantity int
|
||||
|
||||
const (
|
||||
equal quantity = 0
|
||||
lessThan quantity = -1
|
||||
greaterThan quantity = 1
|
||||
)
|
||||
|
||||
// ValidateValueWithPattern validates value with operators and wildcards
|
||||
func ValidateValueWithPattern(log logr.Logger, value, pattern interface{}) bool {
|
||||
switch typedPattern := pattern.(type) {
|
||||
case bool:
|
||||
typedValue, ok := value.(bool)
|
||||
if !ok {
|
||||
log.V(4).Info("Expected type bool", "type", fmt.Sprintf("%T", value), "value", value)
|
||||
return false
|
||||
}
|
||||
return typedPattern == typedValue
|
||||
case int:
|
||||
return validateValueWithIntPattern(log, value, int64(typedPattern))
|
||||
case int64:
|
||||
return validateValueWithIntPattern(log, value, typedPattern)
|
||||
case float64:
|
||||
return validateValueWithFloatPattern(log, value, typedPattern)
|
||||
case string:
|
||||
return validateValueWithStringPatterns(log, value, typedPattern)
|
||||
case nil:
|
||||
return validateValueWithNilPattern(log, value)
|
||||
case map[string]interface{}:
|
||||
return validateValueWithMapPattern(log, value, typedPattern)
|
||||
case []interface{}:
|
||||
log.V(2).Info("arrays are not supported as patterns")
|
||||
return false
|
||||
default:
|
||||
log.V(2).Info("Unknown type", "type", fmt.Sprintf("%T", typedPattern), "value", typedPattern)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func validateValueWithMapPattern(log logr.Logger, value interface{}, typedPattern map[string]interface{}) bool {
|
||||
// verify the type of the resource value is map[string]interface,
|
||||
// we only check for existence of object, not the equality of content and value
|
||||
_, ok := value.(map[string]interface{})
|
||||
if !ok {
|
||||
log.V(2).Info("Expected type map[string]interface{}", "type", fmt.Sprintf("%T", value), "value", value)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Handler for int values during validation process
|
||||
func validateValueWithIntPattern(log logr.Logger, 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 int64(typedValue) == pattern
|
||||
}
|
||||
|
||||
log.V(2).Info("Expected type int", "type", fmt.Sprintf("%T", typedValue), "value", typedValue)
|
||||
return false
|
||||
case string:
|
||||
// extract int64 from string
|
||||
int64Num, err := strconv.ParseInt(typedValue, 10, 64)
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to parse int64 from string")
|
||||
return false
|
||||
}
|
||||
return int64Num == pattern
|
||||
default:
|
||||
log.V(2).Info("Expected type int", "type", fmt.Sprintf("%T", value), "value", value)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for float values during validation process
|
||||
func validateValueWithFloatPattern(log logr.Logger, 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.V(2).Info("Expected type float", "type", fmt.Sprintf("%T", typedValue), "value", typedValue)
|
||||
return false
|
||||
case int64:
|
||||
// check that float has no fraction
|
||||
if pattern == math.Trunc(pattern) {
|
||||
return int64(pattern) == value
|
||||
}
|
||||
log.V(2).Info("Expected type float", "type", fmt.Sprintf("%T", typedValue), "value", typedValue)
|
||||
return false
|
||||
case float64:
|
||||
return typedValue == pattern
|
||||
case string:
|
||||
// extract float64 from string
|
||||
float64Num, err := strconv.ParseFloat(typedValue, 64)
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to parse float64 from string")
|
||||
return false
|
||||
}
|
||||
return float64Num == pattern
|
||||
default:
|
||||
log.V(2).Info("Expected type float", "type", fmt.Sprintf("%T", value), "value", value)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for nil values during validation process
|
||||
func validateValueWithNilPattern(log logr.Logger, value interface{}) bool {
|
||||
switch typed := value.(type) {
|
||||
case float64:
|
||||
return typed == 0.0
|
||||
case int:
|
||||
return typed == 0
|
||||
case int64:
|
||||
return typed == 0
|
||||
case string:
|
||||
return typed == ""
|
||||
case bool:
|
||||
return !typed
|
||||
case nil:
|
||||
return true
|
||||
case map[string]interface{}, []interface{}:
|
||||
log.V(2).Info("Maps and arrays could not be checked with nil pattern")
|
||||
return false
|
||||
default:
|
||||
log.V(2).Info("Unknown type as value when checking for nil pattern", "type", fmt.Sprintf("%T", value), "value", value)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for pattern values during validation process
|
||||
func validateValueWithStringPatterns(log logr.Logger, value interface{}, pattern string) bool {
|
||||
if value == pattern {
|
||||
return true
|
||||
}
|
||||
|
||||
conditions := strings.Split(pattern, "|")
|
||||
for _, condition := range conditions {
|
||||
condition = strings.Trim(condition, " ")
|
||||
if checkForAndConditionsAndValidate(log, value, condition) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func checkForAndConditionsAndValidate(log logr.Logger, value interface{}, pattern string) bool {
|
||||
conditions := strings.Split(pattern, "&")
|
||||
for _, condition := range conditions {
|
||||
condition = strings.Trim(condition, " ")
|
||||
if !validateValueWithStringPattern(log, value, condition) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Handler for single pattern value during validation process
|
||||
// Detects if pattern has a number
|
||||
func validateValueWithStringPattern(log logr.Logger, value interface{}, pattern string) bool {
|
||||
operatorVariable := operator.GetOperatorFromStringPattern(pattern)
|
||||
|
||||
// Upon encountering InRange operator split the string by `-` and basically
|
||||
// verify the result of (x >= leftEndpoint & x <= rightEndpoint)
|
||||
if operatorVariable == operator.InRange {
|
||||
endpoints := strings.Split(pattern, "-")
|
||||
leftEndpoint, rightEndpoint := endpoints[0], endpoints[1]
|
||||
|
||||
gt := validateValueWithStringPattern(log, value, fmt.Sprintf(">=%s", leftEndpoint))
|
||||
if !gt {
|
||||
return false
|
||||
}
|
||||
pattern = fmt.Sprintf("<=%s", rightEndpoint)
|
||||
operatorVariable = operator.LessEqual
|
||||
}
|
||||
|
||||
// Upon encountering NotInRange operator split the string by `!-` and basically
|
||||
// verify the result of (x < leftEndpoint | x > rightEndpoint)
|
||||
if operatorVariable == operator.NotInRange {
|
||||
endpoints := strings.Split(pattern, "!-")
|
||||
leftEndpoint, rightEndpoint := endpoints[0], endpoints[1]
|
||||
|
||||
lt := validateValueWithStringPattern(log, value, fmt.Sprintf("<%s", leftEndpoint))
|
||||
if lt {
|
||||
return true
|
||||
}
|
||||
pattern = fmt.Sprintf(">%s", rightEndpoint)
|
||||
operatorVariable = operator.More
|
||||
}
|
||||
|
||||
pattern = pattern[len(operatorVariable):]
|
||||
pattern = strings.TrimSpace(pattern)
|
||||
number, str := getNumberAndStringPartsFromPattern(pattern)
|
||||
|
||||
if number == "" {
|
||||
return validateString(log, value, str, operatorVariable)
|
||||
}
|
||||
|
||||
return validateNumberWithStr(log, value, pattern, operatorVariable)
|
||||
}
|
||||
|
||||
// Handler for string values
|
||||
func validateString(log logr.Logger, value interface{}, pattern string, operatorVariable operator.Operator) bool {
|
||||
if operator.NotEqual == operatorVariable || operator.Equal == operatorVariable {
|
||||
var strValue string
|
||||
var ok bool = false
|
||||
switch v := value.(type) {
|
||||
case float64:
|
||||
strValue = strconv.FormatFloat(v, 'E', -1, 64)
|
||||
ok = true
|
||||
case int:
|
||||
strValue = strconv.FormatInt(int64(v), 10)
|
||||
ok = true
|
||||
case int64:
|
||||
strValue = strconv.FormatInt(v, 10)
|
||||
ok = true
|
||||
case string:
|
||||
strValue = v
|
||||
ok = true
|
||||
case bool:
|
||||
strValue = strconv.FormatBool(v)
|
||||
ok = true
|
||||
case nil:
|
||||
ok = false
|
||||
}
|
||||
if !ok {
|
||||
log.V(4).Info("unexpected type", "got", value, "expect", pattern)
|
||||
return false
|
||||
}
|
||||
|
||||
wildcardResult := wildcard.Match(pattern, strValue)
|
||||
|
||||
if operator.NotEqual == operatorVariable {
|
||||
return !wildcardResult
|
||||
}
|
||||
|
||||
return wildcardResult
|
||||
}
|
||||
log.V(2).Info("Operators >, >=, <, <= are not applicable to strings")
|
||||
return false
|
||||
}
|
||||
|
||||
// validateNumberWithStr compares quantity if pattern type is quantity
|
||||
// or a wildcard match to pattern string
|
||||
func validateNumberWithStr(log logr.Logger, value interface{}, pattern string, operator operator.Operator) bool {
|
||||
typedValue, err := convertNumberToString(value)
|
||||
if err != nil {
|
||||
log.Error(err, "failed to convert to string")
|
||||
return false
|
||||
}
|
||||
|
||||
patternQuan, err := apiresource.ParseQuantity(pattern)
|
||||
// 1. nil error - quantity comparison
|
||||
if err == nil {
|
||||
valueQuan, err := apiresource.ParseQuantity(typedValue)
|
||||
if err != nil {
|
||||
log.Error(err, "invalid quantity in resource", "type", fmt.Sprintf("%T", typedValue), "value", typedValue)
|
||||
return false
|
||||
}
|
||||
|
||||
return compareQuantity(valueQuan, patternQuan, operator)
|
||||
}
|
||||
|
||||
// 2. wildcard match
|
||||
if validateString(log, value, pattern, operator) {
|
||||
return true
|
||||
} else {
|
||||
log.V(4).Info("value failed wildcard check", "type", fmt.Sprintf("%T", typedValue), "value", typedValue, "check", pattern)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func compareQuantity(value, pattern apiresource.Quantity, op operator.Operator) bool {
|
||||
result := value.Cmp(pattern)
|
||||
switch op {
|
||||
case operator.Equal:
|
||||
return result == int(equal)
|
||||
case operator.NotEqual:
|
||||
return result != int(equal)
|
||||
case operator.More:
|
||||
return result == int(greaterThan)
|
||||
case operator.Less:
|
||||
return result == int(lessThan)
|
||||
case operator.MoreEqual:
|
||||
return (result == int(equal)) || (result == int(greaterThan))
|
||||
case operator.LessEqual:
|
||||
return (result == int(equal)) || (result == int(lessThan))
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// detects numerical and string parts in pattern and returns them
|
||||
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]
|
||||
}
|
|
@ -1,395 +0,0 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/kyverno/kyverno/pkg/engine/operator"
|
||||
"github.com/kyverno/kyverno/pkg/logging"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
func TestValidateValueWithPattern_Bool(t *testing.T) {
|
||||
assert.Assert(t, ValidateValueWithPattern(logging.GlobalLogger(), true, true))
|
||||
assert.Assert(t, !ValidateValueWithPattern(logging.GlobalLogger(), true, false))
|
||||
assert.Assert(t, !ValidateValueWithPattern(logging.GlobalLogger(), false, true))
|
||||
assert.Assert(t, ValidateValueWithPattern(logging.GlobalLogger(), false, false))
|
||||
}
|
||||
|
||||
func TestValidateString_AsteriskTest(t *testing.T) {
|
||||
pattern := "*"
|
||||
value := "anything"
|
||||
empty := ""
|
||||
|
||||
assert.Assert(t, validateString(logging.GlobalLogger(), value, pattern, operator.Equal))
|
||||
assert.Assert(t, validateString(logging.GlobalLogger(), empty, pattern, operator.Equal))
|
||||
}
|
||||
|
||||
func TestValidateString_LeftAsteriskTest(t *testing.T) {
|
||||
pattern := "*right"
|
||||
value := "leftright"
|
||||
right := "right"
|
||||
|
||||
assert.Assert(t, validateString(logging.GlobalLogger(), value, pattern, operator.Equal))
|
||||
assert.Assert(t, validateString(logging.GlobalLogger(), right, pattern, operator.Equal))
|
||||
|
||||
value = "leftmiddle"
|
||||
middle := "middle"
|
||||
|
||||
assert.Assert(t, !validateString(logging.GlobalLogger(), value, pattern, operator.Equal))
|
||||
assert.Assert(t, !validateString(logging.GlobalLogger(), middle, pattern, operator.Equal))
|
||||
}
|
||||
|
||||
func TestValidateString_MiddleAsteriskTest(t *testing.T) {
|
||||
pattern := "ab*ba"
|
||||
value := "abbeba"
|
||||
assert.Assert(t, validateString(logging.GlobalLogger(), value, pattern, operator.Equal))
|
||||
|
||||
value = "abbca"
|
||||
assert.Assert(t, !validateString(logging.GlobalLogger(), value, pattern, operator.Equal))
|
||||
}
|
||||
|
||||
func TestValidateString_QuestionMark(t *testing.T) {
|
||||
pattern := "ab?ba"
|
||||
value := "abbba"
|
||||
assert.Assert(t, validateString(logging.GlobalLogger(), value, pattern, operator.Equal))
|
||||
|
||||
value = "abbbba"
|
||||
assert.Assert(t, !validateString(logging.GlobalLogger(), value, pattern, operator.Equal))
|
||||
}
|
||||
|
||||
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(logging.GlobalLogger(), 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(logging.GlobalLogger(), 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(logging.GlobalLogger(), 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(logging.GlobalLogger(), 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(logging.GlobalLogger(), 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(logging.GlobalLogger(), 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(logging.GlobalLogger(), value, pattern))
|
||||
}
|
||||
|
||||
func TestValidateValueWithPattern_StringsLogicalAnd(t *testing.T) {
|
||||
pattern := ">1 & <20"
|
||||
value := "10"
|
||||
assert.Assert(t, ValidateValueWithPattern(logging.GlobalLogger(), value, pattern))
|
||||
}
|
||||
|
||||
func TestValidateValueWithPattern_StringsAllLogicalOperators(t *testing.T) {
|
||||
pattern := ">1 & <20 | >31 & <33"
|
||||
value := "10"
|
||||
assert.Assert(t, ValidateValueWithPattern(logging.GlobalLogger(), value, pattern))
|
||||
value = "32"
|
||||
assert.Assert(t, ValidateValueWithPattern(logging.GlobalLogger(), value, pattern))
|
||||
value = "21"
|
||||
assert.Assert(t, !ValidateValueWithPattern(logging.GlobalLogger(), value, pattern))
|
||||
}
|
||||
|
||||
func TestValidateValueWithPattern_EqualTwoFloats(t *testing.T) {
|
||||
assert.Assert(t, ValidateValueWithPattern(logging.GlobalLogger(), 7.0, 7.000))
|
||||
}
|
||||
|
||||
func TestValidateValueWithNilPattern_NullPatternStringValue(t *testing.T) {
|
||||
assert.Assert(t, !validateValueWithNilPattern(logging.GlobalLogger(), "value"))
|
||||
}
|
||||
|
||||
func TestValidateValueWithNilPattern_NullPatternDefaultString(t *testing.T) {
|
||||
assert.Assert(t, validateValueWithNilPattern(logging.GlobalLogger(), ""))
|
||||
}
|
||||
|
||||
func TestValidateValueWithNilPattern_NullPatternDefaultFloat(t *testing.T) {
|
||||
assert.Assert(t, validateValueWithNilPattern(logging.GlobalLogger(), 0.0))
|
||||
}
|
||||
|
||||
func TestValidateValueWithNilPattern_NullPatternFloat(t *testing.T) {
|
||||
assert.Assert(t, !validateValueWithNilPattern(logging.GlobalLogger(), 0.1))
|
||||
}
|
||||
|
||||
func TestValidateValueWithNilPattern_NullPatternDefaultInt(t *testing.T) {
|
||||
assert.Assert(t, validateValueWithNilPattern(logging.GlobalLogger(), 0))
|
||||
}
|
||||
|
||||
func TestValidateValueWithNilPattern_NullPatternInt(t *testing.T) {
|
||||
assert.Assert(t, !validateValueWithNilPattern(logging.GlobalLogger(), 1))
|
||||
}
|
||||
|
||||
func TestValidateValueWithNilPattern_NullPatternDefaultBool(t *testing.T) {
|
||||
assert.Assert(t, validateValueWithNilPattern(logging.GlobalLogger(), false))
|
||||
}
|
||||
|
||||
func TestValidateValueWithNilPattern_NullPatternTrueBool(t *testing.T) {
|
||||
assert.Assert(t, !validateValueWithNilPattern(logging.GlobalLogger(), true))
|
||||
}
|
||||
|
||||
func TestValidateValueWithFloatPattern_FloatValue(t *testing.T) {
|
||||
assert.Assert(t, validateValueWithFloatPattern(logging.GlobalLogger(), 7.9914, 7.9914))
|
||||
}
|
||||
|
||||
func TestValidateValueWithFloatPattern_FloatValueNotPass(t *testing.T) {
|
||||
assert.Assert(t, !validateValueWithFloatPattern(logging.GlobalLogger(), 7.9914, 7.99141))
|
||||
}
|
||||
|
||||
func TestValidateValueWithFloatPattern_FloatPatternWithoutFractionIntValue(t *testing.T) {
|
||||
assert.Assert(t, validateValueWithFloatPattern(logging.GlobalLogger(), 7, 7.000000))
|
||||
}
|
||||
|
||||
func TestValidateValueWithFloatPattern_FloatPatternWithoutFraction(t *testing.T) {
|
||||
assert.Assert(t, validateValueWithFloatPattern(logging.GlobalLogger(), 7.000000, 7.000000))
|
||||
}
|
||||
|
||||
func TestValidateValueWithIntPattern_FloatValueWithoutFraction(t *testing.T) {
|
||||
assert.Assert(t, validateValueWithFloatPattern(logging.GlobalLogger(), 7.000000, 7))
|
||||
}
|
||||
|
||||
func TestValidateValueWithIntPattern_FloatValueWitFraction(t *testing.T) {
|
||||
assert.Assert(t, !validateValueWithFloatPattern(logging.GlobalLogger(), 7.000001, 7))
|
||||
}
|
||||
|
||||
func TestValidateValueWithIntPattern_NotPass(t *testing.T) {
|
||||
assert.Assert(t, !validateValueWithFloatPattern(logging.GlobalLogger(), 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 TestValidateValueWithStringPattern_WithSpace(t *testing.T) {
|
||||
assert.Assert(t, validateValueWithStringPattern(logging.GlobalLogger(), 4, ">= 3"))
|
||||
}
|
||||
|
||||
func TestValidateValueWithStringPattern_Ranges(t *testing.T) {
|
||||
assert.Assert(t, validateValueWithStringPattern(logging.GlobalLogger(), 0, "0-2"))
|
||||
assert.Assert(t, validateValueWithStringPattern(logging.GlobalLogger(), 1, "0-2"))
|
||||
assert.Assert(t, validateValueWithStringPattern(logging.GlobalLogger(), 2, "0-2"))
|
||||
assert.Assert(t, !validateValueWithStringPattern(logging.GlobalLogger(), 3, "0-2"))
|
||||
|
||||
assert.Assert(t, validateValueWithStringPattern(logging.GlobalLogger(), 0, "10!-20"))
|
||||
assert.Assert(t, !validateValueWithStringPattern(logging.GlobalLogger(), 15, "10!-20"))
|
||||
assert.Assert(t, validateValueWithStringPattern(logging.GlobalLogger(), 25, "10!-20"))
|
||||
|
||||
assert.Assert(t, !validateValueWithStringPattern(logging.GlobalLogger(), 0, "0.00001-2.00001"))
|
||||
assert.Assert(t, validateValueWithStringPattern(logging.GlobalLogger(), 1, "0.00001-2.00001"))
|
||||
assert.Assert(t, validateValueWithStringPattern(logging.GlobalLogger(), 2, "0.00001-2.00001"))
|
||||
assert.Assert(t, !validateValueWithStringPattern(logging.GlobalLogger(), 2.0001, "0.00001-2.00001"))
|
||||
|
||||
assert.Assert(t, validateValueWithStringPattern(logging.GlobalLogger(), 0, "0.00001!-2.00001"))
|
||||
assert.Assert(t, !validateValueWithStringPattern(logging.GlobalLogger(), 1, "0.00001!-2.00001"))
|
||||
assert.Assert(t, !validateValueWithStringPattern(logging.GlobalLogger(), 2, "0.00001!-2.00001"))
|
||||
assert.Assert(t, validateValueWithStringPattern(logging.GlobalLogger(), 2.0001, "0.00001!-2.00001"))
|
||||
|
||||
assert.Assert(t, validateValueWithStringPattern(logging.GlobalLogger(), 2, "2-2"))
|
||||
assert.Assert(t, !validateValueWithStringPattern(logging.GlobalLogger(), 2, "2!-2"))
|
||||
|
||||
assert.Assert(t, validateValueWithStringPattern(logging.GlobalLogger(), 2.99999, "2.99998-3"))
|
||||
assert.Assert(t, validateValueWithStringPattern(logging.GlobalLogger(), 2.99997, "2.99998!-3"))
|
||||
assert.Assert(t, validateValueWithStringPattern(logging.GlobalLogger(), 3.00001, "2.99998!-3"))
|
||||
|
||||
assert.Assert(t, validateValueWithStringPattern(logging.GlobalLogger(), "256Mi", "128Mi-512Mi"))
|
||||
assert.Assert(t, !validateValueWithStringPattern(logging.GlobalLogger(), "1024Mi", "128Mi-512Mi"))
|
||||
assert.Assert(t, !validateValueWithStringPattern(logging.GlobalLogger(), "64Mi", "128Mi-512Mi"))
|
||||
|
||||
assert.Assert(t, !validateValueWithStringPattern(logging.GlobalLogger(), "256Mi", "128Mi!-512Mi"))
|
||||
assert.Assert(t, validateValueWithStringPattern(logging.GlobalLogger(), "1024Mi", "128Mi!-512Mi"))
|
||||
assert.Assert(t, validateValueWithStringPattern(logging.GlobalLogger(), "64Mi", "128Mi!-512Mi"))
|
||||
}
|
||||
|
||||
func TestValidateNumberWithStr_LessFloatAndInt(t *testing.T) {
|
||||
assert.Assert(t, validateNumberWithStr(logging.GlobalLogger(), 7.00001, "7.000001", operator.More))
|
||||
assert.Assert(t, validateNumberWithStr(logging.GlobalLogger(), 7.00001, "7", operator.NotEqual))
|
||||
|
||||
assert.Assert(t, validateNumberWithStr(logging.GlobalLogger(), 7.0000, "7", operator.Equal))
|
||||
assert.Assert(t, !validateNumberWithStr(logging.GlobalLogger(), 6.000000001, "6", operator.Less))
|
||||
}
|
||||
|
||||
func TestValidateQuantity_InvalidQuantity(t *testing.T) {
|
||||
assert.Assert(t, !validateNumberWithStr(logging.GlobalLogger(), "1024Gi", "", operator.Equal))
|
||||
assert.Assert(t, !validateNumberWithStr(logging.GlobalLogger(), "gii", "1024Gi", operator.Equal))
|
||||
}
|
||||
|
||||
func TestValidateQuantity_Equal(t *testing.T) {
|
||||
assert.Assert(t, validateNumberWithStr(logging.GlobalLogger(), "1024Gi", "1024Gi", operator.Equal))
|
||||
assert.Assert(t, validateNumberWithStr(logging.GlobalLogger(), "1024Mi", "1Gi", operator.Equal))
|
||||
assert.Assert(t, validateNumberWithStr(logging.GlobalLogger(), "0.2", "200m", operator.Equal))
|
||||
assert.Assert(t, validateNumberWithStr(logging.GlobalLogger(), "500", "500", operator.Equal))
|
||||
assert.Assert(t, !validateNumberWithStr(logging.GlobalLogger(), "2048", "1024", operator.Equal))
|
||||
assert.Assert(t, validateNumberWithStr(logging.GlobalLogger(), 1024, "1024", operator.Equal))
|
||||
}
|
||||
|
||||
func TestValidateQuantity_Operation(t *testing.T) {
|
||||
assert.Assert(t, validateNumberWithStr(logging.GlobalLogger(), "1Gi", "1000Mi", operator.More))
|
||||
assert.Assert(t, validateNumberWithStr(logging.GlobalLogger(), "1G", "1Gi", operator.Less))
|
||||
assert.Assert(t, validateNumberWithStr(logging.GlobalLogger(), "500m", "0.5", operator.MoreEqual))
|
||||
assert.Assert(t, validateNumberWithStr(logging.GlobalLogger(), "1", "500m", operator.MoreEqual))
|
||||
assert.Assert(t, validateNumberWithStr(logging.GlobalLogger(), "0.5", ".5", operator.LessEqual))
|
||||
assert.Assert(t, validateNumberWithStr(logging.GlobalLogger(), "0.2", ".5", operator.LessEqual))
|
||||
assert.Assert(t, validateNumberWithStr(logging.GlobalLogger(), "0.2", ".5", operator.NotEqual))
|
||||
}
|
||||
|
||||
func TestGetOperatorFromStringPattern_OneChar(t *testing.T) {
|
||||
assert.Equal(t, operator.GetOperatorFromStringPattern("f"), operator.Equal)
|
||||
}
|
||||
|
||||
func TestGetOperatorFromStringPattern_EmptyString(t *testing.T) {
|
||||
assert.Equal(t, operator.GetOperatorFromStringPattern(""), operator.Equal)
|
||||
}
|
||||
|
||||
func TestValidateKernelVersion_NotEquals(t *testing.T) {
|
||||
assert.Assert(t, validateValueWithStringPattern(logging.GlobalLogger(), "5.16.5-arch1-1", "!5.10.84-1"))
|
||||
assert.Assert(t, !validateValueWithStringPattern(logging.GlobalLogger(), "5.10.84-1", "!5.10.84-1"))
|
||||
assert.Assert(t, validateValueWithStringPatterns(logging.GlobalLogger(), "5.16.5-arch1-1", "!5.10.84-1 & !5.15.2-1"))
|
||||
assert.Assert(t, !validateValueWithStringPatterns(logging.GlobalLogger(), "5.10.84-1", "!5.10.84-1 & !5.15.2-1"))
|
||||
assert.Assert(t, !validateValueWithStringPatterns(logging.GlobalLogger(), "5.15.2-1", "!5.10.84-1 & !5.15.2-1"))
|
||||
}
|
|
@ -26,42 +26,31 @@ const (
|
|||
NotInRange Operator = "!-"
|
||||
)
|
||||
|
||||
// ReferenceSign defines the operator for anchor reference
|
||||
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
|
||||
}
|
||||
|
||||
if match, _ := regexp.Match(`^(\d+(\.\d+)?)([^-]*)!-(\d+(\.\d+)?)([^-]*)$`, []byte(pattern)); match {
|
||||
return NotInRange
|
||||
}
|
||||
|
||||
if match, _ := regexp.Match(`^(\d+(\.\d+)?)([^-]*)-(\d+(\.\d+)?)([^-]*)$`, []byte(pattern)); match {
|
||||
return InRange
|
||||
}
|
||||
|
||||
return Equal
|
||||
}
|
||||
|
|
308
pkg/engine/pattern/pattern.go
Normal file
308
pkg/engine/pattern/pattern.go
Normal file
|
@ -0,0 +1,308 @@
|
|||
package pattern
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/kyverno/kyverno/pkg/engine/operator"
|
||||
wildcard "github.com/kyverno/kyverno/pkg/utils/wildcard"
|
||||
apiresource "k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
type quantity int
|
||||
|
||||
const (
|
||||
equal quantity = 0
|
||||
lessThan quantity = -1
|
||||
greaterThan quantity = 1
|
||||
)
|
||||
|
||||
// Validate validates a value against a pattern
|
||||
func Validate(log logr.Logger, value, pattern interface{}) bool {
|
||||
switch typedPattern := pattern.(type) {
|
||||
case bool:
|
||||
return validateBoolPattern(log, value, typedPattern)
|
||||
case int:
|
||||
return validateIntPattern(log, value, int64(typedPattern))
|
||||
case int64:
|
||||
return validateIntPattern(log, value, typedPattern)
|
||||
case float64:
|
||||
return validateFloatPattern(log, value, typedPattern)
|
||||
case nil:
|
||||
return validateNilPattern(log, value)
|
||||
case map[string]interface{}:
|
||||
return validateMapPattern(log, value, typedPattern)
|
||||
case string:
|
||||
return validateStringPatterns(log, value, typedPattern)
|
||||
case []interface{}:
|
||||
log.V(2).Info("arrays are not supported as patterns")
|
||||
return false
|
||||
default:
|
||||
log.V(2).Info("Unknown type", "type", fmt.Sprintf("%T", typedPattern), "value", typedPattern)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func validateBoolPattern(log logr.Logger, value interface{}, pattern bool) bool {
|
||||
switch typedValue := value.(type) {
|
||||
case bool:
|
||||
return pattern == typedValue
|
||||
default:
|
||||
log.V(4).Info("Expected type bool", "type", fmt.Sprintf("%T", value), "value", value)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func validateIntPattern(log logr.Logger, 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) {
|
||||
log.V(2).Info("Expected type int", "type", fmt.Sprintf("%T", typedValue), "value", typedValue)
|
||||
return false
|
||||
}
|
||||
return int64(typedValue) == pattern
|
||||
case string:
|
||||
value, err := strconv.ParseInt(typedValue, 10, 64)
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to parse int64 from string")
|
||||
return false
|
||||
}
|
||||
return value == pattern
|
||||
default:
|
||||
log.V(2).Info("Expected type int", "type", fmt.Sprintf("%T", value), "value", value)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func validateFloatPattern(log logr.Logger, value interface{}, pattern float64) bool {
|
||||
switch typedValue := value.(type) {
|
||||
case int:
|
||||
// check that float has no fraction
|
||||
if pattern != math.Trunc(pattern) {
|
||||
log.V(2).Info("Expected type float", "type", fmt.Sprintf("%T", typedValue), "value", typedValue)
|
||||
return false
|
||||
}
|
||||
return int(pattern) == value
|
||||
case int64:
|
||||
// check that float has no fraction
|
||||
if pattern != math.Trunc(pattern) {
|
||||
log.V(2).Info("Expected type float", "type", fmt.Sprintf("%T", typedValue), "value", typedValue)
|
||||
return false
|
||||
}
|
||||
return int64(pattern) == value
|
||||
case float64:
|
||||
return typedValue == pattern
|
||||
case string:
|
||||
value, err := strconv.ParseFloat(typedValue, 64)
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to parse float64 from string")
|
||||
return false
|
||||
}
|
||||
return value == pattern
|
||||
default:
|
||||
log.V(2).Info("Expected type float", "type", fmt.Sprintf("%T", value), "value", value)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func validateNilPattern(log logr.Logger, value interface{}) bool {
|
||||
switch typedValue := value.(type) {
|
||||
case float64:
|
||||
return typedValue == 0.0
|
||||
case int:
|
||||
return typedValue == 0
|
||||
case int64:
|
||||
return typedValue == 0
|
||||
case string:
|
||||
return typedValue == ""
|
||||
case bool:
|
||||
return !typedValue
|
||||
case nil:
|
||||
return true
|
||||
case map[string]interface{}, []interface{}:
|
||||
log.V(2).Info("Maps and arrays could not be checked with nil pattern")
|
||||
return false
|
||||
default:
|
||||
log.V(2).Info("Unknown type as value when checking for nil pattern", "type", fmt.Sprintf("%T", value), "value", value)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func validateMapPattern(log logr.Logger, value interface{}, typedPattern map[string]interface{}) bool {
|
||||
// verify the type of the resource value is map[string]interface,
|
||||
// we only check for existence of object, not the equality of content and value
|
||||
_, ok := value.(map[string]interface{})
|
||||
if !ok {
|
||||
log.V(2).Info("Expected type map[string]interface{}", "type", fmt.Sprintf("%T", value), "value", value)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func validateStringPatterns(log logr.Logger, value interface{}, pattern string) bool {
|
||||
if value == pattern {
|
||||
return true
|
||||
}
|
||||
for _, condition := range strings.Split(pattern, "|") {
|
||||
condition = strings.Trim(condition, " ")
|
||||
if checkForAndConditionsAndValidate(log, value, condition) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func checkForAndConditionsAndValidate(log logr.Logger, value interface{}, pattern string) bool {
|
||||
for _, condition := range strings.Split(pattern, "&") {
|
||||
condition = strings.Trim(condition, " ")
|
||||
if !validateStringPattern(log, value, condition) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func validateStringPattern(log logr.Logger, value interface{}, pattern string) bool {
|
||||
op := operator.GetOperatorFromStringPattern(pattern)
|
||||
if op == operator.InRange {
|
||||
// Upon encountering InRange operator split the string by `-` and basically
|
||||
// verify the result of (x >= leftEndpoint & x <= rightEndpoint)
|
||||
endpoints := strings.Split(pattern, "-")
|
||||
return validateStringPattern(log, value, fmt.Sprintf(">= %s", endpoints[0])) &&
|
||||
validateStringPattern(log, value, fmt.Sprintf("<= %s", endpoints[1]))
|
||||
} else if op == operator.NotInRange {
|
||||
// Upon encountering NotInRange operator split the string by `!-` and basically
|
||||
// verify the result of (x < leftEndpoint | x > rightEndpoint)
|
||||
endpoints := strings.Split(pattern, "!-")
|
||||
return validateStringPattern(log, value, fmt.Sprintf("< %s", endpoints[0])) ||
|
||||
validateStringPattern(log, value, fmt.Sprintf("> %s", endpoints[1]))
|
||||
} else {
|
||||
pattern := strings.TrimSpace(pattern[len(op):])
|
||||
return validateString(log, value, pattern, op)
|
||||
}
|
||||
}
|
||||
|
||||
func validateString(log logr.Logger, value interface{}, pattern string, op operator.Operator) bool {
|
||||
return compareDuration(log, value, pattern, op) ||
|
||||
compareQuantity(log, value, pattern, op) ||
|
||||
compareString(log, value, pattern, op)
|
||||
}
|
||||
|
||||
func compareDuration(log logr.Logger, value interface{}, pattern string, op operator.Operator) bool {
|
||||
if pattern, err := time.ParseDuration(pattern); err != nil {
|
||||
return false
|
||||
} else if value, err := convertNumberToString(value); err != nil {
|
||||
return false
|
||||
} else if value, err := time.ParseDuration(value); err != nil {
|
||||
return false
|
||||
} else {
|
||||
switch op {
|
||||
case operator.Equal:
|
||||
return value == pattern
|
||||
case operator.NotEqual:
|
||||
return value != pattern
|
||||
case operator.More:
|
||||
return value > pattern
|
||||
case operator.Less:
|
||||
return value < pattern
|
||||
case operator.MoreEqual:
|
||||
return value >= pattern
|
||||
case operator.LessEqual:
|
||||
return value <= pattern
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func compareQuantity(log logr.Logger, value interface{}, pattern string, op operator.Operator) bool {
|
||||
if pattern, err := apiresource.ParseQuantity(pattern); err != nil {
|
||||
return false
|
||||
} else if value, err := convertNumberToString(value); err != nil {
|
||||
return false
|
||||
} else if value, err := apiresource.ParseQuantity(value); err != nil {
|
||||
return false
|
||||
} else {
|
||||
result := value.Cmp(pattern)
|
||||
switch op {
|
||||
case operator.Equal:
|
||||
return result == int(equal)
|
||||
case operator.NotEqual:
|
||||
return result != int(equal)
|
||||
case operator.More:
|
||||
return result == int(greaterThan)
|
||||
case operator.Less:
|
||||
return result == int(lessThan)
|
||||
case operator.MoreEqual:
|
||||
return (result == int(equal)) || (result == int(greaterThan))
|
||||
case operator.LessEqual:
|
||||
return (result == int(equal)) || (result == int(lessThan))
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func compareString(log logr.Logger, value interface{}, pattern string, operatorVariable operator.Operator) bool {
|
||||
if operator.NotEqual == operatorVariable || operator.Equal == operatorVariable {
|
||||
var strValue string
|
||||
var ok bool = false
|
||||
switch v := value.(type) {
|
||||
case float64:
|
||||
strValue = strconv.FormatFloat(v, 'E', -1, 64)
|
||||
ok = true
|
||||
case int:
|
||||
strValue = strconv.FormatInt(int64(v), 10)
|
||||
ok = true
|
||||
case int64:
|
||||
strValue = strconv.FormatInt(v, 10)
|
||||
ok = true
|
||||
case string:
|
||||
strValue = v
|
||||
ok = true
|
||||
case bool:
|
||||
strValue = strconv.FormatBool(v)
|
||||
ok = true
|
||||
case nil:
|
||||
ok = false
|
||||
}
|
||||
if !ok {
|
||||
log.V(4).Info("unexpected type", "got", value, "expect", pattern)
|
||||
return false
|
||||
}
|
||||
wildcardResult := wildcard.Match(pattern, strValue)
|
||||
if operator.NotEqual == operatorVariable {
|
||||
return !wildcardResult
|
||||
}
|
||||
return wildcardResult
|
||||
}
|
||||
log.V(2).Info("Operators >, >=, <, <= are not applicable to strings")
|
||||
return false
|
||||
}
|
||||
|
||||
func convertNumberToString(value interface{}) (string, error) {
|
||||
if value == nil {
|
||||
return "0", nil
|
||||
}
|
||||
switch typed := value.(type) {
|
||||
case string:
|
||||
return typed, nil
|
||||
case float64:
|
||||
return fmt.Sprintf("%f", typed), nil
|
||||
case int64:
|
||||
return strconv.FormatInt(typed, 10), nil
|
||||
case int:
|
||||
return strconv.Itoa(typed), nil
|
||||
case nil:
|
||||
return "", fmt.Errorf("got empty string, expect %v", value)
|
||||
default:
|
||||
return "", fmt.Errorf("could not convert %v to string", typed)
|
||||
}
|
||||
}
|
382
pkg/engine/pattern/pattern_test.go
Normal file
382
pkg/engine/pattern/pattern_test.go
Normal file
|
@ -0,0 +1,382 @@
|
|||
package pattern
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/kyverno/kyverno/pkg/engine/operator"
|
||||
"github.com/kyverno/kyverno/pkg/logging"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
var logger = logging.GlobalLogger()
|
||||
|
||||
func TestValidateValueWithPattern_Bool(t *testing.T) {
|
||||
assert.Assert(t, Validate(logger, true, true))
|
||||
assert.Assert(t, !Validate(logger, true, false))
|
||||
assert.Assert(t, !Validate(logger, false, true))
|
||||
assert.Assert(t, Validate(logger, false, false))
|
||||
}
|
||||
|
||||
func TestValidateString_AsteriskTest(t *testing.T) {
|
||||
pattern := "*"
|
||||
value := "anything"
|
||||
empty := ""
|
||||
|
||||
assert.Assert(t, compareString(logger, value, pattern, operator.Equal))
|
||||
assert.Assert(t, compareString(logger, empty, pattern, operator.Equal))
|
||||
}
|
||||
|
||||
func TestValidateString_LeftAsteriskTest(t *testing.T) {
|
||||
pattern := "*right"
|
||||
value := "leftright"
|
||||
right := "right"
|
||||
|
||||
assert.Assert(t, compareString(logger, value, pattern, operator.Equal))
|
||||
assert.Assert(t, compareString(logger, right, pattern, operator.Equal))
|
||||
|
||||
value = "leftmiddle"
|
||||
middle := "middle"
|
||||
|
||||
assert.Assert(t, !compareString(logger, value, pattern, operator.Equal))
|
||||
assert.Assert(t, !compareString(logger, middle, pattern, operator.Equal))
|
||||
}
|
||||
|
||||
func TestValidateString_MiddleAsteriskTest(t *testing.T) {
|
||||
pattern := "ab*ba"
|
||||
value := "abbeba"
|
||||
assert.Assert(t, compareString(logger, value, pattern, operator.Equal))
|
||||
|
||||
value = "abbca"
|
||||
assert.Assert(t, !compareString(logger, value, pattern, operator.Equal))
|
||||
}
|
||||
|
||||
func TestValidateString_QuestionMark(t *testing.T) {
|
||||
pattern := "ab?ba"
|
||||
value := "abbba"
|
||||
assert.Assert(t, compareString(logger, value, pattern, operator.Equal))
|
||||
|
||||
value = "abbbba"
|
||||
assert.Assert(t, !compareString(logger, value, pattern, operator.Equal))
|
||||
}
|
||||
|
||||
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, Validate(logger, 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, !Validate(logger, 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, Validate(logger, 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, Validate(logger, 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, Validate(logger, 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, Validate(logger, 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, Validate(logger, value, pattern))
|
||||
}
|
||||
|
||||
func TestValidateValueWithPattern_StringsLogicalAnd(t *testing.T) {
|
||||
pattern := ">1 & <20"
|
||||
value := "10"
|
||||
assert.Assert(t, Validate(logger, value, pattern))
|
||||
}
|
||||
|
||||
func TestValidateValueWithPattern_StringsAllLogicalOperators(t *testing.T) {
|
||||
pattern := ">1 & <20 | >31 & <33"
|
||||
value := "10"
|
||||
assert.Assert(t, Validate(logger, value, pattern))
|
||||
value = "32"
|
||||
assert.Assert(t, Validate(logger, value, pattern))
|
||||
value = "21"
|
||||
assert.Assert(t, !Validate(logger, value, pattern))
|
||||
}
|
||||
|
||||
func TestValidateValueWithPattern_EqualTwoFloats(t *testing.T) {
|
||||
assert.Assert(t, Validate(logger, 7.0, 7.000))
|
||||
}
|
||||
|
||||
func TestValidateValueWithNilPattern_NullPatternStringValue(t *testing.T) {
|
||||
assert.Assert(t, !validateNilPattern(logger, "value"))
|
||||
}
|
||||
|
||||
func TestValidateValueWithNilPattern_NullPatternDefaultString(t *testing.T) {
|
||||
assert.Assert(t, validateNilPattern(logger, ""))
|
||||
}
|
||||
|
||||
func TestValidateValueWithNilPattern_NullPatternDefaultFloat(t *testing.T) {
|
||||
assert.Assert(t, validateNilPattern(logger, 0.0))
|
||||
}
|
||||
|
||||
func TestValidateValueWithNilPattern_NullPatternFloat(t *testing.T) {
|
||||
assert.Assert(t, !validateNilPattern(logger, 0.1))
|
||||
}
|
||||
|
||||
func TestValidateValueWithNilPattern_NullPatternDefaultInt(t *testing.T) {
|
||||
assert.Assert(t, validateNilPattern(logger, 0))
|
||||
}
|
||||
|
||||
func TestValidateValueWithNilPattern_NullPatternInt(t *testing.T) {
|
||||
assert.Assert(t, !validateNilPattern(logger, 1))
|
||||
}
|
||||
|
||||
func TestValidateValueWithNilPattern_NullPatternDefaultBool(t *testing.T) {
|
||||
assert.Assert(t, validateNilPattern(logger, false))
|
||||
}
|
||||
|
||||
func TestValidateValueWithNilPattern_NullPatternTrueBool(t *testing.T) {
|
||||
assert.Assert(t, !validateNilPattern(logger, true))
|
||||
}
|
||||
|
||||
func TestValidateValueWithFloatPattern_FloatValue(t *testing.T) {
|
||||
assert.Assert(t, validateFloatPattern(logger, 7.9914, 7.9914))
|
||||
}
|
||||
|
||||
func TestValidateValueWithFloatPattern_FloatValueNotPass(t *testing.T) {
|
||||
assert.Assert(t, !validateFloatPattern(logger, 7.9914, 7.99141))
|
||||
}
|
||||
|
||||
func TestValidateValueWithFloatPattern_FloatPatternWithoutFractionIntValue(t *testing.T) {
|
||||
assert.Assert(t, validateFloatPattern(logger, 7, 7.000000))
|
||||
}
|
||||
|
||||
func TestValidateValueWithFloatPattern_FloatPatternWithoutFraction(t *testing.T) {
|
||||
assert.Assert(t, validateFloatPattern(logger, 7.000000, 7.000000))
|
||||
}
|
||||
|
||||
func TestValidateValueWithIntPattern_FloatValueWithoutFraction(t *testing.T) {
|
||||
assert.Assert(t, validateFloatPattern(logger, 7.000000, 7))
|
||||
}
|
||||
|
||||
func TestValidateValueWithIntPattern_FloatValueWitFraction(t *testing.T) {
|
||||
assert.Assert(t, !validateFloatPattern(logger, 7.000001, 7))
|
||||
}
|
||||
|
||||
func TestValidateValueWithIntPattern_NotPass(t *testing.T) {
|
||||
assert.Assert(t, !validateFloatPattern(logger, 8, 7))
|
||||
}
|
||||
|
||||
func TestValidateValueWithStringPattern_WithSpace(t *testing.T) {
|
||||
assert.Assert(t, validateStringPattern(logger, 4, ">= 3"))
|
||||
}
|
||||
|
||||
func TestValidateValueWithStringPattern_Ranges(t *testing.T) {
|
||||
assert.Assert(t, validateStringPattern(logger, 0, "0-2"))
|
||||
assert.Assert(t, validateStringPattern(logger, 1, "0-2"))
|
||||
assert.Assert(t, validateStringPattern(logger, 2, "0-2"))
|
||||
assert.Assert(t, !validateStringPattern(logger, 3, "0-2"))
|
||||
|
||||
assert.Assert(t, validateStringPattern(logger, 0, "10!-20"))
|
||||
assert.Assert(t, !validateStringPattern(logger, 15, "10!-20"))
|
||||
assert.Assert(t, validateStringPattern(logger, 25, "10!-20"))
|
||||
|
||||
assert.Assert(t, !validateStringPattern(logger, 0, "0.00001-2.00001"))
|
||||
assert.Assert(t, validateStringPattern(logger, 1, "0.00001-2.00001"))
|
||||
assert.Assert(t, validateStringPattern(logger, 2, "0.00001-2.00001"))
|
||||
assert.Assert(t, !validateStringPattern(logger, 2.0001, "0.00001-2.00001"))
|
||||
|
||||
assert.Assert(t, validateStringPattern(logger, 0, "0.00001!-2.00001"))
|
||||
assert.Assert(t, !validateStringPattern(logger, 1, "0.00001!-2.00001"))
|
||||
assert.Assert(t, !validateStringPattern(logger, 2, "0.00001!-2.00001"))
|
||||
assert.Assert(t, validateStringPattern(logger, 2.0001, "0.00001!-2.00001"))
|
||||
|
||||
assert.Assert(t, validateStringPattern(logger, 2, "2-2"))
|
||||
assert.Assert(t, !validateStringPattern(logger, 2, "2!-2"))
|
||||
|
||||
assert.Assert(t, validateStringPattern(logger, 2.99999, "2.99998-3"))
|
||||
assert.Assert(t, validateStringPattern(logger, 2.99997, "2.99998!-3"))
|
||||
assert.Assert(t, validateStringPattern(logger, 3.00001, "2.99998!-3"))
|
||||
|
||||
assert.Assert(t, validateStringPattern(logger, "256Mi", "128Mi-512Mi"))
|
||||
assert.Assert(t, !validateStringPattern(logger, "1024Mi", "128Mi-512Mi"))
|
||||
assert.Assert(t, !validateStringPattern(logger, "64Mi", "128Mi-512Mi"))
|
||||
|
||||
assert.Assert(t, !validateStringPattern(logger, "256Mi", "128Mi!-512Mi"))
|
||||
assert.Assert(t, validateStringPattern(logger, "1024Mi", "128Mi!-512Mi"))
|
||||
assert.Assert(t, validateStringPattern(logger, "64Mi", "128Mi!-512Mi"))
|
||||
}
|
||||
|
||||
func TestValidateNumberWithStr_LessFloatAndInt(t *testing.T) {
|
||||
assert.Assert(t, validateString(logger, 7.00001, "7.000001", operator.More))
|
||||
assert.Assert(t, validateString(logger, 7.00001, "7", operator.NotEqual))
|
||||
|
||||
assert.Assert(t, validateString(logger, 7.0000, "7", operator.Equal))
|
||||
assert.Assert(t, !validateString(logger, 6.000000001, "6", operator.Less))
|
||||
}
|
||||
|
||||
func TestValidateQuantity_InvalidQuantity(t *testing.T) {
|
||||
assert.Assert(t, !validateString(logger, "1024Gi", "", operator.Equal))
|
||||
assert.Assert(t, !validateString(logger, "gii", "1024Gi", operator.Equal))
|
||||
}
|
||||
|
||||
func TestValidateDuration(t *testing.T) {
|
||||
assert.Assert(t, validateString(logger, "12s", "12s", operator.Equal))
|
||||
assert.Assert(t, validateString(logger, "12s", "15s", operator.NotEqual))
|
||||
assert.Assert(t, validateString(logger, "12s", "15s", operator.Less))
|
||||
assert.Assert(t, validateString(logger, "12s", "15s", operator.LessEqual))
|
||||
assert.Assert(t, validateString(logger, "12s", "12s", operator.LessEqual))
|
||||
assert.Assert(t, !validateString(logger, "15s", "12s", operator.Less))
|
||||
assert.Assert(t, !validateString(logger, "15s", "12s", operator.LessEqual))
|
||||
assert.Assert(t, validateString(logger, "15s", "12s", operator.More))
|
||||
assert.Assert(t, validateString(logger, "15s", "12s", operator.MoreEqual))
|
||||
assert.Assert(t, validateString(logger, "12s", "12s", operator.MoreEqual))
|
||||
assert.Assert(t, !validateString(logger, "12s", "15s", operator.More))
|
||||
assert.Assert(t, !validateString(logger, "12s", "15s", operator.MoreEqual))
|
||||
}
|
||||
|
||||
func TestValidateQuantity_Equal(t *testing.T) {
|
||||
assert.Assert(t, validateString(logger, "1024Gi", "1024Gi", operator.Equal))
|
||||
assert.Assert(t, validateString(logger, "1024Mi", "1Gi", operator.Equal))
|
||||
assert.Assert(t, validateString(logger, "0.2", "200m", operator.Equal))
|
||||
assert.Assert(t, validateString(logger, "500", "500", operator.Equal))
|
||||
assert.Assert(t, !validateString(logger, "2048", "1024", operator.Equal))
|
||||
assert.Assert(t, validateString(logger, 1024, "1024", operator.Equal))
|
||||
}
|
||||
|
||||
func TestValidateQuantity_Operation(t *testing.T) {
|
||||
assert.Assert(t, validateString(logger, "1Gi", "1000Mi", operator.More))
|
||||
assert.Assert(t, validateString(logger, "1G", "1Gi", operator.Less))
|
||||
assert.Assert(t, validateString(logger, "500m", "0.5", operator.MoreEqual))
|
||||
assert.Assert(t, validateString(logger, "1", "500m", operator.MoreEqual))
|
||||
assert.Assert(t, validateString(logger, "0.5", ".5", operator.LessEqual))
|
||||
assert.Assert(t, validateString(logger, "0.2", ".5", operator.LessEqual))
|
||||
assert.Assert(t, validateString(logger, "0.2", ".5", operator.NotEqual))
|
||||
}
|
||||
|
||||
func TestGetOperatorFromStringPattern_OneChar(t *testing.T) {
|
||||
assert.Equal(t, operator.GetOperatorFromStringPattern("f"), operator.Equal)
|
||||
}
|
||||
|
||||
func TestGetOperatorFromStringPattern_EmptyString(t *testing.T) {
|
||||
assert.Equal(t, operator.GetOperatorFromStringPattern(""), operator.Equal)
|
||||
}
|
||||
|
||||
func TestValidateKernelVersion_NotEquals(t *testing.T) {
|
||||
assert.Assert(t, validateStringPattern(logger, "5.16.5-arch1-1", "!5.10.84-1"))
|
||||
assert.Assert(t, !validateStringPattern(logger, "5.10.84-1", "!5.10.84-1"))
|
||||
assert.Assert(t, validateStringPatterns(logger, "5.16.5-arch1-1", "!5.10.84-1 & !5.15.2-1"))
|
||||
assert.Assert(t, !validateStringPatterns(logger, "5.10.84-1", "!5.10.84-1 & !5.15.2-1"))
|
||||
assert.Assert(t, !validateStringPatterns(logger, "5.15.2-1", "!5.10.84-1 & !5.15.2-1"))
|
||||
}
|
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/kyverno/kyverno/pkg/engine/anchor"
|
||||
"github.com/kyverno/kyverno/pkg/engine/common"
|
||||
"github.com/kyverno/kyverno/pkg/engine/pattern"
|
||||
"github.com/kyverno/kyverno/pkg/engine/wildcards"
|
||||
"go.uber.org/multierr"
|
||||
)
|
||||
|
@ -95,13 +95,13 @@ func validateResourceElement(log logr.Logger, resourceElement, patternElement, o
|
|||
switch resource := resourceElement.(type) {
|
||||
case []interface{}:
|
||||
for _, res := range resource {
|
||||
if !common.ValidateValueWithPattern(log, res, patternElement) {
|
||||
if !pattern.Validate(log, res, patternElement) {
|
||||
return path, fmt.Errorf("resource value '%v' does not match '%v' at path %s", resourceElement, patternElement, path)
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
default:
|
||||
if !common.ValidateValueWithPattern(log, resourceElement, patternElement) {
|
||||
if !pattern.Validate(log, resourceElement, patternElement) {
|
||||
return path, fmt.Errorf("resource value '%v' does not match '%v' at path %s", resourceElement, patternElement, path)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/kyverno/kyverno/pkg/engine/common"
|
||||
"github.com/kyverno/kyverno/pkg/engine/context"
|
||||
"github.com/kyverno/kyverno/pkg/engine/operator"
|
||||
"github.com/kyverno/kyverno/pkg/engine/pattern"
|
||||
wildcard "github.com/kyverno/kyverno/pkg/utils/wildcard"
|
||||
)
|
||||
|
||||
|
@ -101,7 +101,7 @@ func anyKeyExistsInArray(key string, value interface{}, log logr.Logger) (invali
|
|||
}
|
||||
|
||||
func handleRange(key string, value interface{}, log logr.Logger) bool {
|
||||
if !common.ValidateValueWithPattern(log, key, value) {
|
||||
if !pattern.Validate(log, key, value) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
|
|
Loading…
Reference in a new issue