Skip to content

Commit

Permalink
Added validate logic for generate to handle multiple items in array (#…
Browse files Browse the repository at this point in the history
…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
NoSkillGirl committed Mar 26, 2021
1 parent fd9acf2 commit a0ddd2c
Show file tree
Hide file tree
Showing 3 changed files with 240 additions and 1 deletion.
156 changes: 156 additions & 0 deletions pkg/generate/validate.go
@@ -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
}
82 changes: 82 additions & 0 deletions pkg/generate/validate_test.go
@@ -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)
}
3 changes: 2 additions & 1 deletion pkg/webhooks/generation.go
Expand Up @@ -20,6 +20,7 @@ import (
enginutils "github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/engine/validate"
"github.com/kyverno/kyverno/pkg/event"
gen "github.com/kyverno/kyverno/pkg/generate"
kyvernoutils "github.com/kyverno/kyverno/pkg/utils"
"github.com/kyverno/kyverno/pkg/webhooks/generate"
v1beta1 "k8s.io/api/admission/v1beta1"
Expand Down Expand Up @@ -149,7 +150,7 @@ func (ws *WebhookServer) handleUpdateTargetResource(request *v1beta1.AdmissionRe
if rule.Generation.Kind == targetSourceKind && rule.Generation.Name == targetSourceName {
data := rule.Generation.DeepCopy().Data
if data != nil {
if _, err := validate.ValidateResourceWithPattern(logger, newRes.Object, data); err != nil {
if _, err := gen.ValidateResourceWithPattern(logger, newRes.Object, data); err != nil {
enqueueBool = true
break
}
Expand Down

0 comments on commit a0ddd2c

Please sign in to comment.