Skip to content

Commit

Permalink
feat: add capi
Browse files Browse the repository at this point in the history
Co-authored-by: Alan Amoyel <[email protected]>
  • Loading branch information
guilhem and aamoyel committed Nov 8, 2024
1 parent 71cbc30 commit 1970018
Show file tree
Hide file tree
Showing 6 changed files with 218 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Designed as a [drop-in replacement](#difference-to-kubectx) for [kubectx](https:
- [Rancher](docs/stores/rancher/rancher.md)
- Scaleway (documentation tbd)
- [Akamai / Linode](docs/stores/akamai/akamai.md)
- [Cluster API (capi)](docs/stores/capi/capi.md)
- Your favorite Cloud Provider or Managed Kubernetes Platform is not supported yet? Looking for contributions!
- **Change the namespace**
- **Change to any context and namespace from the history**
Expand Down
9 changes: 9 additions & 0 deletions cmd/switcher/switcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,15 @@ func initialize() ([]store.KubeconfigStore, *types.Config, error) {
return nil, nil, err
}
s = akamaiStore
case types.StoreKindCapi:
capiStore, err := store.NewCapiStore(kubeconfigStoreFromConfig, stateDirectory)
if err != nil {
if kubeconfigStoreFromConfig.Required != nil && !*kubeconfigStoreFromConfig.Required {
continue
}
return nil, nil, err
}
s = capiStore
default:
return nil, nil, fmt.Errorf("unknown store %q", kubeconfigStoreFromConfig.Kind)
}
Expand Down
16 changes: 16 additions & 0 deletions docs/stores/capi/capi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Cluster API (capi) store

To use the Cluster API (capi) store a kubeconfig file should be created for the management cluster.

## Configuration

The Cluster API store configuration is defined in the `kubeswitch` configuration file.

```yaml
kind: SwitchConfig
version: v1alpha1
kubeconfigStores:
- kind: capi
config:
kubeconfigPath: "/home/user/.kube/management.config"
```
176 changes: 176 additions & 0 deletions pkg/store/kubeconfig_store_capi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Copyright 2024 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 store

import (
"context"
"fmt"
"time"

"github.com/danielfoehrkn/kubeswitch/types"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/tools/clientcmd"
clusterv1beta1 "sigs.k8s.io/cluster-api/api/v1beta1"
utilkubeconfig "sigs.k8s.io/cluster-api/util/kubeconfig"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func NewCapiStore(store types.KubeconfigStore, stateDir string) (*CapiStore, error) {
storeConfig := &types.StoreConfigCapi{}
if store.Config != nil {
buf, err := yaml.Marshal(store.Config)
if err != nil {
return nil, err
}

err = yaml.Unmarshal(buf, storeConfig)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal Azure config: %w", err)
}
}

return &CapiStore{
KubeconfigStore: store,
Logger: logrus.New().WithField("store", types.StoreKindCapi),
Config: storeConfig,
}, nil
}

func (s *CapiStore) InitializeCapiStore() error {
s.Logger.Info("Initializing CAPI client")
k8sclient, err := s.getCapiClient()
if err != nil {
return err
}
s.Client = k8sclient

return nil
}

// GetID returns the unique store ID
func (s *CapiStore) GetID() string {
return fmt.Sprintf("%s.%s", types.StoreKindCapi, s.KubeconfigStore.ID)

Check failure on line 68 in pkg/store/kubeconfig_store_capi.go

View workflow job for this annotation

GitHub Actions / ci

fmt.Sprintf format %s has arg s.KubeconfigStore.ID of wrong type *string

Check failure on line 68 in pkg/store/kubeconfig_store_capi.go

View workflow job for this annotation

GitHub Actions / ci

printf: fmt.Sprintf format %s has arg s.KubeconfigStore.ID of wrong type *string (govet)
}

// GetKind returns the store kind
func (s *CapiStore) GetKind() types.StoreKind {
return types.StoreKindCapi
}

// GetContextPrefix returns the context prefix
func (s *CapiStore) GetContextPrefix(path string) string {
return string(types.StoreKindCapi)
}

// VerifyKubeconfigPaths verifies the kubeconfig paths
func (s *CapiStore) VerifyKubeconfigPaths() error {
return nil
}

func (s *CapiStore) getCapiClient() (client.Client, error) {
scheme := runtime.NewScheme()
utilruntime.Must(corev1.AddToScheme(scheme))
utilruntime.Must(clusterv1beta1.AddToScheme(scheme))

// client from s.Config.KubeconfigPath
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
&clientcmd.ClientConfigLoadingRules{ExplicitPath: s.Config.KubeconfigPath},
&clientcmd.ConfigOverrides{})

