github.com/GoogleCloudPlatform/compute-image-tools/cli_tools@v0.0.0-20240516224744-de2dabc4ed1b/gce_windows_upgrade/upgrader/utils.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 "bytes" 19 "strings" 20 "text/template" 21 22 daisy "github.com/GoogleCloudPlatform/compute-daisy" 23 24 "github.com/GoogleCloudPlatform/compute-image-tools/cli_tools/common/utils/daisyutils" 25 ) 26 27 const ( 28 upgradeIntroductionTemplate = "The following resources will be created/accessed during the upgrade. " + 29 "Please note the names of the following resources in case you need to manually rollback or cleanup resources.\n" + 30 "All resources are in project '{{.project}}', zone '{{.zone}}'.\n" + 31 "1. Instance: {{.instanceName}}\n" + 32 "2. Disk for install media: {{.installMediaDiskName}}\n" + 33 "3. Snapshot for original boot disk: {{.osDiskSnapshotName}}\n" + 34 "4. Original boot disk: {{.osDiskName}}\n" + 35 " - Device name of the attachment: {{.osDiskDeviceName}}\n" + 36 " - AutoDelete setting of the attachment: {{.osDiskAutoDelete}}\n" + 37 "5. Name of the new boot disk: {{.newOSDiskName}}\n" + 38 "6. Name of the machine image: {{.machineImageName}}\n" + 39 "7. Original startup script URL: {{.originalStartupScriptURL}}\n" + 40 "\n" + 41 "If the upgrade succeeds but the cleanup fails, use the following steps to perform a manual cleanup:\n" + 42 "1. Delete 'windows-startup-script-url' from the instance's metadata if there isn't an original value. " + 43 "If there is an original value, restore it. The original value is backed up as metadata 'windows-startup-script-url-backup'.\n" + 44 "2. Detach the install media disk from the instance and delete it.\n" + 45 "\n" + 46 "If the upgrade fails but you didn't enable automatic rollback, auto-rollback " + 47 "failed, or the upgrade succeeded but you need to rollback for another reason, " + 48 "use the following steps to perform a manual rollback:\n" + 49 "1. Detach the new boot disk from the instance and delete the disk.\n" + 50 "2. Attach the original boot disk as a boot disk.\n" + 51 "3. Detach the install media disk from the instance and delete the disk.\n" + 52 "4. Delete 'windows-startup-script-url' from the instance's metadata if there isn't an original value for the script. " + 53 "If there is an original value for the script, restore the value. The original value is backed up as metadata 'windows-startup-script-url-backup'.\n" + 54 "\n" 55 cleanupIntroductionTemplate = "After verifying that the upgrading succeeds and you no longer need to rollback:\n" + 56 "1. Delete the original boot disk: {{.osDiskName}}\n" + 57 "2. Delete the machine image (if you created one): {{.machineImageName}}\n" + 58 "3. Delete the snapshot: {{.osDiskSnapshotName}}\n" + 59 "\n" 60 ) 61 62 func getIntroVarMap(u *upgrader) map[string]interface{} { 63 originalStartupScriptURL := "None." 64 if u.originalWindowsStartupScriptURL != nil { 65 originalStartupScriptURL = *u.originalWindowsStartupScriptURL 66 } 67 if u.machineImageBackupName == "" { 68 u.machineImageBackupName = "Not created. Machine Image backup is disabled." 69 } 70 varMap := map[string]interface{}{ 71 "project": u.instanceProject, 72 "zone": u.instanceZone, 73 "instanceName": u.instanceName, 74 "installMediaDiskName": u.installMediaDiskName, 75 "osDiskSnapshotName": u.osDiskSnapshotName, 76 "osDiskName": daisyutils.GetResourceID(u.osDiskURI), 77 "osDiskDeviceName": u.osDiskDeviceName, 78 "osDiskAutoDelete": u.osDiskAutoDelete, 79 "newOSDiskName": u.newOSDiskName, 80 "machineImageName": u.machineImageBackupName, 81 "originalStartupScriptURL": originalStartupScriptURL, 82 } 83 return varMap 84 } 85 86 func getIntroHelpText(u *upgrader) (string, error) { 87 varMap := getIntroVarMap(u) 88 89 introductionTemplate := upgradeIntroductionTemplate + cleanupIntroductionTemplate 90 t, err := template.New("guide").Option("missingkey=error").Parse(introductionTemplate) 91 if err != nil { 92 return "", daisy.Errf("Failed to parse upgrade guide.") 93 } 94 var buf bytes.Buffer 95 if err := t.Execute(&buf, varMap); err != nil { 96 return "", daisy.Errf("Failed to generate upgrade guide.") 97 } 98 return string(buf.Bytes()), nil 99 } 100 101 func getCleanupIntroduction(u *upgrader) (string, error) { 102 varMap := getIntroVarMap(u) 103 104 t, err := template.New("guide").Option("missingkey=error").Parse(cleanupIntroductionTemplate) 105 if err != nil { 106 return "", daisy.Errf("Failed to parse cleanup guide.") 107 } 108 var buf bytes.Buffer 109 if err := t.Execute(&buf, varMap); err != nil { 110 return "", daisy.Errf("Failed to generate cleanup guide.") 111 } 112 return string(buf.Bytes()), nil 113 } 114 115 func isNewOSDiskAttached(project, zone, instanceName, newOSDiskName string) bool { 116 inst, err := computeClient.GetInstance(project, zone, instanceName) 117 if err != nil { 118 // failed to fetch info. Can't guarantee new OS disk is attached. 119 return false 120 } 121 122 // If the "prepare" workflow failed when the original OS disk has been dettached 123 // but new OS disk hasn't been attached, we won't find a boot disk from the instance. 124 // The instance will either have no disk (while it had only a boot disk originally), 125 // or have some data disks (while it had more than one disks originally). 126 // Boot disk is always with index=0: https://cloud.google.com/compute/docs/reference/rest/v1/instances/attachDisk 127 // "0 is reserved for the boot disk" 128 if len(inst.Disks) == 0 || inst.Disks[0].Boot == false { 129 // if the instance has no boot disk attached 130 return false 131 } 132 133 currentBootDiskURL := inst.Disks[0].Source 134 135 // ignore project / zone, only compare real name, because it's guaranteed that 136 // old OS disk and new OS disk are in the same project and zone. 137 currentBootDiskName := daisyutils.GetResourceID(currentBootDiskURL) 138 return currentBootDiskName == newOSDiskName 139 } 140 141 func needReboot(err error) bool { 142 // windows-2008r2 will emit this error string to the serial port when a 143 // restarting is required 144 return strings.Contains(err.Error(), "Windows needs to be restarted") 145 }