mirror of https://github.com/k3s-io/k3s
snip links from genericapiserver to api/validation
parent
5ee52e8bce
commit
80143ee0b4
|
@ -23,7 +23,7 @@ go_library(
|
|||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/errors: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/apis/meta/v1:go_default_library",
|
||||
"//pkg/genericapiserver/api/request:go_default_library",
|
||||
|
|
|
@ -19,7 +19,7 @@ package rest
|
|||
import (
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"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"
|
||||
genericapirequest "k8s.io/kubernetes/pkg/genericapiserver/api/request"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
|
@ -83,7 +83,7 @@ func BeforeCreate(strategy RESTCreateStrategy, ctx genericapirequest.Context, ob
|
|||
// Custom validation (including name validation) passed
|
||||
// Now run common validation on object meta
|
||||
// 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)
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ import (
|
|||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/errors"
|
||||
"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"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/util/validation/field"
|
||||
|
@ -67,7 +67,7 @@ func validateCommonFields(obj, old runtime.Object) (field.ErrorList, error) {
|
|||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ go_library(
|
|||
"//pkg/api/resource:go_default_library",
|
||||
"//pkg/api/service: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/apis/meta/v1:go_default_library",
|
||||
"//pkg/apis/meta/v1/unstructured:go_default_library",
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
)
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
utilpod "k8s.io/kubernetes/pkg/api/pod"
|
||||
"k8s.io/kubernetes/pkg/api/resource"
|
||||
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"
|
||||
unversionedvalidation "k8s.io/kubernetes/pkg/apis/meta/v1/validation"
|
||||
storageutil "k8s.io/kubernetes/pkg/apis/storage/util"
|
||||
"k8s.io/kubernetes/pkg/capabilities"
|
||||
"k8s.io/kubernetes/pkg/labels"
|
||||
"k8s.io/kubernetes/pkg/runtime/schema"
|
||||
"k8s.io/kubernetes/pkg/security/apparmor"
|
||||
utilconfig "k8s.io/kubernetes/pkg/util/config"
|
||||
"k8s.io/kubernetes/pkg/util/intstr"
|
||||
|
@ -48,22 +48,18 @@ import (
|
|||
|
||||
// TODO: delete this global variable when we enable the validation of common
|
||||
// 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 fieldImmutableErrorMsg string = `field is immutable`
|
||||
const fieldImmutableErrorMsg string = genericvalidation.FieldImmutableErrorMsg
|
||||
const isNotIntegerErrorMsg string = `must be an integer`
|
||||
|
||||
var pdPartitionErrorMsg string = validation.InclusiveRangeError(1, 255)
|
||||
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.
|
||||
var BannedOwners = map[schema.GroupVersionKind]struct{}{
|
||||
v1.SchemeGroupVersion.WithKind("Event"): {},
|
||||
}
|
||||
var BannedOwners = genericvalidation.BannedOwners
|
||||
|
||||
// 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 {
|
||||
|
@ -83,18 +79,7 @@ func ValidateHasLabel(meta api.ObjectMeta, fldPath *field.Path, key, expectedVal
|
|||
|
||||
// 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
|
||||
return genericvalidation.ValidateAnnotations(annotations, fldPath)
|
||||
}
|
||||
|
||||
func ValidateDNS1123Label(value string, fldPath *field.Path) field.ErrorList {
|
||||
|
@ -185,43 +170,8 @@ func ValidateEndpointsSpecificAnnotations(annotations map[string]string, fldPath
|
|||
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
|
||||
return genericvalidation.ValidateOwnerReferences(ownerReferences, fldPath)
|
||||
}
|
||||
|
||||
// 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,
|
||||
// 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
|
||||
type ValidateNameFunc genericvalidation.ValidateNameFunc
|
||||
|
||||
// maskTrailingDash replaces the final character of a string with a subdomain safe
|
||||
// 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.
|
||||
// Prefix indicates this name will be used as part of generation, in which case
|
||||
// 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.
|
||||
// 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.
|
||||
// Prefix indicates this name will be used as part of generation, in which case
|
||||
// trailing dashes are allowed.
|
||||
var ValidateServiceAccountName = NameIsDNSSubdomain
|
||||
var ValidateServiceAccountName = genericvalidation.ValidateServiceAccountName
|
||||
|
||||
// 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
|
||||
|
@ -293,39 +243,27 @@ var ValidateServiceAccountName = NameIsDNSSubdomain
|
|||
var ValidateEndpointsName = NameIsDNSSubdomain
|
||||
|
||||
// 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.
|
||||
func NameIsDNSSubdomain(name string, prefix bool) []string {
|
||||
if prefix {
|
||||
name = maskTrailingDash(name)
|
||||
}
|
||||
return validation.IsDNS1123Subdomain(name)
|
||||
return genericvalidation.NameIsDNSSubdomain(name, prefix)
|
||||
}
|
||||
|
||||
// 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)
|
||||
return genericvalidation.NameIsDNSLabel(name, prefix)
|
||||
}
|
||||
|
||||
// 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)
|
||||
return genericvalidation.NameIsDNS1035Label(name, prefix)
|
||||
}
|
||||
|
||||
// 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
|
||||
return genericvalidation.ValidateNonnegativeField(value, fldPath)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
allErrs := field.ErrorList{}
|
||||
if !api.Semantic.DeepEqual(oldVal, newVal) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, newVal, fieldImmutableErrorMsg))
|
||||
}
|
||||
return allErrs
|
||||
return genericvalidation.ValidateImmutableField(newVal, oldVal, fldPath)
|
||||
}
|
||||
|
||||
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.
|
||||
// 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 {
|
||||
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, 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
|
||||
return genericvalidation.ValidateObjectMeta(meta, requiresNamespace, genericvalidation.ValidateNameFunc(nameFn), fldPath)
|
||||
}
|
||||
|
||||
// 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, 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
|
||||
return genericvalidation.ValidateObjectMetaUpdate(newMeta, oldMeta, fldPath)
|
||||
}
|
||||
|
||||
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
|
||||
return genericvalidation.ValidateNoNewFinalizers(newFinalizers, oldFinalizers, fldPath)
|
||||
}
|
||||
|
||||
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
|
||||
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{}
|
||||
return genericvalidation.ValidateFinalizerName(stringValue, fldPath)
|
||||
}
|
||||
|
||||
// ValidateNamespaceUpdate tests to make sure a namespace update can be applied.
|
||||
|
|
|
@ -17,11 +17,9 @@ limitations under the License.
|
|||
package validation
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"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 {
|
||||
objMeta := api.ObjectMeta{Name: name}
|
||||
if namespace != "" {
|
||||
|
|
|
@ -18,7 +18,7 @@ go_library(
|
|||
deps = [
|
||||
"//pkg/api: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",
|
||||
"//vendor:github.com/dgrijalva/jwt-go",
|
||||
"//vendor:github.com/golang/glog",
|
||||
|
|
|
@ -23,7 +23,7 @@ import (
|
|||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/api/validation"
|
||||
"k8s.io/kubernetes/pkg/api/validation/genericvalidation"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -53,10 +53,10 @@ func SplitUsername(username string) (string, string, error) {
|
|||
return "", "", invalidUsernameErr
|
||||
}
|
||||
namespace, name := parts[0], parts[1]
|
||||
if len(validation.ValidateNamespaceName(namespace, false)) != 0 {
|
||||
if len(genericvalidation.ValidateNamespaceName(namespace, false)) != 0 {
|
||||
return "", "", invalidUsernameErr
|
||||
}
|
||||
if len(validation.ValidateServiceAccountName(name, false)) != 0 {
|
||||
if len(genericvalidation.ValidateServiceAccountName(name, false)) != 0 {
|
||||
return "", "", invalidUsernameErr
|
||||
}
|
||||
return namespace, name, nil
|
||||
|
|
Loading…
Reference in New Issue