package restmapper import ( "sync" kapi "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/client/typed/discovery" ) type discoveryRESTMapper struct { discoveryClient discovery.DiscoveryInterface delegate meta.RESTMapper initLock sync.Mutex } // NewDiscoveryRESTMapper that initializes using the discovery APIs, relying on group ordering and preferred versions // to build its appropriate priorities. Only versions are registered with API machinery are added now. // TODO make this work with generic resources at some point. For now, this handles enabled and disabled resources cleanly. func NewDiscoveryRESTMapper(discoveryClient discovery.DiscoveryInterface) meta.RESTMapper { return &discoveryRESTMapper{discoveryClient: discoveryClient} } func (d *discoveryRESTMapper) getDelegate() (meta.RESTMapper, error) { d.initLock.Lock() defer d.initLock.Unlock() if d.delegate != nil { return d.delegate, nil } serverGroups, err := d.discoveryClient.ServerGroups() if err != nil { return nil, err } // always prefer our default group for now. The version should be discovered from discovery, but this will hold us // for quite some time. resourcePriority := []unversioned.GroupVersionResource{ {Group: kapi.GroupName, Version: meta.AnyVersion, Resource: meta.AnyResource}, } kindPriority := []unversioned.GroupVersionKind{ {Group: kapi.GroupName, Version: meta.AnyVersion, Kind: meta.AnyKind}, } groupPriority := []string{} unionMapper := meta.MultiRESTMapper{} for _, group := range serverGroups.Groups { if len(group.Versions) == 0 { continue } groupPriority = append(groupPriority, group.Name) if len(group.PreferredVersion.Version) != 0 { preferredVersion := unversioned.GroupVersion{Group: group.Name, Version: group.PreferredVersion.Version} if registered.IsEnabledVersion(preferredVersion) { resourcePriority = append(resourcePriority, preferredVersion.WithResource(meta.AnyResource)) kindPriority = append(kindPriority, preferredVersion.WithKind(meta.AnyKind)) } } for _, discoveryVersion := range group.Versions { version := unversioned.GroupVersion{Group: group.Name, Version: discoveryVersion.Version} if !registered.IsEnabledVersion(version) { continue } groupMeta, err := registered.Group(group.Name) if err != nil { return nil, err } resources, err := d.discoveryClient.ServerResourcesForGroupVersion(version.String()) if err != nil { return nil, err } versionMapper := meta.NewDefaultRESTMapper([]unversioned.GroupVersion{version}, groupMeta.InterfacesFor) for _, resource := range resources.APIResources { // TODO properly handle resource versus kind gvk := version.WithKind(resource.Kind) scope := meta.RESTScopeNamespace if !resource.Namespaced { scope = meta.RESTScopeRoot } versionMapper.Add(gvk, scope) // TODO formalize this by checking to see if they support listing versionMapper.Add(version.WithKind(resource.Kind+"List"), scope) } // we need to add List. Its a special case of something we need that isn't in the discovery doc if group.Name == kapi.GroupName { versionMapper.Add(version.WithKind("List"), meta.RESTScopeNamespace) } unionMapper = append(unionMapper, versionMapper) } } for _, group := range groupPriority { resourcePriority = append(resourcePriority, unversioned.GroupVersionResource{Group: group, Version: meta.AnyVersion, Resource: meta.AnyResource}) kindPriority = append(kindPriority, unversioned.GroupVersionKind{Group: group, Version: meta.AnyVersion, Kind: meta.AnyKind}) } d.delegate = meta.PriorityRESTMapper{Delegate: unionMapper, ResourcePriority: resourcePriority, KindPriority: kindPriority} return d.delegate, nil } func (d *discoveryRESTMapper) KindFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionKind, error) { delegate, err := d.getDelegate() if err != nil { return unversioned.GroupVersionKind{}, err } return delegate.KindFor(resource) } func (d *discoveryRESTMapper) KindsFor(resource unversioned.GroupVersionResource) ([]unversioned.GroupVersionKind, error) { delegate, err := d.getDelegate() if err != nil { return nil, err } return delegate.KindsFor(resource) } func (d *discoveryRESTMapper) ResourceFor(input unversioned.GroupVersionResource) (unversioned.GroupVersionResource, error) { delegate, err := d.getDelegate() if err != nil { return unversioned.GroupVersionResource{}, err } return delegate.ResourceFor(input) } func (d *discoveryRESTMapper) ResourcesFor(input unversioned.GroupVersionResource) ([]unversioned.GroupVersionResource, error) { delegate, err := d.getDelegate() if err != nil { return nil, err } return delegate.ResourcesFor(input) } func (d *discoveryRESTMapper) RESTMapping(gk unversioned.GroupKind, versions ...string) (*meta.RESTMapping, error) { delegate, err := d.getDelegate() if err != nil { return nil, err } return delegate.RESTMapping(gk, versions...) } func (d *discoveryRESTMapper) RESTMappings(gk unversioned.GroupKind) ([]*meta.RESTMapping, error) { delegate, err := d.getDelegate() if err != nil { return nil, err } return delegate.RESTMappings(gk) } func (d *discoveryRESTMapper) AliasesForResource(resource string) ([]string, bool) { delegate, err := d.getDelegate() if err != nil { return nil, false } return delegate.AliasesForResource(resource) } func (d *discoveryRESTMapper) ResourceSingularizer(resource string) (singular string, err error) { delegate, err := d.getDelegate() if err != nil { return resource, err } return delegate.ResourceSingularizer(resource) }