Skip to content

Commit

Permalink
Add botkube uninstall command
Browse files Browse the repository at this point in the history
  • Loading branch information
mszostok committed Jul 13, 2023
1 parent fbc4887 commit 5c4b51d
Show file tree
Hide file tree
Showing 14 changed files with 344 additions and 63 deletions.
7 changes: 4 additions & 3 deletions cmd/cli/cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ func NewInstall() *cobra.Command {
var opts install.Config

installCmd := &cobra.Command{
Use: "install [OPTIONS]",
Short: "install Botkube into cluster",
Long: "Use this command to install the Botkube agent.",
Use: "install [OPTIONS]",
Short: "install Botkube into cluster",
Long: "Use this command to install the Botkube agent.",
Aliases: []string{"instl", "deploy"},
Example: heredoc.WithCLIName(`
# Install latest stable Botkube version
<cli> install
Expand Down
2 changes: 2 additions & 0 deletions cmd/cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func NewRoot() *cobra.Command {
Quick Start:
$ <cli> install # Install Botkube
$ <cli> uninstall # Uninstall Botkube
Botkube Cloud:
Expand All @@ -45,6 +46,7 @@ func NewRoot() *cobra.Command {
NewMigrate(),
NewDocs(),
NewInstall(),
NewUninstall(),
extension.NewVersionCobraCmd(
extension.WithUpgradeNotice(orgName, repoName),
),
Expand Down
60 changes: 60 additions & 0 deletions cmd/cli/cmd/uninstall.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package cmd

import (
"os"
"time"

"github.com/pkg/errors"
"github.com/spf13/cobra"

"github.com/kubeshop/botkube/internal/cli"
"github.com/kubeshop/botkube/internal/cli/heredoc"
"github.com/kubeshop/botkube/internal/cli/install"
"github.com/kubeshop/botkube/internal/cli/uninstall"
"github.com/kubeshop/botkube/internal/kubex"
)

// NewUninstall returns a cobra.Command for deleting Botkube Helm release.
func NewUninstall() *cobra.Command {
var opts uninstall.Config

uninstallCmd := &cobra.Command{
Use: "uninstall [OPTIONS]",
Short: "uninstall Botkube from cluster",
Long: "Use this command to uninstall the Botkube agent.",
Aliases: []string{"uninstall", "del", "delete", "un"},
Example: heredoc.WithCLIName(`
# Uninstall latest stable Botkube version
<cli> uninstall
<cli> install --repo @local`, cli.Name),
RunE: func(cmd *cobra.Command, args []string) error {
config, err := kubex.LoadRestConfigWithMetaInformation()
if err != nil {
return err
}
if err != nil {
return errors.Wrap(err, "while creating k8s config")
}

return uninstall.Uninstall(cmd.Context(), os.Stdout, config, opts)
},
}

flags := uninstallCmd.Flags()

kubex.RegisterKubeconfigFlag(flags)

flags.StringVar(&opts.HelmParams.ReleaseName, "release-name", install.ReleaseName, "Botkube Helm release name.")
flags.StringVar(&opts.HelmParams.ReleaseNamespace, "namespace", install.Namespace, "Botkube namespace.")

flags.BoolVar(&opts.HelmParams.DryRun, "dry-run", false, "simulate a uninstall")
flags.BoolVar(&opts.HelmParams.DisableHooks, "no-hooks", false, "prevent hooks from running during uninstallation")
flags.BoolVar(&opts.HelmParams.KeepHistory, "keep-history", false, "remove all associated resources and mark the release as deleted, but retain the release history")
flags.BoolVar(&opts.HelmParams.Wait, "wait", true, "if set, will wait until all the resources are deleted before returning. It will wait for as long as --timeout")
flags.StringVar(&opts.HelmParams.DeletionPropagation, "cascade", "background", "Must be \"background\", \"orphan\", or \"foreground\". Selects the deletion cascading strategy for the dependents. Defaults to background.")
flags.DurationVar(&opts.HelmParams.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
flags.StringVar(&opts.HelmParams.Description, "description", "", "add a custom description")

return uninstallCmd
}
2 changes: 2 additions & 0 deletions cmd/cli/docs/botkube.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ A utility that simplifies working with Botkube.
Quick Start:

$ botkube install # Install Botkube
$ botkube uninstall # Uninstall Botkube

Botkube Cloud:

Expand All @@ -38,5 +39,6 @@ botkube [flags]
* [botkube install](botkube_install.md) - install Botkube into cluster
* [botkube login](botkube_login.md) - Login to a Botkube Cloud
* [botkube migrate](botkube_migrate.md) - Automatically migrates Botkube installation into Botkube Cloud
* [botkube uninstall](botkube_uninstall.md) - uninstall Botkube from cluster
* [botkube version](botkube_version.md) - Print the CLI version

51 changes: 51 additions & 0 deletions cmd/cli/docs/botkube_uninstall.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
title: botkube uninstall
---

## botkube uninstall

uninstall Botkube from cluster

### Synopsis

Use this command to uninstall the Botkube agent.

```
botkube uninstall [OPTIONS] [flags]
```

### Examples

```
# Uninstall latest stable Botkube version
botkube uninstall
botkube install --repo @local
```

### Options

```
--cascade string Must be "background", "orphan", or "foreground". Selects the deletion cascading strategy for the dependents. Defaults to background. (default "background")
--description string add a custom description
--dry-run simulate a uninstall
-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.
--namespace string Botkube namespace. (default "botkube")
--no-hooks prevent hooks from running during uninstallation
--release-name string Botkube Helm release name. (default "botkube")
--timeout duration time to wait for any individual Kubernetes operation (like Jobs for hooks) (default 5m0s)
--wait if set, will wait until all the resources are deleted before returning. It will wait for as long as --timeout (default true)
```

### Options inherited from parent commands

```
-v, --verbose int/string[=simple] Prints more verbose output. Allowed values: 0 - disable, 1 - simple, 2 - trace (default 0 - disable)
```

### SEE ALSO

* [botkube](botkube.md) - Botkube CLI

41 changes: 41 additions & 0 deletions internal/cli/helmx/action_cfg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package helmx

import (
"fmt"

"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/ptr"
)

const helmDriver = "secrets"

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

debugLog := func(format string, v ...interface{}) {
if cli.VerboseMode.IsTracing() {
fmt.Print(" Helm log: ") // if enabled, we need to nest that under Helm step which was already printed with 2 spaces.
fmt.Printf(format, v...)
fmt.Println()
}
}

err := actionConfig.Init(helmCfg, forNamespace, helmDriver, debugLog)
if err != nil {
return nil, fmt.Errorf("while initializing Helm configuration: %v", err)
}

return actionConfig, nil
}
5 changes: 1 addition & 4 deletions internal/cli/install/helm/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ import (
"helm.sh/helm/v3/pkg/cli/values"
)

const (
repositoryCache = "/tmp/helm"
helmDriver = "secrets"
)
const repositoryCache = "/tmp/helm"

// Config holds Helm configuration parameters.
type Config struct {
Expand Down
32 changes: 2 additions & 30 deletions internal/cli/install/helm/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@ import (
"helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/storage/driver"
"k8s.io/cli-runtime/pkg/genericclioptions"
"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/ptr"
)

// Run provides single function signature both for install and upgrade.
Expand All @@ -36,7 +34,7 @@ type Helm struct {

// NewHelm returns a new Helm instance.
func NewHelm(k8sCfg *rest.Config, forNamespace string) (*Helm, error) {
configuration, err := getConfiguration(k8sCfg, forNamespace)
configuration, err := helmx.GetActionConfiguration(k8sCfg, forNamespace)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -183,32 +181,6 @@ func (c *Helm) upgradeAction(opts Config) Run {
}
}

func getConfiguration(k8sCfg *rest.Config, forNamespace string) (*action.Configuration, error) {
actionConfig := new(action.Configuration)
helmCfg := &genericclioptions.ConfigFlags{
APIServer: &k8sCfg.Host,
Insecure: &k8sCfg.Insecure,
CAFile: &k8sCfg.CAFile,
BearerToken: &k8sCfg.BearerToken,
Namespace: ptr.FromType(forNamespace),
}

debugLog := func(format string, v ...interface{}) {
if cli.VerboseMode.IsTracing() {
fmt.Print(" Helm log: ") // if enabled, we need to nest that under Helm step which was already printed with 2 spaces.
fmt.Printf(format, v...)
fmt.Println()
}
}

err := actionConfig.Init(helmCfg, forNamespace, helmDriver, debugLog)
if err != nil {
return nil, fmt.Errorf("while initializing Helm configuration: %v", err)
}

return actionConfig, nil
}

func isLocalDir(in string) bool {
f, err := os.Stat(in)
return err == nil && f.IsDir()
Expand Down
37 changes: 11 additions & 26 deletions internal/cli/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ func Install(ctx context.Context, w io.Writer, k8sCfg *kubex.ConfigWithMeta, opt
opts.HelmParams.Version = ver
}

if err = printInstallationDetails(k8sCfg, opts, status); err != nil {
err = status.InfoStructFields("Installation details:", installDetails{
Version: opts.HelmParams.Version,
HelmRepo: opts.HelmParams.RepoLocation,
K8sCtx: k8sCfg.CurrentContext,
})
if err != nil {
return err
}

Expand Down Expand Up @@ -93,6 +98,10 @@ func Install(ctx context.Context, w io.Writer, k8sCfg *kubex.ConfigWithMeta, opt
return err
}

if opts.HelmParams.DryRun {
return printSuccessInstallMessage(opts.HelmParams.Version, w)
}

if !opts.Watch {
status.Infof("Watching Botkube installation is disabled")
if err := helm.PrintReleaseStatus("Release details:", status, rel); err != nil {
Expand Down Expand Up @@ -231,37 +240,13 @@ func printFailedInstallMessage(version string, namespace string, name string, w
return nil
}

var infoFieldsGoTpl = `{{ AdjustKeyWidth . }}
{{- range $item := (. | Extra) }}
{{ $item.Key | Key }} {{ $item.Value | Val }}
{{- end}}
`

type Custom struct {
type installDetails struct {
// Fields are printed in the same order as defined in struct.
Version string `pretty:"Version"`
HelmRepo string `pretty:"Helm repository"`
K8sCtx string `pretty:"Kubernetes Context"`
}

func printInstallationDetails(cfg *kubex.ConfigWithMeta, opts Config, status *printer.StatusPrinter) error {
renderer := style.NewGoTemplateRender(style.DefaultConfig(infoFieldsGoTpl))

out, err := renderer.Render(Custom{
Version: opts.HelmParams.Version,
HelmRepo: opts.HelmParams.RepoLocation,
K8sCtx: cfg.CurrentContext,
}, cli.IsSmartTerminal(status.Writer()))
if err != nil {
return err
}

status.InfoWithBody("Installation details:", indent.String(out, 4))

return nil
}

// ensureNamespaceCreated creates a k8s namespaces. If it already exists it does nothing.
func ensureNamespaceCreated(ctx context.Context, clientset *kubernetes.Clientset, namespace string) error {
nsName := &corev1.Namespace{
Expand Down
22 changes: 22 additions & 0 deletions internal/cli/printer/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (

"github.com/fatih/color"
"github.com/morikuni/aec"
"github.com/muesli/reflow/indent"
"go.szostok.io/version/style"
"k8s.io/apimachinery/pkg/util/duration"

"github.com/kubeshop/botkube/internal/cli"
Expand Down Expand Up @@ -132,3 +134,23 @@ func (s *StatusPrinter) InfoWithBody(header, body string) {
fmt.Fprint(s.w, aec.Column(0))
fmt.Fprintf(s.w, " • %s\n%s", header, body)
}

var allFieldsGoTpl = `{{ AdjustKeyWidth . }}
{{- range $item := (. | Extra) }}
{{ $item.Key | Key }} {{ $item.Value | Val }}
{{- end}}
`

func (s *StatusPrinter) InfoStructFields(header string, data any) error {
renderer := style.NewGoTemplateRender(style.DefaultConfig(allFieldsGoTpl))

out, err := renderer.Render(data, cli.IsSmartTerminal(s.Writer()))
if err != nil {
return err
}

s.InfoWithBody(header, indent.String(out, 4))

return nil
}
8 changes: 8 additions & 0 deletions internal/cli/uninstall/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package uninstall

import "github.com/kubeshop/botkube/internal/cli/uninstall/helm"

// Config holds parameters for Botkube deletion.
type Config struct {
HelmParams helm.Config
}
19 changes: 19 additions & 0 deletions internal/cli/uninstall/helm/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package helm

import (
"time"
)

// Config holds Helm configuration parameters.
type Config struct {
ReleaseName string
ReleaseNamespace string

DisableHooks bool
DryRun bool
KeepHistory bool
Wait bool
DeletionPropagation string
Timeout time.Duration
Description string
}
Loading

0 comments on commit 5c4b51d

Please sign in to comment.