1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-04-08 10:04:25 +00:00

implement global anchor for patch strategic merge

Signed-off-by: Max Goncharenko <kacejot@fex.net>
This commit is contained in:
Max Goncharenko 2021-07-27 18:20:29 +03:00
parent 749854c589
commit 13a3cc3628
3 changed files with 119 additions and 22 deletions

View file

@ -17,6 +17,24 @@ func IsConditionAnchor(str string) bool {
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
}
//TODO: trim spaces ?
return (str[:len(left)] == left && str[len(str)-len(right):] == right)
}
//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("

View file

@ -22,6 +22,18 @@ func NewConditionError(err error) error {
return ConditionError{err}
}
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 ConditionError{err}
}
// preProcessPattern - Dynamically preProcess the yaml
// 1> For conditional anchor remove anchors from the pattern.
// 2> For Adding anchors remove anchor tags.
@ -52,7 +64,6 @@ func preProcessRecursive(logger logr.Logger, pattern, resource *yaml.RNode) erro
return nil
}
// walkMap - walk through the MappingNode
func walkMap(logger logr.Logger, pattern, resource *yaml.RNode) error {
var err error
@ -92,7 +103,6 @@ func walkMap(logger logr.Logger, pattern, resource *yaml.RNode) error {
return nil
}
// walkList - walk through array elements
func walkList(logger logr.Logger, pattern, resource *yaml.RNode) error {
elements, err := pattern.Elements()
if err != nil {
@ -173,23 +183,18 @@ func processListOfMaps(logger logr.Logger, pattern, resource *yaml.RNode) error
// 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.
// If caller handles list of maps and gets GlobalConditionError, it must skip entire rule.
// If caller handles map, it must stop processing and skip entire rule.
func validateConditions(logger logr.Logger, pattern, resource *yaml.RNode) error {
conditions, err := filterKeys(pattern, anchor.IsConditionAnchor)
var err error
err = validateConditionsInternal(logger, pattern, resource, anchor.IsGlobalAnchor)
if err != nil {
return err
return NewGlobalConditionError(err)
}
for _, condition := range conditions {
conditionKey := removeAnchor(condition)
if resource == nil || resource.Field(conditionKey) == nil {
continue
}
err = checkCondition(logger, pattern.Field(condition).Value, resource.Field(conditionKey).Value)
if err != nil {
return err
}
err = validateConditionsInternal(logger, pattern, resource, anchor.IsConditionAnchor)
if err != nil {
return NewConditionError(err)
}
return nil
@ -247,7 +252,7 @@ func hasAnchors(pattern *yaml.RNode) bool {
}
for _, key := range fields {
if anchor.IsConditionAnchor(key) || anchor.IsAddingAnchor(key) {
if anchor.ContainsCondition(key) || anchor.IsAddingAnchor(key) {
return true
}
@ -304,11 +309,8 @@ func checkCondition(logger logr.Logger, pattern *yaml.RNode, resource *yaml.RNod
}
_, err = validate.ValidateResourceWithPattern(logger, resourceInterface, patternInterface)
if err != nil {
return NewConditionError(err)
}
return nil
return err
}
func deleteConditionsFromNestedMaps(pattern *yaml.RNode) error {
@ -322,7 +324,7 @@ func deleteConditionsFromNestedMaps(pattern *yaml.RNode) error {
}
for _, field := range fields {
if anchor.IsConditionAnchor(field) {
if anchor.ContainsCondition(field) {
err = pattern.PipeE(yaml.Clear(field))
if err != nil {
return err
@ -381,7 +383,7 @@ func deleteAnchors(node *yaml.RNode) (bool, error) {
}
func deleteAnchorsInMap(node *yaml.RNode) (bool, error) {
conditions, err := filterKeys(node, anchor.IsConditionAnchor)
conditions, err := filterKeys(node, anchor.ContainsCondition)
if err != nil {
return false, err
}
@ -457,3 +459,24 @@ func deleteListElement(list *yaml.RNode, i int) {
content := list.YNode().Content
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)
if err != nil {
return err
}
for _, condition := range conditions {
conditionKey := removeAnchor(condition)
if resource == nil || resource.Field(conditionKey) == nil {
continue
}
err = checkCondition(logger, pattern.Field(condition).Value, resource.Field(conditionKey).Value)
if err != nil {
return err
}
}
return nil
}

View file

@ -735,6 +735,62 @@ func Test_preProcessStrategicMergePatch_multipleAnchors(t *testing.T) {
}
}`),
},
{
rawPolicy: []byte(`{
"metadata": {
"annotations": {
"+(cluster-autoscaler.kubernetes.io/safe-to-evict)": true
}
},
"spec": {
"volumes": [
{
"hostPath": {
"<(path)": "*data"
}
}
]
}
}`),
rawResource: []byte(`{
"kind": "Pod",
"apiVersion": "v1",
"metadata": {
"name": "nginx"
},
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx:latest",
"imagePullPolicy": "Never",
"volumeMounts": [
{
"mountPath": "/cache",
"name": "cache-volume"
}
]
}
],
"volumes": [
{
"name": "cache-volume",
"hostPath": {
"path": "/data",
"type": "Directory"
}
}
]
}
}`),
expectedPatch: []byte(`{
"metadata": {
"annotations": {
"cluster-autoscaler.kubernetes.io/safe-to-evict": true
}
}
}`),
},
}
for i, test := range testCases {
@ -807,7 +863,7 @@ func Test_CheckConditionAnchor_DoesNotMatch(t *testing.T) {
resource := yaml.MustParse(string(resourceRaw))
err := checkCondition(log.Log, pattern, resource)
assert.Error(t, err, "Condition failed: Validation rule failed at '/key1/' to validate value 'sample' with pattern 'value*'")
assert.Error(t, err, "Validation rule failed at '/key1/' to validate value 'sample' with pattern 'value*'")
}
func Test_ValidateConditions_MapWithOneCondition_Matches(t *testing.T) {