2020-09-01 21:42:05 +05:30
package mutate
import (
2021-07-23 20:53:37 +03:00
"encoding/json"
"fmt"
"github.com/go-logr/logr"
2020-10-07 11:12:31 -07:00
anchor "github.com/kyverno/kyverno/pkg/engine/anchor/common"
2021-07-23 20:53:37 +03:00
"github.com/kyverno/kyverno/pkg/engine/validate"
2020-09-01 21:42:05 +05:30
yaml "sigs.k8s.io/kustomize/kyaml/yaml"
)
2021-07-23 20:53:37 +03:00
type ConditionError struct {
errorChain error
}
func ( ce ConditionError ) Error ( ) string {
2021-09-13 18:59:28 +03:00
return fmt . Sprintf ( "condition failed: %s" , ce . errorChain . Error ( ) )
2021-07-23 20:53:37 +03:00
}
func NewConditionError ( err error ) error {
return ConditionError { err }
}
2021-09-13 18:59:28 +03:00
type GlobalConditionError struct {
errorChain error
}
func ( ce GlobalConditionError ) Error ( ) string {
return fmt . Sprintf ( "global condition failed: %s" , ce . errorChain . Error ( ) )
}
func NewGlobalConditionError ( err error ) error {
return GlobalConditionError { err }
}
2020-09-01 21:42:05 +05:30
// preProcessPattern - Dynamically preProcess the yaml
// 1> For conditional anchor remove anchors from the pattern.
// 2> For Adding anchors remove anchor tags.
// The whole yaml is structured as a pointer tree.
// https://godoc.org/gopkg.in/yaml.v3#Node
// A single Node contains Tag to identify it as MappingNode (map[string]interface{}), Sequence ([]interface{}), ScalarNode (string, int, float bool etc.)
// A parent node having MappingNode keeps the data as <keyNode>, <ValueNode> inside it's Content field and Tag field as "!!map".
2021-07-23 20:53:37 +03:00
// A parent node having Sequence keeps the data as array of Node inside Content field and a Tag field as "!!seq".
2020-09-01 21:42:05 +05:30
// https://github.com/kubernetes-sigs/kustomize/blob/master/kyaml/yaml/rnode.go
2021-07-23 20:53:37 +03:00
func preProcessPattern ( logger logr . Logger , pattern , resource * yaml . RNode ) error {
err := preProcessRecursive ( logger , pattern , resource )
if err != nil {
return err
}
return deleteConditionElements ( pattern )
}
func preProcessRecursive ( logger logr . Logger , pattern , resource * yaml . RNode ) error {
2020-09-01 21:42:05 +05:30
switch pattern . YNode ( ) . Kind {
case yaml . MappingNode :
2021-07-23 20:53:37 +03:00
return walkMap ( logger , pattern , resource )
2020-09-01 21:42:05 +05:30
case yaml . SequenceNode :
2021-07-23 20:53:37 +03:00
return walkList ( logger , pattern , resource )
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
2020-09-01 21:42:05 +05:30
return nil
}
2021-07-23 20:53:37 +03:00
func walkMap ( logger logr . Logger , pattern , resource * yaml . RNode ) error {
var err error
2020-09-01 21:42:05 +05:30
2021-07-23 20:53:37 +03:00
err = validateConditions ( logger , pattern , resource )
if err != nil {
return err
}
2020-09-01 21:42:05 +05:30
2021-07-23 20:53:37 +03:00
err = handleAddings ( logger , pattern , resource )
if err != nil {
return err
2020-09-01 21:42:05 +05:30
}
2021-09-13 18:59:28 +03:00
nonAnchors , err := filterKeys ( pattern , func ( key string ) bool {
return ! hasAnchor ( key )
} )
2020-09-01 21:42:05 +05:30
if err != nil {
return err
}
2021-09-13 18:59:28 +03:00
var resourceValue * yaml . RNode
2020-09-19 00:48:13 +05:30
2021-09-13 18:59:28 +03:00
for _ , field := range nonAnchors {
2021-07-23 20:53:37 +03:00
if resource == nil || resource . Field ( field ) == nil {
2021-09-13 18:59:28 +03:00
resourceValue = nil
2020-09-19 00:48:13 +05:30
} else {
2021-09-13 18:59:28 +03:00
resourceValue = resource . Field ( field ) . Value
2021-07-23 20:53:37 +03:00
}
2021-09-13 18:59:28 +03:00
err := preProcessRecursive ( logger , pattern . Field ( field ) . Value , resourceValue )
2021-07-23 20:53:37 +03:00
if err != nil {
return err
2020-09-01 21:42:05 +05:30
}
}
2021-07-23 20:53:37 +03:00
2020-09-01 21:42:05 +05:30
return nil
}
2021-07-23 20:53:37 +03:00
func walkList ( logger logr . Logger , pattern , resource * yaml . RNode ) error {
elements , err := pattern . Elements ( )
2020-09-01 21:42:05 +05:30
if err != nil {
return err
}
2021-07-23 20:53:37 +03:00
if len ( elements ) == 0 {
2020-09-01 21:42:05 +05:30
return nil
}
2021-07-23 20:53:37 +03:00
if elements [ 0 ] . YNode ( ) . Kind == yaml . MappingNode {
return processListOfMaps ( logger , pattern , resource )
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
2020-09-01 21:42:05 +05:30
return nil
}
2021-07-23 20:53:37 +03:00
// processListOfMaps - process arrays
2020-09-01 21:42:05 +05:30
// in many cases like containers, volumes kustomize uses name field to match resource for processing
2021-07-23 20:53:37 +03:00
// If any conditional anchor match resource field and if the pattern doesn't contain "name" field and
// resource contains "name" field, then copy the name field from resource to pattern.
func processListOfMaps ( logger logr . Logger , pattern , resource * yaml . RNode ) error {
2020-09-01 21:42:05 +05:30
patternElements , err := pattern . Elements ( )
if err != nil {
return err
}
resourceElements , err := resource . Elements ( )
if err != nil {
return err
}
2021-07-23 20:53:37 +03:00
2020-09-01 21:42:05 +05:30
for _ , patternElement := range patternElements {
2021-07-23 20:53:37 +03:00
// If pattern has conditions, look for matching elements and process them
2021-09-13 18:59:28 +03:00
hasAnyAnchor := hasAnchors ( patternElement , hasAnchor )
hasGlobalConditions := hasAnchors ( patternElement , anchor . IsGlobalAnchor )
if hasAnyAnchor {
anyGlobalConditionPassed := false
var lastGlobalAnchorError error = nil
2020-09-01 21:42:05 +05:30
for _ , resourceElement := range resourceElements {
2021-07-23 20:53:37 +03:00
err := preProcessRecursive ( logger , patternElement , resourceElement )
if err != nil {
2021-09-13 18:59:28 +03:00
switch err . ( type ) {
case ConditionError :
2021-07-23 20:53:37 +03:00
// Skip element, if condition has failed
continue
2021-09-13 18:59:28 +03:00
case GlobalConditionError :
lastGlobalAnchorError = err
continue
2021-07-23 20:53:37 +03:00
}
return err
} else {
2021-09-13 18:59:28 +03:00
if hasGlobalConditions {
// global anchor has passed, there is no need to return an error
anyGlobalConditionPassed = true
}
2021-07-23 20:53:37 +03:00
// If condition is satisfied, create new pattern list element based on patternElement
// but related with current resource element by name.
// Resource element must have name. Without name kustomize won't be able to update this element.
// In case if element does not have name, skip it.
resourceElementName := resourceElement . Field ( "name" )
if resourceElementName . IsNilOrEmpty ( ) {
continue
}
newNode := patternElement . Copy ( )
2021-09-13 18:59:28 +03:00
empty , err := deleteConditionsFromNestedMaps ( newNode )
2021-07-23 20:53:37 +03:00
if err != nil {
return err
}
2021-09-13 18:59:28 +03:00
// Do not add an empty element to the patch
if empty {
continue
}
2021-07-23 20:53:37 +03:00
err = newNode . PipeE ( yaml . SetField ( "name" , resourceElementName . Value ) )
2020-09-01 21:42:05 +05:30
if err != nil {
return err
}
2021-07-23 20:53:37 +03:00
err = pattern . PipeE ( yaml . Append ( newNode . YNode ( ) ) )
if err != nil {
return err
2020-09-01 21:42:05 +05:30
}
}
}
2021-09-13 18:59:28 +03:00
if ! anyGlobalConditionPassed && lastGlobalAnchorError != nil {
return lastGlobalAnchorError
}
2020-09-01 21:42:05 +05:30
}
}
2021-07-23 20:53:37 +03:00
2020-09-01 21:42:05 +05:30
return nil
}
2021-07-23 20:53:37 +03:00
// validateConditions checks all conditions from current map.
// If at least one condition fails, return error.
// If caller handles list of maps and gets an error, it must skip element.
2021-09-13 18:59:28 +03:00
// If caller handles list of maps and gets GlobalConditionError, it must skip entire rule.
2021-07-23 20:53:37 +03:00
// If caller handles map, it must stop processing and skip entire rule.
func validateConditions ( logger logr . Logger , pattern , resource * yaml . RNode ) error {
2021-09-13 18:59:28 +03:00
var err error
err = validateConditionsInternal ( logger , pattern , resource , anchor . IsGlobalAnchor )
2020-09-01 21:42:05 +05:30
if err != nil {
2021-09-13 18:59:28 +03:00
return NewGlobalConditionError ( err )
2020-09-01 21:42:05 +05:30
}
2021-01-08 16:45:39 -08:00
2021-09-13 18:59:28 +03:00
err = validateConditionsInternal ( logger , pattern , resource , anchor . IsConditionAnchor )
if err != nil {
return NewConditionError ( err )
2021-01-08 16:45:39 -08:00
}
2020-09-01 21:42:05 +05:30
return nil
}
2021-07-23 20:53:37 +03:00
// handleAddings handles adding anchors.
// Remove anchor from pattern, if field already exists.
// Remove anchor wrapping from key, if field does not exist in the resource.
func handleAddings ( logger logr . Logger , pattern , resource * yaml . RNode ) error {
addings , err := filterKeys ( pattern , anchor . IsAddingAnchor )
2020-09-01 21:42:05 +05:30
if err != nil {
return err
}
2021-07-23 20:53:37 +03:00
for _ , adding := range addings {
key , _ := anchor . RemoveAnchor ( adding )
if resource != nil && resource . Field ( key ) != nil {
// Resource already has this field.
// Delete the field with adding anchor from patch.
err = pattern . PipeE ( yaml . Clear ( adding ) )
2020-09-01 21:42:05 +05:30
if err != nil {
return err
}
2021-07-23 20:53:37 +03:00
continue
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
// Remove anchor wrap from patch field.
renameField ( adding , key , pattern )
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
2020-09-01 21:42:05 +05:30
return nil
}
2021-07-23 20:53:37 +03:00
func filterKeys ( pattern * yaml . RNode , condition func ( string ) bool ) ( [ ] string , error ) {
keys := make ( [ ] string , 0 )
fields , err := pattern . Fields ( )
2020-09-01 21:42:05 +05:30
if err != nil {
2021-07-23 20:53:37 +03:00
return keys , err
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
for _ , key := range fields {
if condition ( key ) {
keys = append ( keys , key )
continue
}
}
return keys , nil
}
2021-09-13 18:59:28 +03:00
func hasAnchor ( key string ) bool {
return anchor . ContainsCondition ( key ) || anchor . IsAddingAnchor ( key )
}
func hasAnchors ( pattern * yaml . RNode , isAnchor func ( key string ) bool ) bool {
2021-07-23 20:53:37 +03:00
if yaml . MappingNode == pattern . YNode ( ) . Kind {
fields , err := pattern . Fields ( )
if err != nil {
return false
}
for _ , key := range fields {
2021-09-13 18:59:28 +03:00
if isAnchor ( key ) {
2021-07-23 20:53:37 +03:00
return true
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
patternNode := pattern . Field ( key )
if ! patternNode . IsNilOrEmpty ( ) {
2021-09-13 18:59:28 +03:00
if hasAnchors ( patternNode . Value , isAnchor ) {
2021-07-23 20:53:37 +03:00
return true
2020-09-01 21:42:05 +05:30
}
}
}
}
2021-07-23 20:53:37 +03:00
return false
}
func renameField ( name , newName string , pattern * yaml . RNode ) {
field := pattern . Field ( name )
if field == nil {
return
}
field . Key . YNode ( ) . Value = newName
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
func convertRNodeToInterface ( document * yaml . RNode ) ( interface { } , error ) {
if document . YNode ( ) . Kind == yaml . ScalarNode {
return document . YNode ( ) . Value , nil
}
rawDocument , err := document . MarshalJSON ( )
if err != nil {
return nil , err
}
var documentInterface interface { }
err = json . Unmarshal ( rawDocument , & documentInterface )
if err != nil {
return nil , err
}
return documentInterface , nil
}
func checkCondition ( logger logr . Logger , pattern * yaml . RNode , resource * yaml . RNode ) error {
2021-09-26 18:30:53 -07:00
patternInterface , err := convertRNodeToInterface ( pattern ) ;
2020-09-01 21:42:05 +05:30
if err != nil {
return err
}
2021-07-23 20:53:37 +03:00
resourceInterface , err := convertRNodeToInterface ( resource )
2020-09-01 21:42:05 +05:30
if err != nil {
return err
}
2021-07-23 20:53:37 +03:00
2021-09-26 18:30:53 -07:00
err , _ = validate . MatchPattern ( logger , resourceInterface , patternInterface )
if err != nil {
return err
}
return nil
2021-07-23 20:53:37 +03:00
}
2021-09-13 18:59:28 +03:00
func deleteConditionsFromNestedMaps ( pattern * yaml . RNode ) ( bool , error ) {
2021-07-23 20:53:37 +03:00
if pattern . YNode ( ) . Kind != yaml . MappingNode {
2021-09-13 18:59:28 +03:00
return false , nil
2021-07-23 20:53:37 +03:00
}
fields , err := pattern . Fields ( )
if err != nil {
2021-09-13 18:59:28 +03:00
return false , err
2021-07-23 20:53:37 +03:00
}
for _ , field := range fields {
2021-09-13 18:59:28 +03:00
if anchor . ContainsCondition ( field ) {
2021-07-23 20:53:37 +03:00
err = pattern . PipeE ( yaml . Clear ( field ) )
2020-09-01 21:42:05 +05:30
if err != nil {
2021-09-13 18:59:28 +03:00
return false , err
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
} else {
child := pattern . Field ( field ) . Value
if child != nil {
2021-09-13 18:59:28 +03:00
empty , err := deleteConditionsFromNestedMaps ( child )
2021-07-23 20:53:37 +03:00
if err != nil {
2021-09-13 18:59:28 +03:00
return false , err
}
if empty {
err = pattern . PipeE ( yaml . Clear ( field ) )
if err != nil {
return false , err
}
2021-07-23 20:53:37 +03:00
}
2020-09-01 21:42:05 +05:30
}
}
}
2021-07-23 20:53:37 +03:00
2021-09-13 18:59:28 +03:00
fields , err = pattern . Fields ( )
if err != nil {
return false , err
}
if len ( fields ) == 0 {
return true , nil
}
return false , nil
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
func deleteConditionElements ( pattern * yaml . RNode ) error {
2020-09-01 21:42:05 +05:30
fields , err := pattern . Fields ( )
if err != nil {
2021-07-23 20:53:37 +03:00
return err
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
for _ , field := range fields {
ok , err := deleteAnchors ( pattern . Field ( field ) . Value )
if err != nil {
return err
}
if ok {
err = pattern . PipeE ( yaml . Clear ( field ) )
if err != nil {
return err
2020-09-01 21:42:05 +05:30
}
}
}
2021-07-23 20:53:37 +03:00
return nil
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
// deleteAnchors deletes all the anchors and returns true,
// if this node must be deleted from patch.
// Node is considered to be deleted, if there were only
// anchors elemets. After anchors elements are removed,
// we have patch with nil values which could cause
// unnecessary resource elements deletion.
func deleteAnchors ( node * yaml . RNode ) ( bool , error ) {
switch node . YNode ( ) . Kind {
2020-09-01 21:42:05 +05:30
case yaml . MappingNode :
2021-07-23 20:53:37 +03:00
return deleteAnchorsInMap ( node )
case yaml . SequenceNode :
return deleteAnchorsInList ( node )
}
return false , nil
}
func deleteAnchorsInMap ( node * yaml . RNode ) ( bool , error ) {
2021-09-13 18:59:28 +03:00
conditions , err := filterKeys ( node , anchor . ContainsCondition )
2021-07-23 20:53:37 +03:00
if err != nil {
return false , err
}
// Remove all conditions first.
for _ , condition := range conditions {
err = node . PipeE ( yaml . Clear ( condition ) )
2020-09-01 21:42:05 +05:30
if err != nil {
2021-07-23 20:53:37 +03:00
return false , err
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
}
2020-09-01 21:42:05 +05:30
2021-07-23 20:53:37 +03:00
fields , err := node . Fields ( )
if err != nil {
return false , err
}
2021-09-13 18:59:28 +03:00
needToDelete := true
2021-07-23 20:53:37 +03:00
// Go further through the map elements.
for _ , field := range fields {
ok , err := deleteAnchors ( node . Field ( field ) . Value )
2020-09-01 21:42:05 +05:30
if err != nil {
2021-07-23 20:53:37 +03:00
return false , err
2020-09-01 21:42:05 +05:30
}
2021-07-23 20:53:37 +03:00
2021-09-13 18:59:28 +03:00
if ok {
err = node . PipeE ( yaml . Clear ( field ) )
if err != nil {
return false , err
}
} else {
// If we have at least one element without anchor,
// then we don't need to delete this element.
needToDelete = false
2020-09-01 21:42:05 +05:30
}
}
2021-01-08 16:45:39 -08:00
2021-09-13 18:59:28 +03:00
return needToDelete , nil
2021-07-23 20:53:37 +03:00
}
2021-01-08 16:45:39 -08:00
2021-07-23 20:53:37 +03:00
func deleteAnchorsInList ( node * yaml . RNode ) ( bool , error ) {
elements , err := node . Elements ( )
if err != nil {
return false , err
2021-01-08 16:45:39 -08:00
}
2021-09-13 18:59:28 +03:00
wasEmpty := len ( elements ) == 0
2021-07-23 20:53:37 +03:00
for i , element := range elements {
2021-09-13 18:59:28 +03:00
if hasAnchors ( element , hasAnchor ) {
2021-07-23 20:53:37 +03:00
deleteListElement ( node , i )
} else {
// This element also could have some conditions
// inside sub-arrays. Delete them too.
ok , err := deleteAnchors ( element )
2021-01-08 16:45:39 -08:00
if err != nil {
2021-07-23 20:53:37 +03:00
return false , err
2021-01-08 16:45:39 -08:00
}
2021-07-23 20:53:37 +03:00
if ok {
deleteListElement ( node , i )
2021-01-08 16:45:39 -08:00
}
}
}
2021-07-23 20:53:37 +03:00
elements , err = node . Elements ( )
if err != nil {
return false , err
}
2021-09-13 18:59:28 +03:00
if len ( elements ) == 0 && ! wasEmpty {
2021-07-23 20:53:37 +03:00
return true , nil
}
return false , nil
}
func deleteListElement ( list * yaml . RNode , i int ) {
content := list . YNode ( ) . Content
list . YNode ( ) . Content = append ( content [ : i ] , content [ i + 1 : ] ... )
2021-01-08 16:45:39 -08:00
}
2021-09-13 18:59:28 +03:00
func validateConditionsInternal ( logger logr . Logger , pattern , resource * yaml . RNode , filter func ( string ) bool ) error {
conditions , err := filterKeys ( pattern , filter )
if err != nil {
return err
}
for _ , condition := range conditions {
conditionKey := removeAnchor ( condition )
if resource == nil || resource . Field ( conditionKey ) == nil {
return fmt . Errorf ( "could not found \"%s\" key in the resource" , conditionKey )
}
err = checkCondition ( logger , pattern . Field ( condition ) . Value , resource . Field ( conditionKey ) . Value )
if err != nil {
return err
}
}
return nil
}