updated vendor

This commit is contained in:
2026-06-16 08:02:19 +02:00
parent 2f7f99d3f0
commit 77299d0c64
1283 changed files with 67302 additions and 208958 deletions
-202
View File
@@ -1,202 +0,0 @@
/*
Copyright 2019 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 cache
import (
"container/heap"
"sync"
"time"
"k8s.io/utils/clock"
)
// NewExpiring returns an initialized expiring cache.
func NewExpiring() *Expiring {
return NewExpiringWithClock(clock.RealClock{})
}
// NewExpiringWithClock is like NewExpiring but allows passing in a custom
// clock for testing.
func NewExpiringWithClock(clock clock.Clock) *Expiring {
return &Expiring{
clock: clock,
cache: make(map[interface{}]entry),
}
}
// Expiring is a map whose entries expire after a per-entry timeout.
type Expiring struct {
// AllowExpiredGet causes the expiration check to be skipped on Get.
// It should only be used when a key always corresponds to the exact same value.
// Thus when this field is true, expired keys are considered valid
// until the next call to Set (which causes the GC to run).
// It may not be changed concurrently with calls to Get.
AllowExpiredGet bool
clock clock.Clock
// mu protects the below fields
mu sync.RWMutex
// cache is the internal map that backs the cache.
cache map[interface{}]entry
// generation is used as a cheap resource version for cache entries. Cleanups
// are scheduled with a key and generation. When the cleanup runs, it first
// compares its generation with the current generation of the entry. It
// deletes the entry iff the generation matches. This prevents cleanups
// scheduled for earlier versions of an entry from deleting later versions of
// an entry when Set() is called multiple times with the same key.
//
// The integer value of the generation of an entry is meaningless.
generation uint64
heap expiringHeap
}
type entry struct {
val interface{}
expiry time.Time
generation uint64
}
// Get looks up an entry in the cache.
func (c *Expiring) Get(key interface{}) (val interface{}, ok bool) {
c.mu.RLock()
defer c.mu.RUnlock()
e, ok := c.cache[key]
if !ok {
return nil, false
}
if !c.AllowExpiredGet && !c.clock.Now().Before(e.expiry) {
return nil, false
}
return e.val, true
}
// Set sets a key/value/expiry entry in the map, overwriting any previous entry
// with the same key. The entry expires at the given expiry time, but its TTL
// may be lengthened or shortened by additional calls to Set(). Garbage
// collection of expired entries occurs during calls to Set(), however calls to
// Get() will not return expired entries that have not yet been garbage
// collected.
func (c *Expiring) Set(key interface{}, val interface{}, ttl time.Duration) {
now := c.clock.Now()
expiry := now.Add(ttl)
c.mu.Lock()
defer c.mu.Unlock()
c.generation++
c.cache[key] = entry{
val: val,
expiry: expiry,
generation: c.generation,
}
// Run GC inline before pushing the new entry.
c.gc(now)
heap.Push(&c.heap, &expiringHeapEntry{
key: key,
expiry: expiry,
generation: c.generation,
})
}
// Delete deletes an entry in the map.
func (c *Expiring) Delete(key interface{}) {
c.mu.Lock()
defer c.mu.Unlock()
c.del(key, 0)
}
// del deletes the entry for the given key. The generation argument is the
// generation of the entry that should be deleted. If the generation has been
// changed (e.g. if a set has occurred on an existing element but the old
// cleanup still runs), this is a noop. If the generation argument is 0, the
// entry's generation is ignored and the entry is deleted.
//
// del must be called under the write lock.
func (c *Expiring) del(key interface{}, generation uint64) {
e, ok := c.cache[key]
if !ok {
return
}
if generation != 0 && generation != e.generation {
return
}
delete(c.cache, key)
}
// Len returns the number of items in the cache.
func (c *Expiring) Len() int {
c.mu.RLock()
defer c.mu.RUnlock()
return len(c.cache)
}
func (c *Expiring) gc(now time.Time) {
for {
// Return from gc if the heap is empty or the next element is not yet
// expired.
//
// heap[0] is a peek at the next element in the heap, which is not obvious
// from looking at the (*expiringHeap).Pop() implementation below.
// heap.Pop() swaps the first entry with the last entry of the heap, then
// calls (*expiringHeap).Pop() which returns the last element.
if len(c.heap) == 0 || now.Before(c.heap[0].expiry) {
return
}
cleanup := heap.Pop(&c.heap).(*expiringHeapEntry)
c.del(cleanup.key, cleanup.generation)
}
}
type expiringHeapEntry struct {
key interface{}
expiry time.Time
generation uint64
}
// expiringHeap is a min-heap ordered by expiration time of its entries. The
// expiring cache uses this as a priority queue to efficiently organize entries
// which will be garbage collected once they expire.
type expiringHeap []*expiringHeapEntry
var _ heap.Interface = &expiringHeap{}
func (cq expiringHeap) Len() int {
return len(cq)
}
func (cq expiringHeap) Less(i, j int) bool {
return cq[i].expiry.Before(cq[j].expiry)
}
func (cq expiringHeap) Swap(i, j int) {
cq[i], cq[j] = cq[j], cq[i]
}
func (cq *expiringHeap) Push(c interface{}) {
*cq = append(*cq, c.(*expiringHeapEntry))
}
func (cq *expiringHeap) Pop() interface{} {
c := (*cq)[cq.Len()-1]
*cq = (*cq)[:cq.Len()-1]
return c
}
-173
View File
@@ -1,173 +0,0 @@
/*
Copyright 2016 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 cache
import (
"container/list"
"sync"
"time"
)
// Clock defines an interface for obtaining the current time
type Clock interface {
Now() time.Time
}
// realClock implements the Clock interface by calling time.Now()
type realClock struct{}
func (realClock) Now() time.Time { return time.Now() }
// LRUExpireCache is a cache that ensures the mostly recently accessed keys are returned with
// a ttl beyond which keys are forcibly expired.
type LRUExpireCache struct {
// clock is used to obtain the current time
clock Clock
lock sync.Mutex
maxSize int
evictionList list.List
entries map[interface{}]*list.Element
}
// NewLRUExpireCache creates an expiring cache with the given size
func NewLRUExpireCache(maxSize int) *LRUExpireCache {
return NewLRUExpireCacheWithClock(maxSize, realClock{})
}
// NewLRUExpireCacheWithClock creates an expiring cache with the given size, using the specified clock to obtain the current time.
func NewLRUExpireCacheWithClock(maxSize int, clock Clock) *LRUExpireCache {
if maxSize <= 0 {
panic("maxSize must be > 0")
}
return &LRUExpireCache{
clock: clock,
maxSize: maxSize,
entries: map[interface{}]*list.Element{},
}
}
type cacheEntry struct {
key interface{}
value interface{}
expireTime time.Time
}
// Add adds the value to the cache at key with the specified maximum duration.
func (c *LRUExpireCache) Add(key interface{}, value interface{}, ttl time.Duration) {
c.lock.Lock()
defer c.lock.Unlock()
// Key already exists
oldElement, ok := c.entries[key]
if ok {
c.evictionList.MoveToFront(oldElement)
oldElement.Value.(*cacheEntry).value = value
oldElement.Value.(*cacheEntry).expireTime = c.clock.Now().Add(ttl)
return
}
// Make space if necessary
if c.evictionList.Len() >= c.maxSize {
toEvict := c.evictionList.Back()
c.evictionList.Remove(toEvict)
delete(c.entries, toEvict.Value.(*cacheEntry).key)
}
// Add new entry
entry := &cacheEntry{
key: key,
value: value,
expireTime: c.clock.Now().Add(ttl),
}
element := c.evictionList.PushFront(entry)
c.entries[key] = element
}
// Get returns the value at the specified key from the cache if it exists and is not
// expired, or returns false.
func (c *LRUExpireCache) Get(key interface{}) (interface{}, bool) {
c.lock.Lock()
defer c.lock.Unlock()
element, ok := c.entries[key]
if !ok {
return nil, false
}
if c.clock.Now().After(element.Value.(*cacheEntry).expireTime) {
c.evictionList.Remove(element)
delete(c.entries, key)
return nil, false
}
c.evictionList.MoveToFront(element)
return element.Value.(*cacheEntry).value, true
}
// Remove removes the specified key from the cache if it exists
func (c *LRUExpireCache) Remove(key interface{}) {
c.lock.Lock()
defer c.lock.Unlock()
element, ok := c.entries[key]
if !ok {
return
}
c.evictionList.Remove(element)
delete(c.entries, key)
}
// RemoveAll removes all keys that match predicate.
func (c *LRUExpireCache) RemoveAll(predicate func(key any) bool) {
c.lock.Lock()
defer c.lock.Unlock()
for key, element := range c.entries {
if predicate(key) {
c.evictionList.Remove(element)
delete(c.entries, key)
}
}
}
// Keys returns all unexpired keys in the cache.
//
// Keep in mind that subsequent calls to Get() for any of the returned keys
// might return "not found".
//
// Keys are returned ordered from least recently used to most recently used.
func (c *LRUExpireCache) Keys() []interface{} {
c.lock.Lock()
defer c.lock.Unlock()
now := c.clock.Now()
val := make([]interface{}, 0, c.evictionList.Len())
for element := c.evictionList.Back(); element != nil; element = element.Prev() {
// Only return unexpired keys
if !now.After(element.Value.(*cacheEntry).expireTime) {
val = append(val, element.Value.(*cacheEntry).key)
}
}
return val
}
-31
View File
@@ -1,31 +0,0 @@
//go:build usegocmp
// +build usegocmp
/*
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 diff
import (
"github.com/google/go-cmp/cmp" //nolint:depguard
)
// Diff returns a string representation of the difference between two objects.
// When built with the usegocmp tag, it uses go-cmp/cmp to generate a diff
// between the objects.
func Diff(a, b any) string {
return cmp.Diff(a, b)
}
-62
View File
@@ -1,62 +0,0 @@
//go:build !usegocmp
// +build !usegocmp
/*
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 diff
import (
"encoding/json"
"fmt"
"github.com/pmezard/go-difflib/difflib"
"k8s.io/apimachinery/pkg/util/dump"
)
// Diff returns a string representation of the difference between two objects.
// When built without the usegocmp tag, it uses go-difflib/difflib to generate a
// unified diff of the objects. It attempts to use JSON serialization first,
// falling back to an object dump via the dump package if JSON marshaling fails.
func Diff(a, b any) string {
aStr, aErr := toPrettyJSON(a)
bStr, bErr := toPrettyJSON(b)
if aErr != nil || bErr != nil {
aStr = dump.Pretty(a)
bStr = dump.Pretty(b)
}
diff := difflib.UnifiedDiff{
A: difflib.SplitLines(aStr),
B: difflib.SplitLines(bStr),
Context: 3,
}
diffstr, err := difflib.GetUnifiedDiffString(diff)
if err != nil {
return fmt.Sprintf("error generating diff: %v", err)
}
return diffstr
}
// toPrettyJSON converts an object to a pretty-printed JSON string.
func toPrettyJSON(data any) (string, error) {
jsonData, err := json.MarshalIndent(data, "", " ")
return string(jsonData), err
}
-67
View File
@@ -1,67 +0,0 @@
/*
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 diff
import (
"bytes"
"fmt"
"strings"
"text/tabwriter"
"k8s.io/apimachinery/pkg/util/dump"
)
// ObjectGoPrintSideBySide prints a and b as textual dumps side by side,
// enabling easy visual scanning for mismatches.
func ObjectGoPrintSideBySide(a, b interface{}) string {
sA := dump.Pretty(a)
sB := dump.Pretty(b)
linesA := strings.Split(sA, "\n")
linesB := strings.Split(sB, "\n")
width := 0
for _, s := range linesA {
l := len(s)
if l > width {
width = l
}
}
for _, s := range linesB {
l := len(s)
if l > width {
width = l
}
}
buf := &bytes.Buffer{}
w := tabwriter.NewWriter(buf, width, 0, 1, ' ', 0)
max := len(linesA)
if len(linesB) > max {
max = len(linesB)
}
for i := 0; i < max; i++ {
var a, b string
if i < len(linesA) {
a = linesA[i]
}
if i < len(linesB) {
b = linesB[i]
}
_, _ = fmt.Fprintf(w, "%s\t%s\n", a, b)
}
_ = w.Flush()
return buf.String()
}
-54
View File
@@ -1,54 +0,0 @@
/*
Copyright 2021 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 dump
import (
"github.com/davecgh/go-spew/spew"
)
var prettyPrintConfig = &spew.ConfigState{
Indent: " ",
DisableMethods: true,
DisablePointerAddresses: true,
DisableCapacities: true,
}
// The config MUST NOT be changed because that could change the result of a hash operation
var prettyPrintConfigForHash = &spew.ConfigState{
Indent: " ",
SortKeys: true,
DisableMethods: true,
SpewKeys: true,
DisablePointerAddresses: true,
DisableCapacities: true,
}
// Pretty wrap the spew.Sdump with Indent, and disabled methods like error() and String()
// The output may change over time, so for guaranteed output please take more direct control
func Pretty(a interface{}) string {
return prettyPrintConfig.Sdump(a)
}
// ForHash keeps the original Spew.Sprintf format to ensure the same checksum
func ForHash(a interface{}) string {
return prettyPrintConfigForHash.Sprintf("%#v", a)
}
// OneLine outputs the object in one line
func OneLine(a interface{}) string {
return prettyPrintConfig.Sprintf("%#v", a)
}
@@ -1,24 +0,0 @@
//go:build kubernetes_protomessage_one_more_release
// +build kubernetes_protomessage_one_more_release
/*
Copyright 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.
*/
// Code generated by go-to-protobuf. DO NOT EDIT.
package intstr
func (*IntOrString) ProtoMessage() {}
-1
View File
@@ -1,5 +1,4 @@
//go:build !notest
// +build !notest
/*
Copyright 2020 The Kubernetes Authors.
-160
View File
@@ -1,160 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package jsonmergepatch
import (
"fmt"
"reflect"
"gopkg.in/evanphx/json-patch.v4"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/mergepatch"
)
// Create a 3-way merge patch based-on JSON merge patch.
// Calculate addition-and-change patch between current and modified.
// Calculate deletion patch between original and modified.
func CreateThreeWayJSONMergePatch(original, modified, current []byte, fns ...mergepatch.PreconditionFunc) ([]byte, error) {
if len(original) == 0 {
original = []byte(`{}`)
}
if len(modified) == 0 {
modified = []byte(`{}`)
}
if len(current) == 0 {
current = []byte(`{}`)
}
addAndChangePatch, err := jsonpatch.CreateMergePatch(current, modified)
if err != nil {
return nil, err
}
// Only keep addition and changes
addAndChangePatch, addAndChangePatchObj, err := keepOrDeleteNullInJsonPatch(addAndChangePatch, false)
if err != nil {
return nil, err
}
deletePatch, err := jsonpatch.CreateMergePatch(original, modified)
if err != nil {
return nil, err
}
// Only keep deletion
deletePatch, deletePatchObj, err := keepOrDeleteNullInJsonPatch(deletePatch, true)
if err != nil {
return nil, err
}
hasConflicts, err := mergepatch.HasConflicts(addAndChangePatchObj, deletePatchObj)
if err != nil {
return nil, err
}
if hasConflicts {
return nil, mergepatch.NewErrConflict(mergepatch.ToYAMLOrError(addAndChangePatchObj), mergepatch.ToYAMLOrError(deletePatchObj))
}
patch, err := jsonpatch.MergePatch(deletePatch, addAndChangePatch)
if err != nil {
return nil, err
}
var patchMap map[string]interface{}
err = json.Unmarshal(patch, &patchMap)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal patch for precondition check: %s", patch)
}
meetPreconditions, err := meetPreconditions(patchMap, fns...)
if err != nil {
return nil, err
}
if !meetPreconditions {
return nil, mergepatch.NewErrPreconditionFailed(patchMap)
}
return patch, nil
}
// keepOrDeleteNullInJsonPatch takes a json-encoded byte array and a boolean.
// It returns a filtered object and its corresponding json-encoded byte array.
// It is a wrapper of func keepOrDeleteNullInObj
func keepOrDeleteNullInJsonPatch(patch []byte, keepNull bool) ([]byte, map[string]interface{}, error) {
var patchMap map[string]interface{}
err := json.Unmarshal(patch, &patchMap)
if err != nil {
return nil, nil, err
}
filteredMap, err := keepOrDeleteNullInObj(patchMap, keepNull)
if err != nil {
return nil, nil, err
}
o, err := json.Marshal(filteredMap)
return o, filteredMap, err
}
// keepOrDeleteNullInObj will keep only the null value and delete all the others,
// if keepNull is true. Otherwise, it will delete all the null value and keep the others.
func keepOrDeleteNullInObj(m map[string]interface{}, keepNull bool) (map[string]interface{}, error) {
filteredMap := make(map[string]interface{})
var err error
for key, val := range m {
switch {
case keepNull && val == nil:
filteredMap[key] = nil
case val != nil:
switch typedVal := val.(type) {
case map[string]interface{}:
// Explicitly-set empty maps are treated as values instead of empty patches
if len(typedVal) == 0 {
if !keepNull {
filteredMap[key] = typedVal
}
continue
}
var filteredSubMap map[string]interface{}
filteredSubMap, err = keepOrDeleteNullInObj(typedVal, keepNull)
if err != nil {
return nil, err
}
// If the returned filtered submap was empty, this is an empty patch for the entire subdict, so the key
// should not be set
if len(filteredSubMap) != 0 {
filteredMap[key] = filteredSubMap
}
case []interface{}, string, float64, bool, int64, nil:
// Lists are always replaced in Json, no need to check each entry in the list.
if !keepNull {
filteredMap[key] = val
}
default:
return nil, fmt.Errorf("unknown type: %v", reflect.TypeOf(typedVal))
}
}
}
return filteredMap, nil
}
func meetPreconditions(patchObj map[string]interface{}, fns ...mergepatch.PreconditionFunc) (bool, error) {
// Apply the preconditions to the patch, and return an error if any of them fail.
for _, fn := range fns {
if !fn(patchObj) {
return false, fmt.Errorf("precondition failed for: %v", patchObj)
}
}
return true, nil
}
+1 -2
View File
@@ -17,7 +17,6 @@ limitations under the License.
package managedfields
import (
"bytes"
"fmt"
"sigs.k8s.io/structured-merge-diff/v6/fieldpath"
@@ -65,7 +64,7 @@ func ExtractInto(object runtime.Object, objectType typed.ParseableType, fieldMan
return nil
}
fieldset := &fieldpath.Set{}
err = fieldset.FromJSON(bytes.NewReader(fieldsEntry.FieldsV1.Raw))
err = fieldset.FromJSON(fieldsEntry.FieldsV1.GetRawReader())
if err != nil {
return fmt.Errorf("error marshalling FieldsV1 to JSON: %w", err)
}
+3 -4
View File
@@ -17,8 +17,6 @@ limitations under the License.
package internal
import (
"bytes"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/structured-merge-diff/v6/fieldpath"
@@ -36,12 +34,13 @@ var EmptyFields = func() metav1.FieldsV1 {
// FieldsToSet creates a set paths from an input trie of fields
func FieldsToSet(f metav1.FieldsV1) (s fieldpath.Set, err error) {
err = s.FromJSON(bytes.NewReader(f.Raw))
err = s.FromJSON(f.GetRawReader())
return s, err
}
// SetToFields creates a trie of fields from an input set of paths
func SetToFields(s fieldpath.Set) (f metav1.FieldsV1, err error) {
f.Raw, err = s.ToJSON()
raw, err := s.ToJSON()
f.SetRawBytes(raw)
return f, err
}
+1 -1
View File
@@ -20,7 +20,7 @@ import (
"fmt"
"reflect"
"k8s.io/apimachinery/pkg/util/dump"
"k8s.io/utils/dump"
"sigs.k8s.io/yaml"
)
+5
View File
@@ -132,9 +132,11 @@ func SetTransportDefaults(t *http.Transport) *http.Transport {
t = SetOldTransportDefaults(t)
// Allow clients to disable http2 if needed.
if s := os.Getenv("DISABLE_HTTP2"); len(s) > 0 {
//nolint:logcheck // Should be rare, not worth converting.
klog.Info("HTTP2 has been explicitly disabled")
} else if allowsHTTP2(t) {
if err := configureHTTP2Transport(t); err != nil {
//nolint:logcheck // Should be rare, not worth converting.
klog.Warningf("Transport failed http2 configuration: %v", err)
}
}
@@ -148,6 +150,7 @@ func readIdleTimeoutSeconds() int {
if s := os.Getenv("HTTP2_READ_IDLE_TIMEOUT_SECONDS"); len(s) > 0 {
i, err := strconv.Atoi(s)
if err != nil {
//nolint:logcheck // Should be rare, not worth converting.
klog.Warningf("Illegal HTTP2_READ_IDLE_TIMEOUT_SECONDS(%q): %v."+
" Default value %d is used", s, err, ret)
return ret
@@ -162,6 +165,7 @@ func pingTimeoutSeconds() int {
if s := os.Getenv("HTTP2_PING_TIMEOUT_SECONDS"); len(s) > 0 {
i, err := strconv.Atoi(s)
if err != nil {
//nolint:logcheck // Should be rare, not worth converting.
klog.Warningf("Illegal HTTP2_PING_TIMEOUT_SECONDS(%q): %v."+
" Default value %d is used", s, err, ret)
return ret
@@ -256,6 +260,7 @@ func CloseIdleConnectionsFor(transport http.RoundTripper) {
case RoundTripperWrapper:
CloseIdleConnectionsFor(transport.WrappedRoundTripper())
default:
//nolint:logcheck // Should be rare, not worth converting.
klog.Warningf("unknown transport type: %T", transport)
}
}
+72 -40
View File
@@ -201,12 +201,12 @@ func parseIP(str string, family AddressFamily) (net.IP, error) {
return net.IP(bytes), nil
}
func isInterfaceUp(intf *net.Interface) bool {
func isInterfaceUp(logger klog.Logger, intf *net.Interface) bool {
if intf == nil {
return false
}
if intf.Flags&net.FlagUp != 0 {
klog.V(4).Infof("Interface %v is up", intf.Name)
logger.V(4).Info("Interface is up", "interface", intf.Name)
return true
}
return false
@@ -218,23 +218,23 @@ func isLoopbackOrPointToPoint(intf *net.Interface) bool {
// getMatchingGlobalIP returns the first valid global unicast address of the given
// 'family' from the list of 'addrs'.
func getMatchingGlobalIP(addrs []net.Addr, family AddressFamily) (net.IP, error) {
func getMatchingGlobalIP(logger klog.Logger, addrs []net.Addr, family AddressFamily) (net.IP, error) {
if len(addrs) > 0 {
for i := range addrs {
klog.V(4).Infof("Checking addr %s.", addrs[i].String())
logger.V(4).Info("Checking for matching global IP", "address", addrs[i])
ip, _, err := netutils.ParseCIDRSloppy(addrs[i].String())
if err != nil {
return nil, err
}
if memberOf(ip, family) {
if ip.IsGlobalUnicast() {
klog.V(4).Infof("IP found %v", ip)
logger.V(4).Info("IP found", "IP", ip)
return ip, nil
} else {
klog.V(4).Infof("Non-global unicast address found %v", ip)
logger.V(4).Info("Non-global unicast address found", "IP", ip)
}
} else {
klog.V(4).Infof("%v is not an IPv%d address", ip, int(family))
logger.V(4).Info("IP address has wrong version", "IP", ip, "IPVersion", int(family))
}
}
@@ -244,23 +244,23 @@ func getMatchingGlobalIP(addrs []net.Addr, family AddressFamily) (net.IP, error)
// getIPFromInterface gets the IPs on an interface and returns a global unicast address, if any. The
// interface must be up, the IP must in the family requested, and the IP must be a global unicast address.
func getIPFromInterface(intfName string, forFamily AddressFamily, nw networkInterfacer) (net.IP, error) {
func getIPFromInterface(logger klog.Logger, intfName string, forFamily AddressFamily, nw networkInterfacer) (net.IP, error) {
intf, err := nw.InterfaceByName(intfName)
if err != nil {
return nil, err
}
if isInterfaceUp(intf) {
if isInterfaceUp(logger, intf) {
addrs, err := nw.Addrs(intf)
if err != nil {
return nil, err
}
klog.V(4).Infof("Interface %q has %d addresses :%v.", intfName, len(addrs), addrs)
matchingIP, err := getMatchingGlobalIP(addrs, forFamily)
logger.V(4).Info("Found addresses for interface", "interface", intfName, "numAddresses", len(addrs), "addresses", addrs)
matchingIP, err := getMatchingGlobalIP(logger, addrs, forFamily)
if err != nil {
return nil, err
}
if matchingIP != nil {
klog.V(4).Infof("Found valid IPv%d address %v for interface %q.", int(forFamily), matchingIP, intfName)
logger.V(4).Info("Found valid address", "IPVersion", int(forFamily), "IP", matchingIP, "interface", intfName)
return matchingIP, nil
}
}
@@ -269,13 +269,13 @@ func getIPFromInterface(intfName string, forFamily AddressFamily, nw networkInte
// getIPFromLoopbackInterface gets the IPs on a loopback interface and returns a global unicast address, if any.
// The loopback interface must be up, the IP must in the family requested, and the IP must be a global unicast address.
func getIPFromLoopbackInterface(forFamily AddressFamily, nw networkInterfacer) (net.IP, error) {
func getIPFromLoopbackInterface(logger klog.Logger, forFamily AddressFamily, nw networkInterfacer) (net.IP, error) {
intfs, err := nw.Interfaces()
if err != nil {
return nil, err
}
for _, intf := range intfs {
if !isInterfaceUp(&intf) {
if !isInterfaceUp(logger, &intf) {
continue
}
if intf.Flags&(net.FlagLoopback) != 0 {
@@ -283,13 +283,13 @@ func getIPFromLoopbackInterface(forFamily AddressFamily, nw networkInterfacer) (
if err != nil {
return nil, err
}
klog.V(4).Infof("Interface %q has %d addresses :%v.", intf.Name, len(addrs), addrs)
matchingIP, err := getMatchingGlobalIP(addrs, forFamily)
logger.V(4).Info("Found addresses for interface", "interface", intf.Name, "numAddresses", len(addrs), "addresses", addrs)
matchingIP, err := getMatchingGlobalIP(logger, addrs, forFamily)
if err != nil {
return nil, err
}
if matchingIP != nil {
klog.V(4).Infof("Found valid IPv%d address %v for interface %q.", int(forFamily), matchingIP, intf.Name)
logger.V(4).Info("Found valid address", "IPVersion", int(forFamily), "IP", matchingIP, "interface", intf.Name)
return matchingIP, nil
}
}
@@ -309,7 +309,7 @@ func memberOf(ip net.IP, family AddressFamily) bool {
// chooseIPFromHostInterfaces looks at all system interfaces, trying to find one that is up that
// has a global unicast address (non-loopback, non-link local, non-point2point), and returns the IP.
// addressFamilies determines whether it prefers IPv4 or IPv6
func chooseIPFromHostInterfaces(nw networkInterfacer, addressFamilies AddressFamilyPreference) (net.IP, error) {
func chooseIPFromHostInterfaces(logger klog.Logger, nw networkInterfacer, addressFamilies AddressFamilyPreference) (net.IP, error) {
intfs, err := nw.Interfaces()
if err != nil {
return nil, err
@@ -318,14 +318,14 @@ func chooseIPFromHostInterfaces(nw networkInterfacer, addressFamilies AddressFam
return nil, fmt.Errorf("no interfaces found on host.")
}
for _, family := range addressFamilies {
klog.V(4).Infof("Looking for system interface with a global IPv%d address", uint(family))
logger.V(4).Info("Looking for system interface with a global address", "IPVersion", uint(family))
for _, intf := range intfs {
if !isInterfaceUp(&intf) {
klog.V(4).Infof("Skipping: down interface %q", intf.Name)
if !isInterfaceUp(logger, &intf) {
logger.V(4).Info("Skipping: interface is down", "interface", intf.Name)
continue
}
if isLoopbackOrPointToPoint(&intf) {
klog.V(4).Infof("Skipping: LB or P2P interface %q", intf.Name)
logger.V(4).Info("Skipping: is LB or P2P", "interface", intf.Name)
continue
}
addrs, err := nw.Addrs(&intf)
@@ -333,7 +333,7 @@ func chooseIPFromHostInterfaces(nw networkInterfacer, addressFamilies AddressFam
return nil, err
}
if len(addrs) == 0 {
klog.V(4).Infof("Skipping: no addresses on interface %q", intf.Name)
logger.V(4).Info("Skipping: no addresses", "interface", intf.Name)
continue
}
for _, addr := range addrs {
@@ -342,15 +342,15 @@ func chooseIPFromHostInterfaces(nw networkInterfacer, addressFamilies AddressFam
return nil, fmt.Errorf("unable to parse CIDR for interface %q: %s", intf.Name, err)
}
if !memberOf(ip, family) {
klog.V(4).Infof("Skipping: no address family match for %q on interface %q.", ip, intf.Name)
logger.V(4).Info("Skipping: no address family match", "IP", ip, "interface", intf.Name)
continue
}
// TODO: Decide if should open up to allow IPv6 LLAs in future.
if !ip.IsGlobalUnicast() {
klog.V(4).Infof("Skipping: non-global address %q on interface %q.", ip, intf.Name)
logger.V(4).Info("Skipping: non-global address", "IP", ip, "interface", intf.Name)
continue
}
klog.V(4).Infof("Found global unicast address %q on interface %q.", ip, intf.Name)
logger.V(4).Info("Found global unicast address", "IP", ip, "interface", intf.Name)
return ip, nil
}
}
@@ -363,20 +363,31 @@ func chooseIPFromHostInterfaces(nw networkInterfacer, addressFamilies AddressFam
// interfaces. Otherwise, it will use IPv4 and IPv6 route information to return the
// IP of the interface with a gateway on it (with priority given to IPv4). For a node
// with no internet connection, it returns error.
//
//logcheck:context // [ChooseHostInterfaceWithLogger] should be used instead of ChooseHostInterface in code which supports contextual logging.
func ChooseHostInterface() (net.IP, error) {
return chooseHostInterface(preferIPv4)
return ChooseHostInterfaceWithLogger(klog.Background())
}
func chooseHostInterface(addressFamilies AddressFamilyPreference) (net.IP, error) {
// ChooseHostInterfaceWithLogger is a method used fetch an IP for a daemon.
// If there is no routing info file, it will choose a global IP from the system
// interfaces. Otherwise, it will use IPv4 and IPv6 route information to return the
// IP of the interface with a gateway on it (with priority given to IPv4). For a node
// with no internet connection, it returns error.
func ChooseHostInterfaceWithLogger(logger klog.Logger) (net.IP, error) {
return chooseHostInterface(logger, preferIPv4)
}
func chooseHostInterface(logger klog.Logger, addressFamilies AddressFamilyPreference) (net.IP, error) {
var nw networkInterfacer = networkInterface{}
if _, err := os.Stat(ipv4RouteFile); os.IsNotExist(err) {
return chooseIPFromHostInterfaces(nw, addressFamilies)
return chooseIPFromHostInterfaces(logger, nw, addressFamilies)
}
routes, err := getAllDefaultRoutes()
if err != nil {
return nil, err
}
return chooseHostInterfaceFromRoute(routes, nw, addressFamilies)
return chooseHostInterfaceFromRoute(logger, routes, nw, addressFamilies)
}
// networkInterfacer defines an interface for several net library functions. Production
@@ -427,36 +438,36 @@ func getAllDefaultRoutes() ([]Route, error) {
// global IP address from the interface for the route. If there are routes but no global
// address is obtained from the interfaces, it checks if the loopback interface has a global address.
// addressFamilies determines whether it prefers IPv4 or IPv6
func chooseHostInterfaceFromRoute(routes []Route, nw networkInterfacer, addressFamilies AddressFamilyPreference) (net.IP, error) {
func chooseHostInterfaceFromRoute(logger klog.Logger, routes []Route, nw networkInterfacer, addressFamilies AddressFamilyPreference) (net.IP, error) {
for _, family := range addressFamilies {
klog.V(4).Infof("Looking for default routes with IPv%d addresses", uint(family))
logger.V(4).Info("Looking for default routes with IP addresses", "IPVersion", uint(family))
for _, route := range routes {
if route.Family != family {
continue
}
klog.V(4).Infof("Default route transits interface %q", route.Interface)
finalIP, err := getIPFromInterface(route.Interface, family, nw)
logger.V(4).Info("Default route transits interface", "interface", route.Interface)
finalIP, err := getIPFromInterface(logger, route.Interface, family, nw)
if err != nil {
return nil, err
}
if finalIP != nil {
klog.V(4).Infof("Found active IP %v ", finalIP)
logger.V(4).Info("Found active IP", "IP", finalIP)
return finalIP, nil
}
// In case of network setups where default routes are present, but network
// interfaces use only link-local addresses (e.g. as described in RFC5549).
// the global IP is assigned to the loopback interface, and we should use it
loopbackIP, err := getIPFromLoopbackInterface(family, nw)
loopbackIP, err := getIPFromLoopbackInterface(logger, family, nw)
if err != nil {
return nil, err
}
if loopbackIP != nil {
klog.V(4).Infof("Found active IP %v on Loopback interface", loopbackIP)
logger.V(4).Info("Found active IP on Loopback interface", "IP", loopbackIP)
return loopbackIP, nil
}
}
}
klog.V(4).Infof("No active IP found by looking at default routes")
logger.V(4).Info("No active IP found by looking at default routes")
return nil, fmt.Errorf("unable to select an IP from default routes.")
}
@@ -465,14 +476,25 @@ func chooseHostInterfaceFromRoute(routes []Route, nw networkInterfacer, addressF
// If bindAddress is unspecified or loopback, it returns the default IP of the same
// address family as bindAddress.
// Otherwise, it just returns bindAddress.
//
//logcheck:context // [ResolveBindAddressWithLogger] should be used instead of ResolveBindAddress in code which supports contextual logging.
func ResolveBindAddress(bindAddress net.IP) (net.IP, error) {
return ResolveBindAddressWithLogger(klog.Background(), bindAddress)
}
// ResolveBindAddressWithLogger returns the IP address of a daemon, based on the given bindAddress:
// If bindAddress is unset, it returns the host's default IP, as with ChooseHostInterface().
// If bindAddress is unspecified or loopback, it returns the default IP of the same
// address family as bindAddress.
// Otherwise, it just returns bindAddress.
func ResolveBindAddressWithLogger(logger klog.Logger, bindAddress net.IP) (net.IP, error) {
addressFamilies := preferIPv4
if bindAddress != nil && memberOf(bindAddress, familyIPv6) {
addressFamilies = preferIPv6
}
if bindAddress == nil || bindAddress.IsUnspecified() || bindAddress.IsLoopback() {
hostIP, err := chooseHostInterface(addressFamilies)
hostIP, err := chooseHostInterface(logger, addressFamilies)
if err != nil {
return nil, err
}
@@ -485,10 +507,20 @@ func ResolveBindAddress(bindAddress net.IP) (net.IP, error) {
// This is required in case of network setups where default routes are present, but network
// interfaces use only link-local addresses (e.g. as described in RFC5549).
// e.g when using BGP to announce a host IP over link-local ip addresses and this ip address is attached to the lo interface.
//
//logcheck:context // [ChooseBindAddressForInterfaceWithLogger] should be used instead of ChooseBindAddressForInterface in code which supports contextual logging.
func ChooseBindAddressForInterface(intfName string) (net.IP, error) {
return ChooseBindAddressForInterfaceWithLogger(klog.Background(), intfName)
}
// ChooseBindAddressForInterfaceWithLogger choose a global IP for a specific interface, with priority given to IPv4.
// This is required in case of network setups where default routes are present, but network
// interfaces use only link-local addresses (e.g. as described in RFC5549).
// e.g when using BGP to announce a host IP over link-local ip addresses and this ip address is attached to the lo interface.
func ChooseBindAddressForInterfaceWithLogger(logger klog.Logger, intfName string) (net.IP, error) {
var nw networkInterfacer = networkInterface{}
for _, family := range preferIPv4 {
ip, err := getIPFromInterface(intfName, family, nw)
ip, err := getIPFromInterface(logger, intfName, family, nw)
if err != nil {
return nil, err
}
+32 -1
View File
@@ -17,14 +17,17 @@ limitations under the License.
package runtime
import (
"bytes"
"context"
"fmt"
"net/http"
"runtime"
"strings"
"sync"
"time"
"k8s.io/klog/v2"
"k8s.io/klog/v2/textlogger"
)
var (
@@ -55,7 +58,6 @@ func HandleCrash(additionalHandlers ...func(interface{})) {
if r := recover(); r != nil {
additionalHandlersWithContext := make([]func(context.Context, interface{}), len(additionalHandlers))
for i, handler := range additionalHandlers {
handler := handler // capture loop variable
additionalHandlersWithContext[i] = func(_ context.Context, r interface{}) {
handler(r)
}
@@ -155,8 +157,37 @@ var ErrorHandlers = []ErrorHandler{
backoffError(1 * time.Millisecond),
}
// ErrorHandler is called indirectly through [HandleError], [HandleErrorWithContext] or [HandleErrorWithLogger].
// It is passed the same parameters that a structured logging backend needs to log a problem.
// It follows the semantic described for [HandleErrorWithContext] and [logr.Logger.Error]:
// - err is optional and may be nil
// - msg is string that describes the problem
// - keysAndValues contains additional information that varies between different occurrences of the problem
//
// [ErrorToString] can be used to convert these parameters into a single string, using the klog text output.
type ErrorHandler func(ctx context.Context, err error, msg string, keysAndValues ...interface{})
// ErrorToString takes the parameters passed to [ErrorHandler] and
// formats them as a string using the klog text output.
//
// If any of the values is a multi-line string, then the resulting
// string also uses line breaks and indention for the sake of readability.
// Does not include a trailing newline.
//
// Use errors.New if an error instead of a string is needed.
func ErrorToString(err error, msg string, keysAndValues ...interface{}) string {
var buffer bytes.Buffer
config := textlogger.NewConfig(
textlogger.Output(&buffer),
textlogger.WithHeader(false),
)
logger := textlogger.NewLogger(config)
logger.Error(err, msg, keysAndValues...)
result := buffer.String()
result = strings.TrimSpace(result)
return result
}
// HandlerError is a method to invoke when a non-user facing piece of code cannot
// return an error and needs to indicate it has been ignored. Invoking this method
// is preferable to logging the error - the default behavior is to log but the
+2 -6
View File
@@ -1827,9 +1827,7 @@ func (ss SortableSliceOfMaps) Less(i, j int) bool {
}
func (ss SortableSliceOfMaps) Swap(i, j int) {
tmp := ss.s[i]
ss.s[i] = ss.s[j]
ss.s[j] = tmp
ss.s[i], ss.s[j] = ss.s[j], ss.s[i]
}
func deduplicateAndSortScalars(s []interface{}) []interface{} {
@@ -1875,9 +1873,7 @@ func (ss SortableSliceOfScalars) Less(i, j int) bool {
}
func (ss SortableSliceOfScalars) Swap(i, j int) {
tmp := ss.s[i]
ss.s[i] = ss.s[j]
ss.s[j] = tmp
ss.s[i], ss.s[j] = ss.s[j], ss.s[i]
}
// Returns the type of the elements of N slice(s). If the type is different,
+36 -4
View File
@@ -40,10 +40,12 @@ type ErrorMatcher struct {
matchField bool
// TODO(thockin): consider whether value could be assumed - if the
// "want" error has a nil value, don't match on value.
matchValue bool
matchOrigin bool
matchDetail func(want, got string) bool
requireOriginWhenInvalid bool
matchValue bool
matchOrigin bool
matchDetail func(want, got string) bool
requireOriginWhenInvalid bool
matchValidationStabilityLevel bool
matchSource bool
// normalizationRules holds the pre-compiled regex patterns for path normalization.
normalizationRules []NormalizationRule
}
@@ -86,6 +88,14 @@ func (m ErrorMatcher) Matches(want, got *Error) bool {
if m.matchDetail != nil && !m.matchDetail(want.Detail, got.Detail) {
return false
}
if m.matchValidationStabilityLevel && want.ValidationStabilityLevel != got.ValidationStabilityLevel {
return false
}
if m.matchSource && want.FromImperative != got.FromImperative {
return false
}
return true
}
@@ -148,6 +158,14 @@ func (m ErrorMatcher) Render(e *Error) string {
comma()
buf.WriteString(fmt.Sprintf("Detail=%q", e.Detail))
}
if m.matchValidationStabilityLevel {
comma()
buf.WriteString(fmt.Sprintf("ValidationStabilityLevel=%s", e.ValidationStabilityLevel))
}
if m.matchSource {
comma()
buf.WriteString(fmt.Sprintf("FromImperative=%t", e.FromImperative))
}
return "{" + buf.String() + "}"
}
@@ -224,6 +242,20 @@ func (m ErrorMatcher) RequireOriginWhenInvalid() ErrorMatcher {
return m
}
// BySource returns a derived ErrorMatcher which also matches by the error origination
// value of field errors.
func (m ErrorMatcher) BySource() ErrorMatcher {
m.matchSource = true
return m
}
// ByValidationStabilityLevel returns a derived ErrorMatcher which also matches by the validation stability level
// value of field errors.
func (m ErrorMatcher) ByValidationStabilityLevel() ErrorMatcher {
m.matchValidationStabilityLevel = true
return m
}
// ByDetailExact returns a derived ErrorMatcher which also matches errors by
// the exact detail string.
func (m ErrorMatcher) ByDetailExact() ErrorMatcher {
+219 -13
View File
@@ -42,7 +42,7 @@ type Error struct {
// The value should be either:
// - A simple camelCase identifier (e.g., "maximum", "maxItems")
// - A structured format using "format=<dash-style-identifier>" for validation errors related to specific formats
// (e.g., "format=dns-label", "format=qualified-name")
// (e.g. "format=k8s-short-name")
//
// If the Origin corresponds to an existing declarative validation tag or JSON Schema keyword,
// use that same name for consistency.
@@ -55,10 +55,46 @@ type Error struct {
// validation. This field is to identify errors from imperative validation
// that should also be caught by declarative validation.
CoveredByDeclarative bool
// FromImperative denotes these errors are originating from the hand written validations.
FromImperative bool
// ValidationStabilityLevel denotes the validation stability level of the declarative validation from this error is returned. This should be used in the declarative validations only.
ValidationStabilityLevel ValidationStabilityLevel
}
// ValidationStabilityLevel denotes the stability level of a validation.
type ValidationStabilityLevel int
const (
stabilityLevelUnknown ValidationStabilityLevel = iota
stabilityLevelAlpha
stabilityLevelBeta
)
func (v ValidationStabilityLevel) String() string {
switch v {
case stabilityLevelAlpha:
return "alpha"
case stabilityLevelBeta:
return "beta"
default:
return "unknown"
}
}
var _ error = &Error{}
// IsAlpha returns true if the error is an alpha validation error.
func (e *Error) IsAlpha() bool {
return e.ValidationStabilityLevel == stabilityLevelAlpha
}
// IsBeta returns true if the error is a beta validation error.
func (e *Error) IsBeta() bool {
return e.ValidationStabilityLevel == stabilityLevelBeta
}
// Error implements the error interface.
func (e *Error) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.ErrorBody())
@@ -73,10 +109,10 @@ var omitValue = OmitValueType{}
func (e *Error) ErrorBody() string {
var s string
switch e.Type {
case ErrorTypeRequired, ErrorTypeForbidden, ErrorTypeTooLong, ErrorTypeInternal:
case ErrorTypeRequired, ErrorTypeForbidden, ErrorTypeTooLong, ErrorTypeTooShort, ErrorTypeInternal:
s = e.Type.String()
case ErrorTypeInvalid, ErrorTypeTypeInvalid, ErrorTypeNotSupported,
ErrorTypeNotFound, ErrorTypeDuplicate, ErrorTypeTooMany:
ErrorTypeNotFound, ErrorTypeDuplicate, ErrorTypeTooMany, ErrorTypeTooFew:
if e.BadValue == omitValue {
s = e.Type.String()
break
@@ -113,6 +149,7 @@ func (e *Error) ErrorBody() string {
if len(e.Detail) != 0 {
s += fmt.Sprintf(": %s", e.Detail)
}
return s
}
@@ -164,11 +201,18 @@ const (
// report that a given list has too many items. This is similar to FieldValueTooLong,
// but the error indicates quantity instead of length.
ErrorTypeTooMany ErrorType = "FieldValueTooMany"
// ErrorTypeTooFew is used to report "too few". This is used to
// report that a given list has too few items. This is similar to FieldValueTooLong,
// but the error indicates quantity instead of length.
ErrorTypeTooFew ErrorType = "FieldValueTooFew"
// ErrorTypeInternal is used to report other errors that are not related
// to user input. See InternalError().
ErrorTypeInternal ErrorType = "InternalError"
// ErrorTypeTypeInvalid is for the value did not match the schema type for that field
ErrorTypeTypeInvalid ErrorType = "FieldValueTypeInvalid"
// ErrorTypeTooShort is used to report that the given value is too short.
// This is similar to ErrorTypeInvalid. See TooShort().
ErrorTypeTooShort ErrorType = "FieldValueTooShort"
)
// String converts a ErrorType into its corresponding canonical error message.
@@ -190,10 +234,14 @@ func (t ErrorType) String() string {
return "Too long"
case ErrorTypeTooMany:
return "Too many"
case ErrorTypeTooFew:
return "Too few"
case ErrorTypeInternal:
return "Internal error"
case ErrorTypeTypeInvalid:
return "Invalid value"
case ErrorTypeTooShort:
return "Too short"
default:
return fmt.Sprintf("<unknown error %q>", string(t))
}
@@ -201,32 +249,56 @@ func (t ErrorType) String() string {
// TypeInvalid returns a *Error indicating "type is invalid"
func TypeInvalid(field *Path, value interface{}, detail string) *Error {
return &Error{ErrorTypeTypeInvalid, field.String(), value, detail, "", false}
return &Error{
Type: ErrorTypeTypeInvalid,
Field: field.String(),
BadValue: value,
Detail: detail,
}
}
// 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, "", "", false}
return &Error{
Type: ErrorTypeNotFound,
Field: field.String(),
BadValue: 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, "", false}
return &Error{
Type: ErrorTypeRequired,
Field: field.String(),
Detail: detail,
BadValue: "",
}
}
// 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, "", "", false}
return &Error{
Type: ErrorTypeDuplicate,
Field: field.String(),
BadValue: 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, "", false}
return &Error{
Type: ErrorTypeInvalid,
Field: field.String(),
BadValue: value,
Detail: detail,
}
}
// NotSupported returns a *Error indicating "unsupported value".
@@ -241,7 +313,12 @@ func NotSupported[T ~string](field *Path, value interface{}, validValues []T) *E
}
detail = "supported values: " + strings.Join(quotedValues, ", ")
}
return &Error{ErrorTypeNotSupported, field.String(), value, detail, "", false}
return &Error{
Type: ErrorTypeNotSupported,
Field: field.String(),
BadValue: value,
Detail: detail,
}
}
// Forbidden returns a *Error indicating "forbidden". This is used to
@@ -249,7 +326,12 @@ func NotSupported[T ~string](field *Path, value interface{}, validValues []T) *E
// 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, "", false}
return &Error{
Type: ErrorTypeForbidden,
Field: field.String(),
Detail: detail,
BadValue: "",
}
}
// TooLong returns a *Error indicating "too long". This is used to report that
@@ -267,7 +349,35 @@ func TooLong(field *Path, _ interface{}, maxLength int) *Error {
} else {
msg = "value is too long"
}
return &Error{ErrorTypeTooLong, field.String(), "<value omitted>", msg, "", false}
return &Error{
Type: ErrorTypeTooLong,
Field: field.String(),
BadValue: "<value omitted>",
Detail: msg,
}
}
// TooLongCharacters returns a *Error indicating "too long". This is used to report that
// the given value is too long in characters (including multi-byte characters).
// This is similar to Invalid, but the returned error will not include the too-long value.
// If maxLength is negative, it will be included in the message. The value argument is not used.
func TooLongCharacters[T ~string](field *Path, _ T, maxLength int) *Error {
var msg string
if maxLength >= 0 {
bs := "characters"
if maxLength == 1 {
bs = "character"
}
msg = fmt.Sprintf("may not be more than %d %s", maxLength, bs)
} else {
msg = "value is too long"
}
return &Error{
Type: ErrorTypeTooLong,
Field: field.String(),
BadValue: "<value omitted>",
Detail: msg,
}
}
// TooLongMaxLength returns a *Error indicating "too long".
@@ -299,14 +409,46 @@ func TooMany(field *Path, actualQuantity, maxQuantity int) *Error {
actual = omitValue
}
return &Error{ErrorTypeTooMany, field.String(), actual, msg, "", false}
return &Error{
Type: ErrorTypeTooMany,
Field: field.String(),
BadValue: actual,
Detail: msg,
}
}
// 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(), "", false}
return &Error{
Type: ErrorTypeInternal,
Field: field.String(),
BadValue: err,
Detail: err.Error(),
}
}
// TooShort returns a *Error indicating "too short". This is used to report that
// the given value is too short in characters. This is similar to Invalid.
// If minLength is non-negative, it will be included in the message.
func TooShort[T ~string](field *Path, value T, minLength int) *Error {
var msg string
if minLength >= 0 {
bs := "characters"
if minLength == 1 {
bs = "character"
}
msg = fmt.Sprintf("must be at least %d %s", minLength, bs)
} else {
msg = "value is too short"
}
return &Error{
Type: ErrorTypeTooShort,
Field: field.String(),
BadValue: value,
Detail: msg,
}
}
// ErrorList holds a set of Errors. It is plausible that we might one day have
@@ -397,6 +539,46 @@ func (list ErrorList) ExtractCoveredByDeclarative() ErrorList {
return newList
}
// MarkAlpha marks the error as an alpha validation error.
func (e *Error) MarkAlpha() *Error {
e.ValidationStabilityLevel = stabilityLevelAlpha
return e
}
// MarkAlpha marks the errors as alpha validation errors.
func (list ErrorList) MarkAlpha() ErrorList {
for _, err := range list {
err.ValidationStabilityLevel = stabilityLevelAlpha
}
return list
}
// MarkBeta marks the error as a beta validation error.
func (e *Error) MarkBeta() *Error {
e.ValidationStabilityLevel = stabilityLevelBeta
return e
}
// MarkBeta marks the errors as beta validation errors.
func (list ErrorList) MarkBeta() ErrorList {
for _, err := range list {
err.ValidationStabilityLevel = stabilityLevelBeta
}
return list
}
func (e *Error) MarkFromImperative() *Error {
e.FromImperative = true
return e
}
func (list ErrorList) MarkFromImperative() ErrorList {
for _, err := range list {
err.FromImperative = true
}
return list
}
// RemoveCoveredByDeclarative returns a new ErrorList containing only the errors that should not be covered by declarative validation.
func (list ErrorList) RemoveCoveredByDeclarative() ErrorList {
newList := ErrorList{}
@@ -407,3 +589,27 @@ func (list ErrorList) RemoveCoveredByDeclarative() ErrorList {
}
return newList
}
// TooFew returns a *Error indicating "too few". This is used to
// report that a given list has too few items. This is similar to TooLong,
// but the returned error indicates quantity instead of length.
func TooFew(field *Path, actualQuantity, minQuantity int) *Error {
var msg string
if minQuantity >= 0 {
is := "items"
if minQuantity == 1 {
is = "item"
}
msg = fmt.Sprintf("must have at least %d %s", minQuantity, is)
} else {
msg = "has too few items"
}
return &Error{
Type: ErrorTypeTooFew,
Field: field.String(),
BadValue: actualQuantity,
Detail: msg,
}
}
+2 -2
View File
@@ -115,7 +115,7 @@ func GetWarningsForIP(fldPath *field.Path, value string) []string {
// ParseAddr() doesn't) or IPv4-mapped IPv6 (.Is4In6()). Either way,
// re-stringifying the net.IP value will give the preferred form.
return []string{
fmt.Sprintf("%s: non-standard IP address %q will be considered invalid in a future Kubernetes release: use %q", fldPath, value, ip.String()),
fmt.Sprintf("%s: non-standard IP address %q is invalid: use %q", fldPath, value, ip.String()),
}
}
@@ -233,7 +233,7 @@ func GetWarningsForCIDR(fldPath *field.Path, value string) []string {
// ParsePrefix() doesn't) or IPv4-mapped IPv6 (.Is4In6()). Either way,
// re-stringifying the net.IPNet value will give the preferred form.
warnings = append(warnings,
fmt.Sprintf("%s: non-standard CIDR value %q will be considered invalid in a future Kubernetes release: use %q", fldPath, value, ipnet.String()),
fmt.Sprintf("%s: non-standard CIDR value %q is invalid: use %q", fldPath, value, ipnet.String()),
)
}