From 4fff2a4a5ce408f3815080442e73360c92944666 Mon Sep 17 00:00:00 2001 From: Carlisia Date: Tue, 23 Mar 2021 18:00:38 -0700 Subject: [PATCH] Make uninstall more robust and informative Signed-off-by: Carlisia --- pkg/cmd/cli/uninstall/uninstall.go | 173 +++++++++++++++++++---------- test/e2e/velero_utils.go | 7 +- 2 files changed, 123 insertions(+), 57 deletions(-) diff --git a/pkg/cmd/cli/uninstall/uninstall.go b/pkg/cmd/cli/uninstall/uninstall.go index f7ce824ca..3267e644c 100644 --- a/pkg/cmd/cli/uninstall/uninstall.go +++ b/pkg/cmd/cli/uninstall/uninstall.go @@ -19,13 +19,17 @@ package uninstall import ( "context" "fmt" - "strings" "time" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" + + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + kubeerrs "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" @@ -33,82 +37,139 @@ import ( "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" - "github.com/vmware-tanzu/velero/pkg/cmd/util/output" + "github.com/vmware-tanzu/velero/pkg/cmd/cli" "github.com/vmware-tanzu/velero/pkg/install" "github.com/vmware-tanzu/velero/pkg/util/kube" ) -// Uninstall uninstalls all components deployed using velero install command -func Uninstall(ctx context.Context, client *kubernetes.Clientset, extensionsClient *apiextensionsclientset.Clientset, veleroNamespace string) error { - if veleroNamespace == "" { - veleroNamespace = "velero" - } - err := DeleteNamespace(ctx, client, veleroNamespace) - if err != nil { - return errors.WithMessagef(err, "Uninstall failed removing Velero namespace %s", veleroNamespace) - } - - rolebinding := install.ClusterRoleBinding(veleroNamespace) - - err = client.RbacV1().ClusterRoleBindings().Delete(ctx, rolebinding.Name, metav1.DeleteOptions{}) - if err != nil { - return errors.WithMessagef(err, "Uninstall failed removing Velero cluster role binding %s", rolebinding) - } - veleroLabels := labels.FormatLabels(install.Labels()) - - crds, err := extensionsClient.ApiextensionsV1().CustomResourceDefinitions().List(ctx, metav1.ListOptions{ - LabelSelector: veleroLabels, - }) - if err != nil { - return errors.WithMessagef(err, "Uninstall failed listing Velero crds") - } - for _, removeCRD := range crds.Items { - err = extensionsClient.ApiextensionsV1().CustomResourceDefinitions().Delete(ctx, removeCRD.ObjectMeta.Name, metav1.DeleteOptions{}) - if err != nil { - return errors.WithMessagef(err, "Uninstall failed removing CRD %s", removeCRD.ObjectMeta.Name) - } - } - - fmt.Println("Uninstalled Velero") - return nil +// uninstallOptions collects all the options for uninstalling Velero from a Kubernetes cluster. +type uninstallOptions struct { + wait, force bool } -func DeleteNamespace(ctx context.Context, client *kubernetes.Clientset, namespace string) error { - err := client.CoreV1().Namespaces().Delete(ctx, namespace, metav1.DeleteOptions{}) - if err != nil { - return errors.WithMessagef(err, "Delete namespace failed removing namespace %s", namespace) - } - return wait.Poll(1*time.Second, 3*time.Minute, func() (bool, error) { - _, err := client.CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{}) - if err != nil { - // Commented this out because not sure if removing this is okay - // Printing this on Uninstall will lead to confusion - // fmt.Printf("Namespaces.Get after delete return err %v\n", err) - return true, nil // Assume any error means the delete was successful - } - return false, nil - }) +// BindFlags adds command line values to the options struct. +func (o *uninstallOptions) BindFlags(flags *pflag.FlagSet) { + flags.BoolVar(&o.wait, "wait", o.wait, "Wait for Velero deployment to be ready. Optional.") + flags.BoolVar(&o.force, "force", o.force, "Forces the Velero uninstall. Optional.") } // NewCommand creates a cobra command. func NewCommand(f client.Factory) *cobra.Command { + o := &uninstallOptions{} + c := &cobra.Command{ Use: "uninstall", Short: "Uninstall Velero", Long: `Uninstall Velero along with the CRDs. The '--namespace' flag can be used to specify the namespace where velero is installed (default: velero). +Use '--wait' to wait for the Velero Deployment to be ready before proceeding. +Use '--force' to skip the prompt confirming if you want to uninstall Velero. `, - Example: `# velero uninstall -n backup`, + Example: `# velero uninstall -n staging`, Run: func(c *cobra.Command, args []string) { - veleroNs := strings.TrimSpace(f.Namespace()) - cl, extCl, err := kube.GetClusterClient() + + // Confirm if not asked to force-skip confirmation + if !o.force { + fmt.Println("You are about to uninstall Velero.") + if !cli.GetConfirmation() { + // Don't do anything unless we get confirmation + return + } + } + + client, extCl, err := kube.GetClusterClient() cmd.CheckError(err) - cmd.CheckError(Uninstall(context.Background(), cl, extCl, veleroNs)) + cmd.CheckError(Run(context.Background(), client, extCl, f.Namespace(), o.wait)) }, } - output.BindFlags(c.Flags()) - output.ClearOutputFlagDefault(c) + o.BindFlags(c.Flags()) return c } + +// Run removes all components that were deployed using the Velero install command +func Run(ctx context.Context, client *kubernetes.Clientset, extensionsClient *apiextensionsclientset.Clientset, namespace string, waitToTerminate bool) error { + var errs []error + + // namespace + ns, err := client.CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + fmt.Printf("Velero installation namespace %q does not exist, skipping.\n", namespace) + } else { + errs = append(errs, errors.WithStack(err)) + } + } else { + if ns.Status.Phase == corev1.NamespaceTerminating { + fmt.Printf("Velero installation namespace %q is terminating.\n", namespace) + } else { + err = client.CoreV1().Namespaces().Delete(ctx, ns.Name, metav1.DeleteOptions{}) + if err != nil { + errs = append(errs, errors.WithStack(err)) + } + } + } + + // rolebinding + crb := install.ClusterRoleBinding(namespace) + if err := client.RbacV1().ClusterRoleBindings().Delete(ctx, crb.Name, metav1.DeleteOptions{}); err != nil { + if apierrors.IsNotFound(err) { + fmt.Printf("Velero installation rolebinding %q does not exist, skipping.\n", crb.Name) + } else { + errs = append(errs, errors.WithStack(err)) + } + } + + // CRDs + veleroLabels := labels.FormatLabels(install.Labels()) + crds, err := extensionsClient.ApiextensionsV1().CustomResourceDefinitions().List(ctx, metav1.ListOptions{ + LabelSelector: veleroLabels, + }) + if err != nil { + errs = append(errs, errors.WithStack(err)) + } + if len(crds.Items) == 0 { + fmt.Print("Velero CRDs do not exist, skipping.\n") + } else { + for _, removeCRD := range crds.Items { + if err = extensionsClient.ApiextensionsV1().CustomResourceDefinitions().Delete(ctx, removeCRD.ObjectMeta.Name, metav1.DeleteOptions{}); err != nil { + errs = append(errs, errors.WithStack(err)) + } + } + } + + if waitToTerminate && len(ns.Name) != 0 { + fmt.Println("Waiting for Velero uninstall to complete. You may safely press ctrl-c to stop waiting - uninstall will continue in the background.") + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + checkFunc := func() { + nsUpdated, _ := client.CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{}) + if err != nil { + errs = append(errs, errors.WithStack(err)) + } + + if nsUpdated.Status.Phase == corev1.NamespaceTerminating { + fmt.Print(".") + } + + if nsUpdated.Status.Phase != corev1.NamespaceTerminating { + fmt.Print("\n") + cancel() + } + } + + wait.Until(checkFunc, 5*time.Millisecond, ctx.Done()) + + } + + if kubeerrs.NewAggregate(errs) != nil { + fmt.Printf("Errors while attempting to uninstall Velero: %q", kubeerrs.NewAggregate(errs)) + return kubeerrs.NewAggregate(errs) + } + + fmt.Println("Velero uninstalled ⛵") + return nil +} diff --git a/test/e2e/velero_utils.go b/test/e2e/velero_utils.go index d23d969e2..eef8b463b 100644 --- a/test/e2e/velero_utils.go +++ b/test/e2e/velero_utils.go @@ -292,7 +292,12 @@ func VeleroInstall(ctx context.Context, veleroImage string, veleroNamespace stri } func VeleroUninstall(ctx context.Context, client *kubernetes.Clientset, extensionsClient *apiextensionsclient.Clientset, veleroNamespace string) error { - return uninstall.Uninstall(ctx, client, extensionsClient, veleroNamespace) + err := uninstall.Run(ctx, client, extensionsClient, veleroNamespace, true) + if err != nil { + return err + } + + return nil } func VeleroBackupLogs(ctx context.Context, veleroCLI string, veleroNamespace string, backupName string) error {