diff --git a/builder/vmware/common/step_clean_vmx.go b/builder/vmware/common/step_clean_vmx.go index e9bc51987..eb67aa14d 100755 --- a/builder/vmware/common/step_clean_vmx.go +++ b/builder/vmware/common/step_clean_vmx.go @@ -54,6 +54,7 @@ func (s StepCleanVMX) Run(state multistep.StateBag) multistep.StepAction { vmxData[ide+"devicetype"] = "cdrom-raw" vmxData[ide+"filename"] = "auto detect" + vmxData[ide+"clientdevice"] = "TRUE" } ui.Message("Disabling VNC server...") diff --git a/builder/vmware/common/step_output_dir.go b/builder/vmware/common/step_output_dir.go index 17f13d5d3..9807296ba 100644 --- a/builder/vmware/common/step_output_dir.go +++ b/builder/vmware/common/step_output_dir.go @@ -60,15 +60,18 @@ func (s *StepOutputDir) Cleanup(state multistep.StateBag) { dir := state.Get("dir").(OutputDir) ui := state.Get("ui").(packer.Ui) - ui.Say("Deleting output directory...") - for i := 0; i < 5; i++ { - err := dir.RemoveAll() - if err == nil { - break - } + exists, _ := dir.DirExists() + if exists { + ui.Say("Deleting output directory...") + for i := 0; i < 5; i++ { + err := dir.RemoveAll() + if err == nil { + break + } - log.Printf("Error removing output dir: %s", err) - time.Sleep(2 * time.Second) + log.Printf("Error removing output dir: %s", err) + time.Sleep(2 * time.Second) + } } } } diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index bac3dbdff..617e1579b 100755 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -40,6 +40,7 @@ type Config struct { DiskSize uint `mapstructure:"disk_size"` DiskTypeId string `mapstructure:"disk_type_id"` FloppyFiles []string `mapstructure:"floppy_files"` + Format string `mapstruture:"format"` GuestOSType string `mapstructure:"guest_os_type"` ISOChecksum string `mapstructure:"iso_checksum"` ISOChecksumType string `mapstructure:"iso_checksum_type"` @@ -235,6 +236,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe default: dir = new(vmwcommon.LocalOutputDir) } + if b.config.RemoteType != "" && b.config.Format != "" { + b.config.OutputDir = b.config.VMName + } dir.SetOutputDir(b.config.OutputDir) // Setup the state bag @@ -289,7 +293,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe VNCPortMin: b.config.VNCPortMin, VNCPortMax: b.config.VNCPortMax, }, - &StepRegister{}, + &StepRegister{ + Format: b.config.Format, + }, &vmwcommon.StepRun{ BootWait: b.config.BootWait, DurationBeforeStop: 5 * time.Second, @@ -328,6 +334,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &vmwcommon.StepCompactDisk{ Skip: b.config.SkipCompaction, }, + &StepExport{ + Format: b.config.Format, + }, } // Run! @@ -357,7 +366,14 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe } // Compile the artifact list - files, err := state.Get("dir").(OutputDir).ListFiles() + var files []string + if b.config.RemoteType != "" { + dir = new(vmwcommon.LocalOutputDir) + dir.SetOutputDir(b.config.OutputDir) + files, err = dir.ListFiles() + } else { + files, err = state.Get("dir").(OutputDir).ListFiles() + } if err != nil { return nil, err } diff --git a/builder/vmware/iso/driver_esx5.go b/builder/vmware/iso/driver_esx5.go index 75d4d3d25..e17dd7f69 100644 --- a/builder/vmware/iso/driver_esx5.go +++ b/builder/vmware/iso/driver_esx5.go @@ -104,6 +104,18 @@ func (d *ESX5Driver) Unregister(vmxPathLocal string) error { return d.sh("vim-cmd", "vmsvc/unregister", d.vmId) } +func (d *ESX5Driver) Destroy() error { + return d.sh("vim-cmd", "vmsvc/destroy", d.vmId) +} + +func (d *ESX5Driver) IsDestroyed() (bool, error) { + err := d.sh("test", "!", "-e", d.outputDir) + if err != nil { + return false, err + } + return true, err +} + func (d *ESX5Driver) UploadISO(localPath string, checksum string, checksumType string) (string, error) { finalPath := d.cachePath(localPath) if err := d.mkdir(filepath.ToSlash(filepath.Dir(finalPath))); err != nil { diff --git a/builder/vmware/iso/remote_driver.go b/builder/vmware/iso/remote_driver.go index 7c62cd4d7..378c949d4 100644 --- a/builder/vmware/iso/remote_driver.go +++ b/builder/vmware/iso/remote_driver.go @@ -18,6 +18,12 @@ type RemoteDriver interface { // Removes a VM from inventory specified by the path to the VMX given. Unregister(string) error + // Destroys a VM + Destroy() error + + // Checks if the VM is destroyed. + IsDestroyed() (bool, error) + // Uploads a local file to remote side. upload(dst, src string) error diff --git a/builder/vmware/iso/remote_driver_mock.go b/builder/vmware/iso/remote_driver_mock.go index 2f4b3ae81..dcd1ba0aa 100644 --- a/builder/vmware/iso/remote_driver_mock.go +++ b/builder/vmware/iso/remote_driver_mock.go @@ -20,6 +20,13 @@ type RemoteDriverMock struct { UnregisterPath string UnregisterErr error + DestroyCalled bool + DestroyErr error + + IsDestroyedCalled bool + IsDestroyedResult bool + IsDestroyedErr error + uploadErr error ReloadVMErr error @@ -43,6 +50,16 @@ func (d *RemoteDriverMock) Unregister(path string) error { return d.UnregisterErr } +func (d *RemoteDriverMock) Destroy() error { + d.DestroyCalled = true + return d.DestroyErr +} + +func (d *RemoteDriverMock) IsDestroyed() (bool, error) { + d.DestroyCalled = true + return d.IsDestroyedResult, d.IsDestroyedErr +} + func (d *RemoteDriverMock) upload(dst, src string) error { return d.uploadErr } diff --git a/builder/vmware/iso/step_export.go b/builder/vmware/iso/step_export.go new file mode 100644 index 000000000..8c1ed1b93 --- /dev/null +++ b/builder/vmware/iso/step_export.go @@ -0,0 +1,74 @@ +package iso + +import ( + "bytes" + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "net/url" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" +) + +type StepExport struct { + Format string +} + +func (s *StepExport) Run(state multistep.StateBag) multistep.StepAction { + c := state.Get("config").(*Config) + ui := state.Get("ui").(packer.Ui) + + if c.RemoteType != "esx5" || s.Format == "" { + return multistep.ActionContinue + } + + ovftool := "ovftool" + if runtime.GOOS == "windows" { + ovftool = "ovftool.exe" + } + + if _, err := exec.LookPath(ovftool); err != nil { + err := fmt.Errorf("Error %s not found: %s", ovftool, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // Export the VM + outputPath := filepath.Join(c.VMName, c.VMName+"."+s.Format) + + if s.Format == "ova" { + os.MkdirAll(outputPath, 0755) + } + + args := []string{ + "--noSSLVerify=true", + "--skipManifestCheck", + "-tt=" + s.Format, + "vi://" + c.RemoteUser + ":" + url.QueryEscape(c.RemotePassword) + "@" + c.RemoteHost + "/" + c.VMName, + outputPath, + } + + ui.Say("Exporting virtual machine...") + ui.Message(fmt.Sprintf("Executing: %s %s", ovftool, strings.Join(args, " "))) + var out bytes.Buffer + cmd := exec.Command(ovftool, args...) + cmd.Stdout = &out + if err := cmd.Run(); err != nil { + err := fmt.Errorf("Error exporting virtual machine: %s\n%s\n", err, out.String()) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Message(fmt.Sprintf("%s", out.String())) + + state.Put("exportPath", outputPath) + + return multistep.ActionContinue +} + +func (s *StepExport) Cleanup(state multistep.StateBag) {} diff --git a/builder/vmware/iso/step_register.go b/builder/vmware/iso/step_register.go index 6710cbd74..f4c5d776d 100644 --- a/builder/vmware/iso/step_register.go +++ b/builder/vmware/iso/step_register.go @@ -2,6 +2,7 @@ package iso import ( "fmt" + "time" "github.com/mitchellh/multistep" vmwcommon "github.com/mitchellh/packer/builder/vmware/common" @@ -10,6 +11,7 @@ import ( type StepRegister struct { registeredPath string + Format string } func (s *StepRegister) Run(state multistep.StateBag) multistep.StepAction { @@ -41,12 +43,26 @@ func (s *StepRegister) Cleanup(state multistep.StateBag) { ui := state.Get("ui").(packer.Ui) if remoteDriver, ok := driver.(RemoteDriver); ok { - ui.Say("Unregistering virtual machine...") - if err := remoteDriver.Unregister(s.registeredPath); err != nil { - ui.Error(fmt.Sprintf("Error unregistering VM: %s", err)) - } + if s.Format == "" { + ui.Say("Unregistering virtual machine...") + if err := remoteDriver.Unregister(s.registeredPath); err != nil { + ui.Error(fmt.Sprintf("Error unregistering VM: %s", err)) + } - s.registeredPath = "" + s.registeredPath = "" + } else { + ui.Say("Destroying virtual machine...") + if err := remoteDriver.Destroy(); err != nil { + ui.Error(fmt.Sprintf("Error destroying VM: %s", err)) + } + // Wait for the machine to actually destroy + for { + exists, _ := remoteDriver.IsDestroyed() + if !exists { + break + } + time.Sleep(150 * time.Millisecond) + } + } } - } diff --git a/post-processor/vsphere/post-processor.go b/post-processor/vsphere/post-processor.go index 39cd8c15b..21ae9490c 100644 --- a/post-processor/vsphere/post-processor.go +++ b/post-processor/vsphere/post-processor.go @@ -15,7 +15,8 @@ import ( ) var builtins = map[string]string{ - "mitchellh.vmware": "vmware", + "mitchellh.vmware": "vmware", + "mitchellh.vmware-esx": "vmware", } type Config struct { @@ -95,16 +96,16 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac return nil, false, fmt.Errorf("Unknown artifact type, can't build box: %s", artifact.BuilderId()) } - vmx := "" + source := "" for _, path := range artifact.Files() { - if strings.HasSuffix(path, ".vmx") { - vmx = path + if strings.HasSuffix(path, ".vmx") || strings.HasSuffix(path, ".ovf") || strings.HasSuffix(path, ".ova") { + source = path break } } - if vmx == "" { - return nil, false, fmt.Errorf("VMX file not found") + if source == "" { + return nil, false, fmt.Errorf("VMX, OVF or OVA file not found") } ovftool_uri := fmt.Sprintf("vi://%s:%s@%s/%s/host/%s", @@ -126,11 +127,11 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac fmt.Sprintf("--diskMode=%s", p.config.DiskMode), fmt.Sprintf("--network=%s", p.config.VMNetwork), fmt.Sprintf("--vmFolder=%s", p.config.VMFolder), - fmt.Sprintf("%s", vmx), + fmt.Sprintf("%s", source), fmt.Sprintf("%s", ovftool_uri), } - ui.Message(fmt.Sprintf("Uploading %s to vSphere", vmx)) + ui.Message(fmt.Sprintf("Uploading %s to vSphere", source)) var out bytes.Buffer log.Printf("Starting ovftool with parameters: %s", strings.Join(args, " ")) cmd := exec.Command("ovftool", args...) diff --git a/website/source/docs/builders/vmware-iso.html.markdown b/website/source/docs/builders/vmware-iso.html.markdown index 2859cbfa3..8e851dfb9 100644 --- a/website/source/docs/builders/vmware-iso.html.markdown +++ b/website/source/docs/builders/vmware-iso.html.markdown @@ -398,6 +398,10 @@ modify as well: - `remote_password` - The SSH password for access to the remote machine. +- `format` (string) - Either "ovf", "ova" or "vmx", this specifies the output + format of the exported virtual machine. This defaults to "ovf". + Before using this option, you need to install `ovftool`. + ### Using a Floppy for Linux kickstart file or preseed Depending on your network configuration, it may be difficult to use packer's