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:
parent
749854c589
commit
13a3cc3628
3 changed files with 119 additions and 22 deletions
|
@ -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("
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Reference in a new issue