1
16
17 package validation
18
19 import (
20 "encoding/json"
21 "fmt"
22 "math"
23 "net"
24 "path"
25 "path/filepath"
26 "reflect"
27 "regexp"
28 "strings"
29 "sync"
30 "unicode"
31 "unicode/utf8"
32
33 "github.com/google/go-cmp/cmp"
34 v1 "k8s.io/api/core/v1"
35 apiequality "k8s.io/apimachinery/pkg/api/equality"
36 "k8s.io/apimachinery/pkg/api/resource"
37 apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
38 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
39 unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
40 "k8s.io/apimachinery/pkg/conversion"
41 "k8s.io/apimachinery/pkg/labels"
42 "k8s.io/apimachinery/pkg/util/intstr"
43 "k8s.io/apimachinery/pkg/util/sets"
44 "k8s.io/apimachinery/pkg/util/validation"
45 "k8s.io/apimachinery/pkg/util/validation/field"
46 utilfeature "k8s.io/apiserver/pkg/util/feature"
47 utilsysctl "k8s.io/component-helpers/node/util/sysctl"
48 schedulinghelper "k8s.io/component-helpers/scheduling/corev1"
49 kubeletapis "k8s.io/kubelet/pkg/apis"
50 apiservice "k8s.io/kubernetes/pkg/api/service"
51 "k8s.io/kubernetes/pkg/apis/core"
52 "k8s.io/kubernetes/pkg/apis/core/helper"
53 "k8s.io/kubernetes/pkg/apis/core/helper/qos"
54 podshelper "k8s.io/kubernetes/pkg/apis/core/pods"
55 corev1 "k8s.io/kubernetes/pkg/apis/core/v1"
56 "k8s.io/kubernetes/pkg/capabilities"
57 "k8s.io/kubernetes/pkg/cluster/ports"
58 "k8s.io/kubernetes/pkg/features"
59 "k8s.io/kubernetes/pkg/fieldpath"
60 netutils "k8s.io/utils/net"
61 )
62
63 const isNegativeErrorMsg string = apimachineryvalidation.IsNegativeErrorMsg
64 const isInvalidQuotaResource string = `must be a standard resource for quota`
65 const fieldImmutableErrorMsg string = apimachineryvalidation.FieldImmutableErrorMsg
66 const isNotIntegerErrorMsg string = `must be an integer`
67 const isNotPositiveErrorMsg string = `must be greater than zero`
68
69 var pdPartitionErrorMsg string = validation.InclusiveRangeError(1, 255)
70 var fileModeErrorMsg = "must be a number between 0 and 0777 (octal), both inclusive"
71
72
73 var BannedOwners = apimachineryvalidation.BannedOwners
74
75 var iscsiInitiatorIqnRegex = regexp.MustCompile(`iqn\.\d{4}-\d{2}\.([[:alnum:]-.]+)(:[^,;*&$|\s]+)$`)
76 var iscsiInitiatorEuiRegex = regexp.MustCompile(`^eui.[[:alnum:]]{16}$`)
77 var iscsiInitiatorNaaRegex = regexp.MustCompile(`^naa.[[:alnum:]]{32}$`)
78
79 var allowedEphemeralContainerFields = map[string]bool{
80 "Name": true,
81 "Image": true,
82 "Command": true,
83 "Args": true,
84 "WorkingDir": true,
85 "Ports": false,
86 "EnvFrom": true,
87 "Env": true,
88 "Resources": false,
89 "VolumeMounts": true,
90 "VolumeDevices": true,
91 "LivenessProbe": false,
92 "ReadinessProbe": false,
93 "StartupProbe": false,
94 "Lifecycle": false,
95 "TerminationMessagePath": true,
96 "TerminationMessagePolicy": true,
97 "ImagePullPolicy": true,
98 "SecurityContext": true,
99 "Stdin": true,
100 "StdinOnce": true,
101 "TTY": true,
102 }
103
104
105
106
107
108 var validOS = sets.New(core.Linux, core.Windows)
109
110
111 func ValidateHasLabel(meta metav1.ObjectMeta, fldPath *field.Path, key, expectedValue string) field.ErrorList {
112 allErrs := field.ErrorList{}
113 actualValue, found := meta.Labels[key]
114 if !found {
115 allErrs = append(allErrs, field.Required(fldPath.Child("labels").Key(key),
116 fmt.Sprintf("must be '%s'", expectedValue)))
117 return allErrs
118 }
119 if actualValue != expectedValue {
120 allErrs = append(allErrs, field.Invalid(fldPath.Child("labels").Key(key), meta.Labels,
121 fmt.Sprintf("must be '%s'", expectedValue)))
122 }
123 return allErrs
124 }
125
126
127 func ValidateAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList {
128 return apimachineryvalidation.ValidateAnnotations(annotations, fldPath)
129 }
130
131 func ValidateDNS1123Label(value string, fldPath *field.Path) field.ErrorList {
132 allErrs := field.ErrorList{}
133 for _, msg := range validation.IsDNS1123Label(value) {
134 allErrs = append(allErrs, field.Invalid(fldPath, value, msg))
135 }
136 return allErrs
137 }
138
139
140 func ValidateQualifiedName(value string, fldPath *field.Path) field.ErrorList {
141 allErrs := field.ErrorList{}
142 for _, msg := range validation.IsQualifiedName(value) {
143 allErrs = append(allErrs, field.Invalid(fldPath, value, msg))
144 }
145 return allErrs
146 }
147
148
149 func ValidateDNS1123Subdomain(value string, fldPath *field.Path) field.ErrorList {
150 allErrs := field.ErrorList{}
151 for _, msg := range validation.IsDNS1123Subdomain(value) {
152 allErrs = append(allErrs, field.Invalid(fldPath, value, msg))
153 }
154 return allErrs
155 }
156
157 func ValidatePodSpecificAnnotations(annotations map[string]string, spec *core.PodSpec, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
158 allErrs := field.ErrorList{}
159
160 if value, isMirror := annotations[core.MirrorPodAnnotationKey]; isMirror {
161 if len(spec.NodeName) == 0 {
162 allErrs = append(allErrs, field.Invalid(fldPath.Key(core.MirrorPodAnnotationKey), value, "must set spec.nodeName if mirror pod annotation is set"))
163 }
164 }
165
166 if annotations[core.TolerationsAnnotationKey] != "" {
167 allErrs = append(allErrs, ValidateTolerationsInPodAnnotations(annotations, fldPath)...)
168 }
169
170 if !opts.AllowInvalidPodDeletionCost {
171 if _, err := helper.GetDeletionCostFromPodAnnotations(annotations); err != nil {
172 allErrs = append(allErrs, field.Invalid(fldPath.Key(core.PodDeletionCost), annotations[core.PodDeletionCost], "must be a 32bit integer"))
173 }
174 }
175
176 allErrs = append(allErrs, ValidateSeccompPodAnnotations(annotations, fldPath)...)
177 allErrs = append(allErrs, ValidateAppArmorPodAnnotations(annotations, spec, fldPath)...)
178
179 return allErrs
180 }
181
182
183 func ValidateTolerationsInPodAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList {
184 allErrs := field.ErrorList{}
185
186 tolerations, err := helper.GetTolerationsFromPodAnnotations(annotations)
187 if err != nil {
188 allErrs = append(allErrs, field.Invalid(fldPath, core.TolerationsAnnotationKey, err.Error()))
189 return allErrs
190 }
191
192 if len(tolerations) > 0 {
193 allErrs = append(allErrs, ValidateTolerations(tolerations, fldPath.Child(core.TolerationsAnnotationKey))...)
194 }
195
196 return allErrs
197 }
198
199 func ValidatePodSpecificAnnotationUpdates(newPod, oldPod *core.Pod, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
200 allErrs := field.ErrorList{}
201 newAnnotations := newPod.Annotations
202 oldAnnotations := oldPod.Annotations
203 for k, oldVal := range oldAnnotations {
204 if newVal, exists := newAnnotations[k]; exists && newVal == oldVal {
205 continue
206 }
207 if strings.HasPrefix(k, v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix) {
208 allErrs = append(allErrs, field.Forbidden(fldPath.Key(k), "may not remove or update AppArmor annotations"))
209 }
210 if k == core.MirrorPodAnnotationKey {
211 allErrs = append(allErrs, field.Forbidden(fldPath.Key(k), "may not remove or update mirror pod annotation"))
212 }
213 }
214
215 for k := range newAnnotations {
216 if _, ok := oldAnnotations[k]; ok {
217 continue
218 }
219 if strings.HasPrefix(k, v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix) {
220 allErrs = append(allErrs, field.Forbidden(fldPath.Key(k), "may not add AppArmor annotations"))
221 }
222 if k == core.MirrorPodAnnotationKey {
223 allErrs = append(allErrs, field.Forbidden(fldPath.Key(k), "may not add mirror pod annotation"))
224 }
225 }
226 allErrs = append(allErrs, ValidatePodSpecificAnnotations(newAnnotations, &newPod.Spec, fldPath, opts)...)
227 return allErrs
228 }
229
230 func ValidateEndpointsSpecificAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList {
231 allErrs := field.ErrorList{}
232 return allErrs
233 }
234
235
236
237
238
239
240 type ValidateNameFunc apimachineryvalidation.ValidateNameFunc
241
242
243
244
245 var ValidatePodName = apimachineryvalidation.NameIsDNSSubdomain
246
247
248
249
250
251 var ValidateReplicationControllerName = apimachineryvalidation.NameIsDNSSubdomain
252
253
254
255
256 var ValidateServiceName = apimachineryvalidation.NameIsDNS1035Label
257
258
259
260
261 var ValidateNodeName = apimachineryvalidation.NameIsDNSSubdomain
262
263
264
265
266 var ValidateNamespaceName = apimachineryvalidation.ValidateNamespaceName
267
268
269
270
271 var ValidateLimitRangeName = apimachineryvalidation.NameIsDNSSubdomain
272
273
274
275
276
277 var ValidateResourceQuotaName = apimachineryvalidation.NameIsDNSSubdomain
278
279
280
281
282 var ValidateSecretName = apimachineryvalidation.NameIsDNSSubdomain
283
284
285
286
287 var ValidateServiceAccountName = apimachineryvalidation.ValidateServiceAccountName
288
289
290
291
292 var ValidateEndpointsName = apimachineryvalidation.NameIsDNSSubdomain
293
294
295
296
297 var ValidateClassName = apimachineryvalidation.NameIsDNSSubdomain
298
299
300
301 var ValidatePriorityClassName = apimachineryvalidation.NameIsDNSSubdomain
302
303
304
305 var ValidateResourceClaimName = apimachineryvalidation.NameIsDNSSubdomain
306
307
308
309 var ValidateResourceClaimTemplateName = apimachineryvalidation.NameIsDNSSubdomain
310
311
312
313
314 func ValidateRuntimeClassName(name string, fldPath *field.Path) field.ErrorList {
315 var allErrs field.ErrorList
316 for _, msg := range apimachineryvalidation.NameIsDNSSubdomain(name, false) {
317 allErrs = append(allErrs, field.Invalid(fldPath, name, msg))
318 }
319 return allErrs
320 }
321
322
323 func validateOverhead(overhead core.ResourceList, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
324
325 return ValidateResourceRequirements(&core.ResourceRequirements{Limits: overhead}, nil, fldPath, opts)
326 }
327
328
329 func ValidateNonnegativeField(value int64, fldPath *field.Path) field.ErrorList {
330 return apimachineryvalidation.ValidateNonnegativeField(value, fldPath)
331 }
332
333
334 func ValidateNonnegativeQuantity(value resource.Quantity, fldPath *field.Path) field.ErrorList {
335 allErrs := field.ErrorList{}
336 if value.Cmp(resource.Quantity{}) < 0 {
337 allErrs = append(allErrs, field.Invalid(fldPath, value.String(), isNegativeErrorMsg))
338 }
339 return allErrs
340 }
341
342
343 func ValidatePositiveQuantityValue(value resource.Quantity, fldPath *field.Path) field.ErrorList {
344 allErrs := field.ErrorList{}
345 if value.Cmp(resource.Quantity{}) <= 0 {
346 allErrs = append(allErrs, field.Invalid(fldPath, value.String(), isNotPositiveErrorMsg))
347 }
348 return allErrs
349 }
350
351 func ValidateImmutableField(newVal, oldVal interface{}, fldPath *field.Path) field.ErrorList {
352 return apimachineryvalidation.ValidateImmutableField(newVal, oldVal, fldPath)
353 }
354
355 func ValidateImmutableAnnotation(newVal string, oldVal string, annotation string, fldPath *field.Path) field.ErrorList {
356 allErrs := field.ErrorList{}
357
358 if oldVal != newVal {
359 allErrs = append(allErrs, field.Invalid(fldPath.Child("annotations", annotation), newVal, fieldImmutableErrorMsg))
360 }
361 return allErrs
362 }
363
364
365
366
367
368 func ValidateObjectMeta(meta *metav1.ObjectMeta, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path) field.ErrorList {
369 allErrs := apimachineryvalidation.ValidateObjectMeta(meta, requiresNamespace, apimachineryvalidation.ValidateNameFunc(nameFn), fldPath)
370
371 for i := range meta.Finalizers {
372 allErrs = append(allErrs, validateKubeFinalizerName(string(meta.Finalizers[i]), fldPath.Child("finalizers").Index(i))...)
373 }
374 return allErrs
375 }
376
377
378 func ValidateObjectMetaUpdate(newMeta, oldMeta *metav1.ObjectMeta, fldPath *field.Path) field.ErrorList {
379 allErrs := apimachineryvalidation.ValidateObjectMetaUpdate(newMeta, oldMeta, fldPath)
380
381 for i := range newMeta.Finalizers {
382 allErrs = append(allErrs, validateKubeFinalizerName(string(newMeta.Finalizers[i]), fldPath.Child("finalizers").Index(i))...)
383 }
384
385 return allErrs
386 }
387
388 func ValidateVolumes(volumes []core.Volume, podMeta *metav1.ObjectMeta, fldPath *field.Path, opts PodValidationOptions) (map[string]core.VolumeSource, field.ErrorList) {
389 allErrs := field.ErrorList{}
390
391 allNames := sets.Set[string]{}
392 allCreatedPVCs := sets.Set[string]{}
393
394
395
396 if podMeta != nil && podMeta.Name != "" {
397 for _, vol := range volumes {
398 if vol.VolumeSource.Ephemeral != nil {
399 allCreatedPVCs.Insert(podMeta.Name + "-" + vol.Name)
400 }
401 }
402 }
403 vols := make(map[string]core.VolumeSource)
404 for i, vol := range volumes {
405 idxPath := fldPath.Index(i)
406 namePath := idxPath.Child("name")
407 el := validateVolumeSource(&vol.VolumeSource, idxPath, vol.Name, podMeta, opts)
408 if len(vol.Name) == 0 {
409 el = append(el, field.Required(namePath, ""))
410 } else {
411 el = append(el, ValidateDNS1123Label(vol.Name, namePath)...)
412 }
413 if allNames.Has(vol.Name) {
414 el = append(el, field.Duplicate(namePath, vol.Name))
415 }
416 if len(el) == 0 {
417 allNames.Insert(vol.Name)
418 vols[vol.Name] = vol.VolumeSource
419 } else {
420 allErrs = append(allErrs, el...)
421 }
422
423
424 if vol.PersistentVolumeClaim != nil && allCreatedPVCs.Has(vol.PersistentVolumeClaim.ClaimName) {
425 allErrs = append(allErrs, field.Invalid(idxPath.Child("persistentVolumeClaim").Child("claimName"), vol.PersistentVolumeClaim.ClaimName,
426 "must not reference a PVC that gets created for an ephemeral volume"))
427 }
428 }
429
430 return vols, allErrs
431 }
432
433 func IsMatchedVolume(name string, volumes map[string]core.VolumeSource) bool {
434 if _, ok := volumes[name]; ok {
435 return true
436 }
437 return false
438 }
439
440
441
442 func isMatchedDevice(name string, volumes map[string]core.VolumeSource) (isMatched bool, isPVC bool) {
443 if source, ok := volumes[name]; ok {
444 if source.PersistentVolumeClaim != nil ||
445 source.Ephemeral != nil {
446 return true, true
447 }
448 return true, false
449 }
450 return false, false
451 }
452
453 func mountNameAlreadyExists(name string, devices map[string]string) bool {
454 if _, ok := devices[name]; ok {
455 return true
456 }
457 return false
458 }
459
460 func mountPathAlreadyExists(mountPath string, devices map[string]string) bool {
461 for _, devPath := range devices {
462 if mountPath == devPath {
463 return true
464 }
465 }
466
467 return false
468 }
469
470 func deviceNameAlreadyExists(name string, mounts map[string]string) bool {
471 if _, ok := mounts[name]; ok {
472 return true
473 }
474 return false
475 }
476
477 func devicePathAlreadyExists(devicePath string, mounts map[string]string) bool {
478 for _, mountPath := range mounts {
479 if mountPath == devicePath {
480 return true
481 }
482 }
483
484 return false
485 }
486
487 func validateVolumeSource(source *core.VolumeSource, fldPath *field.Path, volName string, podMeta *metav1.ObjectMeta, opts PodValidationOptions) field.ErrorList {
488 numVolumes := 0
489 allErrs := field.ErrorList{}
490 if source.EmptyDir != nil {
491 numVolumes++
492 if source.EmptyDir.SizeLimit != nil && source.EmptyDir.SizeLimit.Cmp(resource.Quantity{}) < 0 {
493 allErrs = append(allErrs, field.Forbidden(fldPath.Child("emptyDir").Child("sizeLimit"), "SizeLimit field must be a valid resource quantity"))
494 }
495 }
496 if source.HostPath != nil {
497 if numVolumes > 0 {
498 allErrs = append(allErrs, field.Forbidden(fldPath.Child("hostPath"), "may not specify more than 1 volume type"))
499 } else {
500 numVolumes++
501 allErrs = append(allErrs, validateHostPathVolumeSource(source.HostPath, fldPath.Child("hostPath"))...)
502 }
503 }
504 if source.GitRepo != nil {
505 if numVolumes > 0 {
506 allErrs = append(allErrs, field.Forbidden(fldPath.Child("gitRepo"), "may not specify more than 1 volume type"))
507 } else {
508 numVolumes++
509 allErrs = append(allErrs, validateGitRepoVolumeSource(source.GitRepo, fldPath.Child("gitRepo"))...)
510 }
511 }
512 if source.GCEPersistentDisk != nil {
513 if numVolumes > 0 {
514 allErrs = append(allErrs, field.Forbidden(fldPath.Child("gcePersistentDisk"), "may not specify more than 1 volume type"))
515 } else {
516 numVolumes++
517 allErrs = append(allErrs, validateGCEPersistentDiskVolumeSource(source.GCEPersistentDisk, fldPath.Child("persistentDisk"))...)
518 }
519 }
520 if source.AWSElasticBlockStore != nil {
521 if numVolumes > 0 {
522 allErrs = append(allErrs, field.Forbidden(fldPath.Child("awsElasticBlockStore"), "may not specify more than 1 volume type"))
523 } else {
524 numVolumes++
525 allErrs = append(allErrs, validateAWSElasticBlockStoreVolumeSource(source.AWSElasticBlockStore, fldPath.Child("awsElasticBlockStore"))...)
526 }
527 }
528 if source.Secret != nil {
529 if numVolumes > 0 {
530 allErrs = append(allErrs, field.Forbidden(fldPath.Child("secret"), "may not specify more than 1 volume type"))
531 } else {
532 numVolumes++
533 allErrs = append(allErrs, validateSecretVolumeSource(source.Secret, fldPath.Child("secret"))...)
534 }
535 }
536 if source.NFS != nil {
537 if numVolumes > 0 {
538 allErrs = append(allErrs, field.Forbidden(fldPath.Child("nfs"), "may not specify more than 1 volume type"))
539 } else {
540 numVolumes++
541 allErrs = append(allErrs, validateNFSVolumeSource(source.NFS, fldPath.Child("nfs"))...)
542 }
543 }
544 if source.ISCSI != nil {
545 if numVolumes > 0 {
546 allErrs = append(allErrs, field.Forbidden(fldPath.Child("iscsi"), "may not specify more than 1 volume type"))
547 } else {
548 numVolumes++
549 allErrs = append(allErrs, validateISCSIVolumeSource(source.ISCSI, fldPath.Child("iscsi"))...)
550 }
551 if source.ISCSI.InitiatorName != nil && len(volName+":"+source.ISCSI.TargetPortal) > 64 {
552 tooLongErr := "Total length of <volume name>:<iscsi.targetPortal> must be under 64 characters if iscsi.initiatorName is specified."
553 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), volName, tooLongErr))
554 }
555 }
556 if source.Glusterfs != nil {
557 if numVolumes > 0 {
558 allErrs = append(allErrs, field.Forbidden(fldPath.Child("glusterfs"), "may not specify more than 1 volume type"))
559 } else {
560 numVolumes++
561 allErrs = append(allErrs, validateGlusterfsVolumeSource(source.Glusterfs, fldPath.Child("glusterfs"))...)
562 }
563 }
564 if source.Flocker != nil {
565 if numVolumes > 0 {
566 allErrs = append(allErrs, field.Forbidden(fldPath.Child("flocker"), "may not specify more than 1 volume type"))
567 } else {
568 numVolumes++
569 allErrs = append(allErrs, validateFlockerVolumeSource(source.Flocker, fldPath.Child("flocker"))...)
570 }
571 }
572 if source.PersistentVolumeClaim != nil {
573 if numVolumes > 0 {
574 allErrs = append(allErrs, field.Forbidden(fldPath.Child("persistentVolumeClaim"), "may not specify more than 1 volume type"))
575 } else {
576 numVolumes++
577 allErrs = append(allErrs, validatePersistentClaimVolumeSource(source.PersistentVolumeClaim, fldPath.Child("persistentVolumeClaim"))...)
578 }
579 }
580 if source.RBD != nil {
581 if numVolumes > 0 {
582 allErrs = append(allErrs, field.Forbidden(fldPath.Child("rbd"), "may not specify more than 1 volume type"))
583 } else {
584 numVolumes++
585 allErrs = append(allErrs, validateRBDVolumeSource(source.RBD, fldPath.Child("rbd"))...)
586 }
587 }
588 if source.Cinder != nil {
589 if numVolumes > 0 {
590 allErrs = append(allErrs, field.Forbidden(fldPath.Child("cinder"), "may not specify more than 1 volume type"))
591 } else {
592 numVolumes++
593 allErrs = append(allErrs, validateCinderVolumeSource(source.Cinder, fldPath.Child("cinder"))...)
594 }
595 }
596 if source.CephFS != nil {
597 if numVolumes > 0 {
598 allErrs = append(allErrs, field.Forbidden(fldPath.Child("cephFS"), "may not specify more than 1 volume type"))
599 } else {
600 numVolumes++
601 allErrs = append(allErrs, validateCephFSVolumeSource(source.CephFS, fldPath.Child("cephfs"))...)
602 }
603 }
604 if source.Quobyte != nil {
605 if numVolumes > 0 {
606 allErrs = append(allErrs, field.Forbidden(fldPath.Child("quobyte"), "may not specify more than 1 volume type"))
607 } else {
608 numVolumes++
609 allErrs = append(allErrs, validateQuobyteVolumeSource(source.Quobyte, fldPath.Child("quobyte"))...)
610 }
611 }
612 if source.DownwardAPI != nil {
613 if numVolumes > 0 {
614 allErrs = append(allErrs, field.Forbidden(fldPath.Child("downwarAPI"), "may not specify more than 1 volume type"))
615 } else {
616 numVolumes++
617 allErrs = append(allErrs, validateDownwardAPIVolumeSource(source.DownwardAPI, fldPath.Child("downwardAPI"), opts)...)
618 }
619 }
620 if source.FC != nil {
621 if numVolumes > 0 {
622 allErrs = append(allErrs, field.Forbidden(fldPath.Child("fc"), "may not specify more than 1 volume type"))
623 } else {
624 numVolumes++
625 allErrs = append(allErrs, validateFCVolumeSource(source.FC, fldPath.Child("fc"))...)
626 }
627 }
628 if source.FlexVolume != nil {
629 if numVolumes > 0 {
630 allErrs = append(allErrs, field.Forbidden(fldPath.Child("flexVolume"), "may not specify more than 1 volume type"))
631 } else {
632 numVolumes++
633 allErrs = append(allErrs, validateFlexVolumeSource(source.FlexVolume, fldPath.Child("flexVolume"))...)
634 }
635 }
636 if source.ConfigMap != nil {
637 if numVolumes > 0 {
638 allErrs = append(allErrs, field.Forbidden(fldPath.Child("configMap"), "may not specify more than 1 volume type"))
639 } else {
640 numVolumes++
641 allErrs = append(allErrs, validateConfigMapVolumeSource(source.ConfigMap, fldPath.Child("configMap"))...)
642 }
643 }
644
645 if source.AzureFile != nil {
646 if numVolumes > 0 {
647 allErrs = append(allErrs, field.Forbidden(fldPath.Child("azureFile"), "may not specify more than 1 volume type"))
648 } else {
649 numVolumes++
650 allErrs = append(allErrs, validateAzureFile(source.AzureFile, fldPath.Child("azureFile"))...)
651 }
652 }
653
654 if source.VsphereVolume != nil {
655 if numVolumes > 0 {
656 allErrs = append(allErrs, field.Forbidden(fldPath.Child("vsphereVolume"), "may not specify more than 1 volume type"))
657 } else {
658 numVolumes++
659 allErrs = append(allErrs, validateVsphereVolumeSource(source.VsphereVolume, fldPath.Child("vsphereVolume"))...)
660 }
661 }
662 if source.PhotonPersistentDisk != nil {
663 if numVolumes > 0 {
664 allErrs = append(allErrs, field.Forbidden(fldPath.Child("photonPersistentDisk"), "may not specify more than 1 volume type"))
665 } else {
666 numVolumes++
667 allErrs = append(allErrs, validatePhotonPersistentDiskVolumeSource(source.PhotonPersistentDisk, fldPath.Child("photonPersistentDisk"))...)
668 }
669 }
670 if source.PortworxVolume != nil {
671 if numVolumes > 0 {
672 allErrs = append(allErrs, field.Forbidden(fldPath.Child("portworxVolume"), "may not specify more than 1 volume type"))
673 } else {
674 numVolumes++
675 allErrs = append(allErrs, validatePortworxVolumeSource(source.PortworxVolume, fldPath.Child("portworxVolume"))...)
676 }
677 }
678 if source.AzureDisk != nil {
679 if numVolumes > 0 {
680 allErrs = append(allErrs, field.Forbidden(fldPath.Child("azureDisk"), "may not specify more than 1 volume type"))
681 } else {
682 numVolumes++
683 allErrs = append(allErrs, validateAzureDisk(source.AzureDisk, fldPath.Child("azureDisk"))...)
684 }
685 }
686 if source.StorageOS != nil {
687 if numVolumes > 0 {
688 allErrs = append(allErrs, field.Forbidden(fldPath.Child("storageos"), "may not specify more than 1 volume type"))
689 } else {
690 numVolumes++
691 allErrs = append(allErrs, validateStorageOSVolumeSource(source.StorageOS, fldPath.Child("storageos"))...)
692 }
693 }
694 if source.Projected != nil {
695 if numVolumes > 0 {
696 allErrs = append(allErrs, field.Forbidden(fldPath.Child("projected"), "may not specify more than 1 volume type"))
697 } else {
698 numVolumes++
699 allErrs = append(allErrs, validateProjectedVolumeSource(source.Projected, fldPath.Child("projected"), opts)...)
700 }
701 }
702 if source.ScaleIO != nil {
703 if numVolumes > 0 {
704 allErrs = append(allErrs, field.Forbidden(fldPath.Child("scaleIO"), "may not specify more than 1 volume type"))
705 } else {
706 numVolumes++
707 allErrs = append(allErrs, validateScaleIOVolumeSource(source.ScaleIO, fldPath.Child("scaleIO"))...)
708 }
709 }
710 if source.CSI != nil {
711 if numVolumes > 0 {
712 allErrs = append(allErrs, field.Forbidden(fldPath.Child("csi"), "may not specify more than 1 volume type"))
713 } else {
714 numVolumes++
715 allErrs = append(allErrs, validateCSIVolumeSource(source.CSI, fldPath.Child("csi"))...)
716 }
717 }
718 if source.Ephemeral != nil {
719 if numVolumes > 0 {
720 allErrs = append(allErrs, field.Forbidden(fldPath.Child("ephemeral"), "may not specify more than 1 volume type"))
721 } else {
722 numVolumes++
723 allErrs = append(allErrs, validateEphemeralVolumeSource(source.Ephemeral, fldPath.Child("ephemeral"))...)
724
725
726
727 if podMeta != nil && podMeta.Name != "" && volName != "" {
728 pvcName := podMeta.Name + "-" + volName
729 for _, msg := range ValidatePersistentVolumeName(pvcName, false) {
730 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), volName, fmt.Sprintf("PVC name %q: %v", pvcName, msg)))
731 }
732 }
733 }
734 }
735
736 if numVolumes == 0 {
737 allErrs = append(allErrs, field.Required(fldPath, "must specify a volume type"))
738 }
739
740 return allErrs
741 }
742
743 func validateHostPathVolumeSource(hostPath *core.HostPathVolumeSource, fldPath *field.Path) field.ErrorList {
744 allErrs := field.ErrorList{}
745 if len(hostPath.Path) == 0 {
746 allErrs = append(allErrs, field.Required(fldPath.Child("path"), ""))
747 return allErrs
748 }
749
750 allErrs = append(allErrs, validatePathNoBacksteps(hostPath.Path, fldPath.Child("path"))...)
751 allErrs = append(allErrs, validateHostPathType(hostPath.Type, fldPath.Child("type"))...)
752 return allErrs
753 }
754
755 func validateGitRepoVolumeSource(gitRepo *core.GitRepoVolumeSource, fldPath *field.Path) field.ErrorList {
756 allErrs := field.ErrorList{}
757 if len(gitRepo.Repository) == 0 {
758 allErrs = append(allErrs, field.Required(fldPath.Child("repository"), ""))
759 }
760
761 pathErrs := validateLocalDescendingPath(gitRepo.Directory, fldPath.Child("directory"))
762 allErrs = append(allErrs, pathErrs...)
763 return allErrs
764 }
765
766 func validateISCSIVolumeSource(iscsi *core.ISCSIVolumeSource, fldPath *field.Path) field.ErrorList {
767 allErrs := field.ErrorList{}
768 if len(iscsi.TargetPortal) == 0 {
769 allErrs = append(allErrs, field.Required(fldPath.Child("targetPortal"), ""))
770 }
771 if len(iscsi.IQN) == 0 {
772 allErrs = append(allErrs, field.Required(fldPath.Child("iqn"), ""))
773 } else {
774 if !strings.HasPrefix(iscsi.IQN, "iqn") && !strings.HasPrefix(iscsi.IQN, "eui") && !strings.HasPrefix(iscsi.IQN, "naa") {
775 allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format starting with iqn, eui, or naa"))
776 } else if strings.HasPrefix(iscsi.IQN, "iqn") && !iscsiInitiatorIqnRegex.MatchString(iscsi.IQN) {
777 allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format"))
778 } else if strings.HasPrefix(iscsi.IQN, "eui") && !iscsiInitiatorEuiRegex.MatchString(iscsi.IQN) {
779 allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format"))
780 } else if strings.HasPrefix(iscsi.IQN, "naa") && !iscsiInitiatorNaaRegex.MatchString(iscsi.IQN) {
781 allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format"))
782 }
783 }
784 if iscsi.Lun < 0 || iscsi.Lun > 255 {
785 allErrs = append(allErrs, field.Invalid(fldPath.Child("lun"), iscsi.Lun, validation.InclusiveRangeError(0, 255)))
786 }
787 if (iscsi.DiscoveryCHAPAuth || iscsi.SessionCHAPAuth) && iscsi.SecretRef == nil {
788 allErrs = append(allErrs, field.Required(fldPath.Child("secretRef"), ""))
789 }
790 if iscsi.InitiatorName != nil {
791 initiator := *iscsi.InitiatorName
792 if !strings.HasPrefix(initiator, "iqn") && !strings.HasPrefix(initiator, "eui") && !strings.HasPrefix(initiator, "naa") {
793 allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format starting with iqn, eui, or naa"))
794 }
795 if strings.HasPrefix(initiator, "iqn") && !iscsiInitiatorIqnRegex.MatchString(initiator) {
796 allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format"))
797 } else if strings.HasPrefix(initiator, "eui") && !iscsiInitiatorEuiRegex.MatchString(initiator) {
798 allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format"))
799 } else if strings.HasPrefix(initiator, "naa") && !iscsiInitiatorNaaRegex.MatchString(initiator) {
800 allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format"))
801 }
802 }
803 return allErrs
804 }
805
806 func validateISCSIPersistentVolumeSource(iscsi *core.ISCSIPersistentVolumeSource, pvName string, fldPath *field.Path) field.ErrorList {
807 allErrs := field.ErrorList{}
808 if len(iscsi.TargetPortal) == 0 {
809 allErrs = append(allErrs, field.Required(fldPath.Child("targetPortal"), ""))
810 }
811 if iscsi.InitiatorName != nil && len(pvName+":"+iscsi.TargetPortal) > 64 {
812 tooLongErr := "Total length of <volume name>:<iscsi.targetPortal> must be under 64 characters if iscsi.initiatorName is specified."
813 allErrs = append(allErrs, field.Invalid(fldPath.Child("targetportal"), iscsi.TargetPortal, tooLongErr))
814 }
815 if len(iscsi.IQN) == 0 {
816 allErrs = append(allErrs, field.Required(fldPath.Child("iqn"), ""))
817 } else {
818 if !strings.HasPrefix(iscsi.IQN, "iqn") && !strings.HasPrefix(iscsi.IQN, "eui") && !strings.HasPrefix(iscsi.IQN, "naa") {
819 allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format"))
820 } else if strings.HasPrefix(iscsi.IQN, "iqn") && !iscsiInitiatorIqnRegex.MatchString(iscsi.IQN) {
821 allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format"))
822 } else if strings.HasPrefix(iscsi.IQN, "eui") && !iscsiInitiatorEuiRegex.MatchString(iscsi.IQN) {
823 allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format"))
824 } else if strings.HasPrefix(iscsi.IQN, "naa") && !iscsiInitiatorNaaRegex.MatchString(iscsi.IQN) {
825 allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format"))
826 }
827 }
828 if iscsi.Lun < 0 || iscsi.Lun > 255 {
829 allErrs = append(allErrs, field.Invalid(fldPath.Child("lun"), iscsi.Lun, validation.InclusiveRangeError(0, 255)))
830 }
831 if (iscsi.DiscoveryCHAPAuth || iscsi.SessionCHAPAuth) && iscsi.SecretRef == nil {
832 allErrs = append(allErrs, field.Required(fldPath.Child("secretRef"), ""))
833 }
834 if iscsi.SecretRef != nil {
835 if len(iscsi.SecretRef.Name) == 0 {
836 allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "name"), ""))
837 }
838 }
839 if iscsi.InitiatorName != nil {
840 initiator := *iscsi.InitiatorName
841 if !strings.HasPrefix(initiator, "iqn") && !strings.HasPrefix(initiator, "eui") && !strings.HasPrefix(initiator, "naa") {
842 allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format"))
843 }
844 if strings.HasPrefix(initiator, "iqn") && !iscsiInitiatorIqnRegex.MatchString(initiator) {
845 allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format"))
846 } else if strings.HasPrefix(initiator, "eui") && !iscsiInitiatorEuiRegex.MatchString(initiator) {
847 allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format"))
848 } else if strings.HasPrefix(initiator, "naa") && !iscsiInitiatorNaaRegex.MatchString(initiator) {
849 allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format"))
850 }
851 }
852 return allErrs
853 }
854
855 func validateFCVolumeSource(fc *core.FCVolumeSource, fldPath *field.Path) field.ErrorList {
856 allErrs := field.ErrorList{}
857 if len(fc.TargetWWNs) < 1 && len(fc.WWIDs) < 1 {
858 allErrs = append(allErrs, field.Required(fldPath.Child("targetWWNs"), "must specify either targetWWNs or wwids, but not both"))
859 }
860
861 if len(fc.TargetWWNs) != 0 && len(fc.WWIDs) != 0 {
862 allErrs = append(allErrs, field.Invalid(fldPath.Child("targetWWNs"), fc.TargetWWNs, "targetWWNs and wwids can not be specified simultaneously"))
863 }
864
865 if len(fc.TargetWWNs) != 0 {
866 if fc.Lun == nil {
867 allErrs = append(allErrs, field.Required(fldPath.Child("lun"), "lun is required if targetWWNs is specified"))
868 } else {
869 if *fc.Lun < 0 || *fc.Lun > 255 {
870 allErrs = append(allErrs, field.Invalid(fldPath.Child("lun"), fc.Lun, validation.InclusiveRangeError(0, 255)))
871 }
872 }
873 }
874 return allErrs
875 }
876
877 func validateGCEPersistentDiskVolumeSource(pd *core.GCEPersistentDiskVolumeSource, fldPath *field.Path) field.ErrorList {
878 allErrs := field.ErrorList{}
879 if len(pd.PDName) == 0 {
880 allErrs = append(allErrs, field.Required(fldPath.Child("pdName"), ""))
881 }
882 if pd.Partition < 0 || pd.Partition > 255 {
883 allErrs = append(allErrs, field.Invalid(fldPath.Child("partition"), pd.Partition, pdPartitionErrorMsg))
884 }
885 return allErrs
886 }
887
888 func validateAWSElasticBlockStoreVolumeSource(PD *core.AWSElasticBlockStoreVolumeSource, fldPath *field.Path) field.ErrorList {
889 allErrs := field.ErrorList{}
890 if len(PD.VolumeID) == 0 {
891 allErrs = append(allErrs, field.Required(fldPath.Child("volumeID"), ""))
892 }
893 if PD.Partition < 0 || PD.Partition > 255 {
894 allErrs = append(allErrs, field.Invalid(fldPath.Child("partition"), PD.Partition, pdPartitionErrorMsg))
895 }
896 return allErrs
897 }
898
899 func validateSecretVolumeSource(secretSource *core.SecretVolumeSource, fldPath *field.Path) field.ErrorList {
900 allErrs := field.ErrorList{}
901 if len(secretSource.SecretName) == 0 {
902 allErrs = append(allErrs, field.Required(fldPath.Child("secretName"), ""))
903 }
904
905 secretMode := secretSource.DefaultMode
906 if secretMode != nil && (*secretMode > 0777 || *secretMode < 0) {
907 allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *secretMode, fileModeErrorMsg))
908 }
909
910 itemsPath := fldPath.Child("items")
911 for i, kp := range secretSource.Items {
912 itemPath := itemsPath.Index(i)
913 allErrs = append(allErrs, validateKeyToPath(&kp, itemPath)...)
914 }
915 return allErrs
916 }
917
918 func validateConfigMapVolumeSource(configMapSource *core.ConfigMapVolumeSource, fldPath *field.Path) field.ErrorList {
919 allErrs := field.ErrorList{}
920 if len(configMapSource.Name) == 0 {
921 allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
922 }
923
924 configMapMode := configMapSource.DefaultMode
925 if configMapMode != nil && (*configMapMode > 0777 || *configMapMode < 0) {
926 allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *configMapMode, fileModeErrorMsg))
927 }
928
929 itemsPath := fldPath.Child("items")
930 for i, kp := range configMapSource.Items {
931 itemPath := itemsPath.Index(i)
932 allErrs = append(allErrs, validateKeyToPath(&kp, itemPath)...)
933 }
934 return allErrs
935 }
936
937 func validateKeyToPath(kp *core.KeyToPath, fldPath *field.Path) field.ErrorList {
938 allErrs := field.ErrorList{}
939 if len(kp.Key) == 0 {
940 allErrs = append(allErrs, field.Required(fldPath.Child("key"), ""))
941 }
942 if len(kp.Path) == 0 {
943 allErrs = append(allErrs, field.Required(fldPath.Child("path"), ""))
944 }
945 allErrs = append(allErrs, ValidateLocalNonReservedPath(kp.Path, fldPath.Child("path"))...)
946 if kp.Mode != nil && (*kp.Mode > 0777 || *kp.Mode < 0) {
947 allErrs = append(allErrs, field.Invalid(fldPath.Child("mode"), *kp.Mode, fileModeErrorMsg))
948 }
949
950 return allErrs
951 }
952
953 func validatePersistentClaimVolumeSource(claim *core.PersistentVolumeClaimVolumeSource, fldPath *field.Path) field.ErrorList {
954 allErrs := field.ErrorList{}
955 if len(claim.ClaimName) == 0 {
956 allErrs = append(allErrs, field.Required(fldPath.Child("claimName"), ""))
957 }
958 return allErrs
959 }
960
961 func validateNFSVolumeSource(nfs *core.NFSVolumeSource, fldPath *field.Path) field.ErrorList {
962 allErrs := field.ErrorList{}
963 if len(nfs.Server) == 0 {
964 allErrs = append(allErrs, field.Required(fldPath.Child("server"), ""))
965 }
966 if len(nfs.Path) == 0 {
967 allErrs = append(allErrs, field.Required(fldPath.Child("path"), ""))
968 }
969 if !path.IsAbs(nfs.Path) {
970 allErrs = append(allErrs, field.Invalid(fldPath.Child("path"), nfs.Path, "must be an absolute path"))
971 }
972 return allErrs
973 }
974
975 func validateQuobyteVolumeSource(quobyte *core.QuobyteVolumeSource, fldPath *field.Path) field.ErrorList {
976 allErrs := field.ErrorList{}
977 if len(quobyte.Registry) == 0 {
978 allErrs = append(allErrs, field.Required(fldPath.Child("registry"), "must be a host:port pair or multiple pairs separated by commas"))
979 } else if len(quobyte.Tenant) >= 65 {
980 allErrs = append(allErrs, field.Required(fldPath.Child("tenant"), "must be a UUID and may not exceed a length of 64 characters"))
981 } else {
982 for _, hostPortPair := range strings.Split(quobyte.Registry, ",") {
983 if _, _, err := net.SplitHostPort(hostPortPair); err != nil {
984 allErrs = append(allErrs, field.Invalid(fldPath.Child("registry"), quobyte.Registry, "must be a host:port pair or multiple pairs separated by commas"))
985 }
986 }
987 }
988
989 if len(quobyte.Volume) == 0 {
990 allErrs = append(allErrs, field.Required(fldPath.Child("volume"), ""))
991 }
992 return allErrs
993 }
994
995 func validateGlusterfsVolumeSource(glusterfs *core.GlusterfsVolumeSource, fldPath *field.Path) field.ErrorList {
996 allErrs := field.ErrorList{}
997 if len(glusterfs.EndpointsName) == 0 {
998 allErrs = append(allErrs, field.Required(fldPath.Child("endpoints"), ""))
999 }
1000 if len(glusterfs.Path) == 0 {
1001 allErrs = append(allErrs, field.Required(fldPath.Child("path"), ""))
1002 }
1003 return allErrs
1004 }
1005 func validateGlusterfsPersistentVolumeSource(glusterfs *core.GlusterfsPersistentVolumeSource, fldPath *field.Path) field.ErrorList {
1006 allErrs := field.ErrorList{}
1007 if len(glusterfs.EndpointsName) == 0 {
1008 allErrs = append(allErrs, field.Required(fldPath.Child("endpoints"), ""))
1009 }
1010 if len(glusterfs.Path) == 0 {
1011 allErrs = append(allErrs, field.Required(fldPath.Child("path"), ""))
1012 }
1013 if glusterfs.EndpointsNamespace != nil {
1014 endpointNs := glusterfs.EndpointsNamespace
1015 if *endpointNs == "" {
1016 allErrs = append(allErrs, field.Invalid(fldPath.Child("endpointsNamespace"), *endpointNs, "if the endpointnamespace is set, it must be a valid namespace name"))
1017 } else {
1018 for _, msg := range ValidateNamespaceName(*endpointNs, false) {
1019 allErrs = append(allErrs, field.Invalid(fldPath.Child("endpointsNamespace"), *endpointNs, msg))
1020 }
1021 }
1022 }
1023 return allErrs
1024 }
1025
1026 func validateFlockerVolumeSource(flocker *core.FlockerVolumeSource, fldPath *field.Path) field.ErrorList {
1027 allErrs := field.ErrorList{}
1028 if len(flocker.DatasetName) == 0 && len(flocker.DatasetUUID) == 0 {
1029
1030 allErrs = append(allErrs, field.Required(fldPath, "one of datasetName and datasetUUID is required"))
1031 }
1032 if len(flocker.DatasetName) != 0 && len(flocker.DatasetUUID) != 0 {
1033 allErrs = append(allErrs, field.Invalid(fldPath, "resource", "datasetName and datasetUUID can not be specified simultaneously"))
1034 }
1035 if strings.Contains(flocker.DatasetName, "/") {
1036 allErrs = append(allErrs, field.Invalid(fldPath.Child("datasetName"), flocker.DatasetName, "must not contain '/'"))
1037 }
1038 return allErrs
1039 }
1040
1041 var validVolumeDownwardAPIFieldPathExpressions = sets.New(
1042 "metadata.name",
1043 "metadata.namespace",
1044 "metadata.labels",
1045 "metadata.annotations",
1046 "metadata.uid")
1047
1048 func validateDownwardAPIVolumeFile(file *core.DownwardAPIVolumeFile, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
1049 allErrs := field.ErrorList{}
1050 if len(file.Path) == 0 {
1051 allErrs = append(allErrs, field.Required(fldPath.Child("path"), ""))
1052 }
1053 allErrs = append(allErrs, ValidateLocalNonReservedPath(file.Path, fldPath.Child("path"))...)
1054 if file.FieldRef != nil {
1055 allErrs = append(allErrs, validateObjectFieldSelector(file.FieldRef, &validVolumeDownwardAPIFieldPathExpressions, fldPath.Child("fieldRef"))...)
1056 if file.ResourceFieldRef != nil {
1057 allErrs = append(allErrs, field.Invalid(fldPath, "resource", "fieldRef and resourceFieldRef can not be specified simultaneously"))
1058 }
1059 allErrs = append(allErrs, validateDownwardAPIHostIPs(file.FieldRef, fldPath.Child("fieldRef"), opts)...)
1060 } else if file.ResourceFieldRef != nil {
1061 localValidContainerResourceFieldPathPrefixes := validContainerResourceFieldPathPrefixesWithDownwardAPIHugePages
1062 allErrs = append(allErrs, validateContainerResourceFieldSelector(file.ResourceFieldRef, &validContainerResourceFieldPathExpressions, &localValidContainerResourceFieldPathPrefixes, fldPath.Child("resourceFieldRef"), true)...)
1063 } else {
1064 allErrs = append(allErrs, field.Required(fldPath, "one of fieldRef and resourceFieldRef is required"))
1065 }
1066 if file.Mode != nil && (*file.Mode > 0777 || *file.Mode < 0) {
1067 allErrs = append(allErrs, field.Invalid(fldPath.Child("mode"), *file.Mode, fileModeErrorMsg))
1068 }
1069
1070 return allErrs
1071 }
1072
1073 func validateDownwardAPIVolumeSource(downwardAPIVolume *core.DownwardAPIVolumeSource, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
1074 allErrs := field.ErrorList{}
1075
1076 downwardAPIMode := downwardAPIVolume.DefaultMode
1077 if downwardAPIMode != nil && (*downwardAPIMode > 0777 || *downwardAPIMode < 0) {
1078 allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *downwardAPIMode, fileModeErrorMsg))
1079 }
1080
1081 for _, file := range downwardAPIVolume.Items {
1082 allErrs = append(allErrs, validateDownwardAPIVolumeFile(&file, fldPath, opts)...)
1083 }
1084 return allErrs
1085 }
1086
1087 func validateProjectionSources(projection *core.ProjectedVolumeSource, projectionMode *int32, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
1088 allErrs := field.ErrorList{}
1089 allPaths := sets.Set[string]{}
1090
1091 for i, source := range projection.Sources {
1092 numSources := 0
1093 srcPath := fldPath.Child("sources").Index(i)
1094 if projPath := srcPath.Child("secret"); source.Secret != nil {
1095 numSources++
1096 if len(source.Secret.Name) == 0 {
1097 allErrs = append(allErrs, field.Required(projPath.Child("name"), ""))
1098 }
1099 itemsPath := projPath.Child("items")
1100 for i, kp := range source.Secret.Items {
1101 itemPath := itemsPath.Index(i)
1102 allErrs = append(allErrs, validateKeyToPath(&kp, itemPath)...)
1103 if len(kp.Path) > 0 {
1104 curPath := kp.Path
1105 if !allPaths.Has(curPath) {
1106 allPaths.Insert(curPath)
1107 } else {
1108 allErrs = append(allErrs, field.Invalid(fldPath, source.Secret.Name, "conflicting duplicate paths"))
1109 }
1110 }
1111 }
1112 }
1113 if projPath := srcPath.Child("configMap"); source.ConfigMap != nil {
1114 numSources++
1115 if len(source.ConfigMap.Name) == 0 {
1116 allErrs = append(allErrs, field.Required(projPath.Child("name"), ""))
1117 }
1118 itemsPath := projPath.Child("items")
1119 for i, kp := range source.ConfigMap.Items {
1120 itemPath := itemsPath.Index(i)
1121 allErrs = append(allErrs, validateKeyToPath(&kp, itemPath)...)
1122 if len(kp.Path) > 0 {
1123 curPath := kp.Path
1124 if !allPaths.Has(curPath) {
1125 allPaths.Insert(curPath)
1126 } else {
1127 allErrs = append(allErrs, field.Invalid(fldPath, source.ConfigMap.Name, "conflicting duplicate paths"))
1128 }
1129 }
1130 }
1131 }
1132 if projPath := srcPath.Child("downwardAPI"); source.DownwardAPI != nil {
1133 numSources++
1134 for _, file := range source.DownwardAPI.Items {
1135 allErrs = append(allErrs, validateDownwardAPIVolumeFile(&file, projPath, opts)...)
1136 if len(file.Path) > 0 {
1137 curPath := file.Path
1138 if !allPaths.Has(curPath) {
1139 allPaths.Insert(curPath)
1140 } else {
1141 allErrs = append(allErrs, field.Invalid(fldPath, curPath, "conflicting duplicate paths"))
1142 }
1143 }
1144 }
1145 }
1146 if projPath := srcPath.Child("serviceAccountToken"); source.ServiceAccountToken != nil {
1147 numSources++
1148 if source.ServiceAccountToken.ExpirationSeconds < 10*60 {
1149 allErrs = append(allErrs, field.Invalid(projPath.Child("expirationSeconds"), source.ServiceAccountToken.ExpirationSeconds, "may not specify a duration less than 10 minutes"))
1150 }
1151 if source.ServiceAccountToken.ExpirationSeconds > 1<<32 {
1152 allErrs = append(allErrs, field.Invalid(projPath.Child("expirationSeconds"), source.ServiceAccountToken.ExpirationSeconds, "may not specify a duration larger than 2^32 seconds"))
1153 }
1154 if source.ServiceAccountToken.Path == "" {
1155 allErrs = append(allErrs, field.Required(fldPath.Child("path"), ""))
1156 } else if !opts.AllowNonLocalProjectedTokenPath {
1157 allErrs = append(allErrs, ValidateLocalNonReservedPath(source.ServiceAccountToken.Path, fldPath.Child("path"))...)
1158 }
1159 }
1160 if projPath := srcPath.Child("clusterTrustBundlePEM"); source.ClusterTrustBundle != nil {
1161 numSources++
1162
1163 usingName := source.ClusterTrustBundle.Name != nil
1164 usingSignerName := source.ClusterTrustBundle.SignerName != nil
1165
1166 switch {
1167 case usingName && usingSignerName:
1168 allErrs = append(allErrs, field.Invalid(projPath, source.ClusterTrustBundle, "only one of name and signerName may be used"))
1169 case usingName:
1170 if *source.ClusterTrustBundle.Name == "" {
1171 allErrs = append(allErrs, field.Required(projPath.Child("name"), "must be a valid object name"))
1172 }
1173
1174 name := *source.ClusterTrustBundle.Name
1175 if signerName, ok := extractSignerNameFromClusterTrustBundleName(name); ok {
1176 validationFunc := ValidateClusterTrustBundleName(signerName)
1177 errMsgs := validationFunc(name, false)
1178 for _, msg := range errMsgs {
1179 allErrs = append(allErrs, field.Invalid(projPath.Child("name"), name, fmt.Sprintf("not a valid clustertrustbundlename: %v", msg)))
1180 }
1181 } else {
1182 validationFunc := ValidateClusterTrustBundleName("")
1183 errMsgs := validationFunc(name, false)
1184 for _, msg := range errMsgs {
1185 allErrs = append(allErrs, field.Invalid(projPath.Child("name"), name, fmt.Sprintf("not a valid clustertrustbundlename: %v", msg)))
1186 }
1187 }
1188
1189 if source.ClusterTrustBundle.LabelSelector != nil {
1190 allErrs = append(allErrs, field.Invalid(projPath.Child("labelSelector"), source.ClusterTrustBundle.LabelSelector, "labelSelector must be unset if name is specified"))
1191 }
1192 case usingSignerName:
1193 if *source.ClusterTrustBundle.SignerName == "" {
1194 allErrs = append(allErrs, field.Required(projPath.Child("signerName"), "must be a valid signer name"))
1195 }
1196
1197 allErrs = append(allErrs, ValidateSignerName(projPath.Child("signerName"), *source.ClusterTrustBundle.SignerName)...)
1198
1199 labelSelectorErrs := unversionedvalidation.ValidateLabelSelector(
1200 source.ClusterTrustBundle.LabelSelector,
1201 unversionedvalidation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: false},
1202 projPath.Child("labelSelector"),
1203 )
1204 allErrs = append(allErrs, labelSelectorErrs...)
1205
1206 default:
1207 allErrs = append(allErrs, field.Required(projPath, "either name or signerName must be specified"))
1208 }
1209
1210 if source.ClusterTrustBundle.Path == "" {
1211 allErrs = append(allErrs, field.Required(projPath.Child("path"), ""))
1212 }
1213
1214 allErrs = append(allErrs, ValidateLocalNonReservedPath(source.ClusterTrustBundle.Path, projPath.Child("path"))...)
1215
1216 curPath := source.ClusterTrustBundle.Path
1217 if !allPaths.Has(curPath) {
1218 allPaths.Insert(curPath)
1219 } else {
1220 allErrs = append(allErrs, field.Invalid(fldPath, curPath, "conflicting duplicate paths"))
1221 }
1222 }
1223 if numSources > 1 {
1224 allErrs = append(allErrs, field.Forbidden(srcPath, "may not specify more than 1 volume type"))
1225 }
1226 }
1227 return allErrs
1228 }
1229
1230 func validateProjectedVolumeSource(projection *core.ProjectedVolumeSource, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
1231 allErrs := field.ErrorList{}
1232
1233 projectionMode := projection.DefaultMode
1234 if projectionMode != nil && (*projectionMode > 0777 || *projectionMode < 0) {
1235 allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *projectionMode, fileModeErrorMsg))
1236 }
1237
1238 allErrs = append(allErrs, validateProjectionSources(projection, projectionMode, fldPath, opts)...)
1239 return allErrs
1240 }
1241
1242 var supportedHostPathTypes = sets.New(
1243 core.HostPathUnset,
1244 core.HostPathDirectoryOrCreate,
1245 core.HostPathDirectory,
1246 core.HostPathFileOrCreate,
1247 core.HostPathFile,
1248 core.HostPathSocket,
1249 core.HostPathCharDev,
1250 core.HostPathBlockDev)
1251
1252 func validateHostPathType(hostPathType *core.HostPathType, fldPath *field.Path) field.ErrorList {
1253 allErrs := field.ErrorList{}
1254
1255 if hostPathType != nil && !supportedHostPathTypes.Has(*hostPathType) {
1256 allErrs = append(allErrs, field.NotSupported(fldPath, hostPathType, sets.List(supportedHostPathTypes)))
1257 }
1258
1259 return allErrs
1260 }
1261
1262
1263
1264
1265 func validateLocalDescendingPath(targetPath string, fldPath *field.Path) field.ErrorList {
1266 allErrs := field.ErrorList{}
1267 if path.IsAbs(targetPath) {
1268 allErrs = append(allErrs, field.Invalid(fldPath, targetPath, "must be a relative path"))
1269 }
1270
1271 allErrs = append(allErrs, validatePathNoBacksteps(targetPath, fldPath)...)
1272
1273 return allErrs
1274 }
1275
1276
1277
1278
1279
1280 func validatePathNoBacksteps(targetPath string, fldPath *field.Path) field.ErrorList {
1281 allErrs := field.ErrorList{}
1282 parts := strings.Split(filepath.ToSlash(targetPath), "/")
1283 for _, item := range parts {
1284 if item == ".." {
1285 allErrs = append(allErrs, field.Invalid(fldPath, targetPath, "must not contain '..'"))
1286 break
1287 }
1288 }
1289 return allErrs
1290 }
1291
1292
1293
1294 func validateMountPropagation(mountPropagation *core.MountPropagationMode, container *core.Container, fldPath *field.Path) field.ErrorList {
1295 allErrs := field.ErrorList{}
1296
1297 if mountPropagation == nil {
1298 return allErrs
1299 }
1300
1301 supportedMountPropagations := sets.New(
1302 core.MountPropagationBidirectional,
1303 core.MountPropagationHostToContainer,
1304 core.MountPropagationNone)
1305
1306 if !supportedMountPropagations.Has(*mountPropagation) {
1307 allErrs = append(allErrs, field.NotSupported(fldPath, *mountPropagation, sets.List(supportedMountPropagations)))
1308 }
1309
1310 if container == nil {
1311
1312
1313
1314 return allErrs
1315 }
1316
1317 privileged := container.SecurityContext != nil && container.SecurityContext.Privileged != nil && *container.SecurityContext.Privileged
1318 if *mountPropagation == core.MountPropagationBidirectional && !privileged {
1319 allErrs = append(allErrs, field.Forbidden(fldPath, "Bidirectional mount propagation is available only to privileged containers"))
1320 }
1321 return allErrs
1322 }
1323
1324
1325 func validateMountRecursiveReadOnly(mount core.VolumeMount, fldPath *field.Path) field.ErrorList {
1326 if mount.RecursiveReadOnly == nil {
1327 return nil
1328 }
1329 allErrs := field.ErrorList{}
1330 switch *mount.RecursiveReadOnly {
1331 case core.RecursiveReadOnlyDisabled:
1332
1333 case core.RecursiveReadOnlyEnabled, core.RecursiveReadOnlyIfPossible:
1334 if !mount.ReadOnly {
1335 allErrs = append(allErrs, field.Forbidden(fldPath, "may only be specified when readOnly is true"))
1336 }
1337 if mount.MountPropagation != nil && *mount.MountPropagation != core.MountPropagationNone {
1338 allErrs = append(allErrs, field.Forbidden(fldPath, "may only be specified when mountPropagation is None or not specified"))
1339 }
1340 default:
1341 supportedRRO := sets.New(
1342 core.RecursiveReadOnlyDisabled,
1343 core.RecursiveReadOnlyIfPossible,
1344 core.RecursiveReadOnlyEnabled)
1345 allErrs = append(allErrs, field.NotSupported(fldPath, *mount.RecursiveReadOnly, sets.List(supportedRRO)))
1346 }
1347 return allErrs
1348 }
1349
1350
1351
1352
1353
1354 func ValidateLocalNonReservedPath(targetPath string, fldPath *field.Path) field.ErrorList {
1355 allErrs := field.ErrorList{}
1356 allErrs = append(allErrs, validateLocalDescendingPath(targetPath, fldPath)...)
1357
1358 if strings.HasPrefix(targetPath, "..") && !strings.HasPrefix(targetPath, "../") {
1359 allErrs = append(allErrs, field.Invalid(fldPath, targetPath, "must not start with '..'"))
1360 }
1361 return allErrs
1362 }
1363
1364 func validateRBDVolumeSource(rbd *core.RBDVolumeSource, fldPath *field.Path) field.ErrorList {
1365 allErrs := field.ErrorList{}
1366 if len(rbd.CephMonitors) == 0 {
1367 allErrs = append(allErrs, field.Required(fldPath.Child("monitors"), ""))
1368 }
1369 if len(rbd.RBDImage) == 0 {
1370 allErrs = append(allErrs, field.Required(fldPath.Child("image"), ""))
1371 }
1372 return allErrs
1373 }
1374
1375 func validateRBDPersistentVolumeSource(rbd *core.RBDPersistentVolumeSource, fldPath *field.Path) field.ErrorList {
1376 allErrs := field.ErrorList{}
1377 if len(rbd.CephMonitors) == 0 {
1378 allErrs = append(allErrs, field.Required(fldPath.Child("monitors"), ""))
1379 }
1380 if len(rbd.RBDImage) == 0 {
1381 allErrs = append(allErrs, field.Required(fldPath.Child("image"), ""))
1382 }
1383 return allErrs
1384 }
1385
1386 func validateCinderVolumeSource(cd *core.CinderVolumeSource, fldPath *field.Path) field.ErrorList {
1387 allErrs := field.ErrorList{}
1388 if len(cd.VolumeID) == 0 {
1389 allErrs = append(allErrs, field.Required(fldPath.Child("volumeID"), ""))
1390 }
1391 if cd.SecretRef != nil {
1392 if len(cd.SecretRef.Name) == 0 {
1393 allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "name"), ""))
1394 }
1395 }
1396 return allErrs
1397 }
1398
1399 func validateCinderPersistentVolumeSource(cd *core.CinderPersistentVolumeSource, fldPath *field.Path) field.ErrorList {
1400 allErrs := field.ErrorList{}
1401 if len(cd.VolumeID) == 0 {
1402 allErrs = append(allErrs, field.Required(fldPath.Child("volumeID"), ""))
1403 }
1404 if cd.SecretRef != nil {
1405 if len(cd.SecretRef.Name) == 0 {
1406 allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "name"), ""))
1407 }
1408 if len(cd.SecretRef.Namespace) == 0 {
1409 allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "namespace"), ""))
1410 }
1411 }
1412 return allErrs
1413 }
1414
1415 func validateCephFSVolumeSource(cephfs *core.CephFSVolumeSource, fldPath *field.Path) field.ErrorList {
1416 allErrs := field.ErrorList{}
1417 if len(cephfs.Monitors) == 0 {
1418 allErrs = append(allErrs, field.Required(fldPath.Child("monitors"), ""))
1419 }
1420 return allErrs
1421 }
1422
1423 func validateCephFSPersistentVolumeSource(cephfs *core.CephFSPersistentVolumeSource, fldPath *field.Path) field.ErrorList {
1424 allErrs := field.ErrorList{}
1425 if len(cephfs.Monitors) == 0 {
1426 allErrs = append(allErrs, field.Required(fldPath.Child("monitors"), ""))
1427 }
1428 return allErrs
1429 }
1430
1431 func validateFlexVolumeSource(fv *core.FlexVolumeSource, fldPath *field.Path) field.ErrorList {
1432 allErrs := field.ErrorList{}
1433 if len(fv.Driver) == 0 {
1434 allErrs = append(allErrs, field.Required(fldPath.Child("driver"), ""))
1435 }
1436
1437
1438 for k := range fv.Options {
1439 namespace := k
1440 if parts := strings.SplitN(k, "/", 2); len(parts) == 2 {
1441 namespace = parts[0]
1442 }
1443 normalized := "." + strings.ToLower(namespace)
1444 if strings.HasSuffix(normalized, ".kubernetes.io") || strings.HasSuffix(normalized, ".k8s.io") {
1445 allErrs = append(allErrs, field.Invalid(fldPath.Child("options").Key(k), k, "kubernetes.io and k8s.io namespaces are reserved"))
1446 }
1447 }
1448
1449 return allErrs
1450 }
1451
1452 func validateFlexPersistentVolumeSource(fv *core.FlexPersistentVolumeSource, fldPath *field.Path) field.ErrorList {
1453 allErrs := field.ErrorList{}
1454 if len(fv.Driver) == 0 {
1455 allErrs = append(allErrs, field.Required(fldPath.Child("driver"), ""))
1456 }
1457
1458
1459 for k := range fv.Options {
1460 namespace := k
1461 if parts := strings.SplitN(k, "/", 2); len(parts) == 2 {
1462 namespace = parts[0]
1463 }
1464 normalized := "." + strings.ToLower(namespace)
1465 if strings.HasSuffix(normalized, ".kubernetes.io") || strings.HasSuffix(normalized, ".k8s.io") {
1466 allErrs = append(allErrs, field.Invalid(fldPath.Child("options").Key(k), k, "kubernetes.io and k8s.io namespaces are reserved"))
1467 }
1468 }
1469
1470 return allErrs
1471 }
1472
1473 func validateAzureFile(azure *core.AzureFileVolumeSource, fldPath *field.Path) field.ErrorList {
1474 allErrs := field.ErrorList{}
1475 if azure.SecretName == "" {
1476 allErrs = append(allErrs, field.Required(fldPath.Child("secretName"), ""))
1477 }
1478 if azure.ShareName == "" {
1479 allErrs = append(allErrs, field.Required(fldPath.Child("shareName"), ""))
1480 }
1481 return allErrs
1482 }
1483
1484 func validateAzureFilePV(azure *core.AzureFilePersistentVolumeSource, fldPath *field.Path) field.ErrorList {
1485 allErrs := field.ErrorList{}
1486 if azure.SecretName == "" {
1487 allErrs = append(allErrs, field.Required(fldPath.Child("secretName"), ""))
1488 }
1489 if azure.ShareName == "" {
1490 allErrs = append(allErrs, field.Required(fldPath.Child("shareName"), ""))
1491 }
1492 if azure.SecretNamespace != nil {
1493 if len(*azure.SecretNamespace) == 0 {
1494 allErrs = append(allErrs, field.Required(fldPath.Child("secretNamespace"), ""))
1495 }
1496 }
1497 return allErrs
1498 }
1499
1500 func validateAzureDisk(azure *core.AzureDiskVolumeSource, fldPath *field.Path) field.ErrorList {
1501 var supportedCachingModes = sets.New(
1502 core.AzureDataDiskCachingNone,
1503 core.AzureDataDiskCachingReadOnly,
1504 core.AzureDataDiskCachingReadWrite)
1505
1506 var supportedDiskKinds = sets.New(
1507 core.AzureSharedBlobDisk,
1508 core.AzureDedicatedBlobDisk,
1509 core.AzureManagedDisk)
1510
1511 diskURISupportedManaged := []string{"/subscriptions/{sub-id}/resourcegroups/{group-name}/providers/microsoft.compute/disks/{disk-id}"}
1512 diskURISupportedblob := []string{"https://{account-name}.blob.core.windows.net/{container-name}/{disk-name}.vhd"}
1513
1514 allErrs := field.ErrorList{}
1515 if azure.DiskName == "" {
1516 allErrs = append(allErrs, field.Required(fldPath.Child("diskName"), ""))
1517 }
1518
1519 if azure.DataDiskURI == "" {
1520 allErrs = append(allErrs, field.Required(fldPath.Child("diskURI"), ""))
1521 }
1522
1523 if azure.CachingMode != nil && !supportedCachingModes.Has(*azure.CachingMode) {
1524 allErrs = append(allErrs, field.NotSupported(fldPath.Child("cachingMode"), *azure.CachingMode, sets.List(supportedCachingModes)))
1525 }
1526
1527 if azure.Kind != nil && !supportedDiskKinds.Has(*azure.Kind) {
1528 allErrs = append(allErrs, field.NotSupported(fldPath.Child("kind"), *azure.Kind, sets.List(supportedDiskKinds)))
1529 }
1530
1531
1532 if azure.Kind != nil && *azure.Kind == core.AzureManagedDisk && strings.Index(azure.DataDiskURI, "/subscriptions/") != 0 {
1533 allErrs = append(allErrs, field.NotSupported(fldPath.Child("diskURI"), azure.DataDiskURI, diskURISupportedManaged))
1534 }
1535
1536 if azure.Kind != nil && *azure.Kind != core.AzureManagedDisk && strings.Index(azure.DataDiskURI, "https://") != 0 {
1537 allErrs = append(allErrs, field.NotSupported(fldPath.Child("diskURI"), azure.DataDiskURI, diskURISupportedblob))
1538 }
1539
1540 return allErrs
1541 }
1542
1543 func validateVsphereVolumeSource(cd *core.VsphereVirtualDiskVolumeSource, fldPath *field.Path) field.ErrorList {
1544 allErrs := field.ErrorList{}
1545 if len(cd.VolumePath) == 0 {
1546 allErrs = append(allErrs, field.Required(fldPath.Child("volumePath"), ""))
1547 }
1548 return allErrs
1549 }
1550
1551 func validatePhotonPersistentDiskVolumeSource(cd *core.PhotonPersistentDiskVolumeSource, fldPath *field.Path) field.ErrorList {
1552 allErrs := field.ErrorList{}
1553 if len(cd.PdID) == 0 {
1554 allErrs = append(allErrs, field.Required(fldPath.Child("pdID"), ""))
1555 }
1556 return allErrs
1557 }
1558
1559 func validatePortworxVolumeSource(pwx *core.PortworxVolumeSource, fldPath *field.Path) field.ErrorList {
1560 allErrs := field.ErrorList{}
1561 if len(pwx.VolumeID) == 0 {
1562 allErrs = append(allErrs, field.Required(fldPath.Child("volumeID"), ""))
1563 }
1564 return allErrs
1565 }
1566
1567 func validateScaleIOVolumeSource(sio *core.ScaleIOVolumeSource, fldPath *field.Path) field.ErrorList {
1568 allErrs := field.ErrorList{}
1569 if sio.Gateway == "" {
1570 allErrs = append(allErrs, field.Required(fldPath.Child("gateway"), ""))
1571 }
1572 if sio.System == "" {
1573 allErrs = append(allErrs, field.Required(fldPath.Child("system"), ""))
1574 }
1575 if sio.VolumeName == "" {
1576 allErrs = append(allErrs, field.Required(fldPath.Child("volumeName"), ""))
1577 }
1578 return allErrs
1579 }
1580
1581 func validateScaleIOPersistentVolumeSource(sio *core.ScaleIOPersistentVolumeSource, fldPath *field.Path) field.ErrorList {
1582 allErrs := field.ErrorList{}
1583 if sio.Gateway == "" {
1584 allErrs = append(allErrs, field.Required(fldPath.Child("gateway"), ""))
1585 }
1586 if sio.System == "" {
1587 allErrs = append(allErrs, field.Required(fldPath.Child("system"), ""))
1588 }
1589 if sio.VolumeName == "" {
1590 allErrs = append(allErrs, field.Required(fldPath.Child("volumeName"), ""))
1591 }
1592 return allErrs
1593 }
1594
1595 func validateLocalVolumeSource(ls *core.LocalVolumeSource, fldPath *field.Path) field.ErrorList {
1596 allErrs := field.ErrorList{}
1597 if ls.Path == "" {
1598 allErrs = append(allErrs, field.Required(fldPath.Child("path"), ""))
1599 return allErrs
1600 }
1601
1602 allErrs = append(allErrs, validatePathNoBacksteps(ls.Path, fldPath.Child("path"))...)
1603 return allErrs
1604 }
1605
1606 func validateStorageOSVolumeSource(storageos *core.StorageOSVolumeSource, fldPath *field.Path) field.ErrorList {
1607 allErrs := field.ErrorList{}
1608 if len(storageos.VolumeName) == 0 {
1609 allErrs = append(allErrs, field.Required(fldPath.Child("volumeName"), ""))
1610 } else {
1611 allErrs = append(allErrs, ValidateDNS1123Label(storageos.VolumeName, fldPath.Child("volumeName"))...)
1612 }
1613 if len(storageos.VolumeNamespace) > 0 {
1614 allErrs = append(allErrs, ValidateDNS1123Label(storageos.VolumeNamespace, fldPath.Child("volumeNamespace"))...)
1615 }
1616 if storageos.SecretRef != nil {
1617 if len(storageos.SecretRef.Name) == 0 {
1618 allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "name"), ""))
1619 }
1620 }
1621 return allErrs
1622 }
1623
1624 func validateStorageOSPersistentVolumeSource(storageos *core.StorageOSPersistentVolumeSource, fldPath *field.Path) field.ErrorList {
1625 allErrs := field.ErrorList{}
1626 if len(storageos.VolumeName) == 0 {
1627 allErrs = append(allErrs, field.Required(fldPath.Child("volumeName"), ""))
1628 } else {
1629 allErrs = append(allErrs, ValidateDNS1123Label(storageos.VolumeName, fldPath.Child("volumeName"))...)
1630 }
1631 if len(storageos.VolumeNamespace) > 0 {
1632 allErrs = append(allErrs, ValidateDNS1123Label(storageos.VolumeNamespace, fldPath.Child("volumeNamespace"))...)
1633 }
1634 if storageos.SecretRef != nil {
1635 if len(storageos.SecretRef.Name) == 0 {
1636 allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "name"), ""))
1637 }
1638 if len(storageos.SecretRef.Namespace) == 0 {
1639 allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "namespace"), ""))
1640 }
1641 }
1642 return allErrs
1643 }
1644
1645
1646
1647 func validatePVSecretReference(secretRef *core.SecretReference, fldPath *field.Path) field.ErrorList {
1648 var allErrs field.ErrorList
1649 if len(secretRef.Name) == 0 {
1650 allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
1651 } else {
1652 allErrs = append(allErrs, ValidateDNS1123Subdomain(secretRef.Name, fldPath.Child("name"))...)
1653 }
1654
1655 if len(secretRef.Namespace) == 0 {
1656 allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), ""))
1657 } else {
1658 allErrs = append(allErrs, ValidateDNS1123Label(secretRef.Namespace, fldPath.Child("namespace"))...)
1659 }
1660 return allErrs
1661 }
1662
1663 func ValidateCSIDriverName(driverName string, fldPath *field.Path) field.ErrorList {
1664 allErrs := field.ErrorList{}
1665
1666 if len(driverName) == 0 {
1667 allErrs = append(allErrs, field.Required(fldPath, ""))
1668 }
1669
1670 if len(driverName) > 63 {
1671 allErrs = append(allErrs, field.TooLong(fldPath, driverName, 63))
1672 }
1673
1674 for _, msg := range validation.IsDNS1123Subdomain(strings.ToLower(driverName)) {
1675 allErrs = append(allErrs, field.Invalid(fldPath, driverName, msg))
1676 }
1677
1678 return allErrs
1679 }
1680
1681 func validateCSIPersistentVolumeSource(csi *core.CSIPersistentVolumeSource, fldPath *field.Path) field.ErrorList {
1682 allErrs := field.ErrorList{}
1683
1684 allErrs = append(allErrs, ValidateCSIDriverName(csi.Driver, fldPath.Child("driver"))...)
1685
1686 if len(csi.VolumeHandle) == 0 {
1687 allErrs = append(allErrs, field.Required(fldPath.Child("volumeHandle"), ""))
1688 }
1689 if csi.ControllerPublishSecretRef != nil {
1690 allErrs = append(allErrs, validatePVSecretReference(csi.ControllerPublishSecretRef, fldPath.Child("controllerPublishSecretRef"))...)
1691 }
1692 if csi.ControllerExpandSecretRef != nil {
1693 allErrs = append(allErrs, validatePVSecretReference(csi.ControllerExpandSecretRef, fldPath.Child("controllerExpandSecretRef"))...)
1694 }
1695 if csi.NodePublishSecretRef != nil {
1696 allErrs = append(allErrs, validatePVSecretReference(csi.NodePublishSecretRef, fldPath.Child("nodePublishSecretRef"))...)
1697 }
1698 if csi.NodeExpandSecretRef != nil {
1699 allErrs = append(allErrs, validatePVSecretReference(csi.NodeExpandSecretRef, fldPath.Child("nodeExpandSecretRef"))...)
1700 }
1701 return allErrs
1702 }
1703
1704 func validateCSIVolumeSource(csi *core.CSIVolumeSource, fldPath *field.Path) field.ErrorList {
1705 allErrs := field.ErrorList{}
1706 allErrs = append(allErrs, ValidateCSIDriverName(csi.Driver, fldPath.Child("driver"))...)
1707
1708 if csi.NodePublishSecretRef != nil {
1709 if len(csi.NodePublishSecretRef.Name) == 0 {
1710 allErrs = append(allErrs, field.Required(fldPath.Child("nodePublishSecretRef", "name"), ""))
1711 } else {
1712 for _, msg := range ValidateSecretName(csi.NodePublishSecretRef.Name, false) {
1713 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), csi.NodePublishSecretRef.Name, msg))
1714 }
1715 }
1716 }
1717
1718 return allErrs
1719 }
1720
1721 func validateEphemeralVolumeSource(ephemeral *core.EphemeralVolumeSource, fldPath *field.Path) field.ErrorList {
1722 allErrs := field.ErrorList{}
1723 if ephemeral.VolumeClaimTemplate == nil {
1724 allErrs = append(allErrs, field.Required(fldPath.Child("volumeClaimTemplate"), ""))
1725 } else {
1726 opts := ValidationOptionsForPersistentVolumeClaimTemplate(ephemeral.VolumeClaimTemplate, nil)
1727 allErrs = append(allErrs, ValidatePersistentVolumeClaimTemplate(ephemeral.VolumeClaimTemplate, fldPath.Child("volumeClaimTemplate"), opts)...)
1728 }
1729 return allErrs
1730 }
1731
1732
1733
1734 func ValidatePersistentVolumeClaimTemplate(claimTemplate *core.PersistentVolumeClaimTemplate, fldPath *field.Path, opts PersistentVolumeClaimSpecValidationOptions) field.ErrorList {
1735 allErrs := ValidateTemplateObjectMeta(&claimTemplate.ObjectMeta, fldPath.Child("metadata"))
1736 allErrs = append(allErrs, ValidatePersistentVolumeClaimSpec(&claimTemplate.Spec, fldPath.Child("spec"), opts)...)
1737 return allErrs
1738 }
1739
1740 func ValidateTemplateObjectMeta(objMeta *metav1.ObjectMeta, fldPath *field.Path) field.ErrorList {
1741 allErrs := apimachineryvalidation.ValidateAnnotations(objMeta.Annotations, fldPath.Child("annotations"))
1742 allErrs = append(allErrs, unversionedvalidation.ValidateLabels(objMeta.Labels, fldPath.Child("labels"))...)
1743
1744
1745
1746
1747
1748 allErrs = append(allErrs, validateFieldAllowList(*objMeta, allowedTemplateObjectMetaFields, "cannot be set", fldPath)...)
1749 return allErrs
1750 }
1751
1752 var allowedTemplateObjectMetaFields = map[string]bool{
1753 "Annotations": true,
1754 "Labels": true,
1755 }
1756
1757
1758 type PersistentVolumeSpecValidationOptions struct {
1759
1760 EnableVolumeAttributesClass bool
1761 }
1762
1763
1764
1765 var ValidatePersistentVolumeName = apimachineryvalidation.NameIsDNSSubdomain
1766
1767 var supportedAccessModes = sets.New(
1768 core.ReadWriteOnce,
1769 core.ReadOnlyMany,
1770 core.ReadWriteMany,
1771 core.ReadWriteOncePod)
1772
1773 var supportedReclaimPolicy = sets.New(
1774 core.PersistentVolumeReclaimDelete,
1775 core.PersistentVolumeReclaimRecycle,
1776 core.PersistentVolumeReclaimRetain)
1777
1778 var supportedVolumeModes = sets.New(core.PersistentVolumeBlock, core.PersistentVolumeFilesystem)
1779
1780 func ValidationOptionsForPersistentVolume(pv, oldPv *core.PersistentVolume) PersistentVolumeSpecValidationOptions {
1781 opts := PersistentVolumeSpecValidationOptions{
1782 EnableVolumeAttributesClass: utilfeature.DefaultMutableFeatureGate.Enabled(features.VolumeAttributesClass),
1783 }
1784 if oldPv != nil && oldPv.Spec.VolumeAttributesClassName != nil {
1785 opts.EnableVolumeAttributesClass = true
1786 }
1787 return opts
1788 }
1789
1790 func ValidatePersistentVolumeSpec(pvSpec *core.PersistentVolumeSpec, pvName string, validateInlinePersistentVolumeSpec bool, fldPath *field.Path, opts PersistentVolumeSpecValidationOptions) field.ErrorList {
1791 allErrs := field.ErrorList{}
1792
1793 if validateInlinePersistentVolumeSpec {
1794 if pvSpec.ClaimRef != nil {
1795 allErrs = append(allErrs, field.Forbidden(fldPath.Child("claimRef"), "may not be specified in the context of inline volumes"))
1796 }
1797 if len(pvSpec.Capacity) != 0 {
1798 allErrs = append(allErrs, field.Forbidden(fldPath.Child("capacity"), "may not be specified in the context of inline volumes"))
1799 }
1800 if pvSpec.CSI == nil {
1801 allErrs = append(allErrs, field.Required(fldPath.Child("csi"), "has to be specified in the context of inline volumes"))
1802 }
1803 }
1804
1805 if len(pvSpec.AccessModes) == 0 {
1806 allErrs = append(allErrs, field.Required(fldPath.Child("accessModes"), ""))
1807 }
1808
1809 foundReadWriteOncePod, foundNonReadWriteOncePod := false, false
1810 for _, mode := range pvSpec.AccessModes {
1811 if !supportedAccessModes.Has(mode) {
1812 allErrs = append(allErrs, field.NotSupported(fldPath.Child("accessModes"), mode, sets.List(supportedAccessModes)))
1813 }
1814
1815 if mode == core.ReadWriteOncePod {
1816 foundReadWriteOncePod = true
1817 } else if supportedAccessModes.Has(mode) {
1818 foundNonReadWriteOncePod = true
1819 }
1820 }
1821 if foundReadWriteOncePod && foundNonReadWriteOncePod {
1822 allErrs = append(allErrs, field.Forbidden(fldPath.Child("accessModes"), "may not use ReadWriteOncePod with other access modes"))
1823 }
1824
1825 if !validateInlinePersistentVolumeSpec {
1826 if len(pvSpec.Capacity) == 0 {
1827 allErrs = append(allErrs, field.Required(fldPath.Child("capacity"), ""))
1828 }
1829
1830 if _, ok := pvSpec.Capacity[core.ResourceStorage]; !ok || len(pvSpec.Capacity) > 1 {
1831 allErrs = append(allErrs, field.NotSupported(fldPath.Child("capacity"), pvSpec.Capacity, []core.ResourceName{core.ResourceStorage}))
1832 }
1833 capPath := fldPath.Child("capacity")
1834 for r, qty := range pvSpec.Capacity {
1835 allErrs = append(allErrs, validateBasicResource(qty, capPath.Key(string(r)))...)
1836 allErrs = append(allErrs, ValidatePositiveQuantityValue(qty, capPath.Key(string(r)))...)
1837 }
1838 }
1839
1840 if len(pvSpec.PersistentVolumeReclaimPolicy) > 0 {
1841 if validateInlinePersistentVolumeSpec {
1842 if pvSpec.PersistentVolumeReclaimPolicy != core.PersistentVolumeReclaimRetain {
1843 allErrs = append(allErrs, field.Forbidden(fldPath.Child("persistentVolumeReclaimPolicy"), "may only be "+string(core.PersistentVolumeReclaimRetain)+" in the context of inline volumes"))
1844 }
1845 } else {
1846 if !supportedReclaimPolicy.Has(pvSpec.PersistentVolumeReclaimPolicy) {
1847 allErrs = append(allErrs, field.NotSupported(fldPath.Child("persistentVolumeReclaimPolicy"), pvSpec.PersistentVolumeReclaimPolicy, sets.List(supportedReclaimPolicy)))
1848 }
1849 }
1850 }
1851
1852 var nodeAffinitySpecified bool
1853 var errs field.ErrorList
1854 if pvSpec.NodeAffinity != nil {
1855 if validateInlinePersistentVolumeSpec {
1856 allErrs = append(allErrs, field.Forbidden(fldPath.Child("nodeAffinity"), "may not be specified in the context of inline volumes"))
1857 } else {
1858 nodeAffinitySpecified, errs = validateVolumeNodeAffinity(pvSpec.NodeAffinity, fldPath.Child("nodeAffinity"))
1859 allErrs = append(allErrs, errs...)
1860 }
1861 }
1862 numVolumes := 0
1863 if pvSpec.HostPath != nil {
1864 if numVolumes > 0 {
1865 allErrs = append(allErrs, field.Forbidden(fldPath.Child("hostPath"), "may not specify more than 1 volume type"))
1866 } else {
1867 numVolumes++
1868 allErrs = append(allErrs, validateHostPathVolumeSource(pvSpec.HostPath, fldPath.Child("hostPath"))...)
1869 }
1870 }
1871 if pvSpec.GCEPersistentDisk != nil {
1872 if numVolumes > 0 {
1873 allErrs = append(allErrs, field.Forbidden(fldPath.Child("gcePersistentDisk"), "may not specify more than 1 volume type"))
1874 } else {
1875 numVolumes++
1876 allErrs = append(allErrs, validateGCEPersistentDiskVolumeSource(pvSpec.GCEPersistentDisk, fldPath.Child("persistentDisk"))...)
1877 }
1878 }
1879 if pvSpec.AWSElasticBlockStore != nil {
1880 if numVolumes > 0 {
1881 allErrs = append(allErrs, field.Forbidden(fldPath.Child("awsElasticBlockStore"), "may not specify more than 1 volume type"))
1882 } else {
1883 numVolumes++
1884 allErrs = append(allErrs, validateAWSElasticBlockStoreVolumeSource(pvSpec.AWSElasticBlockStore, fldPath.Child("awsElasticBlockStore"))...)
1885 }
1886 }
1887 if pvSpec.Glusterfs != nil {
1888 if numVolumes > 0 {
1889 allErrs = append(allErrs, field.Forbidden(fldPath.Child("glusterfs"), "may not specify more than 1 volume type"))
1890 } else {
1891 numVolumes++
1892 allErrs = append(allErrs, validateGlusterfsPersistentVolumeSource(pvSpec.Glusterfs, fldPath.Child("glusterfs"))...)
1893 }
1894 }
1895 if pvSpec.Flocker != nil {
1896 if numVolumes > 0 {
1897 allErrs = append(allErrs, field.Forbidden(fldPath.Child("flocker"), "may not specify more than 1 volume type"))
1898 } else {
1899 numVolumes++
1900 allErrs = append(allErrs, validateFlockerVolumeSource(pvSpec.Flocker, fldPath.Child("flocker"))...)
1901 }
1902 }
1903 if pvSpec.NFS != nil {
1904 if numVolumes > 0 {
1905 allErrs = append(allErrs, field.Forbidden(fldPath.Child("nfs"), "may not specify more than 1 volume type"))
1906 } else {
1907 numVolumes++
1908 allErrs = append(allErrs, validateNFSVolumeSource(pvSpec.NFS, fldPath.Child("nfs"))...)
1909 }
1910 }
1911 if pvSpec.RBD != nil {
1912 if numVolumes > 0 {
1913 allErrs = append(allErrs, field.Forbidden(fldPath.Child("rbd"), "may not specify more than 1 volume type"))
1914 } else {
1915 numVolumes++
1916 allErrs = append(allErrs, validateRBDPersistentVolumeSource(pvSpec.RBD, fldPath.Child("rbd"))...)
1917 }
1918 }
1919 if pvSpec.Quobyte != nil {
1920 if numVolumes > 0 {
1921 allErrs = append(allErrs, field.Forbidden(fldPath.Child("quobyte"), "may not specify more than 1 volume type"))
1922 } else {
1923 numVolumes++
1924 allErrs = append(allErrs, validateQuobyteVolumeSource(pvSpec.Quobyte, fldPath.Child("quobyte"))...)
1925 }
1926 }
1927 if pvSpec.CephFS != nil {
1928 if numVolumes > 0 {
1929 allErrs = append(allErrs, field.Forbidden(fldPath.Child("cephFS"), "may not specify more than 1 volume type"))
1930 } else {
1931 numVolumes++
1932 allErrs = append(allErrs, validateCephFSPersistentVolumeSource(pvSpec.CephFS, fldPath.Child("cephfs"))...)
1933 }
1934 }
1935 if pvSpec.ISCSI != nil {
1936 if numVolumes > 0 {
1937 allErrs = append(allErrs, field.Forbidden(fldPath.Child("iscsi"), "may not specify more than 1 volume type"))
1938 } else {
1939 numVolumes++
1940 allErrs = append(allErrs, validateISCSIPersistentVolumeSource(pvSpec.ISCSI, pvName, fldPath.Child("iscsi"))...)
1941 }
1942 }
1943 if pvSpec.Cinder != nil {
1944 if numVolumes > 0 {
1945 allErrs = append(allErrs, field.Forbidden(fldPath.Child("cinder"), "may not specify more than 1 volume type"))
1946 } else {
1947 numVolumes++
1948 allErrs = append(allErrs, validateCinderPersistentVolumeSource(pvSpec.Cinder, fldPath.Child("cinder"))...)
1949 }
1950 }
1951 if pvSpec.FC != nil {
1952 if numVolumes > 0 {
1953 allErrs = append(allErrs, field.Forbidden(fldPath.Child("fc"), "may not specify more than 1 volume type"))
1954 } else {
1955 numVolumes++
1956 allErrs = append(allErrs, validateFCVolumeSource(pvSpec.FC, fldPath.Child("fc"))...)
1957 }
1958 }
1959 if pvSpec.FlexVolume != nil {
1960 numVolumes++
1961 allErrs = append(allErrs, validateFlexPersistentVolumeSource(pvSpec.FlexVolume, fldPath.Child("flexVolume"))...)
1962 }
1963 if pvSpec.AzureFile != nil {
1964 if numVolumes > 0 {
1965 allErrs = append(allErrs, field.Forbidden(fldPath.Child("azureFile"), "may not specify more than 1 volume type"))
1966
1967 } else {
1968 numVolumes++
1969 allErrs = append(allErrs, validateAzureFilePV(pvSpec.AzureFile, fldPath.Child("azureFile"))...)
1970 }
1971 }
1972
1973 if pvSpec.VsphereVolume != nil {
1974 if numVolumes > 0 {
1975 allErrs = append(allErrs, field.Forbidden(fldPath.Child("vsphereVolume"), "may not specify more than 1 volume type"))
1976 } else {
1977 numVolumes++
1978 allErrs = append(allErrs, validateVsphereVolumeSource(pvSpec.VsphereVolume, fldPath.Child("vsphereVolume"))...)
1979 }
1980 }
1981 if pvSpec.PhotonPersistentDisk != nil {
1982 if numVolumes > 0 {
1983 allErrs = append(allErrs, field.Forbidden(fldPath.Child("photonPersistentDisk"), "may not specify more than 1 volume type"))
1984 } else {
1985 numVolumes++
1986 allErrs = append(allErrs, validatePhotonPersistentDiskVolumeSource(pvSpec.PhotonPersistentDisk, fldPath.Child("photonPersistentDisk"))...)
1987 }
1988 }
1989 if pvSpec.PortworxVolume != nil {
1990 if numVolumes > 0 {
1991 allErrs = append(allErrs, field.Forbidden(fldPath.Child("portworxVolume"), "may not specify more than 1 volume type"))
1992 } else {
1993 numVolumes++
1994 allErrs = append(allErrs, validatePortworxVolumeSource(pvSpec.PortworxVolume, fldPath.Child("portworxVolume"))...)
1995 }
1996 }
1997 if pvSpec.AzureDisk != nil {
1998 if numVolumes > 0 {
1999 allErrs = append(allErrs, field.Forbidden(fldPath.Child("azureDisk"), "may not specify more than 1 volume type"))
2000 } else {
2001 numVolumes++
2002 allErrs = append(allErrs, validateAzureDisk(pvSpec.AzureDisk, fldPath.Child("azureDisk"))...)
2003 }
2004 }
2005 if pvSpec.ScaleIO != nil {
2006 if numVolumes > 0 {
2007 allErrs = append(allErrs, field.Forbidden(fldPath.Child("scaleIO"), "may not specify more than 1 volume type"))
2008 } else {
2009 numVolumes++
2010 allErrs = append(allErrs, validateScaleIOPersistentVolumeSource(pvSpec.ScaleIO, fldPath.Child("scaleIO"))...)
2011 }
2012 }
2013 if pvSpec.Local != nil {
2014 if numVolumes > 0 {
2015 allErrs = append(allErrs, field.Forbidden(fldPath.Child("local"), "may not specify more than 1 volume type"))
2016 } else {
2017 numVolumes++
2018 allErrs = append(allErrs, validateLocalVolumeSource(pvSpec.Local, fldPath.Child("local"))...)
2019
2020 if !nodeAffinitySpecified {
2021 allErrs = append(allErrs, field.Required(fldPath.Child("nodeAffinity"), "Local volume requires node affinity"))
2022 }
2023 }
2024 }
2025 if pvSpec.StorageOS != nil {
2026 if numVolumes > 0 {
2027 allErrs = append(allErrs, field.Forbidden(fldPath.Child("storageos"), "may not specify more than 1 volume type"))
2028 } else {
2029 numVolumes++
2030 allErrs = append(allErrs, validateStorageOSPersistentVolumeSource(pvSpec.StorageOS, fldPath.Child("storageos"))...)
2031 }
2032 }
2033
2034 if pvSpec.CSI != nil {
2035 if numVolumes > 0 {
2036 allErrs = append(allErrs, field.Forbidden(fldPath.Child("csi"), "may not specify more than 1 volume type"))
2037 } else {
2038 numVolumes++
2039 allErrs = append(allErrs, validateCSIPersistentVolumeSource(pvSpec.CSI, fldPath.Child("csi"))...)
2040 }
2041 }
2042
2043 if numVolumes == 0 {
2044 allErrs = append(allErrs, field.Required(fldPath, "must specify a volume type"))
2045 }
2046
2047
2048 if pvSpec.HostPath != nil && path.Clean(pvSpec.HostPath.Path) == "/" && pvSpec.PersistentVolumeReclaimPolicy == core.PersistentVolumeReclaimRecycle {
2049 allErrs = append(allErrs, field.Forbidden(fldPath.Child("persistentVolumeReclaimPolicy"), "may not be 'recycle' for a hostPath mount of '/'"))
2050 }
2051
2052 if len(pvSpec.StorageClassName) > 0 {
2053 if validateInlinePersistentVolumeSpec {
2054 allErrs = append(allErrs, field.Forbidden(fldPath.Child("storageClassName"), "may not be specified in the context of inline volumes"))
2055 } else {
2056 for _, msg := range ValidateClassName(pvSpec.StorageClassName, false) {
2057 allErrs = append(allErrs, field.Invalid(fldPath.Child("storageClassName"), pvSpec.StorageClassName, msg))
2058 }
2059 }
2060 }
2061 if pvSpec.VolumeMode != nil {
2062 if validateInlinePersistentVolumeSpec {
2063 if *pvSpec.VolumeMode != core.PersistentVolumeFilesystem {
2064 allErrs = append(allErrs, field.Forbidden(fldPath.Child("volumeMode"), "may not specify volumeMode other than "+string(core.PersistentVolumeFilesystem)+" in the context of inline volumes"))
2065 }
2066 } else {
2067 if !supportedVolumeModes.Has(*pvSpec.VolumeMode) {
2068 allErrs = append(allErrs, field.NotSupported(fldPath.Child("volumeMode"), *pvSpec.VolumeMode, sets.List(supportedVolumeModes)))
2069 }
2070 }
2071 }
2072 if pvSpec.VolumeAttributesClassName != nil && opts.EnableVolumeAttributesClass {
2073 if len(*pvSpec.VolumeAttributesClassName) == 0 {
2074 allErrs = append(allErrs, field.Required(fldPath.Child("volumeAttributesClassName"), "an empty string is disallowed"))
2075 } else {
2076 for _, msg := range ValidateClassName(*pvSpec.VolumeAttributesClassName, false) {
2077 allErrs = append(allErrs, field.Invalid(fldPath.Child("volumeAttributesClassName"), *pvSpec.VolumeAttributesClassName, msg))
2078 }
2079 }
2080 if pvSpec.CSI == nil {
2081 allErrs = append(allErrs, field.Required(fldPath.Child("csi"), "has to be specified when using volumeAttributesClassName"))
2082 }
2083 }
2084 return allErrs
2085 }
2086
2087 func ValidatePersistentVolume(pv *core.PersistentVolume, opts PersistentVolumeSpecValidationOptions) field.ErrorList {
2088 metaPath := field.NewPath("metadata")
2089 allErrs := ValidateObjectMeta(&pv.ObjectMeta, false, ValidatePersistentVolumeName, metaPath)
2090 allErrs = append(allErrs, ValidatePersistentVolumeSpec(&pv.Spec, pv.ObjectMeta.Name, false, field.NewPath("spec"), opts)...)
2091 return allErrs
2092 }
2093
2094
2095
2096 func ValidatePersistentVolumeUpdate(newPv, oldPv *core.PersistentVolume, opts PersistentVolumeSpecValidationOptions) field.ErrorList {
2097 allErrs := ValidatePersistentVolume(newPv, opts)
2098
2099
2100 if (oldPv.Spec.CSI != nil && oldPv.Spec.CSI.ControllerExpandSecretRef == nil) &&
2101 (newPv.Spec.CSI != nil && newPv.Spec.CSI.ControllerExpandSecretRef != nil) {
2102 newPv = newPv.DeepCopy()
2103 newPv.Spec.CSI.ControllerExpandSecretRef = nil
2104 }
2105
2106
2107 if !apiequality.Semantic.DeepEqual(newPv.Spec.PersistentVolumeSource, oldPv.Spec.PersistentVolumeSource) {
2108 pvcSourceDiff := cmp.Diff(oldPv.Spec.PersistentVolumeSource, newPv.Spec.PersistentVolumeSource)
2109 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "persistentvolumesource"), fmt.Sprintf("spec.persistentvolumesource is immutable after creation\n%v", pvcSourceDiff)))
2110 }
2111 allErrs = append(allErrs, ValidateImmutableField(newPv.Spec.VolumeMode, oldPv.Spec.VolumeMode, field.NewPath("volumeMode"))...)
2112
2113
2114 if oldPv.Spec.NodeAffinity != nil {
2115 allErrs = append(allErrs, validatePvNodeAffinity(newPv.Spec.NodeAffinity, oldPv.Spec.NodeAffinity, field.NewPath("nodeAffinity"))...)
2116 }
2117
2118 if !apiequality.Semantic.DeepEqual(oldPv.Spec.VolumeAttributesClassName, newPv.Spec.VolumeAttributesClassName) {
2119 if !utilfeature.DefaultFeatureGate.Enabled(features.VolumeAttributesClass) {
2120 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "volumeAttributesClassName"), "update is forbidden when the VolumeAttributesClass feature gate is disabled"))
2121 }
2122 if opts.EnableVolumeAttributesClass {
2123 if oldPv.Spec.VolumeAttributesClassName != nil && newPv.Spec.VolumeAttributesClassName == nil {
2124 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "volumeAttributesClassName"), "update from non-nil value to nil is forbidden"))
2125 }
2126 }
2127 }
2128
2129 return allErrs
2130 }
2131
2132
2133 func ValidatePersistentVolumeStatusUpdate(newPv, oldPv *core.PersistentVolume) field.ErrorList {
2134 allErrs := ValidateObjectMetaUpdate(&newPv.ObjectMeta, &oldPv.ObjectMeta, field.NewPath("metadata"))
2135 if len(newPv.ResourceVersion) == 0 {
2136 allErrs = append(allErrs, field.Required(field.NewPath("resourceVersion"), ""))
2137 }
2138 return allErrs
2139 }
2140
2141 type PersistentVolumeClaimSpecValidationOptions struct {
2142
2143 EnableRecoverFromExpansionFailure bool
2144
2145 AllowInvalidLabelValueInSelector bool
2146
2147 AllowInvalidAPIGroupInDataSourceOrRef bool
2148
2149 EnableVolumeAttributesClass bool
2150 }
2151
2152 func ValidationOptionsForPersistentVolumeClaim(pvc, oldPvc *core.PersistentVolumeClaim) PersistentVolumeClaimSpecValidationOptions {
2153 opts := PersistentVolumeClaimSpecValidationOptions{
2154 EnableRecoverFromExpansionFailure: utilfeature.DefaultFeatureGate.Enabled(features.RecoverVolumeExpansionFailure),
2155 AllowInvalidLabelValueInSelector: false,
2156 EnableVolumeAttributesClass: utilfeature.DefaultFeatureGate.Enabled(features.VolumeAttributesClass),
2157 }
2158 if oldPvc == nil {
2159
2160 return opts
2161 }
2162
2163
2164 opts.AllowInvalidAPIGroupInDataSourceOrRef = allowInvalidAPIGroupInDataSourceOrRef(&oldPvc.Spec)
2165
2166 if oldPvc.Spec.VolumeAttributesClassName != nil {
2167
2168 opts.EnableVolumeAttributesClass = true
2169 }
2170
2171 labelSelectorValidationOpts := unversionedvalidation.LabelSelectorValidationOptions{
2172 AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector,
2173 }
2174 if len(unversionedvalidation.ValidateLabelSelector(oldPvc.Spec.Selector, labelSelectorValidationOpts, nil)) > 0 {
2175
2176 opts.AllowInvalidLabelValueInSelector = true
2177 }
2178
2179 if helper.ClaimContainsAllocatedResources(oldPvc) ||
2180 helper.ClaimContainsAllocatedResourceStatus(oldPvc) {
2181 opts.EnableRecoverFromExpansionFailure = true
2182 }
2183 return opts
2184 }
2185
2186 func ValidationOptionsForPersistentVolumeClaimTemplate(claimTemplate, oldClaimTemplate *core.PersistentVolumeClaimTemplate) PersistentVolumeClaimSpecValidationOptions {
2187 opts := PersistentVolumeClaimSpecValidationOptions{
2188 AllowInvalidLabelValueInSelector: false,
2189 EnableVolumeAttributesClass: utilfeature.DefaultFeatureGate.Enabled(features.VolumeAttributesClass),
2190 }
2191 if oldClaimTemplate == nil {
2192
2193 return opts
2194 }
2195 labelSelectorValidationOpts := unversionedvalidation.LabelSelectorValidationOptions{
2196 AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector,
2197 }
2198 if len(unversionedvalidation.ValidateLabelSelector(oldClaimTemplate.Spec.Selector, labelSelectorValidationOpts, nil)) > 0 {
2199
2200 opts.AllowInvalidLabelValueInSelector = true
2201 }
2202 return opts
2203 }
2204
2205
2206 func allowInvalidAPIGroupInDataSourceOrRef(spec *core.PersistentVolumeClaimSpec) bool {
2207 if spec.DataSource != nil && spec.DataSource.APIGroup != nil {
2208 return true
2209 }
2210 if spec.DataSourceRef != nil && spec.DataSourceRef.APIGroup != nil {
2211 return true
2212 }
2213 return false
2214 }
2215
2216
2217 func ValidatePersistentVolumeClaim(pvc *core.PersistentVolumeClaim, opts PersistentVolumeClaimSpecValidationOptions) field.ErrorList {
2218 allErrs := ValidateObjectMeta(&pvc.ObjectMeta, true, ValidatePersistentVolumeName, field.NewPath("metadata"))
2219 allErrs = append(allErrs, ValidatePersistentVolumeClaimSpec(&pvc.Spec, field.NewPath("spec"), opts)...)
2220 return allErrs
2221 }
2222
2223
2224 func validateDataSource(dataSource *core.TypedLocalObjectReference, fldPath *field.Path, allowInvalidAPIGroupInDataSourceOrRef bool) field.ErrorList {
2225 allErrs := field.ErrorList{}
2226
2227 if len(dataSource.Name) == 0 {
2228 allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
2229 }
2230 if len(dataSource.Kind) == 0 {
2231 allErrs = append(allErrs, field.Required(fldPath.Child("kind"), ""))
2232 }
2233 apiGroup := ""
2234 if dataSource.APIGroup != nil {
2235 apiGroup = *dataSource.APIGroup
2236 }
2237 if len(apiGroup) == 0 && dataSource.Kind != "PersistentVolumeClaim" {
2238 allErrs = append(allErrs, field.Invalid(fldPath, dataSource.Kind, "must be 'PersistentVolumeClaim' when referencing the default apiGroup"))
2239 }
2240 if len(apiGroup) > 0 && !allowInvalidAPIGroupInDataSourceOrRef {
2241 for _, errString := range validation.IsDNS1123Subdomain(apiGroup) {
2242 allErrs = append(allErrs, field.Invalid(fldPath.Child("apiGroup"), apiGroup, errString))
2243 }
2244 }
2245
2246 return allErrs
2247 }
2248
2249
2250 func validateDataSourceRef(dataSourceRef *core.TypedObjectReference, fldPath *field.Path, allowInvalidAPIGroupInDataSourceOrRef bool) field.ErrorList {
2251 allErrs := field.ErrorList{}
2252
2253 if len(dataSourceRef.Name) == 0 {
2254 allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
2255 }
2256 if len(dataSourceRef.Kind) == 0 {
2257 allErrs = append(allErrs, field.Required(fldPath.Child("kind"), ""))
2258 }
2259 apiGroup := ""
2260 if dataSourceRef.APIGroup != nil {
2261 apiGroup = *dataSourceRef.APIGroup
2262 }
2263 if len(apiGroup) == 0 && dataSourceRef.Kind != "PersistentVolumeClaim" {
2264 allErrs = append(allErrs, field.Invalid(fldPath, dataSourceRef.Kind, "must be 'PersistentVolumeClaim' when referencing the default apiGroup"))
2265 }
2266 if len(apiGroup) > 0 && !allowInvalidAPIGroupInDataSourceOrRef {
2267 for _, errString := range validation.IsDNS1123Subdomain(apiGroup) {
2268 allErrs = append(allErrs, field.Invalid(fldPath.Child("apiGroup"), apiGroup, errString))
2269 }
2270 }
2271
2272 if dataSourceRef.Namespace != nil && len(*dataSourceRef.Namespace) > 0 {
2273 for _, msg := range ValidateNameFunc(ValidateNamespaceName)(*dataSourceRef.Namespace, false) {
2274 allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), *dataSourceRef.Namespace, msg))
2275 }
2276 }
2277
2278 return allErrs
2279 }
2280
2281
2282 func ValidatePersistentVolumeClaimSpec(spec *core.PersistentVolumeClaimSpec, fldPath *field.Path, opts PersistentVolumeClaimSpecValidationOptions) field.ErrorList {
2283 allErrs := field.ErrorList{}
2284 if len(spec.AccessModes) == 0 {
2285 allErrs = append(allErrs, field.Required(fldPath.Child("accessModes"), "at least 1 access mode is required"))
2286 }
2287 if spec.Selector != nil {
2288 labelSelectorValidationOpts := unversionedvalidation.LabelSelectorValidationOptions{
2289 AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector,
2290 }
2291 allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, labelSelectorValidationOpts, fldPath.Child("selector"))...)
2292 }
2293
2294 foundReadWriteOncePod, foundNonReadWriteOncePod := false, false
2295 for _, mode := range spec.AccessModes {
2296 if !supportedAccessModes.Has(mode) {
2297 allErrs = append(allErrs, field.NotSupported(fldPath.Child("accessModes"), mode, sets.List(supportedAccessModes)))
2298 }
2299
2300 if mode == core.ReadWriteOncePod {
2301 foundReadWriteOncePod = true
2302 } else if supportedAccessModes.Has(mode) {
2303 foundNonReadWriteOncePod = true
2304 }
2305 }
2306 if foundReadWriteOncePod && foundNonReadWriteOncePod {
2307 allErrs = append(allErrs, field.Forbidden(fldPath.Child("accessModes"), "may not use ReadWriteOncePod with other access modes"))
2308 }
2309
2310 storageValue, ok := spec.Resources.Requests[core.ResourceStorage]
2311 if !ok {
2312 allErrs = append(allErrs, field.Required(fldPath.Child("resources").Key(string(core.ResourceStorage)), ""))
2313 } else if errs := ValidatePositiveQuantityValue(storageValue, fldPath.Child("resources").Key(string(core.ResourceStorage))); len(errs) > 0 {
2314 allErrs = append(allErrs, errs...)
2315 } else {
2316 allErrs = append(allErrs, ValidateResourceQuantityValue(core.ResourceStorage, storageValue, fldPath.Child("resources").Key(string(core.ResourceStorage)))...)
2317 }
2318
2319 if spec.StorageClassName != nil && len(*spec.StorageClassName) > 0 {
2320 for _, msg := range ValidateClassName(*spec.StorageClassName, false) {
2321 allErrs = append(allErrs, field.Invalid(fldPath.Child("storageClassName"), *spec.StorageClassName, msg))
2322 }
2323 }
2324 if spec.VolumeMode != nil && !supportedVolumeModes.Has(*spec.VolumeMode) {
2325 allErrs = append(allErrs, field.NotSupported(fldPath.Child("volumeMode"), *spec.VolumeMode, sets.List(supportedVolumeModes)))
2326 }
2327
2328 if spec.DataSource != nil {
2329 allErrs = append(allErrs, validateDataSource(spec.DataSource, fldPath.Child("dataSource"), opts.AllowInvalidAPIGroupInDataSourceOrRef)...)
2330 }
2331 if spec.DataSourceRef != nil {
2332 allErrs = append(allErrs, validateDataSourceRef(spec.DataSourceRef, fldPath.Child("dataSourceRef"), opts.AllowInvalidAPIGroupInDataSourceOrRef)...)
2333 }
2334 if spec.DataSourceRef != nil && spec.DataSourceRef.Namespace != nil && len(*spec.DataSourceRef.Namespace) > 0 {
2335 if spec.DataSource != nil {
2336 allErrs = append(allErrs, field.Invalid(fldPath, fldPath.Child("dataSource"),
2337 "may not be specified when dataSourceRef.namespace is specified"))
2338 }
2339 } else if spec.DataSource != nil && spec.DataSourceRef != nil {
2340 if !isDataSourceEqualDataSourceRef(spec.DataSource, spec.DataSourceRef) {
2341 allErrs = append(allErrs, field.Invalid(fldPath, fldPath.Child("dataSource"),
2342 "must match dataSourceRef"))
2343 }
2344 }
2345 if spec.VolumeAttributesClassName != nil && len(*spec.VolumeAttributesClassName) > 0 && opts.EnableVolumeAttributesClass {
2346 for _, msg := range ValidateClassName(*spec.VolumeAttributesClassName, false) {
2347 allErrs = append(allErrs, field.Invalid(fldPath.Child("volumeAttributesClassName"), *spec.VolumeAttributesClassName, msg))
2348 }
2349 }
2350
2351 return allErrs
2352 }
2353
2354 func isDataSourceEqualDataSourceRef(dataSource *core.TypedLocalObjectReference, dataSourceRef *core.TypedObjectReference) bool {
2355 return reflect.DeepEqual(dataSource.APIGroup, dataSourceRef.APIGroup) && dataSource.Kind == dataSourceRef.Kind && dataSource.Name == dataSourceRef.Name
2356 }
2357
2358
2359 func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *core.PersistentVolumeClaim, opts PersistentVolumeClaimSpecValidationOptions) field.ErrorList {
2360 allErrs := ValidateObjectMetaUpdate(&newPvc.ObjectMeta, &oldPvc.ObjectMeta, field.NewPath("metadata"))
2361 allErrs = append(allErrs, ValidatePersistentVolumeClaim(newPvc, opts)...)
2362 newPvcClone := newPvc.DeepCopy()
2363 oldPvcClone := oldPvc.DeepCopy()
2364
2365
2366
2367 if len(oldPvc.Spec.VolumeName) == 0 {
2368
2369 oldPvcClone.Spec.VolumeName = newPvcClone.Spec.VolumeName
2370 }
2371
2372 if validateStorageClassUpgradeFromAnnotation(oldPvcClone.Annotations, newPvcClone.Annotations,
2373 oldPvcClone.Spec.StorageClassName, newPvcClone.Spec.StorageClassName) {
2374 newPvcClone.Spec.StorageClassName = nil
2375 metav1.SetMetaDataAnnotation(&newPvcClone.ObjectMeta, core.BetaStorageClassAnnotation, oldPvcClone.Annotations[core.BetaStorageClassAnnotation])
2376 } else {
2377
2378
2379 allErrs = append(allErrs, ValidateImmutableAnnotation(newPvc.ObjectMeta.Annotations[v1.BetaStorageClassAnnotation], oldPvc.ObjectMeta.Annotations[v1.BetaStorageClassAnnotation], v1.BetaStorageClassAnnotation, field.NewPath("metadata"))...)
2380
2381
2382 if validateStorageClassUpgradeFromNil(oldPvc.Annotations, oldPvc.Spec.StorageClassName, newPvc.Spec.StorageClassName, opts) {
2383 newPvcClone.Spec.StorageClassName = oldPvcClone.Spec.StorageClassName
2384 }
2385
2386
2387 }
2388
2389
2390 if newPvc.Status.Phase == core.ClaimBound && newPvcClone.Spec.Resources.Requests != nil {
2391 newPvcClone.Spec.Resources.Requests["storage"] = oldPvc.Spec.Resources.Requests["storage"]
2392 }
2393
2394 newPvcClone.Spec.VolumeAttributesClassName = oldPvcClone.Spec.VolumeAttributesClassName
2395
2396 oldSize := oldPvc.Spec.Resources.Requests["storage"]
2397 newSize := newPvc.Spec.Resources.Requests["storage"]
2398 statusSize := oldPvc.Status.Capacity["storage"]
2399
2400 if !apiequality.Semantic.DeepEqual(newPvcClone.Spec, oldPvcClone.Spec) {
2401 specDiff := cmp.Diff(oldPvcClone.Spec, newPvcClone.Spec)
2402 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), fmt.Sprintf("spec is immutable after creation except resources.requests and volumeAttributesClassName for bound claims\n%v", specDiff)))
2403 }
2404 if newSize.Cmp(oldSize) < 0 {
2405 if !opts.EnableRecoverFromExpansionFailure {
2406 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "resources", "requests", "storage"), "field can not be less than previous value"))
2407 } else {
2408
2409
2410
2411 if newSize.Cmp(statusSize) <= 0 {
2412 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "resources", "requests", "storage"), "field can not be less than status.capacity"))
2413 }
2414 }
2415 }
2416
2417 allErrs = append(allErrs, ValidateImmutableField(newPvc.Spec.VolumeMode, oldPvc.Spec.VolumeMode, field.NewPath("volumeMode"))...)
2418
2419 if !apiequality.Semantic.DeepEqual(oldPvc.Spec.VolumeAttributesClassName, newPvc.Spec.VolumeAttributesClassName) {
2420 if !utilfeature.DefaultFeatureGate.Enabled(features.VolumeAttributesClass) {
2421 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "volumeAttributesClassName"), "update is forbidden when the VolumeAttributesClass feature gate is disabled"))
2422 }
2423 if opts.EnableVolumeAttributesClass {
2424 if oldPvc.Spec.VolumeAttributesClassName != nil {
2425 if newPvc.Spec.VolumeAttributesClassName == nil {
2426 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "volumeAttributesClassName"), "update from non-nil value to nil is forbidden"))
2427 } else if len(*newPvc.Spec.VolumeAttributesClassName) == 0 {
2428 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "volumeAttributesClassName"), "update from non-nil value to an empty string is forbidden"))
2429 }
2430 }
2431 }
2432 }
2433
2434 return allErrs
2435 }
2436
2437
2438
2439
2440
2441
2442
2443
2444 func validateStorageClassUpgradeFromAnnotation(oldAnnotations, newAnnotations map[string]string, oldScName, newScName *string) bool {
2445 oldSc, oldAnnotationExist := oldAnnotations[core.BetaStorageClassAnnotation]
2446 newScInAnnotation, newAnnotationExist := newAnnotations[core.BetaStorageClassAnnotation]
2447 return oldAnnotationExist &&
2448 oldScName == nil &&
2449 (newScName != nil && *newScName == oldSc) &&
2450 (!newAnnotationExist || newScInAnnotation == oldSc)
2451 }
2452
2453
2454
2455
2456
2457
2458 func validateStorageClassUpgradeFromNil(oldAnnotations map[string]string, oldScName, newScName *string, opts PersistentVolumeClaimSpecValidationOptions) bool {
2459 oldAnnotation, oldAnnotationExist := oldAnnotations[core.BetaStorageClassAnnotation]
2460 return newScName != nil &&
2461 oldScName == nil &&
2462 (!oldAnnotationExist || *newScName == oldAnnotation)
2463 }
2464
2465 func validatePersistentVolumeClaimResourceKey(value string, fldPath *field.Path) field.ErrorList {
2466 allErrs := field.ErrorList{}
2467 for _, msg := range validation.IsQualifiedName(value) {
2468 allErrs = append(allErrs, field.Invalid(fldPath, value, msg))
2469 }
2470 if len(allErrs) != 0 {
2471 return allErrs
2472 }
2473
2474
2475 if helper.IsNativeResource(core.ResourceName(value)) {
2476 if core.ResourceName(value) != core.ResourceStorage {
2477 return append(allErrs, field.NotSupported(fldPath, value, []core.ResourceName{core.ResourceStorage}))
2478 }
2479 }
2480 return allErrs
2481 }
2482
2483 var resizeStatusSet = sets.New(core.PersistentVolumeClaimControllerResizeInProgress,
2484 core.PersistentVolumeClaimControllerResizeFailed,
2485 core.PersistentVolumeClaimNodeResizePending,
2486 core.PersistentVolumeClaimNodeResizeInProgress,
2487 core.PersistentVolumeClaimNodeResizeFailed)
2488
2489
2490 func ValidatePersistentVolumeClaimStatusUpdate(newPvc, oldPvc *core.PersistentVolumeClaim, validationOpts PersistentVolumeClaimSpecValidationOptions) field.ErrorList {
2491 allErrs := ValidateObjectMetaUpdate(&newPvc.ObjectMeta, &oldPvc.ObjectMeta, field.NewPath("metadata"))
2492 if len(newPvc.ResourceVersion) == 0 {
2493 allErrs = append(allErrs, field.Required(field.NewPath("resourceVersion"), ""))
2494 }
2495 if len(newPvc.Spec.AccessModes) == 0 {
2496 allErrs = append(allErrs, field.Required(field.NewPath("Spec", "accessModes"), ""))
2497 }
2498
2499 capPath := field.NewPath("status", "capacity")
2500 for r, qty := range newPvc.Status.Capacity {
2501 allErrs = append(allErrs, validateBasicResource(qty, capPath.Key(string(r)))...)
2502 }
2503 if validationOpts.EnableRecoverFromExpansionFailure {
2504 resizeStatusPath := field.NewPath("status", "allocatedResourceStatus")
2505 if newPvc.Status.AllocatedResourceStatuses != nil {
2506 resizeStatus := newPvc.Status.AllocatedResourceStatuses
2507 for k, v := range resizeStatus {
2508 if errs := validatePersistentVolumeClaimResourceKey(k.String(), resizeStatusPath); len(errs) > 0 {
2509 allErrs = append(allErrs, errs...)
2510 }
2511 if !resizeStatusSet.Has(v) {
2512 allErrs = append(allErrs, field.NotSupported(resizeStatusPath, k, sets.List(resizeStatusSet)))
2513 continue
2514 }
2515 }
2516 }
2517 allocPath := field.NewPath("status", "allocatedResources")
2518 for r, qty := range newPvc.Status.AllocatedResources {
2519 if errs := validatePersistentVolumeClaimResourceKey(r.String(), allocPath); len(errs) > 0 {
2520 allErrs = append(allErrs, errs...)
2521 continue
2522 }
2523
2524 if errs := validateBasicResource(qty, allocPath.Key(string(r))); len(errs) > 0 {
2525 allErrs = append(allErrs, errs...)
2526 } else {
2527 allErrs = append(allErrs, ValidateResourceQuantityValue(core.ResourceStorage, qty, allocPath.Key(string(r)))...)
2528 }
2529 }
2530 }
2531 return allErrs
2532 }
2533
2534 var supportedPortProtocols = sets.New(
2535 core.ProtocolTCP,
2536 core.ProtocolUDP,
2537 core.ProtocolSCTP)
2538
2539 func validateContainerPorts(ports []core.ContainerPort, fldPath *field.Path) field.ErrorList {
2540 allErrs := field.ErrorList{}
2541
2542 allNames := sets.Set[string]{}
2543 for i, port := range ports {
2544 idxPath := fldPath.Index(i)
2545 if len(port.Name) > 0 {
2546 if msgs := validation.IsValidPortName(port.Name); len(msgs) != 0 {
2547 for i = range msgs {
2548 allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), port.Name, msgs[i]))
2549 }
2550 } else if allNames.Has(port.Name) {
2551 allErrs = append(allErrs, field.Duplicate(idxPath.Child("name"), port.Name))
2552 } else {
2553 allNames.Insert(port.Name)
2554 }
2555 }
2556 if port.ContainerPort == 0 {
2557 allErrs = append(allErrs, field.Required(idxPath.Child("containerPort"), ""))
2558 } else {
2559 for _, msg := range validation.IsValidPortNum(int(port.ContainerPort)) {
2560 allErrs = append(allErrs, field.Invalid(idxPath.Child("containerPort"), port.ContainerPort, msg))
2561 }
2562 }
2563 if port.HostPort != 0 {
2564 for _, msg := range validation.IsValidPortNum(int(port.HostPort)) {
2565 allErrs = append(allErrs, field.Invalid(idxPath.Child("hostPort"), port.HostPort, msg))
2566 }
2567 }
2568 if len(port.Protocol) == 0 {
2569 allErrs = append(allErrs, field.Required(idxPath.Child("protocol"), ""))
2570 } else if !supportedPortProtocols.Has(port.Protocol) {
2571 allErrs = append(allErrs, field.NotSupported(idxPath.Child("protocol"), port.Protocol, sets.List(supportedPortProtocols)))
2572 }
2573 }
2574 return allErrs
2575 }
2576
2577
2578 func ValidateEnv(vars []core.EnvVar, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
2579 allErrs := field.ErrorList{}
2580
2581 for i, ev := range vars {
2582 idxPath := fldPath.Index(i)
2583 if len(ev.Name) == 0 {
2584 allErrs = append(allErrs, field.Required(idxPath.Child("name"), ""))
2585 } else {
2586 if opts.AllowRelaxedEnvironmentVariableValidation {
2587 for _, msg := range validation.IsRelaxedEnvVarName(ev.Name) {
2588 allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), ev.Name, msg))
2589 }
2590 } else {
2591 for _, msg := range validation.IsEnvVarName(ev.Name) {
2592 allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), ev.Name, msg))
2593 }
2594 }
2595 }
2596 allErrs = append(allErrs, validateEnvVarValueFrom(ev, idxPath.Child("valueFrom"), opts)...)
2597 }
2598 return allErrs
2599 }
2600
2601 var validEnvDownwardAPIFieldPathExpressions = sets.New(
2602 "metadata.name",
2603 "metadata.namespace",
2604 "metadata.uid",
2605 "spec.nodeName",
2606 "spec.serviceAccountName",
2607 "status.hostIP",
2608 "status.hostIPs",
2609 "status.podIP",
2610 "status.podIPs",
2611 )
2612
2613 var validContainerResourceFieldPathExpressions = sets.New(
2614 "limits.cpu",
2615 "limits.memory",
2616 "limits.ephemeral-storage",
2617 "requests.cpu",
2618 "requests.memory",
2619 "requests.ephemeral-storage",
2620 )
2621
2622 var validContainerResourceFieldPathPrefixesWithDownwardAPIHugePages = sets.New(hugepagesRequestsPrefixDownwardAPI, hugepagesLimitsPrefixDownwardAPI)
2623
2624 const hugepagesRequestsPrefixDownwardAPI string = `requests.hugepages-`
2625 const hugepagesLimitsPrefixDownwardAPI string = `limits.hugepages-`
2626
2627 func validateEnvVarValueFrom(ev core.EnvVar, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
2628 allErrs := field.ErrorList{}
2629
2630 if ev.ValueFrom == nil {
2631 return allErrs
2632 }
2633
2634 numSources := 0
2635
2636 if ev.ValueFrom.FieldRef != nil {
2637 numSources++
2638 allErrs = append(allErrs, validateObjectFieldSelector(ev.ValueFrom.FieldRef, &validEnvDownwardAPIFieldPathExpressions, fldPath.Child("fieldRef"))...)
2639 allErrs = append(allErrs, validateDownwardAPIHostIPs(ev.ValueFrom.FieldRef, fldPath.Child("fieldRef"), opts)...)
2640 }
2641 if ev.ValueFrom.ResourceFieldRef != nil {
2642 numSources++
2643 localValidContainerResourceFieldPathPrefixes := validContainerResourceFieldPathPrefixesWithDownwardAPIHugePages
2644 allErrs = append(allErrs, validateContainerResourceFieldSelector(ev.ValueFrom.ResourceFieldRef, &validContainerResourceFieldPathExpressions, &localValidContainerResourceFieldPathPrefixes, fldPath.Child("resourceFieldRef"), false)...)
2645 }
2646 if ev.ValueFrom.ConfigMapKeyRef != nil {
2647 numSources++
2648 allErrs = append(allErrs, validateConfigMapKeySelector(ev.ValueFrom.ConfigMapKeyRef, fldPath.Child("configMapKeyRef"))...)
2649 }
2650 if ev.ValueFrom.SecretKeyRef != nil {
2651 numSources++
2652 allErrs = append(allErrs, validateSecretKeySelector(ev.ValueFrom.SecretKeyRef, fldPath.Child("secretKeyRef"))...)
2653 }
2654
2655 if numSources == 0 {
2656 allErrs = append(allErrs, field.Invalid(fldPath, "", "must specify one of: `fieldRef`, `resourceFieldRef`, `configMapKeyRef` or `secretKeyRef`"))
2657 } else if len(ev.Value) != 0 {
2658 if numSources != 0 {
2659 allErrs = append(allErrs, field.Invalid(fldPath, "", "may not be specified when `value` is not empty"))
2660 }
2661 } else if numSources > 1 {
2662 allErrs = append(allErrs, field.Invalid(fldPath, "", "may not have more than one field specified at a time"))
2663 }
2664
2665 return allErrs
2666 }
2667
2668 func validateObjectFieldSelector(fs *core.ObjectFieldSelector, expressions *sets.Set[string], fldPath *field.Path) field.ErrorList {
2669 allErrs := field.ErrorList{}
2670
2671 if len(fs.APIVersion) == 0 {
2672 allErrs = append(allErrs, field.Required(fldPath.Child("apiVersion"), ""))
2673 return allErrs
2674 }
2675 if len(fs.FieldPath) == 0 {
2676 allErrs = append(allErrs, field.Required(fldPath.Child("fieldPath"), ""))
2677 return allErrs
2678 }
2679
2680 internalFieldPath, _, err := podshelper.ConvertDownwardAPIFieldLabel(fs.APIVersion, fs.FieldPath, "")
2681 if err != nil {
2682 allErrs = append(allErrs, field.Invalid(fldPath.Child("fieldPath"), fs.FieldPath, fmt.Sprintf("error converting fieldPath: %v", err)))
2683 return allErrs
2684 }
2685
2686 if path, subscript, ok := fieldpath.SplitMaybeSubscriptedPath(internalFieldPath); ok {
2687 switch path {
2688 case "metadata.annotations":
2689 allErrs = append(allErrs, ValidateQualifiedName(strings.ToLower(subscript), fldPath)...)
2690 case "metadata.labels":
2691 allErrs = append(allErrs, ValidateQualifiedName(subscript, fldPath)...)
2692 default:
2693 allErrs = append(allErrs, field.Invalid(fldPath, path, "does not support subscript"))
2694 }
2695 } else if !expressions.Has(path) {
2696 allErrs = append(allErrs, field.NotSupported(fldPath.Child("fieldPath"), path, sets.List(*expressions)))
2697 return allErrs
2698 }
2699
2700 return allErrs
2701 }
2702
2703 func validateDownwardAPIHostIPs(fieldSel *core.ObjectFieldSelector, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
2704 allErrs := field.ErrorList{}
2705 if !opts.AllowHostIPsField {
2706 if fieldSel.FieldPath == "status.hostIPs" {
2707 allErrs = append(allErrs, field.Forbidden(fldPath, "may not be set when feature gate 'PodHostIPs' is not enabled"))
2708 }
2709 }
2710 return allErrs
2711 }
2712
2713 func validateContainerResourceFieldSelector(fs *core.ResourceFieldSelector, expressions *sets.Set[string], prefixes *sets.Set[string], fldPath *field.Path, volume bool) field.ErrorList {
2714 allErrs := field.ErrorList{}
2715
2716 if volume && len(fs.ContainerName) == 0 {
2717 allErrs = append(allErrs, field.Required(fldPath.Child("containerName"), ""))
2718 } else if len(fs.Resource) == 0 {
2719 allErrs = append(allErrs, field.Required(fldPath.Child("resource"), ""))
2720 } else if !expressions.Has(fs.Resource) {
2721
2722 foundPrefix := false
2723 if prefixes != nil {
2724 for _, prefix := range sets.List(*prefixes) {
2725 if strings.HasPrefix(fs.Resource, prefix) {
2726 foundPrefix = true
2727 }
2728 }
2729 }
2730 if !foundPrefix {
2731 allErrs = append(allErrs, field.NotSupported(fldPath.Child("resource"), fs.Resource, sets.List(*expressions)))
2732 }
2733 }
2734 allErrs = append(allErrs, validateContainerResourceDivisor(fs.Resource, fs.Divisor, fldPath)...)
2735 return allErrs
2736 }
2737
2738 func ValidateEnvFrom(vars []core.EnvFromSource, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
2739 allErrs := field.ErrorList{}
2740 for i, ev := range vars {
2741 idxPath := fldPath.Index(i)
2742 if len(ev.Prefix) > 0 {
2743 if opts.AllowRelaxedEnvironmentVariableValidation {
2744 for _, msg := range validation.IsRelaxedEnvVarName(ev.Prefix) {
2745 allErrs = append(allErrs, field.Invalid(idxPath.Child("prefix"), ev.Prefix, msg))
2746 }
2747 } else {
2748 for _, msg := range validation.IsEnvVarName(ev.Prefix) {
2749 allErrs = append(allErrs, field.Invalid(idxPath.Child("prefix"), ev.Prefix, msg))
2750 }
2751 }
2752 }
2753
2754 numSources := 0
2755 if ev.ConfigMapRef != nil {
2756 numSources++
2757 allErrs = append(allErrs, validateConfigMapEnvSource(ev.ConfigMapRef, idxPath.Child("configMapRef"))...)
2758 }
2759 if ev.SecretRef != nil {
2760 numSources++
2761 allErrs = append(allErrs, validateSecretEnvSource(ev.SecretRef, idxPath.Child("secretRef"))...)
2762 }
2763
2764 if numSources == 0 {
2765 allErrs = append(allErrs, field.Invalid(fldPath, "", "must specify one of: `configMapRef` or `secretRef`"))
2766 } else if numSources > 1 {
2767 allErrs = append(allErrs, field.Invalid(fldPath, "", "may not have more than one field specified at a time"))
2768 }
2769 }
2770 return allErrs
2771 }
2772
2773 func validateConfigMapEnvSource(configMapSource *core.ConfigMapEnvSource, fldPath *field.Path) field.ErrorList {
2774 allErrs := field.ErrorList{}
2775 if len(configMapSource.Name) == 0 {
2776 allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
2777 } else {
2778 for _, msg := range ValidateConfigMapName(configMapSource.Name, true) {
2779 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), configMapSource.Name, msg))
2780 }
2781 }
2782 return allErrs
2783 }
2784
2785 func validateSecretEnvSource(secretSource *core.SecretEnvSource, fldPath *field.Path) field.ErrorList {
2786 allErrs := field.ErrorList{}
2787 if len(secretSource.Name) == 0 {
2788 allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
2789 } else {
2790 for _, msg := range ValidateSecretName(secretSource.Name, true) {
2791 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), secretSource.Name, msg))
2792 }
2793 }
2794 return allErrs
2795 }
2796
2797 var validContainerResourceDivisorForCPU = sets.New("1m", "1")
2798 var validContainerResourceDivisorForMemory = sets.New(
2799 "1",
2800 "1k", "1M", "1G", "1T", "1P", "1E",
2801 "1Ki", "1Mi", "1Gi", "1Ti", "1Pi", "1Ei")
2802 var validContainerResourceDivisorForHugePages = sets.New(
2803 "1",
2804 "1k", "1M", "1G", "1T", "1P", "1E",
2805 "1Ki", "1Mi", "1Gi", "1Ti", "1Pi", "1Ei")
2806 var validContainerResourceDivisorForEphemeralStorage = sets.New(
2807 "1",
2808 "1k", "1M", "1G", "1T", "1P", "1E",
2809 "1Ki", "1Mi", "1Gi", "1Ti", "1Pi", "1Ei")
2810
2811 func validateContainerResourceDivisor(rName string, divisor resource.Quantity, fldPath *field.Path) field.ErrorList {
2812 allErrs := field.ErrorList{}
2813 unsetDivisor := resource.Quantity{}
2814 if unsetDivisor.Cmp(divisor) == 0 {
2815 return allErrs
2816 }
2817 switch rName {
2818 case "limits.cpu", "requests.cpu":
2819 if !validContainerResourceDivisorForCPU.Has(divisor.String()) {
2820 allErrs = append(allErrs, field.Invalid(fldPath.Child("divisor"), rName, "only divisor's values 1m and 1 are supported with the cpu resource"))
2821 }
2822 case "limits.memory", "requests.memory":
2823 if !validContainerResourceDivisorForMemory.Has(divisor.String()) {
2824 allErrs = append(allErrs, field.Invalid(fldPath.Child("divisor"), rName, "only divisor's values 1, 1k, 1M, 1G, 1T, 1P, 1E, 1Ki, 1Mi, 1Gi, 1Ti, 1Pi, 1Ei are supported with the memory resource"))
2825 }
2826 case "limits.ephemeral-storage", "requests.ephemeral-storage":
2827 if !validContainerResourceDivisorForEphemeralStorage.Has(divisor.String()) {
2828 allErrs = append(allErrs, field.Invalid(fldPath.Child("divisor"), rName, "only divisor's values 1, 1k, 1M, 1G, 1T, 1P, 1E, 1Ki, 1Mi, 1Gi, 1Ti, 1Pi, 1Ei are supported with the local ephemeral storage resource"))
2829 }
2830 }
2831 if strings.HasPrefix(rName, hugepagesRequestsPrefixDownwardAPI) || strings.HasPrefix(rName, hugepagesLimitsPrefixDownwardAPI) {
2832 if !validContainerResourceDivisorForHugePages.Has(divisor.String()) {
2833 allErrs = append(allErrs, field.Invalid(fldPath.Child("divisor"), rName, "only divisor's values 1, 1k, 1M, 1G, 1T, 1P, 1E, 1Ki, 1Mi, 1Gi, 1Ti, 1Pi, 1Ei are supported with the hugepages resource"))
2834 }
2835 }
2836 return allErrs
2837 }
2838
2839 func validateConfigMapKeySelector(s *core.ConfigMapKeySelector, fldPath *field.Path) field.ErrorList {
2840 allErrs := field.ErrorList{}
2841
2842 nameFn := ValidateNameFunc(ValidateSecretName)
2843 for _, msg := range nameFn(s.Name, false) {
2844 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), s.Name, msg))
2845 }
2846 if len(s.Key) == 0 {
2847 allErrs = append(allErrs, field.Required(fldPath.Child("key"), ""))
2848 } else {
2849 for _, msg := range validation.IsConfigMapKey(s.Key) {
2850 allErrs = append(allErrs, field.Invalid(fldPath.Child("key"), s.Key, msg))
2851 }
2852 }
2853
2854 return allErrs
2855 }
2856
2857 func validateSecretKeySelector(s *core.SecretKeySelector, fldPath *field.Path) field.ErrorList {
2858 allErrs := field.ErrorList{}
2859
2860 nameFn := ValidateNameFunc(ValidateSecretName)
2861 for _, msg := range nameFn(s.Name, false) {
2862 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), s.Name, msg))
2863 }
2864 if len(s.Key) == 0 {
2865 allErrs = append(allErrs, field.Required(fldPath.Child("key"), ""))
2866 } else {
2867 for _, msg := range validation.IsConfigMapKey(s.Key) {
2868 allErrs = append(allErrs, field.Invalid(fldPath.Child("key"), s.Key, msg))
2869 }
2870 }
2871
2872 return allErrs
2873 }
2874
2875 func GetVolumeMountMap(mounts []core.VolumeMount) map[string]string {
2876 volmounts := make(map[string]string)
2877
2878 for _, mnt := range mounts {
2879 volmounts[mnt.Name] = mnt.MountPath
2880 }
2881
2882 return volmounts
2883 }
2884
2885 func GetVolumeDeviceMap(devices []core.VolumeDevice) map[string]string {
2886 volDevices := make(map[string]string)
2887
2888 for _, dev := range devices {
2889 volDevices[dev.Name] = dev.DevicePath
2890 }
2891
2892 return volDevices
2893 }
2894
2895 func ValidateVolumeMounts(mounts []core.VolumeMount, voldevices map[string]string, volumes map[string]core.VolumeSource, container *core.Container, fldPath *field.Path) field.ErrorList {
2896 allErrs := field.ErrorList{}
2897 mountpoints := sets.New[string]()
2898
2899 for i, mnt := range mounts {
2900 idxPath := fldPath.Index(i)
2901 if len(mnt.Name) == 0 {
2902 allErrs = append(allErrs, field.Required(idxPath.Child("name"), ""))
2903 }
2904 if !IsMatchedVolume(mnt.Name, volumes) {
2905 allErrs = append(allErrs, field.NotFound(idxPath.Child("name"), mnt.Name))
2906 }
2907 if len(mnt.MountPath) == 0 {
2908 allErrs = append(allErrs, field.Required(idxPath.Child("mountPath"), ""))
2909 }
2910 if mountpoints.Has(mnt.MountPath) {
2911 allErrs = append(allErrs, field.Invalid(idxPath.Child("mountPath"), mnt.MountPath, "must be unique"))
2912 }
2913 mountpoints.Insert(mnt.MountPath)
2914
2915
2916 if mountNameAlreadyExists(mnt.Name, voldevices) {
2917 allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), mnt.Name, "must not already exist in volumeDevices"))
2918 }
2919 if mountPathAlreadyExists(mnt.MountPath, voldevices) {
2920 allErrs = append(allErrs, field.Invalid(idxPath.Child("mountPath"), mnt.MountPath, "must not already exist as a path in volumeDevices"))
2921 }
2922
2923 if len(mnt.SubPath) > 0 {
2924 allErrs = append(allErrs, validateLocalDescendingPath(mnt.SubPath, fldPath.Child("subPath"))...)
2925 }
2926
2927 if len(mnt.SubPathExpr) > 0 {
2928 if len(mnt.SubPath) > 0 {
2929 allErrs = append(allErrs, field.Invalid(idxPath.Child("subPathExpr"), mnt.SubPathExpr, "subPathExpr and subPath are mutually exclusive"))
2930 }
2931
2932 allErrs = append(allErrs, validateLocalDescendingPath(mnt.SubPathExpr, fldPath.Child("subPathExpr"))...)
2933 }
2934
2935 if mnt.MountPropagation != nil {
2936 allErrs = append(allErrs, validateMountPropagation(mnt.MountPropagation, container, fldPath.Child("mountPropagation"))...)
2937 }
2938 allErrs = append(allErrs, validateMountRecursiveReadOnly(mnt, fldPath.Child("recursiveReadOnly"))...)
2939 }
2940 return allErrs
2941 }
2942
2943 func ValidateVolumeDevices(devices []core.VolumeDevice, volmounts map[string]string, volumes map[string]core.VolumeSource, fldPath *field.Path) field.ErrorList {
2944 allErrs := field.ErrorList{}
2945 devicepath := sets.New[string]()
2946 devicename := sets.New[string]()
2947
2948 for i, dev := range devices {
2949 idxPath := fldPath.Index(i)
2950 devName := dev.Name
2951 devPath := dev.DevicePath
2952 didMatch, isPVC := isMatchedDevice(devName, volumes)
2953 if len(devName) == 0 {
2954 allErrs = append(allErrs, field.Required(idxPath.Child("name"), ""))
2955 }
2956 if devicename.Has(devName) {
2957 allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), devName, "must be unique"))
2958 }
2959
2960 if didMatch && !isPVC {
2961 allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), devName, "can only use volume source type of PersistentVolumeClaim or Ephemeral for block mode"))
2962 }
2963 if !didMatch {
2964 allErrs = append(allErrs, field.NotFound(idxPath.Child("name"), devName))
2965 }
2966 if len(devPath) == 0 {
2967 allErrs = append(allErrs, field.Required(idxPath.Child("devicePath"), ""))
2968 }
2969 if devicepath.Has(devPath) {
2970 allErrs = append(allErrs, field.Invalid(idxPath.Child("devicePath"), devPath, "must be unique"))
2971 }
2972 if len(devPath) > 0 && len(validatePathNoBacksteps(devPath, fldPath.Child("devicePath"))) > 0 {
2973 allErrs = append(allErrs, field.Invalid(idxPath.Child("devicePath"), devPath, "can not contain backsteps ('..')"))
2974 } else {
2975 devicepath.Insert(devPath)
2976 }
2977
2978 if deviceNameAlreadyExists(devName, volmounts) {
2979 allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), devName, "must not already exist in volumeMounts"))
2980 }
2981 if devicePathAlreadyExists(devPath, volmounts) {
2982 allErrs = append(allErrs, field.Invalid(idxPath.Child("devicePath"), devPath, "must not already exist as a path in volumeMounts"))
2983 }
2984 if len(devName) > 0 {
2985 devicename.Insert(devName)
2986 }
2987 }
2988 return allErrs
2989 }
2990
2991 func validatePodResourceClaims(podMeta *metav1.ObjectMeta, claims []core.PodResourceClaim, fldPath *field.Path) field.ErrorList {
2992 var allErrs field.ErrorList
2993 podClaimNames := sets.New[string]()
2994 for i, claim := range claims {
2995 allErrs = append(allErrs, validatePodResourceClaim(podMeta, claim, &podClaimNames, fldPath.Index(i))...)
2996 }
2997 return allErrs
2998 }
2999
3000
3001
3002
3003 func gatherPodResourceClaimNames(claims []core.PodResourceClaim) sets.Set[string] {
3004 podClaimNames := sets.Set[string]{}
3005 for _, claim := range claims {
3006 if claim.Name != "" {
3007 podClaimNames.Insert(claim.Name)
3008 }
3009 }
3010 return podClaimNames
3011 }
3012
3013 func validatePodResourceClaim(podMeta *metav1.ObjectMeta, claim core.PodResourceClaim, podClaimNames *sets.Set[string], fldPath *field.Path) field.ErrorList {
3014 var allErrs field.ErrorList
3015 if claim.Name == "" {
3016 allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
3017 } else if podClaimNames.Has(claim.Name) {
3018 allErrs = append(allErrs, field.Duplicate(fldPath.Child("name"), claim.Name))
3019 } else {
3020 nameErrs := ValidateDNS1123Label(claim.Name, fldPath.Child("name"))
3021 if len(nameErrs) > 0 {
3022 allErrs = append(allErrs, nameErrs...)
3023 } else if podMeta != nil && claim.Source.ResourceClaimTemplateName != nil {
3024 claimName := podMeta.Name + "-" + claim.Name
3025 for _, detail := range ValidateResourceClaimName(claimName, false) {
3026 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), claimName, "final ResourceClaim name: "+detail))
3027 }
3028 }
3029 podClaimNames.Insert(claim.Name)
3030 }
3031 allErrs = append(allErrs, validatePodResourceClaimSource(claim.Source, fldPath.Child("source"))...)
3032
3033 return allErrs
3034 }
3035
3036 func validatePodResourceClaimSource(claimSource core.ClaimSource, fldPath *field.Path) field.ErrorList {
3037 var allErrs field.ErrorList
3038 if claimSource.ResourceClaimName != nil && claimSource.ResourceClaimTemplateName != nil {
3039 allErrs = append(allErrs, field.Invalid(fldPath, claimSource, "at most one of `resourceClaimName` or `resourceClaimTemplateName` may be specified"))
3040 }
3041 if claimSource.ResourceClaimName == nil && claimSource.ResourceClaimTemplateName == nil {
3042 allErrs = append(allErrs, field.Invalid(fldPath, claimSource, "must specify one of: `resourceClaimName`, `resourceClaimTemplateName`"))
3043 }
3044 if claimSource.ResourceClaimName != nil {
3045 for _, detail := range ValidateResourceClaimName(*claimSource.ResourceClaimName, false) {
3046 allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceClaimName"), *claimSource.ResourceClaimName, detail))
3047 }
3048 }
3049 if claimSource.ResourceClaimTemplateName != nil {
3050 for _, detail := range ValidateResourceClaimTemplateName(*claimSource.ResourceClaimTemplateName, false) {
3051 allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceClaimTemplateName"), *claimSource.ResourceClaimTemplateName, detail))
3052 }
3053 }
3054 return allErrs
3055 }
3056
3057 func validateLivenessProbe(probe *core.Probe, gracePeriod int64, fldPath *field.Path) field.ErrorList {
3058 allErrs := field.ErrorList{}
3059
3060 if probe == nil {
3061 return allErrs
3062 }
3063 allErrs = append(allErrs, validateProbe(probe, gracePeriod, fldPath)...)
3064 if probe.SuccessThreshold != 1 {
3065 allErrs = append(allErrs, field.Invalid(fldPath.Child("successThreshold"), probe.SuccessThreshold, "must be 1"))
3066 }
3067 return allErrs
3068 }
3069
3070 func validateReadinessProbe(probe *core.Probe, gracePeriod int64, fldPath *field.Path) field.ErrorList {
3071 allErrs := field.ErrorList{}
3072
3073 if probe == nil {
3074 return allErrs
3075 }
3076 allErrs = append(allErrs, validateProbe(probe, gracePeriod, fldPath)...)
3077 if probe.TerminationGracePeriodSeconds != nil {
3078 allErrs = append(allErrs, field.Invalid(fldPath.Child("terminationGracePeriodSeconds"), probe.TerminationGracePeriodSeconds, "must not be set for readinessProbes"))
3079 }
3080 return allErrs
3081 }
3082
3083 func validateStartupProbe(probe *core.Probe, gracePeriod int64, fldPath *field.Path) field.ErrorList {
3084 allErrs := field.ErrorList{}
3085
3086 if probe == nil {
3087 return allErrs
3088 }
3089 allErrs = append(allErrs, validateProbe(probe, gracePeriod, fldPath)...)
3090 if probe.SuccessThreshold != 1 {
3091 allErrs = append(allErrs, field.Invalid(fldPath.Child("successThreshold"), probe.SuccessThreshold, "must be 1"))
3092 }
3093 return allErrs
3094 }
3095
3096 func validateProbe(probe *core.Probe, gracePeriod int64, fldPath *field.Path) field.ErrorList {
3097 allErrs := field.ErrorList{}
3098
3099 if probe == nil {
3100 return allErrs
3101 }
3102 allErrs = append(allErrs, validateHandler(handlerFromProbe(&probe.ProbeHandler), gracePeriod, fldPath)...)
3103
3104 allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.InitialDelaySeconds), fldPath.Child("initialDelaySeconds"))...)
3105 allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.TimeoutSeconds), fldPath.Child("timeoutSeconds"))...)
3106 allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.PeriodSeconds), fldPath.Child("periodSeconds"))...)
3107 allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.SuccessThreshold), fldPath.Child("successThreshold"))...)
3108 allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.FailureThreshold), fldPath.Child("failureThreshold"))...)
3109 if probe.TerminationGracePeriodSeconds != nil && *probe.TerminationGracePeriodSeconds <= 0 {
3110 allErrs = append(allErrs, field.Invalid(fldPath.Child("terminationGracePeriodSeconds"), *probe.TerminationGracePeriodSeconds, "must be greater than 0"))
3111 }
3112 return allErrs
3113 }
3114
3115 func validateInitContainerRestartPolicy(restartPolicy *core.ContainerRestartPolicy, fldPath *field.Path) field.ErrorList {
3116 var allErrors field.ErrorList
3117
3118 if restartPolicy == nil {
3119 return allErrors
3120 }
3121 switch *restartPolicy {
3122 case core.ContainerRestartPolicyAlways:
3123 break
3124 default:
3125 validValues := []core.ContainerRestartPolicy{core.ContainerRestartPolicyAlways}
3126 allErrors = append(allErrors, field.NotSupported(fldPath, *restartPolicy, validValues))
3127 }
3128
3129 return allErrors
3130 }
3131
3132 type commonHandler struct {
3133 Exec *core.ExecAction
3134 HTTPGet *core.HTTPGetAction
3135 TCPSocket *core.TCPSocketAction
3136 GRPC *core.GRPCAction
3137 Sleep *core.SleepAction
3138 }
3139
3140 func handlerFromProbe(ph *core.ProbeHandler) commonHandler {
3141 return commonHandler{
3142 Exec: ph.Exec,
3143 HTTPGet: ph.HTTPGet,
3144 TCPSocket: ph.TCPSocket,
3145 GRPC: ph.GRPC,
3146 }
3147 }
3148
3149 func handlerFromLifecycle(lh *core.LifecycleHandler) commonHandler {
3150 return commonHandler{
3151 Exec: lh.Exec,
3152 HTTPGet: lh.HTTPGet,
3153 TCPSocket: lh.TCPSocket,
3154 Sleep: lh.Sleep,
3155 }
3156 }
3157
3158 func validateSleepAction(sleep *core.SleepAction, gracePeriod int64, fldPath *field.Path) field.ErrorList {
3159 allErrors := field.ErrorList{}
3160 if sleep.Seconds <= 0 || sleep.Seconds > gracePeriod {
3161 invalidStr := fmt.Sprintf("must be greater than 0 and less than terminationGracePeriodSeconds (%d)", gracePeriod)
3162 allErrors = append(allErrors, field.Invalid(fldPath, sleep.Seconds, invalidStr))
3163 }
3164 return allErrors
3165 }
3166
3167 func validateClientIPAffinityConfig(config *core.SessionAffinityConfig, fldPath *field.Path) field.ErrorList {
3168 allErrs := field.ErrorList{}
3169 if config == nil {
3170 allErrs = append(allErrs, field.Required(fldPath, fmt.Sprintf("when session affinity type is %s", core.ServiceAffinityClientIP)))
3171 return allErrs
3172 }
3173 if config.ClientIP == nil {
3174 allErrs = append(allErrs, field.Required(fldPath.Child("clientIP"), fmt.Sprintf("when session affinity type is %s", core.ServiceAffinityClientIP)))
3175 return allErrs
3176 }
3177 if config.ClientIP.TimeoutSeconds == nil {
3178 allErrs = append(allErrs, field.Required(fldPath.Child("clientIP").Child("timeoutSeconds"), fmt.Sprintf("when session affinity type is %s", core.ServiceAffinityClientIP)))
3179 return allErrs
3180 }
3181 allErrs = append(allErrs, validateAffinityTimeout(config.ClientIP.TimeoutSeconds, fldPath.Child("clientIP").Child("timeoutSeconds"))...)
3182
3183 return allErrs
3184 }
3185
3186 func validateAffinityTimeout(timeout *int32, fldPath *field.Path) field.ErrorList {
3187 allErrs := field.ErrorList{}
3188 if *timeout <= 0 || *timeout > core.MaxClientIPServiceAffinitySeconds {
3189 allErrs = append(allErrs, field.Invalid(fldPath, timeout, fmt.Sprintf("must be greater than 0 and less than %d", core.MaxClientIPServiceAffinitySeconds)))
3190 }
3191 return allErrs
3192 }
3193
3194
3195
3196 func AccumulateUniqueHostPorts(containers []core.Container, accumulator *sets.Set[string], fldPath *field.Path) field.ErrorList {
3197 allErrs := field.ErrorList{}
3198
3199 for ci, ctr := range containers {
3200 idxPath := fldPath.Index(ci)
3201 portsPath := idxPath.Child("ports")
3202 for pi := range ctr.Ports {
3203 idxPath := portsPath.Index(pi)
3204 port := ctr.Ports[pi].HostPort
3205 if port == 0 {
3206 continue
3207 }
3208 str := fmt.Sprintf("%s/%s/%d", ctr.Ports[pi].Protocol, ctr.Ports[pi].HostIP, port)
3209 if accumulator.Has(str) {
3210 allErrs = append(allErrs, field.Duplicate(idxPath.Child("hostPort"), str))
3211 } else {
3212 accumulator.Insert(str)
3213 }
3214 }
3215 }
3216 return allErrs
3217 }
3218
3219
3220
3221 func checkHostPortConflicts(containers []core.Container, fldPath *field.Path) field.ErrorList {
3222 allPorts := sets.Set[string]{}
3223 return AccumulateUniqueHostPorts(containers, &allPorts, fldPath)
3224 }
3225
3226 func validateExecAction(exec *core.ExecAction, fldPath *field.Path) field.ErrorList {
3227 allErrors := field.ErrorList{}
3228 if len(exec.Command) == 0 {
3229 allErrors = append(allErrors, field.Required(fldPath.Child("command"), ""))
3230 }
3231 return allErrors
3232 }
3233
3234 var supportedHTTPSchemes = sets.New(core.URISchemeHTTP, core.URISchemeHTTPS)
3235
3236 func validateHTTPGetAction(http *core.HTTPGetAction, fldPath *field.Path) field.ErrorList {
3237 allErrors := field.ErrorList{}
3238 if len(http.Path) == 0 {
3239 allErrors = append(allErrors, field.Required(fldPath.Child("path"), ""))
3240 }
3241 allErrors = append(allErrors, ValidatePortNumOrName(http.Port, fldPath.Child("port"))...)
3242 if !supportedHTTPSchemes.Has(http.Scheme) {
3243 allErrors = append(allErrors, field.NotSupported(fldPath.Child("scheme"), http.Scheme, sets.List(supportedHTTPSchemes)))
3244 }
3245 for _, header := range http.HTTPHeaders {
3246 for _, msg := range validation.IsHTTPHeaderName(header.Name) {
3247 allErrors = append(allErrors, field.Invalid(fldPath.Child("httpHeaders"), header.Name, msg))
3248 }
3249 }
3250 return allErrors
3251 }
3252
3253 func ValidatePortNumOrName(port intstr.IntOrString, fldPath *field.Path) field.ErrorList {
3254 allErrs := field.ErrorList{}
3255 if port.Type == intstr.Int {
3256 for _, msg := range validation.IsValidPortNum(port.IntValue()) {
3257 allErrs = append(allErrs, field.Invalid(fldPath, port.IntValue(), msg))
3258 }
3259 } else if port.Type == intstr.String {
3260 for _, msg := range validation.IsValidPortName(port.StrVal) {
3261 allErrs = append(allErrs, field.Invalid(fldPath, port.StrVal, msg))
3262 }
3263 } else {
3264 allErrs = append(allErrs, field.InternalError(fldPath, fmt.Errorf("unknown type: %v", port.Type)))
3265 }
3266 return allErrs
3267 }
3268
3269 func validateTCPSocketAction(tcp *core.TCPSocketAction, fldPath *field.Path) field.ErrorList {
3270 return ValidatePortNumOrName(tcp.Port, fldPath.Child("port"))
3271 }
3272 func validateGRPCAction(grpc *core.GRPCAction, fldPath *field.Path) field.ErrorList {
3273 return ValidatePortNumOrName(intstr.FromInt32(grpc.Port), fldPath.Child("port"))
3274 }
3275 func validateHandler(handler commonHandler, gracePeriod int64, fldPath *field.Path) field.ErrorList {
3276 numHandlers := 0
3277 allErrors := field.ErrorList{}
3278 if handler.Exec != nil {
3279 if numHandlers > 0 {
3280 allErrors = append(allErrors, field.Forbidden(fldPath.Child("exec"), "may not specify more than 1 handler type"))
3281 } else {
3282 numHandlers++
3283 allErrors = append(allErrors, validateExecAction(handler.Exec, fldPath.Child("exec"))...)
3284 }
3285 }
3286 if handler.HTTPGet != nil {
3287 if numHandlers > 0 {
3288 allErrors = append(allErrors, field.Forbidden(fldPath.Child("httpGet"), "may not specify more than 1 handler type"))
3289 } else {
3290 numHandlers++
3291 allErrors = append(allErrors, validateHTTPGetAction(handler.HTTPGet, fldPath.Child("httpGet"))...)
3292 }
3293 }
3294 if handler.TCPSocket != nil {
3295 if numHandlers > 0 {
3296 allErrors = append(allErrors, field.Forbidden(fldPath.Child("tcpSocket"), "may not specify more than 1 handler type"))
3297 } else {
3298 numHandlers++
3299 allErrors = append(allErrors, validateTCPSocketAction(handler.TCPSocket, fldPath.Child("tcpSocket"))...)
3300 }
3301 }
3302 if handler.GRPC != nil {
3303 if numHandlers > 0 {
3304 allErrors = append(allErrors, field.Forbidden(fldPath.Child("grpc"), "may not specify more than 1 handler type"))
3305 } else {
3306 numHandlers++
3307 allErrors = append(allErrors, validateGRPCAction(handler.GRPC, fldPath.Child("grpc"))...)
3308 }
3309 }
3310 if handler.Sleep != nil {
3311 if numHandlers > 0 {
3312 allErrors = append(allErrors, field.Forbidden(fldPath.Child("sleep"), "may not specify more than 1 handler type"))
3313 } else {
3314 numHandlers++
3315 allErrors = append(allErrors, validateSleepAction(handler.Sleep, gracePeriod, fldPath.Child("sleep"))...)
3316 }
3317 }
3318 if numHandlers == 0 {
3319 allErrors = append(allErrors, field.Required(fldPath, "must specify a handler type"))
3320 }
3321 return allErrors
3322 }
3323
3324 func validateLifecycle(lifecycle *core.Lifecycle, gracePeriod int64, fldPath *field.Path) field.ErrorList {
3325 allErrs := field.ErrorList{}
3326 if lifecycle.PostStart != nil {
3327 allErrs = append(allErrs, validateHandler(handlerFromLifecycle(lifecycle.PostStart), gracePeriod, fldPath.Child("postStart"))...)
3328 }
3329 if lifecycle.PreStop != nil {
3330 allErrs = append(allErrs, validateHandler(handlerFromLifecycle(lifecycle.PreStop), gracePeriod, fldPath.Child("preStop"))...)
3331 }
3332 return allErrs
3333 }
3334
3335 var supportedPullPolicies = sets.New(
3336 core.PullAlways,
3337 core.PullIfNotPresent,
3338 core.PullNever)
3339
3340 func validatePullPolicy(policy core.PullPolicy, fldPath *field.Path) field.ErrorList {
3341 allErrors := field.ErrorList{}
3342
3343 switch policy {
3344 case core.PullAlways, core.PullIfNotPresent, core.PullNever:
3345 break
3346 case "":
3347 allErrors = append(allErrors, field.Required(fldPath, ""))
3348 default:
3349 allErrors = append(allErrors, field.NotSupported(fldPath, policy, sets.List(supportedPullPolicies)))
3350 }
3351
3352 return allErrors
3353 }
3354
3355 var supportedResizeResources = sets.New(core.ResourceCPU, core.ResourceMemory)
3356 var supportedResizePolicies = sets.New(core.NotRequired, core.RestartContainer)
3357
3358 func validateResizePolicy(policyList []core.ContainerResizePolicy, fldPath *field.Path, podRestartPolicy *core.RestartPolicy) field.ErrorList {
3359 allErrors := field.ErrorList{}
3360
3361
3362 resources := make(map[core.ResourceName]bool)
3363 for i, p := range policyList {
3364 if _, found := resources[p.ResourceName]; found {
3365 allErrors = append(allErrors, field.Duplicate(fldPath.Index(i), p.ResourceName))
3366 }
3367 resources[p.ResourceName] = true
3368 switch p.ResourceName {
3369 case core.ResourceCPU, core.ResourceMemory:
3370 case "":
3371 allErrors = append(allErrors, field.Required(fldPath, ""))
3372 default:
3373 allErrors = append(allErrors, field.NotSupported(fldPath, p.ResourceName, sets.List(supportedResizeResources)))
3374 }
3375 switch p.RestartPolicy {
3376 case core.NotRequired, core.RestartContainer:
3377 case "":
3378 allErrors = append(allErrors, field.Required(fldPath, ""))
3379 default:
3380 allErrors = append(allErrors, field.NotSupported(fldPath, p.RestartPolicy, sets.List(supportedResizePolicies)))
3381 }
3382
3383 if *podRestartPolicy == core.RestartPolicyNever && p.RestartPolicy != core.NotRequired {
3384 allErrors = append(allErrors, field.Invalid(fldPath, p.RestartPolicy, "must be 'NotRequired' when `restartPolicy` is 'Never'"))
3385 }
3386 }
3387 return allErrors
3388 }
3389
3390
3391
3392 func validateEphemeralContainers(ephemeralContainers []core.EphemeralContainer, containers, initContainers []core.Container, volumes map[string]core.VolumeSource, podClaimNames sets.Set[string], fldPath *field.Path, opts PodValidationOptions, podRestartPolicy *core.RestartPolicy, hostUsers bool) field.ErrorList {
3393 var allErrs field.ErrorList
3394
3395 if len(ephemeralContainers) == 0 {
3396 return allErrs
3397 }
3398
3399 otherNames, allNames := sets.Set[string]{}, sets.Set[string]{}
3400 for _, c := range containers {
3401 otherNames.Insert(c.Name)
3402 allNames.Insert(c.Name)
3403 }
3404 for _, c := range initContainers {
3405 otherNames.Insert(c.Name)
3406 allNames.Insert(c.Name)
3407 }
3408
3409 for i, ec := range ephemeralContainers {
3410 idxPath := fldPath.Index(i)
3411
3412 c := (*core.Container)(&ec.EphemeralContainerCommon)
3413 allErrs = append(allErrs, validateContainerCommon(c, volumes, podClaimNames, idxPath, opts, podRestartPolicy, hostUsers)...)
3414
3415
3416 allErrs = append(allErrs, validateContainerOnlyForPod(c, idxPath)...)
3417
3418
3419 if allNames.Has(ec.Name) {
3420 allErrs = append(allErrs, field.Duplicate(idxPath.Child("name"), ec.Name))
3421 } else {
3422 allNames.Insert(ec.Name)
3423 }
3424
3425
3426 if ec.TargetContainerName != "" && !otherNames.Has(ec.TargetContainerName) {
3427 allErrs = append(allErrs, field.NotFound(idxPath.Child("targetContainerName"), ec.TargetContainerName))
3428 }
3429
3430
3431
3432
3433 allErrs = append(allErrs, validateFieldAllowList(ec.EphemeralContainerCommon, allowedEphemeralContainerFields, "cannot be set for an Ephemeral Container", idxPath)...)
3434
3435
3436
3437
3438 for i, vm := range ec.VolumeMounts {
3439 if vm.SubPath != "" {
3440 allErrs = append(allErrs, field.Forbidden(idxPath.Child("volumeMounts").Index(i).Child("subPath"), "cannot be set for an Ephemeral Container"))
3441 }
3442 if vm.SubPathExpr != "" {
3443 allErrs = append(allErrs, field.Forbidden(idxPath.Child("volumeMounts").Index(i).Child("subPathExpr"), "cannot be set for an Ephemeral Container"))
3444 }
3445 }
3446 }
3447
3448 return allErrs
3449 }
3450
3451
3452
3453 func validateFieldAllowList(value interface{}, allowedFields map[string]bool, errorText string, fldPath *field.Path) field.ErrorList {
3454 var allErrs field.ErrorList
3455
3456 reflectType, reflectValue := reflect.TypeOf(value), reflect.ValueOf(value)
3457 for i := 0; i < reflectType.NumField(); i++ {
3458 f := reflectType.Field(i)
3459 if allowedFields[f.Name] {
3460 continue
3461 }
3462
3463
3464 if !reflect.DeepEqual(reflectValue.Field(i).Interface(), reflect.Zero(f.Type).Interface()) {
3465 r, n := utf8.DecodeRuneInString(f.Name)
3466 lcName := string(unicode.ToLower(r)) + f.Name[n:]
3467 allErrs = append(allErrs, field.Forbidden(fldPath.Child(lcName), errorText))
3468 }
3469 }
3470
3471 return allErrs
3472 }
3473
3474
3475 func validateInitContainers(containers []core.Container, regularContainers []core.Container, volumes map[string]core.VolumeSource, podClaimNames sets.Set[string], gracePeriod int64, fldPath *field.Path, opts PodValidationOptions, podRestartPolicy *core.RestartPolicy, hostUsers bool) field.ErrorList {
3476 var allErrs field.ErrorList
3477
3478 allNames := sets.Set[string]{}
3479 for _, ctr := range regularContainers {
3480 allNames.Insert(ctr.Name)
3481 }
3482 for i, ctr := range containers {
3483 idxPath := fldPath.Index(i)
3484
3485
3486 allErrs = append(allErrs, validateContainerCommon(&ctr, volumes, podClaimNames, idxPath, opts, podRestartPolicy, hostUsers)...)
3487
3488 restartAlways := false
3489
3490 if ctr.RestartPolicy != nil {
3491 allErrs = append(allErrs, validateInitContainerRestartPolicy(ctr.RestartPolicy, idxPath.Child("restartPolicy"))...)
3492 restartAlways = *ctr.RestartPolicy == core.ContainerRestartPolicyAlways
3493 }
3494
3495
3496
3497 if allNames.Has(ctr.Name) {
3498 allErrs = append(allErrs, field.Duplicate(idxPath.Child("name"), ctr.Name))
3499 } else if len(ctr.Name) > 0 {
3500 allNames.Insert(ctr.Name)
3501 }
3502
3503
3504 allErrs = append(allErrs, checkHostPortConflicts([]core.Container{ctr}, fldPath)...)
3505
3506 switch {
3507 case restartAlways:
3508 if ctr.Lifecycle != nil {
3509 allErrs = append(allErrs, validateLifecycle(ctr.Lifecycle, gracePeriod, idxPath.Child("lifecycle"))...)
3510 }
3511 allErrs = append(allErrs, validateLivenessProbe(ctr.LivenessProbe, gracePeriod, idxPath.Child("livenessProbe"))...)
3512 allErrs = append(allErrs, validateReadinessProbe(ctr.ReadinessProbe, gracePeriod, idxPath.Child("readinessProbe"))...)
3513 allErrs = append(allErrs, validateStartupProbe(ctr.StartupProbe, gracePeriod, idxPath.Child("startupProbe"))...)
3514
3515 default:
3516
3517 if ctr.Lifecycle != nil {
3518 allErrs = append(allErrs, field.Forbidden(idxPath.Child("lifecycle"), "may not be set for init containers without restartPolicy=Always"))
3519 }
3520 if ctr.LivenessProbe != nil {
3521 allErrs = append(allErrs, field.Forbidden(idxPath.Child("livenessProbe"), "may not be set for init containers without restartPolicy=Always"))
3522 }
3523 if ctr.ReadinessProbe != nil {
3524 allErrs = append(allErrs, field.Forbidden(idxPath.Child("readinessProbe"), "may not be set for init containers without restartPolicy=Always"))
3525 }
3526 if ctr.StartupProbe != nil {
3527 allErrs = append(allErrs, field.Forbidden(idxPath.Child("startupProbe"), "may not be set for init containers without restartPolicy=Always"))
3528 }
3529 }
3530
3531 if len(ctr.ResizePolicy) > 0 {
3532 allErrs = append(allErrs, field.Invalid(idxPath.Child("resizePolicy"), ctr.ResizePolicy, "must not be set for init containers"))
3533 }
3534 }
3535
3536 return allErrs
3537 }
3538
3539
3540
3541 func validateContainerCommon(ctr *core.Container, volumes map[string]core.VolumeSource, podClaimNames sets.Set[string], path *field.Path, opts PodValidationOptions, podRestartPolicy *core.RestartPolicy, hostUsers bool) field.ErrorList {
3542 var allErrs field.ErrorList
3543
3544 namePath := path.Child("name")
3545 if len(ctr.Name) == 0 {
3546 allErrs = append(allErrs, field.Required(namePath, ""))
3547 } else {
3548 allErrs = append(allErrs, ValidateDNS1123Label(ctr.Name, namePath)...)
3549 }
3550
3551
3552
3553
3554 if len(ctr.Image) == 0 {
3555 allErrs = append(allErrs, field.Required(path.Child("image"), ""))
3556 }
3557
3558 switch ctr.TerminationMessagePolicy {
3559 case core.TerminationMessageReadFile, core.TerminationMessageFallbackToLogsOnError:
3560 case "":
3561 allErrs = append(allErrs, field.Required(path.Child("terminationMessagePolicy"), ""))
3562 default:
3563 supported := []core.TerminationMessagePolicy{
3564 core.TerminationMessageReadFile,
3565 core.TerminationMessageFallbackToLogsOnError,
3566 }
3567 allErrs = append(allErrs, field.NotSupported(path.Child("terminationMessagePolicy"), ctr.TerminationMessagePolicy, supported))
3568 }
3569
3570 volMounts := GetVolumeMountMap(ctr.VolumeMounts)
3571 volDevices := GetVolumeDeviceMap(ctr.VolumeDevices)
3572 allErrs = append(allErrs, validateContainerPorts(ctr.Ports, path.Child("ports"))...)
3573 allErrs = append(allErrs, ValidateEnv(ctr.Env, path.Child("env"), opts)...)
3574 allErrs = append(allErrs, ValidateEnvFrom(ctr.EnvFrom, path.Child("envFrom"), opts)...)
3575 allErrs = append(allErrs, ValidateVolumeMounts(ctr.VolumeMounts, volDevices, volumes, ctr, path.Child("volumeMounts"))...)
3576 allErrs = append(allErrs, ValidateVolumeDevices(ctr.VolumeDevices, volMounts, volumes, path.Child("volumeDevices"))...)
3577 allErrs = append(allErrs, validatePullPolicy(ctr.ImagePullPolicy, path.Child("imagePullPolicy"))...)
3578 allErrs = append(allErrs, ValidateResourceRequirements(&ctr.Resources, podClaimNames, path.Child("resources"), opts)...)
3579 allErrs = append(allErrs, validateResizePolicy(ctr.ResizePolicy, path.Child("resizePolicy"), podRestartPolicy)...)
3580 allErrs = append(allErrs, ValidateSecurityContext(ctr.SecurityContext, path.Child("securityContext"), hostUsers)...)
3581 return allErrs
3582 }
3583
3584 func validateHostUsers(spec *core.PodSpec, fldPath *field.Path) field.ErrorList {
3585 allErrs := field.ErrorList{}
3586
3587
3588
3589 if spec.SecurityContext == nil || spec.SecurityContext.HostUsers == nil || *spec.SecurityContext.HostUsers {
3590 return allErrs
3591 }
3592
3593
3594
3595
3596
3597
3598
3599 if spec.SecurityContext.HostNetwork {
3600 allErrs = append(allErrs, field.Forbidden(fldPath.Child("hostNetwork"), "when `pod.Spec.HostUsers` is false"))
3601 }
3602 if spec.SecurityContext.HostPID {
3603 allErrs = append(allErrs, field.Forbidden(fldPath.Child("HostPID"), "when `pod.Spec.HostUsers` is false"))
3604 }
3605 if spec.SecurityContext.HostIPC {
3606 allErrs = append(allErrs, field.Forbidden(fldPath.Child("HostIPC"), "when `pod.Spec.HostUsers` is false"))
3607 }
3608
3609 return allErrs
3610 }
3611
3612
3613 func validateContainers(containers []core.Container, volumes map[string]core.VolumeSource, podClaimNames sets.Set[string], gracePeriod int64, fldPath *field.Path, opts PodValidationOptions, podRestartPolicy *core.RestartPolicy, hostUsers bool) field.ErrorList {
3614 allErrs := field.ErrorList{}
3615
3616 if len(containers) == 0 {
3617 return append(allErrs, field.Required(fldPath, ""))
3618 }
3619
3620 allNames := sets.Set[string]{}
3621 for i, ctr := range containers {
3622 path := fldPath.Index(i)
3623
3624
3625 allErrs = append(allErrs, validateContainerCommon(&ctr, volumes, podClaimNames, path, opts, podRestartPolicy, hostUsers)...)
3626
3627
3628
3629
3630 if allNames.Has(ctr.Name) {
3631 allErrs = append(allErrs, field.Duplicate(path.Child("name"), ctr.Name))
3632 } else {
3633 allNames.Insert(ctr.Name)
3634 }
3635
3636
3637
3638
3639
3640 if ctr.Lifecycle != nil {
3641 allErrs = append(allErrs, validateLifecycle(ctr.Lifecycle, gracePeriod, path.Child("lifecycle"))...)
3642 }
3643 allErrs = append(allErrs, validateLivenessProbe(ctr.LivenessProbe, gracePeriod, path.Child("livenessProbe"))...)
3644 allErrs = append(allErrs, validateReadinessProbe(ctr.ReadinessProbe, gracePeriod, path.Child("readinessProbe"))...)
3645 allErrs = append(allErrs, validateStartupProbe(ctr.StartupProbe, gracePeriod, path.Child("startupProbe"))...)
3646
3647
3648 if ctr.RestartPolicy != nil {
3649 allErrs = append(allErrs, field.Forbidden(path.Child("restartPolicy"), "may not be set for non-init containers"))
3650 }
3651 }
3652
3653
3654 allErrs = append(allErrs, checkHostPortConflicts(containers, fldPath)...)
3655
3656 return allErrs
3657 }
3658
3659 func validateRestartPolicy(restartPolicy *core.RestartPolicy, fldPath *field.Path) field.ErrorList {
3660 allErrors := field.ErrorList{}
3661 switch *restartPolicy {
3662 case core.RestartPolicyAlways, core.RestartPolicyOnFailure, core.RestartPolicyNever:
3663 break
3664 case "":
3665 allErrors = append(allErrors, field.Required(fldPath, ""))
3666 default:
3667 validValues := []core.RestartPolicy{core.RestartPolicyAlways, core.RestartPolicyOnFailure, core.RestartPolicyNever}
3668 allErrors = append(allErrors, field.NotSupported(fldPath, *restartPolicy, validValues))
3669 }
3670
3671 return allErrors
3672 }
3673
3674 func ValidatePreemptionPolicy(preemptionPolicy *core.PreemptionPolicy, fldPath *field.Path) field.ErrorList {
3675 allErrors := field.ErrorList{}
3676 switch *preemptionPolicy {
3677 case core.PreemptLowerPriority, core.PreemptNever:
3678 case "":
3679 allErrors = append(allErrors, field.Required(fldPath, ""))
3680 default:
3681 validValues := []core.PreemptionPolicy{core.PreemptLowerPriority, core.PreemptNever}
3682 allErrors = append(allErrors, field.NotSupported(fldPath, preemptionPolicy, validValues))
3683 }
3684 return allErrors
3685 }
3686
3687 func validateDNSPolicy(dnsPolicy *core.DNSPolicy, fldPath *field.Path) field.ErrorList {
3688 allErrors := field.ErrorList{}
3689 switch *dnsPolicy {
3690 case core.DNSClusterFirstWithHostNet, core.DNSClusterFirst, core.DNSDefault, core.DNSNone:
3691 case "":
3692 allErrors = append(allErrors, field.Required(fldPath, ""))
3693 default:
3694 validValues := []core.DNSPolicy{core.DNSClusterFirstWithHostNet, core.DNSClusterFirst, core.DNSDefault, core.DNSNone}
3695 allErrors = append(allErrors, field.NotSupported(fldPath, dnsPolicy, validValues))
3696 }
3697 return allErrors
3698 }
3699
3700 var validFSGroupChangePolicies = sets.New(core.FSGroupChangeOnRootMismatch, core.FSGroupChangeAlways)
3701
3702 func validateFSGroupChangePolicy(fsGroupPolicy *core.PodFSGroupChangePolicy, fldPath *field.Path) field.ErrorList {
3703 allErrors := field.ErrorList{}
3704 if !validFSGroupChangePolicies.Has(*fsGroupPolicy) {
3705 allErrors = append(allErrors, field.NotSupported(fldPath, fsGroupPolicy, sets.List(validFSGroupChangePolicies)))
3706 }
3707 return allErrors
3708 }
3709
3710 const (
3711
3712
3713
3714 MaxDNSNameservers = 3
3715
3716 MaxDNSSearchPaths = 32
3717
3718 MaxDNSSearchListChars = 2048
3719 )
3720
3721 func validateReadinessGates(readinessGates []core.PodReadinessGate, fldPath *field.Path) field.ErrorList {
3722 allErrs := field.ErrorList{}
3723 for i, value := range readinessGates {
3724 allErrs = append(allErrs, ValidateQualifiedName(string(value.ConditionType), fldPath.Index(i).Child("conditionType"))...)
3725 }
3726 return allErrs
3727 }
3728
3729 func validateSchedulingGates(schedulingGates []core.PodSchedulingGate, fldPath *field.Path) field.ErrorList {
3730 allErrs := field.ErrorList{}
3731
3732 seen := sets.Set[string]{}
3733 for i, schedulingGate := range schedulingGates {
3734 allErrs = append(allErrs, ValidateQualifiedName(schedulingGate.Name, fldPath.Index(i))...)
3735 if seen.Has(schedulingGate.Name) {
3736 allErrs = append(allErrs, field.Duplicate(fldPath.Index(i), schedulingGate.Name))
3737 }
3738 seen.Insert(schedulingGate.Name)
3739 }
3740 return allErrs
3741 }
3742
3743 func validatePodDNSConfig(dnsConfig *core.PodDNSConfig, dnsPolicy *core.DNSPolicy, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
3744 allErrs := field.ErrorList{}
3745
3746
3747 if dnsPolicy != nil && *dnsPolicy == core.DNSNone {
3748 if dnsConfig == nil {
3749 return append(allErrs, field.Required(fldPath, fmt.Sprintf("must provide `dnsConfig` when `dnsPolicy` is %s", core.DNSNone)))
3750 }
3751 if len(dnsConfig.Nameservers) == 0 {
3752 return append(allErrs, field.Required(fldPath.Child("nameservers"), fmt.Sprintf("must provide at least one DNS nameserver when `dnsPolicy` is %s", core.DNSNone)))
3753 }
3754 }
3755
3756 if dnsConfig != nil {
3757
3758 if len(dnsConfig.Nameservers) > MaxDNSNameservers {
3759 allErrs = append(allErrs, field.Invalid(fldPath.Child("nameservers"), dnsConfig.Nameservers, fmt.Sprintf("must not have more than %v nameservers", MaxDNSNameservers)))
3760 }
3761 for i, ns := range dnsConfig.Nameservers {
3762 allErrs = append(allErrs, validation.IsValidIP(fldPath.Child("nameservers").Index(i), ns)...)
3763 }
3764
3765 if len(dnsConfig.Searches) > MaxDNSSearchPaths {
3766 allErrs = append(allErrs, field.Invalid(fldPath.Child("searches"), dnsConfig.Searches, fmt.Sprintf("must not have more than %v search paths", MaxDNSSearchPaths)))
3767 }
3768
3769 if len(strings.Join(dnsConfig.Searches, " ")) > MaxDNSSearchListChars {
3770 allErrs = append(allErrs, field.Invalid(fldPath.Child("searches"), dnsConfig.Searches, fmt.Sprintf("must not have more than %v characters (including spaces) in the search list", MaxDNSSearchListChars)))
3771 }
3772 for i, search := range dnsConfig.Searches {
3773
3774 search = strings.TrimSuffix(search, ".")
3775 allErrs = append(allErrs, ValidateDNS1123Subdomain(search, fldPath.Child("searches").Index(i))...)
3776 }
3777
3778 for i, option := range dnsConfig.Options {
3779 if len(option.Name) == 0 {
3780 allErrs = append(allErrs, field.Required(fldPath.Child("options").Index(i), "must not be empty"))
3781 }
3782 }
3783 }
3784 return allErrs
3785 }
3786
3787
3788
3789
3790
3791
3792 func validatePodHostNetworkDeps(spec *core.PodSpec, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
3793
3794
3795 hostNetwork := false
3796 if spec.SecurityContext != nil {
3797 hostNetwork = spec.SecurityContext.HostNetwork
3798 }
3799
3800 allErrors := field.ErrorList{}
3801
3802 if hostNetwork {
3803 fldPath := fldPath.Child("containers")
3804 for i, container := range spec.Containers {
3805 portsPath := fldPath.Index(i).Child("ports")
3806 for i, port := range container.Ports {
3807 idxPath := portsPath.Index(i)
3808
3809
3810
3811
3812
3813
3814 if hp, cp := port.HostPort, port.ContainerPort; (opts.ResourceIsPod || hp != 0) && hp != cp {
3815 allErrors = append(allErrors, field.Invalid(idxPath.Child("hostPort"), port.HostPort, "must match `containerPort` when `hostNetwork` is true"))
3816 }
3817 }
3818 }
3819 }
3820 return allErrors
3821 }
3822
3823
3824
3825
3826
3827 func validateImagePullSecrets(imagePullSecrets []core.LocalObjectReference, fldPath *field.Path) field.ErrorList {
3828 allErrors := field.ErrorList{}
3829 for i, currPullSecret := range imagePullSecrets {
3830 idxPath := fldPath.Index(i)
3831 strippedRef := core.LocalObjectReference{Name: currPullSecret.Name}
3832 if !reflect.DeepEqual(strippedRef, currPullSecret) {
3833 allErrors = append(allErrors, field.Invalid(idxPath, currPullSecret, "only name may be set"))
3834 }
3835 }
3836 return allErrors
3837 }
3838
3839
3840 func validateAffinity(affinity *core.Affinity, opts PodValidationOptions, fldPath *field.Path) field.ErrorList {
3841 allErrs := field.ErrorList{}
3842
3843 if affinity != nil {
3844 if affinity.NodeAffinity != nil {
3845 allErrs = append(allErrs, validateNodeAffinity(affinity.NodeAffinity, fldPath.Child("nodeAffinity"))...)
3846 }
3847 if affinity.PodAffinity != nil {
3848 allErrs = append(allErrs, validatePodAffinity(affinity.PodAffinity, opts.AllowInvalidLabelValueInSelector, fldPath.Child("podAffinity"))...)
3849 }
3850 if affinity.PodAntiAffinity != nil {
3851 allErrs = append(allErrs, validatePodAntiAffinity(affinity.PodAntiAffinity, opts.AllowInvalidLabelValueInSelector, fldPath.Child("podAntiAffinity"))...)
3852 }
3853 }
3854
3855 return allErrs
3856 }
3857
3858 func validateTaintEffect(effect *core.TaintEffect, allowEmpty bool, fldPath *field.Path) field.ErrorList {
3859 if !allowEmpty && len(*effect) == 0 {
3860 return field.ErrorList{field.Required(fldPath, "")}
3861 }
3862
3863 allErrors := field.ErrorList{}
3864 switch *effect {
3865
3866 case core.TaintEffectNoSchedule, core.TaintEffectPreferNoSchedule, core.TaintEffectNoExecute:
3867
3868 default:
3869 validValues := []core.TaintEffect{
3870 core.TaintEffectNoSchedule,
3871 core.TaintEffectPreferNoSchedule,
3872 core.TaintEffectNoExecute,
3873
3874
3875 }
3876 allErrors = append(allErrors, field.NotSupported(fldPath, *effect, validValues))
3877 }
3878 return allErrors
3879 }
3880
3881
3882 func validateOnlyAddedTolerations(newTolerations []core.Toleration, oldTolerations []core.Toleration, fldPath *field.Path) field.ErrorList {
3883 allErrs := field.ErrorList{}
3884 for _, old := range oldTolerations {
3885 found := false
3886 oldTolerationClone := old.DeepCopy()
3887 for _, newToleration := range newTolerations {
3888
3889 oldTolerationClone.TolerationSeconds = newToleration.TolerationSeconds
3890 if reflect.DeepEqual(*oldTolerationClone, newToleration) {
3891 found = true
3892 break
3893 }
3894 }
3895 if !found {
3896 allErrs = append(allErrs, field.Forbidden(fldPath, "existing toleration can not be modified except its tolerationSeconds"))
3897 return allErrs
3898 }
3899 }
3900
3901 allErrs = append(allErrs, ValidateTolerations(newTolerations, fldPath)...)
3902 return allErrs
3903 }
3904
3905 func validateOnlyDeletedSchedulingGates(newGates, oldGates []core.PodSchedulingGate, fldPath *field.Path) field.ErrorList {
3906 allErrs := field.ErrorList{}
3907 if len(newGates) == 0 {
3908 return allErrs
3909 }
3910
3911 additionalGates := make(map[string]int)
3912 for i, newGate := range newGates {
3913 additionalGates[newGate.Name] = i
3914 }
3915
3916 for _, oldGate := range oldGates {
3917 delete(additionalGates, oldGate.Name)
3918 }
3919
3920 for gate, i := range additionalGates {
3921 allErrs = append(allErrs, field.Forbidden(fldPath.Index(i).Child("name"), fmt.Sprintf("only deletion is allowed, but found new scheduling gate '%s'", gate)))
3922 }
3923
3924 return allErrs
3925 }
3926
3927 func ValidateHostAliases(hostAliases []core.HostAlias, fldPath *field.Path) field.ErrorList {
3928 allErrs := field.ErrorList{}
3929 for i, hostAlias := range hostAliases {
3930 allErrs = append(allErrs, validation.IsValidIP(fldPath.Index(i).Child("ip"), hostAlias.IP)...)
3931 for j, hostname := range hostAlias.Hostnames {
3932 allErrs = append(allErrs, ValidateDNS1123Subdomain(hostname, fldPath.Index(i).Child("hostnames").Index(j))...)
3933 }
3934 }
3935 return allErrs
3936 }
3937
3938
3939 func ValidateTolerations(tolerations []core.Toleration, fldPath *field.Path) field.ErrorList {
3940 allErrors := field.ErrorList{}
3941 for i, toleration := range tolerations {
3942 idxPath := fldPath.Index(i)
3943
3944 if len(toleration.Key) > 0 {
3945 allErrors = append(allErrors, unversionedvalidation.ValidateLabelName(toleration.Key, idxPath.Child("key"))...)
3946 }
3947
3948
3949 if len(toleration.Key) == 0 && toleration.Operator != core.TolerationOpExists {
3950 allErrors = append(allErrors, field.Invalid(idxPath.Child("operator"), toleration.Operator,
3951 "operator must be Exists when `key` is empty, which means \"match all values and all keys\""))
3952 }
3953
3954 if toleration.TolerationSeconds != nil && toleration.Effect != core.TaintEffectNoExecute {
3955 allErrors = append(allErrors, field.Invalid(idxPath.Child("effect"), toleration.Effect,
3956 "effect must be 'NoExecute' when `tolerationSeconds` is set"))
3957 }
3958
3959
3960 switch toleration.Operator {
3961
3962 case core.TolerationOpEqual, "":
3963 if errs := validation.IsValidLabelValue(toleration.Value); len(errs) != 0 {
3964 allErrors = append(allErrors, field.Invalid(idxPath.Child("operator"), toleration.Value, strings.Join(errs, ";")))
3965 }
3966 case core.TolerationOpExists:
3967 if len(toleration.Value) > 0 {
3968 allErrors = append(allErrors, field.Invalid(idxPath.Child("operator"), toleration, "value must be empty when `operator` is 'Exists'"))
3969 }
3970 default:
3971 validValues := []core.TolerationOperator{core.TolerationOpEqual, core.TolerationOpExists}
3972 allErrors = append(allErrors, field.NotSupported(idxPath.Child("operator"), toleration.Operator, validValues))
3973 }
3974
3975
3976 if len(toleration.Effect) > 0 {
3977 allErrors = append(allErrors, validateTaintEffect(&toleration.Effect, true, idxPath.Child("effect"))...)
3978 }
3979 }
3980 return allErrors
3981 }
3982
3983
3984
3985
3986 func validateContainersOnlyForPod(containers []core.Container, fldPath *field.Path) field.ErrorList {
3987 allErrs := field.ErrorList{}
3988 for i, ctr := range containers {
3989 allErrs = append(allErrs, validateContainerOnlyForPod(&ctr, fldPath.Index(i))...)
3990 }
3991 return allErrs
3992 }
3993
3994
3995
3996 func validateContainerOnlyForPod(ctr *core.Container, path *field.Path) field.ErrorList {
3997 allErrs := field.ErrorList{}
3998 if len(ctr.Image) != len(strings.TrimSpace(ctr.Image)) {
3999 allErrs = append(allErrs, field.Invalid(path.Child("image"), ctr.Image, "must not have leading or trailing whitespace"))
4000 }
4001 return allErrs
4002 }
4003
4004
4005 type PodValidationOptions struct {
4006
4007 AllowInvalidPodDeletionCost bool
4008
4009 AllowInvalidLabelValueInSelector bool
4010
4011 AllowIndivisibleHugePagesValues bool
4012
4013 AllowHostIPsField bool
4014
4015 AllowInvalidTopologySpreadConstraintLabelSelector bool
4016
4017 AllowNonLocalProjectedTokenPath bool
4018
4019 AllowNamespacedSysctlsForHostNetAndHostIPC bool
4020
4021
4022 ResourceIsPod bool
4023
4024 AllowRelaxedEnvironmentVariableValidation bool
4025 }
4026
4027
4028
4029 func validatePodMetadataAndSpec(pod *core.Pod, opts PodValidationOptions) field.ErrorList {
4030 metaPath := field.NewPath("metadata")
4031 specPath := field.NewPath("spec")
4032
4033 allErrs := ValidateObjectMeta(&pod.ObjectMeta, true, ValidatePodName, metaPath)
4034 allErrs = append(allErrs, ValidatePodSpecificAnnotations(pod.ObjectMeta.Annotations, &pod.Spec, metaPath.Child("annotations"), opts)...)
4035 allErrs = append(allErrs, ValidatePodSpec(&pod.Spec, &pod.ObjectMeta, specPath, opts)...)
4036
4037
4038
4039
4040 if pod.Spec.ServiceAccountName == "" {
4041 for vi, volume := range pod.Spec.Volumes {
4042 path := specPath.Child("volumes").Index(vi).Child("projected")
4043 if volume.Projected != nil {
4044 for si, source := range volume.Projected.Sources {
4045 saPath := path.Child("sources").Index(si).Child("serviceAccountToken")
4046 if source.ServiceAccountToken != nil {
4047 allErrs = append(allErrs, field.Forbidden(saPath, "must not be specified when serviceAccountName is not set"))
4048 }
4049 }
4050 }
4051 }
4052 }
4053
4054 allErrs = append(allErrs, validateContainersOnlyForPod(pod.Spec.Containers, specPath.Child("containers"))...)
4055 allErrs = append(allErrs, validateContainersOnlyForPod(pod.Spec.InitContainers, specPath.Child("initContainers"))...)
4056
4057
4058 return allErrs
4059 }
4060
4061
4062 func validatePodIPs(pod *core.Pod) field.ErrorList {
4063 allErrs := field.ErrorList{}
4064
4065 podIPsField := field.NewPath("status", "podIPs")
4066
4067
4068 for i, podIP := range pod.Status.PodIPs {
4069 allErrs = append(allErrs, validation.IsValidIP(podIPsField.Index(i), podIP.IP)...)
4070 }
4071
4072
4073
4074
4075 if len(pod.Status.PodIPs) > 1 {
4076 podIPs := make([]string, 0, len(pod.Status.PodIPs))
4077 for _, podIP := range pod.Status.PodIPs {
4078 podIPs = append(podIPs, podIP.IP)
4079 }
4080
4081 dualStack, err := netutils.IsDualStackIPStrings(podIPs)
4082 if err != nil {
4083 allErrs = append(allErrs, field.InternalError(podIPsField, fmt.Errorf("failed to check for dual stack with error:%v", err)))
4084 }
4085
4086
4087 if !dualStack || len(podIPs) > 2 {
4088 allErrs = append(allErrs, field.Invalid(podIPsField, pod.Status.PodIPs, "may specify no more than one IP for each IP family"))
4089 }
4090
4091
4092 seen := sets.Set[string]{}
4093 for i, podIP := range pod.Status.PodIPs {
4094 if seen.Has(podIP.IP) {
4095 allErrs = append(allErrs, field.Duplicate(podIPsField.Index(i), podIP))
4096 }
4097 seen.Insert(podIP.IP)
4098 }
4099 }
4100
4101 return allErrs
4102 }
4103
4104
4105 func validateHostIPs(pod *core.Pod) field.ErrorList {
4106 allErrs := field.ErrorList{}
4107
4108 if len(pod.Status.HostIPs) == 0 {
4109 return allErrs
4110 }
4111
4112 hostIPsField := field.NewPath("status", "hostIPs")
4113
4114
4115 if pod.Status.HostIP != pod.Status.HostIPs[0].IP {
4116 allErrs = append(allErrs, field.Invalid(hostIPsField.Index(0).Child("ip"), pod.Status.HostIPs[0].IP, "must be equal to `hostIP`"))
4117 }
4118
4119
4120 for i, hostIP := range pod.Status.HostIPs {
4121 allErrs = append(allErrs, validation.IsValidIP(hostIPsField.Index(i), hostIP.IP)...)
4122 }
4123
4124
4125
4126
4127 if len(pod.Status.HostIPs) > 1 {
4128 seen := sets.Set[string]{}
4129 hostIPs := make([]string, 0, len(pod.Status.HostIPs))
4130
4131
4132 for i, hostIP := range pod.Status.HostIPs {
4133 hostIPs = append(hostIPs, hostIP.IP)
4134 if seen.Has(hostIP.IP) {
4135 allErrs = append(allErrs, field.Duplicate(hostIPsField.Index(i), hostIP))
4136 }
4137 seen.Insert(hostIP.IP)
4138 }
4139
4140 dualStack, err := netutils.IsDualStackIPStrings(hostIPs)
4141 if err != nil {
4142 allErrs = append(allErrs, field.InternalError(hostIPsField, fmt.Errorf("failed to check for dual stack with error:%v", err)))
4143 }
4144
4145
4146 if !dualStack || len(hostIPs) > 2 {
4147 allErrs = append(allErrs, field.Invalid(hostIPsField, pod.Status.HostIPs, "may specify no more than one IP for each IP family"))
4148 }
4149 }
4150
4151 return allErrs
4152 }
4153
4154
4155
4156
4157
4158
4159
4160 func ValidatePodSpec(spec *core.PodSpec, podMeta *metav1.ObjectMeta, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
4161 allErrs := field.ErrorList{}
4162
4163 var gracePeriod int64
4164 if spec.TerminationGracePeriodSeconds != nil {
4165
4166 gracePeriod = *spec.TerminationGracePeriodSeconds
4167 }
4168
4169
4170
4171 hostUsers := spec.SecurityContext == nil || spec.SecurityContext.HostUsers == nil || *spec.SecurityContext.HostUsers
4172
4173 vols, vErrs := ValidateVolumes(spec.Volumes, podMeta, fldPath.Child("volumes"), opts)
4174 allErrs = append(allErrs, vErrs...)
4175 podClaimNames := gatherPodResourceClaimNames(spec.ResourceClaims)
4176 allErrs = append(allErrs, validatePodResourceClaims(podMeta, spec.ResourceClaims, fldPath.Child("resourceClaims"))...)
4177 allErrs = append(allErrs, validateContainers(spec.Containers, vols, podClaimNames, gracePeriod, fldPath.Child("containers"), opts, &spec.RestartPolicy, hostUsers)...)
4178 allErrs = append(allErrs, validateInitContainers(spec.InitContainers, spec.Containers, vols, podClaimNames, gracePeriod, fldPath.Child("initContainers"), opts, &spec.RestartPolicy, hostUsers)...)
4179 allErrs = append(allErrs, validateEphemeralContainers(spec.EphemeralContainers, spec.Containers, spec.InitContainers, vols, podClaimNames, fldPath.Child("ephemeralContainers"), opts, &spec.RestartPolicy, hostUsers)...)
4180 allErrs = append(allErrs, validatePodHostNetworkDeps(spec, fldPath, opts)...)
4181 allErrs = append(allErrs, validateRestartPolicy(&spec.RestartPolicy, fldPath.Child("restartPolicy"))...)
4182 allErrs = append(allErrs, validateDNSPolicy(&spec.DNSPolicy, fldPath.Child("dnsPolicy"))...)
4183 allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.NodeSelector, fldPath.Child("nodeSelector"))...)
4184 allErrs = append(allErrs, validatePodSpecSecurityContext(spec.SecurityContext, spec, fldPath, fldPath.Child("securityContext"), opts)...)
4185 allErrs = append(allErrs, validateImagePullSecrets(spec.ImagePullSecrets, fldPath.Child("imagePullSecrets"))...)
4186 allErrs = append(allErrs, validateAffinity(spec.Affinity, opts, fldPath.Child("affinity"))...)
4187 allErrs = append(allErrs, validatePodDNSConfig(spec.DNSConfig, &spec.DNSPolicy, fldPath.Child("dnsConfig"), opts)...)
4188 allErrs = append(allErrs, validateReadinessGates(spec.ReadinessGates, fldPath.Child("readinessGates"))...)
4189 allErrs = append(allErrs, validateSchedulingGates(spec.SchedulingGates, fldPath.Child("schedulingGates"))...)
4190 allErrs = append(allErrs, validateTopologySpreadConstraints(spec.TopologySpreadConstraints, fldPath.Child("topologySpreadConstraints"), opts)...)
4191 allErrs = append(allErrs, validateWindowsHostProcessPod(spec, fldPath)...)
4192 allErrs = append(allErrs, validateHostUsers(spec, fldPath)...)
4193 if len(spec.ServiceAccountName) > 0 {
4194 for _, msg := range ValidateServiceAccountName(spec.ServiceAccountName, false) {
4195 allErrs = append(allErrs, field.Invalid(fldPath.Child("serviceAccountName"), spec.ServiceAccountName, msg))
4196 }
4197 }
4198
4199 if len(spec.NodeName) > 0 {
4200 for _, msg := range ValidateNodeName(spec.NodeName, false) {
4201 allErrs = append(allErrs, field.Invalid(fldPath.Child("nodeName"), spec.NodeName, msg))
4202 }
4203 }
4204
4205 if spec.ActiveDeadlineSeconds != nil {
4206 value := *spec.ActiveDeadlineSeconds
4207 if value < 1 || value > math.MaxInt32 {
4208 allErrs = append(allErrs, field.Invalid(fldPath.Child("activeDeadlineSeconds"), value, validation.InclusiveRangeError(1, math.MaxInt32)))
4209 }
4210 }
4211
4212 if len(spec.Hostname) > 0 {
4213 allErrs = append(allErrs, ValidateDNS1123Label(spec.Hostname, fldPath.Child("hostname"))...)
4214 }
4215
4216 if len(spec.Subdomain) > 0 {
4217 allErrs = append(allErrs, ValidateDNS1123Label(spec.Subdomain, fldPath.Child("subdomain"))...)
4218 }
4219
4220 if len(spec.Tolerations) > 0 {
4221 allErrs = append(allErrs, ValidateTolerations(spec.Tolerations, fldPath.Child("tolerations"))...)
4222 }
4223
4224 if len(spec.HostAliases) > 0 {
4225 allErrs = append(allErrs, ValidateHostAliases(spec.HostAliases, fldPath.Child("hostAliases"))...)
4226 }
4227
4228 if len(spec.PriorityClassName) > 0 {
4229 for _, msg := range ValidatePriorityClassName(spec.PriorityClassName, false) {
4230 allErrs = append(allErrs, field.Invalid(fldPath.Child("priorityClassName"), spec.PriorityClassName, msg))
4231 }
4232 }
4233
4234 if spec.RuntimeClassName != nil {
4235 allErrs = append(allErrs, ValidateRuntimeClassName(*spec.RuntimeClassName, fldPath.Child("runtimeClassName"))...)
4236 }
4237
4238 if spec.PreemptionPolicy != nil {
4239 allErrs = append(allErrs, ValidatePreemptionPolicy(spec.PreemptionPolicy, fldPath.Child("preemptionPolicy"))...)
4240 }
4241
4242 if spec.Overhead != nil {
4243 allErrs = append(allErrs, validateOverhead(spec.Overhead, fldPath.Child("overhead"), opts)...)
4244 }
4245
4246 if spec.OS != nil {
4247 osErrs := validateOS(spec, fldPath.Child("os"), opts)
4248 switch {
4249 case len(osErrs) > 0:
4250 allErrs = append(allErrs, osErrs...)
4251 case spec.OS.Name == core.Linux:
4252 allErrs = append(allErrs, validateLinux(spec, fldPath)...)
4253 case spec.OS.Name == core.Windows:
4254 allErrs = append(allErrs, validateWindows(spec, fldPath)...)
4255 }
4256 }
4257 return allErrs
4258 }
4259
4260 func validateLinux(spec *core.PodSpec, fldPath *field.Path) field.ErrorList {
4261 allErrs := field.ErrorList{}
4262 securityContext := spec.SecurityContext
4263 if securityContext != nil && securityContext.WindowsOptions != nil {
4264 allErrs = append(allErrs, field.Forbidden(fldPath.Child("securityContext").Child("windowsOptions"), "windows options cannot be set for a linux pod"))
4265 }
4266 podshelper.VisitContainersWithPath(spec, fldPath, func(c *core.Container, cFldPath *field.Path) bool {
4267 sc := c.SecurityContext
4268 if sc != nil && sc.WindowsOptions != nil {
4269 fldPath := cFldPath.Child("securityContext")
4270 allErrs = append(allErrs, field.Forbidden(fldPath.Child("windowsOptions"), "windows options cannot be set for a linux pod"))
4271 }
4272 return true
4273 })
4274 return allErrs
4275 }
4276
4277 func validateWindows(spec *core.PodSpec, fldPath *field.Path) field.ErrorList {
4278 allErrs := field.ErrorList{}
4279 securityContext := spec.SecurityContext
4280
4281 if securityContext != nil {
4282 if securityContext.AppArmorProfile != nil {
4283 allErrs = append(allErrs, field.Forbidden(fldPath.Child("securityContext").Child("appArmorProfile"), "cannot be set for a windows pod"))
4284 }
4285 if securityContext.SELinuxOptions != nil {
4286 allErrs = append(allErrs, field.Forbidden(fldPath.Child("securityContext").Child("seLinuxOptions"), "cannot be set for a windows pod"))
4287 }
4288 if securityContext.HostUsers != nil {
4289 allErrs = append(allErrs, field.Forbidden(fldPath.Child("hostUsers"), "cannot be set for a windows pod"))
4290 }
4291 if securityContext.HostPID {
4292 allErrs = append(allErrs, field.Forbidden(fldPath.Child("hostPID"), "cannot be set for a windows pod"))
4293 }
4294 if securityContext.HostIPC {
4295 allErrs = append(allErrs, field.Forbidden(fldPath.Child("hostIPC"), "cannot be set for a windows pod"))
4296 }
4297 if securityContext.SeccompProfile != nil {
4298 allErrs = append(allErrs, field.Forbidden(fldPath.Child("securityContext").Child("seccompProfile"), "cannot be set for a windows pod"))
4299 }
4300 if securityContext.FSGroup != nil {
4301 allErrs = append(allErrs, field.Forbidden(fldPath.Child("securityContext").Child("fsGroup"), "cannot be set for a windows pod"))
4302 }
4303 if securityContext.FSGroupChangePolicy != nil {
4304 allErrs = append(allErrs, field.Forbidden(fldPath.Child("securityContext").Child("fsGroupChangePolicy"), "cannot be set for a windows pod"))
4305 }
4306 if len(securityContext.Sysctls) > 0 {
4307 allErrs = append(allErrs, field.Forbidden(fldPath.Child("securityContext").Child("sysctls"), "cannot be set for a windows pod"))
4308 }
4309 if securityContext.ShareProcessNamespace != nil {
4310 allErrs = append(allErrs, field.Forbidden(fldPath.Child("shareProcessNamespace"), "cannot be set for a windows pod"))
4311 }
4312 if securityContext.RunAsUser != nil {
4313 allErrs = append(allErrs, field.Forbidden(fldPath.Child("securityContext").Child("runAsUser"), "cannot be set for a windows pod"))
4314 }
4315 if securityContext.RunAsGroup != nil {
4316 allErrs = append(allErrs, field.Forbidden(fldPath.Child("securityContext").Child("runAsGroup"), "cannot be set for a windows pod"))
4317 }
4318 if securityContext.SupplementalGroups != nil {
4319 allErrs = append(allErrs, field.Forbidden(fldPath.Child("securityContext").Child("supplementalGroups"), "cannot be set for a windows pod"))
4320 }
4321 }
4322 podshelper.VisitContainersWithPath(spec, fldPath, func(c *core.Container, cFldPath *field.Path) bool {
4323
4324 sc := c.SecurityContext
4325
4326
4327
4328
4329 if sc != nil {
4330 fldPath := cFldPath.Child("securityContext")
4331 if sc.AppArmorProfile != nil {
4332 allErrs = append(allErrs, field.Forbidden(fldPath.Child("appArmorProfile"), "cannot be set for a windows pod"))
4333 }
4334 if sc.SELinuxOptions != nil {
4335 allErrs = append(allErrs, field.Forbidden(fldPath.Child("seLinuxOptions"), "cannot be set for a windows pod"))
4336 }
4337 if sc.SeccompProfile != nil {
4338 allErrs = append(allErrs, field.Forbidden(fldPath.Child("seccompProfile"), "cannot be set for a windows pod"))
4339 }
4340 if sc.Capabilities != nil {
4341 allErrs = append(allErrs, field.Forbidden(fldPath.Child("capabilities"), "cannot be set for a windows pod"))
4342 }
4343 if sc.ReadOnlyRootFilesystem != nil {
4344 allErrs = append(allErrs, field.Forbidden(fldPath.Child("readOnlyRootFilesystem"), "cannot be set for a windows pod"))
4345 }
4346 if sc.Privileged != nil {
4347 allErrs = append(allErrs, field.Forbidden(fldPath.Child("privileged"), "cannot be set for a windows pod"))
4348 }
4349 if sc.AllowPrivilegeEscalation != nil {
4350 allErrs = append(allErrs, field.Forbidden(fldPath.Child("allowPrivilegeEscalation"), "cannot be set for a windows pod"))
4351 }
4352 if sc.ProcMount != nil {
4353 allErrs = append(allErrs, field.Forbidden(fldPath.Child("procMount"), "cannot be set for a windows pod"))
4354 }
4355 if sc.RunAsUser != nil {
4356 allErrs = append(allErrs, field.Forbidden(fldPath.Child("runAsUser"), "cannot be set for a windows pod"))
4357 }
4358 if sc.RunAsGroup != nil {
4359 allErrs = append(allErrs, field.Forbidden(fldPath.Child("runAsGroup"), "cannot be set for a windows pod"))
4360 }
4361 }
4362 return true
4363 })
4364 return allErrs
4365 }
4366
4367
4368 func ValidateNodeSelectorRequirement(rq core.NodeSelectorRequirement, fldPath *field.Path) field.ErrorList {
4369 allErrs := field.ErrorList{}
4370 switch rq.Operator {
4371 case core.NodeSelectorOpIn, core.NodeSelectorOpNotIn:
4372 if len(rq.Values) == 0 {
4373 allErrs = append(allErrs, field.Required(fldPath.Child("values"), "must be specified when `operator` is 'In' or 'NotIn'"))
4374 }
4375 case core.NodeSelectorOpExists, core.NodeSelectorOpDoesNotExist:
4376 if len(rq.Values) > 0 {
4377 allErrs = append(allErrs, field.Forbidden(fldPath.Child("values"), "may not be specified when `operator` is 'Exists' or 'DoesNotExist'"))
4378 }
4379
4380 case core.NodeSelectorOpGt, core.NodeSelectorOpLt:
4381 if len(rq.Values) != 1 {
4382 allErrs = append(allErrs, field.Required(fldPath.Child("values"), "must be specified single value when `operator` is 'Lt' or 'Gt'"))
4383 }
4384 default:
4385 allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), rq.Operator, "not a valid selector operator"))
4386 }
4387
4388 allErrs = append(allErrs, unversionedvalidation.ValidateLabelName(rq.Key, fldPath.Child("key"))...)
4389
4390 return allErrs
4391 }
4392
4393 var nodeFieldSelectorValidators = map[string]func(string, bool) []string{
4394 metav1.ObjectNameField: ValidateNodeName,
4395 }
4396
4397
4398 func ValidateNodeFieldSelectorRequirement(req core.NodeSelectorRequirement, fldPath *field.Path) field.ErrorList {
4399 allErrs := field.ErrorList{}
4400
4401 switch req.Operator {
4402 case core.NodeSelectorOpIn, core.NodeSelectorOpNotIn:
4403 if len(req.Values) != 1 {
4404 allErrs = append(allErrs, field.Required(fldPath.Child("values"),
4405 "must be only one value when `operator` is 'In' or 'NotIn' for node field selector"))
4406 }
4407 default:
4408 allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), req.Operator, "not a valid selector operator"))
4409 }
4410
4411 if vf, found := nodeFieldSelectorValidators[req.Key]; !found {
4412 allErrs = append(allErrs, field.Invalid(fldPath.Child("key"), req.Key, "not a valid field selector key"))
4413 } else {
4414 for i, v := range req.Values {
4415 for _, msg := range vf(v, false) {
4416 allErrs = append(allErrs, field.Invalid(fldPath.Child("values").Index(i), v, msg))
4417 }
4418 }
4419 }
4420
4421 return allErrs
4422 }
4423
4424
4425 func ValidateNodeSelectorTerm(term core.NodeSelectorTerm, fldPath *field.Path) field.ErrorList {
4426 allErrs := field.ErrorList{}
4427
4428 for j, req := range term.MatchExpressions {
4429 allErrs = append(allErrs, ValidateNodeSelectorRequirement(req, fldPath.Child("matchExpressions").Index(j))...)
4430 }
4431
4432 for j, req := range term.MatchFields {
4433 allErrs = append(allErrs, ValidateNodeFieldSelectorRequirement(req, fldPath.Child("matchFields").Index(j))...)
4434 }
4435
4436 return allErrs
4437 }
4438
4439
4440 func ValidateNodeSelector(nodeSelector *core.NodeSelector, fldPath *field.Path) field.ErrorList {
4441 allErrs := field.ErrorList{}
4442
4443 termFldPath := fldPath.Child("nodeSelectorTerms")
4444 if len(nodeSelector.NodeSelectorTerms) == 0 {
4445 return append(allErrs, field.Required(termFldPath, "must have at least one node selector term"))
4446 }
4447
4448 for i, term := range nodeSelector.NodeSelectorTerms {
4449 allErrs = append(allErrs, ValidateNodeSelectorTerm(term, termFldPath.Index(i))...)
4450 }
4451
4452 return allErrs
4453 }
4454
4455
4456
4457 func validateTopologySelectorLabelRequirement(rq core.TopologySelectorLabelRequirement, fldPath *field.Path) (sets.Set[string], field.ErrorList) {
4458 allErrs := field.ErrorList{}
4459 valueSet := make(sets.Set[string])
4460 valuesPath := fldPath.Child("values")
4461 if len(rq.Values) == 0 {
4462 allErrs = append(allErrs, field.Required(valuesPath, ""))
4463 }
4464
4465
4466 for i, value := range rq.Values {
4467 if valueSet.Has(value) {
4468 allErrs = append(allErrs, field.Duplicate(valuesPath.Index(i), value))
4469 }
4470 valueSet.Insert(value)
4471 }
4472
4473 allErrs = append(allErrs, unversionedvalidation.ValidateLabelName(rq.Key, fldPath.Child("key"))...)
4474
4475 return valueSet, allErrs
4476 }
4477
4478
4479
4480 func ValidateTopologySelectorTerm(term core.TopologySelectorTerm, fldPath *field.Path) (map[string]sets.Set[string], field.ErrorList) {
4481 allErrs := field.ErrorList{}
4482 exprMap := make(map[string]sets.Set[string])
4483 exprPath := fldPath.Child("matchLabelExpressions")
4484
4485
4486 for i, req := range term.MatchLabelExpressions {
4487 idxPath := exprPath.Index(i)
4488 valueSet, exprErrs := validateTopologySelectorLabelRequirement(req, idxPath)
4489 allErrs = append(allErrs, exprErrs...)
4490
4491
4492 if _, exists := exprMap[req.Key]; exists {
4493 allErrs = append(allErrs, field.Duplicate(idxPath.Child("key"), req.Key))
4494 }
4495 exprMap[req.Key] = valueSet
4496 }
4497
4498 return exprMap, allErrs
4499 }
4500
4501
4502 func ValidateAvoidPodsInNodeAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList {
4503 allErrs := field.ErrorList{}
4504
4505 v1Avoids, err := schedulinghelper.GetAvoidPodsFromNodeAnnotations(annotations)
4506 if err != nil {
4507 allErrs = append(allErrs, field.Invalid(fldPath.Child("AvoidPods"), core.PreferAvoidPodsAnnotationKey, err.Error()))
4508 return allErrs
4509 }
4510 var avoids core.AvoidPods
4511 if err := corev1.Convert_v1_AvoidPods_To_core_AvoidPods(&v1Avoids, &avoids, nil); err != nil {
4512 allErrs = append(allErrs, field.Invalid(fldPath.Child("AvoidPods"), core.PreferAvoidPodsAnnotationKey, err.Error()))
4513 return allErrs
4514 }
4515
4516 if len(avoids.PreferAvoidPods) != 0 {
4517 for i, pa := range avoids.PreferAvoidPods {
4518 idxPath := fldPath.Child(core.PreferAvoidPodsAnnotationKey).Index(i)
4519 allErrs = append(allErrs, validatePreferAvoidPodsEntry(pa, idxPath)...)
4520 }
4521 }
4522
4523 return allErrs
4524 }
4525
4526
4527 func validatePreferAvoidPodsEntry(avoidPodEntry core.PreferAvoidPodsEntry, fldPath *field.Path) field.ErrorList {
4528 allErrors := field.ErrorList{}
4529 if avoidPodEntry.PodSignature.PodController == nil {
4530 allErrors = append(allErrors, field.Required(fldPath.Child("PodSignature"), ""))
4531 } else {
4532 if !*(avoidPodEntry.PodSignature.PodController.Controller) {
4533 allErrors = append(allErrors,
4534 field.Invalid(fldPath.Child("PodSignature").Child("PodController").Child("Controller"),
4535 *(avoidPodEntry.PodSignature.PodController.Controller), "must point to a controller"))
4536 }
4537 }
4538 return allErrors
4539 }
4540
4541
4542 func ValidatePreferredSchedulingTerms(terms []core.PreferredSchedulingTerm, fldPath *field.Path) field.ErrorList {
4543 allErrs := field.ErrorList{}
4544
4545 for i, term := range terms {
4546 if term.Weight <= 0 || term.Weight > 100 {
4547 allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("weight"), term.Weight, "must be in the range 1-100"))
4548 }
4549
4550 allErrs = append(allErrs, ValidateNodeSelectorTerm(term.Preference, fldPath.Index(i).Child("preference"))...)
4551 }
4552 return allErrs
4553 }
4554
4555
4556 func validatePodAffinityTerm(podAffinityTerm core.PodAffinityTerm, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList {
4557 allErrs := field.ErrorList{}
4558
4559 allErrs = append(allErrs, ValidatePodAffinityTermSelector(podAffinityTerm, allowInvalidLabelValueInSelector, fldPath)...)
4560 for _, name := range podAffinityTerm.Namespaces {
4561 for _, msg := range ValidateNamespaceName(name, false) {
4562 allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), name, msg))
4563 }
4564 }
4565 allErrs = append(allErrs, validateMatchLabelKeysAndMismatchLabelKeys(fldPath, podAffinityTerm.MatchLabelKeys, podAffinityTerm.MismatchLabelKeys, podAffinityTerm.LabelSelector)...)
4566 if len(podAffinityTerm.TopologyKey) == 0 {
4567 allErrs = append(allErrs, field.Required(fldPath.Child("topologyKey"), "can not be empty"))
4568 }
4569 return append(allErrs, unversionedvalidation.ValidateLabelName(podAffinityTerm.TopologyKey, fldPath.Child("topologyKey"))...)
4570 }
4571
4572
4573 func validatePodAffinityTerms(podAffinityTerms []core.PodAffinityTerm, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList {
4574 allErrs := field.ErrorList{}
4575 for i, podAffinityTerm := range podAffinityTerms {
4576 allErrs = append(allErrs, validatePodAffinityTerm(podAffinityTerm, allowInvalidLabelValueInSelector, fldPath.Index(i))...)
4577 }
4578 return allErrs
4579 }
4580
4581
4582 func validateWeightedPodAffinityTerms(weightedPodAffinityTerms []core.WeightedPodAffinityTerm, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList {
4583 allErrs := field.ErrorList{}
4584 for j, weightedTerm := range weightedPodAffinityTerms {
4585 if weightedTerm.Weight <= 0 || weightedTerm.Weight > 100 {
4586 allErrs = append(allErrs, field.Invalid(fldPath.Index(j).Child("weight"), weightedTerm.Weight, "must be in the range 1-100"))
4587 }
4588 allErrs = append(allErrs, validatePodAffinityTerm(weightedTerm.PodAffinityTerm, allowInvalidLabelValueInSelector, fldPath.Index(j).Child("podAffinityTerm"))...)
4589 }
4590 return allErrs
4591 }
4592
4593
4594 func validatePodAntiAffinity(podAntiAffinity *core.PodAntiAffinity, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList {
4595 allErrs := field.ErrorList{}
4596
4597
4598
4599
4600
4601 if podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil {
4602 allErrs = append(allErrs, validatePodAffinityTerms(podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution, allowInvalidLabelValueInSelector,
4603 fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...)
4604 }
4605 if podAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil {
4606 allErrs = append(allErrs, validateWeightedPodAffinityTerms(podAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution, allowInvalidLabelValueInSelector,
4607 fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...)
4608 }
4609 return allErrs
4610 }
4611
4612
4613 func validateNodeAffinity(na *core.NodeAffinity, fldPath *field.Path) field.ErrorList {
4614 allErrs := field.ErrorList{}
4615
4616
4617
4618
4619 if na.RequiredDuringSchedulingIgnoredDuringExecution != nil {
4620 allErrs = append(allErrs, ValidateNodeSelector(na.RequiredDuringSchedulingIgnoredDuringExecution, fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...)
4621 }
4622 if len(na.PreferredDuringSchedulingIgnoredDuringExecution) > 0 {
4623 allErrs = append(allErrs, ValidatePreferredSchedulingTerms(na.PreferredDuringSchedulingIgnoredDuringExecution, fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...)
4624 }
4625 return allErrs
4626 }
4627
4628
4629 func validatePodAffinity(podAffinity *core.PodAffinity, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList {
4630 allErrs := field.ErrorList{}
4631
4632
4633
4634
4635
4636 if podAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil {
4637 allErrs = append(allErrs, validatePodAffinityTerms(podAffinity.RequiredDuringSchedulingIgnoredDuringExecution, allowInvalidLabelValueInSelector,
4638 fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...)
4639 }
4640 if podAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil {
4641 allErrs = append(allErrs, validateWeightedPodAffinityTerms(podAffinity.PreferredDuringSchedulingIgnoredDuringExecution, allowInvalidLabelValueInSelector,
4642 fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...)
4643 }
4644 return allErrs
4645 }
4646
4647 func validateSeccompProfileField(sp *core.SeccompProfile, fldPath *field.Path) field.ErrorList {
4648 allErrs := field.ErrorList{}
4649 if sp == nil {
4650 return allErrs
4651 }
4652
4653 if err := validateSeccompProfileType(fldPath.Child("type"), sp.Type); err != nil {
4654 allErrs = append(allErrs, err)
4655 }
4656
4657 if sp.Type == core.SeccompProfileTypeLocalhost {
4658 if sp.LocalhostProfile == nil {
4659 allErrs = append(allErrs, field.Required(fldPath.Child("localhostProfile"), "must be set when seccomp type is Localhost"))
4660 } else {
4661 allErrs = append(allErrs, validateLocalDescendingPath(*sp.LocalhostProfile, fldPath.Child("localhostProfile"))...)
4662 }
4663 } else {
4664 if sp.LocalhostProfile != nil {
4665 allErrs = append(allErrs, field.Invalid(fldPath.Child("localhostProfile"), sp, "can only be set when seccomp type is Localhost"))
4666 }
4667 }
4668
4669 return allErrs
4670 }
4671
4672 func ValidateSeccompProfile(p string, fldPath *field.Path) field.ErrorList {
4673 if p == core.SeccompProfileRuntimeDefault || p == core.DeprecatedSeccompProfileDockerDefault {
4674 return nil
4675 }
4676 if p == v1.SeccompProfileNameUnconfined {
4677 return nil
4678 }
4679 if strings.HasPrefix(p, v1.SeccompLocalhostProfileNamePrefix) {
4680 return validateLocalDescendingPath(strings.TrimPrefix(p, v1.SeccompLocalhostProfileNamePrefix), fldPath)
4681 }
4682 return field.ErrorList{field.Invalid(fldPath, p, "must be a valid seccomp profile")}
4683 }
4684
4685 func ValidateSeccompPodAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList {
4686 allErrs := field.ErrorList{}
4687 if p, exists := annotations[core.SeccompPodAnnotationKey]; exists {
4688 allErrs = append(allErrs, ValidateSeccompProfile(p, fldPath.Child(core.SeccompPodAnnotationKey))...)
4689 }
4690 for k, p := range annotations {
4691 if strings.HasPrefix(k, core.SeccompContainerAnnotationKeyPrefix) {
4692 allErrs = append(allErrs, ValidateSeccompProfile(p, fldPath.Child(k))...)
4693 }
4694 }
4695
4696 return allErrs
4697 }
4698
4699
4700 func validateSeccompProfileType(fldPath *field.Path, seccompProfileType core.SeccompProfileType) *field.Error {
4701 switch seccompProfileType {
4702 case core.SeccompProfileTypeLocalhost, core.SeccompProfileTypeRuntimeDefault, core.SeccompProfileTypeUnconfined:
4703 return nil
4704 case "":
4705 return field.Required(fldPath, "type is required when seccompProfile is set")
4706 default:
4707 return field.NotSupported(fldPath, seccompProfileType, []core.SeccompProfileType{core.SeccompProfileTypeLocalhost, core.SeccompProfileTypeRuntimeDefault, core.SeccompProfileTypeUnconfined})
4708 }
4709 }
4710
4711 func ValidateAppArmorProfileField(profile *core.AppArmorProfile, fldPath *field.Path) field.ErrorList {
4712 if profile == nil {
4713 return nil
4714 }
4715
4716 allErrs := field.ErrorList{}
4717
4718 switch profile.Type {
4719 case core.AppArmorProfileTypeLocalhost:
4720 if profile.LocalhostProfile == nil {
4721 allErrs = append(allErrs, field.Required(fldPath.Child("localhostProfile"), "must be set when AppArmor type is Localhost"))
4722 } else {
4723 localhostProfile := strings.TrimSpace(*profile.LocalhostProfile)
4724 if localhostProfile != *profile.LocalhostProfile {
4725 allErrs = append(allErrs, field.Invalid(fldPath.Child("localhostProfile"), *profile.LocalhostProfile, "must not be padded with whitespace"))
4726 } else if localhostProfile == "" {
4727 allErrs = append(allErrs, field.Required(fldPath.Child("localhostProfile"), "must be set when AppArmor type is Localhost"))
4728 }
4729
4730 const maxLocalhostProfileLength = 4095
4731 if len(*profile.LocalhostProfile) > maxLocalhostProfileLength {
4732 allErrs = append(allErrs, field.TooLongMaxLength(fldPath.Child("localhostProfile"), *profile.LocalhostProfile, maxLocalhostProfileLength))
4733 }
4734 }
4735
4736 case core.AppArmorProfileTypeRuntimeDefault, core.AppArmorProfileTypeUnconfined:
4737 if profile.LocalhostProfile != nil {
4738 allErrs = append(allErrs, field.Invalid(fldPath.Child("localhostProfile"), profile.LocalhostProfile, "can only be set when AppArmor type is Localhost"))
4739 }
4740
4741 case "":
4742 allErrs = append(allErrs, field.Required(fldPath.Child("type"), "type is required when appArmorProfile is set"))
4743
4744 default:
4745 allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), profile.Type,
4746 []core.AppArmorProfileType{core.AppArmorProfileTypeLocalhost, core.AppArmorProfileTypeRuntimeDefault, core.AppArmorProfileTypeUnconfined}))
4747 }
4748
4749 return allErrs
4750
4751 }
4752
4753 func ValidateAppArmorPodAnnotations(annotations map[string]string, spec *core.PodSpec, fldPath *field.Path) field.ErrorList {
4754 allErrs := field.ErrorList{}
4755 for k, p := range annotations {
4756 if !strings.HasPrefix(k, v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix) {
4757 continue
4758 }
4759 containerName := strings.TrimPrefix(k, v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix)
4760 if !podSpecHasContainer(spec, containerName) {
4761 allErrs = append(allErrs, field.Invalid(fldPath.Key(k), containerName, "container not found"))
4762 }
4763
4764 if err := ValidateAppArmorProfileFormat(p); err != nil {
4765 allErrs = append(allErrs, field.Invalid(fldPath.Key(k), p, err.Error()))
4766 }
4767 }
4768
4769 return allErrs
4770 }
4771
4772 func ValidateAppArmorProfileFormat(profile string) error {
4773 if profile == "" || profile == v1.DeprecatedAppArmorBetaProfileRuntimeDefault || profile == v1.DeprecatedAppArmorBetaProfileNameUnconfined {
4774 return nil
4775 }
4776 if !strings.HasPrefix(profile, v1.DeprecatedAppArmorBetaProfileNamePrefix) {
4777 return fmt.Errorf("invalid AppArmor profile name: %q", profile)
4778 }
4779 return nil
4780 }
4781
4782
4783 func validateAppArmorAnnotationsAndFieldsMatchOnCreate(objectMeta metav1.ObjectMeta, podSpec *core.PodSpec, specPath *field.Path) field.ErrorList {
4784 if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmorFields) {
4785 return nil
4786 }
4787 if podSpec.OS != nil && podSpec.OS.Name == core.Windows {
4788
4789 return nil
4790 }
4791
4792 allErrs := field.ErrorList{}
4793
4794 var podProfile *core.AppArmorProfile
4795 if podSpec.SecurityContext != nil {
4796 podProfile = podSpec.SecurityContext.AppArmorProfile
4797 }
4798 podshelper.VisitContainersWithPath(podSpec, specPath, func(c *core.Container, cFldPath *field.Path) bool {
4799 containerProfile := podProfile
4800 if c.SecurityContext != nil && c.SecurityContext.AppArmorProfile != nil {
4801 containerProfile = c.SecurityContext.AppArmorProfile
4802 }
4803
4804 if containerProfile == nil {
4805 return true
4806 }
4807
4808 key := core.DeprecatedAppArmorAnnotationKeyPrefix + c.Name
4809 if annotation, found := objectMeta.Annotations[key]; found {
4810 apparmorPath := cFldPath.Child("securityContext").Child("appArmorProfile")
4811
4812 switch containerProfile.Type {
4813 case core.AppArmorProfileTypeUnconfined:
4814 if annotation != core.DeprecatedAppArmorAnnotationValueUnconfined {
4815 allErrs = append(allErrs, field.Forbidden(apparmorPath.Child("type"), "apparmor type in annotation and field must match"))
4816 }
4817
4818 case core.AppArmorProfileTypeRuntimeDefault:
4819 if annotation != core.DeprecatedAppArmorAnnotationValueRuntimeDefault {
4820 allErrs = append(allErrs, field.Forbidden(apparmorPath.Child("type"), "apparmor type in annotation and field must match"))
4821 }
4822
4823 case core.AppArmorProfileTypeLocalhost:
4824 if !strings.HasPrefix(annotation, core.DeprecatedAppArmorAnnotationValueLocalhostPrefix) {
4825 allErrs = append(allErrs, field.Forbidden(apparmorPath.Child("type"), "apparmor type in annotation and field must match"))
4826 } else if containerProfile.LocalhostProfile == nil || strings.TrimPrefix(annotation, core.DeprecatedAppArmorAnnotationValueLocalhostPrefix) != *containerProfile.LocalhostProfile {
4827 allErrs = append(allErrs, field.Forbidden(apparmorPath.Child("localhostProfile"), "apparmor profile in annotation and field must match"))
4828 }
4829 }
4830 }
4831 return true
4832 })
4833
4834 return allErrs
4835 }
4836
4837 func podSpecHasContainer(spec *core.PodSpec, containerName string) bool {
4838 var hasContainer bool
4839 podshelper.VisitContainersWithPath(spec, field.NewPath("spec"), func(c *core.Container, _ *field.Path) bool {
4840 if c.Name == containerName {
4841 hasContainer = true
4842 return false
4843 }
4844 return true
4845 })
4846 return hasContainer
4847 }
4848
4849 const (
4850
4851 SysctlSegmentFmt string = "[a-z0-9]([-_a-z0-9]*[a-z0-9])?"
4852
4853
4854 SysctlContainSlashFmt string = "(" + SysctlSegmentFmt + "[\\./])*" + SysctlSegmentFmt
4855
4856
4857 SysctlMaxLength int = 253
4858 )
4859
4860 var sysctlContainSlashRegexp = regexp.MustCompile("^" + SysctlContainSlashFmt + "$")
4861
4862
4863
4864
4865
4866
4867
4868 func IsValidSysctlName(name string) bool {
4869 if len(name) > SysctlMaxLength {
4870 return false
4871 }
4872 return sysctlContainSlashRegexp.MatchString(name)
4873 }
4874
4875 func validateSysctls(securityContext *core.PodSecurityContext, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
4876 allErrs := field.ErrorList{}
4877 names := make(map[string]struct{})
4878 for i, s := range securityContext.Sysctls {
4879 if len(s.Name) == 0 {
4880 allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("name"), ""))
4881 } else if !IsValidSysctlName(s.Name) {
4882 allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("name"), s.Name, fmt.Sprintf("must have at most %d characters and match regex %s", SysctlMaxLength, sysctlContainSlashRegexp)))
4883 } else if _, ok := names[s.Name]; ok {
4884 allErrs = append(allErrs, field.Duplicate(fldPath.Index(i).Child("name"), s.Name))
4885 }
4886 if !opts.AllowNamespacedSysctlsForHostNetAndHostIPC {
4887 err := ValidateHostSysctl(s.Name, securityContext, fldPath.Index(i).Child("name"))
4888 if err != nil {
4889 allErrs = append(allErrs, err)
4890 }
4891 }
4892 names[s.Name] = struct{}{}
4893 }
4894 return allErrs
4895 }
4896
4897
4898 func ValidateHostSysctl(sysctl string, securityContext *core.PodSecurityContext, fldPath *field.Path) *field.Error {
4899 ns, _, _ := utilsysctl.GetNamespace(sysctl)
4900 switch {
4901 case securityContext.HostNetwork && ns == utilsysctl.NetNamespace:
4902 return field.Invalid(fldPath, sysctl, "may not be specified when 'hostNetwork' is true")
4903 case securityContext.HostIPC && ns == utilsysctl.IPCNamespace:
4904 return field.Invalid(fldPath, sysctl, "may not be specified when 'hostIPC' is true")
4905 }
4906 return nil
4907 }
4908
4909
4910
4911
4912 func validatePodSpecSecurityContext(securityContext *core.PodSecurityContext, spec *core.PodSpec, specPath, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
4913 allErrs := field.ErrorList{}
4914
4915 if securityContext != nil {
4916 if securityContext.FSGroup != nil {
4917 for _, msg := range validation.IsValidGroupID(*securityContext.FSGroup) {
4918 allErrs = append(allErrs, field.Invalid(fldPath.Child("fsGroup"), *(securityContext.FSGroup), msg))
4919 }
4920 }
4921 if securityContext.RunAsUser != nil {
4922 for _, msg := range validation.IsValidUserID(*securityContext.RunAsUser) {
4923 allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsUser"), *(securityContext.RunAsUser), msg))
4924 }
4925 }
4926 if securityContext.RunAsGroup != nil {
4927 for _, msg := range validation.IsValidGroupID(*securityContext.RunAsGroup) {
4928 allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsGroup"), *(securityContext.RunAsGroup), msg))
4929 }
4930 }
4931 for g, gid := range securityContext.SupplementalGroups {
4932 for _, msg := range validation.IsValidGroupID(gid) {
4933 allErrs = append(allErrs, field.Invalid(fldPath.Child("supplementalGroups").Index(g), gid, msg))
4934 }
4935 }
4936 if securityContext.ShareProcessNamespace != nil && securityContext.HostPID && *securityContext.ShareProcessNamespace {
4937 allErrs = append(allErrs, field.Invalid(fldPath.Child("shareProcessNamespace"), *securityContext.ShareProcessNamespace, "ShareProcessNamespace and HostPID cannot both be enabled"))
4938 }
4939
4940 if len(securityContext.Sysctls) != 0 {
4941 allErrs = append(allErrs, validateSysctls(securityContext, fldPath.Child("sysctls"), opts)...)
4942 }
4943
4944 if securityContext.FSGroupChangePolicy != nil {
4945 allErrs = append(allErrs, validateFSGroupChangePolicy(securityContext.FSGroupChangePolicy, fldPath.Child("fsGroupChangePolicy"))...)
4946 }
4947
4948 allErrs = append(allErrs, validateSeccompProfileField(securityContext.SeccompProfile, fldPath.Child("seccompProfile"))...)
4949 allErrs = append(allErrs, validateWindowsSecurityContextOptions(securityContext.WindowsOptions, fldPath.Child("windowsOptions"))...)
4950 allErrs = append(allErrs, ValidateAppArmorProfileField(securityContext.AppArmorProfile, fldPath.Child("appArmorProfile"))...)
4951 }
4952
4953 return allErrs
4954 }
4955
4956 func ValidateContainerUpdates(newContainers, oldContainers []core.Container, fldPath *field.Path) (allErrs field.ErrorList, stop bool) {
4957 allErrs = field.ErrorList{}
4958 if len(newContainers) != len(oldContainers) {
4959
4960 allErrs = append(allErrs, field.Forbidden(fldPath, "pod updates may not add or remove containers"))
4961 return allErrs, true
4962 }
4963
4964
4965 for i, ctr := range newContainers {
4966 if len(ctr.Image) == 0 {
4967 allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("image"), ""))
4968 }
4969
4970 if len(strings.TrimSpace(ctr.Image)) != len(ctr.Image) {
4971 allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("image"), ctr.Image, "must not have leading or trailing whitespace"))
4972 }
4973 }
4974 return allErrs, false
4975 }
4976
4977
4978 func ValidatePodCreate(pod *core.Pod, opts PodValidationOptions) field.ErrorList {
4979 allErrs := validatePodMetadataAndSpec(pod, opts)
4980
4981 fldPath := field.NewPath("spec")
4982
4983 if len(pod.Spec.EphemeralContainers) > 0 {
4984 allErrs = append(allErrs, field.Forbidden(fldPath.Child("ephemeralContainers"), "cannot be set on create"))
4985 }
4986
4987 if pod.Spec.NodeName != "" && len(pod.Spec.SchedulingGates) != 0 {
4988 allErrs = append(allErrs, field.Forbidden(fldPath.Child("nodeName"), "cannot be set until all schedulingGates have been cleared"))
4989 }
4990 allErrs = append(allErrs, validateSeccompAnnotationsAndFields(pod.ObjectMeta, &pod.Spec, fldPath)...)
4991 allErrs = append(allErrs, validateAppArmorAnnotationsAndFieldsMatchOnCreate(pod.ObjectMeta, &pod.Spec, fldPath)...)
4992
4993 return allErrs
4994 }
4995
4996
4997 func validateSeccompAnnotationsAndFields(objectMeta metav1.ObjectMeta, podSpec *core.PodSpec, specPath *field.Path) field.ErrorList {
4998 allErrs := field.ErrorList{}
4999
5000 if podSpec.SecurityContext != nil && podSpec.SecurityContext.SeccompProfile != nil {
5001
5002 if annotation, found := objectMeta.Annotations[v1.SeccompPodAnnotationKey]; found {
5003 seccompPath := specPath.Child("securityContext").Child("seccompProfile")
5004 err := validateSeccompAnnotationsAndFieldsMatch(annotation, podSpec.SecurityContext.SeccompProfile, seccompPath)
5005 if err != nil {
5006 allErrs = append(allErrs, err)
5007 }
5008 }
5009 }
5010
5011 podshelper.VisitContainersWithPath(podSpec, specPath, func(c *core.Container, cFldPath *field.Path) bool {
5012 var field *core.SeccompProfile
5013 if c.SecurityContext != nil {
5014 field = c.SecurityContext.SeccompProfile
5015 }
5016
5017 if field == nil {
5018 return true
5019 }
5020
5021 key := v1.SeccompContainerAnnotationKeyPrefix + c.Name
5022 if annotation, found := objectMeta.Annotations[key]; found {
5023 seccompPath := cFldPath.Child("securityContext").Child("seccompProfile")
5024 err := validateSeccompAnnotationsAndFieldsMatch(annotation, field, seccompPath)
5025 if err != nil {
5026 allErrs = append(allErrs, err)
5027 }
5028 }
5029 return true
5030 })
5031
5032 return allErrs
5033 }
5034
5035 func validateSeccompAnnotationsAndFieldsMatch(annotationValue string, seccompField *core.SeccompProfile, fldPath *field.Path) *field.Error {
5036 if seccompField == nil {
5037 return nil
5038 }
5039
5040 switch seccompField.Type {
5041 case core.SeccompProfileTypeUnconfined:
5042 if annotationValue != v1.SeccompProfileNameUnconfined {
5043 return field.Forbidden(fldPath.Child("type"), "seccomp type in annotation and field must match")
5044 }
5045
5046 case core.SeccompProfileTypeRuntimeDefault:
5047 if annotationValue != v1.SeccompProfileRuntimeDefault && annotationValue != v1.DeprecatedSeccompProfileDockerDefault {
5048 return field.Forbidden(fldPath.Child("type"), "seccomp type in annotation and field must match")
5049 }
5050
5051 case core.SeccompProfileTypeLocalhost:
5052 if !strings.HasPrefix(annotationValue, v1.SeccompLocalhostProfileNamePrefix) {
5053 return field.Forbidden(fldPath.Child("type"), "seccomp type in annotation and field must match")
5054 } else if seccompField.LocalhostProfile == nil || strings.TrimPrefix(annotationValue, v1.SeccompLocalhostProfileNamePrefix) != *seccompField.LocalhostProfile {
5055 return field.Forbidden(fldPath.Child("localhostProfile"), "seccomp profile in annotation and field must match")
5056 }
5057 }
5058
5059 return nil
5060 }
5061
5062 var updatablePodSpecFields = []string{
5063 "`spec.containers[*].image`",
5064 "`spec.initContainers[*].image`",
5065 "`spec.activeDeadlineSeconds`",
5066 "`spec.tolerations` (only additions to existing tolerations)",
5067 "`spec.terminationGracePeriodSeconds` (allow it to be set to 1 if it was previously negative)",
5068 "`spec.containers[*].resources` (for CPU/memory only)",
5069 }
5070
5071
5072 var updatablePodSpecFieldsNoResources = []string{
5073 "`spec.containers[*].image`",
5074 "`spec.initContainers[*].image`",
5075 "`spec.activeDeadlineSeconds`",
5076 "`spec.tolerations` (only additions to existing tolerations)",
5077 "`spec.terminationGracePeriodSeconds` (allow it to be set to 1 if it was previously negative)",
5078 }
5079
5080
5081
5082 func ValidatePodUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) field.ErrorList {
5083 fldPath := field.NewPath("metadata")
5084 allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, fldPath)
5085 allErrs = append(allErrs, validatePodMetadataAndSpec(newPod, opts)...)
5086 allErrs = append(allErrs, ValidatePodSpecificAnnotationUpdates(newPod, oldPod, fldPath.Child("annotations"), opts)...)
5087 specPath := field.NewPath("spec")
5088
5089
5090
5091
5092
5093
5094
5095
5096 containerErrs, stop := ValidateContainerUpdates(newPod.Spec.Containers, oldPod.Spec.Containers, specPath.Child("containers"))
5097 allErrs = append(allErrs, containerErrs...)
5098 if stop {
5099 return allErrs
5100 }
5101 containerErrs, stop = ValidateContainerUpdates(newPod.Spec.InitContainers, oldPod.Spec.InitContainers, specPath.Child("initContainers"))
5102 allErrs = append(allErrs, containerErrs...)
5103 if stop {
5104 return allErrs
5105 }
5106
5107
5108
5109
5110 if newPod.Spec.ActiveDeadlineSeconds != nil {
5111 newActiveDeadlineSeconds := *newPod.Spec.ActiveDeadlineSeconds
5112 if newActiveDeadlineSeconds < 0 || newActiveDeadlineSeconds > math.MaxInt32 {
5113 allErrs = append(allErrs, field.Invalid(specPath.Child("activeDeadlineSeconds"), newActiveDeadlineSeconds, validation.InclusiveRangeError(0, math.MaxInt32)))
5114 return allErrs
5115 }
5116 if oldPod.Spec.ActiveDeadlineSeconds != nil {
5117 oldActiveDeadlineSeconds := *oldPod.Spec.ActiveDeadlineSeconds
5118 if oldActiveDeadlineSeconds < newActiveDeadlineSeconds {
5119 allErrs = append(allErrs, field.Invalid(specPath.Child("activeDeadlineSeconds"), newActiveDeadlineSeconds, "must be less than or equal to previous value"))
5120 return allErrs
5121 }
5122 }
5123 } else if oldPod.Spec.ActiveDeadlineSeconds != nil {
5124 allErrs = append(allErrs, field.Invalid(specPath.Child("activeDeadlineSeconds"), newPod.Spec.ActiveDeadlineSeconds, "must not update from a positive integer to nil value"))
5125 }
5126
5127
5128 allErrs = append(allErrs, validateOnlyAddedTolerations(newPod.Spec.Tolerations, oldPod.Spec.Tolerations, specPath.Child("tolerations"))...)
5129
5130
5131 allErrs = append(allErrs, validateOnlyDeletedSchedulingGates(newPod.Spec.SchedulingGates, oldPod.Spec.SchedulingGates, specPath.Child("schedulingGates"))...)
5132
5133
5134
5135 if apiequality.Semantic.DeepEqual(newPod.Spec, oldPod.Spec) {
5136 return allErrs
5137 }
5138
5139 if qos.GetPodQOS(oldPod) != qos.ComputePodQOS(newPod) {
5140 allErrs = append(allErrs, field.Invalid(fldPath, newPod.Status.QOSClass, "Pod QoS is immutable"))
5141 }
5142
5143
5144 mungedPodSpec := *newPod.Spec.DeepCopy()
5145
5146 var newContainers []core.Container
5147 for ix, container := range mungedPodSpec.Containers {
5148 container.Image = oldPod.Spec.Containers[ix].Image
5149
5150
5151 if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
5152
5153
5154 mungeCpuMemResources := func(resourceList, oldResourceList core.ResourceList) core.ResourceList {
5155 if oldResourceList == nil {
5156 return nil
5157 }
5158 var mungedResourceList core.ResourceList
5159 if resourceList == nil {
5160 mungedResourceList = make(core.ResourceList)
5161 } else {
5162 mungedResourceList = resourceList.DeepCopy()
5163 }
5164 delete(mungedResourceList, core.ResourceCPU)
5165 delete(mungedResourceList, core.ResourceMemory)
5166 if cpu, found := oldResourceList[core.ResourceCPU]; found {
5167 mungedResourceList[core.ResourceCPU] = cpu
5168 }
5169 if mem, found := oldResourceList[core.ResourceMemory]; found {
5170 mungedResourceList[core.ResourceMemory] = mem
5171 }
5172 return mungedResourceList
5173 }
5174 lim := mungeCpuMemResources(container.Resources.Limits, oldPod.Spec.Containers[ix].Resources.Limits)
5175 req := mungeCpuMemResources(container.Resources.Requests, oldPod.Spec.Containers[ix].Resources.Requests)
5176 container.Resources = core.ResourceRequirements{Limits: lim, Requests: req}
5177 }
5178 newContainers = append(newContainers, container)
5179 }
5180 mungedPodSpec.Containers = newContainers
5181
5182 var newInitContainers []core.Container
5183 for ix, container := range mungedPodSpec.InitContainers {
5184 container.Image = oldPod.Spec.InitContainers[ix].Image
5185 newInitContainers = append(newInitContainers, container)
5186 }
5187 mungedPodSpec.InitContainers = newInitContainers
5188
5189 mungedPodSpec.ActiveDeadlineSeconds = nil
5190 if oldPod.Spec.ActiveDeadlineSeconds != nil {
5191 activeDeadlineSeconds := *oldPod.Spec.ActiveDeadlineSeconds
5192 mungedPodSpec.ActiveDeadlineSeconds = &activeDeadlineSeconds
5193 }
5194
5195 mungedPodSpec.SchedulingGates = oldPod.Spec.SchedulingGates
5196
5197 mungedPodSpec.Tolerations = oldPod.Spec.Tolerations
5198
5199
5200 if oldPod.Spec.TerminationGracePeriodSeconds != nil && *oldPod.Spec.TerminationGracePeriodSeconds < 0 &&
5201 mungedPodSpec.TerminationGracePeriodSeconds != nil && *mungedPodSpec.TerminationGracePeriodSeconds == 1 {
5202 mungedPodSpec.TerminationGracePeriodSeconds = oldPod.Spec.TerminationGracePeriodSeconds
5203 }
5204
5205
5206 podIsGated := len(oldPod.Spec.SchedulingGates) > 0
5207 if podIsGated {
5208
5209 if !apiequality.Semantic.DeepEqual(mungedPodSpec.NodeSelector, oldPod.Spec.NodeSelector) {
5210 allErrs = append(allErrs, validateNodeSelectorMutation(specPath.Child("nodeSelector"), mungedPodSpec.NodeSelector, oldPod.Spec.NodeSelector)...)
5211 mungedPodSpec.NodeSelector = oldPod.Spec.NodeSelector
5212 }
5213
5214
5215 var oldNodeAffinity *core.NodeAffinity
5216 if oldPod.Spec.Affinity != nil {
5217 oldNodeAffinity = oldPod.Spec.Affinity.NodeAffinity
5218 }
5219
5220 var mungedNodeAffinity *core.NodeAffinity
5221 if mungedPodSpec.Affinity != nil {
5222 mungedNodeAffinity = mungedPodSpec.Affinity.NodeAffinity
5223 }
5224
5225 if !apiequality.Semantic.DeepEqual(oldNodeAffinity, mungedNodeAffinity) {
5226 allErrs = append(allErrs, validateNodeAffinityMutation(specPath.Child("affinity").Child("nodeAffinity"), mungedNodeAffinity, oldNodeAffinity)...)
5227 switch {
5228 case mungedPodSpec.Affinity == nil && oldNodeAffinity == nil:
5229
5230 case mungedPodSpec.Affinity == nil && oldNodeAffinity != nil:
5231 mungedPodSpec.Affinity = &core.Affinity{NodeAffinity: oldNodeAffinity}
5232 case mungedPodSpec.Affinity != nil && oldPod.Spec.Affinity == nil &&
5233 mungedPodSpec.Affinity.PodAntiAffinity == nil && mungedPodSpec.Affinity.PodAffinity == nil:
5234
5235
5236 mungedPodSpec.Affinity = nil
5237 default:
5238
5239
5240 mungedPodSpec.Affinity.NodeAffinity = oldNodeAffinity
5241 }
5242 }
5243
5244
5245
5246
5247
5248 }
5249
5250 if !apiequality.Semantic.DeepEqual(mungedPodSpec, oldPod.Spec) {
5251
5252
5253 specDiff := cmp.Diff(oldPod.Spec, mungedPodSpec)
5254 errs := field.Forbidden(specPath, fmt.Sprintf("pod updates may not change fields other than %s\n%v", strings.Join(updatablePodSpecFieldsNoResources, ","), specDiff))
5255 if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
5256 errs = field.Forbidden(specPath, fmt.Sprintf("pod updates may not change fields other than %s\n%v", strings.Join(updatablePodSpecFields, ","), specDiff))
5257 }
5258 allErrs = append(allErrs, errs)
5259 }
5260 return allErrs
5261 }
5262
5263
5264 func ValidateContainerStateTransition(newStatuses, oldStatuses []core.ContainerStatus, fldpath *field.Path, restartPolicy core.RestartPolicy) field.ErrorList {
5265 allErrs := field.ErrorList{}
5266
5267 if restartPolicy == core.RestartPolicyAlways {
5268 return allErrs
5269 }
5270 for i, oldStatus := range oldStatuses {
5271
5272 if oldStatus.State.Terminated == nil {
5273 continue
5274 }
5275
5276 if oldStatus.State.Terminated.ExitCode != 0 && restartPolicy == core.RestartPolicyOnFailure {
5277 continue
5278 }
5279 for _, newStatus := range newStatuses {
5280 if oldStatus.Name == newStatus.Name && newStatus.State.Terminated == nil {
5281 allErrs = append(allErrs, field.Forbidden(fldpath.Index(i).Child("state"), "may not be transitioned to non-terminated state"))
5282 }
5283 }
5284 }
5285 return allErrs
5286 }
5287
5288
5289 func ValidateInitContainerStateTransition(newStatuses, oldStatuses []core.ContainerStatus, fldpath *field.Path, podSpec *core.PodSpec) field.ErrorList {
5290 allErrs := field.ErrorList{}
5291
5292 if podSpec.RestartPolicy == core.RestartPolicyAlways {
5293 return allErrs
5294 }
5295 for i, oldStatus := range oldStatuses {
5296
5297 if oldStatus.State.Terminated == nil {
5298 continue
5299 }
5300
5301 if oldStatus.State.Terminated.ExitCode != 0 && podSpec.RestartPolicy == core.RestartPolicyOnFailure {
5302 continue
5303 }
5304
5305
5306 isRestartableInitContainer := false
5307 for _, c := range podSpec.InitContainers {
5308 if oldStatus.Name == c.Name {
5309 if c.RestartPolicy != nil && *c.RestartPolicy == core.ContainerRestartPolicyAlways {
5310 isRestartableInitContainer = true
5311 }
5312 break
5313 }
5314 }
5315 if isRestartableInitContainer {
5316 continue
5317 }
5318
5319 for _, newStatus := range newStatuses {
5320 if oldStatus.Name == newStatus.Name && newStatus.State.Terminated == nil {
5321 allErrs = append(allErrs, field.Forbidden(fldpath.Index(i).Child("state"), "may not be transitioned to non-terminated state"))
5322 }
5323 }
5324 }
5325 return allErrs
5326 }
5327
5328
5329 func ValidatePodStatusUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) field.ErrorList {
5330 fldPath := field.NewPath("metadata")
5331 allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, fldPath)
5332 allErrs = append(allErrs, ValidatePodSpecificAnnotationUpdates(newPod, oldPod, fldPath.Child("annotations"), opts)...)
5333 allErrs = append(allErrs, validatePodConditions(newPod.Status.Conditions, fldPath.Child("conditions"))...)
5334
5335 fldPath = field.NewPath("status")
5336 if newPod.Spec.NodeName != oldPod.Spec.NodeName {
5337 allErrs = append(allErrs, field.Forbidden(fldPath.Child("nodeName"), "may not be changed directly"))
5338 }
5339
5340 if newPod.Status.NominatedNodeName != oldPod.Status.NominatedNodeName && len(newPod.Status.NominatedNodeName) > 0 {
5341 for _, msg := range ValidateNodeName(newPod.Status.NominatedNodeName, false) {
5342 allErrs = append(allErrs, field.Invalid(fldPath.Child("nominatedNodeName"), newPod.Status.NominatedNodeName, msg))
5343 }
5344 }
5345
5346
5347
5348 allErrs = append(allErrs, ValidateContainerStateTransition(newPod.Status.ContainerStatuses, oldPod.Status.ContainerStatuses, fldPath.Child("containerStatuses"), oldPod.Spec.RestartPolicy)...)
5349 allErrs = append(allErrs, ValidateInitContainerStateTransition(newPod.Status.InitContainerStatuses, oldPod.Status.InitContainerStatuses, fldPath.Child("initContainerStatuses"), &oldPod.Spec)...)
5350
5351 allErrs = append(allErrs, ValidateContainerStateTransition(newPod.Status.EphemeralContainerStatuses, oldPod.Status.EphemeralContainerStatuses, fldPath.Child("ephemeralContainerStatuses"), core.RestartPolicyNever)...)
5352 allErrs = append(allErrs, validatePodResourceClaimStatuses(newPod.Status.ResourceClaimStatuses, newPod.Spec.ResourceClaims, fldPath.Child("resourceClaimStatuses"))...)
5353
5354 if newIPErrs := validatePodIPs(newPod); len(newIPErrs) > 0 {
5355 allErrs = append(allErrs, newIPErrs...)
5356 }
5357
5358 if newIPErrs := validateHostIPs(newPod); len(newIPErrs) > 0 {
5359 allErrs = append(allErrs, newIPErrs...)
5360 }
5361
5362 return allErrs
5363 }
5364
5365
5366 func validatePodConditions(conditions []core.PodCondition, fldPath *field.Path) field.ErrorList {
5367 allErrs := field.ErrorList{}
5368 systemConditions := sets.New(
5369 core.PodScheduled,
5370 core.PodReady,
5371 core.PodInitialized)
5372 for i, condition := range conditions {
5373 if systemConditions.Has(condition.Type) {
5374 continue
5375 }
5376 allErrs = append(allErrs, ValidateQualifiedName(string(condition.Type), fldPath.Index(i).Child("Type"))...)
5377 }
5378 return allErrs
5379 }
5380
5381
5382 func validatePodResourceClaimStatuses(statuses []core.PodResourceClaimStatus, podClaims []core.PodResourceClaim, fldPath *field.Path) field.ErrorList {
5383 var allErrs field.ErrorList
5384
5385 claimNames := sets.New[string]()
5386 for i, status := range statuses {
5387 idxPath := fldPath.Index(i)
5388
5389
5390 if !havePodClaim(podClaims, status.Name) {
5391 allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), status.Name, "must match the name of an entry in `spec.resourceClaims`"))
5392 }
5393 if claimNames.Has(status.Name) {
5394 allErrs = append(allErrs, field.Duplicate(idxPath.Child("name"), status.Name))
5395 } else {
5396 claimNames.Insert(status.Name)
5397 }
5398 if status.ResourceClaimName != nil {
5399 for _, detail := range ValidateResourceClaimName(*status.ResourceClaimName, false) {
5400 allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), status.ResourceClaimName, detail))
5401 }
5402 }
5403 }
5404
5405 return allErrs
5406 }
5407
5408 func havePodClaim(podClaims []core.PodResourceClaim, name string) bool {
5409 for _, podClaim := range podClaims {
5410 if podClaim.Name == name {
5411 return true
5412 }
5413 }
5414 return false
5415 }
5416
5417
5418
5419 func ValidatePodEphemeralContainersUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) field.ErrorList {
5420
5421 fldPath := field.NewPath("metadata")
5422 allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, fldPath)
5423 allErrs = append(allErrs, validatePodMetadataAndSpec(newPod, opts)...)
5424 allErrs = append(allErrs, ValidatePodSpecificAnnotationUpdates(newPod, oldPod, fldPath.Child("annotations"), opts)...)
5425
5426
5427 if _, ok := oldPod.Annotations[core.MirrorPodAnnotationKey]; ok {
5428 return field.ErrorList{field.Forbidden(field.NewPath(""), "static pods do not support ephemeral containers")}
5429 }
5430
5431
5432
5433
5434
5435 newContainerIndex := make(map[string]*core.EphemeralContainer)
5436 specPath := field.NewPath("spec").Child("ephemeralContainers")
5437 for i := range newPod.Spec.EphemeralContainers {
5438 newContainerIndex[newPod.Spec.EphemeralContainers[i].Name] = &newPod.Spec.EphemeralContainers[i]
5439 }
5440 for _, old := range oldPod.Spec.EphemeralContainers {
5441 if new, ok := newContainerIndex[old.Name]; !ok {
5442 allErrs = append(allErrs, field.Forbidden(specPath, fmt.Sprintf("existing ephemeral containers %q may not be removed\n", old.Name)))
5443 } else if !apiequality.Semantic.DeepEqual(old, *new) {
5444 specDiff := cmp.Diff(old, *new)
5445 allErrs = append(allErrs, field.Forbidden(specPath, fmt.Sprintf("existing ephemeral containers %q may not be changed\n%v", old.Name, specDiff)))
5446 }
5447 }
5448
5449 return allErrs
5450 }
5451
5452
5453 func ValidatePodBinding(binding *core.Binding) field.ErrorList {
5454 allErrs := field.ErrorList{}
5455
5456 if len(binding.Target.Kind) != 0 && binding.Target.Kind != "Node" {
5457
5458 allErrs = append(allErrs, field.NotSupported(field.NewPath("target", "kind"), binding.Target.Kind, []string{"Node", "<empty>"}))
5459 }
5460 if len(binding.Target.Name) == 0 {
5461
5462 allErrs = append(allErrs, field.Required(field.NewPath("target", "name"), ""))
5463 }
5464
5465 return allErrs
5466 }
5467
5468
5469 func ValidatePodTemplate(pod *core.PodTemplate, opts PodValidationOptions) field.ErrorList {
5470 allErrs := ValidateObjectMeta(&pod.ObjectMeta, true, ValidatePodName, field.NewPath("metadata"))
5471 allErrs = append(allErrs, ValidatePodTemplateSpec(&pod.Template, field.NewPath("template"), opts)...)
5472 return allErrs
5473 }
5474
5475
5476
5477 func ValidatePodTemplateUpdate(newPod, oldPod *core.PodTemplate, opts PodValidationOptions) field.ErrorList {
5478 allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, field.NewPath("metadata"))
5479 allErrs = append(allErrs, ValidatePodTemplateSpec(&newPod.Template, field.NewPath("template"), opts)...)
5480 return allErrs
5481 }
5482
5483 var supportedSessionAffinityType = sets.New(core.ServiceAffinityClientIP, core.ServiceAffinityNone)
5484 var supportedServiceType = sets.New(core.ServiceTypeClusterIP, core.ServiceTypeNodePort,
5485 core.ServiceTypeLoadBalancer, core.ServiceTypeExternalName)
5486
5487 var supportedServiceInternalTrafficPolicy = sets.New(core.ServiceInternalTrafficPolicyCluster, core.ServiceInternalTrafficPolicyLocal)
5488
5489 var supportedServiceIPFamily = sets.New(core.IPv4Protocol, core.IPv6Protocol)
5490 var supportedServiceIPFamilyPolicy = sets.New(
5491 core.IPFamilyPolicySingleStack,
5492 core.IPFamilyPolicyPreferDualStack,
5493 core.IPFamilyPolicyRequireDualStack)
5494
5495
5496 func ValidateService(service *core.Service) field.ErrorList {
5497 metaPath := field.NewPath("metadata")
5498 allErrs := ValidateObjectMeta(&service.ObjectMeta, true, ValidateServiceName, metaPath)
5499
5500 topologyHintsVal, topologyHintsSet := service.Annotations[core.DeprecatedAnnotationTopologyAwareHints]
5501 topologyModeVal, topologyModeSet := service.Annotations[core.AnnotationTopologyMode]
5502
5503 if topologyModeSet && topologyHintsSet && topologyModeVal != topologyHintsVal {
5504 message := fmt.Sprintf("must match annotations[%s] when both are specified", core.DeprecatedAnnotationTopologyAwareHints)
5505 allErrs = append(allErrs, field.Invalid(metaPath.Child("annotations").Key(core.AnnotationTopologyMode), topologyModeVal, message))
5506 }
5507
5508 specPath := field.NewPath("spec")
5509
5510 if len(service.Spec.Ports) == 0 && !isHeadlessService(service) && service.Spec.Type != core.ServiceTypeExternalName {
5511 allErrs = append(allErrs, field.Required(specPath.Child("ports"), ""))
5512 }
5513 switch service.Spec.Type {
5514 case core.ServiceTypeLoadBalancer:
5515 for ix := range service.Spec.Ports {
5516 port := &service.Spec.Ports[ix]
5517
5518
5519
5520 if port.Port == ports.KubeletPort {
5521 portPath := specPath.Child("ports").Index(ix)
5522 allErrs = append(allErrs, field.Invalid(portPath, port.Port, fmt.Sprintf("may not expose port %v externally since it is used by kubelet", ports.KubeletPort)))
5523 }
5524 }
5525 if isHeadlessService(service) {
5526 allErrs = append(allErrs, field.Invalid(specPath.Child("clusterIPs").Index(0), service.Spec.ClusterIPs[0], "may not be set to 'None' for LoadBalancer services"))
5527 }
5528 case core.ServiceTypeNodePort:
5529 if isHeadlessService(service) {
5530 allErrs = append(allErrs, field.Invalid(specPath.Child("clusterIPs").Index(0), service.Spec.ClusterIPs[0], "may not be set to 'None' for NodePort services"))
5531 }
5532 case core.ServiceTypeExternalName:
5533
5534 if len(service.Spec.ClusterIPs) > 0 {
5535 allErrs = append(allErrs, field.Forbidden(specPath.Child("clusterIPs"), "may not be set for ExternalName services"))
5536 }
5537
5538
5539 if len(service.Spec.IPFamilies) > 0 {
5540 allErrs = append(allErrs, field.Forbidden(specPath.Child("ipFamilies"), "may not be set for ExternalName services"))
5541 }
5542 if service.Spec.IPFamilyPolicy != nil {
5543 allErrs = append(allErrs, field.Forbidden(specPath.Child("ipFamilyPolicy"), "may not be set for ExternalName services"))
5544 }
5545
5546
5547 cname := strings.TrimSuffix(service.Spec.ExternalName, ".")
5548 if len(cname) > 0 {
5549 allErrs = append(allErrs, ValidateDNS1123Subdomain(cname, specPath.Child("externalName"))...)
5550 } else {
5551 allErrs = append(allErrs, field.Required(specPath.Child("externalName"), ""))
5552 }
5553 }
5554
5555 allPortNames := sets.Set[string]{}
5556 portsPath := specPath.Child("ports")
5557 for i := range service.Spec.Ports {
5558 portPath := portsPath.Index(i)
5559 allErrs = append(allErrs, validateServicePort(&service.Spec.Ports[i], len(service.Spec.Ports) > 1, isHeadlessService(service), &allPortNames, portPath)...)
5560 }
5561
5562 if service.Spec.Selector != nil {
5563 allErrs = append(allErrs, unversionedvalidation.ValidateLabels(service.Spec.Selector, specPath.Child("selector"))...)
5564 }
5565
5566 if len(service.Spec.SessionAffinity) == 0 {
5567 allErrs = append(allErrs, field.Required(specPath.Child("sessionAffinity"), ""))
5568 } else if !supportedSessionAffinityType.Has(service.Spec.SessionAffinity) {
5569 allErrs = append(allErrs, field.NotSupported(specPath.Child("sessionAffinity"), service.Spec.SessionAffinity, sets.List(supportedSessionAffinityType)))
5570 }
5571
5572 if service.Spec.SessionAffinity == core.ServiceAffinityClientIP {
5573 allErrs = append(allErrs, validateClientIPAffinityConfig(service.Spec.SessionAffinityConfig, specPath.Child("sessionAffinityConfig"))...)
5574 } else if service.Spec.SessionAffinity == core.ServiceAffinityNone {
5575 if service.Spec.SessionAffinityConfig != nil {
5576 allErrs = append(allErrs, field.Forbidden(specPath.Child("sessionAffinityConfig"), fmt.Sprintf("must not be set when session affinity is %s", core.ServiceAffinityNone)))
5577 }
5578 }
5579
5580
5581 allErrs = append(allErrs, ValidateServiceClusterIPsRelatedFields(service)...)
5582
5583 ipPath := specPath.Child("externalIPs")
5584 for i, ip := range service.Spec.ExternalIPs {
5585 idxPath := ipPath.Index(i)
5586 if errs := validation.IsValidIP(idxPath, ip); len(errs) != 0 {
5587 allErrs = append(allErrs, errs...)
5588 } else {
5589 allErrs = append(allErrs, ValidateNonSpecialIP(ip, idxPath)...)
5590 }
5591 }
5592
5593 if len(service.Spec.Type) == 0 {
5594 allErrs = append(allErrs, field.Required(specPath.Child("type"), ""))
5595 } else if !supportedServiceType.Has(service.Spec.Type) {
5596 allErrs = append(allErrs, field.NotSupported(specPath.Child("type"), service.Spec.Type, sets.List(supportedServiceType)))
5597 }
5598
5599 if service.Spec.Type == core.ServiceTypeClusterIP {
5600 portsPath := specPath.Child("ports")
5601 for i := range service.Spec.Ports {
5602 portPath := portsPath.Index(i)
5603 if service.Spec.Ports[i].NodePort != 0 {
5604 allErrs = append(allErrs, field.Forbidden(portPath.Child("nodePort"), "may not be used when `type` is 'ClusterIP'"))
5605 }
5606 }
5607 }
5608
5609
5610 portsPath = specPath.Child("ports")
5611 nodePorts := make(map[core.ServicePort]bool)
5612 for i := range service.Spec.Ports {
5613 port := &service.Spec.Ports[i]
5614 if port.NodePort == 0 {
5615 continue
5616 }
5617 portPath := portsPath.Index(i)
5618 var key core.ServicePort
5619 key.Protocol = port.Protocol
5620 key.NodePort = port.NodePort
5621 _, found := nodePorts[key]
5622 if found {
5623 allErrs = append(allErrs, field.Duplicate(portPath.Child("nodePort"), port.NodePort))
5624 }
5625 nodePorts[key] = true
5626 }
5627
5628
5629 portsPath = specPath.Child("ports")
5630 ports := make(map[core.ServicePort]bool)
5631 for i, port := range service.Spec.Ports {
5632 portPath := portsPath.Index(i)
5633 key := core.ServicePort{Protocol: port.Protocol, Port: port.Port}
5634 _, found := ports[key]
5635 if found {
5636 allErrs = append(allErrs, field.Duplicate(portPath, key))
5637 }
5638 ports[key] = true
5639 }
5640
5641
5642 if len(service.Spec.LoadBalancerSourceRanges) > 0 {
5643 fieldPath := specPath.Child("LoadBalancerSourceRanges")
5644
5645 if service.Spec.Type != core.ServiceTypeLoadBalancer {
5646 allErrs = append(allErrs, field.Forbidden(fieldPath, "may only be used when `type` is 'LoadBalancer'"))
5647 }
5648 for idx, value := range service.Spec.LoadBalancerSourceRanges {
5649
5650
5651 value = strings.TrimSpace(value)
5652 allErrs = append(allErrs, validation.IsValidCIDR(fieldPath.Index(idx), value)...)
5653 }
5654 } else if val, annotationSet := service.Annotations[core.AnnotationLoadBalancerSourceRangesKey]; annotationSet {
5655 fieldPath := field.NewPath("metadata", "annotations").Key(core.AnnotationLoadBalancerSourceRangesKey)
5656 if service.Spec.Type != core.ServiceTypeLoadBalancer {
5657 allErrs = append(allErrs, field.Forbidden(fieldPath, "may only be used when `type` is 'LoadBalancer'"))
5658 }
5659
5660 val = strings.TrimSpace(val)
5661 if val != "" {
5662 cidrs := strings.Split(val, ",")
5663 for _, value := range cidrs {
5664 value = strings.TrimSpace(value)
5665 allErrs = append(allErrs, validation.IsValidCIDR(fieldPath, value)...)
5666 }
5667 }
5668 }
5669
5670 if service.Spec.AllocateLoadBalancerNodePorts != nil && service.Spec.Type != core.ServiceTypeLoadBalancer {
5671 allErrs = append(allErrs, field.Forbidden(specPath.Child("allocateLoadBalancerNodePorts"), "may only be used when `type` is 'LoadBalancer'"))
5672 }
5673
5674 if service.Spec.Type == core.ServiceTypeLoadBalancer && service.Spec.AllocateLoadBalancerNodePorts == nil {
5675 allErrs = append(allErrs, field.Required(field.NewPath("allocateLoadBalancerNodePorts"), ""))
5676 }
5677
5678
5679 allErrs = append(allErrs, validateLoadBalancerClassField(nil, service)...)
5680
5681
5682 allErrs = append(allErrs, validateServiceExternalTrafficPolicy(service)...)
5683
5684
5685 allErrs = append(allErrs, validateServiceInternalTrafficFieldsValue(service)...)
5686
5687
5688 allErrs = append(allErrs, validateServiceTrafficDistribution(service)...)
5689
5690 return allErrs
5691 }
5692
5693 func validateServicePort(sp *core.ServicePort, requireName, isHeadlessService bool, allNames *sets.Set[string], fldPath *field.Path) field.ErrorList {
5694 allErrs := field.ErrorList{}
5695
5696 if requireName && len(sp.Name) == 0 {
5697 allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
5698 } else if len(sp.Name) != 0 {
5699 allErrs = append(allErrs, ValidateDNS1123Label(sp.Name, fldPath.Child("name"))...)
5700 if allNames.Has(sp.Name) {
5701 allErrs = append(allErrs, field.Duplicate(fldPath.Child("name"), sp.Name))
5702 } else {
5703 allNames.Insert(sp.Name)
5704 }
5705 }
5706
5707 for _, msg := range validation.IsValidPortNum(int(sp.Port)) {
5708 allErrs = append(allErrs, field.Invalid(fldPath.Child("port"), sp.Port, msg))
5709 }
5710
5711 if len(sp.Protocol) == 0 {
5712 allErrs = append(allErrs, field.Required(fldPath.Child("protocol"), ""))
5713 } else if !supportedPortProtocols.Has(sp.Protocol) {
5714 allErrs = append(allErrs, field.NotSupported(fldPath.Child("protocol"), sp.Protocol, sets.List(supportedPortProtocols)))
5715 }
5716
5717 allErrs = append(allErrs, ValidatePortNumOrName(sp.TargetPort, fldPath.Child("targetPort"))...)
5718
5719 if sp.AppProtocol != nil {
5720 allErrs = append(allErrs, ValidateQualifiedName(*sp.AppProtocol, fldPath.Child("appProtocol"))...)
5721 }
5722
5723
5724
5725
5726
5727
5728
5729
5730
5731
5732 return allErrs
5733 }
5734
5735 var validExternalTrafficPolicies = sets.New(core.ServiceExternalTrafficPolicyCluster, core.ServiceExternalTrafficPolicyLocal)
5736
5737 func validateServiceExternalTrafficPolicy(service *core.Service) field.ErrorList {
5738 allErrs := field.ErrorList{}
5739
5740 fldPath := field.NewPath("spec")
5741
5742 if !apiservice.ExternallyAccessible(service) {
5743 if service.Spec.ExternalTrafficPolicy != "" {
5744 allErrs = append(allErrs, field.Invalid(fldPath.Child("externalTrafficPolicy"), service.Spec.ExternalTrafficPolicy,
5745 "may only be set for externally-accessible services"))
5746 }
5747 } else {
5748 if service.Spec.ExternalTrafficPolicy == "" {
5749 allErrs = append(allErrs, field.Required(fldPath.Child("externalTrafficPolicy"), ""))
5750 } else if !validExternalTrafficPolicies.Has(service.Spec.ExternalTrafficPolicy) {
5751 allErrs = append(allErrs, field.NotSupported(fldPath.Child("externalTrafficPolicy"),
5752 service.Spec.ExternalTrafficPolicy, sets.List(validExternalTrafficPolicies)))
5753 }
5754 }
5755
5756 if !apiservice.NeedsHealthCheck(service) {
5757 if service.Spec.HealthCheckNodePort != 0 {
5758 allErrs = append(allErrs, field.Invalid(fldPath.Child("healthCheckNodePort"), service.Spec.HealthCheckNodePort,
5759 "may only be set when `type` is 'LoadBalancer' and `externalTrafficPolicy` is 'Local'"))
5760 }
5761 } else {
5762 if service.Spec.HealthCheckNodePort == 0 {
5763 allErrs = append(allErrs, field.Required(fldPath.Child("healthCheckNodePort"), ""))
5764 } else {
5765 for _, msg := range validation.IsValidPortNum(int(service.Spec.HealthCheckNodePort)) {
5766 allErrs = append(allErrs, field.Invalid(fldPath.Child("healthCheckNodePort"), service.Spec.HealthCheckNodePort, msg))
5767 }
5768 }
5769 }
5770
5771 return allErrs
5772 }
5773
5774 func validateServiceExternalTrafficFieldsUpdate(before, after *core.Service) field.ErrorList {
5775 allErrs := field.ErrorList{}
5776
5777 if apiservice.NeedsHealthCheck(before) && apiservice.NeedsHealthCheck(after) {
5778 if after.Spec.HealthCheckNodePort != before.Spec.HealthCheckNodePort {
5779 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "healthCheckNodePort"), "field is immutable"))
5780 }
5781 }
5782
5783 return allErrs
5784 }
5785
5786
5787
5788 func validateServiceInternalTrafficFieldsValue(service *core.Service) field.ErrorList {
5789 allErrs := field.ErrorList{}
5790
5791 if service.Spec.InternalTrafficPolicy == nil {
5792
5793
5794 if service.Spec.Type == core.ServiceTypeNodePort ||
5795 service.Spec.Type == core.ServiceTypeLoadBalancer || service.Spec.Type == core.ServiceTypeClusterIP {
5796 allErrs = append(allErrs, field.Required(field.NewPath("spec").Child("internalTrafficPolicy"), ""))
5797 }
5798 }
5799
5800 if service.Spec.InternalTrafficPolicy != nil && !supportedServiceInternalTrafficPolicy.Has(*service.Spec.InternalTrafficPolicy) {
5801 allErrs = append(allErrs, field.NotSupported(field.NewPath("spec").Child("internalTrafficPolicy"), *service.Spec.InternalTrafficPolicy, sets.List(supportedServiceInternalTrafficPolicy)))
5802 }
5803
5804 return allErrs
5805 }
5806
5807
5808
5809 func validateServiceTrafficDistribution(service *core.Service) field.ErrorList {
5810 allErrs := field.ErrorList{}
5811
5812 if service.Spec.TrafficDistribution == nil {
5813 return allErrs
5814 }
5815
5816 if *service.Spec.TrafficDistribution != v1.ServiceTrafficDistributionPreferClose {
5817 allErrs = append(allErrs, field.NotSupported(field.NewPath("spec").Child("trafficDistribution"), *service.Spec.TrafficDistribution, []string{v1.ServiceTrafficDistributionPreferClose}))
5818 }
5819
5820 return allErrs
5821 }
5822
5823
5824 func ValidateServiceCreate(service *core.Service) field.ErrorList {
5825 return ValidateService(service)
5826 }
5827
5828
5829 func ValidateServiceUpdate(service, oldService *core.Service) field.ErrorList {
5830 allErrs := ValidateObjectMetaUpdate(&service.ObjectMeta, &oldService.ObjectMeta, field.NewPath("metadata"))
5831
5832
5833
5834
5835
5836
5837 upgradeDowngradeClusterIPsErrs := validateUpgradeDowngradeClusterIPs(oldService, service)
5838 allErrs = append(allErrs, upgradeDowngradeClusterIPsErrs...)
5839
5840 upgradeDowngradeIPFamiliesErrs := validateUpgradeDowngradeIPFamilies(oldService, service)
5841 allErrs = append(allErrs, upgradeDowngradeIPFamiliesErrs...)
5842
5843 upgradeDowngradeLoadBalancerClassErrs := validateLoadBalancerClassField(oldService, service)
5844 allErrs = append(allErrs, upgradeDowngradeLoadBalancerClassErrs...)
5845
5846 allErrs = append(allErrs, validateServiceExternalTrafficFieldsUpdate(oldService, service)...)
5847
5848 return append(allErrs, ValidateService(service)...)
5849 }
5850
5851
5852 func ValidateServiceStatusUpdate(service, oldService *core.Service) field.ErrorList {
5853 allErrs := ValidateObjectMetaUpdate(&service.ObjectMeta, &oldService.ObjectMeta, field.NewPath("metadata"))
5854 allErrs = append(allErrs, ValidateLoadBalancerStatus(&service.Status.LoadBalancer, field.NewPath("status", "loadBalancer"), &service.Spec)...)
5855 return allErrs
5856 }
5857
5858
5859 func ValidateReplicationController(controller *core.ReplicationController, opts PodValidationOptions) field.ErrorList {
5860 allErrs := ValidateObjectMeta(&controller.ObjectMeta, true, ValidateReplicationControllerName, field.NewPath("metadata"))
5861 allErrs = append(allErrs, ValidateReplicationControllerSpec(&controller.Spec, nil, field.NewPath("spec"), opts)...)
5862 return allErrs
5863 }
5864
5865
5866 func ValidateReplicationControllerUpdate(controller, oldController *core.ReplicationController, opts PodValidationOptions) field.ErrorList {
5867 allErrs := ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta, field.NewPath("metadata"))
5868 allErrs = append(allErrs, ValidateReplicationControllerSpec(&controller.Spec, &oldController.Spec, field.NewPath("spec"), opts)...)
5869 return allErrs
5870 }
5871
5872
5873 func ValidateReplicationControllerStatusUpdate(controller, oldController *core.ReplicationController) field.ErrorList {
5874 allErrs := ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta, field.NewPath("metadata"))
5875 allErrs = append(allErrs, ValidateReplicationControllerStatus(controller.Status, field.NewPath("status"))...)
5876 return allErrs
5877 }
5878
5879 func ValidateReplicationControllerStatus(status core.ReplicationControllerStatus, statusPath *field.Path) field.ErrorList {
5880 allErrs := field.ErrorList{}
5881 allErrs = append(allErrs, ValidateNonnegativeField(int64(status.Replicas), statusPath.Child("replicas"))...)
5882 allErrs = append(allErrs, ValidateNonnegativeField(int64(status.FullyLabeledReplicas), statusPath.Child("fullyLabeledReplicas"))...)
5883 allErrs = append(allErrs, ValidateNonnegativeField(int64(status.ReadyReplicas), statusPath.Child("readyReplicas"))...)
5884 allErrs = append(allErrs, ValidateNonnegativeField(int64(status.AvailableReplicas), statusPath.Child("availableReplicas"))...)
5885 allErrs = append(allErrs, ValidateNonnegativeField(int64(status.ObservedGeneration), statusPath.Child("observedGeneration"))...)
5886 msg := "cannot be greater than status.replicas"
5887 if status.FullyLabeledReplicas > status.Replicas {
5888 allErrs = append(allErrs, field.Invalid(statusPath.Child("fullyLabeledReplicas"), status.FullyLabeledReplicas, msg))
5889 }
5890 if status.ReadyReplicas > status.Replicas {
5891 allErrs = append(allErrs, field.Invalid(statusPath.Child("readyReplicas"), status.ReadyReplicas, msg))
5892 }
5893 if status.AvailableReplicas > status.Replicas {
5894 allErrs = append(allErrs, field.Invalid(statusPath.Child("availableReplicas"), status.AvailableReplicas, msg))
5895 }
5896 if status.AvailableReplicas > status.ReadyReplicas {
5897 allErrs = append(allErrs, field.Invalid(statusPath.Child("availableReplicas"), status.AvailableReplicas, "cannot be greater than readyReplicas"))
5898 }
5899 return allErrs
5900 }
5901
5902
5903 func ValidateNonEmptySelector(selectorMap map[string]string, fldPath *field.Path) field.ErrorList {
5904 allErrs := field.ErrorList{}
5905 selector := labels.Set(selectorMap).AsSelector()
5906 if selector.Empty() {
5907 allErrs = append(allErrs, field.Required(fldPath, ""))
5908 }
5909 return allErrs
5910 }
5911
5912
5913 func ValidatePodTemplateSpecForRC(template, oldTemplate *core.PodTemplateSpec, selectorMap map[string]string, replicas int32, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
5914 allErrs := field.ErrorList{}
5915 if template == nil {
5916 allErrs = append(allErrs, field.Required(fldPath, ""))
5917 } else {
5918 selector := labels.Set(selectorMap).AsSelector()
5919 if !selector.Empty() {
5920
5921 labels := labels.Set(template.Labels)
5922 if !selector.Matches(labels) {
5923 allErrs = append(allErrs, field.Invalid(fldPath.Child("metadata", "labels"), template.Labels, "`selector` does not match template `labels`"))
5924 }
5925 }
5926 allErrs = append(allErrs, ValidatePodTemplateSpec(template, fldPath, opts)...)
5927
5928 var oldVols []core.Volume
5929 if oldTemplate != nil {
5930 oldVols = oldTemplate.Spec.Volumes
5931 }
5932 if replicas > 1 {
5933 allErrs = append(allErrs, ValidateReadOnlyPersistentDisks(template.Spec.Volumes, oldVols, fldPath.Child("spec", "volumes"))...)
5934 }
5935
5936 if template.Spec.RestartPolicy != core.RestartPolicyAlways {
5937 allErrs = append(allErrs, field.NotSupported(fldPath.Child("spec", "restartPolicy"), template.Spec.RestartPolicy, []core.RestartPolicy{core.RestartPolicyAlways}))
5938 }
5939 if template.Spec.ActiveDeadlineSeconds != nil {
5940 allErrs = append(allErrs, field.Forbidden(fldPath.Child("spec", "activeDeadlineSeconds"), "activeDeadlineSeconds in ReplicationController is not Supported"))
5941 }
5942 }
5943 return allErrs
5944 }
5945
5946
5947 func ValidateReplicationControllerSpec(spec, oldSpec *core.ReplicationControllerSpec, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
5948 allErrs := field.ErrorList{}
5949 allErrs = append(allErrs, ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...)
5950 allErrs = append(allErrs, ValidateNonEmptySelector(spec.Selector, fldPath.Child("selector"))...)
5951 allErrs = append(allErrs, ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...)
5952
5953 var oldTemplate *core.PodTemplateSpec
5954 if oldSpec != nil {
5955 oldTemplate = oldSpec.Template
5956 }
5957 allErrs = append(allErrs, ValidatePodTemplateSpecForRC(spec.Template, oldTemplate, spec.Selector, spec.Replicas, fldPath.Child("template"), opts)...)
5958 return allErrs
5959 }
5960
5961
5962 func ValidatePodTemplateSpec(spec *core.PodTemplateSpec, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
5963 allErrs := field.ErrorList{}
5964 allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.Labels, fldPath.Child("labels"))...)
5965 allErrs = append(allErrs, ValidateAnnotations(spec.Annotations, fldPath.Child("annotations"))...)
5966 allErrs = append(allErrs, ValidatePodSpecificAnnotations(spec.Annotations, &spec.Spec, fldPath.Child("annotations"), opts)...)
5967 allErrs = append(allErrs, ValidatePodSpec(&spec.Spec, nil, fldPath.Child("spec"), opts)...)
5968 allErrs = append(allErrs, validateSeccompAnnotationsAndFields(spec.ObjectMeta, &spec.Spec, fldPath.Child("spec"))...)
5969 allErrs = append(allErrs, validateAppArmorAnnotationsAndFieldsMatchOnCreate(spec.ObjectMeta, &spec.Spec, fldPath.Child("spec"))...)
5970
5971 if len(spec.Spec.EphemeralContainers) > 0 {
5972 allErrs = append(allErrs, field.Forbidden(fldPath.Child("spec", "ephemeralContainers"), "ephemeral containers not allowed in pod template"))
5973 }
5974
5975 return allErrs
5976 }
5977
5978
5979 func ValidateReadOnlyPersistentDisks(volumes, oldVolumes []core.Volume, fldPath *field.Path) field.ErrorList {
5980 allErrs := field.ErrorList{}
5981
5982 if utilfeature.DefaultFeatureGate.Enabled(features.SkipReadOnlyValidationGCE) {
5983 return field.ErrorList{}
5984 }
5985
5986 isWriteablePD := func(vol *core.Volume) bool {
5987 return vol.GCEPersistentDisk != nil && !vol.GCEPersistentDisk.ReadOnly
5988 }
5989
5990 for i := range oldVolumes {
5991 if isWriteablePD(&oldVolumes[i]) {
5992 return field.ErrorList{}
5993 }
5994 }
5995
5996 for i := range volumes {
5997 idxPath := fldPath.Index(i)
5998 if isWriteablePD(&volumes[i]) {
5999 allErrs = append(allErrs, field.Invalid(idxPath.Child("gcePersistentDisk", "readOnly"), false, "must be true for replicated pods > 1; GCE PD can only be mounted on multiple machines if it is read-only"))
6000 }
6001 }
6002 return allErrs
6003 }
6004
6005
6006 func ValidateTaintsInNodeAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList {
6007 allErrs := field.ErrorList{}
6008
6009 taints, err := helper.GetTaintsFromNodeAnnotations(annotations)
6010 if err != nil {
6011 allErrs = append(allErrs, field.Invalid(fldPath, core.TaintsAnnotationKey, err.Error()))
6012 return allErrs
6013 }
6014
6015 if len(taints) > 0 {
6016 allErrs = append(allErrs, validateNodeTaints(taints, fldPath.Child(core.TaintsAnnotationKey))...)
6017 }
6018
6019 return allErrs
6020 }
6021
6022
6023 func validateNodeTaints(taints []core.Taint, fldPath *field.Path) field.ErrorList {
6024 allErrors := field.ErrorList{}
6025
6026 uniqueTaints := map[core.TaintEffect]sets.Set[string]{}
6027
6028 for i, currTaint := range taints {
6029 idxPath := fldPath.Index(i)
6030
6031 allErrors = append(allErrors, unversionedvalidation.ValidateLabelName(currTaint.Key, idxPath.Child("key"))...)
6032
6033 if errs := validation.IsValidLabelValue(currTaint.Value); len(errs) != 0 {
6034 allErrors = append(allErrors, field.Invalid(idxPath.Child("value"), currTaint.Value, strings.Join(errs, ";")))
6035 }
6036
6037 allErrors = append(allErrors, validateTaintEffect(&currTaint.Effect, false, idxPath.Child("effect"))...)
6038
6039
6040 if len(uniqueTaints[currTaint.Effect]) > 0 && uniqueTaints[currTaint.Effect].Has(currTaint.Key) {
6041 duplicatedError := field.Duplicate(idxPath, currTaint)
6042 duplicatedError.Detail = "taints must be unique by key and effect pair"
6043 allErrors = append(allErrors, duplicatedError)
6044 continue
6045 }
6046
6047
6048 if len(uniqueTaints[currTaint.Effect]) == 0 {
6049 uniqueTaints[currTaint.Effect] = sets.Set[string]{}
6050 }
6051 uniqueTaints[currTaint.Effect].Insert(currTaint.Key)
6052 }
6053 return allErrors
6054 }
6055
6056 func ValidateNodeSpecificAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList {
6057 allErrs := field.ErrorList{}
6058
6059 if annotations[core.TaintsAnnotationKey] != "" {
6060 allErrs = append(allErrs, ValidateTaintsInNodeAnnotations(annotations, fldPath)...)
6061 }
6062
6063 if annotations[core.PreferAvoidPodsAnnotationKey] != "" {
6064 allErrs = append(allErrs, ValidateAvoidPodsInNodeAnnotations(annotations, fldPath)...)
6065 }
6066 return allErrs
6067 }
6068
6069
6070 func ValidateNode(node *core.Node) field.ErrorList {
6071 fldPath := field.NewPath("metadata")
6072 allErrs := ValidateObjectMeta(&node.ObjectMeta, false, ValidateNodeName, fldPath)
6073 allErrs = append(allErrs, ValidateNodeSpecificAnnotations(node.ObjectMeta.Annotations, fldPath.Child("annotations"))...)
6074 if len(node.Spec.Taints) > 0 {
6075 allErrs = append(allErrs, validateNodeTaints(node.Spec.Taints, fldPath.Child("taints"))...)
6076 }
6077
6078
6079
6080
6081 allErrs = append(allErrs, ValidateNodeResources(node)...)
6082
6083
6084 if len(node.Spec.PodCIDRs) > 0 {
6085 podCIDRsField := field.NewPath("spec", "podCIDRs")
6086
6087
6088 for idx, value := range node.Spec.PodCIDRs {
6089 allErrs = append(allErrs, validation.IsValidCIDR(podCIDRsField.Index(idx), value)...)
6090 }
6091
6092
6093
6094
6095 if len(node.Spec.PodCIDRs) > 1 {
6096 dualStack, err := netutils.IsDualStackCIDRStrings(node.Spec.PodCIDRs)
6097 if err != nil {
6098 allErrs = append(allErrs, field.InternalError(podCIDRsField, fmt.Errorf("invalid PodCIDRs. failed to check with dual stack with error:%v", err)))
6099 }
6100 if !dualStack || len(node.Spec.PodCIDRs) > 2 {
6101 allErrs = append(allErrs, field.Invalid(podCIDRsField, node.Spec.PodCIDRs, "may specify no more than one CIDR for each IP family"))
6102 }
6103
6104
6105 seen := sets.Set[string]{}
6106 for i, value := range node.Spec.PodCIDRs {
6107 if seen.Has(value) {
6108 allErrs = append(allErrs, field.Duplicate(podCIDRsField.Index(i), value))
6109 }
6110 seen.Insert(value)
6111 }
6112 }
6113 }
6114
6115 return allErrs
6116 }
6117
6118
6119 func ValidateNodeResources(node *core.Node) field.ErrorList {
6120 allErrs := field.ErrorList{}
6121
6122
6123 for k, v := range node.Status.Capacity {
6124 resPath := field.NewPath("status", "capacity", string(k))
6125 allErrs = append(allErrs, ValidateResourceQuantityValue(k, v, resPath)...)
6126 }
6127
6128
6129 for k, v := range node.Status.Allocatable {
6130 resPath := field.NewPath("status", "allocatable", string(k))
6131 allErrs = append(allErrs, ValidateResourceQuantityValue(k, v, resPath)...)
6132 }
6133 return allErrs
6134 }
6135
6136
6137 func ValidateNodeUpdate(node, oldNode *core.Node) field.ErrorList {
6138 fldPath := field.NewPath("metadata")
6139 allErrs := ValidateObjectMetaUpdate(&node.ObjectMeta, &oldNode.ObjectMeta, fldPath)
6140 allErrs = append(allErrs, ValidateNodeSpecificAnnotations(node.ObjectMeta.Annotations, fldPath.Child("annotations"))...)
6141
6142
6143
6144
6145
6146
6147
6148 allErrs = append(allErrs, ValidateNodeResources(node)...)
6149
6150
6151 addresses := make(map[core.NodeAddress]bool)
6152 for i, address := range node.Status.Addresses {
6153 if _, ok := addresses[address]; ok {
6154 allErrs = append(allErrs, field.Duplicate(field.NewPath("status", "addresses").Index(i), address))
6155 }
6156 addresses[address] = true
6157 }
6158
6159
6160 if len(oldNode.Spec.PodCIDRs) > 0 {
6161
6162 if len(oldNode.Spec.PodCIDRs) != len(node.Spec.PodCIDRs) {
6163 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "podCIDRs"), "node updates may not change podCIDR except from \"\" to valid"))
6164 } else {
6165 for idx, value := range oldNode.Spec.PodCIDRs {
6166 if value != node.Spec.PodCIDRs[idx] {
6167 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "podCIDRs"), "node updates may not change podCIDR except from \"\" to valid"))
6168 }
6169 }
6170 }
6171 }
6172
6173
6174 if len(oldNode.Spec.ProviderID) > 0 && oldNode.Spec.ProviderID != node.Spec.ProviderID {
6175 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "providerID"), "node updates may not change providerID except from \"\" to valid"))
6176 }
6177
6178 if node.Spec.ConfigSource != nil {
6179 allErrs = append(allErrs, validateNodeConfigSourceSpec(node.Spec.ConfigSource, field.NewPath("spec", "configSource"))...)
6180 }
6181 if node.Status.Config != nil {
6182 allErrs = append(allErrs, validateNodeConfigStatus(node.Status.Config, field.NewPath("status", "config"))...)
6183 }
6184
6185
6186 if len(node.Spec.Taints) > 0 {
6187 allErrs = append(allErrs, validateNodeTaints(node.Spec.Taints, fldPath.Child("taints"))...)
6188 }
6189
6190 if node.Spec.DoNotUseExternalID != oldNode.Spec.DoNotUseExternalID {
6191 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "externalID"), "may not be updated"))
6192 }
6193
6194
6195
6196
6197
6198
6199
6200
6201
6202
6203 return allErrs
6204 }
6205
6206
6207
6208
6209 func validateNodeConfigSourceSpec(source *core.NodeConfigSource, fldPath *field.Path) field.ErrorList {
6210 allErrs := field.ErrorList{}
6211 count := int(0)
6212 if source.ConfigMap != nil {
6213 count++
6214 allErrs = append(allErrs, validateConfigMapNodeConfigSourceSpec(source.ConfigMap, fldPath.Child("configMap"))...)
6215 }
6216
6217
6218
6219 if count != 1 {
6220 allErrs = append(allErrs, field.Invalid(fldPath, source, "exactly one reference subfield must be non-nil"))
6221 }
6222 return allErrs
6223 }
6224
6225
6226
6227
6228 func validateConfigMapNodeConfigSourceSpec(source *core.ConfigMapNodeConfigSource, fldPath *field.Path) field.ErrorList {
6229 allErrs := field.ErrorList{}
6230
6231 if string(source.UID) != "" {
6232 allErrs = append(allErrs, field.Forbidden(fldPath.Child("uid"), "uid must not be set in spec"))
6233 }
6234 if source.ResourceVersion != "" {
6235 allErrs = append(allErrs, field.Forbidden(fldPath.Child("resourceVersion"), "resourceVersion must not be set in spec"))
6236 }
6237 return append(allErrs, validateConfigMapNodeConfigSource(source, fldPath)...)
6238 }
6239
6240
6241 func validateNodeConfigStatus(status *core.NodeConfigStatus, fldPath *field.Path) field.ErrorList {
6242 allErrs := field.ErrorList{}
6243 if status.Assigned != nil {
6244 allErrs = append(allErrs, validateNodeConfigSourceStatus(status.Assigned, fldPath.Child("assigned"))...)
6245 }
6246 if status.Active != nil {
6247 allErrs = append(allErrs, validateNodeConfigSourceStatus(status.Active, fldPath.Child("active"))...)
6248 }
6249 if status.LastKnownGood != nil {
6250 allErrs = append(allErrs, validateNodeConfigSourceStatus(status.LastKnownGood, fldPath.Child("lastKnownGood"))...)
6251 }
6252 return allErrs
6253 }
6254
6255
6256 func validateNodeConfigSourceStatus(source *core.NodeConfigSource, fldPath *field.Path) field.ErrorList {
6257 allErrs := field.ErrorList{}
6258 count := int(0)
6259 if source.ConfigMap != nil {
6260 count++
6261 allErrs = append(allErrs, validateConfigMapNodeConfigSourceStatus(source.ConfigMap, fldPath.Child("configMap"))...)
6262 }
6263
6264
6265
6266 if count != 1 {
6267 allErrs = append(allErrs, field.Invalid(fldPath, source, "exactly one reference subfield must be non-nil"))
6268 }
6269 return allErrs
6270 }
6271
6272
6273 func validateConfigMapNodeConfigSourceStatus(source *core.ConfigMapNodeConfigSource, fldPath *field.Path) field.ErrorList {
6274 allErrs := field.ErrorList{}
6275
6276 if string(source.UID) == "" {
6277 allErrs = append(allErrs, field.Required(fldPath.Child("uid"), "uid must be set in status"))
6278 }
6279 if source.ResourceVersion == "" {
6280 allErrs = append(allErrs, field.Required(fldPath.Child("resourceVersion"), "resourceVersion must be set in status"))
6281 }
6282 return append(allErrs, validateConfigMapNodeConfigSource(source, fldPath)...)
6283 }
6284
6285
6286 func validateConfigMapNodeConfigSource(source *core.ConfigMapNodeConfigSource, fldPath *field.Path) field.ErrorList {
6287 allErrs := field.ErrorList{}
6288
6289 if source.Namespace == "" {
6290 allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), "namespace must be set"))
6291 } else {
6292 for _, msg := range ValidateNameFunc(ValidateNamespaceName)(source.Namespace, false) {
6293 allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), source.Namespace, msg))
6294 }
6295 }
6296
6297 if source.Name == "" {
6298 allErrs = append(allErrs, field.Required(fldPath.Child("name"), "name must be set"))
6299 } else {
6300 for _, msg := range ValidateNameFunc(ValidateConfigMapName)(source.Name, false) {
6301 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), source.Name, msg))
6302 }
6303 }
6304
6305 if source.KubeletConfigKey == "" {
6306 allErrs = append(allErrs, field.Required(fldPath.Child("kubeletConfigKey"), "kubeletConfigKey must be set"))
6307 } else {
6308 for _, msg := range validation.IsConfigMapKey(source.KubeletConfigKey) {
6309 allErrs = append(allErrs, field.Invalid(fldPath.Child("kubeletConfigKey"), source.KubeletConfigKey, msg))
6310 }
6311 }
6312 return allErrs
6313 }
6314
6315
6316
6317 func validateResourceName(value core.ResourceName, fldPath *field.Path) field.ErrorList {
6318 allErrs := field.ErrorList{}
6319 for _, msg := range validation.IsQualifiedName(string(value)) {
6320 allErrs = append(allErrs, field.Invalid(fldPath, value, msg))
6321 }
6322 if len(allErrs) != 0 {
6323 return allErrs
6324 }
6325
6326 if len(strings.Split(string(value), "/")) == 1 {
6327 if !helper.IsStandardResourceName(value) {
6328 return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource type or fully qualified"))
6329 }
6330 }
6331
6332 return allErrs
6333 }
6334
6335
6336
6337 func validateContainerResourceName(value core.ResourceName, fldPath *field.Path) field.ErrorList {
6338 allErrs := validateResourceName(value, fldPath)
6339
6340 if len(strings.Split(string(value), "/")) == 1 {
6341 if !helper.IsStandardContainerResourceName(value) {
6342 return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource for containers"))
6343 }
6344 } else if !helper.IsNativeResource(value) {
6345 if !helper.IsExtendedResourceName(value) {
6346 return append(allErrs, field.Invalid(fldPath, value, "doesn't follow extended resource name standard"))
6347 }
6348 }
6349 return allErrs
6350 }
6351
6352
6353
6354 func ValidateResourceQuotaResourceName(value core.ResourceName, fldPath *field.Path) field.ErrorList {
6355 allErrs := validateResourceName(value, fldPath)
6356
6357 if len(strings.Split(string(value), "/")) == 1 {
6358 if !helper.IsStandardQuotaResourceName(value) {
6359 return append(allErrs, field.Invalid(fldPath, value, isInvalidQuotaResource))
6360 }
6361 }
6362 return allErrs
6363 }
6364
6365
6366 func validateLimitRangeTypeName(value core.LimitType, fldPath *field.Path) field.ErrorList {
6367 allErrs := field.ErrorList{}
6368 for _, msg := range validation.IsQualifiedName(string(value)) {
6369 allErrs = append(allErrs, field.Invalid(fldPath, value, msg))
6370 }
6371 if len(allErrs) != 0 {
6372 return allErrs
6373 }
6374
6375 if len(strings.Split(string(value), "/")) == 1 {
6376 if !helper.IsStandardLimitRangeType(value) {
6377 return append(allErrs, field.Invalid(fldPath, value, "must be a standard limit type or fully qualified"))
6378 }
6379 }
6380
6381 return allErrs
6382 }
6383
6384
6385
6386 func validateLimitRangeResourceName(limitType core.LimitType, value core.ResourceName, fldPath *field.Path) field.ErrorList {
6387 switch limitType {
6388 case core.LimitTypePod, core.LimitTypeContainer:
6389 return validateContainerResourceName(value, fldPath)
6390 default:
6391 return validateResourceName(value, fldPath)
6392 }
6393 }
6394
6395
6396 func ValidateLimitRange(limitRange *core.LimitRange) field.ErrorList {
6397 allErrs := ValidateObjectMeta(&limitRange.ObjectMeta, true, ValidateLimitRangeName, field.NewPath("metadata"))
6398
6399
6400 limitTypeSet := map[core.LimitType]bool{}
6401 fldPath := field.NewPath("spec", "limits")
6402 for i := range limitRange.Spec.Limits {
6403 idxPath := fldPath.Index(i)
6404 limit := &limitRange.Spec.Limits[i]
6405 allErrs = append(allErrs, validateLimitRangeTypeName(limit.Type, idxPath.Child("type"))...)
6406
6407 _, found := limitTypeSet[limit.Type]
6408 if found {
6409 allErrs = append(allErrs, field.Duplicate(idxPath.Child("type"), limit.Type))
6410 }
6411 limitTypeSet[limit.Type] = true
6412
6413 keys := sets.Set[string]{}
6414 min := map[string]resource.Quantity{}
6415 max := map[string]resource.Quantity{}
6416 defaults := map[string]resource.Quantity{}
6417 defaultRequests := map[string]resource.Quantity{}
6418 maxLimitRequestRatios := map[string]resource.Quantity{}
6419
6420 for k, q := range limit.Max {
6421 allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, k, idxPath.Child("max").Key(string(k)))...)
6422 keys.Insert(string(k))
6423 max[string(k)] = q
6424 }
6425 for k, q := range limit.Min {
6426 allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, k, idxPath.Child("min").Key(string(k)))...)
6427 keys.Insert(string(k))
6428 min[string(k)] = q
6429 }
6430
6431 if limit.Type == core.LimitTypePod {
6432 if len(limit.Default) > 0 {
6433 allErrs = append(allErrs, field.Forbidden(idxPath.Child("default"), "may not be specified when `type` is 'Pod'"))
6434 }
6435 if len(limit.DefaultRequest) > 0 {
6436 allErrs = append(allErrs, field.Forbidden(idxPath.Child("defaultRequest"), "may not be specified when `type` is 'Pod'"))
6437 }
6438 } else {
6439 for k, q := range limit.Default {
6440 allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, k, idxPath.Child("default").Key(string(k)))...)
6441 keys.Insert(string(k))
6442 defaults[string(k)] = q
6443 }
6444 for k, q := range limit.DefaultRequest {
6445 allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, k, idxPath.Child("defaultRequest").Key(string(k)))...)
6446 keys.Insert(string(k))
6447 defaultRequests[string(k)] = q
6448 }
6449 }
6450
6451 if limit.Type == core.LimitTypePersistentVolumeClaim {
6452 _, minQuantityFound := limit.Min[core.ResourceStorage]
6453 _, maxQuantityFound := limit.Max[core.ResourceStorage]
6454 if !minQuantityFound && !maxQuantityFound {
6455 allErrs = append(allErrs, field.Required(idxPath.Child("limits"), "either minimum or maximum storage value is required, but neither was provided"))
6456 }
6457 }
6458
6459 for k, q := range limit.MaxLimitRequestRatio {
6460 allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, k, idxPath.Child("maxLimitRequestRatio").Key(string(k)))...)
6461 keys.Insert(string(k))
6462 maxLimitRequestRatios[string(k)] = q
6463 }
6464
6465 for k := range keys {
6466 minQuantity, minQuantityFound := min[k]
6467 maxQuantity, maxQuantityFound := max[k]
6468 defaultQuantity, defaultQuantityFound := defaults[k]
6469 defaultRequestQuantity, defaultRequestQuantityFound := defaultRequests[k]
6470 maxRatio, maxRatioFound := maxLimitRequestRatios[k]
6471
6472 if minQuantityFound && maxQuantityFound && minQuantity.Cmp(maxQuantity) > 0 {
6473 allErrs = append(allErrs, field.Invalid(idxPath.Child("min").Key(string(k)), minQuantity, fmt.Sprintf("min value %s is greater than max value %s", minQuantity.String(), maxQuantity.String())))
6474 }
6475
6476 if defaultRequestQuantityFound && minQuantityFound && minQuantity.Cmp(defaultRequestQuantity) > 0 {
6477 allErrs = append(allErrs, field.Invalid(idxPath.Child("defaultRequest").Key(string(k)), defaultRequestQuantity, fmt.Sprintf("min value %s is greater than default request value %s", minQuantity.String(), defaultRequestQuantity.String())))
6478 }
6479
6480 if defaultRequestQuantityFound && maxQuantityFound && defaultRequestQuantity.Cmp(maxQuantity) > 0 {
6481 allErrs = append(allErrs, field.Invalid(idxPath.Child("defaultRequest").Key(string(k)), defaultRequestQuantity, fmt.Sprintf("default request value %s is greater than max value %s", defaultRequestQuantity.String(), maxQuantity.String())))
6482 }
6483
6484 if defaultRequestQuantityFound && defaultQuantityFound && defaultRequestQuantity.Cmp(defaultQuantity) > 0 {
6485 allErrs = append(allErrs, field.Invalid(idxPath.Child("defaultRequest").Key(string(k)), defaultRequestQuantity, fmt.Sprintf("default request value %s is greater than default limit value %s", defaultRequestQuantity.String(), defaultQuantity.String())))
6486 }
6487
6488 if defaultQuantityFound && minQuantityFound && minQuantity.Cmp(defaultQuantity) > 0 {
6489 allErrs = append(allErrs, field.Invalid(idxPath.Child("default").Key(string(k)), minQuantity, fmt.Sprintf("min value %s is greater than default value %s", minQuantity.String(), defaultQuantity.String())))
6490 }
6491
6492 if defaultQuantityFound && maxQuantityFound && defaultQuantity.Cmp(maxQuantity) > 0 {
6493 allErrs = append(allErrs, field.Invalid(idxPath.Child("default").Key(string(k)), maxQuantity, fmt.Sprintf("default value %s is greater than max value %s", defaultQuantity.String(), maxQuantity.String())))
6494 }
6495 if maxRatioFound && maxRatio.Cmp(*resource.NewQuantity(1, resource.DecimalSI)) < 0 {
6496 allErrs = append(allErrs, field.Invalid(idxPath.Child("maxLimitRequestRatio").Key(string(k)), maxRatio, fmt.Sprintf("ratio %s is less than 1", maxRatio.String())))
6497 }
6498 if maxRatioFound && minQuantityFound && maxQuantityFound {
6499 maxRatioValue := float64(maxRatio.Value())
6500 minQuantityValue := minQuantity.Value()
6501 maxQuantityValue := maxQuantity.Value()
6502 if maxRatio.Value() < resource.MaxMilliValue && minQuantityValue < resource.MaxMilliValue && maxQuantityValue < resource.MaxMilliValue {
6503 maxRatioValue = float64(maxRatio.MilliValue()) / 1000
6504 minQuantityValue = minQuantity.MilliValue()
6505 maxQuantityValue = maxQuantity.MilliValue()
6506 }
6507 maxRatioLimit := float64(maxQuantityValue) / float64(minQuantityValue)
6508 if maxRatioValue > maxRatioLimit {
6509 allErrs = append(allErrs, field.Invalid(idxPath.Child("maxLimitRequestRatio").Key(string(k)), maxRatio, fmt.Sprintf("ratio %s is greater than max/min = %f", maxRatio.String(), maxRatioLimit)))
6510 }
6511 }
6512
6513
6514
6515 if !helper.IsOvercommitAllowed(core.ResourceName(k)) && defaultQuantityFound && defaultRequestQuantityFound && defaultQuantity.Cmp(defaultRequestQuantity) != 0 {
6516 allErrs = append(allErrs, field.Invalid(idxPath.Child("defaultRequest").Key(string(k)), defaultRequestQuantity, fmt.Sprintf("default value %s must equal to defaultRequest value %s in %s", defaultQuantity.String(), defaultRequestQuantity.String(), k)))
6517 }
6518 }
6519 }
6520
6521 return allErrs
6522 }
6523
6524
6525 func ValidateServiceAccount(serviceAccount *core.ServiceAccount) field.ErrorList {
6526 allErrs := ValidateObjectMeta(&serviceAccount.ObjectMeta, true, ValidateServiceAccountName, field.NewPath("metadata"))
6527 return allErrs
6528 }
6529
6530
6531 func ValidateServiceAccountUpdate(newServiceAccount, oldServiceAccount *core.ServiceAccount) field.ErrorList {
6532 allErrs := ValidateObjectMetaUpdate(&newServiceAccount.ObjectMeta, &oldServiceAccount.ObjectMeta, field.NewPath("metadata"))
6533 allErrs = append(allErrs, ValidateServiceAccount(newServiceAccount)...)
6534 return allErrs
6535 }
6536
6537
6538 func ValidateSecret(secret *core.Secret) field.ErrorList {
6539 allErrs := ValidateObjectMeta(&secret.ObjectMeta, true, ValidateSecretName, field.NewPath("metadata"))
6540
6541 dataPath := field.NewPath("data")
6542 totalSize := 0
6543 for key, value := range secret.Data {
6544 for _, msg := range validation.IsConfigMapKey(key) {
6545 allErrs = append(allErrs, field.Invalid(dataPath.Key(key), key, msg))
6546 }
6547 totalSize += len(value)
6548 }
6549 if totalSize > core.MaxSecretSize {
6550 allErrs = append(allErrs, field.TooLong(dataPath, "", core.MaxSecretSize))
6551 }
6552
6553 switch secret.Type {
6554 case core.SecretTypeServiceAccountToken:
6555
6556
6557 if value := secret.Annotations[core.ServiceAccountNameKey]; len(value) == 0 {
6558 allErrs = append(allErrs, field.Required(field.NewPath("metadata", "annotations").Key(core.ServiceAccountNameKey), ""))
6559 }
6560 case core.SecretTypeOpaque, "":
6561
6562 case core.SecretTypeDockercfg:
6563 dockercfgBytes, exists := secret.Data[core.DockerConfigKey]
6564 if !exists {
6565 allErrs = append(allErrs, field.Required(dataPath.Key(core.DockerConfigKey), ""))
6566 break
6567 }
6568
6569
6570 if err := json.Unmarshal(dockercfgBytes, &map[string]interface{}{}); err != nil {
6571 allErrs = append(allErrs, field.Invalid(dataPath.Key(core.DockerConfigKey), "<secret contents redacted>", err.Error()))
6572 }
6573 case core.SecretTypeDockerConfigJSON:
6574 dockerConfigJSONBytes, exists := secret.Data[core.DockerConfigJSONKey]
6575 if !exists {
6576 allErrs = append(allErrs, field.Required(dataPath.Key(core.DockerConfigJSONKey), ""))
6577 break
6578 }
6579
6580
6581 if err := json.Unmarshal(dockerConfigJSONBytes, &map[string]interface{}{}); err != nil {
6582 allErrs = append(allErrs, field.Invalid(dataPath.Key(core.DockerConfigJSONKey), "<secret contents redacted>", err.Error()))
6583 }
6584 case core.SecretTypeBasicAuth:
6585 _, usernameFieldExists := secret.Data[core.BasicAuthUsernameKey]
6586 _, passwordFieldExists := secret.Data[core.BasicAuthPasswordKey]
6587
6588
6589 if !usernameFieldExists && !passwordFieldExists {
6590 allErrs = append(allErrs, field.Required(dataPath.Key(core.BasicAuthUsernameKey), ""))
6591 allErrs = append(allErrs, field.Required(dataPath.Key(core.BasicAuthPasswordKey), ""))
6592 break
6593 }
6594 case core.SecretTypeSSHAuth:
6595 if len(secret.Data[core.SSHAuthPrivateKey]) == 0 {
6596 allErrs = append(allErrs, field.Required(dataPath.Key(core.SSHAuthPrivateKey), ""))
6597 break
6598 }
6599
6600 case core.SecretTypeTLS:
6601 if _, exists := secret.Data[core.TLSCertKey]; !exists {
6602 allErrs = append(allErrs, field.Required(dataPath.Key(core.TLSCertKey), ""))
6603 }
6604 if _, exists := secret.Data[core.TLSPrivateKeyKey]; !exists {
6605 allErrs = append(allErrs, field.Required(dataPath.Key(core.TLSPrivateKeyKey), ""))
6606 }
6607 default:
6608
6609 }
6610
6611 return allErrs
6612 }
6613
6614
6615 func ValidateSecretUpdate(newSecret, oldSecret *core.Secret) field.ErrorList {
6616 allErrs := ValidateObjectMetaUpdate(&newSecret.ObjectMeta, &oldSecret.ObjectMeta, field.NewPath("metadata"))
6617
6618 allErrs = append(allErrs, ValidateImmutableField(newSecret.Type, oldSecret.Type, field.NewPath("type"))...)
6619 if oldSecret.Immutable != nil && *oldSecret.Immutable {
6620 if newSecret.Immutable == nil || !*newSecret.Immutable {
6621 allErrs = append(allErrs, field.Forbidden(field.NewPath("immutable"), "field is immutable when `immutable` is set"))
6622 }
6623 if !reflect.DeepEqual(newSecret.Data, oldSecret.Data) {
6624 allErrs = append(allErrs, field.Forbidden(field.NewPath("data"), "field is immutable when `immutable` is set"))
6625 }
6626
6627
6628 }
6629
6630 allErrs = append(allErrs, ValidateSecret(newSecret)...)
6631 return allErrs
6632 }
6633
6634
6635
6636
6637 var ValidateConfigMapName = apimachineryvalidation.NameIsDNSSubdomain
6638
6639
6640 func ValidateConfigMap(cfg *core.ConfigMap) field.ErrorList {
6641 allErrs := field.ErrorList{}
6642 allErrs = append(allErrs, ValidateObjectMeta(&cfg.ObjectMeta, true, ValidateConfigMapName, field.NewPath("metadata"))...)
6643
6644 totalSize := 0
6645
6646 for key, value := range cfg.Data {
6647 for _, msg := range validation.IsConfigMapKey(key) {
6648 allErrs = append(allErrs, field.Invalid(field.NewPath("data").Key(key), key, msg))
6649 }
6650
6651 if _, isValue := cfg.BinaryData[key]; isValue {
6652 msg := "duplicate of key present in binaryData"
6653 allErrs = append(allErrs, field.Invalid(field.NewPath("data").Key(key), key, msg))
6654 }
6655 totalSize += len(value)
6656 }
6657 for key, value := range cfg.BinaryData {
6658 for _, msg := range validation.IsConfigMapKey(key) {
6659 allErrs = append(allErrs, field.Invalid(field.NewPath("binaryData").Key(key), key, msg))
6660 }
6661 totalSize += len(value)
6662 }
6663 if totalSize > core.MaxSecretSize {
6664
6665 allErrs = append(allErrs, field.TooLong(field.NewPath(""), cfg, core.MaxSecretSize))
6666 }
6667
6668 return allErrs
6669 }
6670
6671
6672 func ValidateConfigMapUpdate(newCfg, oldCfg *core.ConfigMap) field.ErrorList {
6673 allErrs := field.ErrorList{}
6674 allErrs = append(allErrs, ValidateObjectMetaUpdate(&newCfg.ObjectMeta, &oldCfg.ObjectMeta, field.NewPath("metadata"))...)
6675
6676 if oldCfg.Immutable != nil && *oldCfg.Immutable {
6677 if newCfg.Immutable == nil || !*newCfg.Immutable {
6678 allErrs = append(allErrs, field.Forbidden(field.NewPath("immutable"), "field is immutable when `immutable` is set"))
6679 }
6680 if !reflect.DeepEqual(newCfg.Data, oldCfg.Data) {
6681 allErrs = append(allErrs, field.Forbidden(field.NewPath("data"), "field is immutable when `immutable` is set"))
6682 }
6683 if !reflect.DeepEqual(newCfg.BinaryData, oldCfg.BinaryData) {
6684 allErrs = append(allErrs, field.Forbidden(field.NewPath("binaryData"), "field is immutable when `immutable` is set"))
6685 }
6686 }
6687
6688 allErrs = append(allErrs, ValidateConfigMap(newCfg)...)
6689 return allErrs
6690 }
6691
6692 func validateBasicResource(quantity resource.Quantity, fldPath *field.Path) field.ErrorList {
6693 if quantity.Value() < 0 {
6694 return field.ErrorList{field.Invalid(fldPath, quantity.Value(), "must be a valid resource quantity")}
6695 }
6696 return field.ErrorList{}
6697 }
6698
6699
6700 func ValidateResourceRequirements(requirements *core.ResourceRequirements, podClaimNames sets.Set[string], fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
6701 allErrs := field.ErrorList{}
6702 limPath := fldPath.Child("limits")
6703 reqPath := fldPath.Child("requests")
6704 limContainsCPUOrMemory := false
6705 reqContainsCPUOrMemory := false
6706 limContainsHugePages := false
6707 reqContainsHugePages := false
6708 supportedQoSComputeResources := sets.New(core.ResourceCPU, core.ResourceMemory)
6709 for resourceName, quantity := range requirements.Limits {
6710
6711 fldPath := limPath.Key(string(resourceName))
6712
6713 allErrs = append(allErrs, validateContainerResourceName(resourceName, fldPath)...)
6714
6715
6716 allErrs = append(allErrs, ValidateResourceQuantityValue(resourceName, quantity, fldPath)...)
6717
6718 if helper.IsHugePageResourceName(resourceName) {
6719 limContainsHugePages = true
6720 if err := validateResourceQuantityHugePageValue(resourceName, quantity, opts); err != nil {
6721 allErrs = append(allErrs, field.Invalid(fldPath, quantity.String(), err.Error()))
6722 }
6723 }
6724
6725 if supportedQoSComputeResources.Has(resourceName) {
6726 limContainsCPUOrMemory = true
6727 }
6728 }
6729 for resourceName, quantity := range requirements.Requests {
6730 fldPath := reqPath.Key(string(resourceName))
6731
6732 allErrs = append(allErrs, validateContainerResourceName(resourceName, fldPath)...)
6733
6734 allErrs = append(allErrs, ValidateResourceQuantityValue(resourceName, quantity, fldPath)...)
6735
6736
6737 limitQuantity, exists := requirements.Limits[resourceName]
6738 if exists {
6739
6740 if quantity.Cmp(limitQuantity) != 0 && !helper.IsOvercommitAllowed(resourceName) {
6741 allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be equal to %s limit of %s", resourceName, limitQuantity.String())))
6742 } else if quantity.Cmp(limitQuantity) > 0 {
6743 allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be less than or equal to %s limit of %s", resourceName, limitQuantity.String())))
6744 }
6745 } else if !helper.IsOvercommitAllowed(resourceName) {
6746 allErrs = append(allErrs, field.Required(limPath, "Limit must be set for non overcommitable resources"))
6747 }
6748 if helper.IsHugePageResourceName(resourceName) {
6749 reqContainsHugePages = true
6750 if err := validateResourceQuantityHugePageValue(resourceName, quantity, opts); err != nil {
6751 allErrs = append(allErrs, field.Invalid(fldPath, quantity.String(), err.Error()))
6752 }
6753 }
6754 if supportedQoSComputeResources.Has(resourceName) {
6755 reqContainsCPUOrMemory = true
6756 }
6757
6758 }
6759 if !limContainsCPUOrMemory && !reqContainsCPUOrMemory && (reqContainsHugePages || limContainsHugePages) {
6760 allErrs = append(allErrs, field.Forbidden(fldPath, "HugePages require cpu or memory"))
6761 }
6762
6763 allErrs = append(allErrs, validateResourceClaimNames(requirements.Claims, podClaimNames, fldPath.Child("claims"))...)
6764
6765 return allErrs
6766 }
6767
6768
6769
6770
6771 func validateResourceClaimNames(claims []core.ResourceClaim, podClaimNames sets.Set[string], fldPath *field.Path) field.ErrorList {
6772 var allErrs field.ErrorList
6773 names := sets.Set[string]{}
6774 for i, claim := range claims {
6775 name := claim.Name
6776 if name == "" {
6777 allErrs = append(allErrs, field.Required(fldPath.Index(i), ""))
6778 } else {
6779 if names.Has(name) {
6780 allErrs = append(allErrs, field.Duplicate(fldPath.Index(i), name))
6781 } else {
6782 names.Insert(name)
6783 }
6784 if !podClaimNames.Has(name) {
6785
6786
6787
6788 error := field.NotFound(fldPath.Index(i), name)
6789 error.Detail = "must be one of the names in pod.spec.resourceClaims"
6790 if len(podClaimNames) == 0 {
6791 error.Detail += " which is empty"
6792 } else {
6793 error.Detail += ": " + strings.Join(sets.List(podClaimNames), ", ")
6794 }
6795 allErrs = append(allErrs, error)
6796 }
6797 }
6798 }
6799 return allErrs
6800 }
6801
6802 func validateResourceQuantityHugePageValue(name core.ResourceName, quantity resource.Quantity, opts PodValidationOptions) error {
6803 if !helper.IsHugePageResourceName(name) {
6804 return nil
6805 }
6806
6807 if !opts.AllowIndivisibleHugePagesValues && !helper.IsHugePageResourceValueDivisible(name, quantity) {
6808 return fmt.Errorf("%s is not positive integer multiple of %s", quantity.String(), name)
6809 }
6810
6811 return nil
6812 }
6813
6814
6815 func validateResourceQuotaScopes(resourceQuotaSpec *core.ResourceQuotaSpec, fld *field.Path) field.ErrorList {
6816 allErrs := field.ErrorList{}
6817 if len(resourceQuotaSpec.Scopes) == 0 {
6818 return allErrs
6819 }
6820 hardLimits := sets.New[core.ResourceName]()
6821 for k := range resourceQuotaSpec.Hard {
6822 hardLimits.Insert(k)
6823 }
6824 fldPath := fld.Child("scopes")
6825 scopeSet := sets.New[core.ResourceQuotaScope]()
6826 for _, scope := range resourceQuotaSpec.Scopes {
6827 if !helper.IsStandardResourceQuotaScope(scope) {
6828 allErrs = append(allErrs, field.Invalid(fldPath, resourceQuotaSpec.Scopes, "unsupported scope"))
6829 }
6830 for _, k := range sets.List(hardLimits) {
6831 if helper.IsStandardQuotaResourceName(k) && !helper.IsResourceQuotaScopeValidForResource(scope, k) {
6832 allErrs = append(allErrs, field.Invalid(fldPath, resourceQuotaSpec.Scopes, "unsupported scope applied to resource"))
6833 }
6834 }
6835 scopeSet.Insert(scope)
6836 }
6837 invalidScopePairs := []sets.Set[core.ResourceQuotaScope]{
6838 sets.New(core.ResourceQuotaScopeBestEffort, core.ResourceQuotaScopeNotBestEffort),
6839 sets.New(core.ResourceQuotaScopeTerminating, core.ResourceQuotaScopeNotTerminating),
6840 }
6841 for _, invalidScopePair := range invalidScopePairs {
6842 if scopeSet.HasAll(sets.List(invalidScopePair)...) {
6843 allErrs = append(allErrs, field.Invalid(fldPath, resourceQuotaSpec.Scopes, "conflicting scopes"))
6844 }
6845 }
6846 return allErrs
6847 }
6848
6849
6850 func validateScopedResourceSelectorRequirement(resourceQuotaSpec *core.ResourceQuotaSpec, fld *field.Path) field.ErrorList {
6851 allErrs := field.ErrorList{}
6852 hardLimits := sets.New[core.ResourceName]()
6853 for k := range resourceQuotaSpec.Hard {
6854 hardLimits.Insert(k)
6855 }
6856 fldPath := fld.Child("matchExpressions")
6857 scopeSet := sets.New[core.ResourceQuotaScope]()
6858 for _, req := range resourceQuotaSpec.ScopeSelector.MatchExpressions {
6859 if !helper.IsStandardResourceQuotaScope(req.ScopeName) {
6860 allErrs = append(allErrs, field.Invalid(fldPath.Child("scopeName"), req.ScopeName, "unsupported scope"))
6861 }
6862 for _, k := range sets.List(hardLimits) {
6863 if helper.IsStandardQuotaResourceName(k) && !helper.IsResourceQuotaScopeValidForResource(req.ScopeName, k) {
6864 allErrs = append(allErrs, field.Invalid(fldPath, resourceQuotaSpec.ScopeSelector, "unsupported scope applied to resource"))
6865 }
6866 }
6867 switch req.ScopeName {
6868 case core.ResourceQuotaScopeBestEffort, core.ResourceQuotaScopeNotBestEffort, core.ResourceQuotaScopeTerminating, core.ResourceQuotaScopeNotTerminating, core.ResourceQuotaScopeCrossNamespacePodAffinity:
6869 if req.Operator != core.ScopeSelectorOpExists {
6870 allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), req.Operator,
6871 "must be 'Exist' when scope is any of ResourceQuotaScopeTerminating, ResourceQuotaScopeNotTerminating, ResourceQuotaScopeBestEffort, ResourceQuotaScopeNotBestEffort or ResourceQuotaScopeCrossNamespacePodAffinity"))
6872 }
6873 }
6874
6875 switch req.Operator {
6876 case core.ScopeSelectorOpIn, core.ScopeSelectorOpNotIn:
6877 if len(req.Values) == 0 {
6878 allErrs = append(allErrs, field.Required(fldPath.Child("values"),
6879 "must be at least one value when `operator` is 'In' or 'NotIn' for scope selector"))
6880 }
6881 case core.ScopeSelectorOpExists, core.ScopeSelectorOpDoesNotExist:
6882 if len(req.Values) != 0 {
6883 allErrs = append(allErrs, field.Invalid(fldPath.Child("values"), req.Values,
6884 "must be no value when `operator` is 'Exist' or 'DoesNotExist' for scope selector"))
6885 }
6886 default:
6887 allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), req.Operator, "not a valid selector operator"))
6888 }
6889 scopeSet.Insert(req.ScopeName)
6890 }
6891 invalidScopePairs := []sets.Set[core.ResourceQuotaScope]{
6892 sets.New(core.ResourceQuotaScopeBestEffort, core.ResourceQuotaScopeNotBestEffort),
6893 sets.New(core.ResourceQuotaScopeTerminating, core.ResourceQuotaScopeNotTerminating),
6894 }
6895 for _, invalidScopePair := range invalidScopePairs {
6896 if scopeSet.HasAll(sets.List(invalidScopePair)...) {
6897 allErrs = append(allErrs, field.Invalid(fldPath, resourceQuotaSpec.Scopes, "conflicting scopes"))
6898 }
6899 }
6900
6901 return allErrs
6902 }
6903
6904
6905 func validateScopeSelector(resourceQuotaSpec *core.ResourceQuotaSpec, fld *field.Path) field.ErrorList {
6906 allErrs := field.ErrorList{}
6907 if resourceQuotaSpec.ScopeSelector == nil {
6908 return allErrs
6909 }
6910 allErrs = append(allErrs, validateScopedResourceSelectorRequirement(resourceQuotaSpec, fld.Child("scopeSelector"))...)
6911 return allErrs
6912 }
6913
6914
6915 func ValidateResourceQuota(resourceQuota *core.ResourceQuota) field.ErrorList {
6916 allErrs := ValidateObjectMeta(&resourceQuota.ObjectMeta, true, ValidateResourceQuotaName, field.NewPath("metadata"))
6917
6918 allErrs = append(allErrs, ValidateResourceQuotaSpec(&resourceQuota.Spec, field.NewPath("spec"))...)
6919 allErrs = append(allErrs, ValidateResourceQuotaStatus(&resourceQuota.Status, field.NewPath("status"))...)
6920
6921 return allErrs
6922 }
6923
6924 func ValidateResourceQuotaStatus(status *core.ResourceQuotaStatus, fld *field.Path) field.ErrorList {
6925 allErrs := field.ErrorList{}
6926
6927 fldPath := fld.Child("hard")
6928 for k, v := range status.Hard {
6929 resPath := fldPath.Key(string(k))
6930 allErrs = append(allErrs, ValidateResourceQuotaResourceName(k, resPath)...)
6931 allErrs = append(allErrs, ValidateResourceQuantityValue(k, v, resPath)...)
6932 }
6933 fldPath = fld.Child("used")
6934 for k, v := range status.Used {
6935 resPath := fldPath.Key(string(k))
6936 allErrs = append(allErrs, ValidateResourceQuotaResourceName(k, resPath)...)
6937 allErrs = append(allErrs, ValidateResourceQuantityValue(k, v, resPath)...)
6938 }
6939
6940 return allErrs
6941 }
6942
6943 func ValidateResourceQuotaSpec(resourceQuotaSpec *core.ResourceQuotaSpec, fld *field.Path) field.ErrorList {
6944 allErrs := field.ErrorList{}
6945
6946 fldPath := fld.Child("hard")
6947 for k, v := range resourceQuotaSpec.Hard {
6948 resPath := fldPath.Key(string(k))
6949 allErrs = append(allErrs, ValidateResourceQuotaResourceName(k, resPath)...)
6950 allErrs = append(allErrs, ValidateResourceQuantityValue(k, v, resPath)...)
6951 }
6952
6953 allErrs = append(allErrs, validateResourceQuotaScopes(resourceQuotaSpec, fld)...)
6954 allErrs = append(allErrs, validateScopeSelector(resourceQuotaSpec, fld)...)
6955
6956 return allErrs
6957 }
6958
6959
6960 func ValidateResourceQuantityValue(resource core.ResourceName, value resource.Quantity, fldPath *field.Path) field.ErrorList {
6961 allErrs := field.ErrorList{}
6962 allErrs = append(allErrs, ValidateNonnegativeQuantity(value, fldPath)...)
6963 if helper.IsIntegerResourceName(resource) {
6964 if value.MilliValue()%int64(1000) != int64(0) {
6965 allErrs = append(allErrs, field.Invalid(fldPath, value, isNotIntegerErrorMsg))
6966 }
6967 }
6968 return allErrs
6969 }
6970
6971
6972 func ValidateResourceQuotaUpdate(newResourceQuota, oldResourceQuota *core.ResourceQuota) field.ErrorList {
6973 allErrs := ValidateObjectMetaUpdate(&newResourceQuota.ObjectMeta, &oldResourceQuota.ObjectMeta, field.NewPath("metadata"))
6974 allErrs = append(allErrs, ValidateResourceQuotaSpec(&newResourceQuota.Spec, field.NewPath("spec"))...)
6975
6976
6977 fldPath := field.NewPath("spec", "scopes")
6978 oldScopes := sets.New[string]()
6979 newScopes := sets.New[string]()
6980 for _, scope := range newResourceQuota.Spec.Scopes {
6981 newScopes.Insert(string(scope))
6982 }
6983 for _, scope := range oldResourceQuota.Spec.Scopes {
6984 oldScopes.Insert(string(scope))
6985 }
6986 if !oldScopes.Equal(newScopes) {
6987 allErrs = append(allErrs, field.Invalid(fldPath, newResourceQuota.Spec.Scopes, fieldImmutableErrorMsg))
6988 }
6989
6990 return allErrs
6991 }
6992
6993
6994 func ValidateResourceQuotaStatusUpdate(newResourceQuota, oldResourceQuota *core.ResourceQuota) field.ErrorList {
6995 allErrs := ValidateObjectMetaUpdate(&newResourceQuota.ObjectMeta, &oldResourceQuota.ObjectMeta, field.NewPath("metadata"))
6996 if len(newResourceQuota.ResourceVersion) == 0 {
6997 allErrs = append(allErrs, field.Required(field.NewPath("resourceVersion"), ""))
6998 }
6999 fldPath := field.NewPath("status", "hard")
7000 for k, v := range newResourceQuota.Status.Hard {
7001 resPath := fldPath.Key(string(k))
7002 allErrs = append(allErrs, ValidateResourceQuotaResourceName(k, resPath)...)
7003 allErrs = append(allErrs, ValidateResourceQuantityValue(k, v, resPath)...)
7004 }
7005 fldPath = field.NewPath("status", "used")
7006 for k, v := range newResourceQuota.Status.Used {
7007 resPath := fldPath.Key(string(k))
7008 allErrs = append(allErrs, ValidateResourceQuotaResourceName(k, resPath)...)
7009 allErrs = append(allErrs, ValidateResourceQuantityValue(k, v, resPath)...)
7010 }
7011 return allErrs
7012 }
7013
7014
7015 func ValidateNamespace(namespace *core.Namespace) field.ErrorList {
7016 allErrs := ValidateObjectMeta(&namespace.ObjectMeta, false, ValidateNamespaceName, field.NewPath("metadata"))
7017 for i := range namespace.Spec.Finalizers {
7018 allErrs = append(allErrs, validateFinalizerName(string(namespace.Spec.Finalizers[i]), field.NewPath("spec", "finalizers"))...)
7019 }
7020 return allErrs
7021 }
7022
7023
7024 func validateFinalizerName(stringValue string, fldPath *field.Path) field.ErrorList {
7025 allErrs := apimachineryvalidation.ValidateFinalizerName(stringValue, fldPath)
7026 allErrs = append(allErrs, validateKubeFinalizerName(stringValue, fldPath)...)
7027 return allErrs
7028 }
7029
7030
7031 func validateKubeFinalizerName(stringValue string, fldPath *field.Path) field.ErrorList {
7032 allErrs := field.ErrorList{}
7033 if len(strings.Split(stringValue, "/")) == 1 {
7034 if !helper.IsStandardFinalizerName(stringValue) {
7035 return append(allErrs, field.Invalid(fldPath, stringValue, "name is neither a standard finalizer name nor is it fully qualified"))
7036 }
7037 }
7038
7039 return allErrs
7040 }
7041
7042
7043 func ValidateNamespaceUpdate(newNamespace *core.Namespace, oldNamespace *core.Namespace) field.ErrorList {
7044 allErrs := ValidateObjectMetaUpdate(&newNamespace.ObjectMeta, &oldNamespace.ObjectMeta, field.NewPath("metadata"))
7045 return allErrs
7046 }
7047
7048
7049 func ValidateNamespaceStatusUpdate(newNamespace, oldNamespace *core.Namespace) field.ErrorList {
7050 allErrs := ValidateObjectMetaUpdate(&newNamespace.ObjectMeta, &oldNamespace.ObjectMeta, field.NewPath("metadata"))
7051 if newNamespace.DeletionTimestamp.IsZero() {
7052 if newNamespace.Status.Phase != core.NamespaceActive {
7053 allErrs = append(allErrs, field.Invalid(field.NewPath("status", "Phase"), newNamespace.Status.Phase, "may only be 'Active' if `deletionTimestamp` is empty"))
7054 }
7055 } else {
7056 if newNamespace.Status.Phase != core.NamespaceTerminating {
7057 allErrs = append(allErrs, field.Invalid(field.NewPath("status", "Phase"), newNamespace.Status.Phase, "may only be 'Terminating' if `deletionTimestamp` is not empty"))
7058 }
7059 }
7060 return allErrs
7061 }
7062
7063
7064 func ValidateNamespaceFinalizeUpdate(newNamespace, oldNamespace *core.Namespace) field.ErrorList {
7065 allErrs := ValidateObjectMetaUpdate(&newNamespace.ObjectMeta, &oldNamespace.ObjectMeta, field.NewPath("metadata"))
7066
7067 fldPath := field.NewPath("spec", "finalizers")
7068 for i := range newNamespace.Spec.Finalizers {
7069 idxPath := fldPath.Index(i)
7070 allErrs = append(allErrs, validateFinalizerName(string(newNamespace.Spec.Finalizers[i]), idxPath)...)
7071 }
7072 return allErrs
7073 }
7074
7075
7076 func ValidateEndpoints(endpoints *core.Endpoints) field.ErrorList {
7077 allErrs := ValidateObjectMeta(&endpoints.ObjectMeta, true, ValidateEndpointsName, field.NewPath("metadata"))
7078 allErrs = append(allErrs, ValidateEndpointsSpecificAnnotations(endpoints.Annotations, field.NewPath("annotations"))...)
7079 allErrs = append(allErrs, validateEndpointSubsets(endpoints.Subsets, field.NewPath("subsets"))...)
7080 return allErrs
7081 }
7082
7083
7084 func ValidateEndpointsCreate(endpoints *core.Endpoints) field.ErrorList {
7085 return ValidateEndpoints(endpoints)
7086 }
7087
7088
7089
7090
7091
7092 func ValidateEndpointsUpdate(newEndpoints, oldEndpoints *core.Endpoints) field.ErrorList {
7093 allErrs := ValidateObjectMetaUpdate(&newEndpoints.ObjectMeta, &oldEndpoints.ObjectMeta, field.NewPath("metadata"))
7094 allErrs = append(allErrs, ValidateEndpoints(newEndpoints)...)
7095 return allErrs
7096 }
7097
7098 func validateEndpointSubsets(subsets []core.EndpointSubset, fldPath *field.Path) field.ErrorList {
7099 allErrs := field.ErrorList{}
7100 for i := range subsets {
7101 ss := &subsets[i]
7102 idxPath := fldPath.Index(i)
7103
7104
7105 if len(ss.Addresses) == 0 && len(ss.NotReadyAddresses) == 0 {
7106
7107 allErrs = append(allErrs, field.Required(idxPath, "must specify `addresses` or `notReadyAddresses`"))
7108 }
7109 for addr := range ss.Addresses {
7110 allErrs = append(allErrs, validateEndpointAddress(&ss.Addresses[addr], idxPath.Child("addresses").Index(addr))...)
7111 }
7112 for addr := range ss.NotReadyAddresses {
7113 allErrs = append(allErrs, validateEndpointAddress(&ss.NotReadyAddresses[addr], idxPath.Child("notReadyAddresses").Index(addr))...)
7114 }
7115 for port := range ss.Ports {
7116 allErrs = append(allErrs, validateEndpointPort(&ss.Ports[port], len(ss.Ports) > 1, idxPath.Child("ports").Index(port))...)
7117 }
7118 }
7119
7120 return allErrs
7121 }
7122
7123 func validateEndpointAddress(address *core.EndpointAddress, fldPath *field.Path) field.ErrorList {
7124 allErrs := field.ErrorList{}
7125 allErrs = append(allErrs, validation.IsValidIP(fldPath.Child("ip"), address.IP)...)
7126 if len(address.Hostname) > 0 {
7127 allErrs = append(allErrs, ValidateDNS1123Label(address.Hostname, fldPath.Child("hostname"))...)
7128 }
7129
7130 if address.NodeName != nil {
7131 for _, msg := range ValidateNodeName(*address.NodeName, false) {
7132 allErrs = append(allErrs, field.Invalid(fldPath.Child("nodeName"), *address.NodeName, msg))
7133 }
7134 }
7135 allErrs = append(allErrs, ValidateNonSpecialIP(address.IP, fldPath.Child("ip"))...)
7136 return allErrs
7137 }
7138
7139
7140
7141
7142
7143
7144
7145
7146
7147 func ValidateNonSpecialIP(ipAddress string, fldPath *field.Path) field.ErrorList {
7148 allErrs := field.ErrorList{}
7149 ip := netutils.ParseIPSloppy(ipAddress)
7150 if ip == nil {
7151 allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "must be a valid IP address"))
7152 return allErrs
7153 }
7154 if ip.IsUnspecified() {
7155 allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, fmt.Sprintf("may not be unspecified (%v)", ipAddress)))
7156 }
7157 if ip.IsLoopback() {
7158 allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "may not be in the loopback range (127.0.0.0/8, ::1/128)"))
7159 }
7160 if ip.IsLinkLocalUnicast() {
7161 allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "may not be in the link-local range (169.254.0.0/16, fe80::/10)"))
7162 }
7163 if ip.IsLinkLocalMulticast() {
7164 allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "may not be in the link-local multicast range (224.0.0.0/24, ff02::/10)"))
7165 }
7166 return allErrs
7167 }
7168
7169 func validateEndpointPort(port *core.EndpointPort, requireName bool, fldPath *field.Path) field.ErrorList {
7170 allErrs := field.ErrorList{}
7171 if requireName && len(port.Name) == 0 {
7172 allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
7173 } else if len(port.Name) != 0 {
7174 allErrs = append(allErrs, ValidateDNS1123Label(port.Name, fldPath.Child("name"))...)
7175 }
7176 for _, msg := range validation.IsValidPortNum(int(port.Port)) {
7177 allErrs = append(allErrs, field.Invalid(fldPath.Child("port"), port.Port, msg))
7178 }
7179 if len(port.Protocol) == 0 {
7180 allErrs = append(allErrs, field.Required(fldPath.Child("protocol"), ""))
7181 } else if !supportedPortProtocols.Has(port.Protocol) {
7182 allErrs = append(allErrs, field.NotSupported(fldPath.Child("protocol"), port.Protocol, sets.List(supportedPortProtocols)))
7183 }
7184 if port.AppProtocol != nil {
7185 allErrs = append(allErrs, ValidateQualifiedName(*port.AppProtocol, fldPath.Child("appProtocol"))...)
7186 }
7187 return allErrs
7188 }
7189
7190
7191 func ValidateSecurityContext(sc *core.SecurityContext, fldPath *field.Path, hostUsers bool) field.ErrorList {
7192 allErrs := field.ErrorList{}
7193
7194 if sc == nil {
7195 return allErrs
7196 }
7197
7198 if sc.Privileged != nil {
7199 if *sc.Privileged && !capabilities.Get().AllowPrivileged {
7200 allErrs = append(allErrs, field.Forbidden(fldPath.Child("privileged"), "disallowed by cluster policy"))
7201 }
7202 }
7203
7204 if sc.RunAsUser != nil {
7205 for _, msg := range validation.IsValidUserID(*sc.RunAsUser) {
7206 allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsUser"), *sc.RunAsUser, msg))
7207 }
7208 }
7209
7210 if sc.RunAsGroup != nil {
7211 for _, msg := range validation.IsValidGroupID(*sc.RunAsGroup) {
7212 allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsGroup"), *sc.RunAsGroup, msg))
7213 }
7214 }
7215
7216 if sc.ProcMount != nil {
7217 if err := ValidateProcMountType(fldPath.Child("procMount"), *sc.ProcMount); err != nil {
7218 allErrs = append(allErrs, err)
7219 }
7220 if hostUsers && *sc.ProcMount == core.UnmaskedProcMount {
7221 allErrs = append(allErrs, field.Invalid(fldPath.Child("procMount"), sc.ProcMount, "`hostUsers` must be false to use `Unmasked`"))
7222 }
7223
7224 }
7225 allErrs = append(allErrs, validateSeccompProfileField(sc.SeccompProfile, fldPath.Child("seccompProfile"))...)
7226 if sc.AllowPrivilegeEscalation != nil && !*sc.AllowPrivilegeEscalation {
7227 if sc.Privileged != nil && *sc.Privileged {
7228 allErrs = append(allErrs, field.Invalid(fldPath, sc, "cannot set `allowPrivilegeEscalation` to false and `privileged` to true"))
7229 }
7230
7231 if sc.Capabilities != nil {
7232 for _, cap := range sc.Capabilities.Add {
7233 if string(cap) == "CAP_SYS_ADMIN" {
7234 allErrs = append(allErrs, field.Invalid(fldPath, sc, "cannot set `allowPrivilegeEscalation` to false and `capabilities.Add` CAP_SYS_ADMIN"))
7235 }
7236 }
7237 }
7238 }
7239
7240 allErrs = append(allErrs, validateWindowsSecurityContextOptions(sc.WindowsOptions, fldPath.Child("windowsOptions"))...)
7241 allErrs = append(allErrs, ValidateAppArmorProfileField(sc.AppArmorProfile, fldPath.Child("appArmorProfile"))...)
7242
7243 return allErrs
7244 }
7245
7246
7247
7248
7249
7250
7251
7252
7253
7254
7255
7256 const (
7257 maxGMSACredentialSpecLengthInKiB = 64
7258 maxGMSACredentialSpecLength = maxGMSACredentialSpecLengthInKiB * 1024
7259 maxRunAsUserNameDomainLength = 256
7260 maxRunAsUserNameUserLength = 104
7261 )
7262
7263 var (
7264
7265 ctrlRegex = regexp.MustCompile(`[[:cntrl:]]+`)
7266
7267
7268
7269 validNetBiosRegex = regexp.MustCompile(`^[^\\/:\*\?"<>|\.][^\\/:\*\?"<>|]{0,14}$`)
7270
7271
7272 dnsLabelFormat = `[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?`
7273 dnsSubdomainFormat = fmt.Sprintf(`^%s(?:\.%s)*$`, dnsLabelFormat, dnsLabelFormat)
7274 validWindowsUserDomainDNSRegex = regexp.MustCompile(dnsSubdomainFormat)
7275
7276
7277
7278 invalidUserNameCharsRegex = regexp.MustCompile(`["/\\:;|=,\+\*\?<>@\[\]]`)
7279 invalidUserNameDotsSpacesRegex = regexp.MustCompile(`^[\. ]+$`)
7280 )
7281
7282 func validateWindowsSecurityContextOptions(windowsOptions *core.WindowsSecurityContextOptions, fieldPath *field.Path) field.ErrorList {
7283 allErrs := field.ErrorList{}
7284
7285 if windowsOptions == nil {
7286 return allErrs
7287 }
7288
7289 if windowsOptions.GMSACredentialSpecName != nil {
7290
7291 for _, msg := range validation.IsDNS1123Subdomain(*windowsOptions.GMSACredentialSpecName) {
7292 allErrs = append(allErrs, field.Invalid(fieldPath.Child("gmsaCredentialSpecName"), windowsOptions.GMSACredentialSpecName, msg))
7293 }
7294 }
7295
7296 if windowsOptions.GMSACredentialSpec != nil {
7297 if l := len(*windowsOptions.GMSACredentialSpec); l == 0 {
7298 allErrs = append(allErrs, field.Invalid(fieldPath.Child("gmsaCredentialSpec"), windowsOptions.GMSACredentialSpec, "gmsaCredentialSpec cannot be an empty string"))
7299 } else if l > maxGMSACredentialSpecLength {
7300 errMsg := fmt.Sprintf("gmsaCredentialSpec size must be under %d KiB", maxGMSACredentialSpecLengthInKiB)
7301 allErrs = append(allErrs, field.Invalid(fieldPath.Child("gmsaCredentialSpec"), windowsOptions.GMSACredentialSpec, errMsg))
7302 }
7303 }
7304
7305 if windowsOptions.RunAsUserName != nil {
7306 if l := len(*windowsOptions.RunAsUserName); l == 0 {
7307 allErrs = append(allErrs, field.Invalid(fieldPath.Child("runAsUserName"), windowsOptions.RunAsUserName, "runAsUserName cannot be an empty string"))
7308 } else if ctrlRegex.MatchString(*windowsOptions.RunAsUserName) {
7309 errMsg := "runAsUserName cannot contain control characters"
7310 allErrs = append(allErrs, field.Invalid(fieldPath.Child("runAsUserName"), windowsOptions.RunAsUserName, errMsg))
7311 } else if parts := strings.Split(*windowsOptions.RunAsUserName, "\\"); len(parts) > 2 {
7312 errMsg := "runAsUserName cannot contain more than one backslash"
7313 allErrs = append(allErrs, field.Invalid(fieldPath.Child("runAsUserName"), windowsOptions.RunAsUserName, errMsg))
7314 } else {
7315 var (
7316 hasDomain = false
7317 domain = ""
7318 user string
7319 )
7320 if len(parts) == 1 {
7321 user = parts[0]
7322 } else {
7323 hasDomain = true
7324 domain = parts[0]
7325 user = parts[1]
7326 }
7327
7328 if len(domain) >= maxRunAsUserNameDomainLength {
7329 errMsg := fmt.Sprintf("runAsUserName's Domain length must be under %d characters", maxRunAsUserNameDomainLength)
7330 allErrs = append(allErrs, field.Invalid(fieldPath.Child("runAsUserName"), windowsOptions.RunAsUserName, errMsg))
7331 }
7332
7333 if hasDomain && !(validNetBiosRegex.MatchString(domain) || validWindowsUserDomainDNSRegex.MatchString(domain)) {
7334 errMsg := "runAsUserName's Domain doesn't match the NetBios nor the DNS format"
7335 allErrs = append(allErrs, field.Invalid(fieldPath.Child("runAsUserName"), windowsOptions.RunAsUserName, errMsg))
7336 }
7337
7338 if l := len(user); l == 0 {
7339 errMsg := "runAsUserName's User cannot be empty"
7340 allErrs = append(allErrs, field.Invalid(fieldPath.Child("runAsUserName"), windowsOptions.RunAsUserName, errMsg))
7341 } else if l > maxRunAsUserNameUserLength {
7342 errMsg := fmt.Sprintf("runAsUserName's User length must not be longer than %d characters", maxRunAsUserNameUserLength)
7343 allErrs = append(allErrs, field.Invalid(fieldPath.Child("runAsUserName"), windowsOptions.RunAsUserName, errMsg))
7344 }
7345
7346 if invalidUserNameDotsSpacesRegex.MatchString(user) {
7347 errMsg := `runAsUserName's User cannot contain only periods or spaces`
7348 allErrs = append(allErrs, field.Invalid(fieldPath.Child("runAsUserName"), windowsOptions.RunAsUserName, errMsg))
7349 }
7350
7351 if invalidUserNameCharsRegex.MatchString(user) {
7352 errMsg := `runAsUserName's User cannot contain the following characters: "/\:;|=,+*?<>@[]`
7353 allErrs = append(allErrs, field.Invalid(fieldPath.Child("runAsUserName"), windowsOptions.RunAsUserName, errMsg))
7354 }
7355 }
7356 }
7357
7358 return allErrs
7359 }
7360
7361 func validateWindowsHostProcessPod(podSpec *core.PodSpec, fieldPath *field.Path) field.ErrorList {
7362 allErrs := field.ErrorList{}
7363
7364
7365 containerCount := 0
7366 hostProcessContainerCount := 0
7367
7368 var podHostProcess *bool
7369 if podSpec.SecurityContext != nil && podSpec.SecurityContext.WindowsOptions != nil {
7370 podHostProcess = podSpec.SecurityContext.WindowsOptions.HostProcess
7371 }
7372
7373 hostNetwork := false
7374 if podSpec.SecurityContext != nil {
7375 hostNetwork = podSpec.SecurityContext.HostNetwork
7376 }
7377
7378 podshelper.VisitContainersWithPath(podSpec, fieldPath, func(c *core.Container, cFieldPath *field.Path) bool {
7379 containerCount++
7380
7381 var containerHostProcess *bool = nil
7382 if c.SecurityContext != nil && c.SecurityContext.WindowsOptions != nil {
7383 containerHostProcess = c.SecurityContext.WindowsOptions.HostProcess
7384 }
7385
7386 if podHostProcess != nil && containerHostProcess != nil && *podHostProcess != *containerHostProcess {
7387 errMsg := fmt.Sprintf("pod hostProcess value must be identical if both are specified, was %v", *podHostProcess)
7388 allErrs = append(allErrs, field.Invalid(cFieldPath.Child("securityContext", "windowsOptions", "hostProcess"), *containerHostProcess, errMsg))
7389 }
7390
7391 switch {
7392 case containerHostProcess != nil && *containerHostProcess:
7393
7394 hostProcessContainerCount++
7395 case containerHostProcess == nil && podHostProcess != nil && *podHostProcess:
7396
7397 hostProcessContainerCount++
7398 }
7399
7400 return true
7401 })
7402
7403 if hostProcessContainerCount > 0 {
7404
7405
7406 if hostProcessContainerCount != containerCount {
7407 errMsg := "If pod contains any hostProcess containers then all containers must be HostProcess containers"
7408 allErrs = append(allErrs, field.Invalid(fieldPath, "", errMsg))
7409 }
7410
7411
7412 if !hostNetwork {
7413 errMsg := "hostNetwork must be true if pod contains any hostProcess containers"
7414 allErrs = append(allErrs, field.Invalid(fieldPath.Child("hostNetwork"), hostNetwork, errMsg))
7415 }
7416
7417 if !capabilities.Get().AllowPrivileged {
7418 errMsg := "hostProcess containers are disallowed by cluster policy"
7419 allErrs = append(allErrs, field.Forbidden(fieldPath, errMsg))
7420 }
7421 }
7422
7423 return allErrs
7424 }
7425
7426
7427 func validateOS(podSpec *core.PodSpec, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
7428 allErrs := field.ErrorList{}
7429 os := podSpec.OS
7430 if os == nil {
7431 return allErrs
7432 }
7433 if len(os.Name) == 0 {
7434 return append(allErrs, field.Required(fldPath.Child("name"), "cannot be empty"))
7435 }
7436 if !validOS.Has(os.Name) {
7437 allErrs = append(allErrs, field.NotSupported(fldPath, os.Name, sets.List(validOS)))
7438 }
7439 return allErrs
7440 }
7441
7442 func ValidatePodLogOptions(opts *core.PodLogOptions) field.ErrorList {
7443 allErrs := field.ErrorList{}
7444 if opts.TailLines != nil && *opts.TailLines < 0 {
7445 allErrs = append(allErrs, field.Invalid(field.NewPath("tailLines"), *opts.TailLines, isNegativeErrorMsg))
7446 }
7447 if opts.LimitBytes != nil && *opts.LimitBytes < 1 {
7448 allErrs = append(allErrs, field.Invalid(field.NewPath("limitBytes"), *opts.LimitBytes, "must be greater than 0"))
7449 }
7450 switch {
7451 case opts.SinceSeconds != nil && opts.SinceTime != nil:
7452 allErrs = append(allErrs, field.Forbidden(field.NewPath(""), "at most one of `sinceTime` or `sinceSeconds` may be specified"))
7453 case opts.SinceSeconds != nil:
7454 if *opts.SinceSeconds < 1 {
7455 allErrs = append(allErrs, field.Invalid(field.NewPath("sinceSeconds"), *opts.SinceSeconds, "must be greater than 0"))
7456 }
7457 }
7458 return allErrs
7459 }
7460
7461 var (
7462 supportedLoadBalancerIPMode = sets.New(core.LoadBalancerIPModeVIP, core.LoadBalancerIPModeProxy)
7463 )
7464
7465
7466 func ValidateLoadBalancerStatus(status *core.LoadBalancerStatus, fldPath *field.Path, spec *core.ServiceSpec) field.ErrorList {
7467 allErrs := field.ErrorList{}
7468 ingrPath := fldPath.Child("ingress")
7469 if !utilfeature.DefaultFeatureGate.Enabled(features.AllowServiceLBStatusOnNonLB) && spec.Type != core.ServiceTypeLoadBalancer && len(status.Ingress) != 0 {
7470 allErrs = append(allErrs, field.Forbidden(ingrPath, "may only be used when `spec.type` is 'LoadBalancer'"))
7471 } else {
7472 for i, ingress := range status.Ingress {
7473 idxPath := ingrPath.Index(i)
7474 if len(ingress.IP) > 0 {
7475 allErrs = append(allErrs, validation.IsValidIP(idxPath.Child("ip"), ingress.IP)...)
7476 }
7477
7478 if utilfeature.DefaultFeatureGate.Enabled(features.LoadBalancerIPMode) && ingress.IPMode == nil {
7479 if len(ingress.IP) > 0 {
7480 allErrs = append(allErrs, field.Required(idxPath.Child("ipMode"), "must be specified when `ip` is set"))
7481 }
7482 } else if ingress.IPMode != nil && len(ingress.IP) == 0 {
7483 allErrs = append(allErrs, field.Forbidden(idxPath.Child("ipMode"), "may not be specified when `ip` is not set"))
7484 } else if ingress.IPMode != nil && !supportedLoadBalancerIPMode.Has(*ingress.IPMode) {
7485 allErrs = append(allErrs, field.NotSupported(idxPath.Child("ipMode"), ingress.IPMode, sets.List(supportedLoadBalancerIPMode)))
7486 }
7487
7488 if len(ingress.Hostname) > 0 {
7489 for _, msg := range validation.IsDNS1123Subdomain(ingress.Hostname) {
7490 allErrs = append(allErrs, field.Invalid(idxPath.Child("hostname"), ingress.Hostname, msg))
7491 }
7492 if isIP := (netutils.ParseIPSloppy(ingress.Hostname) != nil); isIP {
7493 allErrs = append(allErrs, field.Invalid(idxPath.Child("hostname"), ingress.Hostname, "must be a DNS name, not an IP address"))
7494 }
7495 }
7496 }
7497 }
7498 return allErrs
7499 }
7500
7501
7502
7503
7504
7505 func validateVolumeNodeAffinity(nodeAffinity *core.VolumeNodeAffinity, fldPath *field.Path) (bool, field.ErrorList) {
7506 allErrs := field.ErrorList{}
7507
7508 if nodeAffinity == nil {
7509 return false, allErrs
7510 }
7511
7512 if nodeAffinity.Required != nil {
7513 allErrs = append(allErrs, ValidateNodeSelector(nodeAffinity.Required, fldPath.Child("required"))...)
7514 } else {
7515 allErrs = append(allErrs, field.Required(fldPath.Child("required"), "must specify required node constraints"))
7516 }
7517
7518 return true, allErrs
7519 }
7520
7521 func IsDecremented(update, old *int32) bool {
7522 if update == nil && old != nil {
7523 return true
7524 }
7525 if update == nil || old == nil {
7526 return false
7527 }
7528 return *update < *old
7529 }
7530
7531
7532 func ValidateProcMountType(fldPath *field.Path, procMountType core.ProcMountType) *field.Error {
7533 switch procMountType {
7534 case core.DefaultProcMount, core.UnmaskedProcMount:
7535 return nil
7536 default:
7537 return field.NotSupported(fldPath, procMountType, []core.ProcMountType{core.DefaultProcMount, core.UnmaskedProcMount})
7538 }
7539 }
7540
7541 var (
7542 supportedScheduleActions = sets.New(core.DoNotSchedule, core.ScheduleAnyway)
7543 )
7544
7545
7546 func validateTopologySpreadConstraints(constraints []core.TopologySpreadConstraint, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
7547 allErrs := field.ErrorList{}
7548
7549 for i, constraint := range constraints {
7550 subFldPath := fldPath.Index(i)
7551 if err := ValidateMaxSkew(subFldPath.Child("maxSkew"), constraint.MaxSkew); err != nil {
7552 allErrs = append(allErrs, err)
7553 }
7554 if err := ValidateTopologyKey(subFldPath.Child("topologyKey"), constraint.TopologyKey); err != nil {
7555 allErrs = append(allErrs, err)
7556 }
7557 if err := ValidateWhenUnsatisfiable(subFldPath.Child("whenUnsatisfiable"), constraint.WhenUnsatisfiable); err != nil {
7558 allErrs = append(allErrs, err)
7559 }
7560
7561 if err := ValidateSpreadConstraintNotRepeat(subFldPath.Child("{topologyKey, whenUnsatisfiable}"), constraint, constraints[i+1:]); err != nil {
7562 allErrs = append(allErrs, err)
7563 }
7564 allErrs = append(allErrs, validateMinDomains(subFldPath.Child("minDomains"), constraint.MinDomains, constraint.WhenUnsatisfiable)...)
7565 if err := validateNodeInclusionPolicy(subFldPath.Child("nodeAffinityPolicy"), constraint.NodeAffinityPolicy); err != nil {
7566 allErrs = append(allErrs, err)
7567 }
7568 if err := validateNodeInclusionPolicy(subFldPath.Child("nodeTaintsPolicy"), constraint.NodeTaintsPolicy); err != nil {
7569 allErrs = append(allErrs, err)
7570 }
7571 allErrs = append(allErrs, validateMatchLabelKeysInTopologySpread(subFldPath.Child("matchLabelKeys"), constraint.MatchLabelKeys, constraint.LabelSelector)...)
7572 if !opts.AllowInvalidTopologySpreadConstraintLabelSelector {
7573 allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(constraint.LabelSelector, unversionedvalidation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: false}, subFldPath.Child("labelSelector"))...)
7574 }
7575 }
7576
7577 return allErrs
7578 }
7579
7580
7581 func ValidateMaxSkew(fldPath *field.Path, maxSkew int32) *field.Error {
7582 if maxSkew <= 0 {
7583 return field.Invalid(fldPath, maxSkew, isNotPositiveErrorMsg)
7584 }
7585 return nil
7586 }
7587
7588
7589 func validateMinDomains(fldPath *field.Path, minDomains *int32, action core.UnsatisfiableConstraintAction) field.ErrorList {
7590 if minDomains == nil {
7591 return nil
7592 }
7593 var allErrs field.ErrorList
7594 if *minDomains <= 0 {
7595 allErrs = append(allErrs, field.Invalid(fldPath, minDomains, isNotPositiveErrorMsg))
7596 }
7597
7598 if action != core.DoNotSchedule {
7599 allErrs = append(allErrs, field.Invalid(fldPath, minDomains, fmt.Sprintf("can only use minDomains if whenUnsatisfiable=%s, not %s", core.DoNotSchedule, action)))
7600 }
7601 return allErrs
7602 }
7603
7604
7605 func ValidateTopologyKey(fldPath *field.Path, topologyKey string) *field.Error {
7606 if len(topologyKey) == 0 {
7607 return field.Required(fldPath, "can not be empty")
7608 }
7609 return nil
7610 }
7611
7612
7613 func ValidateWhenUnsatisfiable(fldPath *field.Path, action core.UnsatisfiableConstraintAction) *field.Error {
7614 if !supportedScheduleActions.Has(action) {
7615 return field.NotSupported(fldPath, action, sets.List(supportedScheduleActions))
7616 }
7617 return nil
7618 }
7619
7620
7621
7622 func ValidateSpreadConstraintNotRepeat(fldPath *field.Path, constraint core.TopologySpreadConstraint, restingConstraints []core.TopologySpreadConstraint) *field.Error {
7623 for _, restingConstraint := range restingConstraints {
7624 if constraint.TopologyKey == restingConstraint.TopologyKey &&
7625 constraint.WhenUnsatisfiable == restingConstraint.WhenUnsatisfiable {
7626 return field.Duplicate(fldPath, fmt.Sprintf("{%v, %v}", constraint.TopologyKey, constraint.WhenUnsatisfiable))
7627 }
7628 }
7629 return nil
7630 }
7631
7632 var (
7633 supportedPodTopologySpreadNodePolicies = sets.New(core.NodeInclusionPolicyIgnore, core.NodeInclusionPolicyHonor)
7634 )
7635
7636
7637 func validateNodeInclusionPolicy(fldPath *field.Path, policy *core.NodeInclusionPolicy) *field.Error {
7638 if policy == nil {
7639 return nil
7640 }
7641
7642 if !supportedPodTopologySpreadNodePolicies.Has(*policy) {
7643 return field.NotSupported(fldPath, policy, sets.List(supportedPodTopologySpreadNodePolicies))
7644 }
7645 return nil
7646 }
7647
7648
7649
7650
7651
7652 func validateMatchLabelKeysAndMismatchLabelKeys(fldPath *field.Path, matchLabelKeys, mismatchLabelKeys []string, labelSelector *metav1.LabelSelector) field.ErrorList {
7653 var allErrs field.ErrorList
7654
7655 allErrs = append(allErrs, validateLabelKeys(fldPath.Child("matchLabelKeys"), matchLabelKeys, labelSelector)...)
7656 allErrs = append(allErrs, validateLabelKeys(fldPath.Child("mismatchLabelKeys"), mismatchLabelKeys, labelSelector)...)
7657
7658
7659
7660
7661
7662
7663
7664
7665
7666
7667 if labelSelector != nil {
7668 labelKeysMap := map[string]int{}
7669 for i, key := range matchLabelKeys {
7670 labelKeysMap[key] = i
7671 }
7672 labelSelectorKeys := sets.New[string]()
7673 for key := range labelSelector.MatchLabels {
7674 labelSelectorKeys.Insert(key)
7675 }
7676 for _, matchExpression := range labelSelector.MatchExpressions {
7677 key := matchExpression.Key
7678 if i, ok := labelKeysMap[key]; ok && labelSelectorKeys.Has(key) {
7679
7680
7681
7682 allErrs = append(allErrs, field.Invalid(fldPath.Index(i), key, "exists in both matchLabelKeys and labelSelector"))
7683 }
7684
7685 labelSelectorKeys.Insert(key)
7686 }
7687 }
7688
7689
7690 mismatchLabelKeysSet := sets.New(mismatchLabelKeys...)
7691 for i, k := range matchLabelKeys {
7692 if mismatchLabelKeysSet.Has(k) {
7693 allErrs = append(allErrs, field.Invalid(fldPath.Child("matchLabelKeys").Index(i), k, "exists in both matchLabelKeys and mismatchLabelKeys"))
7694 }
7695 }
7696
7697 return allErrs
7698 }
7699
7700
7701 func validateMatchLabelKeysInTopologySpread(fldPath *field.Path, matchLabelKeys []string, labelSelector *metav1.LabelSelector) field.ErrorList {
7702 if len(matchLabelKeys) == 0 {
7703 return nil
7704 }
7705
7706 var allErrs field.ErrorList
7707 labelSelectorKeys := sets.Set[string]{}
7708
7709 if labelSelector != nil {
7710 for key := range labelSelector.MatchLabels {
7711 labelSelectorKeys.Insert(key)
7712 }
7713 for _, matchExpression := range labelSelector.MatchExpressions {
7714 labelSelectorKeys.Insert(matchExpression.Key)
7715 }
7716 } else {
7717 allErrs = append(allErrs, field.Forbidden(fldPath, "must not be specified when labelSelector is not set"))
7718 }
7719
7720 for i, key := range matchLabelKeys {
7721 allErrs = append(allErrs, unversionedvalidation.ValidateLabelName(key, fldPath.Index(i))...)
7722 if labelSelectorKeys.Has(key) {
7723 allErrs = append(allErrs, field.Invalid(fldPath.Index(i), key, "exists in both matchLabelKeys and labelSelector"))
7724 }
7725 }
7726
7727 return allErrs
7728 }
7729
7730
7731
7732 func validateLabelKeys(fldPath *field.Path, labelKeys []string, labelSelector *metav1.LabelSelector) field.ErrorList {
7733 if len(labelKeys) == 0 {
7734 return nil
7735 }
7736
7737 if labelSelector == nil {
7738 return field.ErrorList{field.Forbidden(fldPath, "must not be specified when labelSelector is not set")}
7739 }
7740
7741 var allErrs field.ErrorList
7742 for i, key := range labelKeys {
7743 allErrs = append(allErrs, unversionedvalidation.ValidateLabelName(key, fldPath.Index(i))...)
7744 }
7745
7746 return allErrs
7747 }
7748
7749
7750
7751
7752 func ValidateServiceClusterIPsRelatedFields(service *core.Service) field.ErrorList {
7753
7754 if service.Spec.Type == core.ServiceTypeExternalName {
7755 return field.ErrorList{}
7756 }
7757
7758 allErrs := field.ErrorList{}
7759 hasInvalidIPs := false
7760
7761 specPath := field.NewPath("spec")
7762 clusterIPsField := specPath.Child("clusterIPs")
7763 ipFamiliesField := specPath.Child("ipFamilies")
7764 ipFamilyPolicyField := specPath.Child("ipFamilyPolicy")
7765
7766
7767
7768
7769 if len(service.Spec.ClusterIP) != 0 {
7770
7771 if len(service.Spec.ClusterIPs) == 0 {
7772 allErrs = append(allErrs, field.Required(clusterIPsField, ""))
7773 } else if service.Spec.ClusterIPs[0] != service.Spec.ClusterIP {
7774 allErrs = append(allErrs, field.Invalid(clusterIPsField, service.Spec.ClusterIPs, "first value must match `clusterIP`"))
7775 }
7776 } else {
7777
7778 if len(service.Spec.ClusterIPs) != 0 {
7779 allErrs = append(allErrs, field.Invalid(clusterIPsField, service.Spec.ClusterIPs, "must be empty when `clusterIP` is not specified"))
7780 }
7781 }
7782
7783
7784
7785 seen := sets.Set[core.IPFamily]{}
7786 for i, ipFamily := range service.Spec.IPFamilies {
7787 if !supportedServiceIPFamily.Has(ipFamily) {
7788 allErrs = append(allErrs, field.NotSupported(ipFamiliesField.Index(i), ipFamily, sets.List(supportedServiceIPFamily)))
7789 }
7790
7791 if seen.Has(ipFamily) {
7792 allErrs = append(allErrs, field.Duplicate(ipFamiliesField.Index(i), ipFamily))
7793 }
7794 seen.Insert(ipFamily)
7795 }
7796
7797
7798
7799 if service.Spec.IPFamilyPolicy != nil {
7800
7801 if !supportedServiceIPFamilyPolicy.Has(*(service.Spec.IPFamilyPolicy)) {
7802 allErrs = append(allErrs, field.NotSupported(ipFamilyPolicyField, service.Spec.IPFamilyPolicy, sets.List(supportedServiceIPFamilyPolicy)))
7803 }
7804 }
7805
7806
7807
7808
7809 for i, clusterIP := range service.Spec.ClusterIPs {
7810
7811 if i == 0 && clusterIP == core.ClusterIPNone {
7812 if len(service.Spec.ClusterIPs) > 1 {
7813 hasInvalidIPs = true
7814 allErrs = append(allErrs, field.Invalid(clusterIPsField, service.Spec.ClusterIPs, "'None' must be the first and only value"))
7815 }
7816 continue
7817 }
7818
7819
7820 errorMessages := validation.IsValidIP(clusterIPsField.Index(i), clusterIP)
7821 hasInvalidIPs = (len(errorMessages) != 0) || hasInvalidIPs
7822 allErrs = append(allErrs, errorMessages...)
7823 }
7824
7825
7826 if len(service.Spec.ClusterIPs) > 2 {
7827 allErrs = append(allErrs, field.Invalid(clusterIPsField, service.Spec.ClusterIPs, "may only hold up to 2 values"))
7828 }
7829
7830
7831
7832
7833 if hasInvalidIPs {
7834 return allErrs
7835 }
7836
7837
7838 if len(service.Spec.ClusterIPs) > 1 {
7839 dualStack, err := netutils.IsDualStackIPStrings(service.Spec.ClusterIPs)
7840 if err != nil {
7841 allErrs = append(allErrs, field.InternalError(clusterIPsField, fmt.Errorf("failed to check for dual stack with error:%v", err)))
7842 }
7843
7844
7845 if !dualStack {
7846 allErrs = append(allErrs, field.Invalid(clusterIPsField, service.Spec.ClusterIPs, "may specify no more than one IP for each IP family"))
7847 }
7848 }
7849
7850
7851 if !isHeadlessService(service) && len(service.Spec.ClusterIPs) > 0 && len(service.Spec.IPFamilies) > 0 {
7852 for i, ip := range service.Spec.ClusterIPs {
7853 if i > (len(service.Spec.IPFamilies) - 1) {
7854 break
7855 }
7856
7857
7858 if service.Spec.IPFamilies[i] == core.IPv4Protocol && netutils.IsIPv6String(ip) {
7859 allErrs = append(allErrs, field.Invalid(clusterIPsField.Index(i), ip, fmt.Sprintf("expected an IPv4 value as indicated by `ipFamilies[%v]`", i)))
7860 }
7861
7862 if service.Spec.IPFamilies[i] == core.IPv6Protocol && !netutils.IsIPv6String(ip) {
7863 allErrs = append(allErrs, field.Invalid(clusterIPsField.Index(i), ip, fmt.Sprintf("expected an IPv6 value as indicated by `ipFamilies[%v]`", i)))
7864 }
7865 }
7866 }
7867
7868 return allErrs
7869 }
7870
7871
7872 func validateUpgradeDowngradeClusterIPs(oldService, service *core.Service) field.ErrorList {
7873 allErrs := make(field.ErrorList, 0)
7874
7875
7876 if service.Spec.Type == core.ServiceTypeExternalName || oldService.Spec.Type == core.ServiceTypeExternalName {
7877 return allErrs
7878 }
7879 newIsHeadless := isHeadlessService(service)
7880 oldIsHeadless := isHeadlessService(oldService)
7881
7882 if oldIsHeadless && newIsHeadless {
7883 return allErrs
7884 }
7885
7886 switch {
7887
7888
7889 case len(oldService.Spec.ClusterIPs) == len(service.Spec.ClusterIPs):
7890 for i, ip := range oldService.Spec.ClusterIPs {
7891 if ip != service.Spec.ClusterIPs[i] {
7892 allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "clusterIPs").Index(i), service.Spec.ClusterIPs, "may not change once set"))
7893 }
7894 }
7895
7896
7897 case len(oldService.Spec.ClusterIPs) > len(service.Spec.ClusterIPs):
7898
7899 if len(service.Spec.ClusterIPs) == 0 {
7900 allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "clusterIPs").Index(0), service.Spec.ClusterIPs, "primary clusterIP can not be unset"))
7901 }
7902
7903
7904 if len(oldService.Spec.ClusterIPs) > 0 &&
7905 len(service.Spec.ClusterIPs) > 0 &&
7906 service.Spec.ClusterIPs[0] != oldService.Spec.ClusterIPs[0] {
7907 allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "clusterIPs").Index(0), service.Spec.ClusterIPs, "may not change once set"))
7908 }
7909
7910
7911
7912 if len(service.Spec.ClusterIPs) == 1 {
7913 if service.Spec.IPFamilyPolicy == nil || *(service.Spec.IPFamilyPolicy) != core.IPFamilyPolicySingleStack {
7914 allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "ipFamilyPolicy"), service.Spec.IPFamilyPolicy, "must be set to 'SingleStack' when releasing the secondary clusterIP"))
7915 }
7916 }
7917 case len(oldService.Spec.ClusterIPs) < len(service.Spec.ClusterIPs):
7918
7919
7920 if len(oldService.Spec.ClusterIPs) > 0 &&
7921 service.Spec.ClusterIPs[0] != oldService.Spec.ClusterIPs[0] {
7922 allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "clusterIPs").Index(0), service.Spec.ClusterIPs, "may not change once set"))
7923 }
7924
7925 }
7926 return allErrs
7927 }
7928
7929
7930 func validateUpgradeDowngradeIPFamilies(oldService, service *core.Service) field.ErrorList {
7931 allErrs := make(field.ErrorList, 0)
7932
7933 if service.Spec.Type == core.ServiceTypeExternalName || oldService.Spec.Type == core.ServiceTypeExternalName {
7934 return allErrs
7935 }
7936
7937 oldIsHeadless := isHeadlessService(oldService)
7938 newIsHeadless := isHeadlessService(service)
7939
7940
7941 if newIsHeadless != oldIsHeadless {
7942 return allErrs
7943 }
7944
7945 if newIsHeadless {
7946 return allErrs
7947 }
7948
7949 switch {
7950 case len(oldService.Spec.IPFamilies) == len(service.Spec.IPFamilies):
7951
7952
7953
7954 for i, ip := range oldService.Spec.IPFamilies {
7955 if ip != service.Spec.IPFamilies[i] {
7956 allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "ipFamilies").Index(0), service.Spec.IPFamilies, "may not change once set"))
7957 }
7958 }
7959
7960 case len(oldService.Spec.IPFamilies) > len(service.Spec.IPFamilies):
7961
7962
7963
7964 if len(service.Spec.ClusterIPs) == 0 {
7965 allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "ipFamilies").Index(0), service.Spec.IPFamilies, "primary ipFamily can not be unset"))
7966 }
7967
7968
7969 if len(service.Spec.IPFamilies) > 0 &&
7970 service.Spec.IPFamilies[0] != oldService.Spec.IPFamilies[0] {
7971 allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "ipFamilies").Index(0), service.Spec.ClusterIPs, "may not change once set"))
7972 }
7973
7974
7975
7976 if len(service.Spec.IPFamilies) == 1 {
7977 if service.Spec.IPFamilyPolicy == nil || *(service.Spec.IPFamilyPolicy) != core.IPFamilyPolicySingleStack {
7978 allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "ipFamilyPolicy"), service.Spec.IPFamilyPolicy, "must be set to 'SingleStack' when releasing the secondary ipFamily"))
7979 }
7980 }
7981 case len(oldService.Spec.IPFamilies) < len(service.Spec.IPFamilies):
7982
7983
7984
7985 if len(oldService.Spec.IPFamilies) > 0 &&
7986 len(service.Spec.IPFamilies) > 0 &&
7987 service.Spec.IPFamilies[0] != oldService.Spec.IPFamilies[0] {
7988 allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "ipFamilies").Index(0), service.Spec.ClusterIPs, "may not change once set"))
7989 }
7990
7991 }
7992 return allErrs
7993 }
7994
7995 func isHeadlessService(service *core.Service) bool {
7996 return service != nil &&
7997 len(service.Spec.ClusterIPs) == 1 &&
7998 service.Spec.ClusterIPs[0] == core.ClusterIPNone
7999 }
8000
8001
8002 func validateLoadBalancerClassField(oldService, service *core.Service) field.ErrorList {
8003 allErrs := make(field.ErrorList, 0)
8004 if oldService != nil {
8005
8006 if isTypeLoadBalancer(oldService) && isTypeLoadBalancer(service) {
8007
8008 if !sameLoadBalancerClass(oldService, service) {
8009
8010 allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "loadBalancerClass"), service.Spec.LoadBalancerClass, "may not change once set"))
8011 }
8012 }
8013 }
8014
8015 if isTypeLoadBalancer(service) {
8016
8017 if service.Spec.LoadBalancerClass != nil {
8018 allErrs = append(allErrs, ValidateQualifiedName(*service.Spec.LoadBalancerClass, field.NewPath("spec", "loadBalancerClass"))...)
8019 }
8020 } else {
8021
8022 if service.Spec.LoadBalancerClass != nil {
8023 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "loadBalancerClass"), "may only be used when `type` is 'LoadBalancer'"))
8024 }
8025 }
8026 return allErrs
8027 }
8028
8029
8030 func isTypeLoadBalancer(service *core.Service) bool {
8031 return service.Spec.Type == core.ServiceTypeLoadBalancer
8032 }
8033
8034
8035 func sameLoadBalancerClass(oldService, service *core.Service) bool {
8036 if oldService.Spec.LoadBalancerClass == nil && service.Spec.LoadBalancerClass == nil {
8037 return true
8038 }
8039 if oldService.Spec.LoadBalancerClass == nil || service.Spec.LoadBalancerClass == nil {
8040 return false
8041 }
8042 return *oldService.Spec.LoadBalancerClass == *service.Spec.LoadBalancerClass
8043 }
8044
8045 func ValidatePodAffinityTermSelector(podAffinityTerm core.PodAffinityTerm, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList {
8046 var allErrs field.ErrorList
8047 labelSelectorValidationOptions := unversionedvalidation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: allowInvalidLabelValueInSelector}
8048 allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(podAffinityTerm.LabelSelector, labelSelectorValidationOptions, fldPath.Child("labelSelector"))...)
8049 allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(podAffinityTerm.NamespaceSelector, labelSelectorValidationOptions, fldPath.Child("namespaceSelector"))...)
8050 return allErrs
8051 }
8052
8053 var betaToGALabel = map[string]string{
8054 v1.LabelFailureDomainBetaZone: v1.LabelTopologyZone,
8055 v1.LabelFailureDomainBetaRegion: v1.LabelTopologyRegion,
8056 kubeletapis.LabelOS: v1.LabelOSStable,
8057 kubeletapis.LabelArch: v1.LabelArchStable,
8058 v1.LabelInstanceType: v1.LabelInstanceTypeStable,
8059 }
8060
8061 var (
8062 maskNodeSelectorLabelChangeEqualities conversion.Equalities
8063 initMaskNodeSelectorLabelChangeEqualities sync.Once
8064 )
8065
8066 func getMaskNodeSelectorLabelChangeEqualities() conversion.Equalities {
8067 initMaskNodeSelectorLabelChangeEqualities.Do(func() {
8068 var eqs = apiequality.Semantic.Copy()
8069 err := eqs.AddFunc(
8070 func(newReq, oldReq core.NodeSelectorRequirement) bool {
8071
8072 if oldReq.Key != newReq.Key && betaToGALabel[oldReq.Key] == newReq.Key {
8073 oldReq.Key = newReq.Key
8074 }
8075 return apiequality.Semantic.DeepEqual(newReq, oldReq)
8076 },
8077 )
8078 if err != nil {
8079 panic(fmt.Errorf("failed to instantiate semantic equalities: %w", err))
8080 }
8081 maskNodeSelectorLabelChangeEqualities = eqs
8082 })
8083 return maskNodeSelectorLabelChangeEqualities
8084 }
8085
8086 func validatePvNodeAffinity(newPvNodeAffinity, oldPvNodeAffinity *core.VolumeNodeAffinity, fldPath *field.Path) field.ErrorList {
8087 var allErrs field.ErrorList
8088 if !getMaskNodeSelectorLabelChangeEqualities().DeepEqual(newPvNodeAffinity, oldPvNodeAffinity) {
8089 allErrs = append(allErrs, field.Invalid(fldPath, newPvNodeAffinity, fieldImmutableErrorMsg+", except for updating from beta label to GA"))
8090 }
8091 return allErrs
8092 }
8093
8094 func validateNodeSelectorMutation(fldPath *field.Path, newNodeSelector, oldNodeSelector map[string]string) field.ErrorList {
8095 var allErrs field.ErrorList
8096
8097
8098 for k, v1 := range oldNodeSelector {
8099 if v2, ok := newNodeSelector[k]; !ok || v1 != v2 {
8100 allErrs = append(allErrs, field.Invalid(fldPath, newNodeSelector, "only additions to spec.nodeSelector are allowed (no mutations or deletions)"))
8101 return allErrs
8102 }
8103 }
8104 return allErrs
8105 }
8106
8107 func validateNodeAffinityMutation(nodeAffinityPath *field.Path, newNodeAffinity, oldNodeAffinity *core.NodeAffinity) field.ErrorList {
8108 var allErrs field.ErrorList
8109
8110 if oldNodeAffinity == nil || oldNodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution == nil {
8111 return allErrs
8112 }
8113
8114 oldTerms := oldNodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms
8115 var newTerms []core.NodeSelectorTerm
8116 if newNodeAffinity != nil && newNodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil {
8117 newTerms = newNodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms
8118 }
8119
8120
8121
8122 if len(oldTerms) > 0 && len(oldTerms) != len(newTerms) {
8123 return append(allErrs, field.Invalid(nodeAffinityPath.Child("requiredDuringSchedulingIgnoredDuringExecution").Child("nodeSelectorTerms"), newTerms, "no additions/deletions to non-empty NodeSelectorTerms list are allowed"))
8124 }
8125
8126
8127
8128
8129 for i := range oldTerms {
8130 if !validateNodeSelectorTermHasOnlyAdditions(newTerms[i], oldTerms[i]) {
8131 allErrs = append(allErrs, field.Invalid(nodeAffinityPath.Child("requiredDuringSchedulingIgnoredDuringExecution").Child("nodeSelectorTerms").Index(i), newTerms[i], "only additions are allowed (no mutations or deletions)"))
8132 }
8133 }
8134 return allErrs
8135 }
8136
8137 func validateNodeSelectorTermHasOnlyAdditions(newTerm, oldTerm core.NodeSelectorTerm) bool {
8138 if len(oldTerm.MatchExpressions) == 0 && len(oldTerm.MatchFields) == 0 {
8139 if len(newTerm.MatchExpressions) > 0 || len(newTerm.MatchFields) > 0 {
8140 return false
8141 }
8142 }
8143
8144
8145 if l := len(oldTerm.MatchExpressions); l > 0 {
8146 if len(newTerm.MatchExpressions) < l {
8147 return false
8148 }
8149 if !apiequality.Semantic.DeepEqual(newTerm.MatchExpressions[:l], oldTerm.MatchExpressions) {
8150 return false
8151 }
8152 }
8153
8154 if l := len(oldTerm.MatchFields); l > 0 {
8155 if len(newTerm.MatchFields) < l {
8156 return false
8157 }
8158 if !apiequality.Semantic.DeepEqual(newTerm.MatchFields[:l], oldTerm.MatchFields) {
8159 return false
8160 }
8161 }
8162 return true
8163 }
8164
View as plain text