1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-28 10:28:36 +00:00

Replaces manually written logic with regex for matching anchor elements (#6133)

* uses regular expressions

Signed-off-by: Vishal Choudhary <sendtovishalchoudhary@gmail.com>

* adds regex capture

Signed-off-by: Vishal Choudhary <sendtovishalchoudhary@gmail.com>

* creates anchor instance

Signed-off-by: Vishal Choudhary <sendtovishalchoudhary@gmail.com>

* remove IsAnchor

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* added interface

Signed-off-by: Vishal Choudhary <sendtovishalchoudhary@gmail.com>

* remove static funcs

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* adapt

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* value receiver

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* simplify

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* error

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* renames

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* private

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* nit

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* ficx

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* refactor

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* test

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* tests

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* test

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* error

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* unit tests

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* refactor

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* unit tests

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

---------

Signed-off-by: Vishal Choudhary <sendtovishalchoudhary@gmail.com>
Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
Co-authored-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
Co-authored-by: Charles-Edouard Brétéché <charled.breteche@gmail.com>
Co-authored-by: shuting <shuting@nirmata.com>
This commit is contained in:
Vishal Choudhary 2023-01-30 17:47:19 +05:30 committed by GitHub
parent fb94f6ea75
commit 48d9ebba2c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 1794 additions and 730 deletions

View file

@ -1,277 +1,123 @@
package anchor
import (
"fmt"
"strconv"
"github.com/go-logr/logr"
"github.com/kyverno/kyverno/pkg/logging"
"regexp"
"strings"
)
// ValidationHandler for element processes
type ValidationHandler interface {
Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorKey) (string, error)
type AnchorType string
const (
Condition AnchorType = ""
Global AnchorType = "<"
Negation AnchorType = "X"
AddIfNotPresent AnchorType = "+"
Equality AnchorType = "="
Existence AnchorType = "^"
)
var regex = regexp.MustCompile(`^(?P<modifier>[+<=X^])?\((?P<key>.+)\)$`)
// Anchor interface
type Anchor interface {
// Type returns the anchor type
Type() AnchorType
// Key returns the anchor key
Key() string
// String returns the anchor string
String() string
}
type resourceElementHandler = func(log logr.Logger, resourceElement, patternElement, originPattern interface{}, path string, ac *AnchorKey) (string, error)
type anchor struct {
modifier AnchorType
key string
}
// CreateElementHandler factory to process elements
func CreateElementHandler(element string, pattern interface{}, path string) ValidationHandler {
switch {
case IsConditionAnchor(element):
return NewConditionAnchorHandler(element, pattern, path)
case IsGlobalAnchor(element):
return NewGlobalAnchorHandler(element, pattern, path)
case IsExistenceAnchor(element):
return NewExistenceHandler(element, pattern, path)
case IsEqualityAnchor(element):
return NewEqualityHandler(element, pattern, path)
case IsNegationAnchor(element):
return NewNegationHandler(element, pattern, path)
default:
return NewDefaultHandler(element, pattern, path)
// Parse parses a string, returns nil if not an anchor
func Parse(str string) Anchor {
str = strings.TrimSpace(str)
values := regex.FindStringSubmatch(str)
if len(values) == 0 {
return nil
}
return New(AnchorType(values[1]), values[2])
}
// New creates an anchor
func New(modifier AnchorType, key string) Anchor {
if key == "" {
return nil
}
return anchor{
modifier: modifier,
key: key,
}
}
// NewNegationHandler returns instance of negation handler
func NewNegationHandler(anchor string, pattern interface{}, path string) ValidationHandler {
return NegationHandler{
anchor: anchor,
pattern: pattern,
path: path,
// String returns the anchor string.
// Will return an empty string if key is empty.
func String(modifier AnchorType, key string) string {
if key == "" {
return ""
}
return string(modifier) + "(" + key + ")"
}
// NegationHandler provides handler for check if the tag in anchor is not defined
type NegationHandler struct {
anchor string
pattern interface{}
path string
func (a anchor) Type() AnchorType {
return a.modifier
}
// Handle process negation handler
func (nh NegationHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorKey) (string, error) {
anchorKey, _ := RemoveAnchor(nh.anchor)
currentPath := nh.path + anchorKey + "/"
// if anchor is present in the resource then fail
if _, ok := resourceMap[anchorKey]; ok {
// no need to process elements in value as key cannot be present in resource
ac.AnchorError = NewNegationAnchorError(fmt.Sprintf("%s is not allowed", currentPath))
return currentPath, ac.AnchorError.Error()
}
// key is not defined in the resource
return "", nil
func (a anchor) Key() string {
return a.key
}
// NewEqualityHandler returens instance of equality handler
func NewEqualityHandler(anchor string, pattern interface{}, path string) ValidationHandler {
return EqualityHandler{
anchor: anchor,
pattern: pattern,
path: path,
}
func (a anchor) String() string {
return String(a.modifier, a.key)
}
// EqualityHandler provides handler for non anchor element
type EqualityHandler struct {
anchor string
pattern interface{}
path string
}
// Handle processed condition anchor
func (eh EqualityHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorKey) (string, error) {
anchorKey, _ := RemoveAnchor(eh.anchor)
currentPath := eh.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
// validate the values of the pattern
returnPath, err := handler(logging.GlobalLogger(), value, eh.pattern, originPattern, currentPath, ac)
if err != nil {
return returnPath, err
}
return "", nil
}
return "", nil
}
// NewDefaultHandler returns handler for non anchor elements
func NewDefaultHandler(element string, pattern interface{}, path string) ValidationHandler {
return DefaultHandler{
element: element,
pattern: pattern,
path: path,
}
}
// DefaultHandler provides handler for non anchor element
type DefaultHandler struct {
element string
pattern interface{}
path string
}
// Handle process non anchor element
func (dh DefaultHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorKey) (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("%s/%s not found", dh.path, dh.element)
} else {
path, err := handler(logging.GlobalLogger(), resourceMap[dh.element], dh.pattern, originPattern, currentPath, ac)
if err != nil {
return path, err
}
}
return "", nil
}
// NewConditionAnchorHandler returns an instance of condition acnhor handler
func NewConditionAnchorHandler(anchor string, pattern interface{}, path string) ValidationHandler {
return ConditionAnchorHandler{
anchor: anchor,
pattern: pattern,
path: path,
}
}
// ConditionAnchorHandler provides handler for condition anchor
type ConditionAnchorHandler struct {
anchor string
pattern interface{}
path string
}
// Handle processed condition anchor
func (ch ConditionAnchorHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorKey) (string, error) {
anchorKey, _ := RemoveAnchor(ch.anchor)
currentPath := ch.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
// validate the values of the pattern
returnPath, err := handler(logging.GlobalLogger(), value, ch.pattern, originPattern, currentPath, ac)
if err != nil {
ac.AnchorError = NewConditionalAnchorError(err.Error())
return returnPath, ac.AnchorError.Error()
}
return "", nil
} else {
msg := "conditional anchor key doesn't exist in the resource"
return currentPath, NewConditionalAnchorError(msg).Error()
}
}
// NewGlobalAnchorHandler returns an instance of condition acnhor handler
func NewGlobalAnchorHandler(anchor string, pattern interface{}, path string) ValidationHandler {
return GlobalAnchorHandler{
anchor: anchor,
pattern: pattern,
path: path,
}
}
// GlobalAnchorHandler provides handler for global condition anchor
type GlobalAnchorHandler struct {
anchor string
pattern interface{}
path string
}
// Handle processed global condition anchor
func (gh GlobalAnchorHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorKey) (string, error) {
anchorKey, _ := RemoveAnchor(gh.anchor)
currentPath := gh.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
// validate the values of the pattern
returnPath, err := handler(logging.GlobalLogger(), value, gh.pattern, originPattern, currentPath, ac)
if err != nil {
ac.AnchorError = NewGlobalAnchorError(err.Error())
return returnPath, ac.AnchorError.Error()
}
return "", nil
}
return "", nil
}
// NewExistenceHandler returns existence handler
func NewExistenceHandler(anchor string, pattern interface{}, path string) ValidationHandler {
return ExistenceHandler{
anchor: anchor,
pattern: pattern,
path: path,
}
}
// ExistenceHandler provides handlers to process exitence anchor handler
type ExistenceHandler struct {
anchor string
pattern interface{}
path string
}
// Handle processes the existence anchor handler
func (eh ExistenceHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorKey) (string, error) {
// skip is used by existence anchor to not process further if condition is not satisfied
anchorKey, _ := RemoveAnchor(eh.anchor)
currentPath := eh.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
// Existence anchor can only exist on resource value type of list
switch typedResource := value.(type) {
case []interface{}:
typedPattern, ok := eh.pattern.([]interface{})
if !ok {
return currentPath, fmt.Errorf("invalid pattern type %T: Pattern has to be of list to compare against resource", eh.pattern)
// IsOneOf returns checks if anchor is one of the given types
func IsOneOf(a Anchor, types ...AnchorType) bool {
if a != nil {
for _, t := range types {
if t == a.Type() {
return true
}
// loop all item in the pattern array
errorPath := ""
var err error
for _, patternMap := range typedPattern {
typedPatternMap, ok := patternMap.(map[string]interface{})
if !ok {
return currentPath, fmt.Errorf("invalid pattern type %T: Pattern has to be of type map to compare against items in resource", eh.pattern)
}
errorPath, err = validateExistenceListResource(handler, typedResource, typedPatternMap, originPattern, currentPath, ac)
if err != nil {
return errorPath, err
}
}
return errorPath, err
default:
return currentPath, fmt.Errorf("invalid resource type %T: Existence ^ () anchor can be used only on list/array type resource", value)
}
}
return "", nil
return false
}
func validateExistenceListResource(handler resourceElementHandler, resourceList []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string, ac *AnchorKey) (string, error) {
// the idea is all the element in the pattern array should be present atleast once in the resource list
// if non satisfy then throw an error
for i, resourceElement := range resourceList {
currentPath := path + strconv.Itoa(i) + "/"
_, err := handler(logging.GlobalLogger(), resourceElement, patternMap, originPattern, currentPath, ac)
if err == nil {
// condition is satisfied, dont check further
return "", nil
}
}
// none of the existence checks worked, so thats a failure sceanario
return path, fmt.Errorf("existence anchor validation failed at path %s", path)
// ContainsCondition returns true if anchor is either condition anchor or global condition anchor
func ContainsCondition(a Anchor) bool {
return IsOneOf(a, Condition, Global)
}
// GetAnchorsResourcesFromMap returns map of anchors
func GetAnchorsResourcesFromMap(patternMap map[string]interface{}) (map[string]interface{}, map[string]interface{}) {
anchors := map[string]interface{}{}
resources := map[string]interface{}{}
for key, value := range patternMap {
if IsConditionAnchor(key) || IsExistenceAnchor(key) || IsEqualityAnchor(key) || IsNegationAnchor(key) {
anchors[key] = value
continue
}
resources[key] = value
}
return anchors, resources
// IsCondition checks for condition anchor
func IsCondition(a Anchor) bool {
return IsOneOf(a, Condition)
}
// IsGlobal checks for global condition anchor
func IsGlobal(a Anchor) bool {
return IsOneOf(a, Global)
}
// IsNegation checks for negation anchor
func IsNegation(a Anchor) bool {
return IsOneOf(a, Negation)
}
// IsAddIfNotPresent checks for addition anchor
func IsAddIfNotPresent(a Anchor) bool {
return IsOneOf(a, AddIfNotPresent)
}
// IsEquality checks for equality anchor
func IsEquality(a Anchor) bool {
return IsOneOf(a, Equality)
}
// IsExistence checks for existence anchor
func IsExistence(a Anchor) bool {
return IsOneOf(a, Existence)
}

View file

@ -1,167 +0,0 @@
package anchor
import (
"errors"
"fmt"
"strings"
)
// IsNegationAnchorError checks if error message has negation anchor error string
func IsNegationAnchorError(msg string) bool {
return strings.Contains(msg, NegationAnchorErrMsg)
}
// IsConditionalAnchorError checks if error message has conditional anchor error string
func IsConditionalAnchorError(msg string) bool {
return strings.Contains(msg, ConditionalAnchorErrMsg)
}
// IsGlobalAnchorError checks if error message has global anchor error string
func IsGlobalAnchorError(msg string) bool {
return strings.Contains(msg, GlobalAnchorErrMsg)
}
// NewNegationAnchorError returns a new instance of NegationAnchorError
func NewNegationAnchorError(msg string) ValidateAnchorError {
return ValidateAnchorError{
Err: NegationAnchorErr,
Message: fmt.Sprintf("%s: %s", NegationAnchorErrMsg, msg),
}
}
// IsNegationAnchorError checks if the error is a negation anchor error
func (e ValidateAnchorError) IsNegationAnchorError() bool {
return e.Err == NegationAnchorErr
}
// NewConditionalAnchorError returns a new instance of ConditionalAnchorError
func NewConditionalAnchorError(msg string) ValidateAnchorError {
return ValidateAnchorError{
Err: ConditionalAnchorErr,
Message: fmt.Sprintf("%s: %s", ConditionalAnchorErrMsg, msg),
}
}
// IsConditionAnchorError checks if the error is a conditional anchor error
func (e ValidateAnchorError) IsConditionAnchorError() bool {
return e.Err == ConditionalAnchorErr
}
// NewGlobalAnchorError returns a new instance of GlobalAnchorError
func NewGlobalAnchorError(msg string) ValidateAnchorError {
return ValidateAnchorError{
Err: GlobalAnchorErr,
Message: fmt.Sprintf("%s: %s", GlobalAnchorErrMsg, msg),
}
}
// IsGlobalAnchorError checks if the error is a global anchor error
func (e ValidateAnchorError) IsGlobalAnchorError() bool {
return e.Err == GlobalAnchorErr
}
// IsNil checks if the error isn't populated
func (e ValidateAnchorError) IsNil() bool {
return e == ValidateAnchorError{}
}
// Error returns an error instance of the anchor error
func (e ValidateAnchorError) Error() error {
return errors.New(e.Message)
}
// AnchorError is the const specification of anchor errors
type AnchorError int
const (
// ConditionalAnchorErr refers to condition violation
ConditionalAnchorErr AnchorError = iota
// GlobalAnchorErr refers to global condition violation
GlobalAnchorErr
// NegationAnchorErr refers to negation violation
NegationAnchorErr
)
// ValidateAnchorError represents the error type of validation anchors
type ValidateAnchorError struct {
Err AnchorError
Message string
}
// NegationAnchorErrMsg - the error message for negation anchor error
var NegationAnchorErrMsg = "negation anchor matched in resource"
// ConditionalAnchorErrMsg - the error message for conditional anchor error
var ConditionalAnchorErrMsg = "conditional anchor mismatch"
// GlobalAnchorErrMsg - the error message for global anchor error
var GlobalAnchorErrMsg = "global anchor mismatch"
// AnchorKey - contains map of anchors
type AnchorKey struct {
// anchorMap - for each anchor key in the patterns it will maintain information if the key exists in the resource
// if anchor key of the pattern exists in the resource then (key)=true else (key)=false
anchorMap map[string]bool
// AnchorError - used in validate to break execution of the recursion when if condition fails
AnchorError ValidateAnchorError
}
// NewAnchorMap -initialize anchorMap
func NewAnchorMap() *AnchorKey {
return &AnchorKey{anchorMap: make(map[string]bool)}
}
// IsAnchorError - if any of the anchor key doesn't exists in the resource then it will return true
// if any of (key)=false then return IsAnchorError() as true
// if all the keys exists in the pattern exists in resource then return IsAnchorError() as false
func (ac *AnchorKey) IsAnchorError() bool {
for _, v := range ac.anchorMap {
if !v {
return true
}
}
return false
}
// CheckAnchorInResource checks if condition anchor key has values
func (ac *AnchorKey) CheckAnchorInResource(pattern interface{}, resource interface{}) {
switch typed := pattern.(type) {
case map[string]interface{}:
for key := range typed {
if IsConditionAnchor(key) || IsExistenceAnchor(key) || IsNegationAnchor(key) {
val, ok := ac.anchorMap[key]
if !ok {
ac.anchorMap[key] = false
} else if ok && val {
continue
}
if doesAnchorsKeyHasValue(key, resource) {
ac.anchorMap[key] = true
}
}
}
}
}
// Checks if anchor key has value in resource
func doesAnchorsKeyHasValue(key string, resource interface{}) bool {
akey, _ := RemoveAnchor(key)
switch typed := resource.(type) {
case map[string]interface{}:
if _, ok := typed[akey]; ok {
return true
}
return false
case []interface{}:
for _, value := range typed {
if doesAnchorsKeyHasValue(key, value) {
return true
}
}
return false
default:
return false
}
}

View file

@ -0,0 +1,653 @@
package anchor
import (
"reflect"
"testing"
)
func TestNew(t *testing.T) {
type args struct {
modifier AnchorType
key string
}
tests := []struct {
name string
args args
want Anchor
}{{
args: args{Condition, ""},
want: nil,
}, {
args: args{Global, ""},
want: nil,
}, {
args: args{Negation, ""},
want: nil,
}, {
args: args{AddIfNotPresent, ""},
want: nil,
}, {
args: args{Equality, ""},
want: nil,
}, {
args: args{Existence, ""},
want: nil,
}, {
args: args{Condition, "test"},
want: anchor{Condition, "test"},
}, {
args: args{Global, "test"},
want: anchor{Global, "test"},
}, {
args: args{Negation, "test"},
want: anchor{Negation, "test"},
}, {
args: args{AddIfNotPresent, "test"},
want: anchor{AddIfNotPresent, "test"},
}, {
args: args{Equality, "test"},
want: anchor{Equality, "test"},
}, {
args: args{Existence, "test"},
want: anchor{Existence, "test"},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := New(tt.args.modifier, tt.args.key); !reflect.DeepEqual(got, tt.want) {
t.Errorf("New() = %v, want %v", got, tt.want)
}
})
}
}
func TestString(t *testing.T) {
type args struct {
modifier AnchorType
key string
}
tests := []struct {
name string
args args
want string
}{{
args: args{Condition, ""},
want: "",
}, {
args: args{Global, ""},
want: "",
}, {
args: args{Negation, ""},
want: "",
}, {
args: args{AddIfNotPresent, ""},
want: "",
}, {
args: args{Equality, ""},
want: "",
}, {
args: args{Existence, ""},
want: "",
}, {
args: args{Condition, "test"},
want: "(test)",
}, {
args: args{Global, "test"},
want: "<(test)",
}, {
args: args{Negation, "test"},
want: "X(test)",
}, {
args: args{AddIfNotPresent, "test"},
want: "+(test)",
}, {
args: args{Equality, "test"},
want: "=(test)",
}, {
args: args{Existence, "test"},
want: "^(test)",
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := String(tt.args.modifier, tt.args.key); got != tt.want {
t.Errorf("String() = %v, want %v", got, tt.want)
}
})
}
}
func TestIsOneOf(t *testing.T) {
type args struct {
a Anchor
types []AnchorType
}
tests := []struct {
name string
args args
want bool
}{{
args: args{},
want: false,
}, {
args: args{nil, []AnchorType{Condition, Negation}},
want: false,
}, {
args: args{New(Condition, "test"), nil},
want: false,
}, {
args: args{New(Condition, "test"), []AnchorType{}},
want: false,
}, {
args: args{New(Condition, "test"), []AnchorType{Condition}},
want: true,
}, {
args: args{New(Condition, "test"), []AnchorType{Condition, Negation}},
want: true,
}, {
args: args{New(Condition, "test"), []AnchorType{Negation, Global}},
want: false,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsOneOf(tt.args.a, tt.args.types...); got != tt.want {
t.Errorf("IsOneOf() = %v, want %v", got, tt.want)
}
})
}
}
func TestParse(t *testing.T) {
type args struct {
str string
}
tests := []struct {
name string
args args
want Anchor
}{
{
args: args{"(something)"},
want: anchor{Condition, "something"},
}, {
args: args{"()"},
want: nil,
}, {
args: args{"something"},
want: nil,
}, {
args: args{"(something"},
want: nil,
}, {
args: args{"something)"},
want: nil,
}, {
args: args{"so)m(et(hin)g"},
want: nil,
}, {
args: args{""},
want: nil,
}, {
args: args{"^(abc)"},
want: anchor{Existence, "abc"},
}, {
args: args{"^(abc"},
want: nil,
}, {
args: args{"^abc"},
want: nil,
}, {
args: args{"^()"},
want: nil,
}, {
args: args{"(abc)"},
want: anchor{Condition, "abc"},
}, {
args: args{"=(abc)"},
want: anchor{Equality, "abc"},
}, {
args: args{"=(abc"},
want: nil,
}, {
args: args{"=abc"},
want: nil,
}, {
args: args{"+(abc)"},
want: anchor{AddIfNotPresent, "abc"},
}, {
args: args{"+(abc"},
want: nil,
}, {
args: args{"+abc"},
want: nil,
}, {
args: args{"X(abc)"},
want: anchor{Negation, "abc"},
}, {
args: args{"X(abc"},
want: nil,
}, {
args: args{"Xabc"},
want: nil,
}, {
args: args{"<(abc)"},
want: anchor{Global, "abc"},
}, {
args: args{"<(abc"},
want: nil,
}, {
args: args{"<abc"},
want: nil,
}, {
args: args{"(abc)"},
want: anchor{Condition, "abc"},
}, {
args: args{"(abc"},
want: nil,
}, {
args: args{"abc"},
want: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Parse(tt.args.str); !reflect.DeepEqual(got, tt.want) {
t.Errorf("Parse() = %v, want %v", got, tt.want)
}
})
}
}
func Test_anchor_Type(t *testing.T) {
type fields struct {
modifier AnchorType
key string
}
tests := []struct {
name string
fields fields
want AnchorType
}{{
fields: fields{Condition, "abc"},
want: Condition,
}, {
fields: fields{Global, "abc"},
want: Global,
}, {
fields: fields{Negation, "abc"},
want: Negation,
}, {
fields: fields{AddIfNotPresent, "abc"},
want: AddIfNotPresent,
}, {
fields: fields{Equality, "abc"},
want: Equality,
}, {
fields: fields{Existence, "abc"},
want: Existence,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := anchor{
modifier: tt.fields.modifier,
key: tt.fields.key,
}
if got := a.Type(); got != tt.want {
t.Errorf("anchor.Type() = %v, want %v", got, tt.want)
}
})
}
}
func Test_anchor_Key(t *testing.T) {
type fields struct {
modifier AnchorType
key string
}
tests := []struct {
name string
fields fields
want string
}{{
fields: fields{Condition, "abc"},
want: "abc",
}, {
fields: fields{Global, "abc"},
want: "abc",
}, {
fields: fields{Negation, "abc"},
want: "abc",
}, {
fields: fields{AddIfNotPresent, "abc"},
want: "abc",
}, {
fields: fields{Equality, "abc"},
want: "abc",
}, {
fields: fields{Existence, "abc"},
want: "abc",
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := anchor{
modifier: tt.fields.modifier,
key: tt.fields.key,
}
if got := a.Key(); got != tt.want {
t.Errorf("anchor.Key() = %v, want %v", got, tt.want)
}
})
}
}
func Test_anchor_String(t *testing.T) {
type fields struct {
modifier AnchorType
key string
}
tests := []struct {
name string
fields fields
want string
}{{
fields: fields{Condition, "abc"},
want: "(abc)",
}, {
fields: fields{Global, "abc"},
want: "<(abc)",
}, {
fields: fields{Negation, "abc"},
want: "X(abc)",
}, {
fields: fields{AddIfNotPresent, "abc"},
want: "+(abc)",
}, {
fields: fields{Equality, "abc"},
want: "=(abc)",
}, {
fields: fields{Existence, "abc"},
want: "^(abc)",
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := anchor{
modifier: tt.fields.modifier,
key: tt.fields.key,
}
if got := a.String(); got != tt.want {
t.Errorf("anchor.String() = %v, want %v", got, tt.want)
}
})
}
}
func TestIsCondition(t *testing.T) {
type args struct {
a Anchor
}
tests := []struct {
name string
args args
want bool
}{{
args: args{nil},
want: false,
}, {
args: args{New(Condition, "abc")},
want: true,
}, {
args: args{New(Global, "abc")},
want: false,
}, {
args: args{New(Negation, "abc")},
want: false,
}, {
args: args{New(AddIfNotPresent, "abc")},
want: false,
}, {
args: args{New(Equality, "abc")},
want: false,
}, {
args: args{New(Existence, "abc")},
want: false,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsCondition(tt.args.a); got != tt.want {
t.Errorf("IsCondition() = %v, want %v", got, tt.want)
}
})
}
}
func TestIsGlobal(t *testing.T) {
type args struct {
a Anchor
}
tests := []struct {
name string
args args
want bool
}{{
args: args{nil},
want: false,
}, {
args: args{New(Condition, "abc")},
want: false,
}, {
args: args{New(Global, "abc")},
want: true,
}, {
args: args{New(Negation, "abc")},
want: false,
}, {
args: args{New(AddIfNotPresent, "abc")},
want: false,
}, {
args: args{New(Equality, "abc")},
want: false,
}, {
args: args{New(Existence, "abc")},
want: false,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsGlobal(tt.args.a); got != tt.want {
t.Errorf("IsGlobal() = %v, want %v", got, tt.want)
}
})
}
}
func TestIsNegation(t *testing.T) {
type args struct {
a Anchor
}
tests := []struct {
name string
args args
want bool
}{{
args: args{nil},
want: false,
}, {
args: args{New(Condition, "abc")},
want: false,
}, {
args: args{New(Global, "abc")},
want: false,
}, {
args: args{New(Negation, "abc")},
want: true,
}, {
args: args{New(AddIfNotPresent, "abc")},
want: false,
}, {
args: args{New(Equality, "abc")},
want: false,
}, {
args: args{New(Existence, "abc")},
want: false,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsNegation(tt.args.a); got != tt.want {
t.Errorf("IsNegation() = %v, want %v", got, tt.want)
}
})
}
}
func TestIsAddIfNotPresent(t *testing.T) {
type args struct {
a Anchor
}
tests := []struct {
name string
args args
want bool
}{{
args: args{nil},
want: false,
}, {
args: args{New(Condition, "abc")},
want: false,
}, {
args: args{New(Global, "abc")},
want: false,
}, {
args: args{New(Negation, "abc")},
want: false,
}, {
args: args{New(AddIfNotPresent, "abc")},
want: true,
}, {
args: args{New(Equality, "abc")},
want: false,
}, {
args: args{New(Existence, "abc")},
want: false,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsAddIfNotPresent(tt.args.a); got != tt.want {
t.Errorf("IsAddIfNotPresent() = %v, want %v", got, tt.want)
}
})
}
}
func TestIsEquality(t *testing.T) {
type args struct {
a Anchor
}
tests := []struct {
name string
args args
want bool
}{{
args: args{nil},
want: false,
}, {
args: args{New(Condition, "abc")},
want: false,
}, {
args: args{New(Global, "abc")},
want: false,
}, {
args: args{New(Negation, "abc")},
want: false,
}, {
args: args{New(AddIfNotPresent, "abc")},
want: false,
}, {
args: args{New(Equality, "abc")},
want: true,
}, {
args: args{New(Existence, "abc")},
want: false,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsEquality(tt.args.a); got != tt.want {
t.Errorf("IsEquality() = %v, want %v", got, tt.want)
}
})
}
}
func TestIsExistence(t *testing.T) {
type args struct {
a Anchor
}
tests := []struct {
name string
args args
want bool
}{{
args: args{nil},
want: false,
}, {
args: args{New(Condition, "abc")},
want: false,
}, {
args: args{New(Global, "abc")},
want: false,
}, {
args: args{New(Negation, "abc")},
want: false,
}, {
args: args{New(AddIfNotPresent, "abc")},
want: false,
}, {
args: args{New(Equality, "abc")},
want: false,
}, {
args: args{New(Existence, "abc")},
want: true,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsExistence(tt.args.a); got != tt.want {
t.Errorf("IsExistence() = %v, want %v", got, tt.want)
}
})
}
}
func TestContainsCondition(t *testing.T) {
type args struct {
a Anchor
}
tests := []struct {
name string
args args
want bool
}{{
args: args{nil},
want: false,
}, {
args: args{New(Condition, "abc")},
want: true,
}, {
args: args{New(Global, "abc")},
want: true,
}, {
args: args{New(Negation, "abc")},
want: false,
}, {
args: args{New(AddIfNotPresent, "abc")},
want: false,
}, {
args: args{New(Equality, "abc")},
want: false,
}, {
args: args{New(Existence, "abc")},
want: false,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ContainsCondition(tt.args.a); got != tt.want {
t.Errorf("ContainsCondition() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -0,0 +1,44 @@
package anchor
// AnchorMap - contains map of anchors
type AnchorMap struct {
// anchorMap - for each anchor key in the patterns it will maintain information if the key exists in the resource
// if anchor key of the pattern exists in the resource then (key)=true else (key)=false
anchorMap map[string]bool
// AnchorError - used in validate to break execution of the recursion when if condition fails
AnchorError validateAnchorError
}
// NewAnchorMap -initialize anchorMap
func NewAnchorMap() *AnchorMap {
return &AnchorMap{anchorMap: map[string]bool{}}
}
// KeysAreMissing - if any of the anchor key doesn't exists in the resource then it will return true
// if any of (key)=false then return KeysAreMissing() as true
// if all the keys exists in the pattern exists in resource then return KeysAreMissing() as false
func (ac *AnchorMap) KeysAreMissing() bool {
for _, v := range ac.anchorMap {
if !v {
return true
}
}
return false
}
// CheckAnchorInResource checks if condition anchor key has values
func (ac *AnchorMap) CheckAnchorInResource(pattern map[string]interface{}, resource interface{}) {
for key := range pattern {
if a := Parse(key); IsCondition(a) || IsExistence(a) || IsNegation(a) {
val, ok := ac.anchorMap[key]
if !ok {
ac.anchorMap[key] = false
} else if ok && val {
continue
}
if resourceHasValueForKey(resource, a.Key()) {
ac.anchorMap[key] = true
}
}
}
}

View file

@ -0,0 +1,66 @@
package anchor
import (
"reflect"
"testing"
)
func TestNewAnchorMap(t *testing.T) {
tests := []struct {
name string
want *AnchorMap
}{{
want: &AnchorMap{anchorMap: map[string]bool{}},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewAnchorMap(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewAnchorMap() = %v, want %v", got, tt.want)
}
})
}
}
func TestAnchorMap_KeysAreMissing(t *testing.T) {
type fields struct {
anchorMap map[string]bool
AnchorError validateAnchorError
}
tests := []struct {
name string
fields fields
want bool
}{{
fields: fields{
anchorMap: map[string]bool{},
},
want: false,
}, {
fields: fields{
anchorMap: map[string]bool{
"a": true,
"b": false,
},
},
want: true,
}, {
fields: fields{
anchorMap: map[string]bool{
"a": true,
"b": true,
},
},
want: false,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ac := &AnchorMap{
anchorMap: tt.fields.anchorMap,
AnchorError: tt.fields.AnchorError,
}
if got := ac.KeysAreMissing(); got != tt.want {
t.Errorf("AnchorMap.KeysAreMissing() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -1,127 +0,0 @@
package anchor
import (
"path"
"strings"
)
// IsAnchor is a function handler
type IsAnchor func(str string) bool
// IsConditionAnchor checks for condition anchor
func IsConditionAnchor(str string) bool {
if len(str) < 2 {
return false
}
return (str[0] == '(' && str[len(str)-1] == ')')
}
// IsGlobalAnchor checks for global condition anchor
func IsGlobalAnchor(str string) bool {
left := "<("
right := ")"
if len(str) < len(left)+len(right) {
return false
}
leftMatch := strings.TrimSpace(str[:len(left)]) == left
rightMatch := strings.TrimSpace(str[len(str)-len(right):]) == right
return leftMatch && rightMatch
}
// ContainsCondition returns true, if str is either condition anchor or
// global condition anchor
func ContainsCondition(str string) bool {
return IsConditionAnchor(str) || IsGlobalAnchor(str)
}
// IsNegationAnchor checks for negation anchor
func IsNegationAnchor(str string) bool {
left := "X("
right := ")"
if len(str) < len(left)+len(right) {
return false
}
// TODO: trim spaces ?
return (str[:len(left)] == left && str[len(str)-len(right):] == right)
}
// IsAddIfNotPresentAnchor checks for addition anchor
func IsAddIfNotPresentAnchor(key string) bool {
const left = "+("
const right = ")"
if len(key) < len(left)+len(right) {
return false
}
return left == key[:len(left)] && right == key[len(key)-len(right):]
}
// IsEqualityAnchor checks for equality anchor
func IsEqualityAnchor(str string) bool {
left := "=("
right := ")"
if len(str) < len(left)+len(right) {
return false
}
// TODO: trim spaces ?
return (str[:len(left)] == left && str[len(str)-len(right):] == right)
}
// IsExistenceAnchor checks for existence anchor
func IsExistenceAnchor(str string) bool {
left := "^("
right := ")"
if len(str) < len(left)+len(right) {
return false
}
return (str[:len(left)] == left && str[len(str)-len(right):] == right)
}
// IsNonAnchor checks that key does not have any anchor
func IsNonAnchor(str string) bool {
key, _ := RemoveAnchor(str)
return str == key
}
// RemoveAnchor remove anchor from the given key. It returns
// the anchor-free tag value and the prefix of the anchor.
func RemoveAnchor(key string) (string, string) {
if IsConditionAnchor(key) {
return key[1 : len(key)-1], key[0:1]
}
if IsExistenceAnchor(key) || IsAddIfNotPresentAnchor(key) || IsEqualityAnchor(key) || IsNegationAnchor(key) || IsGlobalAnchor(key) {
return key[2 : len(key)-1], key[0:2]
}
return key, ""
}
// RemoveAnchorsFromPath removes all anchor from path string
func RemoveAnchorsFromPath(str string) string {
components := strings.Split(str, "/")
if components[0] == "" {
components = components[1:]
}
for i, component := range components {
components[i], _ = RemoveAnchor(component)
}
newPath := path.Join(components...)
if path.IsAbs(str) {
newPath = "/" + newPath
}
return newPath
}
// AddAnchor adds an anchor with the supplied prefix.
// The suffix is assumed to be ")".
func AddAnchor(key, anchorPrefix string) string {
return anchorPrefix + key + ")"
}

View file

@ -1,68 +0,0 @@
package anchor
import (
"testing"
"gotest.tools/assert"
)
func TestWrappedWithParentheses_StringIsWrappedWithParentheses(t *testing.T) {
str := "(something)"
assert.Assert(t, IsConditionAnchor(str))
}
func TestWrappedWithParentheses_StringHasOnlyParentheses(t *testing.T) {
str := "()"
assert.Assert(t, IsConditionAnchor(str))
}
func TestWrappedWithParentheses_StringHasNoParentheses(t *testing.T) {
str := "something"
assert.Assert(t, !IsConditionAnchor(str))
}
func TestWrappedWithParentheses_StringHasLeftParentheses(t *testing.T) {
str := "(something"
assert.Assert(t, !IsConditionAnchor(str))
}
func TestWrappedWithParentheses_StringHasRightParentheses(t *testing.T) {
str := "something)"
assert.Assert(t, !IsConditionAnchor(str))
}
func TestWrappedWithParentheses_StringParenthesesInside(t *testing.T) {
str := "so)m(et(hin)g"
assert.Assert(t, !IsConditionAnchor(str))
}
func TestWrappedWithParentheses_Empty(t *testing.T) {
str := ""
assert.Assert(t, !IsConditionAnchor(str))
}
func TestIsExistenceAnchor_Yes(t *testing.T) {
assert.Assert(t, IsExistenceAnchor("^(abc)"))
}
func TestIsExistenceAnchor_NoRightBracket(t *testing.T) {
assert.Assert(t, !IsExistenceAnchor("^(abc"))
}
func TestIsExistenceAnchor_OnlyHat(t *testing.T) {
assert.Assert(t, !IsExistenceAnchor("^abc"))
}
func TestIsExistenceAnchor_ConditionAnchor(t *testing.T) {
assert.Assert(t, !IsExistenceAnchor("(abc)"))
}
func TestRemoveAnchorsFromPath_WorksWithAbsolutePath(t *testing.T) {
newPath := RemoveAnchorsFromPath("/path/(to)/X(anchors)")
assert.Equal(t, newPath, "/path/to/anchors")
}
func TestRemoveAnchorsFromPath_WorksWithRelativePath(t *testing.T) {
newPath := RemoveAnchorsFromPath("path/(to)/X(anchors)")
assert.Equal(t, newPath, "path/to/anchors")
}

View file

@ -0,0 +1,89 @@
package anchor
import (
"fmt"
"strings"
)
// anchorError is the const specification of anchor errors
type anchorError int
const (
// conditionalAnchorErr refers to condition violation
conditionalAnchorErr anchorError = iota
// globalAnchorErr refers to global condition violation
globalAnchorErr
// negationAnchorErr refers to negation violation
negationAnchorErr
)
const (
// negationAnchorErrMsg - the error message for negation anchor error
negationAnchorErrMsg = "negation anchor matched in resource"
// conditionalAnchorErrMsg - the error message for conditional anchor error
conditionalAnchorErrMsg = "conditional anchor mismatch"
// globalAnchorErrMsg - the error message for global anchor error
globalAnchorErrMsg = "global anchor mismatch"
)
// validateAnchorError represents the error type of validation anchors
type validateAnchorError struct {
err anchorError
message string
}
// Error implements error interface
func (e validateAnchorError) Error() string {
return e.message
}
// newNegationAnchorError returns a new instance of validateAnchorError
func newValidateAnchorError(err anchorError, prefix, msg string) validateAnchorError {
return validateAnchorError{
err: err,
message: fmt.Sprintf("%s: %s", prefix, msg),
}
}
// newNegationAnchorError returns a new instance of NegationAnchorError
func newNegationAnchorError(msg string) validateAnchorError {
return newValidateAnchorError(negationAnchorErr, negationAnchorErrMsg, msg)
}
// newConditionalAnchorError returns a new instance of ConditionalAnchorError
func newConditionalAnchorError(msg string) validateAnchorError {
return newValidateAnchorError(conditionalAnchorErr, conditionalAnchorErrMsg, msg)
}
// newGlobalAnchorError returns a new instance of GlobalAnchorError
func newGlobalAnchorError(msg string) validateAnchorError {
return newValidateAnchorError(globalAnchorErr, globalAnchorErrMsg, msg)
}
// isError checks if error matches the given error type
func isError(err error, code anchorError, msg string) bool {
if err != nil {
if t, ok := err.(validateAnchorError); ok {
return t.err == code
} else {
// TODO: we shouldn't need this, error is not properly propagated
return strings.Contains(err.Error(), msg)
}
}
return false
}
// IsNegationAnchorError checks if error is a negation anchor error
func IsNegationAnchorError(err error) bool {
return isError(err, negationAnchorErr, negationAnchorErrMsg)
}
// IsConditionalAnchorError checks if error is a conditional anchor error
func IsConditionalAnchorError(err error) bool {
return isError(err, conditionalAnchorErr, conditionalAnchorErrMsg)
}
// IsGlobalAnchorError checks if error is a global global anchor error
func IsGlobalAnchorError(err error) bool {
return isError(err, globalAnchorErr, globalAnchorErrMsg)
}

View file

@ -0,0 +1,276 @@
package anchor
import (
"errors"
"reflect"
"testing"
)
func Test_validateAnchorError_Error(t *testing.T) {
type fields struct {
err anchorError
message string
}
tests := []struct {
name string
fields fields
want string
}{{
fields: fields{
err: negationAnchorErr,
message: "test",
},
want: "test",
}, {
fields: fields{
err: conditionalAnchorErr,
message: "test",
},
want: "test",
}, {
fields: fields{
err: globalAnchorErr,
message: "test",
},
want: "test",
}, {
fields: fields{
err: globalAnchorErr,
message: "",
},
want: "",
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
e := validateAnchorError{
err: tt.fields.err,
message: tt.fields.message,
}
if got := e.Error(); got != tt.want {
t.Errorf("validateAnchorError.Error() = %v, want %v", got, tt.want)
}
})
}
}
func Test_newNegationAnchorError(t *testing.T) {
type args struct {
msg string
}
tests := []struct {
name string
args args
want validateAnchorError
}{{
args: args{
msg: "test",
},
want: validateAnchorError{
err: negationAnchorErr,
message: "negation anchor matched in resource: test",
},
}, {
want: validateAnchorError{
err: negationAnchorErr,
message: "negation anchor matched in resource: ",
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := newNegationAnchorError(tt.args.msg); !reflect.DeepEqual(got, tt.want) {
t.Errorf("newNegationAnchorError() = %v, want %v", got, tt.want)
}
})
}
}
func Test_newConditionalAnchorError(t *testing.T) {
type args struct {
msg string
}
tests := []struct {
name string
args args
want validateAnchorError
}{{
args: args{
msg: "test",
},
want: validateAnchorError{
err: conditionalAnchorErr,
message: "conditional anchor mismatch: test",
},
}, {
want: validateAnchorError{
err: conditionalAnchorErr,
message: "conditional anchor mismatch: ",
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := newConditionalAnchorError(tt.args.msg); !reflect.DeepEqual(got, tt.want) {
t.Errorf("newConditionalAnchorError() = %v, want %v", got, tt.want)
}
})
}
}
func Test_newGlobalAnchorError(t *testing.T) {
type args struct {
msg string
}
tests := []struct {
name string
args args
want validateAnchorError
}{{
args: args{
msg: "test",
},
want: validateAnchorError{
err: globalAnchorErr,
message: "global anchor mismatch: test",
},
}, {
want: validateAnchorError{
err: globalAnchorErr,
message: "global anchor mismatch: ",
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := newGlobalAnchorError(tt.args.msg); !reflect.DeepEqual(got, tt.want) {
t.Errorf("newGlobalAnchorError() = %v, want %v", got, tt.want)
}
})
}
}
func TestIsNegationAnchorError(t *testing.T) {
type args struct {
err error
}
tests := []struct {
name string
args args
want bool
}{{
args: args{
err: nil,
},
want: false,
}, {
args: args{
err: errors.New("negation anchor matched in resource: test"),
},
want: true,
}, {
args: args{
err: newConditionalAnchorError("test"),
},
want: false,
}, {
args: args{
err: newGlobalAnchorError("test"),
},
want: false,
}, {
args: args{
err: newNegationAnchorError("test"),
},
want: true,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsNegationAnchorError(tt.args.err); got != tt.want {
t.Errorf("IsNegationAnchorError() = %v, want %v", got, tt.want)
}
})
}
}
func TestIsConditionalAnchorError(t *testing.T) {
type args struct {
err error
}
tests := []struct {
name string
args args
want bool
}{{
args: args{
err: nil,
},
want: false,
}, {
args: args{
err: errors.New("conditional anchor mismatch: test"),
},
want: true,
}, {
args: args{
err: newConditionalAnchorError("test"),
},
want: true,
}, {
args: args{
err: newGlobalAnchorError("test"),
},
want: false,
}, {
args: args{
err: newNegationAnchorError("test"),
},
want: false,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsConditionalAnchorError(tt.args.err); got != tt.want {
t.Errorf("IsConditionalAnchorError() = %v, want %v", got, tt.want)
}
})
}
}
func TestIsGlobalAnchorError(t *testing.T) {
type args struct {
err error
}
tests := []struct {
name string
args args
want bool
}{{
args: args{
err: nil,
},
want: false,
}, {
args: args{
err: errors.New("global anchor mismatch: test"),
},
want: true,
}, {
args: args{
err: newConditionalAnchorError("test"),
},
want: false,
}, {
args: args{
err: newGlobalAnchorError("test"),
},
want: true,
}, {
args: args{
err: newNegationAnchorError("test"),
},
want: false,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsGlobalAnchorError(tt.args.err); got != tt.want {
t.Errorf("IsGlobalAnchorError() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -0,0 +1,275 @@
package anchor
import (
"fmt"
"strconv"
"github.com/go-logr/logr"
"github.com/kyverno/kyverno/pkg/logging"
)
type resourceElementHandler = func(
log logr.Logger,
resourceElement interface{},
patternElement interface{},
originPattern interface{},
path string,
ac *AnchorMap,
) (string, error)
// ValidationHandler for element processes
type ValidationHandler interface {
Handle(
handler resourceElementHandler,
resourceMap map[string]interface{},
originPattern interface{},
ac *AnchorMap,
) (string, error)
}
// CreateElementHandler factory to process elements
func CreateElementHandler(element string, pattern interface{}, path string) ValidationHandler {
if anchor := Parse(element); anchor != nil {
switch {
case IsCondition(anchor):
return newConditionAnchorHandler(anchor, pattern, path)
case IsGlobal(anchor):
return newGlobalAnchorHandler(anchor, pattern, path)
case IsExistence(anchor):
return newExistenceHandler(anchor, pattern, path)
case IsEquality(anchor):
return newEqualityHandler(anchor, pattern, path)
case IsNegation(anchor):
return newNegationHandler(anchor, pattern, path)
}
}
return newDefaultHandler(element, pattern, path)
}
// negationHandler provides handler for check if the tag in anchor is not defined
type negationHandler struct {
anchor Anchor
pattern interface{}
path string
}
// newNegationHandler returns instance of negation handler
func newNegationHandler(anchor Anchor, pattern interface{}, path string) ValidationHandler {
return negationHandler{
anchor: anchor,
pattern: pattern,
path: path,
}
}
// Handle process negation handler
func (nh negationHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorMap) (string, error) {
anchorKey := nh.anchor.Key()
currentPath := nh.path + anchorKey + "/"
// if anchor is present in the resource then fail
if _, ok := resourceMap[anchorKey]; ok {
// no need to process elements in value as key cannot be present in resource
ac.AnchorError = newNegationAnchorError(fmt.Sprintf("%s is not allowed", currentPath))
return currentPath, ac.AnchorError
}
// key is not defined in the resource
return "", nil
}
// equalityHandler provides handler for non anchor element
type equalityHandler struct {
anchor Anchor
pattern interface{}
path string
}
// newEqualityHandler returens instance of equality handler
func newEqualityHandler(anchor Anchor, pattern interface{}, path string) ValidationHandler {
return equalityHandler{
anchor: anchor,
pattern: pattern,
path: path,
}
}
// Handle processed condition anchor
func (eh equalityHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorMap) (string, error) {
anchorKey := eh.anchor.Key()
currentPath := eh.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
// validate the values of the pattern
returnPath, err := handler(logging.GlobalLogger(), value, eh.pattern, originPattern, currentPath, ac)
if err != nil {
return returnPath, err
}
return "", nil
}
return "", nil
}
// defaultHandler provides handler for non anchor element
type defaultHandler struct {
element string
pattern interface{}
path string
}
// newDefaultHandler returns handler for non anchor elements
func newDefaultHandler(element string, pattern interface{}, path string) ValidationHandler {
return defaultHandler{
element: element,
pattern: pattern,
path: path,
}
}
// Handle process non anchor element
func (dh defaultHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorMap) (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("%s/%s not found", dh.path, dh.element)
} else {
path, err := handler(logging.GlobalLogger(), resourceMap[dh.element], dh.pattern, originPattern, currentPath, ac)
if err != nil {
return path, err
}
}
return "", nil
}
// conditionAnchorHandler provides handler for condition anchor
type conditionAnchorHandler struct {
anchor Anchor
pattern interface{}
path string
}
// newConditionAnchorHandler returns an instance of condition acnhor handler
func newConditionAnchorHandler(anchor Anchor, pattern interface{}, path string) ValidationHandler {
return conditionAnchorHandler{
anchor: anchor,
pattern: pattern,
path: path,
}
}
// Handle processed condition anchor
func (ch conditionAnchorHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorMap) (string, error) {
anchorKey := ch.anchor.Key()
currentPath := ch.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
// validate the values of the pattern
returnPath, err := handler(logging.GlobalLogger(), value, ch.pattern, originPattern, currentPath, ac)
if err != nil {
ac.AnchorError = newConditionalAnchorError(err.Error())
return returnPath, ac.AnchorError
}
return "", nil
} else {
msg := "conditional anchor key doesn't exist in the resource"
return currentPath, newConditionalAnchorError(msg)
}
}
// globalAnchorHandler provides handler for global condition anchor
type globalAnchorHandler struct {
anchor Anchor
pattern interface{}
path string
}
// newGlobalAnchorHandler returns an instance of condition acnhor handler
func newGlobalAnchorHandler(anchor Anchor, pattern interface{}, path string) ValidationHandler {
return globalAnchorHandler{
anchor: anchor,
pattern: pattern,
path: path,
}
}
// Handle processed global condition anchor
func (gh globalAnchorHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorMap) (string, error) {
anchorKey := gh.anchor.Key()
currentPath := gh.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
// validate the values of the pattern
returnPath, err := handler(logging.GlobalLogger(), value, gh.pattern, originPattern, currentPath, ac)
if err != nil {
ac.AnchorError = newGlobalAnchorError(err.Error())
return returnPath, ac.AnchorError
}
return "", nil
}
return "", nil
}
// existenceHandler provides handlers to process exitence anchor handler
type existenceHandler struct {
anchor Anchor
pattern interface{}
path string
}
// newExistenceHandler returns existence handler
func newExistenceHandler(anchor Anchor, pattern interface{}, path string) ValidationHandler {
return existenceHandler{
anchor: anchor,
pattern: pattern,
path: path,
}
}
// Handle processes the existence anchor handler
func (eh existenceHandler) Handle(handler resourceElementHandler, resourceMap map[string]interface{}, originPattern interface{}, ac *AnchorMap) (string, error) {
// skip is used by existence anchor to not process further if condition is not satisfied
anchorKey := eh.anchor.Key()
currentPath := eh.path + anchorKey + "/"
// check if anchor is present in resource
if value, ok := resourceMap[anchorKey]; ok {
// Existence anchor can only exist on resource value type of list
switch typedResource := value.(type) {
case []interface{}:
typedPattern, ok := eh.pattern.([]interface{})
if !ok {
return currentPath, fmt.Errorf("invalid pattern type %T: Pattern has to be of list to compare against resource", eh.pattern)
}
// loop all item in the pattern array
errorPath := ""
var err error
for _, patternMap := range typedPattern {
typedPatternMap, ok := patternMap.(map[string]interface{})
if !ok {
return currentPath, fmt.Errorf("invalid pattern type %T: Pattern has to be of type map to compare against items in resource", eh.pattern)
}
errorPath, err = validateExistenceListResource(handler, typedResource, typedPatternMap, originPattern, currentPath, ac)
if err != nil {
return errorPath, err
}
}
return errorPath, err
default:
return currentPath, fmt.Errorf("invalid resource type %T: Existence ^ () anchor can be used only on list/array type resource", value)
}
}
return "", nil
}
func validateExistenceListResource(handler resourceElementHandler, resourceList []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string, ac *AnchorMap) (string, error) {
// the idea is all the element in the pattern array should be present atleast once in the resource list
// if non satisfy then throw an error
for i, resourceElement := range resourceList {
currentPath := path + strconv.Itoa(i) + "/"
_, err := handler(logging.GlobalLogger(), resourceElement, patternMap, originPattern, currentPath, ac)
if err == nil {
// condition is satisfied, dont check further
return "", nil
}
}
// none of the existence checks worked, so thats a failure sceanario
return path, fmt.Errorf("existence anchor validation failed at path %s", path)
}

View file

@ -0,0 +1,60 @@
package anchor
import (
"path"
"strings"
)
// GetAnchorsResourcesFromMap returns maps of anchors and resources
func GetAnchorsResourcesFromMap(patternMap map[string]interface{}) (map[string]interface{}, map[string]interface{}) {
anchors := map[string]interface{}{}
resources := map[string]interface{}{}
for key, value := range patternMap {
if a := Parse(key); IsCondition(a) || IsExistence(a) || IsEquality(a) || IsNegation(a) {
anchors[key] = value
} else {
resources[key] = value
}
}
return anchors, resources
}
// RemoveAnchorsFromPath removes all anchor from path string
func RemoveAnchorsFromPath(str string) string {
parts := strings.Split(str, "/")
if parts[0] == "" {
parts = parts[1:]
}
for i, part := range parts {
if a := Parse(part); a != nil {
parts[i] = a.Key()
} else {
parts[i] = part
}
}
newPath := path.Join(parts...)
if path.IsAbs(str) {
newPath = "/" + newPath
}
return newPath
}
// resourceHasValueForKey checks if a resource has value for a given key
func resourceHasValueForKey(resource interface{}, key string) bool {
switch typed := resource.(type) {
case map[string]interface{}:
if _, ok := typed[key]; ok {
return true
}
return false
case []interface{}:
for _, value := range typed {
if resourceHasValueForKey(value, key) {
return true
}
}
return false
default:
return false
}
}

View file

@ -0,0 +1,126 @@
package anchor
import (
"reflect"
"testing"
)
func TestRemoveAnchorsFromPath(t *testing.T) {
tests := []struct {
name string
str string
want string
}{{
str: "/path/(to)/X(anchors)",
want: "/path/to/anchors",
}, {
str: "path/(to)/X(anchors)",
want: "path/to/anchors",
}, {
str: "../(to)/X(anchors)",
want: "../to/anchors",
}, {
str: "/path/(to)/X(anchors)",
want: "/path/to/anchors",
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := RemoveAnchorsFromPath(tt.str); got != tt.want {
t.Errorf("RemoveAnchorsFromPath() = %v, want %v", got, tt.want)
}
})
}
}
func TestGetAnchorsResourcesFromMap(t *testing.T) {
tests := []struct {
name string
patternMap map[string]interface{}
wantAnchors map[string]interface{}
wantResources map[string]interface{}
}{{
patternMap: map[string]interface{}{
"spec": "test",
},
wantAnchors: map[string]interface{}{},
wantResources: map[string]interface{}{
"spec": "test",
},
}, {
patternMap: map[string]interface{}{
"(spec)": "test",
},
wantAnchors: map[string]interface{}{
"(spec)": "test",
},
wantResources: map[string]interface{}{},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
anchors, resources := GetAnchorsResourcesFromMap(tt.patternMap)
if !reflect.DeepEqual(anchors, tt.wantAnchors) {
t.Errorf("GetAnchorsResourcesFromMap() anchors = %v, want %v", anchors, tt.wantAnchors)
}
if !reflect.DeepEqual(resources, tt.wantResources) {
t.Errorf("GetAnchorsResourcesFromMap() resources = %v, want %v", resources, tt.wantResources)
}
})
}
}
func Test_resourceHasValueForKey(t *testing.T) {
type args struct {
resource interface{}
key string
}
tests := []struct {
name string
args args
want bool
}{{
args: args{
resource: map[string]interface{}{
"spec": 123,
},
key: "spec",
},
want: true,
}, {
args: args{
resource: map[string]interface{}{
"spec": 123,
},
key: "metadata",
},
want: false,
}, {
args: args{
resource: []interface{}{1, 2, 3},
key: "spec",
},
want: false,
}, {
args: args{
resource: []interface{}{
map[string]interface{}{
"spec": 123,
},
},
key: "spec",
},
want: true,
}, {
args: args{
resource: 123,
key: "spec",
},
want: false,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := resourceHasValueForKey(tt.args.resource, tt.args.key); got != tt.want {
t.Errorf("resourceHasValueForKey() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -73,12 +73,10 @@ func walkMap(logger logr.Logger, pattern, resource *yaml.RNode) error {
if err := validateConditions(logger, pattern, resource); err != nil {
return err // do not wrap condition errors
}
isNotAnchor := func(key string) bool {
return !hasAnchor(key)
isNotAnchor := func(a anchor.Anchor) bool {
return !hasAnchor(a)
}
nonAnchors, err := filterKeys(pattern, isNotAnchor)
nonAnchors, err := nonAnchorKeys(pattern, isNotAnchor)
if err != nil {
return err
}
@ -133,7 +131,7 @@ func processListOfMaps(logger logr.Logger, pattern, resource *yaml.RNode) error
for _, patternElement := range patternElements {
// If pattern has conditions, look for matching elements and process them
hasAnyAnchor := hasAnchors(patternElement, hasAnchor)
hasGlobalConditions := hasAnchors(patternElement, anchor.IsGlobalAnchor)
hasGlobalConditions := hasAnchors(patternElement, anchor.IsGlobal)
if hasAnyAnchor {
anyGlobalConditionPassed := false
var lastGlobalAnchorError error = nil
@ -238,12 +236,12 @@ func isGlobalConditionError(err error) bool {
// If caller handles map, it must stop processing and skip entire rule.
func validateConditions(logger logr.Logger, pattern, resource *yaml.RNode) error {
var err error
err = validateConditionsInternal(logger, pattern, resource, anchor.IsGlobalAnchor)
err = validateConditionsInternal(logger, pattern, resource, anchor.IsGlobal)
if err != nil {
return NewGlobalConditionError(err)
}
err = validateConditionsInternal(logger, pattern, resource, anchor.IsConditionAnchor)
err = validateConditionsInternal(logger, pattern, resource, anchor.IsCondition)
if err != nil {
return NewConditionError(err)
}
@ -255,62 +253,72 @@ func validateConditions(logger logr.Logger, pattern, resource *yaml.RNode) error
// Remove anchor from pattern, if field already exists.
// Remove anchor wrapping from key, if field does not exist in the resource.
func handleAddIfNotPresentAnchor(pattern, resource *yaml.RNode) (int, error) {
anchors, err := filterKeys(pattern, anchor.IsAddIfNotPresentAnchor)
anchors, err := filterKeys(pattern, anchor.IsAddIfNotPresent)
if err != nil {
return 0, err
}
for _, a := range anchors {
key, _ := anchor.RemoveAnchor(a)
key := a.Key()
if resource != nil && resource.Field(key) != nil {
// Resource already has this field.
// Delete the field with addIfNotPresent anchor from patch.
err = pattern.PipeE(yaml.Clear(a))
err = pattern.PipeE(yaml.Clear(a.String()))
if err != nil {
return 0, err
}
} else {
// Remove anchor tags from patch field key.
renameField(a, key, pattern)
renameField(a.String(), key, pattern)
}
}
return len(anchors), nil
}
func filterKeys(pattern *yaml.RNode, condition func(string) bool) ([]string, error) {
func filterKeys(pattern *yaml.RNode, condition func(anchor.Anchor) bool) ([]anchor.Anchor, error) {
if !isMappingNode(pattern) {
return nil, nil
}
keys := make([]string, 0)
fields, err := pattern.Fields()
if err != nil {
return keys, err
return nil, err
}
var anchors []anchor.Anchor
for _, field := range fields {
if a := anchor.Parse(field); a != nil && condition(a) {
anchors = append(anchors, a)
}
}
return anchors, nil
}
for _, key := range fields {
if condition(key) {
keys = append(keys, key)
continue
func nonAnchorKeys(pattern *yaml.RNode, condition func(anchor.Anchor) bool) ([]string, error) {
if !isMappingNode(pattern) {
return nil, nil
}
fields, err := pattern.Fields()
if err != nil {
return nil, err
}
var keys []string
for _, field := range fields {
if a := anchor.Parse(field); a == nil || condition(a) {
keys = append(keys, field)
}
}
return keys, nil
}
func isMappingNode(node *yaml.RNode) bool {
if err := yaml.ErrorIfInvalid(node, yaml.MappingNode); err != nil {
return false
}
return true
return yaml.ErrorIfInvalid(node, yaml.MappingNode) == nil
}
func hasAnchor(key string) bool {
return anchor.ContainsCondition(key) || anchor.IsAddIfNotPresentAnchor(key)
func hasAnchor(a anchor.Anchor) bool {
return anchor.ContainsCondition(a) || anchor.IsAddIfNotPresent(a)
}
func hasAnchors(pattern *yaml.RNode, isAnchor func(key string) bool) bool {
func hasAnchors(pattern *yaml.RNode, isAnchor func(anchor.Anchor) bool) bool {
ynode := pattern.YNode() //nolint:ifshort
if ynode.Kind == yaml.MappingNode {
fields, err := pattern.Fields()
@ -319,10 +327,9 @@ func hasAnchors(pattern *yaml.RNode, isAnchor func(key string) bool) bool {
}
for _, key := range fields {
if isAnchor(key) {
if a := anchor.Parse(key); a != nil && isAnchor(a) {
return true
}
patternNode := pattern.Field(key)
if !patternNode.IsNilOrEmpty() {
if hasAnchors(patternNode.Value, isAnchor) {
@ -331,8 +338,7 @@ func hasAnchors(pattern *yaml.RNode, isAnchor func(key string) bool) bool {
}
}
} else if ynode.Kind == yaml.ScalarNode {
v := ynode.Value
return anchor.ContainsCondition(v)
return anchor.ContainsCondition(anchor.Parse(ynode.Value))
} else if ynode.Kind == yaml.SequenceNode {
elements, _ := pattern.Elements()
for _, e := range elements {
@ -352,7 +358,6 @@ func renameField(name, newName string, pattern *yaml.RNode) {
if field == nil {
return
}
field.Key.YNode().Value = newName
}
@ -402,7 +407,7 @@ func deleteConditionElements(pattern *yaml.RNode) error {
}
for _, field := range fields {
deleteScalar := anchor.ContainsCondition(field)
deleteScalar := anchor.ContainsCondition(anchor.Parse(field))
canDelete, err := deleteAnchors(pattern.Field(field).Value, deleteScalar, false)
if err != nil {
return err
@ -438,22 +443,20 @@ func deleteAnchors(node *yaml.RNode, deleteScalar, traverseMappingNodes bool) (b
}
func deleteAnchorsInMap(node *yaml.RNode, traverseMappingNodes bool) (bool, error) {
conditions, err := filterKeys(node, anchor.ContainsCondition)
anchors, err := filterKeys(node, anchor.ContainsCondition)
if err != nil {
return false, err
}
// remove all conditional anchors with no child nodes first
anchorsExist := false
for _, condition := range conditions {
field := node.Field(condition)
for _, a := range anchors {
field := node.Field(a.String())
shouldDelete, err := deleteAnchors(field.Value, true, traverseMappingNodes)
if err != nil {
return false, err
}
if shouldDelete {
if err := node.PipeE(yaml.Clear(condition)); err != nil {
if err := node.PipeE(yaml.Clear(a.String())); err != nil {
return false, err
}
} else {
@ -502,14 +505,12 @@ func stripAnchorsFromNode(node *yaml.RNode, key string) error {
if err != nil {
return err
}
for _, a := range anchors {
k, _ := anchor.RemoveAnchor(a)
k := a.Key()
if key == "" || k == key {
renameField(a, k, node)
renameField(a.String(), k, node)
}
}
return nil
}
@ -562,19 +563,17 @@ func deleteListElement(list *yaml.RNode, i int) {
list.YNode().Content = append(content[:i], content[i+1:]...)
}
func validateConditionsInternal(logger logr.Logger, pattern, resource *yaml.RNode, filter func(string) bool) error {
conditions, err := filterKeys(pattern, filter)
func validateConditionsInternal(logger logr.Logger, pattern, resource *yaml.RNode, filter func(anchor.Anchor) bool) error {
anchors, err := filterKeys(pattern, filter)
if err != nil {
return err
}
for _, condition := range conditions {
conditionKey, _ := anchor.RemoveAnchor(condition)
for _, a := range anchors {
conditionKey := a.Key()
if resource == nil || resource.Field(conditionKey) == nil {
return fmt.Errorf("could not found \"%s\" key in the resource", conditionKey)
}
patternValue := pattern.Field(condition).Value
patternValue := pattern.Field(a.String()).Value
resourceValue := resource.Field(conditionKey).Value
if count, err := handleAddIfNotPresentAnchor(patternValue, resourceValue); err != nil {
return err

View file

@ -986,7 +986,7 @@ func Test_FilterKeys_NoConditions(t *testing.T) {
}`)
pattern := yaml.MustParse(string(patternRaw))
conditions, err := filterKeys(pattern, anchor.IsConditionAnchor)
conditions, err := filterKeys(pattern, anchor.IsCondition)
assert.NilError(t, err)
assert.Equal(t, len(conditions), 0)
@ -1000,18 +1000,18 @@ func Test_FilterKeys_ConditionsArePresent(t *testing.T) {
}`)
pattern := yaml.MustParse(string(patternRaw))
conditions, err := filterKeys(pattern, anchor.IsConditionAnchor)
conditions, err := filterKeys(pattern, anchor.IsCondition)
assert.NilError(t, err)
assert.Equal(t, len(conditions), 2)
assert.Equal(t, conditions[0], "(key2)")
assert.Equal(t, conditions[1], "(key3)")
assert.Equal(t, conditions[0].String(), "(key2)")
assert.Equal(t, conditions[1].String(), "(key3)")
}
func Test_FilterKeys_EmptyList(t *testing.T) {
patternRaw := []byte(`{}`)
pattern := yaml.MustParse(string(patternRaw))
conditions, err := filterKeys(pattern, anchor.IsConditionAnchor)
conditions, err := filterKeys(pattern, anchor.IsCondition)
assert.NilError(t, err)
assert.Equal(t, len(conditions), 0)

View file

@ -2,7 +2,7 @@ package utils
import (
jsonpatch "github.com/evanphx/json-patch/v5"
commonAnchor "github.com/kyverno/kyverno/pkg/engine/anchor"
"github.com/kyverno/kyverno/pkg/engine/anchor"
"github.com/kyverno/kyverno/pkg/logging"
jsonutils "github.com/kyverno/kyverno/pkg/utils/json"
)
@ -48,12 +48,10 @@ func ApplyPatchNew(resource, patch []byte) ([]byte, error) {
// GetAnchorsFromMap gets the conditional anchor map
func GetAnchorsFromMap(anchorsMap map[string]interface{}) map[string]interface{} {
result := make(map[string]interface{})
for key, value := range anchorsMap {
if commonAnchor.IsConditionAnchor(key) {
if anchor.IsCondition(anchor.Parse(key)) {
result[key] = value
}
}
return result
}

View file

@ -4,7 +4,7 @@ import (
"container/list"
"sort"
commonAnchors "github.com/kyverno/kyverno/pkg/engine/anchor"
"github.com/kyverno/kyverno/pkg/engine/anchor"
)
// Checks if pattern has anchors
@ -44,7 +44,7 @@ func getSortedNestedAnchorResource(resources map[string]interface{}) *list.List
for _, k := range keys {
v := resources[k]
if commonAnchors.IsGlobalAnchor(k) {
if anchor.IsGlobal(anchor.Parse(k)) {
sortedResourceKeys.PushFront(k)
continue
}
@ -61,7 +61,7 @@ func getSortedNestedAnchorResource(resources map[string]interface{}) *list.List
func getAnchorsFromMap(anchorsMap map[string]interface{}) map[string]interface{} {
result := make(map[string]interface{})
for key, value := range anchorsMap {
if commonAnchors.IsConditionAnchor(key) || commonAnchors.IsExistenceAnchor(key) || commonAnchors.IsEqualityAnchor(key) || commonAnchors.IsNegationAnchor(key) || commonAnchors.IsGlobalAnchor(key) {
if a := anchor.Parse(key); anchor.IsCondition(a) || anchor.IsExistence(a) || anchor.IsEquality(a) || anchor.IsNegation(a) || anchor.IsGlobal(a) {
result[key] = value
}
}

View file

@ -44,7 +44,7 @@ func MatchPattern(logger logr.Logger, resource, pattern interface{}) error {
}
// check if an anchor defined in the policy rule is missing in the resource
if ac.IsAnchorError() {
if ac.KeysAreMissing() {
logger.V(3).Info("missing anchor in resource")
return &PatternError{err, "", false}
}
@ -57,18 +57,18 @@ func MatchPattern(logger logr.Logger, resource, pattern interface{}) error {
func skip(err error) bool {
// if conditional or global anchors report errors, the rule does not apply to the resource
return anchor.IsConditionalAnchorError(err.Error()) || anchor.IsGlobalAnchorError(err.Error())
return anchor.IsConditionalAnchorError(err) || anchor.IsGlobalAnchorError(err)
}
func fail(err error) bool {
// if negation anchors report errors, the rule will fail
return anchor.IsNegationAnchorError(err.Error())
return anchor.IsNegationAnchorError(err)
}
// 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, ac *anchor.AnchorKey) (string, error) {
func validateResourceElement(log logr.Logger, resourceElement, patternElement, originPattern interface{}, path string, ac *anchor.AnchorMap) (string, error) {
switch typedPatternElement := patternElement.(type) {
// map
case map[string]interface{}:
@ -115,7 +115,7 @@ func validateResourceElement(log logr.Logger, resourceElement, patternElement, o
// 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, ac *anchor.AnchorKey) (string, error) {
func validateMap(log logr.Logger, resourceMap, patternMap map[string]interface{}, origPattern interface{}, path string, ac *anchor.AnchorMap) (string, error) {
patternMap = wildcards.ExpandInMetadata(patternMap, resourceMap)
// check if there is anchor in pattern
// Phase 1 : Evaluate all the anchors
@ -160,7 +160,7 @@ func validateMap(log logr.Logger, resourceMap, patternMap map[string]interface{}
return "", nil
}
func validateArray(log logr.Logger, resourceArray, patternArray []interface{}, originPattern interface{}, path string, ac *anchor.AnchorKey) (string, error) {
func validateArray(log logr.Logger, resourceArray, patternArray []interface{}, originPattern interface{}, path string, ac *anchor.AnchorMap) (string, error) {
if len(patternArray) == 0 {
return path, fmt.Errorf("pattern Array empty")
}
@ -215,7 +215,7 @@ func validateArray(log logr.Logger, resourceArray, patternArray []interface{}, o
// validateArrayOfMaps gets anchors from pattern array map element, applies anchors logic
// and then validates each map due to the pattern
func validateArrayOfMaps(log logr.Logger, resourceMapArray []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string, ac *anchor.AnchorKey) (string, error) {
func validateArrayOfMaps(log logr.Logger, resourceMapArray []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string, ac *anchor.AnchorMap) (string, error) {
applyCount := 0
skipErrors := make([]error, 0)
for i, resourceElement := range resourceMapArray {

View file

@ -3,7 +3,7 @@ package wildcards
import (
"strings"
commonAnchor "github.com/kyverno/kyverno/pkg/engine/anchor"
"github.com/kyverno/kyverno/pkg/engine/anchor"
wildcard "github.com/kyverno/kyverno/pkg/utils/wildcard"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -27,7 +27,6 @@ func replaceWildcardsInMapKeyValues(patternMap map[string]string, resourceMap ma
result[k] = v
}
}
return result
}
@ -41,12 +40,10 @@ func expandWildcards(k, v string, resourceMap map[string]string, matchValue, rep
}
}
}
if replace {
k = replaceWildCardChars(k)
v = replaceWildCardChars(v)
}
return k, v
}
@ -78,23 +75,22 @@ func ExpandInMetadata(patternMap, resourceMap map[string]interface{}) map[string
if labels != nil {
metadata[labelsKey] = labels
}
annotationsKey, annotations := expandWildcardsInTag("annotations", patternMetadata, resourceMetadata)
if annotations != nil {
metadata[annotationsKey] = annotations
}
return patternMap
}
func getPatternValue(tag string, pattern map[string]interface{}) (string, interface{}) {
for k, v := range pattern {
k2, _ := commonAnchor.RemoveAnchor(k)
if k2 == tag {
if k == tag {
return k, v
}
if a := anchor.Parse(k); a != nil && a.Key() == tag {
return k, v
}
}
return "", nil
}
@ -140,17 +136,16 @@ func replaceWildcardsInMapKeys(patternData, resourceData map[string]string) map[
results := map[string]interface{}{}
for k, v := range patternData {
if wildcard.ContainsWildcard(k) {
anchorFreeKey, anchorPrefix := commonAnchor.RemoveAnchor(k)
matchK, _ := expandWildcards(anchorFreeKey, v, resourceData, false, false)
if anchorPrefix != "" {
matchK = commonAnchor.AddAnchor(matchK, anchorPrefix)
if a := anchor.Parse(k); a != nil {
matchK, _ := expandWildcards(a.Key(), v, resourceData, false, false)
results[anchor.String(a.Type(), matchK)] = v
} else {
matchK, _ := expandWildcards(k, v, resourceData, false, false)
results[matchK] = v
}
results[matchK] = v
} else {
results[k] = v
}
}
return results
}

View file

@ -2,19 +2,18 @@ package common
import (
"fmt"
"regexp"
"strconv"
commonAnchors "github.com/kyverno/kyverno/pkg/engine/anchor"
"github.com/kyverno/kyverno/pkg/engine/anchor"
)
// ValidatePattern validates the pattern
func ValidatePattern(patternElement interface{}, path string, supportedAnchors []commonAnchors.IsAnchor) (string, error) {
func ValidatePattern(patternElement interface{}, path string, isSupported func(anchor.Anchor) bool) (string, error) {
switch typedPatternElement := patternElement.(type) {
case map[string]interface{}:
return validateMap(typedPatternElement, path, supportedAnchors)
return validateMap(typedPatternElement, path, isSupported)
case []interface{}:
return validateArray(typedPatternElement, path, supportedAnchors)
return validateArray(typedPatternElement, path, isSupported)
case string, float64, int, int64, bool, nil:
// TODO: check operator
return "", nil
@ -23,30 +22,21 @@ func ValidatePattern(patternElement interface{}, path string, supportedAnchors [
}
}
func validateMap(patternMap map[string]interface{}, path string, supportedAnchors []commonAnchors.IsAnchor) (string, error) {
func validateMap(patternMap map[string]interface{}, path string, isSupported func(anchor.Anchor) bool) (string, error) {
// check if anchors are defined
for key, value := range patternMap {
// if key is anchor
// check regex () -> this is anchor
// ()
// single char ()
re, err := regexp.Compile(`^.?\(.+\)$`)
if err != nil {
return path + "/" + key, fmt.Errorf("unable to parse the field %s: %v", key, err)
}
matched := re.MatchString(key)
a := anchor.Parse(key)
// check the type of anchor
if matched {
if a != nil {
// some type of anchor
// check if valid anchor
if !checkAnchors(key, supportedAnchors) {
if !checkAnchors(a, isSupported) {
return path + "/" + key, fmt.Errorf("unsupported anchor %s", key)
}
// addition check for existence anchor
// value must be of type list
if commonAnchors.IsExistenceAnchor(key) {
if anchor.IsExistence(a) {
typedValue, ok := value.([]interface{})
if !ok {
return path + "/" + key, fmt.Errorf("existence anchor should have value of type list")
@ -58,29 +48,27 @@ func validateMap(patternMap map[string]interface{}, path string, supportedAnchor
}
}
// lets validate the values now :)
if errPath, err := ValidatePattern(value, path+"/"+key, supportedAnchors); err != nil {
if errPath, err := ValidatePattern(value, path+"/"+key, isSupported); err != nil {
return errPath, err
}
}
return "", nil
}
func validateArray(patternArray []interface{}, path string, supportedAnchors []commonAnchors.IsAnchor) (string, error) {
func validateArray(patternArray []interface{}, path string, isSupported func(anchor.Anchor) bool) (string, error) {
for i, patternElement := range patternArray {
currentPath := path + strconv.Itoa(i) + "/"
// lets validate the values now :)
if errPath, err := ValidatePattern(patternElement, currentPath, supportedAnchors); err != nil {
if errPath, err := ValidatePattern(patternElement, currentPath, isSupported); err != nil {
return errPath, err
}
}
return "", nil
}
func checkAnchors(key string, supportedAnchors []commonAnchors.IsAnchor) bool {
for _, f := range supportedAnchors {
if f(key) {
return true
}
func checkAnchors(a anchor.Anchor, isSupported func(anchor.Anchor) bool) bool {
if isSupported == nil {
return false
}
return false
return isSupported(a)
}

View file

@ -8,7 +8,6 @@ import (
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
commonAnchors "github.com/kyverno/kyverno/pkg/engine/anchor"
"github.com/kyverno/kyverno/pkg/engine/variables"
"github.com/kyverno/kyverno/pkg/policy/common"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
@ -79,7 +78,7 @@ func (g *Generate) Validate() (string, error) {
if target := rule.GetData(); target != nil {
// TODO: is this required ?? as anchors can only be on pattern and not resource
// we can add this check by not sure if its needed here
if path, err := common.ValidatePattern(target, "/", []commonAnchors.IsAnchor{}); err != nil {
if path, err := common.ValidatePattern(target, "/", nil); err != nil {
return fmt.Sprintf("data.%s", path), fmt.Errorf("anchors not supported on generate resources: %v", err)
}
}

View file

@ -4,7 +4,7 @@ import (
"fmt"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
commonAnchors "github.com/kyverno/kyverno/pkg/engine/anchor"
"github.com/kyverno/kyverno/pkg/engine/anchor"
"github.com/kyverno/kyverno/pkg/policy/common"
)
@ -30,7 +30,13 @@ func (v *Validate) Validate() (string, error) {
}
if target := v.rule.GetPattern(); target != nil {
if path, err := common.ValidatePattern(target, "/", []commonAnchors.IsAnchor{commonAnchors.IsConditionAnchor, commonAnchors.IsExistenceAnchor, commonAnchors.IsEqualityAnchor, commonAnchors.IsNegationAnchor, commonAnchors.IsGlobalAnchor}); err != nil {
if path, err := common.ValidatePattern(target, "/", func(a anchor.Anchor) bool {
return anchor.IsCondition(a) ||
anchor.IsExistence(a) ||
anchor.IsEquality(a) ||
anchor.IsNegation(a) ||
anchor.IsGlobal(a)
}); err != nil {
return fmt.Sprintf("pattern.%s", path), err
}
}
@ -41,7 +47,13 @@ func (v *Validate) Validate() (string, error) {
return "anyPattern", fmt.Errorf("failed to deserialize anyPattern, expect array: %v", err)
}
for i, pattern := range anyPattern {
if path, err := common.ValidatePattern(pattern, "/", []commonAnchors.IsAnchor{commonAnchors.IsConditionAnchor, commonAnchors.IsExistenceAnchor, commonAnchors.IsEqualityAnchor, commonAnchors.IsNegationAnchor, commonAnchors.IsGlobalAnchor}); err != nil {
if path, err := common.ValidatePattern(pattern, "/", func(a anchor.Anchor) bool {
return anchor.IsCondition(a) ||
anchor.IsExistence(a) ||
anchor.IsEquality(a) ||
anchor.IsNegation(a) ||
anchor.IsGlobal(a)
}); err != nil {
return fmt.Sprintf("anyPattern[%d].%s", i, path), err
}
}