From 89514e7245e24d98f021b4c5a52bc9c6e0b6e7ce Mon Sep 17 00:00:00 2001 From: Zadkiel Aharonian Date: Fri, 3 Feb 2023 22:35:59 +0100 Subject: [PATCH] feat: add namespace and context to API Resources method (#410) * feat: Add namespace and context to API Resources method Signed-off-by: GitHub * Update root.go * Update helm.go --------- Signed-off-by: GitHub Co-authored-by: Robert Brennan Co-authored-by: Stevie <4719798+transient1@users.noreply.github.com> --- cmd/root.go | 60 +++++++++---------- pkg/discovery-api/discovery_api.go | 48 +++++---------- pkg/helm/helm.go | 11 ++-- pkg/helm/helm_test.go | 14 ++++- pkg/{helm => kube}/kube.go | 41 +++++++++---- pkg/{helm => kube}/kube_test.go | 11 +--- pkg/{helm => kube}/testdata/kubeconfig | 0 .../testdata/kubeconfig_invalid | 0 8 files changed, 93 insertions(+), 92 deletions(-) rename pkg/{helm => kube}/kube.go (63%) rename pkg/{helm => kube}/kube_test.go (84%) rename pkg/{helm => kube}/testdata/kubeconfig (100%) rename pkg/{helm => kube}/testdata/kubeconfig_invalid (100%) diff --git a/cmd/root.go b/cmd/root.go index 65312e69..239b5f59 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -99,11 +99,13 @@ func init() { detectHelmCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "", "Only detect releases in a specific namespace.") detectHelmCmd.PersistentFlags().StringVar(&kubeContext, "kube-context", "", "The kube context to use. If blank, defaults to current context.") + rootCmd.AddCommand(detectApiResourceCmd) + detectApiResourceCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "", "Only detect resources in a specific namespace.") + detectApiResourceCmd.PersistentFlags().StringVar(&kubeContext, "kube-context", "", "The kube context to use. If blank, defaults to current context.") + rootCmd.AddCommand(listVersionsCmd) rootCmd.AddCommand(detectCmd) - rootCmd.AddCommand(detectApiResourceCmd) - klog.InitFlags(nil) pflag.CommandLine.AddGoFlag(flag.CommandLine.Lookup("v")) } @@ -286,14 +288,12 @@ var detectFilesCmd = &cobra.Command{ Short: "detect-files", Long: `Detect Kubernetes apiVersions in a directory.`, Run: func(cmd *cobra.Command, args []string) { - dir := finder.NewFinder(directory, apiInstance) err := dir.FindVersions() if err != nil { fmt.Println("Error running finder:", err) os.Exit(1) } - err = apiInstance.DisplayOutput() if err != nil { fmt.Println("Error Parsing Output:", err) @@ -319,7 +319,32 @@ var detectHelmCmd = &cobra.Command{ fmt.Println("Error running helm-detect:", err) os.Exit(1) } + err = apiInstance.DisplayOutput() + if err != nil { + fmt.Println("Error Parsing Output:", err) + os.Exit(1) + } + retCode := apiInstance.GetReturnCode() + klog.V(5).Infof("retCode: %d", retCode) + os.Exit(retCode) + }, +} +var detectApiResourceCmd = &cobra.Command{ + Use: "detect-api-resources", + Short: "detect-api-resources", + Long: `Detect Kubernetes apiVersions from an active cluster (using last-applied-configuration annotation)`, + Run: func(cmd *cobra.Command, args []string) { + disCl, err := discoveryapi.NewDiscoveryClient(namespace, kubeContext, apiInstance) + if err != nil { + fmt.Println("Error creating Discovery REST Client: ", err) + os.Exit(1) + } + err = disCl.GetApiResources() + if err != nil { + fmt.Println("Error getting API resources using discovery client:", err) + os.Exit(1) + } err = apiInstance.DisplayOutput() if err != nil { fmt.Println("Error Parsing Output:", err) @@ -403,33 +428,6 @@ var listVersionsCmd = &cobra.Command{ }, } -var detectApiResourceCmd = &cobra.Command{ - Use: "detect-api-resources", - Short: "detect-api-resources", - Long: `Detect Kubernetes apiVersions from an active cluster.`, - Run: func(cmd *cobra.Command, args []string) { - - disCl, err := discoveryapi.NewDiscoveryClient(apiInstance) - if err != nil { - fmt.Println("Error creating Discovery REST Client: ", err) - os.Exit(1) - } - err = disCl.GetApiResources() - if err != nil { - fmt.Println("Error getting API resources using discovery client:", err) - os.Exit(1) - } - - err = apiInstance.DisplayOutput() - if err != nil { - fmt.Println("Error Parsing Output:", err) - os.Exit(1) - } - exitCode = apiInstance.GetReturnCode() - klog.V(5).Infof("retCode: %d", exitCode) - }, -} - // Execute the stuff func Execute(VERSION string, COMMIT string, versionsFile []byte) { version = VERSION diff --git a/pkg/discovery-api/discovery_api.go b/pkg/discovery-api/discovery_api.go index e1ca36c1..aaf4c534 100644 --- a/pkg/discovery-api/discovery_api.go +++ b/pkg/discovery-api/discovery_api.go @@ -38,10 +38,10 @@ import ( "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" "k8s.io/klog/v2" "github.com/fairwindsops/pluto/v5/pkg/api" + kube "github.com/fairwindsops/pluto/v5/pkg/kube" ) // DiscoveryClient is the declaration to hold objects needed for client-go/discovery. @@ -50,16 +50,17 @@ type DiscoveryClient struct { restConfig *rest.Config DiscoveryClient discovery.DiscoveryInterface Instance *api.Instance + namespace string } // NewDiscoveryClient returns a new struct with config portions complete. -func NewDiscoveryClient(instance *api.Instance) (*DiscoveryClient, error) { +func NewDiscoveryClient(namespace string, kubeContext string, instance *api.Instance) (*DiscoveryClient, error) { cl := &DiscoveryClient{ Instance: instance, } var err error - cl.restConfig, err = NewRestClientConfig(rest.InClusterConfig) + cl.ClientSet, cl.restConfig, err = kube.GetKubeDynamicClient(kubeContext) if err != nil { return nil, err } @@ -68,41 +69,13 @@ func NewDiscoveryClient(instance *api.Instance) (*DiscoveryClient, error) { return nil, err } - cl.ClientSet, err = dynamic.NewForConfig(cl.restConfig) - if err != nil { - return nil, err - } - return cl, nil -} - -// NewRestClientConfig returns a new Rest Client config portions complete. -func NewRestClientConfig(inClusterFn func() (*rest.Config, error)) (*rest.Config, error) { + cl.namespace = namespace - if restConfig, err := inClusterFn(); err == nil { - return restConfig, nil - } - - pathOptions := clientcmd.NewDefaultPathOptions() - - config, err := pathOptions.GetStartingConfig() - if err != nil { - return nil, err - } - - configOverrides := clientcmd.ConfigOverrides{} - - clientConfig := clientcmd.NewDefaultClientConfig(*config, &configOverrides) - restConfig, err := clientConfig.ClientConfig() - if err != nil { - return nil, err - } - - return restConfig, nil + return cl, nil } // GetApiResources discovers the api-resources for a cluster func (cl *DiscoveryClient) GetApiResources() error { - resourcelist, err := cl.DiscoveryClient.ServerPreferredResources() if err != nil { if apierrors.IsNotFound(err) { @@ -117,6 +90,9 @@ func (cl *DiscoveryClient) GetApiResources() error { gvrs := []schema.GroupVersionResource{} for _, rl := range resourcelist { for i := range rl.APIResources { + if cl.namespace != "" && !rl.APIResources[i].Namespaced { + continue + } gv, _ := schema.ParseGroupVersion(rl.GroupVersion) ResourceName := rl.APIResources[i].Name g := schema.GroupVersionResource{Group: gv.Group, Version: gv.Version, Resource: ResourceName} @@ -126,7 +102,11 @@ func (cl *DiscoveryClient) GetApiResources() error { var results []map[string]interface{} for _, g := range gvrs { - ri := cl.ClientSet.Resource(g) + nri := cl.ClientSet.Resource(g) + var ri dynamic.ResourceInterface = nri + if cl.namespace != "" { + ri = nri.Namespace(cl.namespace) + } klog.V(2).Infof("Retrieving : %s.%s.%s", g.Resource, g.Version, g.Group) rs, err := ri.List(context.TODO(), metav1.ListOptions{}) if err != nil { diff --git a/pkg/helm/helm.go b/pkg/helm/helm.go index c89800b0..ef10d8af 100644 --- a/pkg/helm/helm.go +++ b/pkg/helm/helm.go @@ -43,12 +43,13 @@ import ( "k8s.io/klog/v2" "github.com/fairwindsops/pluto/v5/pkg/api" + kube "github.com/fairwindsops/pluto/v5/pkg/kube" ) // Helm represents all current releases that we can find in the cluster type Helm struct { Releases []*Release - Kube *kube + Kube *kube.Kube Namespace string Instance *api.Instance } @@ -73,8 +74,8 @@ type ChartMeta struct { } // NewHelm returns a basic helm struct with the version of helm requested -func NewHelm(namespace, kubeContext string, instance *api.Instance) (*Helm, error) { - config, err := getConfigInstance(kubeContext) +func NewHelm(namespace string, kubeContext string, instance *api.Instance) (*Helm, error) { + config, err := kube.GetConfigInstance(kubeContext) if err != nil { return nil, err } @@ -88,9 +89,9 @@ func NewHelm(namespace, kubeContext string, instance *api.Instance) (*Helm, erro // NewHelmWithKubeClient returns a helm struct with version of helm requested // and uses the passed in kube client as the cluster to operate on -func NewHelmWithKubeClient(version, store, namespace string, instance *api.Instance, kubeClient kubernetes.Interface) *Helm { +func NewHelmWithKubeClient(version string, store string, namespace string, instance *api.Instance, kubeClient kubernetes.Interface) *Helm { return &Helm{ - Kube: &kube{ + Kube: &kube.Kube{ Client: kubeClient, }, Namespace: namespace, diff --git a/pkg/helm/helm_test.go b/pkg/helm/helm_test.go index 133ca5b2..81c6df00 100644 --- a/pkg/helm/helm_test.go +++ b/pkg/helm/helm_test.go @@ -33,10 +33,13 @@ import ( "testing" "github.com/fairwindsops/pluto/v5/pkg/api" + kube "github.com/fairwindsops/pluto/v5/pkg/kube" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + testclient "k8s.io/client-go/kubernetes/fake" + _ "k8s.io/client-go/plugin/pkg/client/auth" "k8s.io/client-go/rest" ) @@ -150,6 +153,13 @@ var ( } ) +func getMockConfigInstance() *kube.Kube { + kubeClient := &kube.Kube{ + Client: testclient.NewSimpleClientset(), + } + return kubeClient +} + func newMockHelm(namespace string) *Helm { return &Helm{ Namespace: namespace, @@ -185,10 +195,10 @@ func newMockHelm(namespace string) *Helm { } } -func newBadKubeClient() (k *kube) { +func newBadKubeClient() (k *kube.Kube) { conf := new(rest.Config) conf.Host = "127.0.0.1:9999" - k = new(kube) + k = new(kube.Kube) k.Client, _ = kubernetes.NewForConfig(conf) return } diff --git a/pkg/helm/kube.go b/pkg/kube/kube.go similarity index 63% rename from pkg/helm/kube.go rename to pkg/kube/kube.go index 80f1b83b..df2c4bb0 100644 --- a/pkg/helm/kube.go +++ b/pkg/kube/kube.go @@ -31,7 +31,9 @@ package helm import ( "sync" + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "k8s.io/klog/v2" // This is required to auth to cloud providers (i.e. GKE) @@ -39,23 +41,23 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/config" ) -type kube struct { +type Kube struct { Client kubernetes.Interface } -var kubeClient *kube +var kubeClient *Kube var once sync.Once -// GetConfigInstance returns a Kubernetes interface based on the current configuration -func getConfigInstance(kubeContext string) (*kube, error) { +// GetConfigInstance returns a Pluto Kubernetes interface based on the current configuration +func GetConfigInstance(kubeContext string) (*Kube, error) { var err error var client kubernetes.Interface once.Do(func() { if kubeClient == nil { - client, err = getKubeClient(kubeContext) + client, err = GetKubeClient(kubeContext) - kubeClient = &kube{ + kubeClient = &Kube{ Client: client, } } @@ -66,19 +68,38 @@ func getConfigInstance(kubeContext string) (*kube, error) { return kubeClient, nil } -func getKubeClient(kubeContext string) (kubernetes.Interface, error) { +// GetKubeClient returns a Kubernetes.Interface based on the current configuration +func GetKubeClient(kubeContext string) (kubernetes.Interface, error) { if kubeContext != "" { klog.V(3).Infof("using kube context: %s", kubeContext) } - kubeConfig, err := config.GetConfigWithContext(kubeContext) - + config, err := config.GetConfigWithContext(kubeContext) if err != nil { return nil, err } - clientset, err := kubernetes.NewForConfig(kubeConfig) + + clientset, err := kubernetes.NewForConfig(config) if err != nil { return nil, err } return clientset, nil } + +// GetKubeDynamicClient returns a dynamic.Interface, rest.Config based on the current configuration +func GetKubeDynamicClient(kubeContext string) (dynamic.Interface, *rest.Config, error) { + if kubeContext != "" { + klog.V(3).Infof("using kube context: %s", kubeContext) + } + + config, err := config.GetConfigWithContext(kubeContext) + if err != nil { + return nil, nil, err + } + + clientset, err := dynamic.NewForConfig(config) + if err != nil { + return nil, nil, err + } + return clientset, config, nil +} diff --git a/pkg/helm/kube_test.go b/pkg/kube/kube_test.go similarity index 84% rename from pkg/helm/kube_test.go rename to pkg/kube/kube_test.go index 5cf024b0..9fb166f5 100644 --- a/pkg/helm/kube_test.go +++ b/pkg/kube/kube_test.go @@ -19,17 +19,8 @@ import ( "testing" "github.com/stretchr/testify/assert" - testclient "k8s.io/client-go/kubernetes/fake" - _ "k8s.io/client-go/plugin/pkg/client/auth" ) -func getMockConfigInstance() *kube { - kubeClient = &kube{ - Client: testclient.NewSimpleClientset(), - } - return kubeClient -} - func Test_getKubeClient(t *testing.T) { tests := []struct { name string @@ -59,7 +50,7 @@ func Test_getKubeClient(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { os.Setenv("KUBECONFIG", tt.kubeConfig) - _, err := getKubeClient(tt.kubeContext) + _, err := GetKubeClient(tt.kubeContext) if tt.wantErr { assert.Error(t, err) } else { diff --git a/pkg/helm/testdata/kubeconfig b/pkg/kube/testdata/kubeconfig similarity index 100% rename from pkg/helm/testdata/kubeconfig rename to pkg/kube/testdata/kubeconfig diff --git a/pkg/helm/testdata/kubeconfig_invalid b/pkg/kube/testdata/kubeconfig_invalid similarity index 100% rename from pkg/helm/testdata/kubeconfig_invalid rename to pkg/kube/testdata/kubeconfig_invalid