Skip to content

Commit

Permalink
feat(operators): supporting subset checking (#1613)
Browse files Browse the repository at this point in the history
* fix(operators): supporting subset checking

Signed-off-by: Arsh Sharma <arshsharma461@gmail.com>

* fix(operators): removed print statement

Signed-off-by: Arsh Sharma <arshsharma461@gmail.com>

* test(operators): added test file for in

Signed-off-by: Arsh Sharma <arshsharma461@gmail.com>

* fix(operators): fixed switching

Signed-off-by: Arsh Sharma <arshsharma461@gmail.com>

* tests(operators): completed tests for In and NotIn

Signed-off-by: Arsh Sharma <arshsharma461@gmail.com>

* chore(operators): code cleanup

Signed-off-by: Arsh Sharma <arshsharma461@gmail.com>

* chore(operators): added comments for tests

Signed-off-by: Arsh Sharma <arshsharma461@gmail.com>

* fix(operators): changed logic based on new definitions

Signed-off-by: Arsh Sharma <arshsharma461@gmail.com>

* test: updated NotIn tests

Signed-off-by: Arsh Sharma <arshsharma461@gmail.com>
  • Loading branch information
RinkiyaKeDad committed Feb 26, 2021
1 parent 070f137 commit 86879bd
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 32 deletions.
102 changes: 102 additions & 0 deletions pkg/engine/variables/evaluate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1013,3 +1013,105 @@ func Test_Eval_Equal_Var_Fail(t *testing.T) {
t.Error("expected to fail")
}
}

// subset test

// test passes if ALL values in "key" are in "value" ("key" is a subset of "value")
func Test_Eval_In_String_Set_Pass(t *testing.T) {
ctx := context.NewContext()
key := [2]string{"1.1.1.1", "2.2.2.2"}
keyInterface := make([]interface{}, len(key), len(key))
for i := range key {
keyInterface[i] = key[i]
}
value := [3]string{"1.1.1.1", "2.2.2.2", "3.3.3.3"}
valueInterface := make([]interface{}, len(value), len(value))
for i := range value {
valueInterface[i] = value[i]
}

condition := kyverno.Condition{
Key: keyInterface,
Operator: kyverno.In,
Value: valueInterface,
}

if !Evaluate(log.Log, ctx, condition) {
t.Error("expected to pass")
}
}

// test passes if NOT ALL values in "key" are in "value" ("key" is not a subset of "value")
func Test_Eval_In_String_Set_Fail(t *testing.T) {
ctx := context.NewContext()
key := [2]string{"1.1.1.1", "4.4.4.4"}
keyInterface := make([]interface{}, len(key), len(key))
for i := range key {
keyInterface[i] = key[i]
}
value := [3]string{"1.1.1.1", "2.2.2.2", "3.3.3.3"}
valueInterface := make([]interface{}, len(value), len(value))
for i := range value {
valueInterface[i] = value[i]
}

condition := kyverno.Condition{
Key: keyInterface,
Operator: kyverno.In,
Value: valueInterface,
}

if Evaluate(log.Log, ctx, condition) {
t.Error("expected to fail")
}
}

// test passes if ALL of the values in "key" are NOT in "value" ("key" is not a subset of "value")
func Test_Eval_NotIn_String_Set_Pass(t *testing.T) {
ctx := context.NewContext()
key := [2]string{"1.1.1.1", "4.4.4.4"}
keyInterface := make([]interface{}, len(key), len(key))
for i := range key {
keyInterface[i] = key[i]
}
value := [3]string{"1.1.1.1", "2.2.2.2", "3.3.3.3"}
valueInterface := make([]interface{}, len(value), len(value))
for i := range value {
valueInterface[i] = value[i]
}

condition := kyverno.Condition{
Key: keyInterface,
Operator: kyverno.NotIn,
Value: valueInterface,
}

if !Evaluate(log.Log, ctx, condition) {
t.Error("expected to pass")
}
}

