Merge pull request #758 from mitchellh/f-vmware-vmx

Build VMware machine from VMX (source VM)
This commit is contained in:
Mitchell Hashimoto 2013-12-26 16:16:00 -08:00
commit 8be172d19a
98 changed files with 3726 additions and 1161 deletions

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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()))
}
}

View File

@ -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
}

View File

@ -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 := ""

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -0,0 +1,9 @@
package common
import (
"testing"
)
func TestDriverMock_impl(t *testing.T) {
var _ Driver = new(DriverMock)
}

View File

@ -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 {

View File

@ -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 {

View File

@ -1,6 +1,6 @@
// +build !windows
package vmware
package common
import (
"errors"

View File

@ -1,6 +1,6 @@
// +build windows
package vmware
package common
import (
"log"

View File

@ -1,4 +1,4 @@
package vmware
package common
import (
"errors"

View File

@ -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";
`

View File

@ -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
}

View File

@ -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")
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -0,0 +1,9 @@
package common
import (
"testing"
)
func TestLocalOuputDir_impl(t *testing.T) {
var _ OutputDir = new(LocalOutputDir)
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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-----
`

View File

@ -1,4 +1,4 @@
package vmware
package common
import (
"fmt"
@ -21,9 +21,9 @@ var KeepFileExtensions = []string{".nvram", ".vmdk", ".vmsd", ".vmx", ".vmxf"}
//
// Produces:
// <nothing>
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) {}

View File

@ -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:
// <nothing>
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) {}

View File

@ -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"
`

View File

@ -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:
// <nothing>
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) {}

View File

@ -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")
}
}

View File

@ -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) {
}

View File

@ -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])
}
}
}
}

View File

@ -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)
}
}
}

View File

@ -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")
}
}

View File

@ -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:
// <nothing>
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
}
}
}

View File

@ -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")
}
}

View File

@ -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:
// <nothing>
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) {}

View File

@ -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")
}
}

View File

@ -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) {}

View File

@ -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")
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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")
}
}

View File

@ -1,4 +1,4 @@
package vmware
package common
import "testing"

View File

@ -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()
}

View File

@ -1,4 +1,4 @@
package vmware
package iso
import (
"github.com/mitchellh/packer/packer"

View File

@ -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
}

View File

@ -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")
}
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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) {

View File

@ -1,4 +1,4 @@
package vmware
package iso
// Interface to help find the host IP that is available from within
// the VMware virtual machines.

View File

@ -1,4 +1,4 @@
package vmware
package iso
import (
"bytes"

View File

@ -1,4 +1,4 @@
package vmware
package iso
import "testing"

View File

@ -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 == "" {

View File

@ -1,4 +1,4 @@
package vmware
package iso
import "testing"

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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)
}

View File

@ -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())

View File

@ -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")

View File

@ -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())

View File

@ -1,4 +1,4 @@
package vmware
package iso
import (
"fmt"

View File

@ -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

View File

@ -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 = ""
}
}

View File

@ -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")
}
}

View File

@ -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)

View File

@ -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
}

View File

@ -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)

View File

@ -1,4 +1,4 @@
package vmware
package iso
import (
"fmt"

View File

@ -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
}

View File

@ -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
}

View File

@ -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()
}
}

View File

@ -0,0 +1 @@
package vmx

View File

@ -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
}

View File

@ -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)
}

View File

@ -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) {
}

View File

@ -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"
`

View File

@ -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
}

View File

@ -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",
}
}

View File

@ -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",

View File

@ -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"`
}

View File

@ -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)
}
}
}

View File

@ -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": {

View File

@ -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()
}

View File

@ -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()
}

View File

@ -0,0 +1 @@
package main

View File

@ -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
}

View File

@ -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:
<pre class="prettyprint">
{
"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"
}
</pre>
## 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:
* `<enter>` and `<return>` - Simulates an actual "enter" or "return" keypress.
* `<esc>` - Simulates pressing the escape key.
* `<tab>` - Simulates pressing the tab key.
* `<wait>` `<wait5>` `<wait10>` - 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:
<pre class="prettyprint">
[
"&lt;esc&gt;&lt;esc&gt;&lt;enter&gt;&lt;wait&gt;",
"/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 -- &lt;enter&gt;"
]
</pre>
## 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.
<div class="alert alert-block alert-warn">
<p>
<strong>This is an advanced feature.</strong> 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.
</p>
</div>
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.

View File

@ -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:
<pre class="prettyprint">
{
"type": "vmware-vmx",
"source_path": "/path/to/a/vm.vmx",
"ssh_username": "root",
"ssh_password": "root",
"shutdown_command": "shutdown -P now"
}
</pre>
## 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.

View File

@ -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:
<pre class="prettyprint">
{
"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"
}
</pre>
## 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:
* `<enter>` and `<return>` - Simulates an actual "enter" or "return" keypress.
* `<esc>` - Simulates pressing the escape key.
* `<tab>` - Simulates pressing the tab key.
* `<wait>` `<wait5>` `<wait10>` - 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:
<pre class="prettyprint">
[
"&lt;esc&gt;&lt;esc&gt;&lt;enter&gt;&lt;wait&gt;",
"/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 -- &lt;enter&gt;"
]
</pre>
## 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.
<div class="alert alert-block alert-warn">
<p>
<strong>This is an advanced feature.</strong> 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.
</p>
</div>
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.