hyperv: add support for setting the boot order (#9046)
This commit is contained in:
parent
5eb4620602
commit
86ac132056
|
@ -163,6 +163,20 @@ type CommonConfig struct {
|
|||
// - `CD` *or* `DVD`
|
||||
// - `NET`
|
||||
FirstBootDevice string `mapstructure:"first_boot_device" required:"false"`
|
||||
// When configured, the boot order determines the order of the devices
|
||||
// from which to boot.
|
||||
//
|
||||
// The device name must be in the form of `SCSI:x:y`, for example,
|
||||
// to boot from the first scsi device use `SCSI:0:0`.
|
||||
//
|
||||
// **NB** You should also set `first_boot_device` (e.g. `DVD`).
|
||||
//
|
||||
// **NB** Although the VM will have this initial boot order, the OS can
|
||||
// change it, for example, Ubuntu 18.04 will modify the boot order to
|
||||
// include itself as the first boot option.
|
||||
//
|
||||
// **NB** This only works for Generation 2 machines.
|
||||
BootOrder []string `mapstructure:"boot_order" required:"false"`
|
||||
}
|
||||
|
||||
func (c *CommonConfig) Prepare(ctx *interpolate.Context, pc *common.PackerConfig) ([]error, []string) {
|
||||
|
|
|
@ -115,6 +115,8 @@ type Driver interface {
|
|||
|
||||
SetFirstBootDevice(string, string, uint, uint, uint) error
|
||||
|
||||
SetBootOrder(string, []string) error
|
||||
|
||||
UnmountDvdDrive(string, uint, uint) error
|
||||
|
||||
DeleteDvdDrive(string, uint, uint) error
|
||||
|
|
|
@ -244,6 +244,11 @@ type DriverMock struct {
|
|||
SetFirstBootDevice_Generation uint
|
||||
SetFirstBootDevice_Err error
|
||||
|
||||
SetBootOrder_Called bool
|
||||
SetBootOrder_VmName string
|
||||
SetBootOrder_BootOrder []string
|
||||
SetBootOrder_Err error
|
||||
|
||||
UnmountDvdDrive_Called bool
|
||||
UnmountDvdDrive_VmName string
|
||||
UnmountDvdDrive_ControllerNumber uint
|
||||
|
@ -594,6 +599,13 @@ func (d *DriverMock) SetFirstBootDevice(vmName string, controllerType string, co
|
|||
return d.SetFirstBootDevice_Err
|
||||
}
|
||||
|
||||
func (d *DriverMock) SetBootOrder(vmName string, bootOrder []string) error {
|
||||
d.SetBootOrder_Called = true
|
||||
d.SetBootOrder_VmName = vmName
|
||||
d.SetBootOrder_BootOrder = bootOrder
|
||||
return d.SetBootOrder_Err
|
||||
}
|
||||
|
||||
func (d *DriverMock) UnmountDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error {
|
||||
d.UnmountDvdDrive_Called = true
|
||||
d.UnmountDvdDrive_VmName = vmName
|
||||
|
|
|
@ -272,6 +272,10 @@ func (d *HypervPS4Driver) SetFirstBootDevice(vmName string, controllerType strin
|
|||
return hyperv.SetFirstBootDevice(vmName, controllerType, controllerNumber, controllerLocation, generation)
|
||||
}
|
||||
|
||||
func (d *HypervPS4Driver) SetBootOrder(vmName string, bootOrder []string) error {
|
||||
return hyperv.SetBootOrder(vmName, bootOrder)
|
||||
}
|
||||
|
||||
func (d *HypervPS4Driver) UnmountDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error {
|
||||
return hyperv.UnmountDvdDrive(vmName, controllerNumber, controllerLocation)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type StepSetBootOrder struct {
|
||||
BootOrder []string
|
||||
}
|
||||
|
||||
func (s *StepSetBootOrder) 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.BootOrder != nil {
|
||||
ui.Say(fmt.Sprintf("Setting boot order to %q", s.BootOrder))
|
||||
err := driver.SetBootOrder(vmName, s.BootOrder)
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error setting the boot order: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepSetBootOrder) Cleanup(state multistep.StateBag) {
|
||||
// do nothing
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
)
|
||||
|
||||
type bootOrderTest struct {
|
||||
bootOrder []string
|
||||
}
|
||||
|
||||
var bootOrderTests = [...]bootOrderTest{
|
||||
{[]string{"SCSI:0:0"}},
|
||||
}
|
||||
|
||||
func TestStepSetBootOrder(t *testing.T) {
|
||||
step := new(StepSetBootOrder)
|
||||
|
||||
for _, d := range bootOrderTests {
|
||||
state := testState(t)
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
vmName := "test"
|
||||
|
||||
state.Put("vmName", vmName)
|
||||
step.BootOrder = d.bootOrder
|
||||
|
||||
action := step.Run(context.Background(), state)
|
||||
|
||||
if multistep.ActionContinue != action {
|
||||
t.Fatalf("Should have returned action %v but got %v", multistep.ActionContinue, action)
|
||||
}
|
||||
|
||||
if vmName != driver.SetBootOrder_VmName {
|
||||
t.Fatalf("Should have set VmName to %v but got %v", vmName, driver.SetBootOrder_VmName)
|
||||
}
|
||||
|
||||
if !driver.SetBootOrder_Called {
|
||||
t.Fatalf("Should have called SetBootOrder")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(d.bootOrder, driver.SetBootOrder_BootOrder) {
|
||||
t.Fatalf("Should have set BootOrder to %v but got %v", d.bootOrder, driver.SetBootOrder_BootOrder)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -265,6 +265,9 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
SwitchVlanId: b.config.SwitchVlanId,
|
||||
},
|
||||
|
||||
&hypervcommon.StepSetBootOrder{
|
||||
BootOrder: b.config.BootOrder,
|
||||
},
|
||||
&hypervcommon.StepSetFirstBootDevice{
|
||||
Generation: b.config.Generation,
|
||||
FirstBootDevice: b.config.FirstBootDevice,
|
||||
|
|
|
@ -99,6 +99,7 @@ type FlatConfig struct {
|
|||
SkipExport *bool `mapstructure:"skip_export" required:"false" cty:"skip_export"`
|
||||
Headless *bool `mapstructure:"headless" required:"false" cty:"headless"`
|
||||
FirstBootDevice *string `mapstructure:"first_boot_device" required:"false" cty:"first_boot_device"`
|
||||
BootOrder []string `mapstructure:"boot_order" required:"false" cty:"boot_order"`
|
||||
ShutdownCommand *string `mapstructure:"shutdown_command" required:"false" cty:"shutdown_command"`
|
||||
ShutdownTimeout *string `mapstructure:"shutdown_timeout" required:"false" cty:"shutdown_timeout"`
|
||||
DiskSize *uint `mapstructure:"disk_size" required:"false" cty:"disk_size"`
|
||||
|
@ -209,6 +210,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||
"skip_export": &hcldec.AttrSpec{Name: "skip_export", Type: cty.Bool, Required: false},
|
||||
"headless": &hcldec.AttrSpec{Name: "headless", Type: cty.Bool, Required: false},
|
||||
"first_boot_device": &hcldec.AttrSpec{Name: "first_boot_device", Type: cty.String, Required: false},
|
||||
"boot_order": &hcldec.AttrSpec{Name: "boot_order", Type: cty.List(cty.String), Required: false},
|
||||
"shutdown_command": &hcldec.AttrSpec{Name: "shutdown_command", Type: cty.String, Required: false},
|
||||
"shutdown_timeout": &hcldec.AttrSpec{Name: "shutdown_timeout", Type: cty.String, Required: false},
|
||||
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
|
||||
|
|
|
@ -305,6 +305,9 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
SwitchVlanId: b.config.SwitchVlanId,
|
||||
},
|
||||
|
||||
&hypervcommon.StepSetBootOrder{
|
||||
BootOrder: b.config.BootOrder,
|
||||
},
|
||||
&hypervcommon.StepSetFirstBootDevice{
|
||||
Generation: b.config.Generation,
|
||||
FirstBootDevice: b.config.FirstBootDevice,
|
||||
|
|
|
@ -99,6 +99,7 @@ type FlatConfig struct {
|
|||
SkipExport *bool `mapstructure:"skip_export" required:"false" cty:"skip_export"`
|
||||
Headless *bool `mapstructure:"headless" required:"false" cty:"headless"`
|
||||
FirstBootDevice *string `mapstructure:"first_boot_device" required:"false" cty:"first_boot_device"`
|
||||
BootOrder []string `mapstructure:"boot_order" required:"false" cty:"boot_order"`
|
||||
ShutdownCommand *string `mapstructure:"shutdown_command" required:"false" cty:"shutdown_command"`
|
||||
ShutdownTimeout *string `mapstructure:"shutdown_timeout" required:"false" cty:"shutdown_timeout"`
|
||||
CloneFromVMCXPath *string `mapstructure:"clone_from_vmcx_path" cty:"clone_from_vmcx_path"`
|
||||
|
@ -211,6 +212,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||
"skip_export": &hcldec.AttrSpec{Name: "skip_export", Type: cty.Bool, Required: false},
|
||||
"headless": &hcldec.AttrSpec{Name: "headless", Type: cty.Bool, Required: false},
|
||||
"first_boot_device": &hcldec.AttrSpec{Name: "first_boot_device", Type: cty.String, Required: false},
|
||||
"boot_order": &hcldec.AttrSpec{Name: "boot_order", Type: cty.List(cty.String), Required: false},
|
||||
"shutdown_command": &hcldec.AttrSpec{Name: "shutdown_command", Type: cty.String, Required: false},
|
||||
"shutdown_timeout": &hcldec.AttrSpec{Name: "shutdown_timeout", Type: cty.String, Required: false},
|
||||
"clone_from_vmcx_path": &hcldec.AttrSpec{Name: "clone_from_vmcx_path", Type: cty.String, Required: false},
|
||||
|
|
|
@ -249,6 +249,27 @@ func SetFirstBootDevice(vmName string, controllerType string, controllerNumber u
|
|||
}
|
||||
}
|
||||
|
||||
func SetBootOrder(vmName string, bootOrder []string) error {
|
||||
var script = `
|
||||
param([string]$vmName, [Parameter(ValueFromRemainingArguments=$true)]$bootOrder)
|
||||
|
||||
$bootOrderDrives = $bootOrder | ForEach-Object {
|
||||
if ($_ -match 'SCSI:([0-9]+):([0-9]+)') {
|
||||
$controllerNumber = $Matches[1]
|
||||
$controllerLocation = $Matches[2]
|
||||
$controller = Hyper-V\Get-VMScsiController -ControllerNumber $controllerNumber $vmName
|
||||
$controller.Drives | Where-Object {$_.ControllerLocation -eq $controllerLocation} | Select-Object -First 1
|
||||
}
|
||||
}
|
||||
|
||||
Hyper-V\Set-VMFirmware $vmName -BootOrder $bootOrderDrives
|
||||
`
|
||||
var ps powershell.PowerShellCmd
|
||||
params := append([]string{vmName}, bootOrder...)
|
||||
err := ps.Run(script, params...)
|
||||
return err
|
||||
}
|
||||
|
||||
func DeleteDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error {
|
||||
var script = `
|
||||
param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation)
|
||||
|
|
|
@ -124,4 +124,18 @@
|
|||
- `SCSI:x:y`
|
||||
- `CD` *or* `DVD`
|
||||
- `NET`
|
||||
|
||||
- `boot_order` ([]string) - When configured, the boot order determines the order of the devices
|
||||
from which to boot.
|
||||
|
||||
The device name must be in the form of `SCSI:x:y`, for example,
|
||||
to boot from the first scsi device use `SCSI:0:0`.
|
||||
|
||||
**NB** You should also set `first_boot_device` (e.g. `DVD`).
|
||||
|
||||
**NB** Although the VM will have this initial boot order, the OS can
|
||||
change it, for example, Ubuntu 18.04 will modify the boot order to
|
||||
include itself as the first boot option.
|
||||
|
||||
**NB** This only works for Generation 2 machines.
|
||||
|
Loading…
Reference in New Issue