build source

This commit is contained in:
build 2026-04-16 04:16:36 +00:00
commit ee1fec43ed
4171 changed files with 1351288 additions and 0 deletions

64
vendor/k8s.io/apimachinery/pkg/api/validate/README.md generated vendored Normal file
View file

@ -0,0 +1,64 @@
# API validation
This package holds functions which validate fields and types in the Kubernetes
API. It may be useful beyond API validation, but this is the primary goal.
Most of the public functions here have signatures which adhere to the following
pattern, which is assumed by automation and code-generation:
```
import (
"context"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
func <Name>(ctx context.Context, op operation.Operation, fldPath *field.Path, value, oldValue <ValueType>, <OtherArgs...>) field.ErrorList
```
The name of validator functions should consider that callers will generally be
spelling out the package name and the function name, and so should aim for
legibility. E.g. `validate.Concept()`.
The `ctx` argument is Go's usual Context.
The `opCtx` argument provides information about the API operation in question.
The `fldPath` argument indicates the path to the field in question, to be used
in errors.
The `value` and `oldValue` arguments are the thing(s) being validated. For
CREATE operations (`opCtx.Operation == operation.Create`), the `oldValue`
argument will be nil. Many validators functions only look at the current value
(`value`) and disregard `oldValue`.
The `value` and `oldValue` arguments are always nilable - pointers to primitive
types, slices of any type, or maps of any type. Validator functions should
avoid dereferencing nil. Callers are expected to not pass a nil `value` unless the
API field itself was nilable. `oldValue` is always nil for CREATE operations and
is also nil for UPDATE operations if the `value` is not correlated with an `oldValue`.
Simple content-validators may have no `<OtherArgs>`, but validator functions
may take additional arguments. Some validator functions will be built as
generics, e.g. to allow any integer type or to handle arbitrary slices.
Examples:
```
// NonEmpty validates that a string is not empty.
func NonEmpty(ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ *string) field.ErrorList
// Even validates that a slice has an even number of items.
func Even[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ []T) field.ErrorList
// KeysMaxLen validates that all of the string keys in a map are under the
// specified length.
func KeysMaxLen[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ map[string]T, maxLen int) field.ErrorList
```
Validator functions always return an `ErrorList` where each item is a distinct
validation failure and a zero-length return value (not just nil) indicates
success.
Good validation failure messages follow the Kubernetes API conventions, for
example using "must" instead of "should".

28
vendor/k8s.io/apimachinery/pkg/api/validate/common.go generated vendored Normal file
View file

