updated vendor
This commit is contained in:
-202
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
-24
@@ -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
@@ -1,5 +1,4 @@
|
||||
//go:build !notest
|
||||
// +build !notest
|
||||
|
||||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
|
||||
-160
@@ -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
@@ -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
@@ -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
@@ -20,7 +20,7 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/dump"
|
||||
"k8s.io/utils/dump"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
|
||||
+5
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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()),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user