145 lines
4.7 KiB
Go
145 lines
4.7 KiB
Go
package restmapper
|
|
|
|
import (
|
|
"net/http"
|
|
"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"
|
|
)
|
|
|
|
// 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
|
|
discoveryClient discovery.DiscoveryInterface
|
|
gvkToGVR sync.Map
|
|
// mu is used to provide thread-safe mapper reloading.
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
func (g *cachedRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) {
|
|
return g.getMapper().KindFor(resource)
|
|
}
|
|
|
|
func (g *cachedRESTMapper) KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) {
|
|
return g.getMapper().KindsFor(resource)
|
|
}
|
|
|
|
func (g *cachedRESTMapper) ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, error) {
|
|
return g.getMapper().ResourceFor(input)
|
|
}
|
|
|
|
func (g *cachedRESTMapper) ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) {
|
|
return g.getMapper().ResourcesFor(input)
|
|
}
|
|
|
|
func (g *cachedRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*meta.RESTMapping, error) {
|
|
return g.getMapper().RESTMappings(gk, versions...)
|
|
}
|
|
|
|
func (g *cachedRESTMapper) ResourceSingularizer(resource string) (singular string, err error) {
|
|
return g.getMapper().ResourceSingularizer(resource)
|
|
}
|
|
|
|
func (g *cachedRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) {
|
|
// in case of multi-versions or no 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.getMapper().RESTMapping(gk, versions...)
|
|
}
|
|
|
|
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.getMapper().RESTMapping(gk, versions...)
|
|
if meta.IsNoMatchError(err) {
|
|
// hit here means a resource might be missing from the current rest mapper,
|
|
// probably because a new resource(CRD) has been added, we have to reload
|
|
// resource and rebuild the rest mapper.
|
|
|
|
var groupResources []*restmapper.APIGroupResources
|
|
groupResources, err = restmapper.GetAPIGroupResources(g.discoveryClient)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
newMapper := restmapper.NewDiscoveryRESTMapper(groupResources)
|
|
restMapping, err = newMapper.RESTMapping(gk, versions...)
|
|
if err == nil {
|
|
// hit here means after reloading, the new rest mapper can recognize
|
|
// the resource, we have to replace the mapper and clear cache.
|
|
g.mu.Lock()
|
|
g.restMapper = newMapper
|
|
g.mu.Unlock()
|
|
g.gvkToGVR.Range(func(key, value any) bool {
|
|
g.gvkToGVR.Delete(key)
|
|
return true
|
|
})
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
return restMapping, err
|
|
}
|
|
g.gvkToGVR.Store(gvk, restMapping)
|
|
|
|
return restMapping, nil
|
|
}
|
|
|
|
func (g *cachedRESTMapper) getMapper() meta.RESTMapper {
|
|
g.mu.RLock()
|
|
defer g.mu.RUnlock()
|
|
return g.restMapper
|
|
}
|
|
|
|
// NewCachedRESTMapper builds a cachedRESTMapper with a customized underlyingMapper.
|
|
// If underlyingMapper is nil, defaults to DiscoveryRESTMapper.
|
|
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
|
|
}
|
|
|
|
// loading current resources for building a base rest mapper.
|
|
groupResources, err := restmapper.GetAPIGroupResources(client)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cachedMapper.restMapper = restmapper.NewDiscoveryRESTMapper(groupResources)
|
|
cachedMapper.discoveryClient = client
|
|
|
|
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, _ *http.Client) (meta.RESTMapper, error) {
|
|
return NewCachedRESTMapper(c, nil)
|
|
}
|