@ -0,0 +1,28 @@
/*
Copyright 2024 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 validate
import (
"context"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// ValidateFunc is a function that validates a value, possibly considering the
// old value (if any).
type ValidateFunc[T any] func(ctx context.Context, op operation.Operation, fldPath *field.Path, newValue, oldValue T) field.ErrorList

View file

@ -0,0 +1,32 @@
/*
Copyright 2025 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 constraints
// Signed is a constraint that permits any signed integer type.
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
// Unsigned is a constraint that permits any unsigned integer type.
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
// Integer is a constraint that permits any integer type.
type Integer interface {
Signed | Unsigned
}

View file

@ -0,0 +1,62 @@
/*
Copyright 2025 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 content
const decimalIntegerErrMsg string = "must be a valid decimal integer in canonical form"
// IsDecimalInteger validates that a string represents a decimal integer in strict canonical form.
// This means the string must be formatted exactly as a human would naturally write an integer,
// without any programming language conventions like leading zeros, plus signs, or alternate bases.
//
// valid values:"0" or Non-zero integers (i.e., "123", "-456") where the first digit is 1-9,
// followed by any digits 0-9.
//
// This validator is stricter than strconv.ParseInt, which accepts leading zeros values (i.e, "0700")
// and interprets them as decimal 700, potentially causing confusion with octal notation.
func IsDecimalInteger(value string) []string {
n := len(value)
if n == 0 {
return []string{EmptyError()}
}
i := 0
if value[0] == '-' {
if n == 1 {
return []string{decimalIntegerErrMsg}
}
i = 1
}
if value[i] == '0' {
if n == 1 && i == 0 {
return nil
}
return []string{decimalIntegerErrMsg}
}
if value[i] < '1' || value[i] > '9' {
return []string{decimalIntegerErrMsg}
}
for i++; i < n; i++ {
if value[i] < '0' || value[i] > '9' {
return []string{decimalIntegerErrMsg}
}
}
return nil
}

View file

@ -0,0 +1,101 @@
/*
Copyright 2025 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 content
import (
"regexp"
)
const dns1123LabelFmt string = "[a-z0-9]([-a-z0-9]*[a-z0-9])?"
const dns1123LabelErrMsg string = "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character"
// DNS1123LabelMaxLength is a label's max length in DNS (RFC 1123)
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) {
if dns1123SubdomainRegexp.MatchString(value) {
// It was a valid subdomain and not a valid label. Since we
// already checked length, it must be dots.
errs = append(errs, "must not contain dots")
} else {
errs = append(errs, RegexError(dns1123LabelErrMsg, dns1123LabelFmt, "my-name", "123-abc"))
}
}
return errs
}
const dns1123SubdomainFmt string = dns1123LabelFmt + "(\\." + dns1123LabelFmt + ")*"
const dns1123SubdomainFmtCaseless string = "(?i)" + dns1123SubdomainFmt
const dns1123SubdomainErrorMsg string = "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"
const dns1123SubdomainCaselessErrorMsg string = "an RFC 1123 subdomain must consist of alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"
// DNS1123SubdomainMaxLength is a subdomain's max length in DNS (RFC 1123)
const DNS1123SubdomainMaxLength int = 253
var dns1123SubdomainRegexp = regexp.MustCompile("^" + dns1123SubdomainFmt + "$")
var dns1123SubdomainCaselessRegexp = regexp.MustCompile("^" + dns1123SubdomainFmtCaseless + "$")
// IsDNS1123Subdomain tests for a string that conforms to the definition of a
// subdomain in DNS (RFC 1123) lowercase.
func IsDNS1123Subdomain(value string) []string {
return isDNS1123Subdomain(value, false)
}
// IsDNS1123SubdomainCaseless tests for a string that conforms to the definition of a
// subdomain in DNS (RFC 1123).
//
// Deprecated: API validation should never be caseless. Caseless validation is a vector
// for bugs and failed uniqueness assumptions. For example, names like "foo.com" and
// "FOO.COM" are both accepted as valid, but they are typically not treated as equal by
// consumers (e.g. CSI and DRA driver names). This fails the "least surprise" principle and
// can cause inconsistent behaviors.
//
// Note: This allows uppercase names but is not caseless — uppercase and lowercase are
// treated as different values. Use IsDNS1123Subdomain for strict, lowercase validation
// instead.
func IsDNS1123SubdomainCaseless(value string) []string {
return isDNS1123Subdomain(value, true)
}
func isDNS1123Subdomain(value string, caseless bool) []string {
var errs []string
if len(value) > DNS1123SubdomainMaxLength {
errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
}
errorMsg := dns1123SubdomainErrorMsg
example := "example.com"
regexp := dns1123SubdomainRegexp
if caseless {
errorMsg = dns1123SubdomainCaselessErrorMsg
example = "Example.com"
regexp = dns1123SubdomainCaselessRegexp
}
if !regexp.MatchString(value) {
errs = append(errs, RegexError(errorMsg, dns1123SubdomainFmt, example))
}
return errs
}

View file

@ -0,0 +1,66 @@
/*
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 content
import (
"fmt"
"reflect"
"k8s.io/apimachinery/pkg/api/validate/constraints"
)
// MinError returns a string explanation of a "must be greater than or equal"
// validation failure.
func MinError[T constraints.Integer](min T) string {
return fmt.Sprintf("must be greater than or equal to %d", min)
}
// 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 bytes", length)
}
// EmptyError returns a string explanation of an "empty string" validation.
func EmptyError() string {
return "must be non-empty"
}
// RegexError returns a string explanation of a regex validation failure.
func RegexError(msg string, re string, examples ...string) string {
if len(examples) == 0 {
return msg + " (regex used for validation is '" + re + "')"
}
msg += " (e.g. "
for i := range examples {
if i > 0 {
msg += " or "
}
msg += "'" + examples[i] + "', "
}
msg += "regex used for validation is '" + re + "')"
return msg
}
// NEQError returns a string explanation of a "must not be equal to" validation failure.
func NEQError[T any](disallowed T) string {
format := "%v"
if reflect.ValueOf(disallowed).Kind() == reflect.String {
format = "%q"
}
return fmt.Sprintf("must not be equal to "+format, disallowed)
}

View file

@ -0,0 +1,35 @@
/*
Copyright 2025 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 content
import (
"regexp"
)
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
}

View file

@ -0,0 +1,101 @@
/*
Copyright 2025 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 content
import (
"regexp"
"strings"
)
const labelKeyCharFmt string = "[A-Za-z0-9]"
const labelKeyExtCharFmt string = "[-A-Za-z0-9_.]"
const labelKeyFmt string = "(" + labelKeyCharFmt + labelKeyExtCharFmt + "*)?" + labelKeyCharFmt
const labelKeyErrMsg string = "must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
const labelKeyMaxLength int = 63
var labelKeyRegexp = regexp.MustCompile("^" + labelKeyFmt + "$")
// IsQualifiedName tests whether the value passed is what Kubernetes calls a
// "qualified name", which is the same as a label key.
//
// Deprecated: use IsLabelKey instead.
var IsQualifiedName = IsLabelKey
// IsLabelKey tests whether the value passed is a valid label key. This format
// is used to validate many fields in the Kubernetes API.
// Label keys consist of an optional prefix and a name, separated by a '/'.
// If the value is not valid, a list of error strings is returned. Otherwise, an
// empty list (or nil) is returned.
func IsLabelKey(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 valid label key "+RegexError(labelKeyErrMsg, labelKeyFmt, "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) > labelKeyMaxLength {
errs = append(errs, "name part "+MaxLenError(labelKeyMaxLength))
}
if !labelKeyRegexp.MatchString(name) {
errs = append(errs, "name part "+RegexError(labelKeyErrMsg, labelKeyFmt, "MyName", "my.name", "123-abc"))
}
return errs
}
const labelValueFmt string = "(" + labelKeyFmt + ")?"
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"
// LabelValueMaxLength is a label's max length
const LabelValueMaxLength int = 63
var labelValueRegexp = regexp.MustCompile("^" + labelValueFmt + "$")
// IsLabelValue 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 IsLabelValue(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
}
func prefixEach(msgs []string, prefix string) []string {
for i := range msgs {
msgs[i] = prefix + msgs[i]
}
return msgs
}

50
vendor/k8s.io/apimachinery/pkg/api/validate/doc.go generated vendored Normal file
View file

@ -0,0 +1,50 @@
/*
Copyright 2024 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 validate holds API validation functions which are designed for use
// with the k8s.io/code-generator/cmd/validation-gen tool. Each validation
// function has a similar fingerprint:
//
// func <Name>(ctx context.Context,
// op operation.Operation,
// fldPath *field.Path,
// value, oldValue <nilable type>,
// <other args...>) field.ErrorList
//
// The value and oldValue arguments will always be a nilable type. If the
// original value was a string, these will be a *string. If the original value
// was a slice or map, these will be the same slice or map type.
//
// For a CREATE operation, the oldValue will always be nil. For an UPDATE
// operation, either value or oldValue may be nil, e.g. when adding or removing
// a value in a list-map. Validators which care about UPDATE operations should
// look at the opCtx argument to know which operation is being executed.
//
// Tightened validation (also known as ratcheting validation) is supported by
// defining a new validation function. For example:
//
// func TightenedMaxLength(ctx context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *string) field.ErrorList {
// if oldValue != nil && len(MaxLength(ctx, op, fldPath, oldValue, nil)) > 0 {
// // old value is not valid, so this value skips the tightened validation
// return nil
// }
// return MaxLength(ctx, op, fldPath, value, nil)
// }
//
// In general, we cannot distinguish a non-specified slice or map from one that
// is specified but empty. Validators should not rely on nil values, but use
// len() instead.
package validate

186
vendor/k8s.io/apimachinery/pkg/api/validate/each.go generated vendored Normal file
View file

@ -0,0 +1,186 @@
/*
Copyright 2024 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 validate
import (
"context"
"sort"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// MatchFunc is a function that compares two values of the same type,
// according to some criteria, and returns true if they match.
type MatchFunc[T any] func(T, T) bool
// EachSliceVal performs validation on each element of newSlice using the provided validation function.
//
// For update operations, the match function finds corresponding values in oldSlice for each
// value in newSlice. This comparison can be either full or partial (e.g., matching only
// specific struct fields that serve as a unique identifier). If match is nil, validation
// proceeds without considering old values, and the equiv function is not used.
//
// For update operations, the equiv function checks if a new value is equivalent to its
// corresponding old value, enabling validation ratcheting. If equiv is nil but match is
// provided, the match function is assumed to perform full value comparison.
//
// Note: The slice element type must be non-nilable.
func EachSliceVal[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, newSlice, oldSlice []T,
match, equiv MatchFunc[T], validator ValidateFunc[*T]) field.ErrorList {
var errs field.ErrorList
for i, val := range newSlice {
var old *T
if match != nil && len(oldSlice) > 0 {
old = lookup(oldSlice, val, match)
}
// If the operation is an update, for validation ratcheting, skip re-validating if the old
// value exists and either:
// 1. The match function provides full comparison (equiv is nil)
// 2. The equiv function confirms the values are equivalent (either directly or semantically)
//
// The equiv function provides equality comparison when match uses partial comparison.
if op.Type == operation.Update && old != nil && (equiv == nil || equiv(val, *old)) {
continue
}
errs = append(errs, validator(ctx, op, fldPath.Index(i), &val, old)...)
}
return errs
}
// lookup returns a pointer to the first element in the list that matches the
// target, according to the provided comparison function, or else nil.
func lookup[T any](list []T, target T, match MatchFunc[T]) *T {
for i := range list {
if match(list[i], target) {
return &list[i]
}
}
return nil
}
// EachMapVal validates each value in newMap using the specified validation
// function, passing the corresponding old value from oldMap if the key exists in oldMap.
// For update operations, it implements validation ratcheting by skipping validation
// when the old value exists and the equiv function confirms the values are equivalent.
// The value-type of the map is assumed to not be nilable.
// If equiv is nil, value-based ratcheting is disabled and all values will be validated.
func EachMapVal[K ~string, V any](ctx context.Context, op operation.Operation, fldPath *field.Path, newMap, oldMap map[K]V,
equiv MatchFunc[V], validator ValidateFunc[*V]) field.ErrorList {
var errs field.ErrorList
for key, val := range newMap {
var old *V
if o, found := oldMap[key]; found {
old = &o
}
// If the operation is an update, for validation ratcheting, skip re-validating if the old
// value is found and the equiv function confirms the values are equivalent.
if op.Type == operation.Update && old != nil && equiv != nil && equiv(val, *old) {
continue
}
errs = append(errs, validator(ctx, op, fldPath.Key(string(key)), &val, old)...)
}
return errs
}
// EachMapKey validates each element of newMap with the specified
// validation function.
func EachMapKey[K ~string, T any](ctx context.Context, op operation.Operation, fldPath *field.Path, newMap, oldMap map[K]T,
validator ValidateFunc[*K]) field.ErrorList {
var errs field.ErrorList
for key := range newMap {
var old *K
if _, found := oldMap[key]; found {
old = &key
}
// If the operation is an update, for validation ratcheting, skip re-validating if
// the key is found in oldMap.
if op.Type == operation.Update && old != nil {
continue
}
// Note: the field path is the field, not the key.
errs = append(errs, validator(ctx, op, fldPath, &key, nil)...)
}
return errs
}
// Unique verifies that each element of newSlice is unique, according to the
// match function. It compares every element of the slice with every other
// element and returns errors for non-unique items.
func Unique[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, newSlice, _ []T, match MatchFunc[T]) field.ErrorList {
var dups []int
for i, val := range newSlice {
for j := i + 1; j < len(newSlice); j++ {
other := newSlice[j]
if match(val, other) {
if dups == nil {
dups = make([]int, 0, len(newSlice))
}
if lookup(dups, j, func(a, b int) bool { return a == b }) == nil {
dups = append(dups, j)
}
}
}
}
var errs field.ErrorList
sort.Ints(dups)
for _, i := range dups {
var val any = newSlice[i]
// TODO: we don't want the whole item to be logged in the error, just
// the key(s). Unfortunately, the way errors are rendered, it comes out
// as something like "map[string]any{...}" which is not very nice. Once
// that is fixed, we can consider adding a way for this function to
// specify that just the keys should be rendered in the error.
errs = append(errs, field.Duplicate(fldPath.Index(i), val))
}
return errs
}
// SemanticDeepEqual is a MatchFunc that uses equality.Semantic.DeepEqual to
// compare two values.
// This wrapper is needed because MatchFunc requires a function that takes two
// arguments of specific type T, while equality.Semantic.DeepEqual takes
// arguments of type interface{}/any. The wrapper satisfies the type
// constraints of MatchFunc while leveraging the underlying semantic equality
// logic. It can be used by any other function that needs to call DeepEqual.
func SemanticDeepEqual[T any](a, b T) bool {
return equality.Semantic.DeepEqual(a, b)
}
// DirectEqual is a MatchFunc that uses the == operator to compare two values.
// It can be used by any other function that needs to compare two values
// directly.
func DirectEqual[T comparable](a, b T) bool {
return a == b
}
// DirectEqualPtr is a MatchFunc that dereferences two pointers and uses the ==
// operator to compare the values. If both pointers are nil, it returns true.
// If one pointer is nil and the other is not, it returns false.
// It can be used by any other function that needs to compare two pointees
// directly.
func DirectEqualPtr[T comparable](a, b *T) bool {
if a == b {
return true
}
if a == nil || b == nil {
return false
}
return *a == *b
}

74
vendor/k8s.io/apimachinery/pkg/api/validate/enum.go generated vendored Normal file
View file

@ -0,0 +1,74 @@
/*
Copyright 2024 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 validate
import (
"context"
"slices"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// Enum verifies that a given value is a member of a set of enum values.
// Exclude Rules that apply when options are enabled or disabled are also considered.
// If ANY exclude rule matches for a value, that value is excluded from the enum when validating.
func Enum[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T, validValues sets.Set[T], exclusions []EnumExclusion[T]) field.ErrorList {
if value == nil {
return nil
}
if !validValues.Has(*value) || isExcluded(op, exclusions, *value) {
return field.ErrorList{field.NotSupported[T](fldPath, *value, supportedValues(op, validValues, exclusions))}
}
return nil
}
// supportedValues returns a sorted list of supported values.
// Excluded enum values are not included in the list.
func supportedValues[T ~string](op operation.Operation, values sets.Set[T], exclusions []EnumExclusion[T]) []T {
res := make([]T, 0, len(values))
for key := range values {
if isExcluded(op, exclusions, key) {
continue
}
res = append(res, key)
}
slices.Sort(res)
return res
}
// EnumExclusion represents a single enum exclusion rule.
type EnumExclusion[T ~string] struct {
// Value specifies the enum value to be conditionally excluded.
Value T
// ExcludeWhen determines the condition for exclusion.
// If true, the value is excluded if the option is present.
// If false, the value is excluded if the option is NOT present.
ExcludeWhen bool
// Option is the name of the feature option that controls the exclusion.
Option string
}
func isExcluded[T ~string](op operation.Operation, exclusions []EnumExclusion[T], value T) bool {
for _, rule := range exclusions {
if rule.Value == value && rule.ExcludeWhen == op.HasOption(rule.Option) {
return true
}
}
return false
}

View file

@ -0,0 +1,38 @@
/*
Copyright 2025 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 validate
import (
"context"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/api/validate/content"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// NEQ validates that the specified comparable value is not equal to the disallowed value.
func NEQ[T comparable](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T, disallowed T) field.ErrorList {
if value == nil {
return nil
}
if *value == disallowed {
return field.ErrorList{
field.Invalid(fldPath, *value, content.NEQError(disallowed)).WithOrigin("neq"),
}
}
return nil
}

View file

@ -0,0 +1,40 @@
/*
Copyright 2025 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 validate
import (
"context"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// Immutable verifies that the specified value has not changed in the course of
// an update operation. It does nothing if the old value is not provided.
//
// This function unconditionally returns a validation error as it
// relies on the default ratcheting mechanism to only be called when a
// change to the field has already been detected. This avoids a redundant
// equivalence check across ratcheting and this function.
func Immutable[T any](_ context.Context, op operation.Operation, fldPath *field.Path, _, _ T) field.ErrorList {
if op.Type != operation.Update {
return nil
}
return field.ErrorList{
field.Invalid(fldPath, nil, "field is immutable").WithOrigin("immutable"),
}
}

77
vendor/k8s.io/apimachinery/pkg/api/validate/item.go generated vendored Normal file
View file

@ -0,0 +1,77 @@
/*
Copyright 2024 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 validate
import (
"context"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// MatchItemFn takes a pointer to an item and returns true if it matches the criteria.
type MatchItemFn[T any] func(*T) bool
// SliceItem finds the first item in newList that satisfies the match function,
// and if found, also looks for a matching item in oldList. If the value of the
// item is the same as the previous value, as per the equiv function, then no
// validation is performed. Otherwise, it invokes 'itemValidator' on these items.
//
// This function processes only the *first* matching item found in newList. It
// assumes that the match functions targets a unique identifier (primary key)
// and will match at most one element per list. If this assumption is violated,
// changes in list order can lead this function to have inconsistent behavior.
//
// The fldPath passed to itemValidator is indexed to the matched item's
// position in newList.
//
// This function does not validate items that were removed (present in oldList
// but not in newList).
func SliceItem[TList ~[]TItem, TItem any](
ctx context.Context, op operation.Operation, fldPath *field.Path,
newList, oldList TList,
matches MatchItemFn[TItem],
equiv MatchFunc[TItem],
itemValidator func(ctx context.Context, op operation.Operation, fldPath *field.Path, newObj, oldObj *TItem) field.ErrorList,
) field.ErrorList {
var matchedNew, matchedOld *TItem
var newIndex int
for i := range newList {
if matches(&newList[i]) {
matchedNew = &newList[i]
newIndex = i
break
}
}
if matchedNew == nil {
return nil
}
for i := range oldList {
if matches(&oldList[i]) {
matchedOld = &oldList[i]
break
}
}
if op.Type == operation.Update && matchedOld != nil && equiv(*matchedNew, *matchedOld) {
return nil
}
return itemValidator(ctx, op, fldPath.Index(newIndex), matchedNew, matchedOld)
}

57
vendor/k8s.io/apimachinery/pkg/api/validate/limits.go generated vendored Normal file
View file

@ -0,0 +1,57 @@
/*
Copyright 2024 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 validate
import (
"context"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/api/validate/constraints"
"k8s.io/apimachinery/pkg/api/validate/content"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// MaxLength verifies that the specified value is not longer than max
// characters.
func MaxLength[T ~string](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T, max int) field.ErrorList {
if value == nil {
return nil
}
if len(*value) > max {
return field.ErrorList{field.TooLong(fldPath, *value, max).WithOrigin("maxLength")}
}
return nil
}
// MaxItems verifies that the specified slice is not longer than max items.
func MaxItems[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ []T, max int) field.ErrorList {
if len(value) > max {
return field.ErrorList{field.TooMany(fldPath, len(value), max).WithOrigin("maxItems")}
}
return nil
}
// Minimum verifies that the specified value is greater than or equal to min.
func Minimum[T constraints.Integer](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T, min T) field.ErrorList {
if value == nil {
return nil
}
if *value < min {
return field.ErrorList{field.Invalid(fldPath, *value, content.MinError(min)).WithOrigin("minimum")}
}
return nil
}

35
vendor/k8s.io/apimachinery/pkg/api/validate/options.go generated vendored Normal file
View file

@ -0,0 +1,35 @@
/*
Copyright 2025 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 validate
import (
"context"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// IfOption conditionally evaluates a validation function. If the option and enabled are both true the validator
// is called. If the option and enabled are both false the validator is called. Otherwise, the validator is not called.
func IfOption[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T,
optionName string, enabled bool, validator func(context.Context, operation.Operation, *field.Path, *T, *T) field.ErrorList,
) field.ErrorList {
if op.HasOption(optionName) == enabled {
return validator(ctx, op, fldPath, value, oldValue)
}
return nil
}

133
vendor/k8s.io/apimachinery/pkg/api/validate/required.go generated vendored Normal file
View file

@ -0,0 +1,133 @@
/*
Copyright 2024 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 validate
import (
"context"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// RequiredValue verifies that the specified value is not the zero-value for
// its type.
func RequiredValue[T comparable](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
var zero T
if *value != zero {
return nil
}
return field.ErrorList{field.Required(fldPath, "")}
}
// RequiredPointer verifies that the specified pointer is not nil.
func RequiredPointer[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
if value != nil {
return nil
}
return field.ErrorList{field.Required(fldPath, "")}
}
// RequiredSlice verifies that the specified slice is not empty.
func RequiredSlice[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ []T) field.ErrorList {
if len(value) > 0 {
return nil
}
return field.ErrorList{field.Required(fldPath, "")}
}
// RequiredMap verifies that the specified map is not empty.
func RequiredMap[K comparable, T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ map[K]T) field.ErrorList {
if len(value) > 0 {
return nil
}
return field.ErrorList{field.Required(fldPath, "")}
}
// ForbiddenValue verifies that the specified value is the zero-value for its
// type.
func ForbiddenValue[T comparable](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
var zero T
if *value == zero {
return nil
}
return field.ErrorList{field.Forbidden(fldPath, "")}
}
// ForbiddenPointer verifies that the specified pointer is nil.
func ForbiddenPointer[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
if value == nil {
return nil
}
return field.ErrorList{field.Forbidden(fldPath, "")}
}
// ForbiddenSlice verifies that the specified slice is empty.
func ForbiddenSlice[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ []T) field.ErrorList {
if len(value) == 0 {
return nil
}
return field.ErrorList{field.Forbidden(fldPath, "")}
}
// ForbiddenMap verifies that the specified map is empty.
func ForbiddenMap[K comparable, T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ map[K]T) field.ErrorList {
if len(value) == 0 {
return nil
}
return field.ErrorList{field.Forbidden(fldPath, "")}
}
// OptionalValue verifies that the specified value is not the zero-value for
// its type. This is identical to RequiredValue, but the caller should treat an
// error here as an indication that the optional value was not specified.
func OptionalValue[T comparable](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
var zero T
if *value != zero {
return nil
}
return field.ErrorList{field.Required(fldPath, "optional value was not specified")}
}
// OptionalPointer verifies that the specified pointer is not nil. This is
// identical to RequiredPointer, but the caller should treat an error here as an
// indication that the optional value was not specified.
func OptionalPointer[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
if value != nil {
return nil
}
return field.ErrorList{field.Required(fldPath, "optional value was not specified")}
}
// OptionalSlice verifies that the specified slice is not empty. This is
// identical to RequiredSlice, but the caller should treat an error here as an
// indication that the optional value was not specified.
func OptionalSlice[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ []T) field.ErrorList {
if len(value) > 0 {
return nil
}
return field.ErrorList{field.Required(fldPath, "optional value was not specified")}
}
// OptionalMap verifies that the specified map is not empty. This is identical
// to RequiredMap, but the caller should treat an error here as an indication that
// the optional value was not specified.
func OptionalMap[K comparable, T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ map[K]T) field.ErrorList {
if len(value) > 0 {
return nil
}
return field.ErrorList{field.Required(fldPath, "optional value was not specified")}
}

290
vendor/k8s.io/apimachinery/pkg/api/validate/strfmt.go generated vendored Normal file
View file

@ -0,0 +1,290 @@
/*
Copyright 2025 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 validate
import (
"context"
"fmt"
"strings"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/api/validate/content"
"k8s.io/apimachinery/pkg/util/validation/field"
)
const (
uuidErrorMessage = "must be a lowercase UUID in 8-4-4-4-12 format"
defaultResourceRequestsPrefix = "requests."
// Default namespace prefix.
resourceDefaultNamespacePrefix = "kubernetes.io/"
resourceDeviceMaxLength = 32
)
// ShortName verifies that the specified value is a valid "short name"
// (sometimes known as a "DNS label").
// - must not be empty
// - must be less than 64 characters long
// - must start and end with lower-case alphanumeric characters
// - must contain only lower-case alphanumeric characters or dashes
//
// All errors returned by this function will be "invalid" type errors. If the
// caller wants better errors, it must take responsibility for checking things
// like required/optional and max-length.
func ShortName[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
if value == nil {
return nil
}
var allErrs field.ErrorList
for _, msg := range content.IsDNS1123Label((string)(*value)) {
allErrs = append(allErrs, field.Invalid(fldPath, *value, msg).WithOrigin("format=k8s-short-name"))
}
return allErrs
}
// LongName verifies that the specified value is a valid "long name"
// (sometimes known as a "DNS subdomain").
// - must not be empty
// - must be less than 254 characters long
// - each element must start and end with lower-case alphanumeric characters
// - each element must contain only lower-case alphanumeric characters or dashes
//
// All errors returned by this function will be "invalid" type errors. If the
// caller wants better errors, it must take responsibility for checking things
// like required/optional and max-length.
func LongName[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
if value == nil {
return nil
}
var allErrs field.ErrorList
for _, msg := range content.IsDNS1123Subdomain((string)(*value)) {
allErrs = append(allErrs, field.Invalid(fldPath, *value, msg).WithOrigin("format=k8s-long-name"))
}
return allErrs
}
// LabelKey verifies that the specified value is a valid label key.
// A label key is composed of an optional prefix and a name, separated by a '/'.
// The name part is required and must:
// - be 63 characters or less
// - begin and end with an alphanumeric character ([a-z0-9A-Z])
// - contain only alphanumeric characters, dashes (-), underscores (_), or dots (.)
//
// The prefix is optional and must:
// - be a DNS subdomain
// - be no more than 253 characters
func LabelKey[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
if value == nil {
return nil
}
var allErrs field.ErrorList
for _, msg := range content.IsLabelKey((string)(*value)) {
allErrs = append(allErrs, field.Invalid(fldPath, *value, msg).WithOrigin("format=k8s-label-key"))
}
return allErrs
}
// LongNameCaseless verifies that the specified value is a valid "long name"
// (sometimes known as a "DNS subdomain"), but is case-insensitive.
// - must not be empty
// - must be less than 254 characters long
// - each element must start and end with alphanumeric characters
// - each element must contain only alphanumeric characters or dashes
//
// Deprecated: Case-insensitive names are not recommended as they can lead to ambiguity
// (e.g., 'Foo', 'FOO', and 'foo' would be allowed names for foo). Use LongName for strict, lowercase validation.
func LongNameCaseless[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
if value == nil {
return nil
}
var allErrs field.ErrorList
for _, msg := range content.IsDNS1123SubdomainCaseless((string)(*value)) {
allErrs = append(allErrs, field.Invalid(fldPath, *value, msg).WithOrigin("format=k8s-long-name-caseless"))
}
return allErrs
}
// LabelValue verifies that the specified value is a valid label value.
// - can be empty
// - must be no more than 63 characters
// - must start and end with alphanumeric characters
// - must contain only alphanumeric characters, dashes, underscores, or dots
func LabelValue[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
if value == nil {
return nil
}
var allErrs field.ErrorList
for _, msg := range content.IsLabelValue((string)(*value)) {
allErrs = append(allErrs, field.Invalid(fldPath, *value, msg).WithOrigin("format=k8s-label-value"))
}
return allErrs
}
// UUID verifies that the specified value is a valid UUID (RFC 4122).
// - must be 36 characters long
// - must be in the normalized form `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
// - must use only lowercase hexadecimal characters
func UUID[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
if value == nil {
return nil
}
val := (string)(*value)
if len(val) != 36 {
return field.ErrorList{field.Invalid(fldPath, val, uuidErrorMessage).WithOrigin("format=k8s-uuid")}
}
for idx := 0; idx < len(val); idx++ {
character := val[idx]
switch idx {
case 8, 13, 18, 23:
if character != '-' {
return field.ErrorList{field.Invalid(fldPath, val, uuidErrorMessage).WithOrigin("format=k8s-uuid")}
}
default:
// should be lower case hexadecimal.
if (character < '0' || character > '9') && (character < 'a' || character > 'f') {
return field.ErrorList{field.Invalid(fldPath, val, uuidErrorMessage).WithOrigin("format=k8s-uuid")}
}
}
}
return nil
}
// ResourcePoolName verifies that the specified value is one or more valid "long name"
// parts separated by a '/' and no longer than 253 characters.
func ResourcePoolName[T ~string](ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
if value == nil {
return nil
}
val := (string)(*value)
var allErrs field.ErrorList
if len(val) > 253 {
allErrs = append(allErrs, field.TooLong(fldPath, val, 253))
}
parts := strings.Split(val, "/")
for i, part := range parts {
if len(part) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath, val, fmt.Sprintf("segment %d: must not be empty", i)))
continue
}
// Note that we are overwriting the origin from the underlying LongName validation.
allErrs = append(allErrs, LongName(ctx, op, fldPath, &part, nil).PrefixDetail(fmt.Sprintf("segment %d: ", i))...)
}
return allErrs.WithOrigin("format=k8s-resource-pool-name")
}
// ExtendedResourceName verifies that the specified value is a valid extended resource name.
// An extended resource name is a domain-prefixed name that does not use the "kubernetes.io"
// or "requests." prefixes. Must be a valid label key when appended to "requests.", as in quota.
//
// - must have slash domain and name.
// - must not have the "kubernetes.io" domain
// - must not have the "requests." prefix
// - name must be 63 characters or less
// - must be a valid label key when appended to "requests.", as in quota
// -- must contain only alphanumeric characters, dashes, underscores, or dots
// -- must end with an alphanumeric character
func ExtendedResourceName[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
if value == nil {
return nil
}
val := string(*value)
allErrs := field.ErrorList{}
if !strings.Contains(val, "/") {
allErrs = append(allErrs, field.Invalid(fldPath, val, "a name must be a domain-prefixed path, such as 'example.com/my-prop'"))
} else if strings.Contains(val, resourceDefaultNamespacePrefix) {
allErrs = append(allErrs, field.Invalid(fldPath, val, fmt.Sprintf("must not have %q domain", resourceDefaultNamespacePrefix)))
}
// Ensure extended resource is not type of quota.
if strings.HasPrefix(val, defaultResourceRequestsPrefix) {
allErrs = append(allErrs, field.Invalid(fldPath, val, fmt.Sprintf("must not have %q prefix", defaultResourceRequestsPrefix)))
}
// Ensure it satisfies the rules in IsLabelKey() after converted into quota resource name
nameForQuota := fmt.Sprintf("%s%s", defaultResourceRequestsPrefix, val)
for _, msg := range content.IsLabelKey(nameForQuota) {
allErrs = append(allErrs, field.Invalid(fldPath, val, msg))
}
return allErrs.WithOrigin("format=k8s-extended-resource-name")
}
// resourcesQualifiedName verifies that the specified value is a valid Kubernetes resources
// qualified name.
// - must not be empty
// - must be composed of an optional prefix and a name, separated by a slash (e.g., "prefix/name")
// - the prefix, if specified, must be a DNS subdomain
// - the name part must be a C identifier
// - the name part must be no more than 32 characters
func resourcesQualifiedName[T ~string](ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
if value == nil {
return nil
}
var allErrs field.ErrorList
s := string(*value)
parts := strings.Split(s, "/")
// TODO: This validation and the corresponding handwritten validation validateQualifiedName in
// pkg/apis/resource/validation/validation.go are not validating whether there are more than 1
// slash. This should be fixed in both places.
switch len(parts) {
case 1:
allErrs = append(allErrs, validateCIdentifier(parts[0], resourceDeviceMaxLength, fldPath)...)
case 2:
if len(parts[0]) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath, "", "prefix must not be empty"))
} else {
if len(parts[0]) > 63 {
allErrs = append(allErrs, field.TooLong(fldPath, parts[0], 63))
}
allErrs = append(allErrs, LongName(ctx, op, fldPath, &parts[0], nil).PrefixDetail("prefix: ")...)
}
if len(parts[1]) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath, "", "name must not be empty"))
} else {
allErrs = append(allErrs, validateCIdentifier(parts[1], resourceDeviceMaxLength, fldPath)...)
}
}
return allErrs
}
// ResourceFullyQualifiedName verifies that the specified value is a valid Kubernetes
// fully qualified name.
// - must not be empty
// - must be composed of a prefix and a name, separated by a slash (e.g., "prefix/name")
// - the prefix must be a DNS subdomain
// - the name part must be a C identifier
// - the name part must be no more than 32 characters
func ResourceFullyQualifiedName[T ~string](ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
if value == nil {
return nil
}
var allErrs field.ErrorList
s := string(*value)
allErrs = append(allErrs, resourcesQualifiedName(ctx, op, fldPath, &s, nil)...)
if !strings.Contains(s, "/") {
allErrs = append(allErrs, field.Invalid(fldPath, s, "a fully qualified name must be a domain and a name separated by a slash"))
}
return allErrs.WithOrigin("format=k8s-resource-fully-qualified-name")
}
func validateCIdentifier(id string, length int, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
if len(id) > length {
allErrs = append(allErrs, field.TooLong(fldPath, id, length))
}
for _, msg := range content.IsCIdentifier(id) {
allErrs = append(allErrs, field.Invalid(fldPath, id, msg))
}
return allErrs
}

View file

@ -0,0 +1,53 @@
/*
Copyright 2025 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 validate
import (
"context"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// GetFieldFunc is a function that extracts a field from a type and returns a
// nilable value.
type GetFieldFunc[Tstruct any, Tfield any] func(*Tstruct) Tfield
// Subfield validates a subfield of a struct against a validator function. If
// the value of the subfield is the same as the previous value, as per the
// equiv function, then no validation is performed.
//
// The fldPath passed to the validator includes the subfield name.
func Subfield[Tstruct any, Tfield any](
ctx context.Context, op operation.Operation, fldPath *field.Path,
newStruct, oldStruct *Tstruct,
fldName string, getField GetFieldFunc[Tstruct, Tfield],
equiv MatchFunc[Tfield],
validator ValidateFunc[Tfield],
) field.ErrorList {
var errs field.ErrorList
newVal := getField(newStruct)
var oldVal Tfield
if oldStruct != nil {
oldVal = getField(oldStruct)
}
if op.Type == operation.Update && oldStruct != nil && equiv(newVal, oldVal) {
return nil
}
errs = append(errs, validator(ctx, op, fldPath.Child(fldName), newVal, oldVal)...)
return errs
}

35
vendor/k8s.io/apimachinery/pkg/api/validate/testing.go generated vendored Normal file
View file

@ -0,0 +1,35 @@
/*
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 validate
import (
"context"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// FixedResult asserts a fixed boolean result. This is mostly useful for
// testing.
func FixedResult[T any](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ T, result bool, arg string) field.ErrorList {
if result {
return nil
}
return field.ErrorList{
field.Invalid(fldPath, value, "forced failure: "+arg).WithOrigin("validateFalse"),
}
}

227
vendor/k8s.io/apimachinery/pkg/api/validate/union.go generated vendored Normal file
View file

@ -0,0 +1,227 @@
/*
Copyright 2024 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 validate
import (
"context"
"fmt"
"strings"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// ExtractorFn extracts a value from a parent object. Depending on the context,
// that could be the value of a field or just whether that field was set or
// not.
// Note: obj is not guaranteed to be non-nil, need to handle nil obj in the
// extractor.
type ExtractorFn[T, V any] func(obj T) V
// UnionValidationOptions configures how union validation behaves
type UnionValidationOptions struct {
// ErrorForEmpty returns error when no fields are set (nil means no error)
ErrorForEmpty func(fldPath *field.Path, allFields []string) *field.Error
// ErrorForMultiple returns error when multiple fields are set (nil means no error)
ErrorForMultiple func(fldPath *field.Path, specifiedFields []string, allFields []string) *field.Error
}
// Union verifies that exactly one member of a union is specified.
//
// UnionMembership must define all the members of the union.
//
// For example:
//
// var UnionMembershipForABC := validate.NewUnionMembership(
// validate.NewUnionMember("a"),
// validate.NewUnionMember("b"),
// validate.NewUnionMember("c"),
// )
// func ValidateABC(ctx context.Context, op operation.Operation, fldPath *field.Path, in *ABC) (errs field.ErrorList) {
// errs = append(errs, Union(ctx, op, fldPath, in, oldIn, UnionMembershipForABC,
// func(in *ABC) bool { return in.A != nil },
// func(in *ABC) bool { return in.B != "" },
// func(in *ABC) bool { return in.C != 0 },
// )...)
// return errs
// }
func Union[T any](_ context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj T, union *UnionMembership, isSetFns ...ExtractorFn[T, bool]) field.ErrorList {
options := UnionValidationOptions{
ErrorForEmpty: func(fldPath *field.Path, allFields []string) *field.Error {
return field.Invalid(fldPath, "",
fmt.Sprintf("must specify one of: %s", strings.Join(allFields, ", ")))
},
ErrorForMultiple: func(fldPath *field.Path, specifiedFields []string, allFields []string) *field.Error {
return field.Invalid(fldPath, fmt.Sprintf("{%s}", strings.Join(specifiedFields, ", ")),
fmt.Sprintf("must specify exactly one of: %s", strings.Join(allFields, ", ")))
},
}
return unionValidate(op, fldPath, obj, oldObj, union, options, isSetFns...)
}
// DiscriminatedUnion verifies specified union member matches the discriminator.
//
// UnionMembership must define all the members of the union and the discriminator.
//
// For example:
//
// var UnionMembershipForABC = validate.NewDiscriminatedUnionMembership("type",
// validate.NewDiscriminatedUnionMember("a", "A"),
// validate.NewDiscriminatedUnionMember("b", "B"),
// validate.NewDiscriminatedUnionMember("c", "C"),
// )
// func ValidateABC(ctx context.Context, op operation.Operation, fldPath *field.Path, in *ABC) (errs field.ErrorList) {
// errs = append(errs, DiscriminatedUnion(ctx, op, fldPath, in, oldIn, UnionMembershipForABC,
// func(in *ABC) string { return string(in.Type) },
// func(in *ABC) bool { return in.A != nil },
// func(in *ABC) bool { return in.B != "" },
// func(in *ABC) bool { return in.C != 0 },
// )...)
// return errs
// }
//
// It is not an error for the discriminatorValue to be unknown. That must be
// validated on its own.
func DiscriminatedUnion[T any, D ~string](_ context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj T, union *UnionMembership, discriminatorExtractor ExtractorFn[T, D], isSetFns ...ExtractorFn[T, bool]) (errs field.ErrorList) {
if len(union.members) != len(isSetFns) {
return field.ErrorList{
field.InternalError(fldPath,
fmt.Errorf("number of extractors (%d) does not match number of union members (%d)",
len(isSetFns), len(union.members))),
}
}
var changed bool
discriminatorValue := discriminatorExtractor(obj)
if op.Type == operation.Update {
oldDiscriminatorValue := discriminatorExtractor(oldObj)
changed = discriminatorValue != oldDiscriminatorValue
}
for i, fieldIsSet := range isSetFns {
member := union.members[i]
isDiscriminatedMember := string(discriminatorValue) == member.discriminatorValue
newIsSet := fieldIsSet(obj)
if op.Type == operation.Update && !changed {
oldIsSet := fieldIsSet(oldObj)
changed = changed || newIsSet != oldIsSet
}
if newIsSet && !isDiscriminatedMember {
errs = append(errs, field.Invalid(fldPath.Child(member.fieldName), "",
fmt.Sprintf("may only be specified when `%s` is %q", union.discriminatorName, member.discriminatorValue)))
} else if !newIsSet && isDiscriminatedMember {
errs = append(errs, field.Invalid(fldPath.Child(member.fieldName), "",
fmt.Sprintf("must be specified when `%s` is %q", union.discriminatorName, discriminatorValue)))
}
}
// If the union discriminator and membership is unchanged, we don't need to
// re-validate.
if op.Type == operation.Update && !changed {
return nil
}
return errs
}
// UnionMember represents a member of a union.
type UnionMember struct {
fieldName string
discriminatorValue string
}
// NewUnionMember returns a new UnionMember for the given field name.
func NewUnionMember(fieldName string) UnionMember {
return UnionMember{fieldName: fieldName}
}
// NewDiscriminatedUnionMember returns a new UnionMember for the given field
// name and discriminator value.
func NewDiscriminatedUnionMember(fieldName, discriminatorValue string) UnionMember {
return UnionMember{fieldName: fieldName, discriminatorValue: discriminatorValue}
}
// UnionMembership represents an ordered list of field union memberships.
type UnionMembership struct {
discriminatorName string
members []UnionMember
}
// NewUnionMembership returns a new UnionMembership for the given list of members.
// Member names must be unique.
func NewUnionMembership(member ...UnionMember) *UnionMembership {
return NewDiscriminatedUnionMembership("", member...)
}
// NewDiscriminatedUnionMembership returns a new UnionMembership for the given discriminator field and list of members.
// members are provided in the same way as for NewUnionMembership.
func NewDiscriminatedUnionMembership(discriminatorFieldName string, members ...UnionMember) *UnionMembership {
return &UnionMembership{
discriminatorName: discriminatorFieldName,
members: members,
}
}
// allFields returns a string listing all the field names of the member of a union for use in error reporting.
func (u UnionMembership) allFields() []string {
memberNames := make([]string, 0, len(u.members))
for _, f := range u.members {
memberNames = append(memberNames, fmt.Sprintf("`%s`", f.fieldName))
}
return memberNames
}
func unionValidate[T any](op operation.Operation, fldPath *field.Path,
obj, oldObj T, union *UnionMembership, options UnionValidationOptions, isSetFns ...ExtractorFn[T, bool],
) field.ErrorList {
if len(union.members) != len(isSetFns) {
return field.ErrorList{
field.InternalError(fldPath,
fmt.Errorf("number of extractors (%d) does not match number of union members (%d)",
len(isSetFns), len(union.members))),
}
}
var specifiedFields []string
var changed bool
for i, fieldIsSet := range isSetFns {
newIsSet := fieldIsSet(obj)
if op.Type == operation.Update && !changed {
oldIsSet := fieldIsSet(oldObj)
changed = changed || newIsSet != oldIsSet
}
if newIsSet {
specifiedFields = append(specifiedFields, union.members[i].fieldName)
}
}
// If the union membership is unchanged, we don't need to re-validate.
if op.Type == operation.Update && !changed {
return nil
}
var errs field.ErrorList
if len(specifiedFields) > 1 && options.ErrorForMultiple != nil {
errs = append(errs, options.ErrorForMultiple(fldPath, specifiedFields, union.allFields()))
}
if len(specifiedFields) == 0 && options.ErrorForEmpty != nil {
errs = append(errs, options.ErrorForEmpty(fldPath, union.allFields()))
}
return errs
}

160
vendor/k8s.io/apimachinery/pkg/api/validate/update.go generated vendored Normal file
View file

@ -0,0 +1,160 @@
/*
Copyright 2025 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 validate
import (
"context"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// UpdateConstraint represents a constraint on update operations
type UpdateConstraint int
const (
// NoSet prevents unset->set transitions
NoSet UpdateConstraint = iota
// NoUnset prevents set->unset transitions
NoUnset
// NoModify prevents value changes but allows set/unset transitions
NoModify
)
// UpdateValueByCompare verifies update constraints for comparable value types.
func UpdateValueByCompare[T comparable](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, constraints ...UpdateConstraint) field.ErrorList {
if op.Type != operation.Update {
return nil
}
var errs field.ErrorList
var zero T
for _, constraint := range constraints {
switch constraint {
case NoSet:
if *oldValue == zero && *value != zero {
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be set once created").WithOrigin("update"))
}
case NoUnset:
if *oldValue != zero && *value == zero {
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be cleared once set").WithOrigin("update"))
}
case NoModify:
// Rely on validation ratcheting to detect that the value has changed.
// This check only verifies that the field was set in both the old and
// new objects, confirming it was a modification, not a set/unset.
if *oldValue != zero && *value != zero {
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be modified once set").WithOrigin("update"))
}
}
}
return errs
}
// UpdatePointer verifies update constraints for pointer types.
func UpdatePointer[T any](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, constraints ...UpdateConstraint) field.ErrorList {
if op.Type != operation.Update {
return nil
}
var errs field.ErrorList
for _, constraint := range constraints {
switch constraint {
case NoSet:
if oldValue == nil && value != nil {
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be set once created").WithOrigin("update"))
}
case NoUnset:
if oldValue != nil && value == nil {
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be cleared once set").WithOrigin("update"))
}
case NoModify:
// Rely on validation ratcheting to detect that the value has changed.
// This check only verifies that the field was non-nil in both the old
// and new objects, confirming it was a modification, not a set/unset.
if oldValue != nil && value != nil {
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be modified once set").WithOrigin("update"))
}
}
}
return errs
}
// UpdateValueByReflect verifies update constraints for non-comparable value types using reflection.
func UpdateValueByReflect[T any](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, constraints ...UpdateConstraint) field.ErrorList {
if op.Type != operation.Update {
return nil
}
var errs field.ErrorList
var zero T
valueIsZero := equality.Semantic.DeepEqual(*value, zero)
oldValueIsZero := equality.Semantic.DeepEqual(*oldValue, zero)
for _, constraint := range constraints {
switch constraint {
case NoSet:
if oldValueIsZero && !valueIsZero {
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be set once created").WithOrigin("update"))
}
case NoUnset:
if !oldValueIsZero && valueIsZero {
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be cleared once set").WithOrigin("update"))
}
case NoModify:
// Rely on validation ratcheting to detect that the value has changed.
// This check only verifies that the field was set in both the old and
// new objects, confirming it was a modification, not a set/unset.
if !oldValueIsZero && !valueIsZero {
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be modified once set").WithOrigin("update"))
}
}
}
return errs
}
// UpdateStruct verifies update constraints for non-pointer struct types.
// Non-pointer structs are always considered "set" and never "unset".
func UpdateStruct[T any](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, constraints ...UpdateConstraint) field.ErrorList {
if op.Type != operation.Update {
return nil
}
var errs field.ErrorList
for _, constraint := range constraints {
switch constraint {
case NoSet, NoUnset:
// These constraints don't apply to non-pointer structs
// as they can't be unset. This should be caught at generation time.
continue
case NoModify:
// Non-pointer structs are always considered "set". Therefore, any
// change detected by validation ratcheting is a modification.
// The deep equality check is redundant and has been removed.
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be modified once set").WithOrigin("update"))
}
}
return errs
}

View file

@ -0,0 +1,59 @@
/*
Copyright 2024 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 validate
import (
"context"
"fmt"
"strings"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// ZeroOrOneOfUnion verifies that at most one member of a union is specified.
//
// ZeroOrOneOfMembership must define all the members of the union.
//
// For example:
//
// var ZeroOrOneOfMembershipForABC = validate.NewUnionMembership(
// validate.NewUnionMember("a"),
// validate.NewUnionMember("b"),
// validate.NewUnionMember("c"),
// )
// func ValidateABC(ctx context.Context, op operation.Operation, fldPath *field.Path, in *ABC) (errs field.ErrorList) {
// errs = append(errs, validate.ZeroOrOneOfUnion(ctx, op, fldPath, in, oldIn,
// ZeroOrOneOfMembershipForABC,
// func(in *ABC) bool { return in.A != nil },
// func(in *ABC) bool { return in.B != ""},
// func(in *ABC) bool { return in.C != 0 },
// )...)
// return errs
// }
func ZeroOrOneOfUnion[T any](_ context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj T, union *UnionMembership, isSetFns ...ExtractorFn[T, bool]) field.ErrorList {
options := UnionValidationOptions{
ErrorForEmpty: nil,
ErrorForMultiple: func(fldPath *field.Path, specifiedFields []string, allFields []string) *field.Error {
return field.Invalid(fldPath, fmt.Sprintf("{%s}", strings.Join(specifiedFields, ", ")),
fmt.Sprintf("must specify at most one of: %s", strings.Join(allFields, ", "))).WithOrigin("zeroOrOneOf")
},
}
errs := unionValidate(op, fldPath, obj, oldObj, union, options, isSetFns...)
return errs
}