Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added validate logic for generate to handle multiple items in array (#…
…1727) * added validate logic for generate Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com> * format fix Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com> * gofmt fix Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com> * added test cases Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>
- Loading branch information
1 parent
fd9acf2
commit a0ddd2c
Showing
3 changed files
with
240 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
package generate | ||
|
||
import ( | ||
"container/list" | ||
"fmt" | ||
"strconv" | ||
|
||
"github.com/go-logr/logr" | ||
"github.com/kyverno/kyverno/pkg/engine/validate" | ||
"github.com/kyverno/kyverno/pkg/engine/wildcards" | ||
"sigs.k8s.io/controller-runtime/pkg/log" | ||
) | ||
|
||
type Handler struct { | ||
element string | ||
pattern interface{} | ||
path string | ||
} | ||
|
||
type resourceElementHandler = func(log logr.Logger, resourceElement, patternElement, originPattern interface{}, path string) (string, error) | ||
|
||
// ValidateResourceWithPattern is a start of element-by-element validation process | ||
// It assumes that validation is started from root, so "/" is passed | ||
// Anchors are not expected in the pattern | ||
func ValidateResourceWithPattern(log logr.Logger, resource, pattern interface{}) (string, error) { | ||
elemPath, err := validateResourceElement(log, resource, pattern, pattern, "/") | ||
if err != nil { | ||
return elemPath, err | ||
} | ||
return "", nil | ||
} | ||
|
||
// validateResourceElement detects the element type (map, array, nil, string, int, bool, float) | ||
// and calls corresponding handler | ||
// Pattern tree and resource tree can have different structure. In this case validation fails | ||
func validateResourceElement(log logr.Logger, resourceElement, patternElement, originPattern interface{}, path string) (string, error) { | ||
// var err error | ||
switch typedPatternElement := patternElement.(type) { | ||
// map | ||
case map[string]interface{}: | ||
typedResourceElement, ok := resourceElement.(map[string]interface{}) | ||
if !ok { | ||
log.V(4).Info("Pattern and resource have different structures.", "path", path, "expected", fmt.Sprintf("%T", patternElement), "current", fmt.Sprintf("%T", resourceElement)) | ||
return path, fmt.Errorf("Pattern and resource have different structures. Path: %s. Expected %T, found %T", path, patternElement, resourceElement) | ||
} | ||
return validateMap(log, typedResourceElement, typedPatternElement, originPattern, path) | ||
// array | ||
case []interface{}: | ||
typedResourceElement, ok := resourceElement.([]interface{}) | ||
if !ok { | ||
log.V(4).Info("Pattern and resource have different structures.", "path", path, "expected", fmt.Sprintf("%T", patternElement), "current", fmt.Sprintf("%T", resourceElement)) | ||
return path, fmt.Errorf("Validation rule Failed at path %s, resource does not satisfy the expected overlay pattern", path) | ||
} | ||
return validateArray(log, typedResourceElement, typedPatternElement, originPattern, path) | ||
// elementary values | ||
case string, float64, int, int64, bool, nil: | ||
if !validate.ValidateValueWithPattern(log, resourceElement, patternElement) { | ||
return path, fmt.Errorf("Validation rule failed at '%s' to validate value '%v' with pattern '%v'", path, resourceElement, patternElement) | ||
} | ||
|
||
default: | ||
log.V(4).Info("Pattern contains unknown type", "path", path, "current", fmt.Sprintf("%T", patternElement)) | ||
return path, fmt.Errorf("Validation rule failed at '%s', pattern contains unknown type", path) | ||
} | ||
return "", nil | ||
} | ||
|
||
// If validateResourceElement detects map element inside resource and pattern trees, it goes to validateMap | ||
// For each element of the map we must detect the type again, so we pass these elements to validateResourceElement | ||
func validateMap(log logr.Logger, resourceMap, patternMap map[string]interface{}, origPattern interface{}, path string) (string, error) { | ||
patternMap = wildcards.ExpandInMetadata(patternMap, resourceMap) | ||
sortedResourceKeys := list.New() | ||
for k := range patternMap { | ||
sortedResourceKeys.PushBack(k) | ||
} | ||
|
||
for e := sortedResourceKeys.Front(); e != nil; e = e.Next() { | ||
key := e.Value.(string) | ||
handler := NewHandler(key, patternMap[key], path) | ||
handlerPath, err := handler.Handle(validateResourceElement, resourceMap, origPattern) | ||
if err != nil { | ||
return handlerPath, err | ||
} | ||
} | ||
return "", nil | ||
} | ||
|
||
// If validateResourceElement detects array element inside resource and pattern trees, it goes to validateArray | ||
func validateArray(log logr.Logger, resourceArray, patternArray []interface{}, originPattern interface{}, path string) (string, error) { | ||
if 0 == len(patternArray) { | ||
return path, fmt.Errorf("Pattern Array empty") | ||
} | ||
|
||
switch patternArray[0].(type) { | ||
case map[string]interface{}: | ||
for _, patternElement := range patternArray { | ||
elemPath, err := validateArrayOfMaps(log, resourceArray, patternElement.(map[string]interface{}), originPattern, path) | ||
if err != nil { | ||
return elemPath, err | ||
} | ||
} | ||
default: | ||
if len(resourceArray) >= len(patternArray) { | ||
for i, patternElement := range patternArray { | ||
currentPath := path + strconv.Itoa(i) + "/" | ||
elemPath, err := validateResourceElement(log, resourceArray[i], patternElement, originPattern, currentPath) | ||
if err != nil { | ||
return elemPath, err | ||
} | ||
} | ||
} else { | ||
return "", fmt.Errorf("Validate Array failed, array length mismatch, resource Array len is %d and pattern Array len is %d", len(resourceArray), len(patternArray)) | ||
} | ||
} | ||
return "", nil | ||
} | ||
|
||
// Matches all the elements in resource with the pattern | ||
func validateArrayOfMaps(log logr.Logger, resourceMapArray []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string) (string, error) { | ||
lengthOflenResourceMapArray := len(resourceMapArray) - 1 | ||
for i, resourceElement := range resourceMapArray { | ||
currentPath := path + strconv.Itoa(i) + "/" | ||
returnpath, err := validateResourceElement(log, resourceElement, patternMap, originPattern, currentPath) | ||
if err != nil { | ||
if i < lengthOflenResourceMapArray { | ||
continue | ||
} | ||
return returnpath, err | ||
} | ||
break | ||
} | ||
return "", nil | ||
} | ||
|
||
func NewHandler(element string, pattern interface{}, path string) Handler { | ||
return Handler{ | ||
element: element, | ||
pattern: pattern, | ||
path: path, | ||
} | ||
} | ||
|
||
func (dh Handler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}) (string, error) { | ||
currentPath := dh.path + dh.element + "/" | ||
if dh.pattern == "*" && resourceMap[dh.element] != nil { | ||
return "", nil | ||
} else if dh.pattern == "*" && resourceMap[dh.element] == nil { | ||
return dh.path, fmt.Errorf("Validation rule failed at %s, Field %s is not present", dh.path, dh.element) | ||
} else { | ||
path, err := handler(log.Log, resourceMap[dh.element], dh.pattern, originPattern, currentPath) | ||
if err != nil { | ||
return path, err | ||
} | ||
} | ||
return "", nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package generate | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/go-logr/logr" | ||
"gotest.tools/assert" | ||
) | ||
|
||
func TestValidatePass(t *testing.T) { | ||
resource := map[string]interface{}{ | ||
"spec": map[string]interface{}{ | ||
"egress": map[string]interface{}{ | ||
"port": []interface{}{ | ||
map[string]interface{}{ | ||
"port": 5353, | ||
"protocol": "UDP", | ||
}, | ||
map[string]interface{}{ | ||
"port": 5353, | ||
"protocol": "TCP", | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
pattern := map[string]interface{}{ | ||
"spec": map[string]interface{}{ | ||
"egress": map[string]interface{}{ | ||
"port": []interface{}{ | ||
map[string]interface{}{ | ||
"port": 5353, | ||
"protocol": "UDP", | ||
}, | ||
map[string]interface{}{ | ||
"port": 5353, | ||
"protocol": "TCP", | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
var log logr.Logger | ||
_, err := ValidateResourceWithPattern(log, resource, pattern) | ||
assert.NilError(t, err) | ||
} | ||
|
||
func TestValidateFail(t *testing.T) { | ||
resource := map[string]interface{}{ | ||
"spec": map[string]interface{}{ | ||
"egress": map[string]interface{}{ | ||
"port": []interface{}{ | ||
map[string]interface{}{ | ||
"port": 5353, | ||
"protocol": "TCP", | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
pattern := map[string]interface{}{ | ||
"spec": map[string]interface{}{ | ||
"egress": map[string]interface{}{ | ||
"port": []interface{}{ | ||
map[string]interface{}{ | ||
"port": 5353, | ||
"protocol": "UDP", | ||
}, | ||
map[string]interface{}{ | ||
"port": 5353, | ||
"protocol": "TCP", | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
var log logr.Logger | ||
_, err := ValidateResourceWithPattern(log, resource, pattern) | ||
assert.Assert(t, err != nil) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters