mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 10:33:35 +00:00
vendor files
This commit is contained in:
38
vendor/k8s.io/apimachinery/pkg/util/validation/BUILD
generated
vendored
Normal file
38
vendor/k8s.io/apimachinery/pkg/util/validation/BUILD
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["validation_test.go"],
|
||||
importpath = "k8s.io/apimachinery/pkg/util/validation",
|
||||
library = ":go_default_library",
|
||||
deps = ["//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["validation.go"],
|
||||
importpath = "k8s.io/apimachinery/pkg/util/validation",
|
||||
deps = ["//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
43
vendor/k8s.io/apimachinery/pkg/util/validation/field/BUILD
generated
vendored
Normal file
43
vendor/k8s.io/apimachinery/pkg/util/validation/field/BUILD
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"errors_test.go",
|
||||
"path_test.go",
|
||||
],
|
||||
importpath = "k8s.io/apimachinery/pkg/util/validation/field",
|
||||
library = ":go_default_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"errors.go",
|
||||
"path.go",
|
||||
],
|
||||
importpath = "k8s.io/apimachinery/pkg/util/validation/field",
|
||||
deps = [
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
259
vendor/k8s.io/apimachinery/pkg/util/validation/field/errors.go
generated
vendored
Normal file
259
vendor/k8s.io/apimachinery/pkg/util/validation/field/errors.go
generated
vendored
Normal file
@ -0,0 +1,259 @@
|
||||
/*
|
||||
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 field
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// Error is an implementation of the 'error' interface, which represents a
|
||||
// field-level validation error.
|
||||
type Error struct {
|
||||
Type ErrorType
|
||||
Field string
|
||||
BadValue interface{}
|
||||
Detail string
|
||||
}
|
||||
|
||||
var _ error = &Error{}
|
||||
|
||||
// Error implements the error interface.
|
||||
func (v *Error) Error() string {
|
||||
return fmt.Sprintf("%s: %s", v.Field, v.ErrorBody())
|
||||
}
|
||||
|
||||
// ErrorBody returns the error message without the field name. This is useful
|
||||
// for building nice-looking higher-level error reporting.
|
||||
func (v *Error) ErrorBody() string {
|
||||
var s string
|
||||
switch v.Type {
|
||||
case ErrorTypeRequired, ErrorTypeForbidden, ErrorTypeTooLong, ErrorTypeInternal:
|
||||
s = fmt.Sprintf("%s", v.Type)
|
||||
default:
|
||||
value := v.BadValue
|
||||
valueType := reflect.TypeOf(value)
|
||||
if value == nil || valueType == nil {
|
||||
value = "null"
|
||||
} else if valueType.Kind() == reflect.Ptr {
|
||||
if reflectValue := reflect.ValueOf(value); reflectValue.IsNil() {
|
||||
value = "null"
|
||||
} else {
|
||||
value = reflectValue.Elem().Interface()
|
||||
}
|
||||
}
|
||||
switch t := value.(type) {
|
||||
case int64, int32, float64, float32, bool:
|
||||
// use simple printer for simple types
|
||||
s = fmt.Sprintf("%s: %v", v.Type, value)
|
||||
case string:
|
||||
s = fmt.Sprintf("%s: %q", v.Type, t)
|
||||
case fmt.Stringer:
|
||||
// anything that defines String() is better than raw struct
|
||||
s = fmt.Sprintf("%s: %s", v.Type, t.String())
|
||||
default:
|
||||
// fallback to raw struct
|
||||
// TODO: internal types have panic guards against json.Marshalling to prevent
|
||||
// accidental use of internal types in external serialized form. For now, use
|
||||
// %#v, although it would be better to show a more expressive output in the future
|
||||
s = fmt.Sprintf("%s: %#v", v.Type, value)
|
||||
}
|
||||
}
|
||||
if len(v.Detail) != 0 {
|
||||
s += fmt.Sprintf(": %s", v.Detail)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// ErrorType is a machine readable value providing more detail about why
|
||||
// a field is invalid. These values are expected to match 1-1 with
|
||||
// CauseType in api/types.go.
|
||||
type ErrorType string
|
||||
|
||||
// TODO: These values are duplicated in api/types.go, but there's a circular dep. Fix it.
|
||||
const (
|
||||
// ErrorTypeNotFound is used to report failure to find a requested value
|
||||
// (e.g. looking up an ID). See NotFound().
|
||||
ErrorTypeNotFound ErrorType = "FieldValueNotFound"
|
||||
// ErrorTypeRequired is used to report required values that are not
|
||||
// provided (e.g. empty strings, null values, or empty arrays). See
|
||||
// Required().
|
||||
ErrorTypeRequired ErrorType = "FieldValueRequired"
|
||||
// ErrorTypeDuplicate is used to report collisions of values that must be
|
||||
// unique (e.g. unique IDs). See Duplicate().
|
||||
ErrorTypeDuplicate ErrorType = "FieldValueDuplicate"
|
||||
// ErrorTypeInvalid is used to report malformed values (e.g. failed regex
|
||||
// match, too long, out of bounds). See Invalid().
|
||||
ErrorTypeInvalid ErrorType = "FieldValueInvalid"
|
||||
// ErrorTypeNotSupported is used to report unknown values for enumerated
|
||||
// fields (e.g. a list of valid values). See NotSupported().
|
||||
ErrorTypeNotSupported ErrorType = "FieldValueNotSupported"
|
||||
// ErrorTypeForbidden is used to report valid (as per formatting rules)
|
||||
// values which would be accepted under some conditions, but which are not
|
||||
// permitted by the current conditions (such as security policy). See
|
||||
// Forbidden().
|
||||
ErrorTypeForbidden ErrorType = "FieldValueForbidden"
|
||||
// ErrorTypeTooLong is used to report that the given value is too long.
|
||||
// This is similar to ErrorTypeInvalid, but the error will not include the
|
||||
// too-long value. See TooLong().
|
||||
ErrorTypeTooLong ErrorType = "FieldValueTooLong"
|
||||
// ErrorTypeInternal is used to report other errors that are not related
|
||||
// to user input. See InternalError().
|
||||
ErrorTypeInternal ErrorType = "InternalError"
|
||||
)
|
||||
|
||||
// String converts a ErrorType into its corresponding canonical error message.
|
||||
func (t ErrorType) String() string {
|
||||
switch t {
|
||||
case ErrorTypeNotFound:
|
||||
return "Not found"
|
||||
case ErrorTypeRequired:
|
||||
return "Required value"
|
||||
case ErrorTypeDuplicate:
|
||||
return "Duplicate value"
|
||||
case ErrorTypeInvalid:
|
||||
return "Invalid value"
|
||||
case ErrorTypeNotSupported:
|
||||
return "Unsupported value"
|
||||
case ErrorTypeForbidden:
|
||||
return "Forbidden"
|
||||
case ErrorTypeTooLong:
|
||||
return "Too long"
|
||||
case ErrorTypeInternal:
|
||||
return "Internal error"
|
||||
default:
|
||||
panic(fmt.Sprintf("unrecognized validation error: %q", string(t)))
|
||||
}
|
||||
}
|
||||
|
||||
// NotFound returns a *Error indicating "value not found". This is
|
||||
// used to report failure to find a requested value (e.g. looking up an ID).
|
||||
func NotFound(field *Path, value interface{}) *Error {
|
||||
return &Error{ErrorTypeNotFound, field.String(), value, ""}
|
||||
}
|
||||
|
||||
// Required returns a *Error indicating "value required". This is used
|
||||
// to report required values that are not provided (e.g. empty strings, null
|
||||
// values, or empty arrays).
|
||||
func Required(field *Path, detail string) *Error {
|
||||
return &Error{ErrorTypeRequired, field.String(), "", detail}
|
||||
}
|
||||
|
||||
// Duplicate returns a *Error indicating "duplicate value". This is
|
||||
// used to report collisions of values that must be unique (e.g. names or IDs).
|
||||
func Duplicate(field *Path, value interface{}) *Error {
|
||||
return &Error{ErrorTypeDuplicate, field.String(), value, ""}
|
||||
}
|
||||
|
||||
// Invalid returns a *Error indicating "invalid value". This is used
|
||||
// to report malformed values (e.g. failed regex match, too long, out of bounds).
|
||||
func Invalid(field *Path, value interface{}, detail string) *Error {
|
||||
return &Error{ErrorTypeInvalid, field.String(), value, detail}
|
||||
}
|
||||
|
||||
// NotSupported returns a *Error indicating "unsupported value".
|
||||
// This is used to report unknown values for enumerated fields (e.g. a list of
|
||||
// valid values).
|
||||
func NotSupported(field *Path, value interface{}, validValues []string) *Error {
|
||||
detail := ""
|
||||
if validValues != nil && len(validValues) > 0 {
|
||||
quotedValues := make([]string, len(validValues))
|
||||
for i, v := range validValues {
|
||||
quotedValues[i] = strconv.Quote(v)
|
||||
}
|
||||
detail = "supported values: " + strings.Join(quotedValues, ", ")
|
||||
}
|
||||
return &Error{ErrorTypeNotSupported, field.String(), value, detail}
|
||||
}
|
||||
|
||||
// Forbidden returns a *Error indicating "forbidden". This is used to
|
||||
// report valid (as per formatting rules) values which would be accepted under
|
||||
// some conditions, but which are not permitted by current conditions (e.g.
|
||||
// security policy).
|
||||
func Forbidden(field *Path, detail string) *Error {
|
||||
return &Error{ErrorTypeForbidden, field.String(), "", detail}
|
||||
}
|
||||
|
||||
// TooLong returns a *Error indicating "too long". This is used to
|
||||
// report that the given value is too long. This is similar to
|
||||
// Invalid, but the returned error will not include the too-long
|
||||
// value.
|
||||
func TooLong(field *Path, value interface{}, maxLength int) *Error {
|
||||
return &Error{ErrorTypeTooLong, field.String(), value, fmt.Sprintf("must have at most %d characters", maxLength)}
|
||||
}
|
||||
|
||||
// InternalError returns a *Error indicating "internal error". This is used
|
||||
// to signal that an error was found that was not directly related to user
|
||||
// input. The err argument must be non-nil.
|
||||
func InternalError(field *Path, err error) *Error {
|
||||
return &Error{ErrorTypeInternal, field.String(), nil, err.Error()}
|
||||
}
|
||||
|
||||
// ErrorList holds a set of Errors. It is plausible that we might one day have
|
||||
// non-field errors in this same umbrella package, but for now we don't, so
|
||||
// we can keep it simple and leave ErrorList here.
|
||||
type ErrorList []*Error
|
||||
|
||||
// NewErrorTypeMatcher returns an errors.Matcher that returns true
|
||||
// if the provided error is a Error and has the provided ErrorType.
|
||||
func NewErrorTypeMatcher(t ErrorType) utilerrors.Matcher {
|
||||
return func(err error) bool {
|
||||
if e, ok := err.(*Error); ok {
|
||||
return e.Type == t
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// ToAggregate converts the ErrorList into an errors.Aggregate.
|
||||
func (list ErrorList) ToAggregate() utilerrors.Aggregate {
|
||||
errs := make([]error, 0, len(list))
|
||||
errorMsgs := sets.NewString()
|
||||
for _, err := range list {
|
||||
msg := fmt.Sprintf("%v", err)
|
||||
if errorMsgs.Has(msg) {
|
||||
continue
|
||||
}
|
||||
errorMsgs.Insert(msg)
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func fromAggregate(agg utilerrors.Aggregate) ErrorList {
|
||||
errs := agg.Errors()
|
||||
list := make(ErrorList, len(errs))
|
||||
for i := range errs {
|
||||
list[i] = errs[i].(*Error)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// Filter removes items from the ErrorList that match the provided fns.
|
||||
func (list ErrorList) Filter(fns ...utilerrors.Matcher) ErrorList {
|
||||
err := utilerrors.FilterOut(list.ToAggregate(), fns...)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
// FilterOut takes an Aggregate and returns an Aggregate
|
||||
return fromAggregate(err.(utilerrors.Aggregate))
|
||||
}
|
175
vendor/k8s.io/apimachinery/pkg/util/validation/field/errors_test.go
generated
vendored
Normal file
175
vendor/k8s.io/apimachinery/pkg/util/validation/field/errors_test.go
generated
vendored
Normal file
@ -0,0 +1,175 @@
|
||||
/*
|
||||
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 field
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMakeFuncs(t *testing.T) {
|
||||
testCases := []struct {
|
||||
fn func() *Error
|
||||
expected ErrorType
|
||||
}{
|
||||
{
|
||||
func() *Error { return Invalid(NewPath("f"), "v", "d") },
|
||||
ErrorTypeInvalid,
|
||||
},
|
||||
{
|
||||
func() *Error { return NotSupported(NewPath("f"), "v", nil) },
|
||||
ErrorTypeNotSupported,
|
||||
},
|
||||
{
|
||||
func() *Error { return Duplicate(NewPath("f"), "v") },
|
||||
ErrorTypeDuplicate,
|
||||
},
|
||||
{
|
||||
func() *Error { return NotFound(NewPath("f"), "v") },
|
||||
ErrorTypeNotFound,
|
||||
},
|
||||
{
|
||||
func() *Error { return Required(NewPath("f"), "d") },
|
||||
ErrorTypeRequired,
|
||||
},
|
||||
{
|
||||
func() *Error { return InternalError(NewPath("f"), fmt.Errorf("e")) },
|
||||
ErrorTypeInternal,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
err := testCase.fn()
|
||||
if err.Type != testCase.expected {
|
||||
t.Errorf("expected Type %q, got %q", testCase.expected, err.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorUsefulMessage(t *testing.T) {
|
||||
{
|
||||
s := Invalid(nil, nil, "").Error()
|
||||
t.Logf("message: %v", s)
|
||||
if !strings.Contains(s, "null") {
|
||||
t.Errorf("error message did not contain 'null': %s", s)
|
||||
}
|
||||
}
|
||||
|
||||
s := Invalid(NewPath("foo"), "bar", "deet").Error()
|
||||
t.Logf("message: %v", s)
|
||||
for _, part := range []string{"foo", "bar", "deet", ErrorTypeInvalid.String()} {
|
||||
if !strings.Contains(s, part) {
|
||||
t.Errorf("error message did not contain expected part '%v'", part)
|
||||
}
|
||||
}
|
||||
|
||||
type complicated struct {
|
||||
Baz int
|
||||
Qux string
|
||||
Inner interface{}
|
||||
KV map[string]int
|
||||
}
|
||||
s = Invalid(
|
||||
NewPath("foo"),
|
||||
&complicated{
|
||||
Baz: 1,
|
||||
Qux: "aoeu",
|
||||
Inner: &complicated{Qux: "asdf"},
|
||||
KV: map[string]int{"Billy": 2},
|
||||
},
|
||||
"detail",
|
||||
).Error()
|
||||
t.Logf("message: %v", s)
|
||||
for _, part := range []string{
|
||||
"foo", ErrorTypeInvalid.String(),
|
||||
"Baz", "Qux", "Inner", "KV", "detail",
|
||||
"1", "aoeu", "Billy", "2",
|
||||
// "asdf", TODO: reenable once we have a better nested printer
|
||||
} {
|
||||
if !strings.Contains(s, part) {
|
||||
t.Errorf("error message did not contain expected part '%v'", part)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestToAggregate(t *testing.T) {
|
||||
testCases := struct {
|
||||
ErrList []ErrorList
|
||||
NumExpectedErrs []int
|
||||
}{
|
||||
[]ErrorList{
|
||||
nil,
|
||||
{},
|
||||
{Invalid(NewPath("f"), "v", "d")},
|
||||
{Invalid(NewPath("f"), "v", "d"), Invalid(NewPath("f"), "v", "d")},
|
||||
{Invalid(NewPath("f"), "v", "d"), InternalError(NewPath(""), fmt.Errorf("e"))},
|
||||
},
|
||||
[]int{
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
},
|
||||
}
|
||||
|
||||
if len(testCases.ErrList) != len(testCases.NumExpectedErrs) {
|
||||
t.Errorf("Mismatch: length of NumExpectedErrs does not match length of ErrList")
|
||||
}
|
||||
for i, tc := range testCases.ErrList {
|
||||
agg := tc.ToAggregate()
|
||||
numErrs := 0
|
||||
|
||||
if agg != nil {
|
||||
numErrs = len(agg.Errors())
|
||||
}
|
||||
if numErrs != testCases.NumExpectedErrs[i] {
|
||||
t.Errorf("[%d] Expected %d, got %d", i, testCases.NumExpectedErrs[i], numErrs)
|
||||
}
|
||||
|
||||
if len(tc) == 0 {
|
||||
if agg != nil {
|
||||
t.Errorf("[%d] Expected nil, got %#v", i, agg)
|
||||
}
|
||||
} else if agg == nil {
|
||||
t.Errorf("[%d] Expected non-nil", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrListFilter(t *testing.T) {
|
||||
list := ErrorList{
|
||||
Invalid(NewPath("test.field"), "", ""),
|
||||
Invalid(NewPath("field.test"), "", ""),
|
||||
Duplicate(NewPath("test"), "value"),
|
||||
}
|
||||
if len(list.Filter(NewErrorTypeMatcher(ErrorTypeDuplicate))) != 2 {
|
||||
t.Errorf("should not filter")
|
||||
}
|
||||
if len(list.Filter(NewErrorTypeMatcher(ErrorTypeInvalid))) != 1 {
|
||||
t.Errorf("should filter")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotSupported(t *testing.T) {
|
||||
notSupported := NotSupported(NewPath("f"), "v", []string{"a", "b", "c"})
|
||||
expected := `Unsupported value: "v": supported values: "a", "b", "c"`
|
||||
if notSupported.ErrorBody() != expected {
|
||||
t.Errorf("Expected: %s\n, but got: %s\n", expected, notSupported.ErrorBody())
|
||||
}
|
||||
}
|
91
vendor/k8s.io/apimachinery/pkg/util/validation/field/path.go
generated
vendored
Normal file
91
vendor/k8s.io/apimachinery/pkg/util/validation/field/path.go
generated
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
Copyright 2015 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 field
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Path represents the path from some root to a particular field.
|
||||
type Path struct {
|
||||
name string // the name of this field or "" if this is an index
|
||||
index string // if name == "", this is a subscript (index or map key) of the previous element
|
||||
parent *Path // nil if this is the root element
|
||||
}
|
||||
|
||||
// NewPath creates a root Path object.
|
||||
func NewPath(name string, moreNames ...string) *Path {
|
||||
r := &Path{name: name, parent: nil}
|
||||
for _, anotherName := range moreNames {
|
||||
r = &Path{name: anotherName, parent: r}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Root returns the root element of this Path.
|
||||
func (p *Path) Root() *Path {
|
||||
for ; p.parent != nil; p = p.parent {
|
||||
// Do nothing.
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Child creates a new Path that is a child of the method receiver.
|
||||
func (p *Path) Child(name string, moreNames ...string) *Path {
|
||||
r := NewPath(name, moreNames...)
|
||||
r.Root().parent = p
|
||||
return r
|
||||
}
|
||||
|
||||
// Index indicates that the previous Path is to be subscripted by an int.
|
||||
// This sets the same underlying value as Key.
|
||||
func (p *Path) Index(index int) *Path {
|
||||
return &Path{index: strconv.Itoa(index), parent: p}
|
||||
}
|
||||
|
||||
// Key indicates that the previous Path is to be subscripted by a string.
|
||||
// This sets the same underlying value as Index.
|
||||
func (p *Path) Key(key string) *Path {
|
||||
return &Path{index: key, parent: p}
|
||||
}
|
||||
|
||||
// String produces a string representation of the Path.
|
||||
func (p *Path) String() string {
|
||||
// make a slice to iterate
|
||||
elems := []*Path{}
|
||||
for ; p != nil; p = p.parent {
|
||||
elems = append(elems, p)
|
||||
}
|
||||
|
||||
// iterate, but it has to be backwards
|
||||
buf := bytes.NewBuffer(nil)
|
||||
for i := range elems {
|
||||
p := elems[len(elems)-1-i]
|
||||
if p.parent != nil && len(p.name) > 0 {
|
||||
// This is either the root or it is a subscript.
|
||||
buf.WriteString(".")
|
||||
}
|
||||
if len(p.name) > 0 {
|
||||
buf.WriteString(p.name)
|
||||
} else {
|
||||
fmt.Fprintf(buf, "[%s]", p.index)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
123
vendor/k8s.io/apimachinery/pkg/util/validation/field/path_test.go
generated
vendored
Normal file
123
vendor/k8s.io/apimachinery/pkg/util/validation/field/path_test.go
generated
vendored
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
Copyright 2015 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 field
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestPath(t *testing.T) {
|
||||
testCases := []struct {
|
||||
op func(*Path) *Path
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
func(p *Path) *Path { return p },
|
||||
"root",
|
||||
},
|
||||
{
|
||||
func(p *Path) *Path { return p.Child("first") },
|
||||
"root.first",
|
||||
},
|
||||
{
|
||||
func(p *Path) *Path { return p.Child("second") },
|
||||
"root.first.second",
|
||||
},
|
||||
{
|
||||
func(p *Path) *Path { return p.Index(0) },
|
||||
"root.first.second[0]",
|
||||
},
|
||||
{
|
||||
func(p *Path) *Path { return p.Child("third") },
|
||||
"root.first.second[0].third",
|
||||
},
|
||||
{
|
||||
func(p *Path) *Path { return p.Index(93) },
|
||||
"root.first.second[0].third[93]",
|
||||
},
|
||||
{
|
||||
func(p *Path) *Path { return p.parent },
|
||||
"root.first.second[0].third",
|
||||
},
|
||||
{
|
||||
func(p *Path) *Path { return p.parent },
|
||||
"root.first.second[0]",
|
||||
},
|
||||
{
|
||||
func(p *Path) *Path { return p.Key("key") },
|
||||
"root.first.second[0][key]",
|
||||
},
|
||||
}
|
||||
|
||||
root := NewPath("root")
|
||||
p := root
|
||||
for i, tc := range testCases {
|
||||
p = tc.op(p)
|
||||
if p.String() != tc.expected {
|
||||
t.Errorf("[%d] Expected %q, got %q", i, tc.expected, p.String())
|
||||
}
|
||||
if p.Root() != root {
|
||||
t.Errorf("[%d] Wrong root: %#v", i, p.Root())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathMultiArg(t *testing.T) {
|
||||
testCases := []struct {
|
||||
op func(*Path) *Path
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
func(p *Path) *Path { return p },
|
||||
"root.first",
|
||||
},
|
||||
{
|
||||
func(p *Path) *Path { return p.Child("second", "third") },
|
||||
"root.first.second.third",
|
||||
},
|
||||
{
|
||||
func(p *Path) *Path { return p.Index(0) },
|
||||
"root.first.second.third[0]",
|
||||
},
|
||||
{
|
||||
func(p *Path) *Path { return p.parent },
|
||||
"root.first.second.third",
|
||||
},
|
||||
{
|
||||
func(p *Path) *Path { return p.parent },
|
||||
"root.first.second",
|
||||
},
|
||||
{
|
||||
func(p *Path) *Path { return p.parent },
|
||||
"root.first",
|
||||
},
|
||||
{
|
||||
func(p *Path) *Path { return p.parent },
|
||||
"root",
|
||||
},
|
||||
}
|
||||
|
||||
root := NewPath("root", "first")
|
||||
p := root
|
||||
for i, tc := range testCases {
|
||||
p = tc.op(p)
|
||||
if p.String() != tc.expected {
|
||||
t.Errorf("[%d] Expected %q, got %q", i, tc.expected, p.String())
|
||||
}
|
||||
if p.Root() != root.Root() {
|
||||
t.Errorf("[%d] Wrong root: %#v", i, p.Root())
|
||||
}
|
||||
}
|
||||
}
|
391
vendor/k8s.io/apimachinery/pkg/util/validation/validation.go
generated
vendored
Normal file
391
vendor/k8s.io/apimachinery/pkg/util/validation/validation.go
generated
vendored
Normal file
@ -0,0 +1,391 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package validation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
const qnameCharFmt string = "[A-Za-z0-9]"
|
||||
const qnameExtCharFmt string = "[-A-Za-z0-9_.]"
|
||||
const qualifiedNameFmt string = "(" + qnameCharFmt + qnameExtCharFmt + "*)?" + qnameCharFmt
|
||||
const qualifiedNameErrMsg string = "must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
|
||||
const qualifiedNameMaxLength int = 63
|
||||
|
||||
var qualifiedNameRegexp = regexp.MustCompile("^" + qualifiedNameFmt + "$")
|
||||
|
||||
// IsQualifiedName tests whether the value passed is what Kubernetes calls a
|
||||
// "qualified name". This is a format used in various places throughout the
|
||||
// system. If the value is not valid, a list of error strings is returned.
|
||||
// Otherwise an empty list (or nil) is returned.
|
||||
func IsQualifiedName(value string) []string {
|
||||
var errs []string
|
||||
parts := strings.Split(value, "/")
|
||||
var name string
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
name = parts[0]
|
||||
case 2:
|
||||
var prefix string
|
||||
prefix, name = parts[0], parts[1]
|
||||
if len(prefix) == 0 {
|
||||
errs = append(errs, "prefix part "+EmptyError())
|
||||
} else if msgs := IsDNS1123Subdomain(prefix); len(msgs) != 0 {
|
||||
errs = append(errs, prefixEach(msgs, "prefix part ")...)
|
||||
}
|
||||
default:
|
||||
return append(errs, "a qualified name "+RegexError(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc")+
|
||||
" with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')")
|
||||
}
|
||||
|
||||
if len(name) == 0 {
|
||||
errs = append(errs, "name part "+EmptyError())
|
||||
} else if len(name) > qualifiedNameMaxLength {
|
||||
errs = append(errs, "name part "+MaxLenError(qualifiedNameMaxLength))
|
||||
}
|
||||
if !qualifiedNameRegexp.MatchString(name) {
|
||||
errs = append(errs, "name part "+RegexError(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc"))
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// IsFullyQualifiedName checks if the name is fully qualified.
|
||||
func IsFullyQualifiedName(fldPath *field.Path, name string) field.ErrorList {
|
||||
var allErrors field.ErrorList
|
||||
if len(name) == 0 {
|
||||
return append(allErrors, field.Required(fldPath, ""))
|
||||
}
|
||||
if errs := IsDNS1123Subdomain(name); len(errs) > 0 {
|
||||
return append(allErrors, field.Invalid(fldPath, name, strings.Join(errs, ",")))
|
||||
}
|
||||
if len(strings.Split(name, ".")) < 3 {
|
||||
return append(allErrors, field.Invalid(fldPath, name, "should be a domain with at least three segments separated by dots"))
|
||||
}
|
||||
return allErrors
|
||||
}
|
||||
|
||||
const labelValueFmt string = "(" + qualifiedNameFmt + ")?"
|
||||
const labelValueErrMsg string = "a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
|
||||
const LabelValueMaxLength int = 63
|
||||
|
||||
var labelValueRegexp = regexp.MustCompile("^" + labelValueFmt + "$")
|
||||
|
||||
// IsValidLabelValue tests whether the value passed is a valid label value. If
|
||||
// the value is not valid, a list of error strings is returned. Otherwise an
|
||||
// empty list (or nil) is returned.
|
||||
func IsValidLabelValue(value string) []string {
|
||||
var errs []string
|
||||
if len(value) > LabelValueMaxLength {
|
||||
errs = append(errs, MaxLenError(LabelValueMaxLength))
|
||||
}
|
||||
if !labelValueRegexp.MatchString(value) {
|
||||
errs = append(errs, RegexError(labelValueErrMsg, labelValueFmt, "MyValue", "my_value", "12345"))
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
const dns1123LabelFmt string = "[a-z0-9]([-a-z0-9]*[a-z0-9])?"
|
||||
const dns1123LabelErrMsg string = "a DNS-1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character"
|
||||
const DNS1123LabelMaxLength int = 63
|
||||
|
||||
var dns1123LabelRegexp = regexp.MustCompile("^" + dns1123LabelFmt + "$")
|
||||
|
||||
// IsDNS1123Label tests for a string that conforms to the definition of a label in
|
||||
// DNS (RFC 1123).
|
||||
func IsDNS1123Label(value string) []string {
|
||||
var errs []string
|
||||
if len(value) > DNS1123LabelMaxLength {
|
||||
errs = append(errs, MaxLenError(DNS1123LabelMaxLength))
|
||||
}
|
||||
if !dns1123LabelRegexp.MatchString(value) {
|
||||
errs = append(errs, RegexError(dns1123LabelErrMsg, dns1123LabelFmt, "my-name", "123-abc"))
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
const dns1123SubdomainFmt string = dns1123LabelFmt + "(\\." + dns1123LabelFmt + ")*"
|
||||
const dns1123SubdomainErrorMsg string = "a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"
|
||||
const DNS1123SubdomainMaxLength int = 253
|
||||
|
||||
var dns1123SubdomainRegexp = regexp.MustCompile("^" + dns1123SubdomainFmt + "$")
|
||||
|
||||
// IsDNS1123Subdomain tests for a string that conforms to the definition of a
|
||||
// subdomain in DNS (RFC 1123).
|
||||
func IsDNS1123Subdomain(value string) []string {
|
||||
var errs []string
|
||||
if len(value) > DNS1123SubdomainMaxLength {
|
||||
errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
|
||||
}
|
||||
if !dns1123SubdomainRegexp.MatchString(value) {
|
||||
errs = append(errs, RegexError(dns1123SubdomainErrorMsg, dns1123SubdomainFmt, "example.com"))
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
const dns1035LabelFmt string = "[a-z]([-a-z0-9]*[a-z0-9])?"
|
||||
const dns1035LabelErrMsg string = "a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character"
|
||||
const DNS1035LabelMaxLength int = 63
|
||||
|
||||
var dns1035LabelRegexp = regexp.MustCompile("^" + dns1035LabelFmt + "$")
|
||||
|
||||
// IsDNS1035Label tests for a string that conforms to the definition of a label in
|
||||
// DNS (RFC 1035).
|
||||
func IsDNS1035Label(value string) []string {
|
||||
var errs []string
|
||||
if len(value) > DNS1035LabelMaxLength {
|
||||
errs = append(errs, MaxLenError(DNS1035LabelMaxLength))
|
||||
}
|
||||
if !dns1035LabelRegexp.MatchString(value) {
|
||||
errs = append(errs, RegexError(dns1035LabelErrMsg, dns1035LabelFmt, "my-name", "abc-123"))
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// wildcard definition - RFC 1034 section 4.3.3.
|
||||
// examples:
|
||||
// - valid: *.bar.com, *.foo.bar.com
|
||||
// - invalid: *.*.bar.com, *.foo.*.com, *bar.com, f*.bar.com, *
|
||||
const wildcardDNS1123SubdomainFmt = "\\*\\." + dns1123SubdomainFmt
|
||||
const wildcardDNS1123SubdomainErrMsg = "a wildcard DNS-1123 subdomain must start with '*.', followed by a valid DNS subdomain, which must consist of lower case alphanumeric characters, '-' or '.' and end with an alphanumeric character"
|
||||
|
||||
// IsWildcardDNS1123Subdomain tests for a string that conforms to the definition of a
|
||||
// wildcard subdomain in DNS (RFC 1034 section 4.3.3).
|
||||
func IsWildcardDNS1123Subdomain(value string) []string {
|
||||
wildcardDNS1123SubdomainRegexp := regexp.MustCompile("^" + wildcardDNS1123SubdomainFmt + "$")
|
||||
|
||||
var errs []string
|
||||
if len(value) > DNS1123SubdomainMaxLength {
|
||||
errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
|
||||
}
|
||||
if !wildcardDNS1123SubdomainRegexp.MatchString(value) {
|
||||
errs = append(errs, RegexError(wildcardDNS1123SubdomainErrMsg, wildcardDNS1123SubdomainFmt, "*.example.com"))
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
const cIdentifierFmt string = "[A-Za-z_][A-Za-z0-9_]*"
|
||||
const identifierErrMsg string = "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_'"
|
||||
|
||||
var cIdentifierRegexp = regexp.MustCompile("^" + cIdentifierFmt + "$")
|
||||
|
||||
// IsCIdentifier tests for a string that conforms the definition of an identifier
|
||||
// in C. This checks the format, but not the length.
|
||||
func IsCIdentifier(value string) []string {
|
||||
if !cIdentifierRegexp.MatchString(value) {
|
||||
return []string{RegexError(identifierErrMsg, cIdentifierFmt, "my_name", "MY_NAME", "MyName")}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsValidPortNum tests that the argument is a valid, non-zero port number.
|
||||
func IsValidPortNum(port int) []string {
|
||||
if 1 <= port && port <= 65535 {
|
||||
return nil
|
||||
}
|
||||
return []string{InclusiveRangeError(1, 65535)}
|
||||
}
|
||||
|
||||
// IsInRange tests that the argument is in an inclusive range.
|
||||
func IsInRange(value int, min int, max int) []string {
|
||||
if value >= min && value <= max {
|
||||
return nil
|
||||
}
|
||||
return []string{InclusiveRangeError(min, max)}
|
||||
}
|
||||
|
||||
// Now in libcontainer UID/GID limits is 0 ~ 1<<31 - 1
|
||||
// TODO: once we have a type for UID/GID we should make these that type.
|
||||
const (
|
||||
minUserID = 0
|
||||
maxUserID = math.MaxInt32
|
||||
minGroupID = 0
|
||||
maxGroupID = math.MaxInt32
|
||||
)
|
||||
|
||||
// IsValidGroupID tests that the argument is a valid Unix GID.
|
||||
func IsValidGroupID(gid int64) []string {
|
||||
if minGroupID <= gid && gid <= maxGroupID {
|
||||
return nil
|
||||
}
|
||||
return []string{InclusiveRangeError(minGroupID, maxGroupID)}
|
||||
}
|
||||
|
||||
// IsValidUserID tests that the argument is a valid Unix UID.
|
||||
func IsValidUserID(uid int64) []string {
|
||||
if minUserID <= uid && uid <= maxUserID {
|
||||
return nil
|
||||
}
|
||||
return []string{InclusiveRangeError(minUserID, maxUserID)}
|
||||
}
|
||||
|
||||
var portNameCharsetRegex = regexp.MustCompile("^[-a-z0-9]+$")
|
||||
var portNameOneLetterRegexp = regexp.MustCompile("[a-z]")
|
||||
|
||||
// IsValidPortName check that the argument is valid syntax. It must be
|
||||
// non-empty and no more than 15 characters long. It may contain only [-a-z0-9]
|
||||
// and must contain at least one letter [a-z]. It must not start or end with a
|
||||
// hyphen, nor contain adjacent hyphens.
|
||||
//
|
||||
// Note: We only allow lower-case characters, even though RFC 6335 is case
|
||||
// insensitive.
|
||||
func IsValidPortName(port string) []string {
|
||||
var errs []string
|
||||
if len(port) > 15 {
|
||||
errs = append(errs, MaxLenError(15))
|
||||
}
|
||||
if !portNameCharsetRegex.MatchString(port) {
|
||||
errs = append(errs, "must contain only alpha-numeric characters (a-z, 0-9), and hyphens (-)")
|
||||
}
|
||||
if !portNameOneLetterRegexp.MatchString(port) {
|
||||
errs = append(errs, "must contain at least one letter or number (a-z, 0-9)")
|
||||
}
|
||||
if strings.Contains(port, "--") {
|
||||
errs = append(errs, "must not contain consecutive hyphens")
|
||||
}
|
||||
if len(port) > 0 && (port[0] == '-' || port[len(port)-1] == '-') {
|
||||
errs = append(errs, "must not begin or end with a hyphen")
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// IsValidIP tests that the argument is a valid IP address.
|
||||
func IsValidIP(value string) []string {
|
||||
if net.ParseIP(value) == nil {
|
||||
return []string{"must be a valid IP address, (e.g. 10.9.8.7)"}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const percentFmt string = "[0-9]+%"
|
||||
const percentErrMsg string = "a valid percent string must be a numeric string followed by an ending '%'"
|
||||
|
||||
var percentRegexp = regexp.MustCompile("^" + percentFmt + "$")
|
||||
|
||||
func IsValidPercent(percent string) []string {
|
||||
if !percentRegexp.MatchString(percent) {
|
||||
return []string{RegexError(percentErrMsg, percentFmt, "1%", "93%")}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const httpHeaderNameFmt string = "[-A-Za-z0-9]+"
|
||||
const httpHeaderNameErrMsg string = "a valid HTTP header must consist of alphanumeric characters or '-'"
|
||||
|
||||
var httpHeaderNameRegexp = regexp.MustCompile("^" + httpHeaderNameFmt + "$")
|
||||
|
||||
// IsHTTPHeaderName checks that a string conforms to the Go HTTP library's
|
||||
// definition of a valid header field name (a stricter subset than RFC7230).
|
||||
func IsHTTPHeaderName(value string) []string {
|
||||
if !httpHeaderNameRegexp.MatchString(value) {
|
||||
return []string{RegexError(httpHeaderNameErrMsg, httpHeaderNameFmt, "X-Header-Name")}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const envVarNameFmt = "[-._a-zA-Z][-._a-zA-Z0-9]*"
|
||||
const envVarNameFmtErrMsg string = "a valid environment variable name must consist of alphabetic characters, digits, '_', '-', or '.', and must not start with a digit"
|
||||
|
||||
var envVarNameRegexp = regexp.MustCompile("^" + envVarNameFmt + "$")
|
||||
|
||||
// IsEnvVarName tests if a string is a valid environment variable name.
|
||||
func IsEnvVarName(value string) []string {
|
||||
var errs []string
|
||||
if !envVarNameRegexp.MatchString(value) {
|
||||
errs = append(errs, RegexError(envVarNameFmtErrMsg, envVarNameFmt, "my.env-name", "MY_ENV.NAME", "MyEnvName1"))
|
||||
}
|
||||
|
||||
errs = append(errs, hasChDirPrefix(value)...)
|
||||
return errs
|
||||
}
|
||||
|
||||
const configMapKeyFmt = `[-._a-zA-Z0-9]+`
|
||||
const configMapKeyErrMsg string = "a valid config key must consist of alphanumeric characters, '-', '_' or '.'"
|
||||
|
||||
var configMapKeyRegexp = regexp.MustCompile("^" + configMapKeyFmt + "$")
|
||||
|
||||
// IsConfigMapKey tests for a string that is a valid key for a ConfigMap or Secret
|
||||
func IsConfigMapKey(value string) []string {
|
||||
var errs []string
|
||||
if len(value) > DNS1123SubdomainMaxLength {
|
||||
errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
|
||||
}
|
||||
if !configMapKeyRegexp.MatchString(value) {
|
||||
errs = append(errs, RegexError(configMapKeyErrMsg, configMapKeyFmt, "key.name", "KEY_NAME", "key-name"))
|
||||
}
|
||||
errs = append(errs, hasChDirPrefix(value)...)
|
||||
return errs
|
||||
}
|
||||
|
||||
// MaxLenError returns a string explanation of a "string too long" validation
|
||||
// failure.
|
||||
func MaxLenError(length int) string {
|
||||
return fmt.Sprintf("must be no more than %d characters", length)
|
||||
}
|
||||
|
||||
// RegexError returns a string explanation of a regex validation failure.
|
||||
func RegexError(msg string, fmt string, examples ...string) string {
|
||||
if len(examples) == 0 {
|
||||
return msg + " (regex used for validation is '" + fmt + "')"
|
||||
}
|
||||
msg += " (e.g. "
|
||||
for i := range examples {
|
||||
if i > 0 {
|
||||
msg += " or "
|
||||
}
|
||||
msg += "'" + examples[i] + "', "
|
||||
}
|
||||
msg += "regex used for validation is '" + fmt + "')"
|
||||
return msg
|
||||
}
|
||||
|
||||
// EmptyError returns a string explanation of a "must not be empty" validation
|
||||
// failure.
|
||||
func EmptyError() string {
|
||||
return "must be non-empty"
|
||||
}
|
||||
|
||||
func prefixEach(msgs []string, prefix string) []string {
|
||||
for i := range msgs {
|
||||
msgs[i] = prefix + msgs[i]
|
||||
}
|
||||
return msgs
|
||||
}
|
||||
|
||||
// InclusiveRangeError returns a string explanation of a numeric "must be
|
||||
// between" validation failure.
|
||||
func InclusiveRangeError(lo, hi int) string {
|
||||
return fmt.Sprintf(`must be between %d and %d, inclusive`, lo, hi)
|
||||
}
|
||||
|
||||
func hasChDirPrefix(value string) []string {
|
||||
var errs []string
|
||||
switch {
|
||||
case value == ".":
|
||||
errs = append(errs, `must not be '.'`)
|
||||
case value == "..":
|
||||
errs = append(errs, `must not be '..'`)
|
||||
case strings.HasPrefix(value, ".."):
|
||||
errs = append(errs, `must not start with '..'`)
|
||||
}
|
||||
return errs
|
||||
}
|
513
vendor/k8s.io/apimachinery/pkg/util/validation/validation_test.go
generated
vendored
Normal file
513
vendor/k8s.io/apimachinery/pkg/util/validation/validation_test.go
generated
vendored
Normal file
@ -0,0 +1,513 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package validation
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
func TestIsDNS1123Label(t *testing.T) {
|
||||
goodValues := []string{
|
||||
"a", "ab", "abc", "a1", "a-1", "a--1--2--b",
|
||||
"0", "01", "012", "1a", "1-a", "1--a--b--2",
|
||||
strings.Repeat("a", 63),
|
||||
}
|
||||
for _, val := range goodValues {
|
||||
if msgs := IsDNS1123Label(val); len(msgs) != 0 {
|
||||
t.Errorf("expected true for '%s': %v", val, msgs)
|
||||
}
|
||||
}
|
||||
|
||||
badValues := []string{
|
||||
"", "A", "ABC", "aBc", "A1", "A-1", "1-A",
|
||||
"-", "a-", "-a", "1-", "-1",
|
||||
"_", "a_", "_a", "a_b", "1_", "_1", "1_2",
|
||||
".", "a.", ".a", "a.b", "1.", ".1", "1.2",
|
||||
" ", "a ", " a", "a b", "1 ", " 1", "1 2",
|
||||
strings.Repeat("a", 64),
|
||||
}
|
||||
for _, val := range badValues {
|
||||
if msgs := IsDNS1123Label(val); len(msgs) == 0 {
|
||||
t.Errorf("expected false for '%s'", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDNS1123Subdomain(t *testing.T) {
|
||||
goodValues := []string{
|
||||
"a", "ab", "abc", "a1", "a-1", "a--1--2--b",
|
||||
"0", "01", "012", "1a", "1-a", "1--a--b--2",
|
||||
"a.a", "ab.a", "abc.a", "a1.a", "a-1.a", "a--1--2--b.a",
|
||||
"a.1", "ab.1", "abc.1", "a1.1", "a-1.1", "a--1--2--b.1",
|
||||
"0.a", "01.a", "012.a", "1a.a", "1-a.a", "1--a--b--2",
|
||||
"0.1", "01.1", "012.1", "1a.1", "1-a.1", "1--a--b--2.1",
|
||||
"a.b.c.d.e", "aa.bb.cc.dd.ee", "1.2.3.4.5", "11.22.33.44.55",
|
||||
strings.Repeat("a", 253),
|
||||
}
|
||||
for _, val := range goodValues {
|
||||
if msgs := IsDNS1123Subdomain(val); len(msgs) != 0 {
|
||||
t.Errorf("expected true for '%s': %v", val, msgs)
|
||||
}
|
||||
}
|
||||
|
||||
badValues := []string{
|
||||
"", "A", "ABC", "aBc", "A1", "A-1", "1-A",
|
||||
"-", "a-", "-a", "1-", "-1",
|
||||
"_", "a_", "_a", "a_b", "1_", "_1", "1_2",
|
||||
".", "a.", ".a", "a..b", "1.", ".1", "1..2",
|
||||
" ", "a ", " a", "a b", "1 ", " 1", "1 2",
|
||||
"A.a", "aB.a", "ab.A", "A1.a", "a1.A",
|
||||
"A.1", "aB.1", "A1.1", "1A.1",
|
||||
"0.A", "01.A", "012.A", "1A.a", "1a.A",
|
||||
"A.B.C.D.E", "AA.BB.CC.DD.EE", "a.B.c.d.e", "aa.bB.cc.dd.ee",
|
||||
"a@b", "a,b", "a_b", "a;b",
|
||||
"a:b", "a%b", "a?b", "a$b",
|
||||
strings.Repeat("a", 254),
|
||||
}
|
||||
for _, val := range badValues {
|
||||
if msgs := IsDNS1123Subdomain(val); len(msgs) == 0 {
|
||||
t.Errorf("expected false for '%s'", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDNS1035Label(t *testing.T) {
|
||||
goodValues := []string{
|
||||
"a", "ab", "abc", "a1", "a-1", "a--1--2--b",
|
||||
strings.Repeat("a", 63),
|
||||
}
|
||||
for _, val := range goodValues {
|
||||
if msgs := IsDNS1035Label(val); len(msgs) != 0 {
|
||||
t.Errorf("expected true for '%s': %v", val, msgs)
|
||||
}
|
||||
}
|
||||
|
||||
badValues := []string{
|
||||
"0", "01", "012", "1a", "1-a", "1--a--b--2",
|
||||
"", "A", "ABC", "aBc", "A1", "A-1", "1-A",
|
||||
"-", "a-", "-a", "1-", "-1",
|
||||
"_", "a_", "_a", "a_b", "1_", "_1", "1_2",
|
||||
".", "a.", ".a", "a.b", "1.", ".1", "1.2",
|
||||
" ", "a ", " a", "a b", "1 ", " 1", "1 2",
|
||||
strings.Repeat("a", 64),
|
||||
}
|
||||
for _, val := range badValues {
|
||||
if msgs := IsDNS1035Label(val); len(msgs) == 0 {
|
||||
t.Errorf("expected false for '%s'", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsCIdentifier(t *testing.T) {
|
||||
goodValues := []string{
|
||||
"a", "ab", "abc", "a1", "_a", "a_", "a_b", "a_1", "a__1__2__b", "__abc_123",
|
||||
"A", "AB", "AbC", "A1", "_A", "A_", "A_B", "A_1", "A__1__2__B", "__123_ABC",
|
||||
}
|
||||
for _, val := range goodValues {
|
||||
if msgs := IsCIdentifier(val); len(msgs) != 0 {
|
||||
t.Errorf("expected true for '%s': %v", val, msgs)
|
||||
}
|
||||
}
|
||||
|
||||
badValues := []string{
|
||||
"", "1", "123", "1a",
|
||||
"-", "a-", "-a", "1-", "-1", "1_", "1_2",
|
||||
".", "a.", ".a", "a.b", "1.", ".1", "1.2",
|
||||
" ", "a ", " a", "a b", "1 ", " 1", "1 2",
|
||||
"#a#",
|
||||
}
|
||||
for _, val := range badValues {
|
||||
if msgs := IsCIdentifier(val); len(msgs) == 0 {
|
||||
t.Errorf("expected false for '%s'", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidPortNum(t *testing.T) {
|
||||
goodValues := []int{1, 2, 1000, 16384, 32768, 65535}
|
||||
for _, val := range goodValues {
|
||||
if msgs := IsValidPortNum(val); len(msgs) != 0 {
|
||||
t.Errorf("expected true for %d, got %v", val, msgs)
|
||||
}
|
||||
}
|
||||
|
||||
badValues := []int{0, -1, 65536, 100000}
|
||||
for _, val := range badValues {
|
||||
if msgs := IsValidPortNum(val); len(msgs) == 0 {
|
||||
t.Errorf("expected false for %d", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsInRange(t *testing.T) {
|
||||
goodValues := []struct {
|
||||
value int
|
||||
min int
|
||||
max int
|
||||
}{{1, 0, 10}, {5, 5, 20}, {25, 10, 25}}
|
||||
for _, val := range goodValues {
|
||||
if msgs := IsInRange(val.value, val.min, val.max); len(msgs) > 0 {
|
||||
t.Errorf("expected no errors for %#v, but got %v", val, msgs)
|
||||
}
|
||||
}
|
||||
|
||||
badValues := []struct {
|
||||
value int
|
||||
min int
|
||||
max int
|
||||
}{{1, 2, 10}, {5, -4, 2}, {25, 100, 120}}
|
||||
for _, val := range badValues {
|
||||
if msgs := IsInRange(val.value, val.min, val.max); len(msgs) == 0 {
|
||||
t.Errorf("expected errors for %#v", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createGroupIDs(ids ...int64) []int64 {
|
||||
var output []int64
|
||||
for _, id := range ids {
|
||||
output = append(output, int64(id))
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
func createUserIDs(ids ...int64) []int64 {
|
||||
var output []int64
|
||||
for _, id := range ids {
|
||||
output = append(output, int64(id))
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
func TestIsValidGroupID(t *testing.T) {
|
||||
goodValues := createGroupIDs(0, 1, 1000, 65535, 2147483647)
|
||||
for _, val := range goodValues {
|
||||
if msgs := IsValidGroupID(val); len(msgs) != 0 {
|
||||
t.Errorf("expected true for '%d': %v", val, msgs)
|
||||
}
|
||||
}
|
||||
|
||||
badValues := createGroupIDs(-1, -1003, 2147483648, 4147483647)
|
||||
for _, val := range badValues {
|
||||
if msgs := IsValidGroupID(val); len(msgs) == 0 {
|
||||
t.Errorf("expected false for '%d'", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidUserID(t *testing.T) {
|
||||
goodValues := createUserIDs(0, 1, 1000, 65535, 2147483647)
|
||||
for _, val := range goodValues {
|
||||
if msgs := IsValidUserID(val); len(msgs) != 0 {
|
||||
t.Errorf("expected true for '%d': %v", val, msgs)
|
||||
}
|
||||
}
|
||||
|
||||
badValues := createUserIDs(-1, -1003, 2147483648, 4147483647)
|
||||
for _, val := range badValues {
|
||||
if msgs := IsValidUserID(val); len(msgs) == 0 {
|
||||
t.Errorf("expected false for '%d'", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidPortName(t *testing.T) {
|
||||
goodValues := []string{"telnet", "re-mail-ck", "pop3", "a", "a-1", "1-a", "a-1-b-2-c", "1-a-2-b-3"}
|
||||
for _, val := range goodValues {
|
||||
if msgs := IsValidPortName(val); len(msgs) != 0 {
|
||||
t.Errorf("expected true for %q: %v", val, msgs)
|
||||
}
|
||||
}
|
||||
|
||||
badValues := []string{"longerthan15characters", "", strings.Repeat("a", 16), "12345", "1-2-3-4", "-begin", "end-", "two--hyphens", "whois++"}
|
||||
for _, val := range badValues {
|
||||
if msgs := IsValidPortName(val); len(msgs) == 0 {
|
||||
t.Errorf("expected false for %q", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsQualifiedName(t *testing.T) {
|
||||
successCases := []string{
|
||||
"simple",
|
||||
"now-with-dashes",
|
||||
"1-starts-with-num",
|
||||
"1234",
|
||||
"simple/simple",
|
||||
"now-with-dashes/simple",
|
||||
"now-with-dashes/now-with-dashes",
|
||||
"now.with.dots/simple",
|
||||
"now-with.dashes-and.dots/simple",
|
||||
"1-num.2-num/3-num",
|
||||
"1234/5678",
|
||||
"1.2.3.4/5678",
|
||||
"Uppercase_Is_OK_123",
|
||||
"example.com/Uppercase_Is_OK_123",
|
||||
"requests.storage-foo",
|
||||
strings.Repeat("a", 63),
|
||||
strings.Repeat("a", 253) + "/" + strings.Repeat("b", 63),
|
||||
}
|
||||
for i := range successCases {
|
||||
if errs := IsQualifiedName(successCases[i]); len(errs) != 0 {
|
||||
t.Errorf("case[%d]: %q: expected success: %v", i, successCases[i], errs)
|
||||
}
|
||||
}
|
||||
|
||||
errorCases := []string{
|
||||
"nospecialchars%^=@",
|
||||
"cantendwithadash-",
|
||||
"-cantstartwithadash-",
|
||||
"only/one/slash",
|
||||
"Example.com/abc",
|
||||
"example_com/abc",
|
||||
"example.com/",
|
||||
"/simple",
|
||||
strings.Repeat("a", 64),
|
||||
strings.Repeat("a", 254) + "/abc",
|
||||
}
|
||||
for i := range errorCases {
|
||||
if errs := IsQualifiedName(errorCases[i]); len(errs) == 0 {
|
||||
t.Errorf("case[%d]: %q: expected failure", i, errorCases[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidLabelValue(t *testing.T) {
|
||||
successCases := []string{
|
||||
"simple",
|
||||
"now-with-dashes",
|
||||
"1-starts-with-num",
|
||||
"end-with-num-1",
|
||||
"1234", // only num
|
||||
strings.Repeat("a", 63), // to the limit
|
||||
"", // empty value
|
||||
}
|
||||
for i := range successCases {
|
||||
if errs := IsValidLabelValue(successCases[i]); len(errs) != 0 {
|
||||
t.Errorf("case %s expected success: %v", successCases[i], errs)
|
||||
}
|
||||
}
|
||||
|
||||
errorCases := []string{
|
||||
"nospecialchars%^=@",
|
||||
"Tama-nui-te-rā.is.Māori.sun",
|
||||
"\\backslashes\\are\\bad",
|
||||
"-starts-with-dash",
|
||||
"ends-with-dash-",
|
||||
".starts.with.dot",
|
||||
"ends.with.dot.",
|
||||
strings.Repeat("a", 64), // over the limit
|
||||
}
|
||||
for i := range errorCases {
|
||||
if errs := IsValidLabelValue(errorCases[i]); len(errs) == 0 {
|
||||
t.Errorf("case[%d] expected failure", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidIP(t *testing.T) {
|
||||
goodValues := []string{
|
||||
"::1",
|
||||
"2a00:79e0:2:0:f1c3:e797:93c1:df80",
|
||||
"::",
|
||||
"2001:4860:4860::8888",
|
||||
"::fff:1.1.1.1",
|
||||
"1.1.1.1",
|
||||
"1.1.1.01",
|
||||
"255.0.0.1",
|
||||
"1.0.0.0",
|
||||
"0.0.0.0",
|
||||
}
|
||||
for _, val := range goodValues {
|
||||
if msgs := IsValidIP(val); len(msgs) != 0 {
|
||||
t.Errorf("expected true for %q: %v", val, msgs)
|
||||
}
|
||||
}
|
||||
|
||||
badValues := []string{
|
||||
"[2001:db8:0:1]:80",
|
||||
"myhost.mydomain",
|
||||
"-1.0.0.0",
|
||||
"[2001:db8:0:1]",
|
||||
"a",
|
||||
}
|
||||
for _, val := range badValues {
|
||||
if msgs := IsValidIP(val); len(msgs) == 0 {
|
||||
t.Errorf("expected false for %q", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsHTTPHeaderName(t *testing.T) {
|
||||
goodValues := []string{
|
||||
// Common ones
|
||||
"Accept-Encoding", "Host", "If-Modified-Since", "X-Forwarded-For",
|
||||
// Weirdo, but still conforming names
|
||||
"a", "ab", "abc", "a1", "-a", "a-", "a-b", "a-1", "a--1--2--b", "--abc-123",
|
||||
"A", "AB", "AbC", "A1", "-A", "A-", "A-B", "A-1", "A--1--2--B", "--123-ABC",
|
||||
}
|
||||
for _, val := range goodValues {
|
||||
if msgs := IsHTTPHeaderName(val); len(msgs) != 0 {
|
||||
t.Errorf("expected true for '%s': %v", val, msgs)
|
||||
}
|
||||
}
|
||||
|
||||
badValues := []string{
|
||||
"Host:", "X-Forwarded-For:", "X-@Home",
|
||||
"", "_", "a_", "_a", "1_", "1_2", ".", "a.", ".a", "a.b", "1.", ".1", "1.2",
|
||||
" ", "a ", " a", "a b", "1 ", " 1", "1 2", "#a#", "^", ",", ";", "=", "<",
|
||||
"?", "@", "{",
|
||||
}
|
||||
for _, val := range badValues {
|
||||
if msgs := IsHTTPHeaderName(val); len(msgs) == 0 {
|
||||
t.Errorf("expected false for '%s'", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidPercent(t *testing.T) {
|
||||
goodValues := []string{
|
||||
"0%",
|
||||
"00000%",
|
||||
"1%",
|
||||
"01%",
|
||||
"99%",
|
||||
"100%",
|
||||
"101%",
|
||||
}
|
||||
for _, val := range goodValues {
|
||||
if msgs := IsValidPercent(val); len(msgs) != 0 {
|
||||
t.Errorf("expected true for %q: %v", val, msgs)
|
||||
}
|
||||
}
|
||||
|
||||
badValues := []string{
|
||||
"",
|
||||
"0",
|
||||
"100",
|
||||
"0.0%",
|
||||
"99.9%",
|
||||
"hundred",
|
||||
" 1%",
|
||||
"1% ",
|
||||
"-0%",
|
||||
"-1%",
|
||||
"+1%",
|
||||
}
|
||||
for _, val := range badValues {
|
||||
if msgs := IsValidPercent(val); len(msgs) == 0 {
|
||||
t.Errorf("expected false for %q", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsConfigMapKey(t *testing.T) {
|
||||
successCases := []string{
|
||||
"a",
|
||||
"good",
|
||||
"good-good",
|
||||
"still.good",
|
||||
"this.is.also.good",
|
||||
".so.is.this",
|
||||
"THIS_IS_GOOD",
|
||||
"so_is_this_17",
|
||||
}
|
||||
|
||||
for i := range successCases {
|
||||
if errs := IsConfigMapKey(successCases[i]); len(errs) != 0 {
|
||||
t.Errorf("[%d] expected success: %v", i, errs)
|
||||
}
|
||||
}
|
||||
|
||||
failureCases := []string{
|
||||
".",
|
||||
"..",
|
||||
"..bad",
|
||||
"b*d",
|
||||
"bad!&bad",
|
||||
}
|
||||
|
||||
for i := range failureCases {
|
||||
if errs := IsConfigMapKey(failureCases[i]); len(errs) == 0 {
|
||||
t.Errorf("[%d] expected failure", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsWildcardDNS1123Subdomain(t *testing.T) {
|
||||
goodValues := []string{
|
||||
"*.example.com",
|
||||
"*.bar.com",
|
||||
"*.foo.bar.com",
|
||||
}
|
||||
for _, val := range goodValues {
|
||||
if errs := IsWildcardDNS1123Subdomain(val); len(errs) != 0 {
|
||||
t.Errorf("expected no errors for %q: %v", val, errs)
|
||||
}
|
||||
}
|
||||
|
||||
badValues := []string{
|
||||
"*.*.bar.com",
|
||||
"*.foo.*.com",
|
||||
"*bar.com",
|
||||
"f*.bar.com",
|
||||
"*",
|
||||
}
|
||||
for _, val := range badValues {
|
||||
if errs := IsWildcardDNS1123Subdomain(val); len(errs) == 0 {
|
||||
t.Errorf("expected errors for %q", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsFullyQualifiedName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
targetName string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "name needs to be fully qualified, i.e., contains at least 2 dots",
|
||||
targetName: "k8s.io",
|
||||
err: "should be a domain with at least three segments separated by dots",
|
||||
},
|
||||
{
|
||||
name: "name cannot be empty",
|
||||
targetName: "",
|
||||
err: "Required value",
|
||||
},
|
||||
{
|
||||
name: "name must conform to RFC 1123",
|
||||
targetName: "A.B.C",
|
||||
err: "a DNS-1123 subdomain must consist of lower case alphanumeric characters",
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
err := IsFullyQualifiedName(field.NewPath(""), tc.targetName).ToAggregate()
|
||||
switch {
|
||||
case tc.err == "" && err != nil:
|
||||
t.Errorf("%q: unexpected error: %v", tc.name, err)
|
||||
case tc.err != "" && err == nil:
|
||||
t.Errorf("%q: unexpected no error, expected %s", tc.name, tc.err)
|
||||
case tc.err != "" && err != nil && !strings.Contains(err.Error(), tc.err):
|
||||
t.Errorf("%q: expected %s, got %v", tc.name, tc.err, err)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user