karmada/pkg/util/restmapper/restmapper.go

121 lines
3.9 KiB
Go

package restmapper
import (
"fmt"
"sync"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/rest"
"k8s.io/client-go/restmapper"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
)
// GetGroupVersionResource is a helper to map GVK(schema.GroupVersionKind) to GVR(schema.GroupVersionResource).
func GetGroupVersionResource(restMapper meta.RESTMapper, gvk schema.GroupVersionKind) (schema.GroupVersionResource, error) {
restMapping, err := restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if err != nil {
return schema.GroupVersionResource{}, err
}
return restMapping.Resource, nil
}
// cachedRESTMapper caches the previous result to accelerate subsequent queries.
// Note: now the acceleration applies only to RESTMapping() which is heavily used by Karmada.
type cachedRESTMapper struct {
restMapper meta.RESTMapper
gvkToGVR sync.Map
}
func (g *cachedRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) {
return g.restMapper.KindFor(resource)
}
func (g *cachedRESTMapper) KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) {
return g.restMapper.KindsFor(resource)
}
func (g *cachedRESTMapper) ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, error) {
return g.restMapper.ResourceFor(input)
}
func (g *cachedRESTMapper) ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) {
return g.restMapper.ResourcesFor(input)
}
func (g *cachedRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*meta.RESTMapping, error) {
return g.restMapper.RESTMappings(gk, versions...)
}
func (g *cachedRESTMapper) ResourceSingularizer(resource string) (singular string, err error) {
return g.restMapper.ResourceSingularizer(resource)
}
func (g *cachedRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) {
// in case of multi-versions, cachedRESTMapper don't know which is the preferred version,
// so just bypass the cache and consult the underlying mapper.
if len(versions) > 1 {
return g.restMapper.RESTMapping(gk, versions...)
}
if len(versions) == 0 {
return nil, fmt.Errorf("expected at least one version")
}
gvk := gk.WithVersion(versions[0])
value, ok := g.gvkToGVR.Load(gvk)
if ok { // hit cache, just return
return value.(*meta.RESTMapping), nil
}
// consult underlying mapper and then update cache
restMapping, err := g.restMapper.RESTMapping(gk, versions...)
if err != nil {
return restMapping, err
}
g.gvkToGVR.Store(gvk, restMapping)
return restMapping, nil
}
// NewCachedRESTMapper builds a cachedRESTMapper with a customized underlyingMapper.
// If underlyingMapper is nil, defaults to DynamicRESTMapper.
func NewCachedRESTMapper(cfg *rest.Config, underlyingMapper meta.RESTMapper) (meta.RESTMapper, error) {
cachedMapper := cachedRESTMapper{}
// short path, build with customized underlying mapper.
if underlyingMapper != nil {
cachedMapper.restMapper = underlyingMapper
return &cachedMapper, nil
}
client, err := discovery.NewDiscoveryClientForConfig(cfg)
if err != nil {
return nil, err
}
option := apiutil.WithCustomMapper(func() (meta.RESTMapper, error) {
groupResources, err := restmapper.GetAPIGroupResources(client)
if err != nil {
return nil, err
}
// clear the cache map when reloading DiscoveryRESTMapper
cachedMapper.gvkToGVR = sync.Map{}
return restmapper.NewDiscoveryRESTMapper(groupResources), nil
})
underlyingMapper, err = apiutil.NewDynamicRESTMapper(cfg, option)
if err != nil {
return nil, err
}
cachedMapper.restMapper = underlyingMapper
return &cachedMapper, nil
}
// MapperProvider is a wrapper of cachedRESTMapper which is typically used
// to generate customized RESTMapper for controller-runtime framework.
func MapperProvider(c *rest.Config) (meta.RESTMapper, error) {
return NewCachedRESTMapper(c, nil)
}