cli_tools/gce_windows_upgrade/upgrader/utils.go (105 lines of code) (raw):
// Copyright 2020 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package upgrader
import (
"bytes"
"strings"
"text/template"
daisy "github.com/GoogleCloudPlatform/compute-daisy"
"github.com/GoogleCloudPlatform/compute-image-tools/cli_tools/common/utils/daisyutils"
)
const (
upgradeIntroductionTemplate = "The following resources will be created/accessed during the upgrade. " +
"Please note the names of the following resources in case you need to manually rollback or cleanup resources.\n" +
"All resources are in project '{{.project}}', zone '{{.zone}}'.\n" +
"1. Instance: {{.instanceName}}\n" +
"2. Disk for install media: {{.installMediaDiskName}}\n" +
"3. Snapshot for original boot disk: {{.osDiskSnapshotName}}\n" +
"4. Original boot disk: {{.osDiskName}}\n" +
" - Device name of the attachment: {{.osDiskDeviceName}}\n" +
" - AutoDelete setting of the attachment: {{.osDiskAutoDelete}}\n" +
"5. Name of the new boot disk: {{.newOSDiskName}}\n" +
"6. Name of the machine image: {{.machineImageName}}\n" +
"7. Original startup script URL: {{.originalStartupScriptURL}}\n" +
"\n" +
"If the upgrade succeeds but the cleanup fails, use the following steps to perform a manual cleanup:\n" +
"1. Delete 'windows-startup-script-url' from the instance's metadata if there isn't an original value. " +
"If there is an original value, restore it. The original value is backed up as metadata 'windows-startup-script-url-backup'.\n" +
"2. Detach the install media disk from the instance and delete it.\n" +
"\n" +
"If the upgrade fails but you didn't enable automatic rollback, auto-rollback " +
"failed, or the upgrade succeeded but you need to rollback for another reason, " +
"use the following steps to perform a manual rollback:\n" +
"1. Detach the new boot disk from the instance and delete the disk.\n" +
"2. Attach the original boot disk as a boot disk.\n" +
"3. Detach the install media disk from the instance and delete the disk.\n" +
"4. Delete 'windows-startup-script-url' from the instance's metadata if there isn't an original value for the script. " +
"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" +
"\n"
cleanupIntroductionTemplate = "After verifying that the upgrading succeeds and you no longer need to rollback:\n" +
"1. Delete the original boot disk: {{.osDiskName}}\n" +
"2. Delete the machine image (if you created one): {{.machineImageName}}\n" +
"3. Delete the snapshot: {{.osDiskSnapshotName}}\n" +
"\n"
)
func getIntroVarMap(u *upgrader) map[string]interface{} {
originalStartupScriptURL := "None."
if u.originalWindowsStartupScriptURL != nil {
originalStartupScriptURL = *u.originalWindowsStartupScriptURL
}
if u.machineImageBackupName == "" {
u.machineImageBackupName = "Not created. Machine Image backup is disabled."
}
varMap := map[string]interface{}{
"project": u.instanceProject,
"zone": u.instanceZone,
"instanceName": u.instanceName,
"installMediaDiskName": u.installMediaDiskName,
"osDiskSnapshotName": u.osDiskSnapshotName,
"osDiskName": daisyutils.GetResourceID(u.osDiskURI),
"osDiskDeviceName": u.osDiskDeviceName,
"osDiskAutoDelete": u.osDiskAutoDelete,
"newOSDiskName": u.newOSDiskName,
"machineImageName": u.machineImageBackupName,
"originalStartupScriptURL": originalStartupScriptURL,
}
return varMap
}
func getIntroHelpText(u *upgrader) (string, error) {
varMap := getIntroVarMap(u)
introductionTemplate := upgradeIntroductionTemplate + cleanupIntroductionTemplate
t, err := template.New("guide").Option("missingkey=error").Parse(introductionTemplate)
if err != nil {
return "", daisy.Errf("Failed to parse upgrade guide.")
}
var buf bytes.Buffer
if err := t.Execute(&buf, varMap); err != nil {
return "", daisy.Errf("Failed to generate upgrade guide.")
}
return string(buf.Bytes()), nil
}
func getCleanupIntroduction(u *upgrader) (string, error) {
varMap := getIntroVarMap(u)
t, err := template.New("guide").Option("missingkey=error").Parse(cleanupIntroductionTemplate)
if err != nil {
return "", daisy.Errf("Failed to parse cleanup guide.")
}
var buf bytes.Buffer
if err := t.Execute(&buf, varMap); err != nil {
return "", daisy.Errf("Failed to generate cleanup guide.")
}
return string(buf.Bytes()), nil
}
func isNewOSDiskAttached(project, zone, instanceName, newOSDiskName string) bool {
inst, err := computeClient.GetInstance(project, zone, instanceName)
if err != nil {
// failed to fetch info. Can't guarantee new OS disk is attached.
return false
}
// If the "prepare" workflow failed when the original OS disk has been dettached
// but new OS disk hasn't been attached, we won't find a boot disk from the instance.
// The instance will either have no disk (while it had only a boot disk originally),
// or have some data disks (while it had more than one disks originally).
// Boot disk is always with index=0: https://cloud.google.com/compute/docs/reference/rest/v1/instances/attachDisk
// "0 is reserved for the boot disk"
if len(inst.Disks) == 0 || inst.Disks[0].Boot == false {
// if the instance has no boot disk attached
return false
}
currentBootDiskURL := inst.Disks[0].Source
// ignore project / zone, only compare real name, because it's guaranteed that
// old OS disk and new OS disk are in the same project and zone.
currentBootDiskName := daisyutils.GetResourceID(currentBootDiskURL)
return currentBootDiskName == newOSDiskName
}
func needReboot(err error) bool {
// windows-2008r2 will emit this error string to the serial port when a
// restarting is required
return strings.Contains(err.Error(), "Windows needs to be restarted")
}