// test passes if ALL of the values in "key" are in "value" ("key" is a subset of "value")
func Test_Eval_NotIn_String_Set_Fail(t *testing.T) {
ctx := context.NewContext()
key := [2]string{"1.1.1.1", "2.2.2.2"}
keyInterface := make([]interface{}, len(key), len(key))
for i := range key {
keyInterface[i] = key[i]
}
value := [3]string{"1.1.1.1", "2.2.2.2", "3.3.3.3"}
valueInterface := make([]interface{}, len(value), len(value))
for i := range value {
valueInterface[i] = value[i]
}

condition := kyverno.Condition{
Key: keyInterface,
Operator: kyverno.NotIn,
Value: valueInterface,
}

if Evaluate(log.Log, ctx, condition) {
t.Error("expected to fail")
}
}
27 changes: 14 additions & 13 deletions pkg/engine/variables/operator/in.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ func (in InHandler) Evaluate(key, value interface{}) bool {
switch typedKey := key.(type) {
case string:
return in.validateValueWithStringPattern(typedKey, value)
case []interface{}:
var stringSlice []string
for _, v := range typedKey {
stringSlice = append(stringSlice, v.(string))
}
return in.validateValueWithStringSetPattern(stringSlice, value)
default:
in.log.Info("Unsupported type", "value", typedKey, "type", fmt.Sprintf("%T", typedKey))
return false
Expand Down Expand Up @@ -104,7 +110,7 @@ func keyExistsInArray(key string, value interface{}, log logr.Logger) (invalidTy
}

func (in InHandler) validateValueWithStringSetPattern(key []string, value interface{}) (keyExists bool) {
invalidType, keyExists := setExistsInArray(key, value, in.log, false)
invalidType, keyExists := setExistsInArray(key, value, in.log)
if invalidType {
in.log.Info("expected type []string", "value", value, "type", fmt.Sprintf("%T", value))
return false
Expand All @@ -116,8 +122,7 @@ func (in InHandler) validateValueWithStringSetPattern(key []string, value interf
// setExistsInArray checks if the key is a subset of value
// The value can be a string, an array of strings, or a JSON format
// array of strings (e.g. ["val1", "val2", "val3"].
// notIn argument is set to true when we want to check if key is NOT a subset of value
func setExistsInArray(key []string, value interface{}, log logr.Logger, notIn bool) (invalidType bool, keyExists bool) {
func setExistsInArray(key []string, value interface{}, log logr.Logger) (invalidType bool, keyExists bool) {
switch valuesAvailable := value.(type) {

case []interface{}:
Expand All @@ -129,10 +134,8 @@ func setExistsInArray(key []string, value interface{}, log logr.Logger, notIn bo
}
valueSlice = append(valueSlice, v)
}
if notIn {
return false, checkInSubsetForNotIn(key, valueSlice)
}
return false, checkInSubset(key, valueSlice)

return false, isSubset(key, valueSlice)

case string:

Expand All @@ -145,18 +148,16 @@ func setExistsInArray(key []string, value interface{}, log logr.Logger, notIn bo
log.Error(err, "failed to unmarshal value to JSON string array", "key", key, "value", value)
return true, false
}
if notIn {
return false, checkInSubsetForNotIn(key, arr)
}
return false, checkInSubset(key, arr)

return false, isSubset(key, arr)

default:
return true, false
}
}

// checkInSubset checks if ALL values of S1 are in S2
func checkInSubset(key []string, value []string) bool {
// isSubset checks if S1 is a subset of S2 i.e. ALL values of S1 are in S2
func isSubset(key []string, value []string) bool {
set := make(map[string]int)

for _, val := range value {
Expand Down
26 changes: 7 additions & 19 deletions pkg/engine/variables/operator/notin.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ func (nin NotInHandler) Evaluate(key, value interface{}) bool {
switch typedKey := key.(type) {
case string:
return nin.validateValueWithStringPattern(typedKey, value)
case []interface{}:
var stringSlice []string
for _, v := range typedKey {
stringSlice = append(stringSlice, v.(string))
}
return nin.validateValueWithStringSetPattern(stringSlice, value)
default:
nin.log.Info("Unsupported type", "value", typedKey, "type", fmt.Sprintf("%T", typedKey))
return false
Expand All @@ -58,7 +64,7 @@ func (nin NotInHandler) validateValueWithStringPattern(key string, value interfa
}

func (nin NotInHandler) validateValueWithStringSetPattern(key []string, value interface{}) bool {
invalidType, keyExists := setExistsInArray(key, value, nin.log, true)
invalidType, keyExists := setExistsInArray(key, value, nin.log)
if invalidType {
nin.log.Info("expected type []string", "value", value, "type", fmt.Sprintf("%T", value))
return false
Expand All @@ -67,24 +73,6 @@ func (nin NotInHandler) validateValueWithStringSetPattern(key []string, value in
return !keyExists
}

// checkInSubsetForNotIn checks if ANY of the values of S1 is in S2
func checkInSubsetForNotIn(key []string, value []string) bool {
set := make(map[string]int)

for _, val := range value {
set[val]++
}

for _, val := range key {
_, found := set[val]
if found {
return true
}
}

return false
}

func (nin NotInHandler) validateValueWithBoolPattern(_ bool, _ interface{}) bool {
return false
}
Expand Down

0 comments on commit 86879bd

Please sign in to comment.