From c68bd3132a25e64c7e5e022b7f7c457393b62320 Mon Sep 17 00:00:00 2001 From: Guilhem Lettron Date: Fri, 15 Nov 2024 16:53:17 +0100 Subject: [PATCH] refactor: move store types in their own package this prevent import cycle --- cmd/switcher/switcher.go | 7 +- pkg/cache/cache.go | 6 +- pkg/cache/file/fileCache.go | 10 +-- pkg/cache/memory/memoryCache.go | 10 +-- pkg/main.go | 16 ++-- pkg/search.go | 19 ++--- pkg/store/kubeconfig_store_akamai.go | 12 +-- pkg/store/kubeconfig_store_azure.go | 15 ++-- pkg/store/kubeconfig_store_capi.go | 9 ++- pkg/store/kubeconfig_store_digital_ocean.go | 11 +-- pkg/store/kubeconfig_store_eks.go | 9 ++- pkg/store/kubeconfig_store_file.go | 11 +-- pkg/store/kubeconfig_store_gardener.go | 17 +++-- pkg/store/kubeconfig_store_gke.go | 9 ++- pkg/store/kubeconfig_store_ovh.go | 11 +-- pkg/store/kubeconfig_store_rancher.go | 9 ++- pkg/store/kubeconfig_store_scaleway.go | 13 ++-- pkg/store/kubeconfig_store_vault.go | 5 +- pkg/store/types.go | 62 +-------------- pkg/store/types/types.go | 76 +++++++++++++++++++ pkg/subcommands/alias/alias.go | 9 ++- pkg/subcommands/clean/clean.go | 4 +- pkg/subcommands/exec/exec.go | 4 +- pkg/subcommands/gardener/controlplane.go | 6 +- pkg/subcommands/history/history.go | 13 ++-- .../list-contexts/list-contexts.go | 7 +- pkg/subcommands/set-context/set_context.go | 9 ++- 27 files changed, 216 insertions(+), 173 deletions(-) create mode 100644 pkg/store/types/types.go diff --git a/cmd/switcher/switcher.go b/cmd/switcher/switcher.go index 386b287e..fff2e833 100644 --- a/cmd/switcher/switcher.go +++ b/cmd/switcher/switcher.go @@ -33,6 +33,7 @@ import ( switchconfig "github.com/danielfoehrkn/kubeswitch/pkg/config" "github.com/danielfoehrkn/kubeswitch/pkg/config/validation" "github.com/danielfoehrkn/kubeswitch/pkg/store" + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" "github.com/danielfoehrkn/kubeswitch/types" ) @@ -160,7 +161,7 @@ func setCommonFlags(command *cobra.Command) { "path to the local directory used for storing internal state.") } -func initialize() ([]store.KubeconfigStore, *types.Config, error) { +func initialize() ([]storetypes.KubeconfigStore, *types.Config, error) { if showDebugLogs { logrus.SetLevel(logrus.DebugLevel) } @@ -194,11 +195,11 @@ func initialize() ([]store.KubeconfigStore, *types.Config, error) { } var ( - stores []store.KubeconfigStore + stores []storetypes.KubeconfigStore digitalOceanStoreAddedViaConfig bool ) for _, kubeconfigStoreFromConfig := range config.KubeconfigStores { - var s store.KubeconfigStore + var s storetypes.KubeconfigStore if kubeconfigStoreFromConfig.KubeconfigName != nil && *kubeconfigStoreFromConfig.KubeconfigName != "" { kubeconfigName = *kubeconfigStoreFromConfig.KubeconfigName diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index 7b160d91..9583e3fd 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -18,7 +18,7 @@ import ( "fmt" "sync" - "github.com/danielfoehrkn/kubeswitch/pkg/store" + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" "github.com/danielfoehrkn/kubeswitch/types" ) @@ -27,7 +27,7 @@ var ( caches = make(map[string]CacheFactory) ) -type CacheFactory func(store store.KubeconfigStore, cfg *types.Cache) (store.KubeconfigStore, error) +type CacheFactory func(store storetypes.KubeconfigStore, cfg *types.Cache) (storetypes.KubeconfigStore, error) func Register(kind string, creator CacheFactory) { cachesMu.Lock() @@ -35,7 +35,7 @@ func Register(kind string, creator CacheFactory) { caches[kind] = creator } -func New(kind string, store store.KubeconfigStore, cfg *types.Cache) (store.KubeconfigStore, error) { +func New(kind string, store storetypes.KubeconfigStore, cfg *types.Cache) (storetypes.KubeconfigStore, error) { cachesMu.RLock() create, ok := caches[kind] cachesMu.RUnlock() diff --git a/pkg/cache/file/fileCache.go b/pkg/cache/file/fileCache.go index 48279a58..ab195106 100644 --- a/pkg/cache/file/fileCache.go +++ b/pkg/cache/file/fileCache.go @@ -22,7 +22,7 @@ import ( "strings" "github.com/danielfoehrkn/kubeswitch/pkg/cache" - "github.com/danielfoehrkn/kubeswitch/pkg/store" + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" "github.com/danielfoehrkn/kubeswitch/pkg/util" kubeconfigutil "github.com/danielfoehrkn/kubeswitch/pkg/util/kubectx_copied" "github.com/danielfoehrkn/kubeswitch/types" @@ -37,7 +37,7 @@ func init() { cache.Register(cacheKey, New) } -func New(upstream store.KubeconfigStore, ccfg *types.Cache) (store.KubeconfigStore, error) { +func New(upstream storetypes.KubeconfigStore, ccfg *types.Cache) (storetypes.KubeconfigStore, error) { if ccfg == nil { return nil, fmt.Errorf("cache config must be provided for file cache") } @@ -72,7 +72,7 @@ func New(upstream store.KubeconfigStore, ccfg *types.Cache) (store.KubeconfigSto } type fileCache struct { - upstream store.KubeconfigStore + upstream storetypes.KubeconfigStore cfg fileCacheCfg logger *logrus.Entry } @@ -182,7 +182,7 @@ func (c *fileCache) VerifyKubeconfigPaths() error { return c.upstream.VerifyKubeconfigPaths() } -func (c *fileCache) StartSearch(channel chan store.SearchResult) { +func (c *fileCache) StartSearch(channel chan storetypes.SearchResult) { c.upstream.StartSearch(channel) } @@ -194,7 +194,7 @@ func (c *fileCache) GetStoreConfig() types.KubeconfigStore { } func (c *fileCache) GetSearchPreview(path string, optionalTags map[string]string) (string, error) { - previewer, ok := c.upstream.(store.Previewer) + previewer, ok := c.upstream.(storetypes.Previewer) if !ok { // if the wrapped store is not a previewer, simply return an empty string, hence causing no visual distortion return "", nil diff --git a/pkg/cache/memory/memoryCache.go b/pkg/cache/memory/memoryCache.go index d4ad2757..e659441a 100644 --- a/pkg/cache/memory/memoryCache.go +++ b/pkg/cache/memory/memoryCache.go @@ -16,7 +16,7 @@ package memory import ( "github.com/danielfoehrkn/kubeswitch/pkg/cache" - "github.com/danielfoehrkn/kubeswitch/pkg/store" + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" "github.com/danielfoehrkn/kubeswitch/types" "github.com/sirupsen/logrus" ) @@ -25,7 +25,7 @@ func init() { cache.Register("memory", New) } -func New(upstream store.KubeconfigStore, _ *types.Cache) (store.KubeconfigStore, error) { +func New(upstream storetypes.KubeconfigStore, _ *types.Cache) (storetypes.KubeconfigStore, error) { return &memoryCache{ upstream: upstream, cache: make(map[string][]byte), @@ -33,7 +33,7 @@ func New(upstream store.KubeconfigStore, _ *types.Cache) (store.KubeconfigStore, } type memoryCache struct { - upstream store.KubeconfigStore + upstream storetypes.KubeconfigStore cache map[string][]byte } @@ -70,7 +70,7 @@ func (c *memoryCache) VerifyKubeconfigPaths() error { return c.upstream.VerifyKubeconfigPaths() } -func (c *memoryCache) StartSearch(channel chan store.SearchResult) { +func (c *memoryCache) StartSearch(channel chan storetypes.SearchResult) { c.upstream.StartSearch(channel) } @@ -83,7 +83,7 @@ func (c *memoryCache) GetStoreConfig() types.KubeconfigStore { } func (c *memoryCache) GetSearchPreview(path string, optionalTags map[string]string) (string, error) { - previewer, ok := c.upstream.(store.Previewer) + previewer, ok := c.upstream.(storetypes.Previewer) if !ok { // if the wrapped store is not a previewer, simply return an empty string, hence causing no visual distortion return "", nil diff --git a/pkg/main.go b/pkg/main.go index 22034320..bcc0300d 100644 --- a/pkg/main.go +++ b/pkg/main.go @@ -27,7 +27,7 @@ import ( "gopkg.in/yaml.v2" "github.com/danielfoehrkn/kubeswitch/pkg/index" - "github.com/danielfoehrkn/kubeswitch/pkg/store" + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" aliasutil "github.com/danielfoehrkn/kubeswitch/pkg/subcommands/alias/util" "github.com/danielfoehrkn/kubeswitch/pkg/util" kubeconfigutil "github.com/danielfoehrkn/kubeswitch/pkg/util/kubectx_copied" @@ -64,7 +64,7 @@ var ( logger = logrus.New() ) -func Switcher(stores []store.KubeconfigStore, config *types.Config, stateDir string, noIndex, showPreview bool) (*string, *string, error) { +func Switcher(stores []storetypes.KubeconfigStore, config *types.Config, stateDir string, noIndex, showPreview bool) (*string, *string, error) { c, err := DoSearch(stores, config, stateDir, noIndex) if err != nil { return nil, nil, err @@ -108,7 +108,7 @@ func Switcher(stores []store.KubeconfigStore, config *types.Config, stateDir str }(*c) // remember the store for later kubeconfig retrieval - var kindToStore = map[string]store.KubeconfigStore{} + var kindToStore = map[string]storetypes.KubeconfigStore{} for _, s := range stores { kindToStore[s.GetID()] = s } @@ -180,7 +180,7 @@ func Switcher(stores []store.KubeconfigStore, config *types.Config, stateDir str // writeIndex tries to write the Index file for the kubeconfig store // if it fails to do so, it logs a warning, but does not panic -func writeIndex(store store.KubeconfigStore, searchIndex *index.SearchIndex, ctxToPathMapping map[string]string, ctxToTagsMapping map[string]map[string]string) { +func writeIndex(store storetypes.KubeconfigStore, searchIndex *index.SearchIndex, ctxToPathMapping map[string]string, ctxToTagsMapping map[string]map[string]string) { index := types.Index{ Kind: store.GetKind(), ContextToPathMapping: ctxToPathMapping, @@ -202,7 +202,7 @@ func writeIndex(store store.KubeconfigStore, searchIndex *index.SearchIndex, ctx } } -func showFuzzySearch(storeIDToStore map[string]store.KubeconfigStore, showPreview bool) (string, string, error) { +func showFuzzySearch(storeIDToStore map[string]storetypes.KubeconfigStore, showPreview bool) (string, string, error) { // display selection dialog for all kubeconfig context names idx, err := fuzzyfinder.Find( &allKubeconfigContextNames, @@ -224,7 +224,7 @@ func showFuzzySearch(storeIDToStore map[string]store.KubeconfigStore, showPrevie } // getFuzzyFinderOptions returns a list of fuzzy finder options -func getFuzzyFinderOptions(storeIDToStore map[string]store.KubeconfigStore, showPreview bool) []fuzzyfinder.Option { +func getFuzzyFinderOptions(storeIDToStore map[string]storetypes.KubeconfigStore, showPreview bool) []fuzzyfinder.Option { options := []fuzzyfinder.Option{fuzzyfinder.WithHotReloadLock(hotReloadLock.RLocker())} if showPreview { @@ -245,7 +245,7 @@ func getFuzzyFinderOptions(storeIDToStore map[string]store.KubeconfigStore, show kubeconfigStore := storeIDToStore[storeID] var storeSpecificPreview *string - previewer, ok := kubeconfigStore.(store.Previewer) + previewer, ok := kubeconfigStore.(storetypes.Previewer) if ok { pr, err := previewer.GetSearchPreview(path, tags) if err != nil { @@ -278,7 +278,7 @@ func getFuzzyFinderOptions(storeIDToStore map[string]store.KubeconfigStore, show return options } -func getSanitizedKubeconfigForKubeconfigPath(kubeconfigStore store.KubeconfigStore, path string, tags map[string]string) (string, error) { +func getSanitizedKubeconfigForKubeconfigPath(kubeconfigStore storetypes.KubeconfigStore, path string, tags map[string]string) (string, error) { // during first run without index, the files are already read in the getContextsForKubeconfigPath and saved in-memory kubeconfig := readFromPathToKubeconfig(path) if len(kubeconfig) > 0 { diff --git a/pkg/search.go b/pkg/search.go index 2d6f9aeb..5471915f 100644 --- a/pkg/search.go +++ b/pkg/search.go @@ -19,13 +19,14 @@ import ( "os" "sync" + "github.com/sirupsen/logrus" + "github.com/danielfoehrkn/kubeswitch/pkg/index" - "github.com/danielfoehrkn/kubeswitch/pkg/store" + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" aliasstate "github.com/danielfoehrkn/kubeswitch/pkg/subcommands/alias/state" aliasutil "github.com/danielfoehrkn/kubeswitch/pkg/subcommands/alias/util" "github.com/danielfoehrkn/kubeswitch/pkg/util" "github.com/danielfoehrkn/kubeswitch/types" - "github.com/sirupsen/logrus" ) type DiscoveredContext struct { @@ -39,14 +40,14 @@ type DiscoveredContext struct { // This metadata is later handed over in the getKubeconfigForPath() function when retrieving the kubeconfig bytes for the path Tags map[string]string // Store is a reference to the backing store that contains the kubeconfig - Store *store.KubeconfigStore + Store *storetypes.KubeconfigStore // Error is an error that occured during the search Error error } // DoSearch executes a concurrent search over the given kubeconfig stores // returns results from all stores on the return channel -func DoSearch(stores []store.KubeconfigStore, config *types.Config, stateDir string, noIndex bool) (*chan DiscoveredContext, error) { +func DoSearch(stores []storetypes.KubeconfigStore, config *types.Config, stateDir string, noIndex bool) (*chan DiscoveredContext, error) { // Silence STDOUT during search to not interfere with the search selection screen // restore after search is over originalSTDOUT := os.Stdout @@ -102,7 +103,7 @@ func DoSearch(stores []store.KubeconfigStore, config *types.Config, stateDir str if readFromIndex { logrus.Debugf("Reading from index for store %s with kind %s", kubeconfigStore.GetID(), kubeconfigStore.GetKind()) - go func(store store.KubeconfigStore, index index.SearchIndex) { + go func(store storetypes.KubeconfigStore, index index.SearchIndex) { // reading from this store is finished, decrease wait counter defer wgResultChannel.Done() @@ -129,15 +130,15 @@ func DoSearch(stores []store.KubeconfigStore, config *types.Config, stateDir str } // otherwise, we need to query the backing store for the kubeconfig files - c := make(chan store.SearchResult) - go func(store store.KubeconfigStore, channel chan store.SearchResult) { + c := make(chan storetypes.SearchResult) + go func(store storetypes.KubeconfigStore, channel chan storetypes.SearchResult) { // only close when directory search is over, otherwise send on closed resultChannel defer close(channel) store.GetLogger().Debugf("Starting search for store: %s", store.GetKind()) store.StartSearch(channel) }(kubeconfigStore, c) - go func(store store.KubeconfigStore, storeSearchChannel chan store.SearchResult, index index.SearchIndex) { + go func(store storetypes.KubeconfigStore, storeSearchChannel chan storetypes.SearchResult, index index.SearchIndex) { // remember the context to kubeconfig path mapping for this store // to write it to the index. Do not use the global "ContextToPathMapping" // as this contains contexts names from all stores combined @@ -217,7 +218,7 @@ func DoSearch(stores []store.KubeconfigStore, config *types.Config, stateDir str return &resultChannel, nil } -func shouldReadFromIndex(searchIndex *index.SearchIndex, kubeconfigStore store.KubeconfigStore, config *types.Config) (bool, error) { +func shouldReadFromIndex(searchIndex *index.SearchIndex, kubeconfigStore storetypes.KubeconfigStore, config *types.Config) (bool, error) { // never write an index for the store from env variables and --kubeconfig-path command line falg if kubeconfigStore.GetID() == fmt.Sprintf("%s.%s", types.StoreKindFilesystem, "env-and-flag") { return false, nil diff --git a/pkg/store/kubeconfig_store_akamai.go b/pkg/store/kubeconfig_store_akamai.go index b0556a2b..4c9d928c 100644 --- a/pkg/store/kubeconfig_store_akamai.go +++ b/pkg/store/kubeconfig_store_akamai.go @@ -26,9 +26,11 @@ import ( "golang.org/x/oauth2" "gopkg.in/yaml.v3" - "github.com/danielfoehrkn/kubeswitch/types" "github.com/linode/linodego" "github.com/sirupsen/logrus" + + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" + "github.com/danielfoehrkn/kubeswitch/types" ) func NewAkamaiStore(store types.KubeconfigStore) (*AkamaiStore, error) { @@ -105,14 +107,14 @@ func (s *AkamaiStore) GetLogger() *logrus.Entry { return s.Logger } -func (s *AkamaiStore) StartSearch(channel chan SearchResult) { +func (s *AkamaiStore) StartSearch(channel chan storetypes.SearchResult) { s.Logger.Debug("Akamai: start search") ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := s.InitializeAkamaiStore(); err != nil { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: "", Error: err, } @@ -122,7 +124,7 @@ func (s *AkamaiStore) StartSearch(channel chan SearchResult) { // list linode instances instances, err := s.Client.ListLKEClusters(ctx, nil) if err != nil { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: "", Error: err, } @@ -130,7 +132,7 @@ func (s *AkamaiStore) StartSearch(channel chan SearchResult) { } for _, instance := range instances { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: instance.Label, Tags: map[string]string{ "clusterID": strconv.Itoa(instance.ID), diff --git a/pkg/store/kubeconfig_store_azure.go b/pkg/store/kubeconfig_store_azure.go index ce889bca..9cddbdda 100644 --- a/pkg/store/kubeconfig_store_azure.go +++ b/pkg/store/kubeconfig_store_azure.go @@ -29,6 +29,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice" + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" "github.com/danielfoehrkn/kubeswitch/types" ) @@ -90,13 +91,13 @@ func (s *AzureStore) InitializeAzureStore() error { // StartSearch starts the search for AKS clusters // Limitation: Two seperate subscriptions should not have the same (resource_group, cluster-name) touple -func (s *AzureStore) StartSearch(channel chan SearchResult) { +func (s *AzureStore) StartSearch(channel chan storetypes.SearchResult) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := s.InitializeAzureStore(); err != nil { err := fmt.Errorf("failed to initialize store: %w", err) - channel <- SearchResult{ + channel <- storetypes.SearchResult{ Error: err, } return @@ -140,25 +141,25 @@ func (s *AzureStore) StartSearch(channel chan SearchResult) { s.Logger.Debugf("Search done for AKS") } -func handleAzureError(channel chan SearchResult, err error) { +func handleAzureError(channel chan storetypes.SearchResult, err error) { if err, ok := err.(armcontainerservice.CloudError); ok && err.InnerError != nil { // TODO: if 401 is returned, execute `az cli` to re-authenticate // similar to gcp - channel <- SearchResult{ + channel <- storetypes.SearchResult{ Error: fmt.Errorf("AKS returned an error listing AKS clusters: %w", err), } return } if err != nil { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ Error: fmt.Errorf("Failed to list AKS clusters: %w", err), } return } } -func (s *AzureStore) returnSearchResultsForClusters(channel chan SearchResult, managedClusters []*armcontainerservice.ManagedCluster) { +func (s *AzureStore) returnSearchResultsForClusters(channel chan storetypes.SearchResult, managedClusters []*armcontainerservice.ManagedCluster) { for _, cluster := range managedClusters { s.Logger.Debugf("Found cluster with name %q and id %q", *cluster.Name, *cluster.ID) if cluster.Name == nil { @@ -190,7 +191,7 @@ func (s *AzureStore) returnSearchResultsForClusters(channel chan SearchResult, m kubeconfigPath := getAzureKubeconfigPath(*resourceGroup, *cluster.Name) s.insertIntoClusterCache(kubeconfigPath, cluster) - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: kubeconfigPath, Error: nil, } diff --git a/pkg/store/kubeconfig_store_capi.go b/pkg/store/kubeconfig_store_capi.go index 69606b8c..881f21f9 100644 --- a/pkg/store/kubeconfig_store_capi.go +++ b/pkg/store/kubeconfig_store_capi.go @@ -19,6 +19,7 @@ import ( "fmt" "time" + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" "github.com/danielfoehrkn/kubeswitch/types" "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" @@ -108,7 +109,7 @@ func (s *CapiStore) getCapiClient() (client.Client, error) { } // StartSearch starts the search over the configured search paths -func (s *CapiStore) StartSearch(channel chan SearchResult) { +func (s *CapiStore) StartSearch(channel chan storetypes.SearchResult) { s.Logger.Debug("CAPI: start search") ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) @@ -116,7 +117,7 @@ func (s *CapiStore) StartSearch(channel chan SearchResult) { // initialize CAPI client if err := s.InitializeCapiStore(); err != nil { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: "", Error: err, } @@ -127,7 +128,7 @@ func (s *CapiStore) StartSearch(channel chan SearchResult) { clusters := &clusterv1beta1.ClusterList{} err := s.Client.List(ctx, clusters) if err != nil { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: "", Error: err, } @@ -137,7 +138,7 @@ func (s *CapiStore) StartSearch(channel chan SearchResult) { for _, cluster := range clusters.Items { s.Logger.Debug("CAPI: found cluster", "name", cluster.Name, "namespace", cluster.Namespace) - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: fmt.Sprintf("%s-%s", cluster.Namespace, cluster.Name), Error: nil, Tags: map[string]string{ diff --git a/pkg/store/kubeconfig_store_digital_ocean.go b/pkg/store/kubeconfig_store_digital_ocean.go index 18ef740b..3f4b30f5 100644 --- a/pkg/store/kubeconfig_store_digital_ocean.go +++ b/pkg/store/kubeconfig_store_digital_ocean.go @@ -22,6 +22,7 @@ import ( "sync" "github.com/danielfoehrkn/kubeswitch/pkg/store/doks" + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" "github.com/disiqueira/gotree" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -125,10 +126,10 @@ func (d *DigitalOceanStore) getDoClient(accessToken string) (*godo.Client, error } // StartSearch starts the search for Digital Ocean clusters -func (d *DigitalOceanStore) StartSearch(channel chan SearchResult) { +func (d *DigitalOceanStore) StartSearch(channel chan storetypes.SearchResult) { if err := d.InitializeDigitalOceanStore(); err != nil { err := fmt.Errorf("failed to initialize store: %w", err) - channel <- SearchResult{ + channel <- storetypes.SearchResult{ Error: err, } return @@ -139,14 +140,14 @@ func (d *DigitalOceanStore) StartSearch(channel chan SearchResult) { for doctlContextName, doSvc := range d.ContextToKubernetesService { // parallelize. - go func(resultChannel chan SearchResult, doctlCtxName string, svc do.KubernetesService) { + go func(resultChannel chan storetypes.SearchResult, doctlCtxName string, svc do.KubernetesService) { // reading from this context is finished, decrease wait counter defer wgResultChannel.Done() d.Logger.Debugf("Digital Ocean: Start listing clusters for context %q", doctlCtxName) clusters, err := svc.List() if err != nil { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ Error: fmt.Errorf("error listing DOKS clusters for context %s: %w", doctlCtxName, err), } return @@ -162,7 +163,7 @@ func (d *DigitalOceanStore) StartSearch(channel chan SearchResult) { } nodePools = fmt.Sprintf("%s]", nodePools) - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: kubeconfigPath, Tags: map[string]string{ tagDOKSClusterID: cluster.ID, diff --git a/pkg/store/kubeconfig_store_eks.go b/pkg/store/kubeconfig_store_eks.go index 3067c57f..a76ae766 100644 --- a/pkg/store/kubeconfig_store_eks.go +++ b/pkg/store/kubeconfig_store_eks.go @@ -25,6 +25,7 @@ import ( awseks "github.com/aws/aws-sdk-go-v2/service/eks" awsekstypes "github.com/aws/aws-sdk-go-v2/service/eks/types" "github.com/aws/smithy-go/logging" + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" "github.com/danielfoehrkn/kubeswitch/types" "github.com/disiqueira/gotree" "github.com/sirupsen/logrus" @@ -148,13 +149,13 @@ func (s *EKSStore) VerifyKubeconfigPaths() error { return nil } -func (s *EKSStore) StartSearch(channel chan SearchResult) { +func (s *EKSStore) StartSearch(channel chan storetypes.SearchResult) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := s.InitializeEKSStore(); err != nil { err := fmt.Errorf("failed to initialize store. This is most likely a problem with your provided aws credentials: %v", err) - channel <- SearchResult{ + channel <- storetypes.SearchResult{ Error: err, } return @@ -166,7 +167,7 @@ func (s *EKSStore) StartSearch(channel chan SearchResult) { s.GetLogger().Debugf("next page found") resp, err := pager.NextPage(ctx) if err != nil { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ Error: err, } return @@ -177,7 +178,7 @@ func (s *EKSStore) StartSearch(channel chan SearchResult) { // eks_---- kubeconfigPath := fmt.Sprintf("eks_%s--%s--%s", s.Config.Profile, *s.Config.Region, clusterName) - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: kubeconfigPath, Error: nil, } diff --git a/pkg/store/kubeconfig_store_file.go b/pkg/store/kubeconfig_store_file.go index 4d812c6b..c4478a4f 100644 --- a/pkg/store/kubeconfig_store_file.go +++ b/pkg/store/kubeconfig_store_file.go @@ -25,6 +25,7 @@ import ( "github.com/karrick/godirwalk" "github.com/sirupsen/logrus" + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" "github.com/danielfoehrkn/kubeswitch/types" ) @@ -68,9 +69,9 @@ func (s *FilesystemStore) GetLogger() *logrus.Entry { return s.Logger } -func (s *FilesystemStore) StartSearch(channel chan SearchResult) { +func (s *FilesystemStore) StartSearch(channel chan storetypes.SearchResult) { for _, path := range s.kubeconfigFilepaths { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: path, Error: nil, } @@ -87,7 +88,7 @@ func (s *FilesystemStore) StartSearch(channel chan SearchResult) { func (s *FilesystemStore) searchDirectory( wg *sync.WaitGroup, searchPath string, - channel chan SearchResult, + channel chan storetypes.SearchResult, ) { defer wg.Done() @@ -99,7 +100,7 @@ func (s *FilesystemStore) searchDirectory( return err } if matched { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: osPathname, Error: nil, } @@ -109,7 +110,7 @@ func (s *FilesystemStore) searchDirectory( Unsorted: false, // (optional) set true for faster yet non-deterministic enumeration FollowSymbolicLinks: true, }); err != nil { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: "", Error: fmt.Errorf("failed to find kubeconfig files in directory: %v", err), } diff --git a/pkg/store/kubeconfig_store_gardener.go b/pkg/store/kubeconfig_store_gardener.go index caa0ae63..1c878ee2 100644 --- a/pkg/store/kubeconfig_store_gardener.go +++ b/pkg/store/kubeconfig_store_gardener.go @@ -23,6 +23,7 @@ import ( "time" gardenclient "github.com/danielfoehrkn/kubeswitch/pkg/store/gardener/copied_gardenctlv2" + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" kubeconfigutil "github.com/danielfoehrkn/kubeswitch/pkg/util/kubectx_copied" "github.com/disiqueira/gotree" gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1" @@ -208,13 +209,13 @@ func writeGardenloginConfig(path string, config *GardenloginConfig) error { } // StartSearch starts the search for Shoots and Managed Seeds -func (s *GardenerStore) StartSearch(channel chan SearchResult) { +func (s *GardenerStore) StartSearch(channel chan storetypes.SearchResult) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := s.InitializeGardenerStore(); err != nil { err := fmt.Errorf("failed to initialize store. This is most likely a problem with your provided kubeconfig: %v", err) - channel <- SearchResult{ + channel <- storetypes.SearchResult{ Error: err, } return @@ -241,7 +242,7 @@ func (s *GardenerStore) StartSearch(channel chan SearchResult) { shoots, err := s.GardenClient.ListShoots(ctx, &listOptions) if err != nil { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ Error: fmt.Errorf("failed to call list Shoots from the Gardener API for namespace %q: %v", path, err), } return @@ -251,7 +252,7 @@ func (s *GardenerStore) StartSearch(channel chan SearchResult) { listOptions.LabelSelector = selector secrets := &corev1.SecretList{} if err := s.Client.List(ctx, secrets, &listOptions); err != nil { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ Error: fmt.Errorf("failed to list CA secrets for namespace %q: %v", path, err), } return @@ -556,7 +557,7 @@ func (s *GardenerStore) GetSearchPreview(path string, optionalTags map[string]st } } -func (s *GardenerStore) sendKubeconfigPaths(channel chan SearchResult, shoots []gardencorev1beta1.Shoot, managedSeeds []seedmanagementv1alpha1.ManagedSeed) { +func (s *GardenerStore) sendKubeconfigPaths(channel chan storetypes.SearchResult, shoots []gardencorev1beta1.Shoot, managedSeeds []seedmanagementv1alpha1.ManagedSeed) { var landscapeName = s.LandscapeIdentity // first, send the garden context name configured in the switch config @@ -564,7 +565,7 @@ func (s *GardenerStore) sendKubeconfigPaths(channel chan SearchResult, shoots [] // the kubeconfig from the filesystem (set in SwitchConfig for the GardenerStore) instead of // from the Gardener API gardenKubeconfigPath := gardenerstore.GetGardenKubeconfigPath(s.LandscapeIdentity) - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: gardenKubeconfigPath, Error: nil, } @@ -635,7 +636,7 @@ func (s *GardenerStore) sendKubeconfigPaths(channel chan SearchResult, shoots [] continue } - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: kubeconfigPath, Error: nil, } @@ -646,7 +647,7 @@ func (s *GardenerStore) sendKubeconfigPaths(channel chan SearchResult, shoots [] // when populating the path. This avoids cache misses. s.PathToManagedSeedLock.RLock() for pathForSeed := range s.CachePathToManagedSeed { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: pathForSeed, Error: nil, } diff --git a/pkg/store/kubeconfig_store_gke.go b/pkg/store/kubeconfig_store_gke.go index 757270d0..1e1f9c20 100644 --- a/pkg/store/kubeconfig_store_gke.go +++ b/pkg/store/kubeconfig_store_gke.go @@ -29,6 +29,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" apiv1 "k8s.io/client-go/tools/clientcmd/api/v1" + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" "github.com/danielfoehrkn/kubeswitch/types" "google.golang.org/api/cloudresourcemanager/v1" ) @@ -168,13 +169,13 @@ func (s *GKEStore) InitializeGKEStore() error { return nil } -func (s *GKEStore) StartSearch(channel chan SearchResult) { +func (s *GKEStore) StartSearch(channel chan storetypes.SearchResult) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := s.InitializeGKEStore(); err != nil { err := fmt.Errorf("failed to initialize store: %w", err) - channel <- SearchResult{ + channel <- storetypes.SearchResult{ Error: err, } return @@ -183,7 +184,7 @@ func (s *GKEStore) StartSearch(channel chan SearchResult) { for projectName, projectId := range s.ProjectNameToID { resp, err := s.GkeClient.Projects.Zones.Clusters.List(projectId, "-").Context(ctx).Do() if err != nil { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ Error: fmt.Errorf("failed to list GKE clusters for project with ID %q: %w", projectId, err), } continue @@ -199,7 +200,7 @@ func (s *GKEStore) StartSearch(channel chan SearchResult) { // cache for when getting the kubeconfig for the unique path later s.DiscoveredClusters[kubeconfigPath] = f - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: kubeconfigPath, Error: nil, } diff --git a/pkg/store/kubeconfig_store_ovh.go b/pkg/store/kubeconfig_store_ovh.go index 1d0f601d..e093ab51 100644 --- a/pkg/store/kubeconfig_store_ovh.go +++ b/pkg/store/kubeconfig_store_ovh.go @@ -21,6 +21,7 @@ import ( "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" "github.com/danielfoehrkn/kubeswitch/types" ) @@ -106,14 +107,14 @@ func (r *OVHStore) GetLogger() *logrus.Entry { return r.Logger } -func (r *OVHStore) StartSearch(channel chan SearchResult) { +func (r *OVHStore) StartSearch(channel chan storetypes.SearchResult) { r.Logger.Debug("OVH: start search") projects := []string{} // list OVH projects err := r.Client.Get("/cloud/project", &projects) if err != nil { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: "", Error: err, } @@ -125,7 +126,7 @@ func (r *OVHStore) StartSearch(channel chan SearchResult) { clustersID := []string{} err := r.Client.Get(fmt.Sprintf("/cloud/project/%v/kube", project), &clustersID) if err != nil { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: "", Error: err, } @@ -136,7 +137,7 @@ func (r *OVHStore) StartSearch(channel chan SearchResult) { var kube OVHKube err := r.Client.Get(fmt.Sprintf("/cloud/project/%v/kube/%v", project, id), &kube) if err != nil { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: "", Error: err, } @@ -145,7 +146,7 @@ func (r *OVHStore) StartSearch(channel chan SearchResult) { kube.Project = project r.OVHKubeCache[kube.ID] = kube - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: kube.Name, Error: nil, } diff --git a/pkg/store/kubeconfig_store_rancher.go b/pkg/store/kubeconfig_store_rancher.go index ddf31b1f..deb9266f 100644 --- a/pkg/store/kubeconfig_store_rancher.go +++ b/pkg/store/kubeconfig_store_rancher.go @@ -21,6 +21,7 @@ import ( "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" "github.com/danielfoehrkn/kubeswitch/types" managementClient "github.com/rancher/rancher/pkg/client/generated/management/v3" ) @@ -103,11 +104,11 @@ func (r *RancherStore) initClient() error { return nil } -func (r *RancherStore) StartSearch(channel chan SearchResult) { +func (r *RancherStore) StartSearch(channel chan storetypes.SearchResult) { r.Logger.Debug("Rancher: start search") if err := r.initClient(); err != nil { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: "", Error: fmt.Errorf("failed to initialize Rancher client: %w", err), } @@ -116,7 +117,7 @@ func (r *RancherStore) StartSearch(channel chan SearchResult) { cluster, err := r.Client.Cluster.ListAll(nil) if err != nil { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: "", Error: err, } @@ -130,7 +131,7 @@ func (r *RancherStore) StartSearch(channel chan SearchResult) { // As a workaround the id of the store is used for the local cluster id = r.GetID() } - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: id, Error: nil, } diff --git a/pkg/store/kubeconfig_store_scaleway.go b/pkg/store/kubeconfig_store_scaleway.go index 78d5b62d..2f2c21d9 100644 --- a/pkg/store/kubeconfig_store_scaleway.go +++ b/pkg/store/kubeconfig_store_scaleway.go @@ -23,6 +23,7 @@ import ( "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" "github.com/danielfoehrkn/kubeswitch/types" ) @@ -114,12 +115,12 @@ func (s *ScalewayStore) GetLogger() *logrus.Entry { return s.Logger } -func (s *ScalewayStore) StartSearch(channel chan SearchResult) { +func (s *ScalewayStore) StartSearch(channel chan storetypes.SearchResult) { s.Logger.Debug("Scaleway: start search") papi := account.NewProjectAPI(s.Client) if papi == nil { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: "", Error: fmt.Errorf("Failed to create scaleway project API"), } @@ -129,7 +130,7 @@ func (s *ScalewayStore) StartSearch(channel chan SearchResult) { &account.ProjectAPIListProjectsRequest{}, ) if err != nil { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: "", Error: fmt.Errorf("Could no list projects in Scaleway err: %w", err), } @@ -139,7 +140,7 @@ func (s *ScalewayStore) StartSearch(channel chan SearchResult) { kapi := k8s.NewAPI(s.Client) if kapi == nil { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: "", Error: fmt.Errorf("Failed to create Kubernetes API instance for scaleway err: %w", err), } @@ -149,7 +150,7 @@ func (s *ScalewayStore) StartSearch(channel chan SearchResult) { for _, project := range pres.Projects { cres, err := kapi.ListClusters(&k8s.ListClustersRequest{ProjectID: &project.ID}) if err != nil { - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: "", Error: fmt.Errorf("Failed to retrieve Kubernetes cluster for project %v err: %w", project.Name, err), } @@ -161,7 +162,7 @@ func (s *ScalewayStore) StartSearch(channel chan SearchResult) { } for _, cluster := range cres.Clusters { s.DiscoveredClusters[cluster.ID] = ScalewayKube{ID: cluster.ID, Name: cluster.Name, Project: project.ID} - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: cluster.Name, Error: nil, } diff --git a/pkg/store/kubeconfig_store_vault.go b/pkg/store/kubeconfig_store_vault.go index 6f59d115..0c9130c8 100644 --- a/pkg/store/kubeconfig_store_vault.go +++ b/pkg/store/kubeconfig_store_vault.go @@ -32,6 +32,7 @@ import ( "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" "github.com/danielfoehrkn/kubeswitch/types" ) @@ -212,7 +213,7 @@ func (s *VaultStore) recursivePathTraversal(wg *sync.WaitGroup, ctx context.Cont } } -func (s *VaultStore) StartSearch(channel chan SearchResult) { +func (s *VaultStore) StartSearch(channel chan storetypes.SearchResult) { wg := sync.WaitGroup{} // start multiple recursive searches from different root paths for _, path := range s.vaultPaths { @@ -231,7 +232,7 @@ func (s *VaultStore) StartSearch(channel chan SearchResult) { go s.recursivePathTraversal(&wg, context.Background(), s.Client, secretsPath, func(path string, directory bool) error { // found an actual secret, but remove "metadata/" from the path rawPath := shimKVv2Metadata(path) - channel <- SearchResult{ + channel <- storetypes.SearchResult{ KubeconfigPath: rawPath, Error: nil, } diff --git a/pkg/store/types.go b/pkg/store/types.go index f44912b4..6fa41475 100644 --- a/pkg/store/types.go +++ b/pkg/store/types.go @@ -18,13 +18,14 @@ import ( "sync" "github.com/danielfoehrkn/kubeswitch/pkg/store/doks" - "github.com/digitalocean/doctl/do" + gardenclient "github.com/danielfoehrkn/kubeswitch/pkg/store/gardener/copied_gardenctlv2" + "github.com/danielfoehrkn/kubeswitch/pkg/store/plugins" + "github.com/danielfoehrkn/kubeswitch/types" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice" awseks "github.com/aws/aws-sdk-go-v2/service/eks" eks "github.com/aws/aws-sdk-go-v2/service/eks/types" - gardenclient "github.com/danielfoehrkn/kubeswitch/pkg/store/gardener/copied_gardenctlv2" - "github.com/danielfoehrkn/kubeswitch/types" + "github.com/digitalocean/doctl/do" gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1" seedmanagementv1alpha1 "github.com/gardener/gardener/pkg/apis/seedmanagement/v1alpha1" vaultapi "github.com/hashicorp/vault/api" @@ -39,61 +40,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -// SearchResult is a full kubeconfig path discovered from the kubeconfig store -// given the contained kubeconfig path, the store knows how to retrieve and return the -// actual kubeconfig -type SearchResult struct { - // KubeconfigPath is the kubeconfig path in the backing store which most of the time encodes enough information to - // retrieve the kubeconfig associated with it. - KubeconfigPath string - // Tags contains the additional metadata that the store wants to associate with a context name. - // This metadata is later handed over in the getKubeconfigForPath() function when retrieving the kubeconfig bytes for the path and might contain - // information necessary to retrieve the kubeconfig from the backing store (such a unique ID for the cluster required for the API) - Tags map[string]string - // Error is an error which occured when trying to discover kubeconfig paths in the backing store - Error error -} - -type KubeconfigStore interface { - // GetID returns the unique store ID - // should be - // - ".default" if the kubeconfigStore.ID is not set - // - "." if the kubeconfigStore.ID is set - GetID() string - - // GetKind returns the store kind (e.g., filesystem) - GetKind() types.StoreKind - - // GetContextPrefix returns the prefix for the kubeconfig context names displayed in the search result - // includes the path to the kubeconfig in the backing store because some stores compute the prefix based on that - GetContextPrefix(path string) string - - // VerifyKubeconfigPaths verifies that the configured search paths are valid - // can also include additional preprocessing - VerifyKubeconfigPaths() error - - // StartSearch starts the search over the configured search paths - // and populates the results via the given channel - StartSearch(channel chan SearchResult) - - // GetKubeconfigForPath returns the byte representation of the kubeconfig - // the kubeconfig has to fetch the kubeconfig from its backing store (e.g., uses the HTTP API) - // Optional tags might help identify the cluster in the backing store, but typically such information is already encoded in the kubeconfig path (implementation specific) - GetKubeconfigForPath(path string, tags map[string]string) ([]byte, error) - - // GetLogger returns the logger of the store - GetLogger() *logrus.Entry - - // GetStoreConfig returns the store's configuration from the switch config file - GetStoreConfig() types.KubeconfigStore -} - -// Previewer can be optionally implemented by stores to show custom preview content -// before the kubeconfig -type Previewer interface { - GetSearchPreview(path string, optionalTags map[string]string) (string, error) -} - type FilesystemStore struct { Logger *logrus.Entry KubeconfigStore types.KubeconfigStore diff --git a/pkg/store/types/types.go b/pkg/store/types/types.go new file mode 100644 index 00000000..9a2a3375 --- /dev/null +++ b/pkg/store/types/types.go @@ -0,0 +1,76 @@ +// Copyright 2021 The Kubeswitch authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "github.com/danielfoehrkn/kubeswitch/types" + + "github.com/sirupsen/logrus" +) + +// SearchResult is a full kubeconfig path discovered from the kubeconfig store +// given the contained kubeconfig path, the store knows how to retrieve and return the +// actual kubeconfig +type SearchResult struct { + // KubeconfigPath is the kubeconfig path in the backing store which most of the time encodes enough information to + // retrieve the kubeconfig associated with it. + KubeconfigPath string + // Tags contains the additional metadata that the store wants to associate with a context name. + // This metadata is later handed over in the getKubeconfigForPath() function when retrieving the kubeconfig bytes for the path and might contain + // information necessary to retrieve the kubeconfig from the backing store (such a unique ID for the cluster required for the API) + Tags map[string]string + // Error is an error which occured when trying to discover kubeconfig paths in the backing store + Error error +} + +type KubeconfigStore interface { + // GetID returns the unique store ID + // should be + // - ".default" if the kubeconfigStore.ID is not set + // - "." if the kubeconfigStore.ID is set + GetID() string + + // GetKind returns the store kind (e.g., filesystem) + GetKind() types.StoreKind + + // GetContextPrefix returns the prefix for the kubeconfig context names displayed in the search result + // includes the path to the kubeconfig in the backing store because some stores compute the prefix based on that + GetContextPrefix(path string) string + + // VerifyKubeconfigPaths verifies that the configured search paths are valid + // can also include additional preprocessing + VerifyKubeconfigPaths() error + + // StartSearch starts the search over the configured search paths + // and populates the results via the given channel + StartSearch(channel chan SearchResult) + + // GetKubeconfigForPath returns the byte representation of the kubeconfig + // the kubeconfig has to fetch the kubeconfig from its backing store (e.g., uses the HTTP API) + // Optional tags might help identify the cluster in the backing store, but typically such information is already encoded in the kubeconfig path (implementation specific) + GetKubeconfigForPath(path string, tags map[string]string) ([]byte, error) + + // GetLogger returns the logger of the store + GetLogger() *logrus.Entry + + // GetStoreConfig returns the store's configuration from the switch config file + GetStoreConfig() types.KubeconfigStore +} + +// Previewer can be optionally implemented by stores to show custom preview content +// before the kubeconfig +type Previewer interface { + GetSearchPreview(path string, optionalTags map[string]string) (string, error) +} diff --git a/pkg/subcommands/alias/alias.go b/pkg/subcommands/alias/alias.go index a7cd7601..19a3617f 100644 --- a/pkg/subcommands/alias/alias.go +++ b/pkg/subcommands/alias/alias.go @@ -19,12 +19,13 @@ import ( "os" "strings" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/sirupsen/logrus" + "github.com/danielfoehrkn/kubeswitch/pkg" - "github.com/danielfoehrkn/kubeswitch/pkg/store" + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" "github.com/danielfoehrkn/kubeswitch/pkg/subcommands/alias/state" "github.com/danielfoehrkn/kubeswitch/types" - "github.com/jedib0t/go-pretty/v6/table" - "github.com/sirupsen/logrus" ) var logger = logrus.New() @@ -128,7 +129,7 @@ func RemoveAlias(aliasToRemove, stateDir string) error { // Alias just maintains an alias record in the switch // state folder instead of renaming a context in the kubeconfig // this works independent of the backing store -func Alias(aliasName, ctxNameToBeAliased string, stores []store.KubeconfigStore, config *types.Config, stateDir string, noIndex bool) error { +func Alias(aliasName, ctxNameToBeAliased string, stores []storetypes.KubeconfigStore, config *types.Config, stateDir string, noIndex bool) error { if _, err := os.Stat(stateDir); os.IsNotExist(err) { if err := os.Mkdir(stateDir, 0755); err != nil { return err diff --git a/pkg/subcommands/clean/clean.go b/pkg/subcommands/clean/clean.go index b8754f28..f0533888 100644 --- a/pkg/subcommands/clean/clean.go +++ b/pkg/subcommands/clean/clean.go @@ -19,11 +19,11 @@ import ( "os" "github.com/danielfoehrkn/kubeswitch/pkg/cache" - "github.com/danielfoehrkn/kubeswitch/pkg/store" + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" kubeconfigutil "github.com/danielfoehrkn/kubeswitch/pkg/util/kubectx_copied" ) -func Clean(stores []store.KubeconfigStore) error { +func Clean(stores []storetypes.KubeconfigStore) error { // cleanup temporary kubeconfig files tempDir := os.ExpandEnv(kubeconfigutil.TemporaryKubeconfigDir) files, _ := os.ReadDir(tempDir) diff --git a/pkg/subcommands/exec/exec.go b/pkg/subcommands/exec/exec.go index 9c2b25ab..22d91ae8 100644 --- a/pkg/subcommands/exec/exec.go +++ b/pkg/subcommands/exec/exec.go @@ -22,13 +22,13 @@ import ( "github.com/sirupsen/logrus" easy "github.com/t-tomalak/logrus-easy-formatter" - "github.com/danielfoehrkn/kubeswitch/pkg/store" + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" list_contexts "github.com/danielfoehrkn/kubeswitch/pkg/subcommands/list-contexts" setcontext "github.com/danielfoehrkn/kubeswitch/pkg/subcommands/set-context" "github.com/danielfoehrkn/kubeswitch/types" ) -func ExecuteCommand(pattern string, command []string, stores []store.KubeconfigStore, config *types.Config, stateDir string, noIndex bool, showDebugLogs bool) error { +func ExecuteCommand(pattern string, command []string, stores []storetypes.KubeconfigStore, config *types.Config, stateDir string, noIndex bool, showDebugLogs bool) error { contexts, err := list_contexts.ListContexts(pattern, stores, config, stateDir, noIndex) if err != nil { return err diff --git a/pkg/subcommands/gardener/controlplane.go b/pkg/subcommands/gardener/controlplane.go index 53c44d3f..4e9596cf 100644 --- a/pkg/subcommands/gardener/controlplane.go +++ b/pkg/subcommands/gardener/controlplane.go @@ -19,12 +19,14 @@ import ( "os" "strings" + "github.com/sirupsen/logrus" + "github.com/danielfoehrkn/kubeswitch/pkg/store" gardenerstore "github.com/danielfoehrkn/kubeswitch/pkg/store/gardener" + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" historyutil "github.com/danielfoehrkn/kubeswitch/pkg/subcommands/history/util" kubeconfigutil "github.com/danielfoehrkn/kubeswitch/pkg/util/kubectx_copied" "github.com/danielfoehrkn/kubeswitch/types" - "github.com/sirupsen/logrus" ) const ( @@ -37,7 +39,7 @@ var ( kubeconfigPathFromEnv = os.Getenv("KUBECONFIG") ) -func SwitchToControlplane(stores []store.KubeconfigStore, kubeconfigPathFromFlag string) (*string, error) { +func SwitchToControlplane(stores []storetypes.KubeconfigStore, kubeconfigPathFromFlag string) (*string, error) { kubeconfig, err := getKubeconfig(kubeconfigPathFromFlag) if err != nil { return nil, err diff --git a/pkg/subcommands/history/history.go b/pkg/subcommands/history/history.go index 31aaf007..6937a697 100644 --- a/pkg/subcommands/history/history.go +++ b/pkg/subcommands/history/history.go @@ -18,18 +18,19 @@ import ( "bytes" "fmt" - "github.com/danielfoehrkn/kubeswitch/pkg/store" + "github.com/ktr0731/go-fuzzyfinder" + "github.com/sirupsen/logrus" + + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" "github.com/danielfoehrkn/kubeswitch/pkg/subcommands/history/util" setcontext "github.com/danielfoehrkn/kubeswitch/pkg/subcommands/set-context" kubeconfigutil "github.com/danielfoehrkn/kubeswitch/pkg/util/kubectx_copied" "github.com/danielfoehrkn/kubeswitch/types" - "github.com/ktr0731/go-fuzzyfinder" - "github.com/sirupsen/logrus" ) var logger = logrus.New() -func SwitchToHistory(stores []store.KubeconfigStore, config *types.Config, stateDir string, noIndex bool) (*string, *string, error) { +func SwitchToHistory(stores []storetypes.KubeconfigStore, config *types.Config, stateDir string, noIndex bool) (*string, *string, error) { history, err := util.ReadHistory() if err != nil { return nil, nil, err @@ -130,7 +131,7 @@ func setNamespace(ns string, tmpKubeconfigFile string) error { // SetPreviousContext sets the previously used context from the history (position 1) // does not add a history entry -func SetPreviousContext(stores []store.KubeconfigStore, config *types.Config, stateDir string, noIndex bool) (*string, *string, error) { +func SetPreviousContext(stores []storetypes.KubeconfigStore, config *types.Config, stateDir string, noIndex bool) (*string, *string, error) { history, err := util.ReadHistory() if err != nil { return nil, nil, err @@ -167,7 +168,7 @@ func SetPreviousContext(stores []store.KubeconfigStore, config *types.Config, st // SetLastContext sets the last used context from the history (position 0) // does not add a history entry -func SetLastContext(stores []store.KubeconfigStore, config *types.Config, stateDir string, noIndex bool) (*string, *string, error) { +func SetLastContext(stores []storetypes.KubeconfigStore, config *types.Config, stateDir string, noIndex bool) (*string, *string, error) { history, err := util.ReadHistory() if err != nil { return nil, nil, err diff --git a/pkg/subcommands/list-contexts/list-contexts.go b/pkg/subcommands/list-contexts/list-contexts.go index 00c5e55c..d4f7ff7a 100644 --- a/pkg/subcommands/list-contexts/list-contexts.go +++ b/pkg/subcommands/list-contexts/list-contexts.go @@ -19,15 +19,16 @@ import ( "sort" "github.com/becheran/wildmatch-go" + "github.com/sirupsen/logrus" + "github.com/danielfoehrkn/kubeswitch/pkg" - "github.com/danielfoehrkn/kubeswitch/pkg/store" + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" "github.com/danielfoehrkn/kubeswitch/types" - "github.com/sirupsen/logrus" ) var logger = logrus.New() -func ListContexts(pattern string, stores []store.KubeconfigStore, config *types.Config, stateDir string, noIndex bool) ([]string, error) { +func ListContexts(pattern string, stores []storetypes.KubeconfigStore, config *types.Config, stateDir string, noIndex bool) ([]string, error) { c, err := pkg.DoSearch(stores, config, stateDir, noIndex) if err != nil { return nil, fmt.Errorf("cannot list contexts: %v", err) diff --git a/pkg/subcommands/set-context/set_context.go b/pkg/subcommands/set-context/set_context.go index cdcd1470..8dfd8aef 100644 --- a/pkg/subcommands/set-context/set_context.go +++ b/pkg/subcommands/set-context/set_context.go @@ -18,18 +18,19 @@ import ( "fmt" "strings" + "github.com/hashicorp/go-multierror" + "github.com/sirupsen/logrus" + "github.com/danielfoehrkn/kubeswitch/pkg" - "github.com/danielfoehrkn/kubeswitch/pkg/store" + storetypes "github.com/danielfoehrkn/kubeswitch/pkg/store/types" historyutil "github.com/danielfoehrkn/kubeswitch/pkg/subcommands/history/util" kubeconfigutil "github.com/danielfoehrkn/kubeswitch/pkg/util/kubectx_copied" "github.com/danielfoehrkn/kubeswitch/types" - "github.com/hashicorp/go-multierror" - "github.com/sirupsen/logrus" ) var logger = logrus.New() -func SetContext(desiredContext string, stores []store.KubeconfigStore, config *types.Config, stateDir string, noIndex bool, appendToHistory bool) (*string, *string, error) { +func SetContext(desiredContext string, stores []storetypes.KubeconfigStore, config *types.Config, stateDir string, noIndex bool, appendToHistory bool) (*string, *string, error) { c, err := pkg.DoSearch(stores, config, stateDir, noIndex) if err != nil { return nil, nil, err