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:
parent
070f13783f
commit
86879bd267
3 changed files with 123 additions and 32 deletions
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue