123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 |
- /*
- Copyright 2014 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package validation
- import (
- "fmt"
- "strings"
- apiequality "k8s.io/apimachinery/pkg/api/equality"
- "k8s.io/apimachinery/pkg/api/meta"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- v1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
- "k8s.io/apimachinery/pkg/runtime/schema"
- "k8s.io/apimachinery/pkg/util/sets"
- "k8s.io/apimachinery/pkg/util/validation"
- "k8s.io/apimachinery/pkg/util/validation/field"
- )
- // FieldImmutableErrorMsg is a error message for field is immutable.
- const FieldImmutableErrorMsg string = `field is immutable`
- const TotalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB
- // BannedOwners is a black list of object that are not allowed to be owners.
- var BannedOwners = map[schema.GroupVersionKind]struct{}{
- {Group: "", Version: "v1", Kind: "Event"}: {},
- }
- // ValidateAnnotations validates that a set of annotations are correctly defined.
- func ValidateAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- for k := range annotations {
- // The rule is QualifiedName except that case doesn't matter, so convert to lowercase before checking.
- for _, msg := range validation.IsQualifiedName(strings.ToLower(k)) {
- allErrs = append(allErrs, field.Invalid(fldPath, k, msg))
- }
- }
- if err := ValidateAnnotationsSize(annotations); err != nil {
- allErrs = append(allErrs, field.TooLong(fldPath, "", TotalAnnotationSizeLimitB))
- }
- return allErrs
- }
- func ValidateAnnotationsSize(annotations map[string]string) error {
- var totalSize int64
- for k, v := range annotations {
- totalSize += (int64)(len(k)) + (int64)(len(v))
- }
- if totalSize > (int64)(TotalAnnotationSizeLimitB) {
- return fmt.Errorf("annotations size %d is larger than limit %d", totalSize, TotalAnnotationSizeLimitB)
- }
- return nil
- }
- func validateOwnerReference(ownerReference metav1.OwnerReference, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- gvk := schema.FromAPIVersionAndKind(ownerReference.APIVersion, ownerReference.Kind)
- // gvk.Group is empty for the legacy group.
- if len(gvk.Version) == 0 {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("apiVersion"), ownerReference.APIVersion, "version must not be empty"))
- }
- if len(gvk.Kind) == 0 {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), ownerReference.Kind, "kind must not be empty"))
- }
- if len(ownerReference.Name) == 0 {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), ownerReference.Name, "name must not be empty"))
- }
- if len(ownerReference.UID) == 0 {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("uid"), ownerReference.UID, "uid must not be empty"))
- }
- if _, ok := BannedOwners[gvk]; ok {
- allErrs = append(allErrs, field.Invalid(fldPath, ownerReference, fmt.Sprintf("%s is disallowed from being an owner", gvk)))
- }
- return allErrs
- }
- // ValidateOwnerReferences validates that a set of owner references are correctly defined.
- func ValidateOwnerReferences(ownerReferences []metav1.OwnerReference, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- firstControllerName := ""
- for _, ref := range ownerReferences {
- allErrs = append(allErrs, validateOwnerReference(ref, fldPath)...)
- if ref.Controller != nil && *ref.Controller {
- curControllerName := ref.Kind + "/" + ref.Name
- if firstControllerName != "" {
- allErrs = append(allErrs, field.Invalid(fldPath, ownerReferences,
- fmt.Sprintf("Only one reference can have Controller set to true. Found \"true\" in references for %v and %v", firstControllerName, curControllerName)))
- } else {
- firstControllerName = curControllerName
- }
- }
- }
- return allErrs
- }
- // ValidateFinalizerName validates finalizer names.
- func ValidateFinalizerName(stringValue string, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- for _, msg := range validation.IsQualifiedName(stringValue) {
- allErrs = append(allErrs, field.Invalid(fldPath, stringValue, msg))
- }
- return allErrs
- }
- // ValidateNoNewFinalizers validates the new finalizers has no new finalizers compare to old finalizers.
- func ValidateNoNewFinalizers(newFinalizers []string, oldFinalizers []string, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- extra := sets.NewString(newFinalizers...).Difference(sets.NewString(oldFinalizers...))
- if len(extra) != 0 {
- allErrs = append(allErrs, field.Forbidden(fldPath, fmt.Sprintf("no new finalizers can be added if the object is being deleted, found new finalizers %#v", extra.List())))
- }
- return allErrs
- }
- // ValidateImmutableField validates the new value and the old value are deeply equal.
- func ValidateImmutableField(newVal, oldVal interface{}, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- if !apiequality.Semantic.DeepEqual(oldVal, newVal) {
- allErrs = append(allErrs, field.Invalid(fldPath, newVal, FieldImmutableErrorMsg))
- }
- return allErrs
- }
- // ValidateObjectMeta validates an object's metadata on creation. It expects that name generation has already
- // been performed.
- // It doesn't return an error for rootscoped resources with namespace, because namespace should already be cleared before.
- func ValidateObjectMeta(objMeta *metav1.ObjectMeta, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path) field.ErrorList {
- metadata, err := meta.Accessor(objMeta)
- if err != nil {
- var allErrs field.ErrorList
- allErrs = append(allErrs, field.Invalid(fldPath, objMeta, err.Error()))
- return allErrs
- }
- return ValidateObjectMetaAccessor(metadata, requiresNamespace, nameFn, fldPath)
- }
- // ValidateObjectMetaAccessor validates an object's metadata on creation. It expects that name generation has already
- // been performed.
- // It doesn't return an error for rootscoped resources with namespace, because namespace should already be cleared before.
- func ValidateObjectMetaAccessor(meta metav1.Object, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path) field.ErrorList {
- var allErrs field.ErrorList
- if len(meta.GetGenerateName()) != 0 {
- for _, msg := range nameFn(meta.GetGenerateName(), true) {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("generateName"), meta.GetGenerateName(), msg))
- }
- }
- // If the generated name validates, but the calculated value does not, it's a problem with generation, and we
- // report it here. This may confuse users, but indicates a programming bug and still must be validated.
- // If there are multiple fields out of which one is required then add an or as a separator
- if len(meta.GetName()) == 0 {
- allErrs = append(allErrs, field.Required(fldPath.Child("name"), "name or generateName is required"))
- } else {
- for _, msg := range nameFn(meta.GetName(), false) {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), meta.GetName(), msg))
- }
- }
- if requiresNamespace {
- if len(meta.GetNamespace()) == 0 {
- allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), ""))
- } else {
- for _, msg := range ValidateNamespaceName(meta.GetNamespace(), false) {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), meta.GetNamespace(), msg))
- }
- }
- } else {
- if len(meta.GetNamespace()) != 0 {
- allErrs = append(allErrs, field.Forbidden(fldPath.Child("namespace"), "not allowed on this type"))
- }
- }
- allErrs = append(allErrs, ValidateNonnegativeField(meta.GetGeneration(), fldPath.Child("generation"))...)
- allErrs = append(allErrs, v1validation.ValidateLabels(meta.GetLabels(), fldPath.Child("labels"))...)
- allErrs = append(allErrs, ValidateAnnotations(meta.GetAnnotations(), fldPath.Child("annotations"))...)
- allErrs = append(allErrs, ValidateOwnerReferences(meta.GetOwnerReferences(), fldPath.Child("ownerReferences"))...)
- allErrs = append(allErrs, ValidateFinalizers(meta.GetFinalizers(), fldPath.Child("finalizers"))...)
- allErrs = append(allErrs, v1validation.ValidateManagedFields(meta.GetManagedFields(), fldPath.Child("managedFields"))...)
- return allErrs
- }
- // ValidateFinalizers tests if the finalizers name are valid, and if there are conflicting finalizers.
- func ValidateFinalizers(finalizers []string, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- hasFinalizerOrphanDependents := false
- hasFinalizerDeleteDependents := false
- for _, finalizer := range finalizers {
- allErrs = append(allErrs, ValidateFinalizerName(finalizer, fldPath)...)
- if finalizer == metav1.FinalizerOrphanDependents {
- hasFinalizerOrphanDependents = true
- }
- if finalizer == metav1.FinalizerDeleteDependents {
- hasFinalizerDeleteDependents = true
- }
- }
- if hasFinalizerDeleteDependents && hasFinalizerOrphanDependents {
- allErrs = append(allErrs, field.Invalid(fldPath, finalizers, fmt.Sprintf("finalizer %s and %s cannot be both set", metav1.FinalizerOrphanDependents, metav1.FinalizerDeleteDependents)))
- }
- return allErrs
- }
- // ValidateObjectMetaUpdate validates an object's metadata when updated.
- func ValidateObjectMetaUpdate(newMeta, oldMeta *metav1.ObjectMeta, fldPath *field.Path) field.ErrorList {
- newMetadata, err := meta.Accessor(newMeta)
- if err != nil {
- allErrs := field.ErrorList{}
- allErrs = append(allErrs, field.Invalid(fldPath, newMeta, err.Error()))
- return allErrs
- }
- oldMetadata, err := meta.Accessor(oldMeta)
- if err != nil {
- allErrs := field.ErrorList{}
- allErrs = append(allErrs, field.Invalid(fldPath, oldMeta, err.Error()))
- return allErrs
- }
- return ValidateObjectMetaAccessorUpdate(newMetadata, oldMetadata, fldPath)
- }
- // ValidateObjectMetaAccessorUpdate validates an object's metadata when updated.
- func ValidateObjectMetaAccessorUpdate(newMeta, oldMeta metav1.Object, fldPath *field.Path) field.ErrorList {
- var allErrs field.ErrorList
- // Finalizers cannot be added if the object is already being deleted.
- if oldMeta.GetDeletionTimestamp() != nil {
- allErrs = append(allErrs, ValidateNoNewFinalizers(newMeta.GetFinalizers(), oldMeta.GetFinalizers(), fldPath.Child("finalizers"))...)
- }
- // Reject updates that don't specify a resource version
- if len(newMeta.GetResourceVersion()) == 0 {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceVersion"), newMeta.GetResourceVersion(), "must be specified for an update"))
- }
- // Generation shouldn't be decremented
- if newMeta.GetGeneration() < oldMeta.GetGeneration() {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("generation"), newMeta.GetGeneration(), "must not be decremented"))
- }
- allErrs = append(allErrs, ValidateImmutableField(newMeta.GetName(), oldMeta.GetName(), fldPath.Child("name"))...)
- allErrs = append(allErrs, ValidateImmutableField(newMeta.GetNamespace(), oldMeta.GetNamespace(), fldPath.Child("namespace"))...)
- allErrs = append(allErrs, ValidateImmutableField(newMeta.GetUID(), oldMeta.GetUID(), fldPath.Child("uid"))...)
- allErrs = append(allErrs, ValidateImmutableField(newMeta.GetCreationTimestamp(), oldMeta.GetCreationTimestamp(), fldPath.Child("creationTimestamp"))...)
- allErrs = append(allErrs, ValidateImmutableField(newMeta.GetDeletionTimestamp(), oldMeta.GetDeletionTimestamp(), fldPath.Child("deletionTimestamp"))...)
- allErrs = append(allErrs, ValidateImmutableField(newMeta.GetDeletionGracePeriodSeconds(), oldMeta.GetDeletionGracePeriodSeconds(), fldPath.Child("deletionGracePeriodSeconds"))...)
- allErrs = append(allErrs, v1validation.ValidateLabels(newMeta.GetLabels(), fldPath.Child("labels"))...)
- allErrs = append(allErrs, ValidateAnnotations(newMeta.GetAnnotations(), fldPath.Child("annotations"))...)
- allErrs = append(allErrs, ValidateOwnerReferences(newMeta.GetOwnerReferences(), fldPath.Child("ownerReferences"))...)
- allErrs = append(allErrs, v1validation.ValidateManagedFields(newMeta.GetManagedFields(), fldPath.Child("managedFields"))...)
- return allErrs
- }
|