From eb31feccd379c5691284036785793e6bc058e2cf Mon Sep 17 00:00:00 2001 From: Nitya Dhanushkodi Date: Tue, 21 Sep 2021 23:34:04 -0700 Subject: [PATCH] Address review comments and bugs. - delete roles, rolebindings, service accounts by label - use hack for setting namespace for helm kube client - fallback to release name/namespace consul --- cli/cmd/common/utils.go | 11 +- cli/cmd/install/install.go | 12 +- cli/cmd/uninstall/uninstall.go | 234 ++++++++++++++++++++-------- cli/cmd/uninstall/uninstall_test.go | 61 ++++++++ cli/go.mod | 1 + 5 files changed, 245 insertions(+), 74 deletions(-) diff --git a/cli/cmd/common/utils.go b/cli/cmd/common/utils.go index fe73724230..f1f19908b3 100644 --- a/cli/cmd/common/utils.go +++ b/cli/cmd/common/utils.go @@ -2,10 +2,11 @@ package common import "strings" -func PrefixLines(prefix, lines string) string { - var prefixedLines string - for _, l := range strings.Split(lines, "\n") { - prefixedLines += prefix + l + "\n" +// 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 strings.TrimSuffix(prefixedLines, "\n") + return false } diff --git a/cli/cmd/install/install.go b/cli/cmd/install/install.go index 0946a8af8b..bed7954ec5 100644 --- a/cli/cmd/install/install.go +++ b/cli/cmd/install/install.go @@ -170,15 +170,16 @@ 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 @@ -290,8 +291,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 } diff --git a/cli/cmd/uninstall/uninstall.go b/cli/cmd/uninstall/uninstall.go index 97bcbf3e03..5c6324b17c 100644 --- a/cli/cmd/uninstall/uninstall.go +++ b/cli/cmd/uninstall/uninstall.go @@ -14,6 +14,7 @@ import ( "helm.sh/helm/v3/pkg/action" helmCLI "helm.sh/helm/v3/pkg/cli" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/kubernetes" ) @@ -29,6 +30,9 @@ const ( flagWipeData = "wipe-data" defaultWipeData = false + + flagTimeout = "timeout" + defaultTimeout = "10m" ) type Command struct { @@ -42,6 +46,8 @@ type Command struct { flagReleaseName string flagAutoApprove bool flagWipeData bool + flagTimeout string + timeoutDuration time.Duration flagKubeConfig string flagKubeContext string @@ -77,6 +83,12 @@ func (c *Command) init() { Default: defaultAnyReleaseName, Usage: "Name of the installation. This can be used to uninstall and/or delete the resources of a specific Helm release.", }) + f.StringVar(&flag.StringVar{ + Name: flagTimeout, + Target: &c.flagTimeout, + Default: defaultTimeout, + Usage: "Timeout to wait for uninstall.", + }) f = c.set.NewSet("Global Options") f.StringVar(&flag.StringVar{ @@ -102,16 +114,16 @@ 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 uninstall so log lines would be prefixed with uninstall. + c.Log.ResetNamed("uninstall") + defer func() { if err := c.Close(); err != nil { - c.UI.Output(err.Error(), terminal.WithErrorStyle()) + c.Log.Error(err.Error()) os.Exit(1) } }() - // The logger is initialized in main with the name cli. Here, we reset the name to uninstall so log lines would be prefixed with uninstall. - c.Log.ResetNamed("uninstall") - if err := c.set.Parse(args); err != nil { c.UI.Output(err.Error(), terminal.WithErrorStyle()) return 1 @@ -121,13 +133,18 @@ func (c *Command) Run(args []string) int { return 1 } if c.flagWipeData && !c.flagAutoApprove { - c.UI.Output("Can't set -wipe-data alone. Omit this flag to interactively uninstall, or use it with -auto-approve to wipe all data during the uninstall.") + c.UI.Output("Can't set -wipe-data alone. Omit this flag to interactively uninstall, or use it with -auto-approve to wipe all data during the uninstall.", terminal.WithErrorStyle()) return 1 } + duration, err := time.ParseDuration(c.flagTimeout) + if err != nil { + c.UI.Output("unable to parse -%s: %s", flagTimeout, err, terminal.WithErrorStyle()) + return 1 + } + c.timeoutDuration = duration // helmCLI.New() will create a settings object which is used by the Helm Go SDK calls. settings := helmCLI.New() - if c.flagKubeConfig != "" { settings.KubeConfig = c.flagKubeConfig } @@ -151,7 +168,7 @@ func (c *Command) Run(args []string) int { } } - // Setup logger to stream Helm library logs + // Setup logger to stream Helm library logs. var uiLogger = func(s string, args ...interface{}) { logMsg := fmt.Sprintf(s, args...) c.UI.Output(logMsg, terminal.WithLibraryStyle()) @@ -161,7 +178,7 @@ func (c *Command) Run(args []string) int { // Search for Consul installation by calling `helm list`. Depends on what's already specified. actionConfig := new(action.Configuration) - actionConfig, err := c.initActionConfig(actionConfig, c.flagNamespace, settings, uiLogger) + actionConfig, err = c.initActionConfig(actionConfig, c.flagNamespace, settings, uiLogger) if err != nil { c.UI.Output(err.Error(), terminal.WithErrorStyle()) return 1 @@ -176,8 +193,8 @@ func (c *Command) Run(args []string) int { c.UI.Output("No existing Consul installations.", terminal.WithSuccessStyle()) } else { c.UI.Output("Existing Consul installation found.", terminal.WithSuccessStyle()) - c.UI.Output("Consul Un-Install Summary", terminal.WithHeaderStyle()) - c.UI.Output("Installation name: %s", foundReleaseName, terminal.WithInfoStyle()) + c.UI.Output("Consul Uninstall Summary", terminal.WithHeaderStyle()) + c.UI.Output("Name: %s", foundReleaseName, terminal.WithInfoStyle()) c.UI.Output("Namespace: %s", foundReleaseNamespace, terminal.WithInfoStyle()) // Prompt for approval to uninstall Helm release. @@ -191,29 +208,35 @@ 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") { - c.UI.Output("Un-install aborted. To learn how to customize the uninstall, run:\nconsul-k8s uninstall --help", terminal.WithInfoStyle()) + if common.Abort(confirmation) { + c.UI.Output("Uninstall aborted. To learn how to customize the uninstall, run:\nconsul-k8s uninstall --help", terminal.WithInfoStyle()) return 1 } } + // Recreating settings with the found namespace so that the kube client in Helm is re-initialized with the right namespace facepalm + //prevHelmNSEnv := os.Getenv("HELM_NAMESPACE") + //os.Setenv("HELM_NAMESPACE", foundReleaseNamespace) + //settings = helmCLI.New() + //os.Setenv("HELM_NAMESPACE", prevHelmNSEnv) + // Actually call out to `helm delete`. actionConfig, err = c.initActionConfig(actionConfig, foundReleaseNamespace, settings, uiLogger) if err != nil { c.UI.Output(err.Error(), terminal.WithErrorStyle()) return 1 } + uninstaller := action.NewUninstall(actionConfig) + uninstaller.Timeout = c.timeoutDuration res, err := uninstaller.Run(foundReleaseName) if err != nil { c.UI.Output("unable to uninstall: %s", err, terminal.WithErrorStyle()) return 1 - } else if res != nil && res.Info != "" { - c.UI.Output("uninstall result: %s", res.Info, terminal.WithErrorStyle()) - return 1 - } else { - c.UI.Output("Successfully uninstalled Consul Helm release", terminal.WithSuccessStyle()) } + if res != nil && res.Info != "" { + c.UI.Output("Uninstall result: %s", res.Info, terminal.WithInfoStyle()) + } + c.UI.Output("Successfully uninstalled Consul Helm release", terminal.WithSuccessStyle()) } // If -auto-approve=true and -wipe-data=false, we should only uninstall the release, and skip deleting resources. @@ -225,21 +248,28 @@ func (c *Command) Run(args []string) int { // At this point, even if no Helm release was found and uninstalled, there could // still be PVCs, Secrets, and Service Accounts left behind from a previous installation. // If there isn't a foundReleaseName and foundReleaseNamespace, we'll use the values of the - // flags c.flagReleaseName and c.flagNamespace. If those are empty we'll prompt the user that - // those should be provided to fully clean up the installation. + // flags c.flagReleaseName and c.flagNamespace. If those are empty we'll fall back to defaults "consul" for the + // installation name and "consul" for the namespace. if !found { if c.flagReleaseName == "" || c.flagNamespace == "" { - c.UI.Output("No existing Consul Helm installation was found. To search for existing PVCs, secrets, and service accounts left behind by a Consul installation, please provide -release-name and -namespace.", terminal.WithInfoStyle()) - return 0 + foundReleaseName = "consul" + foundReleaseNamespace = "consul" + } else { + foundReleaseName = c.flagReleaseName + foundReleaseNamespace = c.flagNamespace } - foundReleaseName = c.flagReleaseName - foundReleaseNamespace = c.flagNamespace } - // Prompt with a warning for approval before deleting PVCs, Secrets and ServiceAccounts. + c.UI.Output("Other Consul Resources", terminal.WithHeaderStyle()) + if c.flagAutoApprove { + c.UI.Output("Deleting data for installation: ", terminal.WithInfoStyle()) + c.UI.Output("Name: %s", foundReleaseName, terminal.WithInfoStyle()) + c.UI.Output("Namespace %s", foundReleaseNamespace, terminal.WithInfoStyle()) + } + // Prompt with a warning for approval before deleting PVCs, Secrets, Service Accounts, Roles, and Role Bindings. if !c.flagAutoApprove { confirmation, err := c.UI.Input(&terminal.Input{ - Prompt: "WARNING: Proceed with deleting PVCs, Secrets, and ServiceAccounts? \n Only approve if all data from previous installation can be deleted (y/N)", + Prompt: fmt.Sprintf("WARNING: Proceed with deleting PVCs, Secrets, Service Accounts, Roles, and Role Bindings for the following installation? \n\n Name: %s \n Namespace: %s \n\n Only approve if all data from this installation can be deleted. (y/N)", foundReleaseName, foundReleaseNamespace), Style: terminal.WarningStyle, Secret: false, }) @@ -247,27 +277,33 @@ 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") { - c.UI.Output("Uninstall aborted without deleting PVCs, Secrets, and ServiceAccounts.", terminal.WithInfoStyle()) + if common.Abort(confirmation) { + c.UI.Output("Uninstall aborted without deleting PVCs and Secrets.", terminal.WithInfoStyle()) return 1 } } - err = c.deletePVCs(foundReleaseName, foundReleaseNamespace) - if err != nil { + if err := c.deletePVCs(foundReleaseName, foundReleaseNamespace); err != nil { c.UI.Output(err.Error(), terminal.WithErrorStyle()) return 1 } - err = c.deleteSecrets(foundReleaseName, foundReleaseNamespace) - if err != nil { + if err := c.deleteSecrets(foundReleaseName, foundReleaseNamespace); err != nil { c.UI.Output(err.Error(), terminal.WithErrorStyle()) return 1 } - err = c.deleteServiceAccounts(foundReleaseName, foundReleaseNamespace) - if err != nil { + if err := c.deleteServiceAccounts(foundReleaseName, foundReleaseNamespace); err != nil { + c.UI.Output(err.Error(), terminal.WithErrorStyle()) + return 1 + } + + if err := c.deleteRoles(foundReleaseName, foundReleaseNamespace); err != nil { + c.UI.Output(err.Error(), terminal.WithErrorStyle()) + return 1 + } + + if err := c.deleteRoleBindings(foundReleaseName, foundReleaseNamespace); err != nil { c.UI.Output(err.Error(), terminal.WithErrorStyle()) return 1 } @@ -277,7 +313,7 @@ func (c *Command) Run(args []string) int { func (c *Command) Help() string { c.once.Do(c.init) - s := "Usage: kubectl consul uninstall [options]" + "\n" + "Uninstall Consul with options to delete data and resources associated with Consul installation." + "\n\n" + c.help + s := "Usage: consul-k8s uninstall [options]" + "\n" + "Uninstall Consul with options to delete data and resources associated with Consul installation." + "\n\n" + c.help return s } @@ -286,14 +322,11 @@ func (c *Command) Synopsis() string { } func (c *Command) initActionConfig(actionConfig *action.Configuration, namespace string, settings *helmCLI.EnvSettings, logger action.DebugLog) (*action.Configuration, error) { - var err error - if namespace == defaultAllNamespaces { - err = actionConfig.Init(settings.RESTClientGetter(), "", - os.Getenv("HELM_DRIVER"), logger) - } else { - err = actionConfig.Init(settings.RESTClientGetter(), namespace, - os.Getenv("HELM_DRIVER"), logger) - } + 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) } @@ -302,6 +335,8 @@ func (c *Command) initActionConfig(actionConfig *action.Configuration, namespace func (c *Command) findExistingInstallation(actionConfig *action.Configuration) (bool, string, string, error) { lister := action.NewList(actionConfig) + // lister.All will search for helm installations in all states, such as deployed, pending, uninstalling, etc. + lister.All = true if c.flagNamespace == defaultAllNamespaces { lister.AllNamespaces = true } @@ -316,11 +351,15 @@ func (c *Command) findExistingInstallation(actionConfig *action.Configuration) ( for _, rel := range res { if rel.Chart.Metadata.Name == "consul" { if c.flagNamespace != defaultAllNamespaces { - if c.flagNamespace == rel.Name { + // If we found a chart named "consul" and -namespace was specified, we only found the release if the + // release namespace matches the -namespace flag. + if c.flagNamespace == rel.Namespace { found = true foundReleaseName = rel.Name foundReleaseNamespace = rel.Namespace break + } else { + continue } } found = true @@ -333,6 +372,7 @@ func (c *Command) findExistingInstallation(actionConfig *action.Configuration) ( return found, foundReleaseName, foundReleaseNamespace, nil } +// deletePVCs deletes any pvcs that have the label release={{foundReleaseName}} and waits for them to be deleted. func (c *Command) deletePVCs(foundReleaseName, foundReleaseNamespace string) error { var pvcNames []string pvcSelector := metav1.ListOptions{LabelSelector: fmt.Sprintf("release=%s", foundReleaseName)} @@ -340,6 +380,10 @@ func (c *Command) deletePVCs(foundReleaseName, foundReleaseNamespace string) err if err != nil { return fmt.Errorf("deletePVCs: %s", err) } + if len(pvcs.Items) == 0 { + c.UI.Output("No PVCs found.", terminal.WithSuccessStyle()) + return nil + } for _, pvc := range pvcs.Items { err := c.kubernetes.CoreV1().PersistentVolumeClaims(foundReleaseNamespace).Delete(c.Ctx, pvc.Name, metav1.DeleteOptions{}) if err != nil { @@ -353,19 +397,18 @@ func (c *Command) deletePVCs(foundReleaseName, foundReleaseNamespace string) err return fmt.Errorf("deletePVCs: %s", err) } if len(pvcs.Items) > 0 { - err = fmt.Errorf("deletePVCs: pvcs still exist") - return err + return fmt.Errorf("deletePVCs: pvcs still exist") } - return err + return nil }, backoff.WithMaxRetries(backoff.NewConstantBackOff(100*time.Millisecond), 1800)) if err != nil { return fmt.Errorf("deletePVCs: timed out waiting for PVCs to be deleted") } if len(pvcNames) > 0 { - c.UI.Output(common.PrefixLines(" Deleted PVC => ", strings.Join(pvcNames, "\n")), terminal.WithSuccessStyle()) + for _, pvc := range pvcNames { + c.UI.Output("Deleted PVC => %s", pvc, terminal.WithSuccessStyle()) + } c.UI.Output("PVCs deleted.", terminal.WithSuccessStyle()) - } else { - c.UI.Output("No PVCs found.", terminal.WithSuccessStyle()) } return nil } @@ -377,6 +420,10 @@ func (c *Command) deleteSecrets(foundReleaseName, foundReleaseNamespace string) if err != nil { return fmt.Errorf("deleteSecrets: %s", err) } + if len(secrets.Items) == 0 { + c.UI.Output("No Consul secrets found.", terminal.WithSuccessStyle()) + return nil + } for _, secret := range secrets.Items { if strings.HasPrefix(secret.Name, foundReleaseName) { err := c.kubernetes.CoreV1().Secrets(foundReleaseNamespace).Delete(c.Ctx, secret.Name, metav1.DeleteOptions{}) @@ -387,33 +434,94 @@ func (c *Command) deleteSecrets(foundReleaseName, foundReleaseNamespace string) } } if len(secretNames) > 0 { + for _, secret := range secretNames { + c.UI.Output("Deleted Secret => %s", secret, terminal.WithSuccessStyle()) + } c.UI.Output("Consul secrets deleted.", terminal.WithSuccessStyle()) - } else { - c.UI.Output("No Consul secrets found.", terminal.WithSuccessStyle()) } return nil } -// deleteServiceAccounts deletes service accounts that have foundReleaseName in their name. +// deleteServiceAccounts deletes service accounts that have the label release={{foundReleaseName}}. func (c *Command) deleteServiceAccounts(foundReleaseName, foundReleaseNamespace string) error { var serviceAccountNames []string - sas, err := c.kubernetes.CoreV1().ServiceAccounts(foundReleaseNamespace).List(c.Ctx, metav1.ListOptions{}) + saSelector := metav1.ListOptions{LabelSelector: fmt.Sprintf("release=%s", foundReleaseName)} + sas, err := c.kubernetes.CoreV1().ServiceAccounts(foundReleaseNamespace).List(c.Ctx, saSelector) if err != nil { return fmt.Errorf("deleteServiceAccounts: %s", err) } + if len(sas.Items) == 0 { + c.UI.Output("No Consul service accounts found.", terminal.WithSuccessStyle()) + return nil + } for _, sa := range sas.Items { - if strings.HasPrefix(sa.Name, foundReleaseName) { - err := c.kubernetes.CoreV1().ServiceAccounts(foundReleaseNamespace).Delete(c.Ctx, sa.Name, metav1.DeleteOptions{}) - if err != nil { - return fmt.Errorf("deleteServiceAccounts: error deleting ServiceAccount %q: %s", sa.Name, err) - } - serviceAccountNames = append(serviceAccountNames, sa.Name) + err := c.kubernetes.CoreV1().ServiceAccounts(foundReleaseNamespace).Delete(c.Ctx, sa.Name, metav1.DeleteOptions{}) + if err != nil { + return fmt.Errorf("deleteServiceAccounts: error deleting ServiceAccount %q: %s", sa.Name, err) } + serviceAccountNames = append(serviceAccountNames, sa.Name) } if len(serviceAccountNames) > 0 { + for _, sa := range serviceAccountNames { + c.UI.Output("Deleted Service Account => %s", sa, terminal.WithSuccessStyle()) + } c.UI.Output("Consul service accounts deleted.", terminal.WithSuccessStyle()) - } else { - c.UI.Output("No Consul service accounts found.", terminal.WithSuccessStyle()) + } + return nil +} + +// deleteRoles deletes roles that have the label release={{foundReleaseName}}. +func (c *Command) deleteRoles(foundReleaseName, foundReleaseNamespace string) error { + var roleNames []string + roleSelector := metav1.ListOptions{LabelSelector: fmt.Sprintf("release=%s", foundReleaseName)} + roles, err := c.kubernetes.RbacV1().Roles(foundReleaseNamespace).List(c.Ctx, roleSelector) + if err != nil { + return fmt.Errorf("deleteRoles: %s", err) + } + if len(roles.Items) == 0 { + c.UI.Output("No Consul roles found.", terminal.WithSuccessStyle()) + return nil + } + for _, role := range roles.Items { + err := c.kubernetes.RbacV1().Roles(foundReleaseNamespace).Delete(c.Ctx, role.Name, metav1.DeleteOptions{}) + if err != nil { + return fmt.Errorf("deleteRoles: error deleting Role %q: %s", role.Name, err) + } + roleNames = append(roleNames, role.Name) + } + if len(roleNames) > 0 { + for _, role := range roleNames { + c.UI.Output("Deleted Role => %s", role, terminal.WithSuccessStyle()) + } + c.UI.Output("Consul roles deleted.", terminal.WithSuccessStyle()) + } + return nil +} + +// deleteRoleBindings deletes rolebindings that have the label release={{foundReleaseName}}. +func (c *Command) deleteRoleBindings(foundReleaseName, foundReleaseNamespace string) error { + var rolebindingNames []string + rolebindingSelector := metav1.ListOptions{LabelSelector: fmt.Sprintf("release=%s", foundReleaseName)} + rolebindings, err := c.kubernetes.RbacV1().RoleBindings(foundReleaseNamespace).List(c.Ctx, rolebindingSelector) + if err != nil { + return fmt.Errorf("deleteRoleBindings: %s", err) + } + if len(rolebindings.Items) == 0 { + c.UI.Output("No Consul rolebindings found.", terminal.WithSuccessStyle()) + return nil + } + for _, rolebinding := range rolebindings.Items { + err := c.kubernetes.RbacV1().RoleBindings(foundReleaseNamespace).Delete(c.Ctx, rolebinding.Name, metav1.DeleteOptions{}) + if err != nil { + return fmt.Errorf("deleteRoleBindings: error deleting Role %q: %s", rolebinding.Name, err) + } + rolebindingNames = append(rolebindingNames, rolebinding.Name) + } + if len(rolebindingNames) > 0 { + for _, rolebinding := range rolebindingNames { + c.UI.Output("Deleted Role Binding => %s", rolebinding, terminal.WithSuccessStyle()) + } + c.UI.Output("Consul rolebindings deleted.", terminal.WithSuccessStyle()) } return nil } diff --git a/cli/cmd/uninstall/uninstall_test.go b/cli/cmd/uninstall/uninstall_test.go index 3fd774e073..2d7b41cf87 100644 --- a/cli/cmd/uninstall/uninstall_test.go +++ b/cli/cmd/uninstall/uninstall_test.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" ) @@ -103,6 +104,66 @@ func TestDeleteServiceAccounts(t *testing.T) { require.Len(t, sas.Items, 0) } +func TestDeleteRoles(t *testing.T) { + c := getInitializedCommand(t) + c.kubernetes = fake.NewSimpleClientset() + role := &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-test-role1", + Labels: map[string]string{ + "release": "consul", + }, + }, + } + role2 := &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-test-role2", + Labels: map[string]string{ + "release": "consul", + }, + }, + } + _, err := c.kubernetes.RbacV1().Roles("default").Create(context.TODO(), role, metav1.CreateOptions{}) + require.NoError(t, err) + _, err = c.kubernetes.RbacV1().Roles("default").Create(context.TODO(), role2, metav1.CreateOptions{}) + require.NoError(t, err) + err = c.deleteRoles("consul", "default") + require.NoError(t, err) + roles, err := c.kubernetes.RbacV1().Roles("default").List(context.TODO(), metav1.ListOptions{}) + require.NoError(t, err) + require.Len(t, roles.Items, 0) +} + +func TestDeleteRoleBindings(t *testing.T) { + c := getInitializedCommand(t) + c.kubernetes = fake.NewSimpleClientset() + rolebinding := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-test-role1", + Labels: map[string]string{ + "release": "consul", + }, + }, + } + rolebinding2 := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "consul-test-role2", + Labels: map[string]string{ + "release": "consul", + }, + }, + } + _, err := c.kubernetes.RbacV1().RoleBindings("default").Create(context.TODO(), rolebinding, metav1.CreateOptions{}) + require.NoError(t, err) + _, err = c.kubernetes.RbacV1().RoleBindings("default").Create(context.TODO(), rolebinding2, metav1.CreateOptions{}) + require.NoError(t, err) + err = c.deleteRoleBindings("consul", "default") + require.NoError(t, err) + rolebindings, err := c.kubernetes.RbacV1().RoleBindings("default").List(context.TODO(), metav1.ListOptions{}) + require.NoError(t, err) + require.Len(t, rolebindings.Items, 0) +} + // getInitializedCommand sets up a command struct for tests. func getInitializedCommand(t *testing.T) *Command { t.Helper() diff --git a/cli/go.mod b/cli/go.mod index 65ff4aa36e..38ed649c60 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -22,6 +22,7 @@ require ( helm.sh/helm/v3 v3.6.1 k8s.io/api v0.21.2 k8s.io/apimachinery v0.21.2 + k8s.io/cli-runtime v0.21.0 k8s.io/client-go v0.21.2 rsc.io/letsencrypt v0.0.3 // indirect sigs.k8s.io/yaml v1.2.0