github.com/GoogleCloudPlatform/compute-image-tools/cli_tools@v0.0.0-20240516224744-de2dabc4ed1b/gce_windows_upgrade/upgrader/upgrader.go (about)

     1  //  Copyright 2020 Google Inc. All Rights Reserved.
     2  //
     3  //  Licensed under the Apache License, Version 2.0 (the "License");
     4  //  you may not use this file except in compliance with the License.
     5  //  You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  //  Unless required by applicable law or agreed to in writing, software
    10  //  distributed under the License is distributed on an "AS IS" BASIS,
    11  //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  //  See the License for the specific language governing permissions and
    13  //  limitations under the License.
    14  
    15  package upgrader
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"log"
    21  	"os"
    22  
    23  	daisy "github.com/GoogleCloudPlatform/compute-daisy"
    24  	daisyCompute "github.com/GoogleCloudPlatform/compute-daisy/compute"
    25  	"google.golang.org/api/option"
    26  
    27  	"github.com/GoogleCloudPlatform/compute-image-tools/cli_tools/common/utils/compute"
    28  	"github.com/GoogleCloudPlatform/compute-image-tools/cli_tools/common/utils/daisyutils"
    29  	"github.com/GoogleCloudPlatform/compute-image-tools/cli_tools/common/utils/logging"
    30  	"github.com/GoogleCloudPlatform/compute-image-tools/cli_tools/common/utils/path"
    31  )
    32  
    33  // Parameter key shared with external packages
    34  const (
    35  	ClientIDFlagKey = "client-id"
    36  	DefaultTimeout  = "90m"
    37  )
    38  
    39  const (
    40  	logPrefix = "[windows-upgrade]"
    41  
    42  	metadataWindowsStartupScriptURL       = "windows-startup-script-url"
    43  	metadataWindowsStartupScriptURLBackup = "windows-startup-script-url-backup"
    44  )
    45  
    46  type derivedVars struct {
    47  	instanceProject string
    48  	instanceZone    string
    49  	instanceURI     string
    50  
    51  	osDiskURI        string
    52  	osDiskType       string
    53  	osDiskDeviceName string
    54  	osDiskAutoDelete bool
    55  
    56  	instanceName           string
    57  	machineImageBackupName string
    58  	osDiskSnapshotName     string
    59  	newOSDiskName          string
    60  	installMediaDiskName   string
    61  
    62  	originalWindowsStartupScriptURL *string
    63  	executionID                     string
    64  }
    65  
    66  // InputParams contains input params for the upgrade.
    67  type InputParams struct {
    68  	ClientID               string
    69  	ProjectPtr             *string
    70  	Zone                   string
    71  	Instance               string
    72  	CreateMachineBackup    bool
    73  	AutoRollback           bool
    74  	SourceOS               string
    75  	TargetOS               string
    76  	Timeout                string
    77  	UseStagingInstallMedia bool
    78  	ScratchBucketGcsPath   string
    79  	Oauth                  string
    80  	Ce                     string
    81  	GcsLogsDisabled        bool
    82  	CloudLogsDisabled      bool
    83  	StdoutLogsDisabled     bool
    84  }
    85  
    86  type upgrader struct {
    87  	*InputParams
    88  	*derivedVars
    89  
    90  	ctx context.Context
    91  
    92  	initFn                    func() error
    93  	printIntroHelpTextFn      func() error
    94  	validateAndDeriveParamsFn func() error
    95  	prepareFn                 func() (daisyutils.DaisyWorker, error)
    96  	upgradeFn                 func() (daisyutils.DaisyWorker, error)
    97  	retryUpgradeFn            func() (daisyutils.DaisyWorker, error)
    98  	rebootFn                  func() (daisyutils.DaisyWorker, error)
    99  	cleanupFn                 func() (daisyutils.DaisyWorker, error)
   100  	rollbackFn                func() (daisyutils.DaisyWorker, error)
   101  
   102  	logger logging.Logger
   103  }
   104  
   105  // Run runs upgrader.
   106  func Run(p *InputParams, logger logging.Logger) error {
   107  	u := upgrader{
   108  		InputParams: p,
   109  		logger:      logger,
   110  		derivedVars: &derivedVars{
   111  			executionID: os.Getenv(daisyutils.BuildIDOSEnvVarName),
   112  		},
   113  	}
   114  	if u.executionID == "" {
   115  		u.executionID = path.RandString(5)
   116  	}
   117  	return u.run()
   118  }
   119  
   120  func (u *upgrader) run() error {
   121  	if err := u.init(); err != nil {
   122  		return err
   123  	}
   124  	if err := u.validateAndDeriveParams(); err != nil {
   125  		return err
   126  	}
   127  	if err := u.printIntroHelpText(); err != nil {
   128  		return err
   129  	}
   130  	_, err := u.runUpgradeWorkflow()
   131  	return err
   132  }
   133  
   134  func (u *upgrader) init() error {
   135  	if u.initFn != nil {
   136  		return u.initFn()
   137  	}
   138  
   139  	log.SetPrefix(logPrefix + " ")
   140  
   141  	var err error
   142  	u.ctx = context.Background()
   143  	computeClient, err = daisyCompute.NewClient(u.ctx, option.WithCredentialsFile(u.Oauth))
   144  	mgce = &compute.MetadataGCE{}
   145  	if err != nil {
   146  		return daisy.Errf("Failed to create GCE client: %v", err)
   147  	}
   148  	return nil
   149  }
   150  
   151  func (u *upgrader) printIntroHelpText() error {
   152  	if u.printIntroHelpTextFn != nil {
   153  		return u.printIntroHelpTextFn()
   154  	}
   155  
   156  	guide, err := getIntroHelpText(u)
   157  	if err != nil {
   158  		return err
   159  	}
   160  	fmt.Print(guide, "\n\n")
   161  	return nil
   162  }
   163  
   164  func (u *upgrader) runUpgradeWorkflow() (daisyutils.DaisyWorker, error) {
   165  	var err error
   166  
   167  	// If upgrade failed, run cleanup or rollback before exiting.
   168  	defer func() {
   169  		u.handleResult(err)
   170  	}()
   171  
   172  	// step 1: preparation - take snapshot, attach install media, backup/set startup script
   173  	fmt.Print("\nPreparing for upgrade...\n\n")
   174  	prepareWf, err := u.prepare()
   175  	if err != nil {
   176  		return prepareWf, err
   177  	}
   178  
   179  	// step 2: run upgrade.
   180  	fmt.Print("\nRunning upgrade...\n\n")
   181  	upgradeWf, err := u.upgrade()
   182  	if err == nil {
   183  		return upgradeWf, nil
   184  	}
   185  
   186  	// step 3: reboot if necessary.
   187  	if !needReboot(err) {
   188  		return upgradeWf, err
   189  	}
   190  	fmt.Print("\nRebooting...\n\n")
   191  	rebootWf, err := u.reboot()
   192  	if err != nil {
   193  		return rebootWf, err
   194  	}
   195  
   196  	// step 4: retry upgrade.
   197  	fmt.Print("\nRetrying upgrade...\n\n")
   198  	retryUpgradeWf, err := u.retryUpgrade()
   199  	return retryUpgradeWf, err
   200  }
   201  
   202  func (u *upgrader) handleResult(err error) {
   203  	if err == nil {
   204  		fmt.Printf("\nSuccessfully upgraded instance '%v' to '%v'.\n", u.instanceURI, u.TargetOS)
   205  		fmt.Printf("\nPlease verify your application's functionality on the " +
   206  			"instance, and if you run into any issues, please manually rollback following " +
   207  			"the instructions in the guide." +
   208  			"\nFull document: https://cloud.google.com/compute/docs/tutorials/performing-an-automated-in-place-upgrade-windows-server\n\n")
   209  		if cleanupIntro, err := getCleanupIntroduction(u); err == nil {
   210  			fmt.Printf(cleanupIntro)
   211  		}
   212  		return
   213  	}
   214  
   215  	isNewOSDiskAttached := isNewOSDiskAttached(u.instanceProject, u.instanceZone, u.instanceName, u.newOSDiskName)
   216  	if u.AutoRollback {
   217  		if isNewOSDiskAttached {
   218  			fmt.Printf("\nUpgrade failed to finish. Rolling back to the "+
   219  				"original state from the original boot disk '%v'...\n\n", u.osDiskURI)
   220  			_, err := u.rollback()
   221  			if err != nil {
   222  				fmt.Printf("\nRollback failed. Error: %v\n"+
   223  					"Please rollback the image manually following the instructions in the guide.\n\n", err)
   224  			} else {
   225  				fmt.Printf("\nCompleted rollback to the original boot disk. Please " +
   226  					"verify the rollback. If the rollback does not function as expected, " +
   227  					"consider restoring the instance from the machine image.\n\n")
   228  			}
   229  			return
   230  		}
   231  		fmt.Printf("\nNo boot disk attached during the failure. No need to rollback. "+
   232  			"If the instance doesn't work as expected, please verify that the original "+
   233  			"boot disk (%v) is attached and whether the instance has started. If necessary, "+
   234  			"please manually rollback by using the instructions in the guide..\n\n", u.osDiskURI)
   235  	}
   236  
   237  	fmt.Printf("\nUpgrade failed. Please manually rollback following the " +
   238  		"instructions in the guide.\n\n")
   239  
   240  	fmt.Print("\nCleaning up temporary resources...\n\n")
   241  	if _, err := u.cleanup(); err != nil {
   242  		fmt.Printf("\nFailed to cleanup temporary resources: %v\n"+
   243  			"Please cleanup the resources manually by following the instructions in the guide.\n\n", err)
   244  	}
   245  }