From d7300f46357085aac54098b45a22438a3ba407ba Mon Sep 17 00:00:00 2001 From: William Brooks Date: Fri, 21 Feb 2020 01:01:09 -0600 Subject: [PATCH] Remove legacy_boot and replace with first_boot_device (initial) --- builder/hyperv/common/config.go | 22 ++- builder/hyperv/common/driver.go | 4 +- builder/hyperv/common/driver_ps_4.go | 9 +- builder/hyperv/common/step_mount_dvddrive.go | 30 ++-- .../common/step_set_first_boot_device.go | 140 ++++++++++++++++++ builder/hyperv/iso/builder.go | 7 +- builder/hyperv/vmcx/builder.go | 7 +- common/powershell/hyperv/hyperv.go | 62 ++++++-- 8 files changed, 247 insertions(+), 34 deletions(-) create mode 100644 builder/hyperv/common/step_set_first_boot_device.go diff --git a/builder/hyperv/common/config.go b/builder/hyperv/common/config.go index 142d399ef..949266454 100644 --- a/builder/hyperv/common/config.go +++ b/builder/hyperv/common/config.go @@ -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) { diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index 545596bee..9731d0572 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -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 diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index 5b072a72e..c1738830d 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -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 { diff --git a/builder/hyperv/common/step_mount_dvddrive.go b/builder/hyperv/common/step_mount_dvddrive.go index 777a288b6..faa2a49c7 100644 --- a/builder/hyperv/common/step_mount_dvddrive.go +++ b/builder/hyperv/common/step_mount_dvddrive.go @@ -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)) diff --git a/builder/hyperv/common/step_set_first_boot_device.go b/builder/hyperv/common/step_set_first_boot_device.go new file mode 100644 index 000000000..f17e9a178 --- /dev/null +++ b/builder/hyperv/common/step_set_first_boot_device.go @@ -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 +} diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index a39da7b7b..5735cbbfe 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -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, diff --git a/builder/hyperv/vmcx/builder.go b/builder/hyperv/vmcx/builder.go index f151354a6..6eed0a3c3 100644 --- a/builder/hyperv/vmcx/builder.go +++ b/builder/hyperv/vmcx/builder.go @@ -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, diff --git a/common/powershell/hyperv/hyperv.go b/common/powershell/hyperv/hyperv.go index a60322bdf..bebc4119f 100644 --- a/common/powershell/hyperv/hyperv.go +++ b/common/powershell/hyperv/hyperv.go @@ -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)