github.com/oam-dev/kubevela@v1.9.11/references/cli/uninstall.go (about)

     1  /*
     2  Copyright 2021 The KubeVela Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package cli
    18  
    19  import (
    20  	"bufio"
    21  	"context"
    22  	"fmt"
    23  	"time"
    24  
    25  	"github.com/pkg/errors"
    26  	"github.com/spf13/cobra"
    27  	corev1 "k8s.io/api/core/v1"
    28  	apierror "k8s.io/apimachinery/pkg/api/errors"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	apitypes "k8s.io/apimachinery/pkg/types"
    31  	"k8s.io/client-go/rest"
    32  	"sigs.k8s.io/controller-runtime/pkg/client"
    33  
    34  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    35  	"github.com/oam-dev/kubevela/apis/types"
    36  	pkgaddon "github.com/oam-dev/kubevela/pkg/addon"
    37  	"github.com/oam-dev/kubevela/pkg/oam"
    38  	"github.com/oam-dev/kubevela/pkg/utils/common"
    39  	"github.com/oam-dev/kubevela/pkg/utils/helm"
    40  	"github.com/oam-dev/kubevela/pkg/utils/util"
    41  )
    42  
    43  // UnInstallArgs the args for uninstall command
    44  type UnInstallArgs struct {
    45  	userInput  *UserInput
    46  	helmHelper *helm.Helper
    47  	Args       common.Args
    48  	Namespace  string
    49  	Detail     bool
    50  	force      bool
    51  	cancel     bool
    52  }
    53  
    54  // NewUnInstallCommand creates `uninstall` command to uninstall vela core
    55  func NewUnInstallCommand(c common.Args, order string, ioStreams util.IOStreams) *cobra.Command {
    56  	unInstallArgs := &UnInstallArgs{Args: c, userInput: &UserInput{
    57  		Writer: ioStreams.Out,
    58  		Reader: bufio.NewReader(ioStreams.In),
    59  	}, helmHelper: helm.NewHelper()}
    60  	cmd := &cobra.Command{
    61  		Use:     "uninstall",
    62  		Short:   "Uninstalls KubeVela from a Kubernetes cluster.",
    63  		Example: `vela uninstall`,
    64  		Long:    "Uninstalls KubeVela from a Kubernetes cluster.",
    65  		Args:    cobra.ExactArgs(0),
    66  		PreRunE: func(cmd *cobra.Command, args []string) error {
    67  			unInstallArgs.cancel = unInstallArgs.userInput.AskBool("Would you like to uninstall KubeVela from this cluster?", &UserInputOptions{AssumeYes: assumeYes})
    68  			if !unInstallArgs.cancel {
    69  				return nil
    70  			}
    71  			kubeClient, err := c.GetClient()
    72  			if err != nil {
    73  				return errors.Wrapf(err, "failed to get kube client")
    74  			}
    75  
    76  			if !unInstallArgs.force {
    77  				// if use --force flag will skip checking the addon
    78  				addons, err := checkInstallAddon(kubeClient)
    79  				if err != nil {
    80  					return errors.Wrapf(err, "cannot check installed addon")
    81  				}
    82  				if len(addons) != 0 {
    83  					return fmt.Errorf("these addons have been enabled :%v, please guarantee there is no application using these addons and use `vela uninstall -f` uninstall include addon ", addons)
    84  				}
    85  			}
    86  
    87  			// filter out addon related app, these app will be delete by force uninstall
    88  			// ignore the error, this error cannot be not nil
    89  			labels, _ := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{
    90  				MatchExpressions: []metav1.LabelSelectorRequirement{{Key: oam.LabelAddonName, Operator: metav1.LabelSelectorOpDoesNotExist}}})
    91  			var apps v1beta1.ApplicationList
    92  			err = kubeClient.List(context.Background(), &apps, &client.ListOptions{
    93  				Namespace:     "",
    94  				LabelSelector: labels,
    95  			})
    96  			if err != nil {
    97  				return errors.Wrapf(err, "failed to check app in cluster")
    98  			}
    99  			if len(apps.Items) > 0 {
   100  				return fmt.Errorf("please delete all applications before uninstall. using \"vela ls -A\" view all applications")
   101  			}
   102  			return nil
   103  		},
   104  		RunE: func(cmd *cobra.Command, args []string) error {
   105  			if !unInstallArgs.cancel {
   106  				return nil
   107  			}
   108  			ioStreams.Info("Starting to uninstall KubeVela")
   109  			restConfig, err := c.GetConfig()
   110  			if err != nil {
   111  				return errors.Wrapf(err, "failed to get kube config, You can set KUBECONFIG env or make file ~/.kube/config")
   112  			}
   113  			kubeClient, err := c.GetClient()
   114  			if err != nil {
   115  				return errors.Wrapf(err, "failed to get kube client")
   116  			}
   117  			if unInstallArgs.force {
   118  				// if use --force disable all addons
   119  				err := forceDisableAddon(context.Background(), kubeClient, restConfig)
   120  				if err != nil {
   121  					return errors.Wrapf(err, "cannot force disabe all addons")
   122  				}
   123  			}
   124  			if err := unInstallArgs.helmHelper.UninstallRelease(kubeVelaReleaseName, unInstallArgs.Namespace, restConfig, unInstallArgs.Detail, ioStreams); err != nil {
   125  				return err
   126  			}
   127  			// Clean up vela-system namespace
   128  			if err := deleteNamespace(kubeClient, unInstallArgs.Namespace); err != nil {
   129  				return err
   130  			}
   131  			var namespace corev1.Namespace
   132  			var namespaceExists = true
   133  			if err := kubeClient.Get(cmd.Context(), apitypes.NamespacedName{Name: "kubevela"}, &namespace); err != nil {
   134  				if !apierror.IsNotFound(err) {
   135  					return fmt.Errorf("failed to check if namespace kubevela already exists: %w", err)
   136  				}
   137  				namespaceExists = false
   138  			}
   139  			if namespaceExists {
   140  				fmt.Printf("The namespace kubevela is exist, it is the default database of the velaux\n\n")
   141  				userConfirmation := unInstallArgs.userInput.AskBool("Do you want to delete it?", &UserInputOptions{assumeYes})
   142  				if userConfirmation {
   143  					if err := deleteNamespace(kubeClient, "kubevela"); err != nil {
   144  						return err
   145  					}
   146  				}
   147  			}
   148  			ioStreams.Info("Successfully uninstalled KubeVela")
   149  			ioStreams.Info("Please delete all CRD from cluster using \"kubectl get crd |grep oam | awk '{print $1}' | xargs kubectl delete crd\"")
   150  			return nil
   151  		},
   152  		Annotations: map[string]string{
   153  			types.TagCommandOrder: order,
   154  			types.TagCommandType:  types.TypeSystem,
   155  		},
   156  	}
   157  
   158  	cmd.Flags().StringVarP(&unInstallArgs.Namespace, "namespace", "n", "vela-system", "namespace scope for installing KubeVela Core")
   159  	cmd.Flags().BoolVarP(&unInstallArgs.Detail, "detail", "d", true, "show detail log of installation")
   160  	cmd.Flags().BoolVarP(&unInstallArgs.force, "force", "f", false, "force uninstall whole vela include all addons")
   161  	return cmd
   162  }
   163  
   164  func deleteNamespace(kubeClient client.Client, namespace string) error {
   165  	var ns corev1.Namespace
   166  	ns.Name = namespace
   167  	return kubeClient.Delete(context.Background(), &ns)
   168  }
   169  
   170  func checkInstallAddon(kubeClient client.Client) ([]string, error) {
   171  	apps := &v1beta1.ApplicationList{}
   172  	if err := kubeClient.List(context.Background(), apps, client.InNamespace(types.DefaultKubeVelaNS), client.HasLabels{oam.LabelAddonName}); err != nil {
   173  		return nil, err
   174  	}
   175  	var res []string
   176  	for _, application := range apps.Items {
   177  		res = append(res, application.Labels[oam.LabelAddonName])
   178  	}
   179  	return res, nil
   180  }
   181  
   182  // forceDisableAddon force delete all enabled addons, fluxcd must be the last one to be deleted
   183  func forceDisableAddon(ctx context.Context, kubeClient client.Client, config *rest.Config) error {
   184  	addons, err := checkInstallAddon(kubeClient)
   185  	if err != nil {
   186  		return errors.Wrapf(err, "cannot check the installed addon")
   187  	}
   188  	// fluxcd addon should be deleted lastly
   189  	fluxcdFlag := false
   190  	for _, addon := range addons {
   191  		if addon == "fluxcd" {
   192  			fluxcdFlag = true
   193  			continue
   194  		}
   195  		if err := pkgaddon.DisableAddon(ctx, kubeClient, addon, config, true); err != nil {
   196  			return err
   197  		}
   198  	}
   199  	if fluxcdFlag {
   200  		timeConsumed := time.Now()
   201  		var addons []string
   202  		for {
   203  			// block 5 minute until other addons have been deleted
   204  			if time.Now().After(timeConsumed.Add(5 * time.Minute)) {
   205  				return fmt.Errorf("timeout disable addon, please disable theis addons: %v", addons)
   206  			}
   207  			addons, err = checkInstallAddon(kubeClient)
   208  			if err != nil {
   209  				return err
   210  			}
   211  			if len(addons) == 1 && addons[0] == "fluxcd" {
   212  				break
   213  			}
   214  			time.Sleep(5 * time.Second)
   215  		}
   216  		if err := pkgaddon.DisableAddon(ctx, kubeClient, "fluxcd", config, true); err != nil {
   217  			return err
   218  		}
   219  		timeConsumed = time.Now()
   220  		for {
   221  			if time.Now().After(timeConsumed.Add(5 * time.Minute)) {
   222  				return errors.New("timeout disable fluxcd addon, please disable the addon manually")
   223  			}
   224  			addons, err := checkInstallAddon(kubeClient)
   225  			if err != nil {
   226  				return err
   227  			}
   228  			if len(addons) == 0 {
   229  				break
   230  			}
   231  			fmt.Printf("Waiting delete the fluxcd addon, timeout left %s . \r\n", 5*time.Minute-time.Since(timeConsumed))
   232  			time.Sleep(2 * time.Second)
   233  		}
   234  	}
   235  	return nil
   236  }