diff --git a/builder/qemu/builder.go b/builder/qemu/builder.go index 96f1609fc..f151bf68c 100644 --- a/builder/qemu/builder.go +++ b/builder/qemu/builder.go @@ -104,6 +104,7 @@ type Config struct { ShutdownCommand string `mapstructure:"shutdown_command"` SSHHostPortMin uint `mapstructure:"ssh_host_port_min"` SSHHostPortMax uint `mapstructure:"ssh_host_port_max"` + VNCBindAddress string `mapstructure:"vnc_bind_address"` VNCPortMin uint `mapstructure:"vnc_port_min"` VNCPortMax uint `mapstructure:"vnc_port_max"` VMName string `mapstructure:"vm_name"` @@ -194,6 +195,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.config.SSHHostPortMax = 4444 } + if b.config.VNCBindAddress == "" { + b.config.VNCBindAddress = "127.0.0.1" + } + if b.config.VNCPortMin == 0 { b.config.VNCPortMin = 5900 } diff --git a/builder/qemu/builder_test.go b/builder/qemu/builder_test.go index cebcf6fbb..5c68a9316 100644 --- a/builder/qemu/builder_test.go +++ b/builder/qemu/builder_test.go @@ -132,6 +132,25 @@ func TestBuilderPrepare_BootWait(t *testing.T) { } } +func TestBuilderPrepare_VNCBindAddress(t *testing.T) { + var b Builder + config := testConfig() + + // Test a default boot_wait + delete(config, "vnc_bind_address") + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("err: %s", err) + } + + if b.config.VNCBindAddress != "127.0.0.1" { + t.Fatalf("bad value: %s", b.config.VNCBindAddress) + } +} + func TestBuilderPrepare_DiskCompaction(t *testing.T) { var b Builder config := testConfig() diff --git a/builder/qemu/step_configure_vnc.go b/builder/qemu/step_configure_vnc.go index e24c4f9ca..d7bace29e 100644 --- a/builder/qemu/step_configure_vnc.go +++ b/builder/qemu/step_configure_vnc.go @@ -26,7 +26,7 @@ func (stepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction { // Find an open VNC port. Note that this can still fail later on // because we have to release the port at some point. But this does its // best. - msg := fmt.Sprintf("Looking for available port between %d and %d", config.VNCPortMin, config.VNCPortMax) + msg := fmt.Sprintf("Looking for available port between %d and %d on %s", config.VNCPortMin, config.VNCPortMax, config.VNCBindAddress) ui.Say(msg) log.Printf(msg) var vncPort uint @@ -39,15 +39,16 @@ func (stepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction { } log.Printf("Trying port: %d", vncPort) - l, err := net.Listen("tcp", fmt.Sprintf(":%d", vncPort)) + l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", config.VNCBindAddress, vncPort)) if err == nil { defer l.Close() break } } - ui.Say(fmt.Sprintf("Found available VNC port: %d", vncPort)) + log.Printf("Found available VNC port: %d on IP: %s", vncPort, config.VNCBindAddress) state.Put("vnc_port", vncPort) + state.Put("vnc_ip", config.VNCBindAddress) return multistep.ActionContinue } diff --git a/builder/qemu/step_run.go b/builder/qemu/step_run.go index d7dfc3e29..82bcbe7c0 100644 --- a/builder/qemu/step_run.go +++ b/builder/qemu/step_run.go @@ -61,12 +61,13 @@ func (s *stepRun) Cleanup(state multistep.StateBag) { func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error) { config := state.Get("config").(*Config) isoPath := state.Get("iso_path").(string) + vncIP := state.Get("vnc_ip").(string) vncPort := state.Get("vnc_port").(uint) sshHostPort := state.Get("sshHostPort").(uint) ui := state.Get("ui").(packer.Ui) driver := state.Get("driver").(Driver) - vnc := fmt.Sprintf("0.0.0.0:%d", vncPort-5900) + vnc := fmt.Sprintf("%s:%d", vncIP, vncPort-5900) vmName := config.VMName imgPath := filepath.Join(config.OutputDir, vmName) @@ -100,9 +101,22 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error deviceArgs = append(deviceArgs, fmt.Sprintf("%s,netdev=user.0", config.NetDevice)) if config.Headless == true { - ui.Message("WARNING: The VM will be started in headless mode, as configured.\n" + - "In headless mode, errors during the boot sequence or OS setup\n" + - "won't be easily visible. Use at your own discretion.") + vncIpRaw, vncIpOk := state.GetOk("vnc_ip") + vncPortRaw, vncPortOk := state.GetOk("vnc_port") + + if vncIpOk && vncPortOk { + vncIp := vncIpRaw.(string) + vncPort := vncPortRaw.(uint) + + ui.Message(fmt.Sprintf( + "The VM will be run headless, without a GUI. If you want to\n"+ + "view the screen of the VM, connect via VNC without a password to\n"+ + "%s:%d", vncIp, vncPort)) + } else { + ui.Message("The VM will be run headless, without a GUI, as configured.\n" + + "If the run isn't succeeding as you expect, please enable the GUI\n" + + "to inspect the progress of the build.") + } } else { if qemuMajor >= 2 { defaultArgs["-display"] = "sdl" diff --git a/website/source/docs/builders/qemu.html.md b/website/source/docs/builders/qemu.html.md index cdacaf3d3..e5aee5ce1 100644 --- a/website/source/docs/builders/qemu.html.md +++ b/website/source/docs/builders/qemu.html.md @@ -306,6 +306,10 @@ default port of `5985` or whatever value you have the service set to listen on. `BUILDNAME` is the name of the build. Currently, no file extension will be used unless it is specified in this option. +- `vnc_bind_address` (string / IP address) - The IP address that should be binded + to for VNC. By default packer will use 127.0.0.1 for this. If you wish to bind + to all interfaces use 0.0.0.0 + - `vnc_port_min` and `vnc_port_max` (integer) - The minimum and maximum port to use for VNC access to the virtual machine. The builder uses VNC to type the initial `boot_command`. Because Packer generally runs in parallel,