Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make uninstall more robust and informative #3618

Merged
merged 6 commits into from
Mar 26, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 117 additions & 55 deletions pkg/cmd/cli/uninstall/uninstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,96 +19,158 @@ 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"

apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"

"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)
// uninstallOptions collects all the options for uninstalling Velero from a Kubernetes cluster.
type uninstallOptions struct {
wait, force bool
}

// 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 uninstall to be ready. Optional.")
flags.BoolVar(&o.force, "force", o.force, "Forces the Velero uninstall. Optional.")
carlisia marked this conversation as resolved.
Show resolved Hide resolved
}

// 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 and clusterrolebinding.

The '--namespace' flag can be used to specify the namespace where velero is installed (default: velero).
Use '--wait' to wait for the Velero uninstall to be ready before proceeding.
Use '--force' to skip the prompt confirming if you want to uninstall Velero.
`,
Example: ` # velero uninstall --namespace staging`,
Run: func(c *cobra.Command, args []string) {

// 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(Run(context.Background(), client, extCl, f.Namespace(), o.wait))
},
}

rolebinding := install.ClusterRoleBinding(veleroNamespace)
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

err = client.RbacV1().ClusterRoleBindings().Delete(ctx, rolebinding.Name, metav1.DeleteOptions{})
// namespace
ns, err := client.CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{})
if err != nil {
return errors.WithMessagef(err, "Uninstall failed removing Velero cluster role binding %s", rolebinding)
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))
}
}
}
veleroLabels := labels.FormatLabels(install.Labels())

// 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 clusterrolebinding %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 {
return errors.WithMessagef(err, "Uninstall failed listing Velero crds")
errs = append(errs, errors.WithStack(err))
}
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)
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 {
err2 := errors.WithMessagef(err, "Uninstall failed removing CRD %s", removeCRD.ObjectMeta.Name)
errs = append(errs, errors.WithStack(err2))
}
}
}

fmt.Println("Uninstalled Velero")
return nil
}
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.")

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
ctx, cancel := context.WithCancel(ctx)
defer cancel()

checkFunc := func() {
nsUpdated, _ := client.CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{})
carlisia marked this conversation as resolved.
Show resolved Hide resolved
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()
carlisia marked this conversation as resolved.
Show resolved Hide resolved
}
}
return false, nil
})
}

// NewCommand creates a cobra command.
func NewCommand(f client.Factory) *cobra.Command {
c := &cobra.Command{
Use: "uninstall",
Short: "Uninstall Velero",
Long: `Uninstall Velero along with the CRDs.
wait.Until(checkFunc, 5*time.Millisecond, ctx.Done())

The '--namespace' flag can be used to specify the namespace where velero is installed (default: velero).
`,
Example: `# velero uninstall -n backup`,
Run: func(c *cobra.Command, args []string) {
veleroNs := strings.TrimSpace(f.Namespace())
cl, extCl, err := kube.GetClusterClient()
cmd.CheckError(err)
cmd.CheckError(Uninstall(context.Background(), cl, extCl, veleroNs))
},
}

output.BindFlags(c.Flags())
output.ClearOutputFlagDefault(c)
return c
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
}
2 changes: 1 addition & 1 deletion test/e2e/velero_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ 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)
return uninstall.Run(ctx, client, extensionsClient, veleroNamespace, true)
}

func VeleroBackupLogs(ctx context.Context, veleroCLI string, veleroNamespace string, backupName string) error {
Expand Down