1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00

feat(operators): supporting subset checking (#1613)

* 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>
This commit is contained in:
Arsh Sharma 2021-02-26 18:53:54 +00:00 committed by GitHub
parent 070f13783f
commit 86879bd267
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 123 additions and 32 deletions

View file

@ -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")
}
}

View file

@ -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
@ -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
@ -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{}:
@ -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:
@ -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 {

View file

@ -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
@ -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
@ -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
}