2021-03-26 05:34:43 +05:30
package generate
import (
"container/list"
"fmt"
"strconv"
"github.com/go-logr/logr"
2023-01-10 14:07:26 +01:00
"github.com/kyverno/kyverno/pkg/engine/pattern"
2021-03-26 05:34:43 +05:30
"github.com/kyverno/kyverno/pkg/engine/wildcards"
2022-10-02 20:45:03 +01:00
"github.com/kyverno/kyverno/pkg/logging"
2021-03-26 05:34:43 +05:30
)
type Handler struct {
element string
pattern interface { }
path string
}
type resourceElementHandler = func ( log logr . Logger , resourceElement , patternElement , originPattern interface { } , path string ) ( string , error )
// ValidateResourceWithPattern is a start of element-by-element validation process
// It assumes that validation is started from root, so "/" is passed
// Anchors are not expected in the pattern
func ValidateResourceWithPattern ( log logr . Logger , resource , pattern interface { } ) ( string , error ) {
elemPath , err := validateResourceElement ( log , resource , pattern , pattern , "/" )
if err != nil {
return elemPath , err
}
return "" , nil
}
// validateResourceElement detects the element type (map, array, nil, string, int, bool, float)
// and calls corresponding handler
// Pattern tree and resource tree can have different structure. In this case validation fails
func validateResourceElement ( log logr . Logger , resourceElement , patternElement , originPattern interface { } , path string ) ( string , error ) {
// var err error
switch typedPatternElement := patternElement . ( type ) {
// map
case map [ string ] interface { } :
typedResourceElement , ok := resourceElement . ( map [ string ] interface { } )
if ! ok {
log . V ( 4 ) . Info ( "Pattern and resource have different structures." , "path" , path , "expected" , fmt . Sprintf ( "%T" , patternElement ) , "current" , fmt . Sprintf ( "%T" , resourceElement ) )
2022-05-15 02:34:35 +05:30
return path , fmt . Errorf ( "pattern and resource have different structures. Path: %s. Expected %T, found %T" , path , patternElement , resourceElement )
2021-03-26 05:34:43 +05:30
}
2024-08-14 01:14:06 +08:00
return validateMap ( typedResourceElement , typedPatternElement , originPattern , path )
2021-03-26 05:34:43 +05:30
// array
case [ ] interface { } :
typedResourceElement , ok := resourceElement . ( [ ] interface { } )
if ! ok {
log . V ( 4 ) . Info ( "Pattern and resource have different structures." , "path" , path , "expected" , fmt . Sprintf ( "%T" , patternElement ) , "current" , fmt . Sprintf ( "%T" , resourceElement ) )
2022-05-15 02:34:35 +05:30
return path , fmt . Errorf ( "validation rule failed at path %s, resource does not satisfy the expected overlay pattern" , path )
2021-03-26 05:34:43 +05:30
}
return validateArray ( log , typedResourceElement , typedPatternElement , originPattern , path )
// elementary values
case string , float64 , int , int64 , bool , nil :
2023-01-10 14:07:26 +01:00
if ! pattern . Validate ( log , resourceElement , patternElement ) {
2021-10-03 03:15:22 -07:00
return path , fmt . Errorf ( "value '%v' does not match '%v' at path %s" , resourceElement , patternElement , path )
2021-03-26 05:34:43 +05:30
}
default :
log . V ( 4 ) . Info ( "Pattern contains unknown type" , "path" , path , "current" , fmt . Sprintf ( "%T" , patternElement ) )
2021-10-03 03:15:22 -07:00
return path , fmt . Errorf ( "failed at path '%s', pattern contains unknown type" , path )
2021-03-26 05:34:43 +05:30
}
return "" , nil
}
// If validateResourceElement detects map element inside resource and pattern trees, it goes to validateMap
// For each element of the map we must detect the type again, so we pass these elements to validateResourceElement
2024-08-14 01:14:06 +08:00
func validateMap ( resourceMap , patternMap map [ string ] interface { } , origPattern interface { } , path string ) ( string , error ) {
2021-03-26 05:34:43 +05:30
patternMap = wildcards . ExpandInMetadata ( patternMap , resourceMap )
sortedResourceKeys := list . New ( )
for k := range patternMap {
sortedResourceKeys . PushBack ( k )
}
for e := sortedResourceKeys . Front ( ) ; e != nil ; e = e . Next ( ) {
key := e . Value . ( string )
handler := NewHandler ( key , patternMap [ key ] , path )
handlerPath , err := handler . Handle ( validateResourceElement , resourceMap , origPattern )
if err != nil {
return handlerPath , err
}
}
return "" , nil
}
// If validateResourceElement detects array element inside resource and pattern trees, it goes to validateArray
func validateArray ( log logr . Logger , resourceArray , patternArray [ ] interface { } , originPattern interface { } , path string ) ( string , error ) {
2022-05-15 02:34:35 +05:30
if len ( patternArray ) == 0 {
return path , fmt . Errorf ( "pattern Array empty" )
2021-03-26 05:34:43 +05:30
}
switch patternArray [ 0 ] . ( type ) {
case map [ string ] interface { } :
for _ , patternElement := range patternArray {
elemPath , err := validateArrayOfMaps ( log , resourceArray , patternElement . ( map [ string ] interface { } ) , originPattern , path )
if err != nil {
return elemPath , err
}
}
default :
if len ( resourceArray ) >= len ( patternArray ) {
for i , patternElement := range patternArray {
currentPath := path + strconv . Itoa ( i ) + "/"
elemPath , err := validateResourceElement ( log , resourceArray [ i ] , patternElement , originPattern , currentPath )
if err != nil {
return elemPath , err
}
}
} else {
2022-05-15 02:34:35 +05:30
return "" , fmt . Errorf ( "validate Array failed, array length mismatch, resource Array len is %d and pattern Array len is %d" , len ( resourceArray ) , len ( patternArray ) )
2021-03-26 05:34:43 +05:30
}
}
return "" , nil
}
// Matches all the elements in resource with the pattern
func validateArrayOfMaps ( log logr . Logger , resourceMapArray [ ] interface { } , patternMap map [ string ] interface { } , originPattern interface { } , path string ) ( string , error ) {
lengthOflenResourceMapArray := len ( resourceMapArray ) - 1
for i , resourceElement := range resourceMapArray {
currentPath := path + strconv . Itoa ( i ) + "/"
returnpath , err := validateResourceElement ( log , resourceElement , patternMap , originPattern , currentPath )
if err != nil {
if i < lengthOflenResourceMapArray {
continue
}
return returnpath , err
}
break
}
return "" , nil
}
func NewHandler ( element string , pattern interface { } , path string ) Handler {
return Handler {
element : element ,
pattern : pattern ,
path : path ,
}
}
func ( dh Handler ) Handle ( handler resourceElementHandler , resourceMap map [ string ] interface { } , originPattern interface { } ) ( string , error ) {
currentPath := dh . path + dh . element + "/"
if dh . pattern == "*" && resourceMap [ dh . element ] != nil {
return "" , nil
} else if dh . pattern == "*" && resourceMap [ dh . element ] == nil {
2021-10-03 03:15:22 -07:00
return dh . path , fmt . Errorf ( "failed at path %s, field %s is not present" , dh . path , dh . element )
2021-03-26 05:34:43 +05:30
} else {
2022-10-02 20:45:03 +01:00
path , err := handler ( logging . GlobalLogger ( ) , resourceMap [ dh . element ] , dh . pattern , originPattern , currentPath )
2021-03-26 05:34:43 +05:30
if err != nil {
return path , err
}
}
return "" , nil
}