diff --git a/pkg/restmapper/caching.go b/pkg/restmapper/caching.go index 10e4a469..88b9f7d8 100644 --- a/pkg/restmapper/caching.go +++ b/pkg/restmapper/caching.go @@ -8,6 +8,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/discovery" "k8s.io/klog/v2" @@ -17,6 +18,7 @@ import ( // cache is our cache of schema information. type cache struct { mutex sync.Mutex + groups map[string]metav1.APIGroup groupVersions map[schema.GroupVersion]*cachedGroupVersion } @@ -27,6 +29,34 @@ func newCache() *cache { } } +// findGroupInfo returns the APIGroup for the specified group, querying discovery if not cached. +// If not found, returns APIGroup{}, false, nil +func (c *cache) findGroupInfo(ctx context.Context, discovery discovery.DiscoveryInterface, groupName string) (metav1.APIGroup, bool, error) { + log := log.FromContext(ctx) + + c.mutex.Lock() + defer c.mutex.Unlock() + + if c.groups == nil { + log.Info("discovering server groups") + serverGroups, err := discovery.ServerGroups() + if err != nil { + klog.Infof("unexpected error from ServerGroups: %v", err) + return metav1.APIGroup{}, false, fmt.Errorf("error from ServerGroups: %w", err) + } + + groups := make(map[string]metav1.APIGroup) + for i := range serverGroups.Groups { + group := &serverGroups.Groups[i] + groups[group.Name] = *group + } + c.groups = groups + } + + group, found := c.groups[groupName] + return group, found, nil +} + // cachedGroupVersion caches (all) the resource information for a particular groupversion. type cachedGroupVersion struct { gv schema.GroupVersion @@ -88,7 +118,7 @@ func (c *cachedGroupVersion) fetch(ctx context.Context, discovery discovery.Disc if meta.IsNoMatchError(err) || apierrors.IsNotFound(err) { return nil, nil } else { - klog.Infof("unexpected error from ServerResourcesForGroupVersion(%v): %w", c.gv, err) + klog.Infof("unexpected error from ServerResourcesForGroupVersion(%v): %v", c.gv, err) return nil, fmt.Errorf("error from ServerResourcesForGroupVersion(%v): %w", c.gv, err) } } diff --git a/pkg/restmapper/controllerrestmapper.go b/pkg/restmapper/controllerrestmapper.go index 3c3c65e5..eca395dd 100644 --- a/pkg/restmapper/controllerrestmapper.go +++ b/pkg/restmapper/controllerrestmapper.go @@ -35,22 +35,22 @@ var _ meta.RESTMapper = &ControllerRESTMapper{} // KindFor takes a partial resource and returns the single match. Returns an error if there are multiple matches func (m *ControllerRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) { - return schema.GroupVersionKind{}, fmt.Errorf("ControllerRESTMaper does not support KindFor operation") + return schema.GroupVersionKind{}, fmt.Errorf("ControllerRESTMapper does not support KindFor operation") } // KindsFor takes a partial resource and returns the list of potential kinds in priority order func (m *ControllerRESTMapper) KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) { - return nil, fmt.Errorf("ControllerRESTMaper does not support KindsFor operation") + return nil, fmt.Errorf("ControllerRESTMapper does not support KindsFor operation") } // ResourceFor takes a partial resource and returns the single match. Returns an error if there are multiple matches func (m *ControllerRESTMapper) ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, error) { - return schema.GroupVersionResource{}, fmt.Errorf("ControllerRESTMaper does not support ResourceFor operation") + return schema.GroupVersionResource{}, fmt.Errorf("ControllerRESTMapper does not support ResourceFor operation") } // ResourcesFor takes a partial resource and returns the list of potential resource in priority order func (m *ControllerRESTMapper) ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) { - return nil, fmt.Errorf("ControllerRESTMaper does not support ResourcesFor operation") + return nil, fmt.Errorf("ControllerRESTMapper does not support ResourcesFor operation") } // RESTMapping identifies a preferred resource mapping for the provided group kind. @@ -75,9 +75,53 @@ func (m *ControllerRESTMapper) RESTMapping(gk schema.GroupKind, versions ...stri // version search is provided. Otherwise identifies a preferred resource mapping for // the provided version(s). func (m *ControllerRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*meta.RESTMapping, error) { - return nil, fmt.Errorf("ControllerRESTMaper does not support RESTMappings operation") + ctx := context.TODO() + + if len(versions) != 0 { + return nil, fmt.Errorf("ControllerRESTMapper does not support RESTMappings operation with specified versions") + } + + group, found, err := m.cache.findGroupInfo(ctx, m.uncached, gk.Group) + if err != nil { + return nil, err + } + if !found { + return nil, &meta.NoResourceMatchError{PartialResource: schema.GroupVersionResource{Group: gk.Group, Resource: gk.Kind}} + } + + var mappings []*meta.RESTMapping + + if group.PreferredVersion.Version != "" { + gv := schema.GroupVersion{Group: gk.Group, Version: group.PreferredVersion.Version} + mapping, err := m.cache.findRESTMapping(ctx, m.uncached, gv, gk.Kind) + if err != nil { + return nil, err + } + if mapping != nil { + mappings = append(mappings, mapping) + } + } + + for i := range group.Versions { + gv := schema.GroupVersion{Group: gk.Group, Version: group.Versions[i].Version} + if gv.Version == group.PreferredVersion.Version { + continue + } + mapping, err := m.cache.findRESTMapping(ctx, m.uncached, gv, gk.Kind) + if err != nil { + return nil, err + } + if mapping != nil { + mappings = append(mappings, mapping) + } + } + + if len(mappings) == 0 { + return nil, &meta.NoResourceMatchError{PartialResource: schema.GroupVersionResource{Group: gk.Group, Resource: gk.Kind}} + } + return mappings, nil } func (m *ControllerRESTMapper) ResourceSingularizer(resource string) (singular string, err error) { - return "", fmt.Errorf("ControllerRESTMaper does not support ResourceSingularizer operation") + return "", fmt.Errorf("ControllerRESTMapper does not support ResourceSingularizer operation") }