mirror of
https://github.com/kubernetes/client-go.git
synced 2026-07-16 08:48:13 +00:00
Merge pull request #129109 from pohly/log-client-go-restmapper-discovery
restmapper + discovery: add context-aware APIs Kubernetes-commit: b308e6c5067486765de78de1dc809fb30b0b68c3
This commit is contained in:
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package disk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -42,7 +43,7 @@ import (
|
||||
// CachedDiscoveryClient implements the functions that discovery server-supported API groups,
|
||||
// versions and resources.
|
||||
type CachedDiscoveryClient struct {
|
||||
delegate discovery.DiscoveryInterface
|
||||
delegate discovery.DiscoveryInterfaceWithContext
|
||||
|
||||
// cacheDirectory is the directory where discovery docs are held. It must be unique per host:port combination to work well.
|
||||
cacheDirectory string
|
||||
@@ -61,72 +62,104 @@ type CachedDiscoveryClient struct {
|
||||
fresh bool
|
||||
|
||||
// caching openapi v3 client which wraps the delegate's client
|
||||
openapiClient openapi.Client
|
||||
openapiClient *cachedopenapi.Client
|
||||
}
|
||||
|
||||
var _ discovery.CachedDiscoveryInterface = &CachedDiscoveryClient{}
|
||||
var (
|
||||
//nolint:staticcheck // Intentionally using the old interface here.
|
||||
_ discovery.CachedDiscoveryInterface = &CachedDiscoveryClient{}
|
||||
_ discovery.CachedDiscoveryInterfaceWithContext = &CachedDiscoveryClient{}
|
||||
)
|
||||
|
||||
// ServerResourcesForGroupVersion returns the supported resources for a group and version.
|
||||
//
|
||||
// ServerResourcesForGroupVersionWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerResourcesForGroupVersionWithContext instead.
|
||||
func (d *CachedDiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
|
||||
return d.ServerResourcesForGroupVersionWithContext(context.Background(), groupVersion)
|
||||
}
|
||||
|
||||
// ServerResourcesForGroupVersionWithContext returns the supported resources for a group and version.
|
||||
func (d *CachedDiscoveryClient) ServerResourcesForGroupVersionWithContext(ctx context.Context, groupVersion string) (*metav1.APIResourceList, error) {
|
||||
filename := filepath.Join(d.cacheDirectory, groupVersion, "serverresources.json")
|
||||
cachedBytes, err := d.getCachedFile(filename)
|
||||
// don't fail on errors, we either don't have a file or won't be able to run the cached check. Either way we can fallback.
|
||||
if err == nil {
|
||||
cachedResources := &metav1.APIResourceList{}
|
||||
if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), cachedBytes, cachedResources); err == nil {
|
||||
klog.V(10).Infof("returning cached discovery info from %v", filename)
|
||||
klog.FromContext(ctx).V(10).Info("Returning cached discovery info", "fileName", filename)
|
||||
return cachedResources, nil
|
||||
}
|
||||
}
|
||||
|
||||
liveResources, err := d.delegate.ServerResourcesForGroupVersion(groupVersion)
|
||||
liveResources, err := d.delegate.ServerResourcesForGroupVersionWithContext(ctx, groupVersion)
|
||||
if err != nil {
|
||||
klog.V(3).Infof("skipped caching discovery info due to %v", err)
|
||||
klog.FromContext(ctx).V(3).Info("Skipped caching discovery info due to error", "err", err)
|
||||
return liveResources, err
|
||||
}
|
||||
if liveResources == nil || len(liveResources.APIResources) == 0 {
|
||||
klog.V(3).Infof("skipped caching discovery info, no resources found")
|
||||
klog.FromContext(ctx).V(3).Info("skipped caching discovery info, no resources found")
|
||||
return liveResources, err
|
||||
}
|
||||
|
||||
if err := d.writeCachedFile(filename, liveResources); err != nil {
|
||||
klog.V(1).Infof("failed to write cache to %v due to %v", filename, err)
|
||||
klog.FromContext(ctx).V(1).Info("Failed to write cache", "fileName", filename, "err", err)
|
||||
}
|
||||
|
||||
return liveResources, nil
|
||||
}
|
||||
|
||||
// ServerGroupsAndResources returns the supported groups and resources for all groups and versions.
|
||||
//
|
||||
// ServerGroupsAndResourcesWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerGroupsAndResourcesWithContext instead.
|
||||
func (d *CachedDiscoveryClient) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
|
||||
return discovery.ServerGroupsAndResources(d)
|
||||
return d.ServerGroupsAndResourcesWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ServerGroupsAndResourcesWithContext returns the supported groups and resources for all groups and versions.
|
||||
func (d *CachedDiscoveryClient) ServerGroupsAndResourcesWithContext(ctx context.Context) ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
|
||||
return discovery.ServerGroupsAndResourcesWithContext(ctx, d)
|
||||
}
|
||||
|
||||
// ServerGroups returns the supported groups, with information like supported versions and the
|
||||
// preferred version.
|
||||
//
|
||||
// ServerGroupsWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerGroupsWithContext instead.
|
||||
func (d *CachedDiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {
|
||||
return d.ServerGroupsWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ServerGroupsWithContext returns the supported groups, with information like supported versions and the
|
||||
// preferred version.
|
||||
func (d *CachedDiscoveryClient) ServerGroupsWithContext(ctx context.Context) (*metav1.APIGroupList, error) {
|
||||
filename := filepath.Join(d.cacheDirectory, "servergroups.json")
|
||||
cachedBytes, err := d.getCachedFile(filename)
|
||||
// don't fail on errors, we either don't have a file or won't be able to run the cached check. Either way we can fallback.
|
||||
if err == nil {
|
||||
cachedGroups := &metav1.APIGroupList{}
|
||||
if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), cachedBytes, cachedGroups); err == nil {
|
||||
klog.V(10).Infof("returning cached discovery info from %v", filename)
|
||||
klog.FromContext(ctx).V(10).Info("Returning cached discovery info", "fileName", filename)
|
||||
return cachedGroups, nil
|
||||
}
|
||||
}
|
||||
|
||||
liveGroups, err := d.delegate.ServerGroups()
|
||||
liveGroups, err := d.delegate.ServerGroupsWithContext(ctx)
|
||||
if err != nil {
|
||||
klog.V(3).Infof("skipped caching discovery info due to %v", err)
|
||||
klog.FromContext(ctx).V(3).Info("Skipped caching discovery info due to error", "err", err)
|
||||
return liveGroups, err
|
||||
}
|
||||
if liveGroups == nil || len(liveGroups.Groups) == 0 {
|
||||
klog.V(3).Infof("skipped caching discovery info, no groups found")
|
||||
klog.FromContext(ctx).V(3).Info("Skipped caching discovery info, no groups found")
|
||||
return liveGroups, err
|
||||
}
|
||||
|
||||
if err := d.writeCachedFile(filename, liveGroups); err != nil {
|
||||
klog.V(1).Infof("failed to write cache to %v due to %v", filename, err)
|
||||
klog.FromContext(ctx).V(1).Info("Failed to write cache", "fileName", filename, "err", err)
|
||||
}
|
||||
|
||||
return liveGroups, nil
|
||||
@@ -219,28 +252,75 @@ func (d *CachedDiscoveryClient) RESTClient() restclient.Interface {
|
||||
|
||||
// ServerPreferredResources returns the supported resources with the version preferred by the
|
||||
// server.
|
||||
//
|
||||
// ServerPreferredResourcesWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerPreferredResourcesWithContext instead.
|
||||
func (d *CachedDiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
|
||||
return discovery.ServerPreferredResources(d)
|
||||
return d.ServerPreferredResourcesWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ServerPreferredResourcesWithContext returns the supported resources with the version preferred by the
|
||||
// server.
|
||||
func (d *CachedDiscoveryClient) ServerPreferredResourcesWithContext(ctx context.Context) ([]*metav1.APIResourceList, error) {
|
||||
return discovery.ServerPreferredResourcesWithContext(ctx, d)
|
||||
}
|
||||
|
||||
// ServerPreferredNamespacedResources returns the supported namespaced resources with the
|
||||
// version preferred by the server.
|
||||
//
|
||||
// ServerPreferredNamespacedResourcesWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerPreferredNamespacedResourcesWithContext instead.
|
||||
func (d *CachedDiscoveryClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
|
||||
return discovery.ServerPreferredNamespacedResources(d)
|
||||
return d.ServerPreferredNamespacedResourcesWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ServerPreferredNamespacedResourcesWithContext returns the supported namespaced resources with the
|
||||
// version preferred by the server.
|
||||
func (d *CachedDiscoveryClient) ServerPreferredNamespacedResourcesWithContext(ctx context.Context) ([]*metav1.APIResourceList, error) {
|
||||
return discovery.ServerPreferredNamespacedResourcesWithContext(ctx, d)
|
||||
}
|
||||
|
||||
// ServerVersion retrieves and parses the server's version (git version).
|
||||
//
|
||||
// ServerVersionWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerVersionWithContext instead.
|
||||
func (d *CachedDiscoveryClient) ServerVersion() (*version.Info, error) {
|
||||
return d.delegate.ServerVersion()
|
||||
return d.ServerVersionWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ServerVersionWithContext retrieves and parses the server's version (git version).
|
||||
func (d *CachedDiscoveryClient) ServerVersionWithContext(ctx context.Context) (*version.Info, error) {
|
||||
return d.delegate.ServerVersionWithContext(ctx)
|
||||
}
|
||||
|
||||
// OpenAPISchema retrieves and parses the swagger API schema the server supports.
|
||||
//
|
||||
// OpenAPISchemaWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use OpenAPISchemaWithContext instead.
|
||||
func (d *CachedDiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
|
||||
return d.delegate.OpenAPISchema()
|
||||
return d.OpenAPISchemaWithContext(context.Background())
|
||||
}
|
||||
|
||||
// OpenAPISchemaWithContext retrieves and parses the swagger API schema the server supports.
|
||||
func (d *CachedDiscoveryClient) OpenAPISchemaWithContext(ctx context.Context) (*openapi_v2.Document, error) {
|
||||
return d.delegate.OpenAPISchemaWithContext(ctx)
|
||||
}
|
||||
|
||||
// OpenAPIV3 retrieves and parses the OpenAPIV3 specs exposed by the server
|
||||
func (d *CachedDiscoveryClient) OpenAPIV3() openapi.Client {
|
||||
return d.openAPIV3(context.Background())
|
||||
}
|
||||
|
||||
// OpenAPIV3WithContext retrieves and parses the OpenAPIV3 specs exposed by the server
|
||||
func (d *CachedDiscoveryClient) OpenAPIV3WithContext(ctx context.Context) openapi.ClientWithContext {
|
||||
return d.openAPIV3(ctx)
|
||||
}
|
||||
|
||||
func (d *CachedDiscoveryClient) openAPIV3(ctx context.Context) *cachedopenapi.Client {
|
||||
// Must take lock since Invalidate call may modify openapiClient
|
||||
d.mutex.Lock()
|
||||
defer d.mutex.Unlock()
|
||||
@@ -248,7 +328,7 @@ func (d *CachedDiscoveryClient) OpenAPIV3() openapi.Client {
|
||||
if d.openapiClient == nil {
|
||||
// Delegate is discovery client created with special HTTP client which
|
||||
// respects E-Tag cache responses to serve cache from disk.
|
||||
d.openapiClient = cachedopenapi.NewClient(d.delegate.OpenAPIV3())
|
||||
d.openapiClient = cachedopenapi.NewClientWithContext(d.delegate.OpenAPIV3WithContext(ctx))
|
||||
}
|
||||
|
||||
return d.openapiClient
|
||||
@@ -256,7 +336,17 @@ func (d *CachedDiscoveryClient) OpenAPIV3() openapi.Client {
|
||||
|
||||
// Fresh is supposed to tell the caller whether or not to retry if the cache
|
||||
// fails to find something (false = retry, true = no need to retry).
|
||||
//
|
||||
// FreshWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use FreshWithContext instead.
|
||||
func (d *CachedDiscoveryClient) Fresh() bool {
|
||||
return d.FreshWithContext(context.Background())
|
||||
}
|
||||
|
||||
// FreshWithContext is supposed to tell the caller whether or not to retry if the cache
|
||||
// fails to find something (false = retry, true = no need to retry).
|
||||
func (d *CachedDiscoveryClient) FreshWithContext(ctx context.Context) bool {
|
||||
d.mutex.Lock()
|
||||
defer d.mutex.Unlock()
|
||||
|
||||
@@ -264,7 +354,16 @@ func (d *CachedDiscoveryClient) Fresh() bool {
|
||||
}
|
||||
|
||||
// Invalidate enforces that no cached data is used in the future that is older than the current time.
|
||||
//
|
||||
// InvalidateWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use InvalidateWithContext instead.
|
||||
func (d *CachedDiscoveryClient) Invalidate() {
|
||||
d.InvalidateWithContext(context.Background())
|
||||
}
|
||||
|
||||
// InvalidateWithContext enforces that no cached data is used in the future that is older than the current time.
|
||||
func (d *CachedDiscoveryClient) InvalidateWithContext(ctx context.Context) {
|
||||
d.mutex.Lock()
|
||||
defer d.mutex.Unlock()
|
||||
|
||||
@@ -272,8 +371,16 @@ func (d *CachedDiscoveryClient) Invalidate() {
|
||||
d.fresh = true
|
||||
d.invalidated = true
|
||||
d.openapiClient = nil
|
||||
if ad, ok := d.delegate.(discovery.CachedDiscoveryInterface); ok {
|
||||
ad.Invalidate()
|
||||
ad, ok := d.delegate.(discovery.CachedDiscoveryInterfaceWithContext)
|
||||
if !ok {
|
||||
//nolint:staticcheck // Intentionally using the old interface here.
|
||||
if ad2, ok2 := d.delegate.(discovery.CachedDiscoveryInterface); ok2 {
|
||||
ad = discovery.ToCachedDiscoveryInterfaceWithContext(ad2)
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
ad.InvalidateWithContext(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,6 +390,12 @@ func (d *CachedDiscoveryClient) WithLegacy() discovery.DiscoveryInterface {
|
||||
return d
|
||||
}
|
||||
|
||||
// WithLegacyWithContext returns current cached discovery client;
|
||||
// current client does not support legacy-only discovery.
|
||||
func (d *CachedDiscoveryClient) WithLegacyWithContext(ctx context.Context) discovery.DiscoveryInterfaceWithContext {
|
||||
return d
|
||||
}
|
||||
|
||||
// NewCachedDiscoveryClientForConfig creates a new DiscoveryClient for the given config, and wraps
|
||||
// the created client in a CachedDiscoveryClient. The provided configuration is updated with a
|
||||
// custom transport that understands cache responses.
|
||||
@@ -310,11 +423,11 @@ func NewCachedDiscoveryClientForConfig(config *restclient.Config, discoveryCache
|
||||
// The delegate caches the discovery groups and resources (memcache). "ServerGroups",
|
||||
// which usually only returns (and caches) the groups, can now store the resources as
|
||||
// well if the server supports the newer aggregated discovery format.
|
||||
return newCachedDiscoveryClient(memory.NewMemCacheClient(discoveryClient), discoveryCacheDir, ttl), nil
|
||||
return newCachedDiscoveryClient(memory.NewMemCacheClientWithContext(discoveryClient), discoveryCacheDir, ttl), nil
|
||||
}
|
||||
|
||||
// NewCachedDiscoveryClient creates a new DiscoveryClient. cacheDirectory is the directory where discovery docs are held. It must be unique per host:port combination to work well.
|
||||
func newCachedDiscoveryClient(delegate discovery.DiscoveryInterface, cacheDirectory string, ttl time.Duration) *CachedDiscoveryClient {
|
||||
func newCachedDiscoveryClient(delegate discovery.DiscoveryInterfaceWithContext, cacheDirectory string, ttl time.Duration) *CachedDiscoveryClient {
|
||||
return &CachedDiscoveryClient{
|
||||
delegate: delegate,
|
||||
cacheDirectory: cacheDirectory,
|
||||
|
||||
@@ -52,7 +52,7 @@ func TestCachedDiscoveryClient_Fresh(t *testing.T) {
|
||||
defer os.RemoveAll(d)
|
||||
|
||||
c := fakeDiscoveryClient{}
|
||||
cdc := newCachedDiscoveryClient(&c, d, 60*time.Second)
|
||||
cdc := newCachedDiscoveryClient(discovery.ToDiscoveryInterfaceWithContext(&c), d, 60*time.Second)
|
||||
assert.True(cdc.Fresh(), "should be fresh after creation")
|
||||
|
||||
cdc.ServerGroups()
|
||||
@@ -71,7 +71,7 @@ func TestCachedDiscoveryClient_Fresh(t *testing.T) {
|
||||
assert.True(cdc.Fresh(), "should be fresh after another resources call")
|
||||
assert.Equal(1, c.resourceCalls)
|
||||
|
||||
cdc = newCachedDiscoveryClient(&c, d, 60*time.Second)
|
||||
cdc = newCachedDiscoveryClient(discovery.ToDiscoveryInterfaceWithContext(&c), d, 60*time.Second)
|
||||
cdc.ServerGroups()
|
||||
assert.False(cdc.Fresh(), "should NOT be fresh after recreation with existing groups cache")
|
||||
assert.Equal(1, c.groupCalls)
|
||||
@@ -96,7 +96,7 @@ func TestNewCachedDiscoveryClient_TTL(t *testing.T) {
|
||||
defer os.RemoveAll(d)
|
||||
|
||||
c := fakeDiscoveryClient{}
|
||||
cdc := newCachedDiscoveryClient(&c, d, 1*time.Nanosecond)
|
||||
cdc := newCachedDiscoveryClient(discovery.ToDiscoveryInterfaceWithContext(&c), d, 1*time.Nanosecond)
|
||||
cdc.ServerGroups()
|
||||
assert.Equal(1, c.groupCalls)
|
||||
|
||||
@@ -115,7 +115,7 @@ func TestNewCachedDiscoveryClient_PathPerm(t *testing.T) {
|
||||
defer os.RemoveAll(d)
|
||||
|
||||
c := fakeDiscoveryClient{}
|
||||
cdc := newCachedDiscoveryClient(&c, d, 1*time.Nanosecond)
|
||||
cdc := newCachedDiscoveryClient(discovery.ToDiscoveryInterfaceWithContext(&c), d, 1*time.Nanosecond)
|
||||
cdc.ServerGroups()
|
||||
|
||||
err = filepath.Walk(d, func(path string, info os.FileInfo, err error) error {
|
||||
|
||||
@@ -60,7 +60,7 @@ func (rt *cacheRoundTripper) CancelRequest(req *http.Request) {
|
||||
if cr, ok := rt.rt.Transport.(canceler); ok {
|
||||
cr.CancelRequest(req)
|
||||
} else {
|
||||
klog.Errorf("CancelRequest not implemented by %T", rt.rt.Transport)
|
||||
klog.FromContext(req.Context()).Error(nil, "CancelRequest not implemented by round-tripper", "type", fmt.Sprintf("%T", rt.rt.Transport))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package memory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
@@ -47,13 +48,13 @@ type cacheEntry struct {
|
||||
// TODO: Switch to a watch interface. Right now it will poll after each
|
||||
// Invalidate() call.
|
||||
type memCacheClient struct {
|
||||
delegate discovery.DiscoveryInterface
|
||||
delegate discovery.DiscoveryInterfaceWithContext
|
||||
|
||||
lock sync.RWMutex
|
||||
groupToServerResources map[string]*cacheEntry
|
||||
groupList *metav1.APIGroupList
|
||||
cacheValid bool
|
||||
openapiClient openapi.Client
|
||||
openapiClient *cachedopenapi.Client
|
||||
receivedAggregatedDiscovery bool
|
||||
}
|
||||
|
||||
@@ -72,6 +73,7 @@ func (e *emptyResponseError) Error() string {
|
||||
}
|
||||
|
||||
var _ discovery.CachedDiscoveryInterface = &memCacheClient{}
|
||||
var _ discovery.CachedDiscoveryInterfaceWithContext = &memCacheClient{}
|
||||
|
||||
// isTransientConnectionError checks whether given error is "Connection refused" or
|
||||
// "Connection reset" error which usually means that apiserver is temporarily
|
||||
@@ -85,6 +87,11 @@ func isTransientConnectionError(err error) bool {
|
||||
}
|
||||
|
||||
func isTransientError(err error) bool {
|
||||
// Context cancelation is transient, retrying with a different context can work.
|
||||
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
||||
return true
|
||||
}
|
||||
|
||||
if isTransientConnectionError(err) {
|
||||
return true
|
||||
}
|
||||
@@ -98,10 +105,15 @@ func isTransientError(err error) bool {
|
||||
|
||||
// ServerResourcesForGroupVersion returns the supported resources for a group and version.
|
||||
func (d *memCacheClient) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
|
||||
return d.ServerResourcesForGroupVersionWithContext(context.Background(), groupVersion)
|
||||
}
|
||||
|
||||
// ServerResourcesForGroupVersion returns the supported resources for a group and version.
|
||||
func (d *memCacheClient) ServerResourcesForGroupVersionWithContext(ctx context.Context, groupVersion string) (*metav1.APIResourceList, error) {
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
if !d.cacheValid {
|
||||
if err := d.refreshLocked(); err != nil {
|
||||
if err := d.refreshLocked(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -111,14 +123,14 @@ func (d *memCacheClient) ServerResourcesForGroupVersion(groupVersion string) (*m
|
||||
}
|
||||
|
||||
if cachedVal.err != nil && isTransientError(cachedVal.err) {
|
||||
r, err := d.serverResourcesForGroupVersion(groupVersion)
|
||||
r, err := d.serverResourcesForGroupVersion(ctx, groupVersion)
|
||||
if err != nil {
|
||||
// Don't log "empty response" as an error; it is a common response for metrics.
|
||||
if _, emptyErr := err.(*emptyResponseError); emptyErr {
|
||||
// Log at same verbosity as disk cache.
|
||||
klog.V(3).Infof("%v", err)
|
||||
klog.FromContext(ctx).V(3).Info(err.Error())
|
||||
} else {
|
||||
utilruntime.HandleError(fmt.Errorf("couldn't get resource list for %v: %v", groupVersion, err))
|
||||
utilruntime.HandleErrorWithContext(ctx, err, "Couldn't get resource list", "gv", groupVersion)
|
||||
}
|
||||
}
|
||||
cachedVal = &cacheEntry{r, err}
|
||||
@@ -129,19 +141,43 @@ func (d *memCacheClient) ServerResourcesForGroupVersion(groupVersion string) (*m
|
||||
}
|
||||
|
||||
// ServerGroupsAndResources returns the groups and supported resources for all groups and versions.
|
||||
//
|
||||
// ServerGroupsAndResourcesWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerGroupsAndResourcesWithContext instead.
|
||||
func (d *memCacheClient) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
|
||||
return discovery.ServerGroupsAndResources(d)
|
||||
return d.ServerGroupsAndResourcesWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ServerGroupsAndResources returns the groups and supported resources for all groups and versions.
|
||||
func (d *memCacheClient) ServerGroupsAndResourcesWithContext(ctx context.Context) ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
|
||||
return discovery.ServerGroupsAndResourcesWithContext(ctx, d)
|
||||
}
|
||||
|
||||
// GroupsAndMaybeResources returns the list of APIGroups, and possibly the map of group/version
|
||||
// to resources. The returned groups will never be nil, but the resources map can be nil
|
||||
// if there are no cached resources.
|
||||
//
|
||||
// GroupsAndMaybeResourcesWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use GroupsAndMaybeResourcesWithContext instead.
|
||||
func (d *memCacheClient) GroupsAndMaybeResources() (*metav1.APIGroupList, map[schema.GroupVersion]*metav1.APIResourceList, map[schema.GroupVersion]error, error) {
|
||||
return d.GroupsAndMaybeResourcesWithContext(context.Background())
|
||||
}
|
||||
|
||||
// GroupsAndMaybeResourcesWithContext returns the list of APIGroups, and possibly the map of group/version
|
||||
// to resources. The returned groups will never be nil, but the resources map can be nil
|
||||
// if there are no cached resources.
|
||||
func (d *memCacheClient) GroupsAndMaybeResourcesWithContext(ctx context.Context) (*metav1.APIGroupList, map[schema.GroupVersion]*metav1.APIResourceList, map[schema.GroupVersion]error, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
|
||||
if !d.cacheValid {
|
||||
if err := d.refreshLocked(); err != nil {
|
||||
if err := d.refreshLocked(ctx); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
@@ -166,8 +202,15 @@ func (d *memCacheClient) GroupsAndMaybeResources() (*metav1.APIGroupList, map[sc
|
||||
return d.groupList, resourcesMap, failedGVs, nil
|
||||
}
|
||||
|
||||
// ServerGroupsWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerGroupsWithContext instead.
|
||||
func (d *memCacheClient) ServerGroups() (*metav1.APIGroupList, error) {
|
||||
groups, _, _, err := d.GroupsAndMaybeResources()
|
||||
return d.ServerGroupsWithContext(context.Background())
|
||||
}
|
||||
|
||||
func (d *memCacheClient) ServerGroupsWithContext(ctx context.Context) (*metav1.APIGroupList, error) {
|
||||
groups, _, _, err := d.GroupsAndMaybeResourcesWithContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -178,35 +221,81 @@ func (d *memCacheClient) RESTClient() restclient.Interface {
|
||||
return d.delegate.RESTClient()
|
||||
}
|
||||
|
||||
// ServerPreferredResourcesWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerPreferredResourcesWithContext instead.
|
||||
func (d *memCacheClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
|
||||
return discovery.ServerPreferredResources(d)
|
||||
return d.ServerPreferredResourcesWithContext(context.Background())
|
||||
}
|
||||
|
||||
func (d *memCacheClient) ServerPreferredResourcesWithContext(ctx context.Context) ([]*metav1.APIResourceList, error) {
|
||||
return discovery.ServerPreferredResourcesWithContext(ctx, d)
|
||||
}
|
||||
|
||||
// ServerPreferredNamespacedResourcesWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerPreferredNamespacedResourcesWithContext instead.
|
||||
func (d *memCacheClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
|
||||
return discovery.ServerPreferredNamespacedResources(d)
|
||||
return d.ServerPreferredNamespacedResourcesWithContext(context.Background())
|
||||
}
|
||||
|
||||
func (d *memCacheClient) ServerPreferredNamespacedResourcesWithContext(ctx context.Context) ([]*metav1.APIResourceList, error) {
|
||||
return discovery.ServerPreferredNamespacedResourcesWithContext(ctx, d)
|
||||
}
|
||||
|
||||
// ServerVersionWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerVersionWithContext instead.
|
||||
func (d *memCacheClient) ServerVersion() (*version.Info, error) {
|
||||
return d.delegate.ServerVersion()
|
||||
return d.ServerVersionWithContext(context.Background())
|
||||
}
|
||||
|
||||
func (d *memCacheClient) ServerVersionWithContext(ctx context.Context) (*version.Info, error) {
|
||||
return d.delegate.ServerVersionWithContext(ctx)
|
||||
}
|
||||
|
||||
// OpenAPISchemaWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use OpenAPISchemaWithContext instead.
|
||||
func (d *memCacheClient) OpenAPISchema() (*openapi_v2.Document, error) {
|
||||
return d.delegate.OpenAPISchema()
|
||||
return d.OpenAPISchemaWithContext(context.Background())
|
||||
}
|
||||
|
||||
func (d *memCacheClient) OpenAPISchemaWithContext(ctx context.Context) (*openapi_v2.Document, error) {
|
||||
return d.delegate.OpenAPISchemaWithContext(ctx)
|
||||
}
|
||||
|
||||
// OpenAPIV3WithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use OpenAPIV3WithContext instead.
|
||||
func (d *memCacheClient) OpenAPIV3() openapi.Client {
|
||||
return d.openAPIV3(context.Background())
|
||||
}
|
||||
|
||||
func (d *memCacheClient) OpenAPIV3WithContext(ctx context.Context) openapi.ClientWithContext {
|
||||
return d.openAPIV3(ctx)
|
||||
}
|
||||
|
||||
func (d *memCacheClient) openAPIV3(ctx context.Context) *cachedopenapi.Client {
|
||||
// Must take lock since Invalidate call may modify openapiClient
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
|
||||
if d.openapiClient == nil {
|
||||
d.openapiClient = cachedopenapi.NewClient(d.delegate.OpenAPIV3())
|
||||
d.openapiClient = cachedopenapi.NewClientWithContext(d.delegate.OpenAPIV3WithContext(ctx))
|
||||
}
|
||||
|
||||
return d.openapiClient
|
||||
}
|
||||
|
||||
// FreshWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use FreshWithContext instead.
|
||||
func (d *memCacheClient) Fresh() bool {
|
||||
return d.FreshWithContext(context.Background())
|
||||
}
|
||||
|
||||
func (d *memCacheClient) FreshWithContext(ctx context.Context) bool {
|
||||
d.lock.RLock()
|
||||
defer d.lock.RUnlock()
|
||||
// Return whether the cache is populated at all. It is still possible that
|
||||
@@ -217,7 +306,17 @@ func (d *memCacheClient) Fresh() bool {
|
||||
|
||||
// Invalidate enforces that no cached data that is older than the current time
|
||||
// is used.
|
||||
//
|
||||
// InvalidateWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use InvalidateWithContext instead.
|
||||
func (d *memCacheClient) Invalidate() {
|
||||
d.InvalidateWithContext(context.Background())
|
||||
}
|
||||
|
||||
// InvalidateWithContext enforces that no cached data that is older than the current time
|
||||
// is used.
|
||||
func (d *memCacheClient) InvalidateWithContext(ctx context.Context) {
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
d.cacheValid = false
|
||||
@@ -225,24 +324,41 @@ func (d *memCacheClient) Invalidate() {
|
||||
d.groupList = nil
|
||||
d.openapiClient = nil
|
||||
d.receivedAggregatedDiscovery = false
|
||||
if ad, ok := d.delegate.(discovery.CachedDiscoveryInterface); ok {
|
||||
ad.Invalidate()
|
||||
ad, ok := d.delegate.(discovery.CachedDiscoveryInterfaceWithContext)
|
||||
if !ok {
|
||||
//nolint:staticcheck // Intentionally using the old interface here.
|
||||
ad2, ok2 := d.delegate.(discovery.CachedDiscoveryInterface)
|
||||
if ok2 {
|
||||
ad = discovery.ToCachedDiscoveryInterfaceWithContext(ad2)
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
ad.InvalidateWithContext(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// refreshLocked refreshes the state of cache. The caller must hold d.lock for
|
||||
// writing.
|
||||
func (d *memCacheClient) refreshLocked() error {
|
||||
func (d *memCacheClient) refreshLocked(ctx context.Context) error {
|
||||
// TODO: Could this multiplicative set of calls be replaced by a single call
|
||||
// to ServerResources? If it's possible for more than one resulting
|
||||
// APIResourceList to have the same GroupVersion, the lists would need merged.
|
||||
var gl *metav1.APIGroupList
|
||||
var err error
|
||||
|
||||
if ad, ok := d.delegate.(discovery.AggregatedDiscoveryInterface); ok {
|
||||
ad, ok := d.delegate.(discovery.AggregatedDiscoveryInterfaceWithContext)
|
||||
if !ok {
|
||||
//nolint:staticcheck // Intentionally using the old interface here.
|
||||
if ad2, ok2 := d.delegate.(discovery.AggregatedDiscoveryInterface); ok2 {
|
||||
ad = discovery.ToAggregatedDiscoveryInterfaceWithContext(ad2)
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
var resources map[schema.GroupVersion]*metav1.APIResourceList
|
||||
var failedGVs map[schema.GroupVersion]error
|
||||
gl, resources, failedGVs, err = ad.GroupsAndMaybeResources()
|
||||
gl, resources, failedGVs, err = ad.GroupsAndMaybeResourcesWithContext(ctx)
|
||||
if resources != nil && err == nil {
|
||||
// Cache the resources.
|
||||
d.groupToServerResources = map[string]*cacheEntry{}
|
||||
@@ -259,10 +375,10 @@ func (d *memCacheClient) refreshLocked() error {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
gl, err = d.delegate.ServerGroups()
|
||||
gl, err = d.delegate.ServerGroupsWithContext(ctx)
|
||||
}
|
||||
if err != nil || len(gl.Groups) == 0 {
|
||||
utilruntime.HandleError(fmt.Errorf("couldn't get current server API group list: %v", err))
|
||||
utilruntime.HandleErrorWithContext(ctx, err, "Couldn't get current server API group list")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -275,16 +391,16 @@ func (d *memCacheClient) refreshLocked() error {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer utilruntime.HandleCrash()
|
||||
defer utilruntime.HandleCrashWithContext(ctx)
|
||||
|
||||
r, err := d.serverResourcesForGroupVersion(gv)
|
||||
r, err := d.serverResourcesForGroupVersion(ctx, gv)
|
||||
if err != nil {
|
||||
// Don't log "empty response" as an error; it is a common response for metrics.
|
||||
if _, emptyErr := err.(*emptyResponseError); emptyErr {
|
||||
// Log at same verbosity as disk cache.
|
||||
klog.V(3).Infof("%v", err)
|
||||
klog.FromContext(ctx).V(3).Info(err.Error())
|
||||
} else {
|
||||
utilruntime.HandleError(fmt.Errorf("couldn't get resource list for %v: %v", gv, err))
|
||||
utilruntime.HandleErrorWithContext(ctx, err, "Couldn't get resource list", "groupVersion", gv)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,8 +417,8 @@ func (d *memCacheClient) refreshLocked() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *memCacheClient) serverResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
|
||||
r, err := d.delegate.ServerResourcesForGroupVersion(groupVersion)
|
||||
func (d *memCacheClient) serverResourcesForGroupVersion(ctx context.Context, groupVersion string) (*metav1.APIResourceList, error) {
|
||||
r, err := d.delegate.ServerResourcesForGroupVersionWithContext(ctx, groupVersion)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
@@ -314,16 +430,43 @@ func (d *memCacheClient) serverResourcesForGroupVersion(groupVersion string) (*m
|
||||
|
||||
// WithLegacy returns current memory-cached discovery client;
|
||||
// current client does not support legacy-only discovery.
|
||||
//
|
||||
// WithLegacyWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use WithLegacyWithContext instead.
|
||||
func (d *memCacheClient) WithLegacy() discovery.DiscoveryInterface {
|
||||
return d
|
||||
}
|
||||
|
||||
// WithLegacyWithContext returns current memory-cached discovery client;
|
||||
// current client does not support legacy-only discovery.
|
||||
func (d *memCacheClient) WithLegacyWithContext(ctx context.Context) discovery.DiscoveryInterfaceWithContext {
|
||||
return d
|
||||
}
|
||||
|
||||
// NewMemCacheClient creates a new CachedDiscoveryInterface which caches
|
||||
// discovery information in memory and will stay up-to-date if Invalidate is
|
||||
// called with regularity.
|
||||
//
|
||||
// NOTE: The client will NOT resort to live lookups on cache misses.
|
||||
//
|
||||
// NewMemCacheClientWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use NewMemCacheClientWithContext instead.
|
||||
func NewMemCacheClient(delegate discovery.DiscoveryInterface) discovery.CachedDiscoveryInterface {
|
||||
return newMemCacheClient(discovery.ToDiscoveryInterfaceWithContext(delegate))
|
||||
}
|
||||
|
||||
// NewMemCacheClientWithContext creates a new CachedDiscoveryInterfaceWithContext which caches
|
||||
// discovery information in memory and will stay up-to-date if Invalidate is
|
||||
// called with regularity.
|
||||
//
|
||||
// NOTE: The client will NOT resort to live lookups on cache misses.
|
||||
func NewMemCacheClientWithContext(delegate discovery.DiscoveryInterfaceWithContext) discovery.CachedDiscoveryInterfaceWithContext {
|
||||
return newMemCacheClient(delegate)
|
||||
}
|
||||
|
||||
func newMemCacheClient(delegate discovery.DiscoveryInterfaceWithContext) *memCacheClient {
|
||||
return &memCacheClient{
|
||||
delegate: delegate,
|
||||
groupToServerResources: map[string]*cacheEntry{},
|
||||
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package memory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -35,10 +36,10 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/discovery/fake"
|
||||
"k8s.io/client-go/openapi"
|
||||
"k8s.io/client-go/rest"
|
||||
testutil "k8s.io/client-go/util/testing"
|
||||
"k8s.io/klog/v2/ktesting"
|
||||
)
|
||||
|
||||
type resourceMapEntry struct {
|
||||
@@ -47,7 +48,10 @@ type resourceMapEntry struct {
|
||||
}
|
||||
|
||||
type fakeDiscovery struct {
|
||||
*fake.FakeDiscovery
|
||||
// Intentionally limited to discovery.DiscoveryInterface because that is all that this
|
||||
// code knows about and partly overrides. *fake.FakeDiscovery would inherit
|
||||
// e.g. ServerGroupsWithContext which then would have to be overridden.
|
||||
discovery.DiscoveryInterface
|
||||
|
||||
lock sync.Mutex
|
||||
groupList *metav1.APIGroupList
|
||||
@@ -73,34 +77,77 @@ func (c *fakeDiscovery) ServerGroups() (*metav1.APIGroupList, error) {
|
||||
return c.groupList, c.groupListErr
|
||||
}
|
||||
|
||||
// fakeDiscoveryWithContext is a variant of fakeDiscovery which supports only the *WithContext methods.
|
||||
type fakeDiscoveryWithContext struct {
|
||||
*fakeDiscovery
|
||||
discovery.DiscoveryInterfaceWithContext
|
||||
|
||||
// Invoked at the end of each ServerGroupsWithContext.
|
||||
serverGroupsWithContextPostCallback func()
|
||||
}
|
||||
|
||||
func (c *fakeDiscoveryWithContext) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
|
||||
panic("fakeDiscoveryWithContext.ServerResourcesForGroupVersion should not have been called")
|
||||
}
|
||||
|
||||
func (c *fakeDiscoveryWithContext) ServerGroups() (*metav1.APIGroupList, error) {
|
||||
panic("fakeDiscoveryWithContext.ServerGroups should not have been called")
|
||||
}
|
||||
|
||||
func (c *fakeDiscoveryWithContext) ServerResourcesForGroupVersionWithContext(ctx context.Context, groupVersion string) (*metav1.APIResourceList, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.fakeDiscovery.ServerResourcesForGroupVersion(groupVersion)
|
||||
}
|
||||
|
||||
func (c *fakeDiscoveryWithContext) ServerGroupsWithContext(ctx context.Context) (*metav1.APIGroupList, error) {
|
||||
if c.serverGroupsWithContextPostCallback != nil {
|
||||
defer c.serverGroupsWithContextPostCallback()
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.fakeDiscovery.ServerGroups()
|
||||
}
|
||||
|
||||
func TestClient(t *testing.T) {
|
||||
fake := &fakeDiscovery{
|
||||
groupList: &metav1.APIGroupList{
|
||||
Groups: []metav1.APIGroup{{
|
||||
Name: "astronomy",
|
||||
Versions: []metav1.GroupVersionForDiscovery{{
|
||||
GroupVersion: "astronomy/v8beta1",
|
||||
Version: "v8beta1",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
resourceMap: map[string]*resourceMapEntry{
|
||||
"astronomy/v8beta1": {
|
||||
list: &metav1.APIResourceList{
|
||||
GroupVersion: "astronomy/v8beta1",
|
||||
APIResources: []metav1.APIResource{{
|
||||
Name: "dwarfplanets",
|
||||
SingularName: "dwarfplanet",
|
||||
Namespaced: true,
|
||||
Kind: "DwarfPlanet",
|
||||
ShortNames: []string{"dp"},
|
||||
newFake := func() *fakeDiscovery {
|
||||
return &fakeDiscovery{
|
||||
groupList: &metav1.APIGroupList{
|
||||
Groups: []metav1.APIGroup{{
|
||||
Name: "astronomy",
|
||||
Versions: []metav1.GroupVersionForDiscovery{{
|
||||
GroupVersion: "astronomy/v8beta1",
|
||||
Version: "v8beta1",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
resourceMap: map[string]*resourceMapEntry{
|
||||
"astronomy/v8beta1": {
|
||||
list: &metav1.APIResourceList{
|
||||
GroupVersion: "astronomy/v8beta1",
|
||||
APIResources: []metav1.APIResource{{
|
||||
Name: "dwarfplanets",
|
||||
SingularName: "dwarfplanet",
|
||||
Namespaced: true,
|
||||
Kind: "DwarfPlanet",
|
||||
ShortNames: []string{"dp"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
fake := newFake()
|
||||
fakeWithContext := &fakeDiscoveryWithContext{fakeDiscovery: newFake()}
|
||||
|
||||
c := NewMemCacheClient(fake)
|
||||
t.Run("DiscoveryInterface", func(t *testing.T) { testClient(t, fake, fake) })
|
||||
t.Run("DiscoveryInterfaceWithContext", func(t *testing.T) { testClient(t, fakeWithContext.fakeDiscovery, fakeWithContext) })
|
||||
}
|
||||
|
||||
func testClient(t *testing.T, fake *fakeDiscovery, delegate discovery.DiscoveryInterface) {
|
||||
c := NewMemCacheClient(delegate)
|
||||
if c.Fresh() {
|
||||
t.Errorf("Expected not fresh.")
|
||||
}
|
||||
@@ -164,6 +211,62 @@ func TestClient(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetryAfterCancellation(t *testing.T) {
|
||||
fake := &fakeDiscoveryWithContext{
|
||||
fakeDiscovery: &fakeDiscovery{
|
||||
groupList: &metav1.APIGroupList{
|
||||
Groups: []metav1.APIGroup{{
|
||||
Name: "astronomy",
|
||||
Versions: []metav1.GroupVersionForDiscovery{{
|
||||
GroupVersion: "astronomy/v8beta1",
|
||||
Version: "v8beta1",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
resourceMap: map[string]*resourceMapEntry{
|
||||
"astronomy/v8beta1": {
|
||||
list: &metav1.APIResourceList{
|
||||
GroupVersion: "astronomy/v8beta1",
|
||||
APIResources: []metav1.APIResource{{
|
||||
Name: "dwarfplanets",
|
||||
SingularName: "dwarfplanet",
|
||||
Namespaced: true,
|
||||
Kind: "DwarfPlanet",
|
||||
ShortNames: []string{"dp"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
cancelCtx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
c := NewMemCacheClientWithContext(fake)
|
||||
|
||||
// The context gets canceled in the middle of refreshLocked.
|
||||
// This is important for code path which handles per-gv errors.
|
||||
fake.serverGroupsWithContextPostCallback = cancel
|
||||
_, err := c.ServerResourcesForGroupVersionWithContext(cancelCtx, "astronomy/v8beta1")
|
||||
if err == nil {
|
||||
t.Errorf("Expected error")
|
||||
}
|
||||
|
||||
// This retries the lookup with a fresh context.
|
||||
r, err := c.ServerResourcesForGroupVersionWithContext(ctx, "astronomy/v8beta1")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %#v, got %#v", e, a)
|
||||
}
|
||||
if !c.FreshWithContext(ctx) {
|
||||
t.Errorf("Expected not fresh.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerGroupsFails(t *testing.T) {
|
||||
fake := &fakeDiscovery{
|
||||
groupList: &metav1.APIGroupList{
|
||||
@@ -435,6 +538,8 @@ func TestOpenAPIMemCache(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
|
||||
// This is the original OpenAPI client. It is not affected
|
||||
// by client.Invalidate() below.
|
||||
pathsAgain, err := openapiClient.Paths()
|
||||
if !assert.NoError(t, err) {
|
||||
continue
|
||||
@@ -445,7 +550,8 @@ func TestOpenAPIMemCache(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
|
||||
assert.Equal(t, reflect.ValueOf(paths).Pointer(), reflect.ValueOf(pathsAgain).Pointer())
|
||||
// The map itself might be different, but the content should not be.
|
||||
assert.Equal(t, reflect.ValueOf(paths[k]).Pointer(), reflect.ValueOf(pathsAgain[k]).Pointer())
|
||||
assert.Equal(t, reflect.ValueOf(original).Pointer(), reflect.ValueOf(schemaAgain).Pointer())
|
||||
|
||||
// Invalidate and try again. This time pointers should not be equal
|
||||
@@ -461,6 +567,76 @@ func TestOpenAPIMemCache(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
|
||||
assert.NotEqual(t, reflect.ValueOf(paths[k]).Pointer(), reflect.ValueOf(pathsAgain[k]).Pointer())
|
||||
assert.NotEqual(t, reflect.ValueOf(original).Pointer(), reflect.ValueOf(schemaAgain).Pointer())
|
||||
assert.Equal(t, original, schemaAgain)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that schema instances returned by openapi are cached and returned after
|
||||
// successive calls.
|
||||
func TestOpenAPIMemCacheWithContext(t *testing.T) {
|
||||
_, ctx := ktesting.NewTestContext(t)
|
||||
fakeServer, err := testutil.NewFakeOpenAPIV3Server("../../testdata")
|
||||
require.NoError(t, err)
|
||||
defer fakeServer.HttpServer.Close()
|
||||
|
||||
require.NotEmpty(t, fakeServer.ServedDocuments)
|
||||
|
||||
client := NewMemCacheClientWithContext(
|
||||
discovery.NewDiscoveryClientForConfigOrDie(
|
||||
&rest.Config{Host: fakeServer.HttpServer.URL},
|
||||
),
|
||||
)
|
||||
openapiClient := client.OpenAPIV3WithContext(ctx)
|
||||
|
||||
paths, err := openapiClient.PathsWithContext(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
contentTypes := []string{
|
||||
runtime.ContentTypeJSON, openapi.ContentTypeOpenAPIV3PB,
|
||||
}
|
||||
|
||||
for _, contentType := range contentTypes {
|
||||
t.Run(contentType, func(t *testing.T) {
|
||||
//nolint:testifylint // "for error assertions use require" is a false positive here, we abort the iteration on failures.
|
||||
for k, v := range paths {
|
||||
original, err := v.SchemaWithContext(ctx, contentType)
|
||||
if !assert.NoError(t, err) {
|
||||
continue
|
||||
}
|
||||
|
||||
// This is the original OpenAPI client. It is not affected
|
||||
// by client.Invalidate() below.
|
||||
pathsAgain, err := openapiClient.PathsWithContext(ctx)
|
||||
if !assert.NoError(t, err) {
|
||||
continue
|
||||
}
|
||||
|
||||
schemaAgain, err := pathsAgain[k].SchemaWithContext(ctx, contentType)
|
||||
if !assert.NoError(t, err) {
|
||||
continue
|
||||
}
|
||||
|
||||
// When using the *WithContext API, the map itself is cached.
|
||||
assert.Equal(t, reflect.ValueOf(paths).Pointer(), reflect.ValueOf(pathsAgain).Pointer())
|
||||
assert.Equal(t, reflect.ValueOf(original).Pointer(), reflect.ValueOf(schemaAgain).Pointer())
|
||||
|
||||
// Invalidate and try again. This time pointers should not be equal
|
||||
client.InvalidateWithContext(ctx)
|
||||
|
||||
pathsAgain, err = client.OpenAPIV3WithContext(ctx).PathsWithContext(ctx)
|
||||
if !assert.NoError(t, err) {
|
||||
continue
|
||||
}
|
||||
|
||||
schemaAgain, err = pathsAgain[k].SchemaWithContext(ctx, contentType)
|
||||
if !assert.NoError(t, err) {
|
||||
continue
|
||||
}
|
||||
|
||||
assert.NotEqual(t, reflect.ValueOf(paths).Pointer(), reflect.ValueOf(pathsAgain).Pointer())
|
||||
assert.NotEqual(t, reflect.ValueOf(original).Pointer(), reflect.ValueOf(schemaAgain).Pointer())
|
||||
assert.Equal(t, original, schemaAgain)
|
||||
|
||||
@@ -73,6 +73,10 @@ var v2GVK = schema.GroupVersionKind{Group: "apidiscovery.k8s.io", Version: "v2",
|
||||
|
||||
// DiscoveryInterface holds the methods that discover server-supported API groups,
|
||||
// versions and resources.
|
||||
//
|
||||
// DiscoveryInterfaceWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use DiscoveryInterfaceWithContext instead.
|
||||
type DiscoveryInterface interface {
|
||||
RESTClient() restclient.Interface
|
||||
ServerGroupsInterface
|
||||
@@ -83,22 +87,123 @@ type DiscoveryInterface interface {
|
||||
// Returns copy of current discovery client that will only
|
||||
// receive the legacy discovery format, or pointer to current
|
||||
// discovery client if it does not support legacy-only discovery.
|
||||
//
|
||||
// DiscoveryInterfaceWithContext.WithLegacyWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use DiscoveryInterfaceWithContext.WithLegacyWithContext instead.
|
||||
WithLegacy() DiscoveryInterface
|
||||
}
|
||||
|
||||
// DiscoveryInterfaceWithContext holds the methods that discover server-supported API groups,
|
||||
// versions and resources.
|
||||
type DiscoveryInterfaceWithContext interface {
|
||||
RESTClient() restclient.Interface
|
||||
ServerGroupsInterfaceWithContext
|
||||
ServerResourcesInterfaceWithContext
|
||||
ServerVersionInterfaceWithContext
|
||||
OpenAPISchemaInterfaceWithContext
|
||||
OpenAPIV3SchemaInterfaceWithContext
|
||||
// Returns copy of current discovery client that will only
|
||||
// receive the legacy discovery format, or pointer to current
|
||||
// discovery client if it does not support legacy-only discovery.
|
||||
WithLegacyWithContext(ctx context.Context) DiscoveryInterfaceWithContext
|
||||
}
|
||||
|
||||
// DiscoveryInterfaces combines both interface variants.
|
||||
type DiscoveryInterfaces interface {
|
||||
DiscoveryInterface
|
||||
DiscoveryInterfaceWithContext
|
||||
}
|
||||
|
||||
func ToDiscoveryInterfaceWithContext(i DiscoveryInterface) DiscoveryInterfaceWithContext {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
if i, ok := i.(DiscoveryInterfaceWithContext); ok {
|
||||
return i
|
||||
}
|
||||
return &discoveryInterfaceWrapper{
|
||||
ServerGroupsInterfaceWithContext: ToServerGroupsInterfaceWithContext(i),
|
||||
ServerResourcesInterfaceWithContext: ToServerResourcesInterfaceWithContext(i),
|
||||
ServerVersionInterfaceWithContext: ToServerVersionInterfaceWithContext(i),
|
||||
OpenAPISchemaInterfaceWithContext: ToOpenAPISchemaInterfaceWithContext(i),
|
||||
OpenAPIV3SchemaInterfaceWithContext: OpenAPIV3ToSchemaInterfaceWithContext(i),
|
||||
delegate: i,
|
||||
}
|
||||
}
|
||||
|
||||
type discoveryInterfaceWrapper struct {
|
||||
ServerGroupsInterfaceWithContext
|
||||
ServerResourcesInterfaceWithContext
|
||||
ServerVersionInterfaceWithContext
|
||||
OpenAPISchemaInterfaceWithContext
|
||||
OpenAPIV3SchemaInterfaceWithContext
|
||||
delegate DiscoveryInterface
|
||||
}
|
||||
|
||||
func (i *discoveryInterfaceWrapper) RESTClient() restclient.Interface {
|
||||
return i.delegate.RESTClient()
|
||||
}
|
||||
|
||||
func (i *discoveryInterfaceWrapper) WithLegacyWithContext(ctx context.Context) DiscoveryInterfaceWithContext {
|
||||
return ToDiscoveryInterfaceWithContext(i.delegate.WithLegacy())
|
||||
}
|
||||
|
||||
// AggregatedDiscoveryInterface extends DiscoveryInterface to include a method to possibly
|
||||
// return discovery resources along with the discovery groups, which is what the newer
|
||||
// aggregated discovery format does (APIGroupDiscoveryList).
|
||||
//
|
||||
// AggregatedDiscoveryInterfaceWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use AggregatedDiscoveryInterfaceWithContext instead.
|
||||
type AggregatedDiscoveryInterface interface {
|
||||
DiscoveryInterface
|
||||
|
||||
// AggregatedDiscoveryInterfaceWithContext.GroupsAndMaybeResourcesWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use AggregatedDiscoveryInterfaceWithContext.GroupsAndMaybeResourcesWithContext instead.
|
||||
GroupsAndMaybeResources() (*metav1.APIGroupList, map[schema.GroupVersion]*metav1.APIResourceList, map[schema.GroupVersion]error, error)
|
||||
}
|
||||
|
||||
// AggregatedDiscoveryInterfaceWithContext extends DiscoveryInterface to include a method to possibly
|
||||
// return discovery resources along with the discovery groups, which is what the newer
|
||||
// aggregated discovery format does (APIGroupDiscoveryList).
|
||||
type AggregatedDiscoveryInterfaceWithContext interface {
|
||||
DiscoveryInterfaceWithContext
|
||||
|
||||
GroupsAndMaybeResourcesWithContext(ctx context.Context) (*metav1.APIGroupList, map[schema.GroupVersion]*metav1.APIResourceList, map[schema.GroupVersion]error, error)
|
||||
}
|
||||
|
||||
func ToAggregatedDiscoveryInterfaceWithContext(i AggregatedDiscoveryInterface) AggregatedDiscoveryInterfaceWithContext {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
if i, ok := i.(AggregatedDiscoveryInterfaceWithContext); ok {
|
||||
return i
|
||||
}
|
||||
return &aggregatedDiscoveryInterfaceWrapper{
|
||||
DiscoveryInterfaceWithContext: ToDiscoveryInterfaceWithContext(i),
|
||||
delegate: i,
|
||||
}
|
||||
}
|
||||
|
||||
type aggregatedDiscoveryInterfaceWrapper struct {
|
||||
DiscoveryInterfaceWithContext
|
||||
delegate AggregatedDiscoveryInterface
|
||||
}
|
||||
|
||||
func (i *aggregatedDiscoveryInterfaceWrapper) GroupsAndMaybeResourcesWithContext(ctx context.Context) (*metav1.APIGroupList, map[schema.GroupVersion]*metav1.APIResourceList, map[schema.GroupVersion]error, error) {
|
||||
return i.delegate.GroupsAndMaybeResources()
|
||||
}
|
||||
|
||||
// CachedDiscoveryInterface is a DiscoveryInterface with cache invalidation and freshness.
|
||||
// Note that If the ServerResourcesForGroupVersion method returns a cache miss
|
||||
// error, the user needs to explicitly call Invalidate to clear the cache,
|
||||
// otherwise the same cache miss error will be returned next time.
|
||||
//
|
||||
// CachedDiscoveryInterfaceWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use CachedDiscoveryInterfaceWithContext instead.
|
||||
type CachedDiscoveryInterface interface {
|
||||
DiscoveryInterface
|
||||
// Fresh is supposed to tell the caller whether or not to retry if the cache
|
||||
@@ -106,58 +211,343 @@ type CachedDiscoveryInterface interface {
|
||||
//
|
||||
// TODO: this needs to be revisited, this interface can't be locked properly
|
||||
// and doesn't make a lot of sense.
|
||||
//
|
||||
// CachedDiscoveryInterfaceWithContext.FreshWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use CachedDiscoveryInterfaceWithContext.FreshWithContext instead.
|
||||
Fresh() bool
|
||||
// Invalidate enforces that no cached data that is older than the current time
|
||||
// is used.
|
||||
//
|
||||
// CachedDiscoveryInterfaceWithContext.InvalidateWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use CachedDiscoveryInterfaceWithContext.InvalidateWithContext instead.
|
||||
Invalidate()
|
||||
}
|
||||
|
||||
// CachedDiscoveryInterfaceWithContext is a DiscoveryInterfaceWithContext with cache invalidation and freshness.
|
||||
// Note that If the ServerResourcesForGroupVersion method returns a cache miss
|
||||
// error, the user needs to explicitly call Invalidate to clear the cache,
|
||||
// otherwise the same cache miss error will be returned next time.
|
||||
type CachedDiscoveryInterfaceWithContext interface {
|
||||
DiscoveryInterfaceWithContext
|
||||
// Fresh is supposed to tell the caller whether or not to retry if the cache
|
||||
// fails to find something (false = retry, true = no need to retry).
|
||||
//
|
||||
// TODO: this needs to be revisited, this interface can't be locked properly
|
||||
// and doesn't make a lot of sense.
|
||||
FreshWithContext(ctx context.Context) bool
|
||||
// Invalidate enforces that no cached data that is older than the current time
|
||||
// is used.
|
||||
InvalidateWithContext(ctx context.Context)
|
||||
}
|
||||
|
||||
func ToCachedDiscoveryInterfaceWithContext(i CachedDiscoveryInterface) CachedDiscoveryInterfaceWithContext {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
if i, ok := i.(CachedDiscoveryInterfaceWithContext); ok {
|
||||
return i
|
||||
}
|
||||
return &cachedDiscoveryInterfaceWrapper{
|
||||
DiscoveryInterfaceWithContext: ToDiscoveryInterfaceWithContext(i),
|
||||
delegate: i,
|
||||
}
|
||||
}
|
||||
|
||||
type cachedDiscoveryInterfaceWrapper struct {
|
||||
DiscoveryInterfaceWithContext
|
||||
delegate CachedDiscoveryInterface
|
||||
}
|
||||
|
||||
func (i *cachedDiscoveryInterfaceWrapper) FreshWithContext(ctx context.Context) bool {
|
||||
return i.delegate.Fresh()
|
||||
}
|
||||
|
||||
func (i *cachedDiscoveryInterfaceWrapper) InvalidateWithContext(ctx context.Context) {
|
||||
i.delegate.Invalidate()
|
||||
}
|
||||
|
||||
// ServerGroupsInterface has methods for obtaining supported groups on the API server
|
||||
//
|
||||
// ServerGroupsInterfaceWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerGroupsInterfaceWithContext instead.
|
||||
type ServerGroupsInterface interface {
|
||||
// ServerGroups returns the supported groups, with information like supported versions and the
|
||||
// preferred version.
|
||||
//
|
||||
// ServerGroupsInterfaceWithContext.ServerGroups is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerGroupsInterfaceWithContext.ServerGroups instead.
|
||||
ServerGroups() (*metav1.APIGroupList, error)
|
||||
}
|
||||
|
||||
// ServerGroupsInterfaceWithContext has methods for obtaining supported groups on the API server
|
||||
type ServerGroupsInterfaceWithContext interface {
|
||||
// ServerGroups returns the supported groups, with information like supported versions and the
|
||||
// preferred version.
|
||||
ServerGroupsWithContext(ctx context.Context) (*metav1.APIGroupList, error)
|
||||
}
|
||||
|
||||
func ToServerGroupsInterfaceWithContext(i ServerGroupsInterface) ServerGroupsInterfaceWithContext {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
if i, ok := i.(ServerGroupsInterfaceWithContext); ok {
|
||||
return i
|
||||
}
|
||||
return &serverGroupsInterfaceWrapper{
|
||||
delegate: i,
|
||||
}
|
||||
}
|
||||
|
||||
type serverGroupsInterfaceWrapper struct {
|
||||
delegate ServerGroupsInterface
|
||||
}
|
||||
|
||||
func (i *serverGroupsInterfaceWrapper) ServerGroupsWithContext(ctx context.Context) (*metav1.APIGroupList, error) {
|
||||
return i.delegate.ServerGroups()
|
||||
}
|
||||
|
||||
// ServerResourcesInterface has methods for obtaining supported resources on the API server
|
||||
//
|
||||
// ServerGroupsAndResourcesWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerGroupsAndResourcesWithContext instead.
|
||||
type ServerResourcesInterface interface {
|
||||
// ServerResourcesForGroupVersion returns the supported resources for a group and version.
|
||||
//
|
||||
// ServerGroupsAndResourcesWithContext.ServerResourcesForGroupVersionWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerGroupsAndResourcesWithContext.ServerResourcesForGroupVersionWithContext instead.
|
||||
ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error)
|
||||
// ServerGroupsAndResources returns the supported groups and resources for all groups and versions.
|
||||
//
|
||||
// The returned group and resource lists might be non-nil with partial results even in the
|
||||
// case of non-nil error.
|
||||
//
|
||||
// ServerGroupsAndResourcesWithContext.ServerGroupsAndResourcesWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerGroupsAndResourcesWithContext.ServerGroupsAndResourcesWithContext instead.
|
||||
ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error)
|
||||
// ServerPreferredResources returns the supported resources with the version preferred by the
|
||||
// server.
|
||||
//
|
||||
// The returned group and resource lists might be non-nil with partial results even in the
|
||||
// case of non-nil error.
|
||||
//
|
||||
// ServerResourcesInterfaceWithContext.ServerPreferredResourcesWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerResourcesInterfaceWithContext.ServerPreferredResourcesWithContext instead.
|
||||
ServerPreferredResources() ([]*metav1.APIResourceList, error)
|
||||
// ServerPreferredNamespacedResources returns the supported namespaced resources with the
|
||||
// version preferred by the server.
|
||||
//
|
||||
// The returned resource list might be non-nil with partial results even in the case of
|
||||
// non-nil error.
|
||||
//
|
||||
// ServerResourcesInterfaceWithContext.ServerPreferredNamespacedResourcesWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerResourcesInterfaceWithContext.ServerPreferredNamespacedResourcesWithContext instead.
|
||||
ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error)
|
||||
}
|
||||
|
||||
// ServerResourcesInterfaceWithContext has methods for obtaining supported resources on the API server
|
||||
type ServerResourcesInterfaceWithContext interface {
|
||||
// ServerResourcesForGroupVersionWithContext returns the supported resources for a group and version.
|
||||
ServerResourcesForGroupVersionWithContext(ctx context.Context, groupVersion string) (*metav1.APIResourceList, error)
|
||||
// ServerGroupsAndResourcesWithContext returns the supported groups and resources for all groups and versions.
|
||||
//
|
||||
// The returned group and resource lists might be non-nil with partial results even in the
|
||||
// case of non-nil error.
|
||||
ServerGroupsAndResourcesWithContext(ctx context.Context) ([]*metav1.APIGroup, []*metav1.APIResourceList, error)
|
||||
// ServerPreferredResourcesWithContext returns the supported resources with the version preferred by the
|
||||
// server.
|
||||
//
|
||||
// The returned group and resource lists might be non-nil with partial results even in the
|
||||
// case of non-nil error.
|
||||
ServerPreferredResourcesWithContext(ctx context.Context) ([]*metav1.APIResourceList, error)
|
||||
// ServerPreferredNamespacedResourcesWithContext returns the supported namespaced resources with the
|
||||
// version preferred by the server.
|
||||
//
|
||||
// The returned resource list might be non-nil with partial results even in the case of
|
||||
// non-nil error.
|
||||
ServerPreferredNamespacedResourcesWithContext(ctx context.Context) ([]*metav1.APIResourceList, error)
|
||||
}
|
||||
|
||||
func ToServerResourcesInterfaceWithContext(i ServerResourcesInterface) ServerResourcesInterfaceWithContext {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
if i, ok := i.(ServerResourcesInterfaceWithContext); ok {
|
||||
return i
|
||||
}
|
||||
return &serverResourcesInterfaceWrapper{
|
||||
delegate: i,
|
||||
}
|
||||
}
|
||||
|
||||
type serverResourcesInterfaceWrapper struct {
|
||||
delegate ServerResourcesInterface
|
||||
}
|
||||
|
||||
func (i *serverResourcesInterfaceWrapper) ServerResourcesForGroupVersionWithContext(ctx context.Context, groupVersion string) (*metav1.APIResourceList, error) {
|
||||
return i.delegate.ServerResourcesForGroupVersion(groupVersion)
|
||||
}
|
||||
|
||||
func (i *serverResourcesInterfaceWrapper) ServerGroupsAndResourcesWithContext(ctx context.Context) ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
|
||||
return i.delegate.ServerGroupsAndResources()
|
||||
}
|
||||
|
||||
func (i *serverResourcesInterfaceWrapper) ServerPreferredResourcesWithContext(ctx context.Context) ([]*metav1.APIResourceList, error) {
|
||||
return i.delegate.ServerPreferredResources()
|
||||
}
|
||||
|
||||
func (i *serverResourcesInterfaceWrapper) ServerPreferredNamespacedResourcesWithContext(ctx context.Context) ([]*metav1.APIResourceList, error) {
|
||||
return i.delegate.ServerPreferredNamespacedResources()
|
||||
}
|
||||
|
||||
// ServerVersionInterface has a method for retrieving the server's version.
|
||||
//
|
||||
// ServerVersionInterfaceWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerVersionInterfaceWithContext instead.
|
||||
type ServerVersionInterface interface {
|
||||
// ServerVersion retrieves and parses the server's version (git version).
|
||||
//
|
||||
// ServerVersionInterfaceWithContext.ServerVersionWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerVersionInterfaceWithContext.ServerVersionWithContext instead.
|
||||
ServerVersion() (*version.Info, error)
|
||||
}
|
||||
|
||||
// ServerVersionInterface has a method for retrieving the server's version.
|
||||
type ServerVersionInterfaceWithContext interface {
|
||||
// ServerVersion retrieves and parses the server's version (git version).
|
||||
ServerVersionWithContext(ctx context.Context) (*version.Info, error)
|
||||
}
|
||||
|
||||
func ToServerVersionInterfaceWithContext(i ServerVersionInterface) ServerVersionInterfaceWithContext {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
if i, ok := i.(ServerVersionInterfaceWithContext); ok {
|
||||
return i
|
||||
}
|
||||
return &serverVersionInterfaceWrapper{
|
||||
delegate: i,
|
||||
}
|
||||
}
|
||||
|
||||
type serverVersionInterfaceWrapper struct {
|
||||
delegate ServerVersionInterface
|
||||
}
|
||||
|
||||
func (i *serverVersionInterfaceWrapper) ServerVersionWithContext(ctx context.Context) (*version.Info, error) {
|
||||
return i.delegate.ServerVersion()
|
||||
}
|
||||
|
||||
// OpenAPISchemaInterface has a method to retrieve the open API schema.
|
||||
//
|
||||
// OpenAPISchemaInterfaceWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use OpenAPISchemaInterfaceWithContext instead.
|
||||
type OpenAPISchemaInterface interface {
|
||||
// OpenAPISchema retrieves and parses the swagger API schema the server supports.
|
||||
//
|
||||
// OpenAPISchemaInterfaceWithContext.OpenAPISchemaWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use OpenAPISchemaInterfaceWithContext.OpenAPISchemaWithContext instead.
|
||||
OpenAPISchema() (*openapi_v2.Document, error)
|
||||
}
|
||||
|
||||
// OpenAPISchemaInterfaceWithContext has a method to retrieve the open API schema.
|
||||
type OpenAPISchemaInterfaceWithContext interface {
|
||||
// OpenAPISchemaWithContext retrieves and parses the swagger API schema the server supports.
|
||||
OpenAPISchemaWithContext(ctx context.Context) (*openapi_v2.Document, error)
|
||||
}
|
||||
|
||||
func ToOpenAPISchemaInterfaceWithContext(i OpenAPISchemaInterface) OpenAPISchemaInterfaceWithContext {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
if i, ok := i.(OpenAPISchemaInterfaceWithContext); ok {
|
||||
return i
|
||||
}
|
||||
return &openAPISchemaInterfaceWrapper{
|
||||
delegate: i,
|
||||
}
|
||||
}
|
||||
|
||||
type openAPISchemaInterfaceWrapper struct {
|
||||
delegate OpenAPISchemaInterface
|
||||
}
|
||||
|
||||
func (i *openAPISchemaInterfaceWrapper) OpenAPISchemaWithContext(ctx context.Context) (*openapi_v2.Document, error) {
|
||||
return i.delegate.OpenAPISchema()
|
||||
}
|
||||
|
||||
// OpenAPIV3SchemaInterfaceWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use OpenAPIV3SchemaInterfaceWithContext instead.
|
||||
type OpenAPIV3SchemaInterface interface {
|
||||
// OpenAPIV3SchemaInterfaceWithContext.OpenAPIV3WithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use OpenAPIV3SchemaInterfaceWithContext.OpenAPIV3WithContext instead.
|
||||
OpenAPIV3() openapi.Client
|
||||
}
|
||||
|
||||
type OpenAPIV3SchemaInterfaceWithContext interface {
|
||||
OpenAPIV3WithContext(ctx context.Context) openapi.ClientWithContext
|
||||
}
|
||||
|
||||
func OpenAPIV3ToSchemaInterfaceWithContext(i OpenAPIV3SchemaInterface) OpenAPIV3SchemaInterfaceWithContext {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
if i, ok := i.(OpenAPIV3SchemaInterfaceWithContext); ok {
|
||||
return i
|
||||
}
|
||||
return &openAPIV3SchemaInterfaceWrapper{
|
||||
delegate: i,
|
||||
}
|
||||
}
|
||||
|
||||
type openAPIV3SchemaInterfaceWrapper struct {
|
||||
delegate OpenAPIV3SchemaInterface
|
||||
}
|
||||
|
||||
func (i *openAPIV3SchemaInterfaceWrapper) OpenAPIV3WithContext(ctx context.Context) openapi.ClientWithContext {
|
||||
return ToOpenAPIClientWithContext(i.delegate.OpenAPIV3())
|
||||
}
|
||||
|
||||
func ToOpenAPIClientWithContext(i openapi.Client) openapi.ClientWithContext { //nolint:staticcheck // Intentionally using the old interface here.
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
if i, ok := i.(openapi.ClientWithContext); ok {
|
||||
return i
|
||||
}
|
||||
return &openAPIClientWrapper{
|
||||
delegate: i,
|
||||
}
|
||||
}
|
||||
|
||||
type openAPIClientWrapper struct {
|
||||
//nolint:staticcheck // Intentionally using the old interface here.
|
||||
delegate openapi.Client
|
||||
}
|
||||
|
||||
func (i *openAPIClientWrapper) PathsWithContext(ctx context.Context) (map[string]openapi.GroupVersionWithContext, error) {
|
||||
resultWithoutContext, err := i.delegate.Paths()
|
||||
result := make(map[string]openapi.GroupVersionWithContext, len(resultWithoutContext))
|
||||
for key, entry := range resultWithoutContext {
|
||||
result[key] = openapi.ToGroupVersionWithContext(entry)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
// DiscoveryClient implements the functions that discover server-supported API groups,
|
||||
// versions and resources.
|
||||
type DiscoveryClient struct {
|
||||
@@ -199,19 +589,42 @@ func apiVersionsToAPIGroup(apiVersions *metav1.APIVersions) (apiGroup metav1.API
|
||||
// as aggregated discovery format or legacy format. For safety, resources will only
|
||||
// be returned if both endpoints returned resources. Returned "failedGVs" can be
|
||||
// empty, but will only be nil in the case an error is returned.
|
||||
//
|
||||
// GroupsAndMaybeResourcesWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use GroupsAndMaybeResourcesWithContext instead.
|
||||
func (d *DiscoveryClient) GroupsAndMaybeResources() (
|
||||
*metav1.APIGroupList,
|
||||
map[schema.GroupVersion]*metav1.APIResourceList,
|
||||
map[schema.GroupVersion]error,
|
||||
error) {
|
||||
return d.GroupsAndMaybeResourcesWithContext(context.Background())
|
||||
}
|
||||
|
||||
// GroupsAndMaybeResources returns the discovery groups, and (if new aggregated
|
||||
// discovery format) the resources keyed by group/version. Merges discovery groups
|
||||
// and resources from /api and /apis (either aggregated or not). Legacy groups
|
||||
// must be ordered first. The server will either return both endpoints (/api, /apis)
|
||||
// as aggregated discovery format or legacy format. For safety, resources will only
|
||||
// be returned if both endpoints returned resources. Returned "failedGVs" can be
|
||||
// empty, but will only be nil in the case an error is returned.
|
||||
func (d *DiscoveryClient) GroupsAndMaybeResourcesWithContext(ctx context.Context) (
|
||||
*metav1.APIGroupList,
|
||||
map[schema.GroupVersion]*metav1.APIResourceList,
|
||||
map[schema.GroupVersion]error,
|
||||
error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
// Legacy group ordered first (there is only one -- core/v1 group). Returned groups must
|
||||
// be non-nil, but it could be empty. Returned resources, apiResources map could be nil.
|
||||
groups, resources, failedGVs, err := d.downloadLegacy()
|
||||
groups, resources, failedGVs, err := d.downloadLegacy(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
// Discovery groups and (possibly) resources downloaded from /apis.
|
||||
apiGroups, apiResources, failedApisGVs, aerr := d.downloadAPIs()
|
||||
apiGroups, apiResources, failedApisGVs, aerr := d.downloadAPIs(ctx)
|
||||
if aerr != nil {
|
||||
return nil, nil, nil, aerr
|
||||
}
|
||||
@@ -239,7 +652,7 @@ func (d *DiscoveryClient) GroupsAndMaybeResources() (
|
||||
// possible for the resource map to be nil if the server returned
|
||||
// the unaggregated discovery. Returned "failedGVs" can be empty, but
|
||||
// will only be nil in the case of a returned error.
|
||||
func (d *DiscoveryClient) downloadLegacy() (
|
||||
func (d *DiscoveryClient) downloadLegacy(ctx context.Context) (
|
||||
*metav1.APIGroupList,
|
||||
map[schema.GroupVersion]*metav1.APIResourceList,
|
||||
map[schema.GroupVersion]error,
|
||||
@@ -249,7 +662,7 @@ func (d *DiscoveryClient) downloadLegacy() (
|
||||
body, err := d.restClient.Get().
|
||||
AbsPath("/api").
|
||||
SetHeader("Accept", accept).
|
||||
Do(context.TODO()).
|
||||
Do(ctx).
|
||||
ContentType(&responseContentType).
|
||||
Raw()
|
||||
apiGroupList := &metav1.APIGroupList{}
|
||||
@@ -295,7 +708,7 @@ func (d *DiscoveryClient) downloadLegacy() (
|
||||
// discovery resources. The returned groups will always exist, but the
|
||||
// resources map may be nil. Returned "failedGVs" can be empty, but will
|
||||
// only be nil in the case of a returned error.
|
||||
func (d *DiscoveryClient) downloadAPIs() (
|
||||
func (d *DiscoveryClient) downloadAPIs(ctx context.Context) (
|
||||
*metav1.APIGroupList,
|
||||
map[schema.GroupVersion]*metav1.APIResourceList,
|
||||
map[schema.GroupVersion]error,
|
||||
@@ -305,7 +718,7 @@ func (d *DiscoveryClient) downloadAPIs() (
|
||||
body, err := d.restClient.Get().
|
||||
AbsPath("/apis").
|
||||
SetHeader("Accept", accept).
|
||||
Do(context.TODO()).
|
||||
Do(ctx).
|
||||
ContentType(&responseContentType).
|
||||
Raw()
|
||||
if err != nil {
|
||||
@@ -371,8 +784,18 @@ func ContentTypeIsGVK(contentType string, gvk schema.GroupVersionKind) (bool, er
|
||||
|
||||
// ServerGroups returns the supported groups, with information like supported versions and the
|
||||
// preferred version.
|
||||
//
|
||||
// ServerGroupsWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerGroupsWithContext instead.
|
||||
func (d *DiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {
|
||||
groups, _, _, err := d.GroupsAndMaybeResources()
|
||||
return d.ServerGroupsWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ServerGroupsWithContext returns the supported groups, with information like supported versions and the
|
||||
// preferred version.
|
||||
func (d *DiscoveryClient) ServerGroupsWithContext(ctx context.Context) (*metav1.APIGroupList, error) {
|
||||
groups, _, _, err := d.GroupsAndMaybeResourcesWithContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -380,7 +803,16 @@ func (d *DiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {
|
||||
}
|
||||
|
||||
// ServerResourcesForGroupVersion returns the supported resources for a group and version.
|
||||
//
|
||||
// ServerResourcesForGroupVersionWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerResourcesForGroupVersionWithContext instead.
|
||||
func (d *DiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (resources *metav1.APIResourceList, err error) {
|
||||
return d.ServerResourcesForGroupVersionWithContext(context.Background(), groupVersion)
|
||||
}
|
||||
|
||||
// ServerResourcesForGroupVersionWithContext returns the supported resources for a group and version.
|
||||
func (d *DiscoveryClient) ServerResourcesForGroupVersionWithContext(ctx context.Context, groupVersion string) (resources *metav1.APIResourceList, err error) {
|
||||
url := url.URL{}
|
||||
if len(groupVersion) == 0 {
|
||||
return nil, fmt.Errorf("groupVersion shouldn't be empty")
|
||||
@@ -393,7 +825,7 @@ func (d *DiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (r
|
||||
resources = &metav1.APIResourceList{
|
||||
GroupVersion: groupVersion,
|
||||
}
|
||||
err = d.restClient.Get().AbsPath(url.String()).Do(context.TODO()).Into(resources)
|
||||
err = d.restClient.Get().AbsPath(url.String()).Do(ctx).Into(resources)
|
||||
if err != nil {
|
||||
// Tolerate core/v1 not found response by returning empty resource list;
|
||||
// this probably should not happen. But we should verify all callers are
|
||||
@@ -408,8 +840,13 @@ func (d *DiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (r
|
||||
|
||||
// ServerGroupsAndResources returns the supported resources for all groups and versions.
|
||||
func (d *DiscoveryClient) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
|
||||
return withRetries(defaultRetries, func() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
|
||||
return ServerGroupsAndResources(d)
|
||||
return d.ServerGroupsAndResourcesWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ServerGroupsAndResourcesWithContext returns the supported resources for all groups and versions.
|
||||
func (d *DiscoveryClient) ServerGroupsAndResourcesWithContext(ctx context.Context) ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
|
||||
return withRetries(ctx, defaultRetries, func() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
|
||||
return ServerGroupsAndResourcesWithContext(ctx, d)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -452,7 +889,18 @@ func GroupDiscoveryFailedErrorGroups(err error) (map[schema.GroupVersion]error,
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// ServerGroupsAndResourcesWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerGroupsAndResourcesWithContext instead.
|
||||
func ServerGroupsAndResources(d DiscoveryInterface) ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
|
||||
return ServerGroupsAndResourcesWithContext(context.Background(), ToDiscoveryInterfaceWithContext(d))
|
||||
}
|
||||
|
||||
func ServerGroupsAndResourcesWithContext(ctx context.Context, d DiscoveryInterfaceWithContext) ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var sgs *metav1.APIGroupList
|
||||
var resources []*metav1.APIResourceList
|
||||
var failedGVs map[schema.GroupVersion]error
|
||||
@@ -460,14 +908,21 @@ func ServerGroupsAndResources(d DiscoveryInterface) ([]*metav1.APIGroup, []*meta
|
||||
|
||||
// If the passed discovery object implements the wider AggregatedDiscoveryInterface,
|
||||
// then attempt to retrieve aggregated discovery with both groups and the resources.
|
||||
if ad, ok := d.(AggregatedDiscoveryInterface); ok {
|
||||
ad, ok := d.(AggregatedDiscoveryInterfaceWithContext)
|
||||
if !ok {
|
||||
if ad2, ok2 := d.(AggregatedDiscoveryInterface); ok2 {
|
||||
ad = ToAggregatedDiscoveryInterfaceWithContext(ad2)
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
var resourcesByGV map[schema.GroupVersion]*metav1.APIResourceList
|
||||
sgs, resourcesByGV, failedGVs, err = ad.GroupsAndMaybeResources()
|
||||
sgs, resourcesByGV, failedGVs, err = ad.GroupsAndMaybeResourcesWithContext(ctx)
|
||||
for _, resourceList := range resourcesByGV {
|
||||
resources = append(resources, resourceList)
|
||||
}
|
||||
} else {
|
||||
sgs, err = d.ServerGroups()
|
||||
sgs, err = d.ServerGroupsWithContext(ctx)
|
||||
}
|
||||
|
||||
if sgs == nil {
|
||||
@@ -488,7 +943,7 @@ func ServerGroupsAndResources(d DiscoveryInterface) ([]*metav1.APIGroup, []*meta
|
||||
return resultGroups, resources, ferr
|
||||
}
|
||||
|
||||
groupVersionResources, failedGroups := fetchGroupVersionResources(d, sgs)
|
||||
groupVersionResources, failedGroups := fetchGroupVersionResources(ctx, d, sgs)
|
||||
|
||||
// order results by group/version discovery order
|
||||
result := []*metav1.APIResourceList{}
|
||||
@@ -509,7 +964,20 @@ func ServerGroupsAndResources(d DiscoveryInterface) ([]*metav1.APIGroup, []*meta
|
||||
}
|
||||
|
||||
// ServerPreferredResources uses the provided discovery interface to look up preferred resources
|
||||
//
|
||||
// ServerPreferredResourcesWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerPreferredResourcesWithContext instead.
|
||||
func ServerPreferredResources(d DiscoveryInterface) ([]*metav1.APIResourceList, error) {
|
||||
return ServerPreferredResourcesWithContext(context.Background(), ToDiscoveryInterfaceWithContext(d))
|
||||
}
|
||||
|
||||
// ServerPreferredResourcesWithContext uses the provided discovery interface to look up preferred resources
|
||||
func ServerPreferredResourcesWithContext(ctx context.Context, d DiscoveryInterfaceWithContext) ([]*metav1.APIResourceList, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var serverGroupList *metav1.APIGroupList
|
||||
var failedGroups map[schema.GroupVersion]error
|
||||
var groupVersionResources map[schema.GroupVersion]*metav1.APIResourceList
|
||||
@@ -518,18 +986,24 @@ func ServerPreferredResources(d DiscoveryInterface) ([]*metav1.APIResourceList,
|
||||
// If the passed discovery object implements the wider AggregatedDiscoveryInterface,
|
||||
// then it is attempt to retrieve both the groups and the resources. "failedGroups"
|
||||
// are Group/Versions returned as stale in AggregatedDiscovery format.
|
||||
ad, ok := d.(AggregatedDiscoveryInterface)
|
||||
ad, ok := d.(AggregatedDiscoveryInterfaceWithContext)
|
||||
if !ok {
|
||||
if ad2, ok2 := d.(AggregatedDiscoveryInterface); ok2 {
|
||||
ad = ToAggregatedDiscoveryInterfaceWithContext(ad2)
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
serverGroupList, groupVersionResources, failedGroups, err = ad.GroupsAndMaybeResources()
|
||||
serverGroupList, groupVersionResources, failedGroups, err = ad.GroupsAndMaybeResourcesWithContext(ctx)
|
||||
} else {
|
||||
serverGroupList, err = d.ServerGroups()
|
||||
serverGroupList, err = d.ServerGroupsWithContext(ctx)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Non-aggregated discovery must fetch resources from Groups.
|
||||
if groupVersionResources == nil {
|
||||
groupVersionResources, failedGroups = fetchGroupVersionResources(d, serverGroupList)
|
||||
groupVersionResources, failedGroups = fetchGroupVersionResources(ctx, d, serverGroupList)
|
||||
}
|
||||
|
||||
result := []*metav1.APIResourceList{}
|
||||
@@ -585,7 +1059,7 @@ func ServerPreferredResources(d DiscoveryInterface) ([]*metav1.APIResourceList,
|
||||
}
|
||||
|
||||
// fetchServerResourcesForGroupVersions uses the discovery client to fetch the resources for the specified groups in parallel.
|
||||
func fetchGroupVersionResources(d DiscoveryInterface, apiGroups *metav1.APIGroupList) (map[schema.GroupVersion]*metav1.APIResourceList, map[schema.GroupVersion]error) {
|
||||
func fetchGroupVersionResources(ctx context.Context, d DiscoveryInterfaceWithContext, apiGroups *metav1.APIGroupList) (map[schema.GroupVersion]*metav1.APIResourceList, map[schema.GroupVersion]error) {
|
||||
groupVersionResources := make(map[schema.GroupVersion]*metav1.APIResourceList)
|
||||
failedGroups := make(map[schema.GroupVersion]error)
|
||||
|
||||
@@ -597,9 +1071,9 @@ func fetchGroupVersionResources(d DiscoveryInterface, apiGroups *metav1.APIGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer utilruntime.HandleCrash()
|
||||
defer utilruntime.HandleCrashWithContext(ctx)
|
||||
|
||||
apiResourceList, err := d.ServerResourcesForGroupVersion(groupVersion.String())
|
||||
apiResourceList, err := d.ServerResourcesForGroupVersionWithContext(ctx, groupVersion.String())
|
||||
|
||||
// lock to record results
|
||||
resultLock.Lock()
|
||||
@@ -623,23 +1097,56 @@ func fetchGroupVersionResources(d DiscoveryInterface, apiGroups *metav1.APIGroup
|
||||
|
||||
// ServerPreferredResources returns the supported resources with the version preferred by the
|
||||
// server.
|
||||
//
|
||||
// ServerPreferredResourcesWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerPreferredResourcesWithContext instead.
|
||||
func (d *DiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
|
||||
_, rs, err := withRetries(defaultRetries, func() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
|
||||
_, rs, err := withRetries(context.Background(), defaultRetries, func() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
|
||||
rs, err := ServerPreferredResources(d)
|
||||
return nil, rs, err
|
||||
})
|
||||
return rs, err
|
||||
}
|
||||
|
||||
// ServerPreferredResourcesWithContext returns the supported resources with the version preferred by the
|
||||
// server.
|
||||
func (d *DiscoveryClient) ServerPreferredResourcesWithContext(ctx context.Context) ([]*metav1.APIResourceList, error) {
|
||||
_, rs, err := withRetries(ctx, defaultRetries, func() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
|
||||
rs, err := ServerPreferredResourcesWithContext(ctx, d)
|
||||
return nil, rs, err
|
||||
})
|
||||
return rs, err
|
||||
}
|
||||
|
||||
// ServerPreferredNamespacedResources returns the supported namespaced resources with the
|
||||
// version preferred by the server.
|
||||
//
|
||||
// ServerPreferredNamespacedResourcesWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerPreferredNamespacedResourcesWithContext instead.
|
||||
func (d *DiscoveryClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
|
||||
return ServerPreferredNamespacedResources(d)
|
||||
}
|
||||
|
||||
// ServerPreferredNamespacedResources returns the supported namespaced resources with the
|
||||
// version preferred by the server.
|
||||
func (d *DiscoveryClient) ServerPreferredNamespacedResourcesWithContext(ctx context.Context) ([]*metav1.APIResourceList, error) {
|
||||
return ServerPreferredNamespacedResourcesWithContext(ctx, d)
|
||||
}
|
||||
|
||||
// ServerPreferredNamespacedResources uses the provided discovery interface to look up preferred namespaced resources
|
||||
//
|
||||
// ServerPreferredNamespacedResourcesWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerPreferredNamespacedResourcesWithContext instead.
|
||||
func ServerPreferredNamespacedResources(d DiscoveryInterface) ([]*metav1.APIResourceList, error) {
|
||||
all, err := ServerPreferredResources(d)
|
||||
return ServerPreferredNamespacedResourcesWithContext(context.Background(), ToDiscoveryInterfaceWithContext(d))
|
||||
}
|
||||
|
||||
// ServerPreferredNamespacedResourcesWithContext uses the provided discovery interface to look up preferred namespaced resources
|
||||
func ServerPreferredNamespacedResourcesWithContext(ctx context.Context, d DiscoveryInterfaceWithContext) ([]*metav1.APIResourceList, error) {
|
||||
all, err := ServerPreferredResourcesWithContext(ctx, d)
|
||||
return FilteredBy(ResourcePredicateFunc(func(groupVersion string, r *metav1.APIResource) bool {
|
||||
return r.Namespaced
|
||||
}), all), err
|
||||
@@ -647,7 +1154,12 @@ func ServerPreferredNamespacedResources(d DiscoveryInterface) ([]*metav1.APIReso
|
||||
|
||||
// ServerVersion retrieves and parses the server's version (git version).
|
||||
func (d *DiscoveryClient) ServerVersion() (*version.Info, error) {
|
||||
body, err := d.restClient.Get().AbsPath("/version").Do(context.TODO()).Raw()
|
||||
return d.ServerVersionWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ServerVersionWithContext retrieves and parses the server's version (git version).
|
||||
func (d *DiscoveryClient) ServerVersionWithContext(ctx context.Context) (*version.Info, error) {
|
||||
body, err := d.restClient.Get().AbsPath("/version").Do(ctx).Raw()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -661,7 +1173,12 @@ func (d *DiscoveryClient) ServerVersion() (*version.Info, error) {
|
||||
|
||||
// OpenAPISchema fetches the open api v2 schema using a rest client and parses the proto.
|
||||
func (d *DiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
|
||||
data, err := d.restClient.Get().AbsPath("/openapi/v2").SetHeader("Accept", openAPIV2mimePb).Do(context.TODO()).Raw()
|
||||
return d.OpenAPISchemaWithContext(context.Background())
|
||||
}
|
||||
|
||||
// OpenAPISchemaWithContext fetches the open api v2 schema using a rest client and parses the proto.
|
||||
func (d *DiscoveryClient) OpenAPISchemaWithContext(ctx context.Context) (*openapi_v2.Document, error) {
|
||||
data, err := d.restClient.Get().AbsPath("/openapi/v2").SetHeader("Accept", openAPIV2mimePb).Do(ctx).Raw()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -673,20 +1190,40 @@ func (d *DiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
|
||||
return document, nil
|
||||
}
|
||||
|
||||
// OpenAPIV3WithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use OpenAPIV3WithContext instead.
|
||||
func (d *DiscoveryClient) OpenAPIV3() openapi.Client {
|
||||
return openapi.NewClient(d.restClient)
|
||||
}
|
||||
|
||||
func (d *DiscoveryClient) OpenAPIV3WithContext(ctx context.Context) openapi.ClientWithContext {
|
||||
return openapi.NewClientWithContext(d.restClient)
|
||||
}
|
||||
|
||||
// WithLegacy returns copy of current discovery client that will only
|
||||
// receive the legacy discovery format.
|
||||
//
|
||||
// WithLegacyWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use WithLegacyWithContext instead.
|
||||
func (d *DiscoveryClient) WithLegacy() DiscoveryInterface {
|
||||
client := *d
|
||||
client.UseLegacyDiscovery = true
|
||||
return &client
|
||||
}
|
||||
|
||||
// WithLegacyWithContext returns copy of current discovery client that will only
|
||||
// receive the legacy discovery format.
|
||||
func (d *DiscoveryClient) WithLegacyWithContext(ctx context.Context) DiscoveryInterfaceWithContext {
|
||||
client := *d
|
||||
client.UseLegacyDiscovery = true
|
||||
return &client
|
||||
}
|
||||
|
||||
// withRetries retries the given recovery function in case the groups supported by the server change after ServerGroup() returns.
|
||||
func withRetries(maxRetries int, f func() ([]*metav1.APIGroup, []*metav1.APIResourceList, error)) ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
|
||||
// It retries only if the context isn't canceled.
|
||||
func withRetries(ctx context.Context, maxRetries int, f func() ([]*metav1.APIGroup, []*metav1.APIResourceList, error)) ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
|
||||
var result []*metav1.APIResourceList
|
||||
var resultGroups []*metav1.APIGroup
|
||||
var err error
|
||||
@@ -698,6 +1235,10 @@ func withRetries(maxRetries int, f func() ([]*metav1.APIGroup, []*metav1.APIReso
|
||||
if _, ok := err.(*ErrGroupDiscoveryFailed); !ok {
|
||||
return nil, nil, err
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
// Give up retrying because of context cancellation and propagate the error returned by f.
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
return resultGroups, result, err
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package fake
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
@@ -33,16 +34,33 @@ import (
|
||||
"k8s.io/client-go/testing"
|
||||
)
|
||||
|
||||
// FakeDiscovery implements discovery.DiscoveryInterface and sometimes calls testing.Fake.Invoke with an action,
|
||||
// FakeDiscovery implements discovery.DiscoveryInterface and discovery.DiscoveryInterfaceWithContext.
|
||||
// It sometimes calls testing.Fake.Invoke with an action,
|
||||
// but doesn't respect the return value if any. There is a way to fake static values like ServerVersion by using the Faked... fields on the struct.
|
||||
type FakeDiscovery struct {
|
||||
*testing.Fake
|
||||
FakedServerVersion *version.Info
|
||||
}
|
||||
|
||||
var (
|
||||
//nolint:staticcheck // Intentionally using the old interface here.
|
||||
_ discovery.DiscoveryInterface = &FakeDiscovery{}
|
||||
_ discovery.DiscoveryInterfaceWithContext = &FakeDiscovery{}
|
||||
)
|
||||
|
||||
// ServerResourcesForGroupVersion returns the supported resources for a group
|
||||
// and version.
|
||||
//
|
||||
// ServerResourcesForGroupVersionWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerResourcesForGroupVersionWithContext instead.
|
||||
func (c *FakeDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
|
||||
return c.ServerResourcesForGroupVersionWithContext(context.Background(), groupVersion)
|
||||
}
|
||||
|
||||
// ServerResourcesForGroupVersionWithContext returns the supported resources for a group
|
||||
// and version.
|
||||
func (c *FakeDiscovery) ServerResourcesForGroupVersionWithContext(ctx context.Context, groupVersion string) (*metav1.APIResourceList, error) {
|
||||
action := testing.ActionImpl{
|
||||
Verb: "get",
|
||||
Resource: schema.GroupVersionResource{Resource: "resource"},
|
||||
@@ -65,8 +83,17 @@ func (c *FakeDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*me
|
||||
}
|
||||
|
||||
// ServerGroupsAndResources returns the supported groups and resources for all groups and versions.
|
||||
//
|
||||
// ServerGroupsAndResourcesWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerGroupsAndResourcesWithContext instead.
|
||||
func (c *FakeDiscovery) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
|
||||
sgs, err := c.ServerGroups()
|
||||
return c.ServerGroupsAndResourcesWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ServerGroupsAndResourcesWithContext returns the supported groups and resources for all groups and versions.
|
||||
func (c *FakeDiscovery) ServerGroupsAndResourcesWithContext(ctx context.Context) ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
|
||||
sgs, err := c.ServerGroupsWithContext(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -87,19 +114,49 @@ func (c *FakeDiscovery) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav
|
||||
|
||||
// ServerPreferredResources returns the supported resources with the version
|
||||
// preferred by the server.
|
||||
//
|
||||
// ServerPreferredResourcesWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerPreferredResourcesWithContext instead.
|
||||
func (c *FakeDiscovery) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
|
||||
return c.ServerPreferredResourcesWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ServerPreferredResourcesWithContext returns the supported resources with the version
|
||||
// preferred by the server.
|
||||
func (c *FakeDiscovery) ServerPreferredResourcesWithContext(ctx context.Context) ([]*metav1.APIResourceList, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ServerPreferredNamespacedResources returns the supported namespaced resources
|
||||
// with the version preferred by the server.
|
||||
//
|
||||
// ServerPreferredNamespacedResourcesWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerPreferredNamespacedResourcesWithContext instead.
|
||||
func (c *FakeDiscovery) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
|
||||
return c.ServerPreferredNamespacedResourcesWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ServerPreferredNamespacedResourcesWithContext returns the supported namespaced resources
|
||||
// with the version preferred by the server.
|
||||
func (c *FakeDiscovery) ServerPreferredNamespacedResourcesWithContext(ctx context.Context) ([]*metav1.APIResourceList, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ServerGroups returns the supported groups, with information like supported
|
||||
// versions and the preferred version.
|
||||
//
|
||||
// ServerGroupsWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerGroupsWithContext instead.
|
||||
func (c *FakeDiscovery) ServerGroups() (*metav1.APIGroupList, error) {
|
||||
return c.ServerGroupsWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ServerGroupsWithContext returns the supported groups, with information like supported
|
||||
// versions and the preferred version.
|
||||
func (c *FakeDiscovery) ServerGroupsWithContext(ctx context.Context) (*metav1.APIGroupList, error) {
|
||||
action := testing.ActionImpl{
|
||||
Verb: "get",
|
||||
Resource: schema.GroupVersionResource{Resource: "group"},
|
||||
@@ -143,7 +200,16 @@ func (c *FakeDiscovery) ServerGroups() (*metav1.APIGroupList, error) {
|
||||
}
|
||||
|
||||
// ServerVersion retrieves and parses the server's version.
|
||||
//
|
||||
// ServerVersionWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ServerVersionWithContext instead.
|
||||
func (c *FakeDiscovery) ServerVersion() (*version.Info, error) {
|
||||
return c.ServerVersionWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ServerVersionWithContext retrieves and parses the server's version.
|
||||
func (c *FakeDiscovery) ServerVersionWithContext(ctx context.Context) (*version.Info, error) {
|
||||
action := testing.ActionImpl{}
|
||||
action.Verb = "get"
|
||||
action.Resource = schema.GroupVersionResource{Resource: "version"}
|
||||
@@ -161,20 +227,43 @@ func (c *FakeDiscovery) ServerVersion() (*version.Info, error) {
|
||||
}
|
||||
|
||||
// OpenAPISchema retrieves and parses the swagger API schema the server supports.
|
||||
//
|
||||
// OpenAPISchemaWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use OpenAPISchemaWithContext instead.
|
||||
func (c *FakeDiscovery) OpenAPISchema() (*openapi_v2.Document, error) {
|
||||
return c.OpenAPISchemaWithContext(context.Background())
|
||||
}
|
||||
|
||||
// OpenAPISchemaWithContext retrieves and parses the swagger API schema the server supports.
|
||||
func (c *FakeDiscovery) OpenAPISchemaWithContext(ctx context.Context) (*openapi_v2.Document, error) {
|
||||
return &openapi_v2.Document{}, nil
|
||||
}
|
||||
|
||||
// OpenAPIV3WithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use OpenAPIV3WithContext instead.
|
||||
func (c *FakeDiscovery) OpenAPIV3() openapi.Client {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (c *FakeDiscovery) OpenAPIV3WithContext(ctx context.Context) openapi.ClientWithContext {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
// RESTClient returns a RESTClient that is used to communicate with API server
|
||||
// by this client implementation.
|
||||
func (c *FakeDiscovery) RESTClient() restclient.Interface {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithLegacyWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use WithLegacyWithContext instead.
|
||||
func (c *FakeDiscovery) WithLegacy() discovery.DiscoveryInterface {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (c *FakeDiscovery) WithLegacyWithContext(ctx context.Context) discovery.DiscoveryInterfaceWithContext {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
2
go.mod
2
go.mod
@@ -24,7 +24,7 @@ require (
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0
|
||||
k8s.io/api v0.0.0-20260626213116-2b6c2012d75f
|
||||
k8s.io/apimachinery v0.0.0-20260626172716-6fa8dff7b19f
|
||||
k8s.io/apimachinery v0.0.0-20260701082954-b16db0ad9a39
|
||||
k8s.io/klog/v2 v2.140.0
|
||||
k8s.io/kube-openapi v0.0.0-20260618221249-bc653b64f974
|
||||
k8s.io/streaming v0.0.0-20260626172358-68009e8e6aa5
|
||||
|
||||
4
go.sum
4
go.sum
@@ -120,8 +120,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
k8s.io/api v0.0.0-20260626213116-2b6c2012d75f h1:WfCuhwj5sO/oWkTS3MovBWuwL7vBXPWn2ROuQf26qok=
|
||||
k8s.io/api v0.0.0-20260626213116-2b6c2012d75f/go.mod h1:7NNJfcrPo0BNrOSUudMNZpLxWf/OYIRNbwS+/yAaJwg=
|
||||
k8s.io/apimachinery v0.0.0-20260626172716-6fa8dff7b19f h1:WAFkshKyNvj5avlHoK0nhd0B0G+O+YUH3ntvUkUmDsE=
|
||||
k8s.io/apimachinery v0.0.0-20260626172716-6fa8dff7b19f/go.mod h1:T9tvL1Yxf+TRVyTz+Q7KtLAncCr9xxxx1zrF6g/QuR0=
|
||||
k8s.io/apimachinery v0.0.0-20260701082954-b16db0ad9a39 h1:2G7SZqq7fq1MXbKZEV85vdUI8Do8JzqM5dwfVDMqrWo=
|
||||
k8s.io/apimachinery v0.0.0-20260701082954-b16db0ad9a39/go.mod h1:T9tvL1Yxf+TRVyTz+Q7KtLAncCr9xxxx1zrF6g/QuR0=
|
||||
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
|
||||
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
|
||||
k8s.io/kube-openapi v0.0.0-20260618221249-bc653b64f974 h1:JVogoTvOj6gutlx8bUwGh0e8o8L4X8nDbTLyONmoVvk=
|
||||
|
||||
@@ -81,7 +81,7 @@ import (
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
Discovery() discovery.DiscoveryInterface
|
||||
Discovery() discovery.DiscoveryInterfaces
|
||||
AdmissionregistrationV1() admissionregistrationv1.AdmissionregistrationV1Interface
|
||||
AdmissionregistrationV1alpha1() admissionregistrationv1alpha1.AdmissionregistrationV1alpha1Interface
|
||||
AdmissionregistrationV1beta1() admissionregistrationv1beta1.AdmissionregistrationV1beta1Interface
|
||||
@@ -461,7 +461,7 @@ func (c *Clientset) StoragemigrationV1beta1() storagemigrationv1beta1.Storagemig
|
||||
}
|
||||
|
||||
// Discovery retrieves the DiscoveryClient
|
||||
func (c *Clientset) Discovery() discovery.DiscoveryInterface {
|
||||
func (c *Clientset) Discovery() discovery.DiscoveryInterfaces {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ type Clientset struct {
|
||||
tracker testing.ObjectTracker
|
||||
}
|
||||
|
||||
func (c *Clientset) Discovery() discovery.DiscoveryInterface {
|
||||
func (c *Clientset) Discovery() discovery.DiscoveryInterfaces {
|
||||
return c.discovery
|
||||
}
|
||||
|
||||
|
||||
@@ -17,34 +17,71 @@ limitations under the License.
|
||||
package cached
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"k8s.io/client-go/openapi"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
delegate openapi.Client
|
||||
type Client struct {
|
||||
delegate openapi.ClientWithContext
|
||||
|
||||
once sync.Once
|
||||
result map[string]openapi.GroupVersion
|
||||
result map[string]openapi.GroupVersionWithContext
|
||||
err error
|
||||
}
|
||||
|
||||
var (
|
||||
//nolint:staticcheck // Intentionally using the old interface here.
|
||||
_ openapi.Client = &Client{}
|
||||
_ openapi.ClientWithContext = &Client{}
|
||||
)
|
||||
|
||||
// NewClientWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use NewClientWithContext instead.
|
||||
func NewClient(other openapi.Client) openapi.Client {
|
||||
return &client{
|
||||
return newClient(openapi.ToClientWithContext(other))
|
||||
}
|
||||
|
||||
func NewClientWithContext(other openapi.ClientWithContext) *Client {
|
||||
return newClient(other)
|
||||
}
|
||||
|
||||
func newClient(other openapi.ClientWithContext) *Client {
|
||||
return &Client{
|
||||
delegate: other,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) Paths() (map[string]openapi.GroupVersion, error) {
|
||||
// PathsWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use PathsWithContext instead.
|
||||
func (c *Client) Paths() (map[string]openapi.GroupVersion, error) {
|
||||
// For efficiency reasons in the *WithContext case this is a map to
|
||||
// openapi.GroupVersionWithContext. But we know that all entries
|
||||
// also implement openapi.GroupVersion.
|
||||
resultWithContext, err := c.PathsWithContext(context.Background())
|
||||
result := make(map[string]openapi.GroupVersion, len(resultWithContext))
|
||||
for key, entry := range resultWithContext {
|
||||
result[key] = entry.(openapi.GroupVersion)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (c *Client) PathsWithContext(ctx context.Context) (map[string]openapi.GroupVersionWithContext, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.once.Do(func() {
|
||||
uncached, err := c.delegate.Paths()
|
||||
uncached, err := c.delegate.PathsWithContext(ctx)
|
||||
if err != nil {
|
||||
c.err = err
|
||||
return
|
||||
}
|
||||
|
||||
result := make(map[string]openapi.GroupVersion, len(uncached))
|
||||
result := make(map[string]openapi.GroupVersionWithContext, len(uncached))
|
||||
for k, v := range uncached {
|
||||
result[k] = newGroupVersion(v)
|
||||
}
|
||||
|
||||
@@ -17,30 +17,48 @@ limitations under the License.
|
||||
package cached
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"k8s.io/client-go/openapi"
|
||||
)
|
||||
|
||||
type groupversion struct {
|
||||
delegate openapi.GroupVersion
|
||||
delegate openapi.GroupVersionWithContext
|
||||
|
||||
lock sync.Mutex
|
||||
docs map[string]docInfo
|
||||
}
|
||||
|
||||
var (
|
||||
//nolint:staticcheck // Intentionally using the old interface here.
|
||||
_ openapi.GroupVersion = &groupversion{}
|
||||
_ openapi.GroupVersionWithContext = &groupversion{}
|
||||
)
|
||||
|
||||
type docInfo struct {
|
||||
data []byte
|
||||
err error
|
||||
}
|
||||
|
||||
func newGroupVersion(delegate openapi.GroupVersion) *groupversion {
|
||||
func newGroupVersion(delegate openapi.GroupVersionWithContext) *groupversion {
|
||||
return &groupversion{
|
||||
delegate: delegate,
|
||||
}
|
||||
}
|
||||
|
||||
// SchemaWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use SchemaWithContext instead.
|
||||
func (g *groupversion) Schema(contentType string) ([]byte, error) {
|
||||
return g.SchemaWithContext(context.Background(), contentType)
|
||||
}
|
||||
|
||||
func (g *groupversion) SchemaWithContext(ctx context.Context, contentType string) ([]byte, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
g.lock.Lock()
|
||||
defer g.lock.Unlock()
|
||||
|
||||
@@ -50,7 +68,7 @@ func (g *groupversion) Schema(contentType string) ([]byte, error) {
|
||||
g.docs = make(map[string]docInfo)
|
||||
}
|
||||
|
||||
cachedInfo.data, cachedInfo.err = g.delegate.Schema(contentType)
|
||||
cachedInfo.data, cachedInfo.err = g.delegate.SchemaWithContext(ctx, contentType)
|
||||
g.docs[contentType] = cachedInfo
|
||||
}
|
||||
|
||||
|
||||
@@ -25,25 +25,81 @@ import (
|
||||
"k8s.io/kube-openapi/pkg/handler3"
|
||||
)
|
||||
|
||||
// ClientWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ClientWithContext instead.
|
||||
type Client interface {
|
||||
Paths() (map[string]GroupVersion, error)
|
||||
}
|
||||
|
||||
type ClientWithContext interface {
|
||||
PathsWithContext(ctx context.Context) (map[string]GroupVersionWithContext, error)
|
||||
}
|
||||
|
||||
func ToClientWithContext(c Client) ClientWithContext {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
if c, ok := c.(ClientWithContext); ok {
|
||||
return c
|
||||
}
|
||||
return &clientWrapper{
|
||||
delegate: c,
|
||||
}
|
||||
}
|
||||
|
||||
type clientWrapper struct {
|
||||
delegate Client
|
||||
}
|
||||
|
||||
func (c *clientWrapper) PathsWithContext(ctx context.Context) (map[string]GroupVersionWithContext, error) {
|
||||
resultWithoutContext, err := c.delegate.Paths()
|
||||
result := make(map[string]GroupVersionWithContext, len(resultWithoutContext))
|
||||
for key, entry := range resultWithoutContext {
|
||||
result[key] = ToGroupVersionWithContext(entry)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
type client struct {
|
||||
// URL includes the `hash` query param to take advantage of cache busting
|
||||
restClient rest.Interface
|
||||
}
|
||||
|
||||
// NewClientWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use NewClientWithContext instead.
|
||||
func NewClient(restClient rest.Interface) Client {
|
||||
return newClient(restClient)
|
||||
}
|
||||
|
||||
func NewClientWithContext(restClient rest.Interface) ClientWithContext {
|
||||
return newClient(restClient)
|
||||
}
|
||||
|
||||
func newClient(restClient rest.Interface) *client {
|
||||
return &client{
|
||||
restClient: restClient,
|
||||
}
|
||||
}
|
||||
|
||||
// PathsWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use PathsWithContext instead.
|
||||
func (c *client) Paths() (map[string]GroupVersion, error) {
|
||||
resultWithContext, err := c.PathsWithContext(context.Background())
|
||||
result := make(map[string]GroupVersion, len(resultWithContext))
|
||||
for key, entry := range resultWithContext {
|
||||
// We know that this is a *groupversion which implements GroupVersion.
|
||||
result[key] = entry.(GroupVersion)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (c *client) PathsWithContext(ctx context.Context) (map[string]GroupVersionWithContext, error) {
|
||||
data, err := c.restClient.Get().
|
||||
AbsPath("/openapi/v3").
|
||||
Do(context.TODO()).
|
||||
Do(ctx).
|
||||
Raw()
|
||||
|
||||
if err != nil {
|
||||
@@ -59,7 +115,7 @@ func (c *client) Paths() (map[string]GroupVersion, error) {
|
||||
// Calculate the client-side prefix for a "root" request
|
||||
rootPrefix := strings.TrimSuffix(c.restClient.Get().AbsPath("/").URL().Path, "/")
|
||||
// Create GroupVersions for each element of the result
|
||||
result := map[string]GroupVersion{}
|
||||
result := make(map[string]GroupVersionWithContext, len(discoMap.Paths))
|
||||
for k, v := range discoMap.Paths {
|
||||
// Trim off the prefix that will always be added in client-side
|
||||
v.ServerRelativeURL = strings.TrimPrefix(v.ServerRelativeURL, rootPrefix)
|
||||
|
||||
@@ -25,6 +25,9 @@ import (
|
||||
|
||||
const ContentTypeOpenAPIV3PB = "application/com.github.proto-openapi.spec.v3@v1.0+protobuf"
|
||||
|
||||
// GroupVersionWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use GroupVersionWithContext instead.
|
||||
type GroupVersion interface {
|
||||
Schema(contentType string) ([]byte, error)
|
||||
|
||||
@@ -35,22 +38,61 @@ type GroupVersion interface {
|
||||
ServerRelativeURL() string
|
||||
}
|
||||
|
||||
type GroupVersionWithContext interface {
|
||||
SchemaWithContext(ctx context.Context, contentType string) ([]byte, error)
|
||||
|
||||
// ServerRelativeURL. Returns the path and parameters used to fetch the schema.
|
||||
// You should use the Schema method to fetch it, but this value can be used
|
||||
// to key the current version of the schema in a cache since it contains a
|
||||
// hash string which changes upon schema update.
|
||||
ServerRelativeURL() string
|
||||
}
|
||||
|
||||
func ToGroupVersionWithContext(gv GroupVersion) GroupVersionWithContext {
|
||||
if gv == nil {
|
||||
return nil
|
||||
}
|
||||
if gv, ok := gv.(GroupVersionWithContext); ok {
|
||||
return gv
|
||||
}
|
||||
return &groupVersionWrapper{
|
||||
GroupVersion: gv,
|
||||
}
|
||||
}
|
||||
|
||||
type groupVersionWrapper struct {
|
||||
GroupVersion
|
||||
}
|
||||
|
||||
func (gv *groupVersionWrapper) SchemaWithContext(ctx context.Context, contentType string) ([]byte, error) {
|
||||
return gv.Schema(contentType)
|
||||
}
|
||||
|
||||
type groupversion struct {
|
||||
client *client
|
||||
item handler3.OpenAPIV3DiscoveryGroupVersion
|
||||
useClientPrefix bool
|
||||
}
|
||||
|
||||
var (
|
||||
_ GroupVersion = &groupversion{}
|
||||
_ GroupVersionWithContext = &groupversion{}
|
||||
)
|
||||
|
||||
func newGroupVersion(client *client, item handler3.OpenAPIV3DiscoveryGroupVersion, useClientPrefix bool) *groupversion {
|
||||
return &groupversion{client: client, item: item, useClientPrefix: useClientPrefix}
|
||||
}
|
||||
|
||||
func (g *groupversion) Schema(contentType string) ([]byte, error) {
|
||||
return g.SchemaWithContext(context.Background(), contentType)
|
||||
}
|
||||
|
||||
func (g *groupversion) SchemaWithContext(ctx context.Context, contentType string) ([]byte, error) {
|
||||
if !g.useClientPrefix {
|
||||
return g.client.restClient.Get().
|
||||
RequestURI(g.item.ServerRelativeURL).
|
||||
SetHeader("Accept", contentType).
|
||||
Do(context.TODO()).
|
||||
Do(ctx).
|
||||
Raw()
|
||||
}
|
||||
|
||||
@@ -72,7 +114,7 @@ func (g *groupversion) Schema(contentType string) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return path.Do(context.TODO()).Raw()
|
||||
return path.Do(ctx).Raw()
|
||||
}
|
||||
|
||||
// URL used for fetching the schema. The URL includes a hash and can be used
|
||||
|
||||
@@ -17,38 +17,97 @@ limitations under the License.
|
||||
package restmapper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"iter"
|
||||
"slices"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/discovery"
|
||||
)
|
||||
|
||||
// CategoryExpander maps category strings to GroupResources.
|
||||
// Categories are classification or 'tag' of a group of resources.
|
||||
//
|
||||
// CategoryExpanderWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use CategoryExpanderWithContext instead.
|
||||
type CategoryExpander interface {
|
||||
Expand(category string) ([]schema.GroupResource, bool)
|
||||
}
|
||||
|
||||
// SimpleCategoryExpander implements CategoryExpander interface
|
||||
// CategoryExpanderWithContext maps category strings to GroupResources.
|
||||
// Categories are classification or 'tag' of a group of resources.
|
||||
type CategoryExpanderWithContext interface {
|
||||
ExpandWithContext(ctx context.Context, category string) ([]schema.GroupResource, bool)
|
||||
}
|
||||
|
||||
func ToCategoryExpanderWithContext(c CategoryExpander) CategoryExpanderWithContext {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
if c, ok := c.(CategoryExpanderWithContext); ok {
|
||||
return c
|
||||
}
|
||||
return &categoryExpanderWrapper{
|
||||
delegate: c,
|
||||
}
|
||||
}
|
||||
|
||||
type categoryExpanderWrapper struct {
|
||||
delegate CategoryExpander
|
||||
}
|
||||
|
||||
func (c *categoryExpanderWrapper) ExpandWithContext(ctx context.Context, category string) ([]schema.GroupResource, bool) {
|
||||
return c.delegate.Expand(category)
|
||||
}
|
||||
|
||||
// SimpleCategoryExpander implements CategoryExpander and CategoryExpanderWithContext interface
|
||||
// using a static mapping of categories to GroupResource mapping.
|
||||
type SimpleCategoryExpander struct {
|
||||
Expansions map[string][]schema.GroupResource
|
||||
}
|
||||
|
||||
var (
|
||||
_ CategoryExpander = SimpleCategoryExpander{}
|
||||
_ CategoryExpanderWithContext = SimpleCategoryExpander{}
|
||||
)
|
||||
|
||||
// Expand fulfills CategoryExpander
|
||||
func (e SimpleCategoryExpander) Expand(category string) ([]schema.GroupResource, bool) {
|
||||
ret, ok := e.Expansions[category]
|
||||
return ret, ok
|
||||
}
|
||||
|
||||
// ExpandWithContext fulfills CategoryExpanderWithContext
|
||||
func (e SimpleCategoryExpander) ExpandWithContext(ctx context.Context, category string) ([]schema.GroupResource, bool) {
|
||||
return e.Expand(category)
|
||||
}
|
||||
|
||||
// discoveryCategoryExpander struct lets a REST Client wrapper (discoveryClient) to retrieve list of APIResourceList,
|
||||
// and then convert to fallbackExpander
|
||||
type discoveryCategoryExpander struct {
|
||||
discoveryClient discovery.DiscoveryInterface
|
||||
discoveryClient discovery.DiscoveryInterfaceWithContext
|
||||
}
|
||||
|
||||
// NewDiscoveryCategoryExpander returns a category expander that makes use of the "categories" fields from
|
||||
// the API, found through the discovery client. In case of any error or no category found (which likely
|
||||
// means we're at a cluster prior to categories support, fallback to the expander provided.
|
||||
//
|
||||
// NewDiscoveryCategoryExpanderWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use NewDiscoveryCategoryExpanderWithContext instead.
|
||||
func NewDiscoveryCategoryExpander(client discovery.DiscoveryInterface) CategoryExpander {
|
||||
return newDiscoveryCategoryExpander(discovery.ToDiscoveryInterfaceWithContext(client))
|
||||
}
|
||||
|
||||
// NewDiscoveryCategoryExpanderWithContext returns a category expander that makes use of the "categories" fields from
|
||||
// the API, found through the discovery client. In case of any error or no category found (which likely
|
||||
// means we're at a cluster prior to categories support, fallback to the expander provided.
|
||||
func NewDiscoveryCategoryExpanderWithContext(client discovery.DiscoveryInterfaceWithContext) CategoryExpanderWithContext {
|
||||
return newDiscoveryCategoryExpander(client)
|
||||
}
|
||||
|
||||
func newDiscoveryCategoryExpander(client discovery.DiscoveryInterfaceWithContext) discoveryCategoryExpander {
|
||||
if client == nil {
|
||||
panic("Please provide discovery client to shortcut expander")
|
||||
}
|
||||
@@ -56,9 +115,18 @@ func NewDiscoveryCategoryExpander(client discovery.DiscoveryInterface) CategoryE
|
||||
}
|
||||
|
||||
// Expand fulfills CategoryExpander
|
||||
//
|
||||
// ExpandWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ExpandWithContext instead.
|
||||
func (e discoveryCategoryExpander) Expand(category string) ([]schema.GroupResource, bool) {
|
||||
return e.ExpandWithContext(context.Background(), category)
|
||||
}
|
||||
|
||||
// ExpandWithContext fulfills CategoryExpanderWithContext
|
||||
func (e discoveryCategoryExpander) ExpandWithContext(ctx context.Context, category string) ([]schema.GroupResource, bool) {
|
||||
// Get all supported resources for groups and versions from server, if no resource found, fallback anyway.
|
||||
_, apiResourceLists, _ := e.discoveryClient.ServerGroupsAndResources()
|
||||
_, apiResourceLists, _ := e.discoveryClient.ServerGroupsAndResourcesWithContext(ctx)
|
||||
if len(apiResourceLists) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
@@ -89,16 +157,43 @@ func (e discoveryCategoryExpander) Expand(category string) ([]schema.GroupResour
|
||||
|
||||
// UnionCategoryExpander implements CategoryExpander interface.
|
||||
// It maps given category string to union of expansions returned by all the CategoryExpanders in the list.
|
||||
//
|
||||
// UnionCategoryExpanderWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use UnionCategoryExpanderWithContext instead.
|
||||
type UnionCategoryExpander []CategoryExpander
|
||||
|
||||
var _ CategoryExpander = UnionCategoryExpander{}
|
||||
|
||||
// Expand fulfills CategoryExpander
|
||||
func (u UnionCategoryExpander) Expand(category string) ([]schema.GroupResource, bool) {
|
||||
return unionExpand(context.Background(), func(yield func(i int, expander CategoryExpanderWithContext) bool) {
|
||||
for i, expander := range u {
|
||||
if !yield(i, ToCategoryExpanderWithContext(expander)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}, category)
|
||||
}
|
||||
|
||||
// UnionCategoryExpanderWithContext implements CategoryExpanderWithContext interface.
|
||||
// It maps given category string to union of expansions returned by all the CategoryExpanders in the list.
|
||||
type UnionCategoryExpanderWithContext []CategoryExpanderWithContext
|
||||
|
||||
var _ CategoryExpanderWithContext = UnionCategoryExpanderWithContext{}
|
||||
|
||||
// ExpandWithContext fulfills CategoryExpanderWithContext
|
||||
func (u UnionCategoryExpanderWithContext) ExpandWithContext(ctx context.Context, category string) ([]schema.GroupResource, bool) {
|
||||
return unionExpand(ctx, slices.All(u), category)
|
||||
}
|
||||
|
||||
func unionExpand(ctx context.Context, expanders iter.Seq2[int, CategoryExpanderWithContext], category string) ([]schema.GroupResource, bool) {
|
||||
ret := []schema.GroupResource{}
|
||||
ok := false
|
||||
|
||||
// Expand the category for each CategoryExpander in the list and merge/combine the results.
|
||||
for _, expansion := range u {
|
||||
curr, currOk := expansion.Expand(category)
|
||||
for _, expansion := range expanders {
|
||||
curr, currOk := expansion.ExpandWithContext(ctx, category)
|
||||
|
||||
for _, currGR := range curr {
|
||||
found := false
|
||||
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package restmapper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -40,8 +41,40 @@ type APIGroupResources struct {
|
||||
|
||||
// NewDiscoveryRESTMapper returns a PriorityRESTMapper based on the discovered
|
||||
// groups and resources passed in.
|
||||
//
|
||||
// NewDiscoveryRESTMapperWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use NewDiscoveryRESTMapperWithContext instead.
|
||||
func NewDiscoveryRESTMapper(groupResources []*APIGroupResources) meta.RESTMapper {
|
||||
unionMapper := meta.MultiRESTMapper{}
|
||||
mappers, resourcePriority, kindPriority := newDiscoveryRESTMapper(groupResources)
|
||||
multiMapper := make(meta.MultiRESTMapper, len(mappers))
|
||||
for i, m := range mappers {
|
||||
multiMapper[i] = m
|
||||
}
|
||||
return &meta.PriorityRESTMapper{
|
||||
Delegate: multiMapper,
|
||||
ResourcePriority: resourcePriority,
|
||||
KindPriority: kindPriority,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDiscoveryRESTMapperWithContext returns a PriorityRESTMapper based on the discovered
|
||||
// groups and resources passed in.
|
||||
func NewDiscoveryRESTMapperWithContext(groupResources []*APIGroupResources) meta.RESTMapperWithContext {
|
||||
mappers, resourcePriority, kindPriority := newDiscoveryRESTMapper(groupResources)
|
||||
multiMapper := make(meta.MultiRESTMapperWithContext, len(mappers))
|
||||
for i, m := range mappers {
|
||||
multiMapper[i] = m
|
||||
}
|
||||
return &meta.PriorityRESTMapperWithContext{
|
||||
Delegate: multiMapper,
|
||||
ResourcePriority: resourcePriority,
|
||||
KindPriority: kindPriority,
|
||||
}
|
||||
}
|
||||
|
||||
func newDiscoveryRESTMapper(groupResources []*APIGroupResources) ([]*meta.DefaultRESTMapper, []schema.GroupVersionResource, []schema.GroupVersionKind) {
|
||||
var unionMapper []*meta.DefaultRESTMapper
|
||||
|
||||
var groupPriority []string
|
||||
// /v1 is special. It should always come first
|
||||
@@ -135,17 +168,23 @@ func NewDiscoveryRESTMapper(groupResources []*APIGroupResources) meta.RESTMapper
|
||||
})
|
||||
}
|
||||
|
||||
return meta.PriorityRESTMapper{
|
||||
Delegate: unionMapper,
|
||||
ResourcePriority: resourcePriority,
|
||||
KindPriority: kindPriority,
|
||||
}
|
||||
return unionMapper, resourcePriority, kindPriority
|
||||
}
|
||||
|
||||
// GetAPIGroupResources uses the provided discovery client to gather
|
||||
// discovery information and populate a slice of APIGroupResources.
|
||||
//
|
||||
// GetAPIGroupResourcesWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use GetAPIGroupResourcesWithContext instead.
|
||||
func GetAPIGroupResources(cl discovery.DiscoveryInterface) ([]*APIGroupResources, error) {
|
||||
gs, rs, err := cl.ServerGroupsAndResources()
|
||||
return GetAPIGroupResourcesWithContext(context.Background(), discovery.ToDiscoveryInterfaceWithContext(cl))
|
||||
}
|
||||
|
||||
// GetAPIGroupResourcesWithContext uses the provided discovery client to gather
|
||||
// discovery information and populate a slice of APIGroupResources.
|
||||
func GetAPIGroupResourcesWithContext(ctx context.Context, cl discovery.DiscoveryInterfaceWithContext) ([]*APIGroupResources, error) {
|
||||
gs, rs, err := cl.ServerGroupsAndResourcesWithContext(ctx)
|
||||
if rs == nil || gs == nil {
|
||||
return nil, err
|
||||
// TODO track the errors and update callers to handle partial errors.
|
||||
@@ -173,25 +212,45 @@ func GetAPIGroupResources(cl discovery.DiscoveryInterface) ([]*APIGroupResources
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// DeferredDiscoveryRESTMapper is a RESTMapper that will defer
|
||||
// DeferredDiscoveryRESTMapper is a RESTMapper and RESTMapperContext that will defer
|
||||
// initialization of the RESTMapper until the first mapping is
|
||||
// requested.
|
||||
type DeferredDiscoveryRESTMapper struct {
|
||||
initMu sync.Mutex
|
||||
delegate meta.RESTMapper
|
||||
cl discovery.CachedDiscoveryInterface
|
||||
delegate meta.RESTMapperWithContext
|
||||
cl discovery.CachedDiscoveryInterfaceWithContext
|
||||
}
|
||||
|
||||
var (
|
||||
//nolint:staticcheck // Intentionally using the old interface here.
|
||||
_ meta.ResettableRESTMapper = &DeferredDiscoveryRESTMapper{}
|
||||
_ meta.ResettableRESTMapperWithContext = &DeferredDiscoveryRESTMapper{}
|
||||
_ fmt.Stringer = &DeferredDiscoveryRESTMapper{}
|
||||
)
|
||||
|
||||
// NewDeferredDiscoveryRESTMapper returns a
|
||||
// DeferredDiscoveryRESTMapper that will lazily query the provided
|
||||
// client for discovery information to do REST mappings.
|
||||
//
|
||||
// NewDeferredDiscoveryRESTMapperWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use NewDeferredDiscoveryRESTMapperWithContext instead. NewDeferredDiscoveryRESTMapper will try to convert cl to discovery.CachedDiscoveryInterfaceWithContext and use a wrapper if that is not possible, but NewDeferredDiscoveryRESTMapperWithContext ensures that no such conversion is necessary.
|
||||
func NewDeferredDiscoveryRESTMapper(cl discovery.CachedDiscoveryInterface) *DeferredDiscoveryRESTMapper {
|
||||
return &DeferredDiscoveryRESTMapper{
|
||||
cl: discovery.ToCachedDiscoveryInterfaceWithContext(cl),
|
||||
}
|
||||
}
|
||||
|
||||
// NewDeferredDiscoveryRESTMapperWithContext returns a
|
||||
// DeferredDiscoveryRESTMapper that will lazily query the provided
|
||||
// client for discovery information to do REST mappings.
|
||||
func NewDeferredDiscoveryRESTMapperWithContext(cl discovery.CachedDiscoveryInterfaceWithContext) *DeferredDiscoveryRESTMapper {
|
||||
return &DeferredDiscoveryRESTMapper{
|
||||
cl: cl,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DeferredDiscoveryRESTMapper) getDelegate() (meta.RESTMapper, error) {
|
||||
func (d *DeferredDiscoveryRESTMapper) getDelegate(ctx context.Context) (meta.RESTMapperWithContext, error) {
|
||||
d.initMu.Lock()
|
||||
defer d.initMu.Unlock()
|
||||
|
||||
@@ -199,98 +258,154 @@ func (d *DeferredDiscoveryRESTMapper) getDelegate() (meta.RESTMapper, error) {
|
||||
return d.delegate, nil
|
||||
}
|
||||
|
||||
groupResources, err := GetAPIGroupResources(d.cl)
|
||||
groupResources, err := GetAPIGroupResourcesWithContext(ctx, d.cl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.delegate = NewDiscoveryRESTMapper(groupResources)
|
||||
d.delegate = NewDiscoveryRESTMapperWithContext(groupResources)
|
||||
return d.delegate, nil
|
||||
}
|
||||
|
||||
// Reset resets the internally cached Discovery information and will
|
||||
// cause the next mapping request to re-discover.
|
||||
func (d *DeferredDiscoveryRESTMapper) Reset() {
|
||||
klog.V(5).Info("Invalidating discovery information")
|
||||
d.ResetWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ResetWithContext resets the internally cached Discovery information and will
|
||||
// cause the next mapping request to re-discover.
|
||||
func (d *DeferredDiscoveryRESTMapper) ResetWithContext(ctx context.Context) {
|
||||
klog.FromContext(ctx).V(5).Info("Invalidating discovery information")
|
||||
|
||||
d.initMu.Lock()
|
||||
defer d.initMu.Unlock()
|
||||
|
||||
d.cl.Invalidate()
|
||||
d.cl.InvalidateWithContext(ctx)
|
||||
d.delegate = nil
|
||||
}
|
||||
|
||||
// KindFor takes a partial resource and returns back the single match.
|
||||
// It returns an error if there are multiple matches.
|
||||
//
|
||||
// KindForWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use KindForWithContext instead.
|
||||
func (d *DeferredDiscoveryRESTMapper) KindFor(resource schema.GroupVersionResource) (gvk schema.GroupVersionKind, err error) {
|
||||
del, err := d.getDelegate()
|
||||
return d.KindForWithContext(context.Background(), resource)
|
||||
}
|
||||
|
||||
// KindForWithContext takes a partial resource and returns back the single match.
|
||||
// It returns an error if there are multiple matches.
|
||||
func (d *DeferredDiscoveryRESTMapper) KindForWithContext(ctx context.Context, resource schema.GroupVersionResource) (gvk schema.GroupVersionKind, err error) {
|
||||
del, err := d.getDelegate(ctx)
|
||||
if err != nil {
|
||||
return schema.GroupVersionKind{}, err
|
||||
}
|
||||
gvk, err = del.KindFor(resource)
|
||||
if err != nil && !d.cl.Fresh() {
|
||||
d.Reset()
|
||||
gvk, err = d.KindFor(resource)
|
||||
gvk, err = del.KindForWithContext(ctx, resource)
|
||||
if err != nil && !d.cl.FreshWithContext(ctx) {
|
||||
d.ResetWithContext(ctx)
|
||||
gvk, err = d.KindForWithContext(ctx, resource)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// KindsFor takes a partial resource and returns back the list of
|
||||
// potential kinds in priority order.
|
||||
//
|
||||
// KindsForWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use KindsForWithContext instead.
|
||||
func (d *DeferredDiscoveryRESTMapper) KindsFor(resource schema.GroupVersionResource) (gvks []schema.GroupVersionKind, err error) {
|
||||
del, err := d.getDelegate()
|
||||
return d.KindsForWithContext(context.Background(), resource)
|
||||
}
|
||||
|
||||
// KindsForWithContext takes a partial resource and returns back the list of
|
||||
// potential kinds in priority order.
|
||||
func (d *DeferredDiscoveryRESTMapper) KindsForWithContext(ctx context.Context, resource schema.GroupVersionResource) (gvks []schema.GroupVersionKind, err error) {
|
||||
del, err := d.getDelegate(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gvks, err = del.KindsFor(resource)
|
||||
if len(gvks) == 0 && !d.cl.Fresh() {
|
||||
d.Reset()
|
||||
gvks, err = d.KindsFor(resource)
|
||||
gvks, err = del.KindsForWithContext(ctx, resource)
|
||||
if len(gvks) == 0 && !d.cl.FreshWithContext(ctx) {
|
||||
d.ResetWithContext(ctx)
|
||||
gvks, err = d.KindsForWithContext(ctx, resource)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ResourceFor takes a partial resource and returns back the single
|
||||
// match. It returns an error if there are multiple matches.
|
||||
//
|
||||
// ResourceForWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ResourceForWithContext instead.
|
||||
func (d *DeferredDiscoveryRESTMapper) ResourceFor(input schema.GroupVersionResource) (gvr schema.GroupVersionResource, err error) {
|
||||
del, err := d.getDelegate()
|
||||
return d.ResourceForWithContext(context.Background(), input)
|
||||
}
|
||||
|
||||
// ResourceForWithContext takes a partial resource and returns back the single
|
||||
// match. It returns an error if there are multiple matches.
|
||||
func (d *DeferredDiscoveryRESTMapper) ResourceForWithContext(ctx context.Context, input schema.GroupVersionResource) (gvr schema.GroupVersionResource, err error) {
|
||||
del, err := d.getDelegate(ctx)
|
||||
if err != nil {
|
||||
return schema.GroupVersionResource{}, err
|
||||
}
|
||||
gvr, err = del.ResourceFor(input)
|
||||
if err != nil && !d.cl.Fresh() {
|
||||
d.Reset()
|
||||
gvr, err = d.ResourceFor(input)
|
||||
gvr, err = del.ResourceForWithContext(ctx, input)
|
||||
if err != nil && !d.cl.FreshWithContext(ctx) {
|
||||
d.ResetWithContext(ctx)
|
||||
gvr, err = d.ResourceForWithContext(ctx, input)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ResourcesFor takes a partial resource and returns back the list of
|
||||
// potential resource in priority order.
|
||||
//
|
||||
// ResourcesForWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ResourcesForWithContext instead.
|
||||
func (d *DeferredDiscoveryRESTMapper) ResourcesFor(input schema.GroupVersionResource) (gvrs []schema.GroupVersionResource, err error) {
|
||||
del, err := d.getDelegate()
|
||||
return d.ResourcesForWithContext(context.Background(), input)
|
||||
}
|
||||
|
||||
// ResourcesForWithContext takes a partial resource and returns back the list of
|
||||
// potential resource in priority order.
|
||||
func (d *DeferredDiscoveryRESTMapper) ResourcesForWithContext(ctx context.Context, input schema.GroupVersionResource) (gvrs []schema.GroupVersionResource, err error) {
|
||||
del, err := d.getDelegate(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gvrs, err = del.ResourcesFor(input)
|
||||
if len(gvrs) == 0 && !d.cl.Fresh() {
|
||||
d.Reset()
|
||||
gvrs, err = d.ResourcesFor(input)
|
||||
gvrs, err = del.ResourcesForWithContext(ctx, input)
|
||||
if len(gvrs) == 0 && !d.cl.FreshWithContext(ctx) {
|
||||
d.ResetWithContext(ctx)
|
||||
gvrs, err = d.ResourcesForWithContext(ctx, input)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// RESTMapping identifies a preferred resource mapping for the
|
||||
// provided group kind.
|
||||
//
|
||||
// RESTMappingWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use RESTMappingWithContext instead.
|
||||
func (d *DeferredDiscoveryRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (m *meta.RESTMapping, err error) {
|
||||
del, err := d.getDelegate()
|
||||
return d.RESTMappingWithContext(context.Background(), gk, versions...)
|
||||
}
|
||||
|
||||
// RESTMappingWithContext identifies a preferred resource mapping for the
|
||||
// provided group kind.
|
||||
func (d *DeferredDiscoveryRESTMapper) RESTMappingWithContext(ctx context.Context, gk schema.GroupKind, versions ...string) (m *meta.RESTMapping, err error) {
|
||||
del, err := d.getDelegate(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m, err = del.RESTMapping(gk, versions...)
|
||||
if err != nil && !d.cl.Fresh() {
|
||||
d.Reset()
|
||||
m, err = d.RESTMapping(gk, versions...)
|
||||
m, err = del.RESTMappingWithContext(ctx, gk, versions...)
|
||||
if err != nil && !d.cl.FreshWithContext(ctx) {
|
||||
d.ResetWithContext(ctx)
|
||||
m, err = d.RESTMappingWithContext(ctx, gk, versions...)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -298,41 +413,59 @@ func (d *DeferredDiscoveryRESTMapper) RESTMapping(gk schema.GroupKind, versions
|
||||
// RESTMappings returns the RESTMappings for the provided group kind
|
||||
// in a rough internal preferred order. If no kind is found, it will
|
||||
// return a NoResourceMatchError.
|
||||
//
|
||||
// RESTMappingsWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use RESTMappingsWithContext instead.
|
||||
func (d *DeferredDiscoveryRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) (ms []*meta.RESTMapping, err error) {
|
||||
del, err := d.getDelegate()
|
||||
return d.RESTMappingsWithContext(context.Background(), gk, versions...)
|
||||
}
|
||||
|
||||
// RESTMappingsWithContext returns the RESTMappings for the provided group kind
|
||||
// in a rough internal preferred order. If no kind is found, it will
|
||||
// return a NoResourceMatchError.
|
||||
func (d *DeferredDiscoveryRESTMapper) RESTMappingsWithContext(ctx context.Context, gk schema.GroupKind, versions ...string) (ms []*meta.RESTMapping, err error) {
|
||||
del, err := d.getDelegate(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ms, err = del.RESTMappings(gk, versions...)
|
||||
if len(ms) == 0 && !d.cl.Fresh() {
|
||||
d.Reset()
|
||||
ms, err = d.RESTMappings(gk, versions...)
|
||||
ms, err = del.RESTMappingsWithContext(ctx, gk, versions...)
|
||||
if len(ms) == 0 && !d.cl.FreshWithContext(ctx) {
|
||||
d.ResetWithContext(ctx)
|
||||
ms, err = d.RESTMappingsWithContext(ctx, gk, versions...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ResourceSingularizer converts a resource name from plural to
|
||||
// singular (e.g., from pods to pod).
|
||||
//
|
||||
// ResourceSingularizerWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ResourceSingularizerWithContext instead.
|
||||
func (d *DeferredDiscoveryRESTMapper) ResourceSingularizer(resource string) (singular string, err error) {
|
||||
del, err := d.getDelegate()
|
||||
return d.ResourceSingularizerWithContext(context.Background(), resource)
|
||||
}
|
||||
|
||||
// ResourceSingularizerWithContext converts a resource name from plural to
|
||||
// singular (e.g., from pods to pod).
|
||||
func (d *DeferredDiscoveryRESTMapper) ResourceSingularizerWithContext(ctx context.Context, resource string) (singular string, err error) {
|
||||
del, err := d.getDelegate(ctx)
|
||||
if err != nil {
|
||||
return resource, err
|
||||
}
|
||||
singular, err = del.ResourceSingularizer(resource)
|
||||
if err != nil && !d.cl.Fresh() {
|
||||
d.Reset()
|
||||
singular, err = d.ResourceSingularizer(resource)
|
||||
singular, err = del.ResourceSingularizerWithContext(ctx, resource)
|
||||
if err != nil && !d.cl.FreshWithContext(ctx) {
|
||||
d.ResetWithContext(ctx)
|
||||
singular, err = d.ResourceSingularizerWithContext(ctx, resource)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *DeferredDiscoveryRESTMapper) String() string {
|
||||
del, err := d.getDelegate()
|
||||
del, err := d.getDelegate(context.Background() /* hopefully we already have a delegate and don't need the context */)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("DeferredDiscoveryRESTMapper{%v}", err)
|
||||
}
|
||||
return fmt.Sprintf("DeferredDiscoveryRESTMapper{\n\t%v\n}", del)
|
||||
}
|
||||
|
||||
// Make sure it satisfies the interface
|
||||
var _ meta.ResettableRESTMapper = &DeferredDiscoveryRESTMapper{}
|
||||
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package restmapper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -30,22 +31,41 @@ import (
|
||||
|
||||
// shortcutExpander is a RESTMapper that can be used for Kubernetes resources. It expands the resource first, then invokes the wrapped
|
||||
type shortcutExpander struct {
|
||||
RESTMapper meta.RESTMapper
|
||||
RESTMapper meta.RESTMapperWithContext
|
||||
|
||||
discoveryClient discovery.DiscoveryInterface
|
||||
discoveryClient discovery.DiscoveryInterfaceWithContext
|
||||
|
||||
warningHandler func(string)
|
||||
}
|
||||
|
||||
var _ meta.ResettableRESTMapper = shortcutExpander{}
|
||||
var _ meta.ResettableRESTMapperWithContext = shortcutExpander{}
|
||||
|
||||
// NewShortcutExpander wraps a restmapper in a layer that expands shortcuts found via discovery
|
||||
//
|
||||
// NewShortcutExpanderWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use NewShortcutExpanderWithContext instead.
|
||||
func NewShortcutExpander(delegate meta.RESTMapper, client discovery.DiscoveryInterface, warningHandler func(string)) meta.RESTMapper {
|
||||
return newShortcutExpander(meta.ToRESTMapperWithContext(delegate), discovery.ToDiscoveryInterfaceWithContext(client), warningHandler)
|
||||
}
|
||||
|
||||
// NewShortcutExpanderWithContext wraps a restmapper in a layer that expands shortcuts found via discovery
|
||||
func NewShortcutExpanderWithContext(delegate meta.RESTMapperWithContext, client discovery.DiscoveryInterfaceWithContext, warningHandler func(string)) meta.RESTMapperWithContext {
|
||||
return newShortcutExpander(delegate, client, warningHandler)
|
||||
}
|
||||
|
||||
func newShortcutExpander(delegate meta.RESTMapperWithContext, client discovery.DiscoveryInterfaceWithContext, warningHandler func(string)) shortcutExpander {
|
||||
return shortcutExpander{RESTMapper: delegate, discoveryClient: client, warningHandler: warningHandler}
|
||||
}
|
||||
|
||||
// KindFor fulfills meta.RESTMapper
|
||||
func (e shortcutExpander) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) {
|
||||
return e.KindForWithContext(context.Background(), resource)
|
||||
}
|
||||
|
||||
// KindForWithContext fulfills meta.RESTMapperWithContext
|
||||
func (e shortcutExpander) KindForWithContext(ctx context.Context, resource schema.GroupVersionResource) (schema.GroupVersionKind, error) {
|
||||
// expandResourceShortcut works with current API resources as read from discovery cache.
|
||||
// In case of new CRDs this means we potentially don't have current state of discovery.
|
||||
// In the current wiring in k8s.io/cli-runtime/pkg/genericclioptions/config_flags.go#toRESTMapper,
|
||||
@@ -53,59 +73,113 @@ func (e shortcutExpander) KindFor(resource schema.GroupVersionResource) (schema.
|
||||
// cache and fetch all data from a cluster (see k8s.io/client-go/restmapper/discovery.go#KindFor).
|
||||
// Thus another call to expandResourceShortcut, after a NoMatchError should successfully
|
||||
// read Kind to the user or an error.
|
||||
gvk, err := e.RESTMapper.KindFor(e.expandResourceShortcut(resource))
|
||||
gvk, err := e.RESTMapper.KindForWithContext(ctx, e.expandResourceShortcut(ctx, resource))
|
||||
if meta.IsNoMatchError(err) {
|
||||
return e.RESTMapper.KindFor(e.expandResourceShortcut(resource))
|
||||
return e.RESTMapper.KindForWithContext(ctx, e.expandResourceShortcut(ctx, resource))
|
||||
}
|
||||
return gvk, err
|
||||
}
|
||||
|
||||
// KindsFor fulfills meta.RESTMapper
|
||||
//
|
||||
// KindsForWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use KindsForWithContext instead.
|
||||
func (e shortcutExpander) KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) {
|
||||
return e.RESTMapper.KindsFor(e.expandResourceShortcut(resource))
|
||||
return e.KindsForWithContext(context.Background(), resource)
|
||||
}
|
||||
|
||||
// KindsForWithContext fulfills meta.RESTMapperWithContext
|
||||
func (e shortcutExpander) KindsForWithContext(ctx context.Context, resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) {
|
||||
return e.RESTMapper.KindsForWithContext(ctx, e.expandResourceShortcut(ctx, resource))
|
||||
}
|
||||
|
||||
// ResourcesFor fulfills meta.RESTMapper
|
||||
//
|
||||
// ResourcesForWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ResourcesForWithContext instead.
|
||||
func (e shortcutExpander) ResourcesFor(resource schema.GroupVersionResource) ([]schema.GroupVersionResource, error) {
|
||||
return e.RESTMapper.ResourcesFor(e.expandResourceShortcut(resource))
|
||||
return e.ResourcesForWithContext(context.Background(), resource)
|
||||
}
|
||||
|
||||
// ResourcesForWithContext fulfills meta.RESTMapperWithContext
|
||||
func (e shortcutExpander) ResourcesForWithContext(ctx context.Context, resource schema.GroupVersionResource) ([]schema.GroupVersionResource, error) {
|
||||
return e.RESTMapper.ResourcesForWithContext(ctx, e.expandResourceShortcut(ctx, resource))
|
||||
}
|
||||
|
||||
// ResourceFor fulfills meta.RESTMapper
|
||||
//
|
||||
// ResourceForWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ResourceForWithContext instead.
|
||||
func (e shortcutExpander) ResourceFor(resource schema.GroupVersionResource) (schema.GroupVersionResource, error) {
|
||||
return e.RESTMapper.ResourceFor(e.expandResourceShortcut(resource))
|
||||
return e.ResourceForWithContext(context.Background(), resource)
|
||||
}
|
||||
|
||||
// ResourceForWithContext fulfills meta.RESTMapperWithContext
|
||||
func (e shortcutExpander) ResourceForWithContext(ctx context.Context, resource schema.GroupVersionResource) (schema.GroupVersionResource, error) {
|
||||
return e.RESTMapper.ResourceForWithContext(ctx, e.expandResourceShortcut(ctx, resource))
|
||||
}
|
||||
|
||||
// ResourceSingularizer fulfills meta.RESTMapper
|
||||
//
|
||||
// ResourceSingularizerWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ResourceSingularizerWithContext instead.
|
||||
func (e shortcutExpander) ResourceSingularizer(resource string) (string, error) {
|
||||
return e.RESTMapper.ResourceSingularizer(e.expandResourceShortcut(schema.GroupVersionResource{Resource: resource}).Resource)
|
||||
return e.ResourceSingularizerWithContext(context.Background(), resource)
|
||||
}
|
||||
|
||||
// ResourceSingularizerWithContext fulfills meta.RESTMapperWithContext
|
||||
func (e shortcutExpander) ResourceSingularizerWithContext(ctx context.Context, resource string) (string, error) {
|
||||
return e.RESTMapper.ResourceSingularizerWithContext(ctx, e.expandResourceShortcut(ctx, schema.GroupVersionResource{Resource: resource}).Resource)
|
||||
}
|
||||
|
||||
// RESTMapping fulfills meta.RESTMapper
|
||||
//
|
||||
// RESTMappingWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use RESTMappingWithContext instead.
|
||||
func (e shortcutExpander) RESTMapping(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) {
|
||||
return e.RESTMapper.RESTMapping(gk, versions...)
|
||||
return e.RESTMappingWithContext(context.Background(), gk, versions...)
|
||||
}
|
||||
|
||||
// RESTMappingWithContext fulfills meta.RESTMapperWithContext
|
||||
func (e shortcutExpander) RESTMappingWithContext(ctx context.Context, gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) {
|
||||
return e.RESTMapper.RESTMappingWithContext(ctx, gk, versions...)
|
||||
}
|
||||
|
||||
// RESTMappings fulfills meta.RESTMapper
|
||||
//
|
||||
// RESTMappingsWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use RESTMappingsWithContext instead.
|
||||
func (e shortcutExpander) RESTMappings(gk schema.GroupKind, versions ...string) ([]*meta.RESTMapping, error) {
|
||||
return e.RESTMapper.RESTMappings(gk, versions...)
|
||||
return e.RESTMappingsWithContext(context.Background(), gk, versions...)
|
||||
}
|
||||
|
||||
// RESTMappingsWithContext fulfills meta.RESTMapperWithContext
|
||||
func (e shortcutExpander) RESTMappingsWithContext(ctx context.Context, gk schema.GroupKind, versions ...string) ([]*meta.RESTMapping, error) {
|
||||
return e.RESTMapper.RESTMappingsWithContext(ctx, gk, versions...)
|
||||
}
|
||||
|
||||
// getShortcutMappings returns a set of tuples which holds short names for resources.
|
||||
// First the list of potential resources will be taken from the API server.
|
||||
// Next we will append the hardcoded list of resources - to be backward compatible with old servers.
|
||||
// NOTE that the list is ordered by group priority.
|
||||
func (e shortcutExpander) getShortcutMappings() ([]*metav1.APIResourceList, []resourceShortcuts, error) {
|
||||
func (e shortcutExpander) getShortcutMappings(ctx context.Context) ([]*metav1.APIResourceList, []resourceShortcuts, error) {
|
||||
res := []resourceShortcuts{}
|
||||
// get server resources
|
||||
// This can return an error *and* the results it was able to find. We don't need to fail on the error.
|
||||
_, apiResList, err := e.discoveryClient.ServerGroupsAndResources()
|
||||
_, apiResList, err := e.discoveryClient.ServerGroupsAndResourcesWithContext(ctx)
|
||||
if err != nil {
|
||||
klog.V(1).Infof("Error loading discovery information: %v", err)
|
||||
klog.FromContext(ctx).V(1).Info("Error loading discovery information", "err", err)
|
||||
}
|
||||
for _, apiResources := range apiResList {
|
||||
gv, err := schema.ParseGroupVersion(apiResources.GroupVersion)
|
||||
if err != nil {
|
||||
klog.V(1).Infof("Unable to parse groupversion = %s due to = %s", apiResources.GroupVersion, err.Error())
|
||||
klog.FromContext(ctx).V(1).Info("Unable to parse group/version", "groupVersion", apiResources.GroupVersion, "err", err.Error())
|
||||
continue
|
||||
}
|
||||
for _, apiRes := range apiResources.APIResources {
|
||||
@@ -126,9 +200,9 @@ func (e shortcutExpander) getShortcutMappings() ([]*metav1.APIResourceList, []re
|
||||
// (something that a pkg/api/meta.RESTMapper can understand), if it is
|
||||
// indeed a shortcut. If no match has been found, we will match on group prefixing.
|
||||
// Lastly we will return resource unmodified.
|
||||
func (e shortcutExpander) expandResourceShortcut(resource schema.GroupVersionResource) schema.GroupVersionResource {
|
||||
func (e shortcutExpander) expandResourceShortcut(ctx context.Context, resource schema.GroupVersionResource) schema.GroupVersionResource {
|
||||
// get the shortcut mappings and return on first match.
|
||||
if allResources, shortcutResources, err := e.getShortcutMappings(); err == nil {
|
||||
if allResources, shortcutResources, err := e.getShortcutMappings(ctx); err == nil {
|
||||
// avoid expanding if there's an exact match to a full resource name
|
||||
for _, apiResources := range allResources {
|
||||
gv, err := schema.ParseGroupVersion(apiResources.GroupVersion)
|
||||
@@ -199,8 +273,15 @@ func (e shortcutExpander) expandResourceShortcut(resource schema.GroupVersionRes
|
||||
return resource
|
||||
}
|
||||
|
||||
// ResetWithContext is a better alternative because it supports contextual logging and cancellation.
|
||||
//
|
||||
// Contextual logging: Use ResetWithContext instead.
|
||||
func (e shortcutExpander) Reset() {
|
||||
meta.MaybeResetRESTMapper(e.RESTMapper)
|
||||
e.ResetWithContext(context.Background())
|
||||
}
|
||||
|
||||
func (e shortcutExpander) ResetWithContext(ctx context.Context) {
|
||||
meta.MaybeResetRESTMapperWithContext(ctx, e.RESTMapper)
|
||||
}
|
||||
|
||||
// ResourceShortcuts represents a structure that holds the information how to
|
||||
|
||||
@@ -31,9 +31,12 @@ import (
|
||||
"k8s.io/client-go/openapi"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
"k8s.io/klog/v2/ktesting"
|
||||
)
|
||||
|
||||
func TestReplaceAliases(t *testing.T) {
|
||||
_, ctx := ktesting.NewTestContext(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
arg string
|
||||
@@ -135,7 +138,7 @@ func TestReplaceAliases(t *testing.T) {
|
||||
}
|
||||
mapper := NewShortcutExpander(&fakeRESTMapper{}, ds, nil).(shortcutExpander)
|
||||
|
||||
actual := mapper.expandResourceShortcut(schema.GroupVersionResource{Resource: test.arg})
|
||||
actual := mapper.expandResourceShortcut(ctx, schema.GroupVersionResource{Resource: test.arg})
|
||||
if actual != test.expected {
|
||||
t.Errorf("%s: unexpected argument: expected %s, got %s", test.name, test.expected, actual)
|
||||
}
|
||||
@@ -260,6 +263,8 @@ func TestKindForWithNewCRDs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWarnAmbigious(t *testing.T) {
|
||||
_, ctx := ktesting.NewTestContext(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
arg string
|
||||
@@ -439,7 +444,7 @@ func TestWarnAmbigious(t *testing.T) {
|
||||
actualWarnings = append(actualWarnings, a)
|
||||
}).(shortcutExpander)
|
||||
|
||||
actual := mapper.expandResourceShortcut(schema.GroupVersionResource{Resource: test.arg})
|
||||
actual := mapper.expandResourceShortcut(ctx, schema.GroupVersionResource{Resource: test.arg})
|
||||
if actual != test.expected {
|
||||
t.Errorf("%s: unexpected argument: expected %s, got %s", test.name, test.expected, actual)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user