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 }