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:
parent
fb94f6ea75
commit
48d9ebba2c
21 changed files with 1794 additions and 730 deletions
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
653
pkg/engine/anchor/anchor_test.go
Normal file
653
pkg/engine/anchor/anchor_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
44
pkg/engine/anchor/anchormap.go
Normal file
44
pkg/engine/anchor/anchormap.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
66
pkg/engine/anchor/anchormap_test.go
Normal file
66
pkg/engine/anchor/anchormap_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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 + ")"
|
||||
}
|
|
@ -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")
|
||||
}
|
89
pkg/engine/anchor/error.go
Normal file
89
pkg/engine/anchor/error.go
Normal 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)
|
||||
}
|
276
pkg/engine/anchor/error_test.go
Normal file
276
pkg/engine/anchor/error_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
275
pkg/engine/anchor/handlers.go
Normal file
275
pkg/engine/anchor/handlers.go
Normal 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)
|
||||
}
|
60
pkg/engine/anchor/utils.go
Normal file
60
pkg/engine/anchor/utils.go
Normal 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
|
||||
}
|
||||
}
|
126
pkg/engine/anchor/utils_test.go
Normal file
126
pkg/engine/anchor/utils_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue