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 (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testShutdownConfig() *ShutdownConfig {
|
func testShutdownConfig() *ShutdownConfig {
|
||||||
|
@ -38,4 +39,7 @@ func TestShutdownConfigPrepare_ShutdownTimeout(t *testing.T) {
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
t.Fatalf("err: %#v", errs)
|
t.Fatalf("err: %#v", errs)
|
||||||
}
|
}
|
||||||
|
if c.ShutdownTimeout != 5*time.Second {
|
||||||
|
t.Fatalf("bad: %s", c.ShutdownTimeout)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package ovf
|
package ovf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testConfig(t *testing.T) map[string]interface{} {
|
func testConfig(t *testing.T) map[string]interface{} {
|
||||||
|
@ -31,7 +31,6 @@ func testConfigOk(t *testing.T, warns []string, err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func TestNewConfig_sourcePath(t *testing.T) {
|
func TestNewConfig_sourcePath(t *testing.T) {
|
||||||
// Bad
|
// Bad
|
||||||
c := testConfig(t)
|
c := testConfig(t)
|
||||||
|
@ -58,4 +57,3 @@ func TestNewConfig_sourcePath(t *testing.T) {
|
||||||
_, warns, errs = NewConfig(c)
|
_, warns, errs = NewConfig(c)
|
||||||
testConfigOk(t, warns, errs)
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/multistep"
|
|
||||||
"log"
|
"log"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A driver is able to talk to VMware, control virtual machines, etc.
|
// A driver is able to talk to VMware, control virtual machines, etc.
|
||||||
type Driver interface {
|
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 compacts a virtual disk.
|
||||||
CompactDisk(string) error
|
CompactDisk(string) error
|
||||||
|
|
||||||
|
@ -50,40 +56,41 @@ type Driver interface {
|
||||||
|
|
||||||
// NewDriver returns a new driver implementation for this operating
|
// NewDriver returns a new driver implementation for this operating
|
||||||
// system, or an error if the driver couldn't be initialized.
|
// 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{}
|
drivers := []Driver{}
|
||||||
|
|
||||||
if config.RemoteType != "" {
|
|
||||||
drivers = []Driver{
|
|
||||||
&ESX5Driver{
|
|
||||||
Host: config.RemoteHost,
|
|
||||||
Port: config.RemotePort,
|
|
||||||
Username: config.RemoteUser,
|
|
||||||
Password: config.RemotePassword,
|
|
||||||
Datastore: config.RemoteDatastore,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "darwin":
|
case "darwin":
|
||||||
drivers = []Driver{
|
drivers = []Driver{
|
||||||
|
&Fusion6Driver{
|
||||||
|
Fusion5Driver: Fusion5Driver{
|
||||||
|
AppPath: "/Applications/VMware Fusion.app",
|
||||||
|
SSHConfig: config,
|
||||||
|
},
|
||||||
|
},
|
||||||
&Fusion5Driver{
|
&Fusion5Driver{
|
||||||
AppPath: "/Applications/VMware Fusion.app",
|
AppPath: "/Applications/VMware Fusion.app",
|
||||||
|
SSHConfig: config,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
case "linux":
|
case "linux":
|
||||||
drivers = []Driver{
|
drivers = []Driver{
|
||||||
new(Workstation9Driver),
|
&Workstation9Driver{
|
||||||
new(Player5LinuxDriver),
|
SSHConfig: config,
|
||||||
|
},
|
||||||
|
&Player5LinuxDriver{
|
||||||
|
SSHConfig: config,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
case "windows":
|
case "windows":
|
||||||
drivers = []Driver{
|
drivers = []Driver{
|
||||||
new(Workstation9Driver),
|
&Workstation9Driver{
|
||||||
|
SSHConfig: config,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("can't find driver for OS: %s", runtime.GOOS)
|
return nil, fmt.Errorf("can't find driver for OS: %s", runtime.GOOS)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
errs := ""
|
errs := ""
|
||||||
for _, driver := range drivers {
|
for _, driver := range drivers {
|
|
@ -1,19 +1,28 @@
|
||||||
package vmware
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/multistep"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Fusion5Driver is a driver that can run VMWare Fusion 5.
|
// Fusion5Driver is a driver that can run VMWare Fusion 5.
|
||||||
type Fusion5Driver struct {
|
type Fusion5Driver struct {
|
||||||
// This is the path to the "VMware Fusion.app"
|
// This is the path to the "VMware Fusion.app"
|
||||||
AppPath string
|
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 {
|
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) {
|
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 {
|
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 _, err := os.Stat(d.vmrunPath()); err != nil {
|
||||||
if os.IsNotExist(err) {
|
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
|
return err
|
||||||
|
@ -115,7 +125,9 @@ func (d *Fusion5Driver) Verify() error {
|
||||||
|
|
||||||
if _, err := os.Stat(d.vdiskManagerPath()); err != nil {
|
if _, err := os.Stat(d.vdiskManagerPath()); err != nil {
|
||||||
if os.IsNotExist(err) {
|
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
|
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 (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/multistep"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Player5LinuxDriver is a driver that can run VMware Player 5 on Linux.
|
// Player5LinuxDriver is a driver that can run VMware Player 5 on Linux.
|
||||||
|
@ -15,6 +17,13 @@ type Player5LinuxDriver struct {
|
||||||
VdiskManagerPath string
|
VdiskManagerPath string
|
||||||
QemuImgPath string
|
QemuImgPath string
|
||||||
VmrunPath 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 {
|
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) {
|
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 {
|
func (d *Player5LinuxDriver) Start(vmxPath string, headless bool) error {
|
|
@ -1,13 +1,15 @@
|
||||||
package vmware
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/multistep"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Workstation9Driver is a driver that can run VMware Workstation 9
|
// Workstation9Driver is a driver that can run VMware Workstation 9
|
||||||
|
@ -16,6 +18,13 @@ type Workstation9Driver struct {
|
||||||
AppPath string
|
AppPath string
|
||||||
VdiskManagerPath string
|
VdiskManagerPath string
|
||||||
VmrunPath 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 {
|
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) {
|
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 {
|
func (d *Workstation9Driver) Start(vmxPath string, headless bool) error {
|
|
@ -1,6 +1,6 @@
|
||||||
// +build !windows
|
// +build !windows
|
||||||
|
|
||||||
package vmware
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
|
@ -1,6 +1,6 @@
|
||||||
// +build windows
|
// +build windows
|
||||||
|
|
||||||
package vmware
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
|
@ -1,4 +1,4 @@
|
||||||
package vmware
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -21,9 +21,9 @@ var KeepFileExtensions = []string{".nvram", ".vmdk", ".vmsd", ".vmx", ".vmxf"}
|
||||||
//
|
//
|
||||||
// Produces:
|
// Produces:
|
||||||
// <nothing>
|
// <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)
|
dir := state.Get("dir").(OutputDir)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
@ -49,7 +49,9 @@ func (stepCleanFiles) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
if !keep {
|
if !keep {
|
||||||
ui.Message(fmt.Sprintf("Deleting: %s", path))
|
ui.Message(fmt.Sprintf("Deleting: %s", path))
|
||||||
if err = dir.Remove(path); err != nil {
|
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) {
|
if _, serr := os.Stat(path); serr == nil || !os.IsNotExist(serr) {
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
|
@ -61,4 +63,4 @@ func (stepCleanFiles) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (stepCleanFiles) Cleanup(multistep.StateBag) {}
|
func (StepCleanFiles) Cleanup(multistep.StateBag) {}
|
|
@ -1,12 +1,10 @@
|
||||||
package vmware
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -20,16 +18,15 @@ import (
|
||||||
//
|
//
|
||||||
// Produces:
|
// Produces:
|
||||||
// <nothing>
|
// <nothing>
|
||||||
type stepCleanVMX struct{}
|
type StepCleanVMX struct{}
|
||||||
|
|
||||||
func (s stepCleanVMX) Run(state multistep.StateBag) multistep.StepAction {
|
func (s StepCleanVMX) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
isoPath := state.Get("iso_path").(string)
|
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vmxPath := state.Get("vmx_path").(string)
|
vmxPath := state.Get("vmx_path").(string)
|
||||||
|
|
||||||
ui.Say("Cleaning VMX prior to finishing up...")
|
ui.Say("Cleaning VMX prior to finishing up...")
|
||||||
|
|
||||||
vmxData, err := s.readVMX(vmxPath)
|
vmxData, err := ReadVMX(vmxPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
state.Put("error", fmt.Errorf("Error reading VMX: %s", err))
|
state.Put("error", fmt.Errorf("Error reading VMX: %s", err))
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
|
@ -47,6 +44,9 @@ func (s stepCleanVMX) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
vmxData["floppy0.present"] = "FALSE"
|
vmxData["floppy0.present"] = "FALSE"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isoPathRaw, ok := state.GetOk("iso_path"); ok {
|
||||||
|
isoPath := isoPathRaw.(string)
|
||||||
|
|
||||||
ui.Message("Detaching ISO from CD-ROM device...")
|
ui.Message("Detaching ISO from CD-ROM device...")
|
||||||
devRe := regexp.MustCompile(`^ide\d:\d\.`)
|
devRe := regexp.MustCompile(`^ide\d:\d\.`)
|
||||||
for k, _ := range vmxData {
|
for k, _ := range vmxData {
|
||||||
|
@ -64,6 +64,7 @@ func (s stepCleanVMX) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Rewrite the VMX
|
// Rewrite the VMX
|
||||||
if err := WriteVMX(vmxPath, vmxData); err != nil {
|
if err := WriteVMX(vmxPath, vmxData); err != nil {
|
||||||
|
@ -74,19 +75,4 @@ func (s stepCleanVMX) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (stepCleanVMX) Cleanup(multistep.StateBag) {}
|
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
|
|
||||||
}
|
|
|
@ -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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -11,22 +11,22 @@ import (
|
||||||
// boolean is true.
|
// boolean is true.
|
||||||
//
|
//
|
||||||
// Uses:
|
// Uses:
|
||||||
// config *config
|
|
||||||
// driver Driver
|
// driver Driver
|
||||||
// full_disk_path string
|
// full_disk_path string
|
||||||
// ui packer.Ui
|
// ui packer.Ui
|
||||||
//
|
//
|
||||||
// Produces:
|
// Produces:
|
||||||
// <nothing>
|
// <nothing>
|
||||||
type stepCompactDisk struct{}
|
type StepCompactDisk struct {
|
||||||
|
Skip bool
|
||||||
|
}
|
||||||
|
|
||||||
func (stepCompactDisk) Run(state multistep.StateBag) multistep.StepAction {
|
func (s StepCompactDisk) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*config)
|
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
full_disk_path := state.Get("full_disk_path").(string)
|
full_disk_path := state.Get("full_disk_path").(string)
|
||||||
|
|
||||||
if config.SkipCompaction == true {
|
if s.Skip {
|
||||||
log.Println("Skipping disk compaction step...")
|
log.Println("Skipping disk compaction step...")
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
@ -40,4 +40,4 @@ func (stepCompactDisk) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
return multistep.ActionContinue
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -10,52 +10,51 @@ import (
|
||||||
// This step runs the created virtual machine.
|
// This step runs the created virtual machine.
|
||||||
//
|
//
|
||||||
// Uses:
|
// Uses:
|
||||||
// config *config
|
|
||||||
// driver Driver
|
// driver Driver
|
||||||
// ui packer.Ui
|
// ui packer.Ui
|
||||||
// vmx_path string
|
// vmx_path string
|
||||||
//
|
//
|
||||||
// Produces:
|
// Produces:
|
||||||
// <nothing>
|
// <nothing>
|
||||||
type stepRun struct {
|
type StepRun struct {
|
||||||
|
BootWait time.Duration
|
||||||
|
DurationBeforeStop time.Duration
|
||||||
|
Headless bool
|
||||||
|
|
||||||
bootTime time.Time
|
bootTime time.Time
|
||||||
vmxPath string
|
vmxPath string
|
||||||
|
|
||||||
registered bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *StepRun) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*config)
|
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vmxPath := state.Get("vmx_path").(string)
|
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
|
// Set the VMX path so that we know we started the machine
|
||||||
s.bootTime = time.Now()
|
s.bootTime = time.Now()
|
||||||
s.vmxPath = vmxPath
|
s.vmxPath = vmxPath
|
||||||
|
|
||||||
ui.Say("Starting virtual machine...")
|
ui.Say("Starting virtual machine...")
|
||||||
if config.Headless {
|
if s.Headless {
|
||||||
|
vncIpRaw, vncIpOk := state.GetOk("vnc_ip")
|
||||||
|
vncPortRaw, vncPortOk := state.GetOk("vnc_port")
|
||||||
|
|
||||||
|
if vncIpOk && vncPortOk {
|
||||||
|
vncIp := vncIpRaw.(string)
|
||||||
|
vncPort := vncPortRaw.(uint)
|
||||||
|
|
||||||
ui.Message(fmt.Sprintf(
|
ui.Message(fmt.Sprintf(
|
||||||
"The VM will be run headless, without a GUI. If you want to\n"+
|
"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"+
|
"view the screen of the VM, connect via VNC without a password to\n"+
|
||||||
"%s:%d", vncIp, vncPort))
|
"%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.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if remoteDriver, ok := driver.(RemoteDriver); ok {
|
if err := driver.Start(vmxPath, s.Headless); err != nil {
|
||||||
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.registered = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := driver.Start(vmxPath, config.Headless); err != nil {
|
|
||||||
err := fmt.Errorf("Error starting VM: %s", err)
|
err := fmt.Errorf("Error starting VM: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
|
@ -63,9 +62,9 @@ func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait the wait amount
|
// Wait the wait amount
|
||||||
if int64(config.bootWait) > 0 {
|
if int64(s.BootWait) > 0 {
|
||||||
ui.Say(fmt.Sprintf("Waiting %s for boot...", config.bootWait.String()))
|
ui.Say(fmt.Sprintf("Waiting %s for boot...", s.BootWait.String()))
|
||||||
wait := time.After(config.bootWait)
|
wait := time.After(s.BootWait)
|
||||||
WAITLOOP:
|
WAITLOOP:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
@ -83,7 +82,7 @@ func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stepRun) Cleanup(state multistep.StateBag) {
|
func (s *StepRun) Cleanup(state multistep.StateBag) {
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
@ -91,10 +90,11 @@ func (s *stepRun) Cleanup(state multistep.StateBag) {
|
||||||
if s.vmxPath != "" {
|
if s.vmxPath != "" {
|
||||||
// If we started it less than 5 seconds ago... wait.
|
// If we started it less than 5 seconds ago... wait.
|
||||||
sinceBootTime := time.Since(s.bootTime)
|
sinceBootTime := time.Since(s.bootTime)
|
||||||
waitBootTime := 5 * time.Second
|
waitBootTime := s.DurationBeforeStop
|
||||||
if sinceBootTime < waitBootTime {
|
if sinceBootTime < waitBootTime {
|
||||||
sleepTime := waitBootTime - sinceBootTime
|
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)
|
time.Sleep(sleepTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,14 +106,5 @@ func (s *stepRun) Cleanup(state multistep.StateBag) {
|
||||||
ui.Error(fmt.Sprintf("Error stopping VM: %s", err))
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -7,7 +7,7 @@ import (
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
"log"
|
"log"
|
||||||
"path/filepath"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -18,29 +18,32 @@ import (
|
||||||
//
|
//
|
||||||
// Uses:
|
// Uses:
|
||||||
// communicator packer.Communicator
|
// communicator packer.Communicator
|
||||||
// config *config
|
// dir OutputDir
|
||||||
// driver Driver
|
// driver Driver
|
||||||
// ui packer.Ui
|
// ui packer.Ui
|
||||||
// vmx_path string
|
// vmx_path string
|
||||||
//
|
//
|
||||||
// Produces:
|
// Produces:
|
||||||
// <nothing>
|
// <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)
|
comm := state.Get("communicator").(packer.Communicator)
|
||||||
config := state.Get("config").(*config)
|
dir := state.Get("dir").(OutputDir)
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vmxPath := state.Get("vmx_path").(string)
|
vmxPath := state.Get("vmx_path").(string)
|
||||||
|
|
||||||
if config.ShutdownCommand != "" {
|
if s.Command != "" {
|
||||||
ui.Say("Gracefully halting virtual machine...")
|
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
|
var stdout, stderr bytes.Buffer
|
||||||
cmd := &packer.RemoteCmd{
|
cmd := &packer.RemoteCmd{
|
||||||
Command: config.ShutdownCommand,
|
Command: s.Command,
|
||||||
Stdout: &stdout,
|
Stdout: &stdout,
|
||||||
Stderr: &stderr,
|
Stderr: &stderr,
|
||||||
}
|
}
|
||||||
|
@ -66,8 +69,8 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
log.Printf("Shutdown stderr: %s", stderr.String())
|
log.Printf("Shutdown stderr: %s", stderr.String())
|
||||||
|
|
||||||
// Wait for the machine to actually shut down
|
// Wait for the machine to actually shut down
|
||||||
log.Printf("Waiting max %s for shutdown to complete", config.shutdownTimeout)
|
log.Printf("Waiting max %s for shutdown to complete", s.Timeout)
|
||||||
shutdownTimer := time.After(config.shutdownTimeout)
|
shutdownTimer := time.After(s.Timeout)
|
||||||
for {
|
for {
|
||||||
running, _ := driver.IsRunning(vmxPath)
|
running, _ := driver.IsRunning(vmxPath)
|
||||||
if !running {
|
if !running {
|
||||||
|
@ -81,10 +84,11 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
default:
|
default:
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(150 * time.Millisecond)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
ui.Say("Forcibly halting virtual machine...")
|
||||||
if err := driver.Stop(vmxPath); err != nil {
|
if err := driver.Stop(vmxPath); err != nil {
|
||||||
err := fmt.Errorf("Error stopping VM: %s", err)
|
err := fmt.Errorf("Error stopping VM: %s", err)
|
||||||
state.Put("error", 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...")
|
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)
|
timer := time.After(15 * time.Second)
|
||||||
LockWaitLoop:
|
LockWaitLoop:
|
||||||
for {
|
for {
|
||||||
locks, err := filepath.Glob(lockPattern)
|
files, err := dir.ListFiles()
|
||||||
if err == nil {
|
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 {
|
if len(locks) == 0 {
|
||||||
log.Println("No more lock files found. VMware is clean.")
|
log.Println("No more lock files found. VMware is clean.")
|
||||||
break
|
break
|
||||||
|
@ -117,7 +130,7 @@ LockWaitLoop:
|
||||||
case <-timer:
|
case <-timer:
|
||||||
log.Println("Reached timeout on waiting for clean VMware. Assuming clean.")
|
log.Println("Reached timeout on waiting for clean VMware. Assuming clean.")
|
||||||
break LockWaitLoop
|
break LockWaitLoop
|
||||||
case <-time.After(1 * time.Second):
|
case <-time.After(150 * time.Millisecond):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,4 +145,4 @@ LockWaitLoop:
|
||||||
return multistep.ActionContinue
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -8,9 +8,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// This step suppresses any messages that VMware product might show.
|
// 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)
|
driver := state.Get("driver").(Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vmxPath := state.Get("vmx_path").(string)
|
vmxPath := state.Get("vmx_path").(string)
|
||||||
|
@ -26,4 +26,4 @@ func (s *stepSuppressMessages) Run(state multistep.StateBag) multistep.StepActio
|
||||||
return multistep.ActionContinue
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -68,3 +69,13 @@ func WriteVMX(path string, data map[string]string) (err error) {
|
||||||
|
|
||||||
return
|
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"
|
import "testing"
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
package vmware
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Artifact is the result of running the VMware builder, namely a set
|
// Artifact is the result of running the VMware builder, namely a set
|
||||||
// of files associated with the resulting machine.
|
// of files associated with the resulting machine.
|
||||||
type Artifact struct {
|
type Artifact struct {
|
||||||
builderId string
|
builderId string
|
||||||
dir string
|
dir OutputDir
|
||||||
f []string
|
f []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,5 +29,5 @@ func (a *Artifact) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Artifact) Destroy() error {
|
func (a *Artifact) Destroy() error {
|
||||||
return os.RemoveAll(a.dir)
|
return a.dir.RemoveAll()
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package vmware
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
|
@ -1,9 +1,10 @@
|
||||||
package vmware
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
|
vmwcommon "github.com/mitchellh/packer/builder/vmware/common"
|
||||||
"github.com/mitchellh/packer/common"
|
"github.com/mitchellh/packer/common"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -15,7 +16,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const BuilderId = "mitchellh.vmware"
|
|
||||||
const BuilderIdESX = "mitchellh.vmware-esx"
|
const BuilderIdESX = "mitchellh.vmware-esx"
|
||||||
|
|
||||||
type Builder struct {
|
type Builder struct {
|
||||||
|
@ -25,6 +25,11 @@ type Builder struct {
|
||||||
|
|
||||||
type config 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"`
|
DiskName string `mapstructure:"vmdk_name"`
|
||||||
DiskSize uint `mapstructure:"disk_size"`
|
DiskSize uint `mapstructure:"disk_size"`
|
||||||
|
@ -35,22 +40,13 @@ type config struct {
|
||||||
ISOChecksumType string `mapstructure:"iso_checksum_type"`
|
ISOChecksumType string `mapstructure:"iso_checksum_type"`
|
||||||
ISOUrls []string `mapstructure:"iso_urls"`
|
ISOUrls []string `mapstructure:"iso_urls"`
|
||||||
VMName string `mapstructure:"vm_name"`
|
VMName string `mapstructure:"vm_name"`
|
||||||
OutputDir string `mapstructure:"output_directory"`
|
|
||||||
Headless bool `mapstructure:"headless"`
|
|
||||||
HTTPDir string `mapstructure:"http_directory"`
|
HTTPDir string `mapstructure:"http_directory"`
|
||||||
HTTPPortMin uint `mapstructure:"http_port_min"`
|
HTTPPortMin uint `mapstructure:"http_port_min"`
|
||||||
HTTPPortMax uint `mapstructure:"http_port_max"`
|
HTTPPortMax uint `mapstructure:"http_port_max"`
|
||||||
BootCommand []string `mapstructure:"boot_command"`
|
BootCommand []string `mapstructure:"boot_command"`
|
||||||
SkipCompaction bool `mapstructure:"skip_compaction"`
|
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"`
|
ToolsUploadFlavor string `mapstructure:"tools_upload_flavor"`
|
||||||
ToolsUploadPath string `mapstructure:"tools_upload_path"`
|
ToolsUploadPath string `mapstructure:"tools_upload_path"`
|
||||||
VMXData map[string]string `mapstructure:"vmx_data"`
|
|
||||||
VMXTemplatePath string `mapstructure:"vmx_template_path"`
|
VMXTemplatePath string `mapstructure:"vmx_template_path"`
|
||||||
VNCPortMin uint `mapstructure:"vnc_port_min"`
|
VNCPortMin uint `mapstructure:"vnc_port_min"`
|
||||||
VNCPortMax uint `mapstructure:"vnc_port_max"`
|
VNCPortMax uint `mapstructure:"vnc_port_max"`
|
||||||
|
@ -62,14 +58,8 @@ type config struct {
|
||||||
RemoteUser string `mapstructure:"remote_username"`
|
RemoteUser string `mapstructure:"remote_username"`
|
||||||
RemotePassword string `mapstructure:"remote_password"`
|
RemotePassword string `mapstructure:"remote_password"`
|
||||||
|
|
||||||
RawBootWait string `mapstructure:"boot_wait"`
|
|
||||||
RawSingleISOUrl string `mapstructure:"iso_url"`
|
RawSingleISOUrl string `mapstructure:"iso_url"`
|
||||||
RawShutdownTimeout string `mapstructure:"shutdown_timeout"`
|
|
||||||
RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"`
|
|
||||||
|
|
||||||
bootWait time.Duration ``
|
|
||||||
shutdownTimeout time.Duration ``
|
|
||||||
sshWaitTimeout time.Duration ``
|
|
||||||
tpl *packer.ConfigTemplate
|
tpl *packer.ConfigTemplate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +77,12 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
|
|
||||||
// Accumulate any errors
|
// Accumulate any errors
|
||||||
errs := common.CheckUnusedConfig(md)
|
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)
|
warnings := make([]string, 0)
|
||||||
|
|
||||||
if b.config.DiskName == "" {
|
if b.config.DiskName == "" {
|
||||||
|
@ -126,10 +122,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
b.config.HTTPPortMax = 9000
|
b.config.HTTPPortMax = 9000
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.config.RawBootWait == "" {
|
|
||||||
b.config.RawBootWait = "10s"
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.VNCPortMin == 0 {
|
if b.config.VNCPortMin == 0 {
|
||||||
b.config.VNCPortMin = 5900
|
b.config.VNCPortMin = 5900
|
||||||
}
|
}
|
||||||
|
@ -138,10 +130,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
b.config.VNCPortMax = 6000
|
b.config.VNCPortMax = 6000
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.config.OutputDir == "" {
|
|
||||||
b.config.OutputDir = fmt.Sprintf("output-%s", b.config.PackerBuildName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.RemoteUser == "" {
|
if b.config.RemoteUser == "" {
|
||||||
b.config.RemoteUser = "root"
|
b.config.RemoteUser = "root"
|
||||||
}
|
}
|
||||||
|
@ -154,10 +142,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
b.config.RemotePort = 22
|
b.config.RemotePort = 22
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.config.SSHPort == 0 {
|
|
||||||
b.config.SSHPort = 22
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.config.ToolsUploadPath == "" {
|
if b.config.ToolsUploadPath == "" {
|
||||||
b.config.ToolsUploadPath = "{{ .Flavor }}.iso"
|
b.config.ToolsUploadPath = "{{ .Flavor }}.iso"
|
||||||
}
|
}
|
||||||
|
@ -170,16 +154,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
"iso_checksum": &b.config.ISOChecksum,
|
"iso_checksum": &b.config.ISOChecksum,
|
||||||
"iso_checksum_type": &b.config.ISOChecksumType,
|
"iso_checksum_type": &b.config.ISOChecksumType,
|
||||||
"iso_url": &b.config.RawSingleISOUrl,
|
"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,
|
"tools_upload_flavor": &b.config.ToolsUploadFlavor,
|
||||||
"vm_name": &b.config.VMName,
|
"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,
|
"vmx_template_path": &b.config.VMXTemplatePath,
|
||||||
"remote_type": &b.config.RemoteType,
|
"remote_type": &b.config.RemoteType,
|
||||||
"remote_host": &b.config.RemoteHost,
|
"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 {
|
if b.config.HTTPPortMin > b.config.HTTPPortMax {
|
||||||
errs = packer.MultiErrorAppend(
|
errs = packer.MultiErrorAppend(
|
||||||
errs, errors.New("http_port_min must be less than http_port_max"))
|
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 {
|
if _, err := template.New("path").Parse(b.config.ToolsUploadPath); err != nil {
|
||||||
errs = packer.MultiErrorAppend(
|
errs = packer.MultiErrorAppend(
|
||||||
errs, fmt.Errorf("tools_upload_path invalid: %s", err))
|
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)
|
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
|
// Seed the random number generator
|
||||||
rand.Seed(time.Now().UTC().UnixNano())
|
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",
|
ResultKey: "iso_path",
|
||||||
Url: b.config.ISOUrls,
|
Url: b.config.ISOUrls,
|
||||||
},
|
},
|
||||||
&stepPrepareOutputDir{},
|
&vmwcommon.StepOutputDir{
|
||||||
|
Force: b.config.PackerForce,
|
||||||
|
},
|
||||||
&common.StepCreateFloppy{
|
&common.StepCreateFloppy{
|
||||||
Files: b.config.FloppyFiles,
|
Files: b.config.FloppyFiles,
|
||||||
},
|
},
|
||||||
|
@ -405,33 +330,38 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
},
|
},
|
||||||
&stepCreateDisk{},
|
&stepCreateDisk{},
|
||||||
&stepCreateVMX{},
|
&stepCreateVMX{},
|
||||||
&stepSuppressMessages{},
|
&vmwcommon.StepConfigureVMX{
|
||||||
|
CustomData: b.config.VMXData,
|
||||||
|
},
|
||||||
|
&vmwcommon.StepSuppressMessages{},
|
||||||
&stepHTTPServer{},
|
&stepHTTPServer{},
|
||||||
&stepConfigureVNC{},
|
&stepConfigureVNC{},
|
||||||
&stepRun{},
|
&StepRegister{},
|
||||||
|
&vmwcommon.StepRun{
|
||||||
|
BootWait: b.config.BootWait,
|
||||||
|
DurationBeforeStop: 5 * time.Second,
|
||||||
|
Headless: b.config.Headless,
|
||||||
|
},
|
||||||
&stepTypeBootCommand{},
|
&stepTypeBootCommand{},
|
||||||
&common.StepConnectSSH{
|
&common.StepConnectSSH{
|
||||||
SSHAddress: driver.SSHAddress,
|
SSHAddress: driver.SSHAddress,
|
||||||
SSHConfig: sshConfig,
|
SSHConfig: vmwcommon.SSHConfigFunc(&b.config.SSHConfig),
|
||||||
SSHWaitTimeout: b.config.sshWaitTimeout,
|
SSHWaitTimeout: b.config.SSHWaitTimeout,
|
||||||
NoPty: b.config.SSHSkipRequestPty,
|
NoPty: b.config.SSHSkipRequestPty,
|
||||||
},
|
},
|
||||||
&stepUploadTools{},
|
&stepUploadTools{},
|
||||||
&common.StepProvision{},
|
&common.StepProvision{},
|
||||||
&stepShutdown{},
|
&vmwcommon.StepShutdown{
|
||||||
&stepCleanFiles{},
|
Command: b.config.ShutdownCommand,
|
||||||
&stepCleanVMX{},
|
Timeout: b.config.ShutdownTimeout,
|
||||||
&stepCompactDisk{},
|
},
|
||||||
|
&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!
|
// Run!
|
||||||
if b.config.PackerDebug {
|
if b.config.PackerDebug {
|
||||||
b.runner = &multistep.DebugRunner{
|
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
|
// Set the proper builder ID
|
||||||
builderId := BuilderId
|
builderId := vmwcommon.BuilderId
|
||||||
if b.config.RemoteType != "" {
|
if b.config.RemoteType != "" {
|
||||||
builderId = BuilderIdESX
|
builderId = BuilderIdESX
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Artifact{
|
return &Artifact{
|
||||||
builderId: builderId,
|
builderId: builderId,
|
||||||
dir: b.config.OutputDir,
|
dir: dir,
|
||||||
f: files,
|
f: files,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package vmware
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
|
@ -9,36 +9,6 @@ import (
|
||||||
"time"
|
"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{} {
|
func testConfig() map[string]interface{} {
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"iso_checksum": "foo",
|
"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) {
|
func TestBuilderPrepare_ISOChecksum(t *testing.T) {
|
||||||
var b Builder
|
var b Builder
|
||||||
config := testConfig()
|
config := testConfig()
|
||||||
|
@ -188,8 +118,8 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
|
||||||
t.Errorf("bad output dir: %s", b.config.OutputDir)
|
t.Errorf("bad output dir: %s", b.config.OutputDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.config.sshWaitTimeout != (20 * time.Minute) {
|
if b.config.SSHWaitTimeout != (20 * time.Minute) {
|
||||||
t.Errorf("bad wait timeout: %s", b.config.sshWaitTimeout)
|
t.Errorf("bad wait timeout: %s", b.config.SSHWaitTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.config.VMName != "packer-foo" {
|
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) {
|
func TestBuilderPrepare_ToolsUploadPath(t *testing.T) {
|
||||||
var b Builder
|
var b Builder
|
||||||
config := testConfig()
|
config := testConfig()
|
||||||
|
@ -743,25 +487,3 @@ func TestBuilderPrepare_VNCPort(t *testing.T) {
|
||||||
t.Fatalf("should not have error: %s", err)
|
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 (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
@ -33,6 +33,10 @@ type ESX5Driver struct {
|
||||||
outputDir string
|
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 {
|
func (d *ESX5Driver) CompactDisk(diskPathLocal string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
|
@ -1,11 +1,12 @@
|
||||||
package vmware
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
vmwcommon "github.com/mitchellh/packer/builder/vmware/common"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestESX5Driver_implDriver(t *testing.T) {
|
func TestESX5Driver_implDriver(t *testing.T) {
|
||||||
var _ Driver = new(ESX5Driver)
|
var _ vmwcommon.Driver = new(ESX5Driver)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestESX5Driver_implRemoteDriver(t *testing.T) {
|
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
|
// Interface to help find the host IP that is available from within
|
||||||
// the VMware virtual machines.
|
// the VMware virtual machines.
|
|
@ -1,4 +1,4 @@
|
||||||
package vmware
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
|
@ -1,4 +1,4 @@
|
||||||
package vmware
|
package iso
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package vmware
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
@ -8,6 +8,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
vmwcommon "github.com/mitchellh/packer/builder/vmware/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// VMnetNatConfIPFinder finds the IP address of the host machine by
|
// VMnetNatConfIPFinder finds the IP address of the host machine by
|
||||||
|
@ -16,7 +18,7 @@ import (
|
||||||
type VMnetNatConfIPFinder struct{}
|
type VMnetNatConfIPFinder struct{}
|
||||||
|
|
||||||
func (*VMnetNatConfIPFinder) HostIP() (string, error) {
|
func (*VMnetNatConfIPFinder) HostIP() (string, error) {
|
||||||
driver := &Workstation9Driver{}
|
driver := &vmwcommon.Workstation9Driver{}
|
||||||
|
|
||||||
vmnetnat := driver.VmnetnatConfPath()
|
vmnetnat := driver.VmnetnatConfPath()
|
||||||
if vmnetnat == "" {
|
if vmnetnat == "" {
|
|
@ -1,4 +1,4 @@
|
||||||
package vmware
|
package iso
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package vmware
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
@ -60,3 +60,7 @@ func (d *localOutputDir) RemoveAll() error {
|
||||||
func (d *localOutputDir) SetOutputDir(path string) {
|
func (d *localOutputDir) SetOutputDir(path string) {
|
||||||
d.dir = path
|
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 {
|
type RemoteDriver interface {
|
||||||
Driver
|
vmwcommon.Driver
|
||||||
|
|
||||||
// UploadISO uploads a local ISO to the remote side and returns the
|
// 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
|
// 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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
|
vmwcommon "github.com/mitchellh/packer/builder/vmware/common"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
@ -46,7 +47,7 @@ func (stepConfigureVNC) VNCAddress(portMin, portMax uint) (string, uint) {
|
||||||
|
|
||||||
func (s *stepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *stepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*config)
|
config := state.Get("config").(*config)
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(vmwcommon.Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vmxPath := state.Get("vmx_path").(string)
|
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)
|
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.enabled"] = "TRUE"
|
||||||
vmxData["remotedisplay.vnc.port"] = fmt.Sprintf("%d", vncPort)
|
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)
|
err := fmt.Errorf("Error writing VMX data: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
|
@ -1,8 +1,9 @@
|
||||||
package vmware
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
|
vmwcommon "github.com/mitchellh/packer/builder/vmware/common"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
@ -20,7 +21,7 @@ type stepCreateDisk struct{}
|
||||||
|
|
||||||
func (stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction {
|
func (stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*config)
|
config := state.Get("config").(*config)
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(vmwcommon.Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
ui.Say("Creating virtual machine disk")
|
ui.Say("Creating virtual machine disk")
|
|
@ -1,14 +1,13 @@
|
||||||
package vmware
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
|
vmwcommon "github.com/mitchellh/packer/builder/vmware/common"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type vmxTemplateData struct {
|
type vmxTemplateData struct {
|
||||||
|
@ -75,26 +74,6 @@ func (s *stepCreateVMX) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
return multistep.ActionHalt
|
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
|
vmxDir := config.OutputDir
|
||||||
if config.RemoteType != "" {
|
if config.RemoteType != "" {
|
||||||
// For remote builds, we just put the VMX in a temporary
|
// 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")
|
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)
|
err := fmt.Errorf("Error creating VMX file: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
|
@ -1,4 +1,4 @@
|
||||||
package vmware
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
|
@ -1,8 +1,9 @@
|
||||||
package vmware
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
|
vmwcommon "github.com/mitchellh/packer/builder/vmware/common"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -10,7 +11,7 @@ type stepPrepareTools struct{}
|
||||||
|
|
||||||
func (*stepPrepareTools) Run(state multistep.StateBag) multistep.StepAction {
|
func (*stepPrepareTools) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*config)
|
config := state.Get("config").(*config)
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(vmwcommon.Driver)
|
||||||
|
|
||||||
if config.ToolsUploadFlavor == "" {
|
if config.ToolsUploadFlavor == "" {
|
||||||
return multistep.ActionContinue
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
|
vmwcommon "github.com/mitchellh/packer/builder/vmware/common"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
"log"
|
"log"
|
||||||
)
|
)
|
||||||
|
@ -15,7 +16,7 @@ type stepRemoteUpload struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stepRemoteUpload) Run(state multistep.StateBag) multistep.StepAction {
|
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)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
remote, ok := driver.(RemoteDriver)
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/go-vnc"
|
"github.com/mitchellh/go-vnc"
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
|
vmwcommon "github.com/mitchellh/packer/builder/vmware/common"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
@ -36,7 +37,7 @@ type stepTypeBootCommand struct{}
|
||||||
|
|
||||||
func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction {
|
func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*config)
|
config := state.Get("config").(*config)
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(vmwcommon.Driver)
|
||||||
httpPort := state.Get("http_port").(uint)
|
httpPort := state.Get("http_port").(uint)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vncIp := state.Get("vnc_ip").(string)
|
vncIp := state.Get("vnc_ip").(string)
|
|
@ -1,4 +1,4 @@
|
||||||
package vmware
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"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),
|
"pp-vagrant-override": new(FixerVagrantPPOverride),
|
||||||
"virtualbox-gaattach": new(FixerVirtualBoxGAAttach),
|
"virtualbox-gaattach": new(FixerVirtualBoxGAAttach),
|
||||||
"virtualbox-rename": new(FixerVirtualBoxRename),
|
"virtualbox-rename": new(FixerVirtualBoxRename),
|
||||||
|
"vmware-rename": new(FixerVMwareRename),
|
||||||
}
|
}
|
||||||
|
|
||||||
FixerOrder = []string{
|
FixerOrder = []string{
|
||||||
|
@ -33,5 +34,6 @@ func init() {
|
||||||
"virtualbox-gaattach",
|
"virtualbox-gaattach",
|
||||||
"pp-vagrant-override",
|
"pp-vagrant-override",
|
||||||
"virtualbox-rename",
|
"virtualbox-rename",
|
||||||
|
"vmware-rename",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ func TestFixerVirtualBoxRename_Fix(t *testing.T) {
|
||||||
Input map[string]interface{}
|
Input map[string]interface{}
|
||||||
Expected map[string]interface{}
|
Expected map[string]interface{}
|
||||||
}{
|
}{
|
||||||
// No attach field
|
|
||||||
{
|
{
|
||||||
Input: map[string]interface{}{
|
Input: map[string]interface{}{
|
||||||
"type": "virtualbox",
|
"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",
|
"qemu": "packer-builder-qemu",
|
||||||
"virtualbox-iso": "packer-builder-virtualbox-iso",
|
"virtualbox-iso": "packer-builder-virtualbox-iso",
|
||||||
"virtualbox-ovf": "packer-builder-virtualbox-ovf",
|
"virtualbox-ovf": "packer-builder-virtualbox-ovf",
|
||||||
"vmware": "packer-builder-vmware"
|
"vmware-iso": "packer-builder-vmware-iso",
|
||||||
|
"vmware-vmx": "packer-builder-vmware-vmx"
|
||||||
},
|
},
|
||||||
|
|
||||||
"commands": {
|
"commands": {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/mitchellh/packer/builder/vmware"
|
"github.com/mitchellh/packer/builder/vmware/iso"
|
||||||
"github.com/mitchellh/packer/packer/plugin"
|
"github.com/mitchellh/packer/packer/plugin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -10,6 +10,6 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
server.RegisterBuilder(new(vmware.Builder))
|
server.RegisterBuilder(new(iso.Builder))
|
||||||
server.Serve()
|
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 {
|
func providerForName(name string) Provider {
|
||||||
switch name {
|
switch name {
|
||||||
|
case "aws":
|
||||||
|
return new(AWSProvider)
|
||||||
|
case "digitalocean":
|
||||||
|
return new(DigitalOceanProvider)
|
||||||
case "virtualbox":
|
case "virtualbox":
|
||||||
return new(VBoxProvider)
|
return new(VBoxProvider)
|
||||||
|
case "vmware":
|
||||||
|
return new(VMwareProvider)
|
||||||
default:
|
default:
|
||||||
return nil
|
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"
|
layout: "docs"
|
||||||
|
page_title: "VMware Builder"
|
||||||
---
|
---
|
||||||
|
|
||||||
# VMware Builder
|
# VMware Builder
|
||||||
|
|
||||||
Type: `vmware`
|
The VMware builder is able to create VMware virtual machines for use
|
||||||
|
with any VMware product.
|
||||||
The VMware builder is able to create VMware virtual machines. It currently
|
|
||||||
supports building virtual machines on hosts running
|
Packer actually comes with multiple builders able to create VMware
|
||||||
[VMware Fusion](http://www.vmware.com/products/fusion/overview.html) for OS X,
|
machines, depending on the strategy you want to use to build the image.
|
||||||
[VMware Workstation](http://www.vmware.com/products/workstation/overview.html)
|
Packer supports the following VMware builders:
|
||||||
for Linux and Windows, and
|
|
||||||
[VMware Player](http://www.vmware.com/products/player/) on Linux. It can
|
* [vmware-iso](/docs/builders/vmware-iso.html) - Starts from
|
||||||
also build machines directly on
|
an ISO file, creates a brand new VMware VM, installs an OS,
|
||||||
[VMware vSphere Hypervisor](http://www.vmware.com/products/vsphere-hypervisor/)
|
provisions software within the OS, then exports that machine to create
|
||||||
using SSH as opposed to the vSphere API.
|
an image. This is best for people who want to start from scratch.
|
||||||
|
|
||||||
The builder builds a virtual machine by creating a new virtual machine
|
* [vmware-vmx](/docs/builders/vmware-vmx.html) - This builder
|
||||||
from scratch, booting it, installing an OS, provisioning software within
|
imports an existing VMware machine (from a VMX file), runs provisioners
|
||||||
the OS, then shutting it down. The result of the VMware builder is a directory
|
on top of that VM, and exports that machine to create an image.
|
||||||
containing all the files necessary to run the virtual machine.
|
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
|
||||||
## Basic Example
|
builder back into Packer to iterate on a machine.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
Loading…
Reference in New Issue