From 5947d77f05e227d2bd6bb5ce86d9a178757d8f10 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 7 Jun 2013 14:48:59 -0700 Subject: [PATCH] builder/vmware: configure VNC port randomly --- builder/vmware/builder.go | 15 ++++++ builder/vmware/step_configure_vnc.go | 70 ++++++++++++++++++++++++++++ builder/vmware/vmx.go | 42 +++++++++++++++++ builder/vmware/vmx_test.go | 16 +++++++ 4 files changed, 143 insertions(+) create mode 100644 builder/vmware/step_configure_vnc.go diff --git a/builder/vmware/builder.go b/builder/vmware/builder.go index a3c09d15e..19b97c667 100644 --- a/builder/vmware/builder.go +++ b/builder/vmware/builder.go @@ -33,6 +33,8 @@ type config struct { SSHUser string `mapstructure:"ssh_username"` SSHPassword string `mapstructure:"ssh_password"` SSHWaitTimeout time.Duration + VNCPortMin uint `mapstructure:"vnc_port_min"` + VNCPortMax uint `mapstructure:"vnc_port_max"` RawBootWait string `mapstructure:"boot_wait"` RawShutdownTimeout string `mapstructure:"shutdown_timeout"` @@ -53,6 +55,14 @@ func (b *Builder) Prepare(raw interface{}) (err error) { b.config.VMName = "packer" } + if b.config.VNCPortMin == 0 { + b.config.VNCPortMin = 5900 + } + + if b.config.VNCPortMax == 0 { + b.config.VNCPortMax = 6000 + } + if b.config.OutputDir == "" { b.config.OutputDir = "vmware" } @@ -93,6 +103,10 @@ func (b *Builder) Prepare(raw interface{}) (err error) { errs = append(errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %s", err)) } + if b.config.VNCPortMin > b.config.VNCPortMax { + errs = append(errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max")) + } + b.driver, err = b.newDriver() if err != nil { errs = append(errs, fmt.Errorf("Failed creating VMware driver: %s", err)) @@ -111,6 +125,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook) packer.Artifact { &stepCreateDisk{}, &stepCreateVMX{}, &stepHTTPServer{}, + &stepConfigureVNC{}, &stepRun{}, &stepTypeBootCommand{}, &stepWaitForSSH{}, diff --git a/builder/vmware/step_configure_vnc.go b/builder/vmware/step_configure_vnc.go new file mode 100644 index 000000000..e91a9f12e --- /dev/null +++ b/builder/vmware/step_configure_vnc.go @@ -0,0 +1,70 @@ +package vmware + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "io/ioutil" + "math/rand" + "net" + "os" +) + +// This step configures the VM to enable the VNC server. +// +// Uses: +// config *config +// ui packer.Ui +// vmx_path string +// +// Produces: +// vnc_port uint - The port that VNC is configured to listen on. +type stepConfigureVNC struct{} + +func (stepConfigureVNC) Run(state map[string]interface{}) multistep.StepAction { + config := state["config"].(*config) + ui := state["ui"].(packer.Ui) + vmxPath := state["vmx_path"].(string) + + f, err := os.Open(vmxPath) + if err != nil { + ui.Error(fmt.Sprintf("Error while reading VMX data: %s", err)) + return multistep.ActionHalt + } + + vmxBytes, err := ioutil.ReadAll(f) + if err != nil { + ui.Error(fmt.Sprintf("Error reading VMX data: %s", err)) + return multistep.ActionHalt + } + + // 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. + var vncPort uint + portRange := int(config.VNCPortMax - config.VNCPortMin) + for { + vncPort = uint(rand.Intn(portRange) + portRange) + l, err := net.Listen("tcp", fmt.Sprintf(":%d", vncPort)) + if err == nil { + defer l.Close() + break + } + } + + vmxData := ParseVMX(string(vmxBytes)) + vmxData["RemoteDisplay.vnc.enabled"] = "TRUE" + vmxData["RemoteDisplay.vnc.port"] = fmt.Sprintf("%d", vncPort) + + if err := WriteVMX(vmxPath, vmxData); err != nil { + ui.Error(fmt.Sprintf("Error writing VMX data: %s", err)) + return multistep.ActionHalt + } + + state["vnc_port"] = vncPort + + return multistep.ActionContinue +} + +func (stepConfigureVNC) Cleanup(map[string]interface{}) { +} diff --git a/builder/vmware/vmx.go b/builder/vmware/vmx.go index ee5c3f1b7..4211d956f 100644 --- a/builder/vmware/vmx.go +++ b/builder/vmware/vmx.go @@ -1,7 +1,12 @@ package vmware import ( + "bytes" + "fmt" + "io" + "os" "regexp" + "sort" "strings" ) @@ -23,3 +28,40 @@ func ParseVMX(contents string) map[string]string { return results } + +// EncodeVMX takes a map and turns it into valid VMX contents. +func EncodeVMX(contents map[string]string) string { + var buf bytes.Buffer + + i := 0 + keys := make([]string, len(contents)) + for k, _ := range contents { + keys[i] = k + i++ + } + + sort.Strings(keys) + for _, k := range keys { + buf.WriteString(fmt.Sprintf("%s = \"%s\"\n", k, contents[k])) + } + + return buf.String() +} + +// WriteVMX takes a path to a VMX file and contents in the form of a +// map and writes it out. +func WriteVMX(path string, data map[string]string) (err error) { + f, err := os.Create(path) + if err != nil { + return + } + defer f.Close() + + var buf bytes.Buffer + buf.WriteString(EncodeVMX(data)) + if _, err = io.Copy(f, &buf); err != nil { + return + } + + return +} diff --git a/builder/vmware/vmx_test.go b/builder/vmware/vmx_test.go index 758d934f9..d8d554150 100644 --- a/builder/vmware/vmx_test.go +++ b/builder/vmware/vmx_test.go @@ -21,3 +21,19 @@ config.version = "8" t.Errorf("invalid config.version: %s", results["config.version"]) } } + +func TestEncodeVMX(t *testing.T) { + contents := map[string]string{ + ".encoding": "UTF-8", + "config.version": "8", + } + + expected := `.encoding = "UTF-8" +config.version = "8" +` + + result := EncodeVMX(contents) + if result != expected { + t.Errorf("invalid results: %s", result) + } +}