Merge pull request #758 from mitchellh/f-vmware-vmx
Build VMware machine from VMX (source VM)
This commit is contained in:
commit
8be172d19a
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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()))
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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 := ""
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDriverMock_impl(t *testing.T) {
|
||||
var _ Driver = new(DriverMock)
|
||||
}
|
|
@ -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 {
|
|
@ -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 {
|
|
@ -1,6 +1,6 @@
|
|||
// +build !windows
|
||||
|
||||
package vmware
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
|
@ -1,6 +1,6 @@
|
|||
// +build windows
|
||||
|
||||
package vmware
|
||||
package common
|
||||
|
||||
import (
|
||||
"log"
|
|
@ -1,4 +1,4 @@
|
|||
package vmware
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
|
@ -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";
|
||||
|
||||
`
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLocalOuputDir_impl(t *testing.T) {
|
||||
var _ OutputDir = new(LocalOutputDir)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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-----
|
||||
`
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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"
|
||||
`
|
|
@ -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) {}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package vmware
|
||||
package common
|
||||
|
||||
import "testing"
|
||||
|
|
@ -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()
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package vmware
|
||||
package iso
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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) {
|
|
@ -1,4 +1,4 @@
|
|||
package vmware
|
||||
package iso
|
||||
|
||||
// Interface to help find the host IP that is available from within
|
||||
// the VMware virtual machines.
|
|
@ -1,4 +1,4 @@
|
|||
package vmware
|
||||
package iso
|
||||
|
||||
import (
|
||||
"bytes"
|
|
@ -1,4 +1,4 @@
|
|||
package vmware
|
||||
package iso
|
||||
|
||||
import "testing"
|
||||
|
|
@ -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 == "" {
|
|
@ -1,4 +1,4 @@
|
|||
package vmware
|
||||
package iso
|
||||
|
||||
import "testing"
|
||||
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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())
|
|
@ -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")
|
|
@ -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())
|
|
@ -1,4 +1,4 @@
|
|||
package vmware
|
||||
package iso
|
||||
|
||||
import (
|
||||
"fmt"
|
|
@ -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
|
|
@ -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 = ""
|
||||
}
|
||||
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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)
|
|
@ -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
|
||||
}
|
|
@ -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)
|
|
@ -1,4 +1,4 @@
|
|||
package vmware
|
||||
package iso
|
||||
|
||||
import (
|
||||
"fmt"
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package vmx
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -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"
|
||||
`
|
|
@ -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
|
||||
}
|
|
@ -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",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"`
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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": {
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package main
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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">
|
||||
[
|
||||
"<esc><esc><enter><wait>",
|
||||
"/install/vmlinuz noapic ",
|
||||
"preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg ",
|
||||
"debian-installer=en_US auto locale=en_US kbd-chooser/method=us ",
|
||||
"hostname={{ .Name }} ",
|
||||
"fb=false debconf/frontend=noninteractive ",
|
||||
"keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ",
|
||||
"keyboard-configuration/variant=USA console-setup/ask_detect=false ",
|
||||
"initrd=/install/initrd.gz -- <enter>"
|
||||
]
|
||||
</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.
|
|
@ -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.
|
|
@ -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">
|
||||
[
|
||||
"<esc><esc><enter><wait>",
|
||||
"/install/vmlinuz noapic ",
|
||||
"preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg ",
|
||||
"debian-installer=en_US auto locale=en_US kbd-chooser/method=us ",
|
||||
"hostname={{ .Name }} ",
|
||||
"fb=false debconf/frontend=noninteractive ",
|
||||
"keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ",
|
||||
"keyboard-configuration/variant=USA console-setup/ask_detect=false ",
|
||||
"initrd=/install/initrd.gz -- <enter>"
|
||||
]
|
||||
</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.
|
||||
|
|
Loading…
Reference in New Issue