Skip to content

Commit

Permalink
cli: uninstall command (#725)
Browse files Browse the repository at this point in the history
Uninstalls Consul on Kubernetes with options to delete all Consul installation related resources.

Also removes a hack using HELM_NAMESPACE environment variable to set the Helm Go SDK Kube Client namespace and replaces it with common.InitActionConfig, which also uses a slight hack. That hack can be removed when helm/helm#10148 is merged.
  • Loading branch information
ndhanushkodi committed Sep 23, 2021
1 parent e2d07a3 commit 9a974c1
Show file tree
Hide file tree
Showing 9 changed files with 889 additions and 103 deletions.
46 changes: 45 additions & 1 deletion cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ This repository contains a CLI tool for installing and operating [Consul](https:

## Installation & Setup
Currently the tool is not available on any releases page. Instead clone the repository and run `go build -o bin/consul-k8s`
and proceed to run the binary.
from this directory and proceed to run the binary.

## Commands
* [consul-k8s install](#consul-k8s-install)
* [consul-k8s uninstall](#consul-k8s-uninstall)

### consul-k8s install
This command installs Consul on a Kubernetes cluster. It allows `demo` and `secure` installations via preset configurations
Expand Down Expand Up @@ -81,3 +82,46 @@ Global Options:
Path to kubeconfig file. This is aliased as "-c".
```

### consul-k8s uninstall
This command uninstalls Consul on Kubernetes, while prompting whether to uninstall the release and whether to delete all
related resources such as PVCs, Secrets, and ServiceAccounts.

Get started with:
```bash
consul-k8s uninstall
```

```
Usage: consul-k8s uninstall [flags]
Uninstall Consul with options to delete data and resources associated with Consul installation.
Command Options:
-auto-approve
Skip approval prompt for uninstalling Consul. The default is false.
-name=<string>
Name of the installation. This can be used to uninstall and/or delete
the resources of a specific Helm release.
-namespace=<string>
Namespace for the Consul installation.
-timeout=<string>
Timeout to wait for uninstall. The default is 10m.
-wipe-data
When used in combination with -auto-approve, all persisted data (PVCs
and Secrets) from previous installations will be deleted. Only set this
to true when data from previous installations is no longer necessary.
The default is false.
Global Options:
-context=<string>
Kubernetes context to use.
-kubeconfig=<string>
Path to kubeconfig file. This is aliased as "-c".
```
39 changes: 39 additions & 0 deletions cli/cmd/common/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package common

import (
"fmt"
"os"
"strings"

"helm.sh/helm/v3/pkg/action"
helmCLI "helm.sh/helm/v3/pkg/cli"
"k8s.io/cli-runtime/pkg/genericclioptions"
)

const (
DefaultReleaseName = "consul"
DefaultReleaseNamespace = "consul"
)

// Abort returns true if the raw input string is not equal to "y" or "yes".
func Abort(raw string) bool {
confirmation := strings.TrimSuffix(raw, "\n")
if !(strings.ToLower(confirmation) == "y" || strings.ToLower(confirmation) == "yes") {
return true
}
return false
}

// InitActionConfig initializes a Helm Go SDK action configuration. This function currently uses a hack to override the
// namespace field that gets set in the K8s client set up by the SDK.
func InitActionConfig(actionConfig *action.Configuration, namespace string, settings *helmCLI.EnvSettings, logger action.DebugLog) (*action.Configuration, error) {
getter := settings.RESTClientGetter()
configFlags := getter.(*genericclioptions.ConfigFlags)
configFlags.Namespace = &namespace
err := actionConfig.Init(settings.RESTClientGetter(), namespace,
os.Getenv("HELM_DRIVER"), logger)
if err != nil {
return nil, fmt.Errorf("error setting up helm action configuration to find existing installations: %s", err)
}
return actionConfig, nil
}
184 changes: 87 additions & 97 deletions cli/cmd/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ const (
flagNamePreset = "preset"
defaultPreset = ""

defaultReleaseName = "consul"

flagNameConfigFile = "config-file"
flagNameSetStringValues = "set-string"
flagNameSetValues = "set"
Expand All @@ -41,7 +39,6 @@ const (
defaultAutoApprove = false

flagNameNamespace = "namespace"
defaultNamespace = "consul"

flagNameTimeout = "timeout"
defaultTimeout = "10m"
Expand Down Expand Up @@ -86,82 +83,80 @@ func (c *Command) init() {
}

c.set = flag.NewSets()
{
f := c.set.NewSet("Command Options")
f.BoolVar(&flag.BoolVar{
Name: flagNameAutoApprove,
Target: &c.flagAutoApprove,
Default: defaultAutoApprove,
Usage: "Skip confirmation prompt.",
})
f.BoolVar(&flag.BoolVar{
Name: flagNameDryRun,
Target: &c.flagDryRun,
Default: defaultDryRun,
Usage: "Run pre-install checks and display summary of installation.",
})
f.StringSliceVar(&flag.StringSliceVar{
Name: flagNameConfigFile,
Aliases: []string{"f"},
Target: &c.flagValueFiles,
Usage: "Path to a file to customize the installation, such as Consul Helm chart values file. Can be specified multiple times.",
})
f.StringVar(&flag.StringVar{
Name: flagNameNamespace,
Target: &c.flagNamespace,
Default: defaultNamespace,
Usage: "Namespace for the Consul installation.",
})
f.StringVar(&flag.StringVar{
Name: flagNamePreset,
Target: &c.flagPreset,
Default: defaultPreset,
Usage: fmt.Sprintf("Use an installation preset, one of %s. Defaults to none", strings.Join(presetList, ", ")),
})
f.StringSliceVar(&flag.StringSliceVar{
Name: flagNameSetValues,
Target: &c.flagSetValues,
Usage: "Set a value to customize. Can be specified multiple times. Supports Consul Helm chart values.",
})
f.StringSliceVar(&flag.StringSliceVar{
Name: flagNameFileValues,
Target: &c.flagFileValues,
Usage: "Set a value to customize via a file. The contents of the file will be set as the value. Can be " +
"specified multiple times. Supports Consul Helm chart values.",
})
f.StringSliceVar(&flag.StringSliceVar{
Name: flagNameSetStringValues,
Target: &c.flagSetStringValues,
Usage: "Set a string value to customize. Can be specified multiple times. Supports Consul Helm chart values.",
})
f.StringVar(&flag.StringVar{
Name: flagNameTimeout,
Target: &c.flagTimeout,
Default: defaultTimeout,
Usage: "Timeout to wait for installation to be ready.",
})
f.BoolVar(&flag.BoolVar{
Name: flagNameWait,
Target: &c.flagWait,
Default: defaultWait,
Usage: "Determines whether to wait for resources in installation to be ready before exiting command.",
})

f = c.set.NewSet("Global Options")
f.StringVar(&flag.StringVar{
Name: "kubeconfig",
Aliases: []string{"c"},
Target: &c.flagKubeConfig,
Default: "",
Usage: "Path to kubeconfig file.",
})
f.StringVar(&flag.StringVar{
Name: "context",
Target: &c.flagKubeContext,
Default: "",
Usage: "Kubernetes context to use.",
})
}
f := c.set.NewSet("Command Options")
f.BoolVar(&flag.BoolVar{
Name: flagNameAutoApprove,
Target: &c.flagAutoApprove,
Default: defaultAutoApprove,
Usage: "Skip confirmation prompt.",
})
f.BoolVar(&flag.BoolVar{
Name: flagNameDryRun,
Target: &c.flagDryRun,
Default: defaultDryRun,
Usage: "Run pre-install checks and display summary of installation.",
})
f.StringSliceVar(&flag.StringSliceVar{
Name: flagNameConfigFile,
Aliases: []string{"f"},
Target: &c.flagValueFiles,
Usage: "Path to a file to customize the installation, such as Consul Helm chart values file. Can be specified multiple times.",
})
f.StringVar(&flag.StringVar{
Name: flagNameNamespace,
Target: &c.flagNamespace,
Default: common.DefaultReleaseNamespace,
Usage: "Namespace for the Consul installation.",
})
f.StringVar(&flag.StringVar{
Name: flagNamePreset,
Target: &c.flagPreset,
Default: defaultPreset,
Usage: fmt.Sprintf("Use an installation preset, one of %s. Defaults to none", strings.Join(presetList, ", ")),
})
f.StringSliceVar(&flag.StringSliceVar{
Name: flagNameSetValues,
Target: &c.flagSetValues,
Usage: "Set a value to customize. Can be specified multiple times. Supports Consul Helm chart values.",
})
f.StringSliceVar(&flag.StringSliceVar{
Name: flagNameFileValues,
Target: &c.flagFileValues,
Usage: "Set a value to customize via a file. The contents of the file will be set as the value. Can be " +
"specified multiple times. Supports Consul Helm chart values.",
})
f.StringSliceVar(&flag.StringSliceVar{
Name: flagNameSetStringValues,
Target: &c.flagSetStringValues,
Usage: "Set a string value to customize. Can be specified multiple times. Supports Consul Helm chart values.",
})
f.StringVar(&flag.StringVar{
Name: flagNameTimeout,
Target: &c.flagTimeout,
Default: defaultTimeout,
Usage: "Timeout to wait for installation to be ready.",
})
f.BoolVar(&flag.BoolVar{
Name: flagNameWait,
Target: &c.flagWait,
Default: defaultWait,
Usage: "Determines whether to wait for resources in installation to be ready before exiting command.",
})

f = c.set.NewSet("Global Options")
f.StringVar(&flag.StringVar{
Name: "kubeconfig",
Aliases: []string{"c"},
Target: &c.flagKubeConfig,
Default: "",
Usage: "Path to kubeconfig file.",
})
f.StringVar(&flag.StringVar{
Name: "context",
Target: &c.flagKubeContext,
Default: "",
Usage: "Kubernetes context to use.",
})

c.help = c.set.Help()

Expand All @@ -172,31 +167,27 @@ func (c *Command) init() {
func (c *Command) Run(args []string) int {
c.once.Do(c.init)

// The logger is initialized in main with the name cli. Here, we reset the name to install so log lines would be prefixed with install.
c.Log.ResetNamed("install")

defer func() {
if err := c.Close(); err != nil {
c.UI.Output(err.Error())
c.Log.Error(err.Error())
os.Exit(1)
}
}()

// The logger is initialized in main with the name cli. Here, we reset the name to install so log lines would be prefixed with install.
c.Log.ResetNamed("install")

if err := c.validateFlags(args); err != nil {
c.UI.Output(err.Error())
return 1
}

// A hack to set namespace via the HELM_NAMESPACE env var until we merge a PR that will allow us to use the latest
// Helm templates.
prevHelmNSEnv := os.Getenv("HELM_NAMESPACE")
os.Setenv("HELM_NAMESPACE", c.flagNamespace)
// helmCLI.New() will create a settings object which is used by the Helm Go SDK calls.
settings := helmCLI.New()

// Any overrides by our kubeconfig and kubecontext flags is done here. The Kube client that
// is created will use this command's flags first, then the HELM_KUBECONTEXT environment variable,
// then call out to genericclioptions.ConfigFlag
settings := helmCLI.New()
os.Setenv("HELM_NAMESPACE", prevHelmNSEnv)

if c.flagKubeConfig != "" {
settings.KubeConfig = c.flagKubeConfig
}
Expand Down Expand Up @@ -260,7 +251,7 @@ func (c *Command) Run(args []string) int {
// Print out the installation summary.
if !c.flagAutoApprove {
c.UI.Output("Consul Installation Summary", terminal.WithHeaderStyle())
c.UI.Output("Installation name: %s", defaultReleaseName, terminal.WithInfoStyle())
c.UI.Output("Installation name: %s", common.DefaultReleaseName, terminal.WithInfoStyle())
c.UI.Output("Namespace: %s", c.flagNamespace, terminal.WithInfoStyle())

if len(vals) == 0 {
Expand Down Expand Up @@ -292,8 +283,7 @@ func (c *Command) Run(args []string) int {
c.UI.Output(err.Error(), terminal.WithErrorStyle())
return 1
}
confirmation = strings.TrimSuffix(confirmation, "\n")
if !(strings.ToLower(confirmation) == "y" || strings.ToLower(confirmation) == "yes") {
if common.Abort(confirmation) {
c.UI.Output("Install aborted. To learn how to customize your installation, run:\nconsul-k8s install --help", terminal.WithInfoStyle())
return 1
}
Expand All @@ -303,15 +293,15 @@ func (c *Command) Run(args []string) int {

// Setup action configuration for Helm Go SDK function calls.
actionConfig := new(action.Configuration)
if err := actionConfig.Init(settings.RESTClientGetter(), c.flagNamespace,
os.Getenv("HELM_DRIVER"), uiLogger); err != nil {
c.UI.Output(err.Error())
actionConfig, err = common.InitActionConfig(actionConfig, c.flagNamespace, settings, uiLogger)
if err != nil {
c.UI.Output(err.Error(), terminal.WithErrorStyle())
return 1
}

// Setup the installation action.
install := action.NewInstall(actionConfig)
install.ReleaseName = defaultReleaseName
install.ReleaseName = common.DefaultReleaseName
install.Namespace = c.flagNamespace
install.CreateNamespace = true
install.ChartPathOptions.RepoURL = helmRepository
Expand Down
10 changes: 5 additions & 5 deletions cli/cmd/install/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ func TestCheckForPreviousPVCs(t *testing.T) {
Name: "consul-server-test2",
},
}
c.kubernetes.CoreV1().PersistentVolumeClaims("default").Create(context.TODO(), pvc, metav1.CreateOptions{})
c.kubernetes.CoreV1().PersistentVolumeClaims("default").Create(context.TODO(), pvc2, metav1.CreateOptions{})
c.kubernetes.CoreV1().PersistentVolumeClaims("default").Create(context.Background(), pvc, metav1.CreateOptions{})
c.kubernetes.CoreV1().PersistentVolumeClaims("default").Create(context.Background(), pvc2, metav1.CreateOptions{})
err := c.checkForPreviousPVCs()
require.Error(t, err)
require.Contains(t, err.Error(), "found PVCs from previous installations (default/consul-server-test1,default/consul-server-test2), delete before re-installing")
Expand All @@ -43,7 +43,7 @@ func TestCheckForPreviousPVCs(t *testing.T) {
Name: "irrelevant-pvc",
},
}
c.kubernetes.CoreV1().PersistentVolumeClaims("default").Create(context.TODO(), pvc, metav1.CreateOptions{})
c.kubernetes.CoreV1().PersistentVolumeClaims("default").Create(context.Background(), pvc, metav1.CreateOptions{})
err = c.checkForPreviousPVCs()
require.NoError(t, err)
}
Expand All @@ -56,7 +56,7 @@ func TestCheckForPreviousSecrets(t *testing.T) {
Name: "test-consul-bootstrap-acl-token",
},
}
c.kubernetes.CoreV1().Secrets("default").Create(context.TODO(), secret, metav1.CreateOptions{})
c.kubernetes.CoreV1().Secrets("default").Create(context.Background(), secret, metav1.CreateOptions{})
err := c.checkForPreviousSecrets()
require.Error(t, err)
require.Contains(t, err.Error(), "found consul-acl-bootstrap-token secret from previous installations: \"test-consul-bootstrap-acl-token\" in namespace \"default\". To delete, run kubectl delete secret test-consul-bootstrap-acl-token --namespace default")
Expand All @@ -72,7 +72,7 @@ func TestCheckForPreviousSecrets(t *testing.T) {
Name: "irrelevant-secret",
},
}
c.kubernetes.CoreV1().Secrets("default").Create(context.TODO(), secret, metav1.CreateOptions{})
c.kubernetes.CoreV1().Secrets("default").Create(context.Background(), secret, metav1.CreateOptions{})
err = c.checkForPreviousSecrets()
require.NoError(t, err)
}
Expand Down
Loading

0 comments on commit 9a974c1

Please sign in to comment.