From 500cddc5c3e2ac55825e0ade708810afc5eebc05 Mon Sep 17 00:00:00 2001 From: deads2k Date: Fri, 9 Sep 2016 15:39:44 -0400 Subject: [PATCH] use discovery restmapper for kubectl --- pkg/api/meta/restmapper.go | 4 +- pkg/apimachinery/registered/registered.go | 10 ++ pkg/kubectl/cmd/util/factory.go | 167 ++++++++++++---------- 3 files changed, 102 insertions(+), 79 deletions(-) diff --git a/pkg/api/meta/restmapper.go b/pkg/api/meta/restmapper.go index 29fdaaa35e3..19d0b252071 100644 --- a/pkg/api/meta/restmapper.go +++ b/pkg/api/meta/restmapper.go @@ -526,7 +526,7 @@ func (m *DefaultRESTMapper) RESTMapping(gk unversioned.GroupKind, versions ...st interfaces, err := m.interfacesFunc(gvk.GroupVersion()) if err != nil { - return nil, fmt.Errorf("the provided version %q has no relevant versions", gvk.GroupVersion().String()) + return nil, fmt.Errorf("the provided version %q has no relevant versions: %v", gvk.GroupVersion().String(), err) } retVal := &RESTMapping{ @@ -565,7 +565,7 @@ func (m *DefaultRESTMapper) RESTMappings(gk unversioned.GroupKind) ([]*RESTMappi interfaces, err := m.interfacesFunc(gvk.GroupVersion()) if err != nil { - return nil, fmt.Errorf("the provided version %q has no relevant versions", gvk.GroupVersion().String()) + return nil, fmt.Errorf("the provided version %q has no relevant versions: %v", gvk.GroupVersion().String(), err) } mappings = append(mappings, &RESTMapping{ diff --git a/pkg/apimachinery/registered/registered.go b/pkg/apimachinery/registered/registered.go index 3d3a5c8ff8f..72aa3db17f7 100644 --- a/pkg/apimachinery/registered/registered.go +++ b/pkg/apimachinery/registered/registered.go @@ -111,6 +111,7 @@ var ( EnableVersions = DefaultAPIRegistrationManager.EnableVersions RegisterGroup = DefaultAPIRegistrationManager.RegisterGroup RegisterVersions = DefaultAPIRegistrationManager.RegisterVersions + InterfacesFor = DefaultAPIRegistrationManager.InterfacesFor ) // RegisterVersions adds the given group versions to the list of registered group versions. @@ -265,6 +266,15 @@ func (m *APIRegistrationManager) AddThirdPartyAPIGroupVersions(gvs ...unversione return skippedGVs } +// InterfacesFor is a union meta.VersionInterfacesFunc func for all registered types +func (m *APIRegistrationManager) InterfacesFor(version unversioned.GroupVersion) (*meta.VersionInterfaces, error) { + groupMeta, err := m.Group(version.Group) + if err != nil { + return nil, err + } + return groupMeta.InterfacesFor(version) +} + // TODO: This is an expedient function, because we don't check if a Group is // supported throughout the code base. We will abandon this function and // checking the error returned by the Group() function. diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go index ec84e0ac123..187423e08c3 100644 --- a/pkg/kubectl/cmd/util/factory.go +++ b/pkg/kubectl/cmd/util/factory.go @@ -32,6 +32,7 @@ import ( "strings" "time" + "github.com/blang/semver" "github.com/emicklei/go-restful/swagger" "github.com/imdario/mergo" "github.com/spf13/cobra" @@ -47,9 +48,9 @@ import ( "k8s.io/kubernetes/pkg/apimachinery" "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/apis/apps" - "k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" + extensionsv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" "k8s.io/kubernetes/pkg/client/restclient" @@ -278,8 +279,6 @@ func makeInterfacesFor(versionList []unversioned.GroupVersion) func(version unve // if optionalClientConfig is nil, then flags will be bound to a new clientcmd.ClientConfig. // if optionalClientConfig is not nil, then this factory will make use of it. func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { - mapper := kubectl.ShortcutExpander{RESTMapper: registered.RESTMapper()} - flags := pflag.NewFlagSet("", pflag.ContinueOnError) flags.SetNormalizeFunc(utilflag.WarnWordSepNormalizeFunc) // Warn for "_" flags @@ -294,8 +293,6 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { clients: clients, flags: flags, - // If discoverDynamicAPIs is true, make API calls to the discovery service to find APIs that - // have been dynamically added to the apiserver Object: func(discoverDynamicAPIs bool) (meta.RESTMapper, runtime.ObjectTyper) { cfg, err := clientConfig.ClientConfig() checkErrWithPrefix("failed to get client config: ", err) @@ -303,82 +300,33 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { if cfg.GroupVersion != nil { cmdApiVersion = *cfg.GroupVersion } - if discoverDynamicAPIs { - clientset, err := clients.ClientSetForVersion(&unversioned.GroupVersion{Version: "v1"}) - checkErrWithPrefix("failed to find client for version v1: ", err) - var versions []unversioned.GroupVersion - var gvks []unversioned.GroupVersionKind - retries := 3 - for i := 0; i < retries; i++ { - versions, gvks, err = GetThirdPartyGroupVersions(clientset.Discovery()) - // Retry if we got a NotFound error, because user may delete - // a thirdparty group when the GetThirdPartyGroupVersions is - // running. - if err == nil || !apierrors.IsNotFound(err) { - break + mapper := registered.RESTMapper() + // if we can find the server version and it's current enough to have discovery information, use it. Otherwise, + // fallback to our hardcoded list + if discoveryClient, err := discovery.NewDiscoveryClientForConfig(cfg); err == nil { + if serverVersion, err := discoveryClient.ServerVersion(); err == nil && useDiscoveryRESTMapper(serverVersion.GitVersion) { + // register third party resources with the api machinery groups. This probably should be done, but + // its consistent with old code, so we'll start with it. + if err := registerThirdPartyResources(discoveryClient); err != nil { + fmt.Fprintf(os.Stderr, "Unable to register third party resources: %v\n", err) + } + // ThirdPartyResourceData is special. It's not discoverable, but needed for thirdparty resource listing + // TODO eliminate this once we're truly generic. + thirdPartyResourceDataMapper := meta.NewDefaultRESTMapper([]unversioned.GroupVersion{extensionsv1beta1.SchemeGroupVersion}, registered.InterfacesFor) + thirdPartyResourceDataMapper.Add(extensionsv1beta1.SchemeGroupVersion.WithKind("ThirdPartyResourceData"), meta.RESTScopeNamespace) + mapper = meta.MultiRESTMapper{ + discovery.NewDeferredDiscoveryRESTMapper(discoveryClient, registered.InterfacesFor), + thirdPartyResourceDataMapper, } } - checkErrWithPrefix("failed to get third-party group versions: ", err) - if len(versions) > 0 { - priorityMapper, ok := mapper.RESTMapper.(meta.PriorityRESTMapper) - if !ok { - CheckErr(fmt.Errorf("expected PriorityMapper, saw: %v", mapper.RESTMapper)) - return nil, nil - } - multiMapper, ok := priorityMapper.Delegate.(meta.MultiRESTMapper) - if !ok { - CheckErr(fmt.Errorf("unexpected type: %v", mapper.RESTMapper)) - return nil, nil - } - groupsMap := map[string][]unversioned.GroupVersion{} - for _, version := range versions { - groupsMap[version.Group] = append(groupsMap[version.Group], version) - } - for group, versionList := range groupsMap { - preferredExternalVersion := versionList[0] + } - thirdPartyMapper, err := kubectl.NewThirdPartyResourceMapper(versionList, getGroupVersionKinds(gvks, group)) - checkErrWithPrefix("failed to create third party resource mapper: ", err) - accessor := meta.NewAccessor() - groupMeta := apimachinery.GroupMeta{ - GroupVersion: preferredExternalVersion, - GroupVersions: versionList, - RESTMapper: thirdPartyMapper, - SelfLinker: runtime.SelfLinker(accessor), - InterfacesFor: makeInterfacesFor(versionList), - } - - checkErrWithPrefix("failed to register group: ", registered.RegisterGroup(groupMeta)) - registered.AddThirdPartyAPIGroupVersions(versionList...) - multiMapper = append(meta.MultiRESTMapper{thirdPartyMapper}, multiMapper...) - } - priorityMapper.Delegate = multiMapper - // Reassign to the RESTMapper here because priorityMapper is actually a copy, so if we - // don't reassign, the above assignement won't actually update mapper.RESTMapper - mapper.RESTMapper = priorityMapper - } - } - outputRESTMapper := kubectl.OutputVersionMapper{RESTMapper: mapper, OutputVersions: []unversioned.GroupVersion{cmdApiVersion}} - priorityRESTMapper := meta.PriorityRESTMapper{ - Delegate: outputRESTMapper, - } - // TODO: this should come from registered versions - groups := []string{api.GroupName, autoscaling.GroupName, extensions.GroupName, federation.GroupName, batch.GroupName} - // set a preferred version - for _, group := range groups { - gvs := registered.EnabledVersionsForGroup(group) - if len(gvs) == 0 { - continue - } - priorityRESTMapper.ResourcePriority = append(priorityRESTMapper.ResourcePriority, unversioned.GroupVersionResource{Group: group, Version: gvs[0].Version, Resource: meta.AnyResource}) - priorityRESTMapper.KindPriority = append(priorityRESTMapper.KindPriority, unversioned.GroupVersionKind{Group: group, Version: gvs[0].Version, Kind: meta.AnyKind}) - } - for _, group := range groups { - priorityRESTMapper.ResourcePriority = append(priorityRESTMapper.ResourcePriority, unversioned.GroupVersionResource{Group: group, Version: meta.AnyVersion, Resource: meta.AnyResource}) - priorityRESTMapper.KindPriority = append(priorityRESTMapper.KindPriority, unversioned.GroupVersionKind{Group: group, Version: meta.AnyVersion, Kind: meta.AnyKind}) - } - return priorityRESTMapper, api.Scheme + // wrap with shortcuts + mapper = kubectl.ShortcutExpander{RESTMapper: mapper} + // wrap with output preferences + mapper = kubectl.OutputVersionMapper{RESTMapper: mapper, OutputVersions: []unversioned.GroupVersion{cmdApiVersion}} + return mapper, api.Scheme }, UnstructuredObject: func() (meta.RESTMapper, runtime.ObjectTyper, error) { cfg, err := clients.ClientConfigForVersion(nil) @@ -1305,3 +1253,68 @@ func (f *Factory) NewBuilder(thirdPartyDiscovery bool) *resource.Builder { return resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)) } + +// useDiscoveryRESTMapper checks the server version to see if its recent enough to have +// enough discovery information available to reliably build a RESTMapper. If not, use the +// hardcoded mapper in this client (legacy behavior) +func useDiscoveryRESTMapper(serverVersion string) bool { + if len(serverVersion) == 0 { + return false + } + serverSemVer, err := semver.Parse(serverVersion[1:]) + if err != nil { + return false + } + return serverSemVer.GE(semver.MustParse("1.3.0")) +} + +// registerThirdPartyResources inspects the discovery endpoint to find thirdpartyresources in the discovery doc +// and then registers them with the apimachinery code. I think this is done so that scheme/codec stuff works, +// but I really don't know. Feels like this code should go away once kubectl is completely generic for generic +// CRUD +func registerThirdPartyResources(discoveryClient discovery.DiscoveryInterface) error { + var versions []unversioned.GroupVersion + var gvks []unversioned.GroupVersionKind + var err error + retries := 3 + for i := 0; i < retries; i++ { + versions, gvks, err = GetThirdPartyGroupVersions(discoveryClient) + // Retry if we got a NotFound error, because user may delete + // a thirdparty group when the GetThirdPartyGroupVersions is + // running. + if err == nil || !apierrors.IsNotFound(err) { + break + } + } + if err != nil { + return err + } + + groupsMap := map[string][]unversioned.GroupVersion{} + for _, version := range versions { + groupsMap[version.Group] = append(groupsMap[version.Group], version) + } + for group, versionList := range groupsMap { + preferredExternalVersion := versionList[0] + + thirdPartyMapper, err := kubectl.NewThirdPartyResourceMapper(versionList, getGroupVersionKinds(gvks, group)) + if err != nil { + return err + } + + accessor := meta.NewAccessor() + groupMeta := apimachinery.GroupMeta{ + GroupVersion: preferredExternalVersion, + GroupVersions: versionList, + RESTMapper: thirdPartyMapper, + SelfLinker: runtime.SelfLinker(accessor), + InterfacesFor: makeInterfacesFor(versionList), + } + if err := registered.RegisterGroup(groupMeta); err != nil { + return err + } + registered.AddThirdPartyAPIGroupVersions(versionList...) + } + + return nil +}