diff --git a/pkg/engine/jmespath/functions.go b/pkg/engine/jmespath/functions.go index cd4d4b62db..c6f01133d8 100644 --- a/pkg/engine/jmespath/functions.go +++ b/pkg/engine/jmespath/functions.go @@ -49,6 +49,7 @@ var ( regexMatch = "regex_match" patternMatch = "pattern_match" labelMatch = "label_match" + toBoolean = "to_boolean" add = "add" subtract = "subtract" multiply = "multiply" @@ -226,6 +227,16 @@ func GetFunctions() []FunctionEntry { }, ReturnType: []jpType{jpBool}, Note: "object arguments must be enclosed in backticks; ex. `{{request.object.spec.template.metadata.labels}}`", + }, { + FunctionEntry: gojmespath.FunctionEntry{ + Name: toBoolean, + Arguments: []argSpec{ + {Types: []jpType{jpString}}, + }, + Handler: jpToBoolean, + }, + ReturnType: []jpType{jpBool}, + Note: "It returns true or false for any string, such as 'True', 'TruE', 'False', 'FAlse', 'faLSE', etc.", }, { FunctionEntry: gojmespath.FunctionEntry{ Name: add, @@ -736,6 +747,21 @@ func jpLabelMatch(arguments []interface{}) (interface{}, error) { return true, nil } +func jpToBoolean(arguments []interface{}) (interface{}, error) { + if input, err := validateArg(toBoolean, arguments, 0, reflect.String); err != nil { + return nil, err + } else { + switch strings.ToLower(input.String()) { + case "true": + return true, nil + case "false": + return false, nil + default: + return nil, formatError(genericError, toBoolean, fmt.Sprintf("lowercase argument must be 'true' or 'false' (provided: '%s')", input.String())) + } + } +} + func jpAdd(arguments []interface{}) (interface{}, error) { op1, op2, err := ParseArithemticOperands(arguments, add) if err != nil { diff --git a/pkg/engine/jmespath/functions_test.go b/pkg/engine/jmespath/functions_test.go index d768757c8d..5c03f966e5 100644 --- a/pkg/engine/jmespath/functions_test.go +++ b/pkg/engine/jmespath/functions_test.go @@ -597,6 +597,35 @@ func Test_LabelMatch(t *testing.T) { } } +func Test_JpToBoolean(t *testing.T) { + testCases := []struct { + input interface{} + expected interface{} + err bool + }{ + {"true", true, false}, + {"TRue", true, false}, + {"FaLse", false, false}, + {"FaLsee", nil, true}, + {"false", false, false}, + {"foo", nil, true}, + {1, nil, true}, + {nil, nil, true}, + } + for _, tc := range testCases { + res, err := jpToBoolean([]interface{}{tc.input}) + if tc.err && err == nil { + t.Errorf("Expected an error but received nil") + } + if !tc.err && err != nil { + t.Errorf("Expected nil error but received: %s", err) + } + if res != tc.expected { + t.Errorf("Expected %v but received %v", tc.expected, res) + } + } +} + func Test_Base64Decode(t *testing.T) { jp, err := New("base64_decode('SGVsbG8sIHdvcmxkIQ==')") assert.NilError(t, err) diff --git a/pkg/engine/jmespath/utils.go b/pkg/engine/jmespath/utils.go index bae133c99d..757ed53730 100644 --- a/pkg/engine/jmespath/utils.go +++ b/pkg/engine/jmespath/utils.go @@ -8,6 +8,9 @@ func validateArg(f string, arguments []interface{}, index int, expectedType refl if index >= len(arguments) { return reflect.Value{}, formatError(argOutOfBoundsError, f, index+1, len(arguments)) } + if arguments[index] == nil { + return reflect.Value{}, formatError(invalidArgumentTypeError, f, index+1, expectedType.String()) + } arg := reflect.ValueOf(arguments[index]) if arg.Type().Kind() != expectedType { return reflect.Value{}, formatError(invalidArgumentTypeError, f, index+1, expectedType.String())