From 53ab079c38eeb2c58706fe4715ee74c381a3ebd8 Mon Sep 17 00:00:00 2001 From: nferraro Date: Wed, 18 Sep 2019 13:20:36 +0200 Subject: [PATCH] Fix #792: making CLI super-fast --- cmd/builder/main.go | 2 +- pkg/client/client.go | 12 +++++-- pkg/client/fastmapper.go | 70 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 pkg/client/fastmapper.go diff --git a/cmd/builder/main.go b/cmd/builder/main.go index 2a20e0e82c..e526aedd38 100644 --- a/cmd/builder/main.go +++ b/cmd/builder/main.go @@ -54,7 +54,7 @@ func main() { rand.Seed(time.Now().UTC().UnixNano()) printVersion() - c, err := client.NewClient() + c, err := client.NewClient(false) exitOnError(err) ctx := cancellable.NewContext() diff --git a/pkg/client/client.go b/pkg/client/client.go index f800fb68d5..ddb833229c 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -23,6 +23,7 @@ import ( "os/user" "path/filepath" + "k8s.io/apimachinery/pkg/api/meta" "github.com/apache/camel-k/pkg/apis" "github.com/operator-framework/operator-sdk/pkg/k8sutil" "github.com/pkg/errors" @@ -68,11 +69,12 @@ func (c *defaultClient) GetScheme() *runtime.Scheme { // NewOutOfClusterClient creates a new k8s client that can be used from outside the cluster func NewOutOfClusterClient(kubeconfig string) (Client, error) { initialize(kubeconfig) - return NewClient() + // using fast discovery from outside the cluster + return NewClient(true) } // NewClient creates a new k8s client that can be used from outside or in the cluster -func NewClient() (Client, error) { +func NewClient(fastDiscovery bool) (Client, error) { // Get a config to talk to the apiserver cfg, err := config.GetConfig() if err != nil { @@ -91,9 +93,15 @@ func NewClient() (Client, error) { return nil, err } + var mapper meta.RESTMapper + if fastDiscovery { + mapper = newFastDiscoveryRESTMapper(cfg) + } + // Create a new client to avoid using cache (enabled by default on operator-sdk client) clientOptions := controller.Options{ Scheme: scheme, + Mapper: mapper, } dynClient, err := controller.New(cfg, clientOptions) if err != nil { diff --git a/pkg/client/fastmapper.go b/pkg/client/fastmapper.go new file mode 100644 index 0000000000..2752953891 --- /dev/null +++ b/pkg/client/fastmapper.go @@ -0,0 +1,70 @@ +package client + +import ( + "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/discovery" + "k8s.io/client-go/rest" + "k8s.io/client-go/restmapper" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// allowedAPIGroups contains a set of API groups that are allowed when using the fastmapper. +// Those must correspond to all groups used by the "kamel" binary tool when running out-of-cluster. +var allowedAPIGroups = map[string]bool{ + "": true, // core APIs + "apiextensions.k8s.io": true, + "apps": true, + "camel.apache.org": true, + "rbac.authorization.k8s.io": true, +} + +func newFastDiscoveryRESTMapper(config *rest.Config) meta.RESTMapper { + return meta.NewLazyRESTMapperLoader(func() (meta.RESTMapper, error) { + return newFastDiscoveryRESTMapperWithFilter(config, func(g *metav1.APIGroup) bool { + return allowedAPIGroups[g.Name] + }) + }) +} + +func newFastDiscoveryRESTMapperWithFilter(config *rest.Config, filter func(*metav1.APIGroup) bool) (meta.RESTMapper, error) { + dc := discovery.NewDiscoveryClientForConfigOrDie(config) + groups, err := dc.ServerGroups() + if err != nil { + return nil, err + } + wg := wait.Group{} + totalCount := 0 + pickedCount := 0 + var grs []*restmapper.APIGroupResources + for _, group := range groups.Groups { + pick := filter(&group) + logrus.Debugf("Group: %s %v", group.Name, pick) + totalCount++ + if !pick { + continue + } + pickedCount++ + gr := &restmapper.APIGroupResources{ + Group: group, + VersionedResources: make(map[string][]metav1.APIResource), + } + grs = append(grs, gr) + wg.Start(func() { discoverGroupResources(dc, gr) }) + } + wg.Wait() + logrus.Debugf("Picked %d/%d", pickedCount, totalCount) + return restmapper.NewDiscoveryRESTMapper(grs), nil +} + +func discoverGroupResources(dc discovery.DiscoveryInterface, gr *restmapper.APIGroupResources) { + for _, version := range gr.Group.Versions { + resources, err := dc.ServerResourcesForGroupVersion(version.GroupVersion) + if err != nil { + logrus.Fatal(err, version.GroupVersion) + } + gr.VersionedResources[version.Version] = resources.APIResources + } +}