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  }