diff --git a/builder/vmware/common/artifact.go b/builder/vmware/common/artifact.go index e0b3e875e..b8056e544 100644 --- a/builder/vmware/common/artifact.go +++ b/builder/vmware/common/artifact.go @@ -2,68 +2,87 @@ package common import ( "fmt" - "os" - "path/filepath" + "strconv" + "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" ) -// BuilderId for the local artifacts -const BuilderId = "mitchellh.vmware" +const ( + // BuilderId for the local artifacts + BuilderId = "mitchellh.vmware" + BuilderIdESX = "mitchellh.vmware-esx" + + ArtifactConfFormat = "artifact.conf.format" + ArtifactConfKeepRegistered = "artifact.conf.keep_registered" + ArtifactConfSkipExport = "artifact.conf.skip_export" +) // Artifact is the result of running the VMware builder, namely a set // of files associated with the resulting machine. -type localArtifact struct { - id string - dir string - f []string -} - -// NewLocalArtifact returns a VMware artifact containing the files -// in the given directory. -func NewLocalArtifact(id string, dir string) (packer.Artifact, error) { - files := make([]string, 0, 5) - visit := func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if !info.IsDir() { - files = append(files, path) - } - return nil - } - - if err := filepath.Walk(dir, visit); err != nil { - return nil, err - } - - return &localArtifact{ - id: id, - dir: dir, - f: files, - }, nil +type artifact struct { + builderId string + id string + dir OutputDir + f []string + config map[string]string } -func (a *localArtifact) BuilderId() string { - return BuilderId +func (a *artifact) BuilderId() string { + return a.builderId } -func (a *localArtifact) Files() []string { +func (a *artifact) Files() []string { return a.f } -func (a *localArtifact) Id() string { +func (a *artifact) Id() string { return a.id } -func (a *localArtifact) String() string { +func (a *artifact) String() string { return fmt.Sprintf("VM files in directory: %s", a.dir) } -func (a *localArtifact) State(name string) interface{} { - return nil +func (a *artifact) State(name string) interface{} { + return a.config[name] } -func (a *localArtifact) Destroy() error { - return os.RemoveAll(a.dir) +func (a *artifact) Destroy() error { + return a.dir.RemoveAll() +} + +func NewArtifact(remoteType string, format string, exportOutputPath string, vmName string, skipExport bool, keepRegistered bool, state multistep.StateBag) (packer.Artifact, error) { + var files []string + var dir OutputDir + var err error + if remoteType != "" && !skipExport { + dir = new(LocalOutputDir) + dir.SetOutputDir(exportOutputPath) + files, err = dir.ListFiles() + } else { + files, err = state.Get("dir").(OutputDir).ListFiles() + } + if err != nil { + return nil, err + } + + // Set the proper builder ID + builderId := BuilderId + if remoteType != "" { + builderId = BuilderIdESX + } + + config := make(map[string]string) + config[ArtifactConfKeepRegistered] = strconv.FormatBool(keepRegistered) + config[ArtifactConfFormat] = format + config[ArtifactConfSkipExport] = strconv.FormatBool(skipExport) + + return &artifact{ + builderId: builderId, + id: vmName, + dir: dir, + f: files, + config: config, + }, nil } diff --git a/builder/vmware/common/artifact_test.go b/builder/vmware/common/artifact_test.go index 53b8364f1..dd2c64b74 100644 --- a/builder/vmware/common/artifact_test.go +++ b/builder/vmware/common/artifact_test.go @@ -1,46 +1,11 @@ package common import ( - "io/ioutil" - "os" - "path/filepath" "testing" "github.com/hashicorp/packer/packer" ) func TestLocalArtifact_impl(t *testing.T) { - var _ packer.Artifact = new(localArtifact) -} - -func TestNewLocalArtifact(t *testing.T) { - td, err := ioutil.TempDir("", "packer") - if err != nil { - t.Fatalf("err: %s", err) - } - defer os.RemoveAll(td) - - err = ioutil.WriteFile(filepath.Join(td, "a"), []byte("foo"), 0644) - if err != nil { - t.Fatalf("err: %s", err) - } - - if err := os.Mkdir(filepath.Join(td, "b"), 0755); err != nil { - t.Fatalf("err: %s", err) - } - - a, err := NewLocalArtifact("vm1", td) - if err != nil { - t.Fatalf("err: %s", err) - } - - if a.BuilderId() != BuilderId { - t.Fatalf("bad: %#v", a.BuilderId()) - } - if a.Id() != "vm1" { - t.Fatalf("bad: %#v", a.Id()) - } - if len(a.Files()) != 1 { - t.Fatalf("should length 1: %d", len(a.Files())) - } + var _ packer.Artifact = new(artifact) } diff --git a/builder/vmware/common/driver.go b/builder/vmware/common/driver.go index ec34e70b5..525c13d2e 100644 --- a/builder/vmware/common/driver.go +++ b/builder/vmware/common/driver.go @@ -81,46 +81,64 @@ type Driver interface { // NewDriver returns a new driver implementation for this operating // system, or an error if the driver couldn't be initialized. -func NewDriver(dconfig *DriverConfig, config *SSHConfig) (Driver, error) { +func NewDriver(dconfig *DriverConfig, config *SSHConfig, vmName string) (Driver, error) { drivers := []Driver{} - switch runtime.GOOS { - case "darwin": + if dconfig.RemoteType != "" { drivers = []Driver{ - &Fusion6Driver{ - Fusion5Driver: Fusion5Driver{ + &ESX5Driver{ + Host: dconfig.RemoteHost, + Port: dconfig.RemotePort, + Username: dconfig.RemoteUser, + Password: dconfig.RemotePassword, + PrivateKeyFile: dconfig.RemotePrivateKey, + Datastore: dconfig.RemoteDatastore, + CacheDatastore: dconfig.RemoteCacheDatastore, + CacheDirectory: dconfig.RemoteCacheDirectory, + VMName: vmName, + CommConfig: config.Comm, + }, + } + + } else { + switch runtime.GOOS { + case "darwin": + drivers = []Driver{ + &Fusion6Driver{ + Fusion5Driver: Fusion5Driver{ + AppPath: dconfig.FusionAppPath, + SSHConfig: config, + }, + }, + &Fusion5Driver{ AppPath: dconfig.FusionAppPath, SSHConfig: config, }, - }, - &Fusion5Driver{ - AppPath: dconfig.FusionAppPath, - SSHConfig: config, - }, - } - case "linux": - fallthrough - case "windows": - drivers = []Driver{ - &Workstation10Driver{ - Workstation9Driver: Workstation9Driver{ + } + case "linux": + fallthrough + case "windows": + drivers = []Driver{ + &Workstation10Driver{ + Workstation9Driver: Workstation9Driver{ + SSHConfig: config, + }, + }, + &Workstation9Driver{ SSHConfig: config, }, - }, - &Workstation9Driver{ - SSHConfig: config, - }, - &Player6Driver{ - Player5Driver: Player5Driver{ + &Player6Driver{ + Player5Driver: Player5Driver{ + SSHConfig: config, + }, + }, + &Player5Driver{ SSHConfig: config, }, - }, - &Player5Driver{ - SSHConfig: config, - }, + } + default: + return nil, fmt.Errorf("can't find driver for OS: %s", runtime.GOOS) } - default: - return nil, fmt.Errorf("can't find driver for OS: %s", runtime.GOOS) } errs := "" diff --git a/builder/vmware/common/driver_config.go b/builder/vmware/common/driver_config.go index 13c9e96ac..6367b1734 100644 --- a/builder/vmware/common/driver_config.go +++ b/builder/vmware/common/driver_config.go @@ -7,7 +7,16 @@ import ( ) type DriverConfig struct { - FusionAppPath string `mapstructure:"fusion_app_path"` + FusionAppPath string `mapstructure:"fusion_app_path"` + RemoteType string `mapstructure:"remote_type"` + RemoteDatastore string `mapstructure:"remote_datastore"` + RemoteCacheDatastore string `mapstructure:"remote_cache_datastore"` + RemoteCacheDirectory string `mapstructure:"remote_cache_directory"` + RemoteHost string `mapstructure:"remote_host"` + RemotePort uint `mapstructure:"remote_port"` + RemoteUser string `mapstructure:"remote_username"` + RemotePassword string `mapstructure:"remote_password"` + RemotePrivateKey string `mapstructure:"remote_private_key_file"` } func (c *DriverConfig) Prepare(ctx *interpolate.Context) []error { @@ -17,6 +26,21 @@ func (c *DriverConfig) Prepare(ctx *interpolate.Context) []error { if c.FusionAppPath == "" { c.FusionAppPath = "/Applications/VMware Fusion.app" } + if c.RemoteUser == "" { + c.RemoteUser = "root" + } + if c.RemoteDatastore == "" { + c.RemoteDatastore = "datastore1" + } + if c.RemoteCacheDatastore == "" { + c.RemoteCacheDatastore = c.RemoteDatastore + } + if c.RemoteCacheDirectory == "" { + c.RemoteCacheDirectory = "packer_cache" + } + if c.RemotePort == 0 { + c.RemotePort = 22 + } return nil } diff --git a/builder/vmware/iso/driver_esx5.go b/builder/vmware/common/driver_esx5.go similarity index 84% rename from builder/vmware/iso/driver_esx5.go rename to builder/vmware/common/driver_esx5.go index 2f37f60b9..602680ddc 100644 --- a/builder/vmware/iso/driver_esx5.go +++ b/builder/vmware/common/driver_esx5.go @@ -1,4 +1,4 @@ -package iso +package common import ( "bufio" @@ -10,13 +10,14 @@ import ( "log" "net" "os" + "path" "path/filepath" "strconv" "strings" "time" - vmwcommon "github.com/hashicorp/packer/builder/vmware/common" "github.com/hashicorp/packer/communicator/ssh" + "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" helperssh "github.com/hashicorp/packer/helper/ssh" "github.com/hashicorp/packer/packer" @@ -26,7 +27,7 @@ import ( // ESX5 driver talks to an ESXi5 hypervisor remotely over SSH to build // virtual machines. This driver can only manage one machine at a time. type ESX5Driver struct { - base vmwcommon.VmwareDriver + base VmwareDriver Host string Port uint @@ -36,6 +37,8 @@ type ESX5Driver struct { Datastore string CacheDatastore string CacheDirectory string + VMName string + CommConfig communicator.Config comm packer.Communicator outputDir string @@ -43,7 +46,61 @@ type ESX5Driver struct { } func (d *ESX5Driver) Clone(dst, src string, linked bool) error { - return errors.New("Cloning is not supported with the ESX driver.") + + linesToArray := func(lines string) []string { return strings.Split(strings.Trim(lines, "\n"), "\n") } + + d.SetOutputDir(path.Dir(filepath.ToSlash(dst))) + srcVmx := d.datastorePath(src) + dstVmx := d.datastorePath(dst) + srcDir := path.Dir(srcVmx) + dstDir := path.Dir(dstVmx) + + log.Printf("Source: %s\n", srcVmx) + log.Printf("Dest: %s\n", dstVmx) + + err := d.MkdirAll() + if err != nil { + return fmt.Errorf("Failed to create the destination directory %s: %s", d.outputDir, err) + } + + err = d.sh("cp", strconv.Quote(srcVmx), strconv.Quote(dstVmx)) + if err != nil { + return fmt.Errorf("Failed to copy the vmx file %s: %s", srcVmx, err) + } + + filesToClone, err := d.run(nil, "find", strconv.Quote(srcDir), "! -name '*.vmdk' ! -name '*.vmx' -type f ! -size 0") + if err != nil { + return fmt.Errorf("Failed to get the file list to copy: %s", err) + } + + for _, f := range linesToArray(filesToClone) { + // TODO: linesToArray should really return [] if the string is empty. Instead it returns [""] + if f == "" { + continue + } + err := d.sh("cp", strconv.Quote(f), strconv.Quote(dstDir)) + if err != nil { + return fmt.Errorf("Failing to copy %s to %s: %s", f, dstDir, err) + } + } + + disksToClone, err := d.run(nil, "sed -ne 's/.*file[Nn]ame = \"\\(.*vmdk\\)\"/\\1/p'", strconv.Quote(srcVmx)) + if err != nil { + return fmt.Errorf("Failing to get the vmdk list to clone %s", err) + } + for _, disk := range linesToArray(disksToClone) { + srcDisk := path.Join(srcDir, disk) + if path.IsAbs(disk) { + srcDisk = disk + } + destDisk := path.Join(dstDir, path.Base(disk)) + err = d.sh("vmkfstools", "-d thin", "-i", strconv.Quote(srcDisk), strconv.Quote(destDisk)) + if err != nil { + return fmt.Errorf("Failing to clone disk %s: %s", srcDisk, err) + } + } + log.Printf("Successfully cloned %s to %s\n", src, dst) + return nil } func (d *ESX5Driver) CompactDisk(diskPathLocal string) error { @@ -65,7 +122,11 @@ func (d *ESX5Driver) IsRunning(string) (bool, error) { } func (d *ESX5Driver) ReloadVM() error { - return d.sh("vim-cmd", "vmsvc/reload", d.vmId) + if d.vmId != "" { + return d.sh("vim-cmd", "vmsvc/reload", d.vmId) + } else { + return nil + } } func (d *ESX5Driver) Start(vmxPathLocal string, headless bool) error { @@ -122,13 +183,13 @@ func (d *ESX5Driver) IsDestroyed() (bool, error) { } func (d *ESX5Driver) UploadISO(localPath string, checksum string, checksumType string) (string, error) { - finalPath := d.cachePath(localPath) + finalPath := d.CachePath(localPath) if err := d.mkdir(filepath.ToSlash(filepath.Dir(finalPath))); err != nil { return "", err } log.Printf("Verifying checksum of %s", finalPath) - if d.verifyChecksum(checksumType, checksum, finalPath) { + if d.VerifyChecksum(checksumType, checksum, finalPath) { log.Println("Initial checksum matched, no upload needed.") return finalPath, nil } @@ -141,7 +202,7 @@ func (d *ESX5Driver) UploadISO(localPath string, checksum string, checksumType s } func (d *ESX5Driver) RemoveCache(localPath string) error { - finalPath := d.cachePath(localPath) + finalPath := d.CachePath(localPath) log.Printf("Removing remote cache path %s (local %s)", finalPath, localPath) return d.sh("rm", "-f", strconv.Quote(finalPath)) } @@ -375,14 +436,18 @@ func (ESX5Driver) UpdateVMX(_, password string, port uint, data map[string]strin } func (d *ESX5Driver) CommHost(state multistep.StateBag) (string, error) { - config := state.Get("config").(*Config) - sshc := config.SSHConfig.Comm + sshc := state.Get("sshConfig").(*SSHConfig).Comm port := sshc.SSHPort if sshc.Type == "winrm" { port = sshc.WinRMPort } - if address := config.CommConfig.Host(); address != "" { + if address, ok := state.GetOk("vm_address"); ok { + return address.(string), nil + } + + if address := d.CommConfig.Host(); address != "" { + state.Put("vm_address", address) return address, nil } @@ -396,7 +461,12 @@ func (d *ESX5Driver) CommHost(state multistep.StateBag) (string, error) { var displayName string if v, ok := state.GetOk("display_name"); ok { displayName = v.(string) + } else { + displayName = strings.Replace(d.VMName, " ", "_", -1) + log.Printf("No display_name set; falling back to using VMName %s "+ + "to look for SSH IP", displayName) } + record, err := r.find("Name", displayName) if err != nil { return "", err @@ -432,14 +502,12 @@ func (d *ESX5Driver) CommHost(state multistep.StateBag) (string, error) { if e.Timeout() { log.Printf("Timeout connecting to %s", record["IPAddress"]) continue - } else if strings.Contains(e.Error(), "connection refused") { - log.Printf("Connection refused when connecting to: %s", record["IPAddress"]) - continue } } } else { defer conn.Close() address := record["IPAddress"] + state.Put("vm_address", address) return address, nil } } @@ -456,7 +524,7 @@ func (d *ESX5Driver) DirExists() (bool, error) { } func (d *ESX5Driver) ListFiles() ([]string, error) { - stdout, err := d.ssh("ls -1p "+d.outputDir, nil) + stdout, err := d.ssh("ls -1p "+strconv.Quote(d.outputDir), nil) if err != nil { return nil, err } @@ -503,7 +571,7 @@ func (d *ESX5Driver) datastorePath(path string) string { return filepath.ToSlash(filepath.Join("/vmfs/volumes", d.Datastore, dirPath, filepath.Base(path))) } -func (d *ESX5Driver) cachePath(path string) string { +func (d *ESX5Driver) CachePath(path string) string { return filepath.ToSlash(filepath.Join("/vmfs/volumes", d.CacheDatastore, d.CacheDirectory, filepath.Base(path))) } @@ -592,7 +660,16 @@ func (d *ESX5Driver) upload(dst, src string) error { return d.comm.Upload(dst, f, nil) } -func (d *ESX5Driver) verifyChecksum(ctype string, hash string, file string) bool { +func (d *ESX5Driver) Download(src, dst string) error { + file, err := os.Create(dst) + if err != nil { + return err + } + defer file.Close() + return d.comm.Download(d.datastorePath(src), file) +} + +func (d *ESX5Driver) VerifyChecksum(ctype string, hash string, file string) bool { if ctype == "none" { if err := d.sh("stat", strconv.Quote(file)); err != nil { return false @@ -661,7 +738,7 @@ func (d *ESX5Driver) esxcli(args ...string) (*esxcliReader, error) { return &esxcliReader{r, header}, nil } -func (d *ESX5Driver) GetVmwareDriver() vmwcommon.VmwareDriver { +func (d *ESX5Driver) GetVmwareDriver() VmwareDriver { return d.base } diff --git a/builder/vmware/iso/driver_esx5_test.go b/builder/vmware/common/driver_esx5_test.go similarity index 68% rename from builder/vmware/iso/driver_esx5_test.go rename to builder/vmware/common/driver_esx5_test.go index 097d6e186..38232bbe7 100644 --- a/builder/vmware/iso/driver_esx5_test.go +++ b/builder/vmware/common/driver_esx5_test.go @@ -1,16 +1,17 @@ -package iso +package common import ( "fmt" "net" "testing" - vmwcommon "github.com/hashicorp/packer/builder/vmware/common" + "github.com/hashicorp/packer/helper/communicator" + "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/helper/multistep" ) func TestESX5Driver_implDriver(t *testing.T) { - var _ vmwcommon.Driver = new(ESX5Driver) + var _ Driver = new(ESX5Driver) } func TestESX5Driver_UpdateVMX(t *testing.T) { @@ -30,11 +31,11 @@ func TestESX5Driver_UpdateVMX(t *testing.T) { } func TestESX5Driver_implOutputDir(t *testing.T) { - var _ vmwcommon.OutputDir = new(ESX5Driver) + var _ OutputDir = new(ESX5Driver) } func TestESX5Driver_implVNCAddressFinder(t *testing.T) { - var _ vmwcommon.VNCAddressFinder = new(ESX5Driver) + var _ VNCAddressFinder = new(ESX5Driver) } func TestESX5Driver_implRemoteDriver(t *testing.T) { @@ -60,28 +61,19 @@ func TestESX5Driver_HostIP(t *testing.T) { func TestESX5Driver_CommHost(t *testing.T) { const expected_host = "127.0.0.1" - config := testConfig() - config["communicator"] = "winrm" - config["winrm_username"] = "username" - config["winrm_password"] = "password" - config["winrm_host"] = expected_host - - var b Builder - warns, err := b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("should not have error: %s", err) - } - if host := b.config.CommConfig.Host(); host != expected_host { - t.Fatalf("setup failed, bad host name: %s", host) - } + conf := make(map[string]interface{}) + conf["communicator"] = "winrm" + conf["winrm_username"] = "username" + conf["winrm_password"] = "password" + conf["winrm_host"] = expected_host + var commConfig communicator.Config + err := config.Decode(&commConfig, nil, conf) state := new(multistep.BasicStateBag) - state.Put("config", &b.config) + sshConfig := SSHConfig{Comm: commConfig} + state.Put("sshConfig", &sshConfig) + driver := ESX5Driver{CommConfig: *(&sshConfig.Comm)} - var driver ESX5Driver host, err := driver.CommHost(state) if err != nil { t.Fatalf("should not have error: %s", err) @@ -89,4 +81,11 @@ func TestESX5Driver_CommHost(t *testing.T) { if host != expected_host { t.Errorf("bad host name: %s", host) } + address, ok := state.GetOk("vm_address") + if !ok { + t.Error("state not updated with vm_address") + } + if address.(string) != expected_host { + t.Errorf("bad vm_address: %s", address.(string)) + } } diff --git a/builder/vmware/common/export_config.go b/builder/vmware/common/export_config.go new file mode 100644 index 000000000..79c4eaeea --- /dev/null +++ b/builder/vmware/common/export_config.go @@ -0,0 +1,26 @@ +package common + +import ( + "fmt" + + "github.com/hashicorp/packer/template/interpolate" +) + +type ExportConfig struct { + Format string `mapstructure:"format"` + OVFToolOptions []string `mapstructure:"ovftool_options"` + SkipExport bool `mapstructure:"skip_export"` + KeepRegistered bool `mapstructure:"keep_registered"` + SkipCompaction bool `mapstructure:"skip_compaction"` +} + +func (c *ExportConfig) Prepare(ctx *interpolate.Context) []error { + var errs []error + if c.Format != "" { + if !(c.Format == "ova" || c.Format == "ovf" || c.Format == "vmx") { + errs = append( + errs, fmt.Errorf("format must be one of ova, ovf, or vmx")) + } + } + return errs +} diff --git a/builder/vmware/common/output_dir.go b/builder/vmware/common/output_dir.go index 8af513a19..85b690f31 100644 --- a/builder/vmware/common/output_dir.go +++ b/builder/vmware/common/output_dir.go @@ -4,6 +4,7 @@ package common // of the output directory for VMware-based products. The abstraction is made // so that the output directory can be properly made on remote (ESXi) based // VMware products as well as local. +// For remote builds, OutputDir interface is satisfied by the ESX5Driver. type OutputDir interface { DirExists() (bool, error) ListFiles() ([]string, error) diff --git a/builder/vmware/iso/remote_driver.go b/builder/vmware/common/remote_driver.go similarity index 87% rename from builder/vmware/iso/remote_driver.go rename to builder/vmware/common/remote_driver.go index 6e830042c..c0c6ffb60 100644 --- a/builder/vmware/iso/remote_driver.go +++ b/builder/vmware/common/remote_driver.go @@ -1,11 +1,7 @@ -package iso - -import ( - vmwcommon "github.com/hashicorp/packer/builder/vmware/common" -) +package common type RemoteDriver interface { - vmwcommon.Driver + Driver // UploadISO uploads a local ISO to the remote side and returns the // new path that should be used in the VMX along with an error if it @@ -30,6 +26,9 @@ type RemoteDriver interface { // Uploads a local file to remote side. upload(dst, src string) error + // Download a remote file to a local file. + Download(src, dst string) error + // Reload VM on remote side. ReloadVM() error } diff --git a/builder/vmware/iso/remote_driver_mock.go b/builder/vmware/common/remote_driver_mock.go similarity index 88% rename from builder/vmware/iso/remote_driver_mock.go rename to builder/vmware/common/remote_driver_mock.go index 03830ddcf..773c7103e 100644 --- a/builder/vmware/iso/remote_driver_mock.go +++ b/builder/vmware/common/remote_driver_mock.go @@ -1,11 +1,7 @@ -package iso - -import ( - vmwcommon "github.com/hashicorp/packer/builder/vmware/common" -) +package common type RemoteDriverMock struct { - vmwcommon.DriverMock + DriverMock UploadISOCalled bool UploadISOPath string @@ -27,7 +23,8 @@ type RemoteDriverMock struct { IsDestroyedResult bool IsDestroyedErr error - uploadErr error + UploadErr error + DownloadErr error ReloadVMErr error } @@ -61,7 +58,11 @@ func (d *RemoteDriverMock) IsDestroyed() (bool, error) { } func (d *RemoteDriverMock) upload(dst, src string) error { - return d.uploadErr + return d.UploadErr +} + +func (d *RemoteDriverMock) Download(src, dst string) error { + return d.DownloadErr } func (d *RemoteDriverMock) RemoveCache(localPath string) error { diff --git a/builder/vmware/common/remote_driver_mock_test.go b/builder/vmware/common/remote_driver_mock_test.go new file mode 100644 index 000000000..110e4990b --- /dev/null +++ b/builder/vmware/common/remote_driver_mock_test.go @@ -0,0 +1,10 @@ +package common + +import ( + "testing" +) + +func TestRemoteDriverMock_impl(t *testing.T) { + var _ Driver = new(RemoteDriverMock) + var _ RemoteDriver = new(RemoteDriverMock) +} diff --git a/builder/vmware/common/step_configure_vmx.go b/builder/vmware/common/step_configure_vmx.go index c3f63b2b2..3692a2c0c 100644 --- a/builder/vmware/common/step_configure_vmx.go +++ b/builder/vmware/common/step_configure_vmx.go @@ -22,12 +22,16 @@ import ( type StepConfigureVMX struct { CustomData map[string]string SkipFloppy bool + VMName string } func (s *StepConfigureVMX) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { + log.Printf("Configuring VMX...\n") + + var err error ui := state.Get("ui").(packer.Ui) - vmxPath := state.Get("vmx_path").(string) + vmxPath := state.Get("vmx_path").(string) vmxData, err := ReadVMX(vmxPath) if err != nil { err := fmt.Errorf("Error reading VMX file: %s", err) @@ -69,7 +73,9 @@ func (s *StepConfigureVMX) Run(_ context.Context, state multistep.StateBag) mult } } - if err := WriteVMX(vmxPath, vmxData); err != nil { + err = WriteVMX(vmxPath, vmxData) + + if err != nil { err := fmt.Errorf("Error writing VMX file: %s", err) state.Put("error", err) ui.Error(err.Error()) diff --git a/builder/vmware/iso/step_export.go b/builder/vmware/common/step_export.go similarity index 81% rename from builder/vmware/iso/step_export.go rename to builder/vmware/common/step_export.go index c39443c8f..173de488b 100644 --- a/builder/vmware/iso/step_export.go +++ b/builder/vmware/common/step_export.go @@ -1,4 +1,4 @@ -package iso +package common import ( "bytes" @@ -19,12 +19,14 @@ import ( // Uses: // display_name string type StepExport struct { - Format string - SkipExport bool - OutputDir string + Format string + SkipExport bool + VMName string + OVFToolOptions []string + OutputDir string } -func (s *StepExport) generateArgs(c *Config, displayName string, hidePassword bool) []string { +func (s *StepExport) generateArgs(c *DriverConfig, displayName string, hidePassword bool) []string { password := url.QueryEscape(c.RemotePassword) if hidePassword { password = "****" @@ -33,18 +35,19 @@ func (s *StepExport) generateArgs(c *Config, displayName string, hidePassword bo "--noSSLVerify=true", "--skipManifestCheck", "-tt=" + s.Format, + "vi://" + c.RemoteUser + ":" + password + "@" + c.RemoteHost + "/" + displayName, s.OutputDir, } - return append(c.OVFToolOptions, args...) + return append(s.OVFToolOptions, args...) } func (s *StepExport) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { - c := state.Get("config").(*Config) + c := state.Get("driverConfig").(*DriverConfig) ui := state.Get("ui").(packer.Ui) // Skip export if requested - if c.SkipExport { + if s.SkipExport { ui.Say("Skipping export of virtual machine...") return multistep.ActionContinue } @@ -60,7 +63,7 @@ func (s *StepExport) Run(_ context.Context, state multistep.StateBag) multistep. } if _, err := exec.LookPath(ovftool); err != nil { - err := fmt.Errorf("Error %s not found: %s", ovftool, err) + err = fmt.Errorf("Error %s not found: %s", ovftool, err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt @@ -68,7 +71,7 @@ func (s *StepExport) Run(_ context.Context, state multistep.StateBag) multistep. // Export the VM if s.OutputDir == "" { - s.OutputDir = c.VMName + "." + s.Format + s.OutputDir = s.VMName + "." + s.Format } if s.Format == "ova" { diff --git a/builder/vmware/iso/step_export_test.go b/builder/vmware/common/step_export_test.go similarity index 90% rename from builder/vmware/iso/step_export_test.go rename to builder/vmware/common/step_export_test.go index 27c2a0c36..70197c827 100644 --- a/builder/vmware/iso/step_export_test.go +++ b/builder/vmware/common/step_export_test.go @@ -1,4 +1,4 @@ -package iso +package common import ( "context" @@ -15,9 +15,9 @@ func testStepExport_wrongtype_impl(t *testing.T, remoteType string) { state := testState(t) step := new(StepExport) - var config Config + var config DriverConfig config.RemoteType = "foo" - state.Put("config", &config) + state.Put("driverConfig", &config) if action := step.Run(context.Background(), state); action != multistep.ActionContinue { t.Fatalf("bad action: %#v", action) diff --git a/builder/vmware/iso/step_register.go b/builder/vmware/common/step_register.go similarity index 82% rename from builder/vmware/iso/step_register.go rename to builder/vmware/common/step_register.go index c97800910..6866e6703 100644 --- a/builder/vmware/iso/step_register.go +++ b/builder/vmware/common/step_register.go @@ -1,11 +1,10 @@ -package iso +package common import ( "context" "fmt" "time" - vmwcommon "github.com/hashicorp/packer/builder/vmware/common" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" ) @@ -13,11 +12,14 @@ import ( type StepRegister struct { registeredPath string Format string + KeepRegistered bool + SkipExport bool } func (s *StepRegister) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { - driver := state.Get("driver").(vmwcommon.Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) + vmxPath := state.Get("vmx_path").(string) if remoteDriver, ok := driver.(RemoteDriver); ok { @@ -40,19 +42,18 @@ func (s *StepRegister) Cleanup(state multistep.StateBag) { return } - driver := state.Get("driver").(vmwcommon.Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) - config := state.Get("config").(*Config) _, cancelled := state.GetOk(multistep.StateCancelled) _, halted := state.GetOk(multistep.StateHalted) - if (config.KeepRegistered) && (!cancelled && !halted) { + if (s.KeepRegistered) && (!cancelled && !halted) { ui.Say("Keeping virtual machine registered with ESX host (keep_registered = true)") return } if remoteDriver, ok := driver.(RemoteDriver); ok { - if s.Format == "" || config.SkipExport { + if s.SkipExport { ui.Say("Unregistering virtual machine...") if err := remoteDriver.Unregister(s.registeredPath); err != nil { ui.Error(fmt.Sprintf("Error unregistering VM: %s", err)) @@ -70,7 +71,7 @@ func (s *StepRegister) Cleanup(state multistep.StateBag) { if destroyed { break } - time.Sleep(150 * time.Millisecond) + time.Sleep(1 * time.Second) } } } diff --git a/builder/vmware/iso/step_register_test.go b/builder/vmware/common/step_register_test.go similarity index 89% rename from builder/vmware/iso/step_register_test.go rename to builder/vmware/common/step_register_test.go index 099e067ab..a9bcc7b61 100644 --- a/builder/vmware/iso/step_register_test.go +++ b/builder/vmware/common/step_register_test.go @@ -1,4 +1,4 @@ -package iso +package common import ( "context" @@ -31,12 +31,12 @@ func TestStepRegister_regularDriver(t *testing.T) { func TestStepRegister_remoteDriver(t *testing.T) { state := testState(t) - step := new(StepRegister) + step := &StepRegister{ + KeepRegistered: false, + SkipExport: true, + } driver := new(RemoteDriverMock) - var config Config - config.KeepRegistered = false - state.Put("config", &config) state.Put("driver", driver) state.Put("vmx_path", "foo") @@ -71,12 +71,9 @@ func TestStepRegister_remoteDriver(t *testing.T) { } func TestStepRegister_WithoutUnregister_remoteDriver(t *testing.T) { state := testState(t) - step := new(StepRegister) + step := &StepRegister{KeepRegistered: true} driver := new(RemoteDriverMock) - var config Config - config.KeepRegistered = true - state.Put("config", &config) state.Put("driver", driver) state.Put("vmx_path", "foo") diff --git a/builder/vmware/common/step_type_boot_command.go b/builder/vmware/common/step_type_boot_command.go index ac33fd24c..b241d57ba 100644 --- a/builder/vmware/common/step_type_boot_command.go +++ b/builder/vmware/common/step_type_boot_command.go @@ -70,6 +70,7 @@ func (s *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag) // Connect to VNC ui.Say(fmt.Sprintf("Connecting to VM via VNC (%s:%d)", vncIp, vncPort)) + nc, err := net.Dial("tcp", fmt.Sprintf("%s:%d", vncIp, vncPort)) if err != nil { err := fmt.Errorf("Error connecting to VNC: %s", err) diff --git a/builder/vmware/iso/step_upload_vmx.go b/builder/vmware/common/step_upload_vmx.go similarity index 89% rename from builder/vmware/iso/step_upload_vmx.go rename to builder/vmware/common/step_upload_vmx.go index f0fec436c..d0c7b1043 100644 --- a/builder/vmware/iso/step_upload_vmx.go +++ b/builder/vmware/common/step_upload_vmx.go @@ -1,11 +1,10 @@ -package iso +package common import ( "context" "fmt" "path/filepath" - vmwcommon "github.com/hashicorp/packer/builder/vmware/common" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" ) @@ -24,7 +23,7 @@ type StepUploadVMX struct { } func (c *StepUploadVMX) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { - driver := state.Get("driver").(vmwcommon.Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) vmxPath := state.Get("vmx_path").(string) diff --git a/builder/vmware/iso/artifact.go b/builder/vmware/iso/artifact.go deleted file mode 100644 index 026b4580f..000000000 --- a/builder/vmware/iso/artifact.go +++ /dev/null @@ -1,45 +0,0 @@ -package iso - -import ( - "fmt" -) - -const ( - ArtifactConfFormat = "artifact.conf.format" - ArtifactConfKeepRegistered = "artifact.conf.keep_registered" - ArtifactConfSkipExport = "artifact.conf.skip_export" -) - -// Artifact is the result of running the VMware builder, namely a set -// of files associated with the resulting machine. -type Artifact struct { - builderId string - id string - dir OutputDir - f []string - config map[string]string -} - -func (a *Artifact) BuilderId() string { - return a.builderId -} - -func (a *Artifact) Files() []string { - return a.f -} - -func (a *Artifact) Id() string { - return a.id -} - -func (a *Artifact) String() string { - return fmt.Sprintf("VM files in directory: %s", a.dir) -} - -func (a *Artifact) State(name string) interface{} { - return a.config[name] -} - -func (a *Artifact) Destroy() error { - return a.dir.RemoveAll() -} diff --git a/builder/vmware/iso/artifact_test.go b/builder/vmware/iso/artifact_test.go deleted file mode 100644 index ea4bab42b..000000000 --- a/builder/vmware/iso/artifact_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package iso - -import ( - "testing" - - "github.com/hashicorp/packer/packer" -) - -func TestArtifact_Impl(t *testing.T) { - var raw interface{} - raw = &Artifact{} - if _, ok := raw.(packer.Artifact); !ok { - t.Fatal("Artifact must be a proper artifact") - } -} diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 054ff4664..1a6b66171 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -6,7 +6,6 @@ import ( "io/ioutil" "log" "os" - "strconv" "time" vmwcommon "github.com/hashicorp/packer/builder/vmware/common" @@ -19,8 +18,6 @@ import ( "github.com/hashicorp/packer/template/interpolate" ) -const BuilderIdESX = "mitchellh.vmware-esx" - type Builder struct { config Config runner multistep.Runner @@ -39,6 +36,7 @@ type Config struct { vmwcommon.SSHConfig `mapstructure:",squash"` vmwcommon.ToolsConfig `mapstructure:",squash"` vmwcommon.VMXConfig `mapstructure:",squash"` + vmwcommon.ExportConfig `mapstructure:",squash"` // disk drives AdditionalDiskSize []uint `mapstructure:"disk_additional_size"` @@ -68,26 +66,8 @@ type Config struct { Serial string `mapstructure:"serial"` Parallel string `mapstructure:"parallel"` - // booting a guest - KeepRegistered bool `mapstructure:"keep_registered"` - OVFToolOptions []string `mapstructure:"ovftool_options"` - SkipCompaction bool `mapstructure:"skip_compaction"` - SkipExport bool `mapstructure:"skip_export"` - VMXDiskTemplatePath string `mapstructure:"vmx_disk_template_path"` - VMXTemplatePath string `mapstructure:"vmx_template_path"` - - // remote vsphere - RemoteType string `mapstructure:"remote_type"` - RemoteDatastore string `mapstructure:"remote_datastore"` - RemoteCacheDatastore string `mapstructure:"remote_cache_datastore"` - RemoteCacheDirectory string `mapstructure:"remote_cache_directory"` - RemoteHost string `mapstructure:"remote_host"` - RemotePort uint `mapstructure:"remote_port"` - RemoteUser string `mapstructure:"remote_username"` - RemotePassword string `mapstructure:"remote_password"` - RemotePrivateKey string `mapstructure:"remote_private_key_file"` - - CommConfig communicator.Config `mapstructure:",squash"` + VMXDiskTemplatePath string `mapstructure:"vmx_disk_template_path"` + VMXTemplatePath string `mapstructure:"vmx_template_path"` ctx interpolate.Context } @@ -125,6 +105,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs = packer.MultiErrorAppend(errs, b.config.VMXConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.VNCConfig.Prepare(&b.config.ctx)...) + errs = packer.MultiErrorAppend(errs, b.config.ExportConfig.Prepare(&b.config.ctx)...) if b.config.DiskName == "" { b.config.DiskName = "disk" @@ -175,26 +156,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.config.Version = "9" } - if b.config.RemoteUser == "" { - b.config.RemoteUser = "root" - } - - if b.config.RemoteDatastore == "" { - b.config.RemoteDatastore = "datastore1" - } - - if b.config.RemoteCacheDatastore == "" { - b.config.RemoteCacheDatastore = b.config.RemoteDatastore - } - - if b.config.RemoteCacheDirectory == "" { - b.config.RemoteCacheDirectory = "packer_cache" - } - - if b.config.RemotePort == 0 { - b.config.RemotePort = 22 - } - if b.config.VMXTemplatePath != "" { if err := b.validateVMXTemplatePath(); err != nil { errs = packer.MultiErrorAppend( @@ -221,6 +182,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs = packer.MultiErrorAppend(errs, fmt.Errorf("remote_host must be specified")) } + if b.config.RemoteType != "esx5" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("Only 'esx5' value is accepted for remote_type")) @@ -262,20 +224,22 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { - driver, err := NewDriver(&b.config) + driver, err := vmwcommon.NewDriver(&b.config.DriverConfig, &b.config.SSHConfig, b.config.VMName) if err != nil { return nil, fmt.Errorf("Failed creating VMware driver: %s", err) } // Determine the output dir implementation - var dir OutputDir + var dir vmwcommon.OutputDir switch d := driver.(type) { - case OutputDir: + case vmwcommon.OutputDir: dir = d default: dir = new(vmwcommon.LocalOutputDir) } + // The OutputDir will track remote esxi output; exportOutputPath preserves + // the path to the output on the machine running Packer. exportOutputPath := b.config.OutputDir if b.config.RemoteType != "" { @@ -292,6 +256,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe state.Put("driver", driver) state.Put("hook", hook) state.Put("ui", ui) + state.Put("sshConfig", &b.config.SSHConfig) + state.Put("driverConfig", &b.config.DriverConfig) steps := []multistep.Step{ &vmwcommon.StepPrepareTools{ @@ -327,6 +293,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &stepCreateVMX{}, &vmwcommon.StepConfigureVMX{ CustomData: b.config.VMXData, + VMName: b.config.VMName, }, &vmwcommon.StepSuppressMessages{}, &common.StepHTTPServer{ @@ -341,8 +308,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe VNCPortMax: b.config.VNCPortMax, VNCDisablePassword: b.config.VNCDisablePassword, }, - &StepRegister{ - Format: b.config.Format, + &vmwcommon.StepRegister{ + Format: b.config.Format, + KeepRegistered: b.config.KeepRegistered, + SkipExport: b.config.SkipExport, }, &vmwcommon.StepRun{ DurationBeforeStop: 5 * time.Second, @@ -382,18 +351,21 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &vmwcommon.StepConfigureVMX{ CustomData: b.config.VMXDataPost, SkipFloppy: true, + VMName: b.config.VMName, }, &vmwcommon.StepCleanVMX{ RemoveEthernetInterfaces: b.config.VMXConfig.VMXRemoveEthernet, VNCEnabled: !b.config.DisableVNC, }, - &StepUploadVMX{ + &vmwcommon.StepUploadVMX{ RemoteType: b.config.RemoteType, }, - &StepExport{ - Format: b.config.Format, - SkipExport: b.config.SkipExport, - OutputDir: exportOutputPath, + &vmwcommon.StepExport{ + Format: b.config.Format, + SkipExport: b.config.SkipExport, + VMName: b.config.VMName, + OVFToolOptions: b.config.OVFToolOptions, + OutputDir: exportOutputPath, }, } @@ -416,36 +388,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe } // Compile the artifact list - var files []string - if b.config.RemoteType != "" && b.config.Format != "" && !b.config.SkipExport { - dir = new(vmwcommon.LocalOutputDir) - dir.SetOutputDir(exportOutputPath) - files, err = dir.ListFiles() - } else { - files, err = state.Get("dir").(OutputDir).ListFiles() - } - if err != nil { - return nil, err - } - - // Set the proper builder ID - builderId := vmwcommon.BuilderId - if b.config.RemoteType != "" { - builderId = BuilderIdESX - } - - config := make(map[string]string) - config[ArtifactConfKeepRegistered] = strconv.FormatBool(b.config.KeepRegistered) - config[ArtifactConfFormat] = b.config.Format - config[ArtifactConfSkipExport] = strconv.FormatBool(b.config.SkipExport) - - return &Artifact{ - builderId: builderId, - id: b.config.VMName, - dir: dir, - f: files, - config: config, - }, nil + return vmwcommon.NewArtifact(b.config.RemoteType, b.config.Format, exportOutputPath, + b.config.VMName, b.config.SkipExport, b.config.KeepRegistered, state) } func (b *Builder) Cancel() { diff --git a/builder/vmware/iso/builder_test.go b/builder/vmware/iso/builder_test.go index 59e18907d..b17fb95ab 100644 --- a/builder/vmware/iso/builder_test.go +++ b/builder/vmware/iso/builder_test.go @@ -459,13 +459,13 @@ func TestBuilderPrepare_CommConfig(t *testing.T) { t.Fatalf("should not have error: %s", err) } - if b.config.CommConfig.WinRMUser != "username" { - t.Errorf("bad winrm_username: %s", b.config.CommConfig.WinRMUser) + if b.config.SSHConfig.Comm.WinRMUser != "username" { + t.Errorf("bad winrm_username: %s", b.config.SSHConfig.Comm.WinRMUser) } - if b.config.CommConfig.WinRMPassword != "password" { - t.Errorf("bad winrm_password: %s", b.config.CommConfig.WinRMPassword) + if b.config.SSHConfig.Comm.WinRMPassword != "password" { + t.Errorf("bad winrm_password: %s", b.config.SSHConfig.Comm.WinRMPassword) } - if host := b.config.CommConfig.Host(); host != "1.2.3.4" { + if host := b.config.SSHConfig.Comm.Host(); host != "1.2.3.4" { t.Errorf("bad host: %s", host) } } @@ -487,13 +487,13 @@ func TestBuilderPrepare_CommConfig(t *testing.T) { t.Fatalf("should not have error: %s", err) } - if b.config.CommConfig.SSHUsername != "username" { - t.Errorf("bad ssh_username: %s", b.config.CommConfig.SSHUsername) + if b.config.SSHConfig.Comm.SSHUsername != "username" { + t.Errorf("bad ssh_username: %s", b.config.SSHConfig.Comm.SSHUsername) } - if b.config.CommConfig.SSHPassword != "password" { - t.Errorf("bad ssh_password: %s", b.config.CommConfig.SSHPassword) + if b.config.SSHConfig.Comm.SSHPassword != "password" { + t.Errorf("bad ssh_password: %s", b.config.SSHConfig.Comm.SSHPassword) } - if host := b.config.CommConfig.Host(); host != "1.2.3.4" { + if host := b.config.SSHConfig.Comm.Host(); host != "1.2.3.4" { t.Errorf("bad host: %s", host) } } diff --git a/builder/vmware/iso/driver.go b/builder/vmware/iso/driver.go deleted file mode 100644 index f461c0927..000000000 --- a/builder/vmware/iso/driver.go +++ /dev/null @@ -1,44 +0,0 @@ -package iso - -import ( - "fmt" - - vmwcommon "github.com/hashicorp/packer/builder/vmware/common" -) - -// NewDriver returns a new driver implementation for this operating -// system, or an error if the driver couldn't be initialized. -func NewDriver(config *Config) (vmwcommon.Driver, error) { - drivers := []vmwcommon.Driver{} - - if config.RemoteType == "" { - return vmwcommon.NewDriver(&config.DriverConfig, &config.SSHConfig) - } - - drivers = []vmwcommon.Driver{ - &ESX5Driver{ - Host: config.RemoteHost, - Port: config.RemotePort, - Username: config.RemoteUser, - Password: config.RemotePassword, - PrivateKeyFile: config.RemotePrivateKey, - Datastore: config.RemoteDatastore, - CacheDatastore: config.RemoteCacheDatastore, - CacheDirectory: config.RemoteCacheDirectory, - }, - } - - errs := "" - for _, driver := range drivers { - err := driver.Verify() - if err == nil { - return driver, nil - } - errs += "* " + err.Error() + "\n" - } - - return nil, fmt.Errorf( - "Unable to initialize any driver for this platform. The errors\n"+ - "from each driver are shown below. Please fix at least one driver\n"+ - "to continue:\n%s", errs) -} diff --git a/builder/vmware/iso/output_dir.go b/builder/vmware/iso/output_dir.go deleted file mode 100644 index 30eab2e31..000000000 --- a/builder/vmware/iso/output_dir.go +++ /dev/null @@ -1,14 +0,0 @@ -package iso - -// OutputDir is an interface type that abstracts the creation and handling -// of the output directory for VMware-based products. The abstraction is made -// so that the output directory can be properly made on remote (ESXi) based -// VMware products as well as local. -type OutputDir interface { - DirExists() (bool, error) - ListFiles() ([]string, error) - MkdirAll() error - Remove(string) error - RemoveAll() error - SetOutputDir(string) -} diff --git a/builder/vmware/iso/remote_driver_mock_test.go b/builder/vmware/iso/remote_driver_mock_test.go deleted file mode 100644 index dd7a26e0a..000000000 --- a/builder/vmware/iso/remote_driver_mock_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package iso - -import ( - "testing" - - vmwcommon "github.com/hashicorp/packer/builder/vmware/common" -) - -func TestRemoteDriverMock_impl(t *testing.T) { - var _ vmwcommon.Driver = new(RemoteDriverMock) - var _ RemoteDriver = new(RemoteDriverMock) -} diff --git a/builder/vmware/iso/step_remote_upload.go b/builder/vmware/iso/step_remote_upload.go index 05050326d..f034d1fc3 100644 --- a/builder/vmware/iso/step_remote_upload.go +++ b/builder/vmware/iso/step_remote_upload.go @@ -22,7 +22,7 @@ func (s *stepRemoteUpload) Run(_ context.Context, state multistep.StateBag) mult driver := state.Get("driver").(vmwcommon.Driver) ui := state.Get("ui").(packer.Ui) - remote, ok := driver.(RemoteDriver) + remote, ok := driver.(vmwcommon.RemoteDriver) if !ok { return multistep.ActionContinue } @@ -36,10 +36,10 @@ func (s *stepRemoteUpload) Run(_ context.Context, state multistep.StateBag) mult checksum := config.ISOChecksum checksumType := config.ISOChecksumType - if esx5, ok := remote.(*ESX5Driver); ok { - remotePath := esx5.cachePath(path) + if esx5, ok := remote.(*vmwcommon.ESX5Driver); ok { + remotePath := esx5.CachePath(path) - if esx5.verifyChecksum(checksumType, checksum, remotePath) { + if esx5.VerifyChecksum(checksumType, checksum, remotePath) { ui.Say("Remote cache was verified skipping remote upload...") state.Put(s.Key, remotePath) return multistep.ActionContinue @@ -68,7 +68,7 @@ func (s *stepRemoteUpload) Cleanup(state multistep.StateBag) { driver := state.Get("driver").(vmwcommon.Driver) - remote, ok := driver.(RemoteDriver) + remote, ok := driver.(vmwcommon.RemoteDriver) if !ok { return } diff --git a/builder/vmware/vmx/builder.go b/builder/vmware/vmx/builder.go index 42a550f29..9a633a40b 100644 --- a/builder/vmware/vmx/builder.go +++ b/builder/vmware/vmx/builder.go @@ -34,13 +34,27 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { // Run executes a Packer build and returns a packer.Artifact representing // a VMware image. func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { - driver, err := vmwcommon.NewDriver(&b.config.DriverConfig, &b.config.SSHConfig) + driver, err := vmwcommon.NewDriver(&b.config.DriverConfig, &b.config.SSHConfig, b.config.VMName) if err != nil { return nil, fmt.Errorf("Failed creating VMware driver: %s", err) } - // Setup the directory - dir := new(vmwcommon.LocalOutputDir) + // Determine the output dir implementation + var dir vmwcommon.OutputDir + switch d := driver.(type) { + case vmwcommon.OutputDir: + dir = d + default: + dir = new(vmwcommon.LocalOutputDir) + } + + // The OutputDir will track remote esxi output; exportOutputPath preserves + // the path to the output on the machine running Packer. + exportOutputPath := b.config.OutputDir + + if b.config.RemoteType != "" { + b.config.OutputDir = b.config.VMName + } dir.SetOutputDir(b.config.OutputDir) // Set up the state. @@ -51,6 +65,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe state.Put("driver", driver) state.Put("hook", hook) state.Put("ui", ui) + state.Put("sshConfig", &b.config.SSHConfig) + state.Put("driverConfig", &b.config.DriverConfig) // Build the steps. steps := []multistep.Step{ @@ -73,6 +89,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe }, &vmwcommon.StepConfigureVMX{ CustomData: b.config.VMXData, + VMName: b.config.VMName, }, &vmwcommon.StepSuppressMessages{}, &common.StepHTTPServer{ @@ -80,6 +97,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe HTTPPortMin: b.config.HTTPPortMin, HTTPPortMax: b.config.HTTPPortMax, }, + &vmwcommon.StepUploadVMX{ + RemoteType: b.config.RemoteType, + }, &vmwcommon.StepConfigureVNC{ Enabled: !b.config.DisableVNC, VNCBindAddress: b.config.VNCBindAddress, @@ -87,6 +107,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe VNCPortMax: b.config.VNCPortMax, VNCDisablePassword: b.config.VNCDisablePassword, }, + &vmwcommon.StepRegister{ + Format: b.config.Format, + KeepRegistered: b.config.KeepRegistered, + SkipExport: b.config.SkipExport, + }, &vmwcommon.StepRun{ DurationBeforeStop: 5 * time.Second, Headless: b.config.Headless, @@ -125,11 +150,22 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &vmwcommon.StepConfigureVMX{ CustomData: b.config.VMXDataPost, SkipFloppy: true, + VMName: b.config.VMName, }, &vmwcommon.StepCleanVMX{ RemoveEthernetInterfaces: b.config.VMXConfig.VMXRemoveEthernet, VNCEnabled: !b.config.DisableVNC, }, + &vmwcommon.StepUploadVMX{ + RemoteType: b.config.RemoteType, + }, + &vmwcommon.StepExport{ + Format: b.config.Format, + SkipExport: b.config.SkipExport, + VMName: b.config.VMName, + OVFToolOptions: b.config.OVFToolOptions, + OutputDir: exportOutputPath, + }, } // Run the steps. @@ -150,7 +186,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe return nil, errors.New("Build was halted.") } - return vmwcommon.NewLocalArtifact(b.config.VMName, b.config.OutputDir) + // Artifact + log.Printf("Generating artifact...") + return vmwcommon.NewArtifact(b.config.RemoteType, b.config.Format, exportOutputPath, + b.config.VMName, b.config.SkipExport, b.config.KeepRegistered, state) } // Cancel. diff --git a/builder/vmware/vmx/config.go b/builder/vmware/vmx/config.go index 1e32ac7b7..38a252787 100644 --- a/builder/vmware/vmx/config.go +++ b/builder/vmware/vmx/config.go @@ -25,12 +25,12 @@ type Config struct { vmwcommon.SSHConfig `mapstructure:",squash"` vmwcommon.ToolsConfig `mapstructure:",squash"` vmwcommon.VMXConfig `mapstructure:",squash"` + vmwcommon.ExportConfig `mapstructure:",squash"` - Linked bool `mapstructure:"linked"` - RemoteType string `mapstructure:"remote_type"` - SkipCompaction bool `mapstructure:"skip_compaction"` - SourcePath string `mapstructure:"source_path"` - VMName string `mapstructure:"vm_name"` + Linked bool `mapstructure:"linked"` + RemoteType string `mapstructure:"remote_type"` + SourcePath string `mapstructure:"source_path"` + VMName string `mapstructure:"vm_name"` ctx interpolate.Context } @@ -69,16 +69,39 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { errs = packer.MultiErrorAppend(errs, c.VMXConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.FloppyConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.VNCConfig.Prepare(&c.ctx)...) + errs = packer.MultiErrorAppend(errs, c.ExportConfig.Prepare(&c.ctx)...) - if c.SourcePath == "" { - errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is blank, but is required")) + if c.RemoteType == "" { + if c.SourcePath == "" { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is blank, but is required")) + } else { + if _, err := os.Stat(c.SourcePath); err != nil { + errs = packer.MultiErrorAppend(errs, + fmt.Errorf("source_path is invalid: %s", err)) + } + } } else { - if _, err := os.Stat(c.SourcePath); err != nil { + // Remote configuration validation + if c.RemoteHost == "" { + errs = packer.MultiErrorAppend(errs, + fmt.Errorf("remote_host must be specified")) + } + + if c.RemoteType != "esx5" { errs = packer.MultiErrorAppend(errs, - fmt.Errorf("source_path is invalid: %s", err)) + fmt.Errorf("Only 'esx5' value is accepted for remote_type")) } } + if c.Format == "" { + c.Format = "ovf" + } + + if !(c.Format == "ova" || c.Format == "ovf" || c.Format == "vmx") { + errs = packer.MultiErrorAppend(errs, + fmt.Errorf("format must be one of ova, ovf, or vmx")) + } + // Warnings var warnings []string if c.ShutdownCommand == "" { diff --git a/builder/vmware/vmx/step_clone_vmx.go b/builder/vmware/vmx/step_clone_vmx.go index 848476e9a..2028b5d3e 100644 --- a/builder/vmware/vmx/step_clone_vmx.go +++ b/builder/vmware/vmx/step_clone_vmx.go @@ -3,7 +3,9 @@ package vmx import ( "context" "fmt" + "io/ioutil" "log" + "os" "path/filepath" "regexp" @@ -18,9 +20,15 @@ type StepCloneVMX struct { Path string VMName string Linked bool + tempDir string } func (s *StepCloneVMX) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { + halt := func(err error) multistep.StepAction { + state.Put("error", err) + return multistep.ActionHalt + } + driver := state.Get("driver").(vmwcommon.Driver) ui := state.Get("ui").(packer.Ui) @@ -29,9 +37,11 @@ func (s *StepCloneVMX) Run(_ context.Context, state multistep.StateBag) multiste ui.Say("Cloning source VM...") log.Printf("Cloning from: %s", s.Path) log.Printf("Cloning to: %s", vmxPath) + if err := driver.Clone(vmxPath, s.Path, s.Linked); err != nil { state.Put("error", err) - return multistep.ActionHalt + return halt(err) + } // Read in the machine configuration from the cloned VMX file @@ -40,10 +50,22 @@ func (s *StepCloneVMX) Run(_ context.Context, state multistep.StateBag) multiste // network type so that it can work out things like IP's and MAC // addresses // * The disk compaction step needs the paths to all attached disks + if remoteDriver, ok := driver.(vmwcommon.RemoteDriver); ok { + remoteVmxPath := vmxPath + tempDir, err := ioutil.TempDir("", "packer-vmx") + if err != nil { + return halt(err) + } + s.tempDir = tempDir + vmxPath = filepath.Join(tempDir, s.VMName+".vmx") + if err = remoteDriver.Download(remoteVmxPath, vmxPath); err != nil { + return halt(err) + } + } + vmxData, err := vmwcommon.ReadVMX(vmxPath) if err != nil { - state.Put("error", err) - return multistep.ActionHalt + return halt(err) } var diskFilenames []string @@ -104,4 +126,7 @@ func (s *StepCloneVMX) Run(_ context.Context, state multistep.StateBag) multiste } func (s *StepCloneVMX) Cleanup(state multistep.StateBag) { + if s.tempDir != "" { + os.RemoveAll(s.tempDir) + } } diff --git a/post-processor/vsphere-template/post-processor.go b/post-processor/vsphere-template/post-processor.go index deed5c2d7..d6b4fa769 100644 --- a/post-processor/vsphere-template/post-processor.go +++ b/post-processor/vsphere-template/post-processor.go @@ -8,7 +8,7 @@ import ( "strings" "time" - "github.com/hashicorp/packer/builder/vmware/iso" + vmwcommon "github.com/hashicorp/packer/builder/vmware/common" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/helper/multistep" @@ -19,8 +19,8 @@ import ( ) var builtins = map[string]string{ - vsphere.BuilderId: "vmware", - iso.BuilderIdESX: "vmware", + vsphere.BuilderId: "vmware", + vmwcommon.BuilderIdESX: "vmware", } type Config struct { @@ -96,9 +96,9 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac "Artifact type %s does not fit this requirement", artifact.BuilderId()) } - f := artifact.State(iso.ArtifactConfFormat) - k := artifact.State(iso.ArtifactConfKeepRegistered) - s := artifact.State(iso.ArtifactConfSkipExport) + f := artifact.State(vmwcommon.ArtifactConfFormat) + k := artifact.State(vmwcommon.ArtifactConfKeepRegistered) + s := artifact.State(vmwcommon.ArtifactConfSkipExport) if f != "" && k != "true" && s == "false" { return nil, false, errors.New("To use this post-processor with exporting behavior you need set keep_registered as true") diff --git a/website/source/docs/builders/vmware-vmx.html.md.erb b/website/source/docs/builders/vmware-vmx.html.md.erb index 2829c2592..b7d319b11 100644 --- a/website/source/docs/builders/vmware-vmx.html.md.erb +++ b/website/source/docs/builders/vmware-vmx.html.md.erb @@ -56,7 +56,8 @@ builder. ### Required: -- `source_path` (string) - Path to the source VMX file to clone. +- `source_path` (string) - Path to the source VMX file to clone. If + `remote_type` is enabled then this specifies a path on the `remote_host`. ### Optional: @@ -124,6 +125,40 @@ builder. the builder. By default this is `output-BUILDNAME` where "BUILDNAME" is the name of the build. +- `remote_cache_datastore` (string) - The path to the datastore where + supporting files will be stored during the build on the remote machine. By + default this is the same as the `remote_datastore` option. This only has an + effect if `remote_type` is enabled. + +- `remote_cache_directory` (string) - The path where the ISO and/or floppy + files will be stored during the build on the remote machine. The path is + relative to the `remote_cache_datastore` on the remote machine. By default + this is "packer\_cache". This only has an effect if `remote_type` + is enabled. + +- `remote_datastore` (string) - The path to the datastore where the resulting + VM will be stored when it is built on the remote machine. By default this + is "datastore1". This only has an effect if `remote_type` is enabled. + +- `remote_host` (string) - The host of the remote machine used for access. + This is only required if `remote_type` is enabled. + +- `remote_password` (string) - The SSH password for the user used to access + the remote machine. By default this is empty. This only has an effect if + `remote_type` is enabled. + +- `remote_private_key_file` (string) - The path to the PEM encoded private key + file for the user used to access the remote machine. By default this is empty. + This only has an effect if `remote_type` is enabled. + +- `remote_type` (string) - The type of remote machine that will be used to + build this VM rather than a local desktop product. The only value accepted + for this currently is "esx5". If this is not set, a desktop product will + be used. By default, this is not set. + +- `remote_username` (string) - The username for the SSH user that will access + the remote machine. This is required if `remote_type` is enabled. + - `shutdown_command` (string) - The command to use to gracefully shut down the machine once all the provisioning is done. By default this is an empty string, which tells Packer to just forcefully shut down the machine unless a @@ -157,6 +192,25 @@ builder. slightly larger. If you find this to be the case, you can disable compaction using this configuration value. Defaults to `false`. +- `skip_export` (boolean) - Defaults to `false`. When enabled, Packer will + not export the VM. Useful if the build output is not the resultant image, + but created inside the VM. + +- `keep_registered` (boolean) - Set this to `true` if you would like to keep + the VM registered with the remote ESXi server. This is convenient if you + use packer to provision VMs on ESXi and don't want to use ovftool to + deploy the resulting artifact (VMX or OVA or whatever you used as `format`). + Defaults to `false`. + +- `ovftool_options` (array of strings) - Extra options to pass to ovftool + during export. Each item in the array is a new argument. The options + `--noSSLVerify`, `--skipManifestCheck`, and `--targetType` are reserved, + and should not be passed to this argument. + +- `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`. + - `tools_upload_flavor` (string) - The flavor of the VMware Tools ISO to upload into the VM. Valid values are `darwin`, `linux`, and `windows`. By default, this is empty, which means VMware tools won't be uploaded. @@ -230,6 +284,46 @@ contention. You can tune this delay on a per-builder basis by specifying } ``` +- `` - `` - Simulates pressing a function key. + +- `` `` `` `` - Simulates pressing an arrow key. + +- `` - Simulates pressing the spacebar. + +- `` - Simulates pressing the insert key. + +- `` `` - Simulates pressing the home and end keys. + +- `` `` - Simulates pressing the page up and page down keys. + +- `` `` - Simulates pressing the alt key. + +- `` `` - Simulates pressing the ctrl key. + +- `` `` - Simulates pressing the shift key. + +- `` `` - Simulates pressing and holding the alt key. + +- `` `` - Simulates pressing and holding the ctrl + key. + +- `` `` - Simulates pressing and holding the + shift key. + +- `` `` - Simulates releasing a held alt key. + +- `` `` - Simulates releasing a held ctrl key. + +- `` `` - Simulates releasing a held shift key. + +- `` `` `` - Adds a 1, 5 or 10 second pause before + sending any additional keys. This is useful if you have to generally wait + for the UI to update before typing more. + +In addition to the special keys, each command to type is treated as a +[configuration template](/docs/templates/configuration-templates.html). The +available variables are: + <%= partial "partials/builders/boot-command" %> Example boot command. This is actually a working boot command used to start an