Remove legacy_boot and replace with first_boot_device (initial)

This commit is contained in:
William Brooks 2020-02-21 01:01:09 -06:00
parent 807f39284d
commit d7300f4635
8 changed files with 247 additions and 34 deletions

View File

@ -148,13 +148,21 @@ type CommonConfig struct {
// built. When this value is set to true, the machine will start without a
// console.
Headless bool `mapstructure:"headless" required:"false"`
// Over time the Hyper-V builder has been modified to change the original
// boot order that is used when an ISO is mounted. Hyper-V's default is to
// boot from the CD first, the original Hyper-V builder included code to
// codify this setting when the primary ISO is mounted, that code was eventually
// modified to place the IDE adapter before the the CD (only in generation 1).
// Setting this value to true, forces the original method of operation.
LegacyGen1BootOrder bool `mapstructure:"legacy_gen1_boot_order" required:"false"`
// When configured, determines the device or device type that is given preferential
// treatment when choosing a boot device.
//
// For Generation 1:
// - `IDE`
// - `CD` *or* `DVD`
// - `Floppy`
// - `NET`
//
// For Generation 2:
// - `IDE:x:y`
// - `SCSI:x:y`
// - `CD` *or* `DVD`
// - `NET`
FirstBootDevice string `mapstructure:"first_boot_device" required:"false"`
}
func (c *CommonConfig) Prepare(ctx *interpolate.Context, pc *common.PackerConfig) ([]error, []string) {

View File

@ -111,7 +111,9 @@ type Driver interface {
MountDvdDrive(string, string, uint, uint) error
SetBootDvdDrive(string, uint, uint, uint, bool) error
SetBootDvdDrive(string, uint, uint, uint) error
SetFirstBootDevice(string, string, uint, uint, uint) error
UnmountDvdDrive(string, uint, uint) error

View File

@ -263,8 +263,13 @@ func (d *HypervPS4Driver) MountDvdDrive(vmName string, path string, controllerNu
}
func (d *HypervPS4Driver) SetBootDvdDrive(vmName string, controllerNumber uint, controllerLocation uint,
generation uint, legacyGen1BootOrder bool) error {
return hyperv.SetBootDvdDrive(vmName, controllerNumber, controllerLocation, generation, legacyGen1BootOrder)
generation uint) error {
return hyperv.SetBootDvdDrive(vmName, controllerNumber, controllerLocation, generation)
}
func (d *HypervPS4Driver) SetFirstBootDevice(vmName string, controllerType string, controllerNumber uint,
controllerLocation uint, generation uint) error {
return hyperv.SetFirstBootDevice(vmName, controllerType, controllerNumber, controllerLocation, generation)
}
func (d *HypervPS4Driver) UnmountDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error {

View File

@ -13,7 +13,7 @@ import (
type StepMountDvdDrive struct {
Generation uint
LegacyGen1BootOrder bool
FirstBootDevice string
}
func (s *StepMountDvdDrive) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
@ -58,18 +58,24 @@ func (s *StepMountDvdDrive) Run(ctx context.Context, state multistep.StateBag) m
state.Put("os.dvd.properties", dvdControllerProperties)
if (s.Generation == 1) && (!s.LegacyGen1BootOrder) {
ui.Say("Setting boot drive to IDE and then CD drive. Use legacy_gen1_boot_order to override.")
} else {
ui.Say(fmt.Sprintf("Setting boot drive to os dvd drive %s ...", isoPath))
}
// the "first_boot_device" setting has precedence over the legacy boot order
// configuration, but only if its been assigned a value.
if s.FirstBootDevice == "" {
if s.Generation > 1 {
// only print this message for Gen2, it's not a true statement for Gen1 VMs
ui.Say(fmt.Sprintf("Setting boot drive to os dvd drive %s ...", isoPath))
}
err = driver.SetBootDvdDrive(vmName, controllerNumber, controllerLocation, s.Generation)
if err != nil {
err := fmt.Errorf(errorMsg, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
err = driver.SetBootDvdDrive(vmName, controllerNumber, controllerLocation, s.Generation, s.LegacyGen1BootOrder)
if err != nil {
err := fmt.Errorf(errorMsg, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Mounting os dvd drive %s ...", isoPath))

View File

@ -0,0 +1,140 @@
package common
import (
"context"
"fmt"
"regexp"
"strconv"
"strings"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type StepSetFirstBootDevice struct {
Generation uint
FirstBootDevice string
}
func ParseBootDeviceIdentifier(deviceIdentifier string, generation uint) (string, uint, uint, error) {
captureExpression := "^(FLOPPY|IDE|NET)|(CD|DVD)$"
if generation > 1 {
captureExpression = "^((IDE|SCSI):(\\d+):(\\d+))|(DVD|CD)|(NET)$"
}
r, err := regexp.Compile(captureExpression)
if err != nil {
return "", 0, 0, err
}
// match against the appropriate set of values.. we force to uppercase to ensure that
// all devices are always in the same case
identifierMatches := r.FindStringSubmatch(strings.ToUpper(deviceIdentifier))
if identifierMatches == nil {
return "", 0, 0, fmt.Errorf("The value %q is not a properly formatted device or device group identifier.", deviceIdentifier)
}
switch {
// CD or DVD are always returned as "CD"
case ((generation == 1) && (identifierMatches[2] != "")) || ((generation > 1) && (identifierMatches[5] != "")):
return "CD", 0, 0, nil
// generation 1 only has FLOPPY, IDE or NET remaining..
case (generation == 1):
return identifierMatches[0], 0, 0, nil
// generation 2, check for IDE or SCSI and parse location and number
case (identifierMatches[2] != ""):
{
var controllerLocation int64
var controllerNumber int64
// NOTE: controllerNumber and controllerLocation cannot be negative, the regex expression
// would not have matched if either number was signed
controllerNumber, err = strconv.ParseInt(identifierMatches[3], 10, 8)
if err == nil {
controllerLocation, err = strconv.ParseInt(identifierMatches[4], 10, 8)
if err == nil {
return identifierMatches[2], uint(controllerNumber), uint(controllerLocation), nil
}
}
return "", 0, 0, err
}
// only "NET" left on generation 2
default:
return "NET", 0, 0, nil
}
}
func (s *StepSetFirstBootDevice) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
if s.FirstBootDevice != "" {
controllerType, controllerNumber, controllerLocation, err := ParseBootDeviceIdentifier(s.FirstBootDevice, s.Generation)
if err == nil {
switch {
case controllerType == "CD":
{
// the "DVD" controller is special, we only apply the setting if we actually mounted
// an ISO and only if that was mounted as the "IsoUrl" not a secondary ISO.
dvdControllerState := state.Get("os.dvd.properties")
if dvdControllerState == nil {
ui.Say("First Boot Device is DVD, but no primary ISO mounted. Ignoring.")
return multistep.ActionContinue
}
ui.Say(fmt.Sprintf("Setting boot device to %q", s.FirstBootDevice))
dvdController := dvdControllerState.(DvdControllerProperties)
err = driver.SetFirstBootDevice(vmName, controllerType, dvdController.ControllerNumber, dvdController.ControllerLocation, s.Generation)
}
default:
{
// anything else, we just pass as is..
ui.Say(fmt.Sprintf("Setting boot device to %q", s.FirstBootDevice))
err = driver.SetFirstBootDevice(vmName, controllerType, controllerNumber, controllerLocation, s.Generation)
}
}
}
if err != nil {
err := fmt.Errorf("Error setting first boot device: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepSetFirstBootDevice) Cleanup(state multistep.StateBag) {
// do nothing
}

View File

@ -243,7 +243,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&hypervcommon.StepMountDvdDrive{
Generation: b.config.Generation,
LegacyGen1BootOrder: b.config.LegacyGen1BootOrder,
FirstBootDevice: b.config.FirstBootDevice,
},
&hypervcommon.StepMountFloppydrive{
Generation: b.config.Generation,
@ -265,6 +265,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
SwitchVlanId: b.config.SwitchVlanId,
},
&hypervcommon.StepSetFirstBootDevice{
Generation: b.config.Generation,
FirstBootDevice: b.config.FirstBootDevice,
},
&hypervcommon.StepRun{
Headless: b.config.Headless,
SwitchName: b.config.SwitchName,

View File

@ -283,7 +283,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&hypervcommon.StepMountDvdDrive{
Generation: b.config.Generation,
LegacyGen1BootOrder: b.config.LegacyGen1BootOrder,
FirstBootDevice: b.config.FirstBootDevice,
},
&hypervcommon.StepMountFloppydrive{
Generation: b.config.Generation,
@ -305,6 +305,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
SwitchVlanId: b.config.SwitchVlanId,
},
&hypervcommon.StepSetFirstBootDevice{
Generation: b.config.Generation,
FirstBootDevice: b.config.FirstBootDevice,
},
&hypervcommon.StepRun{
Headless: b.config.Headless,
SwitchName: b.config.SwitchName,

View File

@ -156,21 +156,13 @@ Hyper-V\Set-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -Cont
return err
}
func SetBootDvdDrive(vmName string, controllerNumber uint, controllerLocation uint, generation uint, legacyGen1BootOrder bool) error {
func SetBootDvdDrive(vmName string, controllerNumber uint, controllerLocation uint, generation uint) error {
if generation < 2 {
var script string
if legacyGen1BootOrder {
script = `
param([string]$vmName)
Hyper-V\Set-VMBios -VMName $vmName -StartupOrder @("CD","IDE","LegacyNetworkAdapter","Floppy")
`
} else {
script = `
script := `
param([string]$vmName)
Hyper-V\Set-VMBios -VMName $vmName -StartupOrder @("IDE","CD","LegacyNetworkAdapter","Floppy")
`
}
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName)
return err
@ -188,6 +180,56 @@ Hyper-V\Set-VMFirmware -VMName $vmName -FirstBootDevice $vmDvdDrive -ErrorAction
}
}
func SetFirstBootDeviceGen1(vmName string, controllerType string) error {
// for Generation 1 VMs, we read the value of the VM's boot order, strip the value specified in
// controllerType and insert that value back at the beginning of the list.
//
// controllerType must be 'NET', 'DVD', 'IDE' or 'FLOPPY' (case sensitive)
// The 'NET' value is always replaced with 'LegacyNetworkAdapter'
if (controllerType == "NET") {
controllerType = "LegacyNetworkAdapter"
}
script := `
param([string] $vmName, [string] $controllerType)
$vmBootOrder = Hyper-V\Get-VMBios -VMName $vmName | Select-Object -ExpandProperty StartupOrder | Where-Object { $_ -ne $controllerType }
Hyper-V\Set-VMBios -VMName $vmName -StartupOrder (@($controllerType) + $vmBootOrder)
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName, controllerType)
return err
}
func SetFirstBootDeviceGen2(vmName string, controllerType string, controllerNumber uint, controllerLocation uint) error {
// script := `
// param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation)
// $vmDvdDrive = Hyper-V\Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation
// if (!$vmDvdDrive) {throw 'unable to find dvd drive'}
// Hyper-V\Set-VMFirmware -VMName $vmName -FirstBootDevice $vmDvdDrive -ErrorAction SilentlyContinue
// `
// script := `
// param([string] $vmName, [string] $controllerType, )
//`
return nil
}
func SetFirstBootDevice(vmName string, controllerType string, controllerNumber uint, controllerLocation uint, generation uint) error {
if generation == 1 {
return SetFirstBootDeviceGen1(vmName, controllerType)
} else {
return SetFirstBootDeviceGen2(vmName, controllerType, controllerNumber, controllerLocation)
}
}
func DeleteDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error {
var script = `
param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation)