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`
|
// - `CD` *or* `DVD`
|
||||||
// - `NET`
|
// - `NET`
|
||||||
FirstBootDevice string `mapstructure:"first_boot_device" required:"false"`
|
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) {
|
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
|
SetFirstBootDevice(string, string, uint, uint, uint) error
|
||||||
|
|
||||||
|
SetBootOrder(string, []string) error
|
||||||
|
|
||||||
UnmountDvdDrive(string, uint, uint) error
|
UnmountDvdDrive(string, uint, uint) error
|
||||||
|
|
||||||
DeleteDvdDrive(string, uint, uint) error
|
DeleteDvdDrive(string, uint, uint) error
|
||||||
|
|
|
@ -244,6 +244,11 @@ type DriverMock struct {
|
||||||
SetFirstBootDevice_Generation uint
|
SetFirstBootDevice_Generation uint
|
||||||
SetFirstBootDevice_Err error
|
SetFirstBootDevice_Err error
|
||||||
|
|
||||||
|
SetBootOrder_Called bool
|
||||||
|
SetBootOrder_VmName string
|
||||||
|
SetBootOrder_BootOrder []string
|
||||||
|
SetBootOrder_Err error
|
||||||
|
|
||||||
UnmountDvdDrive_Called bool
|
UnmountDvdDrive_Called bool
|
||||||
UnmountDvdDrive_VmName string
|
UnmountDvdDrive_VmName string
|
||||||
UnmountDvdDrive_ControllerNumber uint
|
UnmountDvdDrive_ControllerNumber uint
|
||||||
|
@ -594,6 +599,13 @@ func (d *DriverMock) SetFirstBootDevice(vmName string, controllerType string, co
|
||||||
return d.SetFirstBootDevice_Err
|
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 {
|
func (d *DriverMock) UnmountDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error {
|
||||||
d.UnmountDvdDrive_Called = true
|
d.UnmountDvdDrive_Called = true
|
||||||
d.UnmountDvdDrive_VmName = vmName
|
d.UnmountDvdDrive_VmName = vmName
|
||||||
|
|
|
@ -272,6 +272,10 @@ func (d *HypervPS4Driver) SetFirstBootDevice(vmName string, controllerType strin
|
||||||
return hyperv.SetFirstBootDevice(vmName, controllerType, controllerNumber, controllerLocation, generation)
|
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 {
|
func (d *HypervPS4Driver) UnmountDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error {
|
||||||
return hyperv.UnmountDvdDrive(vmName, controllerNumber, controllerLocation)
|
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,
|
SwitchVlanId: b.config.SwitchVlanId,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
&hypervcommon.StepSetBootOrder{
|
||||||
|
BootOrder: b.config.BootOrder,
|
||||||
|
},
|
||||||
&hypervcommon.StepSetFirstBootDevice{
|
&hypervcommon.StepSetFirstBootDevice{
|
||||||
Generation: b.config.Generation,
|
Generation: b.config.Generation,
|
||||||
FirstBootDevice: b.config.FirstBootDevice,
|
FirstBootDevice: b.config.FirstBootDevice,
|
||||||
|
|
|
@ -99,6 +99,7 @@ type FlatConfig struct {
|
||||||
SkipExport *bool `mapstructure:"skip_export" required:"false" cty:"skip_export"`
|
SkipExport *bool `mapstructure:"skip_export" required:"false" cty:"skip_export"`
|
||||||
Headless *bool `mapstructure:"headless" required:"false" cty:"headless"`
|
Headless *bool `mapstructure:"headless" required:"false" cty:"headless"`
|
||||||
FirstBootDevice *string `mapstructure:"first_boot_device" required:"false" cty:"first_boot_device"`
|
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"`
|
ShutdownCommand *string `mapstructure:"shutdown_command" required:"false" cty:"shutdown_command"`
|
||||||
ShutdownTimeout *string `mapstructure:"shutdown_timeout" required:"false" cty:"shutdown_timeout"`
|
ShutdownTimeout *string `mapstructure:"shutdown_timeout" required:"false" cty:"shutdown_timeout"`
|
||||||
DiskSize *uint `mapstructure:"disk_size" required:"false" cty:"disk_size"`
|
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},
|
"skip_export": &hcldec.AttrSpec{Name: "skip_export", Type: cty.Bool, Required: false},
|
||||||
"headless": &hcldec.AttrSpec{Name: "headless", 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},
|
"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_command": &hcldec.AttrSpec{Name: "shutdown_command", Type: cty.String, Required: false},
|
||||||
"shutdown_timeout": &hcldec.AttrSpec{Name: "shutdown_timeout", 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},
|
"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,
|
SwitchVlanId: b.config.SwitchVlanId,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
&hypervcommon.StepSetBootOrder{
|
||||||
|
BootOrder: b.config.BootOrder,
|
||||||
|
},
|
||||||
&hypervcommon.StepSetFirstBootDevice{
|
&hypervcommon.StepSetFirstBootDevice{
|
||||||
Generation: b.config.Generation,
|
Generation: b.config.Generation,
|
||||||
FirstBootDevice: b.config.FirstBootDevice,
|
FirstBootDevice: b.config.FirstBootDevice,
|
||||||
|
|
|
@ -99,6 +99,7 @@ type FlatConfig struct {
|
||||||
SkipExport *bool `mapstructure:"skip_export" required:"false" cty:"skip_export"`
|
SkipExport *bool `mapstructure:"skip_export" required:"false" cty:"skip_export"`
|
||||||
Headless *bool `mapstructure:"headless" required:"false" cty:"headless"`
|
Headless *bool `mapstructure:"headless" required:"false" cty:"headless"`
|
||||||
FirstBootDevice *string `mapstructure:"first_boot_device" required:"false" cty:"first_boot_device"`
|
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"`
|
ShutdownCommand *string `mapstructure:"shutdown_command" required:"false" cty:"shutdown_command"`
|
||||||
ShutdownTimeout *string `mapstructure:"shutdown_timeout" required:"false" cty:"shutdown_timeout"`
|
ShutdownTimeout *string `mapstructure:"shutdown_timeout" required:"false" cty:"shutdown_timeout"`
|
||||||
CloneFromVMCXPath *string `mapstructure:"clone_from_vmcx_path" cty:"clone_from_vmcx_path"`
|
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},
|
"skip_export": &hcldec.AttrSpec{Name: "skip_export", Type: cty.Bool, Required: false},
|
||||||
"headless": &hcldec.AttrSpec{Name: "headless", 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},
|
"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_command": &hcldec.AttrSpec{Name: "shutdown_command", Type: cty.String, Required: false},
|
||||||
"shutdown_timeout": &hcldec.AttrSpec{Name: "shutdown_timeout", 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},
|
"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 {
|
func DeleteDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error {
|
||||||
var script = `
|
var script = `
|
||||||
param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation)
|
param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation)
|
||||||
|
|
|
@ -124,4 +124,18 @@
|
||||||
- `SCSI:x:y`
|
- `SCSI:x:y`
|
||||||
- `CD` *or* `DVD`
|
- `CD` *or* `DVD`
|
||||||
- `NET`
|
- `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