From fc734b6bd9575905dbab5f69980f688151336f56 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 6 May 2018 07:54:58 +1000 Subject: [PATCH 1/3] Using vmconnect to display gui for hyper-v vmconnect.exe comes as part of Hyper-V and is the tool used by Hyper-V Manager to connect with a virtual machine. This commits sets behaviour the same as virtualbox and vmware to display the virtual machine connection unless headless is set in the template. --- builder/hyperv/common/driver.go | 10 +++++++++ builder/hyperv/common/driver_mock.go | 22 +++++++++++++++++++ builder/hyperv/common/driver_ps_4.go | 12 ++++++++++ builder/hyperv/common/step_run.go | 14 +++++++++++- builder/hyperv/iso/builder.go | 6 ++++- builder/hyperv/vmcx/builder.go | 6 ++++- common/powershell/hyperv/hyperv.go | 13 +++++++++++ .../docs/builders/hyperv-iso.html.md.erb | 5 +++++ .../docs/builders/hyperv-vmcx.html.md.erb | 5 +++++ 9 files changed, 90 insertions(+), 3 deletions(-) diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index bce480227..187c601cd 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -1,5 +1,9 @@ package common +import ( + "context" +) + // A driver is able to talk to HyperV and perform certain // operations with it. Some of the operations on here may seem overly // specific, but they were built specifically in mind to handle features @@ -109,4 +113,10 @@ type Driver interface { MountFloppyDrive(string, string) error UnmountFloppyDrive(string) error + + // Connect connects to a VM specified by the name given. + Connect(string) context.CancelFunc + + // Disconnect disconnects to a VM specified by the context cancel function. + Disconnect(context.CancelFunc) } diff --git a/builder/hyperv/common/driver_mock.go b/builder/hyperv/common/driver_mock.go index f33cfe29f..674bd5b67 100644 --- a/builder/hyperv/common/driver_mock.go +++ b/builder/hyperv/common/driver_mock.go @@ -1,5 +1,9 @@ package common +import ( + "context" +) + type DriverMock struct { IsRunning_Called bool IsRunning_VmName string @@ -240,6 +244,13 @@ type DriverMock struct { UnmountFloppyDrive_Called bool UnmountFloppyDrive_VmName string UnmountFloppyDrive_Err error + + Connect_Called bool + Connect_VmName string + Connect_Cancel context.CancelFunc + + Disconnect_Called bool + Disconnect_Cancel context.CancelFunc } func (d *DriverMock) IsRunning(vmName string) (bool, error) { @@ -553,3 +564,14 @@ func (d *DriverMock) UnmountFloppyDrive(vmName string) error { d.UnmountFloppyDrive_VmName = vmName return d.UnmountFloppyDrive_Err } + +func (d *DriverMock) Connect(vmName string) context.CancelFunc { + d.Connect_Called = true + d.Connect_VmName = vmName + return d.Connect_Cancel +} + +func (d *DriverMock) Disconnect(cancel context.CancelFunc) { + d.Disconnect_Called = true + d.Disconnect_Cancel = cancel +} diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index ef948867a..4973ea0b0 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -1,6 +1,7 @@ package common import ( + "context" "fmt" "log" "runtime" @@ -347,3 +348,14 @@ func (d *HypervPS4Driver) verifyHypervPermissions() error { return nil } + +// Connect connects to a VM specified by the name given. +func (d *HypervPS4Driver) Connect(vmName string) context.CancelFunc { + return hyperv.ConnectVirtualMachine(vmName) +} + +// Disconnect disconnects to a VM specified by calling the context cancel function returned +// from Connect. +func (d *HypervPS4Driver) Disconnect(cancel context.CancelFunc) { + hyperv.DisconnectVirtualMachine(cancel) +} diff --git a/builder/hyperv/common/step_run.go b/builder/hyperv/common/step_run.go index 02996fb6f..c6cf78d2e 100644 --- a/builder/hyperv/common/step_run.go +++ b/builder/hyperv/common/step_run.go @@ -9,7 +9,8 @@ import ( ) type StepRun struct { - vmName string + Headless bool + vmName string } func (s *StepRun) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { @@ -29,6 +30,11 @@ func (s *StepRun) Run(_ context.Context, state multistep.StateBag) multistep.Ste s.vmName = vmName + if !s.Headless { + ui.Say("Connecting to vmconnect...") + cancel := driver.Connect(vmName) + state.Put("guiCancelFunc", cancel) + } return multistep.ActionContinue } @@ -39,6 +45,12 @@ func (s *StepRun) Cleanup(state multistep.StateBag) { driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) + guiCancelFunc := state.Get("guiCancelFunc").(context.CancelFunc) + + if guiCancelFunc != nil { + ui.Say("Disconnecting from vmconnect...") + guiCancelFunc() + } if running, _ := driver.IsRunning(s.vmName); running { if err := driver.Stop(s.vmName); err != nil { diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 7afd40c52..d50e5e26a 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -115,6 +115,8 @@ type Config struct { // Create the VM with a Fixed VHD format disk instead of Dynamic VHDX FixedVHD bool `mapstructure:"use_fixed_vhd_format"` + Headless bool `mapstructure:"headless"` + ctx interpolate.Context } @@ -427,7 +429,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe SwitchVlanId: b.config.SwitchVlanId, }, - &hypervcommon.StepRun{}, + &hypervcommon.StepRun{ + Headless: b.config.Headless, + }, &hypervcommon.StepTypeBootCommand{ BootCommand: b.config.FlatBootCommand(), diff --git a/builder/hyperv/vmcx/builder.go b/builder/hyperv/vmcx/builder.go index 83d911e30..987c41a12 100644 --- a/builder/hyperv/vmcx/builder.go +++ b/builder/hyperv/vmcx/builder.go @@ -98,6 +98,8 @@ type Config struct { SkipExport bool `mapstructure:"skip_export"` + Headless bool `mapstructure:"headless"` + ctx interpolate.Context } @@ -436,7 +438,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe SwitchVlanId: b.config.SwitchVlanId, }, - &hypervcommon.StepRun{}, + &hypervcommon.StepRun{ + Headless: b.config.Headless, + }, &hypervcommon.StepTypeBootCommand{ BootCommand: b.config.FlatBootCommand(), diff --git a/common/powershell/hyperv/hyperv.go b/common/powershell/hyperv/hyperv.go index ef85d269e..6503b2327 100644 --- a/common/powershell/hyperv/hyperv.go +++ b/common/powershell/hyperv/hyperv.go @@ -1,7 +1,9 @@ package hyperv import ( + "context" "errors" + "os/exec" "strconv" "strings" @@ -1244,3 +1246,14 @@ param([string]$vmName, [string]$scanCodes) err := ps.Run(script, vmName, scanCodes) return err } + +func ConnectVirtualMachine(vmName string) context.CancelFunc { + ctx, cancel := context.WithCancel(context.Background()) + cmd := exec.CommandContext(ctx, "vmconnect.exe", "localhost", vmName) + cmd.Start() + return cancel +} + +func DisconnectVirtualMachine(cancel context.CancelFunc) { + cancel() +} diff --git a/website/source/docs/builders/hyperv-iso.html.md.erb b/website/source/docs/builders/hyperv-iso.html.md.erb index 85b093acc..a2a97a789 100644 --- a/website/source/docs/builders/hyperv-iso.html.md.erb +++ b/website/source/docs/builders/hyperv-iso.html.md.erb @@ -102,6 +102,11 @@ can be configured for this builder. - `differencing_disk` (boolean) - If true enables differencing disks. Only the changes will be written to the new disk. This is especially useful if your source is a vhd/vhdx. This defaults to false. +- `headless` (boolean) - Packer defaults to building Hyper-V virtual + machines by launching a GUI that shows the console of the machine + being built. When this value is set to true, the machine will start without + a console. + - `skip_export` (boolean) - If true skips VM export. If you are interested only in the vhd/vhdx files, you can enable this option. This will create inline disks which improves the build performance. There will not be any copying of source vhds to temp directory. This defaults to false. diff --git a/website/source/docs/builders/hyperv-vmcx.html.md.erb b/website/source/docs/builders/hyperv-vmcx.html.md.erb index 3433decda..2bb00b0b8 100644 --- a/website/source/docs/builders/hyperv-vmcx.html.md.erb +++ b/website/source/docs/builders/hyperv-vmcx.html.md.erb @@ -139,6 +139,11 @@ can be configured for this builder. - `guest_additions_path` (string) - The path to the iso image for guest additions. +- `headless` (boolean) - Packer defaults to building Hyper-V virtual + machines by launching a GUI that shows the console of the machine + being built. When this value is set to true, the machine will start without + a console. + - `http_directory` (string) - Path to a directory to serve using an HTTP server. The files in this directory will be available over HTTP that will be requestable from the virtual machine. This is useful for hosting From 29c4b4436de4737e4480fb365112ab158d5f5697 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 10 May 2018 21:50:56 +1000 Subject: [PATCH 2/3] Changes requested in PR #6243 - Logging error if vmconnect.exe fails. - Using StepRun struct rather than StateBag for command Cancel function - Better handling in Disconnect when headless is true or vmconnect failed in Start --- builder/hyperv/common/driver.go | 2 +- builder/hyperv/common/driver_mock.go | 5 +++-- builder/hyperv/common/driver_ps_4.go | 2 +- builder/hyperv/common/step_run.go | 17 ++++++++++------- common/powershell/hyperv/hyperv.go | 10 +++++++--- 5 files changed, 22 insertions(+), 14 deletions(-) diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index 187c601cd..535652923 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -115,7 +115,7 @@ type Driver interface { UnmountFloppyDrive(string) error // Connect connects to a VM specified by the name given. - Connect(string) context.CancelFunc + Connect(string) (context.CancelFunc, error) // Disconnect disconnects to a VM specified by the context cancel function. Disconnect(context.CancelFunc) diff --git a/builder/hyperv/common/driver_mock.go b/builder/hyperv/common/driver_mock.go index 674bd5b67..3fcb46323 100644 --- a/builder/hyperv/common/driver_mock.go +++ b/builder/hyperv/common/driver_mock.go @@ -248,6 +248,7 @@ type DriverMock struct { Connect_Called bool Connect_VmName string Connect_Cancel context.CancelFunc + Connect_Err error Disconnect_Called bool Disconnect_Cancel context.CancelFunc @@ -565,10 +566,10 @@ func (d *DriverMock) UnmountFloppyDrive(vmName string) error { return d.UnmountFloppyDrive_Err } -func (d *DriverMock) Connect(vmName string) context.CancelFunc { +func (d *DriverMock) Connect(vmName string) (context.CancelFunc, error) { d.Connect_Called = true d.Connect_VmName = vmName - return d.Connect_Cancel + return d.Connect_Cancel, d.Connect_Err } func (d *DriverMock) Disconnect(cancel context.CancelFunc) { diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index 4973ea0b0..cadaa2ba5 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -350,7 +350,7 @@ func (d *HypervPS4Driver) verifyHypervPermissions() error { } // Connect connects to a VM specified by the name given. -func (d *HypervPS4Driver) Connect(vmName string) context.CancelFunc { +func (d *HypervPS4Driver) Connect(vmName string) (context.CancelFunc, error) { return hyperv.ConnectVirtualMachine(vmName) } diff --git a/builder/hyperv/common/step_run.go b/builder/hyperv/common/step_run.go index c6cf78d2e..13bf640df 100644 --- a/builder/hyperv/common/step_run.go +++ b/builder/hyperv/common/step_run.go @@ -3,14 +3,16 @@ package common import ( "context" "fmt" + "log" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" ) type StepRun struct { - Headless bool - vmName string + GuiCancelFunc context.CancelFunc + Headless bool + vmName string } func (s *StepRun) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { @@ -32,8 +34,10 @@ func (s *StepRun) Run(_ context.Context, state multistep.StateBag) multistep.Ste if !s.Headless { ui.Say("Connecting to vmconnect...") - cancel := driver.Connect(vmName) - state.Put("guiCancelFunc", cancel) + s.GuiCancelFunc, err = driver.Connect(vmName) + if err != nil { + log.Printf(fmt.Sprintf("Non-fatal error starting vmconnect: %s. continuing...", err)) + } } return multistep.ActionContinue } @@ -45,11 +49,10 @@ func (s *StepRun) Cleanup(state multistep.StateBag) { driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) - guiCancelFunc := state.Get("guiCancelFunc").(context.CancelFunc) - if guiCancelFunc != nil { + if !s.Headless && s.GuiCancelFunc != nil { ui.Say("Disconnecting from vmconnect...") - guiCancelFunc() + s.GuiCancelFunc() } if running, _ := driver.IsRunning(s.vmName); running { diff --git a/common/powershell/hyperv/hyperv.go b/common/powershell/hyperv/hyperv.go index 6503b2327..f1ed1e45a 100644 --- a/common/powershell/hyperv/hyperv.go +++ b/common/powershell/hyperv/hyperv.go @@ -1247,11 +1247,15 @@ param([string]$vmName, [string]$scanCodes) return err } -func ConnectVirtualMachine(vmName string) context.CancelFunc { +func ConnectVirtualMachine(vmName string) (context.CancelFunc, error) { ctx, cancel := context.WithCancel(context.Background()) cmd := exec.CommandContext(ctx, "vmconnect.exe", "localhost", vmName) - cmd.Start() - return cancel + err := cmd.Start() + if err != nil { + // Failed to start so cancel function not required + cancel = nil + } + return cancel, err } func DisconnectVirtualMachine(cancel context.CancelFunc) { From 5710c0aca17979c519422b1e2572820c640e2782 Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 14 May 2018 20:53:51 +1000 Subject: [PATCH 3/3] Making log output clearer for hyper-v gui connection --- builder/hyperv/common/step_run.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/hyperv/common/step_run.go b/builder/hyperv/common/step_run.go index 13bf640df..bb415ffa1 100644 --- a/builder/hyperv/common/step_run.go +++ b/builder/hyperv/common/step_run.go @@ -33,7 +33,7 @@ func (s *StepRun) Run(_ context.Context, state multistep.StateBag) multistep.Ste s.vmName = vmName if !s.Headless { - ui.Say("Connecting to vmconnect...") + ui.Say("Attempting to connect with vmconnect...") s.GuiCancelFunc, err = driver.Connect(vmName) if err != nil { log.Printf(fmt.Sprintf("Non-fatal error starting vmconnect: %s. continuing...", err))