restConfig, err := clientConfig.ClientConfig()
if err != nil {
return nil, fmt.Errorf("unable to create rest config: %v", err)
}

k8sclient, err := client.New(restConfig, client.Options{
Scheme: scheme,
})
if err != nil {
return nil, fmt.Errorf("unable to create client: %v", err)
}
return k8sclient, nil
}

// StartSearch starts the search over the configured search paths
func (s *CapiStore) StartSearch(channel chan SearchResult) {
s.Logger.Debug("CAPI: start search")

ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()

// initialize CAPI client
if err := s.InitializeCapiStore(); err != nil {
channel <- SearchResult{
KubeconfigPath: "",
Error: err,
}
return
}

// list clusters
clusters := &clusterv1beta1.ClusterList{}
err := s.Client.List(ctx, clusters)
if err != nil {
channel <- SearchResult{
KubeconfigPath: "",
Error: err,
}
return
}

for _, cluster := range clusters.Items {
s.Logger.Debug("CAPI: found cluster", "name", cluster.Name, "namespace", cluster.Namespace)

channel <- SearchResult{
KubeconfigPath: fmt.Sprintf("%s-%s", cluster.Namespace, cluster.Name),
Error: nil,
Tags: map[string]string{
"namespace": cluster.Namespace,
"name": cluster.Name,
},
}
}
}

// GetKubeconfigForPath returns the kubeconfig for the path
func (s *CapiStore) GetKubeconfigForPath(path string, tags map[string]string) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()

s.Logger.Debug("CAPI: GetKubeconfigForPath", "path", path)

obj := client.ObjectKey{
Namespace: tags["namespace"],
Name: tags["name"],
}
dataBytes, err := utilkubeconfig.FromSecret(ctx, s.Client, obj)
if err != nil {
s.Logger.Debug("CAPI: GetKubeconfigForPath", "error", err)
return nil, err
}
return dataBytes, nil
}

func (s *CapiStore) GetLogger() *logrus.Entry {
return s.Logger
}

func (s *CapiStore) GetStoreConfig() types.KubeconfigStore {
return s.KubeconfigStore
}
7 changes: 7 additions & 0 deletions pkg/store/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,10 @@ type AkamaiStore struct {
Client *linodego.Client
Config *types.StoreConfigAkamai
}

type CapiStore struct {
Logger *logrus.Entry
KubeconfigStore types.KubeconfigStore
Client client.Client
Config *types.StoreConfigCapi
}
10 changes: 9 additions & 1 deletion types/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
type StoreKind string

// ValidStoreKinds contains all valid store kinds
var ValidStoreKinds = sets.NewString(string(StoreKindVault), string(StoreKindFilesystem), string(StoreKindGardener), string(StoreKindGKE), string(StoreKindAzure), string(StoreKindEKS), string(StoreKindRancher), string(StoreKindOVH), string(StoreKindScaleway), string(StoreKindDigitalOcean), string(StoreKindAkamai))
var ValidStoreKinds = sets.NewString(string(StoreKindVault), string(StoreKindFilesystem), string(StoreKindGardener), string(StoreKindGKE), string(StoreKindAzure), string(StoreKindEKS), string(StoreKindRancher), string(StoreKindOVH), string(StoreKindScaleway), string(StoreKindDigitalOcean), string(StoreKindAkamai), string(StoreKindCapi))

// ValidConfigVersions contains all valid config versions
var ValidConfigVersions = sets.NewString("v1alpha1")
Expand Down Expand Up @@ -52,6 +52,8 @@ const (
StoreKindDigitalOcean StoreKind = "digitalocean"
// StoreKindAkamai is an identifier for the Akamai store
StoreKindAkamai StoreKind = "akamai"
// StoreKindCapi is an identifier for the CAPI store
StoreKindCapi StoreKind = "capi"
)

type Config struct {
Expand Down Expand Up @@ -256,3 +258,9 @@ type StoreConfigScaleway struct {
type StoreConfigAkamai struct {
LinodeToken string `yaml:"linode_token"`
}

type StoreConfigCapi struct {
// KubeconfigPath is the path on the local filesystem pointing to the kubeconfig
// for the management cluster
KubeconfigPath string `yaml:"kubeconfigPath"`
}

0 comments on commit 1970018

Please sign in to comment.