mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-28 02:18:15 +00:00
add semver_compare
JMESPath function (#2846)
* add semver_compare JMESPath function Signed-off-by: Namanl2001 <namanlakhwani@gmail.com> * adding tests for semver_compare Signed-off-by: Namanl2001 <namanlakhwani@gmail.com> * enabling version compaision via regular operators Signed-off-by: Namanl2001 <namanlakhwani@gmail.com> * adding tests for version compaision via regular operators Signed-off-by: Namanl2001 <namanlakhwani@gmail.com> * removing unnecessary switch cases Signed-off-by: Namanl2001 <namanlakhwani@gmail.com> Co-authored-by: Jim Bugwadia <jim@nirmata.com>
This commit is contained in:
parent
6a942683b0
commit
898520b7cf
6 changed files with 126 additions and 8 deletions
1
go.mod
1
go.mod
|
@ -55,6 +55,7 @@ require (
|
|||
|
||||
require (
|
||||
github.com/aquilax/truncate v1.0.0 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0
|
||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1
|
||||
)
|
||||
|
|
1
go.sum
1
go.sum
|
@ -276,6 +276,7 @@ github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAw
|
|||
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
|
||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||
github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc=
|
||||
github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q=
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"time"
|
||||
|
||||
trunc "github.com/aquilax/truncate"
|
||||
"github.com/blang/semver/v4"
|
||||
gojmespath "github.com/jmespath/go-jmespath"
|
||||
"github.com/minio/pkg/wildcard"
|
||||
)
|
||||
|
@ -55,6 +56,7 @@ var (
|
|||
timeSince = "time_since"
|
||||
pathCanonicalize = "path_canonicalize"
|
||||
truncate = "truncate"
|
||||
semverCompare = "semver_compare"
|
||||
)
|
||||
|
||||
const errorPrefix = "JMESPath function '%s': "
|
||||
|
@ -252,6 +254,14 @@ func getFunctions() []*gojmespath.FunctionEntry {
|
|||
},
|
||||
Handler: jpTruncate,
|
||||
},
|
||||
{
|
||||
Name: semverCompare,
|
||||
Arguments: []ArgSpec{
|
||||
{Types: []JpType{JpString}},
|
||||
{Types: []JpType{JpString}},
|
||||
},
|
||||
Handler: jpSemverCompare,
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -631,6 +641,30 @@ func jpTruncate(arguments []interface{}) (interface{}, error) {
|
|||
return trunc.Truncator(str.String(), int(normalizedLength), trunc.CutStrategy{}), nil
|
||||
}
|
||||
|
||||
func jpSemverCompare(arguments []interface{}) (interface{}, error) {
|
||||
var err error
|
||||
v, err := validateArg(semverCompare, arguments, 0, reflect.String)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, err := validateArg(semverCompare, arguments, 1, reflect.String)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
version, _ := semver.Parse(v.String())
|
||||
expectedRange, err := semver.ParseRange(r.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if expectedRange(version) {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// InterfaceToString casts an interface to a string type
|
||||
func ifaceToString(iface interface{}) (string, error) {
|
||||
switch i := iface.(type) {
|
||||
|
|
|
@ -1023,3 +1023,40 @@ func Test_Truncate(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_SemverCompare(t *testing.T) {
|
||||
testCases := []struct {
|
||||
jmesPath string
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
jmesPath: "semver_compare('4.1.3','>=4.1.x')",
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
jmesPath: "semver_compare('4.1.3','!4.x.x')",
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
jmesPath: "semver_compare('1.8.6','>1.0.0 <2.0.0')", // >1.0.0 AND <2.0.0
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
jmesPath: "semver_compare('2.1.5','<2.0.0 || >=3.0.0')", // <2.0.0 OR >=3.0.0
|
||||
expectedResult: false,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.jmesPath, func(t *testing.T) {
|
||||
jp, err := New(tc.jmesPath)
|
||||
assert.NilError(t, err)
|
||||
|
||||
result, err := jp.Search("")
|
||||
assert.NilError(t, err)
|
||||
|
||||
res, ok := result.(bool)
|
||||
assert.Assert(t, ok)
|
||||
assert.Equal(t, res, tc.expectedResult)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,6 +51,9 @@ func TestEvaluate(t *testing.T) {
|
|||
{kyverno.Condition{Key: []interface{}{map[string]string{"foo": "bar"}}, Operator: kyverno.Equals, Value: []interface{}{map[string]string{"bar": "foo"}}}, false},
|
||||
{kyverno.Condition{Key: "1h", Operator: kyverno.Equals, Value: 3600}, true},
|
||||
{kyverno.Condition{Key: "2h", Operator: kyverno.Equals, Value: 3600}, false},
|
||||
{kyverno.Condition{Key: "1.5.2", Operator: kyverno.Equals, Value: "1.5.2"}, true},
|
||||
{kyverno.Condition{Key: "1.5.2", Operator: kyverno.Equals, Value: "1.5.*"}, true},
|
||||
{kyverno.Condition{Key: "1.5.0", Operator: kyverno.Equals, Value: "1.5.5"}, false},
|
||||
|
||||
// Not Equals
|
||||
{kyverno.Condition{Key: "string", Operator: kyverno.NotEquals, Value: "string"}, false},
|
||||
|
@ -88,6 +91,9 @@ func TestEvaluate(t *testing.T) {
|
|||
{kyverno.Condition{Key: []interface{}{map[string]string{"foo": "bar"}}, Operator: kyverno.NotEquals, Value: []interface{}{map[string]string{"bar": "foo"}}}, true},
|
||||
{kyverno.Condition{Key: "1h", Operator: kyverno.NotEquals, Value: 3600}, false},
|
||||
{kyverno.Condition{Key: "2h", Operator: kyverno.NotEquals, Value: 3600}, true},
|
||||
{kyverno.Condition{Key: "1.5.2", Operator: kyverno.NotEquals, Value: "1.5.5"}, true},
|
||||
{kyverno.Condition{Key: "1.5.2", Operator: kyverno.NotEquals, Value: "1.5.*"}, false},
|
||||
{kyverno.Condition{Key: "1.5.0", Operator: kyverno.NotEquals, Value: "1.5.0"}, false},
|
||||
|
||||
// Greater Than
|
||||
{kyverno.Condition{Key: 10, Operator: kyverno.GreaterThan, Value: 1}, true},
|
||||
|
@ -129,6 +135,8 @@ func TestEvaluate(t *testing.T) {
|
|||
{kyverno.Condition{Key: -5, Operator: kyverno.GreaterThan, Value: 1}, false},
|
||||
{kyverno.Condition{Key: -5, Operator: kyverno.GreaterThan, Value: -10}, true},
|
||||
{kyverno.Condition{Key: 1, Operator: kyverno.GreaterThan, Value: -10}, true},
|
||||
{kyverno.Condition{Key: "1.5.5", Operator: kyverno.GreaterThan, Value: "1.5.0"}, true},
|
||||
{kyverno.Condition{Key: "1.5.0", Operator: kyverno.GreaterThan, Value: "1.5.5"}, false},
|
||||
|
||||
// Less Than
|
||||
{kyverno.Condition{Key: 10, Operator: kyverno.LessThan, Value: 1}, false},
|
||||
|
@ -170,6 +178,8 @@ func TestEvaluate(t *testing.T) {
|
|||
{kyverno.Condition{Key: -5, Operator: kyverno.LessThan, Value: 1}, true},
|
||||
{kyverno.Condition{Key: -5, Operator: kyverno.LessThan, Value: -10}, false},
|
||||
{kyverno.Condition{Key: 1, Operator: kyverno.LessThan, Value: -10}, false},
|
||||
{kyverno.Condition{Key: "1.5.5", Operator: kyverno.LessThan, Value: "1.5.0"}, false},
|
||||
{kyverno.Condition{Key: "1.5.0", Operator: kyverno.LessThan, Value: "1.5.5"}, true},
|
||||
|
||||
// Greater Than or Equal
|
||||
{kyverno.Condition{Key: 10, Operator: kyverno.GreaterThanOrEquals, Value: 1}, true},
|
||||
|
@ -206,6 +216,9 @@ func TestEvaluate(t *testing.T) {
|
|||
{kyverno.Condition{Key: 1, Operator: kyverno.GreaterThanOrEquals, Value: int64(1)}, true},
|
||||
{kyverno.Condition{Key: 10, Operator: kyverno.GreaterThanOrEquals, Value: int64(1)}, true},
|
||||
{kyverno.Condition{Key: 1, Operator: kyverno.GreaterThanOrEquals, Value: int64(10)}, false},
|
||||
{kyverno.Condition{Key: "1.5.5", Operator: kyverno.GreaterThanOrEquals, Value: "1.5.5"}, true},
|
||||
{kyverno.Condition{Key: "1.5.5", Operator: kyverno.GreaterThanOrEquals, Value: "1.5.0"}, true},
|
||||
{kyverno.Condition{Key: "1.5.0", Operator: kyverno.GreaterThanOrEquals, Value: "1.5.5"}, false},
|
||||
|
||||
// Less Than or Equal
|
||||
{kyverno.Condition{Key: 10, Operator: kyverno.LessThanOrEquals, Value: 1}, false},
|
||||
|
@ -242,6 +255,9 @@ func TestEvaluate(t *testing.T) {
|
|||
{kyverno.Condition{Key: 1, Operator: kyverno.LessThanOrEquals, Value: int64(1)}, true},
|
||||
{kyverno.Condition{Key: 10, Operator: kyverno.LessThanOrEquals, Value: int64(1)}, false},
|
||||
{kyverno.Condition{Key: 1, Operator: kyverno.LessThanOrEquals, Value: int64(10)}, true},
|
||||
{kyverno.Condition{Key: "1.5.5", Operator: kyverno.LessThanOrEquals, Value: "1.5.5"}, true},
|
||||
{kyverno.Condition{Key: "1.5.0", Operator: kyverno.LessThanOrEquals, Value: "1.5.5"}, true},
|
||||
{kyverno.Condition{Key: "1.5.5", Operator: kyverno.LessThanOrEquals, Value: "1.5.0"}, false},
|
||||
|
||||
// In
|
||||
{kyverno.Condition{Key: 1, Operator: kyverno.In, Value: []interface{}{1, 2, 3}}, true},
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/blang/semver/v4"
|
||||
"github.com/go-logr/logr"
|
||||
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/engine/context"
|
||||
|
@ -37,14 +38,22 @@ func compareByCondition(key float64, value float64, op kyverno.ConditionOperator
|
|||
return key <= value
|
||||
case kyverno.LessThan:
|
||||
return key < value
|
||||
case kyverno.Equals:
|
||||
return key == value
|
||||
case kyverno.Equal:
|
||||
return key == value
|
||||
case kyverno.NotEquals:
|
||||
return key != value
|
||||
case kyverno.NotEqual:
|
||||
return key != value
|
||||
default:
|
||||
(*log).Info(fmt.Sprintf("Expected operator, one of [GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, Equals, NotEquals], found %s", op))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func compareVersionByCondition(key semver.Version, value semver.Version, op kyverno.ConditionOperator, log *logr.Logger) bool {
|
||||
switch op {
|
||||
case kyverno.GreaterThanOrEquals:
|
||||
return key.GTE(value)
|
||||
case kyverno.GreaterThan:
|
||||
return key.GT(value)
|
||||
case kyverno.LessThanOrEquals:
|
||||
return key.LTE(value)
|
||||
case kyverno.LessThan:
|
||||
return key.LT(value)
|
||||
default:
|
||||
(*log).Info(fmt.Sprintf("Expected operator, one of [GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, Equals, NotEquals], found %s", op))
|
||||
return false
|
||||
|
@ -141,6 +150,21 @@ func (noh NumericOperatorHandler) validateValueWithResourcePattern(key resource.
|
|||
}
|
||||
}
|
||||
|
||||
func (noh NumericOperatorHandler) validateValueWithVersionPattern(key semver.Version, value interface{}) bool {
|
||||
switch typedValue := value.(type) {
|
||||
case string:
|
||||
versionValue, err := semver.Parse(typedValue)
|
||||
if err != nil {
|
||||
noh.log.Error(fmt.Errorf("parse error: "), "Failed to parse value type doesn't match key type")
|
||||
return false
|
||||
}
|
||||
return compareVersionByCondition(key, versionValue, noh.condition, &noh.log)
|
||||
default:
|
||||
noh.log.Info("Expected type string", "value", value, "type", fmt.Sprintf("%T", value))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (noh NumericOperatorHandler) validateValueWithStringPattern(key string, value interface{}) bool {
|
||||
// We need to check duration first as it's the only type that can be compared to a different type
|
||||
durationKey, durationValue, err := parseDuration(key, value)
|
||||
|
@ -162,6 +186,11 @@ func (noh NumericOperatorHandler) validateValueWithStringPattern(key string, val
|
|||
if err == nil {
|
||||
return noh.validateValueWithResourcePattern(resourceKey, value)
|
||||
}
|
||||
// attempt to extract version from string
|
||||
versionKey, err := semver.Parse(key)
|
||||
if err == nil {
|
||||
return noh.validateValueWithVersionPattern(versionKey, value)
|
||||
}
|
||||
|
||||
noh.log.Error(err, "Failed to parse from the string key, value is not float, int nor resource quantity")
|
||||
return false
|
||||
|
|
Loading…
Add table
Reference in a new issue