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 }