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  }