github.com/verrazzano/verrazzano@v1.7.0/tools/vz/cmd/upgrade/upgrade.go (about) 1 // Copyright (c) 2022, 2023, Oracle and/or its affiliates. 2 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. 3 4 package upgrade 5 6 import ( 7 "fmt" 8 "github.com/spf13/cobra" 9 "github.com/verrazzano/verrazzano/pkg/kubectlutil" 10 "github.com/verrazzano/verrazzano/pkg/semver" 11 "github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1beta1" 12 "github.com/verrazzano/verrazzano/tools/vz/cmd/bugreport" 13 cmdhelpers "github.com/verrazzano/verrazzano/tools/vz/cmd/helpers" 14 "github.com/verrazzano/verrazzano/tools/vz/cmd/version" 15 "github.com/verrazzano/verrazzano/tools/vz/pkg/constants" 16 "github.com/verrazzano/verrazzano/tools/vz/pkg/helpers" 17 "k8s.io/apimachinery/pkg/types" 18 "k8s.io/client-go/kubernetes" 19 clipkg "sigs.k8s.io/controller-runtime/pkg/client" 20 "sigs.k8s.io/yaml" 21 "time" 22 ) 23 24 const ( 25 CommandName = "upgrade" 26 helpShort = "Upgrade Verrazzano" 27 helpLong = `Upgrade the Verrazzano Platform Operator to the specified version and update all of the currently installed components` 28 ) 29 30 var helpExample = fmt.Sprintf(` 31 # Upgrade to the latest version of Verrazzano and wait for the command to complete. Stream the logs to the console until the upgrade completes. 32 vz upgrade 33 34 # Upgrade to Verrazzano v%[1]s, stream the logs to the console and timeout after 20m 35 vz upgrade --version v%[1]s --timeout 20m`, version.GetCLIVersion()) 36 37 var logsEnum = cmdhelpers.LogFormatSimple 38 39 func NewCmdUpgrade(vzHelper helpers.VZHelper) *cobra.Command { 40 cmd := cmdhelpers.NewCommand(vzHelper, CommandName, helpShort, helpLong) 41 cmd.RunE = func(cmd *cobra.Command, args []string) error { 42 return runCmdUpgrade(cmd, vzHelper) 43 } 44 cmd.Example = helpExample 45 46 cmd.PersistentFlags().Bool(constants.WaitFlag, constants.WaitFlagDefault, constants.WaitFlagHelp) 47 cmd.PersistentFlags().Duration(constants.TimeoutFlag, time.Minute*30, constants.TimeoutFlagHelp) 48 cmd.PersistentFlags().Duration(constants.VPOTimeoutFlag, time.Minute*5, constants.VPOTimeoutFlagHelp) 49 cmd.PersistentFlags().String(constants.VersionFlag, constants.VersionFlagDefault, constants.VersionFlagUpgradeHelp) 50 cmd.PersistentFlags().Var(&logsEnum, constants.LogFormatFlag, constants.LogFormatHelp) 51 cmd.PersistentFlags().Bool(constants.AutoBugReportFlag, constants.AutoBugReportFlagDefault, constants.AutoBugReportFlagHelp) 52 // Private registry support 53 cmd.PersistentFlags().String(constants.ImageRegistryFlag, constants.ImageRegistryFlagDefault, constants.ImageRegistryFlagHelp) 54 cmd.PersistentFlags().String(constants.ImagePrefixFlag, constants.ImagePrefixFlagDefault, constants.ImagePrefixFlagHelp) 55 56 // Add flags related to specifying the platform operator manifests as a local file or a URL 57 cmdhelpers.AddManifestsFlags(cmd) 58 59 // Flag to skip any confirmation questions 60 cmd.PersistentFlags().BoolP(constants.SkipConfirmationFlag, constants.SkipConfirmationShort, false, constants.SkipConfirmationFlagHelp) 61 62 // Dry run flag is still being discussed - keep hidden for now 63 cmd.PersistentFlags().Bool(constants.DryRunFlag, false, "Simulate an upgrade.") 64 cmd.PersistentFlags().MarkHidden(constants.DryRunFlag) 65 66 // Hide the flag for overriding the default wait timeout for the platform-operator 67 cmd.PersistentFlags().MarkHidden(constants.VPOTimeoutFlag) 68 69 // Set Flag for setting args 70 cmd.PersistentFlags().StringArrayP(constants.SetFlag, constants.SetFlagShorthand, []string{}, constants.SetFlagHelp) 71 72 // Verifies that the CLI args are not set at the creation of a command 73 vzHelper.VerifyCLIArgsNil(cmd) 74 75 return cmd 76 } 77 78 func runCmdUpgrade(cmd *cobra.Command, vzHelper helpers.VZHelper) error { 79 if err := validateCmd(cmd); err != nil { 80 return fmt.Errorf("Command validation failed: %s", err.Error()) 81 } 82 83 // Get the controller runtime client 84 client, err := vzHelper.GetClient(cmd) 85 if err != nil { 86 return err 87 } 88 89 // Find the verrazzano resource that needs to be updated to the new version 90 vz, err := helpers.FindVerrazzanoResource(client) 91 if err != nil { 92 return fmt.Errorf("Verrazzano is not installed: %s", err.Error()) 93 } 94 95 skipConfirm, errConfirm := cmd.PersistentFlags().GetBool(constants.SkipConfirmationFlag) 96 if errConfirm != nil { 97 return errConfirm 98 } 99 100 // If the Verrazzano resource is already Reconciling, confirm with user that they would like to 101 // proceed with an upgrade 102 if vz.Status.State == v1beta1.VzStateReconciling { 103 proceed, err := cmdhelpers.ConfirmWithUser(vzHelper, "Verrazzano is already in the middle of an install. Continue with upgrade anyway?", skipConfirm) 104 if err != nil { 105 return err 106 } 107 if !proceed { 108 fmt.Fprintf(vzHelper.GetOutputStream(), "Operation canceled.") 109 return nil 110 } 111 } 112 113 // Validate any existing private registry settings against new ones and get confirmation from the user 114 if err := cmdhelpers.ValidatePrivateRegistry(cmd, client); err != nil { 115 proceed, err := cmdhelpers.ConfirmWithUser(vzHelper, fmt.Sprintf("%s\nProceed to upgrade with new settings?", err.Error()), skipConfirm) 116 if err != nil { 117 return err 118 } 119 if !proceed { 120 fmt.Fprintf(vzHelper.GetOutputStream(), "Upgrade canceled.") 121 return nil 122 } 123 } 124 125 // Get the version Verrazzano is being upgraded to 126 version, err := cmdhelpers.GetVersion(cmd, vzHelper) 127 if err != nil { 128 return err 129 } 130 131 vzStatusVersion, err := semver.NewSemVersion(vz.Status.Version) 132 if err != nil { 133 return fmt.Errorf("Failed creating semantic version from Verrazzano status version %s: %s", vz.Status.Version, err.Error()) 134 } 135 upgradeVersion, err := semver.NewSemVersion(version) 136 if err != nil { 137 return fmt.Errorf("Failed creating semantic version from version %s specified: %s", version, err.Error()) 138 } 139 140 // Version being upgraded to cannot be less than the installed version 141 if upgradeVersion.IsLessThan(vzStatusVersion) { 142 return fmt.Errorf("Upgrade to a lesser version of Verrazzano is not allowed. Upgrade version specified was %s and current Verrazzano version is %s", version, vz.Status.Version) 143 } 144 145 var vzSpecVersion *semver.SemVersion 146 if vz.Spec.Version != "" { 147 vzSpecVersion, err = semver.NewSemVersion(vz.Spec.Version) 148 if err != nil { 149 return fmt.Errorf("Failed creating semantic version from Verrazzano spec version %s: %s", vz.Spec.Version, err.Error()) 150 } 151 // Version being upgraded to cannot be less than version previously specified during an upgrade 152 if upgradeVersion.IsLessThan(vzSpecVersion) { 153 return fmt.Errorf("Upgrade to a lesser version of Verrazzano is not allowed. Upgrade version specified was %s and the upgrade in progress is %s", version, vz.Spec.Version) 154 } 155 } else { 156 // Installed version is already at the upgrade version specified 157 if upgradeVersion.IsEqualTo(vzStatusVersion) { 158 fmt.Fprintf(vzHelper.GetOutputStream(), fmt.Sprintf("Verrazzano is already at the specified upgrade version of %s\n", version)) 159 return nil 160 } 161 } 162 163 fmt.Fprintf(vzHelper.GetOutputStream(), fmt.Sprintf("Upgrading Verrazzano to version %s\n", version)) 164 165 // Get the timeout value for the upgrade command 166 timeout, err := cmdhelpers.GetWaitTimeout(cmd, constants.TimeoutFlag) 167 if err != nil { 168 return err 169 } 170 171 // Get the log format value 172 logFormat, err := cmdhelpers.GetLogFormat(cmd) 173 if err != nil { 174 return err 175 } 176 177 // Get the VPO timeout 178 vpoTimeout, err := cmdhelpers.GetWaitTimeout(cmd, constants.VPOTimeoutFlag) 179 if err != nil { 180 return err 181 } 182 183 // Get the kubernetes clientset. This will validate that the kubeconfig and context are valid. 184 kubeClient, err := vzHelper.GetKubeClient(cmd) 185 if err != nil { 186 return err 187 } 188 189 if vz.Spec.Version == "" || !upgradeVersion.IsEqualTo(vzSpecVersion) { 190 191 // Apply the Verrazzano operator.yaml 192 err = cmdhelpers.ApplyPlatformOperatorYaml(cmd, client, vzHelper, version) 193 if err != nil { 194 return err 195 } 196 197 err = upgradeVerrazzano(cmd, vzHelper, vz, client, version, vpoTimeout) 198 if err != nil { 199 return bugreport.AutoBugReport(cmd, vzHelper, err) 200 } 201 202 // Wait for the Verrazzano upgrade to complete 203 err = waitForUpgradeToComplete(client, kubeClient, vzHelper, types.NamespacedName{Namespace: vz.Namespace, Name: vz.Name}, timeout, vpoTimeout, logFormat) 204 if err != nil { 205 return bugreport.AutoBugReport(cmd, vzHelper, err) 206 } 207 return nil 208 } 209 210 // If we already started the upgrade no need to apply the operator.yaml, wait for VPO, and update the verrazzano 211 // install resource. This could happen if the upgrade command was aborted and then rerun. We only wait for the upgrade 212 // to complete. 213 if !vzStatusVersion.IsEqualTo(vzSpecVersion) { 214 err = waitForUpgradeToComplete(client, kubeClient, vzHelper, types.NamespacedName{Namespace: vz.Namespace, Name: vz.Name}, timeout, vpoTimeout, logFormat) 215 if err != nil { 216 return bugreport.AutoBugReport(cmd, vzHelper, err) 217 } 218 return nil 219 } 220 fmt.Fprintf(vzHelper.GetOutputStream(), fmt.Sprintf("Verrazzano has already been upgraded to version %s\n", vz.Status.Version)) 221 return nil 222 } 223 224 func upgradeVerrazzano(cmd *cobra.Command, vzHelper helpers.VZHelper, vz *v1beta1.Verrazzano, client clipkg.Client, version string, vpoTimeout time.Duration) error { 225 // Wait for the platform operator to be ready before we update the verrazzano install resource 226 _, err := cmdhelpers.WaitForPlatformOperator(client, vzHelper, v1beta1.CondUpgradeComplete, vpoTimeout) 227 if err != nil { 228 return err 229 } 230 231 err = kubectlutil.SetLastAppliedConfigurationAnnotation(vz) 232 if err != nil { 233 return err 234 } 235 236 // Update the version in the verrazzano install resource. This will initiate the Verrazzano upgrade. 237 // We will retry up to 5 times if there is an error. 238 // Sometimes we see intermittent webhook errors due to timeouts. 239 retry := 0 240 for { 241 // Get the verrazzano install resource each iteration, in case of resource conflicts 242 vz, err = helpers.GetVerrazzanoResource(client, types.NamespacedName{Namespace: vz.Namespace, Name: vz.Name}) 243 if err == nil { 244 vz.Spec.Version = version 245 246 vzWithSetFlags, err := mergeSetFlagsIntoVerrazzanoResource(cmd, vzHelper, vz) 247 if err != nil { 248 return err 249 } 250 if vzWithSetFlags != nil { 251 vz = vzWithSetFlags 252 } 253 254 err = helpers.UpdateVerrazzanoResource(client, vz) 255 if err != nil { 256 return err 257 } 258 } 259 if err != nil { 260 if retry == 5 { 261 return fmt.Errorf("Failed to set the upgrade version in the verrazzano install resource: %s", err.Error()) 262 } 263 time.Sleep(time.Second) 264 retry++ 265 fmt.Fprintf(vzHelper.GetOutputStream(), fmt.Sprintf("Retrying after failing to set the upgrade version in the verrazzano install resource: %s\n", err.Error())) 266 continue 267 } 268 break 269 } 270 return nil 271 } 272 273 // Wait for the upgrade operation to complete 274 func waitForUpgradeToComplete(client clipkg.Client, kubeClient kubernetes.Interface, vzHelper helpers.VZHelper, namespacedName types.NamespacedName, timeout time.Duration, vpoTimeout time.Duration, logFormat cmdhelpers.LogFormat) error { 275 return cmdhelpers.WaitForOperationToComplete(client, kubeClient, vzHelper, namespacedName, timeout, vpoTimeout, logFormat, v1beta1.CondUpgradeComplete) 276 } 277 278 // validateCmd - validate the command line options 279 func validateCmd(cmd *cobra.Command) error { 280 prefix, err := cmd.PersistentFlags().GetString(constants.ImagePrefixFlag) 281 if err != nil { 282 return err 283 } 284 reg, err := cmd.PersistentFlags().GetString(constants.ImageRegistryFlag) 285 if err != nil { 286 return err 287 } 288 if prefix != constants.ImagePrefixFlagDefault && reg == constants.ImageRegistryFlagDefault { 289 return fmt.Errorf("%s cannot be specified without also specifying %s", constants.ImagePrefixFlag, constants.ImageRegistryFlag) 290 } 291 return nil 292 } 293 294 // mergeSetFlagsIntoVerrazzanoResource - determines if any --set flags are used and merges them into the existing Verrazzano resource to be applied at upgrade 295 func mergeSetFlagsIntoVerrazzanoResource(cmd *cobra.Command, vzHelper helpers.VZHelper, vz *v1beta1.Verrazzano) (*v1beta1.Verrazzano, error) { 296 // Check that set flags are set. Otherwise, nothing is returned and the Verrazzano resource is left untouched 297 setFlags, _ := cmd.PersistentFlags().GetStringArray(constants.SetFlag) 298 if len(setFlags) != 0 { 299 // Get the set arguments - returning a list of properties and value 300 pvs, err := cmdhelpers.GetSetArguments(cmd, vzHelper) 301 if err != nil { 302 return nil, err 303 } 304 305 // Generate yaml for the set flags passed on the command line 306 outYAML, err := cmdhelpers.GenerateYAMLForSetFlags(pvs) 307 if err != nil { 308 return nil, err 309 } 310 311 // Merge the set flags passed on the command line. The set flags take precedence over 312 // the yaml files passed on the command line. 313 mergedVZ, _, err := cmdhelpers.MergeSetFlagsUpgrade(vz.GroupVersionKind().GroupVersion(), vz, outYAML) 314 if err != nil { 315 return nil, err 316 } 317 318 // Update requires a Verrazzano resource to apply changes, so here the client Object becomes a Verrazzano resource to be returned 319 vzMarshalled, err := yaml.Marshal(mergedVZ) 320 if err != nil { 321 return nil, err 322 } 323 newVZ := v1beta1.Verrazzano{} 324 err = yaml.Unmarshal(vzMarshalled, &newVZ) 325 if err != nil { 326 return nil, err 327 } 328 329 return &newVZ, err 330 } 331 return nil, nil 332 }