hyperv: add support for setting the boot order (#9046)

This commit is contained in:
Rui Lopes 2020-04-30 12:31:41 +01:00 committed by GitHub
parent 5eb4620602
commit 86ac132056
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 162 additions and 0 deletions

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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)
} }

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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,

View File

@ -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},

View File

@ -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,

View File

@ -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},

View File

@ -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)

View File

@ -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.