diff --git a/builder/virtualbox/common/shutdown_config_test.go b/builder/virtualbox/common/shutdown_config_test.go index b98b3a402..5da613a19 100644 --- a/builder/virtualbox/common/shutdown_config_test.go +++ b/builder/virtualbox/common/shutdown_config_test.go @@ -2,6 +2,7 @@ package common import ( "testing" + "time" ) func testShutdownConfig() *ShutdownConfig { @@ -38,4 +39,7 @@ func TestShutdownConfigPrepare_ShutdownTimeout(t *testing.T) { if len(errs) > 0 { t.Fatalf("err: %#v", errs) } + if c.ShutdownTimeout != 5*time.Second { + t.Fatalf("bad: %s", c.ShutdownTimeout) + } } diff --git a/builder/virtualbox/ovf/config.go b/builder/virtualbox/ovf/config.go index 12141cc23..9e37659fc 100644 --- a/builder/virtualbox/ovf/config.go +++ b/builder/virtualbox/ovf/config.go @@ -57,7 +57,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { templates := map[string]*string{ "source_path": &c.SourcePath, - "vm_name": &c.VMName, + "vm_name": &c.VMName, } for n, ptr := range templates { diff --git a/builder/virtualbox/ovf/config_test.go b/builder/virtualbox/ovf/config_test.go index 56cb088f5..dbb156ce1 100644 --- a/builder/virtualbox/ovf/config_test.go +++ b/builder/virtualbox/ovf/config_test.go @@ -1,14 +1,14 @@ package ovf import ( - "testing" "io/ioutil" "os" + "testing" ) func testConfig(t *testing.T) map[string]interface{} { return map[string]interface{}{ - "ssh_username": "foo", + "ssh_username": "foo", "shutdown_command": "foo", } } @@ -31,7 +31,6 @@ func testConfigOk(t *testing.T, warns []string, err error) { } } - func TestNewConfig_sourcePath(t *testing.T) { // Bad c := testConfig(t) @@ -58,4 +57,3 @@ func TestNewConfig_sourcePath(t *testing.T) { _, warns, errs = NewConfig(c) testConfigOk(t, warns, errs) } - diff --git a/builder/vmware/common/artifact.go b/builder/vmware/common/artifact.go new file mode 100644 index 000000000..ddc2b1a35 --- /dev/null +++ b/builder/vmware/common/artifact.go @@ -0,0 +1,61 @@ +package common + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/mitchellh/packer/packer" +) + +// BuilderId for the local artifacts +const BuilderId = "mitchellh.vmware" + +// Artifact is the result of running the VMware builder, namely a set +// of files associated with the resulting machine. +type localArtifact struct { + dir string + f []string +} + +// NewLocalArtifact returns a VMware artifact containing the files +// in the given directory. +func NewLocalArtifact(dir string) (packer.Artifact, error) { + files := make([]string, 0, 5) + visit := func(path string, info os.FileInfo, err error) error { + if !info.IsDir() { + files = append(files, path) + } + + return err + } + + if err := filepath.Walk(dir, visit); err != nil { + return nil, err + } + + return &localArtifact{ + dir: dir, + f: files, + }, nil +} + +func (a *localArtifact) BuilderId() string { + return BuilderId +} + +func (a *localArtifact) Files() []string { + return a.f +} + +func (*localArtifact) Id() string { + return "VM" +} + +func (a *localArtifact) String() string { + return fmt.Sprintf("VM files in directory: %s", a.dir) +} + +func (a *localArtifact) Destroy() error { + return os.RemoveAll(a.dir) +} diff --git a/builder/vmware/common/artifact_test.go b/builder/vmware/common/artifact_test.go new file mode 100644 index 000000000..a0379b82d --- /dev/null +++ b/builder/vmware/common/artifact_test.go @@ -0,0 +1,43 @@ +package common + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/mitchellh/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(td) + if err != nil { + t.Fatalf("err: %s", err) + } + + if a.BuilderId() != BuilderId { + t.Fatalf("bad: %#v", a.BuilderId()) + } + if len(a.Files()) != 1 { + t.Fatalf("should length 1: %d", len(a.Files())) + } +} diff --git a/builder/vmware/common/config_test.go b/builder/vmware/common/config_test.go new file mode 100644 index 000000000..a84c51bc1 --- /dev/null +++ b/builder/vmware/common/config_test.go @@ -0,0 +1,15 @@ +package common + +import ( + "github.com/mitchellh/packer/packer" + "testing" +) + +func testConfigTemplate(t *testing.T) *packer.ConfigTemplate { + result, err := packer.NewConfigTemplate() + if err != nil { + t.Fatalf("err: %s", err) + } + + return result +} diff --git a/builder/vmware/driver.go b/builder/vmware/common/driver.go similarity index 75% rename from builder/vmware/driver.go rename to builder/vmware/common/driver.go index 7031aebd5..aee7997e1 100644 --- a/builder/vmware/driver.go +++ b/builder/vmware/common/driver.go @@ -1,17 +1,23 @@ -package vmware +package common import ( "bytes" "fmt" - "github.com/mitchellh/multistep" "log" "os/exec" "runtime" "strings" + + "github.com/mitchellh/multistep" ) // A driver is able to talk to VMware, control virtual machines, etc. type Driver interface { + // Clone clones the VMX and the disk to the destination path. The + // destination is a path to the VMX file. The disk will be copied + // to that same directory. + Clone(dst string, src string) error + // CompactDisk compacts a virtual disk. CompactDisk(string) error @@ -50,39 +56,40 @@ 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(config *config) (Driver, error) { +func NewDriver(config *SSHConfig) (Driver, error) { drivers := []Driver{} - if config.RemoteType != "" { + switch runtime.GOOS { + case "darwin": drivers = []Driver{ - &ESX5Driver{ - Host: config.RemoteHost, - Port: config.RemotePort, - Username: config.RemoteUser, - Password: config.RemotePassword, - Datastore: config.RemoteDatastore, + &Fusion6Driver{ + Fusion5Driver: Fusion5Driver{ + AppPath: "/Applications/VMware Fusion.app", + SSHConfig: config, + }, + }, + &Fusion5Driver{ + AppPath: "/Applications/VMware Fusion.app", + SSHConfig: config, }, } - } else { - switch runtime.GOOS { - case "darwin": - drivers = []Driver{ - &Fusion5Driver{ - AppPath: "/Applications/VMware Fusion.app", - }, - } - case "linux": - drivers = []Driver{ - new(Workstation9Driver), - new(Player5LinuxDriver), - } - case "windows": - drivers = []Driver{ - new(Workstation9Driver), - } - default: - return nil, fmt.Errorf("can't find driver for OS: %s", runtime.GOOS) + case "linux": + drivers = []Driver{ + &Workstation9Driver{ + SSHConfig: config, + }, + &Player5LinuxDriver{ + SSHConfig: config, + }, } + case "windows": + drivers = []Driver{ + &Workstation9Driver{ + SSHConfig: config, + }, + } + default: + return nil, fmt.Errorf("can't find driver for OS: %s", runtime.GOOS) } errs := "" diff --git a/builder/vmware/driver_fusion5.go b/builder/vmware/common/driver_fusion5.go similarity index 87% rename from builder/vmware/driver_fusion5.go rename to builder/vmware/common/driver_fusion5.go index a857237ae..40b62ad5c 100644 --- a/builder/vmware/driver_fusion5.go +++ b/builder/vmware/common/driver_fusion5.go @@ -1,19 +1,28 @@ -package vmware +package common import ( + "errors" "fmt" - "github.com/mitchellh/multistep" "io/ioutil" "os" "os/exec" "path/filepath" "strings" + + "github.com/mitchellh/multistep" ) // Fusion5Driver is a driver that can run VMWare Fusion 5. type Fusion5Driver struct { // This is the path to the "VMware Fusion.app" AppPath string + + // SSHConfig are the SSH settings for the Fusion VM + SSHConfig *SSHConfig +} + +func (d *Fusion5Driver) Clone(dst, src string) error { + return errors.New("Cloning is not supported with Fusion 5. Please use Fusion 6+.") } func (d *Fusion5Driver) CompactDisk(diskPath string) error { @@ -61,7 +70,7 @@ func (d *Fusion5Driver) IsRunning(vmxPath string) (bool, error) { } func (d *Fusion5Driver) SSHAddress(state multistep.StateBag) (string, error) { - return sshAddress(state) + return SSHAddressFunc(d.SSHConfig)(state) } func (d *Fusion5Driver) Start(vmxPath string, headless bool) error { @@ -107,7 +116,8 @@ func (d *Fusion5Driver) Verify() error { if _, err := os.Stat(d.vmrunPath()); err != nil { if os.IsNotExist(err) { - return fmt.Errorf("Critical application 'vmrun' not found at path: %s", d.vmrunPath()) + return fmt.Errorf( + "Critical application 'vmrun' not found at path: %s", d.vmrunPath()) } return err @@ -115,7 +125,9 @@ func (d *Fusion5Driver) Verify() error { if _, err := os.Stat(d.vdiskManagerPath()); err != nil { if os.IsNotExist(err) { - return fmt.Errorf("Critical application vdisk manager not found at path: %s", d.vdiskManagerPath()) + return fmt.Errorf( + "Critical application vdisk manager not found at path: %s", + d.vdiskManagerPath()) } return err diff --git a/builder/vmware/common/driver_fusion6.go b/builder/vmware/common/driver_fusion6.go new file mode 100644 index 000000000..98c466cb9 --- /dev/null +++ b/builder/vmware/common/driver_fusion6.go @@ -0,0 +1,67 @@ +package common + +import ( + "bytes" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" +) + +// Fusion6Driver is a driver that can run VMWare Fusion 5. +type Fusion6Driver struct { + Fusion5Driver +} + +func (d *Fusion6Driver) Clone(dst, src string) error { + cmd := exec.Command(d.vmrunPath(), + "-T", "fusion", + "clone", src, dst, + "full") + if _, _, err := runAndLog(cmd); err != nil { + return err + } + + return nil +} + +func (d *Fusion6Driver) Verify() error { + if err := d.Fusion5Driver.Verify(); err != nil { + return err + } + + vmxpath := filepath.Join(d.AppPath, "Contents", "Library", "vmware-vmx") + if _, err := os.Stat(vmxpath); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("vmware-vmx could not be found at path: %s", + vmxpath) + } + + return err + } + + var stderr bytes.Buffer + cmd := exec.Command(vmxpath, "-v") + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + return err + } + + versionRe := regexp.MustCompile(`(?i)VMware [a-z0-9-]+ (\d+\.\d+\.\d+)\s`) + matches := versionRe.FindStringSubmatch(stderr.String()) + if matches == nil { + return fmt.Errorf( + "Couldn't find VMware version in output: %s", stderr.String()) + } + log.Printf("Detected VMware version: %s", matches[1]) + + if !strings.HasPrefix(matches[1], "6.") { + return fmt.Errorf( + "Fusion 6 not detected. Got version: %s", matches[1]) + } + + return nil +} diff --git a/builder/vmware/common/driver_mock.go b/builder/vmware/common/driver_mock.go new file mode 100644 index 000000000..2f5486bac --- /dev/null +++ b/builder/vmware/common/driver_mock.go @@ -0,0 +1,132 @@ +package common + +import ( + "sync" + + "github.com/mitchellh/multistep" +) + +type DriverMock struct { + sync.Mutex + + CloneCalled bool + CloneDst string + CloneSrc string + CloneErr error + + CompactDiskCalled bool + CompactDiskPath string + CompactDiskErr error + + CreateDiskCalled bool + CreateDiskOutput string + CreateDiskSize string + CreateDiskTypeId string + CreateDiskErr error + + IsRunningCalled bool + IsRunningPath string + IsRunningResult bool + IsRunningErr error + + SSHAddressCalled bool + SSHAddressState multistep.StateBag + SSHAddressResult string + SSHAddressErr error + + StartCalled bool + StartPath string + StartHeadless bool + StartErr error + + StopCalled bool + StopPath string + StopErr error + + SuppressMessagesCalled bool + SuppressMessagesPath string + SuppressMessagesErr error + + ToolsIsoPathCalled bool + ToolsIsoPathFlavor string + ToolsIsoPathResult string + + DhcpLeasesPathCalled bool + DhcpLeasesPathDevice string + DhcpLeasesPathResult string + + VerifyCalled bool + VerifyErr error +} + +func (d *DriverMock) Clone(dst string, src string) error { + d.CloneCalled = true + d.CloneDst = dst + d.CloneSrc = src + return d.CloneErr +} + +func (d *DriverMock) CompactDisk(path string) error { + d.CompactDiskCalled = true + d.CompactDiskPath = path + return d.CompactDiskErr +} + +func (d *DriverMock) CreateDisk(output string, size string, typeId string) error { + d.CreateDiskCalled = true + d.CreateDiskOutput = output + d.CreateDiskSize = size + d.CreateDiskTypeId = typeId + return d.CreateDiskErr +} + +func (d *DriverMock) IsRunning(path string) (bool, error) { + d.Lock() + defer d.Unlock() + + d.IsRunningCalled = true + d.IsRunningPath = path + return d.IsRunningResult, d.IsRunningErr +} + +func (d *DriverMock) SSHAddress(state multistep.StateBag) (string, error) { + d.SSHAddressCalled = true + d.SSHAddressState = state + return d.SSHAddressResult, d.SSHAddressErr +} + +func (d *DriverMock) Start(path string, headless bool) error { + d.StartCalled = true + d.StartPath = path + d.StartHeadless = headless + return d.StartErr +} + +func (d *DriverMock) Stop(path string) error { + d.StopCalled = true + d.StopPath = path + return d.StopErr +} + +func (d *DriverMock) SuppressMessages(path string) error { + d.SuppressMessagesCalled = true + d.SuppressMessagesPath = path + return d.SuppressMessagesErr +} + +func (d *DriverMock) ToolsIsoPath(flavor string) string { + d.ToolsIsoPathCalled = true + d.ToolsIsoPathFlavor = flavor + return d.ToolsIsoPathResult +} + +func (d *DriverMock) DhcpLeasesPath(device string) string { + d.DhcpLeasesPathCalled = true + d.DhcpLeasesPathDevice = device + return d.DhcpLeasesPathResult +} + +func (d *DriverMock) Verify() error { + d.VerifyCalled = true + return d.VerifyErr +} diff --git a/builder/vmware/common/driver_mock_test.go b/builder/vmware/common/driver_mock_test.go new file mode 100644 index 000000000..5a056e149 --- /dev/null +++ b/builder/vmware/common/driver_mock_test.go @@ -0,0 +1,9 @@ +package common + +import ( + "testing" +) + +func TestDriverMock_impl(t *testing.T) { + var _ Driver = new(DriverMock) +} diff --git a/builder/vmware/driver_player5.go b/builder/vmware/common/driver_player5.go similarity index 93% rename from builder/vmware/driver_player5.go rename to builder/vmware/common/driver_player5.go index 7f68a80a2..1bec4ea25 100644 --- a/builder/vmware/driver_player5.go +++ b/builder/vmware/common/driver_player5.go @@ -1,12 +1,14 @@ -package vmware +package common import ( + "errors" "fmt" - "github.com/mitchellh/multistep" "os" "os/exec" "path/filepath" "strings" + + "github.com/mitchellh/multistep" ) // Player5LinuxDriver is a driver that can run VMware Player 5 on Linux. @@ -15,6 +17,13 @@ type Player5LinuxDriver struct { VdiskManagerPath string QemuImgPath string VmrunPath string + + // SSHConfig are the SSH settings for the Fusion VM + SSHConfig *SSHConfig +} + +func (d *Player5LinuxDriver) Clone(dst, src string) error { + return errors.New("Cloning is not supported with Player 5. Please use Player 6+.") } func (d *Player5LinuxDriver) CompactDisk(diskPath string) error { @@ -88,7 +97,7 @@ func (d *Player5LinuxDriver) IsRunning(vmxPath string) (bool, error) { } func (d *Player5LinuxDriver) SSHAddress(state multistep.StateBag) (string, error) { - return sshAddress(state) + return SSHAddressFunc(d.SSHConfig)(state) } func (d *Player5LinuxDriver) Start(vmxPath string, headless bool) error { diff --git a/builder/vmware/driver_workstation9.go b/builder/vmware/common/driver_workstation9.go similarity index 92% rename from builder/vmware/driver_workstation9.go rename to builder/vmware/common/driver_workstation9.go index 47ac14220..8643d8b1f 100644 --- a/builder/vmware/driver_workstation9.go +++ b/builder/vmware/common/driver_workstation9.go @@ -1,13 +1,15 @@ -package vmware +package common import ( + "errors" "fmt" - "github.com/mitchellh/multistep" "log" "os" "os/exec" "path/filepath" "strings" + + "github.com/mitchellh/multistep" ) // Workstation9Driver is a driver that can run VMware Workstation 9 @@ -16,6 +18,13 @@ type Workstation9Driver struct { AppPath string VdiskManagerPath string VmrunPath string + + // SSHConfig are the SSH settings for the Fusion VM + SSHConfig *SSHConfig +} + +func (d *Workstation9Driver) Clone(dst, src string) error { + return errors.New("Cloning is not supported with WS 9. Please use WS 10+.") } func (d *Workstation9Driver) CompactDisk(diskPath string) error { @@ -63,7 +72,7 @@ func (d *Workstation9Driver) IsRunning(vmxPath string) (bool, error) { } func (d *Workstation9Driver) SSHAddress(state multistep.StateBag) (string, error) { - return sshAddress(state) + return SSHAddressFunc(d.SSHConfig)(state) } func (d *Workstation9Driver) Start(vmxPath string, headless bool) error { diff --git a/builder/vmware/driver_workstation9_unix.go b/builder/vmware/common/driver_workstation9_unix.go similarity index 98% rename from builder/vmware/driver_workstation9_unix.go rename to builder/vmware/common/driver_workstation9_unix.go index 31d0f33dc..f90c326f4 100644 --- a/builder/vmware/driver_workstation9_unix.go +++ b/builder/vmware/common/driver_workstation9_unix.go @@ -1,6 +1,6 @@ // +build !windows -package vmware +package common import ( "errors" diff --git a/builder/vmware/driver_workstation9_windows.go b/builder/vmware/common/driver_workstation9_windows.go similarity index 99% rename from builder/vmware/driver_workstation9_windows.go rename to builder/vmware/common/driver_workstation9_windows.go index 19a7679c9..17095badc 100644 --- a/builder/vmware/driver_workstation9_windows.go +++ b/builder/vmware/common/driver_workstation9_windows.go @@ -1,6 +1,6 @@ // +build windows -package vmware +package common import ( "log" diff --git a/builder/vmware/guest_ip.go b/builder/vmware/common/guest_ip.go similarity index 99% rename from builder/vmware/guest_ip.go rename to builder/vmware/common/guest_ip.go index 7f259b418..ad345d435 100644 --- a/builder/vmware/guest_ip.go +++ b/builder/vmware/common/guest_ip.go @@ -1,4 +1,4 @@ -package vmware +package common import ( "errors" diff --git a/builder/vmware/common/guest_ip_test.go b/builder/vmware/common/guest_ip_test.go new file mode 100644 index 000000000..fdd6b4c9c --- /dev/null +++ b/builder/vmware/common/guest_ip_test.go @@ -0,0 +1,82 @@ +package common + +import ( + "io/ioutil" + "os" + "testing" +) + +func TestDHCPLeaseGuestLookup_impl(t *testing.T) { + var _ GuestIPFinder = new(DHCPLeaseGuestLookup) +} + +func TestDHCPLeaseGuestLookup(t *testing.T) { + tf, err := ioutil.TempFile("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + if _, err := tf.Write([]byte(testLeaseContents)); err != nil { + t.Fatalf("err: %s", err) + } + tf.Close() + defer os.Remove(tf.Name()) + + driver := new(DriverMock) + driver.DhcpLeasesPathResult = tf.Name() + + finder := &DHCPLeaseGuestLookup{ + Driver: driver, + Device: "vmnet8", + MACAddress: "00:0c:29:59:91:02", + } + + ip, err := finder.GuestIP() + if err != nil { + t.Fatalf("err: %s", err) + } + + if !driver.DhcpLeasesPathCalled { + t.Fatal("should ask for DHCP leases path") + } + if driver.DhcpLeasesPathDevice != "vmnet8" { + t.Fatal("should be vmnet8") + } + + if ip != "192.168.126.130" { + t.Fatalf("bad: %#v", ip) + } +} + +const testLeaseContents = ` +# All times in this file are in UTC (GMT), not your local timezone. This is +# not a bug, so please don't ask about it. There is no portable way to +# store leases in the local timezone, so please don't request this as a +# feature. If this is inconvenient or confusing to you, we sincerely +# apologize. Seriously, though - don't ask. +# The format of this file is documented in the dhcpd.leases(5) manual page. + +lease 192.168.126.129 { + starts 0 2013/09/15 23:58:51; + ends 1 2013/09/16 00:28:51; + hardware ethernet 00:0c:29:59:91:02; + client-hostname "precise64"; +} +lease 192.168.126.130 { + starts 2 2013/09/17 21:39:07; + ends 2 2013/09/17 22:09:07; + hardware ethernet 00:0c:29:59:91:02; + client-hostname "precise64"; +} +lease 192.168.126.128 { + starts 0 2013/09/15 20:09:59; + ends 0 2013/09/15 20:21:58; + hardware ethernet 00:0c:29:59:91:02; + client-hostname "precise64"; +} +lease 192.168.126.127 { + starts 0 2013/09/15 20:09:59; + ends 0 2013/09/15 20:21:58; + hardware ethernet 01:0c:29:59:91:02; + client-hostname "precise64"; + +` diff --git a/builder/vmware/common/output_config.go b/builder/vmware/common/output_config.go new file mode 100644 index 000000000..19be1ba00 --- /dev/null +++ b/builder/vmware/common/output_config.go @@ -0,0 +1,40 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/packer" + "os" +) + +type OutputConfig struct { + OutputDir string `mapstructure:"output_directory"` +} + +func (c *OutputConfig) Prepare(t *packer.ConfigTemplate, pc *common.PackerConfig) []error { + if c.OutputDir == "" { + c.OutputDir = fmt.Sprintf("output-%s", pc.PackerBuildName) + } + + templates := map[string]*string{ + "output_directory": &c.OutputDir, + } + + errs := make([]error, 0) + for n, ptr := range templates { + var err error + *ptr, err = t.Process(*ptr, nil) + if err != nil { + errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + + if !pc.PackerForce { + if _, err := os.Stat(c.OutputDir); err == nil { + errs = append(errs, fmt.Errorf( + "Output directory '%s' already exists. It must not exist.", c.OutputDir)) + } + } + + return errs +} diff --git a/builder/vmware/common/output_config_test.go b/builder/vmware/common/output_config_test.go new file mode 100644 index 000000000..7fa039a16 --- /dev/null +++ b/builder/vmware/common/output_config_test.go @@ -0,0 +1,65 @@ +package common + +import ( + "github.com/mitchellh/packer/common" + "io/ioutil" + "os" + "testing" +) + +func TestOutputConfigPrepare(t *testing.T) { + c := new(OutputConfig) + if c.OutputDir != "" { + t.Fatalf("what: %s", c.OutputDir) + } + + pc := &common.PackerConfig{PackerBuildName: "foo"} + errs := c.Prepare(testConfigTemplate(t), pc) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + + if c.OutputDir == "" { + t.Fatal("should have output dir") + } +} + +func TestOutputConfigPrepare_exists(t *testing.T) { + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(td) + + c := new(OutputConfig) + c.OutputDir = td + + pc := &common.PackerConfig{ + PackerBuildName: "foo", + PackerForce: false, + } + errs := c.Prepare(testConfigTemplate(t), pc) + if len(errs) == 0 { + t.Fatal("should have errors") + } +} + +func TestOutputConfigPrepare_forceExists(t *testing.T) { + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(td) + + c := new(OutputConfig) + c.OutputDir = td + + pc := &common.PackerConfig{ + PackerBuildName: "foo", + PackerForce: true, + } + errs := c.Prepare(testConfigTemplate(t), pc) + if len(errs) > 0 { + t.Fatal("should not have errors") + } +} diff --git a/builder/vmware/common/output_dir.go b/builder/vmware/common/output_dir.go new file mode 100644 index 000000000..8af513a19 --- /dev/null +++ b/builder/vmware/common/output_dir.go @@ -0,0 +1,15 @@ +package common + +// 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) + String() string +} diff --git a/builder/vmware/common/output_dir_local.go b/builder/vmware/common/output_dir_local.go new file mode 100644 index 000000000..39810efa9 --- /dev/null +++ b/builder/vmware/common/output_dir_local.go @@ -0,0 +1,53 @@ +package common + +import ( + "os" + "path/filepath" +) + +// LocalOutputDir is an OutputDir implementation where the directory +// is on the local machine. +type LocalOutputDir struct { + dir string +} + +func (d *LocalOutputDir) DirExists() (bool, error) { + _, err := os.Stat(d.dir) + return err == nil, nil +} + +func (d *LocalOutputDir) ListFiles() ([]string, error) { + files := make([]string, 0, 10) + + visit := func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + files = append(files, path) + } + return nil + } + + return files, filepath.Walk(d.dir, visit) +} + +func (d *LocalOutputDir) MkdirAll() error { + return os.MkdirAll(d.dir, 0755) +} + +func (d *LocalOutputDir) Remove(path string) error { + return os.Remove(path) +} + +func (d *LocalOutputDir) RemoveAll() error { + return os.RemoveAll(d.dir) +} + +func (d *LocalOutputDir) SetOutputDir(path string) { + d.dir = path +} + +func (d *LocalOutputDir) String() string { + return d.dir +} diff --git a/builder/vmware/common/output_dir_local_test.go b/builder/vmware/common/output_dir_local_test.go new file mode 100644 index 000000000..c3197117e --- /dev/null +++ b/builder/vmware/common/output_dir_local_test.go @@ -0,0 +1,9 @@ +package common + +import ( + "testing" +) + +func TestLocalOuputDir_impl(t *testing.T) { + var _ OutputDir = new(LocalOutputDir) +} diff --git a/builder/vmware/common/run_config.go b/builder/vmware/common/run_config.go new file mode 100644 index 000000000..ce3480e9e --- /dev/null +++ b/builder/vmware/common/run_config.go @@ -0,0 +1,44 @@ +package common + +import ( + "fmt" + "time" + + "github.com/mitchellh/packer/packer" +) + +type RunConfig struct { + Headless bool `mapstructure:"headless"` + RawBootWait string `mapstructure:"boot_wait"` + + BootWait time.Duration `` +} + +func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error { + if c.RawBootWait == "" { + c.RawBootWait = "10s" + } + + templates := map[string]*string{ + "boot_wait": &c.RawBootWait, + } + + var err error + errs := make([]error, 0) + for n, ptr := range templates { + *ptr, err = t.Process(*ptr, nil) + if err != nil { + errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + + if c.RawBootWait != "" { + c.BootWait, err = time.ParseDuration(c.RawBootWait) + if err != nil { + errs = append( + errs, fmt.Errorf("Failed parsing boot_wait: %s", err)) + } + } + + return errs +} diff --git a/builder/vmware/common/run_config_test.go b/builder/vmware/common/run_config_test.go new file mode 100644 index 000000000..d94e254f7 --- /dev/null +++ b/builder/vmware/common/run_config_test.go @@ -0,0 +1,36 @@ +package common + +import ( + "testing" +) + +func TestRunConfigPrepare(t *testing.T) { + var c *RunConfig + + // Test a default boot_wait + c = new(RunConfig) + c.RawBootWait = "" + errs := c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("bad: %#v", errs) + } + if c.RawBootWait != "10s" { + t.Fatalf("bad value: %s", c.RawBootWait) + } + + // Test with a bad boot_wait + c = new(RunConfig) + c.RawBootWait = "this is not good" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatal("should error") + } + + // Test with a good one + c = new(RunConfig) + c.RawBootWait = "5s" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("bad: %#v", errs) + } +} diff --git a/builder/vmware/common/shutdown_config.go b/builder/vmware/common/shutdown_config.go new file mode 100644 index 000000000..05e5fdfeb --- /dev/null +++ b/builder/vmware/common/shutdown_config.go @@ -0,0 +1,42 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/packer/packer" + "time" +) + +type ShutdownConfig struct { + ShutdownCommand string `mapstructure:"shutdown_command"` + RawShutdownTimeout string `mapstructure:"shutdown_timeout"` + + ShutdownTimeout time.Duration `` +} + +func (c *ShutdownConfig) Prepare(t *packer.ConfigTemplate) []error { + if c.RawShutdownTimeout == "" { + c.RawShutdownTimeout = "5m" + } + + templates := map[string]*string{ + "shutdown_command": &c.ShutdownCommand, + "shutdown_timeout": &c.RawShutdownTimeout, + } + + errs := make([]error, 0) + for n, ptr := range templates { + var err error + *ptr, err = t.Process(*ptr, nil) + if err != nil { + errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + + var err error + c.ShutdownTimeout, err = time.ParseDuration(c.RawShutdownTimeout) + if err != nil { + errs = append(errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err)) + } + + return errs +} diff --git a/builder/vmware/common/shutdown_config_test.go b/builder/vmware/common/shutdown_config_test.go new file mode 100644 index 000000000..5da613a19 --- /dev/null +++ b/builder/vmware/common/shutdown_config_test.go @@ -0,0 +1,45 @@ +package common + +import ( + "testing" + "time" +) + +func testShutdownConfig() *ShutdownConfig { + return &ShutdownConfig{} +} + +func TestShutdownConfigPrepare_ShutdownCommand(t *testing.T) { + var c *ShutdownConfig + var errs []error + + c = testShutdownConfig() + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } +} + +func TestShutdownConfigPrepare_ShutdownTimeout(t *testing.T) { + var c *ShutdownConfig + var errs []error + + // Test with a bad value + c = testShutdownConfig() + c.RawShutdownTimeout = "this is not good" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatalf("should have error") + } + + // Test with a good one + c = testShutdownConfig() + c.RawShutdownTimeout = "5s" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + if c.ShutdownTimeout != 5*time.Second { + t.Fatalf("bad: %s", c.ShutdownTimeout) + } +} diff --git a/builder/vmware/common/ssh.go b/builder/vmware/common/ssh.go new file mode 100644 index 000000000..7bf8bac72 --- /dev/null +++ b/builder/vmware/common/ssh.go @@ -0,0 +1,106 @@ +package common + +import ( + gossh "code.google.com/p/go.crypto/ssh" + "errors" + "fmt" + "io/ioutil" + "log" + "os" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/communicator/ssh" +) + +func SSHAddressFunc(config *SSHConfig) func(multistep.StateBag) (string, error) { + return func(state multistep.StateBag) (string, error) { + driver := state.Get("driver").(Driver) + vmxPath := state.Get("vmx_path").(string) + + log.Println("Lookup up IP information...") + f, err := os.Open(vmxPath) + if err != nil { + return "", err + } + defer f.Close() + + vmxBytes, err := ioutil.ReadAll(f) + if err != nil { + return "", err + } + + vmxData := ParseVMX(string(vmxBytes)) + + var ok bool + macAddress := "" + if macAddress, ok = vmxData["ethernet0.address"]; !ok || macAddress == "" { + if macAddress, ok = vmxData["ethernet0.generatedaddress"]; !ok || macAddress == "" { + return "", errors.New("couldn't find MAC address in VMX") + } + } + + ipLookup := &DHCPLeaseGuestLookup{ + Driver: driver, + Device: "vmnet8", + MACAddress: macAddress, + } + + ipAddress, err := ipLookup.GuestIP() + if err != nil { + log.Printf("IP lookup failed: %s", err) + return "", fmt.Errorf("IP lookup failed: %s", err) + } + + if ipAddress == "" { + log.Println("IP is blank, no IP yet.") + return "", errors.New("IP is blank") + } + + log.Printf("Detected IP: %s", ipAddress) + return fmt.Sprintf("%s:%d", ipAddress, config.SSHPort), nil + } +} + +func SSHConfigFunc(config *SSHConfig) func(multistep.StateBag) (*gossh.ClientConfig, error) { + return func(state multistep.StateBag) (*gossh.ClientConfig, error) { + auth := []gossh.ClientAuth{ + gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)), + gossh.ClientAuthKeyboardInteractive( + ssh.PasswordKeyboardInteractive(config.SSHPassword)), + } + + if config.SSHKeyPath != "" { + keyring, err := sshKeyToKeyring(config.SSHKeyPath) + if err != nil { + return nil, err + } + + auth = append(auth, gossh.ClientAuthKeyring(keyring)) + } + + return &gossh.ClientConfig{ + User: config.SSHUser, + Auth: auth, + }, nil + } +} + +func sshKeyToKeyring(path string) (gossh.ClientKeyring, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + keyBytes, err := ioutil.ReadAll(f) + if err != nil { + return nil, err + } + + keyring := new(ssh.SimpleKeychain) + if err := keyring.AddPEMKey(string(keyBytes)); err != nil { + return nil, err + } + + return keyring, nil +} diff --git a/builder/vmware/common/ssh_config.go b/builder/vmware/common/ssh_config.go new file mode 100644 index 000000000..0eab2ac3c --- /dev/null +++ b/builder/vmware/common/ssh_config.go @@ -0,0 +1,67 @@ +package common + +import ( + "errors" + "fmt" + "os" + "time" + + "github.com/mitchellh/packer/packer" +) + +type SSHConfig struct { + SSHUser string `mapstructure:"ssh_username"` + SSHKeyPath string `mapstructure:"ssh_key_path"` + SSHPassword string `mapstructure:"ssh_password"` + SSHPort uint `mapstructure:"ssh_port"` + SSHSkipRequestPty bool `mapstructure:"ssh_skip_request_pty"` + RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"` + + SSHWaitTimeout time.Duration +} + +func (c *SSHConfig) Prepare(t *packer.ConfigTemplate) []error { + if c.SSHPort == 0 { + c.SSHPort = 22 + } + + if c.RawSSHWaitTimeout == "" { + c.RawSSHWaitTimeout = "20m" + } + + templates := map[string]*string{ + "ssh_key_path": &c.SSHKeyPath, + "ssh_password": &c.SSHPassword, + "ssh_username": &c.SSHUser, + "ssh_wait_timeout": &c.RawSSHWaitTimeout, + } + + errs := make([]error, 0) + for n, ptr := range templates { + var err error + *ptr, err = t.Process(*ptr, nil) + if err != nil { + errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + + if c.SSHKeyPath != "" { + if _, err := os.Stat(c.SSHKeyPath); err != nil { + errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err)) + } else if _, err := sshKeyToKeyring(c.SSHKeyPath); err != nil { + errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err)) + } + } + + if c.SSHUser == "" { + errs = append(errs, errors.New("An ssh_username must be specified.")) + } + + var err error + c.SSHWaitTimeout, err = time.ParseDuration(c.RawSSHWaitTimeout) + if err != nil { + errs = append(errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %s", err)) + } + + return errs +} diff --git a/builder/vmware/common/ssh_config_test.go b/builder/vmware/common/ssh_config_test.go new file mode 100644 index 000000000..a6c9e8ef5 --- /dev/null +++ b/builder/vmware/common/ssh_config_test.go @@ -0,0 +1,155 @@ +package common + +import ( + "io/ioutil" + "os" + "testing" +) + +func testSSHConfig() *SSHConfig { + return &SSHConfig{ + SSHUser: "foo", + } +} + +func TestSSHConfigPrepare(t *testing.T) { + c := testSSHConfig() + errs := c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + + if c.SSHPort != 22 { + t.Errorf("bad ssh port: %d", c.SSHPort) + } +} + +func TestSSHConfigPrepare_SSHKeyPath(t *testing.T) { + var c *SSHConfig + var errs []error + + c = testSSHConfig() + c.SSHKeyPath = "" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %#v", errs) + } + + c = testSSHConfig() + c.SSHKeyPath = "/i/dont/exist" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatal("should have error") + } + + // Test bad contents + tf, err := ioutil.TempFile("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.Remove(tf.Name()) + defer tf.Close() + + if _, err := tf.Write([]byte("HELLO!")); err != nil { + t.Fatalf("err: %s", err) + } + + c = testSSHConfig() + c.SSHKeyPath = tf.Name() + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatal("should have error") + } + + // Test good contents + tf.Seek(0, 0) + tf.Truncate(0) + tf.Write([]byte(testPem)) + c = testSSHConfig() + c.SSHKeyPath = tf.Name() + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %#v", errs) + } +} + +func TestSSHConfigPrepare_SSHUser(t *testing.T) { + var c *SSHConfig + var errs []error + + c = testSSHConfig() + c.SSHUser = "" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatalf("should have error") + } + + c = testSSHConfig() + c.SSHUser = "exists" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %#v", errs) + } +} + +func TestSSHConfigPrepare_SSHWaitTimeout(t *testing.T) { + var c *SSHConfig + var errs []error + + // Defaults + c = testSSHConfig() + c.RawSSHWaitTimeout = "" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %#v", errs) + } + if c.RawSSHWaitTimeout != "20m" { + t.Fatalf("bad value: %s", c.RawSSHWaitTimeout) + } + + // Test with a bad value + c = testSSHConfig() + c.RawSSHWaitTimeout = "this is not good" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatal("should have error") + } + + // Test with a good one + c = testSSHConfig() + c.RawSSHWaitTimeout = "5s" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %#v", errs) + } +} + +const testPem = ` +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAxd4iamvrwRJvtNDGQSIbNvvIQN8imXTRWlRY62EvKov60vqu +hh+rDzFYAIIzlmrJopvOe0clqmi3mIP9dtkjPFrYflq52a2CF5q+BdwsJXuRHbJW +LmStZUwW1khSz93DhvhmK50nIaczW63u4EO/jJb3xj+wxR1Nkk9bxi3DDsYFt8SN +AzYx9kjlEYQ/+sI4/ATfmdV9h78SVotjScupd9KFzzi76gWq9gwyCBLRynTUWlyD +2UOfJRkOvhN6/jKzvYfVVwjPSfA9IMuooHdScmC4F6KBKJl/zf/zETM0XyzIDNmH +uOPbCiljq2WoRM+rY6ET84EO0kVXbfx8uxUsqQIDAQABAoIBAQCkPj9TF0IagbM3 +5BSs/CKbAWS4dH/D4bPlxx4IRCNirc8GUg+MRb04Xz0tLuajdQDqeWpr6iLZ0RKV +BvreLF+TOdV7DNQ4XE4gSdJyCtCaTHeort/aordL3l0WgfI7mVk0L/yfN1PEG4YG +E9q1TYcyrB3/8d5JwIkjabxERLglCcP+geOEJp+QijbvFIaZR/n2irlKW4gSy6ko +9B0fgUnhkHysSg49ChHQBPQ+o5BbpuLrPDFMiTPTPhdfsvGGcyCGeqfBA56oHcSF +K02Fg8OM+Bd1lb48LAN9nWWY4WbwV+9bkN3Ym8hO4c3a/Dxf2N7LtAQqWZzFjvM3 +/AaDvAgBAoGBAPLD+Xn1IYQPMB2XXCXfOuJewRY7RzoVWvMffJPDfm16O7wOiW5+ +2FmvxUDayk4PZy6wQMzGeGKnhcMMZTyaq2g/QtGfrvy7q1Lw2fB1VFlVblvqhoJa +nMJojjC4zgjBkXMHsRLeTmgUKyGs+fdFbfI6uejBnnf+eMVUMIdJ+6I9AoGBANCn +kWO9640dttyXURxNJ3lBr2H3dJOkmD6XS+u+LWqCSKQe691Y/fZ/ZL0Oc4Mhy7I6 +hsy3kDQ5k2V0fkaNODQIFJvUqXw2pMewUk8hHc9403f4fe9cPrL12rQ8WlQw4yoC +v2B61vNczCCUDtGxlAaw8jzSRaSI5s6ax3K7enbdAoGBAJB1WYDfA2CoAQO6y9Sl +b07A/7kQ8SN5DbPaqrDrBdJziBQxukoMJQXJeGFNUFD/DXFU5Fp2R7C86vXT7HIR +v6m66zH+CYzOx/YE6EsUJms6UP9VIVF0Rg/RU7teXQwM01ZV32LQ8mswhTH20o/3 +uqMHmxUMEhZpUMhrfq0isyApAoGAe1UxGTXfj9AqkIVYylPIq2HqGww7+jFmVEj1 +9Wi6S6Sq72ffnzzFEPkIQL/UA4TsdHMnzsYKFPSbbXLIWUeMGyVTmTDA5c0e5XIR +lPhMOKCAzv8w4VUzMnEkTzkFY5JqFCD/ojW57KvDdNZPVB+VEcdxyAW6aKELXMAc +eHLc1nkCgYEApm/motCTPN32nINZ+Vvywbv64ZD+gtpeMNP3CLrbe1X9O+H52AXa +1jCoOldWR8i2bs2NVPcKZgdo6fFULqE4dBX7Te/uYEIuuZhYLNzRO1IKU/YaqsXG +3bfQ8hKYcSnTfE0gPtLDnqCIxTocaGLSHeG3TH9fTw+dA8FvWpUztI4= +-----END RSA PRIVATE KEY----- +` diff --git a/builder/vmware/step_clean_files.go b/builder/vmware/common/step_clean_files.go similarity index 79% rename from builder/vmware/step_clean_files.go rename to builder/vmware/common/step_clean_files.go index 7b6eaa8b5..de3debccc 100644 --- a/builder/vmware/step_clean_files.go +++ b/builder/vmware/common/step_clean_files.go @@ -1,4 +1,4 @@ -package vmware +package common import ( "fmt" @@ -21,9 +21,9 @@ var KeepFileExtensions = []string{".nvram", ".vmdk", ".vmsd", ".vmx", ".vmxf"} // // Produces: // -type stepCleanFiles struct{} +type StepCleanFiles struct{} -func (stepCleanFiles) Run(state multistep.StateBag) multistep.StepAction { +func (StepCleanFiles) Run(state multistep.StateBag) multistep.StepAction { dir := state.Get("dir").(OutputDir) ui := state.Get("ui").(packer.Ui) @@ -49,7 +49,9 @@ func (stepCleanFiles) Run(state multistep.StateBag) multistep.StepAction { if !keep { ui.Message(fmt.Sprintf("Deleting: %s", path)) if err = dir.Remove(path); err != nil { - // Only report the error if the file still exists + // Only report the error if the file still exists. We do this + // because sometimes the files naturally get removed on their + // own as VMware does its own cleanup. if _, serr := os.Stat(path); serr == nil || !os.IsNotExist(serr) { state.Put("error", err) return multistep.ActionHalt @@ -61,4 +63,4 @@ func (stepCleanFiles) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionContinue } -func (stepCleanFiles) Cleanup(multistep.StateBag) {} +func (StepCleanFiles) Cleanup(multistep.StateBag) {} diff --git a/builder/vmware/step_clean_vmx.go b/builder/vmware/common/step_clean_vmx.go similarity index 53% rename from builder/vmware/step_clean_vmx.go rename to builder/vmware/common/step_clean_vmx.go index a40855d41..b55bcd549 100644 --- a/builder/vmware/step_clean_vmx.go +++ b/builder/vmware/common/step_clean_vmx.go @@ -1,12 +1,10 @@ -package vmware +package common import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "io/ioutil" "log" - "os" "regexp" "strings" ) @@ -20,16 +18,15 @@ import ( // // Produces: // -type stepCleanVMX struct{} +type StepCleanVMX struct{} -func (s stepCleanVMX) Run(state multistep.StateBag) multistep.StepAction { - isoPath := state.Get("iso_path").(string) +func (s StepCleanVMX) Run(state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) vmxPath := state.Get("vmx_path").(string) ui.Say("Cleaning VMX prior to finishing up...") - vmxData, err := s.readVMX(vmxPath) + vmxData, err := ReadVMX(vmxPath) if err != nil { state.Put("error", fmt.Errorf("Error reading VMX: %s", err)) return multistep.ActionHalt @@ -47,20 +44,24 @@ func (s stepCleanVMX) Run(state multistep.StateBag) multistep.StepAction { vmxData["floppy0.present"] = "FALSE" } - ui.Message("Detaching ISO from CD-ROM device...") - devRe := regexp.MustCompile(`^ide\d:\d\.`) - for k, _ := range vmxData { - match := devRe.FindString(k) - if match == "" { - continue - } + if isoPathRaw, ok := state.GetOk("iso_path"); ok { + isoPath := isoPathRaw.(string) - filenameKey := match + "filename" - if filename, ok := vmxData[filenameKey]; ok { - if filename == isoPath { - // Change the CD-ROM device back to auto-detect to eject - vmxData[filenameKey] = "auto detect" - vmxData[match+"devicetype"] = "cdrom-raw" + ui.Message("Detaching ISO from CD-ROM device...") + devRe := regexp.MustCompile(`^ide\d:\d\.`) + for k, _ := range vmxData { + match := devRe.FindString(k) + if match == "" { + continue + } + + filenameKey := match + "filename" + if filename, ok := vmxData[filenameKey]; ok { + if filename == isoPath { + // Change the CD-ROM device back to auto-detect to eject + vmxData[filenameKey] = "auto detect" + vmxData[match+"devicetype"] = "cdrom-raw" + } } } } @@ -74,19 +75,4 @@ func (s stepCleanVMX) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionContinue } -func (stepCleanVMX) Cleanup(multistep.StateBag) {} - -func (stepCleanVMX) readVMX(vmxPath string) (map[string]string, error) { - vmxF, err := os.Open(vmxPath) - if err != nil { - return nil, err - } - defer vmxF.Close() - - vmxBytes, err := ioutil.ReadAll(vmxF) - if err != nil { - return nil, err - } - - return ParseVMX(string(vmxBytes)), nil -} +func (StepCleanVMX) Cleanup(multistep.StateBag) {} diff --git a/builder/vmware/common/step_clean_vmx_test.go b/builder/vmware/common/step_clean_vmx_test.go new file mode 100644 index 000000000..59877027a --- /dev/null +++ b/builder/vmware/common/step_clean_vmx_test.go @@ -0,0 +1,142 @@ +package common + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/mitchellh/multistep" +) + +func TestStepCleanVMX_impl(t *testing.T) { + var _ multistep.Step = new(StepCleanVMX) +} + +func TestStepCleanVMX(t *testing.T) { + state := testState(t) + step := new(StepCleanVMX) + + vmxPath := testVMXFile(t) + defer os.Remove(vmxPath) + state.Put("vmx_path", vmxPath) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } +} + +func TestStepCleanVMX_floppyPath(t *testing.T) { + state := testState(t) + step := new(StepCleanVMX) + + vmxPath := testVMXFile(t) + defer os.Remove(vmxPath) + if err := ioutil.WriteFile(vmxPath, []byte(testVMXFloppyPath), 0644); err != nil { + t.Fatalf("err: %s", err) + } + + state.Put("floppy_path", "foo") + state.Put("vmx_path", vmxPath) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test the resulting data + vmxContents, err := ioutil.ReadFile(vmxPath) + if err != nil { + t.Fatalf("err: %s", err) + } + vmxData := ParseVMX(string(vmxContents)) + + cases := []struct { + Key string + Value string + }{ + {"floppy0.present", "FALSE"}, + {"floppy0.filetype", ""}, + {"floppy0.filename", ""}, + } + + for _, tc := range cases { + if tc.Value == "" { + if _, ok := vmxData[tc.Key]; ok { + t.Fatalf("should not have key: %s", tc.Key) + } + } else { + if vmxData[tc.Key] != tc.Value { + t.Fatalf("bad: %s %#v", tc.Key, vmxData[tc.Key]) + } + } + } +} + +func TestStepCleanVMX_isoPath(t *testing.T) { + state := testState(t) + step := new(StepCleanVMX) + + vmxPath := testVMXFile(t) + defer os.Remove(vmxPath) + if err := ioutil.WriteFile(vmxPath, []byte(testVMXISOPath), 0644); err != nil { + t.Fatalf("err: %s", err) + } + + state.Put("iso_path", "foo") + state.Put("vmx_path", vmxPath) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test the resulting data + vmxContents, err := ioutil.ReadFile(vmxPath) + if err != nil { + t.Fatalf("err: %s", err) + } + vmxData := ParseVMX(string(vmxContents)) + + cases := []struct { + Key string + Value string + }{ + {"ide0:0.filename", "auto detect"}, + {"ide0:0.devicetype", "cdrom-raw"}, + {"ide0:1.filename", "bar"}, + {"foo", "bar"}, + } + + for _, tc := range cases { + if tc.Value == "" { + if _, ok := vmxData[tc.Key]; ok { + t.Fatalf("should not have key: %s", tc.Key) + } + } else { + if vmxData[tc.Key] != tc.Value { + t.Fatalf("bad: %s %#v", tc.Key, vmxData[tc.Key]) + } + } + } +} + +const testVMXFloppyPath = ` +floppy0.present = "TRUE" +floppy0.filetype = "file" +` + +const testVMXISOPath = ` +ide0:0.filename = "foo" +ide0:1.filename = "bar" +foo = "bar" +` diff --git a/builder/vmware/step_compact_disk.go b/builder/vmware/common/step_compact_disk.go similarity index 73% rename from builder/vmware/step_compact_disk.go rename to builder/vmware/common/step_compact_disk.go index 4a87b4a1a..777e8aaea 100644 --- a/builder/vmware/step_compact_disk.go +++ b/builder/vmware/common/step_compact_disk.go @@ -1,4 +1,4 @@ -package vmware +package common import ( "fmt" @@ -11,22 +11,22 @@ import ( // boolean is true. // // Uses: -// config *config // driver Driver // full_disk_path string // ui packer.Ui // // Produces: // -type stepCompactDisk struct{} +type StepCompactDisk struct { + Skip bool +} -func (stepCompactDisk) Run(state multistep.StateBag) multistep.StepAction { - config := state.Get("config").(*config) +func (s StepCompactDisk) Run(state multistep.StateBag) multistep.StepAction { driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) full_disk_path := state.Get("full_disk_path").(string) - if config.SkipCompaction == true { + if s.Skip { log.Println("Skipping disk compaction step...") return multistep.ActionContinue } @@ -40,4 +40,4 @@ func (stepCompactDisk) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionContinue } -func (stepCompactDisk) Cleanup(multistep.StateBag) {} +func (StepCompactDisk) Cleanup(multistep.StateBag) {} diff --git a/builder/vmware/common/step_compact_disk_test.go b/builder/vmware/common/step_compact_disk_test.go new file mode 100644 index 000000000..e59a99a90 --- /dev/null +++ b/builder/vmware/common/step_compact_disk_test.go @@ -0,0 +1,59 @@ +package common + +import ( + "testing" + + "github.com/mitchellh/multistep" +) + +func TestStepCompactDisk_impl(t *testing.T) { + var _ multistep.Step = new(StepCompactDisk) +} + +func TestStepCompactDisk(t *testing.T) { + state := testState(t) + step := new(StepCompactDisk) + + state.Put("full_disk_path", "foo") + + driver := state.Get("driver").(*DriverMock) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test the driver + if !driver.CompactDiskCalled { + t.Fatal("should've called") + } + if driver.CompactDiskPath != "foo" { + t.Fatal("should call with right path") + } +} + +func TestStepCompactDisk_skip(t *testing.T) { + state := testState(t) + step := new(StepCompactDisk) + step.Skip = true + + state.Put("full_disk_path", "foo") + + driver := state.Get("driver").(*DriverMock) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test the driver + if driver.CompactDiskCalled { + t.Fatal("should not have called") + } +} diff --git a/builder/vmware/common/step_configure_vmx.go b/builder/vmware/common/step_configure_vmx.go new file mode 100644 index 000000000..0d53c44e9 --- /dev/null +++ b/builder/vmware/common/step_configure_vmx.go @@ -0,0 +1,78 @@ +package common + +import ( + "fmt" + "io/ioutil" + "log" + "regexp" + "strings" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +// This step configures a VMX by setting some default settings as well +// as taking in custom data to set, attaching a floppy if it exists, etc. +// +// Uses: +// vmx_path string +type StepConfigureVMX struct { + CustomData map[string]string +} + +func (s *StepConfigureVMX) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + vmxPath := state.Get("vmx_path").(string) + + vmxContents, err := ioutil.ReadFile(vmxPath) + if err != nil { + err := fmt.Errorf("Error reading VMX file: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + vmxData := ParseVMX(string(vmxContents)) + + // Set this so that no dialogs ever appear from Packer. + vmxData["msg.autoanswer"] = "true" + + // Create a new UUID for this VM, since it is a new VM + vmxData["uuid.action"] = "create" + + // Delete any generated addresses since we want to regenerate + // them. Conflicting MAC addresses is a bad time. + addrRegex := regexp.MustCompile(`(?i)^ethernet\d+\.generatedAddress`) + for k, _ := range vmxData { + if addrRegex.MatchString(k) { + delete(vmxData, k) + } + } + + // Set custom data + for k, v := range s.CustomData { + log.Printf("Setting VMX: '%s' = '%s'", k, v) + k = strings.ToLower(k) + vmxData[k] = v + } + + // Set a floppy disk if we have one + if floppyPathRaw, ok := state.GetOk("floppy_path"); ok { + log.Println("Floppy path present, setting in VMX") + vmxData["floppy0.present"] = "TRUE" + vmxData["floppy0.filetype"] = "file" + vmxData["floppy0.filename"] = floppyPathRaw.(string) + } + + if err := WriteVMX(vmxPath, vmxData); err != nil { + err := fmt.Errorf("Error writing VMX file: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *StepConfigureVMX) Cleanup(state multistep.StateBag) { +} diff --git a/builder/vmware/common/step_configure_vmx_test.go b/builder/vmware/common/step_configure_vmx_test.go new file mode 100644 index 000000000..293269e3f --- /dev/null +++ b/builder/vmware/common/step_configure_vmx_test.go @@ -0,0 +1,183 @@ +package common + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/mitchellh/multistep" +) + +func testVMXFile(t *testing.T) string { + tf, err := ioutil.TempFile("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + tf.Close() + + return tf.Name() +} + +func TestStepConfigureVMX_impl(t *testing.T) { + var _ multistep.Step = new(StepConfigureVMX) +} + +func TestStepConfigureVMX(t *testing.T) { + state := testState(t) + step := new(StepConfigureVMX) + step.CustomData = map[string]string{ + "foo": "bar", + } + + vmxPath := testVMXFile(t) + defer os.Remove(vmxPath) + state.Put("vmx_path", vmxPath) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test the resulting data + vmxContents, err := ioutil.ReadFile(vmxPath) + if err != nil { + t.Fatalf("err: %s", err) + } + vmxData := ParseVMX(string(vmxContents)) + + cases := []struct { + Key string + Value string + }{ + // Stuff we set + {"msg.autoanswer", "true"}, + {"uuid.action", "create"}, + + // Custom data + {"foo", "bar"}, + + // Stuff that should NOT exist + {"floppy0.present", ""}, + } + + for _, tc := range cases { + if tc.Value == "" { + if _, ok := vmxData[tc.Key]; ok { + t.Fatalf("should not have key: %s", tc.Key) + } + } else { + if vmxData[tc.Key] != tc.Value { + t.Fatalf("bad: %s %#v", tc.Key, vmxData[tc.Key]) + } + } + } +} + +func TestStepConfigureVMX_floppyPath(t *testing.T) { + state := testState(t) + step := new(StepConfigureVMX) + + vmxPath := testVMXFile(t) + defer os.Remove(vmxPath) + + state.Put("floppy_path", "foo") + state.Put("vmx_path", vmxPath) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test the resulting data + vmxContents, err := ioutil.ReadFile(vmxPath) + if err != nil { + t.Fatalf("err: %s", err) + } + vmxData := ParseVMX(string(vmxContents)) + + cases := []struct { + Key string + Value string + }{ + {"floppy0.present", "TRUE"}, + {"floppy0.filetype", "file"}, + {"floppy0.filename", "foo"}, + } + + for _, tc := range cases { + if tc.Value == "" { + if _, ok := vmxData[tc.Key]; ok { + t.Fatalf("should not have key: %s", tc.Key) + } + } else { + if vmxData[tc.Key] != tc.Value { + t.Fatalf("bad: %s %#v", tc.Key, vmxData[tc.Key]) + } + } + } + +} + +func TestStepConfigureVMX_generatedAddresses(t *testing.T) { + state := testState(t) + step := new(StepConfigureVMX) + + vmxPath := testVMXFile(t) + defer os.Remove(vmxPath) + + err := WriteVMX(vmxPath, map[string]string{ + "foo": "bar", + "ethernet0.generatedAddress": "foo", + "ethernet1.generatedAddress": "foo", + "ethernet1.generatedAddressOffset": "foo", + }) + if err != nil { + t.Fatalf("err: %s", err) + } + + state.Put("vmx_path", vmxPath) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test the resulting data + vmxContents, err := ioutil.ReadFile(vmxPath) + if err != nil { + t.Fatalf("err: %s", err) + } + vmxData := ParseVMX(string(vmxContents)) + + cases := []struct { + Key string + Value string + }{ + {"foo", "bar"}, + {"ethernet0.generatedaddress", ""}, + {"ethernet1.generatedaddress", ""}, + {"ethernet1.generatedaddressoffset", ""}, + } + + for _, tc := range cases { + if tc.Value == "" { + if _, ok := vmxData[tc.Key]; ok { + t.Fatalf("should not have key: %s", tc.Key) + } + } else { + if vmxData[tc.Key] != tc.Value { + t.Fatalf("bad: %s %#v", tc.Key, vmxData[tc.Key]) + } + } + } + +} diff --git a/builder/vmware/common/step_output_dir.go b/builder/vmware/common/step_output_dir.go new file mode 100644 index 000000000..17f13d5d3 --- /dev/null +++ b/builder/vmware/common/step_output_dir.go @@ -0,0 +1,74 @@ +package common + +import ( + "fmt" + "log" + "time" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +// StepOutputDir sets up the output directory by creating it if it does +// not exist, deleting it if it does exist and we're forcing, and cleaning +// it up when we're done with it. +type StepOutputDir struct { + Force bool + + success bool +} + +func (s *StepOutputDir) Run(state multistep.StateBag) multistep.StepAction { + dir := state.Get("dir").(OutputDir) + ui := state.Get("ui").(packer.Ui) + + exists, err := dir.DirExists() + if err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + + if exists { + if s.Force { + ui.Say("Deleting previous output directory...") + dir.RemoveAll() + } else { + state.Put("error", fmt.Errorf( + "Output directory '%s' already exists.", dir.String())) + return multistep.ActionHalt + } + } + + if err := dir.MkdirAll(); err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + + s.success = true + return multistep.ActionContinue +} + +func (s *StepOutputDir) Cleanup(state multistep.StateBag) { + if !s.success { + return + } + + _, cancelled := state.GetOk(multistep.StateCancelled) + _, halted := state.GetOk(multistep.StateHalted) + + if cancelled || halted { + 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 + } + + log.Printf("Error removing output dir: %s", err) + time.Sleep(2 * time.Second) + } + } +} diff --git a/builder/vmware/common/step_output_dir_test.go b/builder/vmware/common/step_output_dir_test.go new file mode 100644 index 000000000..fcd64ca7e --- /dev/null +++ b/builder/vmware/common/step_output_dir_test.go @@ -0,0 +1,153 @@ +package common + +import ( + "github.com/mitchellh/multistep" + "io/ioutil" + "os" + "testing" +) + +func testOutputDir(t *testing.T) *LocalOutputDir { + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + os.RemoveAll(td) + + result := new(LocalOutputDir) + result.SetOutputDir(td) + return result +} + +func TestStepOutputDir_impl(t *testing.T) { + var _ multistep.Step = new(StepOutputDir) +} + +func TestStepOutputDir(t *testing.T) { + state := testState(t) + step := new(StepOutputDir) + + dir := testOutputDir(t) + state.Put("dir", dir) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + if _, err := os.Stat(dir.dir); err != nil { + t.Fatalf("err: %s", err) + } + + // Test the cleanup + step.Cleanup(state) + if _, err := os.Stat(dir.dir); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestStepOutputDir_existsNoForce(t *testing.T) { + state := testState(t) + step := new(StepOutputDir) + + dir := testOutputDir(t) + state.Put("dir", dir) + + // Make sure the dir exists + if err := os.MkdirAll(dir.dir, 0755); err != nil { + t.Fatalf("err: %s", err) + } + + // Test the run + if action := step.Run(state); action != multistep.ActionHalt { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); !ok { + t.Fatal("should have error") + } + + // Test the cleanup + step.Cleanup(state) + if _, err := os.Stat(dir.dir); err != nil { + t.Fatal("should not delete dir") + } +} + +func TestStepOutputDir_existsForce(t *testing.T) { + state := testState(t) + step := new(StepOutputDir) + step.Force = true + + dir := testOutputDir(t) + state.Put("dir", dir) + + // Make sure the dir exists + if err := os.MkdirAll(dir.dir, 0755); err != nil { + t.Fatalf("err: %s", err) + } + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + if _, err := os.Stat(dir.dir); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestStepOutputDir_cancel(t *testing.T) { + state := testState(t) + step := new(StepOutputDir) + + dir := testOutputDir(t) + state.Put("dir", dir) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + if _, err := os.Stat(dir.dir); err != nil { + t.Fatalf("err: %s", err) + } + + // Test cancel/halt + state.Put(multistep.StateCancelled, true) + step.Cleanup(state) + if _, err := os.Stat(dir.dir); err == nil { + t.Fatal("directory should not exist") + } +} + +func TestStepOutputDir_halt(t *testing.T) { + state := testState(t) + step := new(StepOutputDir) + + dir := testOutputDir(t) + state.Put("dir", dir) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + if _, err := os.Stat(dir.dir); err != nil { + t.Fatalf("err: %s", err) + } + + // Test cancel/halt + state.Put(multistep.StateHalted, true) + step.Cleanup(state) + if _, err := os.Stat(dir.dir); err == nil { + t.Fatal("directory should not exist") + } +} diff --git a/builder/vmware/step_run.go b/builder/vmware/common/step_run.go similarity index 53% rename from builder/vmware/step_run.go rename to builder/vmware/common/step_run.go index d21f34ba1..4e996d833 100644 --- a/builder/vmware/step_run.go +++ b/builder/vmware/common/step_run.go @@ -1,4 +1,4 @@ -package vmware +package common import ( "fmt" @@ -10,52 +10,51 @@ import ( // This step runs the created virtual machine. // // Uses: -// config *config // driver Driver // ui packer.Ui // vmx_path string // // Produces: // -type stepRun struct { +type StepRun struct { + BootWait time.Duration + DurationBeforeStop time.Duration + Headless bool + bootTime time.Time vmxPath string - - registered bool } -func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction { - config := state.Get("config").(*config) +func (s *StepRun) Run(state multistep.StateBag) multistep.StepAction { driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) vmxPath := state.Get("vmx_path").(string) - vncIp := state.Get("vnc_ip").(string) - vncPort := state.Get("vnc_port").(uint) // Set the VMX path so that we know we started the machine s.bootTime = time.Now() s.vmxPath = vmxPath ui.Say("Starting virtual machine...") - if config.Headless { - 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)) - } + if s.Headless { + vncIpRaw, vncIpOk := state.GetOk("vnc_ip") + vncPortRaw, vncPortOk := state.GetOk("vnc_port") - if remoteDriver, ok := driver.(RemoteDriver); ok { - if err := remoteDriver.Register(vmxPath); err != nil { - err := fmt.Errorf("Error registering VM: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt + 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.") } - - s.registered = true } - if err := driver.Start(vmxPath, config.Headless); err != nil { + if err := driver.Start(vmxPath, s.Headless); err != nil { err := fmt.Errorf("Error starting VM: %s", err) state.Put("error", err) ui.Error(err.Error()) @@ -63,9 +62,9 @@ func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction { } // Wait the wait amount - if int64(config.bootWait) > 0 { - ui.Say(fmt.Sprintf("Waiting %s for boot...", config.bootWait.String())) - wait := time.After(config.bootWait) + if int64(s.BootWait) > 0 { + ui.Say(fmt.Sprintf("Waiting %s for boot...", s.BootWait.String())) + wait := time.After(s.BootWait) WAITLOOP: for { select { @@ -83,7 +82,7 @@ func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionContinue } -func (s *stepRun) Cleanup(state multistep.StateBag) { +func (s *StepRun) Cleanup(state multistep.StateBag) { driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) @@ -91,10 +90,11 @@ func (s *stepRun) Cleanup(state multistep.StateBag) { if s.vmxPath != "" { // If we started it less than 5 seconds ago... wait. sinceBootTime := time.Since(s.bootTime) - waitBootTime := 5 * time.Second + waitBootTime := s.DurationBeforeStop if sinceBootTime < waitBootTime { sleepTime := waitBootTime - sinceBootTime - ui.Say(fmt.Sprintf("Waiting %s to give VMware time to clean up...", sleepTime.String())) + ui.Say(fmt.Sprintf( + "Waiting %s to give VMware time to clean up...", sleepTime.String())) time.Sleep(sleepTime) } @@ -106,14 +106,5 @@ func (s *stepRun) Cleanup(state multistep.StateBag) { ui.Error(fmt.Sprintf("Error stopping VM: %s", err)) } } - - if remoteDriver, ok := driver.(RemoteDriver); ok && s.registered { - ui.Say("Unregistering virtual machine...") - if err := remoteDriver.Unregister(s.vmxPath); err != nil { - ui.Error(fmt.Sprintf("Error unregistering VM: %s", err)) - } - - s.registered = false - } } } diff --git a/builder/vmware/common/step_run_test.go b/builder/vmware/common/step_run_test.go new file mode 100644 index 000000000..9888cdf10 --- /dev/null +++ b/builder/vmware/common/step_run_test.go @@ -0,0 +1,82 @@ +package common + +import ( + "testing" + + "github.com/mitchellh/multistep" +) + +func TestStepRun_impl(t *testing.T) { + var _ multistep.Step = new(StepRun) +} + +func TestStepRun(t *testing.T) { + state := testState(t) + step := new(StepRun) + + state.Put("vmx_path", "foo") + + driver := state.Get("driver").(*DriverMock) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test the driver + if !driver.StartCalled { + t.Fatal("start should be called") + } + if driver.StartPath != "foo" { + t.Fatalf("bad: %#v", driver.StartPath) + } + if driver.StartHeadless { + t.Fatal("bad") + } + + // Test cleanup + step.Cleanup(state) + if driver.StopCalled { + t.Fatal("stop should not be called if not running") + } +} + +func TestStepRun_cleanupRunning(t *testing.T) { + state := testState(t) + step := new(StepRun) + + state.Put("vmx_path", "foo") + + driver := state.Get("driver").(*DriverMock) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test the driver + if !driver.StartCalled { + t.Fatal("start should be called") + } + if driver.StartPath != "foo" { + t.Fatalf("bad: %#v", driver.StartPath) + } + if driver.StartHeadless { + t.Fatal("bad") + } + + // Mark that it is running + driver.IsRunningResult = true + + // Test cleanup + step.Cleanup(state) + if !driver.StopCalled { + t.Fatal("stop should be called") + } +} diff --git a/builder/vmware/step_shutdown.go b/builder/vmware/common/step_shutdown.go similarity index 75% rename from builder/vmware/step_shutdown.go rename to builder/vmware/common/step_shutdown.go index dae402763..92516730e 100644 --- a/builder/vmware/step_shutdown.go +++ b/builder/vmware/common/step_shutdown.go @@ -1,4 +1,4 @@ -package vmware +package common import ( "bytes" @@ -7,7 +7,7 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "log" - "path/filepath" + "regexp" "runtime" "strings" "time" @@ -18,29 +18,32 @@ import ( // // Uses: // communicator packer.Communicator -// config *config +// dir OutputDir // driver Driver // ui packer.Ui // vmx_path string // // Produces: // -type stepShutdown struct{} +type StepShutdown struct { + Command string + Timeout time.Duration +} -func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { +func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction { comm := state.Get("communicator").(packer.Communicator) - config := state.Get("config").(*config) + dir := state.Get("dir").(OutputDir) driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) vmxPath := state.Get("vmx_path").(string) - if config.ShutdownCommand != "" { + if s.Command != "" { ui.Say("Gracefully halting virtual machine...") - log.Printf("Executing shutdown command: %s", config.ShutdownCommand) + log.Printf("Executing shutdown command: %s", s.Command) var stdout, stderr bytes.Buffer cmd := &packer.RemoteCmd{ - Command: config.ShutdownCommand, + Command: s.Command, Stdout: &stdout, Stderr: &stderr, } @@ -66,8 +69,8 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { log.Printf("Shutdown stderr: %s", stderr.String()) // Wait for the machine to actually shut down - log.Printf("Waiting max %s for shutdown to complete", config.shutdownTimeout) - shutdownTimer := time.After(config.shutdownTimeout) + log.Printf("Waiting max %s for shutdown to complete", s.Timeout) + shutdownTimer := time.After(s.Timeout) for { running, _ := driver.IsRunning(vmxPath) if !running { @@ -81,10 +84,11 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { ui.Error(err.Error()) return multistep.ActionHalt default: - time.Sleep(1 * time.Second) + time.Sleep(150 * time.Millisecond) } } } else { + ui.Say("Forcibly halting virtual machine...") if err := driver.Stop(vmxPath); err != nil { err := fmt.Errorf("Error stopping VM: %s", err) state.Put("error", err) @@ -94,12 +98,21 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { } ui.Message("Waiting for VMware to clean up after itself...") - lockPattern := filepath.Join(config.OutputDir, "*.lck") + lockRegex := regexp.MustCompile(`(?i)\.lck$`) timer := time.After(15 * time.Second) LockWaitLoop: for { - locks, err := filepath.Glob(lockPattern) - if err == nil { + files, err := dir.ListFiles() + if err != nil { + log.Printf("Error listing files in outputdir: %s", err) + } else { + var locks []string + for _, file := range files { + if lockRegex.MatchString(file) { + locks = append(locks, file) + } + } + if len(locks) == 0 { log.Println("No more lock files found. VMware is clean.") break @@ -117,7 +130,7 @@ LockWaitLoop: case <-timer: log.Println("Reached timeout on waiting for clean VMware. Assuming clean.") break LockWaitLoop - case <-time.After(1 * time.Second): + case <-time.After(150 * time.Millisecond): } } @@ -132,4 +145,4 @@ LockWaitLoop: return multistep.ActionContinue } -func (s *stepShutdown) Cleanup(state multistep.StateBag) {} +func (s *StepShutdown) Cleanup(state multistep.StateBag) {} diff --git a/builder/vmware/common/step_shutdown_test.go b/builder/vmware/common/step_shutdown_test.go new file mode 100644 index 000000000..531bfc34b --- /dev/null +++ b/builder/vmware/common/step_shutdown_test.go @@ -0,0 +1,174 @@ +package common + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + "time" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +func testStepShutdownState(t *testing.T) multistep.StateBag { + dir := testOutputDir(t) + if err := dir.MkdirAll(); err != nil { + t.Fatalf("err: %s", err) + } + + state := testState(t) + state.Put("communicator", new(packer.MockCommunicator)) + state.Put("dir", dir) + state.Put("vmx_path", "foo") + return state +} + +func TestStepShutdown_impl(t *testing.T) { + var _ multistep.Step = new(StepShutdown) +} + +func TestStepShutdown_command(t *testing.T) { + state := testStepShutdownState(t) + step := new(StepShutdown) + step.Command = "foo" + step.Timeout = 10 * time.Second + + comm := state.Get("communicator").(*packer.MockCommunicator) + driver := state.Get("driver").(*DriverMock) + driver.IsRunningResult = true + + // Set not running after some time + go func() { + time.Sleep(100 * time.Millisecond) + driver.Lock() + defer driver.Unlock() + driver.IsRunningResult = false + }() + + resultCh := make(chan multistep.StepAction, 1) + go func() { + resultCh <- step.Run(state) + }() + + select { + case <-resultCh: + t.Fatal("should not have returned so quickly") + case <-time.After(50 * time.Millisecond): + } + + var action multistep.StepAction + select { + case action = <-resultCh: + case <-time.After(300 * time.Millisecond): + t.Fatal("should've returned by now") + } + + // Test the run + if action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test the driver + if driver.StopCalled { + t.Fatal("stop should not be called") + } + + if !comm.StartCalled { + t.Fatal("start should be called") + } + if comm.StartCmd.Command != "foo" { + t.Fatalf("bad: %#v", comm.StartCmd.Command) + } +} + +func TestStepShutdown_noCommand(t *testing.T) { + state := testStepShutdownState(t) + step := new(StepShutdown) + + comm := state.Get("communicator").(*packer.MockCommunicator) + driver := state.Get("driver").(*DriverMock) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test the driver + if !driver.StopCalled { + t.Fatal("stop should be called") + } + if driver.StopPath != "foo" { + t.Fatal("should call with right path") + } + + if comm.StartCalled { + t.Fatal("start should not be called") + } +} + +func TestStepShutdown_locks(t *testing.T) { + state := testStepShutdownState(t) + step := new(StepShutdown) + + dir := state.Get("dir").(*LocalOutputDir) + comm := state.Get("communicator").(*packer.MockCommunicator) + driver := state.Get("driver").(*DriverMock) + + // Create some lock files + lockPath := filepath.Join(dir.dir, "nope.lck") + err := ioutil.WriteFile(lockPath, []byte("foo"), 0644) + if err != nil { + t.Fatalf("err: %s") + } + + // Remove the lock file after a certain time + go func() { + time.Sleep(100 * time.Millisecond) + os.Remove(lockPath) + }() + + resultCh := make(chan multistep.StepAction, 1) + go func() { + resultCh <- step.Run(state) + }() + + select { + case <-resultCh: + t.Fatal("should not have returned so quickly") + case <-time.After(50 * time.Millisecond): + } + + var action multistep.StepAction + select { + case action = <-resultCh: + case <-time.After(300 * time.Millisecond): + t.Fatal("should've returned by now") + } + + // Test the run + if action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test the driver + if !driver.StopCalled { + t.Fatal("stop should be called") + } + if driver.StopPath != "foo" { + t.Fatal("should call with right path") + } + + if comm.StartCalled { + t.Fatal("start should not be called") + } +} diff --git a/builder/vmware/step_suppress_messages.go b/builder/vmware/common/step_suppress_messages.go similarity index 76% rename from builder/vmware/step_suppress_messages.go rename to builder/vmware/common/step_suppress_messages.go index e4e101933..a02b69336 100644 --- a/builder/vmware/step_suppress_messages.go +++ b/builder/vmware/common/step_suppress_messages.go @@ -1,4 +1,4 @@ -package vmware +package common import ( "fmt" @@ -8,9 +8,9 @@ import ( ) // This step suppresses any messages that VMware product might show. -type stepSuppressMessages struct{} +type StepSuppressMessages struct{} -func (s *stepSuppressMessages) Run(state multistep.StateBag) multistep.StepAction { +func (s *StepSuppressMessages) Run(state multistep.StateBag) multistep.StepAction { driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) vmxPath := state.Get("vmx_path").(string) @@ -26,4 +26,4 @@ func (s *stepSuppressMessages) Run(state multistep.StateBag) multistep.StepActio return multistep.ActionContinue } -func (s *stepSuppressMessages) Cleanup(state multistep.StateBag) {} +func (s *StepSuppressMessages) Cleanup(state multistep.StateBag) {} diff --git a/builder/vmware/common/step_suppress_messages_test.go b/builder/vmware/common/step_suppress_messages_test.go new file mode 100644 index 000000000..998cfdb24 --- /dev/null +++ b/builder/vmware/common/step_suppress_messages_test.go @@ -0,0 +1,36 @@ +package common + +import ( + "testing" + + "github.com/mitchellh/multistep" +) + +func TestStepSuppressMessages_impl(t *testing.T) { + var _ multistep.Step = new(StepSuppressMessages) +} + +func TestStepSuppressMessages(t *testing.T) { + state := testState(t) + step := new(StepSuppressMessages) + + state.Put("vmx_path", "foo") + + driver := state.Get("driver").(*DriverMock) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test the driver + if !driver.SuppressMessagesCalled { + t.Fatal("should've called") + } + if driver.SuppressMessagesPath != "foo" { + t.Fatal("should call with right path") + } +} diff --git a/builder/vmware/common/step_test.go b/builder/vmware/common/step_test.go new file mode 100644 index 000000000..9bf6d5d67 --- /dev/null +++ b/builder/vmware/common/step_test.go @@ -0,0 +1,18 @@ +package common + +import ( + "bytes" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "testing" +) + +func testState(t *testing.T) multistep.StateBag { + state := new(multistep.BasicStateBag) + state.Put("driver", new(DriverMock)) + state.Put("ui", &packer.BasicUi{ + Reader: new(bytes.Buffer), + Writer: new(bytes.Buffer), + }) + return state +} diff --git a/builder/vmware/vmx.go b/builder/vmware/common/vmx.go similarity index 83% rename from builder/vmware/vmx.go rename to builder/vmware/common/vmx.go index ff60d5477..e7cdb662f 100644 --- a/builder/vmware/vmx.go +++ b/builder/vmware/common/vmx.go @@ -1,9 +1,10 @@ -package vmware +package common import ( "bytes" "fmt" "io" + "io/ioutil" "log" "os" "regexp" @@ -68,3 +69,13 @@ func WriteVMX(path string, data map[string]string) (err error) { return } + +// ReadVMX takes a path to a VMX file and reads it into a k/v mapping. +func ReadVMX(path string) (map[string]string, error) { + data, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + return ParseVMX(string(data)), nil +} diff --git a/builder/vmware/common/vmx_config.go b/builder/vmware/common/vmx_config.go new file mode 100644 index 000000000..f876d584b --- /dev/null +++ b/builder/vmware/common/vmx_config.go @@ -0,0 +1,37 @@ +package common + +import ( + "fmt" + + "github.com/mitchellh/packer/packer" +) + +type VMXConfig struct { + VMXData map[string]string `mapstructure:"vmx_data"` +} + +func (c *VMXConfig) Prepare(t *packer.ConfigTemplate) []error { + errs := make([]error, 0) + newVMXData := make(map[string]string) + for k, v := range c.VMXData { + var err error + k, err = t.Process(k, nil) + if err != nil { + errs = append(errs, + fmt.Errorf("Error processing VMX data key %s: %s", k, err)) + continue + } + + v, err = t.Process(v, nil) + if err != nil { + errs = append(errs, + fmt.Errorf("Error processing VMX data value '%s': %s", v, err)) + continue + } + + newVMXData[k] = v + } + c.VMXData = newVMXData + + return errs +} diff --git a/builder/vmware/common/vmx_config_test.go b/builder/vmware/common/vmx_config_test.go new file mode 100644 index 000000000..4b8172cb0 --- /dev/null +++ b/builder/vmware/common/vmx_config_test.go @@ -0,0 +1,22 @@ +package common + +import ( + "testing" +) + +func TestVMXConfigPrepare(t *testing.T) { + c := new(VMXConfig) + c.VMXData = map[string]string{ + "one": "foo", + "two": "bar", + } + + errs := c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("bad: %#v", errs) + } + + if len(c.VMXData) != 2 { + t.Fatal("should have two items in VMXData") + } +} diff --git a/builder/vmware/vmx_test.go b/builder/vmware/common/vmx_test.go similarity index 97% rename from builder/vmware/vmx_test.go rename to builder/vmware/common/vmx_test.go index d8d554150..da07b2a10 100644 --- a/builder/vmware/vmx_test.go +++ b/builder/vmware/common/vmx_test.go @@ -1,4 +1,4 @@ -package vmware +package common import "testing" diff --git a/builder/vmware/artifact.go b/builder/vmware/iso/artifact.go similarity index 88% rename from builder/vmware/artifact.go rename to builder/vmware/iso/artifact.go index 7e6fddb92..d6ddf8921 100644 --- a/builder/vmware/artifact.go +++ b/builder/vmware/iso/artifact.go @@ -1,15 +1,14 @@ -package vmware +package iso import ( "fmt" - "os" ) // Artifact is the result of running the VMware builder, namely a set // of files associated with the resulting machine. type Artifact struct { builderId string - dir string + dir OutputDir f []string } @@ -30,5 +29,5 @@ func (a *Artifact) String() string { } func (a *Artifact) Destroy() error { - return os.RemoveAll(a.dir) + return a.dir.RemoveAll() } diff --git a/builder/vmware/artifact_test.go b/builder/vmware/iso/artifact_test.go similarity index 94% rename from builder/vmware/artifact_test.go rename to builder/vmware/iso/artifact_test.go index ea96fe569..4394c2735 100644 --- a/builder/vmware/artifact_test.go +++ b/builder/vmware/iso/artifact_test.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "github.com/mitchellh/packer/packer" diff --git a/builder/vmware/builder.go b/builder/vmware/iso/builder.go similarity index 62% rename from builder/vmware/builder.go rename to builder/vmware/iso/builder.go index a9fe6c6b9..0a3f33b75 100644 --- a/builder/vmware/builder.go +++ b/builder/vmware/iso/builder.go @@ -1,9 +1,10 @@ -package vmware +package iso import ( "errors" "fmt" "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/packer" "io/ioutil" @@ -15,7 +16,6 @@ import ( "time" ) -const BuilderId = "mitchellh.vmware" const BuilderIdESX = "mitchellh.vmware-esx" type Builder struct { @@ -24,36 +24,32 @@ type Builder struct { } type config struct { - common.PackerConfig `mapstructure:",squash"` + common.PackerConfig `mapstructure:",squash"` + vmwcommon.OutputConfig `mapstructure:",squash"` + vmwcommon.RunConfig `mapstructure:",squash"` + vmwcommon.ShutdownConfig `mapstructure:",squash"` + vmwcommon.SSHConfig `mapstructure:",squash"` + vmwcommon.VMXConfig `mapstructure:",squash"` - DiskName string `mapstructure:"vmdk_name"` - DiskSize uint `mapstructure:"disk_size"` - DiskTypeId string `mapstructure:"disk_type_id"` - FloppyFiles []string `mapstructure:"floppy_files"` - GuestOSType string `mapstructure:"guest_os_type"` - ISOChecksum string `mapstructure:"iso_checksum"` - ISOChecksumType string `mapstructure:"iso_checksum_type"` - ISOUrls []string `mapstructure:"iso_urls"` - VMName string `mapstructure:"vm_name"` - OutputDir string `mapstructure:"output_directory"` - Headless bool `mapstructure:"headless"` - HTTPDir string `mapstructure:"http_directory"` - HTTPPortMin uint `mapstructure:"http_port_min"` - HTTPPortMax uint `mapstructure:"http_port_max"` - BootCommand []string `mapstructure:"boot_command"` - SkipCompaction bool `mapstructure:"skip_compaction"` - ShutdownCommand string `mapstructure:"shutdown_command"` - SSHUser string `mapstructure:"ssh_username"` - SSHKeyPath string `mapstructure:"ssh_key_path"` - SSHPassword string `mapstructure:"ssh_password"` - SSHPort uint `mapstructure:"ssh_port"` - SSHSkipRequestPty bool `mapstructure:"ssh_skip_request_pty"` - ToolsUploadFlavor string `mapstructure:"tools_upload_flavor"` - ToolsUploadPath string `mapstructure:"tools_upload_path"` - VMXData map[string]string `mapstructure:"vmx_data"` - VMXTemplatePath string `mapstructure:"vmx_template_path"` - VNCPortMin uint `mapstructure:"vnc_port_min"` - VNCPortMax uint `mapstructure:"vnc_port_max"` + DiskName string `mapstructure:"vmdk_name"` + DiskSize uint `mapstructure:"disk_size"` + DiskTypeId string `mapstructure:"disk_type_id"` + FloppyFiles []string `mapstructure:"floppy_files"` + GuestOSType string `mapstructure:"guest_os_type"` + ISOChecksum string `mapstructure:"iso_checksum"` + ISOChecksumType string `mapstructure:"iso_checksum_type"` + ISOUrls []string `mapstructure:"iso_urls"` + VMName string `mapstructure:"vm_name"` + HTTPDir string `mapstructure:"http_directory"` + HTTPPortMin uint `mapstructure:"http_port_min"` + HTTPPortMax uint `mapstructure:"http_port_max"` + BootCommand []string `mapstructure:"boot_command"` + SkipCompaction bool `mapstructure:"skip_compaction"` + ToolsUploadFlavor string `mapstructure:"tools_upload_flavor"` + ToolsUploadPath string `mapstructure:"tools_upload_path"` + VMXTemplatePath string `mapstructure:"vmx_template_path"` + VNCPortMin uint `mapstructure:"vnc_port_min"` + VNCPortMax uint `mapstructure:"vnc_port_max"` RemoteType string `mapstructure:"remote_type"` RemoteDatastore string `mapstructure:"remote_datastore"` @@ -62,15 +58,9 @@ type config struct { RemoteUser string `mapstructure:"remote_username"` RemotePassword string `mapstructure:"remote_password"` - RawBootWait string `mapstructure:"boot_wait"` - RawSingleISOUrl string `mapstructure:"iso_url"` - RawShutdownTimeout string `mapstructure:"shutdown_timeout"` - RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"` + RawSingleISOUrl string `mapstructure:"iso_url"` - bootWait time.Duration `` - shutdownTimeout time.Duration `` - sshWaitTimeout time.Duration `` - tpl *packer.ConfigTemplate + tpl *packer.ConfigTemplate } func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { @@ -87,6 +77,12 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { // Accumulate any errors errs := common.CheckUnusedConfig(md) + errs = packer.MultiErrorAppend(errs, + b.config.OutputConfig.Prepare(b.config.tpl, &b.config.PackerConfig)...) + errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.tpl)...) + errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(b.config.tpl)...) + errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(b.config.tpl)...) + errs = packer.MultiErrorAppend(errs, b.config.VMXConfig.Prepare(b.config.tpl)...) warnings := make([]string, 0) if b.config.DiskName == "" { @@ -126,10 +122,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.config.HTTPPortMax = 9000 } - if b.config.RawBootWait == "" { - b.config.RawBootWait = "10s" - } - if b.config.VNCPortMin == 0 { b.config.VNCPortMin = 5900 } @@ -138,10 +130,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.config.VNCPortMax = 6000 } - if b.config.OutputDir == "" { - b.config.OutputDir = fmt.Sprintf("output-%s", b.config.PackerBuildName) - } - if b.config.RemoteUser == "" { b.config.RemoteUser = "root" } @@ -154,10 +142,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.config.RemotePort = 22 } - if b.config.SSHPort == 0 { - b.config.SSHPort = 22 - } - if b.config.ToolsUploadPath == "" { b.config.ToolsUploadPath = "{{ .Flavor }}.iso" } @@ -170,16 +154,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { "iso_checksum": &b.config.ISOChecksum, "iso_checksum_type": &b.config.ISOChecksumType, "iso_url": &b.config.RawSingleISOUrl, - "output_directory": &b.config.OutputDir, - "shutdown_command": &b.config.ShutdownCommand, - "ssh_key_path": &b.config.SSHKeyPath, - "ssh_password": &b.config.SSHPassword, - "ssh_username": &b.config.SSHUser, "tools_upload_flavor": &b.config.ToolsUploadFlavor, "vm_name": &b.config.VMName, - "boot_wait": &b.config.RawBootWait, - "shutdown_timeout": &b.config.RawShutdownTimeout, - "ssh_wait_timeout": &b.config.RawSSHWaitTimeout, "vmx_template_path": &b.config.VMXTemplatePath, "remote_type": &b.config.RemoteType, "remote_host": &b.config.RemoteHost, @@ -223,27 +199,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } - newVMXData := make(map[string]string) - for k, v := range b.config.VMXData { - k, err = b.config.tpl.Process(k, nil) - if err != nil { - errs = packer.MultiErrorAppend(errs, - fmt.Errorf("Error processing VMX data key %s: %s", k, err)) - continue - } - - v, err = b.config.tpl.Process(v, nil) - if err != nil { - errs = packer.MultiErrorAppend(errs, - fmt.Errorf("Error processing VMX data value '%s': %s", v, err)) - continue - } - - newVMXData[k] = v - } - - b.config.VMXData = newVMXData - if b.config.HTTPPortMin > b.config.HTTPPortMax { errs = packer.MultiErrorAppend( errs, errors.New("http_port_min must be less than http_port_max")) @@ -286,57 +241,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } - if !b.config.PackerForce { - if _, err := os.Stat(b.config.OutputDir); err == nil { - errs = packer.MultiErrorAppend( - errs, - fmt.Errorf("Output directory '%s' already exists. It must not exist.", b.config.OutputDir)) - } - } - - if b.config.SSHKeyPath != "" { - if _, err := os.Stat(b.config.SSHKeyPath); err != nil { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("ssh_key_path is invalid: %s", err)) - } else if _, err := sshKeyToKeyring(b.config.SSHKeyPath); err != nil { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("ssh_key_path is invalid: %s", err)) - } - } - - if b.config.SSHUser == "" { - errs = packer.MultiErrorAppend( - errs, errors.New("An ssh_username must be specified.")) - } - - if b.config.RawBootWait != "" { - b.config.bootWait, err = time.ParseDuration(b.config.RawBootWait) - if err != nil { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("Failed parsing boot_wait: %s", err)) - } - } - - if b.config.RawShutdownTimeout == "" { - b.config.RawShutdownTimeout = "5m" - } - - b.config.shutdownTimeout, err = time.ParseDuration(b.config.RawShutdownTimeout) - if err != nil { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err)) - } - - if b.config.RawSSHWaitTimeout == "" { - b.config.RawSSHWaitTimeout = "20m" - } - - b.config.sshWaitTimeout, err = time.ParseDuration(b.config.RawSSHWaitTimeout) - if err != nil { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %s", err)) - } - if _, err := template.New("path").Parse(b.config.ToolsUploadPath); err != nil { errs = packer.MultiErrorAppend( errs, fmt.Errorf("tools_upload_path invalid: %s", err)) @@ -383,6 +287,25 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe return nil, fmt.Errorf("Failed creating VMware driver: %s", err) } + // Determine the output dir implementation + var dir OutputDir + switch d := driver.(type) { + case OutputDir: + dir = d + default: + dir = new(vmwcommon.LocalOutputDir) + } + dir.SetOutputDir(b.config.OutputDir) + + // Setup the state bag + state := new(multistep.BasicStateBag) + state.Put("cache", cache) + state.Put("config", &b.config) + state.Put("dir", dir) + state.Put("driver", driver) + state.Put("hook", hook) + state.Put("ui", ui) + // Seed the random number generator rand.Seed(time.Now().UTC().UnixNano()) @@ -395,7 +318,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ResultKey: "iso_path", Url: b.config.ISOUrls, }, - &stepPrepareOutputDir{}, + &vmwcommon.StepOutputDir{ + Force: b.config.PackerForce, + }, &common.StepCreateFloppy{ Files: b.config.FloppyFiles, }, @@ -405,33 +330,38 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe }, &stepCreateDisk{}, &stepCreateVMX{}, - &stepSuppressMessages{}, + &vmwcommon.StepConfigureVMX{ + CustomData: b.config.VMXData, + }, + &vmwcommon.StepSuppressMessages{}, &stepHTTPServer{}, &stepConfigureVNC{}, - &stepRun{}, + &StepRegister{}, + &vmwcommon.StepRun{ + BootWait: b.config.BootWait, + DurationBeforeStop: 5 * time.Second, + Headless: b.config.Headless, + }, &stepTypeBootCommand{}, &common.StepConnectSSH{ SSHAddress: driver.SSHAddress, - SSHConfig: sshConfig, - SSHWaitTimeout: b.config.sshWaitTimeout, + SSHConfig: vmwcommon.SSHConfigFunc(&b.config.SSHConfig), + SSHWaitTimeout: b.config.SSHWaitTimeout, NoPty: b.config.SSHSkipRequestPty, }, &stepUploadTools{}, &common.StepProvision{}, - &stepShutdown{}, - &stepCleanFiles{}, - &stepCleanVMX{}, - &stepCompactDisk{}, + &vmwcommon.StepShutdown{ + Command: b.config.ShutdownCommand, + Timeout: b.config.ShutdownTimeout, + }, + &vmwcommon.StepCleanFiles{}, + &vmwcommon.StepCleanVMX{}, + &vmwcommon.StepCompactDisk{ + Skip: b.config.SkipCompaction, + }, } - // Setup the state bag - state := new(multistep.BasicStateBag) - state.Put("cache", cache) - state.Put("config", &b.config) - state.Put("driver", driver) - state.Put("hook", hook) - state.Put("ui", ui) - // Run! if b.config.PackerDebug { b.runner = &multistep.DebugRunner{ @@ -465,14 +395,14 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe } // Set the proper builder ID - builderId := BuilderId + builderId := vmwcommon.BuilderId if b.config.RemoteType != "" { builderId = BuilderIdESX } return &Artifact{ builderId: builderId, - dir: b.config.OutputDir, + dir: dir, f: files, }, nil } diff --git a/builder/vmware/builder_test.go b/builder/vmware/iso/builder_test.go similarity index 59% rename from builder/vmware/builder_test.go rename to builder/vmware/iso/builder_test.go index 893970768..f05f4a470 100644 --- a/builder/vmware/builder_test.go +++ b/builder/vmware/iso/builder_test.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "github.com/mitchellh/packer/packer" @@ -9,36 +9,6 @@ import ( "time" ) -var testPem = ` ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEAxd4iamvrwRJvtNDGQSIbNvvIQN8imXTRWlRY62EvKov60vqu -hh+rDzFYAIIzlmrJopvOe0clqmi3mIP9dtkjPFrYflq52a2CF5q+BdwsJXuRHbJW -LmStZUwW1khSz93DhvhmK50nIaczW63u4EO/jJb3xj+wxR1Nkk9bxi3DDsYFt8SN -AzYx9kjlEYQ/+sI4/ATfmdV9h78SVotjScupd9KFzzi76gWq9gwyCBLRynTUWlyD -2UOfJRkOvhN6/jKzvYfVVwjPSfA9IMuooHdScmC4F6KBKJl/zf/zETM0XyzIDNmH -uOPbCiljq2WoRM+rY6ET84EO0kVXbfx8uxUsqQIDAQABAoIBAQCkPj9TF0IagbM3 -5BSs/CKbAWS4dH/D4bPlxx4IRCNirc8GUg+MRb04Xz0tLuajdQDqeWpr6iLZ0RKV -BvreLF+TOdV7DNQ4XE4gSdJyCtCaTHeort/aordL3l0WgfI7mVk0L/yfN1PEG4YG -E9q1TYcyrB3/8d5JwIkjabxERLglCcP+geOEJp+QijbvFIaZR/n2irlKW4gSy6ko -9B0fgUnhkHysSg49ChHQBPQ+o5BbpuLrPDFMiTPTPhdfsvGGcyCGeqfBA56oHcSF -K02Fg8OM+Bd1lb48LAN9nWWY4WbwV+9bkN3Ym8hO4c3a/Dxf2N7LtAQqWZzFjvM3 -/AaDvAgBAoGBAPLD+Xn1IYQPMB2XXCXfOuJewRY7RzoVWvMffJPDfm16O7wOiW5+ -2FmvxUDayk4PZy6wQMzGeGKnhcMMZTyaq2g/QtGfrvy7q1Lw2fB1VFlVblvqhoJa -nMJojjC4zgjBkXMHsRLeTmgUKyGs+fdFbfI6uejBnnf+eMVUMIdJ+6I9AoGBANCn -kWO9640dttyXURxNJ3lBr2H3dJOkmD6XS+u+LWqCSKQe691Y/fZ/ZL0Oc4Mhy7I6 -hsy3kDQ5k2V0fkaNODQIFJvUqXw2pMewUk8hHc9403f4fe9cPrL12rQ8WlQw4yoC -v2B61vNczCCUDtGxlAaw8jzSRaSI5s6ax3K7enbdAoGBAJB1WYDfA2CoAQO6y9Sl -b07A/7kQ8SN5DbPaqrDrBdJziBQxukoMJQXJeGFNUFD/DXFU5Fp2R7C86vXT7HIR -v6m66zH+CYzOx/YE6EsUJms6UP9VIVF0Rg/RU7teXQwM01ZV32LQ8mswhTH20o/3 -uqMHmxUMEhZpUMhrfq0isyApAoGAe1UxGTXfj9AqkIVYylPIq2HqGww7+jFmVEj1 -9Wi6S6Sq72ffnzzFEPkIQL/UA4TsdHMnzsYKFPSbbXLIWUeMGyVTmTDA5c0e5XIR -lPhMOKCAzv8w4VUzMnEkTzkFY5JqFCD/ojW57KvDdNZPVB+VEcdxyAW6aKELXMAc -eHLc1nkCgYEApm/motCTPN32nINZ+Vvywbv64ZD+gtpeMNP3CLrbe1X9O+H52AXa -1jCoOldWR8i2bs2NVPcKZgdo6fFULqE4dBX7Te/uYEIuuZhYLNzRO1IKU/YaqsXG -3bfQ8hKYcSnTfE0gPtLDnqCIxTocaGLSHeG3TH9fTw+dA8FvWpUztI4= ------END RSA PRIVATE KEY----- -` - func testConfig() map[string]interface{} { return map[string]interface{}{ "iso_checksum": "foo", @@ -59,46 +29,6 @@ func TestBuilder_ImplementsBuilder(t *testing.T) { } } -func TestBuilderPrepare_BootWait(t *testing.T) { - var b Builder - config := testConfig() - - // Test a default boot_wait - delete(config, "boot_wait") - 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.RawBootWait != "10s" { - t.Fatalf("bad value: %s", b.config.RawBootWait) - } - - // Test with a bad boot_wait - config["boot_wait"] = "this is not good" - warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err == nil { - t.Fatal("should have error") - } - - // Test with a good one - config["boot_wait"] = "5s" - 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) - } -} - func TestBuilderPrepare_ISOChecksum(t *testing.T) { var b Builder config := testConfig() @@ -188,8 +118,8 @@ func TestBuilderPrepare_Defaults(t *testing.T) { t.Errorf("bad output dir: %s", b.config.OutputDir) } - if b.config.sshWaitTimeout != (20 * time.Minute) { - t.Errorf("bad wait timeout: %s", b.config.sshWaitTimeout) + if b.config.SSHWaitTimeout != (20 * time.Minute) { + t.Errorf("bad wait timeout: %s", b.config.SSHWaitTimeout) } if b.config.VMName != "packer-foo" { @@ -419,192 +349,6 @@ func TestBuilderPrepare_OutputDir(t *testing.T) { } } -func TestBuilderPrepare_ShutdownCommand(t *testing.T) { - var b Builder - config := testConfig() - delete(config, "shutdown_command") - - warns, err := b.Prepare(config) - if err != nil { - t.Fatalf("bad: %s", err) - } - - if len(warns) != 1 { - t.Fatalf("bad: %#v", warns) - } -} - -func TestBuilderPrepare_ShutdownTimeout(t *testing.T) { - var b Builder - config := testConfig() - - // Test with a bad value - config["shutdown_timeout"] = "this is not good" - warns, err := b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err == nil { - t.Fatal("should have error") - } - - // Test with a good one - config["shutdown_timeout"] = "5s" - 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) - } -} - -func TestBuilderPrepare_sshKeyPath(t *testing.T) { - var b Builder - config := testConfig() - - config["ssh_key_path"] = "" - 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) - } - - config["ssh_key_path"] = "/i/dont/exist" - b = Builder{} - warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err == nil { - t.Fatal("should have error") - } - - // Test bad contents - tf, err := ioutil.TempFile("", "packer") - if err != nil { - t.Fatalf("err: %s", err) - } - defer os.Remove(tf.Name()) - defer tf.Close() - - if _, err := tf.Write([]byte("HELLO!")); err != nil { - t.Fatalf("err: %s", err) - } - - config["ssh_key_path"] = tf.Name() - b = Builder{} - warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err == nil { - t.Fatal("should have error") - } - - // Test good contents - tf.Seek(0, 0) - tf.Truncate(0) - tf.Write([]byte(testPem)) - config["ssh_key_path"] = tf.Name() - b = Builder{} - warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestBuilderPrepare_SSHUser(t *testing.T) { - var b Builder - config := testConfig() - - config["ssh_username"] = "" - warns, err := b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err == nil { - t.Fatal("should have error") - } - - config["ssh_username"] = "exists" - 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) - } -} - -func TestBuilderPrepare_SSHPort(t *testing.T) { - var b Builder - config := testConfig() - - // Test with a bad value - delete(config, "ssh_port") - warns, err := b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("bad err: %s", err) - } - - if b.config.SSHPort != 22 { - t.Fatalf("bad ssh port: %d", b.config.SSHPort) - } - - // Test with a good one - config["ssh_port"] = 44 - 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 b.config.SSHPort != 44 { - t.Fatalf("bad ssh port: %d", b.config.SSHPort) - } -} - -func TestBuilderPrepare_SSHWaitTimeout(t *testing.T) { - var b Builder - config := testConfig() - - // Test with a bad value - config["ssh_wait_timeout"] = "this is not good" - warns, err := b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err == nil { - t.Fatal("should have error") - } - - // Test with a good one - config["ssh_wait_timeout"] = "5s" - 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) - } -} - func TestBuilderPrepare_ToolsUploadPath(t *testing.T) { var b Builder config := testConfig() @@ -743,25 +487,3 @@ func TestBuilderPrepare_VNCPort(t *testing.T) { t.Fatalf("should not have error: %s", err) } } - -func TestBuilderPrepare_VMXData(t *testing.T) { - var b Builder - config := testConfig() - - config["vmx_data"] = map[interface{}]interface{}{ - "one": "foo", - "two": "bar", - } - - 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 len(b.config.VMXData) != 2 { - t.Fatal("should have two items in VMXData") - } -} diff --git a/builder/vmware/iso/driver.go b/builder/vmware/iso/driver.go new file mode 100644 index 000000000..22c6b6902 --- /dev/null +++ b/builder/vmware/iso/driver.go @@ -0,0 +1,41 @@ +package iso + +import ( + "fmt" + + vmwcommon "github.com/mitchellh/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.SSHConfig) + } + + drivers = []vmwcommon.Driver{ + &ESX5Driver{ + Host: config.RemoteHost, + Port: config.RemotePort, + Username: config.RemoteUser, + Password: config.RemotePassword, + Datastore: config.RemoteDatastore, + }, + } + + 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/driver_esx5.go b/builder/vmware/iso/driver_esx5.go similarity index 98% rename from builder/vmware/driver_esx5.go rename to builder/vmware/iso/driver_esx5.go index 83d326333..09083d618 100644 --- a/builder/vmware/driver_esx5.go +++ b/builder/vmware/iso/driver_esx5.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "bufio" @@ -33,6 +33,10 @@ type ESX5Driver struct { outputDir string } +func (d *ESX5Driver) Clone(dst, src string) error { + return errors.New("Cloning is not supported with the ESX driver.") +} + func (d *ESX5Driver) CompactDisk(diskPathLocal string) error { return nil } diff --git a/builder/vmware/driver_esx5_test.go b/builder/vmware/iso/driver_esx5_test.go similarity index 58% rename from builder/vmware/driver_esx5_test.go rename to builder/vmware/iso/driver_esx5_test.go index 2c3184199..c7e4f776d 100644 --- a/builder/vmware/driver_esx5_test.go +++ b/builder/vmware/iso/driver_esx5_test.go @@ -1,11 +1,12 @@ -package vmware +package iso import ( + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "testing" ) func TestESX5Driver_implDriver(t *testing.T) { - var _ Driver = new(ESX5Driver) + var _ vmwcommon.Driver = new(ESX5Driver) } func TestESX5Driver_implRemoteDriver(t *testing.T) { diff --git a/builder/vmware/host_ip.go b/builder/vmware/iso/host_ip.go similarity index 91% rename from builder/vmware/host_ip.go rename to builder/vmware/iso/host_ip.go index 452cca7dc..98a6d26df 100644 --- a/builder/vmware/host_ip.go +++ b/builder/vmware/iso/host_ip.go @@ -1,4 +1,4 @@ -package vmware +package iso // Interface to help find the host IP that is available from within // the VMware virtual machines. diff --git a/builder/vmware/host_ip_ifconfig.go b/builder/vmware/iso/host_ip_ifconfig.go similarity index 98% rename from builder/vmware/host_ip_ifconfig.go rename to builder/vmware/iso/host_ip_ifconfig.go index 28e434b33..31243fafa 100644 --- a/builder/vmware/host_ip_ifconfig.go +++ b/builder/vmware/iso/host_ip_ifconfig.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "bytes" diff --git a/builder/vmware/host_ip_ifconfig_test.go b/builder/vmware/iso/host_ip_ifconfig_test.go similarity index 93% rename from builder/vmware/host_ip_ifconfig_test.go rename to builder/vmware/iso/host_ip_ifconfig_test.go index 0f3d9266e..51ccc272c 100644 --- a/builder/vmware/host_ip_ifconfig_test.go +++ b/builder/vmware/iso/host_ip_ifconfig_test.go @@ -1,4 +1,4 @@ -package vmware +package iso import "testing" diff --git a/builder/vmware/host_ip_vmnetnatconf.go b/builder/vmware/iso/host_ip_vmnetnatconf.go similarity index 90% rename from builder/vmware/host_ip_vmnetnatconf.go rename to builder/vmware/iso/host_ip_vmnetnatconf.go index 888dfa81d..0ba0a3727 100644 --- a/builder/vmware/host_ip_vmnetnatconf.go +++ b/builder/vmware/iso/host_ip_vmnetnatconf.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "bufio" @@ -8,6 +8,8 @@ import ( "os" "regexp" "strings" + + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" ) // VMnetNatConfIPFinder finds the IP address of the host machine by @@ -16,7 +18,7 @@ import ( type VMnetNatConfIPFinder struct{} func (*VMnetNatConfIPFinder) HostIP() (string, error) { - driver := &Workstation9Driver{} + driver := &vmwcommon.Workstation9Driver{} vmnetnat := driver.VmnetnatConfPath() if vmnetnat == "" { diff --git a/builder/vmware/host_ip_vmnetnatconf_test.go b/builder/vmware/iso/host_ip_vmnetnatconf_test.go similarity index 93% rename from builder/vmware/host_ip_vmnetnatconf_test.go rename to builder/vmware/iso/host_ip_vmnetnatconf_test.go index a5fc26f27..873a7fa2b 100644 --- a/builder/vmware/host_ip_vmnetnatconf_test.go +++ b/builder/vmware/iso/host_ip_vmnetnatconf_test.go @@ -1,4 +1,4 @@ -package vmware +package iso import "testing" diff --git a/builder/vmware/output_dir.go b/builder/vmware/iso/output_dir.go similarity index 94% rename from builder/vmware/output_dir.go rename to builder/vmware/iso/output_dir.go index c88e3f41b..0c08a225f 100644 --- a/builder/vmware/output_dir.go +++ b/builder/vmware/iso/output_dir.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "os" @@ -60,3 +60,7 @@ func (d *localOutputDir) RemoveAll() error { func (d *localOutputDir) SetOutputDir(path string) { d.dir = path } + +func (d *localOutputDir) String() string { + return d.dir +} diff --git a/builder/vmware/remote_driver.go b/builder/vmware/iso/remote_driver.go similarity index 79% rename from builder/vmware/remote_driver.go rename to builder/vmware/iso/remote_driver.go index 07fb2f8e7..d020cb800 100644 --- a/builder/vmware/remote_driver.go +++ b/builder/vmware/iso/remote_driver.go @@ -1,7 +1,11 @@ -package vmware +package iso + +import ( + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" +) type RemoteDriver interface { - Driver + vmwcommon.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 diff --git a/builder/vmware/iso/remote_driver_mock.go b/builder/vmware/iso/remote_driver_mock.go new file mode 100644 index 000000000..fe494fd86 --- /dev/null +++ b/builder/vmware/iso/remote_driver_mock.go @@ -0,0 +1,40 @@ +package iso + +import ( + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" +) + +type RemoteDriverMock struct { + vmwcommon.DriverMock + + UploadISOCalled bool + UploadISOPath string + UploadISOResult string + UploadISOErr error + + RegisterCalled bool + RegisterPath string + RegisterErr error + + UnregisterCalled bool + UnregisterPath string + UnregisterErr error +} + +func (d *RemoteDriverMock) UploadISO(path string) (string, error) { + d.UploadISOCalled = true + d.UploadISOPath = path + return d.UploadISOResult, d.UploadISOErr +} + +func (d *RemoteDriverMock) Register(path string) error { + d.RegisterCalled = true + d.RegisterPath = path + return d.RegisterErr +} + +func (d *RemoteDriverMock) Unregister(path string) error { + d.UnregisterCalled = true + d.UnregisterPath = path + return d.UnregisterErr +} diff --git a/builder/vmware/iso/remote_driver_mock_test.go b/builder/vmware/iso/remote_driver_mock_test.go new file mode 100644 index 000000000..86779a847 --- /dev/null +++ b/builder/vmware/iso/remote_driver_mock_test.go @@ -0,0 +1,12 @@ +package iso + +import ( + "testing" + + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" +) + +func TestRemoteDriverMock_impl(t *testing.T) { + var _ vmwcommon.Driver = new(RemoteDriverMock) + var _ RemoteDriver = new(RemoteDriverMock) +} diff --git a/builder/vmware/step_configure_vnc.go b/builder/vmware/iso/step_configure_vnc.go similarity index 91% rename from builder/vmware/step_configure_vnc.go rename to builder/vmware/iso/step_configure_vnc.go index f6a41b48e..1f194be76 100644 --- a/builder/vmware/step_configure_vnc.go +++ b/builder/vmware/iso/step_configure_vnc.go @@ -1,8 +1,9 @@ -package vmware +package iso import ( "fmt" "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/packer" "io/ioutil" "log" @@ -46,7 +47,7 @@ func (stepConfigureVNC) VNCAddress(portMin, portMax uint) (string, uint) { func (s *stepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*config) - driver := state.Get("driver").(Driver) + driver := state.Get("driver").(vmwcommon.Driver) ui := state.Get("ui").(packer.Ui) vmxPath := state.Get("vmx_path").(string) @@ -84,11 +85,11 @@ func (s *stepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction { log.Printf("Found available VNC port: %d", vncPort) - vmxData := ParseVMX(string(vmxBytes)) + vmxData := vmwcommon.ParseVMX(string(vmxBytes)) vmxData["remotedisplay.vnc.enabled"] = "TRUE" vmxData["remotedisplay.vnc.port"] = fmt.Sprintf("%d", vncPort) - if err := WriteVMX(vmxPath, vmxData); err != nil { + if err := vmwcommon.WriteVMX(vmxPath, vmxData); err != nil { err := fmt.Errorf("Error writing VMX data: %s", err) state.Put("error", err) ui.Error(err.Error()) diff --git a/builder/vmware/step_create_disk.go b/builder/vmware/iso/step_create_disk.go similarity index 88% rename from builder/vmware/step_create_disk.go rename to builder/vmware/iso/step_create_disk.go index 3b526812e..cade63f75 100644 --- a/builder/vmware/step_create_disk.go +++ b/builder/vmware/iso/step_create_disk.go @@ -1,8 +1,9 @@ -package vmware +package iso import ( "fmt" "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/packer" "path/filepath" ) @@ -20,7 +21,7 @@ type stepCreateDisk struct{} func (stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*config) - driver := state.Get("driver").(Driver) + driver := state.Get("driver").(vmwcommon.Driver) ui := state.Get("ui").(packer.Ui) ui.Say("Creating virtual machine disk") diff --git a/builder/vmware/step_create_vmx.go b/builder/vmware/iso/step_create_vmx.go similarity index 87% rename from builder/vmware/step_create_vmx.go rename to builder/vmware/iso/step_create_vmx.go index 6039b3dfc..8544a1405 100644 --- a/builder/vmware/step_create_vmx.go +++ b/builder/vmware/iso/step_create_vmx.go @@ -1,14 +1,13 @@ -package vmware +package iso import ( "fmt" "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/packer" "io/ioutil" - "log" "os" "path/filepath" - "strings" ) type vmxTemplateData struct { @@ -75,26 +74,6 @@ func (s *stepCreateVMX) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - vmxData := ParseVMX(vmxContents) - if config.VMXData != nil { - log.Println("Setting custom VMX data...") - for k, v := range config.VMXData { - log.Printf("Setting VMX: '%s' = '%s'", k, v) - k = strings.ToLower(k) - vmxData[k] = v - } - } - - if floppyPathRaw, ok := state.GetOk("floppy_path"); ok { - log.Println("Floppy path present, setting in VMX") - vmxData["floppy0.present"] = "TRUE" - vmxData["floppy0.filetype"] = "file" - vmxData["floppy0.filename"] = floppyPathRaw.(string) - } - - // Set this so that no dialogs ever appear from Packer. - vmxData["msg.autoanswer"] = "true" - vmxDir := config.OutputDir if config.RemoteType != "" { // For remote builds, we just put the VMX in a temporary @@ -112,7 +91,7 @@ func (s *stepCreateVMX) Run(state multistep.StateBag) multistep.StepAction { } vmxPath := filepath.Join(vmxDir, config.VMName+".vmx") - if err := WriteVMX(vmxPath, vmxData); err != nil { + if err := vmwcommon.WriteVMX(vmxPath, vmwcommon.ParseVMX(vmxContents)); err != nil { err := fmt.Errorf("Error creating VMX file: %s", err) state.Put("error", err) ui.Error(err.Error()) diff --git a/builder/vmware/step_http_server.go b/builder/vmware/iso/step_http_server.go similarity index 99% rename from builder/vmware/step_http_server.go rename to builder/vmware/iso/step_http_server.go index 084816f63..24da8dd9d 100644 --- a/builder/vmware/step_http_server.go +++ b/builder/vmware/iso/step_http_server.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "fmt" diff --git a/builder/vmware/step_prepare_tools.go b/builder/vmware/iso/step_prepare_tools.go similarity index 87% rename from builder/vmware/step_prepare_tools.go rename to builder/vmware/iso/step_prepare_tools.go index e26bcf8ed..69ff81690 100644 --- a/builder/vmware/step_prepare_tools.go +++ b/builder/vmware/iso/step_prepare_tools.go @@ -1,8 +1,9 @@ -package vmware +package iso import ( "fmt" "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "os" ) @@ -10,7 +11,7 @@ type stepPrepareTools struct{} func (*stepPrepareTools) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*config) - driver := state.Get("driver").(Driver) + driver := state.Get("driver").(vmwcommon.Driver) if config.ToolsUploadFlavor == "" { return multistep.ActionContinue diff --git a/builder/vmware/iso/step_register.go b/builder/vmware/iso/step_register.go new file mode 100644 index 000000000..6710cbd74 --- /dev/null +++ b/builder/vmware/iso/step_register.go @@ -0,0 +1,52 @@ +package iso + +import ( + "fmt" + + "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" + "github.com/mitchellh/packer/packer" +) + +type StepRegister struct { + registeredPath string +} + +func (s *StepRegister) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(vmwcommon.Driver) + ui := state.Get("ui").(packer.Ui) + vmxPath := state.Get("vmx_path").(string) + + if remoteDriver, ok := driver.(RemoteDriver); ok { + ui.Say("Registering remote VM...") + if err := remoteDriver.Register(vmxPath); err != nil { + err := fmt.Errorf("Error registering VM: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + s.registeredPath = vmxPath + } + + return multistep.ActionContinue +} + +func (s *StepRegister) Cleanup(state multistep.StateBag) { + if s.registeredPath == "" { + return + } + + driver := state.Get("driver").(vmwcommon.Driver) + 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)) + } + + s.registeredPath = "" + } + +} diff --git a/builder/vmware/iso/step_register_test.go b/builder/vmware/iso/step_register_test.go new file mode 100644 index 000000000..c654f836d --- /dev/null +++ b/builder/vmware/iso/step_register_test.go @@ -0,0 +1,65 @@ +package iso + +import ( + "github.com/mitchellh/multistep" + "testing" +) + +func TestStepRegister_impl(t *testing.T) { + var _ multistep.Step = new(StepRegister) +} + +func TestStepRegister_regularDriver(t *testing.T) { + state := testState(t) + step := new(StepRegister) + + state.Put("vmx_path", "foo") + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Cleanup + step.Cleanup(state) +} + +func TestStepRegister_remoteDriver(t *testing.T) { + state := testState(t) + step := new(StepRegister) + + driver := new(RemoteDriverMock) + state.Put("driver", driver) + state.Put("vmx_path", "foo") + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // verify + if !driver.RegisterCalled { + t.Fatal("register should be called") + } + if driver.RegisterPath != "foo" { + t.Fatal("should call with correct path") + } + if driver.UnregisterCalled { + t.Fatal("unregister should not be called") + } + + // cleanup + step.Cleanup(state) + if !driver.UnregisterCalled { + t.Fatal("unregister should be called") + } + if driver.UnregisterPath != "foo" { + t.Fatal("should unregister proper path") + } +} diff --git a/builder/vmware/step_remote_upload.go b/builder/vmware/iso/step_remote_upload.go similarity index 88% rename from builder/vmware/step_remote_upload.go rename to builder/vmware/iso/step_remote_upload.go index 47c9d04a7..717b3dc13 100644 --- a/builder/vmware/step_remote_upload.go +++ b/builder/vmware/iso/step_remote_upload.go @@ -1,8 +1,9 @@ -package vmware +package iso import ( "fmt" "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/packer" "log" ) @@ -15,7 +16,7 @@ type stepRemoteUpload struct { } func (s *stepRemoteUpload) Run(state multistep.StateBag) multistep.StepAction { - driver := state.Get("driver").(Driver) + driver := state.Get("driver").(vmwcommon.Driver) ui := state.Get("ui").(packer.Ui) remote, ok := driver.(RemoteDriver) diff --git a/builder/vmware/iso/step_test.go b/builder/vmware/iso/step_test.go new file mode 100644 index 000000000..aad099af5 --- /dev/null +++ b/builder/vmware/iso/step_test.go @@ -0,0 +1,19 @@ +package iso + +import ( + "bytes" + "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" + "github.com/mitchellh/packer/packer" + "testing" +) + +func testState(t *testing.T) multistep.StateBag { + state := new(multistep.BasicStateBag) + state.Put("driver", new(vmwcommon.DriverMock)) + state.Put("ui", &packer.BasicUi{ + Reader: new(bytes.Buffer), + Writer: new(bytes.Buffer), + }) + return state +} diff --git a/builder/vmware/step_type_boot_command.go b/builder/vmware/iso/step_type_boot_command.go similarity index 97% rename from builder/vmware/step_type_boot_command.go rename to builder/vmware/iso/step_type_boot_command.go index 688103c09..89e7ac432 100644 --- a/builder/vmware/step_type_boot_command.go +++ b/builder/vmware/iso/step_type_boot_command.go @@ -1,9 +1,10 @@ -package vmware +package iso import ( "fmt" "github.com/mitchellh/go-vnc" "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/packer" "log" "net" @@ -36,7 +37,7 @@ type stepTypeBootCommand struct{} func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*config) - driver := state.Get("driver").(Driver) + driver := state.Get("driver").(vmwcommon.Driver) httpPort := state.Get("http_port").(uint) ui := state.Get("ui").(packer.Ui) vncIp := state.Get("vnc_ip").(string) diff --git a/builder/vmware/step_upload_tools.go b/builder/vmware/iso/step_upload_tools.go similarity index 98% rename from builder/vmware/step_upload_tools.go rename to builder/vmware/iso/step_upload_tools.go index 630faef8d..95812a748 100644 --- a/builder/vmware/step_upload_tools.go +++ b/builder/vmware/iso/step_upload_tools.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "fmt" diff --git a/builder/vmware/ssh.go b/builder/vmware/ssh.go deleted file mode 100644 index 0b499db29..000000000 --- a/builder/vmware/ssh.go +++ /dev/null @@ -1,104 +0,0 @@ -package vmware - -import ( - gossh "code.google.com/p/go.crypto/ssh" - "errors" - "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/communicator/ssh" - "io/ioutil" - "log" - "os" -) - -func sshAddress(state multistep.StateBag) (string, error) { - config := state.Get("config").(*config) - driver := state.Get("driver").(Driver) - vmxPath := state.Get("vmx_path").(string) - - log.Println("Lookup up IP information...") - f, err := os.Open(vmxPath) - if err != nil { - return "", err - } - defer f.Close() - - vmxBytes, err := ioutil.ReadAll(f) - if err != nil { - return "", err - } - - vmxData := ParseVMX(string(vmxBytes)) - - var ok bool - macAddress := "" - if macAddress, ok = vmxData["ethernet0.address"]; !ok || macAddress == "" { - if macAddress, ok = vmxData["ethernet0.generatedaddress"]; !ok || macAddress == "" { - return "", errors.New("couldn't find MAC address in VMX") - } - } - - ipLookup := &DHCPLeaseGuestLookup{ - Driver: driver, - Device: "vmnet8", - MACAddress: macAddress, - } - - ipAddress, err := ipLookup.GuestIP() - if err != nil { - log.Printf("IP lookup failed: %s", err) - return "", fmt.Errorf("IP lookup failed: %s", err) - } - - if ipAddress == "" { - log.Println("IP is blank, no IP yet.") - return "", errors.New("IP is blank") - } - - log.Printf("Detected IP: %s", ipAddress) - return fmt.Sprintf("%s:%d", ipAddress, config.SSHPort), nil -} - -func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) { - config := state.Get("config").(*config) - - auth := []gossh.ClientAuth{ - gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)), - gossh.ClientAuthKeyboardInteractive( - ssh.PasswordKeyboardInteractive(config.SSHPassword)), - } - - if config.SSHKeyPath != "" { - keyring, err := sshKeyToKeyring(config.SSHKeyPath) - if err != nil { - return nil, err - } - - auth = append(auth, gossh.ClientAuthKeyring(keyring)) - } - - return &gossh.ClientConfig{ - User: config.SSHUser, - Auth: auth, - }, nil -} - -func sshKeyToKeyring(path string) (gossh.ClientKeyring, error) { - f, err := os.Open(path) - if err != nil { - return nil, err - } - defer f.Close() - - keyBytes, err := ioutil.ReadAll(f) - if err != nil { - return nil, err - } - - keyring := new(ssh.SimpleKeychain) - if err := keyring.AddPEMKey(string(keyBytes)); err != nil { - return nil, err - } - - return keyring, nil -} diff --git a/builder/vmware/step_prepare_output_dir.go b/builder/vmware/step_prepare_output_dir.go deleted file mode 100644 index 2909712ef..000000000 --- a/builder/vmware/step_prepare_output_dir.go +++ /dev/null @@ -1,84 +0,0 @@ -package vmware - -import ( - "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - "log" - "time" -) - -type stepPrepareOutputDir struct { - dir OutputDir -} - -func (s *stepPrepareOutputDir) Run(state multistep.StateBag) multistep.StepAction { - config := state.Get("config").(*config) - ui := state.Get("ui").(packer.Ui) - - dir := s.outputDir(state) - dir.SetOutputDir(config.OutputDir) - - exists, err := dir.DirExists() - if err != nil { - state.Put("error", err) - return multistep.ActionHalt - } - - if exists { - if config.PackerForce { - ui.Say("Deleting previous output directory...") - dir.RemoveAll() - } else { - state.Put("error", fmt.Errorf( - "Output directory '%s' already exists.", config.OutputDir)) - return multistep.ActionHalt - } - } - - if err := dir.MkdirAll(); err != nil { - state.Put("error", err) - return multistep.ActionHalt - } - - s.dir = dir - state.Put("dir", dir) - return multistep.ActionContinue -} - -func (s *stepPrepareOutputDir) Cleanup(state multistep.StateBag) { - _, cancelled := state.GetOk(multistep.StateCancelled) - _, halted := state.GetOk(multistep.StateHalted) - - if cancelled || halted { - ui := state.Get("ui").(packer.Ui) - - if s.dir != nil { - ui.Say("Deleting output directory...") - for i := 0; i < 5; i++ { - err := s.dir.RemoveAll() - if err == nil { - break - } - - log.Printf("Error removing output dir: %s", err) - time.Sleep(2 * time.Second) - } - } - } -} - -func (s *stepPrepareOutputDir) outputDir(state multistep.StateBag) (dir OutputDir) { - driver := state.Get("driver").(Driver) - - switch d := driver.(type) { - case OutputDir: - log.Printf("Using driver as the OutputDir implementation") - dir = d - default: - log.Printf("Using localOutputDir implementation") - dir = new(localOutputDir) - } - - return -} diff --git a/builder/vmware/vmx/builder.go b/builder/vmware/vmx/builder.go new file mode 100644 index 000000000..e799efd0b --- /dev/null +++ b/builder/vmware/vmx/builder.go @@ -0,0 +1,124 @@ +package vmx + +import ( + "errors" + "fmt" + "log" + "time" + + "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/packer" +) + +// Builder implements packer.Builder and builds the actual VirtualBox +// images. +type Builder struct { + config *Config + runner multistep.Runner +} + +// Prepare processes the build configuration parameters. +func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { + c, warnings, errs := NewConfig(raws...) + if errs != nil { + return warnings, errs + } + b.config = c + + return warnings, nil +} + +// Run executes a Packer build and returns a packer.Artifact representing +// a VirtualBox appliance. +func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { + driver, err := vmwcommon.NewDriver(&b.config.SSHConfig) + if err != nil { + return nil, fmt.Errorf("Failed creating VMware driver: %s", err) + } + + // Setup the directory + dir := new(vmwcommon.LocalOutputDir) + dir.SetOutputDir(b.config.OutputDir) + + // Set up the state. + state := new(multistep.BasicStateBag) + state.Put("config", b.config) + state.Put("dir", dir) + state.Put("driver", driver) + state.Put("hook", hook) + state.Put("ui", ui) + + // Build the steps. + steps := []multistep.Step{ + &vmwcommon.StepOutputDir{ + Force: b.config.PackerForce, + }, + &StepCloneVMX{ + OutputDir: b.config.OutputDir, + Path: b.config.SourcePath, + VMName: b.config.VMName, + }, + &vmwcommon.StepConfigureVMX{ + CustomData: b.config.VMXData, + }, + &vmwcommon.StepSuppressMessages{}, + &vmwcommon.StepRun{ + BootWait: b.config.BootWait, + DurationBeforeStop: 5 * time.Second, + Headless: b.config.Headless, + }, + &common.StepConnectSSH{ + SSHAddress: driver.SSHAddress, + SSHConfig: vmwcommon.SSHConfigFunc(&b.config.SSHConfig), + SSHWaitTimeout: b.config.SSHWaitTimeout, + NoPty: b.config.SSHSkipRequestPty, + }, + &common.StepProvision{}, + &vmwcommon.StepShutdown{ + Command: b.config.ShutdownCommand, + Timeout: b.config.ShutdownTimeout, + }, + &vmwcommon.StepCleanFiles{}, + &vmwcommon.StepCleanVMX{}, + &vmwcommon.StepCompactDisk{ + Skip: b.config.SkipCompaction, + }, + } + + // Run the steps. + if b.config.PackerDebug { + b.runner = &multistep.DebugRunner{ + Steps: steps, + PauseFn: common.MultistepDebugFn(ui), + } + } else { + b.runner = &multistep.BasicRunner{Steps: steps} + } + b.runner.Run(state) + + // Report any errors. + if rawErr, ok := state.GetOk("error"); ok { + return nil, rawErr.(error) + } + + // If we were interrupted or cancelled, then just exit. + if _, ok := state.GetOk(multistep.StateCancelled); ok { + return nil, errors.New("Build was cancelled.") + } + + if _, ok := state.GetOk(multistep.StateHalted); ok { + return nil, errors.New("Build was halted.") + } + + return vmwcommon.NewLocalArtifact(b.config.OutputDir) +} + +// Cancel. +func (b *Builder) Cancel() { + if b.runner != nil { + log.Println("Cancelling the step runner...") + b.runner.Cancel() + } +} diff --git a/builder/vmware/vmx/builder_test.go b/builder/vmware/vmx/builder_test.go new file mode 100644 index 000000000..e6b128721 --- /dev/null +++ b/builder/vmware/vmx/builder_test.go @@ -0,0 +1 @@ +package vmx diff --git a/builder/vmware/vmx/config.go b/builder/vmware/vmx/config.go new file mode 100644 index 000000000..5f68d1f42 --- /dev/null +++ b/builder/vmware/vmx/config.go @@ -0,0 +1,91 @@ +package vmx + +import ( + "fmt" + "os" + + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/packer" +) + +// Config is the configuration structure for the builder. +type Config struct { + common.PackerConfig `mapstructure:",squash"` + vmwcommon.OutputConfig `mapstructure:",squash"` + vmwcommon.RunConfig `mapstructure:",squash"` + vmwcommon.ShutdownConfig `mapstructure:",squash"` + vmwcommon.SSHConfig `mapstructure:",squash"` + vmwcommon.VMXConfig `mapstructure:",squash"` + + SkipCompaction bool `mapstructure:"skip_compaction"` + SourcePath string `mapstructure:"source_path"` + VMName string `mapstructure:"vm_name"` + + tpl *packer.ConfigTemplate +} + +func NewConfig(raws ...interface{}) (*Config, []string, error) { + c := new(Config) + md, err := common.DecodeConfig(c, raws...) + if err != nil { + return nil, nil, err + } + + c.tpl, err = packer.NewConfigTemplate() + if err != nil { + return nil, nil, err + } + c.tpl.UserVars = c.PackerUserVars + + // Defaults + if c.VMName == "" { + c.VMName = fmt.Sprintf("packer-%s-{{timestamp}}", c.PackerBuildName) + } + + // Prepare the errors + errs := common.CheckUnusedConfig(md) + errs = packer.MultiErrorAppend(errs, c.OutputConfig.Prepare(c.tpl, &c.PackerConfig)...) + errs = packer.MultiErrorAppend(errs, c.RunConfig.Prepare(c.tpl)...) + errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare(c.tpl)...) + errs = packer.MultiErrorAppend(errs, c.SSHConfig.Prepare(c.tpl)...) + errs = packer.MultiErrorAppend(errs, c.VMXConfig.Prepare(c.tpl)...) + + templates := map[string]*string{ + "source_path": &c.SourcePath, + "vm_name": &c.VMName, + } + + for n, ptr := range templates { + var err error + *ptr, err = c.tpl.Process(*ptr, nil) + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + + if c.SourcePath == "" { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is required")) + } else { + if _, err := os.Stat(c.SourcePath); err != nil { + errs = packer.MultiErrorAppend(errs, + fmt.Errorf("source_path is invalid: %s", err)) + } + } + + // Warnings + var warnings []string + if c.ShutdownCommand == "" { + warnings = append(warnings, + "A shutdown_command was not specified. Without a shutdown command, Packer\n"+ + "will forcibly halt the virtual machine, which may result in data loss.") + } + + // Check for any errors. + if errs != nil && len(errs.Errors) > 0 { + return nil, warnings, errs + } + + return c, warnings, nil +} diff --git a/builder/vmware/vmx/config_test.go b/builder/vmware/vmx/config_test.go new file mode 100644 index 000000000..ac49957e3 --- /dev/null +++ b/builder/vmware/vmx/config_test.go @@ -0,0 +1,59 @@ +package vmx + +import ( + "io/ioutil" + "os" + "testing" +) + +func testConfig(t *testing.T) map[string]interface{} { + return map[string]interface{}{ + "ssh_username": "foo", + "shutdown_command": "foo", + } +} + +func testConfigErr(t *testing.T, warns []string, err error) { + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should error") + } +} + +func testConfigOk(t *testing.T, warns []string, err error) { + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("bad: %s", err) + } +} + +func TestNewConfig_sourcePath(t *testing.T) { + // Bad + c := testConfig(t) + delete(c, "source_path") + _, warns, errs := NewConfig(c) + testConfigErr(t, warns, errs) + + // Bad + c = testConfig(t) + c["source_path"] = "/i/dont/exist" + _, warns, errs = NewConfig(c) + testConfigErr(t, warns, errs) + + // Good + tf, err := ioutil.TempFile("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + tf.Close() + defer os.Remove(tf.Name()) + + c = testConfig(t) + c["source_path"] = tf.Name() + _, warns, errs = NewConfig(c) + testConfigOk(t, warns, errs) +} diff --git a/builder/vmware/vmx/step_clone_vmx.go b/builder/vmware/vmx/step_clone_vmx.go new file mode 100644 index 000000000..c1edcff15 --- /dev/null +++ b/builder/vmware/vmx/step_clone_vmx.go @@ -0,0 +1,53 @@ +package vmx + +import ( + "fmt" + "log" + "path/filepath" + + "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" + "github.com/mitchellh/packer/packer" +) + +// StepCloneVMX takes a VMX file and clones the VM into the output directory. +type StepCloneVMX struct { + OutputDir string + Path string + VMName string +} + +func (s *StepCloneVMX) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(vmwcommon.Driver) + ui := state.Get("ui").(packer.Ui) + + vmxPath := filepath.Join(s.OutputDir, s.VMName+".vmx") + + 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); err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + + vmxData, err := vmwcommon.ReadVMX(vmxPath) + if err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + + diskName, ok := vmxData["scsi0:0.filename"] + if !ok { + err := fmt.Errorf("Root disk filename could not be found!") + state.Put("error", err) + return multistep.ActionHalt + } + + state.Put("full_disk_path", filepath.Join(s.OutputDir, diskName)) + state.Put("vmx_path", vmxPath) + return multistep.ActionContinue +} + +func (s *StepCloneVMX) Cleanup(state multistep.StateBag) { +} diff --git a/builder/vmware/vmx/step_clone_vmx_test.go b/builder/vmware/vmx/step_clone_vmx_test.go new file mode 100644 index 000000000..8b8b5a056 --- /dev/null +++ b/builder/vmware/vmx/step_clone_vmx_test.go @@ -0,0 +1,74 @@ +package vmx + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" +) + +func TestStepCloneVMX_impl(t *testing.T) { + var _ multistep.Step = new(StepCloneVMX) +} + +func TestStepCloneVMX(t *testing.T) { + // Setup some state + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(td) + + // Create the source + sourcePath := filepath.Join(td, "source.vmx") + if err := ioutil.WriteFile(sourcePath, []byte(testCloneVMX), 0644); err != nil { + t.Fatalf("err: %s", err) + } + + // Create the dest because the mock driver won't + destPath := filepath.Join(td, "foo.vmx") + if err := ioutil.WriteFile(destPath, []byte(testCloneVMX), 0644); err != nil { + t.Fatalf("err: %s", err) + } + + state := testState(t) + step := new(StepCloneVMX) + step.OutputDir = td + step.Path = sourcePath + step.VMName = "foo" + + driver := state.Get("driver").(*vmwcommon.DriverMock) + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test we cloned + if !driver.CloneCalled { + t.Fatal("should call clone") + } + + // Test that we have our paths + if vmxPath, ok := state.GetOk("vmx_path"); !ok { + t.Fatal("should set vmx_path") + } else if vmxPath != destPath { + t.Fatalf("bad: %#v", vmxPath) + } + + if diskPath, ok := state.GetOk("full_disk_path"); !ok { + t.Fatal("should set full_disk_path") + } else if diskPath != filepath.Join(td, "foo") { + t.Fatalf("bad: %#v", diskPath) + } +} + +const testCloneVMX = ` +scsi0:0.fileName = "foo" +` diff --git a/builder/vmware/vmx/step_test.go b/builder/vmware/vmx/step_test.go new file mode 100644 index 000000000..ad8075a61 --- /dev/null +++ b/builder/vmware/vmx/step_test.go @@ -0,0 +1,20 @@ +package vmx + +import ( + "bytes" + "testing" + + "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" + "github.com/mitchellh/packer/packer" +) + +func testState(t *testing.T) multistep.StateBag { + state := new(multistep.BasicStateBag) + state.Put("driver", new(vmwcommon.DriverMock)) + state.Put("ui", &packer.BasicUi{ + Reader: new(bytes.Buffer), + Writer: new(bytes.Buffer), + }) + return state +} diff --git a/command/fix/fixer.go b/command/fix/fixer.go index b4dc2013c..8da82f48f 100644 --- a/command/fix/fixer.go +++ b/command/fix/fixer.go @@ -25,6 +25,7 @@ func init() { "pp-vagrant-override": new(FixerVagrantPPOverride), "virtualbox-gaattach": new(FixerVirtualBoxGAAttach), "virtualbox-rename": new(FixerVirtualBoxRename), + "vmware-rename": new(FixerVMwareRename), } FixerOrder = []string{ @@ -33,5 +34,6 @@ func init() { "virtualbox-gaattach", "pp-vagrant-override", "virtualbox-rename", + "vmware-rename", } } diff --git a/command/fix/fixer_virtualbox_rename_test.go b/command/fix/fixer_virtualbox_rename_test.go index 1f529def1..78b7bccf2 100644 --- a/command/fix/fixer_virtualbox_rename_test.go +++ b/command/fix/fixer_virtualbox_rename_test.go @@ -14,7 +14,6 @@ func TestFixerVirtualBoxRename_Fix(t *testing.T) { Input map[string]interface{} Expected map[string]interface{} }{ - // No attach field { Input: map[string]interface{}{ "type": "virtualbox", diff --git a/command/fix/fixer_vmware_rename.go b/command/fix/fixer_vmware_rename.go new file mode 100644 index 000000000..7a7b7b920 --- /dev/null +++ b/command/fix/fixer_vmware_rename.go @@ -0,0 +1,46 @@ +package fix + +import ( + "github.com/mitchellh/mapstructure" +) + +// FixerVMwareRename changes "virtualbox" builders to "virtualbox-iso" +type FixerVMwareRename struct{} + +func (FixerVMwareRename) Fix(input map[string]interface{}) (map[string]interface{}, error) { + // The type we'll decode into; we only care about builders + type template struct { + Builders []map[string]interface{} + } + + // Decode the input into our structure, if we can + var tpl template + if err := mapstructure.Decode(input, &tpl); err != nil { + return nil, err + } + + for _, builder := range tpl.Builders { + builderTypeRaw, ok := builder["type"] + if !ok { + continue + } + + builderType, ok := builderTypeRaw.(string) + if !ok { + continue + } + + if builderType != "vmware" { + continue + } + + builder["type"] = "vmware-iso" + } + + input["builders"] = tpl.Builders + return input, nil +} + +func (FixerVMwareRename) Synopsis() string { + return `Updates "vmware" builders to "vmware-iso"` +} diff --git a/command/fix/fixer_vmware_rename_test.go b/command/fix/fixer_vmware_rename_test.go new file mode 100644 index 000000000..059b73ad1 --- /dev/null +++ b/command/fix/fixer_vmware_rename_test.go @@ -0,0 +1,48 @@ +package fix + +import ( + "reflect" + "testing" +) + +func TestFixerVMwareRename_impl(t *testing.T) { + var _ Fixer = new(FixerVMwareRename) +} + +func TestFixerVMwareRename_Fix(t *testing.T) { + cases := []struct { + Input map[string]interface{} + Expected map[string]interface{} + }{ + { + Input: map[string]interface{}{ + "type": "vmware", + }, + + Expected: map[string]interface{}{ + "type": "vmware-iso", + }, + }, + } + + for _, tc := range cases { + var f FixerVMwareRename + + input := map[string]interface{}{ + "builders": []map[string]interface{}{tc.Input}, + } + + expected := map[string]interface{}{ + "builders": []map[string]interface{}{tc.Expected}, + } + + output, err := f.Fix(input) + if err != nil { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(output, expected) { + t.Fatalf("unexpected: %#v\nexpected: %#v\n", output, expected) + } + } +} diff --git a/config.go b/config.go index 15f3d764f..a2fe49297 100644 --- a/config.go +++ b/config.go @@ -29,7 +29,8 @@ const defaultConfig = ` "qemu": "packer-builder-qemu", "virtualbox-iso": "packer-builder-virtualbox-iso", "virtualbox-ovf": "packer-builder-virtualbox-ovf", - "vmware": "packer-builder-vmware" + "vmware-iso": "packer-builder-vmware-iso", + "vmware-vmx": "packer-builder-vmware-vmx" }, "commands": { diff --git a/plugin/builder-vmware/main.go b/plugin/builder-vmware-iso/main.go similarity index 64% rename from plugin/builder-vmware/main.go rename to plugin/builder-vmware-iso/main.go index 14a10cf5c..58121bb49 100644 --- a/plugin/builder-vmware/main.go +++ b/plugin/builder-vmware-iso/main.go @@ -1,7 +1,7 @@ package main import ( - "github.com/mitchellh/packer/builder/vmware" + "github.com/mitchellh/packer/builder/vmware/iso" "github.com/mitchellh/packer/packer/plugin" ) @@ -10,6 +10,6 @@ func main() { if err != nil { panic(err) } - server.RegisterBuilder(new(vmware.Builder)) + server.RegisterBuilder(new(iso.Builder)) server.Serve() } diff --git a/plugin/builder-vmware/main_test.go b/plugin/builder-vmware-iso/main_test.go similarity index 100% rename from plugin/builder-vmware/main_test.go rename to plugin/builder-vmware-iso/main_test.go diff --git a/plugin/builder-vmware-vmx/main.go b/plugin/builder-vmware-vmx/main.go new file mode 100644 index 000000000..f060f3c56 --- /dev/null +++ b/plugin/builder-vmware-vmx/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/mitchellh/packer/builder/vmware/vmx" + "github.com/mitchellh/packer/packer/plugin" +) + +func main() { + server, err := plugin.Server() + if err != nil { + panic(err) + } + server.RegisterBuilder(new(vmx.Builder)) + server.Serve() +} diff --git a/plugin/builder-vmware-vmx/main_test.go b/plugin/builder-vmware-vmx/main_test.go new file mode 100644 index 000000000..06ab7d0f9 --- /dev/null +++ b/plugin/builder-vmware-vmx/main_test.go @@ -0,0 +1 @@ +package main diff --git a/post-processor/vagrant/post-processor.go b/post-processor/vagrant/post-processor.go index 78659488c..eae580151 100644 --- a/post-processor/vagrant/post-processor.go +++ b/post-processor/vagrant/post-processor.go @@ -208,8 +208,14 @@ func (p *PostProcessor) configureSingle(config *Config, raws ...interface{}) err func providerForName(name string) Provider { switch name { + case "aws": + return new(AWSProvider) + case "digitalocean": + return new(DigitalOceanProvider) case "virtualbox": return new(VBoxProvider) + case "vmware": + return new(VMwareProvider) default: return nil } diff --git a/website/source/docs/builders/vmware-iso.html.markdown b/website/source/docs/builders/vmware-iso.html.markdown new file mode 100644 index 000000000..0b03d6d55 --- /dev/null +++ b/website/source/docs/builders/vmware-iso.html.markdown @@ -0,0 +1,333 @@ +--- +layout: "docs" +page_title: "VMware Builder from ISO" +--- + +# VMware Builder (from ISO) + +Type: `vmware-iso` + +This VMware builder is able to create VMware virtual machines from an +ISO file as a source. It currently +supports building virtual machines on hosts running +[VMware Fusion](http://www.vmware.com/products/fusion/overview.html) for OS X, +[VMware Workstation](http://www.vmware.com/products/workstation/overview.html) +for Linux and Windows, and +[VMware Player](http://www.vmware.com/products/player/) on Linux. It can +also build machines directly on +[VMware vSphere Hypervisor](http://www.vmware.com/products/vsphere-hypervisor/) +using SSH as opposed to the vSphere API. + +The builder builds a virtual machine by creating a new virtual machine +from scratch, booting it, installing an OS, provisioning software within +the OS, then shutting it down. The result of the VMware builder is a directory +containing all the files necessary to run the virtual machine. + +## Basic Example + +Here is a basic example. This example is not functional. It will start the +OS installer but then fail because we don't provide the preseed file for +Ubuntu to self-install. Still, the example serves to show the basic configuration: + +
+{
+  "type": "vmware-iso",
+  "iso_url": "http://old-releases.ubuntu.com/releases/precise/ubuntu-12.04.2-server-amd64.iso",
+  "iso_checksum": "af5f788aee1b32c4b2634734309cc9e9",
+  "iso_checksum_type": "md5",
+  "ssh_username": "packer",
+  "ssh_wait_timeout": "30s",
+  "shutdown_command": "shutdown -P now"
+}
+
+ +## Configuration Reference + +There are many configuration options available for the VMware builder. +They are organized below into two categories: required and optional. Within +each category, the available options are alphabetized and described. + +Required: + +* `iso_checksum` (string) - The checksum for the OS ISO file. Because ISO + files are so large, this is required and Packer will verify it prior + to booting a virtual machine with the ISO attached. The type of the + checksum is specified with `iso_checksum_type`, documented below. + +* `iso_checksum_type` (string) - The type of the checksum specified in + `iso_checksum`. Valid values are "md5", "sha1", "sha256", or "sha512" currently. + +* `iso_url` (string) - A URL to the ISO containing the installation image. + This URL can be either an HTTP URL or a file URL (or path to a file). + If this is an HTTP URL, Packer will download it and cache it between + runs. + +* `ssh_username` (string) - The username to use to SSH into the machine + once the OS is installed. + +Optional: + +* `boot_command` (array of strings) - This is an array of commands to type + when the virtual machine is firsted booted. The goal of these commands should + be to type just enough to initialize the operating system installer. Special + keys can be typed as well, and are covered in the section below on the boot + command. If this is not specified, it is assumed the installer will start + itself. + +* `boot_wait` (string) - The time to wait after booting the initial virtual + machine before typing the `boot_command`. The value of this should be + a duration. Examples are "5s" and "1m30s" which will cause Packer to wait + five seconds and one minute 30 seconds, respectively. If this isn't specified, + the default is 10 seconds. + +* `disk_size` (int) - The size of the hard disk for the VM in megabytes. + The builder uses expandable, not fixed-size virtual hard disks, so the + actual file representing the disk will not use the full size unless it is full. + By default this is set to 40,000 (40 GB). + +* `disk_type_id` (string) - The type of VMware virtual disk to create. + The default is "1", which corresponds to a growable virtual disk split in + 2GB files. This option is for advanced usage, modify only if you + know what you're doing. For more information, please consult the + [Virtual Disk Manager User's Guide](http://www.vmware.com/pdf/VirtualDiskManager.pdf) + for desktop VMware clients. For ESXi, refer to the proper ESXi documentation. + +* `floppy_files` (array of strings) - A list of files to put onto a floppy + disk that is attached when the VM is booted for the first time. This is + most useful for unattended Windows installs, which look for an + `Autounattend.xml` file on removable media. By default no floppy will + be attached. The files listed in this configuration will all be put + into the root directory of the floppy disk; sub-directories are not supported. + +* `guest_os_type` (string) - The guest OS type being installed. This will be + set in the VMware VMX. By default this is "other". By specifying a more specific + OS type, VMware may perform some optimizations or virtual hardware changes + to better support the operating system running in the virtual machine. + +* `headless` (bool) - Packer defaults to building VMware + virtual machines by launching a GUI that shows the console of the + machine being built. When this value is set to true, the machine will + start without a console. For VMware machines, Packer will output VNC + connection information in case you need to connect to the console to + debug the build process. + +* `http_directory` (string) - Path to a directory to serve using an HTTP + server. The files in this directory will be available over HTTP that will + be requestable from the virtual machine. This is useful for hosting + kickstart files and so on. By default this is "", which means no HTTP + server will be started. The address and port of the HTTP server will be + available as variables in `boot_command`. This is covered in more detail + below. + +* `http_port_min` and `http_port_max` (int) - These are the minimum and + maximum port to use for the HTTP server started to serve the `http_directory`. + Because Packer often runs in parallel, Packer will choose a randomly available + port in this range to run the HTTP server. If you want to force the HTTP + server to be on one port, make this minimum and maximum port the same. + By default the values are 8000 and 9000, respectively. + +* `iso_urls` (array of strings) - Multiple URLs for the ISO to download. + Packer will try these in order. If anything goes wrong attempting to download + or while downloading a single URL, it will move on to the next. All URLs + must point to the same file (same checksum). By default this is empty + and `iso_url` is used. Only one of `iso_url` or `iso_urls` can be specified. + +* `output_directory` (string) - This is the path to the directory where the + resulting virtual machine will be created. This may be relative or absolute. + If relative, the path is relative to the working directory when `packer` + is executed. This directory must not exist or be empty prior to running the builder. + By default this is "output-BUILDNAME" where "BUILDNAME" is the name + of the build. + +* `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_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_username` (string) - The username for the SSH user that will access + the remote machine. This is required if `remote_type` is enabled. + +* `skip_compaction` (bool) - VMware-created disks are defragmented + and compacted at the end of the build process using `vmware-vdiskmanager`. + In certain rare cases, this might actually end up making the resulting disks + slightly larger. If you find this to be the case, you can disable compaction + using this configuration value. + +* `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. + +* `shutdown_timeout` (string) - The amount of time to wait after executing + the `shutdown_command` for the virtual machine to actually shut down. + If it doesn't shut down in this time, it is an error. By default, the timeout + is "5m", or five minutes. + +* `ssh_key_path` (string) - Path to a private key to use for authenticating + with SSH. By default this is not set (key-based auth won't be used). + The associated public key is expected to already be configured on the + VM being prepared by some other process (kickstart, etc.). + +* `ssh_password` (string) - The password for `ssh_username` to use to + authenticate with SSH. By default this is the empty string. + +* `ssh_port` (int) - The port that SSH will listen on within the virtual + machine. By default this is 22. + +* `ssh_skip_request_pty` (bool) - If true, a pty will not be requested as + part of the SSH connection. By default, this is "false", so a pty + _will_ be requested. + +* `ssh_wait_timeout` (string) - The duration to wait for SSH to become + available. By default this is "20m", or 20 minutes. Note that this should + be quite long since the timer begins as soon as the virtual machine is booted. + +* `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. + +* `tools_upload_path` (string) - The path in the VM to upload the VMware + tools. This only takes effect if `tools_upload_flavor` is non-empty. + This is a [configuration template](/docs/templates/configuration-templates.html) + that has a single valid variable: `Flavor`, which will be the value of + `tools_upload_flavor`. By default the upload path is set to + `{{.Flavor}}.iso`. + +* `vm_name` (string) - This is the name of the VMX file for the new virtual + machine, without the file extension. By default this is "packer-BUILDNAME", + where "BUILDNAME" is the name of the build. + +* `vmdk_name` (string) - The filename of the virtual disk that'll be created, + without the extension. This defaults to "packer". + +* `vmx_data` (object, string keys and string values) - Arbitrary key/values + to enter into the virtual machine VMX file. This is for advanced users + who want to set properties such as memory, CPU, etc. + +* `vnc_port_min` and `vnc_port_max` (int) - 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, Packer + uses a randomly chosen port in this range that appears available. By default + this is 5900 to 6000. The minimum and maximum ports are inclusive. + +* `vmx_template_path` (string) - Path to a + [configuration template](/docs/templates/configuration-templates.html) that + defines the contents of the virtual machine VMX file for VMware. This is + for **advanced users only** as this can render the virtual machine + non-functional. See below for more information. For basic VMX modifications, + try `vmx_data` first. + +## Boot Command + +The `boot_command` configuration is very important: it specifies the keys +to type when the virtual machine is first booted in order to start the +OS installer. This command is typed after `boot_wait`, which gives the +virtual machine some time to actually load the ISO. + +As documented above, the `boot_command` is an array of strings. The +strings are all typed in sequence. It is an array only to improve readability +within the template. + +The boot command is "typed" character for character over a VNC connection +to the machine, simulating a human actually typing the keyboard. There are +a set of special keys available. If these are in your boot command, they +will be replaced by the proper key: + +* `` and `` - Simulates an actual "enter" or "return" keypress. + +* `` - Simulates pressing the escape key. + +* `` - Simulates pressing the tab 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: + +* `HTTPIP` and `HTTPPort` - The IP and port, respectively of an HTTP server + that is started serving the directory specified by the `http_directory` + configuration parameter. If `http_directory` isn't specified, these will + be blank! + +Example boot command. This is actually a working boot command used to start +an Ubuntu 12.04 installer: + +
+[
+  "<esc><esc><enter><wait>",
+  "/install/vmlinuz noapic ",
+  "preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg ",
+  "debian-installer=en_US auto locale=en_US kbd-chooser/method=us ",
+  "hostname={{ .Name }} ",
+  "fb=false debconf/frontend=noninteractive ",
+  "keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ",
+  "keyboard-configuration/variant=USA console-setup/ask_detect=false ",
+  "initrd=/install/initrd.gz -- <enter>"
+]
+
+ +## VMX Template + +The heart of a VMware machine is the "vmx" file. This contains all the +virtual hardware metadata necessary for the VM to function. Packer by default +uses a [safe, flexible VMX file](https://github.com/mitchellh/packer/blob/20541a7eda085aa5cf35bfed5069592ca49d106e/builder/vmware/step_create_vmx.go#L84). +But for advanced users, this template can be customized. This allows +Packer to build virtual machines of effectively any guest operating system +type. + +
+

+This is an advanced feature. Modifying the VMX template +can easily cause your virtual machine to not boot properly. Please only +modify the template if you know what you're doing. +

+
+ +Within the template, a handful of variables are available so that your +template can continue working with the rest of the Packer machinery. Using +these variables isn't required, however. + +* `Name` - The name of the virtual machine. +* `GuestOS` - The VMware-valid guest OS type. +* `DiskName` - The filename (without the suffix) of the main virtual disk. +* `ISOPath` - The path to the ISO to use for the OS installation. + +## Building on a Remote vSphere Hypervisor + +In addition to using the desktop products of VMware locally to build +virtual machines, Packer can use a remote VMware Hypervisor to build +the virtual machine. + +When using a remote VMware Hypervisor, the builder still downloads the +ISO and various files locally, and uploads these to the remote machine. +Packer currently uses SSH to communicate to the ESXi machine rather than +the vSphere API. At some point, the vSphere API may be used. + +To use a remote VMware vSphere Hypervisor to build your virtual machine, +fill in the required `remote_*` configurations: + +* `remote_type` - This must be set to "esx5". + +* `remote_host` - The host of the remote machine. + +Additionally, there are some optional configurations that you'll likely +have to modify as well: + +* `remote_datastore` - The path to the datastore where the VM will be + stored on the ESXi machine. + +* `remote_username` - The SSH username used to access the remote machine. + +* `remote_password` - The SSH password for access to the remote machine. diff --git a/website/source/docs/builders/vmware-vmx.html.markdown b/website/source/docs/builders/vmware-vmx.html.markdown new file mode 100644 index 000000000..e62375514 --- /dev/null +++ b/website/source/docs/builders/vmware-vmx.html.markdown @@ -0,0 +1,121 @@ +--- +layout: "docs" +page_title: "VMware Builder from VMX" +--- + +# VMware Builder (from VMX) + +Type: `vmware-vmx` + +This VMware builder is able to create VMware virtual machines from an +existing VMware virtual machine (a VMX file). It currently +supports building virtual machines on hosts running +[VMware Fusion](http://www.vmware.com/products/fusion/overview.html) for OS X, +[VMware Workstation](http://www.vmware.com/products/workstation/overview.html) +for Linux and Windows, and +[VMware Player](http://www.vmware.com/products/player/) on Linux. + +The builder builds a virtual machine by cloning the VMX file using +the clone capabilities introduced in VMware Fusion 6, Workstation 10, +and Player 6. After cloning the VM, it provisions software within the +new machine, shuts it down, and compacts the disks. The resulting folder +contains a new VMware virtual machine. + +## Basic Example + +Here is an example. This example is fully functional as long as the source +path points to a real VMX file with the proper settings: + +
+{
+  "type": "vmware-vmx",
+  "source_path": "/path/to/a/vm.vmx",
+  "ssh_username": "root",
+  "ssh_password": "root",
+  "shutdown_command": "shutdown -P now"
+}
+
+ +## Configuration Reference + +There are many configuration options available for the VMware builder. +They are organized below into two categories: required and optional. Within +each category, the available options are alphabetized and described. + +Required: + +* `source_path` (string) - Path to the source VMX file to clone. + +* `ssh_username` (string) - The username to use to SSH into the machine + once the OS is installed. + +Optional: + +* `boot_wait` (string) - The time to wait after booting the initial virtual + machine before typing the `boot_command`. The value of this should be + a duration. Examples are "5s" and "1m30s" which will cause Packer to wait + five seconds and one minute 30 seconds, respectively. If this isn't specified, + the default is 10 seconds. + +* `floppy_files` (array of strings) - A list of files to put onto a floppy + disk that is attached when the VM is booted for the first time. This is + most useful for unattended Windows installs, which look for an + `Autounattend.xml` file on removable media. By default no floppy will + be attached. The files listed in this configuration will all be put + into the root directory of the floppy disk; sub-directories are not supported. + +* `headless` (bool) - Packer defaults to building VMware + virtual machines by launching a GUI that shows the console of the + machine being built. When this value is set to true, the machine will + start without a console. For VMware machines, Packer will output VNC + connection information in case you need to connect to the console to + debug the build process. + +* `output_directory` (string) - This is the path to the directory where the + resulting virtual machine will be created. This may be relative or absolute. + If relative, the path is relative to the working directory when `packer` + is executed. This directory must not exist or be empty prior to running the builder. + By default this is "output-BUILDNAME" where "BUILDNAME" is the name + of the build. + +* `skip_compaction` (bool) - VMware-created disks are defragmented + and compacted at the end of the build process using `vmware-vdiskmanager`. + In certain rare cases, this might actually end up making the resulting disks + slightly larger. If you find this to be the case, you can disable compaction + using this configuration value. + +* `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. + +* `shutdown_timeout` (string) - The amount of time to wait after executing + the `shutdown_command` for the virtual machine to actually shut down. + If it doesn't shut down in this time, it is an error. By default, the timeout + is "5m", or five minutes. + +* `ssh_key_path` (string) - Path to a private key to use for authenticating + with SSH. By default this is not set (key-based auth won't be used). + The associated public key is expected to already be configured on the + VM being prepared by some other process (kickstart, etc.). + +* `ssh_password` (string) - The password for `ssh_username` to use to + authenticate with SSH. By default this is the empty string. + +* `ssh_port` (int) - The port that SSH will listen on within the virtual + machine. By default this is 22. + +* `ssh_skip_request_pty` (bool) - If true, a pty will not be requested as + part of the SSH connection. By default, this is "false", so a pty + _will_ be requested. + +* `ssh_wait_timeout` (string) - The duration to wait for SSH to become + available. By default this is "20m", or 20 minutes. Note that this should + be quite long since the timer begins as soon as the virtual machine is booted. + +* `vm_name` (string) - This is the name of the VMX file for the new virtual + machine, without the file extension. By default this is "packer-BUILDNAME", + where "BUILDNAME" is the name of the build. + +* `vmx_data` (object, string keys and string values) - Arbitrary key/values + to enter into the virtual machine VMX file. This is for advanced users + who want to set properties such as memory, CPU, etc. diff --git a/website/source/docs/builders/vmware.html.markdown b/website/source/docs/builders/vmware.html.markdown index f59c92c18..c25172efc 100644 --- a/website/source/docs/builders/vmware.html.markdown +++ b/website/source/docs/builders/vmware.html.markdown @@ -1,331 +1,25 @@ --- layout: "docs" +page_title: "VMware Builder" --- # VMware Builder -Type: `vmware` - -The VMware builder is able to create VMware virtual machines. It currently -supports building virtual machines on hosts running -[VMware Fusion](http://www.vmware.com/products/fusion/overview.html) for OS X, -[VMware Workstation](http://www.vmware.com/products/workstation/overview.html) -for Linux and Windows, and -[VMware Player](http://www.vmware.com/products/player/) on Linux. It can -also build machines directly on -[VMware vSphere Hypervisor](http://www.vmware.com/products/vsphere-hypervisor/) -using SSH as opposed to the vSphere API. - -The builder builds a virtual machine by creating a new virtual machine -from scratch, booting it, installing an OS, provisioning software within -the OS, then shutting it down. The result of the VMware builder is a directory -containing all the files necessary to run the virtual machine. - -## Basic Example - -Here is a basic example. This example is not functional. It will start the -OS installer but then fail because we don't provide the preseed file for -Ubuntu to self-install. Still, the example serves to show the basic configuration: - -
-{
-  "type": "vmware",
-  "iso_url": "http://old-releases.ubuntu.com/releases/precise/ubuntu-12.04.2-server-amd64.iso",
-  "iso_checksum": "af5f788aee1b32c4b2634734309cc9e9",
-  "iso_checksum_type": "md5",
-  "ssh_username": "packer",
-  "ssh_wait_timeout": "30s",
-  "shutdown_command": "shutdown -P now"
-}
-
- -## Configuration Reference - -There are many configuration options available for the VMware builder. -They are organized below into two categories: required and optional. Within -each category, the available options are alphabetized and described. - -Required: - -* `iso_checksum` (string) - The checksum for the OS ISO file. Because ISO - files are so large, this is required and Packer will verify it prior - to booting a virtual machine with the ISO attached. The type of the - checksum is specified with `iso_checksum_type`, documented below. - -* `iso_checksum_type` (string) - The type of the checksum specified in - `iso_checksum`. Valid values are "md5", "sha1", "sha256", or "sha512" currently. - -* `iso_url` (string) - A URL to the ISO containing the installation image. - This URL can be either an HTTP URL or a file URL (or path to a file). - If this is an HTTP URL, Packer will download it and cache it between - runs. - -* `ssh_username` (string) - The username to use to SSH into the machine - once the OS is installed. - -Optional: - -* `boot_command` (array of strings) - This is an array of commands to type - when the virtual machine is firsted booted. The goal of these commands should - be to type just enough to initialize the operating system installer. Special - keys can be typed as well, and are covered in the section below on the boot - command. If this is not specified, it is assumed the installer will start - itself. - -* `boot_wait` (string) - The time to wait after booting the initial virtual - machine before typing the `boot_command`. The value of this should be - a duration. Examples are "5s" and "1m30s" which will cause Packer to wait - five seconds and one minute 30 seconds, respectively. If this isn't specified, - the default is 10 seconds. - -* `disk_size` (int) - The size of the hard disk for the VM in megabytes. - The builder uses expandable, not fixed-size virtual hard disks, so the - actual file representing the disk will not use the full size unless it is full. - By default this is set to 40,000 (40 GB). - -* `disk_type_id` (string) - The type of VMware virtual disk to create. - The default is "1", which corresponds to a growable virtual disk split in - 2GB files. This option is for advanced usage, modify only if you - know what you're doing. For more information, please consult the - [Virtual Disk Manager User's Guide](http://www.vmware.com/pdf/VirtualDiskManager.pdf) - for desktop VMware clients. For ESXi, refer to the proper ESXi documentation. - -* `floppy_files` (array of strings) - A list of files to put onto a floppy - disk that is attached when the VM is booted for the first time. This is - most useful for unattended Windows installs, which look for an - `Autounattend.xml` file on removable media. By default no floppy will - be attached. The files listed in this configuration will all be put - into the root directory of the floppy disk; sub-directories are not supported. - -* `guest_os_type` (string) - The guest OS type being installed. This will be - set in the VMware VMX. By default this is "other". By specifying a more specific - OS type, VMware may perform some optimizations or virtual hardware changes - to better support the operating system running in the virtual machine. - -* `headless` (bool) - Packer defaults to building VMware - virtual machines by launching a GUI that shows the console of the - machine being built. When this value is set to true, the machine will - start without a console. For VMware machines, Packer will output VNC - connection information in case you need to connect to the console to - debug the build process. - -* `http_directory` (string) - Path to a directory to serve using an HTTP - server. The files in this directory will be available over HTTP that will - be requestable from the virtual machine. This is useful for hosting - kickstart files and so on. By default this is "", which means no HTTP - server will be started. The address and port of the HTTP server will be - available as variables in `boot_command`. This is covered in more detail - below. - -* `http_port_min` and `http_port_max` (int) - These are the minimum and - maximum port to use for the HTTP server started to serve the `http_directory`. - Because Packer often runs in parallel, Packer will choose a randomly available - port in this range to run the HTTP server. If you want to force the HTTP - server to be on one port, make this minimum and maximum port the same. - By default the values are 8000 and 9000, respectively. - -* `iso_urls` (array of strings) - Multiple URLs for the ISO to download. - Packer will try these in order. If anything goes wrong attempting to download - or while downloading a single URL, it will move on to the next. All URLs - must point to the same file (same checksum). By default this is empty - and `iso_url` is used. Only one of `iso_url` or `iso_urls` can be specified. - -* `output_directory` (string) - This is the path to the directory where the - resulting virtual machine will be created. This may be relative or absolute. - If relative, the path is relative to the working directory when `packer` - is executed. This directory must not exist or be empty prior to running the builder. - By default this is "output-BUILDNAME" where "BUILDNAME" is the name - of the build. - -* `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_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_username` (string) - The username for the SSH user that will access - the remote machine. This is required if `remote_type` is enabled. - -* `skip_compaction` (bool) - VMware-created disks are defragmented - and compacted at the end of the build process using `vmware-vdiskmanager`. - In certain rare cases, this might actually end up making the resulting disks - slightly larger. If you find this to be the case, you can disable compaction - using this configuration value. - -* `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. - -* `shutdown_timeout` (string) - The amount of time to wait after executing - the `shutdown_command` for the virtual machine to actually shut down. - If it doesn't shut down in this time, it is an error. By default, the timeout - is "5m", or five minutes. - -* `ssh_key_path` (string) - Path to a private key to use for authenticating - with SSH. By default this is not set (key-based auth won't be used). - The associated public key is expected to already be configured on the - VM being prepared by some other process (kickstart, etc.). - -* `ssh_password` (string) - The password for `ssh_username` to use to - authenticate with SSH. By default this is the empty string. - -* `ssh_port` (int) - The port that SSH will listen on within the virtual - machine. By default this is 22. - -* `ssh_skip_request_pty` (bool) - If true, a pty will not be requested as - part of the SSH connection. By default, this is "false", so a pty - _will_ be requested. - -* `ssh_wait_timeout` (string) - The duration to wait for SSH to become - available. By default this is "20m", or 20 minutes. Note that this should - be quite long since the timer begins as soon as the virtual machine is booted. - -* `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. - -* `tools_upload_path` (string) - The path in the VM to upload the VMware - tools. This only takes effect if `tools_upload_flavor` is non-empty. - This is a [configuration template](/docs/templates/configuration-templates.html) - that has a single valid variable: `Flavor`, which will be the value of - `tools_upload_flavor`. By default the upload path is set to - `{{.Flavor}}.iso`. - -* `vm_name` (string) - This is the name of the VMX file for the new virtual - machine, without the file extension. By default this is "packer-BUILDNAME", - where "BUILDNAME" is the name of the build. - -* `vmdk_name` (string) - The filename of the virtual disk that'll be created, - without the extension. This defaults to "packer". - -* `vmx_data` (object, string keys and string values) - Arbitrary key/values - to enter into the virtual machine VMX file. This is for advanced users - who want to set properties such as memory, CPU, etc. - -* `vnc_port_min` and `vnc_port_max` (int) - 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, Packer - uses a randomly chosen port in this range that appears available. By default - this is 5900 to 6000. The minimum and maximum ports are inclusive. - -* `vmx_template_path` (string) - Path to a - [configuration template](/docs/templates/configuration-templates.html) that - defines the contents of the virtual machine VMX file for VMware. This is - for **advanced users only** as this can render the virtual machine - non-functional. See below for more information. For basic VMX modifications, - try `vmx_data` first. - -## Boot Command - -The `boot_command` configuration is very important: it specifies the keys -to type when the virtual machine is first booted in order to start the -OS installer. This command is typed after `boot_wait`, which gives the -virtual machine some time to actually load the ISO. - -As documented above, the `boot_command` is an array of strings. The -strings are all typed in sequence. It is an array only to improve readability -within the template. - -The boot command is "typed" character for character over a VNC connection -to the machine, simulating a human actually typing the keyboard. There are -a set of special keys available. If these are in your boot command, they -will be replaced by the proper key: - -* `` and `` - Simulates an actual "enter" or "return" keypress. - -* `` - Simulates pressing the escape key. - -* `` - Simulates pressing the tab 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: - -* `HTTPIP` and `HTTPPort` - The IP and port, respectively of an HTTP server - that is started serving the directory specified by the `http_directory` - configuration parameter. If `http_directory` isn't specified, these will - be blank! - -Example boot command. This is actually a working boot command used to start -an Ubuntu 12.04 installer: - -
-[
-  "<esc><esc><enter><wait>",
-  "/install/vmlinuz noapic ",
-  "preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg ",
-  "debian-installer=en_US auto locale=en_US kbd-chooser/method=us ",
-  "hostname={{ .Name }} ",
-  "fb=false debconf/frontend=noninteractive ",
-  "keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ",
-  "keyboard-configuration/variant=USA console-setup/ask_detect=false ",
-  "initrd=/install/initrd.gz -- <enter>"
-]
-
- -## VMX Template - -The heart of a VMware machine is the "vmx" file. This contains all the -virtual hardware metadata necessary for the VM to function. Packer by default -uses a [safe, flexible VMX file](https://github.com/mitchellh/packer/blob/20541a7eda085aa5cf35bfed5069592ca49d106e/builder/vmware/step_create_vmx.go#L84). -But for advanced users, this template can be customized. This allows -Packer to build virtual machines of effectively any guest operating system -type. - -
-

-This is an advanced feature. Modifying the VMX template -can easily cause your virtual machine to not boot properly. Please only -modify the template if you know what you're doing. -

-
- -Within the template, a handful of variables are available so that your -template can continue working with the rest of the Packer machinery. Using -these variables isn't required, however. - -* `Name` - The name of the virtual machine. -* `GuestOS` - The VMware-valid guest OS type. -* `DiskName` - The filename (without the suffix) of the main virtual disk. -* `ISOPath` - The path to the ISO to use for the OS installation. - -## Building on a Remote vSphere Hypervisor - -In addition to using the desktop products of VMware locally to build -virtual machines, Packer can use a remote VMware Hypervisor to build -the virtual machine. - -When using a remote VMware Hypervisor, the builder still downloads the -ISO and various files locally, and uploads these to the remote machine. -Packer currently uses SSH to communicate to the ESXi machine rather than -the vSphere API. At some point, the vSphere API may be used. - -To use a remote VMware vSphere Hypervisor to build your virtual machine, -fill in the required `remote_*` configurations: - -* `remote_type` - This must be set to "esx5". - -* `remote_host` - The host of the remote machine. - -Additionally, there are some optional configurations that you'll likely -have to modify as well: - -* `remote_datastore` - The path to the datastore where the VM will be - stored on the ESXi machine. - -* `remote_username` - The SSH username used to access the remote machine. - -* `remote_password` - The SSH password for access to the remote machine. +The VMware builder is able to create VMware virtual machines for use +with any VMware product. + +Packer actually comes with multiple builders able to create VMware +machines, depending on the strategy you want to use to build the image. +Packer supports the following VMware builders: + +* [vmware-iso](/docs/builders/vmware-iso.html) - Starts from + an ISO file, creates a brand new VMware VM, installs an OS, + provisions software within the OS, then exports that machine to create + an image. This is best for people who want to start from scratch. + +* [vmware-vmx](/docs/builders/vmware-vmx.html) - This builder + imports an existing VMware machine (from a VMX file), runs provisioners + on top of that VM, and exports that machine to create an image. + This is best if you have an existing VMware VM you want to use as the + source. As an additional benefit, you can feed the artifact of this + builder back into Packer to iterate on a machine.