mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 10:33:35 +00:00
build: move e2e dependencies into e2e/go.mod
Several packages are only used while running the e2e suite. These packages are less important to update, as the they can not influence the final executable that is part of the Ceph-CSI container-image. By moving these dependencies out of the main Ceph-CSI go.mod, it is easier to identify if a reported CVE affects Ceph-CSI, or only the testing (like most of the Kubernetes CVEs). Signed-off-by: Niels de Vos <ndevos@ibm.com>
This commit is contained in:
committed by
mergify[bot]
parent
15da101b1b
commit
bec6090996
8
e2e/vendor/k8s.io/client-go/tools/auth/OWNERS
generated
vendored
Normal file
8
e2e/vendor/k8s.io/client-go/tools/auth/OWNERS
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- sig-auth-authenticators-approvers
|
||||
reviewers:
|
||||
- sig-auth-authenticators-reviewers
|
||||
labels:
|
||||
- sig/auth
|
125
e2e/vendor/k8s.io/client-go/tools/auth/clientauth.go
generated
vendored
Normal file
125
e2e/vendor/k8s.io/client-go/tools/auth/clientauth.go
generated
vendored
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
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 auth defines a file format for holding authentication
|
||||
information needed by clients of Kubernetes. Typically,
|
||||
a Kubernetes cluster will put auth info for the admin in a known
|
||||
location when it is created, and will (soon) put it in a known
|
||||
location within a Container's file tree for Containers that
|
||||
need access to the Kubernetes API.
|
||||
|
||||
Having a defined format allows:
|
||||
- clients to be implemented in multiple languages
|
||||
- applications which link clients to be portable across
|
||||
clusters with different authentication styles (e.g.
|
||||
some may use SSL Client certs, others may not, etc)
|
||||
- when the format changes, applications only
|
||||
need to update this code.
|
||||
|
||||
The file format is json, marshalled from a struct authcfg.Info.
|
||||
|
||||
Client libraries in other languages should use the same format.
|
||||
|
||||
It is not intended to store general preferences, such as default
|
||||
namespace, output options, etc. CLIs (such as kubectl) and UIs should
|
||||
develop their own format and may wish to inline the authcfg.Info type.
|
||||
|
||||
The authcfg.Info is just a file format. It is distinct from
|
||||
client.Config which holds options for creating a client.Client.
|
||||
Helper functions are provided in this package to fill in a
|
||||
client.Client from an authcfg.Info.
|
||||
|
||||
Example:
|
||||
|
||||
import (
|
||||
"pkg/client"
|
||||
"pkg/client/auth"
|
||||
)
|
||||
|
||||
info, err := auth.LoadFromFile(filename)
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
clientConfig = client.Config{}
|
||||
clientConfig.Host = "example.com:4901"
|
||||
clientConfig = info.MergeWithConfig()
|
||||
client := client.New(clientConfig)
|
||||
client.Pods(ns).List()
|
||||
*/
|
||||
package auth
|
||||
|
||||
// TODO: need a way to rotate Tokens. Therefore, need a way for client object to be reset when the authcfg is updated.
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
restclient "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// Info holds Kubernetes API authorization config. It is intended
|
||||
// to be read/written from a file as a JSON object.
|
||||
type Info struct {
|
||||
User string
|
||||
Password string `datapolicy:"password"`
|
||||
CAFile string
|
||||
CertFile string
|
||||
KeyFile string
|
||||
BearerToken string `datapolicy:"token"`
|
||||
Insecure *bool
|
||||
}
|
||||
|
||||
// LoadFromFile parses an Info object from a file path.
|
||||
// If the file does not exist, then os.IsNotExist(err) == true
|
||||
func LoadFromFile(path string) (*Info, error) {
|
||||
var info Info
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(data, &info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &info, err
|
||||
}
|
||||
|
||||
// MergeWithConfig returns a copy of a client.Config with values from the Info.
|
||||
// The fields of client.Config with a corresponding field in the Info are set
|
||||
// with the value from the Info.
|
||||
func (info Info) MergeWithConfig(c restclient.Config) (restclient.Config, error) {
|
||||
var config = c
|
||||
config.Username = info.User
|
||||
config.Password = info.Password
|
||||
config.CAFile = info.CAFile
|
||||
config.CertFile = info.CertFile
|
||||
config.KeyFile = info.KeyFile
|
||||
config.BearerToken = info.BearerToken
|
||||
if info.Insecure != nil {
|
||||
config.Insecure = *info.Insecure
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// Complete returns true if the Kubernetes API authorization info is complete.
|
||||
func (info Info) Complete() bool {
|
||||
return len(info.User) > 0 ||
|
||||
len(info.CertFile) > 0 ||
|
||||
len(info.BearerToken) > 0
|
||||
}
|
27
e2e/vendor/k8s.io/client-go/tools/cache/OWNERS
generated
vendored
Normal file
27
e2e/vendor/k8s.io/client-go/tools/cache/OWNERS
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- thockin
|
||||
- smarterclayton
|
||||
- wojtek-t
|
||||
- deads2k
|
||||
- caesarxuchao
|
||||
- liggitt
|
||||
reviewers:
|
||||
- thockin
|
||||
- smarterclayton
|
||||
- wojtek-t
|
||||
- deads2k
|
||||
- derekwaynecarr
|
||||
- caesarxuchao
|
||||
- mikedanese
|
||||
- liggitt
|
||||
- janetkuo
|
||||
- justinsb
|
||||
- soltysh
|
||||
- jsafrane
|
||||
- dims
|
||||
- ingvagabund
|
||||
emeritus_approvers:
|
||||
- lavalamp
|
||||
- ncdc
|
607
e2e/vendor/k8s.io/client-go/tools/cache/controller.go
generated
vendored
Normal file
607
e2e/vendor/k8s.io/client-go/tools/cache/controller.go
generated
vendored
Normal file
@ -0,0 +1,607 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
// This file implements a low-level controller that is used in
|
||||
// sharedIndexInformer, which is an implementation of
|
||||
// SharedIndexInformer. Such informers, in turn, are key components
|
||||
// in the high level controllers that form the backbone of the
|
||||
// Kubernetes control plane. Look at those for examples, or the
|
||||
// example in
|
||||
// https://github.com/kubernetes/client-go/tree/master/examples/workqueue
|
||||
// .
|
||||
|
||||
// Config contains all the settings for one of these low-level controllers.
|
||||
type Config struct {
|
||||
// The queue for your objects - has to be a DeltaFIFO due to
|
||||
// assumptions in the implementation. Your Process() function
|
||||
// should accept the output of this Queue's Pop() method.
|
||||
Queue
|
||||
|
||||
// Something that can list and watch your objects.
|
||||
ListerWatcher
|
||||
|
||||
// Something that can process a popped Deltas.
|
||||
Process ProcessFunc
|
||||
|
||||
// ObjectType is an example object of the type this controller is
|
||||
// expected to handle.
|
||||
ObjectType runtime.Object
|
||||
|
||||
// ObjectDescription is the description to use when logging type-specific information about this controller.
|
||||
ObjectDescription string
|
||||
|
||||
// FullResyncPeriod is the period at which ShouldResync is considered.
|
||||
FullResyncPeriod time.Duration
|
||||
|
||||
// MinWatchTimeout, if set, will define the minimum timeout for watch requests send
|
||||
// to kube-apiserver. However, values lower than 5m will not be honored to avoid
|
||||
// negative performance impact on controlplane.
|
||||
// Optional - if unset a default value of 5m will be used.
|
||||
MinWatchTimeout time.Duration
|
||||
|
||||
// ShouldResync is periodically used by the reflector to determine
|
||||
// whether to Resync the Queue. If ShouldResync is `nil` or
|
||||
// returns true, it means the reflector should proceed with the
|
||||
// resync.
|
||||
ShouldResync ShouldResyncFunc
|
||||
|
||||
// If true, when Process() returns an error, re-enqueue the object.
|
||||
// TODO: add interface to let you inject a delay/backoff or drop
|
||||
// the object completely if desired. Pass the object in
|
||||
// question to this interface as a parameter. This is probably moot
|
||||
// now that this functionality appears at a higher level.
|
||||
RetryOnError bool
|
||||
|
||||
// Called whenever the ListAndWatch drops the connection with an error.
|
||||
WatchErrorHandler WatchErrorHandler
|
||||
|
||||
// WatchListPageSize is the requested chunk size of initial and relist watch lists.
|
||||
WatchListPageSize int64
|
||||
}
|
||||
|
||||
// ShouldResyncFunc is a type of function that indicates if a reflector should perform a
|
||||
// resync or not. It can be used by a shared informer to support multiple event handlers with custom
|
||||
// resync periods.
|
||||
type ShouldResyncFunc func() bool
|
||||
|
||||
// ProcessFunc processes a single object.
|
||||
type ProcessFunc func(obj interface{}, isInInitialList bool) error
|
||||
|
||||
// `*controller` implements Controller
|
||||
type controller struct {
|
||||
config Config
|
||||
reflector *Reflector
|
||||
reflectorMutex sync.RWMutex
|
||||
clock clock.Clock
|
||||
}
|
||||
|
||||
// Controller is a low-level controller that is parameterized by a
|
||||
// Config and used in sharedIndexInformer.
|
||||
type Controller interface {
|
||||
// Run does two things. One is to construct and run a Reflector
|
||||
// to pump objects/notifications from the Config's ListerWatcher
|
||||
// to the Config's Queue and possibly invoke the occasional Resync
|
||||
// on that Queue. The other is to repeatedly Pop from the Queue
|
||||
// and process with the Config's ProcessFunc. Both of these
|
||||
// continue until `stopCh` is closed.
|
||||
Run(stopCh <-chan struct{})
|
||||
|
||||
// HasSynced delegates to the Config's Queue
|
||||
HasSynced() bool
|
||||
|
||||
// LastSyncResourceVersion delegates to the Reflector when there
|
||||
// is one, otherwise returns the empty string
|
||||
LastSyncResourceVersion() string
|
||||
}
|
||||
|
||||
// New makes a new Controller from the given Config.
|
||||
func New(c *Config) Controller {
|
||||
ctlr := &controller{
|
||||
config: *c,
|
||||
clock: &clock.RealClock{},
|
||||
}
|
||||
return ctlr
|
||||
}
|
||||
|
||||
// Run begins processing items, and will continue until a value is sent down stopCh or it is closed.
|
||||
// It's an error to call Run more than once.
|
||||
// Run blocks; call via go.
|
||||
func (c *controller) Run(stopCh <-chan struct{}) {
|
||||
defer utilruntime.HandleCrash()
|
||||
go func() {
|
||||
<-stopCh
|
||||
c.config.Queue.Close()
|
||||
}()
|
||||
r := NewReflectorWithOptions(
|
||||
c.config.ListerWatcher,
|
||||
c.config.ObjectType,
|
||||
c.config.Queue,
|
||||
ReflectorOptions{
|
||||
ResyncPeriod: c.config.FullResyncPeriod,
|
||||
MinWatchTimeout: c.config.MinWatchTimeout,
|
||||
TypeDescription: c.config.ObjectDescription,
|
||||
Clock: c.clock,
|
||||
},
|
||||
)
|
||||
r.ShouldResync = c.config.ShouldResync
|
||||
r.WatchListPageSize = c.config.WatchListPageSize
|
||||
if c.config.WatchErrorHandler != nil {
|
||||
r.watchErrorHandler = c.config.WatchErrorHandler
|
||||
}
|
||||
|
||||
c.reflectorMutex.Lock()
|
||||
c.reflector = r
|
||||
c.reflectorMutex.Unlock()
|
||||
|
||||
var wg wait.Group
|
||||
|
||||
wg.StartWithChannel(stopCh, r.Run)
|
||||
|
||||
wait.Until(c.processLoop, time.Second, stopCh)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// Returns true once this controller has completed an initial resource listing
|
||||
func (c *controller) HasSynced() bool {
|
||||
return c.config.Queue.HasSynced()
|
||||
}
|
||||
|
||||
func (c *controller) LastSyncResourceVersion() string {
|
||||
c.reflectorMutex.RLock()
|
||||
defer c.reflectorMutex.RUnlock()
|
||||
if c.reflector == nil {
|
||||
return ""
|
||||
}
|
||||
return c.reflector.LastSyncResourceVersion()
|
||||
}
|
||||
|
||||
// processLoop drains the work queue.
|
||||
// TODO: Consider doing the processing in parallel. This will require a little thought
|
||||
// to make sure that we don't end up processing the same object multiple times
|
||||
// concurrently.
|
||||
//
|
||||
// TODO: Plumb through the stopCh here (and down to the queue) so that this can
|
||||
// actually exit when the controller is stopped. Or just give up on this stuff
|
||||
// ever being stoppable. Converting this whole package to use Context would
|
||||
// also be helpful.
|
||||
func (c *controller) processLoop() {
|
||||
for {
|
||||
obj, err := c.config.Queue.Pop(PopProcessFunc(c.config.Process))
|
||||
if err != nil {
|
||||
if err == ErrFIFOClosed {
|
||||
return
|
||||
}
|
||||
if c.config.RetryOnError {
|
||||
// This is the safe way to re-enqueue.
|
||||
c.config.Queue.AddIfNotPresent(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ResourceEventHandler can handle notifications for events that
|
||||
// happen to a resource. The events are informational only, so you
|
||||
// can't return an error. The handlers MUST NOT modify the objects
|
||||
// received; this concerns not only the top level of structure but all
|
||||
// the data structures reachable from it.
|
||||
// - OnAdd is called when an object is added.
|
||||
// - OnUpdate is called when an object is modified. Note that oldObj is the
|
||||
// last known state of the object-- it is possible that several changes
|
||||
// were combined together, so you can't use this to see every single
|
||||
// change. OnUpdate is also called when a re-list happens, and it will
|
||||
// get called even if nothing changed. This is useful for periodically
|
||||
// evaluating or syncing something.
|
||||
// - OnDelete will get the final state of the item if it is known, otherwise
|
||||
// it will get an object of type DeletedFinalStateUnknown. This can
|
||||
// happen if the watch is closed and misses the delete event and we don't
|
||||
// notice the deletion until the subsequent re-list.
|
||||
type ResourceEventHandler interface {
|
||||
OnAdd(obj interface{}, isInInitialList bool)
|
||||
OnUpdate(oldObj, newObj interface{})
|
||||
OnDelete(obj interface{})
|
||||
}
|
||||
|
||||
// ResourceEventHandlerFuncs is an adaptor to let you easily specify as many or
|
||||
// as few of the notification functions as you want while still implementing
|
||||
// ResourceEventHandler. This adapter does not remove the prohibition against
|
||||
// modifying the objects.
|
||||
//
|
||||
// See ResourceEventHandlerDetailedFuncs if your use needs to propagate
|
||||
// HasSynced.
|
||||
type ResourceEventHandlerFuncs struct {
|
||||
AddFunc func(obj interface{})
|
||||
UpdateFunc func(oldObj, newObj interface{})
|
||||
DeleteFunc func(obj interface{})
|
||||
}
|
||||
|
||||
// OnAdd calls AddFunc if it's not nil.
|
||||
func (r ResourceEventHandlerFuncs) OnAdd(obj interface{}, isInInitialList bool) {
|
||||
if r.AddFunc != nil {
|
||||
r.AddFunc(obj)
|
||||
}
|
||||
}
|
||||
|
||||
// OnUpdate calls UpdateFunc if it's not nil.
|
||||
func (r ResourceEventHandlerFuncs) OnUpdate(oldObj, newObj interface{}) {
|
||||
if r.UpdateFunc != nil {
|
||||
r.UpdateFunc(oldObj, newObj)
|
||||
}
|
||||
}
|
||||
|
||||
// OnDelete calls DeleteFunc if it's not nil.
|
||||
func (r ResourceEventHandlerFuncs) OnDelete(obj interface{}) {
|
||||
if r.DeleteFunc != nil {
|
||||
r.DeleteFunc(obj)
|
||||
}
|
||||
}
|
||||
|
||||
// ResourceEventHandlerDetailedFuncs is exactly like ResourceEventHandlerFuncs
|
||||
// except its AddFunc accepts the isInInitialList parameter, for propagating
|
||||
// HasSynced.
|
||||
type ResourceEventHandlerDetailedFuncs struct {
|
||||
AddFunc func(obj interface{}, isInInitialList bool)
|
||||
UpdateFunc func(oldObj, newObj interface{})
|
||||
DeleteFunc func(obj interface{})
|
||||
}
|
||||
|
||||
// OnAdd calls AddFunc if it's not nil.
|
||||
func (r ResourceEventHandlerDetailedFuncs) OnAdd(obj interface{}, isInInitialList bool) {
|
||||
if r.AddFunc != nil {
|
||||
r.AddFunc(obj, isInInitialList)
|
||||
}
|
||||
}
|
||||
|
||||
// OnUpdate calls UpdateFunc if it's not nil.
|
||||
func (r ResourceEventHandlerDetailedFuncs) OnUpdate(oldObj, newObj interface{}) {
|
||||
if r.UpdateFunc != nil {
|
||||
r.UpdateFunc(oldObj, newObj)
|
||||
}
|
||||
}
|
||||
|
||||
// OnDelete calls DeleteFunc if it's not nil.
|
||||
func (r ResourceEventHandlerDetailedFuncs) OnDelete(obj interface{}) {
|
||||
if r.DeleteFunc != nil {
|
||||
r.DeleteFunc(obj)
|
||||
}
|
||||
}
|
||||
|
||||
// FilteringResourceEventHandler applies the provided filter to all events coming
|
||||
// in, ensuring the appropriate nested handler method is invoked. An object
|
||||
// that starts passing the filter after an update is considered an add, and an
|
||||
// object that stops passing the filter after an update is considered a delete.
|
||||
// Like the handlers, the filter MUST NOT modify the objects it is given.
|
||||
type FilteringResourceEventHandler struct {
|
||||
FilterFunc func(obj interface{}) bool
|
||||
Handler ResourceEventHandler
|
||||
}
|
||||
|
||||
// OnAdd calls the nested handler only if the filter succeeds
|
||||
func (r FilteringResourceEventHandler) OnAdd(obj interface{}, isInInitialList bool) {
|
||||
if !r.FilterFunc(obj) {
|
||||
return
|
||||
}
|
||||
r.Handler.OnAdd(obj, isInInitialList)
|
||||
}
|
||||
|
||||
// OnUpdate ensures the proper handler is called depending on whether the filter matches
|
||||
func (r FilteringResourceEventHandler) OnUpdate(oldObj, newObj interface{}) {
|
||||
newer := r.FilterFunc(newObj)
|
||||
older := r.FilterFunc(oldObj)
|
||||
switch {
|
||||
case newer && older:
|
||||
r.Handler.OnUpdate(oldObj, newObj)
|
||||
case newer && !older:
|
||||
r.Handler.OnAdd(newObj, false)
|
||||
case !newer && older:
|
||||
r.Handler.OnDelete(oldObj)
|
||||
default:
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
// OnDelete calls the nested handler only if the filter succeeds
|
||||
func (r FilteringResourceEventHandler) OnDelete(obj interface{}) {
|
||||
if !r.FilterFunc(obj) {
|
||||
return
|
||||
}
|
||||
r.Handler.OnDelete(obj)
|
||||
}
|
||||
|
||||
// DeletionHandlingMetaNamespaceKeyFunc checks for
|
||||
// DeletedFinalStateUnknown objects before calling
|
||||
// MetaNamespaceKeyFunc.
|
||||
func DeletionHandlingMetaNamespaceKeyFunc(obj interface{}) (string, error) {
|
||||
if d, ok := obj.(DeletedFinalStateUnknown); ok {
|
||||
return d.Key, nil
|
||||
}
|
||||
return MetaNamespaceKeyFunc(obj)
|
||||
}
|
||||
|
||||
// DeletionHandlingObjectToName checks for
|
||||
// DeletedFinalStateUnknown objects before calling
|
||||
// ObjectToName.
|
||||
func DeletionHandlingObjectToName(obj interface{}) (ObjectName, error) {
|
||||
if d, ok := obj.(DeletedFinalStateUnknown); ok {
|
||||
return ParseObjectName(d.Key)
|
||||
}
|
||||
return ObjectToName(obj)
|
||||
}
|
||||
|
||||
// InformerOptions configure a Reflector.
|
||||
type InformerOptions struct {
|
||||
// ListerWatcher implements List and Watch functions for the source of the resource
|
||||
// the informer will be informing about.
|
||||
ListerWatcher ListerWatcher
|
||||
|
||||
// ObjectType is an object of the type that informer is expected to receive.
|
||||
ObjectType runtime.Object
|
||||
|
||||
// Handler defines functions that should called on object mutations.
|
||||
Handler ResourceEventHandler
|
||||
|
||||
// ResyncPeriod is the underlying Reflector's resync period. If non-zero, the store
|
||||
// is re-synced with that frequency - Modify events are delivered even if objects
|
||||
// didn't change.
|
||||
// This is useful for synchronizing objects that configure external resources
|
||||
// (e.g. configure cloud provider functionalities).
|
||||
// Optional - if unset, store resyncing is not happening periodically.
|
||||
ResyncPeriod time.Duration
|
||||
|
||||
// MinWatchTimeout, if set, will define the minimum timeout for watch requests send
|
||||
// to kube-apiserver. However, values lower than 5m will not be honored to avoid
|
||||
// negative performance impact on controlplane.
|
||||
// Optional - if unset a default value of 5m will be used.
|
||||
MinWatchTimeout time.Duration
|
||||
|
||||
// Indexers, if set, are the indexers for the received objects to optimize
|
||||
// certain queries.
|
||||
// Optional - if unset no indexes are maintained.
|
||||
Indexers Indexers
|
||||
|
||||
// Transform function, if set, will be called on all objects before they will be
|
||||
// put into the Store and corresponding Add/Modify/Delete handlers will be invoked
|
||||
// for them.
|
||||
// Optional - if unset no additional transforming is happening.
|
||||
Transform TransformFunc
|
||||
}
|
||||
|
||||
// NewInformerWithOptions returns a Store and a controller for populating the store
|
||||
// while also providing event notifications. You should only used the returned
|
||||
// Store for Get/List operations; Add/Modify/Deletes will cause the event
|
||||
// notifications to be faulty.
|
||||
func NewInformerWithOptions(options InformerOptions) (Store, Controller) {
|
||||
var clientState Store
|
||||
if options.Indexers == nil {
|
||||
clientState = NewStore(DeletionHandlingMetaNamespaceKeyFunc)
|
||||
} else {
|
||||
clientState = NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, options.Indexers)
|
||||
}
|
||||
return clientState, newInformer(clientState, options)
|
||||
}
|
||||
|
||||
// NewInformer returns a Store and a controller for populating the store
|
||||
// while also providing event notifications. You should only used the returned
|
||||
// Store for Get/List operations; Add/Modify/Deletes will cause the event
|
||||
// notifications to be faulty.
|
||||
//
|
||||
// Parameters:
|
||||
// - lw is list and watch functions for the source of the resource you want to
|
||||
// be informed of.
|
||||
// - objType is an object of the type that you expect to receive.
|
||||
// - resyncPeriod: if non-zero, will re-list this often (you will get OnUpdate
|
||||
// calls, even if nothing changed). Otherwise, re-list will be delayed as
|
||||
// long as possible (until the upstream source closes the watch or times out,
|
||||
// or you stop the controller).
|
||||
// - h is the object you want notifications sent to.
|
||||
//
|
||||
// Deprecated: Use NewInformerWithOptions instead.
|
||||
func NewInformer(
|
||||
lw ListerWatcher,
|
||||
objType runtime.Object,
|
||||
resyncPeriod time.Duration,
|
||||
h ResourceEventHandler,
|
||||
) (Store, Controller) {
|
||||
// This will hold the client state, as we know it.
|
||||
clientState := NewStore(DeletionHandlingMetaNamespaceKeyFunc)
|
||||
|
||||
options := InformerOptions{
|
||||
ListerWatcher: lw,
|
||||
ObjectType: objType,
|
||||
Handler: h,
|
||||
ResyncPeriod: resyncPeriod,
|
||||
}
|
||||
return clientState, newInformer(clientState, options)
|
||||
}
|
||||
|
||||
// NewIndexerInformer returns an Indexer and a Controller for populating the index
|
||||
// while also providing event notifications. You should only used the returned
|
||||
// Index for Get/List operations; Add/Modify/Deletes will cause the event
|
||||
// notifications to be faulty.
|
||||
//
|
||||
// Parameters:
|
||||
// - lw is list and watch functions for the source of the resource you want to
|
||||
// be informed of.
|
||||
// - objType is an object of the type that you expect to receive.
|
||||
// - resyncPeriod: if non-zero, will re-list this often (you will get OnUpdate
|
||||
// calls, even if nothing changed). Otherwise, re-list will be delayed as
|
||||
// long as possible (until the upstream source closes the watch or times out,
|
||||
// or you stop the controller).
|
||||
// - h is the object you want notifications sent to.
|
||||
// - indexers is the indexer for the received object type.
|
||||
//
|
||||
// Deprecated: Use NewInformerWithOptions instead.
|
||||
func NewIndexerInformer(
|
||||
lw ListerWatcher,
|
||||
objType runtime.Object,
|
||||
resyncPeriod time.Duration,
|
||||
h ResourceEventHandler,
|
||||
indexers Indexers,
|
||||
) (Indexer, Controller) {
|
||||
// This will hold the client state, as we know it.
|
||||
clientState := NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, indexers)
|
||||
|
||||
options := InformerOptions{
|
||||
ListerWatcher: lw,
|
||||
ObjectType: objType,
|
||||
Handler: h,
|
||||
ResyncPeriod: resyncPeriod,
|
||||
Indexers: indexers,
|
||||
}
|
||||
return clientState, newInformer(clientState, options)
|
||||
}
|
||||
|
||||
// NewTransformingInformer returns a Store and a controller for populating
|
||||
// the store while also providing event notifications. You should only used
|
||||
// the returned Store for Get/List operations; Add/Modify/Deletes will cause
|
||||
// the event notifications to be faulty.
|
||||
// The given transform function will be called on all objects before they will
|
||||
// put into the Store and corresponding Add/Modify/Delete handlers will
|
||||
// be invoked for them.
|
||||
//
|
||||
// Deprecated: Use NewInformerWithOptions instead.
|
||||
func NewTransformingInformer(
|
||||
lw ListerWatcher,
|
||||
objType runtime.Object,
|
||||
resyncPeriod time.Duration,
|
||||
h ResourceEventHandler,
|
||||
transformer TransformFunc,
|
||||
) (Store, Controller) {
|
||||
// This will hold the client state, as we know it.
|
||||
clientState := NewStore(DeletionHandlingMetaNamespaceKeyFunc)
|
||||
|
||||
options := InformerOptions{
|
||||
ListerWatcher: lw,
|
||||
ObjectType: objType,
|
||||
Handler: h,
|
||||
ResyncPeriod: resyncPeriod,
|
||||
Transform: transformer,
|
||||
}
|
||||
return clientState, newInformer(clientState, options)
|
||||
}
|
||||
|
||||
// NewTransformingIndexerInformer returns an Indexer and a controller for
|
||||
// populating the index while also providing event notifications. You should
|
||||
// only used the returned Index for Get/List operations; Add/Modify/Deletes
|
||||
// will cause the event notifications to be faulty.
|
||||
// The given transform function will be called on all objects before they will
|
||||
// be put into the Index and corresponding Add/Modify/Delete handlers will
|
||||
// be invoked for them.
|
||||
//
|
||||
// Deprecated: Use NewInformerWithOptions instead.
|
||||
func NewTransformingIndexerInformer(
|
||||
lw ListerWatcher,
|
||||
objType runtime.Object,
|
||||
resyncPeriod time.Duration,
|
||||
h ResourceEventHandler,
|
||||
indexers Indexers,
|
||||
transformer TransformFunc,
|
||||
) (Indexer, Controller) {
|
||||
// This will hold the client state, as we know it.
|
||||
clientState := NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, indexers)
|
||||
|
||||
options := InformerOptions{
|
||||
ListerWatcher: lw,
|
||||
ObjectType: objType,
|
||||
Handler: h,
|
||||
ResyncPeriod: resyncPeriod,
|
||||
Indexers: indexers,
|
||||
Transform: transformer,
|
||||
}
|
||||
return clientState, newInformer(clientState, options)
|
||||
}
|
||||
|
||||
// Multiplexes updates in the form of a list of Deltas into a Store, and informs
|
||||
// a given handler of events OnUpdate, OnAdd, OnDelete
|
||||
func processDeltas(
|
||||
// Object which receives event notifications from the given deltas
|
||||
handler ResourceEventHandler,
|
||||
clientState Store,
|
||||
deltas Deltas,
|
||||
isInInitialList bool,
|
||||
) error {
|
||||
// from oldest to newest
|
||||
for _, d := range deltas {
|
||||
obj := d.Object
|
||||
|
||||
switch d.Type {
|
||||
case Sync, Replaced, Added, Updated:
|
||||
if old, exists, err := clientState.Get(obj); err == nil && exists {
|
||||
if err := clientState.Update(obj); err != nil {
|
||||
return err
|
||||
}
|
||||
handler.OnUpdate(old, obj)
|
||||
} else {
|
||||
if err := clientState.Add(obj); err != nil {
|
||||
return err
|
||||
}
|
||||
handler.OnAdd(obj, isInInitialList)
|
||||
}
|
||||
case Deleted:
|
||||
if err := clientState.Delete(obj); err != nil {
|
||||
return err
|
||||
}
|
||||
handler.OnDelete(obj)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// newInformer returns a controller for populating the store while also
|
||||
// providing event notifications.
|
||||
//
|
||||
// Parameters
|
||||
// - clientState is the store you want to populate
|
||||
// - options contain the options to configure the controller
|
||||
func newInformer(clientState Store, options InformerOptions) Controller {
|
||||
// This will hold incoming changes. Note how we pass clientState in as a
|
||||
// KeyLister, that way resync operations will result in the correct set
|
||||
// of update/delete deltas.
|
||||
fifo := NewDeltaFIFOWithOptions(DeltaFIFOOptions{
|
||||
KnownObjects: clientState,
|
||||
EmitDeltaTypeReplaced: true,
|
||||
Transformer: options.Transform,
|
||||
})
|
||||
|
||||
cfg := &Config{
|
||||
Queue: fifo,
|
||||
ListerWatcher: options.ListerWatcher,
|
||||
ObjectType: options.ObjectType,
|
||||
FullResyncPeriod: options.ResyncPeriod,
|
||||
MinWatchTimeout: options.MinWatchTimeout,
|
||||
RetryOnError: false,
|
||||
|
||||
Process: func(obj interface{}, isInInitialList bool) error {
|
||||
if deltas, ok := obj.(Deltas); ok {
|
||||
return processDeltas(options.Handler, clientState, deltas, isInInitialList)
|
||||
}
|
||||
return errors.New("object given as Process argument is not Deltas")
|
||||
},
|
||||
}
|
||||
return New(cfg)
|
||||
}
|
816
e2e/vendor/k8s.io/client-go/tools/cache/delta_fifo.go
generated
vendored
Normal file
816
e2e/vendor/k8s.io/client-go/tools/cache/delta_fifo.go
generated
vendored
Normal file
@ -0,0 +1,816 @@
|
||||
/*
|
||||
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 cache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
// DeltaFIFOOptions is the configuration parameters for DeltaFIFO. All are
|
||||
// optional.
|
||||
type DeltaFIFOOptions struct {
|
||||
|
||||
// KeyFunction is used to figure out what key an object should have. (It's
|
||||
// exposed in the returned DeltaFIFO's KeyOf() method, with additional
|
||||
// handling around deleted objects and queue state).
|
||||
// Optional, the default is MetaNamespaceKeyFunc.
|
||||
KeyFunction KeyFunc
|
||||
|
||||
// KnownObjects is expected to return a list of keys that the consumer of
|
||||
// this queue "knows about". It is used to decide which items are missing
|
||||
// when Replace() is called; 'Deleted' deltas are produced for the missing items.
|
||||
// KnownObjects may be nil if you can tolerate missing deletions on Replace().
|
||||
KnownObjects KeyListerGetter
|
||||
|
||||
// EmitDeltaTypeReplaced indicates that the queue consumer
|
||||
// understands the Replaced DeltaType. Before the `Replaced` event type was
|
||||
// added, calls to Replace() were handled the same as Sync(). For
|
||||
// backwards-compatibility purposes, this is false by default.
|
||||
// When true, `Replaced` events will be sent for items passed to a Replace() call.
|
||||
// When false, `Sync` events will be sent instead.
|
||||
EmitDeltaTypeReplaced bool
|
||||
|
||||
// If set, will be called for objects before enqueueing them. Please
|
||||
// see the comment on TransformFunc for details.
|
||||
Transformer TransformFunc
|
||||
}
|
||||
|
||||
// DeltaFIFO is like FIFO, but differs in two ways. One is that the
|
||||
// accumulator associated with a given object's key is not that object
|
||||
// but rather a Deltas, which is a slice of Delta values for that
|
||||
// object. Applying an object to a Deltas means to append a Delta
|
||||
// except when the potentially appended Delta is a Deleted and the
|
||||
// Deltas already ends with a Deleted. In that case the Deltas does
|
||||
// not grow, although the terminal Deleted will be replaced by the new
|
||||
// Deleted if the older Deleted's object is a
|
||||
// DeletedFinalStateUnknown.
|
||||
//
|
||||
// The other difference is that DeltaFIFO has two additional ways that
|
||||
// an object can be applied to an accumulator: Replaced and Sync.
|
||||
// If EmitDeltaTypeReplaced is not set to true, Sync will be used in
|
||||
// replace events for backwards compatibility. Sync is used for periodic
|
||||
// resync events.
|
||||
//
|
||||
// DeltaFIFO is a producer-consumer queue, where a Reflector is
|
||||
// intended to be the producer, and the consumer is whatever calls
|
||||
// the Pop() method.
|
||||
//
|
||||
// DeltaFIFO solves this use case:
|
||||
// - You want to process every object change (delta) at most once.
|
||||
// - When you process an object, you want to see everything
|
||||
// that's happened to it since you last processed it.
|
||||
// - You want to process the deletion of some of the objects.
|
||||
// - You might want to periodically reprocess objects.
|
||||
//
|
||||
// DeltaFIFO's Pop(), Get(), and GetByKey() methods return
|
||||
// interface{} to satisfy the Store/Queue interfaces, but they
|
||||
// will always return an object of type Deltas. List() returns
|
||||
// the newest object from each accumulator in the FIFO.
|
||||
//
|
||||
// A DeltaFIFO's knownObjects KeyListerGetter provides the abilities
|
||||
// to list Store keys and to get objects by Store key. The objects in
|
||||
// question are called "known objects" and this set of objects
|
||||
// modifies the behavior of the Delete, Replace, and Resync methods
|
||||
// (each in a different way).
|
||||
//
|
||||
// A note on threading: If you call Pop() in parallel from multiple
|
||||
// threads, you could end up with multiple threads processing slightly
|
||||
// different versions of the same object.
|
||||
type DeltaFIFO struct {
|
||||
// lock/cond protects access to 'items' and 'queue'.
|
||||
lock sync.RWMutex
|
||||
cond sync.Cond
|
||||
|
||||
// `items` maps a key to a Deltas.
|
||||
// Each such Deltas has at least one Delta.
|
||||
items map[string]Deltas
|
||||
|
||||
// `queue` maintains FIFO order of keys for consumption in Pop().
|
||||
// There are no duplicates in `queue`.
|
||||
// A key is in `queue` if and only if it is in `items`.
|
||||
queue []string
|
||||
|
||||
// populated is true if the first batch of items inserted by Replace() has been populated
|
||||
// or Delete/Add/Update/AddIfNotPresent was called first.
|
||||
populated bool
|
||||
// initialPopulationCount is the number of items inserted by the first call of Replace()
|
||||
initialPopulationCount int
|
||||
|
||||
// keyFunc is used to make the key used for queued item
|
||||
// insertion and retrieval, and should be deterministic.
|
||||
keyFunc KeyFunc
|
||||
|
||||
// knownObjects list keys that are "known" --- affecting Delete(),
|
||||
// Replace(), and Resync()
|
||||
knownObjects KeyListerGetter
|
||||
|
||||
// Used to indicate a queue is closed so a control loop can exit when a queue is empty.
|
||||
// Currently, not used to gate any of CRUD operations.
|
||||
closed bool
|
||||
|
||||
// emitDeltaTypeReplaced is whether to emit the Replaced or Sync
|
||||
// DeltaType when Replace() is called (to preserve backwards compat).
|
||||
emitDeltaTypeReplaced bool
|
||||
|
||||
// Called with every object if non-nil.
|
||||
transformer TransformFunc
|
||||
}
|
||||
|
||||
// TransformFunc allows for transforming an object before it will be processed.
|
||||
//
|
||||
// The most common usage pattern is to clean-up some parts of the object to
|
||||
// reduce component memory usage if a given component doesn't care about them.
|
||||
//
|
||||
// New in v1.27: TransformFunc sees the object before any other actor, and it
|
||||
// is now safe to mutate the object in place instead of making a copy.
|
||||
//
|
||||
// It's recommended for the TransformFunc to be idempotent.
|
||||
// It MUST be idempotent if objects already present in the cache are passed to
|
||||
// the Replace() to avoid re-mutating them. Default informers do not pass
|
||||
// existing objects to Replace though.
|
||||
//
|
||||
// Note that TransformFunc is called while inserting objects into the
|
||||
// notification queue and is therefore extremely performance sensitive; please
|
||||
// do not do anything that will take a long time.
|
||||
type TransformFunc func(interface{}) (interface{}, error)
|
||||
|
||||
// DeltaType is the type of a change (addition, deletion, etc)
|
||||
type DeltaType string
|
||||
|
||||
// Change type definition
|
||||
const (
|
||||
Added DeltaType = "Added"
|
||||
Updated DeltaType = "Updated"
|
||||
Deleted DeltaType = "Deleted"
|
||||
// Replaced is emitted when we encountered watch errors and had to do a
|
||||
// relist. We don't know if the replaced object has changed.
|
||||
//
|
||||
// NOTE: Previous versions of DeltaFIFO would use Sync for Replace events
|
||||
// as well. Hence, Replaced is only emitted when the option
|
||||
// EmitDeltaTypeReplaced is true.
|
||||
Replaced DeltaType = "Replaced"
|
||||
// Sync is for synthetic events during a periodic resync.
|
||||
Sync DeltaType = "Sync"
|
||||
)
|
||||
|
||||
// Delta is a member of Deltas (a list of Delta objects) which
|
||||
// in its turn is the type stored by a DeltaFIFO. It tells you what
|
||||
// change happened, and the object's state after* that change.
|
||||
//
|
||||
// [*] Unless the change is a deletion, and then you'll get the final
|
||||
// state of the object before it was deleted.
|
||||
type Delta struct {
|
||||
Type DeltaType
|
||||
Object interface{}
|
||||
}
|
||||
|
||||
// Deltas is a list of one or more 'Delta's to an individual object.
|
||||
// The oldest delta is at index 0, the newest delta is the last one.
|
||||
type Deltas []Delta
|
||||
|
||||
// NewDeltaFIFO returns a Queue which can be used to process changes to items.
|
||||
//
|
||||
// keyFunc is used to figure out what key an object should have. (It is
|
||||
// exposed in the returned DeltaFIFO's KeyOf() method, with additional handling
|
||||
// around deleted objects and queue state).
|
||||
//
|
||||
// 'knownObjects' may be supplied to modify the behavior of Delete,
|
||||
// Replace, and Resync. It may be nil if you do not need those
|
||||
// modifications.
|
||||
//
|
||||
// TODO: consider merging keyLister with this object, tracking a list of
|
||||
// "known" keys when Pop() is called. Have to think about how that
|
||||
// affects error retrying.
|
||||
//
|
||||
// NOTE: It is possible to misuse this and cause a race when using an
|
||||
// external known object source.
|
||||
// Whether there is a potential race depends on how the consumer
|
||||
// modifies knownObjects. In Pop(), process function is called under
|
||||
// lock, so it is safe to update data structures in it that need to be
|
||||
// in sync with the queue (e.g. knownObjects).
|
||||
//
|
||||
// Example:
|
||||
// In case of sharedIndexInformer being a consumer
|
||||
// (https://github.com/kubernetes/kubernetes/blob/0cdd940f/staging/src/k8s.io/client-go/tools/cache/shared_informer.go#L192),
|
||||
// there is no race as knownObjects (s.indexer) is modified safely
|
||||
// under DeltaFIFO's lock. The only exceptions are GetStore() and
|
||||
// GetIndexer() methods, which expose ways to modify the underlying
|
||||
// storage. Currently these two methods are used for creating Lister
|
||||
// and internal tests.
|
||||
//
|
||||
// Also see the comment on DeltaFIFO.
|
||||
//
|
||||
// Warning: This constructs a DeltaFIFO that does not differentiate between
|
||||
// events caused by a call to Replace (e.g., from a relist, which may
|
||||
// contain object updates), and synthetic events caused by a periodic resync
|
||||
// (which just emit the existing object). See https://issue.k8s.io/86015 for details.
|
||||
//
|
||||
// Use `NewDeltaFIFOWithOptions(DeltaFIFOOptions{..., EmitDeltaTypeReplaced: true})`
|
||||
// instead to receive a `Replaced` event depending on the type.
|
||||
//
|
||||
// Deprecated: Equivalent to NewDeltaFIFOWithOptions(DeltaFIFOOptions{KeyFunction: keyFunc, KnownObjects: knownObjects})
|
||||
func NewDeltaFIFO(keyFunc KeyFunc, knownObjects KeyListerGetter) *DeltaFIFO {
|
||||
return NewDeltaFIFOWithOptions(DeltaFIFOOptions{
|
||||
KeyFunction: keyFunc,
|
||||
KnownObjects: knownObjects,
|
||||
})
|
||||
}
|
||||
|
||||
// NewDeltaFIFOWithOptions returns a Queue which can be used to process changes to
|
||||
// items. See also the comment on DeltaFIFO.
|
||||
func NewDeltaFIFOWithOptions(opts DeltaFIFOOptions) *DeltaFIFO {
|
||||
if opts.KeyFunction == nil {
|
||||
opts.KeyFunction = MetaNamespaceKeyFunc
|
||||
}
|
||||
|
||||
f := &DeltaFIFO{
|
||||
items: map[string]Deltas{},
|
||||
queue: []string{},
|
||||
keyFunc: opts.KeyFunction,
|
||||
knownObjects: opts.KnownObjects,
|
||||
|
||||
emitDeltaTypeReplaced: opts.EmitDeltaTypeReplaced,
|
||||
transformer: opts.Transformer,
|
||||
}
|
||||
f.cond.L = &f.lock
|
||||
return f
|
||||
}
|
||||
|
||||
var (
|
||||
_ = Queue(&DeltaFIFO{}) // DeltaFIFO is a Queue
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrZeroLengthDeltasObject is returned in a KeyError if a Deltas
|
||||
// object with zero length is encountered (should be impossible,
|
||||
// but included for completeness).
|
||||
ErrZeroLengthDeltasObject = errors.New("0 length Deltas object; can't get key")
|
||||
)
|
||||
|
||||
// Close the queue.
|
||||
func (f *DeltaFIFO) Close() {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
f.closed = true
|
||||
f.cond.Broadcast()
|
||||
}
|
||||
|
||||
// KeyOf exposes f's keyFunc, but also detects the key of a Deltas object or
|
||||
// DeletedFinalStateUnknown objects.
|
||||
func (f *DeltaFIFO) KeyOf(obj interface{}) (string, error) {
|
||||
if d, ok := obj.(Deltas); ok {
|
||||
if len(d) == 0 {
|
||||
return "", KeyError{obj, ErrZeroLengthDeltasObject}
|
||||
}
|
||||
obj = d.Newest().Object
|
||||
}
|
||||
if d, ok := obj.(DeletedFinalStateUnknown); ok {
|
||||
return d.Key, nil
|
||||
}
|
||||
return f.keyFunc(obj)
|
||||
}
|
||||
|
||||
// HasSynced returns true if an Add/Update/Delete/AddIfNotPresent are called first,
|
||||
// or the first batch of items inserted by Replace() has been popped.
|
||||
func (f *DeltaFIFO) HasSynced() bool {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
return f.hasSynced_locked()
|
||||
}
|
||||
|
||||
func (f *DeltaFIFO) hasSynced_locked() bool {
|
||||
return f.populated && f.initialPopulationCount == 0
|
||||
}
|
||||
|
||||
// Add inserts an item, and puts it in the queue. The item is only enqueued
|
||||
// if it doesn't already exist in the set.
|
||||
func (f *DeltaFIFO) Add(obj interface{}) error {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
f.populated = true
|
||||
return f.queueActionLocked(Added, obj)
|
||||
}
|
||||
|
||||
// Update is just like Add, but makes an Updated Delta.
|
||||
func (f *DeltaFIFO) Update(obj interface{}) error {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
f.populated = true
|
||||
return f.queueActionLocked(Updated, obj)
|
||||
}
|
||||
|
||||
// Delete is just like Add, but makes a Deleted Delta. If the given
|
||||
// object does not already exist, it will be ignored. (It may have
|
||||
// already been deleted by a Replace (re-list), for example.) In this
|
||||
// method `f.knownObjects`, if not nil, provides (via GetByKey)
|
||||
// _additional_ objects that are considered to already exist.
|
||||
func (f *DeltaFIFO) Delete(obj interface{}) error {
|
||||
id, err := f.KeyOf(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
f.populated = true
|
||||
if f.knownObjects == nil {
|
||||
if _, exists := f.items[id]; !exists {
|
||||
// Presumably, this was deleted when a relist happened.
|
||||
// Don't provide a second report of the same deletion.
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
// We only want to skip the "deletion" action if the object doesn't
|
||||
// exist in knownObjects and it doesn't have corresponding item in items.
|
||||
// Note that even if there is a "deletion" action in items, we can ignore it,
|
||||
// because it will be deduped automatically in "queueActionLocked"
|
||||
_, exists, err := f.knownObjects.GetByKey(id)
|
||||
_, itemsExist := f.items[id]
|
||||
if err == nil && !exists && !itemsExist {
|
||||
// Presumably, this was deleted when a relist happened.
|
||||
// Don't provide a second report of the same deletion.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// exist in items and/or KnownObjects
|
||||
return f.queueActionLocked(Deleted, obj)
|
||||
}
|
||||
|
||||
// AddIfNotPresent inserts an item, and puts it in the queue. If the item is already
|
||||
// present in the set, it is neither enqueued nor added to the set.
|
||||
//
|
||||
// This is useful in a single producer/consumer scenario so that the consumer can
|
||||
// safely retry items without contending with the producer and potentially enqueueing
|
||||
// stale items.
|
||||
//
|
||||
// Important: obj must be a Deltas (the output of the Pop() function). Yes, this is
|
||||
// different from the Add/Update/Delete functions.
|
||||
func (f *DeltaFIFO) AddIfNotPresent(obj interface{}) error {
|
||||
deltas, ok := obj.(Deltas)
|
||||
if !ok {
|
||||
return fmt.Errorf("object must be of type deltas, but got: %#v", obj)
|
||||
}
|
||||
id, err := f.KeyOf(deltas)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
f.addIfNotPresent(id, deltas)
|
||||
return nil
|
||||
}
|
||||
|
||||
// addIfNotPresent inserts deltas under id if it does not exist, and assumes the caller
|
||||
// already holds the fifo lock.
|
||||
func (f *DeltaFIFO) addIfNotPresent(id string, deltas Deltas) {
|
||||
f.populated = true
|
||||
if _, exists := f.items[id]; exists {
|
||||
return
|
||||
}
|
||||
|
||||
f.queue = append(f.queue, id)
|
||||
f.items[id] = deltas
|
||||
f.cond.Broadcast()
|
||||
}
|
||||
|
||||
// re-listing and watching can deliver the same update multiple times in any
|
||||
// order. This will combine the most recent two deltas if they are the same.
|
||||
func dedupDeltas(deltas Deltas) Deltas {
|
||||
n := len(deltas)
|
||||
if n < 2 {
|
||||
return deltas
|
||||
}
|
||||
a := &deltas[n-1]
|
||||
b := &deltas[n-2]
|
||||
if out := isDup(a, b); out != nil {
|
||||
deltas[n-2] = *out
|
||||
return deltas[:n-1]
|
||||
}
|
||||
return deltas
|
||||
}
|
||||
|
||||
// If a & b represent the same event, returns the delta that ought to be kept.
|
||||
// Otherwise, returns nil.
|
||||
// TODO: is there anything other than deletions that need deduping?
|
||||
func isDup(a, b *Delta) *Delta {
|
||||
if out := isDeletionDup(a, b); out != nil {
|
||||
return out
|
||||
}
|
||||
// TODO: Detect other duplicate situations? Are there any?
|
||||
return nil
|
||||
}
|
||||
|
||||
// keep the one with the most information if both are deletions.
|
||||
func isDeletionDup(a, b *Delta) *Delta {
|
||||
if b.Type != Deleted || a.Type != Deleted {
|
||||
return nil
|
||||
}
|
||||
// Do more sophisticated checks, or is this sufficient?
|
||||
if _, ok := b.Object.(DeletedFinalStateUnknown); ok {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// queueActionLocked appends to the delta list for the object.
|
||||
// Caller must lock first.
|
||||
func (f *DeltaFIFO) queueActionLocked(actionType DeltaType, obj interface{}) error {
|
||||
return f.queueActionInternalLocked(actionType, actionType, obj)
|
||||
}
|
||||
|
||||
// queueActionInternalLocked appends to the delta list for the object.
|
||||
// The actionType is emitted and must honor emitDeltaTypeReplaced.
|
||||
// The internalActionType is only used within this function and must
|
||||
// ignore emitDeltaTypeReplaced.
|
||||
// Caller must lock first.
|
||||
func (f *DeltaFIFO) queueActionInternalLocked(actionType, internalActionType DeltaType, obj interface{}) error {
|
||||
id, err := f.KeyOf(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
|
||||
// Every object comes through this code path once, so this is a good
|
||||
// place to call the transform func.
|
||||
//
|
||||
// If obj is a DeletedFinalStateUnknown tombstone or the action is a Sync,
|
||||
// then the object have already gone through the transformer.
|
||||
//
|
||||
// If the objects already present in the cache are passed to Replace(),
|
||||
// the transformer must be idempotent to avoid re-mutating them,
|
||||
// or coordinate with all readers from the cache to avoid data races.
|
||||
// Default informers do not pass existing objects to Replace.
|
||||
if f.transformer != nil {
|
||||
_, isTombstone := obj.(DeletedFinalStateUnknown)
|
||||
if !isTombstone && internalActionType != Sync {
|
||||
var err error
|
||||
obj, err = f.transformer(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
oldDeltas := f.items[id]
|
||||
newDeltas := append(oldDeltas, Delta{actionType, obj})
|
||||
newDeltas = dedupDeltas(newDeltas)
|
||||
|
||||
if len(newDeltas) > 0 {
|
||||
if _, exists := f.items[id]; !exists {
|
||||
f.queue = append(f.queue, id)
|
||||
}
|
||||
f.items[id] = newDeltas
|
||||
f.cond.Broadcast()
|
||||
} else {
|
||||
// This never happens, because dedupDeltas never returns an empty list
|
||||
// when given a non-empty list (as it is here).
|
||||
// If somehow it happens anyway, deal with it but complain.
|
||||
if oldDeltas == nil {
|
||||
klog.Errorf("Impossible dedupDeltas for id=%q: oldDeltas=%#+v, obj=%#+v; ignoring", id, oldDeltas, obj)
|
||||
return nil
|
||||
}
|
||||
klog.Errorf("Impossible dedupDeltas for id=%q: oldDeltas=%#+v, obj=%#+v; breaking invariant by storing empty Deltas", id, oldDeltas, obj)
|
||||
f.items[id] = newDeltas
|
||||
return fmt.Errorf("Impossible dedupDeltas for id=%q: oldDeltas=%#+v, obj=%#+v; broke DeltaFIFO invariant by storing empty Deltas", id, oldDeltas, obj)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// List returns a list of all the items; it returns the object
|
||||
// from the most recent Delta.
|
||||
// You should treat the items returned inside the deltas as immutable.
|
||||
func (f *DeltaFIFO) List() []interface{} {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
return f.listLocked()
|
||||
}
|
||||
|
||||
func (f *DeltaFIFO) listLocked() []interface{} {
|
||||
list := make([]interface{}, 0, len(f.items))
|
||||
for _, item := range f.items {
|
||||
list = append(list, item.Newest().Object)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// ListKeys returns a list of all the keys of the objects currently
|
||||
// in the FIFO.
|
||||
func (f *DeltaFIFO) ListKeys() []string {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
list := make([]string, 0, len(f.queue))
|
||||
for _, key := range f.queue {
|
||||
list = append(list, key)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// Get returns the complete list of deltas for the requested item,
|
||||
// or sets exists=false.
|
||||
// You should treat the items returned inside the deltas as immutable.
|
||||
func (f *DeltaFIFO) Get(obj interface{}) (item interface{}, exists bool, err error) {
|
||||
key, err := f.KeyOf(obj)
|
||||
if err != nil {
|
||||
return nil, false, KeyError{obj, err}
|
||||
}
|
||||
return f.GetByKey(key)
|
||||
}
|
||||
|
||||
// GetByKey returns the complete list of deltas for the requested item,
|
||||
// setting exists=false if that list is empty.
|
||||
// You should treat the items returned inside the deltas as immutable.
|
||||
func (f *DeltaFIFO) GetByKey(key string) (item interface{}, exists bool, err error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
d, exists := f.items[key]
|
||||
if exists {
|
||||
// Copy item's slice so operations on this slice
|
||||
// won't interfere with the object we return.
|
||||
d = copyDeltas(d)
|
||||
}
|
||||
return d, exists, nil
|
||||
}
|
||||
|
||||
// IsClosed checks if the queue is closed
|
||||
func (f *DeltaFIFO) IsClosed() bool {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
return f.closed
|
||||
}
|
||||
|
||||
// Pop blocks until the queue has some items, and then returns one. If
|
||||
// multiple items are ready, they are returned in the order in which they were
|
||||
// added/updated. The item is removed from the queue (and the store) before it
|
||||
// is returned, so if you don't successfully process it, you need to add it back
|
||||
// with AddIfNotPresent().
|
||||
// process function is called under lock, so it is safe to update data structures
|
||||
// in it that need to be in sync with the queue (e.g. knownKeys). The PopProcessFunc
|
||||
// may return an instance of ErrRequeue with a nested error to indicate the current
|
||||
// item should be requeued (equivalent to calling AddIfNotPresent under the lock).
|
||||
// process should avoid expensive I/O operation so that other queue operations, i.e.
|
||||
// Add() and Get(), won't be blocked for too long.
|
||||
//
|
||||
// Pop returns a 'Deltas', which has a complete list of all the things
|
||||
// that happened to the object (deltas) while it was sitting in the queue.
|
||||
func (f *DeltaFIFO) Pop(process PopProcessFunc) (interface{}, error) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
for {
|
||||
for len(f.queue) == 0 {
|
||||
// When the queue is empty, invocation of Pop() is blocked until new item is enqueued.
|
||||
// When Close() is called, the f.closed is set and the condition is broadcasted.
|
||||
// Which causes this loop to continue and return from the Pop().
|
||||
if f.closed {
|
||||
return nil, ErrFIFOClosed
|
||||
}
|
||||
|
||||
f.cond.Wait()
|
||||
}
|
||||
isInInitialList := !f.hasSynced_locked()
|
||||
id := f.queue[0]
|
||||
f.queue = f.queue[1:]
|
||||
depth := len(f.queue)
|
||||
if f.initialPopulationCount > 0 {
|
||||
f.initialPopulationCount--
|
||||
}
|
||||
item, ok := f.items[id]
|
||||
if !ok {
|
||||
// This should never happen
|
||||
klog.Errorf("Inconceivable! %q was in f.queue but not f.items; ignoring.", id)
|
||||
continue
|
||||
}
|
||||
delete(f.items, id)
|
||||
// Only log traces if the queue depth is greater than 10 and it takes more than
|
||||
// 100 milliseconds to process one item from the queue.
|
||||
// Queue depth never goes high because processing an item is locking the queue,
|
||||
// and new items can't be added until processing finish.
|
||||
// https://github.com/kubernetes/kubernetes/issues/103789
|
||||
if depth > 10 {
|
||||
trace := utiltrace.New("DeltaFIFO Pop Process",
|
||||
utiltrace.Field{Key: "ID", Value: id},
|
||||
utiltrace.Field{Key: "Depth", Value: depth},
|
||||
utiltrace.Field{Key: "Reason", Value: "slow event handlers blocking the queue"})
|
||||
defer trace.LogIfLong(100 * time.Millisecond)
|
||||
}
|
||||
err := process(item, isInInitialList)
|
||||
if e, ok := err.(ErrRequeue); ok {
|
||||
f.addIfNotPresent(id, item)
|
||||
err = e.Err
|
||||
}
|
||||
// Don't need to copyDeltas here, because we're transferring
|
||||
// ownership to the caller.
|
||||
return item, err
|
||||
}
|
||||
}
|
||||
|
||||
// Replace atomically does two things: (1) it adds the given objects
|
||||
// using the Sync or Replace DeltaType and then (2) it does some deletions.
|
||||
// In particular: for every pre-existing key K that is not the key of
|
||||
// an object in `list` there is the effect of
|
||||
// `Delete(DeletedFinalStateUnknown{K, O})` where O is the latest known
|
||||
// object of K. The pre-existing keys are those in the union set of the keys in
|
||||
// `f.items` and `f.knownObjects` (if not nil). The last known object for key K is
|
||||
// the one present in the last delta in `f.items`. If there is no delta for K
|
||||
// in `f.items`, it is the object in `f.knownObjects`
|
||||
func (f *DeltaFIFO) Replace(list []interface{}, _ string) error {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
keys := make(sets.String, len(list))
|
||||
|
||||
// keep backwards compat for old clients
|
||||
action := Sync
|
||||
if f.emitDeltaTypeReplaced {
|
||||
action = Replaced
|
||||
}
|
||||
|
||||
// Add Sync/Replaced action for each new item.
|
||||
for _, item := range list {
|
||||
key, err := f.KeyOf(item)
|
||||
if err != nil {
|
||||
return KeyError{item, err}
|
||||
}
|
||||
keys.Insert(key)
|
||||
if err := f.queueActionInternalLocked(action, Replaced, item); err != nil {
|
||||
return fmt.Errorf("couldn't enqueue object: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Do deletion detection against objects in the queue
|
||||
queuedDeletions := 0
|
||||
for k, oldItem := range f.items {
|
||||
if keys.Has(k) {
|
||||
continue
|
||||
}
|
||||
// Delete pre-existing items not in the new list.
|
||||
// This could happen if watch deletion event was missed while
|
||||
// disconnected from apiserver.
|
||||
var deletedObj interface{}
|
||||
if n := oldItem.Newest(); n != nil {
|
||||
deletedObj = n.Object
|
||||
|
||||
// if the previous object is a DeletedFinalStateUnknown, we have to extract the actual Object
|
||||
if d, ok := deletedObj.(DeletedFinalStateUnknown); ok {
|
||||
deletedObj = d.Obj
|
||||
}
|
||||
}
|
||||
queuedDeletions++
|
||||
if err := f.queueActionLocked(Deleted, DeletedFinalStateUnknown{k, deletedObj}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if f.knownObjects != nil {
|
||||
// Detect deletions for objects not present in the queue, but present in KnownObjects
|
||||
knownKeys := f.knownObjects.ListKeys()
|
||||
for _, k := range knownKeys {
|
||||
if keys.Has(k) {
|
||||
continue
|
||||
}
|
||||
if len(f.items[k]) > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
deletedObj, exists, err := f.knownObjects.GetByKey(k)
|
||||
if err != nil {
|
||||
deletedObj = nil
|
||||
klog.Errorf("Unexpected error %v during lookup of key %v, placing DeleteFinalStateUnknown marker without object", err, k)
|
||||
} else if !exists {
|
||||
deletedObj = nil
|
||||
klog.Infof("Key %v does not exist in known objects store, placing DeleteFinalStateUnknown marker without object", k)
|
||||
}
|
||||
queuedDeletions++
|
||||
if err := f.queueActionLocked(Deleted, DeletedFinalStateUnknown{k, deletedObj}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !f.populated {
|
||||
f.populated = true
|
||||
f.initialPopulationCount = keys.Len() + queuedDeletions
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resync adds, with a Sync type of Delta, every object listed by
|
||||
// `f.knownObjects` whose key is not already queued for processing.
|
||||
// If `f.knownObjects` is `nil` then Resync does nothing.
|
||||
func (f *DeltaFIFO) Resync() error {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
if f.knownObjects == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
keys := f.knownObjects.ListKeys()
|
||||
for _, k := range keys {
|
||||
if err := f.syncKeyLocked(k); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *DeltaFIFO) syncKeyLocked(key string) error {
|
||||
obj, exists, err := f.knownObjects.GetByKey(key)
|
||||
if err != nil {
|
||||
klog.Errorf("Unexpected error %v during lookup of key %v, unable to queue object for sync", err, key)
|
||||
return nil
|
||||
} else if !exists {
|
||||
klog.Infof("Key %v does not exist in known objects store, unable to queue object for sync", key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// If we are doing Resync() and there is already an event queued for that object,
|
||||
// we ignore the Resync for it. This is to avoid the race, in which the resync
|
||||
// comes with the previous value of object (since queueing an event for the object
|
||||
// doesn't trigger changing the underlying store <knownObjects>.
|
||||
id, err := f.KeyOf(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
if len(f.items[id]) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := f.queueActionLocked(Sync, obj); err != nil {
|
||||
return fmt.Errorf("couldn't queue object: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// A KeyListerGetter is anything that knows how to list its keys and look up by key.
|
||||
type KeyListerGetter interface {
|
||||
KeyLister
|
||||
KeyGetter
|
||||
}
|
||||
|
||||
// A KeyLister is anything that knows how to list its keys.
|
||||
type KeyLister interface {
|
||||
ListKeys() []string
|
||||
}
|
||||
|
||||
// A KeyGetter is anything that knows how to get the value stored under a given key.
|
||||
type KeyGetter interface {
|
||||
// GetByKey returns the value associated with the key, or sets exists=false.
|
||||
GetByKey(key string) (value interface{}, exists bool, err error)
|
||||
}
|
||||
|
||||
// Oldest is a convenience function that returns the oldest delta, or
|
||||
// nil if there are no deltas.
|
||||
func (d Deltas) Oldest() *Delta {
|
||||
if len(d) > 0 {
|
||||
return &d[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Newest is a convenience function that returns the newest delta, or
|
||||
// nil if there are no deltas.
|
||||
func (d Deltas) Newest() *Delta {
|
||||
if n := len(d); n > 0 {
|
||||
return &d[n-1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyDeltas returns a shallow copy of d; that is, it copies the slice but not
|
||||
// the objects in the slice. This allows Get/List to return an object that we
|
||||
// know won't be clobbered by a subsequent modifications.
|
||||
func copyDeltas(d Deltas) Deltas {
|
||||
d2 := make(Deltas, len(d))
|
||||
copy(d2, d)
|
||||
return d2
|
||||
}
|
||||
|
||||
// DeletedFinalStateUnknown is placed into a DeltaFIFO in the case where an object
|
||||
// was deleted but the watch deletion event was missed while disconnected from
|
||||
// apiserver. In this case we don't know the final "resting" state of the object, so
|
||||
// there's a chance the included `Obj` is stale.
|
||||
type DeletedFinalStateUnknown struct {
|
||||
Key string
|
||||
Obj interface{}
|
||||
}
|
24
e2e/vendor/k8s.io/client-go/tools/cache/doc.go
generated
vendored
Normal file
24
e2e/vendor/k8s.io/client-go/tools/cache/doc.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
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 cache is a client-side caching mechanism. It is useful for
|
||||
// reducing the number of server calls you'd otherwise need to make.
|
||||
// Reflector watches a server and updates a Store. Two stores are provided;
|
||||
// one that simply caches objects (for example, to allow a scheduler to
|
||||
// list currently available nodes), and one that additionally acts as
|
||||
// a FIFO queue (for example, to allow a scheduler to process incoming
|
||||
// pods).
|
||||
package cache // import "k8s.io/client-go/tools/cache"
|
214
e2e/vendor/k8s.io/client-go/tools/cache/expiration_cache.go
generated
vendored
Normal file
214
e2e/vendor/k8s.io/client-go/tools/cache/expiration_cache.go
generated
vendored
Normal file
@ -0,0 +1,214 @@
|
||||
/*
|
||||
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 cache
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
// ExpirationCache implements the store interface
|
||||
// 1. All entries are automatically time stamped on insert
|
||||
// a. The key is computed based off the original item/keyFunc
|
||||
// b. The value inserted under that key is the timestamped item
|
||||
// 2. Expiration happens lazily on read based on the expiration policy
|
||||
// a. No item can be inserted into the store while we're expiring
|
||||
// *any* item in the cache.
|
||||
// 3. Time-stamps are stripped off unexpired entries before return
|
||||
//
|
||||
// Note that the ExpirationCache is inherently slower than a normal
|
||||
// threadSafeStore because it takes a write lock every time it checks if
|
||||
// an item has expired.
|
||||
type ExpirationCache struct {
|
||||
cacheStorage ThreadSafeStore
|
||||
keyFunc KeyFunc
|
||||
clock clock.Clock
|
||||
expirationPolicy ExpirationPolicy
|
||||
// expirationLock is a write lock used to guarantee that we don't clobber
|
||||
// newly inserted objects because of a stale expiration timestamp comparison
|
||||
expirationLock sync.Mutex
|
||||
}
|
||||
|
||||
// ExpirationPolicy dictates when an object expires. Currently only abstracted out
|
||||
// so unittests don't rely on the system clock.
|
||||
type ExpirationPolicy interface {
|
||||
IsExpired(obj *TimestampedEntry) bool
|
||||
}
|
||||
|
||||
// TTLPolicy implements a ttl based ExpirationPolicy.
|
||||
type TTLPolicy struct {
|
||||
// >0: Expire entries with an age > ttl
|
||||
// <=0: Don't expire any entry
|
||||
TTL time.Duration
|
||||
|
||||
// Clock used to calculate ttl expiration
|
||||
Clock clock.Clock
|
||||
}
|
||||
|
||||
// IsExpired returns true if the given object is older than the ttl, or it can't
|
||||
// determine its age.
|
||||
func (p *TTLPolicy) IsExpired(obj *TimestampedEntry) bool {
|
||||
return p.TTL > 0 && p.Clock.Since(obj.Timestamp) > p.TTL
|
||||
}
|
||||
|
||||
// TimestampedEntry is the only type allowed in a ExpirationCache.
|
||||
// Keep in mind that it is not safe to share timestamps between computers.
|
||||
// Behavior may be inconsistent if you get a timestamp from the API Server and
|
||||
// use it on the client machine as part of your ExpirationCache.
|
||||
type TimestampedEntry struct {
|
||||
Obj interface{}
|
||||
Timestamp time.Time
|
||||
key string
|
||||
}
|
||||
|
||||
// getTimestampedEntry returns the TimestampedEntry stored under the given key.
|
||||
func (c *ExpirationCache) getTimestampedEntry(key string) (*TimestampedEntry, bool) {
|
||||
item, _ := c.cacheStorage.Get(key)
|
||||
if tsEntry, ok := item.(*TimestampedEntry); ok {
|
||||
return tsEntry, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// getOrExpire retrieves the object from the TimestampedEntry if and only if it hasn't
|
||||
// already expired. It holds a write lock across deletion.
|
||||
func (c *ExpirationCache) getOrExpire(key string) (interface{}, bool) {
|
||||
// Prevent all inserts from the time we deem an item as "expired" to when we
|
||||
// delete it, so an un-expired item doesn't sneak in under the same key, just
|
||||
// before the Delete.
|
||||
c.expirationLock.Lock()
|
||||
defer c.expirationLock.Unlock()
|
||||
timestampedItem, exists := c.getTimestampedEntry(key)
|
||||
if !exists {
|
||||
return nil, false
|
||||
}
|
||||
if c.expirationPolicy.IsExpired(timestampedItem) {
|
||||
c.cacheStorage.Delete(key)
|
||||
return nil, false
|
||||
}
|
||||
return timestampedItem.Obj, true
|
||||
}
|
||||
|
||||
// GetByKey returns the item stored under the key, or sets exists=false.
|
||||
func (c *ExpirationCache) GetByKey(key string) (interface{}, bool, error) {
|
||||
obj, exists := c.getOrExpire(key)
|
||||
return obj, exists, nil
|
||||
}
|
||||
|
||||
// Get returns unexpired items. It purges the cache of expired items in the
|
||||
// process.
|
||||
func (c *ExpirationCache) Get(obj interface{}) (interface{}, bool, error) {
|
||||
key, err := c.keyFunc(obj)
|
||||
if err != nil {
|
||||
return nil, false, KeyError{obj, err}
|
||||
}
|
||||
obj, exists := c.getOrExpire(key)
|
||||
return obj, exists, nil
|
||||
}
|
||||
|
||||
// List retrieves a list of unexpired items. It purges the cache of expired
|
||||
// items in the process.
|
||||
func (c *ExpirationCache) List() []interface{} {
|
||||
items := c.cacheStorage.List()
|
||||
|
||||
list := make([]interface{}, 0, len(items))
|
||||
for _, item := range items {
|
||||
key := item.(*TimestampedEntry).key
|
||||
if obj, exists := c.getOrExpire(key); exists {
|
||||
list = append(list, obj)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// ListKeys returns a list of all keys in the expiration cache.
|
||||
func (c *ExpirationCache) ListKeys() []string {
|
||||
return c.cacheStorage.ListKeys()
|
||||
}
|
||||
|
||||
// Add timestamps an item and inserts it into the cache, overwriting entries
|
||||
// that might exist under the same key.
|
||||
func (c *ExpirationCache) Add(obj interface{}) error {
|
||||
key, err := c.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
c.expirationLock.Lock()
|
||||
defer c.expirationLock.Unlock()
|
||||
|
||||
c.cacheStorage.Add(key, &TimestampedEntry{obj, c.clock.Now(), key})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update has not been implemented yet for lack of a use case, so this method
|
||||
// simply calls `Add`. This effectively refreshes the timestamp.
|
||||
func (c *ExpirationCache) Update(obj interface{}) error {
|
||||
return c.Add(obj)
|
||||
}
|
||||
|
||||
// Delete removes an item from the cache.
|
||||
func (c *ExpirationCache) Delete(obj interface{}) error {
|
||||
key, err := c.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
c.expirationLock.Lock()
|
||||
defer c.expirationLock.Unlock()
|
||||
c.cacheStorage.Delete(key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Replace will convert all items in the given list to TimestampedEntries
|
||||
// before attempting the replace operation. The replace operation will
|
||||
// delete the contents of the ExpirationCache `c`.
|
||||
func (c *ExpirationCache) Replace(list []interface{}, resourceVersion string) error {
|
||||
items := make(map[string]interface{}, len(list))
|
||||
ts := c.clock.Now()
|
||||
for _, item := range list {
|
||||
key, err := c.keyFunc(item)
|
||||
if err != nil {
|
||||
return KeyError{item, err}
|
||||
}
|
||||
items[key] = &TimestampedEntry{item, ts, key}
|
||||
}
|
||||
c.expirationLock.Lock()
|
||||
defer c.expirationLock.Unlock()
|
||||
c.cacheStorage.Replace(items, resourceVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resync is a no-op for one of these
|
||||
func (c *ExpirationCache) Resync() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewTTLStore creates and returns a ExpirationCache with a TTLPolicy
|
||||
func NewTTLStore(keyFunc KeyFunc, ttl time.Duration) Store {
|
||||
return NewExpirationStore(keyFunc, &TTLPolicy{ttl, clock.RealClock{}})
|
||||
}
|
||||
|
||||
// NewExpirationStore creates and returns a ExpirationCache for a given policy
|
||||
func NewExpirationStore(keyFunc KeyFunc, expirationPolicy ExpirationPolicy) Store {
|
||||
return &ExpirationCache{
|
||||
cacheStorage: NewThreadSafeStore(Indexers{}, Indices{}),
|
||||
keyFunc: keyFunc,
|
||||
clock: clock.RealClock{},
|
||||
expirationPolicy: expirationPolicy,
|
||||
}
|
||||
}
|
57
e2e/vendor/k8s.io/client-go/tools/cache/expiration_cache_fakes.go
generated
vendored
Normal file
57
e2e/vendor/k8s.io/client-go/tools/cache/expiration_cache_fakes.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
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 cache
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
type fakeThreadSafeMap struct {
|
||||
ThreadSafeStore
|
||||
deletedKeys chan<- string
|
||||
}
|
||||
|
||||
func (c *fakeThreadSafeMap) Delete(key string) {
|
||||
if c.deletedKeys != nil {
|
||||
c.ThreadSafeStore.Delete(key)
|
||||
c.deletedKeys <- key
|
||||
}
|
||||
}
|
||||
|
||||
// FakeExpirationPolicy keeps the list for keys which never expires.
|
||||
type FakeExpirationPolicy struct {
|
||||
NeverExpire sets.String
|
||||
RetrieveKeyFunc KeyFunc
|
||||
}
|
||||
|
||||
// IsExpired used to check if object is expired.
|
||||
func (p *FakeExpirationPolicy) IsExpired(obj *TimestampedEntry) bool {
|
||||
key, _ := p.RetrieveKeyFunc(obj)
|
||||
return !p.NeverExpire.Has(key)
|
||||
}
|
||||
|
||||
// NewFakeExpirationStore creates a new instance for the ExpirationCache.
|
||||
func NewFakeExpirationStore(keyFunc KeyFunc, deletedKeys chan<- string, expirationPolicy ExpirationPolicy, cacheClock clock.Clock) Store {
|
||||
cacheStorage := NewThreadSafeStore(Indexers{}, Indices{})
|
||||
return &ExpirationCache{
|
||||
cacheStorage: &fakeThreadSafeMap{cacheStorage, deletedKeys},
|
||||
keyFunc: keyFunc,
|
||||
clock: cacheClock,
|
||||
expirationPolicy: expirationPolicy,
|
||||
}
|
||||
}
|
102
e2e/vendor/k8s.io/client-go/tools/cache/fake_custom_store.go
generated
vendored
Normal file
102
e2e/vendor/k8s.io/client-go/tools/cache/fake_custom_store.go
generated
vendored
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
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
|
||||
|
||||
// FakeCustomStore lets you define custom functions for store operations.
|
||||
type FakeCustomStore struct {
|
||||
AddFunc func(obj interface{}) error
|
||||
UpdateFunc func(obj interface{}) error
|
||||
DeleteFunc func(obj interface{}) error
|
||||
ListFunc func() []interface{}
|
||||
ListKeysFunc func() []string
|
||||
GetFunc func(obj interface{}) (item interface{}, exists bool, err error)
|
||||
GetByKeyFunc func(key string) (item interface{}, exists bool, err error)
|
||||
ReplaceFunc func(list []interface{}, resourceVersion string) error
|
||||
ResyncFunc func() error
|
||||
}
|
||||
|
||||
// Add calls the custom Add function if defined
|
||||
func (f *FakeCustomStore) Add(obj interface{}) error {
|
||||
if f.AddFunc != nil {
|
||||
return f.AddFunc(obj)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update calls the custom Update function if defined
|
||||
func (f *FakeCustomStore) Update(obj interface{}) error {
|
||||
if f.UpdateFunc != nil {
|
||||
return f.UpdateFunc(obj)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete calls the custom Delete function if defined
|
||||
func (f *FakeCustomStore) Delete(obj interface{}) error {
|
||||
if f.DeleteFunc != nil {
|
||||
return f.DeleteFunc(obj)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// List calls the custom List function if defined
|
||||
func (f *FakeCustomStore) List() []interface{} {
|
||||
if f.ListFunc != nil {
|
||||
return f.ListFunc()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListKeys calls the custom ListKeys function if defined
|
||||
func (f *FakeCustomStore) ListKeys() []string {
|
||||
if f.ListKeysFunc != nil {
|
||||
return f.ListKeysFunc()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get calls the custom Get function if defined
|
||||
func (f *FakeCustomStore) Get(obj interface{}) (item interface{}, exists bool, err error) {
|
||||
if f.GetFunc != nil {
|
||||
return f.GetFunc(obj)
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// GetByKey calls the custom GetByKey function if defined
|
||||
func (f *FakeCustomStore) GetByKey(key string) (item interface{}, exists bool, err error) {
|
||||
if f.GetByKeyFunc != nil {
|
||||
return f.GetByKeyFunc(key)
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// Replace calls the custom Replace function if defined
|
||||
func (f *FakeCustomStore) Replace(list []interface{}, resourceVersion string) error {
|
||||
if f.ReplaceFunc != nil {
|
||||
return f.ReplaceFunc(list, resourceVersion)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resync calls the custom Resync function if defined
|
||||
func (f *FakeCustomStore) Resync() error {
|
||||
if f.ResyncFunc != nil {
|
||||
return f.ResyncFunc()
|
||||
}
|
||||
return nil
|
||||
}
|
382
e2e/vendor/k8s.io/client-go/tools/cache/fifo.go
generated
vendored
Normal file
382
e2e/vendor/k8s.io/client-go/tools/cache/fifo.go
generated
vendored
Normal file
@ -0,0 +1,382 @@
|
||||
/*
|
||||
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 cache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// PopProcessFunc is passed to Pop() method of Queue interface.
|
||||
// It is supposed to process the accumulator popped from the queue.
|
||||
type PopProcessFunc func(obj interface{}, isInInitialList bool) error
|
||||
|
||||
// ErrRequeue may be returned by a PopProcessFunc to safely requeue
|
||||
// the current item. The value of Err will be returned from Pop.
|
||||
type ErrRequeue struct {
|
||||
// Err is returned by the Pop function
|
||||
Err error
|
||||
}
|
||||
|
||||
// ErrFIFOClosed used when FIFO is closed
|
||||
var ErrFIFOClosed = errors.New("DeltaFIFO: manipulating with closed queue")
|
||||
|
||||
func (e ErrRequeue) Error() string {
|
||||
if e.Err == nil {
|
||||
return "the popped item should be requeued without returning an error"
|
||||
}
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// Queue extends Store with a collection of Store keys to "process".
|
||||
// Every Add, Update, or Delete may put the object's key in that collection.
|
||||
// A Queue has a way to derive the corresponding key given an accumulator.
|
||||
// A Queue can be accessed concurrently from multiple goroutines.
|
||||
// A Queue can be "closed", after which Pop operations return an error.
|
||||
type Queue interface {
|
||||
Store
|
||||
|
||||
// Pop blocks until there is at least one key to process or the
|
||||
// Queue is closed. In the latter case Pop returns with an error.
|
||||
// In the former case Pop atomically picks one key to process,
|
||||
// removes that (key, accumulator) association from the Store, and
|
||||
// processes the accumulator. Pop returns the accumulator that
|
||||
// was processed and the result of processing. The PopProcessFunc
|
||||
// may return an ErrRequeue{inner} and in this case Pop will (a)
|
||||
// return that (key, accumulator) association to the Queue as part
|
||||
// of the atomic processing and (b) return the inner error from
|
||||
// Pop.
|
||||
Pop(PopProcessFunc) (interface{}, error)
|
||||
|
||||
// AddIfNotPresent puts the given accumulator into the Queue (in
|
||||
// association with the accumulator's key) if and only if that key
|
||||
// is not already associated with a non-empty accumulator.
|
||||
AddIfNotPresent(interface{}) error
|
||||
|
||||
// HasSynced returns true if the first batch of keys have all been
|
||||
// popped. The first batch of keys are those of the first Replace
|
||||
// operation if that happened before any Add, AddIfNotPresent,
|
||||
// Update, or Delete; otherwise the first batch is empty.
|
||||
HasSynced() bool
|
||||
|
||||
// Close the queue
|
||||
Close()
|
||||
}
|
||||
|
||||
// Pop is helper function for popping from Queue.
|
||||
// WARNING: Do NOT use this function in non-test code to avoid races
|
||||
// unless you really really really really know what you are doing.
|
||||
//
|
||||
// NOTE: This function is deprecated and may be removed in the future without
|
||||
// additional warning.
|
||||
func Pop(queue Queue) interface{} {
|
||||
var result interface{}
|
||||
queue.Pop(func(obj interface{}, isInInitialList bool) error {
|
||||
result = obj
|
||||
return nil
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
// FIFO is a Queue in which (a) each accumulator is simply the most
|
||||
// recently provided object and (b) the collection of keys to process
|
||||
// is a FIFO. The accumulators all start out empty, and deleting an
|
||||
// object from its accumulator empties the accumulator. The Resync
|
||||
// operation is a no-op.
|
||||
//
|
||||
// Thus: if multiple adds/updates of a single object happen while that
|
||||
// object's key is in the queue before it has been processed then it
|
||||
// will only be processed once, and when it is processed the most
|
||||
// recent version will be processed. This can't be done with a channel
|
||||
//
|
||||
// FIFO solves this use case:
|
||||
// - You want to process every object (exactly) once.
|
||||
// - You want to process the most recent version of the object when you process it.
|
||||
// - You do not want to process deleted objects, they should be removed from the queue.
|
||||
// - You do not want to periodically reprocess objects.
|
||||
//
|
||||
// Compare with DeltaFIFO for other use cases.
|
||||
type FIFO struct {
|
||||
lock sync.RWMutex
|
||||
cond sync.Cond
|
||||
// We depend on the property that every key in `items` is also in `queue`
|
||||
items map[string]interface{}
|
||||
queue []string
|
||||
|
||||
// populated is true if the first batch of items inserted by Replace() has been populated
|
||||
// or Delete/Add/Update was called first.
|
||||
populated bool
|
||||
// initialPopulationCount is the number of items inserted by the first call of Replace()
|
||||
initialPopulationCount int
|
||||
|
||||
// keyFunc is used to make the key used for queued item insertion and retrieval, and
|
||||
// should be deterministic.
|
||||
keyFunc KeyFunc
|
||||
|
||||
// Indication the queue is closed.
|
||||
// Used to indicate a queue is closed so a control loop can exit when a queue is empty.
|
||||
// Currently, not used to gate any of CRUD operations.
|
||||
closed bool
|
||||
}
|
||||
|
||||
var (
|
||||
_ = Queue(&FIFO{}) // FIFO is a Queue
|
||||
)
|
||||
|
||||
// Close the queue.
|
||||
func (f *FIFO) Close() {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
f.closed = true
|
||||
f.cond.Broadcast()
|
||||
}
|
||||
|
||||
// HasSynced returns true if an Add/Update/Delete/AddIfNotPresent are called first,
|
||||
// or the first batch of items inserted by Replace() has been popped.
|
||||
func (f *FIFO) HasSynced() bool {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
return f.hasSynced_locked()
|
||||
}
|
||||
|
||||
func (f *FIFO) hasSynced_locked() bool {
|
||||
return f.populated && f.initialPopulationCount == 0
|
||||
}
|
||||
|
||||
// Add inserts an item, and puts it in the queue. The item is only enqueued
|
||||
// if it doesn't already exist in the set.
|
||||
func (f *FIFO) Add(obj interface{}) error {
|
||||
id, err := f.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
f.populated = true
|
||||
if _, exists := f.items[id]; !exists {
|
||||
f.queue = append(f.queue, id)
|
||||
}
|
||||
f.items[id] = obj
|
||||
f.cond.Broadcast()
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddIfNotPresent inserts an item, and puts it in the queue. If the item is already
|
||||
// present in the set, it is neither enqueued nor added to the set.
|
||||
//
|
||||
// This is useful in a single producer/consumer scenario so that the consumer can
|
||||
// safely retry items without contending with the producer and potentially enqueueing
|
||||
// stale items.
|
||||
func (f *FIFO) AddIfNotPresent(obj interface{}) error {
|
||||
id, err := f.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
f.addIfNotPresent(id, obj)
|
||||
return nil
|
||||
}
|
||||
|
||||
// addIfNotPresent assumes the fifo lock is already held and adds the provided
|
||||
// item to the queue under id if it does not already exist.
|
||||
func (f *FIFO) addIfNotPresent(id string, obj interface{}) {
|
||||
f.populated = true
|
||||
if _, exists := f.items[id]; exists {
|
||||
return
|
||||
}
|
||||
|
||||
f.queue = append(f.queue, id)
|
||||
f.items[id] = obj
|
||||
f.cond.Broadcast()
|
||||
}
|
||||
|
||||
// Update is the same as Add in this implementation.
|
||||
func (f *FIFO) Update(obj interface{}) error {
|
||||
return f.Add(obj)
|
||||
}
|
||||
|
||||
// Delete removes an item. It doesn't add it to the queue, because
|
||||
// this implementation assumes the consumer only cares about the objects,
|
||||
// not the order in which they were created/added.
|
||||
func (f *FIFO) Delete(obj interface{}) error {
|
||||
id, err := f.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
f.populated = true
|
||||
delete(f.items, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// List returns a list of all the items.
|
||||
func (f *FIFO) List() []interface{} {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
list := make([]interface{}, 0, len(f.items))
|
||||
for _, item := range f.items {
|
||||
list = append(list, item)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// ListKeys returns a list of all the keys of the objects currently
|
||||
// in the FIFO.
|
||||
func (f *FIFO) ListKeys() []string {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
list := make([]string, 0, len(f.items))
|
||||
for key := range f.items {
|
||||
list = append(list, key)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// Get returns the requested item, or sets exists=false.
|
||||
func (f *FIFO) Get(obj interface{}) (item interface{}, exists bool, err error) {
|
||||
key, err := f.keyFunc(obj)
|
||||
if err != nil {
|
||||
return nil, false, KeyError{obj, err}
|
||||
}
|
||||
return f.GetByKey(key)
|
||||
}
|
||||
|
||||
// GetByKey returns the requested item, or sets exists=false.
|
||||
func (f *FIFO) GetByKey(key string) (item interface{}, exists bool, err error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
item, exists = f.items[key]
|
||||
return item, exists, nil
|
||||
}
|
||||
|
||||
// IsClosed checks if the queue is closed
|
||||
func (f *FIFO) IsClosed() bool {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
return f.closed
|
||||
}
|
||||
|
||||
// Pop waits until an item is ready and processes it. If multiple items are
|
||||
// ready, they are returned in the order in which they were added/updated.
|
||||
// The item is removed from the queue (and the store) before it is processed,
|
||||
// so if you don't successfully process it, it should be added back with
|
||||
// AddIfNotPresent(). process function is called under lock, so it is safe
|
||||
// update data structures in it that need to be in sync with the queue.
|
||||
func (f *FIFO) Pop(process PopProcessFunc) (interface{}, error) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
for {
|
||||
for len(f.queue) == 0 {
|
||||
// When the queue is empty, invocation of Pop() is blocked until new item is enqueued.
|
||||
// When Close() is called, the f.closed is set and the condition is broadcasted.
|
||||
// Which causes this loop to continue and return from the Pop().
|
||||
if f.closed {
|
||||
return nil, ErrFIFOClosed
|
||||
}
|
||||
|
||||
f.cond.Wait()
|
||||
}
|
||||
isInInitialList := !f.hasSynced_locked()
|
||||
id := f.queue[0]
|
||||
f.queue = f.queue[1:]
|
||||
if f.initialPopulationCount > 0 {
|
||||
f.initialPopulationCount--
|
||||
}
|
||||
item, ok := f.items[id]
|
||||
if !ok {
|
||||
// Item may have been deleted subsequently.
|
||||
continue
|
||||
}
|
||||
delete(f.items, id)
|
||||
err := process(item, isInInitialList)
|
||||
if e, ok := err.(ErrRequeue); ok {
|
||||
f.addIfNotPresent(id, item)
|
||||
err = e.Err
|
||||
}
|
||||
return item, err
|
||||
}
|
||||
}
|
||||
|
||||
// Replace will delete the contents of 'f', using instead the given map.
|
||||
// 'f' takes ownership of the map, you should not reference the map again
|
||||
// after calling this function. f's queue is reset, too; upon return, it
|
||||
// will contain the items in the map, in no particular order.
|
||||
func (f *FIFO) Replace(list []interface{}, resourceVersion string) error {
|
||||
items := make(map[string]interface{}, len(list))
|
||||
for _, item := range list {
|
||||
key, err := f.keyFunc(item)
|
||||
if err != nil {
|
||||
return KeyError{item, err}
|
||||
}
|
||||
items[key] = item
|
||||
}
|
||||
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
if !f.populated {
|
||||
f.populated = true
|
||||
f.initialPopulationCount = len(items)
|
||||
}
|
||||
|
||||
f.items = items
|
||||
f.queue = f.queue[:0]
|
||||
for id := range items {
|
||||
f.queue = append(f.queue, id)
|
||||
}
|
||||
if len(f.queue) > 0 {
|
||||
f.cond.Broadcast()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resync will ensure that every object in the Store has its key in the queue.
|
||||
// This should be a no-op, because that property is maintained by all operations.
|
||||
func (f *FIFO) Resync() error {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
inQueue := sets.NewString()
|
||||
for _, id := range f.queue {
|
||||
inQueue.Insert(id)
|
||||
}
|
||||
for id := range f.items {
|
||||
if !inQueue.Has(id) {
|
||||
f.queue = append(f.queue, id)
|
||||
}
|
||||
}
|
||||
if len(f.queue) > 0 {
|
||||
f.cond.Broadcast()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewFIFO returns a Store which can be used to queue up items to
|
||||
// process.
|
||||
func NewFIFO(keyFunc KeyFunc) *FIFO {
|
||||
f := &FIFO{
|
||||
items: map[string]interface{}{},
|
||||
queue: []string{},
|
||||
keyFunc: keyFunc,
|
||||
}
|
||||
f.cond.L = &f.lock
|
||||
return f
|
||||
}
|
322
e2e/vendor/k8s.io/client-go/tools/cache/heap.go
generated
vendored
Normal file
322
e2e/vendor/k8s.io/client-go/tools/cache/heap.go
generated
vendored
Normal file
@ -0,0 +1,322 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// This file implements a heap data structure.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
closedMsg = "heap is closed"
|
||||
)
|
||||
|
||||
// LessFunc is used to compare two objects in the heap.
|
||||
type LessFunc func(interface{}, interface{}) bool
|
||||
|
||||
type heapItem struct {
|
||||
obj interface{} // The object which is stored in the heap.
|
||||
index int // The index of the object's key in the Heap.queue.
|
||||
}
|
||||
|
||||
type itemKeyValue struct {
|
||||
key string
|
||||
obj interface{}
|
||||
}
|
||||
|
||||
// heapData is an internal struct that implements the standard heap interface
|
||||
// and keeps the data stored in the heap.
|
||||
type heapData struct {
|
||||
// items is a map from key of the objects to the objects and their index.
|
||||
// We depend on the property that items in the map are in the queue and vice versa.
|
||||
items map[string]*heapItem
|
||||
// queue implements a heap data structure and keeps the order of elements
|
||||
// according to the heap invariant. The queue keeps the keys of objects stored
|
||||
// in "items".
|
||||
queue []string
|
||||
|
||||
// keyFunc is used to make the key used for queued item insertion and retrieval, and
|
||||
// should be deterministic.
|
||||
keyFunc KeyFunc
|
||||
// lessFunc is used to compare two objects in the heap.
|
||||
lessFunc LessFunc
|
||||
}
|
||||
|
||||
var (
|
||||
_ = heap.Interface(&heapData{}) // heapData is a standard heap
|
||||
)
|
||||
|
||||
// Less compares two objects and returns true if the first one should go
|
||||
// in front of the second one in the heap.
|
||||
func (h *heapData) Less(i, j int) bool {
|
||||
if i > len(h.queue) || j > len(h.queue) {
|
||||
return false
|
||||
}
|
||||
itemi, ok := h.items[h.queue[i]]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
itemj, ok := h.items[h.queue[j]]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return h.lessFunc(itemi.obj, itemj.obj)
|
||||
}
|
||||
|
||||
// Len returns the number of items in the Heap.
|
||||
func (h *heapData) Len() int { return len(h.queue) }
|
||||
|
||||
// Swap implements swapping of two elements in the heap. This is a part of standard
|
||||
// heap interface and should never be called directly.
|
||||
func (h *heapData) Swap(i, j int) {
|
||||
h.queue[i], h.queue[j] = h.queue[j], h.queue[i]
|
||||
item := h.items[h.queue[i]]
|
||||
item.index = i
|
||||
item = h.items[h.queue[j]]
|
||||
item.index = j
|
||||
}
|
||||
|
||||
// Push is supposed to be called by heap.Push only.
|
||||
func (h *heapData) Push(kv interface{}) {
|
||||
keyValue := kv.(*itemKeyValue)
|
||||
n := len(h.queue)
|
||||
h.items[keyValue.key] = &heapItem{keyValue.obj, n}
|
||||
h.queue = append(h.queue, keyValue.key)
|
||||
}
|
||||
|
||||
// Pop is supposed to be called by heap.Pop only.
|
||||
func (h *heapData) Pop() interface{} {
|
||||
key := h.queue[len(h.queue)-1]
|
||||
h.queue = h.queue[0 : len(h.queue)-1]
|
||||
item, ok := h.items[key]
|
||||
if !ok {
|
||||
// This is an error
|
||||
return nil
|
||||
}
|
||||
delete(h.items, key)
|
||||
return item.obj
|
||||
}
|
||||
|
||||
// Heap is a thread-safe producer/consumer queue that implements a heap data structure.
|
||||
// It can be used to implement priority queues and similar data structures.
|
||||
type Heap struct {
|
||||
lock sync.RWMutex
|
||||
cond sync.Cond
|
||||
|
||||
// data stores objects and has a queue that keeps their ordering according
|
||||
// to the heap invariant.
|
||||
data *heapData
|
||||
|
||||
// closed indicates that the queue is closed.
|
||||
// It is mainly used to let Pop() exit its control loop while waiting for an item.
|
||||
closed bool
|
||||
}
|
||||
|
||||
// Close the Heap and signals condition variables that may be waiting to pop
|
||||
// items from the heap.
|
||||
func (h *Heap) Close() {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
h.closed = true
|
||||
h.cond.Broadcast()
|
||||
}
|
||||
|
||||
// Add inserts an item, and puts it in the queue. The item is updated if it
|
||||
// already exists.
|
||||
func (h *Heap) Add(obj interface{}) error {
|
||||
key, err := h.data.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
if h.closed {
|
||||
return fmt.Errorf(closedMsg)
|
||||
}
|
||||
if _, exists := h.data.items[key]; exists {
|
||||
h.data.items[key].obj = obj
|
||||
heap.Fix(h.data, h.data.items[key].index)
|
||||
} else {
|
||||
h.addIfNotPresentLocked(key, obj)
|
||||
}
|
||||
h.cond.Broadcast()
|
||||
return nil
|
||||
}
|
||||
|
||||
// BulkAdd adds all the items in the list to the queue and then signals the condition
|
||||
// variable. It is useful when the caller would like to add all of the items
|
||||
// to the queue before consumer starts processing them.
|
||||
func (h *Heap) BulkAdd(list []interface{}) error {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
if h.closed {
|
||||
return fmt.Errorf(closedMsg)
|
||||
}
|
||||
for _, obj := range list {
|
||||
key, err := h.data.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
if _, exists := h.data.items[key]; exists {
|
||||
h.data.items[key].obj = obj
|
||||
heap.Fix(h.data, h.data.items[key].index)
|
||||
} else {
|
||||
h.addIfNotPresentLocked(key, obj)
|
||||
}
|
||||
}
|
||||
h.cond.Broadcast()
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddIfNotPresent inserts an item, and puts it in the queue. If an item with
|
||||
// the key is present in the map, no changes is made to the item.
|
||||
//
|
||||
// This is useful in a single producer/consumer scenario so that the consumer can
|
||||
// safely retry items without contending with the producer and potentially enqueueing
|
||||
// stale items.
|
||||
func (h *Heap) AddIfNotPresent(obj interface{}) error {
|
||||
id, err := h.data.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
if h.closed {
|
||||
return fmt.Errorf(closedMsg)
|
||||
}
|
||||
h.addIfNotPresentLocked(id, obj)
|
||||
h.cond.Broadcast()
|
||||
return nil
|
||||
}
|
||||
|
||||
// addIfNotPresentLocked assumes the lock is already held and adds the provided
|
||||
// item to the queue if it does not already exist.
|
||||
func (h *Heap) addIfNotPresentLocked(key string, obj interface{}) {
|
||||
if _, exists := h.data.items[key]; exists {
|
||||
return
|
||||
}
|
||||
heap.Push(h.data, &itemKeyValue{key, obj})
|
||||
}
|
||||
|
||||
// Update is the same as Add in this implementation. When the item does not
|
||||
// exist, it is added.
|
||||
func (h *Heap) Update(obj interface{}) error {
|
||||
return h.Add(obj)
|
||||
}
|
||||
|
||||
// Delete removes an item.
|
||||
func (h *Heap) Delete(obj interface{}) error {
|
||||
key, err := h.data.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
if item, ok := h.data.items[key]; ok {
|
||||
heap.Remove(h.data, item.index)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("object not found")
|
||||
}
|
||||
|
||||
// Pop waits until an item is ready. If multiple items are
|
||||
// ready, they are returned in the order given by Heap.data.lessFunc.
|
||||
func (h *Heap) Pop() (interface{}, error) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
for len(h.data.queue) == 0 {
|
||||
// When the queue is empty, invocation of Pop() is blocked until new item is enqueued.
|
||||
// When Close() is called, the h.closed is set and the condition is broadcast,
|
||||
// which causes this loop to continue and return from the Pop().
|
||||
if h.closed {
|
||||
return nil, fmt.Errorf("heap is closed")
|
||||
}
|
||||
h.cond.Wait()
|
||||
}
|
||||
obj := heap.Pop(h.data)
|
||||
if obj == nil {
|
||||
return nil, fmt.Errorf("object was removed from heap data")
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
// List returns a list of all the items.
|
||||
func (h *Heap) List() []interface{} {
|
||||
h.lock.RLock()
|
||||
defer h.lock.RUnlock()
|
||||
list := make([]interface{}, 0, len(h.data.items))
|
||||
for _, item := range h.data.items {
|
||||
list = append(list, item.obj)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// ListKeys returns a list of all the keys of the objects currently in the Heap.
|
||||
func (h *Heap) ListKeys() []string {
|
||||
h.lock.RLock()
|
||||
defer h.lock.RUnlock()
|
||||
list := make([]string, 0, len(h.data.items))
|
||||
for key := range h.data.items {
|
||||
list = append(list, key)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// Get returns the requested item, or sets exists=false.
|
||||
func (h *Heap) Get(obj interface{}) (interface{}, bool, error) {
|
||||
key, err := h.data.keyFunc(obj)
|
||||
if err != nil {
|
||||
return nil, false, KeyError{obj, err}
|
||||
}
|
||||
return h.GetByKey(key)
|
||||
}
|
||||
|
||||
// GetByKey returns the requested item, or sets exists=false.
|
||||
func (h *Heap) GetByKey(key string) (interface{}, bool, error) {
|
||||
h.lock.RLock()
|
||||
defer h.lock.RUnlock()
|
||||
item, exists := h.data.items[key]
|
||||
if !exists {
|
||||
return nil, false, nil
|
||||
}
|
||||
return item.obj, true, nil
|
||||
}
|
||||
|
||||
// IsClosed returns true if the queue is closed.
|
||||
func (h *Heap) IsClosed() bool {
|
||||
h.lock.RLock()
|
||||
defer h.lock.RUnlock()
|
||||
return h.closed
|
||||
}
|
||||
|
||||
// NewHeap returns a Heap which can be used to queue up items to process.
|
||||
func NewHeap(keyFn KeyFunc, lessFn LessFunc) *Heap {
|
||||
h := &Heap{
|
||||
data: &heapData{
|
||||
items: map[string]*heapItem{},
|
||||
queue: []string{},
|
||||
keyFunc: keyFn,
|
||||
lessFunc: lessFn,
|
||||
},
|
||||
}
|
||||
h.cond.L = &h.lock
|
||||
return h
|
||||
}
|
100
e2e/vendor/k8s.io/client-go/tools/cache/index.go
generated
vendored
Normal file
100
e2e/vendor/k8s.io/client-go/tools/cache/index.go
generated
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
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 cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// Indexer extends Store with multiple indices and restricts each
|
||||
// accumulator to simply hold the current object (and be empty after
|
||||
// Delete).
|
||||
//
|
||||
// There are three kinds of strings here:
|
||||
// 1. a storage key, as defined in the Store interface,
|
||||
// 2. a name of an index, and
|
||||
// 3. an "indexed value", which is produced by an IndexFunc and
|
||||
// can be a field value or any other string computed from the object.
|
||||
type Indexer interface {
|
||||
Store
|
||||
// Index returns the stored objects whose set of indexed values
|
||||
// intersects the set of indexed values of the given object, for
|
||||
// the named index
|
||||
Index(indexName string, obj interface{}) ([]interface{}, error)
|
||||
// IndexKeys returns the storage keys of the stored objects whose
|
||||
// set of indexed values for the named index includes the given
|
||||
// indexed value
|
||||
IndexKeys(indexName, indexedValue string) ([]string, error)
|
||||
// ListIndexFuncValues returns all the indexed values of the given index
|
||||
ListIndexFuncValues(indexName string) []string
|
||||
// ByIndex returns the stored objects whose set of indexed values
|
||||
// for the named index includes the given indexed value
|
||||
ByIndex(indexName, indexedValue string) ([]interface{}, error)
|
||||
// GetIndexers return the indexers
|
||||
GetIndexers() Indexers
|
||||
|
||||
// AddIndexers adds more indexers to this store. This supports adding indexes after the store already has items.
|
||||
AddIndexers(newIndexers Indexers) error
|
||||
}
|
||||
|
||||
// IndexFunc knows how to compute the set of indexed values for an object.
|
||||
type IndexFunc func(obj interface{}) ([]string, error)
|
||||
|
||||
// IndexFuncToKeyFuncAdapter adapts an indexFunc to a keyFunc. This is only useful if your index function returns
|
||||
// unique values for every object. This conversion can create errors when more than one key is found. You
|
||||
// should prefer to make proper key and index functions.
|
||||
func IndexFuncToKeyFuncAdapter(indexFunc IndexFunc) KeyFunc {
|
||||
return func(obj interface{}) (string, error) {
|
||||
indexKeys, err := indexFunc(obj)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(indexKeys) > 1 {
|
||||
return "", fmt.Errorf("too many keys: %v", indexKeys)
|
||||
}
|
||||
if len(indexKeys) == 0 {
|
||||
return "", fmt.Errorf("unexpected empty indexKeys")
|
||||
}
|
||||
return indexKeys[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// NamespaceIndex is the lookup name for the most common index function, which is to index by the namespace field.
|
||||
NamespaceIndex string = "namespace"
|
||||
)
|
||||
|
||||
// MetaNamespaceIndexFunc is a default index function that indexes based on an object's namespace
|
||||
func MetaNamespaceIndexFunc(obj interface{}) ([]string, error) {
|
||||
meta, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return []string{""}, fmt.Errorf("object has no meta: %v", err)
|
||||
}
|
||||
return []string{meta.GetNamespace()}, nil
|
||||
}
|
||||
|
||||
// Index maps the indexed value to a set of keys in the store that match on that value
|
||||
type Index map[string]sets.String
|
||||
|
||||
// Indexers maps a name to an IndexFunc
|
||||
type Indexers map[string]IndexFunc
|
||||
|
||||
// Indices maps a name to an Index
|
||||
type Indices map[string]Index
|
171
e2e/vendor/k8s.io/client-go/tools/cache/listers.go
generated
vendored
Normal file
171
e2e/vendor/k8s.io/client-go/tools/cache/listers.go
generated
vendored
Normal file
@ -0,0 +1,171 @@
|
||||
/*
|
||||
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 cache
|
||||
|
||||
import (
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// AppendFunc is used to add a matching item to whatever list the caller is using
|
||||
type AppendFunc func(interface{})
|
||||
|
||||
// ListAll lists items in the store matching the given selector, calling appendFn on each one.
|
||||
func ListAll(store Store, selector labels.Selector, appendFn AppendFunc) error {
|
||||
selectAll := selector.Empty()
|
||||
for _, m := range store.List() {
|
||||
if selectAll {
|
||||
// Avoid computing labels of the objects to speed up common flows
|
||||
// of listing all objects.
|
||||
appendFn(m)
|
||||
continue
|
||||
}
|
||||
metadata, err := meta.Accessor(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if selector.Matches(labels.Set(metadata.GetLabels())) {
|
||||
appendFn(m)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListAllByNamespace lists items in the given namespace in the store matching the given selector,
|
||||
// calling appendFn on each one.
|
||||
// If a blank namespace (NamespaceAll) is specified, this delegates to ListAll().
|
||||
func ListAllByNamespace(indexer Indexer, namespace string, selector labels.Selector, appendFn AppendFunc) error {
|
||||
if namespace == metav1.NamespaceAll {
|
||||
return ListAll(indexer, selector, appendFn)
|
||||
}
|
||||
|
||||
items, err := indexer.Index(NamespaceIndex, &metav1.ObjectMeta{Namespace: namespace})
|
||||
if err != nil {
|
||||
// Ignore error; do slow search without index.
|
||||
klog.Warningf("can not retrieve list of objects using index : %v", err)
|
||||
for _, m := range indexer.List() {
|
||||
metadata, err := meta.Accessor(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if metadata.GetNamespace() == namespace && selector.Matches(labels.Set(metadata.GetLabels())) {
|
||||
appendFn(m)
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
selectAll := selector.Empty()
|
||||
for _, m := range items {
|
||||
if selectAll {
|
||||
// Avoid computing labels of the objects to speed up common flows
|
||||
// of listing all objects.
|
||||
appendFn(m)
|
||||
continue
|
||||
}
|
||||
metadata, err := meta.Accessor(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if selector.Matches(labels.Set(metadata.GetLabels())) {
|
||||
appendFn(m)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenericLister is a lister skin on a generic Indexer
|
||||
type GenericLister interface {
|
||||
// List will return all objects across namespaces
|
||||
List(selector labels.Selector) (ret []runtime.Object, err error)
|
||||
// Get will attempt to retrieve assuming that name==key
|
||||
Get(name string) (runtime.Object, error)
|
||||
// ByNamespace will give you a GenericNamespaceLister for one namespace
|
||||
ByNamespace(namespace string) GenericNamespaceLister
|
||||
}
|
||||
|
||||
// GenericNamespaceLister is a lister skin on a generic Indexer
|
||||
type GenericNamespaceLister interface {
|
||||
// List will return all objects in this namespace
|
||||
List(selector labels.Selector) (ret []runtime.Object, err error)
|
||||
// Get will attempt to retrieve by namespace and name
|
||||
Get(name string) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// NewGenericLister creates a new instance for the genericLister.
|
||||
func NewGenericLister(indexer Indexer, resource schema.GroupResource) GenericLister {
|
||||
return &genericLister{indexer: indexer, resource: resource}
|
||||
}
|
||||
|
||||
type genericLister struct {
|
||||
indexer Indexer
|
||||
resource schema.GroupResource
|
||||
}
|
||||
|
||||
func (s *genericLister) List(selector labels.Selector) (ret []runtime.Object, err error) {
|
||||
err = ListAll(s.indexer, selector, func(m interface{}) {
|
||||
ret = append(ret, m.(runtime.Object))
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (s *genericLister) ByNamespace(namespace string) GenericNamespaceLister {
|
||||
return &genericNamespaceLister{indexer: s.indexer, namespace: namespace, resource: s.resource}
|
||||
}
|
||||
|
||||
func (s *genericLister) Get(name string) (runtime.Object, error) {
|
||||
obj, exists, err := s.indexer.GetByKey(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, errors.NewNotFound(s.resource, name)
|
||||
}
|
||||
return obj.(runtime.Object), nil
|
||||
}
|
||||
|
||||
type genericNamespaceLister struct {
|
||||
indexer Indexer
|
||||
namespace string
|
||||
resource schema.GroupResource
|
||||
}
|
||||
|
||||
func (s *genericNamespaceLister) List(selector labels.Selector) (ret []runtime.Object, err error) {
|
||||
err = ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
|
||||
ret = append(ret, m.(runtime.Object))
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (s *genericNamespaceLister) Get(name string) (runtime.Object, error) {
|
||||
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, errors.NewNotFound(s.resource, name)
|
||||
}
|
||||
return obj.(runtime.Object), nil
|
||||
}
|
116
e2e/vendor/k8s.io/client-go/tools/cache/listwatch.go
generated
vendored
Normal file
116
e2e/vendor/k8s.io/client-go/tools/cache/listwatch.go
generated
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// Lister is any object that knows how to perform an initial list.
|
||||
type Lister interface {
|
||||
// List should return a list type object; the Items field will be extracted, and the
|
||||
// ResourceVersion field will be used to start the watch in the right place.
|
||||
List(options metav1.ListOptions) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// Watcher is any object that knows how to start a watch on a resource.
|
||||
type Watcher interface {
|
||||
// Watch should begin a watch at the specified version.
|
||||
//
|
||||
// If Watch returns an error, it should handle its own cleanup, including
|
||||
// but not limited to calling Stop() on the watch, if one was constructed.
|
||||
// This allows the caller to ignore the watch, if the error is non-nil.
|
||||
Watch(options metav1.ListOptions) (watch.Interface, error)
|
||||
}
|
||||
|
||||
// ListerWatcher is any object that knows how to perform an initial list and start a watch on a resource.
|
||||
type ListerWatcher interface {
|
||||
Lister
|
||||
Watcher
|
||||
}
|
||||
|
||||
// ListFunc knows how to list resources
|
||||
type ListFunc func(options metav1.ListOptions) (runtime.Object, error)
|
||||
|
||||
// WatchFunc knows how to watch resources
|
||||
type WatchFunc func(options metav1.ListOptions) (watch.Interface, error)
|
||||
|
||||
// ListWatch knows how to list and watch a set of apiserver resources. It satisfies the ListerWatcher interface.
|
||||
// It is a convenience function for users of NewReflector, etc.
|
||||
// ListFunc and WatchFunc must not be nil
|
||||
type ListWatch struct {
|
||||
ListFunc ListFunc
|
||||
WatchFunc WatchFunc
|
||||
// DisableChunking requests no chunking for this list watcher.
|
||||
DisableChunking bool
|
||||
}
|
||||
|
||||
// Getter interface knows how to access Get method from RESTClient.
|
||||
type Getter interface {
|
||||
Get() *restclient.Request
|
||||
}
|
||||
|
||||
// NewListWatchFromClient creates a new ListWatch from the specified client, resource, namespace and field selector.
|
||||
func NewListWatchFromClient(c Getter, resource string, namespace string, fieldSelector fields.Selector) *ListWatch {
|
||||
optionsModifier := func(options *metav1.ListOptions) {
|
||||
options.FieldSelector = fieldSelector.String()
|
||||
}
|
||||
return NewFilteredListWatchFromClient(c, resource, namespace, optionsModifier)
|
||||
}
|
||||
|
||||
// NewFilteredListWatchFromClient creates a new ListWatch from the specified client, resource, namespace, and option modifier.
|
||||
// Option modifier is a function takes a ListOptions and modifies the consumed ListOptions. Provide customized modifier function
|
||||
// to apply modification to ListOptions with a field selector, a label selector, or any other desired options.
|
||||
func NewFilteredListWatchFromClient(c Getter, resource string, namespace string, optionsModifier func(options *metav1.ListOptions)) *ListWatch {
|
||||
listFunc := func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
optionsModifier(&options)
|
||||
return c.Get().
|
||||
Namespace(namespace).
|
||||
Resource(resource).
|
||||
VersionedParams(&options, metav1.ParameterCodec).
|
||||
Do(context.TODO()).
|
||||
Get()
|
||||
}
|
||||
watchFunc := func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
options.Watch = true
|
||||
optionsModifier(&options)
|
||||
return c.Get().
|
||||
Namespace(namespace).
|
||||
Resource(resource).
|
||||
VersionedParams(&options, metav1.ParameterCodec).
|
||||
Watch(context.TODO())
|
||||
}
|
||||
return &ListWatch{ListFunc: listFunc, WatchFunc: watchFunc}
|
||||
}
|
||||
|
||||
// List a set of apiserver resources
|
||||
func (lw *ListWatch) List(options metav1.ListOptions) (runtime.Object, error) {
|
||||
// ListWatch is used in Reflector, which already supports pagination.
|
||||
// Don't paginate here to avoid duplication.
|
||||
return lw.ListFunc(options)
|
||||
}
|
||||
|
||||
// Watch a set of apiserver resources
|
||||
func (lw *ListWatch) Watch(options metav1.ListOptions) (watch.Interface, error) {
|
||||
return lw.WatchFunc(options)
|
||||
}
|
262
e2e/vendor/k8s.io/client-go/tools/cache/mutation_cache.go
generated
vendored
Normal file
262
e2e/vendor/k8s.io/client-go/tools/cache/mutation_cache.go
generated
vendored
Normal file
@ -0,0 +1,262 @@
|
||||
/*
|
||||
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 cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilcache "k8s.io/apimachinery/pkg/util/cache"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// MutationCache is able to take the result of update operations and stores them in an LRU
|
||||
// that can be used to provide a more current view of a requested object. It requires interpreting
|
||||
// resourceVersions for comparisons.
|
||||
// Implementations must be thread-safe.
|
||||
// TODO find a way to layer this into an informer/lister
|
||||
type MutationCache interface {
|
||||
GetByKey(key string) (interface{}, bool, error)
|
||||
ByIndex(indexName, indexKey string) ([]interface{}, error)
|
||||
Mutation(interface{})
|
||||
}
|
||||
|
||||
// ResourceVersionComparator is able to compare object versions.
|
||||
type ResourceVersionComparator interface {
|
||||
CompareResourceVersion(lhs, rhs runtime.Object) int
|
||||
}
|
||||
|
||||
// NewIntegerResourceVersionMutationCache returns a MutationCache that understands how to
|
||||
// deal with objects that have a resource version that:
|
||||
//
|
||||
// - is an integer
|
||||
// - increases when updated
|
||||
// - is comparable across the same resource in a namespace
|
||||
//
|
||||
// Most backends will have these semantics. Indexer may be nil. ttl controls how long an item
|
||||
// remains in the mutation cache before it is removed.
|
||||
//
|
||||
// If includeAdds is true, objects in the mutation cache will be returned even if they don't exist
|
||||
// in the underlying store. This is only safe if your use of the cache can handle mutation entries
|
||||
// remaining in the cache for up to ttl when mutations and deletes occur very closely in time.
|
||||
func NewIntegerResourceVersionMutationCache(backingCache Store, indexer Indexer, ttl time.Duration, includeAdds bool) MutationCache {
|
||||
return &mutationCache{
|
||||
backingCache: backingCache,
|
||||
indexer: indexer,
|
||||
mutationCache: utilcache.NewLRUExpireCache(100),
|
||||
comparator: etcdObjectVersioner{},
|
||||
ttl: ttl,
|
||||
includeAdds: includeAdds,
|
||||
}
|
||||
}
|
||||
|
||||
// mutationCache doesn't guarantee that it returns values added via Mutation since they can page out and
|
||||
// since you can't distinguish between, "didn't observe create" and "was deleted after create",
|
||||
// if the key is missing from the backing cache, we always return it as missing
|
||||
type mutationCache struct {
|
||||
lock sync.Mutex
|
||||
backingCache Store
|
||||
indexer Indexer
|
||||
mutationCache *utilcache.LRUExpireCache
|
||||
includeAdds bool
|
||||
ttl time.Duration
|
||||
|
||||
comparator ResourceVersionComparator
|
||||
}
|
||||
|
||||
// GetByKey is never guaranteed to return back the value set in Mutation. It could be paged out, it could
|
||||
// be older than another copy, the backingCache may be more recent or, you might have written twice into the same key.
|
||||
// You get a value that was valid at some snapshot of time and will always return the newer of backingCache and mutationCache.
|
||||
func (c *mutationCache) GetByKey(key string) (interface{}, bool, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
obj, exists, err := c.backingCache.GetByKey(key)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if !exists {
|
||||
if !c.includeAdds {
|
||||
// we can't distinguish between, "didn't observe create" and "was deleted after create", so
|
||||
// if the key is missing, we always return it as missing
|
||||
return nil, false, nil
|
||||
}
|
||||
obj, exists = c.mutationCache.Get(key)
|
||||
if !exists {
|
||||
return nil, false, nil
|
||||
}
|
||||
}
|
||||
objRuntime, ok := obj.(runtime.Object)
|
||||
if !ok {
|
||||
return obj, true, nil
|
||||
}
|
||||
return c.newerObject(key, objRuntime), true, nil
|
||||
}
|
||||
|
||||
// ByIndex returns the newer objects that match the provided index and indexer key.
|
||||
// Will return an error if no indexer was provided.
|
||||
func (c *mutationCache) ByIndex(name string, indexKey string) ([]interface{}, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if c.indexer == nil {
|
||||
return nil, fmt.Errorf("no indexer has been provided to the mutation cache")
|
||||
}
|
||||
keys, err := c.indexer.IndexKeys(name, indexKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var items []interface{}
|
||||
keySet := sets.NewString()
|
||||
for _, key := range keys {
|
||||
keySet.Insert(key)
|
||||
obj, exists, err := c.indexer.GetByKey(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
if objRuntime, ok := obj.(runtime.Object); ok {
|
||||
items = append(items, c.newerObject(key, objRuntime))
|
||||
} else {
|
||||
items = append(items, obj)
|
||||
}
|
||||
}
|
||||
|
||||
if c.includeAdds {
|
||||
fn := c.indexer.GetIndexers()[name]
|
||||
// Keys() is returned oldest to newest, so full traversal does not alter the LRU behavior
|
||||
for _, key := range c.mutationCache.Keys() {
|
||||
updated, ok := c.mutationCache.Get(key)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if keySet.Has(key.(string)) {
|
||||
continue
|
||||
}
|
||||
elements, err := fn(updated)
|
||||
if err != nil {
|
||||
klog.V(4).Infof("Unable to calculate an index entry for mutation cache entry %s: %v", key, err)
|
||||
continue
|
||||
}
|
||||
for _, inIndex := range elements {
|
||||
if inIndex != indexKey {
|
||||
continue
|
||||
}
|
||||
items = append(items, updated)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// newerObject checks the mutation cache for a newer object and returns one if found. If the
|
||||
// mutated object is older than the backing object, it is removed from the Must be
|
||||
// called while the lock is held.
|
||||
func (c *mutationCache) newerObject(key string, backing runtime.Object) runtime.Object {
|
||||
mutatedObj, exists := c.mutationCache.Get(key)
|
||||
if !exists {
|
||||
return backing
|
||||
}
|
||||
mutatedObjRuntime, ok := mutatedObj.(runtime.Object)
|
||||
if !ok {
|
||||
return backing
|
||||
}
|
||||
if c.comparator.CompareResourceVersion(backing, mutatedObjRuntime) >= 0 {
|
||||
c.mutationCache.Remove(key)
|
||||
return backing
|
||||
}
|
||||
return mutatedObjRuntime
|
||||
}
|
||||
|
||||
// Mutation adds a change to the cache that can be returned in GetByKey if it is newer than the backingCache
|
||||
// copy. If you call Mutation twice with the same object on different threads, one will win, but its not defined
|
||||
// which one. This doesn't affect correctness, since the GetByKey guaranteed of "later of these two caches" is
|
||||
// preserved, but you may not get the version of the object you want. The object you get is only guaranteed to
|
||||
// "one that was valid at some point in time", not "the one that I want".
|
||||
func (c *mutationCache) Mutation(obj interface{}) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
key, err := DeletionHandlingMetaNamespaceKeyFunc(obj)
|
||||
if err != nil {
|
||||
// this is a "nice to have", so failures shouldn't do anything weird
|
||||
utilruntime.HandleError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if objRuntime, ok := obj.(runtime.Object); ok {
|
||||
if mutatedObj, exists := c.mutationCache.Get(key); exists {
|
||||
if mutatedObjRuntime, ok := mutatedObj.(runtime.Object); ok {
|
||||
if c.comparator.CompareResourceVersion(objRuntime, mutatedObjRuntime) < 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
c.mutationCache.Add(key, obj, c.ttl)
|
||||
}
|
||||
|
||||
// etcdObjectVersioner implements versioning and extracting etcd node information
|
||||
// for objects that have an embedded ObjectMeta or ListMeta field.
|
||||
type etcdObjectVersioner struct{}
|
||||
|
||||
// ObjectResourceVersion implements Versioner
|
||||
func (a etcdObjectVersioner) ObjectResourceVersion(obj runtime.Object) (uint64, error) {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
version := accessor.GetResourceVersion()
|
||||
if len(version) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
return strconv.ParseUint(version, 10, 64)
|
||||
}
|
||||
|
||||
// CompareResourceVersion compares etcd resource versions. Outside this API they are all strings,
|
||||
// but etcd resource versions are special, they're actually ints, so we can easily compare them.
|
||||
func (a etcdObjectVersioner) CompareResourceVersion(lhs, rhs runtime.Object) int {
|
||||
lhsVersion, err := a.ObjectResourceVersion(lhs)
|
||||
if err != nil {
|
||||
// coder error
|
||||
panic(err)
|
||||
}
|
||||
rhsVersion, err := a.ObjectResourceVersion(rhs)
|
||||
if err != nil {
|
||||
// coder error
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if lhsVersion == rhsVersion {
|
||||
return 0
|
||||
}
|
||||
if lhsVersion < rhsVersion {
|
||||
return -1
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
166
e2e/vendor/k8s.io/client-go/tools/cache/mutation_detector.go
generated
vendored
Normal file
166
e2e/vendor/k8s.io/client-go/tools/cache/mutation_detector.go
generated
vendored
Normal file
@ -0,0 +1,166 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
)
|
||||
|
||||
var mutationDetectionEnabled = false
|
||||
|
||||
func init() {
|
||||
mutationDetectionEnabled, _ = strconv.ParseBool(os.Getenv("KUBE_CACHE_MUTATION_DETECTOR"))
|
||||
}
|
||||
|
||||
// MutationDetector is able to monitor objects for mutation within a limited window of time
|
||||
type MutationDetector interface {
|
||||
// AddObject adds the given object to the set being monitored for a while from now
|
||||
AddObject(obj interface{})
|
||||
|
||||
// Run starts the monitoring and does not return until the monitoring is stopped.
|
||||
Run(stopCh <-chan struct{})
|
||||
}
|
||||
|
||||
// NewCacheMutationDetector creates a new instance for the defaultCacheMutationDetector.
|
||||
func NewCacheMutationDetector(name string) MutationDetector {
|
||||
if !mutationDetectionEnabled {
|
||||
return dummyMutationDetector{}
|
||||
}
|
||||
klog.Warningln("Mutation detector is enabled, this will result in memory leakage.")
|
||||
return &defaultCacheMutationDetector{name: name, period: 1 * time.Second, retainDuration: 2 * time.Minute}
|
||||
}
|
||||
|
||||
type dummyMutationDetector struct{}
|
||||
|
||||
func (dummyMutationDetector) Run(stopCh <-chan struct{}) {
|
||||
}
|
||||
func (dummyMutationDetector) AddObject(obj interface{}) {
|
||||
}
|
||||
|
||||
// defaultCacheMutationDetector gives a way to detect if a cached object has been mutated
|
||||
// It has a list of cached objects and their copies. I haven't thought of a way
|
||||
// to see WHO is mutating it, just that it's getting mutated.
|
||||
type defaultCacheMutationDetector struct {
|
||||
name string
|
||||
period time.Duration
|
||||
|
||||
// compareLock ensures only a single call to CompareObjects runs at a time
|
||||
compareObjectsLock sync.Mutex
|
||||
|
||||
// addLock guards addedObjs between AddObject and CompareObjects
|
||||
addedObjsLock sync.Mutex
|
||||
addedObjs []cacheObj
|
||||
|
||||
cachedObjs []cacheObj
|
||||
|
||||
retainDuration time.Duration
|
||||
lastRotated time.Time
|
||||
retainedCachedObjs []cacheObj
|
||||
|
||||
// failureFunc is injectable for unit testing. If you don't have it, the process will panic.
|
||||
// This panic is intentional, since turning on this detection indicates you want a strong
|
||||
// failure signal. This failure is effectively a p0 bug and you can't trust process results
|
||||
// after a mutation anyway.
|
||||
failureFunc func(message string)
|
||||
}
|
||||
|
||||
// cacheObj holds the actual object and a copy
|
||||
type cacheObj struct {
|
||||
cached interface{}
|
||||
copied interface{}
|
||||
}
|
||||
|
||||
func (d *defaultCacheMutationDetector) Run(stopCh <-chan struct{}) {
|
||||
// we DON'T want protection from panics. If we're running this code, we want to die
|
||||
for {
|
||||
if d.lastRotated.IsZero() {
|
||||
d.lastRotated = time.Now()
|
||||
} else if time.Since(d.lastRotated) > d.retainDuration {
|
||||
d.retainedCachedObjs = d.cachedObjs
|
||||
d.cachedObjs = nil
|
||||
d.lastRotated = time.Now()
|
||||
}
|
||||
|
||||
d.CompareObjects()
|
||||
|
||||
select {
|
||||
case <-stopCh:
|
||||
return
|
||||
case <-time.After(d.period):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddObject makes a deep copy of the object for later comparison. It only works on runtime.Object
|
||||
// but that covers the vast majority of our cached objects
|
||||
func (d *defaultCacheMutationDetector) AddObject(obj interface{}) {
|
||||
if _, ok := obj.(DeletedFinalStateUnknown); ok {
|
||||
return
|
||||
}
|
||||
if obj, ok := obj.(runtime.Object); ok {
|
||||
copiedObj := obj.DeepCopyObject()
|
||||
|
||||
d.addedObjsLock.Lock()
|
||||
defer d.addedObjsLock.Unlock()
|
||||
d.addedObjs = append(d.addedObjs, cacheObj{cached: obj, copied: copiedObj})
|
||||
}
|
||||
}
|
||||
|
||||
func (d *defaultCacheMutationDetector) CompareObjects() {
|
||||
d.compareObjectsLock.Lock()
|
||||
defer d.compareObjectsLock.Unlock()
|
||||
|
||||
// move addedObjs into cachedObjs under lock
|
||||
// this keeps the critical section small to avoid blocking AddObject while we compare cachedObjs
|
||||
d.addedObjsLock.Lock()
|
||||
d.cachedObjs = append(d.cachedObjs, d.addedObjs...)
|
||||
d.addedObjs = nil
|
||||
d.addedObjsLock.Unlock()
|
||||
|
||||
altered := false
|
||||
for i, obj := range d.cachedObjs {
|
||||
if !reflect.DeepEqual(obj.cached, obj.copied) {
|
||||
fmt.Printf("CACHE %s[%d] ALTERED!\n%v\n", d.name, i, diff.ObjectGoPrintSideBySide(obj.cached, obj.copied))
|
||||
altered = true
|
||||
}
|
||||
}
|
||||
for i, obj := range d.retainedCachedObjs {
|
||||
if !reflect.DeepEqual(obj.cached, obj.copied) {
|
||||
fmt.Printf("CACHE %s[%d] ALTERED!\n%v\n", d.name, i, diff.ObjectGoPrintSideBySide(obj.cached, obj.copied))
|
||||
altered = true
|
||||
}
|
||||
}
|
||||
|
||||
if altered {
|
||||
msg := fmt.Sprintf("cache %s modified", d.name)
|
||||
if d.failureFunc != nil {
|
||||
d.failureFunc(msg)
|
||||
return
|
||||
}
|
||||
panic(msg)
|
||||
}
|
||||
}
|
65
e2e/vendor/k8s.io/client-go/tools/cache/object-names.go
generated
vendored
Normal file
65
e2e/vendor/k8s.io/client-go/tools/cache/object-names.go
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
Copyright 2023 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 (
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
// ObjectName is a reference to an object of some implicit kind
|
||||
type ObjectName struct {
|
||||
Namespace string
|
||||
Name string
|
||||
}
|
||||
|
||||
// NewObjectName constructs a new one
|
||||
func NewObjectName(namespace, name string) ObjectName {
|
||||
return ObjectName{Namespace: namespace, Name: name}
|
||||
}
|
||||
|
||||
// Parts is the inverse of the constructor
|
||||
func (objName ObjectName) Parts() (namespace, name string) {
|
||||
return objName.Namespace, objName.Name
|
||||
}
|
||||
|
||||
// String returns the standard string encoding,
|
||||
// which is designed to match the historical behavior of MetaNamespaceKeyFunc.
|
||||
// Note this behavior is different from the String method of types.NamespacedName.
|
||||
func (objName ObjectName) String() string {
|
||||
if len(objName.Namespace) > 0 {
|
||||
return objName.Namespace + "/" + objName.Name
|
||||
}
|
||||
return objName.Name
|
||||
}
|
||||
|
||||
// ParseObjectName tries to parse the standard encoding
|
||||
func ParseObjectName(str string) (ObjectName, error) {
|
||||
var objName ObjectName
|
||||
var err error
|
||||
objName.Namespace, objName.Name, err = SplitMetaNamespaceKey(str)
|
||||
return objName, err
|
||||
}
|
||||
|
||||
// NamespacedNameAsObjectName rebrands the given NamespacedName as an ObjectName
|
||||
func NamespacedNameAsObjectName(nn types.NamespacedName) ObjectName {
|
||||
return NewObjectName(nn.Namespace, nn.Name)
|
||||
}
|
||||
|
||||
// AsNamespacedName rebrands as a NamespacedName
|
||||
func (objName ObjectName) AsNamespacedName() types.NamespacedName {
|
||||
return types.NamespacedName{Namespace: objName.Namespace, Name: objName.Name}
|
||||
}
|
1083
e2e/vendor/k8s.io/client-go/tools/cache/reflector.go
generated
vendored
Normal file
1083
e2e/vendor/k8s.io/client-go/tools/cache/reflector.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
43
e2e/vendor/k8s.io/client-go/tools/cache/reflector_data_consistency_detector.go
generated
vendored
Normal file
43
e2e/vendor/k8s.io/client-go/tools/cache/reflector_data_consistency_detector.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/util/consistencydetector"
|
||||
)
|
||||
|
||||
// checkWatchListDataConsistencyIfRequested performs a data consistency check only when
|
||||
// the KUBE_WATCHLIST_INCONSISTENCY_DETECTOR environment variable was set during a binary startup.
|
||||
//
|
||||
// The consistency check is meant to be enforced only in the CI, not in production.
|
||||
// The check ensures that data retrieved by the watch-list api call
|
||||
// is exactly the same as data received by the standard list api call against etcd.
|
||||
//
|
||||
// Note that this function will panic when data inconsistency is detected.
|
||||
// This is intentional because we want to catch it in the CI.
|
||||
func checkWatchListDataConsistencyIfRequested[T runtime.Object, U any](ctx context.Context, identity string, lastSyncedResourceVersion string, listFn consistencydetector.ListFunc[T], retrieveItemsFn consistencydetector.RetrieveItemsFunc[U]) {
|
||||
if !consistencydetector.IsDataConsistencyDetectionForWatchListEnabled() {
|
||||
return
|
||||
}
|
||||
// for informers we pass an empty ListOptions because
|
||||
// listFn might be wrapped for filtering during informer construction.
|
||||
consistencydetector.CheckDataConsistency(ctx, identity, lastSyncedResourceVersion, listFn, metav1.ListOptions{}, retrieveItemsFn)
|
||||
}
|
89
e2e/vendor/k8s.io/client-go/tools/cache/reflector_metrics.go
generated
vendored
Normal file
89
e2e/vendor/k8s.io/client-go/tools/cache/reflector_metrics.go
generated
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// This file provides abstractions for setting the provider (e.g., prometheus)
|
||||
// of metrics.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// GaugeMetric represents a single numerical value that can arbitrarily go up
|
||||
// and down.
|
||||
type GaugeMetric interface {
|
||||
Set(float64)
|
||||
}
|
||||
|
||||
// CounterMetric represents a single numerical value that only ever
|
||||
// goes up.
|
||||
type CounterMetric interface {
|
||||
Inc()
|
||||
}
|
||||
|
||||
// SummaryMetric captures individual observations.
|
||||
type SummaryMetric interface {
|
||||
Observe(float64)
|
||||
}
|
||||
|
||||
type noopMetric struct{}
|
||||
|
||||
func (noopMetric) Inc() {}
|
||||
func (noopMetric) Dec() {}
|
||||
func (noopMetric) Observe(float64) {}
|
||||
func (noopMetric) Set(float64) {}
|
||||
|
||||
// MetricsProvider generates various metrics used by the reflector.
|
||||
type MetricsProvider interface {
|
||||
NewListsMetric(name string) CounterMetric
|
||||
NewListDurationMetric(name string) SummaryMetric
|
||||
NewItemsInListMetric(name string) SummaryMetric
|
||||
|
||||
NewWatchesMetric(name string) CounterMetric
|
||||
NewShortWatchesMetric(name string) CounterMetric
|
||||
NewWatchDurationMetric(name string) SummaryMetric
|
||||
NewItemsInWatchMetric(name string) SummaryMetric
|
||||
|
||||
NewLastResourceVersionMetric(name string) GaugeMetric
|
||||
}
|
||||
|
||||
type noopMetricsProvider struct{}
|
||||
|
||||
func (noopMetricsProvider) NewListsMetric(name string) CounterMetric { return noopMetric{} }
|
||||
func (noopMetricsProvider) NewListDurationMetric(name string) SummaryMetric { return noopMetric{} }
|
||||
func (noopMetricsProvider) NewItemsInListMetric(name string) SummaryMetric { return noopMetric{} }
|
||||
func (noopMetricsProvider) NewWatchesMetric(name string) CounterMetric { return noopMetric{} }
|
||||
func (noopMetricsProvider) NewShortWatchesMetric(name string) CounterMetric { return noopMetric{} }
|
||||
func (noopMetricsProvider) NewWatchDurationMetric(name string) SummaryMetric { return noopMetric{} }
|
||||
func (noopMetricsProvider) NewItemsInWatchMetric(name string) SummaryMetric { return noopMetric{} }
|
||||
func (noopMetricsProvider) NewLastResourceVersionMetric(name string) GaugeMetric {
|
||||
return noopMetric{}
|
||||
}
|
||||
|
||||
var metricsFactory = struct {
|
||||
metricsProvider MetricsProvider
|
||||
setProviders sync.Once
|
||||
}{
|
||||
metricsProvider: noopMetricsProvider{},
|
||||
}
|
||||
|
||||
// SetReflectorMetricsProvider sets the metrics provider
|
||||
func SetReflectorMetricsProvider(metricsProvider MetricsProvider) {
|
||||
metricsFactory.setProviders.Do(func() {
|
||||
metricsFactory.metricsProvider = metricsProvider
|
||||
})
|
||||
}
|
78
e2e/vendor/k8s.io/client-go/tools/cache/retry_with_deadline.go
generated
vendored
Normal file
78
e2e/vendor/k8s.io/client-go/tools/cache/retry_with_deadline.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
Copyright 2022 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 (
|
||||
"k8s.io/utils/clock"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RetryWithDeadline interface {
|
||||
After(error)
|
||||
ShouldRetry() bool
|
||||
}
|
||||
|
||||
type retryWithDeadlineImpl struct {
|
||||
firstErrorTime time.Time
|
||||
lastErrorTime time.Time
|
||||
maxRetryDuration time.Duration
|
||||
minResetPeriod time.Duration
|
||||
isRetryable func(error) bool
|
||||
clock clock.Clock
|
||||
}
|
||||
|
||||
func NewRetryWithDeadline(maxRetryDuration, minResetPeriod time.Duration, isRetryable func(error) bool, clock clock.Clock) RetryWithDeadline {
|
||||
return &retryWithDeadlineImpl{
|
||||
firstErrorTime: time.Time{},
|
||||
lastErrorTime: time.Time{},
|
||||
maxRetryDuration: maxRetryDuration,
|
||||
minResetPeriod: minResetPeriod,
|
||||
isRetryable: isRetryable,
|
||||
clock: clock,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *retryWithDeadlineImpl) reset() {
|
||||
r.firstErrorTime = time.Time{}
|
||||
r.lastErrorTime = time.Time{}
|
||||
}
|
||||
|
||||
func (r *retryWithDeadlineImpl) After(err error) {
|
||||
if r.isRetryable(err) {
|
||||
if r.clock.Now().Sub(r.lastErrorTime) >= r.minResetPeriod {
|
||||
r.reset()
|
||||
}
|
||||
|
||||
if r.firstErrorTime.IsZero() {
|
||||
r.firstErrorTime = r.clock.Now()
|
||||
}
|
||||
r.lastErrorTime = r.clock.Now()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *retryWithDeadlineImpl) ShouldRetry() bool {
|
||||
if r.maxRetryDuration <= time.Duration(0) {
|
||||
return false
|
||||
}
|
||||
|
||||
if r.clock.Now().Sub(r.firstErrorTime) <= r.maxRetryDuration {
|
||||
return true
|
||||
}
|
||||
|
||||
r.reset()
|
||||
return false
|
||||
}
|
1018
e2e/vendor/k8s.io/client-go/tools/cache/shared_informer.go
generated
vendored
Normal file
1018
e2e/vendor/k8s.io/client-go/tools/cache/shared_informer.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
295
e2e/vendor/k8s.io/client-go/tools/cache/store.go
generated
vendored
Normal file
295
e2e/vendor/k8s.io/client-go/tools/cache/store.go
generated
vendored
Normal file
@ -0,0 +1,295 @@
|
||||
/*
|
||||
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 cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Store is a generic object storage and processing interface. A
|
||||
// Store holds a map from string keys to accumulators, and has
|
||||
// operations to add, update, and delete a given object to/from the
|
||||
// accumulator currently associated with a given key. A Store also
|
||||
// knows how to extract the key from a given object, so many operations
|
||||
// are given only the object.
|
||||
//
|
||||
// In the simplest Store implementations each accumulator is simply
|
||||
// the last given object, or empty after Delete, and thus the Store's
|
||||
// behavior is simple storage.
|
||||
//
|
||||
// Reflector knows how to watch a server and update a Store. This
|
||||
// package provides a variety of implementations of Store.
|
||||
type Store interface {
|
||||
|
||||
// Add adds the given object to the accumulator associated with the given object's key
|
||||
Add(obj interface{}) error
|
||||
|
||||
// Update updates the given object in the accumulator associated with the given object's key
|
||||
Update(obj interface{}) error
|
||||
|
||||
// Delete deletes the given object from the accumulator associated with the given object's key
|
||||
Delete(obj interface{}) error
|
||||
|
||||
// List returns a list of all the currently non-empty accumulators
|
||||
List() []interface{}
|
||||
|
||||
// ListKeys returns a list of all the keys currently associated with non-empty accumulators
|
||||
ListKeys() []string
|
||||
|
||||
// Get returns the accumulator associated with the given object's key
|
||||
Get(obj interface{}) (item interface{}, exists bool, err error)
|
||||
|
||||
// GetByKey returns the accumulator associated with the given key
|
||||
GetByKey(key string) (item interface{}, exists bool, err error)
|
||||
|
||||
// Replace will delete the contents of the store, using instead the
|
||||
// given list. Store takes ownership of the list, you should not reference
|
||||
// it after calling this function.
|
||||
Replace([]interface{}, string) error
|
||||
|
||||
// Resync is meaningless in the terms appearing here but has
|
||||
// meaning in some implementations that have non-trivial
|
||||
// additional behavior (e.g., DeltaFIFO).
|
||||
Resync() error
|
||||
}
|
||||
|
||||
// KeyFunc knows how to make a key from an object. Implementations should be deterministic.
|
||||
type KeyFunc func(obj interface{}) (string, error)
|
||||
|
||||
// KeyError will be returned any time a KeyFunc gives an error; it includes the object
|
||||
// at fault.
|
||||
type KeyError struct {
|
||||
Obj interface{}
|
||||
Err error
|
||||
}
|
||||
|
||||
// Error gives a human-readable description of the error.
|
||||
func (k KeyError) Error() string {
|
||||
return fmt.Sprintf("couldn't create key for object %+v: %v", k.Obj, k.Err)
|
||||
}
|
||||
|
||||
// Unwrap implements errors.Unwrap
|
||||
func (k KeyError) Unwrap() error {
|
||||
return k.Err
|
||||
}
|
||||
|
||||
// ExplicitKey can be passed to MetaNamespaceKeyFunc if you have the key for
|
||||
// the object but not the object itself.
|
||||
type ExplicitKey string
|
||||
|
||||
// MetaNamespaceKeyFunc is a convenient default KeyFunc which knows how to make
|
||||
// keys for API objects which implement meta.Interface.
|
||||
// The key uses the format <namespace>/<name> unless <namespace> is empty, then
|
||||
// it's just <name>.
|
||||
//
|
||||
// Clients that want a structured alternative can use ObjectToName or MetaObjectToName.
|
||||
// Note: this would not be a client that wants a key for a Store because those are
|
||||
// necessarily strings.
|
||||
//
|
||||
// TODO maybe some day?: change Store to be keyed differently
|
||||
func MetaNamespaceKeyFunc(obj interface{}) (string, error) {
|
||||
if key, ok := obj.(ExplicitKey); ok {
|
||||
return string(key), nil
|
||||
}
|
||||
objName, err := ObjectToName(obj)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return objName.String(), nil
|
||||
}
|
||||
|
||||
// ObjectToName returns the structured name for the given object,
|
||||
// if indeed it can be viewed as a metav1.Object.
|
||||
func ObjectToName(obj interface{}) (ObjectName, error) {
|
||||
meta, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return ObjectName{}, fmt.Errorf("object has no meta: %v", err)
|
||||
}
|
||||
return MetaObjectToName(meta), nil
|
||||
}
|
||||
|
||||
// MetaObjectToName returns the structured name for the given object
|
||||
func MetaObjectToName(obj metav1.Object) ObjectName {
|
||||
if len(obj.GetNamespace()) > 0 {
|
||||
return ObjectName{Namespace: obj.GetNamespace(), Name: obj.GetName()}
|
||||
}
|
||||
return ObjectName{Namespace: "", Name: obj.GetName()}
|
||||
}
|
||||
|
||||
// SplitMetaNamespaceKey returns the namespace and name that
|
||||
// MetaNamespaceKeyFunc encoded into key.
|
||||
//
|
||||
// TODO: replace key-as-string with a key-as-struct so that this
|
||||
// packing/unpacking won't be necessary.
|
||||
func SplitMetaNamespaceKey(key string) (namespace, name string, err error) {
|
||||
parts := strings.Split(key, "/")
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
// name only, no namespace
|
||||
return "", parts[0], nil
|
||||
case 2:
|
||||
// namespace and name
|
||||
return parts[0], parts[1], nil
|
||||
}
|
||||
|
||||
return "", "", fmt.Errorf("unexpected key format: %q", key)
|
||||
}
|
||||
|
||||
// `*cache` implements Indexer in terms of a ThreadSafeStore and an
|
||||
// associated KeyFunc.
|
||||
type cache struct {
|
||||
// cacheStorage bears the burden of thread safety for the cache
|
||||
cacheStorage ThreadSafeStore
|
||||
// keyFunc is used to make the key for objects stored in and retrieved from items, and
|
||||
// should be deterministic.
|
||||
keyFunc KeyFunc
|
||||
}
|
||||
|
||||
var _ Store = &cache{}
|
||||
|
||||
// Add inserts an item into the cache.
|
||||
func (c *cache) Add(obj interface{}) error {
|
||||
key, err := c.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
c.cacheStorage.Add(key, obj)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update sets an item in the cache to its updated state.
|
||||
func (c *cache) Update(obj interface{}) error {
|
||||
key, err := c.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
c.cacheStorage.Update(key, obj)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes an item from the cache.
|
||||
func (c *cache) Delete(obj interface{}) error {
|
||||
key, err := c.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
c.cacheStorage.Delete(key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// List returns a list of all the items.
|
||||
// List is completely threadsafe as long as you treat all items as immutable.
|
||||
func (c *cache) List() []interface{} {
|
||||
return c.cacheStorage.List()
|
||||
}
|
||||
|
||||
// ListKeys returns a list of all the keys of the objects currently
|
||||
// in the cache.
|
||||
func (c *cache) ListKeys() []string {
|
||||
return c.cacheStorage.ListKeys()
|
||||
}
|
||||
|
||||
// GetIndexers returns the indexers of cache
|
||||
func (c *cache) GetIndexers() Indexers {
|
||||
return c.cacheStorage.GetIndexers()
|
||||
}
|
||||
|
||||
// Index returns a list of items that match on the index function
|
||||
// Index is thread-safe so long as you treat all items as immutable
|
||||
func (c *cache) Index(indexName string, obj interface{}) ([]interface{}, error) {
|
||||
return c.cacheStorage.Index(indexName, obj)
|
||||
}
|
||||
|
||||
// IndexKeys returns the storage keys of the stored objects whose set of
|
||||
// indexed values for the named index includes the given indexed value.
|
||||
// The returned keys are suitable to pass to GetByKey().
|
||||
func (c *cache) IndexKeys(indexName, indexedValue string) ([]string, error) {
|
||||
return c.cacheStorage.IndexKeys(indexName, indexedValue)
|
||||
}
|
||||
|
||||
// ListIndexFuncValues returns the list of generated values of an Index func
|
||||
func (c *cache) ListIndexFuncValues(indexName string) []string {
|
||||
return c.cacheStorage.ListIndexFuncValues(indexName)
|
||||
}
|
||||
|
||||
// ByIndex returns the stored objects whose set of indexed values
|
||||
// for the named index includes the given indexed value.
|
||||
func (c *cache) ByIndex(indexName, indexedValue string) ([]interface{}, error) {
|
||||
return c.cacheStorage.ByIndex(indexName, indexedValue)
|
||||
}
|
||||
|
||||
func (c *cache) AddIndexers(newIndexers Indexers) error {
|
||||
return c.cacheStorage.AddIndexers(newIndexers)
|
||||
}
|
||||
|
||||
// Get returns the requested item, or sets exists=false.
|
||||
// Get is completely threadsafe as long as you treat all items as immutable.
|
||||
func (c *cache) Get(obj interface{}) (item interface{}, exists bool, err error) {
|
||||
key, err := c.keyFunc(obj)
|
||||
if err != nil {
|
||||
return nil, false, KeyError{obj, err}
|
||||
}
|
||||
return c.GetByKey(key)
|
||||
}
|
||||
|
||||
// GetByKey returns the request item, or exists=false.
|
||||
// GetByKey is completely threadsafe as long as you treat all items as immutable.
|
||||
func (c *cache) GetByKey(key string) (item interface{}, exists bool, err error) {
|
||||
item, exists = c.cacheStorage.Get(key)
|
||||
return item, exists, nil
|
||||
}
|
||||
|
||||
// Replace will delete the contents of 'c', using instead the given list.
|
||||
// 'c' takes ownership of the list, you should not reference the list again
|
||||
// after calling this function.
|
||||
func (c *cache) Replace(list []interface{}, resourceVersion string) error {
|
||||
items := make(map[string]interface{}, len(list))
|
||||
for _, item := range list {
|
||||
key, err := c.keyFunc(item)
|
||||
if err != nil {
|
||||
return KeyError{item, err}
|
||||
}
|
||||
items[key] = item
|
||||
}
|
||||
c.cacheStorage.Replace(items, resourceVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resync is meaningless for one of these
|
||||
func (c *cache) Resync() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewStore returns a Store implemented simply with a map and a lock.
|
||||
func NewStore(keyFunc KeyFunc) Store {
|
||||
return &cache{
|
||||
cacheStorage: NewThreadSafeStore(Indexers{}, Indices{}),
|
||||
keyFunc: keyFunc,
|
||||
}
|
||||
}
|
||||
|
||||
// NewIndexer returns an Indexer implemented simply with a map and a lock.
|
||||
func NewIndexer(keyFunc KeyFunc, indexers Indexers) Indexer {
|
||||
return &cache{
|
||||
cacheStorage: NewThreadSafeStore(indexers, Indices{}),
|
||||
keyFunc: keyFunc,
|
||||
}
|
||||
}
|
83
e2e/vendor/k8s.io/client-go/tools/cache/synctrack/lazy.go
generated
vendored
Normal file
83
e2e/vendor/k8s.io/client-go/tools/cache/synctrack/lazy.go
generated
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
Copyright 2023 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 synctrack
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Lazy defers the computation of `Evaluate` to when it is necessary. It is
|
||||
// possible that Evaluate will be called in parallel from multiple goroutines.
|
||||
type Lazy[T any] struct {
|
||||
Evaluate func() (T, error)
|
||||
|
||||
cache atomic.Pointer[cacheEntry[T]]
|
||||
}
|
||||
|
||||
type cacheEntry[T any] struct {
|
||||
eval func() (T, error)
|
||||
lock sync.RWMutex
|
||||
result *T
|
||||
}
|
||||
|
||||
func (e *cacheEntry[T]) get() (T, error) {
|
||||
if cur := func() *T {
|
||||
e.lock.RLock()
|
||||
defer e.lock.RUnlock()
|
||||
return e.result
|
||||
}(); cur != nil {
|
||||
return *cur, nil
|
||||
}
|
||||
|
||||
e.lock.Lock()
|
||||
defer e.lock.Unlock()
|
||||
if e.result != nil {
|
||||
return *e.result, nil
|
||||
}
|
||||
r, err := e.eval()
|
||||
if err == nil {
|
||||
e.result = &r
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
|
||||
func (z *Lazy[T]) newCacheEntry() *cacheEntry[T] {
|
||||
return &cacheEntry[T]{eval: z.Evaluate}
|
||||
}
|
||||
|
||||
// Notify should be called when something has changed necessitating a new call
|
||||
// to Evaluate.
|
||||
func (z *Lazy[T]) Notify() { z.cache.Swap(z.newCacheEntry()) }
|
||||
|
||||
// Get should be called to get the current result of a call to Evaluate. If the
|
||||
// current cached value is stale (due to a call to Notify), then Evaluate will
|
||||
// be called synchronously. If subsequent calls to Get happen (without another
|
||||
// Notify), they will all wait for the same return value.
|
||||
//
|
||||
// Error returns are not cached and will cause multiple calls to evaluate!
|
||||
func (z *Lazy[T]) Get() (T, error) {
|
||||
e := z.cache.Load()
|
||||
if e == nil {
|
||||
// Since we don't force a constructor, nil is a possible value.
|
||||
// If multiple Gets race to set this, the swap makes sure only
|
||||
// one wins.
|
||||
z.cache.CompareAndSwap(nil, z.newCacheEntry())
|
||||
e = z.cache.Load()
|
||||
}
|
||||
return e.get()
|
||||
}
|
120
e2e/vendor/k8s.io/client-go/tools/cache/synctrack/synctrack.go
generated
vendored
Normal file
120
e2e/vendor/k8s.io/client-go/tools/cache/synctrack/synctrack.go
generated
vendored
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
Copyright 2022 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 synctrack contains utilities for helping controllers track whether
|
||||
// they are "synced" or not, that is, whether they have processed all items
|
||||
// from the informer's initial list.
|
||||
package synctrack
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// AsyncTracker helps propagate HasSynced in the face of multiple worker threads.
|
||||
type AsyncTracker[T comparable] struct {
|
||||
UpstreamHasSynced func() bool
|
||||
|
||||
lock sync.Mutex
|
||||
waiting sets.Set[T]
|
||||
}
|
||||
|
||||
// Start should be called prior to processing each key which is part of the
|
||||
// initial list.
|
||||
func (t *AsyncTracker[T]) Start(key T) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
if t.waiting == nil {
|
||||
t.waiting = sets.New[T](key)
|
||||
} else {
|
||||
t.waiting.Insert(key)
|
||||
}
|
||||
}
|
||||
|
||||
// Finished should be called when finished processing a key which was part of
|
||||
// the initial list. Since keys are tracked individually, nothing bad happens
|
||||
// if you call Finished without a corresponding call to Start. This makes it
|
||||
// easier to use this in combination with e.g. queues which don't make it easy
|
||||
// to plumb through the isInInitialList boolean.
|
||||
func (t *AsyncTracker[T]) Finished(key T) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
if t.waiting != nil {
|
||||
t.waiting.Delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
// HasSynced returns true if the source is synced and every key present in the
|
||||
// initial list has been processed. This relies on the source not considering
|
||||
// itself synced until *after* it has delivered the notification for the last
|
||||
// key, and that notification handler must have called Start.
|
||||
func (t *AsyncTracker[T]) HasSynced() bool {
|
||||
// Call UpstreamHasSynced first: it might take a lock, which might take
|
||||
// a significant amount of time, and we can't hold our lock while
|
||||
// waiting on that or a user is likely to get a deadlock.
|
||||
if !t.UpstreamHasSynced() {
|
||||
return false
|
||||
}
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
return t.waiting.Len() == 0
|
||||
}
|
||||
|
||||
// SingleFileTracker helps propagate HasSynced when events are processed in
|
||||
// order (i.e. via a queue).
|
||||
type SingleFileTracker struct {
|
||||
// Important: count is used with atomic operations so it must be 64-bit
|
||||
// aligned, otherwise atomic operations will panic. Having it at the top of
|
||||
// the struct will guarantee that, even on 32-bit arches.
|
||||
// See https://pkg.go.dev/sync/atomic#pkg-note-BUG for more information.
|
||||
count int64
|
||||
|
||||
UpstreamHasSynced func() bool
|
||||
}
|
||||
|
||||
// Start should be called prior to processing each key which is part of the
|
||||
// initial list.
|
||||
func (t *SingleFileTracker) Start() {
|
||||
atomic.AddInt64(&t.count, 1)
|
||||
}
|
||||
|
||||
// Finished should be called when finished processing a key which was part of
|
||||
// the initial list. You must never call Finished() before (or without) its
|
||||
// corresponding Start(), that is a logic error that could cause HasSynced to
|
||||
// return a wrong value. To help you notice this should it happen, Finished()
|
||||
// will panic if the internal counter goes negative.
|
||||
func (t *SingleFileTracker) Finished() {
|
||||
result := atomic.AddInt64(&t.count, -1)
|
||||
if result < 0 {
|
||||
panic("synctrack: negative counter; this logic error means HasSynced may return incorrect value")
|
||||
}
|
||||
}
|
||||
|
||||
// HasSynced returns true if the source is synced and every key present in the
|
||||
// initial list has been processed. This relies on the source not considering
|
||||
// itself synced until *after* it has delivered the notification for the last
|
||||
// key, and that notification handler must have called Start.
|
||||
func (t *SingleFileTracker) HasSynced() bool {
|
||||
// Call UpstreamHasSynced first: it might take a lock, which might take
|
||||
// a significant amount of time, and we don't want to then act on a
|
||||
// stale count value.
|
||||
if !t.UpstreamHasSynced() {
|
||||
return false
|
||||
}
|
||||
return atomic.LoadInt64(&t.count) <= 0
|
||||
}
|
385
e2e/vendor/k8s.io/client-go/tools/cache/thread_safe_store.go
generated
vendored
Normal file
385
e2e/vendor/k8s.io/client-go/tools/cache/thread_safe_store.go
generated
vendored
Normal file
@ -0,0 +1,385 @@
|
||||
/*
|
||||
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 cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// ThreadSafeStore is an interface that allows concurrent indexed
|
||||
// access to a storage backend. It is like Indexer but does not
|
||||
// (necessarily) know how to extract the Store key from a given
|
||||
// object.
|
||||
//
|
||||
// TL;DR caveats: you must not modify anything returned by Get or List as it will break
|
||||
// the indexing feature in addition to not being thread safe.
|
||||
//
|
||||
// The guarantees of thread safety provided by List/Get are only valid if the caller
|
||||
// treats returned items as read-only. For example, a pointer inserted in the store
|
||||
// through `Add` will be returned as is by `Get`. Multiple clients might invoke `Get`
|
||||
// on the same key and modify the pointer in a non-thread-safe way. Also note that
|
||||
// modifying objects stored by the indexers (if any) will *not* automatically lead
|
||||
// to a re-index. So it's not a good idea to directly modify the objects returned by
|
||||
// Get/List, in general.
|
||||
type ThreadSafeStore interface {
|
||||
Add(key string, obj interface{})
|
||||
Update(key string, obj interface{})
|
||||
Delete(key string)
|
||||
Get(key string) (item interface{}, exists bool)
|
||||
List() []interface{}
|
||||
ListKeys() []string
|
||||
Replace(map[string]interface{}, string)
|
||||
Index(indexName string, obj interface{}) ([]interface{}, error)
|
||||
IndexKeys(indexName, indexedValue string) ([]string, error)
|
||||
ListIndexFuncValues(name string) []string
|
||||
ByIndex(indexName, indexedValue string) ([]interface{}, error)
|
||||
GetIndexers() Indexers
|
||||
|
||||
// AddIndexers adds more indexers to this store. This supports adding indexes after the store already has items.
|
||||
AddIndexers(newIndexers Indexers) error
|
||||
// Resync is a no-op and is deprecated
|
||||
Resync() error
|
||||
}
|
||||
|
||||
// storeIndex implements the indexing functionality for Store interface
|
||||
type storeIndex struct {
|
||||
// indexers maps a name to an IndexFunc
|
||||
indexers Indexers
|
||||
// indices maps a name to an Index
|
||||
indices Indices
|
||||
}
|
||||
|
||||
func (i *storeIndex) reset() {
|
||||
i.indices = Indices{}
|
||||
}
|
||||
|
||||
func (i *storeIndex) getKeysFromIndex(indexName string, obj interface{}) (sets.String, error) {
|
||||
indexFunc := i.indexers[indexName]
|
||||
if indexFunc == nil {
|
||||
return nil, fmt.Errorf("Index with name %s does not exist", indexName)
|
||||
}
|
||||
|
||||
indexedValues, err := indexFunc(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
index := i.indices[indexName]
|
||||
|
||||
var storeKeySet sets.String
|
||||
if len(indexedValues) == 1 {
|
||||
// In majority of cases, there is exactly one value matching.
|
||||
// Optimize the most common path - deduping is not needed here.
|
||||
storeKeySet = index[indexedValues[0]]
|
||||
} else {
|
||||
// Need to de-dupe the return list.
|
||||
// Since multiple keys are allowed, this can happen.
|
||||
storeKeySet = sets.String{}
|
||||
for _, indexedValue := range indexedValues {
|
||||
for key := range index[indexedValue] {
|
||||
storeKeySet.Insert(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return storeKeySet, nil
|
||||
}
|
||||
|
||||
func (i *storeIndex) getKeysByIndex(indexName, indexedValue string) (sets.String, error) {
|
||||
indexFunc := i.indexers[indexName]
|
||||
if indexFunc == nil {
|
||||
return nil, fmt.Errorf("Index with name %s does not exist", indexName)
|
||||
}
|
||||
|
||||
index := i.indices[indexName]
|
||||
return index[indexedValue], nil
|
||||
}
|
||||
|
||||
func (i *storeIndex) getIndexValues(indexName string) []string {
|
||||
index := i.indices[indexName]
|
||||
names := make([]string, 0, len(index))
|
||||
for key := range index {
|
||||
names = append(names, key)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func (i *storeIndex) addIndexers(newIndexers Indexers) error {
|
||||
oldKeys := sets.StringKeySet(i.indexers)
|
||||
newKeys := sets.StringKeySet(newIndexers)
|
||||
|
||||
if oldKeys.HasAny(newKeys.List()...) {
|
||||
return fmt.Errorf("indexer conflict: %v", oldKeys.Intersection(newKeys))
|
||||
}
|
||||
|
||||
for k, v := range newIndexers {
|
||||
i.indexers[k] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateSingleIndex modifies the objects location in the named index:
|
||||
// - for create you must provide only the newObj
|
||||
// - for update you must provide both the oldObj and the newObj
|
||||
// - for delete you must provide only the oldObj
|
||||
// updateSingleIndex must be called from a function that already has a lock on the cache
|
||||
func (i *storeIndex) updateSingleIndex(name string, oldObj interface{}, newObj interface{}, key string) {
|
||||
var oldIndexValues, indexValues []string
|
||||
indexFunc, ok := i.indexers[name]
|
||||
if !ok {
|
||||
// Should never happen. Caller is responsible for ensuring this exists, and should call with lock
|
||||
// held to avoid any races.
|
||||
panic(fmt.Errorf("indexer %q does not exist", name))
|
||||
}
|
||||
if oldObj != nil {
|
||||
var err error
|
||||
oldIndexValues, err = indexFunc(oldObj)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("unable to calculate an index entry for key %q on index %q: %v", key, name, err))
|
||||
}
|
||||
} else {
|
||||
oldIndexValues = oldIndexValues[:0]
|
||||
}
|
||||
|
||||
if newObj != nil {
|
||||
var err error
|
||||
indexValues, err = indexFunc(newObj)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("unable to calculate an index entry for key %q on index %q: %v", key, name, err))
|
||||
}
|
||||
} else {
|
||||
indexValues = indexValues[:0]
|
||||
}
|
||||
|
||||
index := i.indices[name]
|
||||
if index == nil {
|
||||
index = Index{}
|
||||
i.indices[name] = index
|
||||
}
|
||||
|
||||
if len(indexValues) == 1 && len(oldIndexValues) == 1 && indexValues[0] == oldIndexValues[0] {
|
||||
// We optimize for the most common case where indexFunc returns a single value which has not been changed
|
||||
return
|
||||
}
|
||||
|
||||
for _, value := range oldIndexValues {
|
||||
i.deleteKeyFromIndex(key, value, index)
|
||||
}
|
||||
for _, value := range indexValues {
|
||||
i.addKeyToIndex(key, value, index)
|
||||
}
|
||||
}
|
||||
|
||||
// updateIndices modifies the objects location in the managed indexes:
|
||||
// - for create you must provide only the newObj
|
||||
// - for update you must provide both the oldObj and the newObj
|
||||
// - for delete you must provide only the oldObj
|
||||
// updateIndices must be called from a function that already has a lock on the cache
|
||||
func (i *storeIndex) updateIndices(oldObj interface{}, newObj interface{}, key string) {
|
||||
for name := range i.indexers {
|
||||
i.updateSingleIndex(name, oldObj, newObj, key)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *storeIndex) addKeyToIndex(key, indexValue string, index Index) {
|
||||
set := index[indexValue]
|
||||
if set == nil {
|
||||
set = sets.String{}
|
||||
index[indexValue] = set
|
||||
}
|
||||
set.Insert(key)
|
||||
}
|
||||
|
||||
func (i *storeIndex) deleteKeyFromIndex(key, indexValue string, index Index) {
|
||||
set := index[indexValue]
|
||||
if set == nil {
|
||||
return
|
||||
}
|
||||
set.Delete(key)
|
||||
// If we don't delete the set when zero, indices with high cardinality
|
||||
// short lived resources can cause memory to increase over time from
|
||||
// unused empty sets. See `kubernetes/kubernetes/issues/84959`.
|
||||
if len(set) == 0 {
|
||||
delete(index, indexValue)
|
||||
}
|
||||
}
|
||||
|
||||
// threadSafeMap implements ThreadSafeStore
|
||||
type threadSafeMap struct {
|
||||
lock sync.RWMutex
|
||||
items map[string]interface{}
|
||||
|
||||
// index implements the indexing functionality
|
||||
index *storeIndex
|
||||
}
|
||||
|
||||
func (c *threadSafeMap) Add(key string, obj interface{}) {
|
||||
c.Update(key, obj)
|
||||
}
|
||||
|
||||
func (c *threadSafeMap) Update(key string, obj interface{}) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
oldObject := c.items[key]
|
||||
c.items[key] = obj
|
||||
c.index.updateIndices(oldObject, obj, key)
|
||||
}
|
||||
|
||||
func (c *threadSafeMap) Delete(key string) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if obj, exists := c.items[key]; exists {
|
||||
c.index.updateIndices(obj, nil, key)
|
||||
delete(c.items, key)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *threadSafeMap) Get(key string) (item interface{}, exists bool) {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
item, exists = c.items[key]
|
||||
return item, exists
|
||||
}
|
||||
|
||||
func (c *threadSafeMap) List() []interface{} {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
list := make([]interface{}, 0, len(c.items))
|
||||
for _, item := range c.items {
|
||||
list = append(list, item)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// ListKeys returns a list of all the keys of the objects currently
|
||||
// in the threadSafeMap.
|
||||
func (c *threadSafeMap) ListKeys() []string {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
list := make([]string, 0, len(c.items))
|
||||
for key := range c.items {
|
||||
list = append(list, key)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func (c *threadSafeMap) Replace(items map[string]interface{}, resourceVersion string) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
c.items = items
|
||||
|
||||
// rebuild any index
|
||||
c.index.reset()
|
||||
for key, item := range c.items {
|
||||
c.index.updateIndices(nil, item, key)
|
||||
}
|
||||
}
|
||||
|
||||
// Index returns a list of items that match the given object on the index function.
|
||||
// Index is thread-safe so long as you treat all items as immutable.
|
||||
func (c *threadSafeMap) Index(indexName string, obj interface{}) ([]interface{}, error) {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
storeKeySet, err := c.index.getKeysFromIndex(indexName, obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list := make([]interface{}, 0, storeKeySet.Len())
|
||||
for storeKey := range storeKeySet {
|
||||
list = append(list, c.items[storeKey])
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// ByIndex returns a list of the items whose indexed values in the given index include the given indexed value
|
||||
func (c *threadSafeMap) ByIndex(indexName, indexedValue string) ([]interface{}, error) {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
set, err := c.index.getKeysByIndex(indexName, indexedValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list := make([]interface{}, 0, set.Len())
|
||||
for key := range set {
|
||||
list = append(list, c.items[key])
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// IndexKeys returns a list of the Store keys of the objects whose indexed values in the given index include the given indexed value.
|
||||
// IndexKeys is thread-safe so long as you treat all items as immutable.
|
||||
func (c *threadSafeMap) IndexKeys(indexName, indexedValue string) ([]string, error) {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
set, err := c.index.getKeysByIndex(indexName, indexedValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return set.List(), nil
|
||||
}
|
||||
|
||||
func (c *threadSafeMap) ListIndexFuncValues(indexName string) []string {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
return c.index.getIndexValues(indexName)
|
||||
}
|
||||
|
||||
func (c *threadSafeMap) GetIndexers() Indexers {
|
||||
return c.index.indexers
|
||||
}
|
||||
|
||||
func (c *threadSafeMap) AddIndexers(newIndexers Indexers) error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if err := c.index.addIndexers(newIndexers); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If there are already items, index them
|
||||
for key, item := range c.items {
|
||||
for name := range newIndexers {
|
||||
c.index.updateSingleIndex(name, nil, item, key)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *threadSafeMap) Resync() error {
|
||||
// Nothing to do
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewThreadSafeStore creates a new instance of ThreadSafeStore.
|
||||
func NewThreadSafeStore(indexers Indexers, indices Indices) ThreadSafeStore {
|
||||
return &threadSafeMap{
|
||||
items: map[string]interface{}{},
|
||||
index: &storeIndex{
|
||||
indexers: indexers,
|
||||
indices: indices,
|
||||
},
|
||||
}
|
||||
}
|
89
e2e/vendor/k8s.io/client-go/tools/cache/undelta_store.go
generated
vendored
Normal file
89
e2e/vendor/k8s.io/client-go/tools/cache/undelta_store.go
generated
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cache
|
||||
|
||||
// UndeltaStore listens to incremental updates and sends complete state on every change.
|
||||
// It implements the Store interface so that it can receive a stream of mirrored objects
|
||||
// from Reflector. Whenever it receives any complete (Store.Replace) or incremental change
|
||||
// (Store.Add, Store.Update, Store.Delete), it sends the complete state by calling PushFunc.
|
||||
// It is thread-safe. It guarantees that every change (Add, Update, Replace, Delete) results
|
||||
// in one call to PushFunc, but sometimes PushFunc may be called twice with the same values.
|
||||
// PushFunc should be thread safe.
|
||||
type UndeltaStore struct {
|
||||
Store
|
||||
PushFunc func([]interface{})
|
||||
}
|
||||
|
||||
// Assert that it implements the Store interface.
|
||||
var _ Store = &UndeltaStore{}
|
||||
|
||||
// Add inserts an object into the store and sends complete state by calling PushFunc.
|
||||
// Note about thread safety. The Store implementation (cache.cache) uses a lock for all methods.
|
||||
// In the functions below, the lock gets released and reacquired betweend the {Add,Delete,etc}
|
||||
// and the List. So, the following can happen, resulting in two identical calls to PushFunc.
|
||||
// time thread 1 thread 2
|
||||
// 0 UndeltaStore.Add(a)
|
||||
// 1 UndeltaStore.Add(b)
|
||||
// 2 Store.Add(a)
|
||||
// 3 Store.Add(b)
|
||||
// 4 Store.List() -> [a,b]
|
||||
// 5 Store.List() -> [a,b]
|
||||
func (u *UndeltaStore) Add(obj interface{}) error {
|
||||
if err := u.Store.Add(obj); err != nil {
|
||||
return err
|
||||
}
|
||||
u.PushFunc(u.Store.List())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update sets an item in the cache to its updated state and sends complete state by calling PushFunc.
|
||||
func (u *UndeltaStore) Update(obj interface{}) error {
|
||||
if err := u.Store.Update(obj); err != nil {
|
||||
return err
|
||||
}
|
||||
u.PushFunc(u.Store.List())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes an item from the cache and sends complete state by calling PushFunc.
|
||||
func (u *UndeltaStore) Delete(obj interface{}) error {
|
||||
if err := u.Store.Delete(obj); err != nil {
|
||||
return err
|
||||
}
|
||||
u.PushFunc(u.Store.List())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Replace will delete the contents of current store, using instead the given list.
|
||||
// 'u' takes ownership of the list, you should not reference the list again
|
||||
// after calling this function.
|
||||
// The new contents complete state will be sent by calling PushFunc after replacement.
|
||||
func (u *UndeltaStore) Replace(list []interface{}, resourceVersion string) error {
|
||||
if err := u.Store.Replace(list, resourceVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
u.PushFunc(u.Store.List())
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewUndeltaStore returns an UndeltaStore implemented with a Store.
|
||||
func NewUndeltaStore(pushFunc func([]interface{}), keyFunc KeyFunc) *UndeltaStore {
|
||||
return &UndeltaStore{
|
||||
Store: NewStore(keyFunc),
|
||||
PushFunc: pushFunc,
|
||||
}
|
||||
}
|
19
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/doc.go
generated
vendored
Normal file
19
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/doc.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
|
||||
package api // import "k8s.io/client-go/tools/clientcmd/api"
|
265
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/helpers.go
generated
vendored
Normal file
265
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/helpers.go
generated
vendored
Normal file
@ -0,0 +1,265 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func init() {
|
||||
sDec, _ := base64.StdEncoding.DecodeString("REDACTED+")
|
||||
redactedBytes = []byte(string(sDec))
|
||||
sDec, _ = base64.StdEncoding.DecodeString("DATA+OMITTED")
|
||||
dataOmittedBytes = []byte(string(sDec))
|
||||
}
|
||||
|
||||
// IsConfigEmpty returns true if the config is empty.
|
||||
func IsConfigEmpty(config *Config) bool {
|
||||
return len(config.AuthInfos) == 0 && len(config.Clusters) == 0 && len(config.Contexts) == 0 &&
|
||||
len(config.CurrentContext) == 0 &&
|
||||
len(config.Preferences.Extensions) == 0 && !config.Preferences.Colors &&
|
||||
len(config.Extensions) == 0
|
||||
}
|
||||
|
||||
// MinifyConfig read the current context and uses that to keep only the relevant pieces of config
|
||||
// This is useful for making secrets based on kubeconfig files
|
||||
func MinifyConfig(config *Config) error {
|
||||
if len(config.CurrentContext) == 0 {
|
||||
return errors.New("current-context must exist in order to minify")
|
||||
}
|
||||
|
||||
currContext, exists := config.Contexts[config.CurrentContext]
|
||||
if !exists {
|
||||
return fmt.Errorf("cannot locate context %v", config.CurrentContext)
|
||||
}
|
||||
|
||||
newContexts := map[string]*Context{}
|
||||
newContexts[config.CurrentContext] = currContext
|
||||
|
||||
newClusters := map[string]*Cluster{}
|
||||
if len(currContext.Cluster) > 0 {
|
||||
if _, exists := config.Clusters[currContext.Cluster]; !exists {
|
||||
return fmt.Errorf("cannot locate cluster %v", currContext.Cluster)
|
||||
}
|
||||
|
||||
newClusters[currContext.Cluster] = config.Clusters[currContext.Cluster]
|
||||
}
|
||||
|
||||
newAuthInfos := map[string]*AuthInfo{}
|
||||
if len(currContext.AuthInfo) > 0 {
|
||||
if _, exists := config.AuthInfos[currContext.AuthInfo]; !exists {
|
||||
return fmt.Errorf("cannot locate user %v", currContext.AuthInfo)
|
||||
}
|
||||
|
||||
newAuthInfos[currContext.AuthInfo] = config.AuthInfos[currContext.AuthInfo]
|
||||
}
|
||||
|
||||
config.AuthInfos = newAuthInfos
|
||||
config.Clusters = newClusters
|
||||
config.Contexts = newContexts
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
dataOmittedBytes []byte
|
||||
redactedBytes []byte
|
||||
)
|
||||
|
||||
// ShortenConfig redacts raw data entries from the config object for a human-readable view.
|
||||
func ShortenConfig(config *Config) {
|
||||
// trick json encoder into printing a human-readable string in the raw data
|
||||
// by base64 decoding what we want to print. Relies on implementation of
|
||||
// http://golang.org/pkg/encoding/json/#Marshal using base64 to encode []byte
|
||||
for key, authInfo := range config.AuthInfos {
|
||||
if len(authInfo.ClientKeyData) > 0 {
|
||||
authInfo.ClientKeyData = dataOmittedBytes
|
||||
}
|
||||
if len(authInfo.ClientCertificateData) > 0 {
|
||||
authInfo.ClientCertificateData = dataOmittedBytes
|
||||
}
|
||||
if len(authInfo.Token) > 0 {
|
||||
authInfo.Token = "REDACTED"
|
||||
}
|
||||
config.AuthInfos[key] = authInfo
|
||||
}
|
||||
for key, cluster := range config.Clusters {
|
||||
if len(cluster.CertificateAuthorityData) > 0 {
|
||||
cluster.CertificateAuthorityData = dataOmittedBytes
|
||||
}
|
||||
config.Clusters[key] = cluster
|
||||
}
|
||||
}
|
||||
|
||||
// FlattenConfig changes the config object into a self-contained config (useful for making secrets)
|
||||
func FlattenConfig(config *Config) error {
|
||||
for key, authInfo := range config.AuthInfos {
|
||||
baseDir, err := MakeAbs(filepath.Dir(authInfo.LocationOfOrigin), "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := FlattenContent(&authInfo.ClientCertificate, &authInfo.ClientCertificateData, baseDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := FlattenContent(&authInfo.ClientKey, &authInfo.ClientKeyData, baseDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.AuthInfos[key] = authInfo
|
||||
}
|
||||
for key, cluster := range config.Clusters {
|
||||
baseDir, err := MakeAbs(filepath.Dir(cluster.LocationOfOrigin), "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := FlattenContent(&cluster.CertificateAuthority, &cluster.CertificateAuthorityData, baseDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.Clusters[key] = cluster
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func FlattenContent(path *string, contents *[]byte, baseDir string) error {
|
||||
if len(*path) != 0 {
|
||||
if len(*contents) > 0 {
|
||||
return errors.New("cannot have values for both path and contents")
|
||||
}
|
||||
|
||||
var err error
|
||||
absPath := ResolvePath(*path, baseDir)
|
||||
*contents, err = os.ReadFile(absPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*path = ""
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResolvePath returns the path as an absolute paths, relative to the given base directory
|
||||
func ResolvePath(path string, base string) string {
|
||||
// Don't resolve empty paths
|
||||
if len(path) > 0 {
|
||||
// Don't resolve absolute paths
|
||||
if !filepath.IsAbs(path) {
|
||||
return filepath.Join(base, path)
|
||||
}
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
func MakeAbs(path, base string) (string, error) {
|
||||
if filepath.IsAbs(path) {
|
||||
return path, nil
|
||||
}
|
||||
if len(base) == 0 {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
base = cwd
|
||||
}
|
||||
return filepath.Join(base, path), nil
|
||||
}
|
||||
|
||||
// RedactSecrets replaces any sensitive values with REDACTED
|
||||
func RedactSecrets(config *Config) error {
|
||||
return redactSecrets(reflect.ValueOf(config), false)
|
||||
}
|
||||
|
||||
func redactSecrets(curr reflect.Value, redact bool) error {
|
||||
redactedBytes = []byte("REDACTED")
|
||||
if !curr.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
actualCurrValue := curr
|
||||
if curr.Kind() == reflect.Ptr {
|
||||
actualCurrValue = curr.Elem()
|
||||
}
|
||||
|
||||
switch actualCurrValue.Kind() {
|
||||
case reflect.Map:
|
||||
for _, v := range actualCurrValue.MapKeys() {
|
||||
err := redactSecrets(actualCurrValue.MapIndex(v), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case reflect.String:
|
||||
if redact {
|
||||
if !actualCurrValue.IsZero() {
|
||||
actualCurrValue.SetString("REDACTED")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case reflect.Slice:
|
||||
if actualCurrValue.Type() == reflect.TypeOf([]byte{}) && redact {
|
||||
if !actualCurrValue.IsNil() {
|
||||
actualCurrValue.SetBytes(redactedBytes)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for i := 0; i < actualCurrValue.Len(); i++ {
|
||||
err := redactSecrets(actualCurrValue.Index(i), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case reflect.Struct:
|
||||
for fieldIndex := 0; fieldIndex < actualCurrValue.NumField(); fieldIndex++ {
|
||||
currFieldValue := actualCurrValue.Field(fieldIndex)
|
||||
currFieldType := actualCurrValue.Type().Field(fieldIndex)
|
||||
currYamlTag := currFieldType.Tag.Get("datapolicy")
|
||||
currFieldTypeYamlName := strings.Split(currYamlTag, ",")[0]
|
||||
if currFieldTypeYamlName != "" {
|
||||
err := redactSecrets(currFieldValue, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := redactSecrets(currFieldValue, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
61
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/latest/latest.go
generated
vendored
Normal file
61
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/latest/latest.go
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
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 latest
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/versioning"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/client-go/tools/clientcmd/api/v1"
|
||||
)
|
||||
|
||||
// Version is the string that represents the current external default version.
|
||||
const Version = "v1"
|
||||
|
||||
var ExternalVersion = schema.GroupVersion{Group: "", Version: "v1"}
|
||||
|
||||
// OldestVersion is the string that represents the oldest server version supported,
|
||||
// for client code that wants to hardcode the lowest common denominator.
|
||||
const OldestVersion = "v1"
|
||||
|
||||
// Versions is the list of versions that are recognized in code. The order provided
|
||||
// may be assumed to be least feature rich to most feature rich, and clients may
|
||||
// choose to prefer the latter items in the list over the former items when presented
|
||||
// with a set of versions to choose.
|
||||
var Versions = []string{"v1"}
|
||||
|
||||
var (
|
||||
Codec runtime.Codec
|
||||
Scheme *runtime.Scheme
|
||||
)
|
||||
|
||||
func init() {
|
||||
Scheme = runtime.NewScheme()
|
||||
utilruntime.Must(api.AddToScheme(Scheme))
|
||||
utilruntime.Must(v1.AddToScheme(Scheme))
|
||||
yamlSerializer := json.NewSerializerWithOptions(json.DefaultMetaFactory, Scheme, Scheme, json.SerializerOptions{Yaml: true})
|
||||
Codec = versioning.NewDefaultingCodecForScheme(
|
||||
Scheme,
|
||||
yamlSerializer,
|
||||
yamlSerializer,
|
||||
schema.GroupVersion{Version: Version},
|
||||
runtime.InternalGroupVersioner,
|
||||
)
|
||||
}
|
46
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/register.go
generated
vendored
Normal file
46
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/register.go
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
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 api
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
// TODO this should be in the "kubeconfig" group
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: "", Version: runtime.APIVersionInternal}
|
||||
|
||||
var (
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&Config{},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (obj *Config) GetObjectKind() schema.ObjectKind { return obj }
|
||||
func (obj *Config) SetGroupVersionKind(gvk schema.GroupVersionKind) {
|
||||
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
|
||||
}
|
||||
func (obj *Config) GroupVersionKind() schema.GroupVersionKind {
|
||||
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
|
||||
}
|
375
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/types.go
generated
vendored
Normal file
375
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/types.go
generated
vendored
Normal file
@ -0,0 +1,375 @@
|
||||
/*
|
||||
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 api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// Where possible, json tags match the cli argument names.
|
||||
// Top level config objects and all values required for proper functioning are not "omitempty". Any truly optional piece of config is allowed to be omitted.
|
||||
|
||||
// Config holds the information needed to build connect to remote kubernetes clusters as a given user
|
||||
// IMPORTANT if you add fields to this struct, please update IsConfigEmpty()
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type Config struct {
|
||||
// Legacy field from pkg/api/types.go TypeMeta.
|
||||
// TODO(jlowdermilk): remove this after eliminating downstream dependencies.
|
||||
// +k8s:conversion-gen=false
|
||||
// +optional
|
||||
Kind string `json:"kind,omitempty"`
|
||||
// Legacy field from pkg/api/types.go TypeMeta.
|
||||
// TODO(jlowdermilk): remove this after eliminating downstream dependencies.
|
||||
// +k8s:conversion-gen=false
|
||||
// +optional
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
// Preferences holds general information to be use for cli interactions
|
||||
Preferences Preferences `json:"preferences"`
|
||||
// Clusters is a map of referencable names to cluster configs
|
||||
Clusters map[string]*Cluster `json:"clusters"`
|
||||
// AuthInfos is a map of referencable names to user configs
|
||||
AuthInfos map[string]*AuthInfo `json:"users"`
|
||||
// Contexts is a map of referencable names to context configs
|
||||
Contexts map[string]*Context `json:"contexts"`
|
||||
// CurrentContext is the name of the context that you would like to use by default
|
||||
CurrentContext string `json:"current-context"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions map[string]runtime.Object `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// IMPORTANT if you add fields to this struct, please update IsConfigEmpty()
|
||||
type Preferences struct {
|
||||
// +optional
|
||||
Colors bool `json:"colors,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions map[string]runtime.Object `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// Cluster contains information about how to communicate with a kubernetes cluster
|
||||
type Cluster struct {
|
||||
// LocationOfOrigin indicates where this object came from. It is used for round tripping config post-merge, but never serialized.
|
||||
// +k8s:conversion-gen=false
|
||||
LocationOfOrigin string `json:"-"`
|
||||
// Server is the address of the kubernetes cluster (https://hostname:port).
|
||||
Server string `json:"server"`
|
||||
// TLSServerName is used to check server certificate. If TLSServerName is empty, the hostname used to contact the server is used.
|
||||
// +optional
|
||||
TLSServerName string `json:"tls-server-name,omitempty"`
|
||||
// InsecureSkipTLSVerify skips the validity check for the server's certificate. This will make your HTTPS connections insecure.
|
||||
// +optional
|
||||
InsecureSkipTLSVerify bool `json:"insecure-skip-tls-verify,omitempty"`
|
||||
// CertificateAuthority is the path to a cert file for the certificate authority.
|
||||
// +optional
|
||||
CertificateAuthority string `json:"certificate-authority,omitempty"`
|
||||
// CertificateAuthorityData contains PEM-encoded certificate authority certificates. Overrides CertificateAuthority
|
||||
// +optional
|
||||
CertificateAuthorityData []byte `json:"certificate-authority-data,omitempty"`
|
||||
// ProxyURL is the URL to the proxy to be used for all requests made by this
|
||||
// client. URLs with "http", "https", and "socks5" schemes are supported. If
|
||||
// this configuration is not provided or the empty string, the client
|
||||
// attempts to construct a proxy configuration from http_proxy and
|
||||
// https_proxy environment variables. If these environment variables are not
|
||||
// set, the client does not attempt to proxy requests.
|
||||
//
|
||||
// socks5 proxying does not currently support spdy streaming endpoints (exec,
|
||||
// attach, port forward).
|
||||
// +optional
|
||||
ProxyURL string `json:"proxy-url,omitempty"`
|
||||
// DisableCompression allows client to opt-out of response compression for all requests to the server. This is useful
|
||||
// to speed up requests (specifically lists) when client-server network bandwidth is ample, by saving time on
|
||||
// compression (server-side) and decompression (client-side): https://github.com/kubernetes/kubernetes/issues/112296.
|
||||
// +optional
|
||||
DisableCompression bool `json:"disable-compression,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions map[string]runtime.Object `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// AuthInfo contains information that describes identity information. This is use to tell the kubernetes cluster who you are.
|
||||
type AuthInfo struct {
|
||||
// LocationOfOrigin indicates where this object came from. It is used for round tripping config post-merge, but never serialized.
|
||||
// +k8s:conversion-gen=false
|
||||
LocationOfOrigin string `json:"-"`
|
||||
// ClientCertificate is the path to a client cert file for TLS.
|
||||
// +optional
|
||||
ClientCertificate string `json:"client-certificate,omitempty"`
|
||||
// ClientCertificateData contains PEM-encoded data from a client cert file for TLS. Overrides ClientCertificate
|
||||
// +optional
|
||||
ClientCertificateData []byte `json:"client-certificate-data,omitempty"`
|
||||
// ClientKey is the path to a client key file for TLS.
|
||||
// +optional
|
||||
ClientKey string `json:"client-key,omitempty"`
|
||||
// ClientKeyData contains PEM-encoded data from a client key file for TLS. Overrides ClientKey
|
||||
// +optional
|
||||
ClientKeyData []byte `json:"client-key-data,omitempty" datapolicy:"security-key"`
|
||||
// Token is the bearer token for authentication to the kubernetes cluster.
|
||||
// +optional
|
||||
Token string `json:"token,omitempty" datapolicy:"token"`
|
||||
// TokenFile is a pointer to a file that contains a bearer token (as described above). If both Token and TokenFile are present, Token takes precedence.
|
||||
// +optional
|
||||
TokenFile string `json:"tokenFile,omitempty"`
|
||||
// Impersonate is the username to act-as.
|
||||
// +optional
|
||||
Impersonate string `json:"act-as,omitempty"`
|
||||
// ImpersonateUID is the uid to impersonate.
|
||||
// +optional
|
||||
ImpersonateUID string `json:"act-as-uid,omitempty"`
|
||||
// ImpersonateGroups is the groups to impersonate.
|
||||
// +optional
|
||||
ImpersonateGroups []string `json:"act-as-groups,omitempty"`
|
||||
// ImpersonateUserExtra contains additional information for impersonated user.
|
||||
// +optional
|
||||
ImpersonateUserExtra map[string][]string `json:"act-as-user-extra,omitempty"`
|
||||
// Username is the username for basic authentication to the kubernetes cluster.
|
||||
// +optional
|
||||
Username string `json:"username,omitempty"`
|
||||
// Password is the password for basic authentication to the kubernetes cluster.
|
||||
// +optional
|
||||
Password string `json:"password,omitempty" datapolicy:"password"`
|
||||
// AuthProvider specifies a custom authentication plugin for the kubernetes cluster.
|
||||
// +optional
|
||||
AuthProvider *AuthProviderConfig `json:"auth-provider,omitempty"`
|
||||
// Exec specifies a custom exec-based authentication plugin for the kubernetes cluster.
|
||||
// +optional
|
||||
Exec *ExecConfig `json:"exec,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions map[string]runtime.Object `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// Context is a tuple of references to a cluster (how do I communicate with a kubernetes cluster), a user (how do I identify myself), and a namespace (what subset of resources do I want to work with)
|
||||
type Context struct {
|
||||
// LocationOfOrigin indicates where this object came from. It is used for round tripping config post-merge, but never serialized.
|
||||
// +k8s:conversion-gen=false
|
||||
LocationOfOrigin string `json:"-"`
|
||||
// Cluster is the name of the cluster for this context
|
||||
Cluster string `json:"cluster"`
|
||||
// AuthInfo is the name of the authInfo for this context
|
||||
AuthInfo string `json:"user"`
|
||||
// Namespace is the default namespace to use on unspecified requests
|
||||
// +optional
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions map[string]runtime.Object `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// AuthProviderConfig holds the configuration for a specified auth provider.
|
||||
type AuthProviderConfig struct {
|
||||
Name string `json:"name"`
|
||||
// +optional
|
||||
Config map[string]string `json:"config,omitempty"`
|
||||
}
|
||||
|
||||
var _ fmt.Stringer = new(AuthProviderConfig)
|
||||
var _ fmt.GoStringer = new(AuthProviderConfig)
|
||||
|
||||
// GoString implements fmt.GoStringer and sanitizes sensitive fields of
|
||||
// AuthProviderConfig to prevent accidental leaking via logs.
|
||||
func (c AuthProviderConfig) GoString() string {
|
||||
return c.String()
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer and sanitizes sensitive fields of
|
||||
// AuthProviderConfig to prevent accidental leaking via logs.
|
||||
func (c AuthProviderConfig) String() string {
|
||||
cfg := "<nil>"
|
||||
if c.Config != nil {
|
||||
cfg = "--- REDACTED ---"
|
||||
}
|
||||
return fmt.Sprintf("api.AuthProviderConfig{Name: %q, Config: map[string]string{%s}}", c.Name, cfg)
|
||||
}
|
||||
|
||||
// ExecConfig specifies a command to provide client credentials. The command is exec'd
|
||||
// and outputs structured stdout holding credentials.
|
||||
//
|
||||
// See the client.authentication.k8s.io API group for specifications of the exact input
|
||||
// and output format
|
||||
type ExecConfig struct {
|
||||
// Command to execute.
|
||||
Command string `json:"command"`
|
||||
// Arguments to pass to the command when executing it.
|
||||
// +optional
|
||||
Args []string `json:"args"`
|
||||
// Env defines additional environment variables to expose to the process. These
|
||||
// are unioned with the host's environment, as well as variables client-go uses
|
||||
// to pass argument to the plugin.
|
||||
// +optional
|
||||
Env []ExecEnvVar `json:"env"`
|
||||
|
||||
// Preferred input version of the ExecInfo. The returned ExecCredentials MUST use
|
||||
// the same encoding version as the input.
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
|
||||
// This text is shown to the user when the executable doesn't seem to be
|
||||
// present. For example, `brew install foo-cli` might be a good InstallHint for
|
||||
// foo-cli on Mac OS systems.
|
||||
InstallHint string `json:"installHint,omitempty"`
|
||||
|
||||
// ProvideClusterInfo determines whether or not to provide cluster information,
|
||||
// which could potentially contain very large CA data, to this exec plugin as a
|
||||
// part of the KUBERNETES_EXEC_INFO environment variable. By default, it is set
|
||||
// to false. Package k8s.io/client-go/tools/auth/exec provides helper methods for
|
||||
// reading this environment variable.
|
||||
ProvideClusterInfo bool `json:"provideClusterInfo"`
|
||||
|
||||
// Config holds additional config data that is specific to the exec
|
||||
// plugin with regards to the cluster being authenticated to.
|
||||
//
|
||||
// This data is sourced from the clientcmd Cluster object's extensions[exec] field:
|
||||
//
|
||||
// clusters:
|
||||
// - name: my-cluster
|
||||
// cluster:
|
||||
// ...
|
||||
// extensions:
|
||||
// - name: client.authentication.k8s.io/exec # reserved extension name for per cluster exec config
|
||||
// extension:
|
||||
// audience: 06e3fbd18de8 # arbitrary config
|
||||
//
|
||||
// In some environments, the user config may be exactly the same across many clusters
|
||||
// (i.e. call this exec plugin) minus some details that are specific to each cluster
|
||||
// such as the audience. This field allows the per cluster config to be directly
|
||||
// specified with the cluster info. Using this field to store secret data is not
|
||||
// recommended as one of the prime benefits of exec plugins is that no secrets need
|
||||
// to be stored directly in the kubeconfig.
|
||||
// +k8s:conversion-gen=false
|
||||
Config runtime.Object `json:"-"`
|
||||
|
||||
// InteractiveMode determines this plugin's relationship with standard input. Valid
|
||||
// values are "Never" (this exec plugin never uses standard input), "IfAvailable" (this
|
||||
// exec plugin wants to use standard input if it is available), or "Always" (this exec
|
||||
// plugin requires standard input to function). See ExecInteractiveMode values for more
|
||||
// details.
|
||||
//
|
||||
// If APIVersion is client.authentication.k8s.io/v1alpha1 or
|
||||
// client.authentication.k8s.io/v1beta1, then this field is optional and defaults
|
||||
// to "IfAvailable" when unset. Otherwise, this field is required.
|
||||
// +optional
|
||||
InteractiveMode ExecInteractiveMode `json:"interactiveMode,omitempty"`
|
||||
|
||||
// StdinUnavailable indicates whether the exec authenticator can pass standard
|
||||
// input through to this exec plugin. For example, a higher level entity might be using
|
||||
// standard input for something else and therefore it would not be safe for the exec
|
||||
// plugin to use standard input. This is kept here in order to keep all of the exec configuration
|
||||
// together, but it is never serialized.
|
||||
// +k8s:conversion-gen=false
|
||||
StdinUnavailable bool `json:"-"`
|
||||
|
||||
// StdinUnavailableMessage is an optional message to be displayed when the exec authenticator
|
||||
// cannot successfully run this exec plugin because it needs to use standard input and
|
||||
// StdinUnavailable is true. For example, a process that is already using standard input to
|
||||
// read user instructions might set this to "used by my-program to read user instructions".
|
||||
// +k8s:conversion-gen=false
|
||||
StdinUnavailableMessage string `json:"-"`
|
||||
}
|
||||
|
||||
var _ fmt.Stringer = new(ExecConfig)
|
||||
var _ fmt.GoStringer = new(ExecConfig)
|
||||
|
||||
// GoString implements fmt.GoStringer and sanitizes sensitive fields of
|
||||
// ExecConfig to prevent accidental leaking via logs.
|
||||
func (c ExecConfig) GoString() string {
|
||||
return c.String()
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer and sanitizes sensitive fields of ExecConfig
|
||||
// to prevent accidental leaking via logs.
|
||||
func (c ExecConfig) String() string {
|
||||
var args []string
|
||||
if len(c.Args) > 0 {
|
||||
args = []string{"--- REDACTED ---"}
|
||||
}
|
||||
env := "[]ExecEnvVar(nil)"
|
||||
if len(c.Env) > 0 {
|
||||
env = "[]ExecEnvVar{--- REDACTED ---}"
|
||||
}
|
||||
config := "runtime.Object(nil)"
|
||||
if c.Config != nil {
|
||||
config = "runtime.Object(--- REDACTED ---)"
|
||||
}
|
||||
return fmt.Sprintf("api.ExecConfig{Command: %q, Args: %#v, Env: %s, APIVersion: %q, ProvideClusterInfo: %t, Config: %s, StdinUnavailable: %t}", c.Command, args, env, c.APIVersion, c.ProvideClusterInfo, config, c.StdinUnavailable)
|
||||
}
|
||||
|
||||
// ExecEnvVar is used for setting environment variables when executing an exec-based
|
||||
// credential plugin.
|
||||
type ExecEnvVar struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// ExecInteractiveMode is a string that describes an exec plugin's relationship with standard input.
|
||||
type ExecInteractiveMode string
|
||||
|
||||
const (
|
||||
// NeverExecInteractiveMode declares that this exec plugin never needs to use standard
|
||||
// input, and therefore the exec plugin will be run regardless of whether standard input is
|
||||
// available for user input.
|
||||
NeverExecInteractiveMode ExecInteractiveMode = "Never"
|
||||
// IfAvailableExecInteractiveMode declares that this exec plugin would like to use standard input
|
||||
// if it is available, but can still operate if standard input is not available. Therefore, the
|
||||
// exec plugin will be run regardless of whether stdin is available for user input. If standard
|
||||
// input is available for user input, then it will be provided to this exec plugin.
|
||||
IfAvailableExecInteractiveMode ExecInteractiveMode = "IfAvailable"
|
||||
// AlwaysExecInteractiveMode declares that this exec plugin requires standard input in order to
|
||||
// run, and therefore the exec plugin will only be run if standard input is available for user
|
||||
// input. If standard input is not available for user input, then the exec plugin will not be run
|
||||
// and an error will be returned by the exec plugin runner.
|
||||
AlwaysExecInteractiveMode ExecInteractiveMode = "Always"
|
||||
)
|
||||
|
||||
// NewConfig is a convenience function that returns a new Config object with non-nil maps
|
||||
func NewConfig() *Config {
|
||||
return &Config{
|
||||
Preferences: *NewPreferences(),
|
||||
Clusters: make(map[string]*Cluster),
|
||||
AuthInfos: make(map[string]*AuthInfo),
|
||||
Contexts: make(map[string]*Context),
|
||||
Extensions: make(map[string]runtime.Object),
|
||||
}
|
||||
}
|
||||
|
||||
// NewContext is a convenience function that returns a new Context
|
||||
// object with non-nil maps
|
||||
func NewContext() *Context {
|
||||
return &Context{Extensions: make(map[string]runtime.Object)}
|
||||
}
|
||||
|
||||
// NewCluster is a convenience function that returns a new Cluster
|
||||
// object with non-nil maps
|
||||
func NewCluster() *Cluster {
|
||||
return &Cluster{Extensions: make(map[string]runtime.Object)}
|
||||
}
|
||||
|
||||
// NewAuthInfo is a convenience function that returns a new AuthInfo
|
||||
// object with non-nil maps
|
||||
func NewAuthInfo() *AuthInfo {
|
||||
return &AuthInfo{
|
||||
Extensions: make(map[string]runtime.Object),
|
||||
ImpersonateUserExtra: make(map[string][]string),
|
||||
}
|
||||
}
|
||||
|
||||
// NewPreferences is a convenience function that returns a new
|
||||
// Preferences object with non-nil maps
|
||||
func NewPreferences() *Preferences {
|
||||
return &Preferences{Extensions: make(map[string]runtime.Object)}
|
||||
}
|
174
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/v1/conversion.go
generated
vendored
Normal file
174
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/v1/conversion.go
generated
vendored
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
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 v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"k8s.io/apimachinery/pkg/conversion"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
func Convert_Slice_v1_NamedCluster_To_Map_string_To_Pointer_api_Cluster(in *[]NamedCluster, out *map[string]*api.Cluster, s conversion.Scope) error {
|
||||
for _, curr := range *in {
|
||||
newCluster := api.NewCluster()
|
||||
if err := Convert_v1_Cluster_To_api_Cluster(&curr.Cluster, newCluster, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if *out == nil {
|
||||
*out = make(map[string]*api.Cluster)
|
||||
}
|
||||
if (*out)[curr.Name] == nil {
|
||||
(*out)[curr.Name] = newCluster
|
||||
} else {
|
||||
return fmt.Errorf("error converting *[]NamedCluster into *map[string]*api.Cluster: duplicate name \"%v\" in list: %v", curr.Name, *in)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_Map_string_To_Pointer_api_Cluster_To_Slice_v1_NamedCluster(in *map[string]*api.Cluster, out *[]NamedCluster, s conversion.Scope) error {
|
||||
allKeys := make([]string, 0, len(*in))
|
||||
for key := range *in {
|
||||
allKeys = append(allKeys, key)
|
||||
}
|
||||
sort.Strings(allKeys)
|
||||
|
||||
for _, key := range allKeys {
|
||||
newCluster := (*in)[key]
|
||||
oldCluster := Cluster{}
|
||||
if err := Convert_api_Cluster_To_v1_Cluster(newCluster, &oldCluster, s); err != nil {
|
||||
return err
|
||||
}
|
||||
namedCluster := NamedCluster{key, oldCluster}
|
||||
*out = append(*out, namedCluster)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_Slice_v1_NamedAuthInfo_To_Map_string_To_Pointer_api_AuthInfo(in *[]NamedAuthInfo, out *map[string]*api.AuthInfo, s conversion.Scope) error {
|
||||
for _, curr := range *in {
|
||||
newAuthInfo := api.NewAuthInfo()
|
||||
if err := Convert_v1_AuthInfo_To_api_AuthInfo(&curr.AuthInfo, newAuthInfo, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if *out == nil {
|
||||
*out = make(map[string]*api.AuthInfo)
|
||||
}
|
||||
if (*out)[curr.Name] == nil {
|
||||
(*out)[curr.Name] = newAuthInfo
|
||||
} else {
|
||||
return fmt.Errorf("error converting *[]NamedAuthInfo into *map[string]*api.AuthInfo: duplicate name \"%v\" in list: %v", curr.Name, *in)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_Map_string_To_Pointer_api_AuthInfo_To_Slice_v1_NamedAuthInfo(in *map[string]*api.AuthInfo, out *[]NamedAuthInfo, s conversion.Scope) error {
|
||||
allKeys := make([]string, 0, len(*in))
|
||||
for key := range *in {
|
||||
allKeys = append(allKeys, key)
|
||||
}
|
||||
sort.Strings(allKeys)
|
||||
|
||||
for _, key := range allKeys {
|
||||
newAuthInfo := (*in)[key]
|
||||
oldAuthInfo := AuthInfo{}
|
||||
if err := Convert_api_AuthInfo_To_v1_AuthInfo(newAuthInfo, &oldAuthInfo, s); err != nil {
|
||||
return err
|
||||
}
|
||||
namedAuthInfo := NamedAuthInfo{key, oldAuthInfo}
|
||||
*out = append(*out, namedAuthInfo)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_Slice_v1_NamedContext_To_Map_string_To_Pointer_api_Context(in *[]NamedContext, out *map[string]*api.Context, s conversion.Scope) error {
|
||||
for _, curr := range *in {
|
||||
newContext := api.NewContext()
|
||||
if err := Convert_v1_Context_To_api_Context(&curr.Context, newContext, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if *out == nil {
|
||||
*out = make(map[string]*api.Context)
|
||||
}
|
||||
if (*out)[curr.Name] == nil {
|
||||
(*out)[curr.Name] = newContext
|
||||
} else {
|
||||
return fmt.Errorf("error converting *[]NamedContext into *map[string]*api.Context: duplicate name \"%v\" in list: %v", curr.Name, *in)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_Map_string_To_Pointer_api_Context_To_Slice_v1_NamedContext(in *map[string]*api.Context, out *[]NamedContext, s conversion.Scope) error {
|
||||
allKeys := make([]string, 0, len(*in))
|
||||
for key := range *in {
|
||||
allKeys = append(allKeys, key)
|
||||
}
|
||||
sort.Strings(allKeys)
|
||||
|
||||
for _, key := range allKeys {
|
||||
newContext := (*in)[key]
|
||||
oldContext := Context{}
|
||||
if err := Convert_api_Context_To_v1_Context(newContext, &oldContext, s); err != nil {
|
||||
return err
|
||||
}
|
||||
namedContext := NamedContext{key, oldContext}
|
||||
*out = append(*out, namedContext)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_Slice_v1_NamedExtension_To_Map_string_To_runtime_Object(in *[]NamedExtension, out *map[string]runtime.Object, s conversion.Scope) error {
|
||||
for _, curr := range *in {
|
||||
var newExtension runtime.Object
|
||||
if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&curr.Extension, &newExtension, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if *out == nil {
|
||||
*out = make(map[string]runtime.Object)
|
||||
}
|
||||
if (*out)[curr.Name] == nil {
|
||||
(*out)[curr.Name] = newExtension
|
||||
} else {
|
||||
return fmt.Errorf("error converting *[]NamedExtension into *map[string]runtime.Object: duplicate name \"%v\" in list: %v", curr.Name, *in)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_Map_string_To_runtime_Object_To_Slice_v1_NamedExtension(in *map[string]runtime.Object, out *[]NamedExtension, s conversion.Scope) error {
|
||||
allKeys := make([]string, 0, len(*in))
|
||||
for key := range *in {
|
||||
allKeys = append(allKeys, key)
|
||||
}
|
||||
sort.Strings(allKeys)
|
||||
|
||||
for _, key := range allKeys {
|
||||
newExtension := (*in)[key]
|
||||
oldExtension := runtime.RawExtension{}
|
||||
if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&newExtension, &oldExtension, s); err != nil {
|
||||
return err
|
||||
}
|
||||
namedExtension := NamedExtension{key, oldExtension}
|
||||
*out = append(*out, namedExtension)
|
||||
}
|
||||
return nil
|
||||
}
|
37
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/v1/defaults.go
generated
vendored
Normal file
37
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/v1/defaults.go
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
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 v1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
func addDefaultingFuncs(scheme *runtime.Scheme) error {
|
||||
return RegisterDefaults(scheme)
|
||||
}
|
||||
|
||||
func SetDefaults_ExecConfig(exec *ExecConfig) {
|
||||
if len(exec.InteractiveMode) == 0 {
|
||||
switch exec.APIVersion {
|
||||
case "client.authentication.k8s.io/v1beta1", "client.authentication.k8s.io/v1alpha1":
|
||||
// default to IfAvailableExecInteractiveMode for backwards compatibility
|
||||
exec.InteractiveMode = IfAvailableExecInteractiveMode
|
||||
default:
|
||||
// require other versions to explicitly declare whether they want stdin or not
|
||||
}
|
||||
}
|
||||
}
|
21
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/v1/doc.go
generated
vendored
Normal file
21
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/v1/doc.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// +k8s:conversion-gen=k8s.io/client-go/tools/clientcmd/api
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +k8s:defaulter-gen=Kind
|
||||
|
||||
package v1 // import "k8s.io/client-go/tools/clientcmd/api/v1"
|
56
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/v1/register.go
generated
vendored
Normal file
56
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/v1/register.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
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 v1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
// TODO this should be in the "kubeconfig" group
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: "", Version: "v1"}
|
||||
|
||||
var (
|
||||
// TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api.
|
||||
// localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes.
|
||||
SchemeBuilder runtime.SchemeBuilder
|
||||
localSchemeBuilder = &SchemeBuilder
|
||||
AddToScheme = localSchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
func init() {
|
||||
// We only register manually written functions here. The registration of the
|
||||
// generated functions takes place in the generated files. The separation
|
||||
// makes the code compile even when the generated files are missing.
|
||||
localSchemeBuilder.Register(addKnownTypes, addDefaultingFuncs)
|
||||
}
|
||||
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&Config{},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (obj *Config) GetObjectKind() schema.ObjectKind { return obj }
|
||||
func (obj *Config) SetGroupVersionKind(gvk schema.GroupVersionKind) {
|
||||
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
|
||||
}
|
||||
func (obj *Config) GroupVersionKind() schema.GroupVersionKind {
|
||||
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
|
||||
}
|
271
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/v1/types.go
generated
vendored
Normal file
271
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/v1/types.go
generated
vendored
Normal file
@ -0,0 +1,271 @@
|
||||
/*
|
||||
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 v1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// Where possible, json tags match the cli argument names.
|
||||
// Top level config objects and all values required for proper functioning are not "omitempty". Any truly optional piece of config is allowed to be omitted.
|
||||
|
||||
// Config holds the information needed to build connect to remote kubernetes clusters as a given user
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type Config struct {
|
||||
// Legacy field from pkg/api/types.go TypeMeta.
|
||||
// TODO(jlowdermilk): remove this after eliminating downstream dependencies.
|
||||
// +k8s:conversion-gen=false
|
||||
// +optional
|
||||
Kind string `json:"kind,omitempty"`
|
||||
// Legacy field from pkg/api/types.go TypeMeta.
|
||||
// TODO(jlowdermilk): remove this after eliminating downstream dependencies.
|
||||
// +k8s:conversion-gen=false
|
||||
// +optional
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
// Preferences holds general information to be use for cli interactions
|
||||
Preferences Preferences `json:"preferences"`
|
||||
// Clusters is a map of referencable names to cluster configs
|
||||
Clusters []NamedCluster `json:"clusters"`
|
||||
// AuthInfos is a map of referencable names to user configs
|
||||
AuthInfos []NamedAuthInfo `json:"users"`
|
||||
// Contexts is a map of referencable names to context configs
|
||||
Contexts []NamedContext `json:"contexts"`
|
||||
// CurrentContext is the name of the context that you would like to use by default
|
||||
CurrentContext string `json:"current-context"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
type Preferences struct {
|
||||
// +optional
|
||||
Colors bool `json:"colors,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// Cluster contains information about how to communicate with a kubernetes cluster
|
||||
type Cluster struct {
|
||||
// Server is the address of the kubernetes cluster (https://hostname:port).
|
||||
Server string `json:"server"`
|
||||
// TLSServerName is used to check server certificate. If TLSServerName is empty, the hostname used to contact the server is used.
|
||||
// +optional
|
||||
TLSServerName string `json:"tls-server-name,omitempty"`
|
||||
// InsecureSkipTLSVerify skips the validity check for the server's certificate. This will make your HTTPS connections insecure.
|
||||
// +optional
|
||||
InsecureSkipTLSVerify bool `json:"insecure-skip-tls-verify,omitempty"`
|
||||
// CertificateAuthority is the path to a cert file for the certificate authority.
|
||||
// +optional
|
||||
CertificateAuthority string `json:"certificate-authority,omitempty"`
|
||||
// CertificateAuthorityData contains PEM-encoded certificate authority certificates. Overrides CertificateAuthority
|
||||
// +optional
|
||||
CertificateAuthorityData []byte `json:"certificate-authority-data,omitempty"`
|
||||
// ProxyURL is the URL to the proxy to be used for all requests made by this
|
||||
// client. URLs with "http", "https", and "socks5" schemes are supported. If
|
||||
// this configuration is not provided or the empty string, the client
|
||||
// attempts to construct a proxy configuration from http_proxy and
|
||||
// https_proxy environment variables. If these environment variables are not
|
||||
// set, the client does not attempt to proxy requests.
|
||||
//
|
||||
// socks5 proxying does not currently support spdy streaming endpoints (exec,
|
||||
// attach, port forward).
|
||||
// +optional
|
||||
ProxyURL string `json:"proxy-url,omitempty"`
|
||||
// DisableCompression allows client to opt-out of response compression for all requests to the server. This is useful
|
||||
// to speed up requests (specifically lists) when client-server network bandwidth is ample, by saving time on
|
||||
// compression (server-side) and decompression (client-side): https://github.com/kubernetes/kubernetes/issues/112296.
|
||||
// +optional
|
||||
DisableCompression bool `json:"disable-compression,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// AuthInfo contains information that describes identity information. This is use to tell the kubernetes cluster who you are.
|
||||
type AuthInfo struct {
|
||||
// ClientCertificate is the path to a client cert file for TLS.
|
||||
// +optional
|
||||
ClientCertificate string `json:"client-certificate,omitempty"`
|
||||
// ClientCertificateData contains PEM-encoded data from a client cert file for TLS. Overrides ClientCertificate
|
||||
// +optional
|
||||
ClientCertificateData []byte `json:"client-certificate-data,omitempty"`
|
||||
// ClientKey is the path to a client key file for TLS.
|
||||
// +optional
|
||||
ClientKey string `json:"client-key,omitempty"`
|
||||
// ClientKeyData contains PEM-encoded data from a client key file for TLS. Overrides ClientKey
|
||||
// +optional
|
||||
ClientKeyData []byte `json:"client-key-data,omitempty" datapolicy:"security-key"`
|
||||
// Token is the bearer token for authentication to the kubernetes cluster.
|
||||
// +optional
|
||||
Token string `json:"token,omitempty" datapolicy:"token"`
|
||||
// TokenFile is a pointer to a file that contains a bearer token (as described above). If both Token and TokenFile are present, Token takes precedence.
|
||||
// +optional
|
||||
TokenFile string `json:"tokenFile,omitempty"`
|
||||
// Impersonate is the username to impersonate. The name matches the flag.
|
||||
// +optional
|
||||
Impersonate string `json:"as,omitempty"`
|
||||
// ImpersonateUID is the uid to impersonate.
|
||||
// +optional
|
||||
ImpersonateUID string `json:"as-uid,omitempty"`
|
||||
// ImpersonateGroups is the groups to impersonate.
|
||||
// +optional
|
||||
ImpersonateGroups []string `json:"as-groups,omitempty"`
|
||||
// ImpersonateUserExtra contains additional information for impersonated user.
|
||||
// +optional
|
||||
ImpersonateUserExtra map[string][]string `json:"as-user-extra,omitempty"`
|
||||
// Username is the username for basic authentication to the kubernetes cluster.
|
||||
// +optional
|
||||
Username string `json:"username,omitempty"`
|
||||
// Password is the password for basic authentication to the kubernetes cluster.
|
||||
// +optional
|
||||
Password string `json:"password,omitempty" datapolicy:"password"`
|
||||
// AuthProvider specifies a custom authentication plugin for the kubernetes cluster.
|
||||
// +optional
|
||||
AuthProvider *AuthProviderConfig `json:"auth-provider,omitempty"`
|
||||
// Exec specifies a custom exec-based authentication plugin for the kubernetes cluster.
|
||||
// +optional
|
||||
Exec *ExecConfig `json:"exec,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// Context is a tuple of references to a cluster (how do I communicate with a kubernetes cluster), a user (how do I identify myself), and a namespace (what subset of resources do I want to work with)
|
||||
type Context struct {
|
||||
// Cluster is the name of the cluster for this context
|
||||
Cluster string `json:"cluster"`
|
||||
// AuthInfo is the name of the authInfo for this context
|
||||
AuthInfo string `json:"user"`
|
||||
// Namespace is the default namespace to use on unspecified requests
|
||||
// +optional
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// NamedCluster relates nicknames to cluster information
|
||||
type NamedCluster struct {
|
||||
// Name is the nickname for this Cluster
|
||||
Name string `json:"name"`
|
||||
// Cluster holds the cluster information
|
||||
Cluster Cluster `json:"cluster"`
|
||||
}
|
||||
|
||||
// NamedContext relates nicknames to context information
|
||||
type NamedContext struct {
|
||||
// Name is the nickname for this Context
|
||||
Name string `json:"name"`
|
||||
// Context holds the context information
|
||||
Context Context `json:"context"`
|
||||
}
|
||||
|
||||
// NamedAuthInfo relates nicknames to auth information
|
||||
type NamedAuthInfo struct {
|
||||
// Name is the nickname for this AuthInfo
|
||||
Name string `json:"name"`
|
||||
// AuthInfo holds the auth information
|
||||
AuthInfo AuthInfo `json:"user"`
|
||||
}
|
||||
|
||||
// NamedExtension relates nicknames to extension information
|
||||
type NamedExtension struct {
|
||||
// Name is the nickname for this Extension
|
||||
Name string `json:"name"`
|
||||
// Extension holds the extension information
|
||||
Extension runtime.RawExtension `json:"extension"`
|
||||
}
|
||||
|
||||
// AuthProviderConfig holds the configuration for a specified auth provider.
|
||||
type AuthProviderConfig struct {
|
||||
Name string `json:"name"`
|
||||
Config map[string]string `json:"config"`
|
||||
}
|
||||
|
||||
// ExecConfig specifies a command to provide client credentials. The command is exec'd
|
||||
// and outputs structured stdout holding credentials.
|
||||
//
|
||||
// See the client.authentication.k8s.io API group for specifications of the exact input
|
||||
// and output format
|
||||
type ExecConfig struct {
|
||||
// Command to execute.
|
||||
Command string `json:"command"`
|
||||
// Arguments to pass to the command when executing it.
|
||||
// +optional
|
||||
Args []string `json:"args"`
|
||||
// Env defines additional environment variables to expose to the process. These
|
||||
// are unioned with the host's environment, as well as variables client-go uses
|
||||
// to pass argument to the plugin.
|
||||
// +optional
|
||||
Env []ExecEnvVar `json:"env"`
|
||||
|
||||
// Preferred input version of the ExecInfo. The returned ExecCredentials MUST use
|
||||
// the same encoding version as the input.
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
|
||||
// This text is shown to the user when the executable doesn't seem to be
|
||||
// present. For example, `brew install foo-cli` might be a good InstallHint for
|
||||
// foo-cli on Mac OS systems.
|
||||
InstallHint string `json:"installHint,omitempty"`
|
||||
|
||||
// ProvideClusterInfo determines whether or not to provide cluster information,
|
||||
// which could potentially contain very large CA data, to this exec plugin as a
|
||||
// part of the KUBERNETES_EXEC_INFO environment variable. By default, it is set
|
||||
// to false. Package k8s.io/client-go/tools/auth/exec provides helper methods for
|
||||
// reading this environment variable.
|
||||
ProvideClusterInfo bool `json:"provideClusterInfo"`
|
||||
|
||||
// InteractiveMode determines this plugin's relationship with standard input. Valid
|
||||
// values are "Never" (this exec plugin never uses standard input), "IfAvailable" (this
|
||||
// exec plugin wants to use standard input if it is available), or "Always" (this exec
|
||||
// plugin requires standard input to function). See ExecInteractiveMode values for more
|
||||
// details.
|
||||
//
|
||||
// If APIVersion is client.authentication.k8s.io/v1alpha1 or
|
||||
// client.authentication.k8s.io/v1beta1, then this field is optional and defaults
|
||||
// to "IfAvailable" when unset. Otherwise, this field is required.
|
||||
//+optional
|
||||
InteractiveMode ExecInteractiveMode `json:"interactiveMode,omitempty"`
|
||||
}
|
||||
|
||||
// ExecEnvVar is used for setting environment variables when executing an exec-based
|
||||
// credential plugin.
|
||||
type ExecEnvVar struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// ExecInteractiveMode is a string that describes an exec plugin's relationship with standard input.
|
||||
type ExecInteractiveMode string
|
||||
|
||||
const (
|
||||
// NeverExecInteractiveMode declares that this exec plugin never needs to use standard
|
||||
// input, and therefore the exec plugin will be run regardless of whether standard input is
|
||||
// available for user input.
|
||||
NeverExecInteractiveMode ExecInteractiveMode = "Never"
|
||||
// IfAvailableExecInteractiveMode declares that this exec plugin would like to use standard input
|
||||
// if it is available, but can still operate if standard input is not available. Therefore, the
|
||||
// exec plugin will be run regardless of whether stdin is available for user input. If standard
|
||||
// input is available for user input, then it will be provided to this exec plugin.
|
||||
IfAvailableExecInteractiveMode ExecInteractiveMode = "IfAvailable"
|
||||
// AlwaysExecInteractiveMode declares that this exec plugin requires standard input in order to
|
||||
// run, and therefore the exec plugin will only be run if standard input is available for user
|
||||
// input. If standard input is not available for user input, then the exec plugin will not be run
|
||||
// and an error will be returned by the exec plugin runner.
|
||||
AlwaysExecInteractiveMode ExecInteractiveMode = "Always"
|
||||
)
|
458
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/v1/zz_generated.conversion.go
generated
vendored
Normal file
458
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/v1/zz_generated.conversion.go
generated
vendored
Normal file
@ -0,0 +1,458 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
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 conversion-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
unsafe "unsafe"
|
||||
|
||||
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
api "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
func init() {
|
||||
localSchemeBuilder.Register(RegisterConversions)
|
||||
}
|
||||
|
||||
// RegisterConversions adds conversion functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
func RegisterConversions(s *runtime.Scheme) error {
|
||||
if err := s.AddGeneratedConversionFunc((*AuthInfo)(nil), (*api.AuthInfo)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_AuthInfo_To_api_AuthInfo(a.(*AuthInfo), b.(*api.AuthInfo), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*api.AuthInfo)(nil), (*AuthInfo)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_api_AuthInfo_To_v1_AuthInfo(a.(*api.AuthInfo), b.(*AuthInfo), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*AuthProviderConfig)(nil), (*api.AuthProviderConfig)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_AuthProviderConfig_To_api_AuthProviderConfig(a.(*AuthProviderConfig), b.(*api.AuthProviderConfig), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*api.AuthProviderConfig)(nil), (*AuthProviderConfig)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_api_AuthProviderConfig_To_v1_AuthProviderConfig(a.(*api.AuthProviderConfig), b.(*AuthProviderConfig), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*Cluster)(nil), (*api.Cluster)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_Cluster_To_api_Cluster(a.(*Cluster), b.(*api.Cluster), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*api.Cluster)(nil), (*Cluster)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_api_Cluster_To_v1_Cluster(a.(*api.Cluster), b.(*Cluster), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*Config)(nil), (*api.Config)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_Config_To_api_Config(a.(*Config), b.(*api.Config), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*api.Config)(nil), (*Config)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_api_Config_To_v1_Config(a.(*api.Config), b.(*Config), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*Context)(nil), (*api.Context)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_Context_To_api_Context(a.(*Context), b.(*api.Context), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*api.Context)(nil), (*Context)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_api_Context_To_v1_Context(a.(*api.Context), b.(*Context), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*ExecConfig)(nil), (*api.ExecConfig)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_ExecConfig_To_api_ExecConfig(a.(*ExecConfig), b.(*api.ExecConfig), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*api.ExecConfig)(nil), (*ExecConfig)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_api_ExecConfig_To_v1_ExecConfig(a.(*api.ExecConfig), b.(*ExecConfig), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*ExecEnvVar)(nil), (*api.ExecEnvVar)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_ExecEnvVar_To_api_ExecEnvVar(a.(*ExecEnvVar), b.(*api.ExecEnvVar), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*api.ExecEnvVar)(nil), (*ExecEnvVar)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_api_ExecEnvVar_To_v1_ExecEnvVar(a.(*api.ExecEnvVar), b.(*ExecEnvVar), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*Preferences)(nil), (*api.Preferences)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_Preferences_To_api_Preferences(a.(*Preferences), b.(*api.Preferences), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*api.Preferences)(nil), (*Preferences)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_api_Preferences_To_v1_Preferences(a.(*api.Preferences), b.(*Preferences), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddConversionFunc((*map[string]*api.AuthInfo)(nil), (*[]NamedAuthInfo)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_Map_string_To_Pointer_api_AuthInfo_To_Slice_v1_NamedAuthInfo(a.(*map[string]*api.AuthInfo), b.(*[]NamedAuthInfo), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddConversionFunc((*map[string]*api.Cluster)(nil), (*[]NamedCluster)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_Map_string_To_Pointer_api_Cluster_To_Slice_v1_NamedCluster(a.(*map[string]*api.Cluster), b.(*[]NamedCluster), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddConversionFunc((*map[string]*api.Context)(nil), (*[]NamedContext)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_Map_string_To_Pointer_api_Context_To_Slice_v1_NamedContext(a.(*map[string]*api.Context), b.(*[]NamedContext), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddConversionFunc((*map[string]runtime.Object)(nil), (*[]NamedExtension)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_Map_string_To_runtime_Object_To_Slice_v1_NamedExtension(a.(*map[string]runtime.Object), b.(*[]NamedExtension), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddConversionFunc((*[]NamedAuthInfo)(nil), (*map[string]*api.AuthInfo)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_Slice_v1_NamedAuthInfo_To_Map_string_To_Pointer_api_AuthInfo(a.(*[]NamedAuthInfo), b.(*map[string]*api.AuthInfo), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddConversionFunc((*[]NamedCluster)(nil), (*map[string]*api.Cluster)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_Slice_v1_NamedCluster_To_Map_string_To_Pointer_api_Cluster(a.(*[]NamedCluster), b.(*map[string]*api.Cluster), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddConversionFunc((*[]NamedContext)(nil), (*map[string]*api.Context)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_Slice_v1_NamedContext_To_Map_string_To_Pointer_api_Context(a.(*[]NamedContext), b.(*map[string]*api.Context), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddConversionFunc((*[]NamedExtension)(nil), (*map[string]runtime.Object)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_Slice_v1_NamedExtension_To_Map_string_To_runtime_Object(a.(*[]NamedExtension), b.(*map[string]runtime.Object), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func autoConvert_v1_AuthInfo_To_api_AuthInfo(in *AuthInfo, out *api.AuthInfo, s conversion.Scope) error {
|
||||
out.ClientCertificate = in.ClientCertificate
|
||||
out.ClientCertificateData = *(*[]byte)(unsafe.Pointer(&in.ClientCertificateData))
|
||||
out.ClientKey = in.ClientKey
|
||||
out.ClientKeyData = *(*[]byte)(unsafe.Pointer(&in.ClientKeyData))
|
||||
out.Token = in.Token
|
||||
out.TokenFile = in.TokenFile
|
||||
out.Impersonate = in.Impersonate
|
||||
out.ImpersonateUID = in.ImpersonateUID
|
||||
out.ImpersonateGroups = *(*[]string)(unsafe.Pointer(&in.ImpersonateGroups))
|
||||
out.ImpersonateUserExtra = *(*map[string][]string)(unsafe.Pointer(&in.ImpersonateUserExtra))
|
||||
out.Username = in.Username
|
||||
out.Password = in.Password
|
||||
out.AuthProvider = (*api.AuthProviderConfig)(unsafe.Pointer(in.AuthProvider))
|
||||
if in.Exec != nil {
|
||||
in, out := &in.Exec, &out.Exec
|
||||
*out = new(api.ExecConfig)
|
||||
if err := Convert_v1_ExecConfig_To_api_ExecConfig(*in, *out, s); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
out.Exec = nil
|
||||
}
|
||||
if err := Convert_Slice_v1_NamedExtension_To_Map_string_To_runtime_Object(&in.Extensions, &out.Extensions, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_AuthInfo_To_api_AuthInfo is an autogenerated conversion function.
|
||||
func Convert_v1_AuthInfo_To_api_AuthInfo(in *AuthInfo, out *api.AuthInfo, s conversion.Scope) error {
|
||||
return autoConvert_v1_AuthInfo_To_api_AuthInfo(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_api_AuthInfo_To_v1_AuthInfo(in *api.AuthInfo, out *AuthInfo, s conversion.Scope) error {
|
||||
// INFO: in.LocationOfOrigin opted out of conversion generation
|
||||
out.ClientCertificate = in.ClientCertificate
|
||||
out.ClientCertificateData = *(*[]byte)(unsafe.Pointer(&in.ClientCertificateData))
|
||||
out.ClientKey = in.ClientKey
|
||||
out.ClientKeyData = *(*[]byte)(unsafe.Pointer(&in.ClientKeyData))
|
||||
out.Token = in.Token
|
||||
out.TokenFile = in.TokenFile
|
||||
out.Impersonate = in.Impersonate
|
||||
out.ImpersonateUID = in.ImpersonateUID
|
||||
out.ImpersonateGroups = *(*[]string)(unsafe.Pointer(&in.ImpersonateGroups))
|
||||
out.ImpersonateUserExtra = *(*map[string][]string)(unsafe.Pointer(&in.ImpersonateUserExtra))
|
||||
out.Username = in.Username
|
||||
out.Password = in.Password
|
||||
out.AuthProvider = (*AuthProviderConfig)(unsafe.Pointer(in.AuthProvider))
|
||||
if in.Exec != nil {
|
||||
in, out := &in.Exec, &out.Exec
|
||||
*out = new(ExecConfig)
|
||||
if err := Convert_api_ExecConfig_To_v1_ExecConfig(*in, *out, s); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
out.Exec = nil
|
||||
}
|
||||
if err := Convert_Map_string_To_runtime_Object_To_Slice_v1_NamedExtension(&in.Extensions, &out.Extensions, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_api_AuthInfo_To_v1_AuthInfo is an autogenerated conversion function.
|
||||
func Convert_api_AuthInfo_To_v1_AuthInfo(in *api.AuthInfo, out *AuthInfo, s conversion.Scope) error {
|
||||
return autoConvert_api_AuthInfo_To_v1_AuthInfo(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_AuthProviderConfig_To_api_AuthProviderConfig(in *AuthProviderConfig, out *api.AuthProviderConfig, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Config = *(*map[string]string)(unsafe.Pointer(&in.Config))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_AuthProviderConfig_To_api_AuthProviderConfig is an autogenerated conversion function.
|
||||
func Convert_v1_AuthProviderConfig_To_api_AuthProviderConfig(in *AuthProviderConfig, out *api.AuthProviderConfig, s conversion.Scope) error {
|
||||
return autoConvert_v1_AuthProviderConfig_To_api_AuthProviderConfig(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_api_AuthProviderConfig_To_v1_AuthProviderConfig(in *api.AuthProviderConfig, out *AuthProviderConfig, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Config = *(*map[string]string)(unsafe.Pointer(&in.Config))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_api_AuthProviderConfig_To_v1_AuthProviderConfig is an autogenerated conversion function.
|
||||
func Convert_api_AuthProviderConfig_To_v1_AuthProviderConfig(in *api.AuthProviderConfig, out *AuthProviderConfig, s conversion.Scope) error {
|
||||
return autoConvert_api_AuthProviderConfig_To_v1_AuthProviderConfig(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_Cluster_To_api_Cluster(in *Cluster, out *api.Cluster, s conversion.Scope) error {
|
||||
out.Server = in.Server
|
||||
out.TLSServerName = in.TLSServerName
|
||||
out.InsecureSkipTLSVerify = in.InsecureSkipTLSVerify
|
||||
out.CertificateAuthority = in.CertificateAuthority
|
||||
out.CertificateAuthorityData = *(*[]byte)(unsafe.Pointer(&in.CertificateAuthorityData))
|
||||
out.ProxyURL = in.ProxyURL
|
||||
out.DisableCompression = in.DisableCompression
|
||||
if err := Convert_Slice_v1_NamedExtension_To_Map_string_To_runtime_Object(&in.Extensions, &out.Extensions, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_Cluster_To_api_Cluster is an autogenerated conversion function.
|
||||
func Convert_v1_Cluster_To_api_Cluster(in *Cluster, out *api.Cluster, s conversion.Scope) error {
|
||||
return autoConvert_v1_Cluster_To_api_Cluster(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_api_Cluster_To_v1_Cluster(in *api.Cluster, out *Cluster, s conversion.Scope) error {
|
||||
// INFO: in.LocationOfOrigin opted out of conversion generation
|
||||
out.Server = in.Server
|
||||
out.TLSServerName = in.TLSServerName
|
||||
out.InsecureSkipTLSVerify = in.InsecureSkipTLSVerify
|
||||
out.CertificateAuthority = in.CertificateAuthority
|
||||
out.CertificateAuthorityData = *(*[]byte)(unsafe.Pointer(&in.CertificateAuthorityData))
|
||||
out.ProxyURL = in.ProxyURL
|
||||
out.DisableCompression = in.DisableCompression
|
||||
if err := Convert_Map_string_To_runtime_Object_To_Slice_v1_NamedExtension(&in.Extensions, &out.Extensions, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_api_Cluster_To_v1_Cluster is an autogenerated conversion function.
|
||||
func Convert_api_Cluster_To_v1_Cluster(in *api.Cluster, out *Cluster, s conversion.Scope) error {
|
||||
return autoConvert_api_Cluster_To_v1_Cluster(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_Config_To_api_Config(in *Config, out *api.Config, s conversion.Scope) error {
|
||||
// INFO: in.Kind opted out of conversion generation
|
||||
// INFO: in.APIVersion opted out of conversion generation
|
||||
if err := Convert_v1_Preferences_To_api_Preferences(&in.Preferences, &out.Preferences, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Convert_Slice_v1_NamedCluster_To_Map_string_To_Pointer_api_Cluster(&in.Clusters, &out.Clusters, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Convert_Slice_v1_NamedAuthInfo_To_Map_string_To_Pointer_api_AuthInfo(&in.AuthInfos, &out.AuthInfos, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Convert_Slice_v1_NamedContext_To_Map_string_To_Pointer_api_Context(&in.Contexts, &out.Contexts, s); err != nil {
|
||||
return err
|
||||
}
|
||||
out.CurrentContext = in.CurrentContext
|
||||
if err := Convert_Slice_v1_NamedExtension_To_Map_string_To_runtime_Object(&in.Extensions, &out.Extensions, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_Config_To_api_Config is an autogenerated conversion function.
|
||||
func Convert_v1_Config_To_api_Config(in *Config, out *api.Config, s conversion.Scope) error {
|
||||
return autoConvert_v1_Config_To_api_Config(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_api_Config_To_v1_Config(in *api.Config, out *Config, s conversion.Scope) error {
|
||||
// INFO: in.Kind opted out of conversion generation
|
||||
// INFO: in.APIVersion opted out of conversion generation
|
||||
if err := Convert_api_Preferences_To_v1_Preferences(&in.Preferences, &out.Preferences, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Convert_Map_string_To_Pointer_api_Cluster_To_Slice_v1_NamedCluster(&in.Clusters, &out.Clusters, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Convert_Map_string_To_Pointer_api_AuthInfo_To_Slice_v1_NamedAuthInfo(&in.AuthInfos, &out.AuthInfos, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Convert_Map_string_To_Pointer_api_Context_To_Slice_v1_NamedContext(&in.Contexts, &out.Contexts, s); err != nil {
|
||||
return err
|
||||
}
|
||||
out.CurrentContext = in.CurrentContext
|
||||
if err := Convert_Map_string_To_runtime_Object_To_Slice_v1_NamedExtension(&in.Extensions, &out.Extensions, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_api_Config_To_v1_Config is an autogenerated conversion function.
|
||||
func Convert_api_Config_To_v1_Config(in *api.Config, out *Config, s conversion.Scope) error {
|
||||
return autoConvert_api_Config_To_v1_Config(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_Context_To_api_Context(in *Context, out *api.Context, s conversion.Scope) error {
|
||||
out.Cluster = in.Cluster
|
||||
out.AuthInfo = in.AuthInfo
|
||||
out.Namespace = in.Namespace
|
||||
if err := Convert_Slice_v1_NamedExtension_To_Map_string_To_runtime_Object(&in.Extensions, &out.Extensions, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_Context_To_api_Context is an autogenerated conversion function.
|
||||
func Convert_v1_Context_To_api_Context(in *Context, out *api.Context, s conversion.Scope) error {
|
||||
return autoConvert_v1_Context_To_api_Context(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_api_Context_To_v1_Context(in *api.Context, out *Context, s conversion.Scope) error {
|
||||
// INFO: in.LocationOfOrigin opted out of conversion generation
|
||||
out.Cluster = in.Cluster
|
||||
out.AuthInfo = in.AuthInfo
|
||||
out.Namespace = in.Namespace
|
||||
if err := Convert_Map_string_To_runtime_Object_To_Slice_v1_NamedExtension(&in.Extensions, &out.Extensions, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_api_Context_To_v1_Context is an autogenerated conversion function.
|
||||
func Convert_api_Context_To_v1_Context(in *api.Context, out *Context, s conversion.Scope) error {
|
||||
return autoConvert_api_Context_To_v1_Context(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_ExecConfig_To_api_ExecConfig(in *ExecConfig, out *api.ExecConfig, s conversion.Scope) error {
|
||||
out.Command = in.Command
|
||||
out.Args = *(*[]string)(unsafe.Pointer(&in.Args))
|
||||
out.Env = *(*[]api.ExecEnvVar)(unsafe.Pointer(&in.Env))
|
||||
out.APIVersion = in.APIVersion
|
||||
out.InstallHint = in.InstallHint
|
||||
out.ProvideClusterInfo = in.ProvideClusterInfo
|
||||
out.InteractiveMode = api.ExecInteractiveMode(in.InteractiveMode)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_ExecConfig_To_api_ExecConfig is an autogenerated conversion function.
|
||||
func Convert_v1_ExecConfig_To_api_ExecConfig(in *ExecConfig, out *api.ExecConfig, s conversion.Scope) error {
|
||||
return autoConvert_v1_ExecConfig_To_api_ExecConfig(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_api_ExecConfig_To_v1_ExecConfig(in *api.ExecConfig, out *ExecConfig, s conversion.Scope) error {
|
||||
out.Command = in.Command
|
||||
out.Args = *(*[]string)(unsafe.Pointer(&in.Args))
|
||||
out.Env = *(*[]ExecEnvVar)(unsafe.Pointer(&in.Env))
|
||||
out.APIVersion = in.APIVersion
|
||||
out.InstallHint = in.InstallHint
|
||||
out.ProvideClusterInfo = in.ProvideClusterInfo
|
||||
// INFO: in.Config opted out of conversion generation
|
||||
out.InteractiveMode = ExecInteractiveMode(in.InteractiveMode)
|
||||
// INFO: in.StdinUnavailable opted out of conversion generation
|
||||
// INFO: in.StdinUnavailableMessage opted out of conversion generation
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_api_ExecConfig_To_v1_ExecConfig is an autogenerated conversion function.
|
||||
func Convert_api_ExecConfig_To_v1_ExecConfig(in *api.ExecConfig, out *ExecConfig, s conversion.Scope) error {
|
||||
return autoConvert_api_ExecConfig_To_v1_ExecConfig(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_ExecEnvVar_To_api_ExecEnvVar(in *ExecEnvVar, out *api.ExecEnvVar, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Value = in.Value
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_ExecEnvVar_To_api_ExecEnvVar is an autogenerated conversion function.
|
||||
func Convert_v1_ExecEnvVar_To_api_ExecEnvVar(in *ExecEnvVar, out *api.ExecEnvVar, s conversion.Scope) error {
|
||||
return autoConvert_v1_ExecEnvVar_To_api_ExecEnvVar(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_api_ExecEnvVar_To_v1_ExecEnvVar(in *api.ExecEnvVar, out *ExecEnvVar, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Value = in.Value
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_api_ExecEnvVar_To_v1_ExecEnvVar is an autogenerated conversion function.
|
||||
func Convert_api_ExecEnvVar_To_v1_ExecEnvVar(in *api.ExecEnvVar, out *ExecEnvVar, s conversion.Scope) error {
|
||||
return autoConvert_api_ExecEnvVar_To_v1_ExecEnvVar(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_Preferences_To_api_Preferences(in *Preferences, out *api.Preferences, s conversion.Scope) error {
|
||||
out.Colors = in.Colors
|
||||
if err := Convert_Slice_v1_NamedExtension_To_Map_string_To_runtime_Object(&in.Extensions, &out.Extensions, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_Preferences_To_api_Preferences is an autogenerated conversion function.
|
||||
func Convert_v1_Preferences_To_api_Preferences(in *Preferences, out *api.Preferences, s conversion.Scope) error {
|
||||
return autoConvert_v1_Preferences_To_api_Preferences(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_api_Preferences_To_v1_Preferences(in *api.Preferences, out *Preferences, s conversion.Scope) error {
|
||||
out.Colors = in.Colors
|
||||
if err := Convert_Map_string_To_runtime_Object_To_Slice_v1_NamedExtension(&in.Extensions, &out.Extensions, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_api_Preferences_To_v1_Preferences is an autogenerated conversion function.
|
||||
func Convert_api_Preferences_To_v1_Preferences(in *api.Preferences, out *Preferences, s conversion.Scope) error {
|
||||
return autoConvert_api_Preferences_To_v1_Preferences(in, out, s)
|
||||
}
|
349
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/v1/zz_generated.deepcopy.go
generated
vendored
Normal file
349
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/v1/zz_generated.deepcopy.go
generated
vendored
Normal file
@ -0,0 +1,349 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
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 deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AuthInfo) DeepCopyInto(out *AuthInfo) {
|
||||
*out = *in
|
||||
if in.ClientCertificateData != nil {
|
||||
in, out := &in.ClientCertificateData, &out.ClientCertificateData
|
||||
*out = make([]byte, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ClientKeyData != nil {
|
||||
in, out := &in.ClientKeyData, &out.ClientKeyData
|
||||
*out = make([]byte, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ImpersonateGroups != nil {
|
||||
in, out := &in.ImpersonateGroups, &out.ImpersonateGroups
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ImpersonateUserExtra != nil {
|
||||
in, out := &in.ImpersonateUserExtra, &out.ImpersonateUserExtra
|
||||
*out = make(map[string][]string, len(*in))
|
||||
for key, val := range *in {
|
||||
var outVal []string
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
in, out := &val, &outVal
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
(*out)[key] = outVal
|
||||
}
|
||||
}
|
||||
if in.AuthProvider != nil {
|
||||
in, out := &in.AuthProvider, &out.AuthProvider
|
||||
*out = new(AuthProviderConfig)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Exec != nil {
|
||||
in, out := &in.Exec, &out.Exec
|
||||
*out = new(ExecConfig)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Extensions != nil {
|
||||
in, out := &in.Extensions, &out.Extensions
|
||||
*out = make([]NamedExtension, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthInfo.
|
||||
func (in *AuthInfo) DeepCopy() *AuthInfo {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AuthInfo)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AuthProviderConfig) DeepCopyInto(out *AuthProviderConfig) {
|
||||
*out = *in
|
||||
if in.Config != nil {
|
||||
in, out := &in.Config, &out.Config
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthProviderConfig.
|
||||
func (in *AuthProviderConfig) DeepCopy() *AuthProviderConfig {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AuthProviderConfig)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Cluster) DeepCopyInto(out *Cluster) {
|
||||
*out = *in
|
||||
if in.CertificateAuthorityData != nil {
|
||||
in, out := &in.CertificateAuthorityData, &out.CertificateAuthorityData
|
||||
*out = make([]byte, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Extensions != nil {
|
||||
in, out := &in.Extensions, &out.Extensions
|
||||
*out = make([]NamedExtension, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Cluster.
|
||||
func (in *Cluster) DeepCopy() *Cluster {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Cluster)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Config) DeepCopyInto(out *Config) {
|
||||
*out = *in
|
||||
in.Preferences.DeepCopyInto(&out.Preferences)
|
||||
if in.Clusters != nil {
|
||||
in, out := &in.Clusters, &out.Clusters
|
||||
*out = make([]NamedCluster, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.AuthInfos != nil {
|
||||
in, out := &in.AuthInfos, &out.AuthInfos
|
||||
*out = make([]NamedAuthInfo, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Contexts != nil {
|
||||
in, out := &in.Contexts, &out.Contexts
|
||||
*out = make([]NamedContext, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Extensions != nil {
|
||||
in, out := &in.Extensions, &out.Extensions
|
||||
*out = make([]NamedExtension, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Config.
|
||||
func (in *Config) DeepCopy() *Config {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Config)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Config) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Context) DeepCopyInto(out *Context) {
|
||||
*out = *in
|
||||
if in.Extensions != nil {
|
||||
in, out := &in.Extensions, &out.Extensions
|
||||
*out = make([]NamedExtension, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Context.
|
||||
func (in *Context) DeepCopy() *Context {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Context)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ExecConfig) DeepCopyInto(out *ExecConfig) {
|
||||
*out = *in
|
||||
if in.Args != nil {
|
||||
in, out := &in.Args, &out.Args
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Env != nil {
|
||||
in, out := &in.Env, &out.Env
|
||||
*out = make([]ExecEnvVar, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExecConfig.
|
||||
func (in *ExecConfig) DeepCopy() *ExecConfig {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ExecConfig)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ExecEnvVar) DeepCopyInto(out *ExecEnvVar) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExecEnvVar.
|
||||
func (in *ExecEnvVar) DeepCopy() *ExecEnvVar {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ExecEnvVar)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NamedAuthInfo) DeepCopyInto(out *NamedAuthInfo) {
|
||||
*out = *in
|
||||
in.AuthInfo.DeepCopyInto(&out.AuthInfo)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamedAuthInfo.
|
||||
func (in *NamedAuthInfo) DeepCopy() *NamedAuthInfo {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NamedAuthInfo)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NamedCluster) DeepCopyInto(out *NamedCluster) {
|
||||
*out = *in
|
||||
in.Cluster.DeepCopyInto(&out.Cluster)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamedCluster.
|
||||
func (in *NamedCluster) DeepCopy() *NamedCluster {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NamedCluster)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NamedContext) DeepCopyInto(out *NamedContext) {
|
||||
*out = *in
|
||||
in.Context.DeepCopyInto(&out.Context)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamedContext.
|
||||
func (in *NamedContext) DeepCopy() *NamedContext {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NamedContext)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NamedExtension) DeepCopyInto(out *NamedExtension) {
|
||||
*out = *in
|
||||
in.Extension.DeepCopyInto(&out.Extension)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamedExtension.
|
||||
func (in *NamedExtension) DeepCopy() *NamedExtension {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NamedExtension)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Preferences) DeepCopyInto(out *Preferences) {
|
||||
*out = *in
|
||||
if in.Extensions != nil {
|
||||
in, out := &in.Extensions, &out.Extensions
|
||||
*out = make([]NamedExtension, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Preferences.
|
||||
func (in *Preferences) DeepCopy() *Preferences {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Preferences)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
43
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/v1/zz_generated.defaults.go
generated
vendored
Normal file
43
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/v1/zz_generated.defaults.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
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 defaulter-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// RegisterDefaults adds defaulters functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
// All generated defaulters are covering - they call all nested defaulters.
|
||||
func RegisterDefaults(scheme *runtime.Scheme) error {
|
||||
scheme.AddTypeDefaultingFunc(&Config{}, func(obj interface{}) { SetObjectDefaults_Config(obj.(*Config)) })
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetObjectDefaults_Config(in *Config) {
|
||||
for i := range in.AuthInfos {
|
||||
a := &in.AuthInfos[i]
|
||||
if a.AuthInfo.Exec != nil {
|
||||
SetDefaults_ExecConfig(a.AuthInfo.Exec)
|
||||
}
|
||||
}
|
||||
}
|
328
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/zz_generated.deepcopy.go
generated
vendored
Normal file
328
e2e/vendor/k8s.io/client-go/tools/clientcmd/api/zz_generated.deepcopy.go
generated
vendored
Normal file
@ -0,0 +1,328 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
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 deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AuthInfo) DeepCopyInto(out *AuthInfo) {
|
||||
*out = *in
|
||||
if in.ClientCertificateData != nil {
|
||||
in, out := &in.ClientCertificateData, &out.ClientCertificateData
|
||||
*out = make([]byte, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ClientKeyData != nil {
|
||||
in, out := &in.ClientKeyData, &out.ClientKeyData
|
||||
*out = make([]byte, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ImpersonateGroups != nil {
|
||||
in, out := &in.ImpersonateGroups, &out.ImpersonateGroups
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ImpersonateUserExtra != nil {
|
||||
in, out := &in.ImpersonateUserExtra, &out.ImpersonateUserExtra
|
||||
*out = make(map[string][]string, len(*in))
|
||||
for key, val := range *in {
|
||||
var outVal []string
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
in, out := &val, &outVal
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
(*out)[key] = outVal
|
||||
}
|
||||
}
|
||||
if in.AuthProvider != nil {
|
||||
in, out := &in.AuthProvider, &out.AuthProvider
|
||||
*out = new(AuthProviderConfig)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Exec != nil {
|
||||
in, out := &in.Exec, &out.Exec
|
||||
*out = new(ExecConfig)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Extensions != nil {
|
||||
in, out := &in.Extensions, &out.Extensions
|
||||
*out = make(map[string]runtime.Object, len(*in))
|
||||
for key, val := range *in {
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
(*out)[key] = val.DeepCopyObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthInfo.
|
||||
func (in *AuthInfo) DeepCopy() *AuthInfo {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AuthInfo)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AuthProviderConfig) DeepCopyInto(out *AuthProviderConfig) {
|
||||
*out = *in
|
||||
if in.Config != nil {
|
||||
in, out := &in.Config, &out.Config
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthProviderConfig.
|
||||
func (in *AuthProviderConfig) DeepCopy() *AuthProviderConfig {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AuthProviderConfig)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Cluster) DeepCopyInto(out *Cluster) {
|
||||
*out = *in
|
||||
if in.CertificateAuthorityData != nil {
|
||||
in, out := &in.CertificateAuthorityData, &out.CertificateAuthorityData
|
||||
*out = make([]byte, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Extensions != nil {
|
||||
in, out := &in.Extensions, &out.Extensions
|
||||
*out = make(map[string]runtime.Object, len(*in))
|
||||
for key, val := range *in {
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
(*out)[key] = val.DeepCopyObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Cluster.
|
||||
func (in *Cluster) DeepCopy() *Cluster {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Cluster)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Config) DeepCopyInto(out *Config) {
|
||||
*out = *in
|
||||
in.Preferences.DeepCopyInto(&out.Preferences)
|
||||
if in.Clusters != nil {
|
||||
in, out := &in.Clusters, &out.Clusters
|
||||
*out = make(map[string]*Cluster, len(*in))
|
||||
for key, val := range *in {
|
||||
var outVal *Cluster
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
in, out := &val, &outVal
|
||||
*out = new(Cluster)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
(*out)[key] = outVal
|
||||
}
|
||||
}
|
||||
if in.AuthInfos != nil {
|
||||
in, out := &in.AuthInfos, &out.AuthInfos
|
||||
*out = make(map[string]*AuthInfo, len(*in))
|
||||
for key, val := range *in {
|
||||
var outVal *AuthInfo
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
in, out := &val, &outVal
|
||||
*out = new(AuthInfo)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
(*out)[key] = outVal
|
||||
}
|
||||
}
|
||||
if in.Contexts != nil {
|
||||
in, out := &in.Contexts, &out.Contexts
|
||||
*out = make(map[string]*Context, len(*in))
|
||||
for key, val := range *in {
|
||||
var outVal *Context
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
in, out := &val, &outVal
|
||||
*out = new(Context)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
(*out)[key] = outVal
|
||||
}
|
||||
}
|
||||
if in.Extensions != nil {
|
||||
in, out := &in.Extensions, &out.Extensions
|
||||
*out = make(map[string]runtime.Object, len(*in))
|
||||
for key, val := range *in {
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
(*out)[key] = val.DeepCopyObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Config.
|
||||
func (in *Config) DeepCopy() *Config {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Config)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Config) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Context) DeepCopyInto(out *Context) {
|
||||
*out = *in
|
||||
if in.Extensions != nil {
|
||||
in, out := &in.Extensions, &out.Extensions
|
||||
*out = make(map[string]runtime.Object, len(*in))
|
||||
for key, val := range *in {
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
(*out)[key] = val.DeepCopyObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Context.
|
||||
func (in *Context) DeepCopy() *Context {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Context)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ExecConfig) DeepCopyInto(out *ExecConfig) {
|
||||
*out = *in
|
||||
if in.Args != nil {
|
||||
in, out := &in.Args, &out.Args
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Env != nil {
|
||||
in, out := &in.Env, &out.Env
|
||||
*out = make([]ExecEnvVar, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Config != nil {
|
||||
out.Config = in.Config.DeepCopyObject()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExecConfig.
|
||||
func (in *ExecConfig) DeepCopy() *ExecConfig {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ExecConfig)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ExecEnvVar) DeepCopyInto(out *ExecEnvVar) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExecEnvVar.
|
||||
func (in *ExecEnvVar) DeepCopy() *ExecEnvVar {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ExecEnvVar)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Preferences) DeepCopyInto(out *Preferences) {
|
||||
*out = *in
|
||||
if in.Extensions != nil {
|
||||
in, out := &in.Extensions, &out.Extensions
|
||||
*out = make(map[string]runtime.Object, len(*in))
|
||||
for key, val := range *in {
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
(*out)[key] = val.DeepCopyObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Preferences.
|
||||
func (in *Preferences) DeepCopy() *Preferences {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Preferences)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
110
e2e/vendor/k8s.io/client-go/tools/clientcmd/auth_loaders.go
generated
vendored
Normal file
110
e2e/vendor/k8s.io/client-go/tools/clientcmd/auth_loaders.go
generated
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
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 clientcmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"golang.org/x/term"
|
||||
|
||||
clientauth "k8s.io/client-go/tools/auth"
|
||||
)
|
||||
|
||||
// AuthLoaders are used to build clientauth.Info objects.
|
||||
type AuthLoader interface {
|
||||
// LoadAuth takes a path to a config file and can then do anything it needs in order to return a valid clientauth.Info
|
||||
LoadAuth(path string) (*clientauth.Info, error)
|
||||
}
|
||||
|
||||
// default implementation of an AuthLoader
|
||||
type defaultAuthLoader struct{}
|
||||
|
||||
// LoadAuth for defaultAuthLoader simply delegates to clientauth.LoadFromFile
|
||||
func (*defaultAuthLoader) LoadAuth(path string) (*clientauth.Info, error) {
|
||||
return clientauth.LoadFromFile(path)
|
||||
}
|
||||
|
||||
type PromptingAuthLoader struct {
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
// LoadAuth parses an AuthInfo object from a file path. It prompts user and creates file if it doesn't exist.
|
||||
func (a *PromptingAuthLoader) LoadAuth(path string) (*clientauth.Info, error) {
|
||||
// Prompt for user/pass and write a file if none exists.
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
authPtr, err := a.Prompt()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
auth := *authPtr
|
||||
data, err := json.Marshal(auth)
|
||||
if err != nil {
|
||||
return &auth, err
|
||||
}
|
||||
err = os.WriteFile(path, data, 0600)
|
||||
return &auth, err
|
||||
}
|
||||
authPtr, err := clientauth.LoadFromFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return authPtr, nil
|
||||
}
|
||||
|
||||
// Prompt pulls the user and password from a reader
|
||||
func (a *PromptingAuthLoader) Prompt() (*clientauth.Info, error) {
|
||||
var err error
|
||||
auth := &clientauth.Info{}
|
||||
auth.User, err = promptForString("Username", a.reader, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
auth.Password, err = promptForString("Password", nil, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
func promptForString(field string, r io.Reader, show bool) (result string, err error) {
|
||||
fmt.Printf("Please enter %s: ", field)
|
||||
if show {
|
||||
_, err = fmt.Fscan(r, &result)
|
||||
} else {
|
||||
var data []byte
|
||||
if term.IsTerminal(int(os.Stdin.Fd())) {
|
||||
data, err = term.ReadPassword(int(os.Stdin.Fd()))
|
||||
result = string(data)
|
||||
} else {
|
||||
return "", fmt.Errorf("error reading input for %s", field)
|
||||
}
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
// NewPromptingAuthLoader is an AuthLoader that parses an AuthInfo object from a file path. It prompts user and creates file if it doesn't exist.
|
||||
func NewPromptingAuthLoader(reader io.Reader) *PromptingAuthLoader {
|
||||
return &PromptingAuthLoader{reader}
|
||||
}
|
||||
|
||||
// NewDefaultAuthLoader returns a default implementation of an AuthLoader that only reads from a config file
|
||||
func NewDefaultAuthLoader() AuthLoader {
|
||||
return &defaultAuthLoader{}
|
||||
}
|
687
e2e/vendor/k8s.io/client-go/tools/clientcmd/client_config.go
generated
vendored
Normal file
687
e2e/vendor/k8s.io/client-go/tools/clientcmd/client_config.go
generated
vendored
Normal file
@ -0,0 +1,687 @@
|
||||
/*
|
||||
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 clientcmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
restclient "k8s.io/client-go/rest"
|
||||
clientauth "k8s.io/client-go/tools/auth"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// clusterExtensionKey is reserved in the cluster extensions list for exec plugin config.
|
||||
clusterExtensionKey = "client.authentication.k8s.io/exec"
|
||||
)
|
||||
|
||||
var (
|
||||
// ClusterDefaults has the same behavior as the old EnvVar and DefaultCluster fields
|
||||
// DEPRECATED will be replaced
|
||||
ClusterDefaults = clientcmdapi.Cluster{Server: getDefaultServer()}
|
||||
// DefaultClientConfig represents the legacy behavior of this package for defaulting
|
||||
// DEPRECATED will be replace
|
||||
DefaultClientConfig = DirectClientConfig{*clientcmdapi.NewConfig(), "", &ConfigOverrides{
|
||||
ClusterDefaults: ClusterDefaults,
|
||||
}, nil, NewDefaultClientConfigLoadingRules(), promptedCredentials{}}
|
||||
)
|
||||
|
||||
// getDefaultServer returns a default setting for DefaultClientConfig
|
||||
// DEPRECATED
|
||||
func getDefaultServer() string {
|
||||
if server := os.Getenv("KUBERNETES_MASTER"); len(server) > 0 {
|
||||
return server
|
||||
}
|
||||
return "http://localhost:8080"
|
||||
}
|
||||
|
||||
// ClientConfig is used to make it easy to get an api server client
|
||||
type ClientConfig interface {
|
||||
// RawConfig returns the merged result of all overrides
|
||||
RawConfig() (clientcmdapi.Config, error)
|
||||
// ClientConfig returns a complete client config
|
||||
ClientConfig() (*restclient.Config, error)
|
||||
// Namespace returns the namespace resulting from the merged
|
||||
// result of all overrides and a boolean indicating if it was
|
||||
// overridden
|
||||
Namespace() (string, bool, error)
|
||||
// ConfigAccess returns the rules for loading/persisting the config.
|
||||
ConfigAccess() ConfigAccess
|
||||
}
|
||||
|
||||
// OverridingClientConfig is used to enable overrriding the raw KubeConfig
|
||||
type OverridingClientConfig interface {
|
||||
ClientConfig
|
||||
// MergedRawConfig return the RawConfig merged with all overrides.
|
||||
MergedRawConfig() (clientcmdapi.Config, error)
|
||||
}
|
||||
|
||||
type PersistAuthProviderConfigForUser func(user string) restclient.AuthProviderConfigPersister
|
||||
|
||||
type promptedCredentials struct {
|
||||
username string
|
||||
password string `datapolicy:"password"`
|
||||
}
|
||||
|
||||
// DirectClientConfig is a ClientConfig interface that is backed by a clientcmdapi.Config, options overrides, and an optional fallbackReader for auth information
|
||||
type DirectClientConfig struct {
|
||||
config clientcmdapi.Config
|
||||
contextName string
|
||||
overrides *ConfigOverrides
|
||||
fallbackReader io.Reader
|
||||
configAccess ConfigAccess
|
||||
// promptedCredentials store the credentials input by the user
|
||||
promptedCredentials promptedCredentials
|
||||
}
|
||||
|
||||
// NewDefaultClientConfig creates a DirectClientConfig using the config.CurrentContext as the context name
|
||||
func NewDefaultClientConfig(config clientcmdapi.Config, overrides *ConfigOverrides) OverridingClientConfig {
|
||||
return &DirectClientConfig{config, config.CurrentContext, overrides, nil, NewDefaultClientConfigLoadingRules(), promptedCredentials{}}
|
||||
}
|
||||
|
||||
// NewNonInteractiveClientConfig creates a DirectClientConfig using the passed context name and does not have a fallback reader for auth information
|
||||
func NewNonInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, configAccess ConfigAccess) OverridingClientConfig {
|
||||
return &DirectClientConfig{config, contextName, overrides, nil, configAccess, promptedCredentials{}}
|
||||
}
|
||||
|
||||
// NewInteractiveClientConfig creates a DirectClientConfig using the passed context name and a reader in case auth information is not provided via files or flags
|
||||
func NewInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, fallbackReader io.Reader, configAccess ConfigAccess) OverridingClientConfig {
|
||||
return &DirectClientConfig{config, contextName, overrides, fallbackReader, configAccess, promptedCredentials{}}
|
||||
}
|
||||
|
||||
// NewClientConfigFromBytes takes your kubeconfig and gives you back a ClientConfig
|
||||
func NewClientConfigFromBytes(configBytes []byte) (OverridingClientConfig, error) {
|
||||
config, err := Load(configBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &DirectClientConfig{*config, "", &ConfigOverrides{}, nil, nil, promptedCredentials{}}, nil
|
||||
}
|
||||
|
||||
// RESTConfigFromKubeConfig is a convenience method to give back a restconfig from your kubeconfig bytes.
|
||||
// For programmatic access, this is what you want 80% of the time
|
||||
func RESTConfigFromKubeConfig(configBytes []byte) (*restclient.Config, error) {
|
||||
clientConfig, err := NewClientConfigFromBytes(configBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return clientConfig.ClientConfig()
|
||||
}
|
||||
|
||||
func (config *DirectClientConfig) RawConfig() (clientcmdapi.Config, error) {
|
||||
return config.config, nil
|
||||
}
|
||||
|
||||
// MergedRawConfig returns the raw kube config merged with the overrides
|
||||
func (config *DirectClientConfig) MergedRawConfig() (clientcmdapi.Config, error) {
|
||||
if err := config.ConfirmUsable(); err != nil {
|
||||
return clientcmdapi.Config{}, err
|
||||
}
|
||||
merged := config.config.DeepCopy()
|
||||
|
||||
// set the AuthInfo merged with overrides in the merged config
|
||||
mergedAuthInfo, err := config.getAuthInfo()
|
||||
if err != nil {
|
||||
return clientcmdapi.Config{}, err
|
||||
}
|
||||
mergedAuthInfoName, _ := config.getAuthInfoName()
|
||||
merged.AuthInfos[mergedAuthInfoName] = &mergedAuthInfo
|
||||
|
||||
// set the Context merged with overrides in the merged config
|
||||
mergedContext, err := config.getContext()
|
||||
if err != nil {
|
||||
return clientcmdapi.Config{}, err
|
||||
}
|
||||
mergedContextName, _ := config.getContextName()
|
||||
merged.Contexts[mergedContextName] = &mergedContext
|
||||
merged.CurrentContext = mergedContextName
|
||||
|
||||
// set the Cluster merged with overrides in the merged config
|
||||
configClusterInfo, err := config.getCluster()
|
||||
if err != nil {
|
||||
return clientcmdapi.Config{}, err
|
||||
}
|
||||
configClusterName, _ := config.getClusterName()
|
||||
merged.Clusters[configClusterName] = &configClusterInfo
|
||||
return *merged, nil
|
||||
}
|
||||
|
||||
// ClientConfig implements ClientConfig
|
||||
func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
|
||||
// check that getAuthInfo, getContext, and getCluster do not return an error.
|
||||
// Do this before checking if the current config is usable in the event that an
|
||||
// AuthInfo, Context, or Cluster config with user-defined names are not found.
|
||||
// This provides a user with the immediate cause for error if one is found
|
||||
configAuthInfo, err := config.getAuthInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = config.getContext()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
configClusterInfo, err := config.getCluster()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := config.ConfirmUsable(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clientConfig := &restclient.Config{}
|
||||
clientConfig.Host = configClusterInfo.Server
|
||||
if configClusterInfo.ProxyURL != "" {
|
||||
u, err := parseProxyURL(configClusterInfo.ProxyURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientConfig.Proxy = http.ProxyURL(u)
|
||||
}
|
||||
|
||||
clientConfig.DisableCompression = configClusterInfo.DisableCompression
|
||||
|
||||
if config.overrides != nil && len(config.overrides.Timeout) > 0 {
|
||||
timeout, err := ParseTimeout(config.overrides.Timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientConfig.Timeout = timeout
|
||||
}
|
||||
|
||||
if u, err := url.ParseRequestURI(clientConfig.Host); err == nil && u.Opaque == "" && len(u.Path) > 1 {
|
||||
u.RawQuery = ""
|
||||
u.Fragment = ""
|
||||
clientConfig.Host = u.String()
|
||||
}
|
||||
if len(configAuthInfo.Impersonate) > 0 {
|
||||
clientConfig.Impersonate = restclient.ImpersonationConfig{
|
||||
UserName: configAuthInfo.Impersonate,
|
||||
UID: configAuthInfo.ImpersonateUID,
|
||||
Groups: configAuthInfo.ImpersonateGroups,
|
||||
Extra: configAuthInfo.ImpersonateUserExtra,
|
||||
}
|
||||
}
|
||||
|
||||
// only try to read the auth information if we are secure
|
||||
if restclient.IsConfigTransportTLS(*clientConfig) {
|
||||
var err error
|
||||
var persister restclient.AuthProviderConfigPersister
|
||||
if config.configAccess != nil {
|
||||
authInfoName, _ := config.getAuthInfoName()
|
||||
persister = PersisterForUser(config.configAccess, authInfoName)
|
||||
}
|
||||
userAuthPartialConfig, err := config.getUserIdentificationPartialConfig(configAuthInfo, config.fallbackReader, persister, configClusterInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := merge(clientConfig, userAuthPartialConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverAuthPartialConfig := getServerIdentificationPartialConfig(configClusterInfo)
|
||||
if err := merge(clientConfig, serverAuthPartialConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return clientConfig, nil
|
||||
}
|
||||
|
||||
// clientauth.Info object contain both user identification and server identification. We want different precedence orders for
|
||||
// both, so we have to split the objects and merge them separately.
|
||||
|
||||
// getServerIdentificationPartialConfig extracts server identification information from configClusterInfo
|
||||
// (the final result of command line flags and merged .kubeconfig files).
|
||||
func getServerIdentificationPartialConfig(configClusterInfo clientcmdapi.Cluster) *restclient.Config {
|
||||
configClientConfig := &restclient.Config{}
|
||||
configClientConfig.CAFile = configClusterInfo.CertificateAuthority
|
||||
configClientConfig.CAData = configClusterInfo.CertificateAuthorityData
|
||||
configClientConfig.Insecure = configClusterInfo.InsecureSkipTLSVerify
|
||||
configClientConfig.ServerName = configClusterInfo.TLSServerName
|
||||
|
||||
return configClientConfig
|
||||
}
|
||||
|
||||
// getUserIdentificationPartialConfig extracts user identification information from configAuthInfo
|
||||
// (the final result of command line flags and merged .kubeconfig files);
|
||||
// if the information available there is insufficient, it prompts (if possible) for additional information.
|
||||
func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fallbackReader io.Reader, persistAuthConfig restclient.AuthProviderConfigPersister, configClusterInfo clientcmdapi.Cluster) (*restclient.Config, error) {
|
||||
mergedConfig := &restclient.Config{}
|
||||
|
||||
// blindly overwrite existing values based on precedence
|
||||
if len(configAuthInfo.Token) > 0 {
|
||||
mergedConfig.BearerToken = configAuthInfo.Token
|
||||
mergedConfig.BearerTokenFile = configAuthInfo.TokenFile
|
||||
} else if len(configAuthInfo.TokenFile) > 0 {
|
||||
tokenBytes, err := os.ReadFile(configAuthInfo.TokenFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mergedConfig.BearerToken = string(tokenBytes)
|
||||
mergedConfig.BearerTokenFile = configAuthInfo.TokenFile
|
||||
}
|
||||
if len(configAuthInfo.Impersonate) > 0 {
|
||||
mergedConfig.Impersonate = restclient.ImpersonationConfig{
|
||||
UserName: configAuthInfo.Impersonate,
|
||||
UID: configAuthInfo.ImpersonateUID,
|
||||
Groups: configAuthInfo.ImpersonateGroups,
|
||||
Extra: configAuthInfo.ImpersonateUserExtra,
|
||||
}
|
||||
}
|
||||
if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 {
|
||||
mergedConfig.CertFile = configAuthInfo.ClientCertificate
|
||||
mergedConfig.CertData = configAuthInfo.ClientCertificateData
|
||||
mergedConfig.KeyFile = configAuthInfo.ClientKey
|
||||
mergedConfig.KeyData = configAuthInfo.ClientKeyData
|
||||
}
|
||||
if len(configAuthInfo.Username) > 0 || len(configAuthInfo.Password) > 0 {
|
||||
mergedConfig.Username = configAuthInfo.Username
|
||||
mergedConfig.Password = configAuthInfo.Password
|
||||
}
|
||||
if configAuthInfo.AuthProvider != nil {
|
||||
mergedConfig.AuthProvider = configAuthInfo.AuthProvider
|
||||
mergedConfig.AuthConfigPersister = persistAuthConfig
|
||||
}
|
||||
if configAuthInfo.Exec != nil {
|
||||
mergedConfig.ExecProvider = configAuthInfo.Exec
|
||||
mergedConfig.ExecProvider.InstallHint = cleanANSIEscapeCodes(mergedConfig.ExecProvider.InstallHint)
|
||||
mergedConfig.ExecProvider.Config = configClusterInfo.Extensions[clusterExtensionKey]
|
||||
}
|
||||
|
||||
// if there still isn't enough information to authenticate the user, try prompting
|
||||
if !canIdentifyUser(*mergedConfig) && (fallbackReader != nil) {
|
||||
if len(config.promptedCredentials.username) > 0 && len(config.promptedCredentials.password) > 0 {
|
||||
mergedConfig.Username = config.promptedCredentials.username
|
||||
mergedConfig.Password = config.promptedCredentials.password
|
||||
return mergedConfig, nil
|
||||
}
|
||||
prompter := NewPromptingAuthLoader(fallbackReader)
|
||||
promptedAuthInfo, err := prompter.Prompt()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
promptedConfig := makeUserIdentificationConfig(*promptedAuthInfo)
|
||||
previouslyMergedConfig := mergedConfig
|
||||
mergedConfig = &restclient.Config{}
|
||||
if err := merge(mergedConfig, promptedConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := merge(mergedConfig, previouslyMergedConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.promptedCredentials.username = mergedConfig.Username
|
||||
config.promptedCredentials.password = mergedConfig.Password
|
||||
}
|
||||
|
||||
return mergedConfig, nil
|
||||
}
|
||||
|
||||
// makeUserIdentificationFieldsConfig returns a client.Config capable of being merged for only user identification information
|
||||
func makeUserIdentificationConfig(info clientauth.Info) *restclient.Config {
|
||||
config := &restclient.Config{}
|
||||
config.Username = info.User
|
||||
config.Password = info.Password
|
||||
config.CertFile = info.CertFile
|
||||
config.KeyFile = info.KeyFile
|
||||
config.BearerToken = info.BearerToken
|
||||
return config
|
||||
}
|
||||
|
||||
func canIdentifyUser(config restclient.Config) bool {
|
||||
return len(config.Username) > 0 ||
|
||||
(len(config.CertFile) > 0 || len(config.CertData) > 0) ||
|
||||
len(config.BearerToken) > 0 ||
|
||||
config.AuthProvider != nil ||
|
||||
config.ExecProvider != nil
|
||||
}
|
||||
|
||||
// cleanANSIEscapeCodes takes an arbitrary string and ensures that there are no
|
||||
// ANSI escape sequences that could put the terminal in a weird state (e.g.,
|
||||
// "\e[1m" bolds text)
|
||||
func cleanANSIEscapeCodes(s string) string {
|
||||
// spaceControlCharacters includes tab, new line, vertical tab, new page, and
|
||||
// carriage return. These are in the unicode.Cc category, but that category also
|
||||
// contains ESC (U+001B) which we don't want.
|
||||
spaceControlCharacters := unicode.RangeTable{
|
||||
R16: []unicode.Range16{
|
||||
{Lo: 0x0009, Hi: 0x000D, Stride: 1},
|
||||
},
|
||||
}
|
||||
|
||||
// Why not make this deny-only (instead of allow-only)? Because unicode.C
|
||||
// contains newline and tab characters that we want.
|
||||
allowedRanges := []*unicode.RangeTable{
|
||||
unicode.L,
|
||||
unicode.M,
|
||||
unicode.N,
|
||||
unicode.P,
|
||||
unicode.S,
|
||||
unicode.Z,
|
||||
&spaceControlCharacters,
|
||||
}
|
||||
builder := strings.Builder{}
|
||||
for _, roon := range s {
|
||||
if unicode.IsOneOf(allowedRanges, roon) {
|
||||
builder.WriteRune(roon) // returns nil error, per go doc
|
||||
} else {
|
||||
fmt.Fprintf(&builder, "%U", roon)
|
||||
}
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// Namespace implements ClientConfig
|
||||
func (config *DirectClientConfig) Namespace() (string, bool, error) {
|
||||
if config.overrides != nil && config.overrides.Context.Namespace != "" {
|
||||
// In the event we have an empty config but we do have a namespace override, we should return
|
||||
// the namespace override instead of having config.ConfirmUsable() return an error. This allows
|
||||
// things like in-cluster clients to execute `kubectl get pods --namespace=foo` and have the
|
||||
// --namespace flag honored instead of being ignored.
|
||||
return config.overrides.Context.Namespace, true, nil
|
||||
}
|
||||
|
||||
if err := config.ConfirmUsable(); err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
configContext, err := config.getContext()
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
if len(configContext.Namespace) == 0 {
|
||||
return "default", false, nil
|
||||
}
|
||||
|
||||
return configContext.Namespace, false, nil
|
||||
}
|
||||
|
||||
// ConfigAccess implements ClientConfig
|
||||
func (config *DirectClientConfig) ConfigAccess() ConfigAccess {
|
||||
return config.configAccess
|
||||
}
|
||||
|
||||
// ConfirmUsable looks a particular context and determines if that particular part of the config is useable. There might still be errors in the config,
|
||||
// but no errors in the sections requested or referenced. It does not return early so that it can find as many errors as possible.
|
||||
func (config *DirectClientConfig) ConfirmUsable() error {
|
||||
validationErrors := make([]error, 0)
|
||||
|
||||
var contextName string
|
||||
if len(config.contextName) != 0 {
|
||||
contextName = config.contextName
|
||||
} else {
|
||||
contextName = config.config.CurrentContext
|
||||
}
|
||||
|
||||
if len(contextName) > 0 {
|
||||
_, exists := config.config.Contexts[contextName]
|
||||
if !exists {
|
||||
validationErrors = append(validationErrors, &errContextNotFound{contextName})
|
||||
}
|
||||
}
|
||||
|
||||
authInfoName, _ := config.getAuthInfoName()
|
||||
authInfo, _ := config.getAuthInfo()
|
||||
validationErrors = append(validationErrors, validateAuthInfo(authInfoName, authInfo)...)
|
||||
clusterName, _ := config.getClusterName()
|
||||
cluster, _ := config.getCluster()
|
||||
validationErrors = append(validationErrors, validateClusterInfo(clusterName, cluster)...)
|
||||
// when direct client config is specified, and our only error is that no server is defined, we should
|
||||
// return a standard "no config" error
|
||||
if len(validationErrors) == 1 && validationErrors[0] == ErrEmptyCluster {
|
||||
return newErrConfigurationInvalid([]error{ErrEmptyConfig})
|
||||
}
|
||||
return newErrConfigurationInvalid(validationErrors)
|
||||
}
|
||||
|
||||
// getContextName returns the default, or user-set context name, and a boolean that indicates
|
||||
// whether the default context name has been overwritten by a user-set flag, or left as its default value
|
||||
func (config *DirectClientConfig) getContextName() (string, bool) {
|
||||
if config.overrides != nil && len(config.overrides.CurrentContext) != 0 {
|
||||
return config.overrides.CurrentContext, true
|
||||
}
|
||||
if len(config.contextName) != 0 {
|
||||
return config.contextName, false
|
||||
}
|
||||
|
||||
return config.config.CurrentContext, false
|
||||
}
|
||||
|
||||
// getAuthInfoName returns a string containing the current authinfo name for the current context,
|
||||
// and a boolean indicating whether the default authInfo name is overwritten by a user-set flag, or
|
||||
// left as its default value
|
||||
func (config *DirectClientConfig) getAuthInfoName() (string, bool) {
|
||||
if config.overrides != nil && len(config.overrides.Context.AuthInfo) != 0 {
|
||||
return config.overrides.Context.AuthInfo, true
|
||||
}
|
||||
context, _ := config.getContext()
|
||||
return context.AuthInfo, false
|
||||
}
|
||||
|
||||
// getClusterName returns a string containing the default, or user-set cluster name, and a boolean
|
||||
// indicating whether the default clusterName has been overwritten by a user-set flag, or left as
|
||||
// its default value
|
||||
func (config *DirectClientConfig) getClusterName() (string, bool) {
|
||||
if config.overrides != nil && len(config.overrides.Context.Cluster) != 0 {
|
||||
return config.overrides.Context.Cluster, true
|
||||
}
|
||||
context, _ := config.getContext()
|
||||
return context.Cluster, false
|
||||
}
|
||||
|
||||
// getContext returns the clientcmdapi.Context, or an error if a required context is not found.
|
||||
func (config *DirectClientConfig) getContext() (clientcmdapi.Context, error) {
|
||||
contexts := config.config.Contexts
|
||||
contextName, required := config.getContextName()
|
||||
|
||||
mergedContext := clientcmdapi.NewContext()
|
||||
if configContext, exists := contexts[contextName]; exists {
|
||||
if err := merge(mergedContext, configContext); err != nil {
|
||||
return clientcmdapi.Context{}, err
|
||||
}
|
||||
} else if required {
|
||||
return clientcmdapi.Context{}, fmt.Errorf("context %q does not exist", contextName)
|
||||
}
|
||||
if config.overrides != nil {
|
||||
if err := merge(mergedContext, &config.overrides.Context); err != nil {
|
||||
return clientcmdapi.Context{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return *mergedContext, nil
|
||||
}
|
||||
|
||||
// getAuthInfo returns the clientcmdapi.AuthInfo, or an error if a required auth info is not found.
|
||||
func (config *DirectClientConfig) getAuthInfo() (clientcmdapi.AuthInfo, error) {
|
||||
authInfos := config.config.AuthInfos
|
||||
authInfoName, required := config.getAuthInfoName()
|
||||
|
||||
mergedAuthInfo := clientcmdapi.NewAuthInfo()
|
||||
if configAuthInfo, exists := authInfos[authInfoName]; exists {
|
||||
if err := merge(mergedAuthInfo, configAuthInfo); err != nil {
|
||||
return clientcmdapi.AuthInfo{}, err
|
||||
}
|
||||
} else if required {
|
||||
return clientcmdapi.AuthInfo{}, fmt.Errorf("auth info %q does not exist", authInfoName)
|
||||
}
|
||||
if config.overrides != nil {
|
||||
if err := merge(mergedAuthInfo, &config.overrides.AuthInfo); err != nil {
|
||||
return clientcmdapi.AuthInfo{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return *mergedAuthInfo, nil
|
||||
}
|
||||
|
||||
// getCluster returns the clientcmdapi.Cluster, or an error if a required cluster is not found.
|
||||
func (config *DirectClientConfig) getCluster() (clientcmdapi.Cluster, error) {
|
||||
clusterInfos := config.config.Clusters
|
||||
clusterInfoName, required := config.getClusterName()
|
||||
|
||||
mergedClusterInfo := clientcmdapi.NewCluster()
|
||||
if config.overrides != nil {
|
||||
if err := merge(mergedClusterInfo, &config.overrides.ClusterDefaults); err != nil {
|
||||
return clientcmdapi.Cluster{}, err
|
||||
}
|
||||
}
|
||||
if configClusterInfo, exists := clusterInfos[clusterInfoName]; exists {
|
||||
if err := merge(mergedClusterInfo, configClusterInfo); err != nil {
|
||||
return clientcmdapi.Cluster{}, err
|
||||
}
|
||||
} else if required {
|
||||
return clientcmdapi.Cluster{}, fmt.Errorf("cluster %q does not exist", clusterInfoName)
|
||||
}
|
||||
if config.overrides != nil {
|
||||
if err := merge(mergedClusterInfo, &config.overrides.ClusterInfo); err != nil {
|
||||
return clientcmdapi.Cluster{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// * An override of --insecure-skip-tls-verify=true and no accompanying CA/CA data should clear already-set CA/CA data
|
||||
// otherwise, a kubeconfig containing a CA reference would return an error that "CA and insecure-skip-tls-verify couldn't both be set".
|
||||
// * An override of --certificate-authority should also override TLS skip settings and CA data, otherwise existing CA data will take precedence.
|
||||
if config.overrides != nil {
|
||||
caLen := len(config.overrides.ClusterInfo.CertificateAuthority)
|
||||
caDataLen := len(config.overrides.ClusterInfo.CertificateAuthorityData)
|
||||
if config.overrides.ClusterInfo.InsecureSkipTLSVerify || caLen > 0 || caDataLen > 0 {
|
||||
mergedClusterInfo.InsecureSkipTLSVerify = config.overrides.ClusterInfo.InsecureSkipTLSVerify
|
||||
mergedClusterInfo.CertificateAuthority = config.overrides.ClusterInfo.CertificateAuthority
|
||||
mergedClusterInfo.CertificateAuthorityData = config.overrides.ClusterInfo.CertificateAuthorityData
|
||||
}
|
||||
|
||||
// if the --tls-server-name has been set in overrides, use that value.
|
||||
// if the --server has been set in overrides, then use the value of --tls-server-name specified on the CLI too. This gives the property
|
||||
// that setting a --server will effectively clear the KUBECONFIG value of tls-server-name if it is specified on the command line which is
|
||||
// usually correct.
|
||||
if config.overrides.ClusterInfo.TLSServerName != "" || config.overrides.ClusterInfo.Server != "" {
|
||||
mergedClusterInfo.TLSServerName = config.overrides.ClusterInfo.TLSServerName
|
||||
}
|
||||
}
|
||||
|
||||
return *mergedClusterInfo, nil
|
||||
}
|
||||
|
||||
// inClusterClientConfig makes a config that will work from within a kubernetes cluster container environment.
|
||||
// Can take options overrides for flags explicitly provided to the command inside the cluster container.
|
||||
type inClusterClientConfig struct {
|
||||
overrides *ConfigOverrides
|
||||
inClusterConfigProvider func() (*restclient.Config, error)
|
||||
}
|
||||
|
||||
var _ ClientConfig = &inClusterClientConfig{}
|
||||
|
||||
func (config *inClusterClientConfig) RawConfig() (clientcmdapi.Config, error) {
|
||||
return clientcmdapi.Config{}, fmt.Errorf("inCluster environment config doesn't support multiple clusters")
|
||||
}
|
||||
|
||||
func (config *inClusterClientConfig) ClientConfig() (*restclient.Config, error) {
|
||||
inClusterConfigProvider := config.inClusterConfigProvider
|
||||
if inClusterConfigProvider == nil {
|
||||
inClusterConfigProvider = restclient.InClusterConfig
|
||||
}
|
||||
|
||||
icc, err := inClusterConfigProvider()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// in-cluster configs only takes a host, token, or CA file
|
||||
// if any of them were individually provided, overwrite anything else
|
||||
if config.overrides != nil {
|
||||
if server := config.overrides.ClusterInfo.Server; len(server) > 0 {
|
||||
icc.Host = server
|
||||
}
|
||||
if len(config.overrides.AuthInfo.Token) > 0 || len(config.overrides.AuthInfo.TokenFile) > 0 {
|
||||
icc.BearerToken = config.overrides.AuthInfo.Token
|
||||
icc.BearerTokenFile = config.overrides.AuthInfo.TokenFile
|
||||
}
|
||||
if certificateAuthorityFile := config.overrides.ClusterInfo.CertificateAuthority; len(certificateAuthorityFile) > 0 {
|
||||
icc.TLSClientConfig.CAFile = certificateAuthorityFile
|
||||
}
|
||||
}
|
||||
|
||||
return icc, nil
|
||||
}
|
||||
|
||||
func (config *inClusterClientConfig) Namespace() (string, bool, error) {
|
||||
// This way assumes you've set the POD_NAMESPACE environment variable using the downward API.
|
||||
// This check has to be done first for backwards compatibility with the way InClusterConfig was originally set up
|
||||
if ns := os.Getenv("POD_NAMESPACE"); ns != "" {
|
||||
return ns, false, nil
|
||||
}
|
||||
|
||||
// Fall back to the namespace associated with the service account token, if available
|
||||
if data, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil {
|
||||
if ns := strings.TrimSpace(string(data)); len(ns) > 0 {
|
||||
return ns, false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "default", false, nil
|
||||
}
|
||||
|
||||
func (config *inClusterClientConfig) ConfigAccess() ConfigAccess {
|
||||
return NewDefaultClientConfigLoadingRules()
|
||||
}
|
||||
|
||||
// Possible returns true if loading an inside-kubernetes-cluster is possible.
|
||||
func (config *inClusterClientConfig) Possible() bool {
|
||||
fi, err := os.Stat("/var/run/secrets/kubernetes.io/serviceaccount/token")
|
||||
return os.Getenv("KUBERNETES_SERVICE_HOST") != "" &&
|
||||
os.Getenv("KUBERNETES_SERVICE_PORT") != "" &&
|
||||
err == nil && !fi.IsDir()
|
||||
}
|
||||
|
||||
// BuildConfigFromFlags is a helper function that builds configs from a master
|
||||
// url or a kubeconfig filepath. These are passed in as command line flags for cluster
|
||||
// components. Warnings should reflect this usage. If neither masterUrl or kubeconfigPath
|
||||
// are passed in we fallback to inClusterConfig. If inClusterConfig fails, we fallback
|
||||
// to the default config.
|
||||
func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config, error) {
|
||||
if kubeconfigPath == "" && masterUrl == "" {
|
||||
klog.Warning("Neither --kubeconfig nor --master was specified. Using the inClusterConfig. This might not work.")
|
||||
kubeconfig, err := restclient.InClusterConfig()
|
||||
if err == nil {
|
||||
return kubeconfig, nil
|
||||
}
|
||||
klog.Warning("error creating inClusterConfig, falling back to default config: ", err)
|
||||
}
|
||||
return NewNonInteractiveDeferredLoadingClientConfig(
|
||||
&ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
|
||||
&ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}}).ClientConfig()
|
||||
}
|
||||
|
||||
// BuildConfigFromKubeconfigGetter is a helper function that builds configs from a master
|
||||
// url and a kubeconfigGetter.
|
||||
func BuildConfigFromKubeconfigGetter(masterUrl string, kubeconfigGetter KubeconfigGetter) (*restclient.Config, error) {
|
||||
// TODO: We do not need a DeferredLoader here. Refactor code and see if we can use DirectClientConfig here.
|
||||
cc := NewNonInteractiveDeferredLoadingClientConfig(
|
||||
&ClientConfigGetter{kubeconfigGetter: kubeconfigGetter},
|
||||
&ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}})
|
||||
return cc.ClientConfig()
|
||||
}
|
499
e2e/vendor/k8s.io/client-go/tools/clientcmd/config.go
generated
vendored
Normal file
499
e2e/vendor/k8s.io/client-go/tools/clientcmd/config.go
generated
vendored
Normal file
@ -0,0 +1,499 @@
|
||||
/*
|
||||
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 clientcmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
restclient "k8s.io/client-go/rest"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
// ConfigAccess is used by subcommands and methods in this package to load and modify the appropriate config files
|
||||
type ConfigAccess interface {
|
||||
// GetLoadingPrecedence returns the slice of files that should be used for loading and inspecting the config
|
||||
GetLoadingPrecedence() []string
|
||||
// GetStartingConfig returns the config that subcommands should being operating against. It may or may not be merged depending on loading rules
|
||||
GetStartingConfig() (*clientcmdapi.Config, error)
|
||||
// GetDefaultFilename returns the name of the file you should write into (create if necessary), if you're trying to create a new stanza as opposed to updating an existing one.
|
||||
GetDefaultFilename() string
|
||||
// IsExplicitFile indicates whether or not this command is interested in exactly one file. This implementation only ever does that via a flag, but implementations that handle local, global, and flags may have more
|
||||
IsExplicitFile() bool
|
||||
// GetExplicitFile returns the particular file this command is operating against. This implementation only ever has one, but implementations that handle local, global, and flags may have more
|
||||
GetExplicitFile() string
|
||||
}
|
||||
|
||||
type PathOptions struct {
|
||||
// GlobalFile is the full path to the file to load as the global (final) option
|
||||
GlobalFile string
|
||||
// EnvVar is the env var name that points to the list of kubeconfig files to load
|
||||
EnvVar string
|
||||
// ExplicitFileFlag is the name of the flag to use for prompting for the kubeconfig file
|
||||
ExplicitFileFlag string
|
||||
|
||||
// GlobalFileSubpath is an optional value used for displaying help
|
||||
GlobalFileSubpath string
|
||||
|
||||
LoadingRules *ClientConfigLoadingRules
|
||||
}
|
||||
|
||||
var (
|
||||
// UseModifyConfigLock ensures that access to kubeconfig file using ModifyConfig method
|
||||
// is being guarded by a lock file.
|
||||
// This variable is intentionaly made public so other consumers of this library
|
||||
// can modify its default behavior, but be caution when disabling it since
|
||||
// this will make your code not threadsafe.
|
||||
UseModifyConfigLock = true
|
||||
)
|
||||
|
||||
func (o *PathOptions) GetEnvVarFiles() []string {
|
||||
if len(o.EnvVar) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
envVarValue := os.Getenv(o.EnvVar)
|
||||
if len(envVarValue) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
fileList := filepath.SplitList(envVarValue)
|
||||
// prevent the same path load multiple times
|
||||
return deduplicate(fileList)
|
||||
}
|
||||
|
||||
func (o *PathOptions) GetLoadingPrecedence() []string {
|
||||
if o.IsExplicitFile() {
|
||||
return []string{o.GetExplicitFile()}
|
||||
}
|
||||
|
||||
if envVarFiles := o.GetEnvVarFiles(); len(envVarFiles) > 0 {
|
||||
return envVarFiles
|
||||
}
|
||||
return []string{o.GlobalFile}
|
||||
}
|
||||
|
||||
func (o *PathOptions) GetStartingConfig() (*clientcmdapi.Config, error) {
|
||||
// don't mutate the original
|
||||
loadingRules := *o.LoadingRules
|
||||
loadingRules.Precedence = o.GetLoadingPrecedence()
|
||||
|
||||
clientConfig := NewNonInteractiveDeferredLoadingClientConfig(&loadingRules, &ConfigOverrides{})
|
||||
rawConfig, err := clientConfig.RawConfig()
|
||||
if os.IsNotExist(err) {
|
||||
return clientcmdapi.NewConfig(), nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &rawConfig, nil
|
||||
}
|
||||
|
||||
func (o *PathOptions) GetDefaultFilename() string {
|
||||
if o.IsExplicitFile() {
|
||||
return o.GetExplicitFile()
|
||||
}
|
||||
|
||||
if envVarFiles := o.GetEnvVarFiles(); len(envVarFiles) > 0 {
|
||||
if len(envVarFiles) == 1 {
|
||||
return envVarFiles[0]
|
||||
}
|
||||
|
||||
// if any of the envvar files already exists, return it
|
||||
for _, envVarFile := range envVarFiles {
|
||||
if _, err := os.Stat(envVarFile); err == nil {
|
||||
return envVarFile
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise, return the last one in the list
|
||||
return envVarFiles[len(envVarFiles)-1]
|
||||
}
|
||||
|
||||
return o.GlobalFile
|
||||
}
|
||||
|
||||
func (o *PathOptions) IsExplicitFile() bool {
|
||||
return len(o.LoadingRules.ExplicitPath) > 0
|
||||
}
|
||||
|
||||
func (o *PathOptions) GetExplicitFile() string {
|
||||
return o.LoadingRules.ExplicitPath
|
||||
}
|
||||
|
||||
func NewDefaultPathOptions() *PathOptions {
|
||||
ret := &PathOptions{
|
||||
GlobalFile: RecommendedHomeFile,
|
||||
EnvVar: RecommendedConfigPathEnvVar,
|
||||
ExplicitFileFlag: RecommendedConfigPathFlag,
|
||||
|
||||
GlobalFileSubpath: filepath.Join(RecommendedHomeDir, RecommendedFileName),
|
||||
|
||||
LoadingRules: NewDefaultClientConfigLoadingRules(),
|
||||
}
|
||||
ret.LoadingRules.DoNotResolvePaths = true
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// ModifyConfig takes a Config object, iterates through Clusters, AuthInfos, and Contexts, uses the LocationOfOrigin if specified or
|
||||
// uses the default destination file to write the results into. This results in multiple file reads, but it's very easy to follow.
|
||||
// Preferences and CurrentContext should always be set in the default destination file. Since we can't distinguish between empty and missing values
|
||||
// (no nil strings), we're forced have separate handling for them. In the kubeconfig cases, newConfig should have at most one difference,
|
||||
// that means that this code will only write into a single file. If you want to relativizePaths, you must provide a fully qualified path in any
|
||||
// modified element.
|
||||
func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config, relativizePaths bool) error {
|
||||
if UseModifyConfigLock {
|
||||
possibleSources := configAccess.GetLoadingPrecedence()
|
||||
// sort the possible kubeconfig files so we always "lock" in the same order
|
||||
// to avoid deadlock (note: this can fail w/ symlinks, but... come on).
|
||||
sort.Strings(possibleSources)
|
||||
for _, filename := range possibleSources {
|
||||
if err := lockFile(filename); err != nil {
|
||||
return err
|
||||
}
|
||||
defer unlockFile(filename)
|
||||
}
|
||||
}
|
||||
|
||||
startingConfig, err := configAccess.GetStartingConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We need to find all differences, locate their original files, read a partial config to modify only that stanza and write out the file.
|
||||
// Special case the test for current context and preferences since those always write to the default file.
|
||||
if reflect.DeepEqual(*startingConfig, newConfig) {
|
||||
// nothing to do
|
||||
return nil
|
||||
}
|
||||
|
||||
if startingConfig.CurrentContext != newConfig.CurrentContext {
|
||||
if err := writeCurrentContext(configAccess, newConfig.CurrentContext); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(startingConfig.Preferences, newConfig.Preferences) {
|
||||
if err := writePreferences(configAccess, newConfig.Preferences); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Search every cluster, authInfo, and context. First from new to old for differences, then from old to new for deletions
|
||||
for key, cluster := range newConfig.Clusters {
|
||||
startingCluster, exists := startingConfig.Clusters[key]
|
||||
if !reflect.DeepEqual(cluster, startingCluster) || !exists {
|
||||
destinationFile := cluster.LocationOfOrigin
|
||||
if len(destinationFile) == 0 {
|
||||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
configToWrite, err := getConfigFromFile(destinationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t := *cluster
|
||||
|
||||
configToWrite.Clusters[key] = &t
|
||||
configToWrite.Clusters[key].LocationOfOrigin = destinationFile
|
||||
if relativizePaths {
|
||||
if err := RelativizeClusterLocalPaths(configToWrite.Clusters[key]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// seenConfigs stores a map of config source filenames to computed config objects
|
||||
seenConfigs := map[string]*clientcmdapi.Config{}
|
||||
|
||||
for key, context := range newConfig.Contexts {
|
||||
startingContext, exists := startingConfig.Contexts[key]
|
||||
if !reflect.DeepEqual(context, startingContext) || !exists {
|
||||
destinationFile := context.LocationOfOrigin
|
||||
if len(destinationFile) == 0 {
|
||||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
// we only obtain a fresh config object from its source file
|
||||
// if we have not seen it already - this prevents us from
|
||||
// reading and writing to the same number of files repeatedly
|
||||
// when multiple / all contexts share the same destination file.
|
||||
configToWrite, seen := seenConfigs[destinationFile]
|
||||
if !seen {
|
||||
var err error
|
||||
configToWrite, err = getConfigFromFile(destinationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
seenConfigs[destinationFile] = configToWrite
|
||||
}
|
||||
|
||||
configToWrite.Contexts[key] = context
|
||||
}
|
||||
}
|
||||
|
||||
// actually persist config object changes
|
||||
for destinationFile, configToWrite := range seenConfigs {
|
||||
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for key, authInfo := range newConfig.AuthInfos {
|
||||
startingAuthInfo, exists := startingConfig.AuthInfos[key]
|
||||
if !reflect.DeepEqual(authInfo, startingAuthInfo) || !exists {
|
||||
destinationFile := authInfo.LocationOfOrigin
|
||||
if len(destinationFile) == 0 {
|
||||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
configToWrite, err := getConfigFromFile(destinationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t := *authInfo
|
||||
configToWrite.AuthInfos[key] = &t
|
||||
configToWrite.AuthInfos[key].LocationOfOrigin = destinationFile
|
||||
if relativizePaths {
|
||||
if err := RelativizeAuthInfoLocalPaths(configToWrite.AuthInfos[key]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, cluster := range startingConfig.Clusters {
|
||||
if _, exists := newConfig.Clusters[key]; !exists {
|
||||
destinationFile := cluster.LocationOfOrigin
|
||||
if len(destinationFile) == 0 {
|
||||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
configToWrite, err := getConfigFromFile(destinationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delete(configToWrite.Clusters, key)
|
||||
|
||||
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, context := range startingConfig.Contexts {
|
||||
if _, exists := newConfig.Contexts[key]; !exists {
|
||||
destinationFile := context.LocationOfOrigin
|
||||
if len(destinationFile) == 0 {
|
||||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
configToWrite, err := getConfigFromFile(destinationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delete(configToWrite.Contexts, key)
|
||||
|
||||
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, authInfo := range startingConfig.AuthInfos {
|
||||
if _, exists := newConfig.AuthInfos[key]; !exists {
|
||||
destinationFile := authInfo.LocationOfOrigin
|
||||
if len(destinationFile) == 0 {
|
||||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
configToWrite, err := getConfigFromFile(destinationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delete(configToWrite.AuthInfos, key)
|
||||
|
||||
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func PersisterForUser(configAccess ConfigAccess, user string) restclient.AuthProviderConfigPersister {
|
||||
return &persister{configAccess, user}
|
||||
}
|
||||
|
||||
type persister struct {
|
||||
configAccess ConfigAccess
|
||||
user string
|
||||
}
|
||||
|
||||
func (p *persister) Persist(config map[string]string) error {
|
||||
newConfig, err := p.configAccess.GetStartingConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authInfo, ok := newConfig.AuthInfos[p.user]
|
||||
if ok && authInfo.AuthProvider != nil {
|
||||
authInfo.AuthProvider.Config = config
|
||||
return ModifyConfig(p.configAccess, *newConfig, false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeCurrentContext takes three possible paths.
|
||||
// If newCurrentContext is the same as the startingConfig's current context, then we exit.
|
||||
// If newCurrentContext has a value, then that value is written into the default destination file.
|
||||
// If newCurrentContext is empty, then we find the config file that is setting the CurrentContext and clear the value from that file
|
||||
func writeCurrentContext(configAccess ConfigAccess, newCurrentContext string) error {
|
||||
if startingConfig, err := configAccess.GetStartingConfig(); err != nil {
|
||||
return err
|
||||
} else if startingConfig.CurrentContext == newCurrentContext {
|
||||
return nil
|
||||
}
|
||||
|
||||
if configAccess.IsExplicitFile() {
|
||||
file := configAccess.GetExplicitFile()
|
||||
currConfig, err := getConfigFromFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currConfig.CurrentContext = newCurrentContext
|
||||
if err := WriteToFile(*currConfig, file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(newCurrentContext) > 0 {
|
||||
destinationFile := configAccess.GetDefaultFilename()
|
||||
config, err := getConfigFromFile(destinationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.CurrentContext = newCurrentContext
|
||||
|
||||
if err := WriteToFile(*config, destinationFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// we're supposed to be clearing the current context. We need to find the first spot in the chain that is setting it and clear it
|
||||
for _, file := range configAccess.GetLoadingPrecedence() {
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
currConfig, err := getConfigFromFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(currConfig.CurrentContext) > 0 {
|
||||
currConfig.CurrentContext = newCurrentContext
|
||||
if err := WriteToFile(*currConfig, file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errors.New("no config found to write context")
|
||||
}
|
||||
|
||||
func writePreferences(configAccess ConfigAccess, newPrefs clientcmdapi.Preferences) error {
|
||||
if startingConfig, err := configAccess.GetStartingConfig(); err != nil {
|
||||
return err
|
||||
} else if reflect.DeepEqual(startingConfig.Preferences, newPrefs) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if configAccess.IsExplicitFile() {
|
||||
file := configAccess.GetExplicitFile()
|
||||
currConfig, err := getConfigFromFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currConfig.Preferences = newPrefs
|
||||
if err := WriteToFile(*currConfig, file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, file := range configAccess.GetLoadingPrecedence() {
|
||||
currConfig, err := getConfigFromFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(currConfig.Preferences, newPrefs) {
|
||||
currConfig.Preferences = newPrefs
|
||||
if err := WriteToFile(*currConfig, file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return errors.New("no config found to write preferences")
|
||||
}
|
||||
|
||||
// getConfigFromFile tries to read a kubeconfig file and if it can't, returns an error. One exception, missing files result in empty configs, not an error.
|
||||
func getConfigFromFile(filename string) (*clientcmdapi.Config, error) {
|
||||
config, err := LoadFromFile(filename)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
if config == nil {
|
||||
config = clientcmdapi.NewConfig()
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// GetConfigFromFileOrDie tries to read a kubeconfig file and if it can't, it calls exit. One exception, missing files result in empty configs, not an exit
|
||||
func GetConfigFromFileOrDie(filename string) *clientcmdapi.Config {
|
||||
config, err := getConfigFromFile(filename)
|
||||
if err != nil {
|
||||
klog.FatalDepth(1, err)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
37
e2e/vendor/k8s.io/client-go/tools/clientcmd/doc.go
generated
vendored
Normal file
37
e2e/vendor/k8s.io/client-go/tools/clientcmd/doc.go
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
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 clientcmd provides one stop shopping for building a working client from a fixed config,
|
||||
from a .kubeconfig file, from command line flags, or from any merged combination.
|
||||
|
||||
Sample usage from merged .kubeconfig files (local directory, home directory)
|
||||
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
// if you want to change the loading rules (which files in which order), you can do so here
|
||||
|
||||
configOverrides := &clientcmd.ConfigOverrides{}
|
||||
// if you want to change override values or bind them to flags, there are methods to help you
|
||||
|
||||
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
|
||||
config, err := kubeConfig.ClientConfig()
|
||||
if err != nil {
|
||||
// Do something
|
||||
}
|
||||
client, err := metav1.New(config)
|
||||
// ...
|
||||
*/
|
||||
package clientcmd // import "k8s.io/client-go/tools/clientcmd"
|
49
e2e/vendor/k8s.io/client-go/tools/clientcmd/flag.go
generated
vendored
Normal file
49
e2e/vendor/k8s.io/client-go/tools/clientcmd/flag.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
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 clientcmd
|
||||
|
||||
// transformingStringValue implements pflag.Value to store string values,
|
||||
// allowing transforming them while being set
|
||||
type transformingStringValue struct {
|
||||
target *string
|
||||
transformer func(string) (string, error)
|
||||
}
|
||||
|
||||
func newTransformingStringValue(val string, target *string, transformer func(string) (string, error)) *transformingStringValue {
|
||||
*target = val
|
||||
return &transformingStringValue{
|
||||
target: target,
|
||||
transformer: transformer,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *transformingStringValue) Set(val string) error {
|
||||
val, err := t.transformer(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*t.target = val
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *transformingStringValue) Type() string {
|
||||
return "string"
|
||||
}
|
||||
|
||||
func (t *transformingStringValue) String() string {
|
||||
return string(*t.target)
|
||||
}
|
50
e2e/vendor/k8s.io/client-go/tools/clientcmd/helpers.go
generated
vendored
Normal file
50
e2e/vendor/k8s.io/client-go/tools/clientcmd/helpers.go
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
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 clientcmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ParseTimeout returns a parsed duration from a string
|
||||
// A duration string value must be a positive integer, optionally followed by a corresponding time unit (s|m|h).
|
||||
func ParseTimeout(duration string) (time.Duration, error) {
|
||||
if i, err := strconv.ParseInt(duration, 10, 64); err == nil && i >= 0 {
|
||||
return (time.Duration(i) * time.Second), nil
|
||||
}
|
||||
if requestTimeout, err := time.ParseDuration(duration); err == nil {
|
||||
return requestTimeout, nil
|
||||
}
|
||||
return 0, fmt.Errorf("Invalid timeout value. Timeout must be a single integer in seconds, or an integer followed by a corresponding time unit (e.g. 1s | 2m | 3h)")
|
||||
}
|
||||
|
||||
func parseProxyURL(proxyURL string) (*url.URL, error) {
|
||||
u, err := url.Parse(proxyURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse: %v", proxyURL)
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "http", "https", "socks5":
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported scheme %q, must be http, https, or socks5", u.Scheme)
|
||||
}
|
||||
return u, nil
|
||||
}
|
676
e2e/vendor/k8s.io/client-go/tools/clientcmd/loader.go
generated
vendored
Normal file
676
e2e/vendor/k8s.io/client-go/tools/clientcmd/loader.go
generated
vendored
Normal file
@ -0,0 +1,676 @@
|
||||
/*
|
||||
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 clientcmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
goruntime "runtime"
|
||||
"strings"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
clientcmdlatest "k8s.io/client-go/tools/clientcmd/api/latest"
|
||||
"k8s.io/client-go/util/homedir"
|
||||
)
|
||||
|
||||
const (
|
||||
RecommendedConfigPathFlag = "kubeconfig"
|
||||
RecommendedConfigPathEnvVar = "KUBECONFIG"
|
||||
RecommendedHomeDir = ".kube"
|
||||
RecommendedFileName = "config"
|
||||
RecommendedSchemaName = "schema"
|
||||
)
|
||||
|
||||
var (
|
||||
RecommendedConfigDir = filepath.Join(homedir.HomeDir(), RecommendedHomeDir)
|
||||
RecommendedHomeFile = filepath.Join(RecommendedConfigDir, RecommendedFileName)
|
||||
RecommendedSchemaFile = filepath.Join(RecommendedConfigDir, RecommendedSchemaName)
|
||||
)
|
||||
|
||||
// currentMigrationRules returns a map that holds the history of recommended home directories used in previous versions.
|
||||
// Any future changes to RecommendedHomeFile and related are expected to add a migration rule here, in order to make
|
||||
// sure existing config files are migrated to their new locations properly.
|
||||
func currentMigrationRules() map[string]string {
|
||||
var oldRecommendedHomeFileName string
|
||||
if goruntime.GOOS == "windows" {
|
||||
oldRecommendedHomeFileName = RecommendedFileName
|
||||
} else {
|
||||
oldRecommendedHomeFileName = ".kubeconfig"
|
||||
}
|
||||
return map[string]string{
|
||||
RecommendedHomeFile: filepath.Join(os.Getenv("HOME"), RecommendedHomeDir, oldRecommendedHomeFileName),
|
||||
}
|
||||
}
|
||||
|
||||
type ClientConfigLoader interface {
|
||||
ConfigAccess
|
||||
// IsDefaultConfig returns true if the returned config matches the defaults.
|
||||
IsDefaultConfig(*restclient.Config) bool
|
||||
// Load returns the latest config
|
||||
Load() (*clientcmdapi.Config, error)
|
||||
}
|
||||
|
||||
type KubeconfigGetter func() (*clientcmdapi.Config, error)
|
||||
|
||||
type ClientConfigGetter struct {
|
||||
kubeconfigGetter KubeconfigGetter
|
||||
}
|
||||
|
||||
// ClientConfigGetter implements the ClientConfigLoader interface.
|
||||
var _ ClientConfigLoader = &ClientConfigGetter{}
|
||||
|
||||
func (g *ClientConfigGetter) Load() (*clientcmdapi.Config, error) {
|
||||
return g.kubeconfigGetter()
|
||||
}
|
||||
|
||||
func (g *ClientConfigGetter) GetLoadingPrecedence() []string {
|
||||
return nil
|
||||
}
|
||||
func (g *ClientConfigGetter) GetStartingConfig() (*clientcmdapi.Config, error) {
|
||||
return g.kubeconfigGetter()
|
||||
}
|
||||
func (g *ClientConfigGetter) GetDefaultFilename() string {
|
||||
return ""
|
||||
}
|
||||
func (g *ClientConfigGetter) IsExplicitFile() bool {
|
||||
return false
|
||||
}
|
||||
func (g *ClientConfigGetter) GetExplicitFile() string {
|
||||
return ""
|
||||
}
|
||||
func (g *ClientConfigGetter) IsDefaultConfig(config *restclient.Config) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ClientConfigLoadingRules is an ExplicitPath and string slice of specific locations that are used for merging together a Config
|
||||
// Callers can put the chain together however they want, but we'd recommend:
|
||||
// EnvVarPathFiles if set (a list of files if set) OR the HomeDirectoryPath
|
||||
// ExplicitPath is special, because if a user specifically requests a certain file be used and error is reported if this file is not present
|
||||
type ClientConfigLoadingRules struct {
|
||||
ExplicitPath string
|
||||
Precedence []string
|
||||
|
||||
// MigrationRules is a map of destination files to source files. If a destination file is not present, then the source file is checked.
|
||||
// If the source file is present, then it is copied to the destination file BEFORE any further loading happens.
|
||||
MigrationRules map[string]string
|
||||
|
||||
// DoNotResolvePaths indicates whether or not to resolve paths with respect to the originating files. This is phrased as a negative so
|
||||
// that a default object that doesn't set this will usually get the behavior it wants.
|
||||
DoNotResolvePaths bool
|
||||
|
||||
// DefaultClientConfig is an optional field indicating what rules to use to calculate a default configuration.
|
||||
// This should match the overrides passed in to ClientConfig loader.
|
||||
DefaultClientConfig ClientConfig
|
||||
|
||||
// WarnIfAllMissing indicates whether the configuration files pointed by KUBECONFIG environment variable are present or not.
|
||||
// In case of missing files, it warns the user about the missing files.
|
||||
WarnIfAllMissing bool
|
||||
|
||||
// Warner is the warning log callback to use in case of missing files.
|
||||
Warner WarningHandler
|
||||
}
|
||||
|
||||
// WarningHandler allows to set the logging function to use
|
||||
type WarningHandler func(error)
|
||||
|
||||
func (handler WarningHandler) Warn(err error) {
|
||||
if handler == nil {
|
||||
klog.V(1).Info(err)
|
||||
} else {
|
||||
handler(err)
|
||||
}
|
||||
}
|
||||
|
||||
type MissingConfigError struct {
|
||||
Missing []string
|
||||
}
|
||||
|
||||
func (c MissingConfigError) Error() string {
|
||||
return fmt.Sprintf("Config not found: %s", strings.Join(c.Missing, ", "))
|
||||
}
|
||||
|
||||
// ClientConfigLoadingRules implements the ClientConfigLoader interface.
|
||||
var _ ClientConfigLoader = &ClientConfigLoadingRules{}
|
||||
|
||||
// NewDefaultClientConfigLoadingRules returns a ClientConfigLoadingRules object with default fields filled in. You are not required to
|
||||
// use this constructor
|
||||
func NewDefaultClientConfigLoadingRules() *ClientConfigLoadingRules {
|
||||
chain := []string{}
|
||||
warnIfAllMissing := false
|
||||
|
||||
envVarFiles := os.Getenv(RecommendedConfigPathEnvVar)
|
||||
if len(envVarFiles) != 0 {
|
||||
fileList := filepath.SplitList(envVarFiles)
|
||||
// prevent the same path load multiple times
|
||||
chain = append(chain, deduplicate(fileList)...)
|
||||
warnIfAllMissing = true
|
||||
|
||||
} else {
|
||||
chain = append(chain, RecommendedHomeFile)
|
||||
}
|
||||
|
||||
return &ClientConfigLoadingRules{
|
||||
Precedence: chain,
|
||||
MigrationRules: currentMigrationRules(),
|
||||
WarnIfAllMissing: warnIfAllMissing,
|
||||
}
|
||||
}
|
||||
|
||||
// Load starts by running the MigrationRules and then
|
||||
// takes the loading rules and returns a Config object based on following rules.
|
||||
//
|
||||
// if the ExplicitPath, return the unmerged explicit file
|
||||
// Otherwise, return a merged config based on the Precedence slice
|
||||
//
|
||||
// A missing ExplicitPath file produces an error. Empty filenames or other missing files are ignored.
|
||||
// Read errors or files with non-deserializable content produce errors.
|
||||
// The first file to set a particular map key wins and map key's value is never changed.
|
||||
// BUT, if you set a struct value that is NOT contained inside of map, the value WILL be changed.
|
||||
// This results in some odd looking logic to merge in one direction, merge in the other, and then merge the two.
|
||||
// It also means that if two files specify a "red-user", only values from the first file's red-user are used. Even
|
||||
// non-conflicting entries from the second file's "red-user" are discarded.
|
||||
// Relative paths inside of the .kubeconfig files are resolved against the .kubeconfig file's parent folder
|
||||
// and only absolute file paths are returned.
|
||||
func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) {
|
||||
if err := rules.Migrate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errlist := []error{}
|
||||
missingList := []string{}
|
||||
|
||||
kubeConfigFiles := []string{}
|
||||
|
||||
// Make sure a file we were explicitly told to use exists
|
||||
if len(rules.ExplicitPath) > 0 {
|
||||
if _, err := os.Stat(rules.ExplicitPath); os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
kubeConfigFiles = append(kubeConfigFiles, rules.ExplicitPath)
|
||||
|
||||
} else {
|
||||
kubeConfigFiles = append(kubeConfigFiles, rules.Precedence...)
|
||||
}
|
||||
|
||||
kubeconfigs := []*clientcmdapi.Config{}
|
||||
// read and cache the config files so that we only look at them once
|
||||
for _, filename := range kubeConfigFiles {
|
||||
if len(filename) == 0 {
|
||||
// no work to do
|
||||
continue
|
||||
}
|
||||
|
||||
config, err := LoadFromFile(filename)
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
// skip missing files
|
||||
// Add to the missing list to produce a warning
|
||||
missingList = append(missingList, filename)
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errlist = append(errlist, fmt.Errorf("error loading config file \"%s\": %v", filename, err))
|
||||
continue
|
||||
}
|
||||
|
||||
kubeconfigs = append(kubeconfigs, config)
|
||||
}
|
||||
|
||||
if rules.WarnIfAllMissing && len(missingList) > 0 && len(kubeconfigs) == 0 {
|
||||
rules.Warner.Warn(MissingConfigError{Missing: missingList})
|
||||
}
|
||||
|
||||
// first merge all of our maps
|
||||
mapConfig := clientcmdapi.NewConfig()
|
||||
|
||||
for _, kubeconfig := range kubeconfigs {
|
||||
if err := merge(mapConfig, kubeconfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// merge all of the struct values in the reverse order so that priority is given correctly
|
||||
// errors are not added to the list the second time
|
||||
nonMapConfig := clientcmdapi.NewConfig()
|
||||
for i := len(kubeconfigs) - 1; i >= 0; i-- {
|
||||
kubeconfig := kubeconfigs[i]
|
||||
if err := merge(nonMapConfig, kubeconfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// since values are overwritten, but maps values are not, we can merge the non-map config on top of the map config and
|
||||
// get the values we expect.
|
||||
config := clientcmdapi.NewConfig()
|
||||
if err := merge(config, mapConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := merge(config, nonMapConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rules.ResolvePaths() {
|
||||
if err := ResolveLocalPaths(config); err != nil {
|
||||
errlist = append(errlist, err)
|
||||
}
|
||||
}
|
||||
return config, utilerrors.NewAggregate(errlist)
|
||||
}
|
||||
|
||||
// Migrate uses the MigrationRules map. If a destination file is not present, then the source file is checked.
|
||||
// If the source file is present, then it is copied to the destination file BEFORE any further loading happens.
|
||||
func (rules *ClientConfigLoadingRules) Migrate() error {
|
||||
if rules.MigrationRules == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for destination, source := range rules.MigrationRules {
|
||||
if _, err := os.Stat(destination); err == nil {
|
||||
// if the destination already exists, do nothing
|
||||
continue
|
||||
} else if os.IsPermission(err) {
|
||||
// if we can't access the file, skip it
|
||||
continue
|
||||
} else if !os.IsNotExist(err) {
|
||||
// if we had an error other than non-existence, fail
|
||||
return err
|
||||
}
|
||||
|
||||
if sourceInfo, err := os.Stat(source); err != nil {
|
||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
||||
// if the source file doesn't exist or we can't access it, there's no work to do.
|
||||
continue
|
||||
}
|
||||
|
||||
// if we had an error other than non-existence, fail
|
||||
return err
|
||||
} else if sourceInfo.IsDir() {
|
||||
return fmt.Errorf("cannot migrate %v to %v because it is a directory", source, destination)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// destination is created with mode 0666 before umask
|
||||
err = os.WriteFile(destination, data, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetLoadingPrecedence implements ConfigAccess
|
||||
func (rules *ClientConfigLoadingRules) GetLoadingPrecedence() []string {
|
||||
if len(rules.ExplicitPath) > 0 {
|
||||
return []string{rules.ExplicitPath}
|
||||
}
|
||||
|
||||
return rules.Precedence
|
||||
}
|
||||
|
||||
// GetStartingConfig implements ConfigAccess
|
||||
func (rules *ClientConfigLoadingRules) GetStartingConfig() (*clientcmdapi.Config, error) {
|
||||
clientConfig := NewNonInteractiveDeferredLoadingClientConfig(rules, &ConfigOverrides{})
|
||||
rawConfig, err := clientConfig.RawConfig()
|
||||
if os.IsNotExist(err) {
|
||||
return clientcmdapi.NewConfig(), nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &rawConfig, nil
|
||||
}
|
||||
|
||||
// GetDefaultFilename implements ConfigAccess
|
||||
func (rules *ClientConfigLoadingRules) GetDefaultFilename() string {
|
||||
// Explicit file if we have one.
|
||||
if rules.IsExplicitFile() {
|
||||
return rules.GetExplicitFile()
|
||||
}
|
||||
// Otherwise, first existing file from precedence.
|
||||
for _, filename := range rules.GetLoadingPrecedence() {
|
||||
if _, err := os.Stat(filename); err == nil {
|
||||
return filename
|
||||
}
|
||||
}
|
||||
// If none exists, use the first from precedence.
|
||||
if len(rules.Precedence) > 0 {
|
||||
return rules.Precedence[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsExplicitFile implements ConfigAccess
|
||||
func (rules *ClientConfigLoadingRules) IsExplicitFile() bool {
|
||||
return len(rules.ExplicitPath) > 0
|
||||
}
|
||||
|
||||
// GetExplicitFile implements ConfigAccess
|
||||
func (rules *ClientConfigLoadingRules) GetExplicitFile() string {
|
||||
return rules.ExplicitPath
|
||||
}
|
||||
|
||||
// IsDefaultConfig returns true if the provided configuration matches the default
|
||||
func (rules *ClientConfigLoadingRules) IsDefaultConfig(config *restclient.Config) bool {
|
||||
if rules.DefaultClientConfig == nil {
|
||||
return false
|
||||
}
|
||||
defaultConfig, err := rules.DefaultClientConfig.ClientConfig()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return reflect.DeepEqual(config, defaultConfig)
|
||||
}
|
||||
|
||||
// LoadFromFile takes a filename and deserializes the contents into Config object
|
||||
func LoadFromFile(filename string) (*clientcmdapi.Config, error) {
|
||||
kubeconfigBytes, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config, err := Load(kubeconfigBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
klog.V(6).Infoln("Config loaded from file: ", filename)
|
||||
|
||||
// set LocationOfOrigin on every Cluster, User, and Context
|
||||
for key, obj := range config.AuthInfos {
|
||||
obj.LocationOfOrigin = filename
|
||||
config.AuthInfos[key] = obj
|
||||
}
|
||||
for key, obj := range config.Clusters {
|
||||
obj.LocationOfOrigin = filename
|
||||
config.Clusters[key] = obj
|
||||
}
|
||||
for key, obj := range config.Contexts {
|
||||
obj.LocationOfOrigin = filename
|
||||
config.Contexts[key] = obj
|
||||
}
|
||||
|
||||
if config.AuthInfos == nil {
|
||||
config.AuthInfos = map[string]*clientcmdapi.AuthInfo{}
|
||||
}
|
||||
if config.Clusters == nil {
|
||||
config.Clusters = map[string]*clientcmdapi.Cluster{}
|
||||
}
|
||||
if config.Contexts == nil {
|
||||
config.Contexts = map[string]*clientcmdapi.Context{}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// Load takes a byte slice and deserializes the contents into Config object.
|
||||
// Encapsulates deserialization without assuming the source is a file.
|
||||
func Load(data []byte) (*clientcmdapi.Config, error) {
|
||||
config := clientcmdapi.NewConfig()
|
||||
// if there's no data in a file, return the default object instead of failing (DecodeInto reject empty input)
|
||||
if len(data) == 0 {
|
||||
return config, nil
|
||||
}
|
||||
decoded, _, err := clientcmdlatest.Codec.Decode(data, &schema.GroupVersionKind{Version: clientcmdlatest.Version, Kind: "Config"}, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decoded.(*clientcmdapi.Config), nil
|
||||
}
|
||||
|
||||
// WriteToFile serializes the config to yaml and writes it out to a file. If not present, it creates the file with the mode 0600. If it is present
|
||||
// it stomps the contents
|
||||
func WriteToFile(config clientcmdapi.Config, filename string) error {
|
||||
content, err := Write(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dir := filepath.Dir(filename)
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.WriteFile(filename, content, 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func lockFile(filename string) error {
|
||||
// TODO: find a way to do this with actual file locks. Will
|
||||
// probably need separate solution for windows and Linux.
|
||||
|
||||
// Make sure the dir exists before we try to create a lock file.
|
||||
dir := filepath.Dir(filename)
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
f, err := os.OpenFile(lockName(filename), os.O_CREATE|os.O_EXCL, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func unlockFile(filename string) error {
|
||||
return os.Remove(lockName(filename))
|
||||
}
|
||||
|
||||
func lockName(filename string) string {
|
||||
return filename + ".lock"
|
||||
}
|
||||
|
||||
// Write serializes the config to yaml.
|
||||
// Encapsulates serialization without assuming the destination is a file.
|
||||
func Write(config clientcmdapi.Config) ([]byte, error) {
|
||||
return runtime.Encode(clientcmdlatest.Codec, &config)
|
||||
}
|
||||
|
||||
func (rules ClientConfigLoadingRules) ResolvePaths() bool {
|
||||
return !rules.DoNotResolvePaths
|
||||
}
|
||||
|
||||
// ResolveLocalPaths resolves all relative paths in the config object with respect to the stanza's LocationOfOrigin
|
||||
// this cannot be done directly inside of LoadFromFile because doing so there would make it impossible to load a file without
|
||||
// modification of its contents.
|
||||
func ResolveLocalPaths(config *clientcmdapi.Config) error {
|
||||
for _, cluster := range config.Clusters {
|
||||
if len(cluster.LocationOfOrigin) == 0 {
|
||||
continue
|
||||
}
|
||||
base, err := filepath.Abs(filepath.Dir(cluster.LocationOfOrigin))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not determine the absolute path of config file %s: %v", cluster.LocationOfOrigin, err)
|
||||
}
|
||||
|
||||
if err := ResolvePaths(GetClusterFileReferences(cluster), base); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, authInfo := range config.AuthInfos {
|
||||
if len(authInfo.LocationOfOrigin) == 0 {
|
||||
continue
|
||||
}
|
||||
base, err := filepath.Abs(filepath.Dir(authInfo.LocationOfOrigin))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not determine the absolute path of config file %s: %v", authInfo.LocationOfOrigin, err)
|
||||
}
|
||||
|
||||
if err := ResolvePaths(GetAuthInfoFileReferences(authInfo), base); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RelativizeClusterLocalPaths first absolutizes the paths by calling ResolveLocalPaths. This assumes that any NEW path is already
|
||||
// absolute, but any existing path will be resolved relative to LocationOfOrigin
|
||||
func RelativizeClusterLocalPaths(cluster *clientcmdapi.Cluster) error {
|
||||
if len(cluster.LocationOfOrigin) == 0 {
|
||||
return fmt.Errorf("no location of origin for %s", cluster.Server)
|
||||
}
|
||||
base, err := filepath.Abs(filepath.Dir(cluster.LocationOfOrigin))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not determine the absolute path of config file %s: %v", cluster.LocationOfOrigin, err)
|
||||
}
|
||||
|
||||
if err := ResolvePaths(GetClusterFileReferences(cluster), base); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := RelativizePathWithNoBacksteps(GetClusterFileReferences(cluster), base); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RelativizeAuthInfoLocalPaths first absolutizes the paths by calling ResolveLocalPaths. This assumes that any NEW path is already
|
||||
// absolute, but any existing path will be resolved relative to LocationOfOrigin
|
||||
func RelativizeAuthInfoLocalPaths(authInfo *clientcmdapi.AuthInfo) error {
|
||||
if len(authInfo.LocationOfOrigin) == 0 {
|
||||
return fmt.Errorf("no location of origin for %v", authInfo)
|
||||
}
|
||||
base, err := filepath.Abs(filepath.Dir(authInfo.LocationOfOrigin))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not determine the absolute path of config file %s: %v", authInfo.LocationOfOrigin, err)
|
||||
}
|
||||
|
||||
if err := ResolvePaths(GetAuthInfoFileReferences(authInfo), base); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := RelativizePathWithNoBacksteps(GetAuthInfoFileReferences(authInfo), base); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RelativizeConfigPaths(config *clientcmdapi.Config, base string) error {
|
||||
return RelativizePathWithNoBacksteps(GetConfigFileReferences(config), base)
|
||||
}
|
||||
|
||||
func ResolveConfigPaths(config *clientcmdapi.Config, base string) error {
|
||||
return ResolvePaths(GetConfigFileReferences(config), base)
|
||||
}
|
||||
|
||||
func GetConfigFileReferences(config *clientcmdapi.Config) []*string {
|
||||
refs := []*string{}
|
||||
|
||||
for _, cluster := range config.Clusters {
|
||||
refs = append(refs, GetClusterFileReferences(cluster)...)
|
||||
}
|
||||
for _, authInfo := range config.AuthInfos {
|
||||
refs = append(refs, GetAuthInfoFileReferences(authInfo)...)
|
||||
}
|
||||
|
||||
return refs
|
||||
}
|
||||
|
||||
func GetClusterFileReferences(cluster *clientcmdapi.Cluster) []*string {
|
||||
return []*string{&cluster.CertificateAuthority}
|
||||
}
|
||||
|
||||
func GetAuthInfoFileReferences(authInfo *clientcmdapi.AuthInfo) []*string {
|
||||
s := []*string{&authInfo.ClientCertificate, &authInfo.ClientKey, &authInfo.TokenFile}
|
||||
// Only resolve exec command if it isn't PATH based.
|
||||
if authInfo.Exec != nil && strings.ContainsRune(authInfo.Exec.Command, filepath.Separator) {
|
||||
s = append(s, &authInfo.Exec.Command)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// ResolvePaths updates the given refs to be absolute paths, relative to the given base directory
|
||||
func ResolvePaths(refs []*string, base string) error {
|
||||
for _, ref := range refs {
|
||||
// Don't resolve empty paths
|
||||
if len(*ref) > 0 {
|
||||
// Don't resolve absolute paths
|
||||
if !filepath.IsAbs(*ref) {
|
||||
*ref = filepath.Join(base, *ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RelativizePathWithNoBacksteps updates the given refs to be relative paths, relative to the given base directory as long as they do not require backsteps.
|
||||
// Any path requiring a backstep is left as-is as long it is absolute. Any non-absolute path that can't be relativized produces an error
|
||||
func RelativizePathWithNoBacksteps(refs []*string, base string) error {
|
||||
for _, ref := range refs {
|
||||
// Don't relativize empty paths
|
||||
if len(*ref) > 0 {
|
||||
rel, err := MakeRelative(*ref, base)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if we have a backstep, don't mess with the path
|
||||
if strings.HasPrefix(rel, "../") {
|
||||
if filepath.IsAbs(*ref) {
|
||||
continue
|
||||
}
|
||||
|
||||
return fmt.Errorf("%v requires backsteps and is not absolute", *ref)
|
||||
}
|
||||
|
||||
*ref = rel
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func MakeRelative(path, base string) (string, error) {
|
||||
if len(path) > 0 {
|
||||
rel, err := filepath.Rel(base, path)
|
||||
if err != nil {
|
||||
return path, err
|
||||
}
|
||||
return rel, nil
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// deduplicate removes any duplicated values and returns a new slice, keeping the order unchanged
|
||||
func deduplicate(s []string) []string {
|
||||
encountered := map[string]bool{}
|
||||
ret := make([]string, 0)
|
||||
for i := range s {
|
||||
if encountered[s[i]] {
|
||||
continue
|
||||
}
|
||||
encountered[s[i]] = true
|
||||
ret = append(ret, s[i])
|
||||
}
|
||||
return ret
|
||||
}
|
121
e2e/vendor/k8s.io/client-go/tools/clientcmd/merge.go
generated
vendored
Normal file
121
e2e/vendor/k8s.io/client-go/tools/clientcmd/merge.go
generated
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package clientcmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// recursively merges src into dst:
|
||||
// - non-pointer struct fields with any exported fields are recursively merged
|
||||
// - non-pointer struct fields with only unexported fields prefer src if the field is non-zero
|
||||
// - maps are shallow merged with src keys taking priority over dst
|
||||
// - non-zero src fields encountered during recursion that are not maps or structs overwrite and recursion stops
|
||||
func merge[T any](dst, src *T) error {
|
||||
if dst == nil {
|
||||
return fmt.Errorf("cannot merge into nil pointer")
|
||||
}
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
return mergeValues(nil, reflect.ValueOf(dst).Elem(), reflect.ValueOf(src).Elem())
|
||||
}
|
||||
|
||||
func mergeValues(fieldNames []string, dst, src reflect.Value) error {
|
||||
dstType := dst.Type()
|
||||
// no-op if we can't read the src
|
||||
if !src.IsValid() {
|
||||
return nil
|
||||
}
|
||||
// sanity check types match
|
||||
if srcType := src.Type(); dstType != srcType {
|
||||
return fmt.Errorf("cannot merge mismatched types (%s, %s) at %s", dstType, srcType, strings.Join(fieldNames, "."))
|
||||
}
|
||||
|
||||
switch dstType.Kind() {
|
||||
case reflect.Struct:
|
||||
if hasExportedField(dstType) {
|
||||
// recursively merge
|
||||
for i, n := 0, dstType.NumField(); i < n; i++ {
|
||||
if err := mergeValues(append(fieldNames, dstType.Field(i).Name), dst.Field(i), src.Field(i)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if dst.CanSet() {
|
||||
// If all fields are unexported, overwrite with src.
|
||||
// Using src.IsZero() would make more sense but that's not what mergo did.
|
||||
dst.Set(src)
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
if dst.CanSet() && !src.IsZero() {
|
||||
// initialize dst if needed
|
||||
if dst.IsZero() {
|
||||
dst.Set(reflect.MakeMap(dstType))
|
||||
}
|
||||
// shallow-merge overwriting dst keys with src keys
|
||||
for _, mapKey := range src.MapKeys() {
|
||||
dst.SetMapIndex(mapKey, src.MapIndex(mapKey))
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Slice:
|
||||
if dst.CanSet() && src.Len() > 0 {
|
||||
// overwrite dst with non-empty src slice
|
||||
dst.Set(src)
|
||||
}
|
||||
|
||||
case reflect.Pointer:
|
||||
if dst.CanSet() && !src.IsZero() {
|
||||
// overwrite dst with non-zero values for other types
|
||||
if dstType.Elem().Kind() == reflect.Struct {
|
||||
// use struct pointer as-is
|
||||
dst.Set(src)
|
||||
} else {
|
||||
// shallow-copy non-struct pointer (interfaces, primitives, etc)
|
||||
dst.Set(reflect.New(dstType.Elem()))
|
||||
dst.Elem().Set(src.Elem())
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
if dst.CanSet() && !src.IsZero() {
|
||||
// overwrite dst with non-zero values for other types
|
||||
dst.Set(src)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// hasExportedField returns true if the given type has any exported fields,
|
||||
// or if it has any anonymous/embedded struct fields with exported fields
|
||||
func hasExportedField(dstType reflect.Type) bool {
|
||||
for i, n := 0, dstType.NumField(); i < n; i++ {
|
||||
field := dstType.Field(i)
|
||||
if field.Anonymous && field.Type.Kind() == reflect.Struct {
|
||||
if hasExportedField(dstType.Field(i).Type) {
|
||||
return true
|
||||
}
|
||||
} else if len(field.PkgPath) == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
172
e2e/vendor/k8s.io/client-go/tools/clientcmd/merged_client_builder.go
generated
vendored
Normal file
172
e2e/vendor/k8s.io/client-go/tools/clientcmd/merged_client_builder.go
generated
vendored
Normal file
@ -0,0 +1,172 @@
|
||||
/*
|
||||
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 clientcmd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
restclient "k8s.io/client-go/rest"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
// DeferredLoadingClientConfig is a ClientConfig interface that is backed by a client config loader.
|
||||
// It is used in cases where the loading rules may change after you've instantiated them and you want to be sure that
|
||||
// the most recent rules are used. This is useful in cases where you bind flags to loading rule parameters before
|
||||
// the parse happens and you want your calling code to be ignorant of how the values are being mutated to avoid
|
||||
// passing extraneous information down a call stack
|
||||
type DeferredLoadingClientConfig struct {
|
||||
loader ClientConfigLoader
|
||||
overrides *ConfigOverrides
|
||||
fallbackReader io.Reader
|
||||
|
||||
clientConfig ClientConfig
|
||||
loadingLock sync.Mutex
|
||||
|
||||
// provided for testing
|
||||
icc InClusterConfig
|
||||
}
|
||||
|
||||
// InClusterConfig abstracts details of whether the client is running in a cluster for testing.
|
||||
type InClusterConfig interface {
|
||||
ClientConfig
|
||||
Possible() bool
|
||||
}
|
||||
|
||||
// NewNonInteractiveDeferredLoadingClientConfig creates a ClientConfig using the passed context name
|
||||
func NewNonInteractiveDeferredLoadingClientConfig(loader ClientConfigLoader, overrides *ConfigOverrides) ClientConfig {
|
||||
return &DeferredLoadingClientConfig{loader: loader, overrides: overrides, icc: &inClusterClientConfig{overrides: overrides}}
|
||||
}
|
||||
|
||||
// NewInteractiveDeferredLoadingClientConfig creates a ClientConfig using the passed context name and the fallback auth reader
|
||||
func NewInteractiveDeferredLoadingClientConfig(loader ClientConfigLoader, overrides *ConfigOverrides, fallbackReader io.Reader) ClientConfig {
|
||||
return &DeferredLoadingClientConfig{loader: loader, overrides: overrides, icc: &inClusterClientConfig{overrides: overrides}, fallbackReader: fallbackReader}
|
||||
}
|
||||
|
||||
func (config *DeferredLoadingClientConfig) createClientConfig() (ClientConfig, error) {
|
||||
config.loadingLock.Lock()
|
||||
defer config.loadingLock.Unlock()
|
||||
|
||||
if config.clientConfig != nil {
|
||||
return config.clientConfig, nil
|
||||
}
|
||||
mergedConfig, err := config.loader.Load()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var currentContext string
|
||||
if config.overrides != nil {
|
||||
currentContext = config.overrides.CurrentContext
|
||||
}
|
||||
if config.fallbackReader != nil {
|
||||
config.clientConfig = NewInteractiveClientConfig(*mergedConfig, currentContext, config.overrides, config.fallbackReader, config.loader)
|
||||
} else {
|
||||
config.clientConfig = NewNonInteractiveClientConfig(*mergedConfig, currentContext, config.overrides, config.loader)
|
||||
}
|
||||
return config.clientConfig, nil
|
||||
}
|
||||
|
||||
func (config *DeferredLoadingClientConfig) RawConfig() (clientcmdapi.Config, error) {
|
||||
mergedConfig, err := config.createClientConfig()
|
||||
if err != nil {
|
||||
return clientcmdapi.Config{}, err
|
||||
}
|
||||
|
||||
return mergedConfig.RawConfig()
|
||||
}
|
||||
|
||||
// ClientConfig implements ClientConfig
|
||||
func (config *DeferredLoadingClientConfig) ClientConfig() (*restclient.Config, error) {
|
||||
mergedClientConfig, err := config.createClientConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// load the configuration and return on non-empty errors and if the
|
||||
// content differs from the default config
|
||||
mergedConfig, err := mergedClientConfig.ClientConfig()
|
||||
switch {
|
||||
case err != nil:
|
||||
if !IsEmptyConfig(err) {
|
||||
// return on any error except empty config
|
||||
return nil, err
|
||||
}
|
||||
case mergedConfig != nil:
|
||||
// the configuration is valid, but if this is equal to the defaults we should try
|
||||
// in-cluster configuration
|
||||
if !config.loader.IsDefaultConfig(mergedConfig) {
|
||||
return mergedConfig, nil
|
||||
}
|
||||
}
|
||||
|
||||
// check for in-cluster configuration and use it
|
||||
if config.icc.Possible() {
|
||||
klog.V(4).Infof("Using in-cluster configuration")
|
||||
return config.icc.ClientConfig()
|
||||
}
|
||||
|
||||
// return the result of the merged client config
|
||||
return mergedConfig, err
|
||||
}
|
||||
|
||||
// Namespace implements KubeConfig
|
||||
func (config *DeferredLoadingClientConfig) Namespace() (string, bool, error) {
|
||||
mergedKubeConfig, err := config.createClientConfig()
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
ns, overridden, err := mergedKubeConfig.Namespace()
|
||||
// if we get an error and it is not empty config, or if the merged config defined an explicit namespace, or
|
||||
// if in-cluster config is not possible, return immediately
|
||||
if (err != nil && !IsEmptyConfig(err)) || overridden || !config.icc.Possible() {
|
||||
// return on any error except empty config
|
||||
return ns, overridden, err
|
||||
}
|
||||
|
||||
if len(ns) > 0 {
|
||||
// if we got a non-default namespace from the kubeconfig, use it
|
||||
if ns != "default" {
|
||||
return ns, false, nil
|
||||
}
|
||||
|
||||
// if we got a default namespace, determine whether it was explicit or implicit
|
||||
if raw, err := mergedKubeConfig.RawConfig(); err == nil {
|
||||
// determine the current context
|
||||
currentContext := raw.CurrentContext
|
||||
if config.overrides != nil && len(config.overrides.CurrentContext) > 0 {
|
||||
currentContext = config.overrides.CurrentContext
|
||||
}
|
||||
if context := raw.Contexts[currentContext]; context != nil && len(context.Namespace) > 0 {
|
||||
return ns, false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
klog.V(4).Infof("Using in-cluster namespace")
|
||||
|
||||
// allow the namespace from the service account token directory to be used.
|
||||
return config.icc.Namespace()
|
||||
}
|
||||
|
||||
// ConfigAccess implements ClientConfig
|
||||
func (config *DeferredLoadingClientConfig) ConfigAccess() ConfigAccess {
|
||||
return config.loader
|
||||
}
|
263
e2e/vendor/k8s.io/client-go/tools/clientcmd/overrides.go
generated
vendored
Normal file
263
e2e/vendor/k8s.io/client-go/tools/clientcmd/overrides.go
generated
vendored
Normal file
@ -0,0 +1,263 @@
|
||||
/*
|
||||
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 clientcmd
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
// ConfigOverrides holds values that should override whatever information is pulled from the actual Config object. You can't
|
||||
// simply use an actual Config object, because Configs hold maps, but overrides are restricted to "at most one"
|
||||
type ConfigOverrides struct {
|
||||
AuthInfo clientcmdapi.AuthInfo
|
||||
// ClusterDefaults are applied before the configured cluster info is loaded.
|
||||
ClusterDefaults clientcmdapi.Cluster
|
||||
ClusterInfo clientcmdapi.Cluster
|
||||
Context clientcmdapi.Context
|
||||
CurrentContext string
|
||||
Timeout string
|
||||
}
|
||||
|
||||
// ConfigOverrideFlags holds the flag names to be used for binding command line flags. Notice that this structure tightly
|
||||
// corresponds to ConfigOverrides
|
||||
type ConfigOverrideFlags struct {
|
||||
AuthOverrideFlags AuthOverrideFlags
|
||||
ClusterOverrideFlags ClusterOverrideFlags
|
||||
ContextOverrideFlags ContextOverrideFlags
|
||||
CurrentContext FlagInfo
|
||||
Timeout FlagInfo
|
||||
}
|
||||
|
||||
// AuthOverrideFlags holds the flag names to be used for binding command line flags for AuthInfo objects
|
||||
type AuthOverrideFlags struct {
|
||||
ClientCertificate FlagInfo
|
||||
ClientKey FlagInfo
|
||||
Token FlagInfo
|
||||
Impersonate FlagInfo
|
||||
ImpersonateUID FlagInfo
|
||||
ImpersonateGroups FlagInfo
|
||||
Username FlagInfo
|
||||
Password FlagInfo
|
||||
}
|
||||
|
||||
// ContextOverrideFlags holds the flag names to be used for binding command line flags for Cluster objects
|
||||
type ContextOverrideFlags struct {
|
||||
ClusterName FlagInfo
|
||||
AuthInfoName FlagInfo
|
||||
Namespace FlagInfo
|
||||
}
|
||||
|
||||
// ClusterOverride holds the flag names to be used for binding command line flags for Cluster objects
|
||||
type ClusterOverrideFlags struct {
|
||||
APIServer FlagInfo
|
||||
APIVersion FlagInfo
|
||||
CertificateAuthority FlagInfo
|
||||
InsecureSkipTLSVerify FlagInfo
|
||||
TLSServerName FlagInfo
|
||||
ProxyURL FlagInfo
|
||||
DisableCompression FlagInfo
|
||||
}
|
||||
|
||||
// FlagInfo contains information about how to register a flag. This struct is useful if you want to provide a way for an extender to
|
||||
// get back a set of recommended flag names, descriptions, and defaults, but allow for customization by an extender. This makes for
|
||||
// coherent extension, without full prescription
|
||||
type FlagInfo struct {
|
||||
// LongName is the long string for a flag. If this is empty, then the flag will not be bound
|
||||
LongName string
|
||||
// ShortName is the single character for a flag. If this is empty, then there will be no short flag
|
||||
ShortName string
|
||||
// Default is the default value for the flag
|
||||
Default string
|
||||
// Description is the description for the flag
|
||||
Description string
|
||||
}
|
||||
|
||||
// AddSecretAnnotation add secret flag to Annotation.
|
||||
func (f FlagInfo) AddSecretAnnotation(flags *pflag.FlagSet) FlagInfo {
|
||||
flags.SetAnnotation(f.LongName, "classified", []string{"true"})
|
||||
return f
|
||||
}
|
||||
|
||||
// BindStringFlag binds the flag based on the provided info. If LongName == "", nothing is registered
|
||||
func (f FlagInfo) BindStringFlag(flags *pflag.FlagSet, target *string) FlagInfo {
|
||||
// you can't register a flag without a long name
|
||||
if len(f.LongName) > 0 {
|
||||
flags.StringVarP(target, f.LongName, f.ShortName, f.Default, f.Description)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// BindTransformingStringFlag binds the flag based on the provided info. If LongName == "", nothing is registered
|
||||
func (f FlagInfo) BindTransformingStringFlag(flags *pflag.FlagSet, target *string, transformer func(string) (string, error)) FlagInfo {
|
||||
// you can't register a flag without a long name
|
||||
if len(f.LongName) > 0 {
|
||||
flags.VarP(newTransformingStringValue(f.Default, target, transformer), f.LongName, f.ShortName, f.Description)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// BindStringSliceFlag binds the flag based on the provided info. If LongName == "", nothing is registered
|
||||
func (f FlagInfo) BindStringArrayFlag(flags *pflag.FlagSet, target *[]string) FlagInfo {
|
||||
// you can't register a flag without a long name
|
||||
if len(f.LongName) > 0 {
|
||||
sliceVal := []string{}
|
||||
if len(f.Default) > 0 {
|
||||
sliceVal = []string{f.Default}
|
||||
}
|
||||
flags.StringArrayVarP(target, f.LongName, f.ShortName, sliceVal, f.Description)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// BindBoolFlag binds the flag based on the provided info. If LongName == "", nothing is registered
|
||||
func (f FlagInfo) BindBoolFlag(flags *pflag.FlagSet, target *bool) FlagInfo {
|
||||
// you can't register a flag without a long name
|
||||
if len(f.LongName) > 0 {
|
||||
// try to parse Default as a bool. If it fails, assume false
|
||||
boolVal, err := strconv.ParseBool(f.Default)
|
||||
if err != nil {
|
||||
boolVal = false
|
||||
}
|
||||
|
||||
flags.BoolVarP(target, f.LongName, f.ShortName, boolVal, f.Description)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
const (
|
||||
FlagClusterName = "cluster"
|
||||
FlagAuthInfoName = "user"
|
||||
FlagContext = "context"
|
||||
FlagNamespace = "namespace"
|
||||
FlagAPIServer = "server"
|
||||
FlagTLSServerName = "tls-server-name"
|
||||
FlagInsecure = "insecure-skip-tls-verify"
|
||||
FlagCertFile = "client-certificate"
|
||||
FlagKeyFile = "client-key"
|
||||
FlagCAFile = "certificate-authority"
|
||||
FlagEmbedCerts = "embed-certs"
|
||||
FlagBearerToken = "token"
|
||||
FlagImpersonate = "as"
|
||||
FlagImpersonateUID = "as-uid"
|
||||
FlagImpersonateGroup = "as-group"
|
||||
FlagUsername = "username"
|
||||
FlagPassword = "password"
|
||||
FlagTimeout = "request-timeout"
|
||||
FlagProxyURL = "proxy-url"
|
||||
FlagDisableCompression = "disable-compression"
|
||||
)
|
||||
|
||||
// RecommendedConfigOverrideFlags is a convenience method to return recommended flag names prefixed with a string of your choosing
|
||||
func RecommendedConfigOverrideFlags(prefix string) ConfigOverrideFlags {
|
||||
return ConfigOverrideFlags{
|
||||
AuthOverrideFlags: RecommendedAuthOverrideFlags(prefix),
|
||||
ClusterOverrideFlags: RecommendedClusterOverrideFlags(prefix),
|
||||
ContextOverrideFlags: RecommendedContextOverrideFlags(prefix),
|
||||
|
||||
CurrentContext: FlagInfo{prefix + FlagContext, "", "", "The name of the kubeconfig context to use"},
|
||||
Timeout: FlagInfo{prefix + FlagTimeout, "", "0", "The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests."},
|
||||
}
|
||||
}
|
||||
|
||||
// RecommendedAuthOverrideFlags is a convenience method to return recommended flag names prefixed with a string of your choosing
|
||||
func RecommendedAuthOverrideFlags(prefix string) AuthOverrideFlags {
|
||||
return AuthOverrideFlags{
|
||||
ClientCertificate: FlagInfo{prefix + FlagCertFile, "", "", "Path to a client certificate file for TLS"},
|
||||
ClientKey: FlagInfo{prefix + FlagKeyFile, "", "", "Path to a client key file for TLS"},
|
||||
Token: FlagInfo{prefix + FlagBearerToken, "", "", "Bearer token for authentication to the API server"},
|
||||
Impersonate: FlagInfo{prefix + FlagImpersonate, "", "", "Username to impersonate for the operation"},
|
||||
ImpersonateUID: FlagInfo{prefix + FlagImpersonateUID, "", "", "UID to impersonate for the operation"},
|
||||
ImpersonateGroups: FlagInfo{prefix + FlagImpersonateGroup, "", "", "Group to impersonate for the operation, this flag can be repeated to specify multiple groups."},
|
||||
Username: FlagInfo{prefix + FlagUsername, "", "", "Username for basic authentication to the API server"},
|
||||
Password: FlagInfo{prefix + FlagPassword, "", "", "Password for basic authentication to the API server"},
|
||||
}
|
||||
}
|
||||
|
||||
// RecommendedClusterOverrideFlags is a convenience method to return recommended flag names prefixed with a string of your choosing
|
||||
func RecommendedClusterOverrideFlags(prefix string) ClusterOverrideFlags {
|
||||
return ClusterOverrideFlags{
|
||||
APIServer: FlagInfo{prefix + FlagAPIServer, "", "", "The address and port of the Kubernetes API server"},
|
||||
CertificateAuthority: FlagInfo{prefix + FlagCAFile, "", "", "Path to a cert file for the certificate authority"},
|
||||
InsecureSkipTLSVerify: FlagInfo{prefix + FlagInsecure, "", "false", "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure"},
|
||||
TLSServerName: FlagInfo{prefix + FlagTLSServerName, "", "", "If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used."},
|
||||
ProxyURL: FlagInfo{prefix + FlagProxyURL, "", "", "If provided, this URL will be used to connect via proxy"},
|
||||
DisableCompression: FlagInfo{prefix + FlagDisableCompression, "", "", "If true, opt-out of response compression for all requests to the server"},
|
||||
}
|
||||
}
|
||||
|
||||
// RecommendedContextOverrideFlags is a convenience method to return recommended flag names prefixed with a string of your choosing
|
||||
func RecommendedContextOverrideFlags(prefix string) ContextOverrideFlags {
|
||||
return ContextOverrideFlags{
|
||||
ClusterName: FlagInfo{prefix + FlagClusterName, "", "", "The name of the kubeconfig cluster to use"},
|
||||
AuthInfoName: FlagInfo{prefix + FlagAuthInfoName, "", "", "The name of the kubeconfig user to use"},
|
||||
Namespace: FlagInfo{prefix + FlagNamespace, "n", "", "If present, the namespace scope for this CLI request"},
|
||||
}
|
||||
}
|
||||
|
||||
// BindOverrideFlags is a convenience method to bind the specified flags to their associated variables
|
||||
func BindOverrideFlags(overrides *ConfigOverrides, flags *pflag.FlagSet, flagNames ConfigOverrideFlags) {
|
||||
BindAuthInfoFlags(&overrides.AuthInfo, flags, flagNames.AuthOverrideFlags)
|
||||
BindClusterFlags(&overrides.ClusterInfo, flags, flagNames.ClusterOverrideFlags)
|
||||
BindContextFlags(&overrides.Context, flags, flagNames.ContextOverrideFlags)
|
||||
flagNames.CurrentContext.BindStringFlag(flags, &overrides.CurrentContext)
|
||||
flagNames.Timeout.BindStringFlag(flags, &overrides.Timeout)
|
||||
}
|
||||
|
||||
// BindAuthInfoFlags is a convenience method to bind the specified flags to their associated variables
|
||||
func BindAuthInfoFlags(authInfo *clientcmdapi.AuthInfo, flags *pflag.FlagSet, flagNames AuthOverrideFlags) {
|
||||
flagNames.ClientCertificate.BindStringFlag(flags, &authInfo.ClientCertificate).AddSecretAnnotation(flags)
|
||||
flagNames.ClientKey.BindStringFlag(flags, &authInfo.ClientKey).AddSecretAnnotation(flags)
|
||||
flagNames.Token.BindStringFlag(flags, &authInfo.Token).AddSecretAnnotation(flags)
|
||||
flagNames.Impersonate.BindStringFlag(flags, &authInfo.Impersonate).AddSecretAnnotation(flags)
|
||||
flagNames.ImpersonateUID.BindStringFlag(flags, &authInfo.ImpersonateUID).AddSecretAnnotation(flags)
|
||||
flagNames.ImpersonateGroups.BindStringArrayFlag(flags, &authInfo.ImpersonateGroups).AddSecretAnnotation(flags)
|
||||
flagNames.Username.BindStringFlag(flags, &authInfo.Username).AddSecretAnnotation(flags)
|
||||
flagNames.Password.BindStringFlag(flags, &authInfo.Password).AddSecretAnnotation(flags)
|
||||
}
|
||||
|
||||
// BindClusterFlags is a convenience method to bind the specified flags to their associated variables
|
||||
func BindClusterFlags(clusterInfo *clientcmdapi.Cluster, flags *pflag.FlagSet, flagNames ClusterOverrideFlags) {
|
||||
flagNames.APIServer.BindStringFlag(flags, &clusterInfo.Server)
|
||||
flagNames.CertificateAuthority.BindStringFlag(flags, &clusterInfo.CertificateAuthority)
|
||||
flagNames.InsecureSkipTLSVerify.BindBoolFlag(flags, &clusterInfo.InsecureSkipTLSVerify)
|
||||
flagNames.TLSServerName.BindStringFlag(flags, &clusterInfo.TLSServerName)
|
||||
flagNames.ProxyURL.BindStringFlag(flags, &clusterInfo.ProxyURL)
|
||||
flagNames.DisableCompression.BindBoolFlag(flags, &clusterInfo.DisableCompression)
|
||||
}
|
||||
|
||||
// BindFlags is a convenience method to bind the specified flags to their associated variables
|
||||
func BindContextFlags(contextInfo *clientcmdapi.Context, flags *pflag.FlagSet, flagNames ContextOverrideFlags) {
|
||||
flagNames.ClusterName.BindStringFlag(flags, &contextInfo.Cluster)
|
||||
flagNames.AuthInfoName.BindStringFlag(flags, &contextInfo.AuthInfo)
|
||||
flagNames.Namespace.BindTransformingStringFlag(flags, &contextInfo.Namespace, RemoveNamespacesPrefix)
|
||||
}
|
||||
|
||||
// RemoveNamespacesPrefix is a transformer that strips "ns/", "namespace/" and "namespaces/" prefixes case-insensitively
|
||||
func RemoveNamespacesPrefix(value string) (string, error) {
|
||||
for _, prefix := range []string{"namespaces/", "namespace/", "ns/"} {
|
||||
if len(value) > len(prefix) && strings.EqualFold(value[0:len(prefix)], prefix) {
|
||||
value = value[len(prefix):]
|
||||
break
|
||||
}
|
||||
}
|
||||
return value, nil
|
||||
}
|
371
e2e/vendor/k8s.io/client-go/tools/clientcmd/validation.go
generated
vendored
Normal file
371
e2e/vendor/k8s.io/client-go/tools/clientcmd/validation.go
generated
vendored
Normal file
@ -0,0 +1,371 @@
|
||||
/*
|
||||
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 clientcmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoContext = errors.New("no context chosen")
|
||||
ErrEmptyConfig = NewEmptyConfigError("no configuration has been provided, try setting KUBERNETES_MASTER environment variable")
|
||||
// message is for consistency with old behavior
|
||||
ErrEmptyCluster = errors.New("cluster has no server defined")
|
||||
)
|
||||
|
||||
// NewEmptyConfigError returns an error wrapping the given message which IsEmptyConfig() will recognize as an empty config error
|
||||
func NewEmptyConfigError(message string) error {
|
||||
return &errEmptyConfig{message}
|
||||
}
|
||||
|
||||
type errEmptyConfig struct {
|
||||
message string
|
||||
}
|
||||
|
||||
func (e *errEmptyConfig) Error() string {
|
||||
return e.message
|
||||
}
|
||||
|
||||
type errContextNotFound struct {
|
||||
ContextName string
|
||||
}
|
||||
|
||||
func (e *errContextNotFound) Error() string {
|
||||
return fmt.Sprintf("context was not found for specified context: %v", e.ContextName)
|
||||
}
|
||||
|
||||
// IsContextNotFound returns a boolean indicating whether the error is known to
|
||||
// report that a context was not found
|
||||
func IsContextNotFound(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
if _, ok := err.(*errContextNotFound); ok || err == ErrNoContext {
|
||||
return true
|
||||
}
|
||||
return strings.Contains(err.Error(), "context was not found for specified context")
|
||||
}
|
||||
|
||||
// IsEmptyConfig returns true if the provided error indicates the provided configuration
|
||||
// is empty.
|
||||
func IsEmptyConfig(err error) bool {
|
||||
switch t := err.(type) {
|
||||
case errConfigurationInvalid:
|
||||
if len(t) != 1 {
|
||||
return false
|
||||
}
|
||||
_, ok := t[0].(*errEmptyConfig)
|
||||
return ok
|
||||
}
|
||||
_, ok := err.(*errEmptyConfig)
|
||||
return ok
|
||||
}
|
||||
|
||||
// errConfigurationInvalid is a set of errors indicating the configuration is invalid.
|
||||
type errConfigurationInvalid []error
|
||||
|
||||
// errConfigurationInvalid implements error and Aggregate
|
||||
var _ error = errConfigurationInvalid{}
|
||||
var _ utilerrors.Aggregate = errConfigurationInvalid{}
|
||||
|
||||
func newErrConfigurationInvalid(errs []error) error {
|
||||
switch len(errs) {
|
||||
case 0:
|
||||
return nil
|
||||
default:
|
||||
return errConfigurationInvalid(errs)
|
||||
}
|
||||
}
|
||||
|
||||
// Error implements the error interface
|
||||
func (e errConfigurationInvalid) Error() string {
|
||||
return fmt.Sprintf("invalid configuration: %v", utilerrors.NewAggregate(e).Error())
|
||||
}
|
||||
|
||||
// Errors implements the utilerrors.Aggregate interface
|
||||
func (e errConfigurationInvalid) Errors() []error {
|
||||
return e
|
||||
}
|
||||
|
||||
// Is implements the utilerrors.Aggregate interface
|
||||
func (e errConfigurationInvalid) Is(target error) bool {
|
||||
return e.visit(func(err error) bool {
|
||||
return errors.Is(err, target)
|
||||
})
|
||||
}
|
||||
|
||||
func (e errConfigurationInvalid) visit(f func(err error) bool) bool {
|
||||
for _, err := range e {
|
||||
switch err := err.(type) {
|
||||
case errConfigurationInvalid:
|
||||
if match := err.visit(f); match {
|
||||
return match
|
||||
}
|
||||
case utilerrors.Aggregate:
|
||||
for _, nestedErr := range err.Errors() {
|
||||
if match := f(nestedErr); match {
|
||||
return match
|
||||
}
|
||||
}
|
||||
default:
|
||||
if match := f(err); match {
|
||||
return match
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsConfigurationInvalid returns true if the provided error indicates the configuration is invalid.
|
||||
func IsConfigurationInvalid(err error) bool {
|
||||
switch err.(type) {
|
||||
case *errContextNotFound, errConfigurationInvalid:
|
||||
return true
|
||||
}
|
||||
return IsContextNotFound(err)
|
||||
}
|
||||
|
||||
// Validate checks for errors in the Config. It does not return early so that it can find as many errors as possible.
|
||||
func Validate(config clientcmdapi.Config) error {
|
||||
validationErrors := make([]error, 0)
|
||||
|
||||
if clientcmdapi.IsConfigEmpty(&config) {
|
||||
return newErrConfigurationInvalid([]error{ErrEmptyConfig})
|
||||
}
|
||||
|
||||
if len(config.CurrentContext) != 0 {
|
||||
if _, exists := config.Contexts[config.CurrentContext]; !exists {
|
||||
validationErrors = append(validationErrors, &errContextNotFound{config.CurrentContext})
|
||||
}
|
||||
}
|
||||
|
||||
for contextName, context := range config.Contexts {
|
||||
validationErrors = append(validationErrors, validateContext(contextName, *context, config)...)
|
||||
}
|
||||
|
||||
for authInfoName, authInfo := range config.AuthInfos {
|
||||
validationErrors = append(validationErrors, validateAuthInfo(authInfoName, *authInfo)...)
|
||||
}
|
||||
|
||||
for clusterName, clusterInfo := range config.Clusters {
|
||||
validationErrors = append(validationErrors, validateClusterInfo(clusterName, *clusterInfo)...)
|
||||
}
|
||||
|
||||
return newErrConfigurationInvalid(validationErrors)
|
||||
}
|
||||
|
||||
// ConfirmUsable looks a particular context and determines if that particular part of the config is useable. There might still be errors in the config,
|
||||
// but no errors in the sections requested or referenced. It does not return early so that it can find as many errors as possible.
|
||||
func ConfirmUsable(config clientcmdapi.Config, passedContextName string) error {
|
||||
validationErrors := make([]error, 0)
|
||||
|
||||
if clientcmdapi.IsConfigEmpty(&config) {
|
||||
return newErrConfigurationInvalid([]error{ErrEmptyConfig})
|
||||
}
|
||||
|
||||
var contextName string
|
||||
if len(passedContextName) != 0 {
|
||||
contextName = passedContextName
|
||||
} else {
|
||||
contextName = config.CurrentContext
|
||||
}
|
||||
|
||||
if len(contextName) == 0 {
|
||||
return ErrNoContext
|
||||
}
|
||||
|
||||
context, exists := config.Contexts[contextName]
|
||||
if !exists {
|
||||
validationErrors = append(validationErrors, &errContextNotFound{contextName})
|
||||
}
|
||||
|
||||
if exists {
|
||||
validationErrors = append(validationErrors, validateContext(contextName, *context, config)...)
|
||||
|
||||
// Default to empty users and clusters and let the validation function report an error.
|
||||
authInfo := config.AuthInfos[context.AuthInfo]
|
||||
if authInfo == nil {
|
||||
authInfo = &clientcmdapi.AuthInfo{}
|
||||
}
|
||||
validationErrors = append(validationErrors, validateAuthInfo(context.AuthInfo, *authInfo)...)
|
||||
|
||||
cluster := config.Clusters[context.Cluster]
|
||||
if cluster == nil {
|
||||
cluster = &clientcmdapi.Cluster{}
|
||||
}
|
||||
validationErrors = append(validationErrors, validateClusterInfo(context.Cluster, *cluster)...)
|
||||
}
|
||||
|
||||
return newErrConfigurationInvalid(validationErrors)
|
||||
}
|
||||
|
||||
// validateClusterInfo looks for conflicts and errors in the cluster info
|
||||
func validateClusterInfo(clusterName string, clusterInfo clientcmdapi.Cluster) []error {
|
||||
validationErrors := make([]error, 0)
|
||||
|
||||
emptyCluster := clientcmdapi.NewCluster()
|
||||
if reflect.DeepEqual(*emptyCluster, clusterInfo) {
|
||||
return []error{ErrEmptyCluster}
|
||||
}
|
||||
|
||||
if len(clusterInfo.Server) == 0 {
|
||||
if len(clusterName) == 0 {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("default cluster has no server defined"))
|
||||
} else {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("no server found for cluster %q", clusterName))
|
||||
}
|
||||
}
|
||||
if proxyURL := clusterInfo.ProxyURL; proxyURL != "" {
|
||||
if _, err := parseProxyURL(proxyURL); err != nil {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("invalid 'proxy-url' %q for cluster %q: %w", proxyURL, clusterName, err))
|
||||
}
|
||||
}
|
||||
// Make sure CA data and CA file aren't both specified
|
||||
if len(clusterInfo.CertificateAuthority) != 0 && len(clusterInfo.CertificateAuthorityData) != 0 {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("certificate-authority-data and certificate-authority are both specified for %v. certificate-authority-data will override.", clusterName))
|
||||
}
|
||||
if len(clusterInfo.CertificateAuthority) != 0 {
|
||||
clientCertCA, err := os.Open(clusterInfo.CertificateAuthority)
|
||||
if err != nil {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("unable to read certificate-authority %v for %v due to %w", clusterInfo.CertificateAuthority, clusterName, err))
|
||||
} else {
|
||||
defer clientCertCA.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return validationErrors
|
||||
}
|
||||
|
||||
// validateAuthInfo looks for conflicts and errors in the auth info
|
||||
func validateAuthInfo(authInfoName string, authInfo clientcmdapi.AuthInfo) []error {
|
||||
validationErrors := make([]error, 0)
|
||||
|
||||
usingAuthPath := false
|
||||
methods := make([]string, 0, 3)
|
||||
if len(authInfo.Token) != 0 {
|
||||
methods = append(methods, "token")
|
||||
}
|
||||
if len(authInfo.Username) != 0 || len(authInfo.Password) != 0 {
|
||||
methods = append(methods, "basicAuth")
|
||||
}
|
||||
|
||||
if len(authInfo.ClientCertificate) != 0 || len(authInfo.ClientCertificateData) != 0 {
|
||||
// Make sure cert data and file aren't both specified
|
||||
if len(authInfo.ClientCertificate) != 0 && len(authInfo.ClientCertificateData) != 0 {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("client-cert-data and client-cert are both specified for %v. client-cert-data will override.", authInfoName))
|
||||
}
|
||||
// Make sure key data and file aren't both specified
|
||||
if len(authInfo.ClientKey) != 0 && len(authInfo.ClientKeyData) != 0 {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("client-key-data and client-key are both specified for %v; client-key-data will override", authInfoName))
|
||||
}
|
||||
// Make sure a key is specified
|
||||
if len(authInfo.ClientKey) == 0 && len(authInfo.ClientKeyData) == 0 {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("client-key-data or client-key must be specified for %v to use the clientCert authentication method.", authInfoName))
|
||||
}
|
||||
|
||||
if len(authInfo.ClientCertificate) != 0 {
|
||||
clientCertFile, err := os.Open(authInfo.ClientCertificate)
|
||||
if err != nil {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("unable to read client-cert %v for %v due to %w", authInfo.ClientCertificate, authInfoName, err))
|
||||
} else {
|
||||
defer clientCertFile.Close()
|
||||
}
|
||||
}
|
||||
if len(authInfo.ClientKey) != 0 {
|
||||
clientKeyFile, err := os.Open(authInfo.ClientKey)
|
||||
if err != nil {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("unable to read client-key %v for %v due to %w", authInfo.ClientKey, authInfoName, err))
|
||||
} else {
|
||||
defer clientKeyFile.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if authInfo.Exec != nil {
|
||||
if authInfo.AuthProvider != nil {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("authProvider cannot be provided in combination with an exec plugin for %s", authInfoName))
|
||||
}
|
||||
if len(authInfo.Exec.Command) == 0 {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("command must be specified for %v to use exec authentication plugin", authInfoName))
|
||||
}
|
||||
if len(authInfo.Exec.APIVersion) == 0 {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("apiVersion must be specified for %v to use exec authentication plugin", authInfoName))
|
||||
}
|
||||
for _, v := range authInfo.Exec.Env {
|
||||
if len(v.Name) == 0 {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("env variable name must be specified for %v to use exec authentication plugin", authInfoName))
|
||||
}
|
||||
}
|
||||
switch authInfo.Exec.InteractiveMode {
|
||||
case "":
|
||||
validationErrors = append(validationErrors, fmt.Errorf("interactiveMode must be specified for %v to use exec authentication plugin", authInfoName))
|
||||
case clientcmdapi.NeverExecInteractiveMode, clientcmdapi.IfAvailableExecInteractiveMode, clientcmdapi.AlwaysExecInteractiveMode:
|
||||
// These are valid
|
||||
default:
|
||||
validationErrors = append(validationErrors, fmt.Errorf("invalid interactiveMode for %v: %q", authInfoName, authInfo.Exec.InteractiveMode))
|
||||
}
|
||||
}
|
||||
|
||||
// authPath also provides information for the client to identify the server, so allow multiple auth methods in that case
|
||||
if (len(methods) > 1) && (!usingAuthPath) {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("more than one authentication method found for %v; found %v, only one is allowed", authInfoName, methods))
|
||||
}
|
||||
|
||||
// ImpersonateUID, ImpersonateGroups or ImpersonateUserExtra should be requested with a user
|
||||
if (len(authInfo.ImpersonateUID) > 0 || len(authInfo.ImpersonateGroups) > 0 || len(authInfo.ImpersonateUserExtra) > 0) && (len(authInfo.Impersonate) == 0) {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("requesting uid, groups or user-extra for %v without impersonating a user", authInfoName))
|
||||
}
|
||||
return validationErrors
|
||||
}
|
||||
|
||||
// validateContext looks for errors in the context. It is not transitive, so errors in the reference authInfo or cluster configs are not included in this return
|
||||
func validateContext(contextName string, context clientcmdapi.Context, config clientcmdapi.Config) []error {
|
||||
validationErrors := make([]error, 0)
|
||||
|
||||
if len(contextName) == 0 {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("empty context name for %#v is not allowed", context))
|
||||
}
|
||||
|
||||
if len(context.AuthInfo) == 0 {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("user was not specified for context %q", contextName))
|
||||
} else if _, exists := config.AuthInfos[context.AuthInfo]; !exists {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("user %q was not found for context %q", context.AuthInfo, contextName))
|
||||
}
|
||||
|
||||
if len(context.Cluster) == 0 {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("cluster was not specified for context %q", contextName))
|
||||
} else if _, exists := config.Clusters[context.Cluster]; !exists {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("cluster %q was not found for context %q", context.Cluster, contextName))
|
||||
}
|
||||
|
||||
if len(context.Namespace) != 0 {
|
||||
if len(validation.IsDNS1123Label(context.Namespace)) != 0 {
|
||||
validationErrors = append(validationErrors, fmt.Errorf("namespace %q for context %q does not conform to the kubernetes DNS_LABEL rules", context.Namespace, contextName))
|
||||
}
|
||||
}
|
||||
|
||||
return validationErrors
|
||||
}
|
10
e2e/vendor/k8s.io/client-go/tools/events/OWNERS
generated
vendored
Normal file
10
e2e/vendor/k8s.io/client-go/tools/events/OWNERS
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- sig-instrumentation-approvers
|
||||
- wojtek-t
|
||||
reviewers:
|
||||
- sig-instrumentation-reviewers
|
||||
- wojtek-t
|
||||
emeritus_approvers:
|
||||
- yastij
|
19
e2e/vendor/k8s.io/client-go/tools/events/doc.go
generated
vendored
Normal file
19
e2e/vendor/k8s.io/client-go/tools/events/doc.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
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 events has all client logic for recording and reporting
|
||||
// "k8s.io/api/events/v1".Event events.
|
||||
package events // import "k8s.io/client-go/tools/events"
|
457
e2e/vendor/k8s.io/client-go/tools/events/event_broadcaster.go
generated
vendored
Normal file
457
e2e/vendor/k8s.io/client-go/tools/events/event_broadcaster.go
generated
vendored
Normal file
@ -0,0 +1,457 @@
|
||||
/*
|
||||
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 events
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
eventsv1 "k8s.io/api/events/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
typedv1core "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
typedeventsv1 "k8s.io/client-go/kubernetes/typed/events/v1"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/tools/record/util"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
const (
|
||||
maxTriesPerEvent = 12
|
||||
finishTime = 6 * time.Minute
|
||||
refreshTime = 30 * time.Minute
|
||||
maxQueuedEvents = 1000
|
||||
)
|
||||
|
||||
var defaultSleepDuration = 10 * time.Second
|
||||
|
||||
// TODO: validate impact of copying and investigate hashing
|
||||
type eventKey struct {
|
||||
eventType string
|
||||
action string
|
||||
reason string
|
||||
reportingController string
|
||||
reportingInstance string
|
||||
regarding corev1.ObjectReference
|
||||
related corev1.ObjectReference
|
||||
}
|
||||
|
||||
type eventBroadcasterImpl struct {
|
||||
*watch.Broadcaster
|
||||
mu sync.Mutex
|
||||
eventCache map[eventKey]*eventsv1.Event
|
||||
sleepDuration time.Duration
|
||||
sink EventSink
|
||||
}
|
||||
|
||||
// EventSinkImpl wraps EventsV1Interface to implement EventSink.
|
||||
// TODO: this makes it easier for testing purpose and masks the logic of performing API calls.
|
||||
// Note that rollbacking to raw clientset should also be transparent.
|
||||
type EventSinkImpl struct {
|
||||
Interface typedeventsv1.EventsV1Interface
|
||||
}
|
||||
|
||||
// Create takes the representation of a event and creates it. Returns the server's representation of the event, and an error, if there is any.
|
||||
func (e *EventSinkImpl) Create(ctx context.Context, event *eventsv1.Event) (*eventsv1.Event, error) {
|
||||
if event.Namespace == "" {
|
||||
return nil, fmt.Errorf("can't create an event with empty namespace")
|
||||
}
|
||||
return e.Interface.Events(event.Namespace).Create(ctx, event, metav1.CreateOptions{})
|
||||
}
|
||||
|
||||
// Update takes the representation of a event and updates it. Returns the server's representation of the event, and an error, if there is any.
|
||||
func (e *EventSinkImpl) Update(ctx context.Context, event *eventsv1.Event) (*eventsv1.Event, error) {
|
||||
if event.Namespace == "" {
|
||||
return nil, fmt.Errorf("can't update an event with empty namespace")
|
||||
}
|
||||
return e.Interface.Events(event.Namespace).Update(ctx, event, metav1.UpdateOptions{})
|
||||
}
|
||||
|
||||
// Patch applies the patch and returns the patched event, and an error, if there is any.
|
||||
func (e *EventSinkImpl) Patch(ctx context.Context, event *eventsv1.Event, data []byte) (*eventsv1.Event, error) {
|
||||
if event.Namespace == "" {
|
||||
return nil, fmt.Errorf("can't patch an event with empty namespace")
|
||||
}
|
||||
return e.Interface.Events(event.Namespace).Patch(ctx, event.Name, types.StrategicMergePatchType, data, metav1.PatchOptions{})
|
||||
}
|
||||
|
||||
// NewBroadcaster Creates a new event broadcaster.
|
||||
func NewBroadcaster(sink EventSink) EventBroadcaster {
|
||||
return newBroadcaster(sink, defaultSleepDuration, map[eventKey]*eventsv1.Event{})
|
||||
}
|
||||
|
||||
// NewBroadcasterForTest Creates a new event broadcaster for test purposes.
|
||||
func newBroadcaster(sink EventSink, sleepDuration time.Duration, eventCache map[eventKey]*eventsv1.Event) EventBroadcaster {
|
||||
return &eventBroadcasterImpl{
|
||||
Broadcaster: watch.NewBroadcaster(maxQueuedEvents, watch.DropIfChannelFull),
|
||||
eventCache: eventCache,
|
||||
sleepDuration: sleepDuration,
|
||||
sink: sink,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *eventBroadcasterImpl) Shutdown() {
|
||||
e.Broadcaster.Shutdown()
|
||||
}
|
||||
|
||||
// refreshExistingEventSeries refresh events TTL
|
||||
func (e *eventBroadcasterImpl) refreshExistingEventSeries(ctx context.Context) {
|
||||
// TODO: Investigate whether lock contention won't be a problem
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
for isomorphicKey, event := range e.eventCache {
|
||||
if event.Series != nil {
|
||||
if recordedEvent, retry := recordEvent(ctx, e.sink, event); !retry {
|
||||
if recordedEvent != nil {
|
||||
e.eventCache[isomorphicKey] = recordedEvent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// finishSeries checks if a series has ended and either:
|
||||
// - write final count to the apiserver
|
||||
// - delete a singleton event (i.e. series field is nil) from the cache
|
||||
func (e *eventBroadcasterImpl) finishSeries(ctx context.Context) {
|
||||
// TODO: Investigate whether lock contention won't be a problem
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
for isomorphicKey, event := range e.eventCache {
|
||||
eventSerie := event.Series
|
||||
if eventSerie != nil {
|
||||
if eventSerie.LastObservedTime.Time.Before(time.Now().Add(-finishTime)) {
|
||||
if _, retry := recordEvent(ctx, e.sink, event); !retry {
|
||||
delete(e.eventCache, isomorphicKey)
|
||||
}
|
||||
}
|
||||
} else if event.EventTime.Time.Before(time.Now().Add(-finishTime)) {
|
||||
delete(e.eventCache, isomorphicKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewRecorder returns an EventRecorder that records events with the given event source.
|
||||
func (e *eventBroadcasterImpl) NewRecorder(scheme *runtime.Scheme, reportingController string) EventRecorderLogger {
|
||||
hostname, _ := os.Hostname()
|
||||
reportingInstance := reportingController + "-" + hostname
|
||||
return &recorderImplLogger{recorderImpl: &recorderImpl{scheme, reportingController, reportingInstance, e.Broadcaster, clock.RealClock{}}, logger: klog.Background()}
|
||||
}
|
||||
|
||||
func (e *eventBroadcasterImpl) recordToSink(ctx context.Context, event *eventsv1.Event, clock clock.Clock) {
|
||||
// Make a copy before modification, because there could be multiple listeners.
|
||||
eventCopy := event.DeepCopy()
|
||||
go func() {
|
||||
evToRecord := func() *eventsv1.Event {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
eventKey := getKey(eventCopy)
|
||||
isomorphicEvent, isIsomorphic := e.eventCache[eventKey]
|
||||
if isIsomorphic {
|
||||
if isomorphicEvent.Series != nil {
|
||||
isomorphicEvent.Series.Count++
|
||||
isomorphicEvent.Series.LastObservedTime = metav1.MicroTime{Time: clock.Now()}
|
||||
return nil
|
||||
}
|
||||
isomorphicEvent.Series = &eventsv1.EventSeries{
|
||||
Count: 2,
|
||||
LastObservedTime: metav1.MicroTime{Time: clock.Now()},
|
||||
}
|
||||
// Make a copy of the Event to make sure that recording it
|
||||
// doesn't mess with the object stored in cache.
|
||||
return isomorphicEvent.DeepCopy()
|
||||
}
|
||||
e.eventCache[eventKey] = eventCopy
|
||||
// Make a copy of the Event to make sure that recording it doesn't
|
||||
// mess with the object stored in cache.
|
||||
return eventCopy.DeepCopy()
|
||||
}()
|
||||
if evToRecord != nil {
|
||||
// TODO: Add a metric counting the number of recording attempts
|
||||
e.attemptRecording(ctx, evToRecord)
|
||||
// We don't want the new recorded Event to be reflected in the
|
||||
// client's cache because server-side mutations could mess with the
|
||||
// aggregation mechanism used by the client.
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (e *eventBroadcasterImpl) attemptRecording(ctx context.Context, event *eventsv1.Event) {
|
||||
tries := 0
|
||||
for {
|
||||
if _, retry := recordEvent(ctx, e.sink, event); !retry {
|
||||
return
|
||||
}
|
||||
tries++
|
||||
if tries >= maxTriesPerEvent {
|
||||
klog.FromContext(ctx).Error(nil, "Unable to write event (retry limit exceeded!)", "event", event)
|
||||
return
|
||||
}
|
||||
// Randomize sleep so that various clients won't all be
|
||||
// synced up if the master goes down. Give up when
|
||||
// the context is canceled.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(wait.Jitter(e.sleepDuration, 0.25)):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func recordEvent(ctx context.Context, sink EventSink, event *eventsv1.Event) (*eventsv1.Event, bool) {
|
||||
var newEvent *eventsv1.Event
|
||||
var err error
|
||||
isEventSeries := event.Series != nil
|
||||
if isEventSeries {
|
||||
patch, patchBytesErr := createPatchBytesForSeries(event)
|
||||
if patchBytesErr != nil {
|
||||
klog.FromContext(ctx).Error(patchBytesErr, "Unable to calculate diff, no merge is possible")
|
||||
return nil, false
|
||||
}
|
||||
newEvent, err = sink.Patch(ctx, event, patch)
|
||||
}
|
||||
// Update can fail because the event may have been removed and it no longer exists.
|
||||
if !isEventSeries || (isEventSeries && util.IsKeyNotFoundError(err)) {
|
||||
// Making sure that ResourceVersion is empty on creation
|
||||
event.ResourceVersion = ""
|
||||
newEvent, err = sink.Create(ctx, event)
|
||||
}
|
||||
if err == nil {
|
||||
return newEvent, false
|
||||
}
|
||||
// If we can't contact the server, then hold everything while we keep trying.
|
||||
// Otherwise, something about the event is malformed and we should abandon it.
|
||||
switch err.(type) {
|
||||
case *restclient.RequestConstructionError:
|
||||
// We will construct the request the same next time, so don't keep trying.
|
||||
klog.FromContext(ctx).Error(err, "Unable to construct event (will not retry!)", "event", event)
|
||||
return nil, false
|
||||
case *errors.StatusError:
|
||||
if errors.IsAlreadyExists(err) {
|
||||
// If we tried to create an Event from an EventSerie, it means that
|
||||
// the original Patch request failed because the Event we were
|
||||
// trying to patch didn't exist. If the creation failed because the
|
||||
// Event now exists, it is safe to retry. This occurs when a new
|
||||
// Event is emitted twice in a very short period of time.
|
||||
if isEventSeries {
|
||||
return nil, true
|
||||
}
|
||||
klog.FromContext(ctx).V(5).Info("Server rejected event (will not retry!)", "event", event, "err", err)
|
||||
} else {
|
||||
klog.FromContext(ctx).Error(err, "Server rejected event (will not retry!)", "event", event)
|
||||
}
|
||||
return nil, false
|
||||
case *errors.UnexpectedObjectError:
|
||||
// We don't expect this; it implies the server's response didn't match a
|
||||
// known pattern. Go ahead and retry.
|
||||
default:
|
||||
// This case includes actual http transport errors. Go ahead and retry.
|
||||
}
|
||||
klog.FromContext(ctx).Error(err, "Unable to write event (may retry after sleeping)")
|
||||
return nil, true
|
||||
}
|
||||
|
||||
func createPatchBytesForSeries(event *eventsv1.Event) ([]byte, error) {
|
||||
oldEvent := event.DeepCopy()
|
||||
oldEvent.Series = nil
|
||||
oldData, err := json.Marshal(oldEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newData, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return strategicpatch.CreateTwoWayMergePatch(oldData, newData, eventsv1.Event{})
|
||||
}
|
||||
|
||||
func getKey(event *eventsv1.Event) eventKey {
|
||||
key := eventKey{
|
||||
eventType: event.Type,
|
||||
action: event.Action,
|
||||
reason: event.Reason,
|
||||
reportingController: event.ReportingController,
|
||||
reportingInstance: event.ReportingInstance,
|
||||
regarding: event.Regarding,
|
||||
}
|
||||
if event.Related != nil {
|
||||
key.related = *event.Related
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
// StartStructuredLogging starts sending events received from this EventBroadcaster to the structured logging function.
|
||||
// The return value can be ignored or used to stop recording, if desired.
|
||||
// TODO: this function should also return an error.
|
||||
//
|
||||
// Deprecated: use StartLogging instead.
|
||||
func (e *eventBroadcasterImpl) StartStructuredLogging(verbosity klog.Level) func() {
|
||||
logger := klog.Background().V(int(verbosity))
|
||||
stopWatcher, err := e.StartLogging(logger)
|
||||
if err != nil {
|
||||
logger.Error(err, "Failed to start event watcher")
|
||||
return func() {}
|
||||
}
|
||||
return stopWatcher
|
||||
}
|
||||
|
||||
// StartLogging starts sending events received from this EventBroadcaster to the structured logger.
|
||||
// To adjust verbosity, use the logger's V method (i.e. pass `logger.V(3)` instead of `logger`).
|
||||
// The returned function can be ignored or used to stop recording, if desired.
|
||||
func (e *eventBroadcasterImpl) StartLogging(logger klog.Logger) (func(), error) {
|
||||
return e.StartEventWatcher(
|
||||
func(obj runtime.Object) {
|
||||
event, ok := obj.(*eventsv1.Event)
|
||||
if !ok {
|
||||
logger.Error(nil, "unexpected type, expected eventsv1.Event")
|
||||
return
|
||||
}
|
||||
logger.Info("Event occurred", "object", klog.KRef(event.Regarding.Namespace, event.Regarding.Name), "kind", event.Regarding.Kind, "apiVersion", event.Regarding.APIVersion, "type", event.Type, "reason", event.Reason, "action", event.Action, "note", event.Note)
|
||||
})
|
||||
}
|
||||
|
||||
// StartEventWatcher starts sending events received from this EventBroadcaster to the given event handler function.
|
||||
// The return value is used to stop recording
|
||||
func (e *eventBroadcasterImpl) StartEventWatcher(eventHandler func(event runtime.Object)) (func(), error) {
|
||||
watcher, err := e.Watch()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go func() {
|
||||
defer utilruntime.HandleCrash()
|
||||
for {
|
||||
watchEvent, ok := <-watcher.ResultChan()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
eventHandler(watchEvent.Object)
|
||||
}
|
||||
}()
|
||||
return watcher.Stop, nil
|
||||
}
|
||||
|
||||
func (e *eventBroadcasterImpl) startRecordingEvents(ctx context.Context) error {
|
||||
eventHandler := func(obj runtime.Object) {
|
||||
event, ok := obj.(*eventsv1.Event)
|
||||
if !ok {
|
||||
klog.FromContext(ctx).Error(nil, "unexpected type, expected eventsv1.Event")
|
||||
return
|
||||
}
|
||||
e.recordToSink(ctx, event, clock.RealClock{})
|
||||
}
|
||||
stopWatcher, err := e.StartEventWatcher(eventHandler)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
stopWatcher()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartRecordingToSink starts sending events received from the specified eventBroadcaster to the given sink.
|
||||
// Deprecated: use StartRecordingToSinkWithContext instead.
|
||||
func (e *eventBroadcasterImpl) StartRecordingToSink(stopCh <-chan struct{}) {
|
||||
err := e.StartRecordingToSinkWithContext(wait.ContextForChannel(stopCh))
|
||||
if err != nil {
|
||||
klog.Background().Error(err, "Failed to start recording to sink")
|
||||
}
|
||||
}
|
||||
|
||||
// StartRecordingToSinkWithContext starts sending events received from the specified eventBroadcaster to the given sink.
|
||||
func (e *eventBroadcasterImpl) StartRecordingToSinkWithContext(ctx context.Context) error {
|
||||
go wait.UntilWithContext(ctx, e.refreshExistingEventSeries, refreshTime)
|
||||
go wait.UntilWithContext(ctx, e.finishSeries, finishTime)
|
||||
return e.startRecordingEvents(ctx)
|
||||
}
|
||||
|
||||
type eventBroadcasterAdapterImpl struct {
|
||||
coreClient typedv1core.EventsGetter
|
||||
coreBroadcaster record.EventBroadcaster
|
||||
eventsv1Client typedeventsv1.EventsV1Interface
|
||||
eventsv1Broadcaster EventBroadcaster
|
||||
}
|
||||
|
||||
// NewEventBroadcasterAdapter creates a wrapper around new and legacy broadcasters to simplify
|
||||
// migration of individual components to the new Event API.
|
||||
//
|
||||
//logcheck:context // NewEventBroadcasterAdapterWithContext should be used instead because record.NewBroadcaster is called and works better when a context is supplied (contextual logging, cancellation).
|
||||
func NewEventBroadcasterAdapter(client clientset.Interface) EventBroadcasterAdapter {
|
||||
return NewEventBroadcasterAdapterWithContext(context.Background(), client)
|
||||
}
|
||||
|
||||
// NewEventBroadcasterAdapterWithContext creates a wrapper around new and legacy broadcasters to simplify
|
||||
// migration of individual components to the new Event API.
|
||||
func NewEventBroadcasterAdapterWithContext(ctx context.Context, client clientset.Interface) EventBroadcasterAdapter {
|
||||
eventClient := &eventBroadcasterAdapterImpl{}
|
||||
if _, err := client.Discovery().ServerResourcesForGroupVersion(eventsv1.SchemeGroupVersion.String()); err == nil {
|
||||
eventClient.eventsv1Client = client.EventsV1()
|
||||
eventClient.eventsv1Broadcaster = NewBroadcaster(&EventSinkImpl{Interface: eventClient.eventsv1Client})
|
||||
}
|
||||
// Even though there can soon exist cases when coreBroadcaster won't really be needed,
|
||||
// we create it unconditionally because its overhead is minor and will simplify using usage
|
||||
// patterns of this library in all components.
|
||||
eventClient.coreClient = client.CoreV1()
|
||||
eventClient.coreBroadcaster = record.NewBroadcaster(record.WithContext(ctx))
|
||||
return eventClient
|
||||
}
|
||||
|
||||
// StartRecordingToSink starts sending events received from the specified eventBroadcaster to the given sink.
|
||||
func (e *eventBroadcasterAdapterImpl) StartRecordingToSink(stopCh <-chan struct{}) {
|
||||
if e.eventsv1Broadcaster != nil && e.eventsv1Client != nil {
|
||||
e.eventsv1Broadcaster.StartRecordingToSink(stopCh)
|
||||
}
|
||||
if e.coreBroadcaster != nil && e.coreClient != nil {
|
||||
e.coreBroadcaster.StartRecordingToSink(&typedv1core.EventSinkImpl{Interface: e.coreClient.Events("")})
|
||||
}
|
||||
}
|
||||
|
||||
func (e *eventBroadcasterAdapterImpl) NewRecorder(name string) EventRecorderLogger {
|
||||
if e.eventsv1Broadcaster != nil && e.eventsv1Client != nil {
|
||||
return e.eventsv1Broadcaster.NewRecorder(scheme.Scheme, name)
|
||||
}
|
||||
return record.NewEventRecorderAdapter(e.DeprecatedNewLegacyRecorder(name))
|
||||
}
|
||||
|
||||
func (e *eventBroadcasterAdapterImpl) DeprecatedNewLegacyRecorder(name string) record.EventRecorderLogger {
|
||||
return e.coreBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: name})
|
||||
}
|
||||
|
||||
func (e *eventBroadcasterAdapterImpl) Shutdown() {
|
||||
if e.coreBroadcaster != nil {
|
||||
e.coreBroadcaster.Shutdown()
|
||||
}
|
||||
if e.eventsv1Broadcaster != nil {
|
||||
e.eventsv1Broadcaster.Shutdown()
|
||||
}
|
||||
}
|
113
e2e/vendor/k8s.io/client-go/tools/events/event_recorder.go
generated
vendored
Normal file
113
e2e/vendor/k8s.io/client-go/tools/events/event_recorder.go
generated
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
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 events
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
eventsv1 "k8s.io/api/events/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/tools/record/util"
|
||||
"k8s.io/client-go/tools/reference"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
type recorderImpl struct {
|
||||
scheme *runtime.Scheme
|
||||
reportingController string
|
||||
reportingInstance string
|
||||
*watch.Broadcaster
|
||||
clock clock.Clock
|
||||
}
|
||||
|
||||
var _ EventRecorder = &recorderImpl{}
|
||||
|
||||
func (recorder *recorderImpl) Eventf(regarding runtime.Object, related runtime.Object, eventtype, reason, action, note string, args ...interface{}) {
|
||||
recorder.eventf(klog.Background(), regarding, related, eventtype, reason, action, note, args...)
|
||||
}
|
||||
|
||||
type recorderImplLogger struct {
|
||||
*recorderImpl
|
||||
logger klog.Logger
|
||||
}
|
||||
|
||||
var _ EventRecorderLogger = &recorderImplLogger{}
|
||||
|
||||
func (recorder *recorderImplLogger) Eventf(regarding runtime.Object, related runtime.Object, eventtype, reason, action, note string, args ...interface{}) {
|
||||
recorder.eventf(recorder.logger, regarding, related, eventtype, reason, action, note, args...)
|
||||
}
|
||||
|
||||
func (recorder *recorderImplLogger) WithLogger(logger klog.Logger) EventRecorderLogger {
|
||||
return &recorderImplLogger{recorderImpl: recorder.recorderImpl, logger: logger}
|
||||
}
|
||||
|
||||
func (recorder *recorderImpl) eventf(logger klog.Logger, regarding runtime.Object, related runtime.Object, eventtype, reason, action, note string, args ...interface{}) {
|
||||
timestamp := metav1.MicroTime{Time: time.Now()}
|
||||
message := fmt.Sprintf(note, args...)
|
||||
refRegarding, err := reference.GetReference(recorder.scheme, regarding)
|
||||
if err != nil {
|
||||
logger.Error(err, "Could not construct reference, will not report event", "object", regarding, "eventType", eventtype, "reason", reason, "message", message)
|
||||
return
|
||||
}
|
||||
|
||||
var refRelated *v1.ObjectReference
|
||||
if related != nil {
|
||||
refRelated, err = reference.GetReference(recorder.scheme, related)
|
||||
if err != nil {
|
||||
logger.V(9).Info("Could not construct reference", "object", related, "err", err)
|
||||
}
|
||||
}
|
||||
if !util.ValidateEventType(eventtype) {
|
||||
logger.Error(nil, "Unsupported event type", "eventType", eventtype)
|
||||
return
|
||||
}
|
||||
event := recorder.makeEvent(refRegarding, refRelated, timestamp, eventtype, reason, message, recorder.reportingController, recorder.reportingInstance, action)
|
||||
go func() {
|
||||
defer utilruntime.HandleCrash()
|
||||
recorder.Action(watch.Added, event)
|
||||
}()
|
||||
}
|
||||
|
||||
func (recorder *recorderImpl) makeEvent(refRegarding *v1.ObjectReference, refRelated *v1.ObjectReference, timestamp metav1.MicroTime, eventtype, reason, message string, reportingController string, reportingInstance string, action string) *eventsv1.Event {
|
||||
t := metav1.Time{Time: recorder.clock.Now()}
|
||||
namespace := refRegarding.Namespace
|
||||
if namespace == "" {
|
||||
namespace = metav1.NamespaceDefault
|
||||
}
|
||||
return &eventsv1.Event{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("%v.%x", refRegarding.Name, t.UnixNano()),
|
||||
Namespace: namespace,
|
||||
},
|
||||
EventTime: timestamp,
|
||||
Series: nil,
|
||||
ReportingController: reportingController,
|
||||
ReportingInstance: reportingInstance,
|
||||
Action: action,
|
||||
Reason: reason,
|
||||
Regarding: *refRegarding,
|
||||
Related: refRelated,
|
||||
Note: message,
|
||||
Type: eventtype,
|
||||
}
|
||||
}
|
52
e2e/vendor/k8s.io/client-go/tools/events/fake.go
generated
vendored
Normal file
52
e2e/vendor/k8s.io/client-go/tools/events/fake.go
generated
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
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 events
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// FakeRecorder is used as a fake during tests. It is thread safe. It is usable
|
||||
// when created manually and not by NewFakeRecorder, however all events may be
|
||||
// thrown away in this case.
|
||||
type FakeRecorder struct {
|
||||
Events chan string
|
||||
}
|
||||
|
||||
var _ EventRecorderLogger = &FakeRecorder{}
|
||||
|
||||
// Eventf emits an event
|
||||
func (f *FakeRecorder) Eventf(regarding runtime.Object, related runtime.Object, eventtype, reason, action, note string, args ...interface{}) {
|
||||
if f.Events != nil {
|
||||
f.Events <- fmt.Sprintf(eventtype+" "+reason+" "+note, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FakeRecorder) WithLogger(logger klog.Logger) EventRecorderLogger {
|
||||
return f
|
||||
}
|
||||
|
||||
// NewFakeRecorder creates new fake event recorder with event channel with
|
||||
// buffer of given size.
|
||||
func NewFakeRecorder(bufferSize int) *FakeRecorder {
|
||||
return &FakeRecorder{
|
||||
Events: make(chan string, bufferSize),
|
||||
}
|
||||
}
|
64
e2e/vendor/k8s.io/client-go/tools/events/helper.go
generated
vendored
Normal file
64
e2e/vendor/k8s.io/client-go/tools/events/helper.go
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
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 events
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
eventsv1 "k8s.io/api/events/v1"
|
||||
eventsv1beta1 "k8s.io/api/events/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
var mapping = map[schema.GroupVersion]string{
|
||||
eventsv1.SchemeGroupVersion: "regarding",
|
||||
eventsv1beta1.SchemeGroupVersion: "regarding",
|
||||
corev1.SchemeGroupVersion: "involvedObject",
|
||||
}
|
||||
|
||||
// GetFieldSelector returns the appropriate field selector based on the API version being used to communicate with the server.
|
||||
// The returned field selector can be used with List and Watch to filter desired events.
|
||||
func GetFieldSelector(eventsGroupVersion schema.GroupVersion, regardingGroupVersionKind schema.GroupVersionKind, regardingName string, regardingUID types.UID) (fields.Selector, error) {
|
||||
field := fields.Set{}
|
||||
|
||||
if _, ok := mapping[eventsGroupVersion]; !ok {
|
||||
return nil, fmt.Errorf("unknown version %v", eventsGroupVersion)
|
||||
}
|
||||
prefix := mapping[eventsGroupVersion]
|
||||
|
||||
if len(regardingName) > 0 {
|
||||
field[prefix+".name"] = regardingName
|
||||
}
|
||||
|
||||
if len(regardingGroupVersionKind.Kind) > 0 {
|
||||
field[prefix+".kind"] = regardingGroupVersionKind.Kind
|
||||
}
|
||||
|
||||
regardingGroupVersion := regardingGroupVersionKind.GroupVersion()
|
||||
if !regardingGroupVersion.Empty() {
|
||||
field[prefix+".apiVersion"] = regardingGroupVersion.String()
|
||||
}
|
||||
|
||||
if len(regardingUID) > 0 {
|
||||
field[prefix+".uid"] = string(regardingUID)
|
||||
}
|
||||
|
||||
return field.AsSelector(), nil
|
||||
}
|
92
e2e/vendor/k8s.io/client-go/tools/events/interfaces.go
generated
vendored
Normal file
92
e2e/vendor/k8s.io/client-go/tools/events/interfaces.go
generated
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
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 events
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
eventsv1 "k8s.io/api/events/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
internalevents "k8s.io/client-go/tools/internal/events"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
type EventRecorder = internalevents.EventRecorder
|
||||
type EventRecorderLogger = internalevents.EventRecorderLogger
|
||||
|
||||
// EventBroadcaster knows how to receive events and send them to any EventSink, watcher, or log.
|
||||
type EventBroadcaster interface {
|
||||
// StartRecordingToSink starts sending events received from the specified eventBroadcaster.
|
||||
// Deprecated: use StartRecordingToSinkWithContext instead.
|
||||
StartRecordingToSink(stopCh <-chan struct{})
|
||||
|
||||
// StartRecordingToSink starts sending events received from the specified eventBroadcaster.
|
||||
StartRecordingToSinkWithContext(ctx context.Context) error
|
||||
|
||||
// NewRecorder returns an EventRecorder that can be used to send events to this EventBroadcaster
|
||||
// with the event source set to the given event source.
|
||||
NewRecorder(scheme *runtime.Scheme, reportingController string) EventRecorderLogger
|
||||
|
||||
// StartEventWatcher enables you to watch for emitted events without usage
|
||||
// of StartRecordingToSink. This lets you also process events in a custom way (e.g. in tests).
|
||||
// NOTE: events received on your eventHandler should be copied before being used.
|
||||
// TODO: figure out if this can be removed.
|
||||
StartEventWatcher(eventHandler func(event runtime.Object)) (func(), error)
|
||||
|
||||
// StartStructuredLogging starts sending events received from this EventBroadcaster to the structured
|
||||
// logging function. The return value can be ignored or used to stop recording, if desired.
|
||||
// Deprecated: use StartLogging instead.
|
||||
StartStructuredLogging(verbosity klog.Level) func()
|
||||
|
||||
// StartLogging starts sending events received from this EventBroadcaster to the structured logger.
|
||||
// To adjust verbosity, use the logger's V method (i.e. pass `logger.V(3)` instead of `logger`).
|
||||
// The returned function can be ignored or used to stop recording, if desired.
|
||||
StartLogging(logger klog.Logger) (func(), error)
|
||||
|
||||
// Shutdown shuts down the broadcaster
|
||||
Shutdown()
|
||||
}
|
||||
|
||||
// EventSink knows how to store events (client-go implements it.)
|
||||
// EventSink must respect the namespace that will be embedded in 'event'.
|
||||
// It is assumed that EventSink will return the same sorts of errors as
|
||||
// client-go's REST client.
|
||||
type EventSink interface {
|
||||
Create(ctx context.Context, event *eventsv1.Event) (*eventsv1.Event, error)
|
||||
Update(ctx context.Context, event *eventsv1.Event) (*eventsv1.Event, error)
|
||||
Patch(ctx context.Context, oldEvent *eventsv1.Event, data []byte) (*eventsv1.Event, error)
|
||||
}
|
||||
|
||||
// EventBroadcasterAdapter is a auxiliary interface to simplify migration to
|
||||
// the new events API. It is a wrapper around new and legacy broadcasters
|
||||
// that smartly chooses which one to use.
|
||||
//
|
||||
// Deprecated: This interface will be removed once migration is completed.
|
||||
type EventBroadcasterAdapter interface {
|
||||
// StartRecordingToSink starts sending events received from the specified eventBroadcaster.
|
||||
StartRecordingToSink(stopCh <-chan struct{})
|
||||
|
||||
// NewRecorder creates a new Event Recorder with specified name.
|
||||
NewRecorder(name string) EventRecorderLogger
|
||||
|
||||
// DeprecatedNewLegacyRecorder creates a legacy Event Recorder with specific name.
|
||||
DeprecatedNewLegacyRecorder(name string) record.EventRecorderLogger
|
||||
|
||||
// Shutdown shuts down the broadcaster.
|
||||
Shutdown()
|
||||
}
|
59
e2e/vendor/k8s.io/client-go/tools/internal/events/interfaces.go
generated
vendored
Normal file
59
e2e/vendor/k8s.io/client-go/tools/internal/events/interfaces.go
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
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 internal is needed to break an import cycle: record.EventRecorderAdapter
|
||||
// needs this interface definition to implement it, but event.NewEventBroadcasterAdapter
|
||||
// needs record.NewBroadcaster. Therefore this interface cannot be in event/interfaces.go.
|
||||
package internal
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// EventRecorder knows how to record events on behalf of an EventSource.
|
||||
type EventRecorder interface {
|
||||
// Eventf constructs an event from the given information and puts it in the queue for sending.
|
||||
// 'regarding' is the object this event is about. Event will make a reference-- or you may also
|
||||
// pass a reference to the object directly.
|
||||
// 'related' is the secondary object for more complex actions. E.g. when regarding object triggers
|
||||
// a creation or deletion of related object.
|
||||
// 'type' of this event, and can be one of Normal, Warning. New types could be added in future
|
||||
// 'reason' is the reason this event is generated. 'reason' should be short and unique; it
|
||||
// should be in UpperCamelCase format (starting with a capital letter). "reason" will be used
|
||||
// to automate handling of events, so imagine people writing switch statements to handle them.
|
||||
// You want to make that easy.
|
||||
// 'action' explains what happened with regarding/what action did the ReportingController
|
||||
// (ReportingController is a type of a Controller reporting an Event, e.g. k8s.io/node-controller, k8s.io/kubelet.)
|
||||
// take in regarding's name; it should be in UpperCamelCase format (starting with a capital letter).
|
||||
// 'note' is intended to be human readable.
|
||||
Eventf(regarding runtime.Object, related runtime.Object, eventtype, reason, action, note string, args ...interface{})
|
||||
}
|
||||
|
||||
// EventRecorderLogger extends EventRecorder such that a logger can
|
||||
// be set for methods in EventRecorder. Normally, those methods
|
||||
// uses the global default logger to record errors and debug messages.
|
||||
// If that is not desired, use WithLogger to provide a logger instance.
|
||||
type EventRecorderLogger interface {
|
||||
EventRecorder
|
||||
|
||||
// WithLogger replaces the context used for logging. This is a cheap call
|
||||
// and meant to be used for contextual logging:
|
||||
// recorder := ...
|
||||
// logger := klog.FromContext(ctx)
|
||||
// recorder.WithLogger(logger).Eventf(...)
|
||||
WithLogger(logger klog.Logger) EventRecorderLogger
|
||||
}
|
5
e2e/vendor/k8s.io/client-go/tools/metrics/OWNERS
generated
vendored
Normal file
5
e2e/vendor/k8s.io/client-go/tools/metrics/OWNERS
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
reviewers:
|
||||
- wojtek-t
|
||||
- jayunit100
|
211
e2e/vendor/k8s.io/client-go/tools/metrics/metrics.go
generated
vendored
Normal file
211
e2e/vendor/k8s.io/client-go/tools/metrics/metrics.go
generated
vendored
Normal file
@ -0,0 +1,211 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package metrics provides abstractions for registering which metrics
|
||||
// to record.
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var registerMetrics sync.Once
|
||||
|
||||
// DurationMetric is a measurement of some amount of time.
|
||||
type DurationMetric interface {
|
||||
Observe(duration time.Duration)
|
||||
}
|
||||
|
||||
// ExpiryMetric sets some time of expiry. If nil, assume not relevant.
|
||||
type ExpiryMetric interface {
|
||||
Set(expiry *time.Time)
|
||||
}
|
||||
|
||||
// LatencyMetric observes client latency partitioned by verb and url.
|
||||
type LatencyMetric interface {
|
||||
Observe(ctx context.Context, verb string, u url.URL, latency time.Duration)
|
||||
}
|
||||
|
||||
type ResolverLatencyMetric interface {
|
||||
Observe(ctx context.Context, host string, latency time.Duration)
|
||||
}
|
||||
|
||||
// SizeMetric observes client response size partitioned by verb and host.
|
||||
type SizeMetric interface {
|
||||
Observe(ctx context.Context, verb string, host string, size float64)
|
||||
}
|
||||
|
||||
// ResultMetric counts response codes partitioned by method and host.
|
||||
type ResultMetric interface {
|
||||
Increment(ctx context.Context, code string, method string, host string)
|
||||
}
|
||||
|
||||
// CallsMetric counts calls that take place for a specific exec plugin.
|
||||
type CallsMetric interface {
|
||||
// Increment increments a counter per exitCode and callStatus.
|
||||
Increment(exitCode int, callStatus string)
|
||||
}
|
||||
|
||||
// RetryMetric counts the number of retries sent to the server
|
||||
// partitioned by code, method, and host.
|
||||
type RetryMetric interface {
|
||||
IncrementRetry(ctx context.Context, code string, method string, host string)
|
||||
}
|
||||
|
||||
// TransportCacheMetric shows the number of entries in the internal transport cache
|
||||
type TransportCacheMetric interface {
|
||||
Observe(value int)
|
||||
}
|
||||
|
||||
// TransportCreateCallsMetric counts the number of times a transport is created
|
||||
// partitioned by the result of the cache: hit, miss, uncacheable
|
||||
type TransportCreateCallsMetric interface {
|
||||
Increment(result string)
|
||||
}
|
||||
|
||||
var (
|
||||
// ClientCertExpiry is the expiry time of a client certificate
|
||||
ClientCertExpiry ExpiryMetric = noopExpiry{}
|
||||
// ClientCertRotationAge is the age of a certificate that has just been rotated.
|
||||
ClientCertRotationAge DurationMetric = noopDuration{}
|
||||
// RequestLatency is the latency metric that rest clients will update.
|
||||
RequestLatency LatencyMetric = noopLatency{}
|
||||
// ResolverLatency is the latency metric that DNS resolver will update
|
||||
ResolverLatency ResolverLatencyMetric = noopResolverLatency{}
|
||||
// RequestSize is the request size metric that rest clients will update.
|
||||
RequestSize SizeMetric = noopSize{}
|
||||
// ResponseSize is the response size metric that rest clients will update.
|
||||
ResponseSize SizeMetric = noopSize{}
|
||||
// RateLimiterLatency is the client side rate limiter latency metric.
|
||||
RateLimiterLatency LatencyMetric = noopLatency{}
|
||||
// RequestResult is the result metric that rest clients will update.
|
||||
RequestResult ResultMetric = noopResult{}
|
||||
// ExecPluginCalls is the number of calls made to an exec plugin, partitioned by
|
||||
// exit code and call status.
|
||||
ExecPluginCalls CallsMetric = noopCalls{}
|
||||
// RequestRetry is the retry metric that tracks the number of
|
||||
// retries sent to the server.
|
||||
RequestRetry RetryMetric = noopRetry{}
|
||||
// TransportCacheEntries is the metric that tracks the number of entries in the
|
||||
// internal transport cache.
|
||||
TransportCacheEntries TransportCacheMetric = noopTransportCache{}
|
||||
// TransportCreateCalls is the metric that counts the number of times a new transport
|
||||
// is created
|
||||
TransportCreateCalls TransportCreateCallsMetric = noopTransportCreateCalls{}
|
||||
)
|
||||
|
||||
// RegisterOpts contains all the metrics to register. Metrics may be nil.
|
||||
type RegisterOpts struct {
|
||||
ClientCertExpiry ExpiryMetric
|
||||
ClientCertRotationAge DurationMetric
|
||||
RequestLatency LatencyMetric
|
||||
ResolverLatency ResolverLatencyMetric
|
||||
RequestSize SizeMetric
|
||||
ResponseSize SizeMetric
|
||||
RateLimiterLatency LatencyMetric
|
||||
RequestResult ResultMetric
|
||||
ExecPluginCalls CallsMetric
|
||||
RequestRetry RetryMetric
|
||||
TransportCacheEntries TransportCacheMetric
|
||||
TransportCreateCalls TransportCreateCallsMetric
|
||||
}
|
||||
|
||||
// Register registers metrics for the rest client to use. This can
|
||||
// only be called once.
|
||||
func Register(opts RegisterOpts) {
|
||||
registerMetrics.Do(func() {
|
||||
if opts.ClientCertExpiry != nil {
|
||||
ClientCertExpiry = opts.ClientCertExpiry
|
||||
}
|
||||
if opts.ClientCertRotationAge != nil {
|
||||
ClientCertRotationAge = opts.ClientCertRotationAge
|
||||
}
|
||||
if opts.RequestLatency != nil {
|
||||
RequestLatency = opts.RequestLatency
|
||||
}
|
||||
if opts.ResolverLatency != nil {
|
||||
ResolverLatency = opts.ResolverLatency
|
||||
}
|
||||
if opts.RequestSize != nil {
|
||||
RequestSize = opts.RequestSize
|
||||
}
|
||||
if opts.ResponseSize != nil {
|
||||
ResponseSize = opts.ResponseSize
|
||||
}
|
||||
if opts.RateLimiterLatency != nil {
|
||||
RateLimiterLatency = opts.RateLimiterLatency
|
||||
}
|
||||
if opts.RequestResult != nil {
|
||||
RequestResult = opts.RequestResult
|
||||
}
|
||||
if opts.ExecPluginCalls != nil {
|
||||
ExecPluginCalls = opts.ExecPluginCalls
|
||||
}
|
||||
if opts.RequestRetry != nil {
|
||||
RequestRetry = opts.RequestRetry
|
||||
}
|
||||
if opts.TransportCacheEntries != nil {
|
||||
TransportCacheEntries = opts.TransportCacheEntries
|
||||
}
|
||||
if opts.TransportCreateCalls != nil {
|
||||
TransportCreateCalls = opts.TransportCreateCalls
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type noopDuration struct{}
|
||||
|
||||
func (noopDuration) Observe(time.Duration) {}
|
||||
|
||||
type noopExpiry struct{}
|
||||
|
||||
func (noopExpiry) Set(*time.Time) {}
|
||||
|
||||
type noopLatency struct{}
|
||||
|
||||
func (noopLatency) Observe(context.Context, string, url.URL, time.Duration) {}
|
||||
|
||||
type noopResolverLatency struct{}
|
||||
|
||||
func (n noopResolverLatency) Observe(ctx context.Context, host string, latency time.Duration) {
|
||||
}
|
||||
|
||||
type noopSize struct{}
|
||||
|
||||
func (noopSize) Observe(context.Context, string, string, float64) {}
|
||||
|
||||
type noopResult struct{}
|
||||
|
||||
func (noopResult) Increment(context.Context, string, string, string) {}
|
||||
|
||||
type noopCalls struct{}
|
||||
|
||||
func (noopCalls) Increment(int, string) {}
|
||||
|
||||
type noopRetry struct{}
|
||||
|
||||
func (noopRetry) IncrementRetry(context.Context, string, string, string) {}
|
||||
|
||||
type noopTransportCache struct{}
|
||||
|
||||
func (noopTransportCache) Observe(int) {}
|
||||
|
||||
type noopTransportCreateCalls struct{}
|
||||
|
||||
func (noopTransportCreateCalls) Increment(string) {}
|
289
e2e/vendor/k8s.io/client-go/tools/pager/pager.go
generated
vendored
Normal file
289
e2e/vendor/k8s.io/client-go/tools/pager/pager.go
generated
vendored
Normal file
@ -0,0 +1,289 @@
|
||||
/*
|
||||
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 pager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
)
|
||||
|
||||
const defaultPageSize = 500
|
||||
const defaultPageBufferSize = 10
|
||||
|
||||
// ListPageFunc returns a list object for the given list options.
|
||||
type ListPageFunc func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error)
|
||||
|
||||
// SimplePageFunc adapts a context-less list function into one that accepts a context.
|
||||
func SimplePageFunc(fn func(opts metav1.ListOptions) (runtime.Object, error)) ListPageFunc {
|
||||
return func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) {
|
||||
return fn(opts)
|
||||
}
|
||||
}
|
||||
|
||||
// ListPager assists client code in breaking large list queries into multiple
|
||||
// smaller chunks of PageSize or smaller. PageFn is expected to accept a
|
||||
// metav1.ListOptions that supports paging and return a list. The pager does
|
||||
// not alter the field or label selectors on the initial options list.
|
||||
type ListPager struct {
|
||||
PageSize int64
|
||||
PageFn ListPageFunc
|
||||
|
||||
FullListIfExpired bool
|
||||
|
||||
// Number of pages to buffer
|
||||
PageBufferSize int32
|
||||
}
|
||||
|
||||
// New creates a new pager from the provided pager function using the default
|
||||
// options. It will fall back to a full list if an expiration error is encountered
|
||||
// as a last resort.
|
||||
func New(fn ListPageFunc) *ListPager {
|
||||
return &ListPager{
|
||||
PageSize: defaultPageSize,
|
||||
PageFn: fn,
|
||||
FullListIfExpired: true,
|
||||
PageBufferSize: defaultPageBufferSize,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: introduce other types of paging functions - such as those that retrieve from a list
|
||||
// of namespaces.
|
||||
|
||||
// List returns a single list object, but attempts to retrieve smaller chunks from the
|
||||
// server to reduce the impact on the server. If the chunk attempt fails, it will load
|
||||
// the full list instead. The Limit field on options, if unset, will default to the page size.
|
||||
//
|
||||
// If items in the returned list are retained for different durations, and you want to avoid
|
||||
// retaining the whole slice returned by p.PageFn as long as any item is referenced,
|
||||
// use ListWithAlloc instead.
|
||||
func (p *ListPager) List(ctx context.Context, options metav1.ListOptions) (runtime.Object, bool, error) {
|
||||
return p.list(ctx, options, false)
|
||||
}
|
||||
|
||||
// ListWithAlloc works like List, but avoids retaining references to the items slice returned by p.PageFn.
|
||||
// It does this by making a shallow copy of non-pointer items in the slice returned by p.PageFn.
|
||||
//
|
||||
// If the items in the returned list are not retained, or are retained for the same duration, use List instead for memory efficiency.
|
||||
func (p *ListPager) ListWithAlloc(ctx context.Context, options metav1.ListOptions) (runtime.Object, bool, error) {
|
||||
return p.list(ctx, options, true)
|
||||
}
|
||||
|
||||
func (p *ListPager) list(ctx context.Context, options metav1.ListOptions, allocNew bool) (runtime.Object, bool, error) {
|
||||
if options.Limit == 0 {
|
||||
options.Limit = p.PageSize
|
||||
}
|
||||
requestedResourceVersion := options.ResourceVersion
|
||||
requestedResourceVersionMatch := options.ResourceVersionMatch
|
||||
var list *metainternalversion.List
|
||||
paginatedResult := false
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, paginatedResult, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
obj, err := p.PageFn(ctx, options)
|
||||
if err != nil {
|
||||
// Only fallback to full list if an "Expired" errors is returned, FullListIfExpired is true, and
|
||||
// the "Expired" error occurred in page 2 or later (since full list is intended to prevent a pager.List from
|
||||
// failing when the resource versions is established by the first page request falls out of the compaction
|
||||
// during the subsequent list requests).
|
||||
if !errors.IsResourceExpired(err) || !p.FullListIfExpired || options.Continue == "" {
|
||||
return nil, paginatedResult, err
|
||||
}
|
||||
// the list expired while we were processing, fall back to a full list at
|
||||
// the requested ResourceVersion.
|
||||
options.Limit = 0
|
||||
options.Continue = ""
|
||||
options.ResourceVersion = requestedResourceVersion
|
||||
options.ResourceVersionMatch = requestedResourceVersionMatch
|
||||
result, err := p.PageFn(ctx, options)
|
||||
return result, paginatedResult, err
|
||||
}
|
||||
m, err := meta.ListAccessor(obj)
|
||||
if err != nil {
|
||||
return nil, paginatedResult, fmt.Errorf("returned object must be a list: %v", err)
|
||||
}
|
||||
|
||||
// exit early and return the object we got if we haven't processed any pages
|
||||
if len(m.GetContinue()) == 0 && list == nil {
|
||||
return obj, paginatedResult, nil
|
||||
}
|
||||
|
||||
// initialize the list and fill its contents
|
||||
if list == nil {
|
||||
list = &metainternalversion.List{Items: make([]runtime.Object, 0, options.Limit+1)}
|
||||
list.ResourceVersion = m.GetResourceVersion()
|
||||
list.SelfLink = m.GetSelfLink()
|
||||
}
|
||||
eachListItemFunc := meta.EachListItem
|
||||
if allocNew {
|
||||
eachListItemFunc = meta.EachListItemWithAlloc
|
||||
}
|
||||
if err := eachListItemFunc(obj, func(obj runtime.Object) error {
|
||||
list.Items = append(list.Items, obj)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, paginatedResult, err
|
||||
}
|
||||
|
||||
// if we have no more items, return the list
|
||||
if len(m.GetContinue()) == 0 {
|
||||
return list, paginatedResult, nil
|
||||
}
|
||||
|
||||
// set the next loop up
|
||||
options.Continue = m.GetContinue()
|
||||
// Clear the ResourceVersion(Match) on the subsequent List calls to avoid the
|
||||
// `specifying resource version is not allowed when using continue` error.
|
||||
// See https://github.com/kubernetes/kubernetes/issues/85221#issuecomment-553748143.
|
||||
options.ResourceVersion = ""
|
||||
options.ResourceVersionMatch = ""
|
||||
// At this point, result is already paginated.
|
||||
paginatedResult = true
|
||||
}
|
||||
}
|
||||
|
||||
// EachListItem fetches runtime.Object items using this ListPager and invokes fn on each item. If
|
||||
// fn returns an error, processing stops and that error is returned. If fn does not return an error,
|
||||
// any error encountered while retrieving the list from the server is returned. If the context
|
||||
// cancels or times out, the context error is returned. Since the list is retrieved in paginated
|
||||
// chunks, an "Expired" error (metav1.StatusReasonExpired) may be returned if the pagination list
|
||||
// requests exceed the expiration limit of the apiserver being called.
|
||||
//
|
||||
// Items are retrieved in chunks from the server to reduce the impact on the server with up to
|
||||
// ListPager.PageBufferSize chunks buffered concurrently in the background.
|
||||
//
|
||||
// If items passed to fn are retained for different durations, and you want to avoid
|
||||
// retaining the whole slice returned by p.PageFn as long as any item is referenced,
|
||||
// use EachListItemWithAlloc instead.
|
||||
func (p *ListPager) EachListItem(ctx context.Context, options metav1.ListOptions, fn func(obj runtime.Object) error) error {
|
||||
return p.eachListChunkBuffered(ctx, options, func(obj runtime.Object) error {
|
||||
return meta.EachListItem(obj, fn)
|
||||
})
|
||||
}
|
||||
|
||||
// EachListItemWithAlloc works like EachListItem, but avoids retaining references to the items slice returned by p.PageFn.
|
||||
// It does this by making a shallow copy of non-pointer items in the slice returned by p.PageFn.
|
||||
//
|
||||
// If the items passed to fn are not retained, or are retained for the same duration, use EachListItem instead for memory efficiency.
|
||||
func (p *ListPager) EachListItemWithAlloc(ctx context.Context, options metav1.ListOptions, fn func(obj runtime.Object) error) error {
|
||||
return p.eachListChunkBuffered(ctx, options, func(obj runtime.Object) error {
|
||||
return meta.EachListItemWithAlloc(obj, fn)
|
||||
})
|
||||
}
|
||||
|
||||
// eachListChunkBuffered fetches runtimeObject list chunks using this ListPager and invokes fn on
|
||||
// each list chunk. If fn returns an error, processing stops and that error is returned. If fn does
|
||||
// not return an error, any error encountered while retrieving the list from the server is
|
||||
// returned. If the context cancels or times out, the context error is returned. Since the list is
|
||||
// retrieved in paginated chunks, an "Expired" error (metav1.StatusReasonExpired) may be returned if
|
||||
// the pagination list requests exceed the expiration limit of the apiserver being called.
|
||||
//
|
||||
// Up to ListPager.PageBufferSize chunks are buffered concurrently in the background.
|
||||
func (p *ListPager) eachListChunkBuffered(ctx context.Context, options metav1.ListOptions, fn func(obj runtime.Object) error) error {
|
||||
if p.PageBufferSize < 0 {
|
||||
return fmt.Errorf("ListPager.PageBufferSize must be >= 0, got %d", p.PageBufferSize)
|
||||
}
|
||||
|
||||
// Ensure background goroutine is stopped if this call exits before all list items are
|
||||
// processed. Cancelation error from this deferred cancel call is never returned to caller;
|
||||
// either the list result has already been sent to bgResultC or the fn error is returned and
|
||||
// the cancelation error is discarded.
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
chunkC := make(chan runtime.Object, p.PageBufferSize)
|
||||
bgResultC := make(chan error, 1)
|
||||
go func() {
|
||||
defer utilruntime.HandleCrash()
|
||||
|
||||
var err error
|
||||
defer func() {
|
||||
close(chunkC)
|
||||
bgResultC <- err
|
||||
}()
|
||||
err = p.eachListChunk(ctx, options, func(chunk runtime.Object) error {
|
||||
select {
|
||||
case chunkC <- chunk: // buffer the chunk, this can block
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}()
|
||||
|
||||
for o := range chunkC {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
err := fn(o)
|
||||
if err != nil {
|
||||
return err // any fn error should be returned immediately
|
||||
}
|
||||
}
|
||||
// promote the results of our background goroutine to the foreground
|
||||
return <-bgResultC
|
||||
}
|
||||
|
||||
// eachListChunk fetches runtimeObject list chunks using this ListPager and invokes fn on each list
|
||||
// chunk. If fn returns an error, processing stops and that error is returned. If fn does not return
|
||||
// an error, any error encountered while retrieving the list from the server is returned. If the
|
||||
// context cancels or times out, the context error is returned. Since the list is retrieved in
|
||||
// paginated chunks, an "Expired" error (metav1.StatusReasonExpired) may be returned if the
|
||||
// pagination list requests exceed the expiration limit of the apiserver being called.
|
||||
func (p *ListPager) eachListChunk(ctx context.Context, options metav1.ListOptions, fn func(obj runtime.Object) error) error {
|
||||
if options.Limit == 0 {
|
||||
options.Limit = p.PageSize
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
obj, err := p.PageFn(ctx, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m, err := meta.ListAccessor(obj)
|
||||
if err != nil {
|
||||
return fmt.Errorf("returned object must be a list: %v", err)
|
||||
}
|
||||
if err := fn(obj); err != nil {
|
||||
return err
|
||||
}
|
||||
// if we have no more items, return.
|
||||
if len(m.GetContinue()) == 0 {
|
||||
return nil
|
||||
}
|
||||
// set the next loop up
|
||||
options.Continue = m.GetContinue()
|
||||
}
|
||||
}
|
10
e2e/vendor/k8s.io/client-go/tools/portforward/OWNERS
generated
vendored
Normal file
10
e2e/vendor/k8s.io/client-go/tools/portforward/OWNERS
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- aojea
|
||||
- liggitt
|
||||
- seans3
|
||||
reviewers:
|
||||
- aojea
|
||||
- liggitt
|
||||
- seans3
|
19
e2e/vendor/k8s.io/client-go/tools/portforward/doc.go
generated
vendored
Normal file
19
e2e/vendor/k8s.io/client-go/tools/portforward/doc.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package portforward adds support for SSH-like port forwarding from the client's
|
||||
// local host to remote containers.
|
||||
package portforward // import "k8s.io/client-go/tools/portforward"
|
57
e2e/vendor/k8s.io/client-go/tools/portforward/fallback_dialer.go
generated
vendored
Normal file
57
e2e/vendor/k8s.io/client-go/tools/portforward/fallback_dialer.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package portforward
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
var _ httpstream.Dialer = &FallbackDialer{}
|
||||
|
||||
// FallbackDialer encapsulates a primary and secondary dialer, including
|
||||
// the boolean function to determine if the primary dialer failed. Implements
|
||||
// the httpstream.Dialer interface.
|
||||
type FallbackDialer struct {
|
||||
primary httpstream.Dialer
|
||||
secondary httpstream.Dialer
|
||||
shouldFallback func(error) bool
|
||||
}
|
||||
|
||||
// NewFallbackDialer creates the FallbackDialer with the primary and secondary dialers,
|
||||
// as well as the boolean function to determine if the primary dialer failed.
|
||||
func NewFallbackDialer(primary, secondary httpstream.Dialer, shouldFallback func(error) bool) httpstream.Dialer {
|
||||
return &FallbackDialer{
|
||||
primary: primary,
|
||||
secondary: secondary,
|
||||
shouldFallback: shouldFallback,
|
||||
}
|
||||
}
|
||||
|
||||
// Dial is the single function necessary to implement the "httpstream.Dialer" interface.
|
||||
// It takes the protocol version strings to request, returning an the upgraded
|
||||
// httstream.Connection and the negotiated protocol version accepted. If the initial
|
||||
// primary dialer fails, this function attempts the secondary dialer. Returns an error
|
||||
// if one occurs.
|
||||
func (f *FallbackDialer) Dial(protocols ...string) (httpstream.Connection, string, error) {
|
||||
conn, version, err := f.primary.Dial(protocols...)
|
||||
if err != nil && f.shouldFallback(err) {
|
||||
klog.V(4).Infof("fallback to secondary dialer from primary dialer err: %v", err)
|
||||
return f.secondary.Dial(protocols...)
|
||||
}
|
||||
return conn, version, err
|
||||
}
|
454
e2e/vendor/k8s.io/client-go/tools/portforward/portforward.go
generated
vendored
Normal file
454
e2e/vendor/k8s.io/client-go/tools/portforward/portforward.go
generated
vendored
Normal file
@ -0,0 +1,454 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package portforward
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
netutils "k8s.io/utils/net"
|
||||
)
|
||||
|
||||
// PortForwardProtocolV1Name is the subprotocol used for port forwarding.
|
||||
// TODO move to API machinery and re-unify with kubelet/server/portfoward
|
||||
const PortForwardProtocolV1Name = "portforward.k8s.io"
|
||||
|
||||
var (
|
||||
// error returned whenever we lost connection to a pod
|
||||
ErrLostConnectionToPod = errors.New("lost connection to pod")
|
||||
|
||||
// set of error we're expecting during port-forwarding
|
||||
networkClosedError = "use of closed network connection"
|
||||
)
|
||||
|
||||
// PortForwarder knows how to listen for local connections and forward them to
|
||||
// a remote pod via an upgraded HTTP request.
|
||||
type PortForwarder struct {
|
||||
addresses []listenAddress
|
||||
ports []ForwardedPort
|
||||
stopChan <-chan struct{}
|
||||
|
||||
dialer httpstream.Dialer
|
||||
streamConn httpstream.Connection
|
||||
listeners []io.Closer
|
||||
Ready chan struct{}
|
||||
requestIDLock sync.Mutex
|
||||
requestID int
|
||||
out io.Writer
|
||||
errOut io.Writer
|
||||
}
|
||||
|
||||
// ForwardedPort contains a Local:Remote port pairing.
|
||||
type ForwardedPort struct {
|
||||
Local uint16
|
||||
Remote uint16
|
||||
}
|
||||
|
||||
/*
|
||||
valid port specifications:
|
||||
|
||||
5000
|
||||
- forwards from localhost:5000 to pod:5000
|
||||
|
||||
8888:5000
|
||||
- forwards from localhost:8888 to pod:5000
|
||||
|
||||
0:5000
|
||||
:5000
|
||||
- selects a random available local port,
|
||||
forwards from localhost:<random port> to pod:5000
|
||||
*/
|
||||
func parsePorts(ports []string) ([]ForwardedPort, error) {
|
||||
var forwards []ForwardedPort
|
||||
for _, portString := range ports {
|
||||
parts := strings.Split(portString, ":")
|
||||
var localString, remoteString string
|
||||
if len(parts) == 1 {
|
||||
localString = parts[0]
|
||||
remoteString = parts[0]
|
||||
} else if len(parts) == 2 {
|
||||
localString = parts[0]
|
||||
if localString == "" {
|
||||
// support :5000
|
||||
localString = "0"
|
||||
}
|
||||
remoteString = parts[1]
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid port format '%s'", portString)
|
||||
}
|
||||
|
||||
localPort, err := strconv.ParseUint(localString, 10, 16)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing local port '%s': %s", localString, err)
|
||||
}
|
||||
|
||||
remotePort, err := strconv.ParseUint(remoteString, 10, 16)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing remote port '%s': %s", remoteString, err)
|
||||
}
|
||||
if remotePort == 0 {
|
||||
return nil, fmt.Errorf("remote port must be > 0")
|
||||
}
|
||||
|
||||
forwards = append(forwards, ForwardedPort{uint16(localPort), uint16(remotePort)})
|
||||
}
|
||||
|
||||
return forwards, nil
|
||||
}
|
||||
|
||||
type listenAddress struct {
|
||||
address string
|
||||
protocol string
|
||||
failureMode string
|
||||
}
|
||||
|
||||
func parseAddresses(addressesToParse []string) ([]listenAddress, error) {
|
||||
var addresses []listenAddress
|
||||
parsed := make(map[string]listenAddress)
|
||||
for _, address := range addressesToParse {
|
||||
if address == "localhost" {
|
||||
if _, exists := parsed["127.0.0.1"]; !exists {
|
||||
ip := listenAddress{address: "127.0.0.1", protocol: "tcp4", failureMode: "all"}
|
||||
parsed[ip.address] = ip
|
||||
}
|
||||
if _, exists := parsed["::1"]; !exists {
|
||||
ip := listenAddress{address: "::1", protocol: "tcp6", failureMode: "all"}
|
||||
parsed[ip.address] = ip
|
||||
}
|
||||
} else if netutils.ParseIPSloppy(address).To4() != nil {
|
||||
parsed[address] = listenAddress{address: address, protocol: "tcp4", failureMode: "any"}
|
||||
} else if netutils.ParseIPSloppy(address) != nil {
|
||||
parsed[address] = listenAddress{address: address, protocol: "tcp6", failureMode: "any"}
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s is not a valid IP", address)
|
||||
}
|
||||
}
|
||||
addresses = make([]listenAddress, len(parsed))
|
||||
id := 0
|
||||
for _, v := range parsed {
|
||||
addresses[id] = v
|
||||
id++
|
||||
}
|
||||
// Sort addresses before returning to get a stable order
|
||||
sort.Slice(addresses, func(i, j int) bool { return addresses[i].address < addresses[j].address })
|
||||
|
||||
return addresses, nil
|
||||
}
|
||||
|
||||
// New creates a new PortForwarder with localhost listen addresses.
|
||||
func New(dialer httpstream.Dialer, ports []string, stopChan <-chan struct{}, readyChan chan struct{}, out, errOut io.Writer) (*PortForwarder, error) {
|
||||
return NewOnAddresses(dialer, []string{"localhost"}, ports, stopChan, readyChan, out, errOut)
|
||||
}
|
||||
|
||||
// NewOnAddresses creates a new PortForwarder with custom listen addresses.
|
||||
func NewOnAddresses(dialer httpstream.Dialer, addresses []string, ports []string, stopChan <-chan struct{}, readyChan chan struct{}, out, errOut io.Writer) (*PortForwarder, error) {
|
||||
if len(addresses) == 0 {
|
||||
return nil, errors.New("you must specify at least 1 address")
|
||||
}
|
||||
parsedAddresses, err := parseAddresses(addresses)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ports) == 0 {
|
||||
return nil, errors.New("you must specify at least 1 port")
|
||||
}
|
||||
parsedPorts, err := parsePorts(ports)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &PortForwarder{
|
||||
dialer: dialer,
|
||||
addresses: parsedAddresses,
|
||||
ports: parsedPorts,
|
||||
stopChan: stopChan,
|
||||
Ready: readyChan,
|
||||
out: out,
|
||||
errOut: errOut,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ForwardPorts formats and executes a port forwarding request. The connection will remain
|
||||
// open until stopChan is closed.
|
||||
func (pf *PortForwarder) ForwardPorts() error {
|
||||
defer pf.Close()
|
||||
|
||||
var err error
|
||||
var protocol string
|
||||
pf.streamConn, protocol, err = pf.dialer.Dial(PortForwardProtocolV1Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error upgrading connection: %s", err)
|
||||
}
|
||||
defer pf.streamConn.Close()
|
||||
if protocol != PortForwardProtocolV1Name {
|
||||
return fmt.Errorf("unable to negotiate protocol: client supports %q, server returned %q", PortForwardProtocolV1Name, protocol)
|
||||
}
|
||||
|
||||
return pf.forward()
|
||||
}
|
||||
|
||||
// forward dials the remote host specific in req, upgrades the request, starts
|
||||
// listeners for each port specified in ports, and forwards local connections
|
||||
// to the remote host via streams.
|
||||
func (pf *PortForwarder) forward() error {
|
||||
var err error
|
||||
|
||||
listenSuccess := false
|
||||
for i := range pf.ports {
|
||||
port := &pf.ports[i]
|
||||
err = pf.listenOnPort(port)
|
||||
switch {
|
||||
case err == nil:
|
||||
listenSuccess = true
|
||||
default:
|
||||
if pf.errOut != nil {
|
||||
fmt.Fprintf(pf.errOut, "Unable to listen on port %d: %v\n", port.Local, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !listenSuccess {
|
||||
return fmt.Errorf("unable to listen on any of the requested ports: %v", pf.ports)
|
||||
}
|
||||
|
||||
if pf.Ready != nil {
|
||||
close(pf.Ready)
|
||||
}
|
||||
|
||||
// wait for interrupt or conn closure
|
||||
select {
|
||||
case <-pf.stopChan:
|
||||
case <-pf.streamConn.CloseChan():
|
||||
return ErrLostConnectionToPod
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// listenOnPort delegates listener creation and waits for connections on requested bind addresses.
|
||||
// An error is raised based on address groups (default and localhost) and their failure modes
|
||||
func (pf *PortForwarder) listenOnPort(port *ForwardedPort) error {
|
||||
var errors []error
|
||||
failCounters := make(map[string]int, 2)
|
||||
successCounters := make(map[string]int, 2)
|
||||
for _, addr := range pf.addresses {
|
||||
err := pf.listenOnPortAndAddress(port, addr.protocol, addr.address)
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
failCounters[addr.failureMode]++
|
||||
} else {
|
||||
successCounters[addr.failureMode]++
|
||||
}
|
||||
}
|
||||
if successCounters["all"] == 0 && failCounters["all"] > 0 {
|
||||
return fmt.Errorf("%s: %v", "Listeners failed to create with the following errors", errors)
|
||||
}
|
||||
if failCounters["any"] > 0 {
|
||||
return fmt.Errorf("%s: %v", "Listeners failed to create with the following errors", errors)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// listenOnPortAndAddress delegates listener creation and waits for new connections
|
||||
// in the background f
|
||||
func (pf *PortForwarder) listenOnPortAndAddress(port *ForwardedPort, protocol string, address string) error {
|
||||
listener, err := pf.getListener(protocol, address, port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pf.listeners = append(pf.listeners, listener)
|
||||
go pf.waitForConnection(listener, *port)
|
||||
return nil
|
||||
}
|
||||
|
||||
// getListener creates a listener on the interface targeted by the given hostname on the given port with
|
||||
// the given protocol. protocol is in net.Listen style which basically admits values like tcp, tcp4, tcp6
|
||||
func (pf *PortForwarder) getListener(protocol string, hostname string, port *ForwardedPort) (net.Listener, error) {
|
||||
listener, err := net.Listen(protocol, net.JoinHostPort(hostname, strconv.Itoa(int(port.Local))))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create listener: Error %s", err)
|
||||
}
|
||||
listenerAddress := listener.Addr().String()
|
||||
host, localPort, _ := net.SplitHostPort(listenerAddress)
|
||||
localPortUInt, err := strconv.ParseUint(localPort, 10, 16)
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintf(pf.out, "Failed to forward from %s:%d -> %d\n", hostname, localPortUInt, port.Remote)
|
||||
return nil, fmt.Errorf("error parsing local port: %s from %s (%s)", err, listenerAddress, host)
|
||||
}
|
||||
port.Local = uint16(localPortUInt)
|
||||
if pf.out != nil {
|
||||
fmt.Fprintf(pf.out, "Forwarding from %s -> %d\n", net.JoinHostPort(hostname, strconv.Itoa(int(localPortUInt))), port.Remote)
|
||||
}
|
||||
|
||||
return listener, nil
|
||||
}
|
||||
|
||||
// waitForConnection waits for new connections to listener and handles them in
|
||||
// the background.
|
||||
func (pf *PortForwarder) waitForConnection(listener net.Listener, port ForwardedPort) {
|
||||
for {
|
||||
select {
|
||||
case <-pf.streamConn.CloseChan():
|
||||
return
|
||||
default:
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
// TODO consider using something like https://github.com/hydrogen18/stoppableListener?
|
||||
if !strings.Contains(strings.ToLower(err.Error()), networkClosedError) {
|
||||
runtime.HandleError(fmt.Errorf("error accepting connection on port %d: %v", port.Local, err))
|
||||
}
|
||||
return
|
||||
}
|
||||
go pf.handleConnection(conn, port)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pf *PortForwarder) nextRequestID() int {
|
||||
pf.requestIDLock.Lock()
|
||||
defer pf.requestIDLock.Unlock()
|
||||
id := pf.requestID
|
||||
pf.requestID++
|
||||
return id
|
||||
}
|
||||
|
||||
// handleConnection copies data between the local connection and the stream to
|
||||
// the remote server.
|
||||
func (pf *PortForwarder) handleConnection(conn net.Conn, port ForwardedPort) {
|
||||
defer conn.Close()
|
||||
|
||||
if pf.out != nil {
|
||||
fmt.Fprintf(pf.out, "Handling connection for %d\n", port.Local)
|
||||
}
|
||||
|
||||
requestID := pf.nextRequestID()
|
||||
|
||||
// create error stream
|
||||
headers := http.Header{}
|
||||
headers.Set(v1.StreamType, v1.StreamTypeError)
|
||||
headers.Set(v1.PortHeader, fmt.Sprintf("%d", port.Remote))
|
||||
headers.Set(v1.PortForwardRequestIDHeader, strconv.Itoa(requestID))
|
||||
errorStream, err := pf.streamConn.CreateStream(headers)
|
||||
if err != nil {
|
||||
runtime.HandleError(fmt.Errorf("error creating error stream for port %d -> %d: %v", port.Local, port.Remote, err))
|
||||
return
|
||||
}
|
||||
// we're not writing to this stream
|
||||
errorStream.Close()
|
||||
defer pf.streamConn.RemoveStreams(errorStream)
|
||||
|
||||
errorChan := make(chan error)
|
||||
go func() {
|
||||
message, err := io.ReadAll(errorStream)
|
||||
switch {
|
||||
case err != nil:
|
||||
errorChan <- fmt.Errorf("error reading from error stream for port %d -> %d: %v", port.Local, port.Remote, err)
|
||||
case len(message) > 0:
|
||||
errorChan <- fmt.Errorf("an error occurred forwarding %d -> %d: %v", port.Local, port.Remote, string(message))
|
||||
}
|
||||
close(errorChan)
|
||||
}()
|
||||
|
||||
// create data stream
|
||||
headers.Set(v1.StreamType, v1.StreamTypeData)
|
||||
dataStream, err := pf.streamConn.CreateStream(headers)
|
||||
if err != nil {
|
||||
runtime.HandleError(fmt.Errorf("error creating forwarding stream for port %d -> %d: %v", port.Local, port.Remote, err))
|
||||
return
|
||||
}
|
||||
defer pf.streamConn.RemoveStreams(dataStream)
|
||||
|
||||
localError := make(chan struct{})
|
||||
remoteDone := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
// Copy from the remote side to the local port.
|
||||
if _, err := io.Copy(conn, dataStream); err != nil && !strings.Contains(strings.ToLower(err.Error()), networkClosedError) {
|
||||
runtime.HandleError(fmt.Errorf("error copying from remote stream to local connection: %v", err))
|
||||
}
|
||||
|
||||
// inform the select below that the remote copy is done
|
||||
close(remoteDone)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
// inform server we're not sending any more data after copy unblocks
|
||||
defer dataStream.Close()
|
||||
|
||||
// Copy from the local port to the remote side.
|
||||
if _, err := io.Copy(dataStream, conn); err != nil && !strings.Contains(strings.ToLower(err.Error()), networkClosedError) {
|
||||
runtime.HandleError(fmt.Errorf("error copying from local connection to remote stream: %v", err))
|
||||
// break out of the select below without waiting for the other copy to finish
|
||||
close(localError)
|
||||
}
|
||||
}()
|
||||
|
||||
// wait for either a local->remote error or for copying from remote->local to finish
|
||||
select {
|
||||
case <-remoteDone:
|
||||
case <-localError:
|
||||
}
|
||||
|
||||
// reset dataStream to discard any unsent data, preventing port forwarding from being blocked.
|
||||
// we must reset dataStream before waiting on errorChan, otherwise,
|
||||
// the blocking data will affect errorStream and cause <-errorChan to block indefinitely.
|
||||
_ = dataStream.Reset()
|
||||
|
||||
// always expect something on errorChan (it may be nil)
|
||||
err = <-errorChan
|
||||
if err != nil {
|
||||
runtime.HandleError(err)
|
||||
pf.streamConn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Close stops all listeners of PortForwarder.
|
||||
func (pf *PortForwarder) Close() {
|
||||
// stop all listeners
|
||||
for _, l := range pf.listeners {
|
||||
if err := l.Close(); err != nil {
|
||||
runtime.HandleError(fmt.Errorf("error closing listener: %v", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetPorts will return the ports that were forwarded; this can be used to
|
||||
// retrieve the locally-bound port in cases where the input was port 0. This
|
||||
// function will signal an error if the Ready channel is nil or if the
|
||||
// listeners are not ready yet; this function will succeed after the Ready
|
||||
// channel has been closed.
|
||||
func (pf *PortForwarder) GetPorts() ([]ForwardedPort, error) {
|
||||
if pf.Ready == nil {
|
||||
return nil, fmt.Errorf("no Ready channel provided")
|
||||
}
|
||||
select {
|
||||
case <-pf.Ready:
|
||||
return pf.ports, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("listeners not ready")
|
||||
}
|
||||
}
|
158
e2e/vendor/k8s.io/client-go/tools/portforward/tunneling_connection.go
generated
vendored
Normal file
158
e2e/vendor/k8s.io/client-go/tools/portforward/tunneling_connection.go
generated
vendored
Normal file
@ -0,0 +1,158 @@
|
||||
/*
|
||||
Copyright 2023 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 portforward
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
gwebsocket "github.com/gorilla/websocket"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
var _ net.Conn = &TunnelingConnection{}
|
||||
|
||||
// TunnelingConnection implements the "httpstream.Connection" interface, wrapping
|
||||
// a websocket connection that tunnels SPDY.
|
||||
type TunnelingConnection struct {
|
||||
name string
|
||||
conn *gwebsocket.Conn
|
||||
inProgressMessage io.Reader
|
||||
closeOnce sync.Once
|
||||
}
|
||||
|
||||
// NewTunnelingConnection wraps the passed gorilla/websockets connection
|
||||
// with the TunnelingConnection struct (implementing net.Conn).
|
||||
func NewTunnelingConnection(name string, conn *gwebsocket.Conn) *TunnelingConnection {
|
||||
return &TunnelingConnection{
|
||||
name: name,
|
||||
conn: conn,
|
||||
}
|
||||
}
|
||||
|
||||
// Read implements "io.Reader" interface, reading from the stored connection
|
||||
// into the passed buffer "p". Returns the number of bytes read and an error.
|
||||
// Can keep track of the "inProgress" messsage from the tunneled connection.
|
||||
func (c *TunnelingConnection) Read(p []byte) (int, error) {
|
||||
klog.V(7).Infof("%s: tunneling connection read...", c.name)
|
||||
defer klog.V(7).Infof("%s: tunneling connection read...complete", c.name)
|
||||
for {
|
||||
if c.inProgressMessage == nil {
|
||||
klog.V(8).Infof("%s: tunneling connection read before NextReader()...", c.name)
|
||||
messageType, nextReader, err := c.conn.NextReader()
|
||||
if err != nil {
|
||||
closeError := &gwebsocket.CloseError{}
|
||||
if errors.As(err, &closeError) && closeError.Code == gwebsocket.CloseNormalClosure {
|
||||
return 0, io.EOF
|
||||
}
|
||||
klog.V(4).Infof("%s:tunneling connection NextReader() error: %v", c.name, err)
|
||||
return 0, err
|
||||
}
|
||||
if messageType != gwebsocket.BinaryMessage {
|
||||
return 0, fmt.Errorf("invalid message type received")
|
||||
}
|
||||
c.inProgressMessage = nextReader
|
||||
}
|
||||
klog.V(8).Infof("%s: tunneling connection read in progress message...", c.name)
|
||||
i, err := c.inProgressMessage.Read(p)
|
||||
if i == 0 && err == io.EOF {
|
||||
c.inProgressMessage = nil
|
||||
} else {
|
||||
klog.V(8).Infof("%s: read %d bytes, error=%v, bytes=% X", c.name, i, err, p[:i])
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write implements "io.Writer" interface, copying the data in the passed
|
||||
// byte array "p" into the stored tunneled connection. Returns the number
|
||||
// of bytes written and an error.
|
||||
func (c *TunnelingConnection) Write(p []byte) (n int, err error) {
|
||||
klog.V(7).Infof("%s: write: %d bytes, bytes=% X", c.name, len(p), p)
|
||||
defer klog.V(7).Infof("%s: tunneling connection write...complete", c.name)
|
||||
w, err := c.conn.NextWriter(gwebsocket.BinaryMessage)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer func() {
|
||||
// close, which flushes the message
|
||||
closeErr := w.Close()
|
||||
if closeErr != nil && err == nil {
|
||||
// if closing/flushing errored and we weren't already returning an error, return the close error
|
||||
err = closeErr
|
||||
}
|
||||
}()
|
||||
|
||||
n, err = w.Write(p)
|
||||
return
|
||||
}
|
||||
|
||||
// Close implements "io.Closer" interface, signaling the other tunneled connection
|
||||
// endpoint, and closing the tunneled connection only once.
|
||||
func (c *TunnelingConnection) Close() error {
|
||||
var err error
|
||||
c.closeOnce.Do(func() {
|
||||
klog.V(7).Infof("%s: tunneling connection Close()...", c.name)
|
||||
// Signal other endpoint that websocket connection is closing; ignore error.
|
||||
normalCloseMsg := gwebsocket.FormatCloseMessage(gwebsocket.CloseNormalClosure, "")
|
||||
writeControlErr := c.conn.WriteControl(gwebsocket.CloseMessage, normalCloseMsg, time.Now().Add(time.Second))
|
||||
closeErr := c.conn.Close()
|
||||
if closeErr != nil {
|
||||
err = closeErr
|
||||
} else if writeControlErr != nil {
|
||||
err = writeControlErr
|
||||
}
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// LocalAddr implements part of the "net.Conn" interface, returning the local
|
||||
// endpoint network address of the tunneled connection.
|
||||
func (c *TunnelingConnection) LocalAddr() net.Addr {
|
||||
return c.conn.LocalAddr()
|
||||
}
|
||||
|
||||
// LocalAddr implements part of the "net.Conn" interface, returning the remote
|
||||
// endpoint network address of the tunneled connection.
|
||||
func (c *TunnelingConnection) RemoteAddr() net.Addr {
|
||||
return c.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
// SetDeadline sets the *absolute* time in the future for both
|
||||
// read and write deadlines. Returns an error if one occurs.
|
||||
func (c *TunnelingConnection) SetDeadline(t time.Time) error {
|
||||
rerr := c.SetReadDeadline(t)
|
||||
werr := c.SetWriteDeadline(t)
|
||||
return errors.Join(rerr, werr)
|
||||
}
|
||||
|
||||
// SetDeadline sets the *absolute* time in the future for the
|
||||
// read deadlines. Returns an error if one occurs.
|
||||
func (c *TunnelingConnection) SetReadDeadline(t time.Time) error {
|
||||
return c.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
// SetDeadline sets the *absolute* time in the future for the
|
||||
// write deadlines. Returns an error if one occurs.
|
||||
func (c *TunnelingConnection) SetWriteDeadline(t time.Time) error {
|
||||
return c.conn.SetWriteDeadline(t)
|
||||
}
|
93
e2e/vendor/k8s.io/client-go/tools/portforward/tunneling_dialer.go
generated
vendored
Normal file
93
e2e/vendor/k8s.io/client-go/tools/portforward/tunneling_dialer.go
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
Copyright 2023 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 portforward
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/spdy"
|
||||
constants "k8s.io/apimachinery/pkg/util/portforward"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/transport/websocket"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
const PingPeriod = 10 * time.Second
|
||||
|
||||
// tunnelingDialer implements "httpstream.Dial" interface
|
||||
type tunnelingDialer struct {
|
||||
url *url.URL
|
||||
transport http.RoundTripper
|
||||
holder websocket.ConnectionHolder
|
||||
}
|
||||
|
||||
// NewTunnelingDialer creates and returns the tunnelingDialer structure which implemements the "httpstream.Dialer"
|
||||
// interface. The dialer can upgrade a websocket request, creating a websocket connection. This function
|
||||
// returns an error if one occurs.
|
||||
func NewSPDYOverWebsocketDialer(url *url.URL, config *restclient.Config) (httpstream.Dialer, error) {
|
||||
transport, holder, err := websocket.RoundTripperFor(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tunnelingDialer{
|
||||
url: url,
|
||||
transport: transport,
|
||||
holder: holder,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Dial upgrades to a tunneling streaming connection, returning a SPDY connection
|
||||
// containing a WebSockets connection (which implements "net.Conn"). Also
|
||||
// returns the protocol negotiated, or an error.
|
||||
func (d *tunnelingDialer) Dial(protocols ...string) (httpstream.Connection, string, error) {
|
||||
// There is no passed context, so skip the context when creating request for now.
|
||||
// Websockets requires "GET" method: RFC 6455 Sec. 4.1 (page 17).
|
||||
req, err := http.NewRequest("GET", d.url.String(), nil)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
// Add the spdy tunneling prefix to the requested protocols. The tunneling
|
||||
// handler will know how to negotiate these protocols.
|
||||
tunnelingProtocols := []string{}
|
||||
for _, protocol := range protocols {
|
||||
tunnelingProtocol := constants.WebsocketsSPDYTunnelingPrefix + protocol
|
||||
tunnelingProtocols = append(tunnelingProtocols, tunnelingProtocol)
|
||||
}
|
||||
klog.V(4).Infoln("Before WebSocket Upgrade Connection...")
|
||||
conn, err := websocket.Negotiate(d.transport, d.holder, req, tunnelingProtocols...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if conn == nil {
|
||||
return nil, "", fmt.Errorf("negotiated websocket connection is nil")
|
||||
}
|
||||
protocol := conn.Subprotocol()
|
||||
protocol = strings.TrimPrefix(protocol, constants.WebsocketsSPDYTunnelingPrefix)
|
||||
klog.V(4).Infof("negotiated protocol: %s", protocol)
|
||||
|
||||
// Wrap the websocket connection which implements "net.Conn".
|
||||
tConn := NewTunnelingConnection("client", conn)
|
||||
// Create SPDY connection injecting the previously created tunneling connection.
|
||||
spdyConn, err := spdy.NewClientConnectionWithPings(tConn, PingPeriod)
|
||||
|
||||
return spdyConn, protocol, err
|
||||
}
|
6
e2e/vendor/k8s.io/client-go/tools/record/OWNERS
generated
vendored
Normal file
6
e2e/vendor/k8s.io/client-go/tools/record/OWNERS
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
reviewers:
|
||||
- sig-instrumentation-reviewers
|
||||
approvers:
|
||||
- sig-instrumentation-approvers
|
19
e2e/vendor/k8s.io/client-go/tools/record/doc.go
generated
vendored
Normal file
19
e2e/vendor/k8s.io/client-go/tools/record/doc.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
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 record has all client logic for recording and reporting
|
||||
// "k8s.io/api/core/v1".Event events.
|
||||
package record // import "k8s.io/client-go/tools/record"
|
527
e2e/vendor/k8s.io/client-go/tools/record/event.go
generated
vendored
Normal file
527
e2e/vendor/k8s.io/client-go/tools/record/event.go
generated
vendored
Normal file
@ -0,0 +1,527 @@
|
||||
/*
|
||||
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 record
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
internalevents "k8s.io/client-go/tools/internal/events"
|
||||
"k8s.io/client-go/tools/record/util"
|
||||
ref "k8s.io/client-go/tools/reference"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
const maxTriesPerEvent = 12
|
||||
|
||||
var defaultSleepDuration = 10 * time.Second
|
||||
|
||||
const maxQueuedEvents = 1000
|
||||
|
||||
// EventSink knows how to store events (client.Client implements it.)
|
||||
// EventSink must respect the namespace that will be embedded in 'event'.
|
||||
// It is assumed that EventSink will return the same sorts of errors as
|
||||
// pkg/client's REST client.
|
||||
type EventSink interface {
|
||||
Create(event *v1.Event) (*v1.Event, error)
|
||||
Update(event *v1.Event) (*v1.Event, error)
|
||||
Patch(oldEvent *v1.Event, data []byte) (*v1.Event, error)
|
||||
}
|
||||
|
||||
// CorrelatorOptions allows you to change the default of the EventSourceObjectSpamFilter
|
||||
// and EventAggregator in EventCorrelator
|
||||
type CorrelatorOptions struct {
|
||||
// The lru cache size used for both EventSourceObjectSpamFilter and the EventAggregator
|
||||
// If not specified (zero value), the default specified in events_cache.go will be picked
|
||||
// This means that the LRUCacheSize has to be greater than 0.
|
||||
LRUCacheSize int
|
||||
// The burst size used by the token bucket rate filtering in EventSourceObjectSpamFilter
|
||||
// If not specified (zero value), the default specified in events_cache.go will be picked
|
||||
// This means that the BurstSize has to be greater than 0.
|
||||
BurstSize int
|
||||
// The fill rate of the token bucket in queries per second in EventSourceObjectSpamFilter
|
||||
// If not specified (zero value), the default specified in events_cache.go will be picked
|
||||
// This means that the QPS has to be greater than 0.
|
||||
QPS float32
|
||||
// The func used by the EventAggregator to group event keys for aggregation
|
||||
// If not specified (zero value), EventAggregatorByReasonFunc will be used
|
||||
KeyFunc EventAggregatorKeyFunc
|
||||
// The func used by the EventAggregator to produced aggregated message
|
||||
// If not specified (zero value), EventAggregatorByReasonMessageFunc will be used
|
||||
MessageFunc EventAggregatorMessageFunc
|
||||
// The number of events in an interval before aggregation happens by the EventAggregator
|
||||
// If not specified (zero value), the default specified in events_cache.go will be picked
|
||||
// This means that the MaxEvents has to be greater than 0
|
||||
MaxEvents int
|
||||
// The amount of time in seconds that must transpire since the last occurrence of a similar event before it is considered new by the EventAggregator
|
||||
// If not specified (zero value), the default specified in events_cache.go will be picked
|
||||
// This means that the MaxIntervalInSeconds has to be greater than 0
|
||||
MaxIntervalInSeconds int
|
||||
// The clock used by the EventAggregator to allow for testing
|
||||
// If not specified (zero value), clock.RealClock{} will be used
|
||||
Clock clock.PassiveClock
|
||||
// The func used by EventFilterFunc, which returns a key for given event, based on which filtering will take place
|
||||
// If not specified (zero value), getSpamKey will be used
|
||||
SpamKeyFunc EventSpamKeyFunc
|
||||
}
|
||||
|
||||
// EventRecorder knows how to record events on behalf of an EventSource.
|
||||
type EventRecorder interface {
|
||||
// Event constructs an event from the given information and puts it in the queue for sending.
|
||||
// 'object' is the object this event is about. Event will make a reference-- or you may also
|
||||
// pass a reference to the object directly.
|
||||
// 'eventtype' of this event, and can be one of Normal, Warning. New types could be added in future
|
||||
// 'reason' is the reason this event is generated. 'reason' should be short and unique; it
|
||||
// should be in UpperCamelCase format (starting with a capital letter). "reason" will be used
|
||||
// to automate handling of events, so imagine people writing switch statements to handle them.
|
||||
// You want to make that easy.
|
||||
// 'message' is intended to be human readable.
|
||||
//
|
||||
// The resulting event will be created in the same namespace as the reference object.
|
||||
Event(object runtime.Object, eventtype, reason, message string)
|
||||
|
||||
// Eventf is just like Event, but with Sprintf for the message field.
|
||||
Eventf(object runtime.Object, eventtype, reason, messageFmt string, args ...interface{})
|
||||
|
||||
// AnnotatedEventf is just like eventf, but with annotations attached
|
||||
AnnotatedEventf(object runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{})
|
||||
}
|
||||
|
||||
// EventRecorderLogger extends EventRecorder such that a logger can
|
||||
// be set for methods in EventRecorder. Normally, those methods
|
||||
// uses the global default logger to record errors and debug messages.
|
||||
// If that is not desired, use WithLogger to provide a logger instance.
|
||||
type EventRecorderLogger interface {
|
||||
EventRecorder
|
||||
|
||||
// WithLogger replaces the context used for logging. This is a cheap call
|
||||
// and meant to be used for contextual logging:
|
||||
// recorder := ...
|
||||
// logger := klog.FromContext(ctx)
|
||||
// recorder.WithLogger(logger).Eventf(...)
|
||||
WithLogger(logger klog.Logger) EventRecorderLogger
|
||||
}
|
||||
|
||||
// EventBroadcaster knows how to receive events and send them to any EventSink, watcher, or log.
|
||||
type EventBroadcaster interface {
|
||||
// StartEventWatcher starts sending events received from this EventBroadcaster to the given
|
||||
// event handler function. The return value can be ignored or used to stop recording, if
|
||||
// desired.
|
||||
StartEventWatcher(eventHandler func(*v1.Event)) watch.Interface
|
||||
|
||||
// StartRecordingToSink starts sending events received from this EventBroadcaster to the given
|
||||
// sink. The return value can be ignored or used to stop recording, if desired.
|
||||
StartRecordingToSink(sink EventSink) watch.Interface
|
||||
|
||||
// StartLogging starts sending events received from this EventBroadcaster to the given logging
|
||||
// function. The return value can be ignored or used to stop recording, if desired.
|
||||
StartLogging(logf func(format string, args ...interface{})) watch.Interface
|
||||
|
||||
// StartStructuredLogging starts sending events received from this EventBroadcaster to the structured
|
||||
// logging function. The return value can be ignored or used to stop recording, if desired.
|
||||
StartStructuredLogging(verbosity klog.Level) watch.Interface
|
||||
|
||||
// NewRecorder returns an EventRecorder that can be used to send events to this EventBroadcaster
|
||||
// with the event source set to the given event source.
|
||||
NewRecorder(scheme *runtime.Scheme, source v1.EventSource) EventRecorderLogger
|
||||
|
||||
// Shutdown shuts down the broadcaster. Once the broadcaster is shut
|
||||
// down, it will only try to record an event in a sink once before
|
||||
// giving up on it with an error message.
|
||||
Shutdown()
|
||||
}
|
||||
|
||||
// EventRecorderAdapter is a wrapper around a "k8s.io/client-go/tools/record".EventRecorder
|
||||
// implementing the new "k8s.io/client-go/tools/events".EventRecorder interface.
|
||||
type EventRecorderAdapter struct {
|
||||
recorder EventRecorderLogger
|
||||
}
|
||||
|
||||
var _ internalevents.EventRecorder = &EventRecorderAdapter{}
|
||||
|
||||
// NewEventRecorderAdapter returns an adapter implementing the new
|
||||
// "k8s.io/client-go/tools/events".EventRecorder interface.
|
||||
func NewEventRecorderAdapter(recorder EventRecorderLogger) *EventRecorderAdapter {
|
||||
return &EventRecorderAdapter{
|
||||
recorder: recorder,
|
||||
}
|
||||
}
|
||||
|
||||
// Eventf is a wrapper around v1 Eventf
|
||||
func (a *EventRecorderAdapter) Eventf(regarding, _ runtime.Object, eventtype, reason, action, note string, args ...interface{}) {
|
||||
a.recorder.Eventf(regarding, eventtype, reason, note, args...)
|
||||
}
|
||||
|
||||
func (a *EventRecorderAdapter) WithLogger(logger klog.Logger) internalevents.EventRecorderLogger {
|
||||
return &EventRecorderAdapter{
|
||||
recorder: a.recorder.WithLogger(logger),
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a new event broadcaster.
|
||||
func NewBroadcaster(opts ...BroadcasterOption) EventBroadcaster {
|
||||
c := config{
|
||||
sleepDuration: defaultSleepDuration,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(&c)
|
||||
}
|
||||
eventBroadcaster := &eventBroadcasterImpl{
|
||||
Broadcaster: watch.NewLongQueueBroadcaster(maxQueuedEvents, watch.DropIfChannelFull),
|
||||
sleepDuration: c.sleepDuration,
|
||||
options: c.CorrelatorOptions,
|
||||
}
|
||||
ctx := c.Context
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
// The are two scenarios where it makes no sense to wait for context cancelation:
|
||||
// - The context was nil.
|
||||
// - The context was context.Background() to begin with.
|
||||
//
|
||||
// Both cases get checked here: we have cancelation if (and only if) there is a channel.
|
||||
haveCtxCancelation := ctx.Done() != nil
|
||||
|
||||
eventBroadcaster.cancelationCtx, eventBroadcaster.cancel = context.WithCancel(ctx)
|
||||
|
||||
if haveCtxCancelation {
|
||||
// Calling Shutdown is not required when a context was provided:
|
||||
// when the context is canceled, this goroutine will shut down
|
||||
// the broadcaster.
|
||||
//
|
||||
// If Shutdown is called first, then this goroutine will
|
||||
// also stop.
|
||||
go func() {
|
||||
<-eventBroadcaster.cancelationCtx.Done()
|
||||
eventBroadcaster.Broadcaster.Shutdown()
|
||||
}()
|
||||
}
|
||||
|
||||
return eventBroadcaster
|
||||
}
|
||||
|
||||
func NewBroadcasterForTests(sleepDuration time.Duration) EventBroadcaster {
|
||||
return NewBroadcaster(WithSleepDuration(sleepDuration))
|
||||
}
|
||||
|
||||
func NewBroadcasterWithCorrelatorOptions(options CorrelatorOptions) EventBroadcaster {
|
||||
return NewBroadcaster(WithCorrelatorOptions(options))
|
||||
}
|
||||
|
||||
func WithCorrelatorOptions(options CorrelatorOptions) BroadcasterOption {
|
||||
return func(c *config) {
|
||||
c.CorrelatorOptions = options
|
||||
}
|
||||
}
|
||||
|
||||
// WithContext sets a context for the broadcaster. Canceling the context will
|
||||
// shut down the broadcaster, Shutdown doesn't need to be called. The context
|
||||
// can also be used to provide a logger.
|
||||
func WithContext(ctx context.Context) BroadcasterOption {
|
||||
return func(c *config) {
|
||||
c.Context = ctx
|
||||
}
|
||||
}
|
||||
|
||||
func WithSleepDuration(sleepDuration time.Duration) BroadcasterOption {
|
||||
return func(c *config) {
|
||||
c.sleepDuration = sleepDuration
|
||||
}
|
||||
}
|
||||
|
||||
type BroadcasterOption func(*config)
|
||||
|
||||
type config struct {
|
||||
CorrelatorOptions
|
||||
context.Context
|
||||
sleepDuration time.Duration
|
||||
}
|
||||
|
||||
type eventBroadcasterImpl struct {
|
||||
*watch.Broadcaster
|
||||
sleepDuration time.Duration
|
||||
options CorrelatorOptions
|
||||
cancelationCtx context.Context
|
||||
cancel func()
|
||||
}
|
||||
|
||||
// StartRecordingToSink starts sending events received from the specified eventBroadcaster to the given sink.
|
||||
// The return value can be ignored or used to stop recording, if desired.
|
||||
// TODO: make me an object with parameterizable queue length and retry interval
|
||||
func (e *eventBroadcasterImpl) StartRecordingToSink(sink EventSink) watch.Interface {
|
||||
eventCorrelator := NewEventCorrelatorWithOptions(e.options)
|
||||
return e.StartEventWatcher(
|
||||
func(event *v1.Event) {
|
||||
e.recordToSink(sink, event, eventCorrelator)
|
||||
})
|
||||
}
|
||||
|
||||
func (e *eventBroadcasterImpl) Shutdown() {
|
||||
e.Broadcaster.Shutdown()
|
||||
e.cancel()
|
||||
}
|
||||
|
||||
func (e *eventBroadcasterImpl) recordToSink(sink EventSink, event *v1.Event, eventCorrelator *EventCorrelator) {
|
||||
// Make a copy before modification, because there could be multiple listeners.
|
||||
// Events are safe to copy like this.
|
||||
eventCopy := *event
|
||||
event = &eventCopy
|
||||
result, err := eventCorrelator.EventCorrelate(event)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
}
|
||||
if result.Skip {
|
||||
return
|
||||
}
|
||||
tries := 0
|
||||
for {
|
||||
if recordEvent(e.cancelationCtx, sink, result.Event, result.Patch, result.Event.Count > 1, eventCorrelator) {
|
||||
break
|
||||
}
|
||||
tries++
|
||||
if tries >= maxTriesPerEvent {
|
||||
klog.FromContext(e.cancelationCtx).Error(nil, "Unable to write event (retry limit exceeded!)", "event", event)
|
||||
break
|
||||
}
|
||||
|
||||
// Randomize the first sleep so that various clients won't all be
|
||||
// synced up if the master goes down.
|
||||
delay := e.sleepDuration
|
||||
if tries == 1 {
|
||||
delay = time.Duration(float64(delay) * rand.Float64())
|
||||
}
|
||||
select {
|
||||
case <-e.cancelationCtx.Done():
|
||||
klog.FromContext(e.cancelationCtx).Error(nil, "Unable to write event (broadcaster is shut down)", "event", event)
|
||||
return
|
||||
case <-time.After(delay):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// recordEvent attempts to write event to a sink. It returns true if the event
|
||||
// was successfully recorded or discarded, false if it should be retried.
|
||||
// If updateExistingEvent is false, it creates a new event, otherwise it updates
|
||||
// existing event.
|
||||
func recordEvent(ctx context.Context, sink EventSink, event *v1.Event, patch []byte, updateExistingEvent bool, eventCorrelator *EventCorrelator) bool {
|
||||
var newEvent *v1.Event
|
||||
var err error
|
||||
if updateExistingEvent {
|
||||
newEvent, err = sink.Patch(event, patch)
|
||||
}
|
||||
// Update can fail because the event may have been removed and it no longer exists.
|
||||
if !updateExistingEvent || (updateExistingEvent && util.IsKeyNotFoundError(err)) {
|
||||
// Making sure that ResourceVersion is empty on creation
|
||||
event.ResourceVersion = ""
|
||||
newEvent, err = sink.Create(event)
|
||||
}
|
||||
if err == nil {
|
||||
// we need to update our event correlator with the server returned state to handle name/resourceversion
|
||||
eventCorrelator.UpdateState(newEvent)
|
||||
return true
|
||||
}
|
||||
|
||||
// If we can't contact the server, then hold everything while we keep trying.
|
||||
// Otherwise, something about the event is malformed and we should abandon it.
|
||||
switch err.(type) {
|
||||
case *restclient.RequestConstructionError:
|
||||
// We will construct the request the same next time, so don't keep trying.
|
||||
klog.FromContext(ctx).Error(err, "Unable to construct event (will not retry!)", "event", event)
|
||||
return true
|
||||
case *errors.StatusError:
|
||||
if errors.IsAlreadyExists(err) || errors.HasStatusCause(err, v1.NamespaceTerminatingCause) {
|
||||
klog.FromContext(ctx).V(5).Info("Server rejected event (will not retry!)", "event", event, "err", err)
|
||||
} else {
|
||||
klog.FromContext(ctx).Error(err, "Server rejected event (will not retry!)", "event", event)
|
||||
}
|
||||
return true
|
||||
case *errors.UnexpectedObjectError:
|
||||
// We don't expect this; it implies the server's response didn't match a
|
||||
// known pattern. Go ahead and retry.
|
||||
default:
|
||||
// This case includes actual http transport errors. Go ahead and retry.
|
||||
}
|
||||
klog.FromContext(ctx).Error(err, "Unable to write event (may retry after sleeping)", "event", event)
|
||||
return false
|
||||
}
|
||||
|
||||
// StartLogging starts sending events received from this EventBroadcaster to the given logging function.
|
||||
// The return value can be ignored or used to stop recording, if desired.
|
||||
func (e *eventBroadcasterImpl) StartLogging(logf func(format string, args ...interface{})) watch.Interface {
|
||||
return e.StartEventWatcher(
|
||||
func(e *v1.Event) {
|
||||
logf("Event(%#v): type: '%v' reason: '%v' %v", e.InvolvedObject, e.Type, e.Reason, e.Message)
|
||||
})
|
||||
}
|
||||
|
||||
// StartStructuredLogging starts sending events received from this EventBroadcaster to a structured logger.
|
||||
// The logger is retrieved from a context if the broadcaster was constructed with a context, otherwise
|
||||
// the global default is used.
|
||||
// The return value can be ignored or used to stop recording, if desired.
|
||||
func (e *eventBroadcasterImpl) StartStructuredLogging(verbosity klog.Level) watch.Interface {
|
||||
loggerV := klog.FromContext(e.cancelationCtx).V(int(verbosity))
|
||||
return e.StartEventWatcher(
|
||||
func(e *v1.Event) {
|
||||
loggerV.Info("Event occurred", "object", klog.KRef(e.InvolvedObject.Namespace, e.InvolvedObject.Name), "fieldPath", e.InvolvedObject.FieldPath, "kind", e.InvolvedObject.Kind, "apiVersion", e.InvolvedObject.APIVersion, "type", e.Type, "reason", e.Reason, "message", e.Message)
|
||||
})
|
||||
}
|
||||
|
||||
// StartEventWatcher starts sending events received from this EventBroadcaster to the given event handler function.
|
||||
// The return value can be ignored or used to stop recording, if desired.
|
||||
func (e *eventBroadcasterImpl) StartEventWatcher(eventHandler func(*v1.Event)) watch.Interface {
|
||||
watcher, err := e.Watch()
|
||||
if err != nil {
|
||||
// This function traditionally returns no error even though it can fail.
|
||||
// Instead, it logs the error and returns an empty watch. The empty
|
||||
// watch ensures that callers don't crash when calling Stop.
|
||||
klog.FromContext(e.cancelationCtx).Error(err, "Unable start event watcher (will not retry!)")
|
||||
return watch.NewEmptyWatch()
|
||||
}
|
||||
go func() {
|
||||
defer utilruntime.HandleCrash()
|
||||
for {
|
||||
select {
|
||||
case <-e.cancelationCtx.Done():
|
||||
watcher.Stop()
|
||||
return
|
||||
case watchEvent := <-watcher.ResultChan():
|
||||
event, ok := watchEvent.Object.(*v1.Event)
|
||||
if !ok {
|
||||
// This is all local, so there's no reason this should
|
||||
// ever happen.
|
||||
continue
|
||||
}
|
||||
eventHandler(event)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return watcher
|
||||
}
|
||||
|
||||
// NewRecorder returns an EventRecorder that records events with the given event source.
|
||||
func (e *eventBroadcasterImpl) NewRecorder(scheme *runtime.Scheme, source v1.EventSource) EventRecorderLogger {
|
||||
return &recorderImplLogger{recorderImpl: &recorderImpl{scheme, source, e.Broadcaster, clock.RealClock{}}, logger: klog.Background()}
|
||||
}
|
||||
|
||||
type recorderImpl struct {
|
||||
scheme *runtime.Scheme
|
||||
source v1.EventSource
|
||||
*watch.Broadcaster
|
||||
clock clock.PassiveClock
|
||||
}
|
||||
|
||||
var _ EventRecorder = &recorderImpl{}
|
||||
|
||||
func (recorder *recorderImpl) generateEvent(logger klog.Logger, object runtime.Object, annotations map[string]string, eventtype, reason, message string) {
|
||||
ref, err := ref.GetReference(recorder.scheme, object)
|
||||
if err != nil {
|
||||
logger.Error(err, "Could not construct reference, will not report event", "object", object, "eventType", eventtype, "reason", reason, "message", message)
|
||||
return
|
||||
}
|
||||
|
||||
if !util.ValidateEventType(eventtype) {
|
||||
logger.Error(nil, "Unsupported event type", "eventType", eventtype)
|
||||
return
|
||||
}
|
||||
|
||||
event := recorder.makeEvent(ref, annotations, eventtype, reason, message)
|
||||
event.Source = recorder.source
|
||||
|
||||
event.ReportingInstance = recorder.source.Host
|
||||
event.ReportingController = recorder.source.Component
|
||||
|
||||
// NOTE: events should be a non-blocking operation, but we also need to not
|
||||
// put this in a goroutine, otherwise we'll race to write to a closed channel
|
||||
// when we go to shut down this broadcaster. Just drop events if we get overloaded,
|
||||
// and log an error if that happens (we've configured the broadcaster to drop
|
||||
// outgoing events anyway).
|
||||
sent, err := recorder.ActionOrDrop(watch.Added, event)
|
||||
if err != nil {
|
||||
logger.Error(err, "Unable to record event (will not retry!)")
|
||||
return
|
||||
}
|
||||
if !sent {
|
||||
logger.Error(nil, "Unable to record event: too many queued events, dropped event", "event", event)
|
||||
}
|
||||
}
|
||||
|
||||
func (recorder *recorderImpl) Event(object runtime.Object, eventtype, reason, message string) {
|
||||
recorder.generateEvent(klog.Background(), object, nil, eventtype, reason, message)
|
||||
}
|
||||
|
||||
func (recorder *recorderImpl) Eventf(object runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) {
|
||||
recorder.Event(object, eventtype, reason, fmt.Sprintf(messageFmt, args...))
|
||||
}
|
||||
|
||||
func (recorder *recorderImpl) AnnotatedEventf(object runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{}) {
|
||||
recorder.generateEvent(klog.Background(), object, annotations, eventtype, reason, fmt.Sprintf(messageFmt, args...))
|
||||
}
|
||||
|
||||
func (recorder *recorderImpl) makeEvent(ref *v1.ObjectReference, annotations map[string]string, eventtype, reason, message string) *v1.Event {
|
||||
t := metav1.Time{Time: recorder.clock.Now()}
|
||||
namespace := ref.Namespace
|
||||
if namespace == "" {
|
||||
namespace = metav1.NamespaceDefault
|
||||
}
|
||||
return &v1.Event{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("%v.%x", ref.Name, t.UnixNano()),
|
||||
Namespace: namespace,
|
||||
Annotations: annotations,
|
||||
},
|
||||
InvolvedObject: *ref,
|
||||
Reason: reason,
|
||||
Message: message,
|
||||
FirstTimestamp: t,
|
||||
LastTimestamp: t,
|
||||
Count: 1,
|
||||
Type: eventtype,
|
||||
}
|
||||
}
|
||||
|
||||
type recorderImplLogger struct {
|
||||
*recorderImpl
|
||||
logger klog.Logger
|
||||
}
|
||||
|
||||
var _ EventRecorderLogger = &recorderImplLogger{}
|
||||
|
||||
func (recorder recorderImplLogger) Event(object runtime.Object, eventtype, reason, message string) {
|
||||
recorder.recorderImpl.generateEvent(recorder.logger, object, nil, eventtype, reason, message)
|
||||
}
|
||||
|
||||
func (recorder recorderImplLogger) Eventf(object runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) {
|
||||
recorder.Event(object, eventtype, reason, fmt.Sprintf(messageFmt, args...))
|
||||
}
|
||||
|
||||
func (recorder recorderImplLogger) AnnotatedEventf(object runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{}) {
|
||||
recorder.generateEvent(recorder.logger, object, annotations, eventtype, reason, fmt.Sprintf(messageFmt, args...))
|
||||
}
|
||||
|
||||
func (recorder recorderImplLogger) WithLogger(logger klog.Logger) EventRecorderLogger {
|
||||
return recorderImplLogger{recorderImpl: recorder.recorderImpl, logger: logger}
|
||||
}
|
521
e2e/vendor/k8s.io/client-go/tools/record/events_cache.go
generated
vendored
Normal file
521
e2e/vendor/k8s.io/client-go/tools/record/events_cache.go
generated
vendored
Normal file
@ -0,0 +1,521 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package record
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
"k8s.io/client-go/util/flowcontrol"
|
||||
"k8s.io/utils/clock"
|
||||
"k8s.io/utils/lru"
|
||||
)
|
||||
|
||||
const (
|
||||
maxLruCacheEntries = 4096
|
||||
|
||||
// if we see the same event that varies only by message
|
||||
// more than 10 times in a 10 minute period, aggregate the event
|
||||
defaultAggregateMaxEvents = 10
|
||||
defaultAggregateIntervalInSeconds = 600
|
||||
|
||||
// by default, allow a source to send 25 events about an object
|
||||
// but control the refill rate to 1 new event every 5 minutes
|
||||
// this helps control the long-tail of events for things that are always
|
||||
// unhealthy
|
||||
defaultSpamBurst = 25
|
||||
defaultSpamQPS = 1. / 300.
|
||||
)
|
||||
|
||||
// getEventKey builds unique event key based on source, involvedObject, reason, message
|
||||
func getEventKey(event *v1.Event) string {
|
||||
return strings.Join([]string{
|
||||
event.Source.Component,
|
||||
event.Source.Host,
|
||||
event.InvolvedObject.Kind,
|
||||
event.InvolvedObject.Namespace,
|
||||
event.InvolvedObject.Name,
|
||||
event.InvolvedObject.FieldPath,
|
||||
string(event.InvolvedObject.UID),
|
||||
event.InvolvedObject.APIVersion,
|
||||
event.Type,
|
||||
event.Reason,
|
||||
event.Message,
|
||||
},
|
||||
"")
|
||||
}
|
||||
|
||||
// getSpamKey builds unique event key based on source, involvedObject
|
||||
func getSpamKey(event *v1.Event) string {
|
||||
return strings.Join([]string{
|
||||
event.Source.Component,
|
||||
event.Source.Host,
|
||||
event.InvolvedObject.Kind,
|
||||
event.InvolvedObject.Namespace,
|
||||
event.InvolvedObject.Name,
|
||||
string(event.InvolvedObject.UID),
|
||||
event.InvolvedObject.APIVersion,
|
||||
event.Type,
|
||||
},
|
||||
"")
|
||||
}
|
||||
|
||||
// EventSpamKeyFunc is a function that returns unique key based on provided event
|
||||
type EventSpamKeyFunc func(event *v1.Event) string
|
||||
|
||||
// EventFilterFunc is a function that returns true if the event should be skipped
|
||||
type EventFilterFunc func(event *v1.Event) bool
|
||||
|
||||
// EventSourceObjectSpamFilter is responsible for throttling
|
||||
// the amount of events a source and object can produce.
|
||||
type EventSourceObjectSpamFilter struct {
|
||||
// the cache that manages last synced state
|
||||
cache *lru.Cache
|
||||
|
||||
// burst is the amount of events we allow per source + object
|
||||
burst int
|
||||
|
||||
// qps is the refill rate of the token bucket in queries per second
|
||||
qps float32
|
||||
|
||||
// clock is used to allow for testing over a time interval
|
||||
clock clock.PassiveClock
|
||||
|
||||
// spamKeyFunc is a func used to create a key based on an event, which is later used to filter spam events.
|
||||
spamKeyFunc EventSpamKeyFunc
|
||||
}
|
||||
|
||||
// NewEventSourceObjectSpamFilter allows burst events from a source about an object with the specified qps refill.
|
||||
func NewEventSourceObjectSpamFilter(lruCacheSize, burst int, qps float32, clock clock.PassiveClock, spamKeyFunc EventSpamKeyFunc) *EventSourceObjectSpamFilter {
|
||||
return &EventSourceObjectSpamFilter{
|
||||
cache: lru.New(lruCacheSize),
|
||||
burst: burst,
|
||||
qps: qps,
|
||||
clock: clock,
|
||||
spamKeyFunc: spamKeyFunc,
|
||||
}
|
||||
}
|
||||
|
||||
// spamRecord holds data used to perform spam filtering decisions.
|
||||
type spamRecord struct {
|
||||
// rateLimiter controls the rate of events about this object
|
||||
rateLimiter flowcontrol.PassiveRateLimiter
|
||||
}
|
||||
|
||||
// Filter controls that a given source+object are not exceeding the allowed rate.
|
||||
func (f *EventSourceObjectSpamFilter) Filter(event *v1.Event) bool {
|
||||
var record spamRecord
|
||||
|
||||
// controls our cached information about this event
|
||||
eventKey := f.spamKeyFunc(event)
|
||||
|
||||
// do we have a record of similar events in our cache?
|
||||
value, found := f.cache.Get(eventKey)
|
||||
if found {
|
||||
record = value.(spamRecord)
|
||||
}
|
||||
|
||||
// verify we have a rate limiter for this record
|
||||
if record.rateLimiter == nil {
|
||||
record.rateLimiter = flowcontrol.NewTokenBucketPassiveRateLimiterWithClock(f.qps, f.burst, f.clock)
|
||||
}
|
||||
|
||||
// ensure we have available rate
|
||||
filter := !record.rateLimiter.TryAccept()
|
||||
|
||||
// update the cache
|
||||
f.cache.Add(eventKey, record)
|
||||
|
||||
return filter
|
||||
}
|
||||
|
||||
// EventAggregatorKeyFunc is responsible for grouping events for aggregation
|
||||
// It returns a tuple of the following:
|
||||
// aggregateKey - key the identifies the aggregate group to bucket this event
|
||||
// localKey - key that makes this event in the local group
|
||||
type EventAggregatorKeyFunc func(event *v1.Event) (aggregateKey string, localKey string)
|
||||
|
||||
// EventAggregatorByReasonFunc aggregates events by exact match on event.Source, event.InvolvedObject, event.Type,
|
||||
// event.Reason, event.ReportingController and event.ReportingInstance
|
||||
func EventAggregatorByReasonFunc(event *v1.Event) (string, string) {
|
||||
return strings.Join([]string{
|
||||
event.Source.Component,
|
||||
event.Source.Host,
|
||||
event.InvolvedObject.Kind,
|
||||
event.InvolvedObject.Namespace,
|
||||
event.InvolvedObject.Name,
|
||||
string(event.InvolvedObject.UID),
|
||||
event.InvolvedObject.APIVersion,
|
||||
event.Type,
|
||||
event.Reason,
|
||||
event.ReportingController,
|
||||
event.ReportingInstance,
|
||||
},
|
||||
""), event.Message
|
||||
}
|
||||
|
||||
// EventAggregatorMessageFunc is responsible for producing an aggregation message
|
||||
type EventAggregatorMessageFunc func(event *v1.Event) string
|
||||
|
||||
// EventAggregatorByReasonMessageFunc returns an aggregate message by prefixing the incoming message
|
||||
func EventAggregatorByReasonMessageFunc(event *v1.Event) string {
|
||||
return "(combined from similar events): " + event.Message
|
||||
}
|
||||
|
||||
// EventAggregator identifies similar events and aggregates them into a single event
|
||||
type EventAggregator struct {
|
||||
sync.RWMutex
|
||||
|
||||
// The cache that manages aggregation state
|
||||
cache *lru.Cache
|
||||
|
||||
// The function that groups events for aggregation
|
||||
keyFunc EventAggregatorKeyFunc
|
||||
|
||||
// The function that generates a message for an aggregate event
|
||||
messageFunc EventAggregatorMessageFunc
|
||||
|
||||
// The maximum number of events in the specified interval before aggregation occurs
|
||||
maxEvents uint
|
||||
|
||||
// The amount of time in seconds that must transpire since the last occurrence of a similar event before it's considered new
|
||||
maxIntervalInSeconds uint
|
||||
|
||||
// clock is used to allow for testing over a time interval
|
||||
clock clock.PassiveClock
|
||||
}
|
||||
|
||||
// NewEventAggregator returns a new instance of an EventAggregator
|
||||
func NewEventAggregator(lruCacheSize int, keyFunc EventAggregatorKeyFunc, messageFunc EventAggregatorMessageFunc,
|
||||
maxEvents int, maxIntervalInSeconds int, clock clock.PassiveClock) *EventAggregator {
|
||||
return &EventAggregator{
|
||||
cache: lru.New(lruCacheSize),
|
||||
keyFunc: keyFunc,
|
||||
messageFunc: messageFunc,
|
||||
maxEvents: uint(maxEvents),
|
||||
maxIntervalInSeconds: uint(maxIntervalInSeconds),
|
||||
clock: clock,
|
||||
}
|
||||
}
|
||||
|
||||
// aggregateRecord holds data used to perform aggregation decisions
|
||||
type aggregateRecord struct {
|
||||
// we track the number of unique local keys we have seen in the aggregate set to know when to actually aggregate
|
||||
// if the size of this set exceeds the max, we know we need to aggregate
|
||||
localKeys sets.String
|
||||
// The last time at which the aggregate was recorded
|
||||
lastTimestamp metav1.Time
|
||||
}
|
||||
|
||||
// EventAggregate checks if a similar event has been seen according to the
|
||||
// aggregation configuration (max events, max interval, etc) and returns:
|
||||
//
|
||||
// - The (potentially modified) event that should be created
|
||||
// - The cache key for the event, for correlation purposes. This will be set to
|
||||
// the full key for normal events, and to the result of
|
||||
// EventAggregatorMessageFunc for aggregate events.
|
||||
func (e *EventAggregator) EventAggregate(newEvent *v1.Event) (*v1.Event, string) {
|
||||
now := metav1.NewTime(e.clock.Now())
|
||||
var record aggregateRecord
|
||||
// eventKey is the full cache key for this event
|
||||
eventKey := getEventKey(newEvent)
|
||||
// aggregateKey is for the aggregate event, if one is needed.
|
||||
aggregateKey, localKey := e.keyFunc(newEvent)
|
||||
|
||||
// Do we have a record of similar events in our cache?
|
||||
e.Lock()
|
||||
defer e.Unlock()
|
||||
value, found := e.cache.Get(aggregateKey)
|
||||
if found {
|
||||
record = value.(aggregateRecord)
|
||||
}
|
||||
|
||||
// Is the previous record too old? If so, make a fresh one. Note: if we didn't
|
||||
// find a similar record, its lastTimestamp will be the zero value, so we
|
||||
// create a new one in that case.
|
||||
maxInterval := time.Duration(e.maxIntervalInSeconds) * time.Second
|
||||
interval := now.Time.Sub(record.lastTimestamp.Time)
|
||||
if interval > maxInterval {
|
||||
record = aggregateRecord{localKeys: sets.NewString()}
|
||||
}
|
||||
|
||||
// Write the new event into the aggregation record and put it on the cache
|
||||
record.localKeys.Insert(localKey)
|
||||
record.lastTimestamp = now
|
||||
e.cache.Add(aggregateKey, record)
|
||||
|
||||
// If we are not yet over the threshold for unique events, don't correlate them
|
||||
if uint(record.localKeys.Len()) < e.maxEvents {
|
||||
return newEvent, eventKey
|
||||
}
|
||||
|
||||
// do not grow our local key set any larger than max
|
||||
record.localKeys.PopAny()
|
||||
|
||||
// create a new aggregate event, and return the aggregateKey as the cache key
|
||||
// (so that it can be overwritten.)
|
||||
eventCopy := &v1.Event{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("%v.%x", newEvent.InvolvedObject.Name, now.UnixNano()),
|
||||
Namespace: newEvent.Namespace,
|
||||
},
|
||||
Count: 1,
|
||||
FirstTimestamp: now,
|
||||
InvolvedObject: newEvent.InvolvedObject,
|
||||
LastTimestamp: now,
|
||||
Message: e.messageFunc(newEvent),
|
||||
Type: newEvent.Type,
|
||||
Reason: newEvent.Reason,
|
||||
Source: newEvent.Source,
|
||||
}
|
||||
return eventCopy, aggregateKey
|
||||
}
|
||||
|
||||
// eventLog records data about when an event was observed
|
||||
type eventLog struct {
|
||||
// The number of times the event has occurred since first occurrence.
|
||||
count uint
|
||||
|
||||
// The time at which the event was first recorded.
|
||||
firstTimestamp metav1.Time
|
||||
|
||||
// The unique name of the first occurrence of this event
|
||||
name string
|
||||
|
||||
// Resource version returned from previous interaction with server
|
||||
resourceVersion string
|
||||
}
|
||||
|
||||
// eventLogger logs occurrences of an event
|
||||
type eventLogger struct {
|
||||
sync.RWMutex
|
||||
cache *lru.Cache
|
||||
clock clock.PassiveClock
|
||||
}
|
||||
|
||||
// newEventLogger observes events and counts their frequencies
|
||||
func newEventLogger(lruCacheEntries int, clock clock.PassiveClock) *eventLogger {
|
||||
return &eventLogger{cache: lru.New(lruCacheEntries), clock: clock}
|
||||
}
|
||||
|
||||
// eventObserve records an event, or updates an existing one if key is a cache hit
|
||||
func (e *eventLogger) eventObserve(newEvent *v1.Event, key string) (*v1.Event, []byte, error) {
|
||||
var (
|
||||
patch []byte
|
||||
err error
|
||||
)
|
||||
eventCopy := *newEvent
|
||||
event := &eventCopy
|
||||
|
||||
e.Lock()
|
||||
defer e.Unlock()
|
||||
|
||||
// Check if there is an existing event we should update
|
||||
lastObservation := e.lastEventObservationFromCache(key)
|
||||
|
||||
// If we found a result, prepare a patch
|
||||
if lastObservation.count > 0 {
|
||||
// update the event based on the last observation so patch will work as desired
|
||||
event.Name = lastObservation.name
|
||||
event.ResourceVersion = lastObservation.resourceVersion
|
||||
event.FirstTimestamp = lastObservation.firstTimestamp
|
||||
event.Count = int32(lastObservation.count) + 1
|
||||
|
||||
eventCopy2 := *event
|
||||
eventCopy2.Count = 0
|
||||
eventCopy2.LastTimestamp = metav1.NewTime(time.Unix(0, 0))
|
||||
eventCopy2.Message = ""
|
||||
|
||||
newData, _ := json.Marshal(event)
|
||||
oldData, _ := json.Marshal(eventCopy2)
|
||||
patch, err = strategicpatch.CreateTwoWayMergePatch(oldData, newData, event)
|
||||
}
|
||||
|
||||
// record our new observation
|
||||
e.cache.Add(
|
||||
key,
|
||||
eventLog{
|
||||
count: uint(event.Count),
|
||||
firstTimestamp: event.FirstTimestamp,
|
||||
name: event.Name,
|
||||
resourceVersion: event.ResourceVersion,
|
||||
},
|
||||
)
|
||||
return event, patch, err
|
||||
}
|
||||
|
||||
// updateState updates its internal tracking information based on latest server state
|
||||
func (e *eventLogger) updateState(event *v1.Event) {
|
||||
key := getEventKey(event)
|
||||
e.Lock()
|
||||
defer e.Unlock()
|
||||
// record our new observation
|
||||
e.cache.Add(
|
||||
key,
|
||||
eventLog{
|
||||
count: uint(event.Count),
|
||||
firstTimestamp: event.FirstTimestamp,
|
||||
name: event.Name,
|
||||
resourceVersion: event.ResourceVersion,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// lastEventObservationFromCache returns the event from the cache, reads must be protected via external lock
|
||||
func (e *eventLogger) lastEventObservationFromCache(key string) eventLog {
|
||||
value, ok := e.cache.Get(key)
|
||||
if ok {
|
||||
observationValue, ok := value.(eventLog)
|
||||
if ok {
|
||||
return observationValue
|
||||
}
|
||||
}
|
||||
return eventLog{}
|
||||
}
|
||||
|
||||
// EventCorrelator processes all incoming events and performs analysis to avoid overwhelming the system. It can filter all
|
||||
// incoming events to see if the event should be filtered from further processing. It can aggregate similar events that occur
|
||||
// frequently to protect the system from spamming events that are difficult for users to distinguish. It performs de-duplication
|
||||
// to ensure events that are observed multiple times are compacted into a single event with increasing counts.
|
||||
type EventCorrelator struct {
|
||||
// the function to filter the event
|
||||
filterFunc EventFilterFunc
|
||||
// the object that performs event aggregation
|
||||
aggregator *EventAggregator
|
||||
// the object that observes events as they come through
|
||||
logger *eventLogger
|
||||
}
|
||||
|
||||
// EventCorrelateResult is the result of a Correlate
|
||||
type EventCorrelateResult struct {
|
||||
// the event after correlation
|
||||
Event *v1.Event
|
||||
// if provided, perform a strategic patch when updating the record on the server
|
||||
Patch []byte
|
||||
// if true, do no further processing of the event
|
||||
Skip bool
|
||||
}
|
||||
|
||||
// NewEventCorrelator returns an EventCorrelator configured with default values.
|
||||
//
|
||||
// The EventCorrelator is responsible for event filtering, aggregating, and counting
|
||||
// prior to interacting with the API server to record the event.
|
||||
//
|
||||
// The default behavior is as follows:
|
||||
// - Aggregation is performed if a similar event is recorded 10 times
|
||||
// in a 10 minute rolling interval. A similar event is an event that varies only by
|
||||
// the Event.Message field. Rather than recording the precise event, aggregation
|
||||
// will create a new event whose message reports that it has combined events with
|
||||
// the same reason.
|
||||
// - Events are incrementally counted if the exact same event is encountered multiple
|
||||
// times.
|
||||
// - A source may burst 25 events about an object, but has a refill rate budget
|
||||
// per object of 1 event every 5 minutes to control long-tail of spam.
|
||||
func NewEventCorrelator(clock clock.PassiveClock) *EventCorrelator {
|
||||
cacheSize := maxLruCacheEntries
|
||||
spamFilter := NewEventSourceObjectSpamFilter(cacheSize, defaultSpamBurst, defaultSpamQPS, clock, getSpamKey)
|
||||
return &EventCorrelator{
|
||||
filterFunc: spamFilter.Filter,
|
||||
aggregator: NewEventAggregator(
|
||||
cacheSize,
|
||||
EventAggregatorByReasonFunc,
|
||||
EventAggregatorByReasonMessageFunc,
|
||||
defaultAggregateMaxEvents,
|
||||
defaultAggregateIntervalInSeconds,
|
||||
clock),
|
||||
|
||||
logger: newEventLogger(cacheSize, clock),
|
||||
}
|
||||
}
|
||||
|
||||
func NewEventCorrelatorWithOptions(options CorrelatorOptions) *EventCorrelator {
|
||||
optionsWithDefaults := populateDefaults(options)
|
||||
spamFilter := NewEventSourceObjectSpamFilter(
|
||||
optionsWithDefaults.LRUCacheSize,
|
||||
optionsWithDefaults.BurstSize,
|
||||
optionsWithDefaults.QPS,
|
||||
optionsWithDefaults.Clock,
|
||||
optionsWithDefaults.SpamKeyFunc)
|
||||
return &EventCorrelator{
|
||||
filterFunc: spamFilter.Filter,
|
||||
aggregator: NewEventAggregator(
|
||||
optionsWithDefaults.LRUCacheSize,
|
||||
optionsWithDefaults.KeyFunc,
|
||||
optionsWithDefaults.MessageFunc,
|
||||
optionsWithDefaults.MaxEvents,
|
||||
optionsWithDefaults.MaxIntervalInSeconds,
|
||||
optionsWithDefaults.Clock),
|
||||
logger: newEventLogger(optionsWithDefaults.LRUCacheSize, optionsWithDefaults.Clock),
|
||||
}
|
||||
}
|
||||
|
||||
// populateDefaults populates the zero value options with defaults
|
||||
func populateDefaults(options CorrelatorOptions) CorrelatorOptions {
|
||||
if options.LRUCacheSize == 0 {
|
||||
options.LRUCacheSize = maxLruCacheEntries
|
||||
}
|
||||
if options.BurstSize == 0 {
|
||||
options.BurstSize = defaultSpamBurst
|
||||
}
|
||||
if options.QPS == 0 {
|
||||
options.QPS = defaultSpamQPS
|
||||
}
|
||||
if options.KeyFunc == nil {
|
||||
options.KeyFunc = EventAggregatorByReasonFunc
|
||||
}
|
||||
if options.MessageFunc == nil {
|
||||
options.MessageFunc = EventAggregatorByReasonMessageFunc
|
||||
}
|
||||
if options.MaxEvents == 0 {
|
||||
options.MaxEvents = defaultAggregateMaxEvents
|
||||
}
|
||||
if options.MaxIntervalInSeconds == 0 {
|
||||
options.MaxIntervalInSeconds = defaultAggregateIntervalInSeconds
|
||||
}
|
||||
if options.Clock == nil {
|
||||
options.Clock = clock.RealClock{}
|
||||
}
|
||||
if options.SpamKeyFunc == nil {
|
||||
options.SpamKeyFunc = getSpamKey
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
// EventCorrelate filters, aggregates, counts, and de-duplicates all incoming events
|
||||
func (c *EventCorrelator) EventCorrelate(newEvent *v1.Event) (*EventCorrelateResult, error) {
|
||||
if newEvent == nil {
|
||||
return nil, fmt.Errorf("event is nil")
|
||||
}
|
||||
aggregateEvent, ckey := c.aggregator.EventAggregate(newEvent)
|
||||
observedEvent, patch, err := c.logger.eventObserve(aggregateEvent, ckey)
|
||||
if c.filterFunc(observedEvent) {
|
||||
return &EventCorrelateResult{Skip: true}, nil
|
||||
}
|
||||
return &EventCorrelateResult{Event: observedEvent, Patch: patch}, err
|
||||
}
|
||||
|
||||
// UpdateState based on the latest observed state from server
|
||||
func (c *EventCorrelator) UpdateState(event *v1.Event) {
|
||||
c.logger.updateState(event)
|
||||
}
|
84
e2e/vendor/k8s.io/client-go/tools/record/fake.go
generated
vendored
Normal file
84
e2e/vendor/k8s.io/client-go/tools/record/fake.go
generated
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package record
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// FakeRecorder is used as a fake during tests. It is thread safe. It is usable
|
||||
// when created manually and not by NewFakeRecorder, however all events may be
|
||||
// thrown away in this case.
|
||||
type FakeRecorder struct {
|
||||
Events chan string
|
||||
|
||||
IncludeObject bool
|
||||
}
|
||||
|
||||
var _ EventRecorderLogger = &FakeRecorder{}
|
||||
|
||||
func objectString(object runtime.Object, includeObject bool) string {
|
||||
if !includeObject {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(" involvedObject{kind=%s,apiVersion=%s}",
|
||||
object.GetObjectKind().GroupVersionKind().Kind,
|
||||
object.GetObjectKind().GroupVersionKind().GroupVersion(),
|
||||
)
|
||||
}
|
||||
|
||||
func annotationsString(annotations map[string]string) string {
|
||||
if len(annotations) == 0 {
|
||||
return ""
|
||||
} else {
|
||||
return " " + fmt.Sprint(annotations)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FakeRecorder) writeEvent(object runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{}) {
|
||||
if f.Events != nil {
|
||||
f.Events <- fmt.Sprintf(eventtype+" "+reason+" "+messageFmt, args...) +
|
||||
objectString(object, f.IncludeObject) + annotationsString(annotations)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FakeRecorder) Event(object runtime.Object, eventtype, reason, message string) {
|
||||
f.writeEvent(object, nil, eventtype, reason, "%s", message)
|
||||
}
|
||||
|
||||
func (f *FakeRecorder) Eventf(object runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) {
|
||||
f.writeEvent(object, nil, eventtype, reason, messageFmt, args...)
|
||||
}
|
||||
|
||||
func (f *FakeRecorder) AnnotatedEventf(object runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{}) {
|
||||
f.writeEvent(object, annotations, eventtype, reason, messageFmt, args...)
|
||||
}
|
||||
|
||||
func (f *FakeRecorder) WithLogger(logger klog.Logger) EventRecorderLogger {
|
||||
return f
|
||||
}
|
||||
|
||||
// NewFakeRecorder creates new fake event recorder with event channel with
|
||||
// buffer of given size.
|
||||
func NewFakeRecorder(bufferSize int) *FakeRecorder {
|
||||
return &FakeRecorder{
|
||||
Events: make(chan string, bufferSize),
|
||||
}
|
||||
}
|
40
e2e/vendor/k8s.io/client-go/tools/record/util/util.go
generated
vendored
Normal file
40
e2e/vendor/k8s.io/client-go/tools/record/util/util.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
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 util
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
)
|
||||
|
||||
// ValidateEventType checks that eventtype is an expected type of event
|
||||
func ValidateEventType(eventtype string) bool {
|
||||
switch eventtype {
|
||||
case v1.EventTypeNormal, v1.EventTypeWarning:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsKeyNotFoundError is utility function that checks if an error is not found error
|
||||
func IsKeyNotFoundError(err error) bool {
|
||||
statusErr, _ := err.(*errors.StatusError)
|
||||
|
||||
return statusErr != nil && statusErr.Status().Code == http.StatusNotFound
|
||||
}
|
109
e2e/vendor/k8s.io/client-go/tools/reference/ref.go
generated
vendored
Normal file
109
e2e/vendor/k8s.io/client-go/tools/reference/ref.go
generated
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
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 reference
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
var (
|
||||
// Errors that could be returned by GetReference.
|
||||
ErrNilObject = errors.New("can't reference a nil object")
|
||||
)
|
||||
|
||||
// GetReference returns an ObjectReference which refers to the given
|
||||
// object, or an error if the object doesn't follow the conventions
|
||||
// that would allow this.
|
||||
// TODO: should take a meta.Interface see https://issue.k8s.io/7127
|
||||
func GetReference(scheme *runtime.Scheme, obj runtime.Object) (*v1.ObjectReference, error) {
|
||||
if obj == nil {
|
||||
return nil, ErrNilObject
|
||||
}
|
||||
if ref, ok := obj.(*v1.ObjectReference); ok {
|
||||
// Don't make a reference to a reference.
|
||||
return ref, nil
|
||||
}
|
||||
|
||||
// An object that implements only List has enough metadata to build a reference
|
||||
var listMeta metav1.Common
|
||||
objectMeta, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
listMeta, err = meta.CommonAccessor(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
listMeta = objectMeta
|
||||
}
|
||||
|
||||
gvk := obj.GetObjectKind().GroupVersionKind()
|
||||
|
||||
// If object meta doesn't contain data about kind and/or version,
|
||||
// we are falling back to scheme.
|
||||
//
|
||||
// TODO: This doesn't work for CRDs, which are not registered in scheme.
|
||||
if gvk.Empty() {
|
||||
gvks, _, err := scheme.ObjectKinds(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(gvks) == 0 || gvks[0].Empty() {
|
||||
return nil, fmt.Errorf("unexpected gvks registered for object %T: %v", obj, gvks)
|
||||
}
|
||||
// TODO: The same object can be registered for multiple group versions
|
||||
// (although in practise this doesn't seem to be used).
|
||||
// In such case, the version set may not be correct.
|
||||
gvk = gvks[0]
|
||||
}
|
||||
|
||||
kind := gvk.Kind
|
||||
version := gvk.GroupVersion().String()
|
||||
|
||||
// only has list metadata
|
||||
if objectMeta == nil {
|
||||
return &v1.ObjectReference{
|
||||
Kind: kind,
|
||||
APIVersion: version,
|
||||
ResourceVersion: listMeta.GetResourceVersion(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &v1.ObjectReference{
|
||||
Kind: kind,
|
||||
APIVersion: version,
|
||||
Name: objectMeta.GetName(),
|
||||
Namespace: objectMeta.GetNamespace(),
|
||||
UID: objectMeta.GetUID(),
|
||||
ResourceVersion: objectMeta.GetResourceVersion(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetPartialReference is exactly like GetReference, but allows you to set the FieldPath.
|
||||
func GetPartialReference(scheme *runtime.Scheme, obj runtime.Object, fieldPath string) (*v1.ObjectReference, error) {
|
||||
ref, err := GetReference(scheme, obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ref.FieldPath = fieldPath
|
||||
return ref, nil
|
||||
}
|
10
e2e/vendor/k8s.io/client-go/tools/remotecommand/OWNERS
generated
vendored
Normal file
10
e2e/vendor/k8s.io/client-go/tools/remotecommand/OWNERS
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- aojea
|
||||
- liggitt
|
||||
- seans3
|
||||
reviewers:
|
||||
- aojea
|
||||
- liggitt
|
||||
- seans3
|
20
e2e/vendor/k8s.io/client-go/tools/remotecommand/doc.go
generated
vendored
Normal file
20
e2e/vendor/k8s.io/client-go/tools/remotecommand/doc.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package remotecommand adds support for executing commands in containers,
|
||||
// with support for separate stdin, stdout, and stderr streams, as well as
|
||||
// TTY.
|
||||
package remotecommand // import "k8s.io/client-go/tools/remotecommand"
|
54
e2e/vendor/k8s.io/client-go/tools/remotecommand/errorstream.go
generated
vendored
Normal file
54
e2e/vendor/k8s.io/client-go/tools/remotecommand/errorstream.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
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 remotecommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
)
|
||||
|
||||
// errorStreamDecoder interprets the data on the error channel and creates a go error object from it.
|
||||
type errorStreamDecoder interface {
|
||||
decode(message []byte) error
|
||||
}
|
||||
|
||||
// watchErrorStream watches the errorStream for remote command error data,
|
||||
// decodes it with the given errorStreamDecoder, sends the decoded error (or nil if the remote
|
||||
// command exited successfully) to the returned error channel, and closes it.
|
||||
// This function returns immediately.
|
||||
func watchErrorStream(errorStream io.Reader, d errorStreamDecoder) chan error {
|
||||
errorChan := make(chan error)
|
||||
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
message, err := io.ReadAll(errorStream)
|
||||
switch {
|
||||
case err != nil && err != io.EOF:
|
||||
errorChan <- fmt.Errorf("error reading from error stream: %s", err)
|
||||
case len(message) > 0:
|
||||
errorChan <- d.decode(message)
|
||||
default:
|
||||
errorChan <- nil
|
||||
}
|
||||
close(errorChan)
|
||||
}()
|
||||
|
||||
return errorChan
|
||||
}
|
60
e2e/vendor/k8s.io/client-go/tools/remotecommand/fallback.go
generated
vendored
Normal file
60
e2e/vendor/k8s.io/client-go/tools/remotecommand/fallback.go
generated
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
Copyright 2023 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 remotecommand
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
var _ Executor = &FallbackExecutor{}
|
||||
|
||||
type FallbackExecutor struct {
|
||||
primary Executor
|
||||
secondary Executor
|
||||
shouldFallback func(error) bool
|
||||
}
|
||||
|
||||
// NewFallbackExecutor creates an Executor that first attempts to use the
|
||||
// WebSocketExecutor, falling back to the legacy SPDYExecutor if the initial
|
||||
// websocket "StreamWithContext" call fails.
|
||||
// func NewFallbackExecutor(config *restclient.Config, method string, url *url.URL) (Executor, error) {
|
||||
func NewFallbackExecutor(primary, secondary Executor, shouldFallback func(error) bool) (Executor, error) {
|
||||
return &FallbackExecutor{
|
||||
primary: primary,
|
||||
secondary: secondary,
|
||||
shouldFallback: shouldFallback,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Stream is deprecated. Please use "StreamWithContext".
|
||||
func (f *FallbackExecutor) Stream(options StreamOptions) error {
|
||||
return f.StreamWithContext(context.Background(), options)
|
||||
}
|
||||
|
||||
// StreamWithContext initially attempts to call "StreamWithContext" using the
|
||||
// primary executor, falling back to calling the secondary executor if the
|
||||
// initial primary call to upgrade to a websocket connection fails.
|
||||
func (f *FallbackExecutor) StreamWithContext(ctx context.Context, options StreamOptions) error {
|
||||
err := f.primary.StreamWithContext(ctx, options)
|
||||
if f.shouldFallback(err) {
|
||||
klog.V(4).Infof("RemoteCommand fallback: %v", err)
|
||||
return f.secondary.StreamWithContext(ctx, options)
|
||||
}
|
||||
return err
|
||||
}
|
41
e2e/vendor/k8s.io/client-go/tools/remotecommand/reader.go
generated
vendored
Normal file
41
e2e/vendor/k8s.io/client-go/tools/remotecommand/reader.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
Copyright 2018 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 remotecommand
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// readerWrapper delegates to an io.Reader so that only the io.Reader interface is implemented,
|
||||
// to keep io.Copy from doing things we don't want when copying from the reader to the data stream.
|
||||
//
|
||||
// If the Stdin io.Reader provided to remotecommand implements a WriteTo function (like bytes.Buffer does[1]),
|
||||
// io.Copy calls that method[2] to attempt to write the entire buffer to the stream in one call.
|
||||
// That results in an oversized call to spdystream.Stream#Write [3],
|
||||
// which results in a single oversized data frame[4] that is too large.
|
||||
//
|
||||
// [1] https://golang.org/pkg/bytes/#Buffer.WriteTo
|
||||
// [2] https://golang.org/pkg/io/#Copy
|
||||
// [3] https://github.com/kubernetes/kubernetes/blob/90295640ef87db9daa0144c5617afe889e7992b2/vendor/github.com/docker/spdystream/stream.go#L66-L73
|
||||
// [4] https://github.com/kubernetes/kubernetes/blob/90295640ef87db9daa0144c5617afe889e7992b2/vendor/github.com/docker/spdystream/spdy/write.go#L302-L304
|
||||
type readerWrapper struct {
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
func (r readerWrapper) Read(p []byte) (int, error) {
|
||||
return r.reader.Read(p)
|
||||
}
|
58
e2e/vendor/k8s.io/client-go/tools/remotecommand/remotecommand.go
generated
vendored
Normal file
58
e2e/vendor/k8s.io/client-go/tools/remotecommand/remotecommand.go
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
)
|
||||
|
||||
// StreamOptions holds information pertaining to the current streaming session:
|
||||
// input/output streams, if the client is requesting a TTY, and a terminal size queue to
|
||||
// support terminal resizing.
|
||||
type StreamOptions struct {
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
Tty bool
|
||||
TerminalSizeQueue TerminalSizeQueue
|
||||
}
|
||||
|
||||
// Executor is an interface for transporting shell-style streams.
|
||||
type Executor interface {
|
||||
// Deprecated: use StreamWithContext instead to avoid possible resource leaks.
|
||||
// See https://github.com/kubernetes/kubernetes/pull/103177 for details.
|
||||
Stream(options StreamOptions) error
|
||||
|
||||
// StreamWithContext initiates the transport of the standard shell streams. It will
|
||||
// transport any non-nil stream to a remote system, and return an error if a problem
|
||||
// occurs. If tty is set, the stderr stream is not used (raw TTY manages stdout and
|
||||
// stderr over the stdout stream).
|
||||
// The context controls the entire lifetime of stream execution.
|
||||
StreamWithContext(ctx context.Context, options StreamOptions) error
|
||||
}
|
||||
|
||||
type streamCreator interface {
|
||||
CreateStream(headers http.Header) (httpstream.Stream, error)
|
||||
}
|
||||
|
||||
type streamProtocolHandler interface {
|
||||
stream(conn streamCreator) error
|
||||
}
|
33
e2e/vendor/k8s.io/client-go/tools/remotecommand/resize.go
generated
vendored
Normal file
33
e2e/vendor/k8s.io/client-go/tools/remotecommand/resize.go
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
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 remotecommand
|
||||
|
||||
// TerminalSize and TerminalSizeQueue was a part of k8s.io/kubernetes/pkg/util/term
|
||||
// and were moved in order to decouple client from other term dependencies
|
||||
|
||||
// TerminalSize represents the width and height of a terminal.
|
||||
type TerminalSize struct {
|
||||
Width uint16
|
||||
Height uint16
|
||||
}
|
||||
|
||||
// TerminalSizeQueue is capable of returning terminal resize events as they occur.
|
||||
type TerminalSizeQueue interface {
|
||||
// Next returns the new terminal size after the terminal has been resized. It returns nil when
|
||||
// monitoring has been stopped.
|
||||
Next() *TerminalSize
|
||||
}
|
171
e2e/vendor/k8s.io/client-go/tools/remotecommand/spdy.go
generated
vendored
Normal file
171
e2e/vendor/k8s.io/client-go/tools/remotecommand/spdy.go
generated
vendored
Normal file
@ -0,0 +1,171 @@
|
||||
/*
|
||||
Copyright 2023 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 remotecommand
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/transport/spdy"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// spdyStreamExecutor handles transporting standard shell streams over an httpstream connection.
|
||||
type spdyStreamExecutor struct {
|
||||
upgrader spdy.Upgrader
|
||||
transport http.RoundTripper
|
||||
|
||||
method string
|
||||
url *url.URL
|
||||
protocols []string
|
||||
rejectRedirects bool // if true, receiving redirect from upstream is an error
|
||||
}
|
||||
|
||||
// NewSPDYExecutor connects to the provided server and upgrades the connection to
|
||||
// multiplexed bidirectional streams.
|
||||
func NewSPDYExecutor(config *restclient.Config, method string, url *url.URL) (Executor, error) {
|
||||
wrapper, upgradeRoundTripper, err := spdy.RoundTripperFor(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewSPDYExecutorForTransports(wrapper, upgradeRoundTripper, method, url)
|
||||
}
|
||||
|
||||
// NewSPDYExecutorRejectRedirects returns an Executor that will upgrade the future
|
||||
// connection to a SPDY bi-directional streaming connection when calling "Stream" (deprecated)
|
||||
// or "StreamWithContext" (preferred). Additionally, if the upstream server returns a redirect
|
||||
// during the attempted upgrade in these "Stream" calls, an error is returned.
|
||||
func NewSPDYExecutorRejectRedirects(transport http.RoundTripper, upgrader spdy.Upgrader, method string, url *url.URL) (Executor, error) {
|
||||
executor, err := NewSPDYExecutorForTransports(transport, upgrader, method, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spdyExecutor := executor.(*spdyStreamExecutor)
|
||||
spdyExecutor.rejectRedirects = true
|
||||
return spdyExecutor, nil
|
||||
}
|
||||
|
||||
// NewSPDYExecutorForTransports connects to the provided server using the given transport,
|
||||
// upgrades the response using the given upgrader to multiplexed bidirectional streams.
|
||||
func NewSPDYExecutorForTransports(transport http.RoundTripper, upgrader spdy.Upgrader, method string, url *url.URL) (Executor, error) {
|
||||
return NewSPDYExecutorForProtocols(
|
||||
transport, upgrader, method, url,
|
||||
remotecommand.StreamProtocolV5Name,
|
||||
remotecommand.StreamProtocolV4Name,
|
||||
remotecommand.StreamProtocolV3Name,
|
||||
remotecommand.StreamProtocolV2Name,
|
||||
remotecommand.StreamProtocolV1Name,
|
||||
)
|
||||
}
|
||||
|
||||
// NewSPDYExecutorForProtocols connects to the provided server and upgrades the connection to
|
||||
// multiplexed bidirectional streams using only the provided protocols. Exposed for testing, most
|
||||
// callers should use NewSPDYExecutor or NewSPDYExecutorForTransports.
|
||||
func NewSPDYExecutorForProtocols(transport http.RoundTripper, upgrader spdy.Upgrader, method string, url *url.URL, protocols ...string) (Executor, error) {
|
||||
return &spdyStreamExecutor{
|
||||
upgrader: upgrader,
|
||||
transport: transport,
|
||||
method: method,
|
||||
url: url,
|
||||
protocols: protocols,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Stream opens a protocol streamer to the server and streams until a client closes
|
||||
// the connection or the server disconnects.
|
||||
func (e *spdyStreamExecutor) Stream(options StreamOptions) error {
|
||||
return e.StreamWithContext(context.Background(), options)
|
||||
}
|
||||
|
||||
// newConnectionAndStream creates a new SPDY connection and a stream protocol handler upon it.
|
||||
func (e *spdyStreamExecutor) newConnectionAndStream(ctx context.Context, options StreamOptions) (httpstream.Connection, streamProtocolHandler, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, e.method, e.url.String(), nil)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error creating request: %v", err)
|
||||
}
|
||||
|
||||
client := http.Client{Transport: e.transport}
|
||||
if e.rejectRedirects {
|
||||
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return fmt.Errorf("redirect not allowed")
|
||||
}
|
||||
}
|
||||
conn, protocol, err := spdy.Negotiate(
|
||||
e.upgrader,
|
||||
&client,
|
||||
req,
|
||||
e.protocols...,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var streamer streamProtocolHandler
|
||||
|
||||
switch protocol {
|
||||
case remotecommand.StreamProtocolV5Name:
|
||||
streamer = newStreamProtocolV5(options)
|
||||
case remotecommand.StreamProtocolV4Name:
|
||||
streamer = newStreamProtocolV4(options)
|
||||
case remotecommand.StreamProtocolV3Name:
|
||||
streamer = newStreamProtocolV3(options)
|
||||
case remotecommand.StreamProtocolV2Name:
|
||||
streamer = newStreamProtocolV2(options)
|
||||
case "":
|
||||
klog.V(4).Infof("The server did not negotiate a streaming protocol version. Falling back to %s", remotecommand.StreamProtocolV1Name)
|
||||
fallthrough
|
||||
case remotecommand.StreamProtocolV1Name:
|
||||
streamer = newStreamProtocolV1(options)
|
||||
}
|
||||
|
||||
return conn, streamer, nil
|
||||
}
|
||||
|
||||
// StreamWithContext opens a protocol streamer to the server and streams until a client closes
|
||||
// the connection or the server disconnects or the context is done.
|
||||
func (e *spdyStreamExecutor) StreamWithContext(ctx context.Context, options StreamOptions) error {
|
||||
conn, streamer, err := e.newConnectionAndStream(ctx, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
panicChan := make(chan any, 1)
|
||||
errorChan := make(chan error, 1)
|
||||
go func() {
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
panicChan <- p
|
||||
}
|
||||
}()
|
||||
errorChan <- streamer.stream(conn)
|
||||
}()
|
||||
|
||||
select {
|
||||
case p := <-panicChan:
|
||||
panic(p)
|
||||
case err := <-errorChan:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
159
e2e/vendor/k8s.io/client-go/tools/remotecommand/v1.go
generated
vendored
Normal file
159
e2e/vendor/k8s.io/client-go/tools/remotecommand/v1.go
generated
vendored
Normal file
@ -0,0 +1,159 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// streamProtocolV1 implements the first version of the streaming exec & attach
|
||||
// protocol. This version has some bugs, such as not being able to detect when
|
||||
// non-interactive stdin data has ended. See https://issues.k8s.io/13394 and
|
||||
// https://issues.k8s.io/13395 for more details.
|
||||
type streamProtocolV1 struct {
|
||||
StreamOptions
|
||||
|
||||
errorStream httpstream.Stream
|
||||
remoteStdin httpstream.Stream
|
||||
remoteStdout httpstream.Stream
|
||||
remoteStderr httpstream.Stream
|
||||
}
|
||||
|
||||
var _ streamProtocolHandler = &streamProtocolV1{}
|
||||
|
||||
func newStreamProtocolV1(options StreamOptions) streamProtocolHandler {
|
||||
return &streamProtocolV1{
|
||||
StreamOptions: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *streamProtocolV1) stream(conn streamCreator) error {
|
||||
doneChan := make(chan struct{}, 2)
|
||||
errorChan := make(chan error)
|
||||
|
||||
cp := func(s string, dst io.Writer, src io.Reader) {
|
||||
klog.V(6).Infof("Copying %s", s)
|
||||
defer klog.V(6).Infof("Done copying %s", s)
|
||||
if _, err := io.Copy(dst, src); err != nil && err != io.EOF {
|
||||
klog.Errorf("Error copying %s: %v", s, err)
|
||||
}
|
||||
if s == v1.StreamTypeStdout || s == v1.StreamTypeStderr {
|
||||
doneChan <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// set up all the streams first
|
||||
var err error
|
||||
headers := http.Header{}
|
||||
headers.Set(v1.StreamType, v1.StreamTypeError)
|
||||
p.errorStream, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer p.errorStream.Reset()
|
||||
|
||||
// Create all the streams first, then start the copy goroutines. The server doesn't start its copy
|
||||
// goroutines until it's received all of the streams. If the client creates the stdin stream and
|
||||
// immediately begins copying stdin data to the server, it's possible to overwhelm and wedge the
|
||||
// spdy frame handler in the server so that it is full of unprocessed frames. The frames aren't
|
||||
// getting processed because the server hasn't started its copying, and it won't do that until it
|
||||
// gets all the streams. By creating all the streams first, we ensure that the server is ready to
|
||||
// process data before the client starts sending any. See https://issues.k8s.io/16373 for more info.
|
||||
if p.Stdin != nil {
|
||||
headers.Set(v1.StreamType, v1.StreamTypeStdin)
|
||||
p.remoteStdin, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer p.remoteStdin.Reset()
|
||||
}
|
||||
|
||||
if p.Stdout != nil {
|
||||
headers.Set(v1.StreamType, v1.StreamTypeStdout)
|
||||
p.remoteStdout, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer p.remoteStdout.Reset()
|
||||
}
|
||||
|
||||
if p.Stderr != nil && !p.Tty {
|
||||
headers.Set(v1.StreamType, v1.StreamTypeStderr)
|
||||
p.remoteStderr, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer p.remoteStderr.Reset()
|
||||
}
|
||||
|
||||
// now that all the streams have been created, proceed with reading & copying
|
||||
|
||||
// always read from errorStream
|
||||
go func() {
|
||||
message, err := io.ReadAll(p.errorStream)
|
||||
if err != nil && err != io.EOF {
|
||||
errorChan <- fmt.Errorf("Error reading from error stream: %s", err)
|
||||
return
|
||||
}
|
||||
if len(message) > 0 {
|
||||
errorChan <- fmt.Errorf("Error executing remote command: %s", message)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
if p.Stdin != nil {
|
||||
// TODO this goroutine will never exit cleanly (the io.Copy never unblocks)
|
||||
// because stdin is not closed until the process exits. If we try to call
|
||||
// stdin.Close(), it returns no error but doesn't unblock the copy. It will
|
||||
// exit when the process exits, instead.
|
||||
go cp(v1.StreamTypeStdin, p.remoteStdin, readerWrapper{p.Stdin})
|
||||
}
|
||||
|
||||
waitCount := 0
|
||||
completedStreams := 0
|
||||
|
||||
if p.Stdout != nil {
|
||||
waitCount++
|
||||
go cp(v1.StreamTypeStdout, p.Stdout, p.remoteStdout)
|
||||
}
|
||||
|
||||
if p.Stderr != nil && !p.Tty {
|
||||
waitCount++
|
||||
go cp(v1.StreamTypeStderr, p.Stderr, p.remoteStderr)
|
||||
}
|
||||
|
||||
Loop:
|
||||
for {
|
||||
select {
|
||||
case <-doneChan:
|
||||
completedStreams++
|
||||
if completedStreams == waitCount {
|
||||
break Loop
|
||||
}
|
||||
case err := <-errorChan:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
199
e2e/vendor/k8s.io/client-go/tools/remotecommand/v2.go
generated
vendored
Normal file
199
e2e/vendor/k8s.io/client-go/tools/remotecommand/v2.go
generated
vendored
Normal file
@ -0,0 +1,199 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
)
|
||||
|
||||
// streamProtocolV2 implements version 2 of the streaming protocol for attach
|
||||
// and exec. The original streaming protocol was metav1. As a result, this
|
||||
// version is referred to as version 2, even though it is the first actual
|
||||
// numbered version.
|
||||
type streamProtocolV2 struct {
|
||||
StreamOptions
|
||||
|
||||
errorStream io.Reader
|
||||
remoteStdin io.ReadWriteCloser
|
||||
remoteStdout io.Reader
|
||||
remoteStderr io.Reader
|
||||
}
|
||||
|
||||
var _ streamProtocolHandler = &streamProtocolV2{}
|
||||
|
||||
func newStreamProtocolV2(options StreamOptions) streamProtocolHandler {
|
||||
return &streamProtocolV2{
|
||||
StreamOptions: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *streamProtocolV2) createStreams(conn streamCreator) error {
|
||||
var err error
|
||||
headers := http.Header{}
|
||||
|
||||
// set up error stream
|
||||
headers.Set(v1.StreamType, v1.StreamTypeError)
|
||||
p.errorStream, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set up stdin stream
|
||||
if p.Stdin != nil {
|
||||
headers.Set(v1.StreamType, v1.StreamTypeStdin)
|
||||
p.remoteStdin, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// set up stdout stream
|
||||
if p.Stdout != nil {
|
||||
headers.Set(v1.StreamType, v1.StreamTypeStdout)
|
||||
p.remoteStdout, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// set up stderr stream
|
||||
if p.Stderr != nil && !p.Tty {
|
||||
headers.Set(v1.StreamType, v1.StreamTypeStderr)
|
||||
p.remoteStderr, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *streamProtocolV2) copyStdin() {
|
||||
if p.Stdin != nil {
|
||||
var once sync.Once
|
||||
|
||||
// copy from client's stdin to container's stdin
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
// if p.stdin is noninteractive, p.g. `echo abc | kubectl exec -i <pod> -- cat`, make sure
|
||||
// we close remoteStdin as soon as the copy from p.stdin to remoteStdin finishes. Otherwise
|
||||
// the executed command will remain running.
|
||||
defer once.Do(func() { p.remoteStdin.Close() })
|
||||
|
||||
if _, err := io.Copy(p.remoteStdin, readerWrapper{p.Stdin}); err != nil {
|
||||
runtime.HandleError(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// read from remoteStdin until the stream is closed. this is essential to
|
||||
// be able to exit interactive sessions cleanly and not leak goroutines or
|
||||
// hang the client's terminal.
|
||||
//
|
||||
// TODO we aren't using go-dockerclient any more; revisit this to determine if it's still
|
||||
// required by engine-api.
|
||||
//
|
||||
// go-dockerclient's current hijack implementation
|
||||
// (https://github.com/fsouza/go-dockerclient/blob/89f3d56d93788dfe85f864a44f85d9738fca0670/client.go#L564)
|
||||
// waits for all three streams (stdin/stdout/stderr) to finish copying
|
||||
// before returning. When hijack finishes copying stdout/stderr, it calls
|
||||
// Close() on its side of remoteStdin, which allows this copy to complete.
|
||||
// When that happens, we must Close() on our side of remoteStdin, to
|
||||
// allow the copy in hijack to complete, and hijack to return.
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
defer once.Do(func() { p.remoteStdin.Close() })
|
||||
|
||||
// this "copy" doesn't actually read anything - it's just here to wait for
|
||||
// the server to close remoteStdin.
|
||||
if _, err := io.Copy(io.Discard, p.remoteStdin); err != nil {
|
||||
runtime.HandleError(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *streamProtocolV2) copyStdout(wg *sync.WaitGroup) {
|
||||
if p.Stdout == nil {
|
||||
return
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
defer wg.Done()
|
||||
// make sure, packet in queue can be consumed.
|
||||
// block in queue may lead to deadlock in conn.server
|
||||
// issue: https://github.com/kubernetes/kubernetes/issues/96339
|
||||
defer io.Copy(io.Discard, p.remoteStdout)
|
||||
|
||||
if _, err := io.Copy(p.Stdout, p.remoteStdout); err != nil {
|
||||
runtime.HandleError(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *streamProtocolV2) copyStderr(wg *sync.WaitGroup) {
|
||||
if p.Stderr == nil || p.Tty {
|
||||
return
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
defer wg.Done()
|
||||
defer io.Copy(io.Discard, p.remoteStderr)
|
||||
|
||||
if _, err := io.Copy(p.Stderr, p.remoteStderr); err != nil {
|
||||
runtime.HandleError(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *streamProtocolV2) stream(conn streamCreator) error {
|
||||
if err := p.createStreams(conn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// now that all the streams have been created, proceed with reading & copying
|
||||
|
||||
errorChan := watchErrorStream(p.errorStream, &errorDecoderV2{})
|
||||
|
||||
p.copyStdin()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
p.copyStdout(&wg)
|
||||
p.copyStderr(&wg)
|
||||
|
||||
// we're waiting for stdout/stderr to finish copying
|
||||
wg.Wait()
|
||||
|
||||
// waits for errorStream to finish reading with an error or nil
|
||||
return <-errorChan
|
||||
}
|
||||
|
||||
// errorDecoderV2 interprets the error channel data as plain text.
|
||||
type errorDecoderV2 struct{}
|
||||
|
||||
func (d *errorDecoderV2) decode(message []byte) error {
|
||||
return fmt.Errorf("error executing remote command: %s", message)
|
||||
}
|
111
e2e/vendor/k8s.io/client-go/tools/remotecommand/v3.go
generated
vendored
Normal file
111
e2e/vendor/k8s.io/client-go/tools/remotecommand/v3.go
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
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 remotecommand
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
)
|
||||
|
||||
// streamProtocolV3 implements version 3 of the streaming protocol for attach
|
||||
// and exec. This version adds support for resizing the container's terminal.
|
||||
type streamProtocolV3 struct {
|
||||
*streamProtocolV2
|
||||
|
||||
resizeStream io.Writer
|
||||
}
|
||||
|
||||
var _ streamProtocolHandler = &streamProtocolV3{}
|
||||
|
||||
func newStreamProtocolV3(options StreamOptions) streamProtocolHandler {
|
||||
return &streamProtocolV3{
|
||||
streamProtocolV2: newStreamProtocolV2(options).(*streamProtocolV2),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *streamProtocolV3) createStreams(conn streamCreator) error {
|
||||
// set up the streams from v2
|
||||
if err := p.streamProtocolV2.createStreams(conn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set up resize stream
|
||||
if p.Tty {
|
||||
headers := http.Header{}
|
||||
headers.Set(v1.StreamType, v1.StreamTypeResize)
|
||||
var err error
|
||||
p.resizeStream, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *streamProtocolV3) handleResizes() {
|
||||
if p.resizeStream == nil || p.TerminalSizeQueue == nil {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
encoder := json.NewEncoder(p.resizeStream)
|
||||
for {
|
||||
size := p.TerminalSizeQueue.Next()
|
||||
if size == nil {
|
||||
return
|
||||
}
|
||||
if err := encoder.Encode(&size); err != nil {
|
||||
runtime.HandleError(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *streamProtocolV3) stream(conn streamCreator) error {
|
||||
if err := p.createStreams(conn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// now that all the streams have been created, proceed with reading & copying
|
||||
|
||||
errorChan := watchErrorStream(p.errorStream, &errorDecoderV3{})
|
||||
|
||||
p.handleResizes()
|
||||
|
||||
p.copyStdin()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
p.copyStdout(&wg)
|
||||
p.copyStderr(&wg)
|
||||
|
||||
// we're waiting for stdout/stderr to finish copying
|
||||
wg.Wait()
|
||||
|
||||
// waits for errorStream to finish reading with an error or nil
|
||||
return <-errorChan
|
||||
}
|
||||
|
||||
type errorDecoderV3 struct {
|
||||
errorDecoderV2
|
||||
}
|
119
e2e/vendor/k8s.io/client-go/tools/remotecommand/v4.go
generated
vendored
Normal file
119
e2e/vendor/k8s.io/client-go/tools/remotecommand/v4.go
generated
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
/*
|
||||
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 remotecommand
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
"k8s.io/client-go/util/exec"
|
||||
)
|
||||
|
||||
// streamProtocolV4 implements version 4 of the streaming protocol for attach
|
||||
// and exec. This version adds support for exit codes on the error stream through
|
||||
// the use of metav1.Status instead of plain text messages.
|
||||
type streamProtocolV4 struct {
|
||||
*streamProtocolV3
|
||||
}
|
||||
|
||||
var _ streamProtocolHandler = &streamProtocolV4{}
|
||||
|
||||
func newStreamProtocolV4(options StreamOptions) streamProtocolHandler {
|
||||
return &streamProtocolV4{
|
||||
streamProtocolV3: newStreamProtocolV3(options).(*streamProtocolV3),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *streamProtocolV4) createStreams(conn streamCreator) error {
|
||||
return p.streamProtocolV3.createStreams(conn)
|
||||
}
|
||||
|
||||
func (p *streamProtocolV4) handleResizes() {
|
||||
p.streamProtocolV3.handleResizes()
|
||||
}
|
||||
|
||||
func (p *streamProtocolV4) stream(conn streamCreator) error {
|
||||
if err := p.createStreams(conn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// now that all the streams have been created, proceed with reading & copying
|
||||
|
||||
errorChan := watchErrorStream(p.errorStream, &errorDecoderV4{})
|
||||
|
||||
p.handleResizes()
|
||||
|
||||
p.copyStdin()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
p.copyStdout(&wg)
|
||||
p.copyStderr(&wg)
|
||||
|
||||
// we're waiting for stdout/stderr to finish copying
|
||||
wg.Wait()
|
||||
|
||||
// waits for errorStream to finish reading with an error or nil
|
||||
return <-errorChan
|
||||
}
|
||||
|
||||
// errorDecoderV4 interprets the json-marshaled metav1.Status on the error channel
|
||||
// and creates an exec.ExitError from it.
|
||||
type errorDecoderV4 struct{}
|
||||
|
||||
func (d *errorDecoderV4) decode(message []byte) error {
|
||||
status := metav1.Status{}
|
||||
err := json.Unmarshal(message, &status)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error stream protocol error: %v in %q", err, string(message))
|
||||
}
|
||||
switch status.Status {
|
||||
case metav1.StatusSuccess:
|
||||
return nil
|
||||
case metav1.StatusFailure:
|
||||
if status.Reason == remotecommand.NonZeroExitCodeReason {
|
||||
if status.Details == nil {
|
||||
return errors.New("error stream protocol error: details must be set")
|
||||
}
|
||||
for i := range status.Details.Causes {
|
||||
c := &status.Details.Causes[i]
|
||||
if c.Type != remotecommand.ExitCodeCauseType {
|
||||
continue
|
||||
}
|
||||
|
||||
rc, err := strconv.ParseUint(c.Message, 10, 8)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error stream protocol error: invalid exit code value %q", c.Message)
|
||||
}
|
||||
return exec.CodeExitError{
|
||||
Err: fmt.Errorf("command terminated with exit code %d", rc),
|
||||
Code: int(rc),
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("error stream protocol error: no %s cause given", remotecommand.ExitCodeCauseType)
|
||||
}
|
||||
default:
|
||||
return errors.New("error stream protocol error: unknown error")
|
||||
}
|
||||
|
||||
return errors.New(status.Message)
|
||||
}
|
35
e2e/vendor/k8s.io/client-go/tools/remotecommand/v5.go
generated
vendored
Normal file
35
e2e/vendor/k8s.io/client-go/tools/remotecommand/v5.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
Copyright 2023 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 remotecommand
|
||||
|
||||
// streamProtocolV5 add support for V5 of the remote command subprotocol.
|
||||
// For the streamProtocolHandler, this version is the same as V4.
|
||||
type streamProtocolV5 struct {
|
||||
*streamProtocolV4
|
||||
}
|
||||
|
||||
var _ streamProtocolHandler = &streamProtocolV5{}
|
||||
|
||||
func newStreamProtocolV5(options StreamOptions) streamProtocolHandler {
|
||||
return &streamProtocolV5{
|
||||
streamProtocolV4: newStreamProtocolV4(options).(*streamProtocolV4),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *streamProtocolV5) stream(conn streamCreator) error {
|
||||
return p.streamProtocolV4.stream(conn)
|
||||
}
|
515
e2e/vendor/k8s.io/client-go/tools/remotecommand/websocket.go
generated
vendored
Normal file
515
e2e/vendor/k8s.io/client-go/tools/remotecommand/websocket.go
generated
vendored
Normal file
@ -0,0 +1,515 @@
|
||||
/*
|
||||
Copyright 2023 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 remotecommand
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
gwebsocket "github.com/gorilla/websocket"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/transport/websocket"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// writeDeadline defines the time that a client-side write to the websocket
|
||||
// connection must complete before an i/o timeout occurs.
|
||||
const writeDeadline = 60 * time.Second
|
||||
|
||||
var (
|
||||
_ Executor = &wsStreamExecutor{}
|
||||
_ streamCreator = &wsStreamCreator{}
|
||||
_ httpstream.Stream = &stream{}
|
||||
|
||||
streamType2streamID = map[string]byte{
|
||||
v1.StreamTypeStdin: remotecommand.StreamStdIn,
|
||||
v1.StreamTypeStdout: remotecommand.StreamStdOut,
|
||||
v1.StreamTypeStderr: remotecommand.StreamStdErr,
|
||||
v1.StreamTypeError: remotecommand.StreamErr,
|
||||
v1.StreamTypeResize: remotecommand.StreamResize,
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
// pingPeriod defines how often a heartbeat "ping" message is sent.
|
||||
pingPeriod = 5 * time.Second
|
||||
// pingReadDeadline defines the time waiting for a response heartbeat
|
||||
// "pong" message before a timeout error occurs for websocket reading.
|
||||
// This duration must always be greater than the "pingPeriod". By defining
|
||||
// this deadline in terms of the ping period, we are essentially saying
|
||||
// we can drop "X" (e.g. 12) pings before firing the timeout.
|
||||
pingReadDeadline = (pingPeriod * 12) + (1 * time.Second)
|
||||
)
|
||||
|
||||
// wsStreamExecutor handles transporting standard shell streams over an httpstream connection.
|
||||
type wsStreamExecutor struct {
|
||||
transport http.RoundTripper
|
||||
upgrader websocket.ConnectionHolder
|
||||
method string
|
||||
url string
|
||||
// requested protocols in priority order (e.g. v5.channel.k8s.io before v4.channel.k8s.io).
|
||||
protocols []string
|
||||
// selected protocol from the handshake process; could be empty string if handshake fails.
|
||||
negotiated string
|
||||
// period defines how often a "ping" heartbeat message is sent to the other endpoint.
|
||||
heartbeatPeriod time.Duration
|
||||
// deadline defines the amount of time before "pong" response must be received.
|
||||
heartbeatDeadline time.Duration
|
||||
}
|
||||
|
||||
func NewWebSocketExecutor(config *restclient.Config, method, url string) (Executor, error) {
|
||||
// Only supports V5 protocol for correct version skew functionality.
|
||||
// Previous api servers will proxy upgrade requests to legacy websocket
|
||||
// servers on container runtimes which support V1-V4. These legacy
|
||||
// websocket servers will not handle the new CLOSE signal.
|
||||
return NewWebSocketExecutorForProtocols(config, method, url, remotecommand.StreamProtocolV5Name)
|
||||
}
|
||||
|
||||
// NewWebSocketExecutorForProtocols allows to execute commands via a WebSocket connection.
|
||||
func NewWebSocketExecutorForProtocols(config *restclient.Config, method, url string, protocols ...string) (Executor, error) {
|
||||
transport, upgrader, err := websocket.RoundTripperFor(config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating websocket transports: %v", err)
|
||||
}
|
||||
return &wsStreamExecutor{
|
||||
transport: transport,
|
||||
upgrader: upgrader,
|
||||
method: method,
|
||||
url: url,
|
||||
protocols: protocols,
|
||||
heartbeatPeriod: pingPeriod,
|
||||
heartbeatDeadline: pingReadDeadline,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Deprecated: use StreamWithContext instead to avoid possible resource leaks.
|
||||
// See https://github.com/kubernetes/kubernetes/pull/103177 for details.
|
||||
func (e *wsStreamExecutor) Stream(options StreamOptions) error {
|
||||
return e.StreamWithContext(context.Background(), options)
|
||||
}
|
||||
|
||||
// StreamWithContext upgrades an HTTPRequest to a WebSocket connection, and starts the various
|
||||
// goroutines to implement the necessary streams over the connection. The "options" parameter
|
||||
// defines which streams are requested. Returns an error if one occurred. This method is NOT
|
||||
// safe to run concurrently with the same executor (because of the state stored in the upgrader).
|
||||
func (e *wsStreamExecutor) StreamWithContext(ctx context.Context, options StreamOptions) error {
|
||||
req, err := http.NewRequestWithContext(ctx, e.method, e.url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn, err := websocket.Negotiate(e.transport, e.upgrader, req, e.protocols...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if conn == nil {
|
||||
panic(fmt.Errorf("websocket connection is nil"))
|
||||
}
|
||||
defer conn.Close()
|
||||
e.negotiated = conn.Subprotocol()
|
||||
klog.V(4).Infof("The subprotocol is %s", e.negotiated)
|
||||
|
||||
var streamer streamProtocolHandler
|
||||
switch e.negotiated {
|
||||
case remotecommand.StreamProtocolV5Name:
|
||||
streamer = newStreamProtocolV5(options)
|
||||
case remotecommand.StreamProtocolV4Name:
|
||||
streamer = newStreamProtocolV4(options)
|
||||
case remotecommand.StreamProtocolV3Name:
|
||||
streamer = newStreamProtocolV3(options)
|
||||
case remotecommand.StreamProtocolV2Name:
|
||||
streamer = newStreamProtocolV2(options)
|
||||
case "":
|
||||
klog.V(4).Infof("The server did not negotiate a streaming protocol version. Falling back to %s", remotecommand.StreamProtocolV1Name)
|
||||
fallthrough
|
||||
case remotecommand.StreamProtocolV1Name:
|
||||
streamer = newStreamProtocolV1(options)
|
||||
}
|
||||
|
||||
panicChan := make(chan any, 1)
|
||||
errorChan := make(chan error, 1)
|
||||
go func() {
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
panicChan <- p
|
||||
}
|
||||
}()
|
||||
creator := newWSStreamCreator(conn)
|
||||
go creator.readDemuxLoop(
|
||||
e.upgrader.DataBufferSize(),
|
||||
e.heartbeatPeriod,
|
||||
e.heartbeatDeadline,
|
||||
)
|
||||
errorChan <- streamer.stream(creator)
|
||||
}()
|
||||
|
||||
select {
|
||||
case p := <-panicChan:
|
||||
panic(p)
|
||||
case err := <-errorChan:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
type wsStreamCreator struct {
|
||||
conn *gwebsocket.Conn
|
||||
// Protects writing to websocket connection; reading is lock-free
|
||||
connWriteLock sync.Mutex
|
||||
// map of stream id to stream; multiple streams read/write the connection
|
||||
streams map[byte]*stream
|
||||
streamsMu sync.Mutex
|
||||
// setStreamErr holds the error to return to anyone calling setStreams.
|
||||
// this is populated in closeAllStreamReaders
|
||||
setStreamErr error
|
||||
}
|
||||
|
||||
func newWSStreamCreator(conn *gwebsocket.Conn) *wsStreamCreator {
|
||||
return &wsStreamCreator{
|
||||
conn: conn,
|
||||
streams: map[byte]*stream{},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *wsStreamCreator) getStream(id byte) *stream {
|
||||
c.streamsMu.Lock()
|
||||
defer c.streamsMu.Unlock()
|
||||
return c.streams[id]
|
||||
}
|
||||
|
||||
func (c *wsStreamCreator) setStream(id byte, s *stream) error {
|
||||
c.streamsMu.Lock()
|
||||
defer c.streamsMu.Unlock()
|
||||
if c.setStreamErr != nil {
|
||||
return c.setStreamErr
|
||||
}
|
||||
c.streams[id] = s
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateStream uses id from passed headers to create a stream over "c.conn" connection.
|
||||
// Returns a Stream structure or nil and an error if one occurred.
|
||||
func (c *wsStreamCreator) CreateStream(headers http.Header) (httpstream.Stream, error) {
|
||||
streamType := headers.Get(v1.StreamType)
|
||||
id, ok := streamType2streamID[streamType]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown stream type: %s", streamType)
|
||||
}
|
||||
if s := c.getStream(id); s != nil {
|
||||
return nil, fmt.Errorf("duplicate stream for type %s", streamType)
|
||||
}
|
||||
reader, writer := io.Pipe()
|
||||
s := &stream{
|
||||
headers: headers,
|
||||
readPipe: reader,
|
||||
writePipe: writer,
|
||||
conn: c.conn,
|
||||
connWriteLock: &c.connWriteLock,
|
||||
id: id,
|
||||
}
|
||||
if err := c.setStream(id, s); err != nil {
|
||||
_ = s.writePipe.Close()
|
||||
_ = s.readPipe.Close()
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// readDemuxLoop is the lock-free reading processor for this endpoint of the websocket
|
||||
// connection. This loop reads the connection, and demultiplexes the data
|
||||
// into one of the individual stream pipes (by checking the stream id). This
|
||||
// loop can *not* be run concurrently, because there can only be one websocket
|
||||
// connection reader at a time (a read mutex would provide no benefit).
|
||||
func (c *wsStreamCreator) readDemuxLoop(bufferSize int, period time.Duration, deadline time.Duration) {
|
||||
// Initialize and start the ping/pong heartbeat.
|
||||
h := newHeartbeat(c.conn, period, deadline)
|
||||
// Set initial timeout for websocket connection reading.
|
||||
if err := c.conn.SetReadDeadline(time.Now().Add(deadline)); err != nil {
|
||||
klog.Errorf("Websocket initial setting read deadline failed %v", err)
|
||||
return
|
||||
}
|
||||
go h.start()
|
||||
// Buffer size must correspond to the same size allocated
|
||||
// for the read buffer during websocket client creation. A
|
||||
// difference can cause incomplete connection reads.
|
||||
readBuffer := make([]byte, bufferSize)
|
||||
for {
|
||||
// NextReader() only returns data messages (BinaryMessage or Text
|
||||
// Message). Even though this call will never return control frames
|
||||
// such as ping, pong, or close, this call is necessary for these
|
||||
// message types to be processed. There can only be one reader
|
||||
// at a time, so this reader loop must *not* be run concurrently;
|
||||
// there is no lock for reading. Calling "NextReader()" before the
|
||||
// current reader has been processed will close the current reader.
|
||||
// If the heartbeat read deadline times out, this "NextReader()" will
|
||||
// return an i/o error, and error handling will clean up.
|
||||
messageType, r, err := c.conn.NextReader()
|
||||
if err != nil {
|
||||
websocketErr, ok := err.(*gwebsocket.CloseError)
|
||||
if ok && websocketErr.Code == gwebsocket.CloseNormalClosure {
|
||||
err = nil // readers will get io.EOF as it's a normal closure
|
||||
} else {
|
||||
err = fmt.Errorf("next reader: %w", err)
|
||||
}
|
||||
c.closeAllStreamReaders(err)
|
||||
return
|
||||
}
|
||||
// All remote command protocols send/receive only binary data messages.
|
||||
if messageType != gwebsocket.BinaryMessage {
|
||||
c.closeAllStreamReaders(fmt.Errorf("unexpected message type: %d", messageType))
|
||||
return
|
||||
}
|
||||
// It's ok to read just a single byte because the underlying library wraps the actual
|
||||
// connection with a buffered reader anyway.
|
||||
_, err = io.ReadFull(r, readBuffer[:1])
|
||||
if err != nil {
|
||||
c.closeAllStreamReaders(fmt.Errorf("read stream id: %w", err))
|
||||
return
|
||||
}
|
||||
streamID := readBuffer[0]
|
||||
s := c.getStream(streamID)
|
||||
if s == nil {
|
||||
klog.Errorf("Unknown stream id %d, discarding message", streamID)
|
||||
continue
|
||||
}
|
||||
for {
|
||||
nr, errRead := r.Read(readBuffer)
|
||||
if nr > 0 {
|
||||
// Write the data to the stream's pipe. This can block.
|
||||
_, errWrite := s.writePipe.Write(readBuffer[:nr])
|
||||
if errWrite != nil {
|
||||
// Pipe must have been closed by the stream user.
|
||||
// Nothing to do, discard the message.
|
||||
break
|
||||
}
|
||||
}
|
||||
if errRead != nil {
|
||||
if errRead == io.EOF {
|
||||
break
|
||||
}
|
||||
c.closeAllStreamReaders(fmt.Errorf("read message: %w", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// closeAllStreamReaders closes readers in all streams.
|
||||
// This unblocks all stream.Read() calls, and keeps any future streams from being created.
|
||||
func (c *wsStreamCreator) closeAllStreamReaders(err error) {
|
||||
c.streamsMu.Lock()
|
||||
defer c.streamsMu.Unlock()
|
||||
for _, s := range c.streams {
|
||||
// Closing writePipe unblocks all readPipe.Read() callers and prevents any future writes.
|
||||
_ = s.writePipe.CloseWithError(err)
|
||||
}
|
||||
// ensure callers to setStreams receive an error after this point
|
||||
if err != nil {
|
||||
c.setStreamErr = err
|
||||
} else {
|
||||
c.setStreamErr = fmt.Errorf("closed all streams")
|
||||
}
|
||||
}
|
||||
|
||||
type stream struct {
|
||||
headers http.Header
|
||||
readPipe *io.PipeReader
|
||||
writePipe *io.PipeWriter
|
||||
// conn is used for writing directly into the connection.
|
||||
// Is nil after Close() / Reset() to prevent future writes.
|
||||
conn *gwebsocket.Conn
|
||||
// connWriteLock protects conn against concurrent write operations. There must be a single writer and a single reader only.
|
||||
// The mutex is shared across all streams because the underlying connection is shared.
|
||||
connWriteLock *sync.Mutex
|
||||
id byte
|
||||
}
|
||||
|
||||
func (s *stream) Read(p []byte) (n int, err error) {
|
||||
return s.readPipe.Read(p)
|
||||
}
|
||||
|
||||
// Write writes directly to the underlying WebSocket connection.
|
||||
func (s *stream) Write(p []byte) (n int, err error) {
|
||||
klog.V(4).Infof("Write() on stream %d", s.id)
|
||||
defer klog.V(4).Infof("Write() done on stream %d", s.id)
|
||||
s.connWriteLock.Lock()
|
||||
defer s.connWriteLock.Unlock()
|
||||
if s.conn == nil {
|
||||
return 0, fmt.Errorf("write on closed stream %d", s.id)
|
||||
}
|
||||
err = s.conn.SetWriteDeadline(time.Now().Add(writeDeadline))
|
||||
if err != nil {
|
||||
klog.V(7).Infof("Websocket setting write deadline failed %v", err)
|
||||
return 0, err
|
||||
}
|
||||
// Message writer buffers the message data, so we don't need to do that ourselves.
|
||||
// Just write id and the data as two separate writes to avoid allocating an intermediate buffer.
|
||||
w, err := s.conn.NextWriter(gwebsocket.BinaryMessage)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer func() {
|
||||
if w != nil {
|
||||
w.Close()
|
||||
}
|
||||
}()
|
||||
_, err = w.Write([]byte{s.id})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err = w.Write(p)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
err = w.Close()
|
||||
w = nil
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Close half-closes the stream, indicating this side is finished with the stream.
|
||||
func (s *stream) Close() error {
|
||||
klog.V(4).Infof("Close() on stream %d", s.id)
|
||||
defer klog.V(4).Infof("Close() done on stream %d", s.id)
|
||||
s.connWriteLock.Lock()
|
||||
defer s.connWriteLock.Unlock()
|
||||
if s.conn == nil {
|
||||
return fmt.Errorf("Close() on already closed stream %d", s.id)
|
||||
}
|
||||
// Communicate the CLOSE stream signal to the other websocket endpoint.
|
||||
err := s.conn.WriteMessage(gwebsocket.BinaryMessage, []byte{remotecommand.StreamClose, s.id})
|
||||
s.conn = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *stream) Reset() error {
|
||||
klog.V(4).Infof("Reset() on stream %d", s.id)
|
||||
defer klog.V(4).Infof("Reset() done on stream %d", s.id)
|
||||
s.Close()
|
||||
return s.writePipe.Close()
|
||||
}
|
||||
|
||||
func (s *stream) Headers() http.Header {
|
||||
return s.headers
|
||||
}
|
||||
|
||||
func (s *stream) Identifier() uint32 {
|
||||
return uint32(s.id)
|
||||
}
|
||||
|
||||
// heartbeat encasulates data necessary for the websocket ping/pong heartbeat. This
|
||||
// heartbeat works by setting a read deadline on the websocket connection, then
|
||||
// pushing this deadline into the future for every successful heartbeat. If the
|
||||
// heartbeat "pong" fails to respond within the deadline, then the "NextReader()" call
|
||||
// inside the "readDemuxLoop" will return an i/o error prompting a connection close
|
||||
// and cleanup.
|
||||
type heartbeat struct {
|
||||
conn *gwebsocket.Conn
|
||||
// period defines how often a "ping" heartbeat message is sent to the other endpoint
|
||||
period time.Duration
|
||||
// closing the "closer" channel will clean up the heartbeat timers
|
||||
closer chan struct{}
|
||||
// optional data to send with "ping" message
|
||||
message []byte
|
||||
// optionally received data message with "pong" message, same as sent with ping
|
||||
pongMessage []byte
|
||||
}
|
||||
|
||||
// newHeartbeat creates heartbeat structure encapsulating fields necessary to
|
||||
// run the websocket connection ping/pong mechanism and sets up handlers on
|
||||
// the websocket connection.
|
||||
func newHeartbeat(conn *gwebsocket.Conn, period time.Duration, deadline time.Duration) *heartbeat {
|
||||
h := &heartbeat{
|
||||
conn: conn,
|
||||
period: period,
|
||||
closer: make(chan struct{}),
|
||||
}
|
||||
// Set up handler for receiving returned "pong" message from other endpoint
|
||||
// by pushing the read deadline into the future. The "msg" received could
|
||||
// be empty.
|
||||
h.conn.SetPongHandler(func(msg string) error {
|
||||
// Push the read deadline into the future.
|
||||
klog.V(8).Infof("Pong message received (%s)--resetting read deadline", msg)
|
||||
err := h.conn.SetReadDeadline(time.Now().Add(deadline))
|
||||
if err != nil {
|
||||
klog.Errorf("Websocket setting read deadline failed %v", err)
|
||||
return err
|
||||
}
|
||||
if len(msg) > 0 {
|
||||
h.pongMessage = []byte(msg)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
// Set up handler to cleanup timers when this endpoint receives "Close" message.
|
||||
closeHandler := h.conn.CloseHandler()
|
||||
h.conn.SetCloseHandler(func(code int, text string) error {
|
||||
close(h.closer)
|
||||
return closeHandler(code, text)
|
||||
})
|
||||
return h
|
||||
}
|
||||
|
||||
// setMessage is optional data sent with "ping" heartbeat. According to the websocket RFC
|
||||
// this data sent with "ping" message should be returned in "pong" message.
|
||||
func (h *heartbeat) setMessage(msg string) {
|
||||
h.message = []byte(msg)
|
||||
}
|
||||
|
||||
// start the heartbeat by setting up necesssary handlers and looping by sending "ping"
|
||||
// message every "period" until the "closer" channel is closed.
|
||||
func (h *heartbeat) start() {
|
||||
// Loop to continually send "ping" message through websocket connection every "period".
|
||||
t := time.NewTicker(h.period)
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-h.closer:
|
||||
klog.V(8).Infof("closed channel--returning")
|
||||
return
|
||||
case <-t.C:
|
||||
// "WriteControl" does not need to be protected by a mutex. According to
|
||||
// gorilla/websockets library docs: "The Close and WriteControl methods can
|
||||
// be called concurrently with all other methods."
|
||||
if err := h.conn.WriteControl(gwebsocket.PingMessage, h.message, time.Now().Add(pingReadDeadline)); err == nil {
|
||||
klog.V(8).Infof("Websocket Ping succeeeded")
|
||||
} else {
|
||||
klog.Errorf("Websocket Ping failed: %v", err)
|
||||
if errors.Is(err, gwebsocket.ErrCloseSent) {
|
||||
// we continue because c.conn.CloseChan will manage closing the connection already
|
||||
continue
|
||||
} else if e, ok := err.(net.Error); ok && e.Timeout() {
|
||||
// Continue, in case this is a transient failure.
|
||||
// c.conn.CloseChan above will tell us when the connection is
|
||||
// actually closed.
|
||||
// If Temporary function hadn't been deprecated, we would have used it.
|
||||
// But most of temporary errors are timeout errors anyway.
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
150
e2e/vendor/k8s.io/client-go/tools/watch/informerwatcher.go
generated
vendored
Normal file
150
e2e/vendor/k8s.io/client-go/tools/watch/informerwatcher.go
generated
vendored
Normal file
@ -0,0 +1,150 @@
|
||||
/*
|
||||
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 watch
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
func newEventProcessor(out chan<- watch.Event) *eventProcessor {
|
||||
return &eventProcessor{
|
||||
out: out,
|
||||
cond: sync.NewCond(&sync.Mutex{}),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// eventProcessor buffers events and writes them to an out chan when a reader
|
||||
// is waiting. Because of the requirement to buffer events, it synchronizes
|
||||
// input with a condition, and synchronizes output with a channels. It needs to
|
||||
// be able to yield while both waiting on an input condition and while blocked
|
||||
// on writing to the output channel.
|
||||
type eventProcessor struct {
|
||||
out chan<- watch.Event
|
||||
|
||||
cond *sync.Cond
|
||||
buff []watch.Event
|
||||
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (e *eventProcessor) run() {
|
||||
for {
|
||||
batch := e.takeBatch()
|
||||
e.writeBatch(batch)
|
||||
if e.stopped() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *eventProcessor) takeBatch() []watch.Event {
|
||||
e.cond.L.Lock()
|
||||
defer e.cond.L.Unlock()
|
||||
|
||||
for len(e.buff) == 0 && !e.stopped() {
|
||||
e.cond.Wait()
|
||||
}
|
||||
|
||||
batch := e.buff
|
||||
e.buff = nil
|
||||
return batch
|
||||
}
|
||||
|
||||
func (e *eventProcessor) writeBatch(events []watch.Event) {
|
||||
for _, event := range events {
|
||||
select {
|
||||
case e.out <- event:
|
||||
case <-e.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *eventProcessor) push(event watch.Event) {
|
||||
e.cond.L.Lock()
|
||||
defer e.cond.L.Unlock()
|
||||
defer e.cond.Signal()
|
||||
e.buff = append(e.buff, event)
|
||||
}
|
||||
|
||||
func (e *eventProcessor) stopped() bool {
|
||||
select {
|
||||
case <-e.done:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (e *eventProcessor) stop() {
|
||||
close(e.done)
|
||||
e.cond.Signal()
|
||||
}
|
||||
|
||||
// NewIndexerInformerWatcher will create an IndexerInformer and wrap it into watch.Interface
|
||||
// so you can use it anywhere where you'd have used a regular Watcher returned from Watch method.
|
||||
// it also returns a channel you can use to wait for the informers to fully shutdown.
|
||||
func NewIndexerInformerWatcher(lw cache.ListerWatcher, objType runtime.Object) (cache.Indexer, cache.Controller, watch.Interface, <-chan struct{}) {
|
||||
ch := make(chan watch.Event)
|
||||
w := watch.NewProxyWatcher(ch)
|
||||
e := newEventProcessor(ch)
|
||||
|
||||
indexer, informer := cache.NewIndexerInformer(lw, objType, 0, cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
e.push(watch.Event{
|
||||
Type: watch.Added,
|
||||
Object: obj.(runtime.Object),
|
||||
})
|
||||
},
|
||||
UpdateFunc: func(old, new interface{}) {
|
||||
e.push(watch.Event{
|
||||
Type: watch.Modified,
|
||||
Object: new.(runtime.Object),
|
||||
})
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
staleObj, stale := obj.(cache.DeletedFinalStateUnknown)
|
||||
if stale {
|
||||
// We have no means of passing the additional information down using
|
||||
// watch API based on watch.Event but the caller can filter such
|
||||
// objects by checking if metadata.deletionTimestamp is set
|
||||
obj = staleObj.Obj
|
||||
}
|
||||
|
||||
e.push(watch.Event{
|
||||
Type: watch.Deleted,
|
||||
Object: obj.(runtime.Object),
|
||||
})
|
||||
},
|
||||
}, cache.Indexers{})
|
||||
|
||||
go e.run()
|
||||
|
||||
doneCh := make(chan struct{})
|
||||
go func() {
|
||||
defer close(doneCh)
|
||||
defer e.stop()
|
||||
informer.Run(w.StopChan())
|
||||
}()
|
||||
|
||||
return indexer, informer, w, doneCh
|
||||
}
|
334
e2e/vendor/k8s.io/client-go/tools/watch/retrywatcher.go
generated
vendored
Normal file
334
e2e/vendor/k8s.io/client-go/tools/watch/retrywatcher.go
generated
vendored
Normal file
@ -0,0 +1,334 @@
|
||||
/*
|
||||
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 watch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/dump"
|
||||
"k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// resourceVersionGetter is an interface used to get resource version from events.
|
||||
// We can't reuse an interface from meta otherwise it would be a cyclic dependency and we need just this one method
|
||||
type resourceVersionGetter interface {
|
||||
GetResourceVersion() string
|
||||
}
|
||||
|
||||
// RetryWatcher will make sure that in case the underlying watcher is closed (e.g. due to API timeout or etcd timeout)
|
||||
// it will get restarted from the last point without the consumer even knowing about it.
|
||||
// RetryWatcher does that by inspecting events and keeping track of resourceVersion.
|
||||
// Especially useful when using watch.UntilWithoutRetry where premature termination is causing issues and flakes.
|
||||
// Please note that this is not resilient to etcd cache not having the resource version anymore - you would need to
|
||||
// use Informers for that.
|
||||
type RetryWatcher struct {
|
||||
lastResourceVersion string
|
||||
watcherClient cache.Watcher
|
||||
resultChan chan watch.Event
|
||||
stopChan chan struct{}
|
||||
doneChan chan struct{}
|
||||
minRestartDelay time.Duration
|
||||
stopChanLock sync.Mutex
|
||||
}
|
||||
|
||||
// NewRetryWatcher creates a new RetryWatcher.
|
||||
// It will make sure that watches gets restarted in case of recoverable errors.
|
||||
// The initialResourceVersion will be given to watch method when first called.
|
||||
func NewRetryWatcher(initialResourceVersion string, watcherClient cache.Watcher) (*RetryWatcher, error) {
|
||||
return newRetryWatcher(initialResourceVersion, watcherClient, 1*time.Second)
|
||||
}
|
||||
|
||||
func newRetryWatcher(initialResourceVersion string, watcherClient cache.Watcher, minRestartDelay time.Duration) (*RetryWatcher, error) {
|
||||
switch initialResourceVersion {
|
||||
case "", "0":
|
||||
// TODO: revisit this if we ever get WATCH v2 where it means start "now"
|
||||
// without doing the synthetic list of objects at the beginning (see #74022)
|
||||
return nil, fmt.Errorf("initial RV %q is not supported due to issues with underlying WATCH", initialResourceVersion)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
rw := &RetryWatcher{
|
||||
lastResourceVersion: initialResourceVersion,
|
||||
watcherClient: watcherClient,
|
||||
stopChan: make(chan struct{}),
|
||||
doneChan: make(chan struct{}),
|
||||
resultChan: make(chan watch.Event, 0),
|
||||
minRestartDelay: minRestartDelay,
|
||||
}
|
||||
|
||||
go rw.receive()
|
||||
return rw, nil
|
||||
}
|
||||
|
||||
func (rw *RetryWatcher) send(event watch.Event) bool {
|
||||
// Writing to an unbuffered channel is blocking operation
|
||||
// and we need to check if stop wasn't requested while doing so.
|
||||
select {
|
||||
case rw.resultChan <- event:
|
||||
return true
|
||||
case <-rw.stopChan:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// doReceive returns true when it is done, false otherwise.
|
||||
// If it is not done the second return value holds the time to wait before calling it again.
|
||||
func (rw *RetryWatcher) doReceive() (bool, time.Duration) {
|
||||
watcher, err := rw.watcherClient.Watch(metav1.ListOptions{
|
||||
ResourceVersion: rw.lastResourceVersion,
|
||||
AllowWatchBookmarks: true,
|
||||
})
|
||||
// We are very unlikely to hit EOF here since we are just establishing the call,
|
||||
// but it may happen that the apiserver is just shutting down (e.g. being restarted)
|
||||
// This is consistent with how it is handled for informers
|
||||
switch err {
|
||||
case nil:
|
||||
break
|
||||
|
||||
case io.EOF:
|
||||
// watch closed normally
|
||||
return false, 0
|
||||
|
||||
case io.ErrUnexpectedEOF:
|
||||
klog.V(1).InfoS("Watch closed with unexpected EOF", "err", err)
|
||||
return false, 0
|
||||
|
||||
default:
|
||||
msg := "Watch failed"
|
||||
if net.IsProbableEOF(err) || net.IsTimeout(err) {
|
||||
klog.V(5).InfoS(msg, "err", err)
|
||||
// Retry
|
||||
return false, 0
|
||||
}
|
||||
|
||||
// Check if the watch failed due to the client not having permission to watch the resource or the credentials
|
||||
// being invalid (e.g. expired token).
|
||||
if apierrors.IsForbidden(err) || apierrors.IsUnauthorized(err) {
|
||||
// Add more detail since the forbidden message returned by the Kubernetes API is just "unknown".
|
||||
klog.ErrorS(err, msg+": ensure the client has valid credentials and watch permissions on the resource")
|
||||
|
||||
if apiStatus, ok := err.(apierrors.APIStatus); ok {
|
||||
statusErr := apiStatus.Status()
|
||||
|
||||
sent := rw.send(watch.Event{
|
||||
Type: watch.Error,
|
||||
Object: &statusErr,
|
||||
})
|
||||
if !sent {
|
||||
// This likely means the RetryWatcher is stopping but return false so the caller to doReceive can
|
||||
// verify this and potentially retry.
|
||||
klog.Error("Failed to send the Unauthorized or Forbidden watch event")
|
||||
|
||||
return false, 0
|
||||
}
|
||||
} else {
|
||||
// This should never happen since apierrors only handles apierrors.APIStatus. Still, this is an
|
||||
// unrecoverable error, so still allow it to return true below.
|
||||
klog.ErrorS(err, msg+": encountered an unexpected Unauthorized or Forbidden error type")
|
||||
}
|
||||
|
||||
return true, 0
|
||||
}
|
||||
|
||||
klog.ErrorS(err, msg)
|
||||
// Retry
|
||||
return false, 0
|
||||
}
|
||||
|
||||
if watcher == nil {
|
||||
klog.ErrorS(nil, "Watch returned nil watcher")
|
||||
// Retry
|
||||
return false, 0
|
||||
}
|
||||
|
||||
ch := watcher.ResultChan()
|
||||
defer watcher.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-rw.stopChan:
|
||||
klog.V(4).InfoS("Stopping RetryWatcher.")
|
||||
return true, 0
|
||||
case event, ok := <-ch:
|
||||
if !ok {
|
||||
klog.V(4).InfoS("Failed to get event! Re-creating the watcher.", "resourceVersion", rw.lastResourceVersion)
|
||||
return false, 0
|
||||
}
|
||||
|
||||
// We need to inspect the event and get ResourceVersion out of it
|
||||
switch event.Type {
|
||||
case watch.Added, watch.Modified, watch.Deleted, watch.Bookmark:
|
||||
metaObject, ok := event.Object.(resourceVersionGetter)
|
||||
if !ok {
|
||||
_ = rw.send(watch.Event{
|
||||
Type: watch.Error,
|
||||
Object: &apierrors.NewInternalError(errors.New("retryWatcher: doesn't support resourceVersion")).ErrStatus,
|
||||
})
|
||||
// We have to abort here because this might cause lastResourceVersion inconsistency by skipping a potential RV with valid data!
|
||||
return true, 0
|
||||
}
|
||||
|
||||
resourceVersion := metaObject.GetResourceVersion()
|
||||
if resourceVersion == "" {
|
||||
_ = rw.send(watch.Event{
|
||||
Type: watch.Error,
|
||||
Object: &apierrors.NewInternalError(fmt.Errorf("retryWatcher: object %#v doesn't support resourceVersion", event.Object)).ErrStatus,
|
||||
})
|
||||
// We have to abort here because this might cause lastResourceVersion inconsistency by skipping a potential RV with valid data!
|
||||
return true, 0
|
||||
}
|
||||
|
||||
// All is fine; send the non-bookmark events and update resource version.
|
||||
if event.Type != watch.Bookmark {
|
||||
ok = rw.send(event)
|
||||
if !ok {
|
||||
return true, 0
|
||||
}
|
||||
}
|
||||
rw.lastResourceVersion = resourceVersion
|
||||
|
||||
continue
|
||||
|
||||
case watch.Error:
|
||||
// This round trip allows us to handle unstructured status
|
||||
errObject := apierrors.FromObject(event.Object)
|
||||
statusErr, ok := errObject.(*apierrors.StatusError)
|
||||
if !ok {
|
||||
klog.Error(fmt.Sprintf("Received an error which is not *metav1.Status but %s", dump.Pretty(event.Object)))
|
||||
// Retry unknown errors
|
||||
return false, 0
|
||||
}
|
||||
|
||||
status := statusErr.ErrStatus
|
||||
|
||||
statusDelay := time.Duration(0)
|
||||
if status.Details != nil {
|
||||
statusDelay = time.Duration(status.Details.RetryAfterSeconds) * time.Second
|
||||
}
|
||||
|
||||
switch status.Code {
|
||||
case http.StatusGone:
|
||||
// Never retry RV too old errors
|
||||
_ = rw.send(event)
|
||||
return true, 0
|
||||
|
||||
case http.StatusGatewayTimeout, http.StatusInternalServerError:
|
||||
// Retry
|
||||
return false, statusDelay
|
||||
|
||||
default:
|
||||
// We retry by default. RetryWatcher is meant to proceed unless it is certain
|
||||
// that it can't. If we are not certain, we proceed with retry and leave it
|
||||
// up to the user to timeout if needed.
|
||||
|
||||
// Log here so we have a record of hitting the unexpected error
|
||||
// and we can whitelist some error codes if we missed any that are expected.
|
||||
klog.V(5).Info(fmt.Sprintf("Retrying after unexpected error: %s", dump.Pretty(event.Object)))
|
||||
|
||||
// Retry
|
||||
return false, statusDelay
|
||||
}
|
||||
|
||||
default:
|
||||
klog.Errorf("Failed to recognize Event type %q", event.Type)
|
||||
_ = rw.send(watch.Event{
|
||||
Type: watch.Error,
|
||||
Object: &apierrors.NewInternalError(fmt.Errorf("retryWatcher failed to recognize Event type %q", event.Type)).ErrStatus,
|
||||
})
|
||||
// We are unable to restart the watch and have to stop the loop or this might cause lastResourceVersion inconsistency by skipping a potential RV with valid data!
|
||||
return true, 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// receive reads the result from a watcher, restarting it if necessary.
|
||||
func (rw *RetryWatcher) receive() {
|
||||
defer close(rw.doneChan)
|
||||
defer close(rw.resultChan)
|
||||
|
||||
klog.V(4).Info("Starting RetryWatcher.")
|
||||
defer klog.V(4).Info("Stopping RetryWatcher.")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
go func() {
|
||||
select {
|
||||
case <-rw.stopChan:
|
||||
cancel()
|
||||
return
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// We use non sliding until so we don't introduce delays on happy path when WATCH call
|
||||
// timeouts or gets closed and we need to reestablish it while also avoiding hot loops.
|
||||
wait.NonSlidingUntilWithContext(ctx, func(ctx context.Context) {
|
||||
done, retryAfter := rw.doReceive()
|
||||
if done {
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
|
||||
timer := time.NewTimer(retryAfter)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
timer.Stop()
|
||||
return
|
||||
case <-timer.C:
|
||||
}
|
||||
|
||||
klog.V(4).Infof("Restarting RetryWatcher at RV=%q", rw.lastResourceVersion)
|
||||
}, rw.minRestartDelay)
|
||||
}
|
||||
|
||||
// ResultChan implements Interface.
|
||||
func (rw *RetryWatcher) ResultChan() <-chan watch.Event {
|
||||
return rw.resultChan
|
||||
}
|
||||
|
||||
// Stop implements Interface.
|
||||
func (rw *RetryWatcher) Stop() {
|
||||
rw.stopChanLock.Lock()
|
||||
defer rw.stopChanLock.Unlock()
|
||||
|
||||
// Prevent closing an already closed channel to prevent a panic
|
||||
select {
|
||||
case <-rw.stopChan:
|
||||
default:
|
||||
close(rw.stopChan)
|
||||
}
|
||||
}
|
||||
|
||||
// Done allows the caller to be notified when Retry watcher stops.
|
||||
func (rw *RetryWatcher) Done() <-chan struct{} {
|
||||
return rw.doneChan
|
||||
}
|
168
e2e/vendor/k8s.io/client-go/tools/watch/until.go
generated
vendored
Normal file
168
e2e/vendor/k8s.io/client-go/tools/watch/until.go
generated
vendored
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
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 watch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// PreconditionFunc returns true if the condition has been reached, false if it has not been reached yet,
|
||||
// or an error if the condition failed or detected an error state.
|
||||
type PreconditionFunc func(store cache.Store) (bool, error)
|
||||
|
||||
// ConditionFunc returns true if the condition has been reached, false if it has not been reached yet,
|
||||
// or an error if the condition cannot be checked and should terminate. In general, it is better to define
|
||||
// level driven conditions over edge driven conditions (pod has ready=true, vs pod modified and ready changed
|
||||
// from false to true).
|
||||
type ConditionFunc func(event watch.Event) (bool, error)
|
||||
|
||||
// ErrWatchClosed is returned when the watch channel is closed before timeout in UntilWithoutRetry.
|
||||
var ErrWatchClosed = errors.New("watch closed before UntilWithoutRetry timeout")
|
||||
|
||||
// UntilWithoutRetry reads items from the watch until each provided condition succeeds, and then returns the last watch
|
||||
// encountered. The first condition that returns an error terminates the watch (and the event is also returned).
|
||||
// If no event has been received, the returned event will be nil.
|
||||
// Conditions are satisfied sequentially so as to provide a useful primitive for higher level composition.
|
||||
// Waits until context deadline or until context is canceled.
|
||||
//
|
||||
// Warning: Unless you have a very specific use case (probably a special Watcher) don't use this function!!!
|
||||
// Warning: This will fail e.g. on API timeouts and/or 'too old resource version' error.
|
||||
// Warning: You are most probably looking for a function *Until* or *UntilWithSync* below,
|
||||
// Warning: solving such issues.
|
||||
// TODO: Consider making this function private to prevent misuse when the other occurrences in our codebase are gone.
|
||||
func UntilWithoutRetry(ctx context.Context, watcher watch.Interface, conditions ...ConditionFunc) (*watch.Event, error) {
|
||||
ch := watcher.ResultChan()
|
||||
defer watcher.Stop()
|
||||
var lastEvent *watch.Event
|
||||
for _, condition := range conditions {
|
||||
// check the next condition against the previous event and short circuit waiting for the next watch
|
||||
if lastEvent != nil {
|
||||
done, err := condition(*lastEvent)
|
||||
if err != nil {
|
||||
return lastEvent, err
|
||||
}
|
||||
if done {
|
||||
continue
|
||||
}
|
||||
}
|
||||
ConditionSucceeded:
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-ch:
|
||||
if !ok {
|
||||
return lastEvent, ErrWatchClosed
|
||||
}
|
||||
lastEvent = &event
|
||||
|
||||
done, err := condition(event)
|
||||
if err != nil {
|
||||
return lastEvent, err
|
||||
}
|
||||
if done {
|
||||
break ConditionSucceeded
|
||||
}
|
||||
|
||||
case <-ctx.Done():
|
||||
return lastEvent, wait.ErrWaitTimeout
|
||||
}
|
||||
}
|
||||
}
|
||||
return lastEvent, nil
|
||||
}
|
||||
|
||||
// Until wraps the watcherClient's watch function with RetryWatcher making sure that watcher gets restarted in case of errors.
|
||||
// The initialResourceVersion will be given to watch method when first called. It shall not be "" or "0"
|
||||
// given the underlying WATCH call issues (#74022).
|
||||
// Remaining behaviour is identical to function UntilWithoutRetry. (See above.)
|
||||
// Until can deal with API timeouts and lost connections.
|
||||
// It guarantees you to see all events and in the order they happened.
|
||||
// Due to this guarantee there is no way it can deal with 'Resource version too old error'. It will fail in this case.
|
||||
// (See `UntilWithSync` if you'd prefer to recover from all the errors including RV too old by re-listing
|
||||
// those items. In normal code you should care about being level driven so you'd not care about not seeing all the edges.)
|
||||
//
|
||||
// The most frequent usage for Until would be a test where you want to verify exact order of events ("edges").
|
||||
func Until(ctx context.Context, initialResourceVersion string, watcherClient cache.Watcher, conditions ...ConditionFunc) (*watch.Event, error) {
|
||||
w, err := NewRetryWatcher(initialResourceVersion, watcherClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return UntilWithoutRetry(ctx, w, conditions...)
|
||||
}
|
||||
|
||||
// UntilWithSync creates an informer from lw, optionally checks precondition when the store is synced,
|
||||
// and watches the output until each provided condition succeeds, in a way that is identical
|
||||
// to function UntilWithoutRetry. (See above.)
|
||||
// UntilWithSync can deal with all errors like API timeout, lost connections and 'Resource version too old'.
|
||||
// It is the only function that can recover from 'Resource version too old', Until and UntilWithoutRetry will
|
||||
// just fail in that case. On the other hand it can't provide you with guarantees as strong as using simple
|
||||
// Watch method with Until. It can skip some intermediate events in case of watch function failing but it will
|
||||
// re-list to recover and you always get an event, if there has been a change, after recovery.
|
||||
// Also with the current implementation based on DeltaFIFO, order of the events you receive is guaranteed only for
|
||||
// particular object, not between more of them even it's the same resource.
|
||||
// The most frequent usage would be a command that needs to watch the "state of the world" and should't fail, like:
|
||||
// waiting for object reaching a state, "small" controllers, ...
|
||||
func UntilWithSync(ctx context.Context, lw cache.ListerWatcher, objType runtime.Object, precondition PreconditionFunc, conditions ...ConditionFunc) (*watch.Event, error) {
|
||||
indexer, informer, watcher, done := NewIndexerInformerWatcher(lw, objType)
|
||||
// We need to wait for the internal informers to fully stop so it's easier to reason about
|
||||
// and it works with non-thread safe clients.
|
||||
defer func() { <-done }()
|
||||
// Proxy watcher can be stopped multiple times so it's fine to use defer here to cover alternative branches and
|
||||
// let UntilWithoutRetry to stop it
|
||||
defer watcher.Stop()
|
||||
|
||||
if precondition != nil {
|
||||
if !cache.WaitForCacheSync(ctx.Done(), informer.HasSynced) {
|
||||
return nil, fmt.Errorf("UntilWithSync: unable to sync caches: %w", ctx.Err())
|
||||
}
|
||||
|
||||
done, err := precondition(indexer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if done {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
return UntilWithoutRetry(ctx, watcher, conditions...)
|
||||
}
|
||||
|
||||
// ContextWithOptionalTimeout wraps context.WithTimeout and handles infinite timeouts expressed as 0 duration.
|
||||
func ContextWithOptionalTimeout(parent context.Context, timeout time.Duration) (context.Context, context.CancelFunc) {
|
||||
if timeout < 0 {
|
||||
// This should be handled in validation
|
||||
klog.Errorf("Timeout for context shall not be negative!")
|
||||
timeout = 0
|
||||
}
|
||||
|
||||
if timeout == 0 {
|
||||
return context.WithCancel(parent)
|
||||
}
|
||||
|
||||
return context.WithTimeout(parent, timeout)
|
||||
}
|
Reference in New Issue
Block a user