1
16
17 package validation
18
19 import (
20 "fmt"
21 "regexp"
22 "unicode"
23
24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25 "k8s.io/apimachinery/pkg/types"
26 "k8s.io/apimachinery/pkg/util/sets"
27 "k8s.io/apimachinery/pkg/util/validation"
28 "k8s.io/apimachinery/pkg/util/validation/field"
29 )
30
31
32 type LabelSelectorValidationOptions struct {
33
34 AllowInvalidLabelValueInSelector bool
35 }
36
37
38
39
40 func LabelSelectorHasInvalidLabelValue(ps *metav1.LabelSelector) bool {
41 if ps == nil {
42 return false
43 }
44 for _, e := range ps.MatchExpressions {
45 for _, v := range e.Values {
46 if len(validation.IsValidLabelValue(v)) > 0 {
47 return true
48 }
49 }
50 }
51 return false
52 }
53
54
55
56 func ValidateLabelSelector(ps *metav1.LabelSelector, opts LabelSelectorValidationOptions, fldPath *field.Path) field.ErrorList {
57 allErrs := field.ErrorList{}
58 if ps == nil {
59 return allErrs
60 }
61 allErrs = append(allErrs, ValidateLabels(ps.MatchLabels, fldPath.Child("matchLabels"))...)
62 for i, expr := range ps.MatchExpressions {
63 allErrs = append(allErrs, ValidateLabelSelectorRequirement(expr, opts, fldPath.Child("matchExpressions").Index(i))...)
64 }
65 return allErrs
66 }
67
68
69
70 func ValidateLabelSelectorRequirement(sr metav1.LabelSelectorRequirement, opts LabelSelectorValidationOptions, fldPath *field.Path) field.ErrorList {
71 allErrs := field.ErrorList{}
72 switch sr.Operator {
73 case metav1.LabelSelectorOpIn, metav1.LabelSelectorOpNotIn:
74 if len(sr.Values) == 0 {
75 allErrs = append(allErrs, field.Required(fldPath.Child("values"), "must be specified when `operator` is 'In' or 'NotIn'"))
76 }
77 case metav1.LabelSelectorOpExists, metav1.LabelSelectorOpDoesNotExist:
78 if len(sr.Values) > 0 {
79 allErrs = append(allErrs, field.Forbidden(fldPath.Child("values"), "may not be specified when `operator` is 'Exists' or 'DoesNotExist'"))
80 }
81 default:
82 allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), sr.Operator, "not a valid selector operator"))
83 }
84 allErrs = append(allErrs, ValidateLabelName(sr.Key, fldPath.Child("key"))...)
85 if !opts.AllowInvalidLabelValueInSelector {
86 for valueIndex, value := range sr.Values {
87 for _, msg := range validation.IsValidLabelValue(value) {
88 allErrs = append(allErrs, field.Invalid(fldPath.Child("values").Index(valueIndex), value, msg))
89 }
90 }
91 }
92 return allErrs
93 }
94
95
96 func ValidateLabelName(labelName string, fldPath *field.Path) field.ErrorList {
97 allErrs := field.ErrorList{}
98 for _, msg := range validation.IsQualifiedName(labelName) {
99 allErrs = append(allErrs, field.Invalid(fldPath, labelName, msg))
100 }
101 return allErrs
102 }
103
104
105 func ValidateLabels(labels map[string]string, fldPath *field.Path) field.ErrorList {
106 allErrs := field.ErrorList{}
107 for k, v := range labels {
108 allErrs = append(allErrs, ValidateLabelName(k, fldPath)...)
109 for _, msg := range validation.IsValidLabelValue(v) {
110 allErrs = append(allErrs, field.Invalid(fldPath, v, msg))
111 }
112 }
113 return allErrs
114 }
115
116 func ValidateDeleteOptions(options *metav1.DeleteOptions) field.ErrorList {
117 allErrs := field.ErrorList{}
118
119 if options.OrphanDependents != nil && options.PropagationPolicy != nil {
120 allErrs = append(allErrs, field.Invalid(field.NewPath("propagationPolicy"), options.PropagationPolicy, "orphanDependents and deletionPropagation cannot be both set"))
121 }
122 if options.PropagationPolicy != nil &&
123 *options.PropagationPolicy != metav1.DeletePropagationForeground &&
124 *options.PropagationPolicy != metav1.DeletePropagationBackground &&
125 *options.PropagationPolicy != metav1.DeletePropagationOrphan {
126 allErrs = append(allErrs, field.NotSupported(field.NewPath("propagationPolicy"), options.PropagationPolicy, []string{string(metav1.DeletePropagationForeground), string(metav1.DeletePropagationBackground), string(metav1.DeletePropagationOrphan), "nil"}))
127 }
128 allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...)
129 return allErrs
130 }
131
132 func ValidateCreateOptions(options *metav1.CreateOptions) field.ErrorList {
133 allErrs := field.ErrorList{}
134 allErrs = append(allErrs, ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager"))...)
135 allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...)
136 allErrs = append(allErrs, ValidateFieldValidation(field.NewPath("fieldValidation"), options.FieldValidation)...)
137 return allErrs
138 }
139
140 func ValidateUpdateOptions(options *metav1.UpdateOptions) field.ErrorList {
141 allErrs := field.ErrorList{}
142 allErrs = append(allErrs, ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager"))...)
143 allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...)
144 allErrs = append(allErrs, ValidateFieldValidation(field.NewPath("fieldValidation"), options.FieldValidation)...)
145 return allErrs
146 }
147
148 func ValidatePatchOptions(options *metav1.PatchOptions, patchType types.PatchType) field.ErrorList {
149 allErrs := field.ErrorList{}
150 if patchType != types.ApplyPatchType {
151 if options.Force != nil {
152 allErrs = append(allErrs, field.Forbidden(field.NewPath("force"), "may not be specified for non-apply patch"))
153 }
154 } else {
155 if options.FieldManager == "" {
156
157 allErrs = append(allErrs, field.Required(field.NewPath("fieldManager"), "is required for apply patch"))
158 }
159 }
160 allErrs = append(allErrs, ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager"))...)
161 allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...)
162 allErrs = append(allErrs, ValidateFieldValidation(field.NewPath("fieldValidation"), options.FieldValidation)...)
163 return allErrs
164 }
165
166 var FieldManagerMaxLength = 128
167
168
169
170 func ValidateFieldManager(fieldManager string, fldPath *field.Path) field.ErrorList {
171 allErrs := field.ErrorList{}
172
173
174
175 if len(fieldManager) > FieldManagerMaxLength {
176 allErrs = append(allErrs, field.TooLong(fldPath, fieldManager, FieldManagerMaxLength))
177 }
178
179 for i, r := range fieldManager {
180 if !unicode.IsPrint(r) {
181 allErrs = append(allErrs, field.Invalid(fldPath, fieldManager, fmt.Sprintf("invalid character %#U (at position %d)", r, i)))
182 }
183 }
184
185 return allErrs
186 }
187
188 var allowedDryRunValues = sets.NewString(metav1.DryRunAll)
189
190
191 func ValidateDryRun(fldPath *field.Path, dryRun []string) field.ErrorList {
192 allErrs := field.ErrorList{}
193 if !allowedDryRunValues.HasAll(dryRun...) {
194 allErrs = append(allErrs, field.NotSupported(fldPath, dryRun, allowedDryRunValues.List()))
195 }
196 return allErrs
197 }
198
199 var allowedFieldValidationValues = sets.NewString("", metav1.FieldValidationIgnore, metav1.FieldValidationWarn, metav1.FieldValidationStrict)
200
201
202 func ValidateFieldValidation(fldPath *field.Path, fieldValidation string) field.ErrorList {
203 allErrs := field.ErrorList{}
204 if !allowedFieldValidationValues.Has(fieldValidation) {
205 allErrs = append(allErrs, field.NotSupported(fldPath, fieldValidation, allowedFieldValidationValues.List()))
206 }
207 return allErrs
208
209 }
210
211 const UninitializedStatusUpdateErrorMsg string = `must not update status when the object is uninitialized`
212
213
214 func ValidateTableOptions(opts *metav1.TableOptions) field.ErrorList {
215 var allErrs field.ErrorList
216 switch opts.IncludeObject {
217 case metav1.IncludeMetadata, metav1.IncludeNone, metav1.IncludeObject, "":
218 default:
219 allErrs = append(allErrs, field.Invalid(field.NewPath("includeObject"), opts.IncludeObject, "must be 'Metadata', 'Object', 'None', or empty"))
220 }
221 return allErrs
222 }
223
224 const MaxSubresourceNameLength = 256
225
226 func ValidateManagedFields(fieldsList []metav1.ManagedFieldsEntry, fldPath *field.Path) field.ErrorList {
227 var allErrs field.ErrorList
228 for i, fields := range fieldsList {
229 fldPath := fldPath.Index(i)
230 switch fields.Operation {
231 case metav1.ManagedFieldsOperationApply, metav1.ManagedFieldsOperationUpdate:
232 default:
233 allErrs = append(allErrs, field.Invalid(fldPath.Child("operation"), fields.Operation, "must be `Apply` or `Update`"))
234 }
235 if len(fields.FieldsType) > 0 && fields.FieldsType != "FieldsV1" {
236 allErrs = append(allErrs, field.Invalid(fldPath.Child("fieldsType"), fields.FieldsType, "must be `FieldsV1`"))
237 }
238 allErrs = append(allErrs, ValidateFieldManager(fields.Manager, fldPath.Child("manager"))...)
239
240 if len(fields.Subresource) > MaxSubresourceNameLength {
241 allErrs = append(allErrs, field.TooLong(fldPath.Child("subresource"), fields.Subresource, MaxSubresourceNameLength))
242 }
243 }
244 return allErrs
245 }
246
247 func ValidateConditions(conditions []metav1.Condition, fldPath *field.Path) field.ErrorList {
248 var allErrs field.ErrorList
249
250 conditionTypeToFirstIndex := map[string]int{}
251 for i, condition := range conditions {
252 if _, ok := conditionTypeToFirstIndex[condition.Type]; ok {
253 allErrs = append(allErrs, field.Duplicate(fldPath.Index(i).Child("type"), condition.Type))
254 } else {
255 conditionTypeToFirstIndex[condition.Type] = i
256 }
257
258 allErrs = append(allErrs, ValidateCondition(condition, fldPath.Index(i))...)
259 }
260
261 return allErrs
262 }
263
264
265 var validConditionStatuses = sets.NewString(string(metav1.ConditionTrue), string(metav1.ConditionFalse), string(metav1.ConditionUnknown))
266
267 const (
268 maxReasonLen = 1 * 1024
269 maxMessageLen = 32 * 1024
270 )
271
272 func ValidateCondition(condition metav1.Condition, fldPath *field.Path) field.ErrorList {
273 var allErrs field.ErrorList
274
275
276 allErrs = append(allErrs, ValidateLabelName(condition.Type, fldPath.Child("type"))...)
277
278
279 if !validConditionStatuses.Has(string(condition.Status)) {
280 allErrs = append(allErrs, field.NotSupported(fldPath.Child("status"), condition.Status, validConditionStatuses.List()))
281 }
282
283 if condition.ObservedGeneration < 0 {
284 allErrs = append(allErrs, field.Invalid(fldPath.Child("observedGeneration"), condition.ObservedGeneration, "must be greater than or equal to zero"))
285 }
286
287 if condition.LastTransitionTime.IsZero() {
288 allErrs = append(allErrs, field.Required(fldPath.Child("lastTransitionTime"), "must be set"))
289 }
290
291 if len(condition.Reason) == 0 {
292 allErrs = append(allErrs, field.Required(fldPath.Child("reason"), "must be set"))
293 } else {
294 for _, currErr := range isValidConditionReason(condition.Reason) {
295 allErrs = append(allErrs, field.Invalid(fldPath.Child("reason"), condition.Reason, currErr))
296 }
297 if len(condition.Reason) > maxReasonLen {
298 allErrs = append(allErrs, field.TooLong(fldPath.Child("reason"), condition.Reason, maxReasonLen))
299 }
300 }
301
302 if len(condition.Message) > maxMessageLen {
303 allErrs = append(allErrs, field.TooLong(fldPath.Child("message"), condition.Message, maxMessageLen))
304 }
305
306 return allErrs
307 }
308
309 const conditionReasonFmt string = "[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?"
310 const conditionReasonErrMsg string = "a condition reason must start with alphabetic character, optionally followed by a string of alphanumeric characters or '_,:', and must end with an alphanumeric character or '_'"
311
312 var conditionReasonRegexp = regexp.MustCompile("^" + conditionReasonFmt + "$")
313
314
315 func isValidConditionReason(value string) []string {
316 if !conditionReasonRegexp.MatchString(value) {
317 return []string{validation.RegexError(conditionReasonErrMsg, conditionReasonFmt, "my_name", "MY_NAME", "MyName", "ReasonA,ReasonB", "ReasonA:ReasonB")}
318 }
319 return nil
320 }
321
View as plain text