-
Notifications
You must be signed in to change notification settings - Fork 19
/
resourceoverride.go
162 lines (141 loc) · 5.55 KB
/
resourceoverride.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/*
Copyright (c) Microsoft Corporation.
Licensed under the MIT license.
*/
// Package validator provides utils to validate ResourceOverride resources.
package validator
import (
"errors"
"fmt"
"strings"
apierrors "k8s.io/apimachinery/pkg/util/errors"
fleetv1alpha1 "go.goms.io/fleet/apis/placement/v1alpha1"
)
// ValidateResourceOverride validates resource override fields and returns error.
func ValidateResourceOverride(ro fleetv1alpha1.ResourceOverride, roList *fleetv1alpha1.ResourceOverrideList) error {
allErr := make([]error, 0)
// Check if the resource is being selected by resource name.
if err := validateResourceSelectors(ro); err != nil {
// Skip the resource limit check because the check is only valid if resource selectors are valid.
return err
}
// Check if the override count limit for the resources has been reached.
if err := validateResourceOverrideResourceLimit(ro, roList); err != nil {
allErr = append(allErr, err)
}
if ro.Spec.Policy != nil {
if err := validateOverridePolicy(ro.Spec.Policy); err != nil {
allErr = append(allErr, err)
}
}
return apierrors.NewAggregate(allErr)
}
// validateResourceSelectors checks if override is selecting a unique resource.
func validateResourceSelectors(ro fleetv1alpha1.ResourceOverride) error {
selectorMap := make(map[fleetv1alpha1.ResourceSelector]bool)
allErr := make([]error, 0)
for _, selector := range ro.Spec.ResourceSelectors {
// Check if there are any duplicate selectors.
if selectorMap[selector] {
allErr = append(allErr, fmt.Errorf("resource selector %+v already exists, and must be unique", selector))
}
selectorMap[selector] = true
}
return apierrors.NewAggregate(allErr)
}
// validateResourceOverrideResourceLimit checks if there is only 1 resource override per resource,
// assuming the resource will be selected by the name only.
func validateResourceOverrideResourceLimit(ro fleetv1alpha1.ResourceOverride, roList *fleetv1alpha1.ResourceOverrideList) error {
// Check if roList is nil or empty, no need to check for resource limit.
if roList == nil || len(roList.Items) == 0 {
return nil
}
overrideMap := make(map[fleetv1alpha1.ResourceSelector]string)
// Add overrides and its selectors to the map.
for _, override := range roList.Items {
selectors := override.Spec.ResourceSelectors
for _, selector := range selectors {
overrideMap[selector] = override.GetName()
}
}
allErr := make([]error, 0)
// Check if any of the ro selectors exist in the override map.
for _, roSelector := range ro.Spec.ResourceSelectors {
if overrideMap[roSelector] != "" {
// Ignore the same resource override.
if ro.GetName() == overrideMap[roSelector] {
continue
}
allErr = append(allErr, fmt.Errorf("invalid resource selector %+v: the resource has been selected by both %v and %v, which is not supported", roSelector, ro.GetName(), overrideMap[roSelector]))
}
}
return apierrors.NewAggregate(allErr)
}
// validateOverridePolicy checks if override rule is selecting resource by name.
func validateOverridePolicy(policy *fleetv1alpha1.OverridePolicy) error {
allErr := make([]error, 0)
for _, rule := range policy.OverrideRules {
if rule.ClusterSelector != nil {
for _, selector := range rule.ClusterSelector.ClusterSelectorTerms {
// Check that only label selector is supported
if selector.PropertySelector != nil || selector.PropertySorter != nil {
allErr = append(allErr, fmt.Errorf("invalid clusterSelector %v: only labelSelector is supported", selector))
continue
}
if selector.LabelSelector == nil {
allErr = append(allErr, fmt.Errorf("invalid clusterSelector %v: labelSelector is required", selector))
} else if err := validateLabelSelector(selector.LabelSelector, "cluster selector"); err != nil {
allErr = append(allErr, err)
}
}
}
if err := validateJSONPatchOverride(rule.JSONPatchOverrides); err != nil {
allErr = append(allErr, err)
}
}
return apierrors.NewAggregate(allErr)
}
// validateJSONPatchOverride checks if JSON patch override is valid.
func validateJSONPatchOverride(jsonPatchOverrides []fleetv1alpha1.JSONPatchOverride) error {
if len(jsonPatchOverrides) == 0 {
return errors.New("invalid JSONPatchOverrides: JSONPatchOverrides cannot be empty")
}
allErr := make([]error, 0)
for _, patch := range jsonPatchOverrides {
if err := validateJSONPatchOverridePath(patch.Path); err != nil {
allErr = append(allErr, fmt.Errorf("invalid JSONPatchOverride %s: %w", patch, err))
}
if patch.Operator == fleetv1alpha1.JSONPatchOverrideOpRemove && len(patch.Value.Raw) != 0 {
allErr = append(allErr, fmt.Errorf("invalid JSONPatchOverride %s: remove operation cannot have value", patch))
}
}
return apierrors.NewAggregate(allErr)
}
func validateJSONPatchOverridePath(path string) error {
if path == "" {
return fmt.Errorf("path cannot be empty")
}
if !strings.HasPrefix(path, "/") {
return fmt.Errorf("path must start with /")
}
// The path begins with a slash, and at least there will be two elements.
parts := strings.Split(path, "/")[1:]
switch parts[0] {
case "kind", "apiVersion":
return fmt.Errorf("cannot override typeMeta fields")
case "metadata":
if len(parts) == 1 {
return fmt.Errorf("cannot override field metadata")
} else if parts[1] != "annotations" && parts[1] != "labels" {
return fmt.Errorf("cannot override metadata fields except annotations and labels")
}
case "status":
return fmt.Errorf("cannot override status fields")
}
for i := range parts {
if len(strings.TrimSpace(parts[i])) == 0 {
return fmt.Errorf("path cannot contain empty string")
}
}
return nil
}