mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
Merge pull request #39431 from deads2k/generic-05-validation
Automatic merge from submit-queue snip links from genericapiserver to api/validation We had a trip dependency on `pkg/api/validation` through `pkg/api/rest` which is used for RESTStorage. This splits the generic validation required for a genericapiserver out from the rest of validation. For now, functions still delegate. @sttts
This commit is contained in:
commit
cb78349735
@ -23,7 +23,7 @@ go_library(
|
|||||||
"//pkg/api:go_default_library",
|
"//pkg/api:go_default_library",
|
||||||
"//pkg/api/errors:go_default_library",
|
"//pkg/api/errors:go_default_library",
|
||||||
"//pkg/api/meta:go_default_library",
|
"//pkg/api/meta:go_default_library",
|
||||||
"//pkg/api/validation:go_default_library",
|
"//pkg/api/validation/genericvalidation:go_default_library",
|
||||||
"//pkg/api/validation/path:go_default_library",
|
"//pkg/api/validation/path:go_default_library",
|
||||||
"//pkg/apis/meta/v1:go_default_library",
|
"//pkg/apis/meta/v1:go_default_library",
|
||||||
"//pkg/genericapiserver/api/request:go_default_library",
|
"//pkg/genericapiserver/api/request:go_default_library",
|
||||||
|
@ -19,7 +19,7 @@ package rest
|
|||||||
import (
|
import (
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/api/errors"
|
"k8s.io/kubernetes/pkg/api/errors"
|
||||||
"k8s.io/kubernetes/pkg/api/validation"
|
"k8s.io/kubernetes/pkg/api/validation/genericvalidation"
|
||||||
path "k8s.io/kubernetes/pkg/api/validation/path"
|
path "k8s.io/kubernetes/pkg/api/validation/path"
|
||||||
genericapirequest "k8s.io/kubernetes/pkg/genericapiserver/api/request"
|
genericapirequest "k8s.io/kubernetes/pkg/genericapiserver/api/request"
|
||||||
"k8s.io/kubernetes/pkg/runtime"
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
@ -83,7 +83,7 @@ func BeforeCreate(strategy RESTCreateStrategy, ctx genericapirequest.Context, ob
|
|||||||
// Custom validation (including name validation) passed
|
// Custom validation (including name validation) passed
|
||||||
// Now run common validation on object meta
|
// Now run common validation on object meta
|
||||||
// Do this *after* custom validation so that specific error messages are shown whenever possible
|
// Do this *after* custom validation so that specific error messages are shown whenever possible
|
||||||
if errs := validation.ValidateObjectMeta(objectMeta, strategy.NamespaceScoped(), path.ValidatePathSegmentName, field.NewPath("metadata")); len(errs) > 0 {
|
if errs := genericvalidation.ValidateObjectMeta(objectMeta, strategy.NamespaceScoped(), path.ValidatePathSegmentName, field.NewPath("metadata")); len(errs) > 0 {
|
||||||
return errors.NewInvalid(kind.GroupKind(), objectMeta.Name, errs)
|
return errors.NewInvalid(kind.GroupKind(), objectMeta.Name, errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/api/errors"
|
"k8s.io/kubernetes/pkg/api/errors"
|
||||||
"k8s.io/kubernetes/pkg/api/meta"
|
"k8s.io/kubernetes/pkg/api/meta"
|
||||||
"k8s.io/kubernetes/pkg/api/validation"
|
"k8s.io/kubernetes/pkg/api/validation/genericvalidation"
|
||||||
genericapirequest "k8s.io/kubernetes/pkg/genericapiserver/api/request"
|
genericapirequest "k8s.io/kubernetes/pkg/genericapiserver/api/request"
|
||||||
"k8s.io/kubernetes/pkg/runtime"
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
"k8s.io/kubernetes/pkg/util/validation/field"
|
"k8s.io/kubernetes/pkg/util/validation/field"
|
||||||
@ -67,7 +67,7 @@ func validateCommonFields(obj, old runtime.Object) (field.ErrorList, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get old object metadata: %v", err)
|
return nil, fmt.Errorf("failed to get old object metadata: %v", err)
|
||||||
}
|
}
|
||||||
allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(objectMeta, oldObjectMeta, field.NewPath("metadata"))...)
|
allErrs = append(allErrs, genericvalidation.ValidateObjectMetaUpdate(objectMeta, oldObjectMeta, field.NewPath("metadata"))...)
|
||||||
|
|
||||||
return allErrs, nil
|
return allErrs, nil
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ go_library(
|
|||||||
"//pkg/api/resource:go_default_library",
|
"//pkg/api/resource:go_default_library",
|
||||||
"//pkg/api/service:go_default_library",
|
"//pkg/api/service:go_default_library",
|
||||||
"//pkg/api/util:go_default_library",
|
"//pkg/api/util:go_default_library",
|
||||||
"//pkg/api/v1:go_default_library",
|
"//pkg/api/validation/genericvalidation:go_default_library",
|
||||||
"//pkg/apimachinery/registered:go_default_library",
|
"//pkg/apimachinery/registered:go_default_library",
|
||||||
"//pkg/apis/meta/v1:go_default_library",
|
"//pkg/apis/meta/v1:go_default_library",
|
||||||
"//pkg/apis/meta/v1/unstructured:go_default_library",
|
"//pkg/apis/meta/v1/unstructured:go_default_library",
|
||||||
|
40
pkg/api/validation/genericvalidation/BUILD
Normal file
40
pkg/api/validation/genericvalidation/BUILD
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_library",
|
||||||
|
"go_test",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"doc.go",
|
||||||
|
"validation.go",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api:go_default_library",
|
||||||
|
"//pkg/api/v1:go_default_library",
|
||||||
|
"//pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//pkg/apis/meta/v1/validation:go_default_library",
|
||||||
|
"//pkg/runtime/schema:go_default_library",
|
||||||
|
"//pkg/util/sets:go_default_library",
|
||||||
|
"//pkg/util/validation:go_default_library",
|
||||||
|
"//pkg/util/validation/field:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["validation_test.go"],
|
||||||
|
library = ":go_default_library",
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api:go_default_library",
|
||||||
|
"//pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//pkg/util/validation/field:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
18
pkg/api/validation/genericvalidation/doc.go
Normal file
18
pkg/api/validation/genericvalidation/doc.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// this package is split out to snip links between the API server and the kube resource validation functions
|
||||||
|
package genericvalidation
|
313
pkg/api/validation/genericvalidation/validation.go
Normal file
313
pkg/api/validation/genericvalidation/validation.go
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
/*
|
||||||
|
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 genericvalidation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
|
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||||
|
v1validation "k8s.io/kubernetes/pkg/apis/meta/v1/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime/schema"
|
||||||
|
"k8s.io/kubernetes/pkg/util/sets"
|
||||||
|
"k8s.io/kubernetes/pkg/util/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/util/validation/field"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: delete this global variable when we enable the validation of common
|
||||||
|
// fields by default.
|
||||||
|
var RepairMalformedUpdates bool = true
|
||||||
|
|
||||||
|
const IsNegativeErrorMsg string = `must be greater than or equal to 0`
|
||||||
|
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{}{
|
||||||
|
v1.SchemeGroupVersion.WithKind("Event"): {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateNameFunc validates that the provided name is valid for a given resource type.
|
||||||
|
// Not all resources have the same validation rules for names. Prefix is true
|
||||||
|
// if the name will have a value appended to it. If the name is not valid,
|
||||||
|
// this returns a list of descriptions of individual characteristics of the
|
||||||
|
// value that were not valid. Otherwise this returns an empty list or nil.
|
||||||
|
type ValidateNameFunc func(name string, prefix bool) []string
|
||||||
|
|
||||||
|
// NameIsDNSSubdomain is a ValidateNameFunc for names that must be a DNS subdomain.
|
||||||
|
func NameIsDNSSubdomain(name string, prefix bool) []string {
|
||||||
|
if prefix {
|
||||||
|
name = maskTrailingDash(name)
|
||||||
|
}
|
||||||
|
return validation.IsDNS1123Subdomain(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameIsDNSLabel is a ValidateNameFunc for names that must be a DNS 1123 label.
|
||||||
|
func NameIsDNSLabel(name string, prefix bool) []string {
|
||||||
|
if prefix {
|
||||||
|
name = maskTrailingDash(name)
|
||||||
|
}
|
||||||
|
return validation.IsDNS1123Label(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameIsDNS1035Label is a ValidateNameFunc for names that must be a DNS 952 label.
|
||||||
|
func NameIsDNS1035Label(name string, prefix bool) []string {
|
||||||
|
if prefix {
|
||||||
|
name = maskTrailingDash(name)
|
||||||
|
}
|
||||||
|
return validation.IsDNS1035Label(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateNamespaceName can be used to check whether the given namespace name is valid.
|
||||||
|
// Prefix indicates this name will be used as part of generation, in which case
|
||||||
|
// trailing dashes are allowed.
|
||||||
|
var ValidateNamespaceName = NameIsDNSLabel
|
||||||
|
|
||||||
|
// ValidateClusterName can be used to check whether the given cluster name is valid.
|
||||||
|
var ValidateClusterName = NameIsDNS1035Label
|
||||||
|
|
||||||
|
// ValidateServiceAccountName can be used to check whether the given service account name is valid.
|
||||||
|
// Prefix indicates this name will be used as part of generation, in which case
|
||||||
|
// trailing dashes are allowed.
|
||||||
|
var ValidateServiceAccountName = NameIsDNSSubdomain
|
||||||
|
|
||||||
|
// maskTrailingDash replaces the final character of a string with a subdomain safe
|
||||||
|
// value if is a dash.
|
||||||
|
func maskTrailingDash(name string) string {
|
||||||
|
if strings.HasSuffix(name, "-") {
|
||||||
|
return name[:len(name)-2] + "a"
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validates that given value is not negative.
|
||||||
|
func ValidateNonnegativeField(value int64, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
if value < 0 {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath, value, IsNegativeErrorMsg))
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateAnnotations validates that a set of annotations are correctly defined.
|
||||||
|
func ValidateAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
var totalSize int64
|
||||||
|
for k, v := range annotations {
|
||||||
|
for _, msg := range validation.IsQualifiedName(strings.ToLower(k)) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath, k, msg))
|
||||||
|
}
|
||||||
|
totalSize += (int64)(len(k)) + (int64)(len(v))
|
||||||
|
}
|
||||||
|
if totalSize > (int64)(totalAnnotationSizeLimitB) {
|
||||||
|
allErrs = append(allErrs, field.TooLong(fldPath, "", totalAnnotationSizeLimitB))
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateOwnerReferences(ownerReferences []metav1.OwnerReference, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
controllerName := ""
|
||||||
|
for _, ref := range ownerReferences {
|
||||||
|
allErrs = append(allErrs, validateOwnerReference(ref, fldPath)...)
|
||||||
|
if ref.Controller != nil && *ref.Controller {
|
||||||
|
if controllerName != "" {
|
||||||
|
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", controllerName, ref.Name)))
|
||||||
|
} else {
|
||||||
|
controllerName = ref.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate 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))
|
||||||
|
}
|
||||||
|
if len(allErrs) != 0 {
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(strings.Split(stringValue, "/")) == 1 {
|
||||||
|
if !api.IsStandardFinalizerName(stringValue) {
|
||||||
|
return append(allErrs, field.Invalid(fldPath, stringValue, "name is neither a standard finalizer name nor is it fully qualified"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return field.ErrorList{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateNoNewFinalizers(newFinalizers []string, oldFinalizers []string, fldPath *field.Path) field.ErrorList {
|
||||||
|
const newFinalizersErrorMsg string = `no new finalizers can be added if the object is being deleted`
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateImmutableField(newVal, oldVal interface{}, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
if !api.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(meta *api.ObjectMeta, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
|
if len(meta.GenerateName) != 0 {
|
||||||
|
for _, msg := range nameFn(meta.GenerateName, true) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("generateName"), meta.GenerateName, 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.Name) == 0 {
|
||||||
|
allErrs = append(allErrs, field.Required(fldPath.Child("name"), "name or generateName is required"))
|
||||||
|
} else {
|
||||||
|
for _, msg := range nameFn(meta.Name, false) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), meta.Name, msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if requiresNamespace {
|
||||||
|
if len(meta.Namespace) == 0 {
|
||||||
|
allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), ""))
|
||||||
|
} else {
|
||||||
|
for _, msg := range ValidateNamespaceName(meta.Namespace, false) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), meta.Namespace, msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(meta.Namespace) != 0 {
|
||||||
|
allErrs = append(allErrs, field.Forbidden(fldPath.Child("namespace"), "not allowed on this type"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(meta.ClusterName) != 0 {
|
||||||
|
for _, msg := range ValidateClusterName(meta.ClusterName, false) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("clusterName"), meta.ClusterName, msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allErrs = append(allErrs, ValidateNonnegativeField(meta.Generation, fldPath.Child("generation"))...)
|
||||||
|
allErrs = append(allErrs, v1validation.ValidateLabels(meta.Labels, fldPath.Child("labels"))...)
|
||||||
|
allErrs = append(allErrs, ValidateAnnotations(meta.Annotations, fldPath.Child("annotations"))...)
|
||||||
|
allErrs = append(allErrs, ValidateOwnerReferences(meta.OwnerReferences, fldPath.Child("ownerReferences"))...)
|
||||||
|
for _, finalizer := range meta.Finalizers {
|
||||||
|
allErrs = append(allErrs, ValidateFinalizerName(finalizer, fldPath.Child("finalizers"))...)
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateObjectMetaUpdate validates an object's metadata when updated
|
||||||
|
func ValidateObjectMetaUpdate(newMeta, oldMeta *api.ObjectMeta, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
|
if !RepairMalformedUpdates && newMeta.UID != oldMeta.UID {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("uid"), newMeta.UID, "field is immutable"))
|
||||||
|
}
|
||||||
|
// in the event it is left empty, set it, to allow clients more flexibility
|
||||||
|
// TODO: remove the following code that repairs the update request when we retire the clients that modify the immutable fields.
|
||||||
|
// Please do not copy this pattern elsewhere; validation functions should not be modifying the objects they are passed!
|
||||||
|
if RepairMalformedUpdates {
|
||||||
|
if len(newMeta.UID) == 0 {
|
||||||
|
newMeta.UID = oldMeta.UID
|
||||||
|
}
|
||||||
|
// ignore changes to timestamp
|
||||||
|
if oldMeta.CreationTimestamp.IsZero() {
|
||||||
|
oldMeta.CreationTimestamp = newMeta.CreationTimestamp
|
||||||
|
} else {
|
||||||
|
newMeta.CreationTimestamp = oldMeta.CreationTimestamp
|
||||||
|
}
|
||||||
|
// an object can never remove a deletion timestamp or clear/change grace period seconds
|
||||||
|
if !oldMeta.DeletionTimestamp.IsZero() {
|
||||||
|
newMeta.DeletionTimestamp = oldMeta.DeletionTimestamp
|
||||||
|
}
|
||||||
|
if oldMeta.DeletionGracePeriodSeconds != nil && newMeta.DeletionGracePeriodSeconds == nil {
|
||||||
|
newMeta.DeletionGracePeriodSeconds = oldMeta.DeletionGracePeriodSeconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: needs to check if newMeta==nil && oldMeta !=nil after the repair logic is removed.
|
||||||
|
if newMeta.DeletionGracePeriodSeconds != nil && (oldMeta.DeletionGracePeriodSeconds == nil || *newMeta.DeletionGracePeriodSeconds != *oldMeta.DeletionGracePeriodSeconds) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("deletionGracePeriodSeconds"), newMeta.DeletionGracePeriodSeconds, "field is immutable; may only be changed via deletion"))
|
||||||
|
}
|
||||||
|
if newMeta.DeletionTimestamp != nil && (oldMeta.DeletionTimestamp == nil || !newMeta.DeletionTimestamp.Equal(*oldMeta.DeletionTimestamp)) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("deletionTimestamp"), newMeta.DeletionTimestamp, "field is immutable; may only be changed via deletion"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalizers cannot be added if the object is already being deleted.
|
||||||
|
if oldMeta.DeletionTimestamp != nil {
|
||||||
|
allErrs = append(allErrs, ValidateNoNewFinalizers(newMeta.Finalizers, oldMeta.Finalizers, fldPath.Child("finalizers"))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject updates that don't specify a resource version
|
||||||
|
if len(newMeta.ResourceVersion) == 0 {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceVersion"), newMeta.ResourceVersion, "must be specified for an update"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generation shouldn't be decremented
|
||||||
|
if newMeta.Generation < oldMeta.Generation {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("generation"), newMeta.Generation, "must not be decremented"))
|
||||||
|
}
|
||||||
|
|
||||||
|
allErrs = append(allErrs, ValidateImmutableField(newMeta.Name, oldMeta.Name, fldPath.Child("name"))...)
|
||||||
|
allErrs = append(allErrs, ValidateImmutableField(newMeta.Namespace, oldMeta.Namespace, fldPath.Child("namespace"))...)
|
||||||
|
allErrs = append(allErrs, ValidateImmutableField(newMeta.UID, oldMeta.UID, fldPath.Child("uid"))...)
|
||||||
|
allErrs = append(allErrs, ValidateImmutableField(newMeta.CreationTimestamp, oldMeta.CreationTimestamp, fldPath.Child("creationTimestamp"))...)
|
||||||
|
allErrs = append(allErrs, ValidateImmutableField(newMeta.ClusterName, oldMeta.ClusterName, fldPath.Child("clusterName"))...)
|
||||||
|
|
||||||
|
allErrs = append(allErrs, v1validation.ValidateLabels(newMeta.Labels, fldPath.Child("labels"))...)
|
||||||
|
allErrs = append(allErrs, ValidateAnnotations(newMeta.Annotations, fldPath.Child("annotations"))...)
|
||||||
|
allErrs = append(allErrs, ValidateOwnerReferences(newMeta.OwnerReferences, fldPath.Child("ownerReferences"))...)
|
||||||
|
|
||||||
|
return allErrs
|
||||||
|
}
|
479
pkg/api/validation/genericvalidation/validation_test.go
Normal file
479
pkg/api/validation/genericvalidation/validation_test.go
Normal file
@ -0,0 +1,479 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 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 genericvalidation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/util/validation/field"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxLengthErrMsg = "must be no more than"
|
||||||
|
namePartErrMsg = "name part must consist of"
|
||||||
|
nameErrMsg = "a qualified name must consist of"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ensure custom name functions are allowed
|
||||||
|
func TestValidateObjectMetaCustomName(t *testing.T) {
|
||||||
|
errs := ValidateObjectMeta(
|
||||||
|
&api.ObjectMeta{Name: "test", GenerateName: "foo"},
|
||||||
|
false,
|
||||||
|
func(s string, prefix bool) []string {
|
||||||
|
if s == "test" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return []string{"name-gen"}
|
||||||
|
},
|
||||||
|
field.NewPath("field"))
|
||||||
|
if len(errs) != 1 {
|
||||||
|
t.Fatalf("unexpected errors: %v", errs)
|
||||||
|
}
|
||||||
|
if !strings.Contains(errs[0].Error(), "name-gen") {
|
||||||
|
t.Errorf("unexpected error message: %v", errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure namespace names follow dns label format
|
||||||
|
func TestValidateObjectMetaNamespaces(t *testing.T) {
|
||||||
|
errs := ValidateObjectMeta(
|
||||||
|
&api.ObjectMeta{Name: "test", Namespace: "foo.bar"},
|
||||||
|
true,
|
||||||
|
func(s string, prefix bool) []string {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
field.NewPath("field"))
|
||||||
|
if len(errs) != 1 {
|
||||||
|
t.Fatalf("unexpected errors: %v", errs)
|
||||||
|
}
|
||||||
|
if !strings.Contains(errs[0].Error(), `Invalid value: "foo.bar"`) {
|
||||||
|
t.Errorf("unexpected error message: %v", errs)
|
||||||
|
}
|
||||||
|
maxLength := 63
|
||||||
|
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||||
|
b := make([]rune, maxLength+1)
|
||||||
|
for i := range b {
|
||||||
|
b[i] = letters[rand.Intn(len(letters))]
|
||||||
|
}
|
||||||
|
errs = ValidateObjectMeta(
|
||||||
|
&api.ObjectMeta{Name: "test", Namespace: string(b)},
|
||||||
|
true,
|
||||||
|
func(s string, prefix bool) []string {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
field.NewPath("field"))
|
||||||
|
if len(errs) != 2 {
|
||||||
|
t.Fatalf("unexpected errors: %v", errs)
|
||||||
|
}
|
||||||
|
if !strings.Contains(errs[0].Error(), "Invalid value") || !strings.Contains(errs[1].Error(), "Invalid value") {
|
||||||
|
t.Errorf("unexpected error message: %v", errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateObjectMetaOwnerReferences(t *testing.T) {
|
||||||
|
trueVar := true
|
||||||
|
falseVar := false
|
||||||
|
testCases := []struct {
|
||||||
|
description string
|
||||||
|
ownerReferences []metav1.OwnerReference
|
||||||
|
expectError bool
|
||||||
|
expectedErrorMessage string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "simple success - third party extension.",
|
||||||
|
ownerReferences: []metav1.OwnerReference{
|
||||||
|
{
|
||||||
|
APIVersion: "thirdpartyVersion",
|
||||||
|
Kind: "thirdpartyKind",
|
||||||
|
Name: "name",
|
||||||
|
UID: "1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
expectedErrorMessage: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "simple failures - event shouldn't be set as an owner",
|
||||||
|
ownerReferences: []metav1.OwnerReference{
|
||||||
|
{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "Event",
|
||||||
|
Name: "name",
|
||||||
|
UID: "1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
expectedErrorMessage: "is disallowed from being an owner",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "simple controller ref success - one reference with Controller set",
|
||||||
|
ownerReferences: []metav1.OwnerReference{
|
||||||
|
{
|
||||||
|
APIVersion: "thirdpartyVersion",
|
||||||
|
Kind: "thirdpartyKind",
|
||||||
|
Name: "name",
|
||||||
|
UID: "1",
|
||||||
|
Controller: &falseVar,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
APIVersion: "thirdpartyVersion",
|
||||||
|
Kind: "thirdpartyKind",
|
||||||
|
Name: "name",
|
||||||
|
UID: "2",
|
||||||
|
Controller: &trueVar,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
APIVersion: "thirdpartyVersion",
|
||||||
|
Kind: "thirdpartyKind",
|
||||||
|
Name: "name",
|
||||||
|
UID: "3",
|
||||||
|
Controller: &falseVar,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
APIVersion: "thirdpartyVersion",
|
||||||
|
Kind: "thirdpartyKind",
|
||||||
|
Name: "name",
|
||||||
|
UID: "4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
expectedErrorMessage: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "simple controller ref failure - two references with Controller set",
|
||||||
|
ownerReferences: []metav1.OwnerReference{
|
||||||
|
{
|
||||||
|
APIVersion: "thirdpartyVersion",
|
||||||
|
Kind: "thirdpartyKind",
|
||||||
|
Name: "name",
|
||||||
|
UID: "1",
|
||||||
|
Controller: &falseVar,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
APIVersion: "thirdpartyVersion",
|
||||||
|
Kind: "thirdpartyKind",
|
||||||
|
Name: "name",
|
||||||
|
UID: "2",
|
||||||
|
Controller: &trueVar,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
APIVersion: "thirdpartyVersion",
|
||||||
|
Kind: "thirdpartyKind",
|
||||||
|
Name: "name",
|
||||||
|
UID: "3",
|
||||||
|
Controller: &trueVar,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
APIVersion: "thirdpartyVersion",
|
||||||
|
Kind: "thirdpartyKind",
|
||||||
|
Name: "name",
|
||||||
|
UID: "4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
expectedErrorMessage: "Only one reference can have Controller set to true",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
errs := ValidateObjectMeta(
|
||||||
|
&api.ObjectMeta{Name: "test", Namespace: "test", OwnerReferences: tc.ownerReferences},
|
||||||
|
true,
|
||||||
|
func(s string, prefix bool) []string {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
field.NewPath("field"))
|
||||||
|
if len(errs) != 0 && !tc.expectError {
|
||||||
|
t.Errorf("unexpected error: %v in test case %v", errs, tc.description)
|
||||||
|
}
|
||||||
|
if len(errs) == 0 && tc.expectError {
|
||||||
|
t.Errorf("expect error in test case %v", tc.description)
|
||||||
|
}
|
||||||
|
if len(errs) != 0 && !strings.Contains(errs[0].Error(), tc.expectedErrorMessage) {
|
||||||
|
t.Errorf("unexpected error message: %v in test case %v", errs, tc.description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateObjectMetaUpdateIgnoresCreationTimestamp(t *testing.T) {
|
||||||
|
if errs := ValidateObjectMetaUpdate(
|
||||||
|
&api.ObjectMeta{Name: "test", ResourceVersion: "1"},
|
||||||
|
&api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(10, 0))},
|
||||||
|
field.NewPath("field"),
|
||||||
|
); len(errs) != 0 {
|
||||||
|
t.Fatalf("unexpected errors: %v", errs)
|
||||||
|
}
|
||||||
|
if errs := ValidateObjectMetaUpdate(
|
||||||
|
&api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(10, 0))},
|
||||||
|
&api.ObjectMeta{Name: "test", ResourceVersion: "1"},
|
||||||
|
field.NewPath("field"),
|
||||||
|
); len(errs) != 0 {
|
||||||
|
t.Fatalf("unexpected errors: %v", errs)
|
||||||
|
}
|
||||||
|
if errs := ValidateObjectMetaUpdate(
|
||||||
|
&api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(10, 0))},
|
||||||
|
&api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(11, 0))},
|
||||||
|
field.NewPath("field"),
|
||||||
|
); len(errs) != 0 {
|
||||||
|
t.Fatalf("unexpected errors: %v", errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateFinalizersUpdate(t *testing.T) {
|
||||||
|
testcases := map[string]struct {
|
||||||
|
Old api.ObjectMeta
|
||||||
|
New api.ObjectMeta
|
||||||
|
ExpectedErr string
|
||||||
|
}{
|
||||||
|
"invalid adding finalizers": {
|
||||||
|
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a"}},
|
||||||
|
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a", "y/b"}},
|
||||||
|
ExpectedErr: "y/b",
|
||||||
|
},
|
||||||
|
"invalid changing finalizers": {
|
||||||
|
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a"}},
|
||||||
|
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/b"}},
|
||||||
|
ExpectedErr: "x/b",
|
||||||
|
},
|
||||||
|
"valid removing finalizers": {
|
||||||
|
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a", "y/b"}},
|
||||||
|
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a"}},
|
||||||
|
ExpectedErr: "",
|
||||||
|
},
|
||||||
|
"valid adding finalizers for objects not being deleted": {
|
||||||
|
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", Finalizers: []string{"x/a"}},
|
||||||
|
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", Finalizers: []string{"x/a", "y/b"}},
|
||||||
|
ExpectedErr: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, tc := range testcases {
|
||||||
|
errs := ValidateObjectMetaUpdate(&tc.New, &tc.Old, field.NewPath("field"))
|
||||||
|
if len(errs) == 0 {
|
||||||
|
if len(tc.ExpectedErr) != 0 {
|
||||||
|
t.Errorf("case: %q, expected error to contain %q", name, tc.ExpectedErr)
|
||||||
|
}
|
||||||
|
} else if e, a := tc.ExpectedErr, errs.ToAggregate().Error(); !strings.Contains(a, e) {
|
||||||
|
t.Errorf("case: %q, expected error to contain %q, got error %q", name, e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateObjectMetaUpdatePreventsDeletionFieldMutation(t *testing.T) {
|
||||||
|
now := metav1.NewTime(time.Unix(1000, 0).UTC())
|
||||||
|
later := metav1.NewTime(time.Unix(2000, 0).UTC())
|
||||||
|
gracePeriodShort := int64(30)
|
||||||
|
gracePeriodLong := int64(40)
|
||||||
|
|
||||||
|
testcases := map[string]struct {
|
||||||
|
Old api.ObjectMeta
|
||||||
|
New api.ObjectMeta
|
||||||
|
ExpectedNew api.ObjectMeta
|
||||||
|
ExpectedErrs []string
|
||||||
|
}{
|
||||||
|
"valid without deletion fields": {
|
||||||
|
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1"},
|
||||||
|
New: api.ObjectMeta{Name: "test", ResourceVersion: "1"},
|
||||||
|
ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1"},
|
||||||
|
ExpectedErrs: []string{},
|
||||||
|
},
|
||||||
|
"valid with deletion fields": {
|
||||||
|
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort},
|
||||||
|
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort},
|
||||||
|
ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort},
|
||||||
|
ExpectedErrs: []string{},
|
||||||
|
},
|
||||||
|
|
||||||
|
"invalid set deletionTimestamp": {
|
||||||
|
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1"},
|
||||||
|
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
|
||||||
|
ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
|
||||||
|
ExpectedErrs: []string{"field.deletionTimestamp: Invalid value: \"1970-01-01T00:16:40Z\": field is immutable; may only be changed via deletion"},
|
||||||
|
},
|
||||||
|
"invalid clear deletionTimestamp": {
|
||||||
|
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
|
||||||
|
New: api.ObjectMeta{Name: "test", ResourceVersion: "1"},
|
||||||
|
ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
|
||||||
|
ExpectedErrs: []string{}, // no errors, validation copies the old value
|
||||||
|
},
|
||||||
|
"invalid change deletionTimestamp": {
|
||||||
|
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
|
||||||
|
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &later},
|
||||||
|
ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
|
||||||
|
ExpectedErrs: []string{}, // no errors, validation copies the old value
|
||||||
|
},
|
||||||
|
|
||||||
|
"invalid set deletionGracePeriodSeconds": {
|
||||||
|
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1"},
|
||||||
|
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort},
|
||||||
|
ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort},
|
||||||
|
ExpectedErrs: []string{"field.deletionGracePeriodSeconds: Invalid value: 30: field is immutable; may only be changed via deletion"},
|
||||||
|
},
|
||||||
|
"invalid clear deletionGracePeriodSeconds": {
|
||||||
|
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort},
|
||||||
|
New: api.ObjectMeta{Name: "test", ResourceVersion: "1"},
|
||||||
|
ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort},
|
||||||
|
ExpectedErrs: []string{}, // no errors, validation copies the old value
|
||||||
|
},
|
||||||
|
"invalid change deletionGracePeriodSeconds": {
|
||||||
|
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort},
|
||||||
|
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodLong},
|
||||||
|
ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodLong},
|
||||||
|
ExpectedErrs: []string{"field.deletionGracePeriodSeconds: Invalid value: 40: field is immutable; may only be changed via deletion"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, tc := range testcases {
|
||||||
|
errs := ValidateObjectMetaUpdate(&tc.New, &tc.Old, field.NewPath("field"))
|
||||||
|
if len(errs) != len(tc.ExpectedErrs) {
|
||||||
|
t.Logf("%s: Expected: %#v", k, tc.ExpectedErrs)
|
||||||
|
t.Logf("%s: Got: %#v", k, errs)
|
||||||
|
t.Errorf("%s: expected %d errors, got %d", k, len(tc.ExpectedErrs), len(errs))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for i := range errs {
|
||||||
|
if errs[i].Error() != tc.ExpectedErrs[i] {
|
||||||
|
t.Errorf("%s: error #%d: expected %q, got %q", k, i, tc.ExpectedErrs[i], errs[i].Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(tc.New, tc.ExpectedNew) {
|
||||||
|
t.Errorf("%s: Expected after validation:\n%#v\ngot\n%#v", k, tc.ExpectedNew, tc.New)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestObjectMetaGenerationUpdate(t *testing.T) {
|
||||||
|
testcases := map[string]struct {
|
||||||
|
Old api.ObjectMeta
|
||||||
|
New api.ObjectMeta
|
||||||
|
ExpectedErrs []string
|
||||||
|
}{
|
||||||
|
"invalid generation change - decremented": {
|
||||||
|
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 5},
|
||||||
|
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 4},
|
||||||
|
ExpectedErrs: []string{"field.generation: Invalid value: 4: must not be decremented"},
|
||||||
|
},
|
||||||
|
"valid generation change - incremented by one": {
|
||||||
|
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 1},
|
||||||
|
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 2},
|
||||||
|
ExpectedErrs: []string{},
|
||||||
|
},
|
||||||
|
"valid generation field - not updated": {
|
||||||
|
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 5},
|
||||||
|
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 5},
|
||||||
|
ExpectedErrs: []string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, tc := range testcases {
|
||||||
|
errList := []string{}
|
||||||
|
errs := ValidateObjectMetaUpdate(&tc.New, &tc.Old, field.NewPath("field"))
|
||||||
|
if len(errs) != len(tc.ExpectedErrs) {
|
||||||
|
t.Logf("%s: Expected: %#v", k, tc.ExpectedErrs)
|
||||||
|
for _, err := range errs {
|
||||||
|
errList = append(errList, err.Error())
|
||||||
|
}
|
||||||
|
t.Logf("%s: Got: %#v", k, errList)
|
||||||
|
t.Errorf("%s: expected %d errors, got %d", k, len(tc.ExpectedErrs), len(errs))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for i := range errList {
|
||||||
|
if errList[i] != tc.ExpectedErrs[i] {
|
||||||
|
t.Errorf("%s: error #%d: expected %q, got %q", k, i, tc.ExpectedErrs[i], errList[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure trailing slash is allowed in generate name
|
||||||
|
func TestValidateObjectMetaTrimsTrailingSlash(t *testing.T) {
|
||||||
|
errs := ValidateObjectMeta(
|
||||||
|
&api.ObjectMeta{Name: "test", GenerateName: "foo-"},
|
||||||
|
false,
|
||||||
|
NameIsDNSSubdomain,
|
||||||
|
field.NewPath("field"))
|
||||||
|
if len(errs) != 0 {
|
||||||
|
t.Fatalf("unexpected errors: %v", errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateAnnotations(t *testing.T) {
|
||||||
|
successCases := []map[string]string{
|
||||||
|
{"simple": "bar"},
|
||||||
|
{"now-with-dashes": "bar"},
|
||||||
|
{"1-starts-with-num": "bar"},
|
||||||
|
{"1234": "bar"},
|
||||||
|
{"simple/simple": "bar"},
|
||||||
|
{"now-with-dashes/simple": "bar"},
|
||||||
|
{"now-with-dashes/now-with-dashes": "bar"},
|
||||||
|
{"now.with.dots/simple": "bar"},
|
||||||
|
{"now-with.dashes-and.dots/simple": "bar"},
|
||||||
|
{"1-num.2-num/3-num": "bar"},
|
||||||
|
{"1234/5678": "bar"},
|
||||||
|
{"1.2.3.4/5678": "bar"},
|
||||||
|
{"UpperCase123": "bar"},
|
||||||
|
{"a": strings.Repeat("b", totalAnnotationSizeLimitB-1)},
|
||||||
|
{
|
||||||
|
"a": strings.Repeat("b", totalAnnotationSizeLimitB/2-1),
|
||||||
|
"c": strings.Repeat("d", totalAnnotationSizeLimitB/2-1),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i := range successCases {
|
||||||
|
errs := ValidateAnnotations(successCases[i], field.NewPath("field"))
|
||||||
|
if len(errs) != 0 {
|
||||||
|
t.Errorf("case[%d] expected success, got %#v", i, errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nameErrorCases := []struct {
|
||||||
|
annotations map[string]string
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{map[string]string{"nospecialchars^=@": "bar"}, namePartErrMsg},
|
||||||
|
{map[string]string{"cantendwithadash-": "bar"}, namePartErrMsg},
|
||||||
|
{map[string]string{"only/one/slash": "bar"}, nameErrMsg},
|
||||||
|
{map[string]string{strings.Repeat("a", 254): "bar"}, maxLengthErrMsg},
|
||||||
|
}
|
||||||
|
for i := range nameErrorCases {
|
||||||
|
errs := ValidateAnnotations(nameErrorCases[i].annotations, field.NewPath("field"))
|
||||||
|
if len(errs) != 1 {
|
||||||
|
t.Errorf("case[%d]: expected failure", i)
|
||||||
|
} else {
|
||||||
|
if !strings.Contains(errs[0].Detail, nameErrorCases[i].expect) {
|
||||||
|
t.Errorf("case[%d]: error details do not include %q: %q", i, nameErrorCases[i].expect, errs[0].Detail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
totalSizeErrorCases := []map[string]string{
|
||||||
|
{"a": strings.Repeat("b", totalAnnotationSizeLimitB)},
|
||||||
|
{
|
||||||
|
"a": strings.Repeat("b", totalAnnotationSizeLimitB/2),
|
||||||
|
"c": strings.Repeat("d", totalAnnotationSizeLimitB/2),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i := range totalSizeErrorCases {
|
||||||
|
errs := ValidateAnnotations(totalSizeErrorCases[i], field.NewPath("field"))
|
||||||
|
if len(errs) != 1 {
|
||||||
|
t.Errorf("case[%d] expected failure", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -27,17 +27,17 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
utilpod "k8s.io/kubernetes/pkg/api/pod"
|
utilpod "k8s.io/kubernetes/pkg/api/pod"
|
||||||
"k8s.io/kubernetes/pkg/api/resource"
|
"k8s.io/kubernetes/pkg/api/resource"
|
||||||
apiservice "k8s.io/kubernetes/pkg/api/service"
|
apiservice "k8s.io/kubernetes/pkg/api/service"
|
||||||
"k8s.io/kubernetes/pkg/api/v1"
|
"k8s.io/kubernetes/pkg/api/validation/genericvalidation"
|
||||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||||
unversionedvalidation "k8s.io/kubernetes/pkg/apis/meta/v1/validation"
|
unversionedvalidation "k8s.io/kubernetes/pkg/apis/meta/v1/validation"
|
||||||
storageutil "k8s.io/kubernetes/pkg/apis/storage/util"
|
storageutil "k8s.io/kubernetes/pkg/apis/storage/util"
|
||||||
"k8s.io/kubernetes/pkg/capabilities"
|
"k8s.io/kubernetes/pkg/capabilities"
|
||||||
"k8s.io/kubernetes/pkg/labels"
|
"k8s.io/kubernetes/pkg/labels"
|
||||||
"k8s.io/kubernetes/pkg/runtime/schema"
|
|
||||||
"k8s.io/kubernetes/pkg/security/apparmor"
|
"k8s.io/kubernetes/pkg/security/apparmor"
|
||||||
utilconfig "k8s.io/kubernetes/pkg/util/config"
|
utilconfig "k8s.io/kubernetes/pkg/util/config"
|
||||||
"k8s.io/kubernetes/pkg/util/intstr"
|
"k8s.io/kubernetes/pkg/util/intstr"
|
||||||
@ -48,22 +48,18 @@ import (
|
|||||||
|
|
||||||
// TODO: delete this global variable when we enable the validation of common
|
// TODO: delete this global variable when we enable the validation of common
|
||||||
// fields by default.
|
// fields by default.
|
||||||
var RepairMalformedUpdates bool = true
|
var RepairMalformedUpdates bool = genericvalidation.RepairMalformedUpdates
|
||||||
|
|
||||||
const isNegativeErrorMsg string = `must be greater than or equal to 0`
|
const isNegativeErrorMsg string = genericvalidation.IsNegativeErrorMsg
|
||||||
const isInvalidQuotaResource string = `must be a standard resource for quota`
|
const isInvalidQuotaResource string = `must be a standard resource for quota`
|
||||||
const fieldImmutableErrorMsg string = `field is immutable`
|
const fieldImmutableErrorMsg string = genericvalidation.FieldImmutableErrorMsg
|
||||||
const isNotIntegerErrorMsg string = `must be an integer`
|
const isNotIntegerErrorMsg string = `must be an integer`
|
||||||
|
|
||||||
var pdPartitionErrorMsg string = validation.InclusiveRangeError(1, 255)
|
var pdPartitionErrorMsg string = validation.InclusiveRangeError(1, 255)
|
||||||
var volumeModeErrorMsg string = "must be a number between 0 and 0777 (octal), both inclusive"
|
var volumeModeErrorMsg string = "must be a number between 0 and 0777 (octal), both inclusive"
|
||||||
|
|
||||||
const totalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB
|
|
||||||
|
|
||||||
// BannedOwners is a black list of object that are not allowed to be owners.
|
// BannedOwners is a black list of object that are not allowed to be owners.
|
||||||
var BannedOwners = map[schema.GroupVersionKind]struct{}{
|
var BannedOwners = genericvalidation.BannedOwners
|
||||||
v1.SchemeGroupVersion.WithKind("Event"): {},
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateHasLabel requires that api.ObjectMeta has a Label with key and expectedValue
|
// ValidateHasLabel requires that api.ObjectMeta has a Label with key and expectedValue
|
||||||
func ValidateHasLabel(meta api.ObjectMeta, fldPath *field.Path, key, expectedValue string) field.ErrorList {
|
func ValidateHasLabel(meta api.ObjectMeta, fldPath *field.Path, key, expectedValue string) field.ErrorList {
|
||||||
@ -83,18 +79,7 @@ func ValidateHasLabel(meta api.ObjectMeta, fldPath *field.Path, key, expectedVal
|
|||||||
|
|
||||||
// ValidateAnnotations validates that a set of annotations are correctly defined.
|
// ValidateAnnotations validates that a set of annotations are correctly defined.
|
||||||
func ValidateAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList {
|
func ValidateAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
return genericvalidation.ValidateAnnotations(annotations, fldPath)
|
||||||
var totalSize int64
|
|
||||||
for k, v := range annotations {
|
|
||||||
for _, msg := range validation.IsQualifiedName(strings.ToLower(k)) {
|
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath, k, msg))
|
|
||||||
}
|
|
||||||
totalSize += (int64)(len(k)) + (int64)(len(v))
|
|
||||||
}
|
|
||||||
if totalSize > (int64)(totalAnnotationSizeLimitB) {
|
|
||||||
allErrs = append(allErrs, field.TooLong(fldPath, "", totalAnnotationSizeLimitB))
|
|
||||||
}
|
|
||||||
return allErrs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateDNS1123Label(value string, fldPath *field.Path) field.ErrorList {
|
func ValidateDNS1123Label(value string, fldPath *field.Path) field.ErrorList {
|
||||||
@ -185,43 +170,8 @@ func ValidateEndpointsSpecificAnnotations(annotations map[string]string, fldPath
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func ValidateOwnerReferences(ownerReferences []metav1.OwnerReference, fldPath *field.Path) field.ErrorList {
|
func ValidateOwnerReferences(ownerReferences []metav1.OwnerReference, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
return genericvalidation.ValidateOwnerReferences(ownerReferences, fldPath)
|
||||||
controllerName := ""
|
|
||||||
for _, ref := range ownerReferences {
|
|
||||||
allErrs = append(allErrs, validateOwnerReference(ref, fldPath)...)
|
|
||||||
if ref.Controller != nil && *ref.Controller {
|
|
||||||
if controllerName != "" {
|
|
||||||
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", controllerName, ref.Name)))
|
|
||||||
} else {
|
|
||||||
controllerName = ref.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return allErrs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateNameFunc validates that the provided name is valid for a given resource type.
|
// ValidateNameFunc validates that the provided name is valid for a given resource type.
|
||||||
@ -229,7 +179,7 @@ func ValidateOwnerReferences(ownerReferences []metav1.OwnerReference, fldPath *f
|
|||||||
// if the name will have a value appended to it. If the name is not valid,
|
// if the name will have a value appended to it. If the name is not valid,
|
||||||
// this returns a list of descriptions of individual characteristics of the
|
// this returns a list of descriptions of individual characteristics of the
|
||||||
// value that were not valid. Otherwise this returns an empty list or nil.
|
// value that were not valid. Otherwise this returns an empty list or nil.
|
||||||
type ValidateNameFunc func(name string, prefix bool) []string
|
type ValidateNameFunc genericvalidation.ValidateNameFunc
|
||||||
|
|
||||||
// maskTrailingDash replaces the final character of a string with a subdomain safe
|
// maskTrailingDash replaces the final character of a string with a subdomain safe
|
||||||
// value if is a dash.
|
// value if is a dash.
|
||||||
@ -264,7 +214,7 @@ var ValidateNodeName = NameIsDNSSubdomain
|
|||||||
// ValidateNamespaceName can be used to check whether the given namespace name is valid.
|
// ValidateNamespaceName can be used to check whether the given namespace name is valid.
|
||||||
// Prefix indicates this name will be used as part of generation, in which case
|
// Prefix indicates this name will be used as part of generation, in which case
|
||||||
// trailing dashes are allowed.
|
// trailing dashes are allowed.
|
||||||
var ValidateNamespaceName = NameIsDNSLabel
|
var ValidateNamespaceName = genericvalidation.ValidateNamespaceName
|
||||||
|
|
||||||
// ValidateLimitRangeName can be used to check whether the given limit range name is valid.
|
// ValidateLimitRangeName can be used to check whether the given limit range name is valid.
|
||||||
// Prefix indicates this name will be used as part of generation, in which case
|
// Prefix indicates this name will be used as part of generation, in which case
|
||||||
@ -285,7 +235,7 @@ var ValidateSecretName = NameIsDNSSubdomain
|
|||||||
// ValidateServiceAccountName can be used to check whether the given service account name is valid.
|
// ValidateServiceAccountName can be used to check whether the given service account name is valid.
|
||||||
// Prefix indicates this name will be used as part of generation, in which case
|
// Prefix indicates this name will be used as part of generation, in which case
|
||||||
// trailing dashes are allowed.
|
// trailing dashes are allowed.
|
||||||
var ValidateServiceAccountName = NameIsDNSSubdomain
|
var ValidateServiceAccountName = genericvalidation.ValidateServiceAccountName
|
||||||
|
|
||||||
// ValidateEndpointsName can be used to check whether the given endpoints name is valid.
|
// ValidateEndpointsName can be used to check whether the given endpoints name is valid.
|
||||||
// Prefix indicates this name will be used as part of generation, in which case
|
// Prefix indicates this name will be used as part of generation, in which case
|
||||||
@ -293,39 +243,27 @@ var ValidateServiceAccountName = NameIsDNSSubdomain
|
|||||||
var ValidateEndpointsName = NameIsDNSSubdomain
|
var ValidateEndpointsName = NameIsDNSSubdomain
|
||||||
|
|
||||||
// ValidateClusterName can be used to check whether the given cluster name is valid.
|
// ValidateClusterName can be used to check whether the given cluster name is valid.
|
||||||
var ValidateClusterName = NameIsDNS1035Label
|
var ValidateClusterName = genericvalidation.ValidateClusterName
|
||||||
|
|
||||||
|
// TODO update all references to these functions to point to the genericvalidation ones
|
||||||
// NameIsDNSSubdomain is a ValidateNameFunc for names that must be a DNS subdomain.
|
// NameIsDNSSubdomain is a ValidateNameFunc for names that must be a DNS subdomain.
|
||||||
func NameIsDNSSubdomain(name string, prefix bool) []string {
|
func NameIsDNSSubdomain(name string, prefix bool) []string {
|
||||||
if prefix {
|
return genericvalidation.NameIsDNSSubdomain(name, prefix)
|
||||||
name = maskTrailingDash(name)
|
|
||||||
}
|
|
||||||
return validation.IsDNS1123Subdomain(name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NameIsDNSLabel is a ValidateNameFunc for names that must be a DNS 1123 label.
|
// NameIsDNSLabel is a ValidateNameFunc for names that must be a DNS 1123 label.
|
||||||
func NameIsDNSLabel(name string, prefix bool) []string {
|
func NameIsDNSLabel(name string, prefix bool) []string {
|
||||||
if prefix {
|
return genericvalidation.NameIsDNSLabel(name, prefix)
|
||||||
name = maskTrailingDash(name)
|
|
||||||
}
|
|
||||||
return validation.IsDNS1123Label(name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NameIsDNS1035Label is a ValidateNameFunc for names that must be a DNS 952 label.
|
// NameIsDNS1035Label is a ValidateNameFunc for names that must be a DNS 952 label.
|
||||||
func NameIsDNS1035Label(name string, prefix bool) []string {
|
func NameIsDNS1035Label(name string, prefix bool) []string {
|
||||||
if prefix {
|
return genericvalidation.NameIsDNS1035Label(name, prefix)
|
||||||
name = maskTrailingDash(name)
|
|
||||||
}
|
|
||||||
return validation.IsDNS1035Label(name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validates that given value is not negative.
|
// Validates that given value is not negative.
|
||||||
func ValidateNonnegativeField(value int64, fldPath *field.Path) field.ErrorList {
|
func ValidateNonnegativeField(value int64, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
return genericvalidation.ValidateNonnegativeField(value, fldPath)
|
||||||
if value < 0 {
|
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath, value, isNegativeErrorMsg))
|
|
||||||
}
|
|
||||||
return allErrs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validates that a Quantity is not negative
|
// Validates that a Quantity is not negative
|
||||||
@ -338,11 +276,7 @@ func ValidateNonnegativeQuantity(value resource.Quantity, fldPath *field.Path) f
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ValidateImmutableField(newVal, oldVal interface{}, fldPath *field.Path) field.ErrorList {
|
func ValidateImmutableField(newVal, oldVal interface{}, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
return genericvalidation.ValidateImmutableField(newVal, oldVal, fldPath)
|
||||||
if !api.Semantic.DeepEqual(oldVal, newVal) {
|
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath, newVal, fieldImmutableErrorMsg))
|
|
||||||
}
|
|
||||||
return allErrs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateImmutableAnnotation(newVal string, oldVal string, annotation string, fldPath *field.Path) field.ErrorList {
|
func ValidateImmutableAnnotation(newVal string, oldVal string, annotation string, fldPath *field.Path) field.ErrorList {
|
||||||
@ -359,124 +293,16 @@ func ValidateImmutableAnnotation(newVal string, oldVal string, annotation string
|
|||||||
// It doesn't return an error for rootscoped resources with namespace, because namespace should already be cleared before.
|
// It doesn't return an error for rootscoped resources with namespace, because namespace should already be cleared before.
|
||||||
// TODO: Remove calls to this method scattered in validations of specific resources, e.g., ValidatePodUpdate.
|
// TODO: Remove calls to this method scattered in validations of specific resources, e.g., ValidatePodUpdate.
|
||||||
func ValidateObjectMeta(meta *api.ObjectMeta, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path) field.ErrorList {
|
func ValidateObjectMeta(meta *api.ObjectMeta, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
return genericvalidation.ValidateObjectMeta(meta, requiresNamespace, genericvalidation.ValidateNameFunc(nameFn), fldPath)
|
||||||
|
|
||||||
if len(meta.GenerateName) != 0 {
|
|
||||||
for _, msg := range nameFn(meta.GenerateName, true) {
|
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("generateName"), meta.GenerateName, 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.Name) == 0 {
|
|
||||||
allErrs = append(allErrs, field.Required(fldPath.Child("name"), "name or generateName is required"))
|
|
||||||
} else {
|
|
||||||
for _, msg := range nameFn(meta.Name, false) {
|
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), meta.Name, msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if requiresNamespace {
|
|
||||||
if len(meta.Namespace) == 0 {
|
|
||||||
allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), ""))
|
|
||||||
} else {
|
|
||||||
for _, msg := range ValidateNamespaceName(meta.Namespace, false) {
|
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), meta.Namespace, msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if len(meta.Namespace) != 0 {
|
|
||||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("namespace"), "not allowed on this type"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(meta.ClusterName) != 0 {
|
|
||||||
for _, msg := range ValidateClusterName(meta.ClusterName, false) {
|
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("clusterName"), meta.ClusterName, msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
allErrs = append(allErrs, ValidateNonnegativeField(meta.Generation, fldPath.Child("generation"))...)
|
|
||||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabels(meta.Labels, fldPath.Child("labels"))...)
|
|
||||||
allErrs = append(allErrs, ValidateAnnotations(meta.Annotations, fldPath.Child("annotations"))...)
|
|
||||||
allErrs = append(allErrs, ValidateOwnerReferences(meta.OwnerReferences, fldPath.Child("ownerReferences"))...)
|
|
||||||
for _, finalizer := range meta.Finalizers {
|
|
||||||
allErrs = append(allErrs, validateFinalizerName(finalizer, fldPath.Child("finalizers"))...)
|
|
||||||
}
|
|
||||||
return allErrs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateObjectMetaUpdate validates an object's metadata when updated
|
// ValidateObjectMetaUpdate validates an object's metadata when updated
|
||||||
func ValidateObjectMetaUpdate(newMeta, oldMeta *api.ObjectMeta, fldPath *field.Path) field.ErrorList {
|
func ValidateObjectMetaUpdate(newMeta, oldMeta *api.ObjectMeta, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
return genericvalidation.ValidateObjectMetaUpdate(newMeta, oldMeta, fldPath)
|
||||||
|
|
||||||
if !RepairMalformedUpdates && newMeta.UID != oldMeta.UID {
|
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("uid"), newMeta.UID, "field is immutable"))
|
|
||||||
}
|
|
||||||
// in the event it is left empty, set it, to allow clients more flexibility
|
|
||||||
// TODO: remove the following code that repairs the update request when we retire the clients that modify the immutable fields.
|
|
||||||
// Please do not copy this pattern elsewhere; validation functions should not be modifying the objects they are passed!
|
|
||||||
if RepairMalformedUpdates {
|
|
||||||
if len(newMeta.UID) == 0 {
|
|
||||||
newMeta.UID = oldMeta.UID
|
|
||||||
}
|
|
||||||
// ignore changes to timestamp
|
|
||||||
if oldMeta.CreationTimestamp.IsZero() {
|
|
||||||
oldMeta.CreationTimestamp = newMeta.CreationTimestamp
|
|
||||||
} else {
|
|
||||||
newMeta.CreationTimestamp = oldMeta.CreationTimestamp
|
|
||||||
}
|
|
||||||
// an object can never remove a deletion timestamp or clear/change grace period seconds
|
|
||||||
if !oldMeta.DeletionTimestamp.IsZero() {
|
|
||||||
newMeta.DeletionTimestamp = oldMeta.DeletionTimestamp
|
|
||||||
}
|
|
||||||
if oldMeta.DeletionGracePeriodSeconds != nil && newMeta.DeletionGracePeriodSeconds == nil {
|
|
||||||
newMeta.DeletionGracePeriodSeconds = oldMeta.DeletionGracePeriodSeconds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: needs to check if newMeta==nil && oldMeta !=nil after the repair logic is removed.
|
|
||||||
if newMeta.DeletionGracePeriodSeconds != nil && (oldMeta.DeletionGracePeriodSeconds == nil || *newMeta.DeletionGracePeriodSeconds != *oldMeta.DeletionGracePeriodSeconds) {
|
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("deletionGracePeriodSeconds"), newMeta.DeletionGracePeriodSeconds, "field is immutable; may only be changed via deletion"))
|
|
||||||
}
|
|
||||||
if newMeta.DeletionTimestamp != nil && (oldMeta.DeletionTimestamp == nil || !newMeta.DeletionTimestamp.Equal(*oldMeta.DeletionTimestamp)) {
|
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("deletionTimestamp"), newMeta.DeletionTimestamp, "field is immutable; may only be changed via deletion"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finalizers cannot be added if the object is already being deleted.
|
|
||||||
if oldMeta.DeletionTimestamp != nil {
|
|
||||||
allErrs = append(allErrs, ValidateNoNewFinalizers(newMeta.Finalizers, oldMeta.Finalizers, fldPath.Child("finalizers"))...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reject updates that don't specify a resource version
|
|
||||||
if len(newMeta.ResourceVersion) == 0 {
|
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceVersion"), newMeta.ResourceVersion, "must be specified for an update"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generation shouldn't be decremented
|
|
||||||
if newMeta.Generation < oldMeta.Generation {
|
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("generation"), newMeta.Generation, "must not be decremented"))
|
|
||||||
}
|
|
||||||
|
|
||||||
allErrs = append(allErrs, ValidateImmutableField(newMeta.Name, oldMeta.Name, fldPath.Child("name"))...)
|
|
||||||
allErrs = append(allErrs, ValidateImmutableField(newMeta.Namespace, oldMeta.Namespace, fldPath.Child("namespace"))...)
|
|
||||||
allErrs = append(allErrs, ValidateImmutableField(newMeta.UID, oldMeta.UID, fldPath.Child("uid"))...)
|
|
||||||
allErrs = append(allErrs, ValidateImmutableField(newMeta.CreationTimestamp, oldMeta.CreationTimestamp, fldPath.Child("creationTimestamp"))...)
|
|
||||||
allErrs = append(allErrs, ValidateImmutableField(newMeta.ClusterName, oldMeta.ClusterName, fldPath.Child("clusterName"))...)
|
|
||||||
|
|
||||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabels(newMeta.Labels, fldPath.Child("labels"))...)
|
|
||||||
allErrs = append(allErrs, ValidateAnnotations(newMeta.Annotations, fldPath.Child("annotations"))...)
|
|
||||||
allErrs = append(allErrs, ValidateOwnerReferences(newMeta.OwnerReferences, fldPath.Child("ownerReferences"))...)
|
|
||||||
|
|
||||||
return allErrs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateNoNewFinalizers(newFinalizers []string, oldFinalizers []string, fldPath *field.Path) field.ErrorList {
|
func ValidateNoNewFinalizers(newFinalizers []string, oldFinalizers []string, fldPath *field.Path) field.ErrorList {
|
||||||
const newFinalizersErrorMsg string = `no new finalizers can be added if the object is being deleted`
|
return genericvalidation.ValidateNoNewFinalizers(newFinalizers, oldFinalizers, fldPath)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateVolumes(volumes []api.Volume, fldPath *field.Path) (sets.String, field.ErrorList) {
|
func validateVolumes(volumes []api.Volume, fldPath *field.Path) (sets.String, field.ErrorList) {
|
||||||
@ -3482,21 +3308,7 @@ func ValidateNamespace(namespace *api.Namespace) field.ErrorList {
|
|||||||
|
|
||||||
// Validate finalizer names
|
// Validate finalizer names
|
||||||
func validateFinalizerName(stringValue string, fldPath *field.Path) field.ErrorList {
|
func validateFinalizerName(stringValue string, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
return genericvalidation.ValidateFinalizerName(stringValue, fldPath)
|
||||||
for _, msg := range validation.IsQualifiedName(stringValue) {
|
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath, stringValue, msg))
|
|
||||||
}
|
|
||||||
if len(allErrs) != 0 {
|
|
||||||
return allErrs
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(strings.Split(stringValue, "/")) == 1 {
|
|
||||||
if !api.IsStandardFinalizerName(stringValue) {
|
|
||||||
return append(allErrs, field.Invalid(fldPath, stringValue, "name is neither a standard finalizer name nor is it fully qualified"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return field.ErrorList{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateNamespaceUpdate tests to make sure a namespace update can be applied.
|
// ValidateNamespaceUpdate tests to make sure a namespace update can be applied.
|
||||||
|
@ -17,11 +17,9 @@ limitations under the License.
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/api/resource"
|
"k8s.io/kubernetes/pkg/api/resource"
|
||||||
@ -55,450 +53,6 @@ func expectPrefix(t *testing.T, prefix string, errs field.ErrorList) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure custom name functions are allowed
|
|
||||||
func TestValidateObjectMetaCustomName(t *testing.T) {
|
|
||||||
errs := ValidateObjectMeta(
|
|
||||||
&api.ObjectMeta{Name: "test", GenerateName: "foo"},
|
|
||||||
false,
|
|
||||||
func(s string, prefix bool) []string {
|
|
||||||
if s == "test" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return []string{"name-gen"}
|
|
||||||
},
|
|
||||||
field.NewPath("field"))
|
|
||||||
if len(errs) != 1 {
|
|
||||||
t.Fatalf("unexpected errors: %v", errs)
|
|
||||||
}
|
|
||||||
if !strings.Contains(errs[0].Error(), "name-gen") {
|
|
||||||
t.Errorf("unexpected error message: %v", errs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure namespace names follow dns label format
|
|
||||||
func TestValidateObjectMetaNamespaces(t *testing.T) {
|
|
||||||
errs := ValidateObjectMeta(
|
|
||||||
&api.ObjectMeta{Name: "test", Namespace: "foo.bar"},
|
|
||||||
true,
|
|
||||||
func(s string, prefix bool) []string {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
field.NewPath("field"))
|
|
||||||
if len(errs) != 1 {
|
|
||||||
t.Fatalf("unexpected errors: %v", errs)
|
|
||||||
}
|
|
||||||
if !strings.Contains(errs[0].Error(), `Invalid value: "foo.bar"`) {
|
|
||||||
t.Errorf("unexpected error message: %v", errs)
|
|
||||||
}
|
|
||||||
maxLength := 63
|
|
||||||
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
|
||||||
b := make([]rune, maxLength+1)
|
|
||||||
for i := range b {
|
|
||||||
b[i] = letters[rand.Intn(len(letters))]
|
|
||||||
}
|
|
||||||
errs = ValidateObjectMeta(
|
|
||||||
&api.ObjectMeta{Name: "test", Namespace: string(b)},
|
|
||||||
true,
|
|
||||||
func(s string, prefix bool) []string {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
field.NewPath("field"))
|
|
||||||
if len(errs) != 2 {
|
|
||||||
t.Fatalf("unexpected errors: %v", errs)
|
|
||||||
}
|
|
||||||
if !strings.Contains(errs[0].Error(), "Invalid value") || !strings.Contains(errs[1].Error(), "Invalid value") {
|
|
||||||
t.Errorf("unexpected error message: %v", errs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidateObjectMetaOwnerReferences(t *testing.T) {
|
|
||||||
trueVar := true
|
|
||||||
falseVar := false
|
|
||||||
testCases := []struct {
|
|
||||||
description string
|
|
||||||
ownerReferences []metav1.OwnerReference
|
|
||||||
expectError bool
|
|
||||||
expectedErrorMessage string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
description: "simple success - third party extension.",
|
|
||||||
ownerReferences: []metav1.OwnerReference{
|
|
||||||
{
|
|
||||||
APIVersion: "thirdpartyVersion",
|
|
||||||
Kind: "thirdpartyKind",
|
|
||||||
Name: "name",
|
|
||||||
UID: "1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectError: false,
|
|
||||||
expectedErrorMessage: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "simple failures - event shouldn't be set as an owner",
|
|
||||||
ownerReferences: []metav1.OwnerReference{
|
|
||||||
{
|
|
||||||
APIVersion: "v1",
|
|
||||||
Kind: "Event",
|
|
||||||
Name: "name",
|
|
||||||
UID: "1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectError: true,
|
|
||||||
expectedErrorMessage: "is disallowed from being an owner",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "simple controller ref success - one reference with Controller set",
|
|
||||||
ownerReferences: []metav1.OwnerReference{
|
|
||||||
{
|
|
||||||
APIVersion: "thirdpartyVersion",
|
|
||||||
Kind: "thirdpartyKind",
|
|
||||||
Name: "name",
|
|
||||||
UID: "1",
|
|
||||||
Controller: &falseVar,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
APIVersion: "thirdpartyVersion",
|
|
||||||
Kind: "thirdpartyKind",
|
|
||||||
Name: "name",
|
|
||||||
UID: "2",
|
|
||||||
Controller: &trueVar,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
APIVersion: "thirdpartyVersion",
|
|
||||||
Kind: "thirdpartyKind",
|
|
||||||
Name: "name",
|
|
||||||
UID: "3",
|
|
||||||
Controller: &falseVar,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
APIVersion: "thirdpartyVersion",
|
|
||||||
Kind: "thirdpartyKind",
|
|
||||||
Name: "name",
|
|
||||||
UID: "4",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectError: false,
|
|
||||||
expectedErrorMessage: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "simple controller ref failure - two references with Controller set",
|
|
||||||
ownerReferences: []metav1.OwnerReference{
|
|
||||||
{
|
|
||||||
APIVersion: "thirdpartyVersion",
|
|
||||||
Kind: "thirdpartyKind",
|
|
||||||
Name: "name",
|
|
||||||
UID: "1",
|
|
||||||
Controller: &falseVar,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
APIVersion: "thirdpartyVersion",
|
|
||||||
Kind: "thirdpartyKind",
|
|
||||||
Name: "name",
|
|
||||||
UID: "2",
|
|
||||||
Controller: &trueVar,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
APIVersion: "thirdpartyVersion",
|
|
||||||
Kind: "thirdpartyKind",
|
|
||||||
Name: "name",
|
|
||||||
UID: "3",
|
|
||||||
Controller: &trueVar,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
APIVersion: "thirdpartyVersion",
|
|
||||||
Kind: "thirdpartyKind",
|
|
||||||
Name: "name",
|
|
||||||
UID: "4",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectError: true,
|
|
||||||
expectedErrorMessage: "Only one reference can have Controller set to true",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
errs := ValidateObjectMeta(
|
|
||||||
&api.ObjectMeta{Name: "test", Namespace: "test", OwnerReferences: tc.ownerReferences},
|
|
||||||
true,
|
|
||||||
func(s string, prefix bool) []string {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
field.NewPath("field"))
|
|
||||||
if len(errs) != 0 && !tc.expectError {
|
|
||||||
t.Errorf("unexpected error: %v in test case %v", errs, tc.description)
|
|
||||||
}
|
|
||||||
if len(errs) == 0 && tc.expectError {
|
|
||||||
t.Errorf("expect error in test case %v", tc.description)
|
|
||||||
}
|
|
||||||
if len(errs) != 0 && !strings.Contains(errs[0].Error(), tc.expectedErrorMessage) {
|
|
||||||
t.Errorf("unexpected error message: %v in test case %v", errs, tc.description)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidateObjectMetaUpdateIgnoresCreationTimestamp(t *testing.T) {
|
|
||||||
if errs := ValidateObjectMetaUpdate(
|
|
||||||
&api.ObjectMeta{Name: "test", ResourceVersion: "1"},
|
|
||||||
&api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(10, 0))},
|
|
||||||
field.NewPath("field"),
|
|
||||||
); len(errs) != 0 {
|
|
||||||
t.Fatalf("unexpected errors: %v", errs)
|
|
||||||
}
|
|
||||||
if errs := ValidateObjectMetaUpdate(
|
|
||||||
&api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(10, 0))},
|
|
||||||
&api.ObjectMeta{Name: "test", ResourceVersion: "1"},
|
|
||||||
field.NewPath("field"),
|
|
||||||
); len(errs) != 0 {
|
|
||||||
t.Fatalf("unexpected errors: %v", errs)
|
|
||||||
}
|
|
||||||
if errs := ValidateObjectMetaUpdate(
|
|
||||||
&api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(10, 0))},
|
|
||||||
&api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(11, 0))},
|
|
||||||
field.NewPath("field"),
|
|
||||||
); len(errs) != 0 {
|
|
||||||
t.Fatalf("unexpected errors: %v", errs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidateFinalizersUpdate(t *testing.T) {
|
|
||||||
testcases := map[string]struct {
|
|
||||||
Old api.ObjectMeta
|
|
||||||
New api.ObjectMeta
|
|
||||||
ExpectedErr string
|
|
||||||
}{
|
|
||||||
"invalid adding finalizers": {
|
|
||||||
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a"}},
|
|
||||||
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a", "y/b"}},
|
|
||||||
ExpectedErr: "y/b",
|
|
||||||
},
|
|
||||||
"invalid changing finalizers": {
|
|
||||||
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a"}},
|
|
||||||
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/b"}},
|
|
||||||
ExpectedErr: "x/b",
|
|
||||||
},
|
|
||||||
"valid removing finalizers": {
|
|
||||||
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a", "y/b"}},
|
|
||||||
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a"}},
|
|
||||||
ExpectedErr: "",
|
|
||||||
},
|
|
||||||
"valid adding finalizers for objects not being deleted": {
|
|
||||||
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", Finalizers: []string{"x/a"}},
|
|
||||||
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", Finalizers: []string{"x/a", "y/b"}},
|
|
||||||
ExpectedErr: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for name, tc := range testcases {
|
|
||||||
errs := ValidateObjectMetaUpdate(&tc.New, &tc.Old, field.NewPath("field"))
|
|
||||||
if len(errs) == 0 {
|
|
||||||
if len(tc.ExpectedErr) != 0 {
|
|
||||||
t.Errorf("case: %q, expected error to contain %q", name, tc.ExpectedErr)
|
|
||||||
}
|
|
||||||
} else if e, a := tc.ExpectedErr, errs.ToAggregate().Error(); !strings.Contains(a, e) {
|
|
||||||
t.Errorf("case: %q, expected error to contain %q, got error %q", name, e, a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidateObjectMetaUpdatePreventsDeletionFieldMutation(t *testing.T) {
|
|
||||||
now := metav1.NewTime(time.Unix(1000, 0).UTC())
|
|
||||||
later := metav1.NewTime(time.Unix(2000, 0).UTC())
|
|
||||||
gracePeriodShort := int64(30)
|
|
||||||
gracePeriodLong := int64(40)
|
|
||||||
|
|
||||||
testcases := map[string]struct {
|
|
||||||
Old api.ObjectMeta
|
|
||||||
New api.ObjectMeta
|
|
||||||
ExpectedNew api.ObjectMeta
|
|
||||||
ExpectedErrs []string
|
|
||||||
}{
|
|
||||||
"valid without deletion fields": {
|
|
||||||
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1"},
|
|
||||||
New: api.ObjectMeta{Name: "test", ResourceVersion: "1"},
|
|
||||||
ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1"},
|
|
||||||
ExpectedErrs: []string{},
|
|
||||||
},
|
|
||||||
"valid with deletion fields": {
|
|
||||||
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort},
|
|
||||||
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort},
|
|
||||||
ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort},
|
|
||||||
ExpectedErrs: []string{},
|
|
||||||
},
|
|
||||||
|
|
||||||
"invalid set deletionTimestamp": {
|
|
||||||
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1"},
|
|
||||||
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
|
|
||||||
ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
|
|
||||||
ExpectedErrs: []string{"field.deletionTimestamp: Invalid value: \"1970-01-01T00:16:40Z\": field is immutable; may only be changed via deletion"},
|
|
||||||
},
|
|
||||||
"invalid clear deletionTimestamp": {
|
|
||||||
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
|
|
||||||
New: api.ObjectMeta{Name: "test", ResourceVersion: "1"},
|
|
||||||
ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
|
|
||||||
ExpectedErrs: []string{}, // no errors, validation copies the old value
|
|
||||||
},
|
|
||||||
"invalid change deletionTimestamp": {
|
|
||||||
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
|
|
||||||
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &later},
|
|
||||||
ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
|
|
||||||
ExpectedErrs: []string{}, // no errors, validation copies the old value
|
|
||||||
},
|
|
||||||
|
|
||||||
"invalid set deletionGracePeriodSeconds": {
|
|
||||||
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1"},
|
|
||||||
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort},
|
|
||||||
ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort},
|
|
||||||
ExpectedErrs: []string{"field.deletionGracePeriodSeconds: Invalid value: 30: field is immutable; may only be changed via deletion"},
|
|
||||||
},
|
|
||||||
"invalid clear deletionGracePeriodSeconds": {
|
|
||||||
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort},
|
|
||||||
New: api.ObjectMeta{Name: "test", ResourceVersion: "1"},
|
|
||||||
ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort},
|
|
||||||
ExpectedErrs: []string{}, // no errors, validation copies the old value
|
|
||||||
},
|
|
||||||
"invalid change deletionGracePeriodSeconds": {
|
|
||||||
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort},
|
|
||||||
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodLong},
|
|
||||||
ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodLong},
|
|
||||||
ExpectedErrs: []string{"field.deletionGracePeriodSeconds: Invalid value: 40: field is immutable; may only be changed via deletion"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, tc := range testcases {
|
|
||||||
errs := ValidateObjectMetaUpdate(&tc.New, &tc.Old, field.NewPath("field"))
|
|
||||||
if len(errs) != len(tc.ExpectedErrs) {
|
|
||||||
t.Logf("%s: Expected: %#v", k, tc.ExpectedErrs)
|
|
||||||
t.Logf("%s: Got: %#v", k, errs)
|
|
||||||
t.Errorf("%s: expected %d errors, got %d", k, len(tc.ExpectedErrs), len(errs))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for i := range errs {
|
|
||||||
if errs[i].Error() != tc.ExpectedErrs[i] {
|
|
||||||
t.Errorf("%s: error #%d: expected %q, got %q", k, i, tc.ExpectedErrs[i], errs[i].Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(tc.New, tc.ExpectedNew) {
|
|
||||||
t.Errorf("%s: Expected after validation:\n%#v\ngot\n%#v", k, tc.ExpectedNew, tc.New)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestObjectMetaGenerationUpdate(t *testing.T) {
|
|
||||||
testcases := map[string]struct {
|
|
||||||
Old api.ObjectMeta
|
|
||||||
New api.ObjectMeta
|
|
||||||
ExpectedErrs []string
|
|
||||||
}{
|
|
||||||
"invalid generation change - decremented": {
|
|
||||||
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 5},
|
|
||||||
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 4},
|
|
||||||
ExpectedErrs: []string{"field.generation: Invalid value: 4: must not be decremented"},
|
|
||||||
},
|
|
||||||
"valid generation change - incremented by one": {
|
|
||||||
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 1},
|
|
||||||
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 2},
|
|
||||||
ExpectedErrs: []string{},
|
|
||||||
},
|
|
||||||
"valid generation field - not updated": {
|
|
||||||
Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 5},
|
|
||||||
New: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 5},
|
|
||||||
ExpectedErrs: []string{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, tc := range testcases {
|
|
||||||
errList := []string{}
|
|
||||||
errs := ValidateObjectMetaUpdate(&tc.New, &tc.Old, field.NewPath("field"))
|
|
||||||
if len(errs) != len(tc.ExpectedErrs) {
|
|
||||||
t.Logf("%s: Expected: %#v", k, tc.ExpectedErrs)
|
|
||||||
for _, err := range errs {
|
|
||||||
errList = append(errList, err.Error())
|
|
||||||
}
|
|
||||||
t.Logf("%s: Got: %#v", k, errList)
|
|
||||||
t.Errorf("%s: expected %d errors, got %d", k, len(tc.ExpectedErrs), len(errs))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for i := range errList {
|
|
||||||
if errList[i] != tc.ExpectedErrs[i] {
|
|
||||||
t.Errorf("%s: error #%d: expected %q, got %q", k, i, tc.ExpectedErrs[i], errList[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure trailing slash is allowed in generate name
|
|
||||||
func TestValidateObjectMetaTrimsTrailingSlash(t *testing.T) {
|
|
||||||
errs := ValidateObjectMeta(
|
|
||||||
&api.ObjectMeta{Name: "test", GenerateName: "foo-"},
|
|
||||||
false,
|
|
||||||
NameIsDNSSubdomain,
|
|
||||||
field.NewPath("field"))
|
|
||||||
if len(errs) != 0 {
|
|
||||||
t.Fatalf("unexpected errors: %v", errs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidateAnnotations(t *testing.T) {
|
|
||||||
successCases := []map[string]string{
|
|
||||||
{"simple": "bar"},
|
|
||||||
{"now-with-dashes": "bar"},
|
|
||||||
{"1-starts-with-num": "bar"},
|
|
||||||
{"1234": "bar"},
|
|
||||||
{"simple/simple": "bar"},
|
|
||||||
{"now-with-dashes/simple": "bar"},
|
|
||||||
{"now-with-dashes/now-with-dashes": "bar"},
|
|
||||||
{"now.with.dots/simple": "bar"},
|
|
||||||
{"now-with.dashes-and.dots/simple": "bar"},
|
|
||||||
{"1-num.2-num/3-num": "bar"},
|
|
||||||
{"1234/5678": "bar"},
|
|
||||||
{"1.2.3.4/5678": "bar"},
|
|
||||||
{"UpperCase123": "bar"},
|
|
||||||
{"a": strings.Repeat("b", totalAnnotationSizeLimitB-1)},
|
|
||||||
{
|
|
||||||
"a": strings.Repeat("b", totalAnnotationSizeLimitB/2-1),
|
|
||||||
"c": strings.Repeat("d", totalAnnotationSizeLimitB/2-1),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i := range successCases {
|
|
||||||
errs := ValidateAnnotations(successCases[i], field.NewPath("field"))
|
|
||||||
if len(errs) != 0 {
|
|
||||||
t.Errorf("case[%d] expected success, got %#v", i, errs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nameErrorCases := []struct {
|
|
||||||
annotations map[string]string
|
|
||||||
expect string
|
|
||||||
}{
|
|
||||||
{map[string]string{"nospecialchars^=@": "bar"}, namePartErrMsg},
|
|
||||||
{map[string]string{"cantendwithadash-": "bar"}, namePartErrMsg},
|
|
||||||
{map[string]string{"only/one/slash": "bar"}, nameErrMsg},
|
|
||||||
{map[string]string{strings.Repeat("a", 254): "bar"}, maxLengthErrMsg},
|
|
||||||
}
|
|
||||||
for i := range nameErrorCases {
|
|
||||||
errs := ValidateAnnotations(nameErrorCases[i].annotations, field.NewPath("field"))
|
|
||||||
if len(errs) != 1 {
|
|
||||||
t.Errorf("case[%d]: expected failure", i)
|
|
||||||
} else {
|
|
||||||
if !strings.Contains(errs[0].Detail, nameErrorCases[i].expect) {
|
|
||||||
t.Errorf("case[%d]: error details do not include %q: %q", i, nameErrorCases[i].expect, errs[0].Detail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
totalSizeErrorCases := []map[string]string{
|
|
||||||
{"a": strings.Repeat("b", totalAnnotationSizeLimitB)},
|
|
||||||
{
|
|
||||||
"a": strings.Repeat("b", totalAnnotationSizeLimitB/2),
|
|
||||||
"c": strings.Repeat("d", totalAnnotationSizeLimitB/2),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i := range totalSizeErrorCases {
|
|
||||||
errs := ValidateAnnotations(totalSizeErrorCases[i], field.NewPath("field"))
|
|
||||||
if len(errs) != 1 {
|
|
||||||
t.Errorf("case[%d] expected failure", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testVolume(name string, namespace string, spec api.PersistentVolumeSpec) *api.PersistentVolume {
|
func testVolume(name string, namespace string, spec api.PersistentVolumeSpec) *api.PersistentVolume {
|
||||||
objMeta := api.ObjectMeta{Name: name}
|
objMeta := api.ObjectMeta{Name: name}
|
||||||
if namespace != "" {
|
if namespace != "" {
|
||||||
|
@ -18,7 +18,7 @@ go_library(
|
|||||||
deps = [
|
deps = [
|
||||||
"//pkg/api:go_default_library",
|
"//pkg/api:go_default_library",
|
||||||
"//pkg/api/v1:go_default_library",
|
"//pkg/api/v1:go_default_library",
|
||||||
"//pkg/api/validation:go_default_library",
|
"//pkg/api/validation/genericvalidation:go_default_library",
|
||||||
"//pkg/auth/authenticator:go_default_library",
|
"//pkg/auth/authenticator:go_default_library",
|
||||||
"//vendor:github.com/dgrijalva/jwt-go",
|
"//vendor:github.com/dgrijalva/jwt-go",
|
||||||
"//vendor:github.com/golang/glog",
|
"//vendor:github.com/golang/glog",
|
||||||
|
@ -23,7 +23,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/authentication/user"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/api/v1"
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
"k8s.io/kubernetes/pkg/api/validation"
|
"k8s.io/kubernetes/pkg/api/validation/genericvalidation"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -53,10 +53,10 @@ func SplitUsername(username string) (string, string, error) {
|
|||||||
return "", "", invalidUsernameErr
|
return "", "", invalidUsernameErr
|
||||||
}
|
}
|
||||||
namespace, name := parts[0], parts[1]
|
namespace, name := parts[0], parts[1]
|
||||||
if len(validation.ValidateNamespaceName(namespace, false)) != 0 {
|
if len(genericvalidation.ValidateNamespaceName(namespace, false)) != 0 {
|
||||||
return "", "", invalidUsernameErr
|
return "", "", invalidUsernameErr
|
||||||
}
|
}
|
||||||
if len(validation.ValidateServiceAccountName(name, false)) != 0 {
|
if len(genericvalidation.ValidateServiceAccountName(name, false)) != 0 {
|
||||||
return "", "", invalidUsernameErr
|
return "", "", invalidUsernameErr
|
||||||
}
|
}
|
||||||
return namespace, name, nil
|
return namespace, name, nil
|
||||||
|
Loading…
Reference in New Issue
Block a user