blob: 44b9b1600663356e85c44c6f6a0a48100804b2c9 [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001/*
2Copyright 2014 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package validation
18
19import (
20 "fmt"
21 "strings"
22
23 apiequality "k8s.io/apimachinery/pkg/api/equality"
24 "k8s.io/apimachinery/pkg/api/meta"
25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 v1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
27 "k8s.io/apimachinery/pkg/runtime/schema"
28 "k8s.io/apimachinery/pkg/util/sets"
29 "k8s.io/apimachinery/pkg/util/validation"
30 "k8s.io/apimachinery/pkg/util/validation/field"
31)
32
33const FieldImmutableErrorMsg string = `field is immutable`
34
35const totalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB
36
37// BannedOwners is a black list of object that are not allowed to be owners.
38var BannedOwners = map[schema.GroupVersionKind]struct{}{
39 {Group: "", Version: "v1", Kind: "Event"}: {},
40}
41
42// ValidateClusterName can be used to check whether the given cluster name is valid.
43var ValidateClusterName = NameIsDNS1035Label
44
45// ValidateAnnotations validates that a set of annotations are correctly defined.
46func ValidateAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList {
47 allErrs := field.ErrorList{}
48 var totalSize int64
49 for k, v := range annotations {
50 for _, msg := range validation.IsQualifiedName(strings.ToLower(k)) {
51 allErrs = append(allErrs, field.Invalid(fldPath, k, msg))
52 }
53 totalSize += (int64)(len(k)) + (int64)(len(v))
54 }
55 if totalSize > (int64)(totalAnnotationSizeLimitB) {
56 allErrs = append(allErrs, field.TooLong(fldPath, "", totalAnnotationSizeLimitB))
57 }
58 return allErrs
59}
60
61func validateOwnerReference(ownerReference metav1.OwnerReference, fldPath *field.Path) field.ErrorList {
62 allErrs := field.ErrorList{}
63 gvk := schema.FromAPIVersionAndKind(ownerReference.APIVersion, ownerReference.Kind)
64 // gvk.Group is empty for the legacy group.
65 if len(gvk.Version) == 0 {
66 allErrs = append(allErrs, field.Invalid(fldPath.Child("apiVersion"), ownerReference.APIVersion, "version must not be empty"))
67 }
68 if len(gvk.Kind) == 0 {
69 allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), ownerReference.Kind, "kind must not be empty"))
70 }
71 if len(ownerReference.Name) == 0 {
72 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), ownerReference.Name, "name must not be empty"))
73 }
74 if len(ownerReference.UID) == 0 {
75 allErrs = append(allErrs, field.Invalid(fldPath.Child("uid"), ownerReference.UID, "uid must not be empty"))
76 }
77 if _, ok := BannedOwners[gvk]; ok {
78 allErrs = append(allErrs, field.Invalid(fldPath, ownerReference, fmt.Sprintf("%s is disallowed from being an owner", gvk)))
79 }
80 return allErrs
81}
82
83func ValidateOwnerReferences(ownerReferences []metav1.OwnerReference, fldPath *field.Path) field.ErrorList {
84 allErrs := field.ErrorList{}
85 controllerName := ""
86 for _, ref := range ownerReferences {
87 allErrs = append(allErrs, validateOwnerReference(ref, fldPath)...)
88 if ref.Controller != nil && *ref.Controller {
89 if controllerName != "" {
90 allErrs = append(allErrs, field.Invalid(fldPath, ownerReferences,
91 fmt.Sprintf("Only one reference can have Controller set to true. Found \"true\" in references for %v and %v", controllerName, ref.Name)))
92 } else {
93 controllerName = ref.Name
94 }
95 }
96 }
97 return allErrs
98}
99
100// Validate finalizer names
101func ValidateFinalizerName(stringValue string, fldPath *field.Path) field.ErrorList {
102 allErrs := field.ErrorList{}
103 for _, msg := range validation.IsQualifiedName(stringValue) {
104 allErrs = append(allErrs, field.Invalid(fldPath, stringValue, msg))
105 }
106
107 return allErrs
108}
109
110func ValidateNoNewFinalizers(newFinalizers []string, oldFinalizers []string, fldPath *field.Path) field.ErrorList {
111 allErrs := field.ErrorList{}
112 extra := sets.NewString(newFinalizers...).Difference(sets.NewString(oldFinalizers...))
113 if len(extra) != 0 {
114 allErrs = append(allErrs, field.Forbidden(fldPath, fmt.Sprintf("no new finalizers can be added if the object is being deleted, found new finalizers %#v", extra.List())))
115 }
116 return allErrs
117}
118
119func ValidateImmutableField(newVal, oldVal interface{}, fldPath *field.Path) field.ErrorList {
120 allErrs := field.ErrorList{}
121 if !apiequality.Semantic.DeepEqual(oldVal, newVal) {
122 allErrs = append(allErrs, field.Invalid(fldPath, newVal, FieldImmutableErrorMsg))
123 }
124 return allErrs
125}
126
127// ValidateObjectMeta validates an object's metadata on creation. It expects that name generation has already
128// been performed.
129// It doesn't return an error for rootscoped resources with namespace, because namespace should already be cleared before.
130func ValidateObjectMeta(objMeta *metav1.ObjectMeta, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path) field.ErrorList {
131 metadata, err := meta.Accessor(objMeta)
132 if err != nil {
133 allErrs := field.ErrorList{}
134 allErrs = append(allErrs, field.Invalid(fldPath, objMeta, err.Error()))
135 return allErrs
136 }
137 return ValidateObjectMetaAccessor(metadata, requiresNamespace, nameFn, fldPath)
138}
139
140// ValidateObjectMeta validates an object's metadata on creation. It expects that name generation has already
141// been performed.
142// It doesn't return an error for rootscoped resources with namespace, because namespace should already be cleared before.
143func ValidateObjectMetaAccessor(meta metav1.Object, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path) field.ErrorList {
144 allErrs := field.ErrorList{}
145
146 if len(meta.GetGenerateName()) != 0 {
147 for _, msg := range nameFn(meta.GetGenerateName(), true) {
148 allErrs = append(allErrs, field.Invalid(fldPath.Child("generateName"), meta.GetGenerateName(), msg))
149 }
150 }
151 // If the generated name validates, but the calculated value does not, it's a problem with generation, and we
152 // report it here. This may confuse users, but indicates a programming bug and still must be validated.
153 // If there are multiple fields out of which one is required then add an or as a separator
154 if len(meta.GetName()) == 0 {
155 allErrs = append(allErrs, field.Required(fldPath.Child("name"), "name or generateName is required"))
156 } else {
157 for _, msg := range nameFn(meta.GetName(), false) {
158 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), meta.GetName(), msg))
159 }
160 }
161 if requiresNamespace {
162 if len(meta.GetNamespace()) == 0 {
163 allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), ""))
164 } else {
165 for _, msg := range ValidateNamespaceName(meta.GetNamespace(), false) {
166 allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), meta.GetNamespace(), msg))
167 }
168 }
169 } else {
170 if len(meta.GetNamespace()) != 0 {
171 allErrs = append(allErrs, field.Forbidden(fldPath.Child("namespace"), "not allowed on this type"))
172 }
173 }
174 if len(meta.GetClusterName()) != 0 {
175 for _, msg := range ValidateClusterName(meta.GetClusterName(), false) {
176 allErrs = append(allErrs, field.Invalid(fldPath.Child("clusterName"), meta.GetClusterName(), msg))
177 }
178 }
179 allErrs = append(allErrs, ValidateNonnegativeField(meta.GetGeneration(), fldPath.Child("generation"))...)
180 allErrs = append(allErrs, v1validation.ValidateLabels(meta.GetLabels(), fldPath.Child("labels"))...)
181 allErrs = append(allErrs, ValidateAnnotations(meta.GetAnnotations(), fldPath.Child("annotations"))...)
182 allErrs = append(allErrs, ValidateOwnerReferences(meta.GetOwnerReferences(), fldPath.Child("ownerReferences"))...)
183 allErrs = append(allErrs, ValidateInitializers(meta.GetInitializers(), fldPath.Child("initializers"))...)
184 allErrs = append(allErrs, ValidateFinalizers(meta.GetFinalizers(), fldPath.Child("finalizers"))...)
185 return allErrs
186}
187
188func ValidateInitializers(initializers *metav1.Initializers, fldPath *field.Path) field.ErrorList {
189 var allErrs field.ErrorList
190 if initializers == nil {
191 return allErrs
192 }
193 for i, initializer := range initializers.Pending {
194 allErrs = append(allErrs, validation.IsFullyQualifiedName(fldPath.Child("pending").Index(i).Child("name"), initializer.Name)...)
195 }
196 allErrs = append(allErrs, validateInitializersResult(initializers.Result, fldPath.Child("result"))...)
197 return allErrs
198}
199
200func validateInitializersResult(result *metav1.Status, fldPath *field.Path) field.ErrorList {
201 var allErrs field.ErrorList
202 if result == nil {
203 return allErrs
204 }
205 switch result.Status {
206 case metav1.StatusFailure:
207 default:
208 allErrs = append(allErrs, field.Invalid(fldPath.Child("status"), result.Status, "must be 'Failure'"))
209 }
210 return allErrs
211}
212
213// ValidateFinalizers tests if the finalizers name are valid, and if there are conflicting finalizers.
214func ValidateFinalizers(finalizers []string, fldPath *field.Path) field.ErrorList {
215 allErrs := field.ErrorList{}
216 hasFinalizerOrphanDependents := false
217 hasFinalizerDeleteDependents := false
218 for _, finalizer := range finalizers {
219 allErrs = append(allErrs, ValidateFinalizerName(finalizer, fldPath)...)
220 if finalizer == metav1.FinalizerOrphanDependents {
221 hasFinalizerOrphanDependents = true
222 }
223 if finalizer == metav1.FinalizerDeleteDependents {
224 hasFinalizerDeleteDependents = true
225 }
226 }
227 if hasFinalizerDeleteDependents && hasFinalizerOrphanDependents {
228 allErrs = append(allErrs, field.Invalid(fldPath, finalizers, fmt.Sprintf("finalizer %s and %s cannot be both set", metav1.FinalizerOrphanDependents, metav1.FinalizerDeleteDependents)))
229 }
230 return allErrs
231}
232
233// ValidateObjectMetaUpdate validates an object's metadata when updated
234func ValidateObjectMetaUpdate(newMeta, oldMeta *metav1.ObjectMeta, fldPath *field.Path) field.ErrorList {
235 newMetadata, err := meta.Accessor(newMeta)
236 if err != nil {
237 allErrs := field.ErrorList{}
238 allErrs = append(allErrs, field.Invalid(fldPath, newMeta, err.Error()))
239 return allErrs
240 }
241 oldMetadata, err := meta.Accessor(oldMeta)
242 if err != nil {
243 allErrs := field.ErrorList{}
244 allErrs = append(allErrs, field.Invalid(fldPath, oldMeta, err.Error()))
245 return allErrs
246 }
247 return ValidateObjectMetaAccessorUpdate(newMetadata, oldMetadata, fldPath)
248}
249
250func ValidateObjectMetaAccessorUpdate(newMeta, oldMeta metav1.Object, fldPath *field.Path) field.ErrorList {
251 var allErrs field.ErrorList
252
253 // Finalizers cannot be added if the object is already being deleted.
254 if oldMeta.GetDeletionTimestamp() != nil {
255 allErrs = append(allErrs, ValidateNoNewFinalizers(newMeta.GetFinalizers(), oldMeta.GetFinalizers(), fldPath.Child("finalizers"))...)
256 }
257
258 // Reject updates that don't specify a resource version
259 if len(newMeta.GetResourceVersion()) == 0 {
260 allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceVersion"), newMeta.GetResourceVersion(), "must be specified for an update"))
261 }
262
263 // Generation shouldn't be decremented
264 if newMeta.GetGeneration() < oldMeta.GetGeneration() {
265 allErrs = append(allErrs, field.Invalid(fldPath.Child("generation"), newMeta.GetGeneration(), "must not be decremented"))
266 }
267
268 allErrs = append(allErrs, ValidateInitializersUpdate(newMeta.GetInitializers(), oldMeta.GetInitializers(), fldPath.Child("initializers"))...)
269
270 allErrs = append(allErrs, ValidateImmutableField(newMeta.GetName(), oldMeta.GetName(), fldPath.Child("name"))...)
271 allErrs = append(allErrs, ValidateImmutableField(newMeta.GetNamespace(), oldMeta.GetNamespace(), fldPath.Child("namespace"))...)
272 allErrs = append(allErrs, ValidateImmutableField(newMeta.GetUID(), oldMeta.GetUID(), fldPath.Child("uid"))...)
273 allErrs = append(allErrs, ValidateImmutableField(newMeta.GetCreationTimestamp(), oldMeta.GetCreationTimestamp(), fldPath.Child("creationTimestamp"))...)
274 allErrs = append(allErrs, ValidateImmutableField(newMeta.GetDeletionTimestamp(), oldMeta.GetDeletionTimestamp(), fldPath.Child("deletionTimestamp"))...)
275 allErrs = append(allErrs, ValidateImmutableField(newMeta.GetDeletionGracePeriodSeconds(), oldMeta.GetDeletionGracePeriodSeconds(), fldPath.Child("deletionGracePeriodSeconds"))...)
276 allErrs = append(allErrs, ValidateImmutableField(newMeta.GetClusterName(), oldMeta.GetClusterName(), fldPath.Child("clusterName"))...)
277
278 allErrs = append(allErrs, v1validation.ValidateLabels(newMeta.GetLabels(), fldPath.Child("labels"))...)
279 allErrs = append(allErrs, ValidateAnnotations(newMeta.GetAnnotations(), fldPath.Child("annotations"))...)
280 allErrs = append(allErrs, ValidateOwnerReferences(newMeta.GetOwnerReferences(), fldPath.Child("ownerReferences"))...)
281
282 return allErrs
283}
284
285// ValidateInitializersUpdate checks the update of the metadata initializers field
286func ValidateInitializersUpdate(newInit, oldInit *metav1.Initializers, fldPath *field.Path) field.ErrorList {
287 var allErrs field.ErrorList
288 switch {
289 case oldInit == nil && newInit != nil:
290 // Initializers may not be set on new objects
291 allErrs = append(allErrs, field.Invalid(fldPath, nil, "field is immutable once initialization has completed"))
292 case oldInit != nil && newInit == nil:
293 // this is a valid transition and means initialization was successful
294 case oldInit != nil && newInit != nil:
295 // validate changes to initializers
296 switch {
297 case oldInit.Result == nil && newInit.Result != nil:
298 // setting a result is allowed
299 allErrs = append(allErrs, validateInitializersResult(newInit.Result, fldPath.Child("result"))...)
300 case oldInit.Result != nil:
301 // setting Result implies permanent failure, and all future updates will be prevented
302 allErrs = append(allErrs, ValidateImmutableField(newInit.Result, oldInit.Result, fldPath.Child("result"))...)
303 default:
304 // leaving the result nil is allowed
305 }
306 }
307 return allErrs
308}