diff --git a/builder/vmware/builder.go b/builder/vmware/builder.go index bac14188a..5a0e2159d 100644 --- a/builder/vmware/builder.go +++ b/builder/vmware/builder.go @@ -13,6 +13,7 @@ import ( "os" "path/filepath" "strings" + "text/template" "time" ) @@ -25,27 +26,29 @@ type Builder struct { } type config struct { - DiskName string `mapstructure:"vmdk_name"` - DiskSize uint `mapstructure:"disk_size"` - GuestOSType string `mapstructure:"guest_os_type"` - ISOMD5 string `mapstructure:"iso_md5"` - ISOUrl string `mapstructure:"iso_url"` - VMName string `mapstructure:"vm_name"` - OutputDir string `mapstructure:"output_directory"` - HTTPDir string `mapstructure:"http_directory"` - HTTPPortMin uint `mapstructure:"http_port_min"` - HTTPPortMax uint `mapstructure:"http_port_max"` - BootCommand []string `mapstructure:"boot_command"` - BootWait time.Duration `` - ShutdownCommand string `mapstructure:"shutdown_command"` - ShutdownTimeout time.Duration `` - SSHUser string `mapstructure:"ssh_username"` - SSHPassword string `mapstructure:"ssh_password"` - SSHPort uint `mapstructure:"ssh_port"` - SSHWaitTimeout time.Duration `` - VMXData map[string]string `mapstructure:"vmx_data"` - VNCPortMin uint `mapstructure:"vnc_port_min"` - VNCPortMax uint `mapstructure:"vnc_port_max"` + DiskName string `mapstructure:"vmdk_name"` + DiskSize uint `mapstructure:"disk_size"` + GuestOSType string `mapstructure:"guest_os_type"` + ISOMD5 string `mapstructure:"iso_md5"` + ISOUrl string `mapstructure:"iso_url"` + VMName string `mapstructure:"vm_name"` + OutputDir string `mapstructure:"output_directory"` + HTTPDir string `mapstructure:"http_directory"` + HTTPPortMin uint `mapstructure:"http_port_min"` + HTTPPortMax uint `mapstructure:"http_port_max"` + BootCommand []string `mapstructure:"boot_command"` + BootWait time.Duration `` + ShutdownCommand string `mapstructure:"shutdown_command"` + ShutdownTimeout time.Duration `` + SSHUser string `mapstructure:"ssh_username"` + SSHPassword string `mapstructure:"ssh_password"` + SSHPort uint `mapstructure:"ssh_port"` + SSHWaitTimeout time.Duration `` + ToolsUploadFlavor string `mapstructure:"tools_upload_flavor"` + ToolsUploadPath string `mapstructure:"tools_upload_path"` + VMXData map[string]string `mapstructure:"vmx_data"` + VNCPortMin uint `mapstructure:"vnc_port_min"` + VNCPortMax uint `mapstructure:"vnc_port_max"` PackerDebug bool `mapstructure:"packer_debug"` @@ -106,6 +109,10 @@ func (b *Builder) Prepare(raws ...interface{}) error { b.config.SSHPort = 22 } + if b.config.ToolsUploadPath == "" { + b.config.ToolsUploadPath = "{{ .Flavor }}.iso" + } + // Accumulate any errors var err error errs := make([]error, 0) @@ -192,6 +199,10 @@ func (b *Builder) Prepare(raws ...interface{}) error { errs = append(errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %s", err)) } + if _, err := template.New("path").Parse(b.config.ToolsUploadPath); err != nil { + errs = append(errs, fmt.Errorf("tools_upload_path invalid: %s", err)) + } + if b.config.VNCPortMin > b.config.VNCPortMax { errs = append(errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max")) } @@ -213,6 +224,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe rand.Seed(time.Now().UTC().UnixNano()) steps := []multistep.Step{ + &stepPrepareTools{}, &stepDownloadISO{}, &stepPrepareOutputDir{}, &stepCreateDisk{}, diff --git a/builder/vmware/builder_test.go b/builder/vmware/builder_test.go index aab7fe505..bc8e8a801 100644 --- a/builder/vmware/builder_test.go +++ b/builder/vmware/builder_test.go @@ -309,6 +309,36 @@ func TestBuilderPrepare_SSHWaitTimeout(t *testing.T) { } } +func TestBuilderPrepare_ToolsUploadPath(t *testing.T) { + var b Builder + config := testConfig() + + // Test a default + delete(config, "tools_upload_path") + err := b.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } + + if b.config.ToolsUploadPath == "" { + t.Fatalf("bad value: %s", b.config.ToolsUploadPath) + } + + // Test with a bad value + config["tools_upload_path"] = "{{{nope}" + err = b.Prepare(config) + if err == nil { + t.Fatal("should have error") + } + + // Test with a good one + config["tools_upload_path"] = "hey" + err = b.Prepare(config) + if err != nil { + t.Fatalf("should not have error: %s", err) + } +} + func TestBuilderPrepare_VNCPort(t *testing.T) { var b Builder config := testConfig() diff --git a/builder/vmware/driver.go b/builder/vmware/driver.go index 906fb66b3..d363aa001 100644 --- a/builder/vmware/driver.go +++ b/builder/vmware/driver.go @@ -24,8 +24,8 @@ type Driver interface { // Stop stops a VM specified by the path to the VMX given. Stop(string) error - // Get the path to the VMware ISO. - ToolsIsoPath() string + // Get the path to the VMware ISO for the given flavor. + ToolsIsoPath(string) string // Verify checks to make sure that this driver should function // properly. This should check that all the files it will use @@ -124,9 +124,8 @@ func (d *Fusion5Driver) vmrunPath() string { return filepath.Join(d.AppPath, "Contents", "Library", "vmrun") } -// @TODO: Be smarter about guest type before deciding on linux.iso. -func (d *Fusion5Driver) ToolsIsoPath() string { - return filepath.Join(d.AppPath, "Contents", "Library", "isoimages", "linux.iso") +func (d *Fusion5Driver) ToolsIsoPath(k string) string { + return filepath.Join(d.AppPath, "Contents", "Library", "isoimages", k+".iso") } func (d *Fusion5Driver) runAndLog(cmd *exec.Cmd) (string, string, error) { diff --git a/builder/vmware/step_prepare_tools.go b/builder/vmware/step_prepare_tools.go new file mode 100644 index 000000000..0df461321 --- /dev/null +++ b/builder/vmware/step_prepare_tools.go @@ -0,0 +1,33 @@ +package vmware + +import ( + "fmt" + "github.com/mitchellh/multistep" + "os" +) + +type stepPrepareTools struct{} + +func (*stepPrepareTools) Run(state map[string]interface{}) multistep.StepAction { + config := state["config"].(*config) + driver := state["driver"].(Driver) + + if config.ToolsUploadFlavor == "" { + return multistep.ActionContinue + } + + path := driver.ToolsIsoPath(config.ToolsUploadFlavor) + if _, err := os.Stat(path); err != nil { + state["error"] = fmt.Errorf( + "Couldn't find VMware tools for '%s'! VMware often downloads these\n"+ + "tools on-demand. However, to do this, you need to create a fake VM\n"+ + "of the proper type then click the 'install tools' option in the\n"+ + "VMware GUI.", config.ToolsUploadFlavor) + return multistep.ActionHalt + } + + state["tools_upload_source"] = path + return multistep.ActionContinue +} + +func (*stepPrepareTools) Cleanup(map[string]interface{}) {} diff --git a/builder/vmware/step_upload_tools.go b/builder/vmware/step_upload_tools.go index 4526e700a..dfe4dfa39 100644 --- a/builder/vmware/step_upload_tools.go +++ b/builder/vmware/step_upload_tools.go @@ -1,29 +1,44 @@ package vmware import ( + "bytes" "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "os" + "text/template" ) +type toolsUploadPathTemplate struct { + Flavor string +} + type stepUploadTools struct{} func (*stepUploadTools) Run(state map[string]interface{}) multistep.StepAction { + config := state["config"].(*config) + if config.ToolsUploadFlavor == "" { + return multistep.ActionContinue + } + comm := state["communicator"].(packer.Communicator) + tools_source := state["tools_upload_source"].(string) ui := state["ui"].(packer.Ui) - driver := state["driver"].(Driver) - ui.Say("Uploading the VMware Tools.") - - f, err := os.Open(driver.ToolsIsoPath()) + ui.Say(fmt.Sprintf("Uploading the '%s' VMware Tools", config.ToolsUploadFlavor)) + f, err := os.Open(tools_source) if err != nil { state["error"] = fmt.Errorf("Error opening VMware Tools ISO: %s", err) return multistep.ActionHalt } defer f.Close() - if err := comm.Upload("/tmp/linux.iso", f); err != nil { + tplData := &toolsUploadPathTemplate{Flavor: config.ToolsUploadFlavor} + var processedPath bytes.Buffer + t := template.Must(template.New("path").Parse(config.ToolsUploadPath)) + t.Execute(&processedPath, tplData) + + if err := comm.Upload(processedPath.String(), f); err != nil { state["error"] = fmt.Errorf("Error uploading VMware Tools: %s", err) return multistep.ActionHalt }