Skip to content

Commit

Permalink
Add support for --kube-context option into botkube CLI (#1457)
Browse files Browse the repository at this point in the history
  • Loading branch information
mickael-ange authored Jun 14, 2024
1 parent d221782 commit acefb61
Show file tree
Hide file tree
Showing 9 changed files with 75 additions and 42 deletions.
1 change: 1 addition & 0 deletions cmd/cli/docs/botkube_install.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ botkube install --repo @local
--force Force resource updates through a replacement strategy
-h, --help help for install
--kubeconfig string Paths to a kubeconfig. Only required if out-of-cluster.
--kubecontext string The name of the kubeconfig context to use.
--namespace string Botkube installation namespace. (default "botkube")
--no-hooks Disable pre/post install/upgrade hooks
--release-name string Botkube Helm chart release name. (default "botkube")
Expand Down
1 change: 1 addition & 0 deletions cmd/cli/docs/botkube_migrate.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ botkube migrate [OPTIONS] [flags]
--image-tag string Botkube image tag, e.g. "latest" or "v1.7.0"
--instance-name string Botkube Cloud Instance name that will be created
--kubeconfig string Paths to a kubeconfig. Only required if out-of-cluster.
--kubecontext string The name of the kubeconfig context to use.
-l, --label string Label used for identifying the Botkube pod (default "app=botkube")
-n, --namespace string Namespace of Botkube pod (default "botkube")
-q, --skip-connect Skips connecting to Botkube Cloud after migration
Expand Down
1 change: 1 addition & 0 deletions cmd/cli/docs/botkube_uninstall.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ botkube uninstall --release-name botkube-dev
-h, --help help for uninstall
--keep-history remove all associated resources and mark the release as deleted, but retain the release history
--kubeconfig string Paths to a kubeconfig. Only required if out-of-cluster.
--kubecontext string The name of the kubeconfig context to use.
--namespace string Botkube namespace. (default "botkube")
--no-hooks prevent hooks from running during uninstallation
--release-name string Botkube Helm release name. (default "botkube")
Expand Down
13 changes: 7 additions & 6 deletions internal/cli/helmx/action_cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,23 @@ import (

"helm.sh/helm/v3/pkg/action"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest"

"github.com/kubeshop/botkube/internal/cli"
"github.com/kubeshop/botkube/internal/kubex"
"github.com/kubeshop/botkube/pkg/ptr"
)

const helmDriver = "secrets"

// GetActionConfiguration returns generic configuration for Helm actions.
func GetActionConfiguration(k8sCfg *rest.Config, forNamespace string) (*action.Configuration, error) {
func GetActionConfiguration(k8sCfg *kubex.ConfigWithMeta, forNamespace string) (*action.Configuration, error) {
actionConfig := new(action.Configuration)
helmCfg := &genericclioptions.ConfigFlags{
APIServer: &k8sCfg.Host,
Insecure: &k8sCfg.Insecure,
CAFile: &k8sCfg.CAFile,
BearerToken: &k8sCfg.BearerToken,
APIServer: &k8sCfg.K8s.Host,
Insecure: &k8sCfg.K8s.Insecure,
CAFile: &k8sCfg.K8s.CAFile,
BearerToken: &k8sCfg.K8s.BearerToken,
Context: &k8sCfg.CurrentContext,
Namespace: ptr.FromType(forNamespace),
}

Expand Down
4 changes: 2 additions & 2 deletions internal/cli/install/helm/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ import (
"helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/storage/driver"
"k8s.io/client-go/rest"

"github.com/kubeshop/botkube/internal/cli"
"github.com/kubeshop/botkube/internal/cli/helmx"
"github.com/kubeshop/botkube/internal/cli/install/iox"
"github.com/kubeshop/botkube/internal/cli/printer"
"github.com/kubeshop/botkube/internal/kubex"
)

const restartAnnotationFmt = "extraAnnotations.cli\\.botkube\\.io\\/restart\\-timestamp=\"%d\""
Expand All @@ -35,7 +35,7 @@ type Helm struct {
}

// NewHelm returns a new Helm instance.
func NewHelm(k8sCfg *rest.Config, forNamespace string) (*Helm, error) {
func NewHelm(k8sCfg *kubex.ConfigWithMeta, forNamespace string) (*Helm, error) {
configuration, err := helmx.GetActionConfiguration(k8sCfg, forNamespace)
if err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func Install(ctx context.Context, w io.Writer, k8sCfg *kubex.ConfigWithMeta, opt
return err
}

helmInstaller, err := helm.NewHelm(k8sCfg.K8s, opts.HelmParams.Namespace)
helmInstaller, err := helm.NewHelm(k8sCfg, opts.HelmParams.Namespace)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions internal/cli/uninstall/helm/uninstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import (

"github.com/avast/retry-go/v4"
"helm.sh/helm/v3/pkg/action"
"k8s.io/client-go/rest"

"github.com/kubeshop/botkube/internal/cli/helmx"
"github.com/kubeshop/botkube/internal/cli/printer"
"github.com/kubeshop/botkube/internal/kubex"
)

// Helm provides option to or delete Helm release.
Expand All @@ -18,7 +18,7 @@ type Helm struct {
}

// NewHelm returns a new Helm instance.
func NewHelm(k8sCfg *rest.Config, forNamespace string) (*Helm, error) {
func NewHelm(k8sCfg *kubex.ConfigWithMeta, forNamespace string) (*Helm, error) {
configuration, err := helmx.GetActionConfiguration(k8sCfg, forNamespace)
if err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/uninstall/uninstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func Uninstall(ctx context.Context, w io.Writer, k8sCfg *kubex.ConfigWithMeta, o
}
}

uninstaller, err := helm.NewHelm(k8sCfg.K8s, opts.HelmParams.ReleaseNamespace)
uninstaller, err := helm.NewHelm(k8sCfg, opts.HelmParams.ReleaseNamespace)
if err != nil {
return err
}
Expand Down
89 changes: 59 additions & 30 deletions internal/kubex/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ import (
)

var kubeconfig string
var kubecontext string

// RegisterKubeconfigFlag registers `--kubeconfig` flag.
func RegisterKubeconfigFlag(flags *pflag.FlagSet) {
flags.StringVar(&kubeconfig, clientcmd.RecommendedConfigPathFlag, "", "Paths to a kubeconfig. Only required if out-of-cluster.")
flags.StringVar(&kubecontext, "kubecontext", "", "The name of the kubeconfig context to use.")
}

type ConfigWithMeta struct {
Expand All @@ -35,53 +37,80 @@ type ConfigWithMeta struct {
//
// code inspired by sigs.k8s.io/[email protected]/pkg/client/config/config.go
func LoadRestConfigWithMetaInformation() (*ConfigWithMeta, error) {
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
// 1. --kubeconfig flag
if kubeconfig != "" {
c := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig}, nil)
return transform(c)
}

// 2. KUBECONFIG environment variable pointing at a file
kubeconfigPath := os.Getenv(clientcmd.RecommendedConfigPathEnvVar)
if len(kubeconfigPath) == 0 {
if c, err := rest.InClusterConfig(); err == nil {
return &ConfigWithMeta{
K8s: c,
CurrentContext: "In cluster",
}, nil
}
}

// 3. In-cluster config if running in cluster
// 4. $HOME/.kube/config if exists
// 5. user.HomeDir/.kube/config if exists
//
// NOTE: For default config file locations, upstream only checks
// $HOME for the user's home directory, but we can also try
// os/user.HomeDir when $HOME is unset.
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
if _, ok := os.LookupEnv("HOME"); !ok {
u, err := user.Current()
if err != nil {
return nil, fmt.Errorf("could not get current user: %w", err)
loadingRules.ExplicitPath = kubeconfig
} else {
// 2. KUBECONFIG environment variable pointing at a file
kubeconfigPath := os.Getenv(clientcmd.RecommendedConfigPathEnvVar)
if len(kubeconfigPath) == 0 {
// 3. In-cluster config if running in cluster
if c, err := rest.InClusterConfig(); err == nil {
return &ConfigWithMeta{
K8s: c,
CurrentContext: "In cluster",
}, nil
}
} else {
loadingRules.ExplicitPath = kubeconfigPath
// 4. $HOME/.kube/config if exists
// 5. user.HomeDir/.kube/config if exists
//
// NOTE: For default config file locations, upstream only checks
// $HOME for the user's home directory, but we can also try
// os/user.HomeDir when $HOME is unset.
if _, ok := os.LookupEnv("HOME"); !ok {
u, err := user.Current()
if err != nil {
return nil, fmt.Errorf("could not get current user: %w", err)
}
loadingRules.Precedence = append(loadingRules.Precedence, filepath.Join(u.HomeDir, clientcmd.RecommendedHomeDir, clientcmd.RecommendedFileName))
}
}
loadingRules.Precedence = append(loadingRules.Precedence, filepath.Join(u.HomeDir, clientcmd.RecommendedHomeDir, clientcmd.RecommendedFileName))
}

return transform(clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, nil))
configOverrides := &clientcmd.ConfigOverrides{CurrentContext: loadKubecontext("")}
return transform(clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides))
}

func transform(c clientcmd.ClientConfig) (*ConfigWithMeta, error) {
rawConfig, err := c.RawConfig()
if err != nil {
return nil, fmt.Errorf("while getting raw config: %v", err)
}

clientConfig, err := c.ClientConfig()
if err != nil {
return nil, fmt.Errorf("while getting client config: %v", err)
}

return &ConfigWithMeta{
K8s: clientConfig,
CurrentContext: rawConfig.CurrentContext,
CurrentContext: loadKubecontext(rawConfig.CurrentContext),
}, nil
}

// Loads kubecontext.
//
// Config precedence:
//
// * --kubecontext flag
//
// * KUBECONTEXT environment variable
//
// * fallback value
func loadKubecontext(fallbackValue string) string {
// 1. --kubecontext flag
if kubecontext != "" {
return kubecontext
}

// 2. KUBECONTEXT env
kubeCtx := os.Getenv("KUBECONTEXT")
if kubeCtx != "" {
return kubeCtx
}

return fallbackValue
}

0 comments on commit acefb61

Please sign in to comment.