github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/kubeblocks/upgrade.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package kubeblocks
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  	"time"
    26  
    27  	"github.com/hashicorp/go-version"
    28  	"github.com/pkg/errors"
    29  	"github.com/spf13/cobra"
    30  	appsv1 "k8s.io/api/apps/v1"
    31  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	apitypes "k8s.io/apimachinery/pkg/types"
    34  	"k8s.io/apimachinery/pkg/util/wait"
    35  	"k8s.io/cli-runtime/pkg/genericiooptions"
    36  	"k8s.io/client-go/kubernetes"
    37  	"k8s.io/klog/v2"
    38  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    39  	"k8s.io/kubectl/pkg/util/templates"
    40  
    41  	"github.com/1aal/kubeblocks/pkg/cli/printer"
    42  	"github.com/1aal/kubeblocks/pkg/cli/spinner"
    43  	"github.com/1aal/kubeblocks/pkg/cli/types"
    44  	"github.com/1aal/kubeblocks/pkg/cli/util"
    45  	"github.com/1aal/kubeblocks/pkg/cli/util/breakingchange"
    46  	"github.com/1aal/kubeblocks/pkg/cli/util/helm"
    47  	"github.com/1aal/kubeblocks/pkg/cli/util/prompt"
    48  )
    49  
    50  var (
    51  	upgradeExample = templates.Examples(`
    52  	# Upgrade KubeBlocks to specified version
    53  	kbcli kubeblocks upgrade --version=0.4.0
    54  
    55  	# Upgrade KubeBlocks other settings, for example, set replicaCount to 3
    56  	kbcli kubeblocks upgrade --set replicaCount=3`)
    57  )
    58  
    59  type getDeploymentFunc func(client kubernetes.Interface) (*appsv1.Deployment, error)
    60  
    61  func newUpgradeCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
    62  	o := &InstallOptions{
    63  		Options: Options{
    64  			IOStreams: streams,
    65  		},
    66  	}
    67  
    68  	cmd := &cobra.Command{
    69  		Use:     "upgrade",
    70  		Short:   "Upgrade KubeBlocks.",
    71  		Args:    cobra.NoArgs,
    72  		Example: upgradeExample,
    73  		Run: func(cmd *cobra.Command, args []string) {
    74  			util.CheckErr(o.Complete(f, cmd))
    75  			util.CheckErr(o.Upgrade())
    76  		},
    77  	}
    78  
    79  	cmd.Flags().StringVar(&o.Version, "version", "", "Set KubeBlocks version")
    80  	cmd.Flags().BoolVar(&o.Check, "check", true, "Check kubernetes environment before upgrade")
    81  	cmd.Flags().DurationVar(&o.Timeout, "timeout", 300*time.Second, "Time to wait for upgrading KubeBlocks, such as --timeout=10m")
    82  	cmd.Flags().BoolVar(&o.Wait, "wait", true, "Wait for KubeBlocks to be ready. It will wait for a --timeout period")
    83  	cmd.Flags().BoolVar(&o.autoApprove, "auto-approve", false, "Skip interactive approval before upgrading KubeBlocks")
    84  	helm.AddValueOptionsFlags(cmd.Flags(), &o.ValueOpts)
    85  
    86  	return cmd
    87  }
    88  
    89  func (o *InstallOptions) Upgrade() error {
    90  	klog.V(1).Info("##### Start to upgrade KubeBlocks #####")
    91  	if o.HelmCfg.Namespace() == "" {
    92  		ns, err := util.GetKubeBlocksNamespace(o.Client)
    93  		if err != nil || ns == "" {
    94  			printer.Warning(o.Out, "Failed to find deployed KubeBlocks.\n\n")
    95  			fmt.Fprint(o.Out, "Use \"kbcli kubeblocks install\" to install KubeBlocks.\n")
    96  			fmt.Fprintf(o.Out, "Use \"kbcli kubeblocks status\" to get information in more details.\n")
    97  			return nil
    98  		}
    99  		o.HelmCfg.SetNamespace(ns)
   100  	}
   101  
   102  	// check flags already been set
   103  	if o.Version == "" && helm.ValueOptsIsEmpty(&o.ValueOpts) {
   104  		fmt.Fprint(o.Out, "Nothing to upgrade, --set, --version should be specified.\n")
   105  		return nil
   106  	}
   107  
   108  	// check if KubeBlocks has been installed
   109  	v, err := util.GetVersionInfo(o.Client)
   110  	if err != nil {
   111  		return err
   112  	}
   113  
   114  	kbVersion := v.KubeBlocks
   115  	if kbVersion == "" {
   116  		return errors.New("KubeBlocks does not exist, try to run \"kbcli kubeblocks install\" to install")
   117  	}
   118  
   119  	if kbVersion == o.Version && helm.ValueOptsIsEmpty(&o.ValueOpts) {
   120  		fmt.Fprintf(o.Out, "Current version %s is same with the upgraded version, no need to upgrade.\n", o.Version)
   121  		return nil
   122  	}
   123  	fmt.Fprintf(o.Out, "Current KubeBlocks version %s.\n", v.KubeBlocks)
   124  
   125  	if err = o.checkVersion(v); err != nil {
   126  		return err
   127  	}
   128  	o.OldVersion = kbVersion
   129  	// double check when KubeBlocks version change
   130  	if !o.autoApprove && o.Version != "" {
   131  		oldVersion, err := version.NewVersion(kbVersion)
   132  		if err != nil {
   133  			return err
   134  		}
   135  		newVersion, err := version.NewVersion(o.Version)
   136  		if err != nil {
   137  			return err
   138  		}
   139  		upgradeWarn := ""
   140  		switch {
   141  		case oldVersion.GreaterThan(newVersion):
   142  			upgradeWarn = printer.BoldYellow(fmt.Sprintf("Warning: You're attempting to downgrade KubeBlocks version from %s to %s, this action may cause your clusters and some KubeBlocks feature unavailable.\nEnsure you proceed after reviewing detailed release notes at https://github.com/1aal/kubeblocks/releases.", kbVersion, o.Version))
   143  		default:
   144  			if err = breakingchange.ValidateUpgradeVersion(kbVersion, o.Version); err != nil {
   145  				return err
   146  			}
   147  			upgradeWarn = fmt.Sprintf("Upgrade KubeBlocks from %s to %s", kbVersion, o.Version)
   148  		}
   149  		if err = prompt.Confirm(nil, o.In, upgradeWarn, "Please type 'Yes/yes' to confirm your operation:"); err != nil {
   150  			return err
   151  		}
   152  	}
   153  
   154  	// add helm repo
   155  	s := spinner.New(o.Out, spinnerMsg("Add and update repo "+types.KubeBlocksChartName))
   156  	defer s.Fail()
   157  	// Add repo, if exists, will update it
   158  	if err = helm.AddRepo(newHelmRepoEntry()); err != nil {
   159  		return err
   160  	}
   161  	s.Success()
   162  
   163  	// stop the old version KubeBlocks, otherwise the old version KubeBlocks will reconcile the
   164  	// new version resources, which may not be compatible. helm will start the new version
   165  	// KubeBlocks after upgrade.
   166  	s = spinner.New(o.Out, spinnerMsg("Stop KubeBlocks "+kbVersion))
   167  	defer s.Fail()
   168  	if err = o.stopDeployment(util.GetKubeBlocksDeploy); err != nil {
   169  		return err
   170  	}
   171  	s.Success()
   172  
   173  	// stop the data protection deployment
   174  	s = spinner.New(o.Out, spinnerMsg("Stop DataProtection"))
   175  	defer s.Fail()
   176  	if err = o.stopDeployment(util.GetDataProtectionDeploy); err != nil {
   177  		return err
   178  	}
   179  	s.Success()
   180  
   181  	// it's time to upgrade
   182  	msg := ""
   183  	if o.Version != "" {
   184  		msg = "to " + o.Version
   185  	}
   186  	s = spinner.New(o.Out, spinnerMsg("Upgrading KubeBlocks "+msg))
   187  	defer s.Fail()
   188  	// upgrade KubeBlocks chart
   189  	if err = o.upgradeChart(); err != nil {
   190  		return err
   191  	}
   192  	// successfully upgraded
   193  	s.Success()
   194  
   195  	if !o.Quiet {
   196  		fmt.Fprintf(o.Out, "\nKubeBlocks has been upgraded %s SUCCESSFULLY!\n", msg)
   197  		o.printNotes()
   198  	}
   199  	return nil
   200  }
   201  
   202  func (o *InstallOptions) upgradeChart() error {
   203  	return o.buildChart().Upgrade(o.HelmCfg)
   204  }
   205  
   206  // stopDeployment stops the deployment by setting the replicas to 0
   207  func (o *InstallOptions) stopDeployment(getDeployFn getDeploymentFunc) error {
   208  	deploy, err := getDeployFn(o.Client)
   209  	if err != nil {
   210  		if apierrors.IsNotFound(err) {
   211  			return nil
   212  		}
   213  		return err
   214  	}
   215  
   216  	if deploy == nil {
   217  		klog.V(1).Info("deployment is not found, no need to stop")
   218  		return nil
   219  	}
   220  
   221  	if _, err = o.Client.AppsV1().Deployments(deploy.Namespace).Patch(
   222  		context.TODO(), deploy.Name, apitypes.JSONPatchType,
   223  		[]byte(`[{"op": "replace", "path": "/spec/replicas", "value": 0}]`),
   224  		metav1.PatchOptions{}); err != nil {
   225  		return err
   226  	}
   227  
   228  	// wait for deployment to be stopped
   229  	return wait.PollUntilContextTimeout(context.Background(), 5*time.Second, o.Timeout, true,
   230  		func(_ context.Context) (bool, error) {
   231  			deploy, err = util.GetKubeBlocksDeploy(o.Client)
   232  			if err != nil {
   233  				return false, err
   234  			}
   235  			if *deploy.Spec.Replicas == 0 &&
   236  				deploy.Status.Replicas == 0 &&
   237  				deploy.Status.AvailableReplicas == 0 {
   238  				return true, nil
   239  			}
   240  			return false, nil
   241  		})
   242  }