github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/plugin/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 plugin 21 22 import ( 23 "fmt" 24 "os" 25 "strings" 26 27 "github.com/pkg/errors" 28 "github.com/spf13/cobra" 29 k8sver "k8s.io/apimachinery/pkg/util/version" 30 "k8s.io/cli-runtime/pkg/genericiooptions" 31 "k8s.io/klog/v2" 32 cmdutil "k8s.io/kubectl/pkg/cmd/util" 33 "k8s.io/kubectl/pkg/util/templates" 34 ) 35 36 var ( 37 pluginUpgradeExample = templates.Examples(` 38 # upgrade installed plugins with specified name 39 kbcli plugin upgrade myplugin 40 41 # upgrade installed plugin to a newer version 42 kbcli plugin upgrade --all 43 `) 44 ) 45 46 type UpgradeOptions struct { 47 // common user flags 48 all bool 49 50 pluginNames []string 51 genericiooptions.IOStreams 52 } 53 54 func NewPluginUpgradeCmd(streams genericiooptions.IOStreams) *cobra.Command { 55 o := &UpgradeOptions{ 56 IOStreams: streams, 57 } 58 59 cmd := &cobra.Command{ 60 Use: "upgrade", 61 Short: "Upgrade kbcli or kubectl plugins", 62 Example: pluginUpgradeExample, 63 Run: func(cmd *cobra.Command, args []string) { 64 cmdutil.CheckErr(o.Complete(args)) 65 cmdutil.CheckErr(o.Run()) 66 }, 67 } 68 69 cmd.Flags().BoolVar(&o.all, "all", o.all, "Upgrade all installed plugins") 70 71 return cmd 72 } 73 74 func (o *UpgradeOptions) Complete(args []string) error { 75 if o.all { 76 installed, err := GetInstalledPluginReceipts(paths.InstallReceiptsPath()) 77 if err != nil { 78 return err 79 } 80 for _, receipt := range installed { 81 o.pluginNames = append(o.pluginNames, receipt.Status.Source.Name+"/"+receipt.Name) 82 } 83 } else { 84 if len(args) == 0 { 85 return errors.New("no plugin name specified") 86 } 87 for _, arg := range args { 88 receipt, err := ReadReceiptFromFile(paths.PluginInstallReceiptPath(arg)) 89 if err != nil { 90 return err 91 } 92 o.pluginNames = append(o.pluginNames, receipt.Status.Source.Name+"/"+receipt.Name) 93 } 94 } 95 96 return nil 97 } 98 99 func (o *UpgradeOptions) Run() error { 100 for _, name := range o.pluginNames { 101 indexName, pluginName := CanonicalPluginName(name) 102 103 plugin, err := LoadPluginByName(paths.IndexPluginsPath(indexName), pluginName) 104 if err != nil { 105 return err 106 } 107 108 fmt.Fprintf(o.Out, "Upgrading plugin: %s\n", name) 109 if err := Upgrade(paths, plugin, indexName); err != nil { 110 if err == ErrIsAlreadyUpgraded { 111 fmt.Fprintf(o.Out, "Plugin %q is already upgraded\n", name) 112 continue 113 } 114 return err 115 } 116 } 117 return nil 118 } 119 120 // Upgrade reinstalls and deletes the old plugin. The operation tries 121 // to keep dir in a healthy state if it fails during the process. 122 func Upgrade(p *Paths, plugin Plugin, indexName string) error { 123 installReceipt, err := ReadReceiptFromFile(p.PluginInstallReceiptPath(plugin.Name)) 124 if err != nil { 125 return errors.Wrapf(err, "failed to load install receipt for plugin %q", plugin.Name) 126 } 127 128 curVersion := installReceipt.Spec.Version 129 curv, err := parseVersion(curVersion) 130 if err != nil { 131 return errors.Wrapf(err, "failed to parse installed plugin version (%q) as a semver value", curVersion) 132 } 133 134 // Find available installation candidate 135 candidate, ok, err := GetMatchingPlatform(plugin.Spec.Platforms) 136 if err != nil { 137 return errors.Wrap(err, "failed trying to find a matching platform in plugin spec") 138 } 139 if !ok { 140 return errors.Errorf("plugin %q does not offer installation for this platform (%s)", 141 plugin.Name, OSArch()) 142 } 143 144 newVersion := plugin.Spec.Version 145 newv, err := parseVersion(newVersion) 146 if err != nil { 147 return errors.Wrapf(err, "failed to parse candidate version spec (%q)", newVersion) 148 } 149 klog.V(2).Infof("Comparing versions: current=%s target=%s", curv, newv) 150 151 // See if it's a newer version 152 if !curv.LessThan(newv) { 153 klog.V(3).Infof("Plugin does not need upgrade (%s ≥ %s)", curv, newv) 154 return ErrIsAlreadyUpgraded 155 } 156 klog.V(1).Infof("Plugin needs upgrade (%s < %s)", curv, newv) 157 158 // Re-Install 159 klog.V(1).Infof("Installing new version %s", newVersion) 160 if err := install(installOperation{ 161 pluginName: plugin.Name, 162 platform: candidate, 163 164 installDir: p.PluginVersionInstallPath(plugin.Name, newVersion), 165 binDir: p.BinPath(), 166 }, InstallOpts{}); err != nil { 167 return errors.Wrap(err, "failed to install new version") 168 } 169 170 klog.V(2).Infof("Upgrading install receipt for plugin %s", plugin.Name) 171 if err = StoreReceipt(NewReceipt(plugin, indexName, installReceipt.CreationTimestamp), p.PluginInstallReceiptPath(plugin.Name)); err != nil { 172 return errors.Wrap(err, "installation receipt could not be stored, uninstall may fail") 173 } 174 175 // Clean old installations 176 klog.V(2).Infof("Starting old version cleanup") 177 return cleanupInstallation(p, plugin, curVersion) 178 } 179 180 // cleanupInstallation removes a plugin directly 181 func cleanupInstallation(p *Paths, plugin Plugin, oldVersion string) error { 182 klog.V(1).Infof("Remove old plugin installation under %q", p.PluginVersionInstallPath(plugin.Name, oldVersion)) 183 return os.RemoveAll(p.PluginVersionInstallPath(plugin.Name, oldVersion)) 184 } 185 186 func parseVersion(s string) (*k8sver.Version, error) { 187 var vv *k8sver.Version 188 if !strings.HasPrefix(s, "v") { 189 return vv, errors.Errorf("version string %q does not start with 'v'", s) 190 } 191 vv, err := k8sver.ParseSemantic(s) 192 if err != nil { 193 return vv, err 194 } 195 return vv, nil 196 }