Merge pull request #6927 from hashicorp/rebased_4591

Extend vmware-vmx builder to allow esxi builds. (Rebase of PR #4591)
This commit is contained in:
Megan Marsh 2018-11-06 09:59:26 -08:00 committed by GitHub
commit 8567be43d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 594 additions and 453 deletions

View File

@ -2,68 +2,87 @@ package common
import (
"fmt"
"os"
"path/filepath"
"strconv"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// BuilderId for the local artifacts
const BuilderId = "mitchellh.vmware"
const (
// BuilderId for the local artifacts
BuilderId = "mitchellh.vmware"
BuilderIdESX = "mitchellh.vmware-esx"
ArtifactConfFormat = "artifact.conf.format"
ArtifactConfKeepRegistered = "artifact.conf.keep_registered"
ArtifactConfSkipExport = "artifact.conf.skip_export"
)
// Artifact is the result of running the VMware builder, namely a set
// of files associated with the resulting machine.
type localArtifact struct {
type artifact struct {
builderId string
id string
dir string
dir OutputDir
f []string
config map[string]string
}
// NewLocalArtifact returns a VMware artifact containing the files
// in the given directory.
func NewLocalArtifact(id string, dir string) (packer.Artifact, error) {
files := make([]string, 0, 5)
visit := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
files = append(files, path)
}
return nil
}
if err := filepath.Walk(dir, visit); err != nil {
return nil, err
}
return &localArtifact{
id: id,
dir: dir,
f: files,
}, nil
func (a *artifact) BuilderId() string {
return a.builderId
}
func (a *localArtifact) BuilderId() string {
return BuilderId
}
func (a *localArtifact) Files() []string {
func (a *artifact) Files() []string {
return a.f
}
func (a *localArtifact) Id() string {
func (a *artifact) Id() string {
return a.id
}
func (a *localArtifact) String() string {
func (a *artifact) String() string {
return fmt.Sprintf("VM files in directory: %s", a.dir)
}
func (a *localArtifact) State(name string) interface{} {
return nil
func (a *artifact) State(name string) interface{} {
return a.config[name]
}
func (a *localArtifact) Destroy() error {
return os.RemoveAll(a.dir)
func (a *artifact) Destroy() error {
return a.dir.RemoveAll()
}
func NewArtifact(remoteType string, format string, exportOutputPath string, vmName string, skipExport bool, keepRegistered bool, state multistep.StateBag) (packer.Artifact, error) {
var files []string
var dir OutputDir
var err error
if remoteType != "" && !skipExport {
dir = new(LocalOutputDir)
dir.SetOutputDir(exportOutputPath)
files, err = dir.ListFiles()
} else {
files, err = state.Get("dir").(OutputDir).ListFiles()
}
if err != nil {
return nil, err
}
// Set the proper builder ID
builderId := BuilderId
if remoteType != "" {
builderId = BuilderIdESX
}
config := make(map[string]string)
config[ArtifactConfKeepRegistered] = strconv.FormatBool(keepRegistered)
config[ArtifactConfFormat] = format
config[ArtifactConfSkipExport] = strconv.FormatBool(skipExport)
return &artifact{
builderId: builderId,
id: vmName,
dir: dir,
f: files,
config: config,
}, nil
}

View File

@ -1,46 +1,11 @@
package common
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/hashicorp/packer/packer"
)
func TestLocalArtifact_impl(t *testing.T) {
var _ packer.Artifact = new(localArtifact)
}
func TestNewLocalArtifact(t *testing.T) {
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
err = ioutil.WriteFile(filepath.Join(td, "a"), []byte("foo"), 0644)
if err != nil {
t.Fatalf("err: %s", err)
}
if err := os.Mkdir(filepath.Join(td, "b"), 0755); err != nil {
t.Fatalf("err: %s", err)
}
a, err := NewLocalArtifact("vm1", td)
if err != nil {
t.Fatalf("err: %s", err)
}
if a.BuilderId() != BuilderId {
t.Fatalf("bad: %#v", a.BuilderId())
}
if a.Id() != "vm1" {
t.Fatalf("bad: %#v", a.Id())
}
if len(a.Files()) != 1 {
t.Fatalf("should length 1: %d", len(a.Files()))
}
var _ packer.Artifact = new(artifact)
}

View File

@ -81,9 +81,26 @@ type Driver interface {
// NewDriver returns a new driver implementation for this operating
// system, or an error if the driver couldn't be initialized.
func NewDriver(dconfig *DriverConfig, config *SSHConfig) (Driver, error) {
func NewDriver(dconfig *DriverConfig, config *SSHConfig, vmName string) (Driver, error) {
drivers := []Driver{}
if dconfig.RemoteType != "" {
drivers = []Driver{
&ESX5Driver{
Host: dconfig.RemoteHost,
Port: dconfig.RemotePort,
Username: dconfig.RemoteUser,
Password: dconfig.RemotePassword,
PrivateKeyFile: dconfig.RemotePrivateKey,
Datastore: dconfig.RemoteDatastore,
CacheDatastore: dconfig.RemoteCacheDatastore,
CacheDirectory: dconfig.RemoteCacheDirectory,
VMName: vmName,
CommConfig: config.Comm,
},
}
} else {
switch runtime.GOOS {
case "darwin":
drivers = []Driver{
@ -122,6 +139,7 @@ func NewDriver(dconfig *DriverConfig, config *SSHConfig) (Driver, error) {
default:
return nil, fmt.Errorf("can't find driver for OS: %s", runtime.GOOS)
}
}
errs := ""
for _, driver := range drivers {

View File

@ -8,6 +8,15 @@ import (
type DriverConfig struct {
FusionAppPath string `mapstructure:"fusion_app_path"`
RemoteType string `mapstructure:"remote_type"`
RemoteDatastore string `mapstructure:"remote_datastore"`
RemoteCacheDatastore string `mapstructure:"remote_cache_datastore"`
RemoteCacheDirectory string `mapstructure:"remote_cache_directory"`
RemoteHost string `mapstructure:"remote_host"`
RemotePort uint `mapstructure:"remote_port"`
RemoteUser string `mapstructure:"remote_username"`
RemotePassword string `mapstructure:"remote_password"`
RemotePrivateKey string `mapstructure:"remote_private_key_file"`
}
func (c *DriverConfig) Prepare(ctx *interpolate.Context) []error {
@ -17,6 +26,21 @@ func (c *DriverConfig) Prepare(ctx *interpolate.Context) []error {
if c.FusionAppPath == "" {
c.FusionAppPath = "/Applications/VMware Fusion.app"
}
if c.RemoteUser == "" {
c.RemoteUser = "root"
}
if c.RemoteDatastore == "" {
c.RemoteDatastore = "datastore1"
}
if c.RemoteCacheDatastore == "" {
c.RemoteCacheDatastore = c.RemoteDatastore
}
if c.RemoteCacheDirectory == "" {
c.RemoteCacheDirectory = "packer_cache"
}
if c.RemotePort == 0 {
c.RemotePort = 22
}
return nil
}

View File

@ -1,4 +1,4 @@
package iso
package common
import (
"bufio"
@ -10,13 +10,14 @@ import (
"log"
"net"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"time"
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
"github.com/hashicorp/packer/communicator/ssh"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
helperssh "github.com/hashicorp/packer/helper/ssh"
"github.com/hashicorp/packer/packer"
@ -26,7 +27,7 @@ import (
// ESX5 driver talks to an ESXi5 hypervisor remotely over SSH to build
// virtual machines. This driver can only manage one machine at a time.
type ESX5Driver struct {
base vmwcommon.VmwareDriver
base VmwareDriver
Host string
Port uint
@ -36,6 +37,8 @@ type ESX5Driver struct {
Datastore string
CacheDatastore string
CacheDirectory string
VMName string
CommConfig communicator.Config
comm packer.Communicator
outputDir string
@ -43,7 +46,61 @@ type ESX5Driver struct {
}
func (d *ESX5Driver) Clone(dst, src string, linked bool) error {
return errors.New("Cloning is not supported with the ESX driver.")
linesToArray := func(lines string) []string { return strings.Split(strings.Trim(lines, "\n"), "\n") }
d.SetOutputDir(path.Dir(filepath.ToSlash(dst)))
srcVmx := d.datastorePath(src)
dstVmx := d.datastorePath(dst)
srcDir := path.Dir(srcVmx)
dstDir := path.Dir(dstVmx)
log.Printf("Source: %s\n", srcVmx)
log.Printf("Dest: %s\n", dstVmx)
err := d.MkdirAll()
if err != nil {
return fmt.Errorf("Failed to create the destination directory %s: %s", d.outputDir, err)
}
err = d.sh("cp", strconv.Quote(srcVmx), strconv.Quote(dstVmx))
if err != nil {
return fmt.Errorf("Failed to copy the vmx file %s: %s", srcVmx, err)
}
filesToClone, err := d.run(nil, "find", strconv.Quote(srcDir), "! -name '*.vmdk' ! -name '*.vmx' -type f ! -size 0")
if err != nil {
return fmt.Errorf("Failed to get the file list to copy: %s", err)
}
for _, f := range linesToArray(filesToClone) {
// TODO: linesToArray should really return [] if the string is empty. Instead it returns [""]
if f == "" {
continue
}
err := d.sh("cp", strconv.Quote(f), strconv.Quote(dstDir))
if err != nil {
return fmt.Errorf("Failing to copy %s to %s: %s", f, dstDir, err)
}
}
disksToClone, err := d.run(nil, "sed -ne 's/.*file[Nn]ame = \"\\(.*vmdk\\)\"/\\1/p'", strconv.Quote(srcVmx))
if err != nil {
return fmt.Errorf("Failing to get the vmdk list to clone %s", err)
}
for _, disk := range linesToArray(disksToClone) {
srcDisk := path.Join(srcDir, disk)
if path.IsAbs(disk) {
srcDisk = disk
}
destDisk := path.Join(dstDir, path.Base(disk))
err = d.sh("vmkfstools", "-d thin", "-i", strconv.Quote(srcDisk), strconv.Quote(destDisk))
if err != nil {
return fmt.Errorf("Failing to clone disk %s: %s", srcDisk, err)
}
}
log.Printf("Successfully cloned %s to %s\n", src, dst)
return nil
}
func (d *ESX5Driver) CompactDisk(diskPathLocal string) error {
@ -65,7 +122,11 @@ func (d *ESX5Driver) IsRunning(string) (bool, error) {
}
func (d *ESX5Driver) ReloadVM() error {
if d.vmId != "" {
return d.sh("vim-cmd", "vmsvc/reload", d.vmId)
} else {
return nil
}
}
func (d *ESX5Driver) Start(vmxPathLocal string, headless bool) error {
@ -122,13 +183,13 @@ func (d *ESX5Driver) IsDestroyed() (bool, error) {
}
func (d *ESX5Driver) UploadISO(localPath string, checksum string, checksumType string) (string, error) {
finalPath := d.cachePath(localPath)
finalPath := d.CachePath(localPath)
if err := d.mkdir(filepath.ToSlash(filepath.Dir(finalPath))); err != nil {
return "", err
}
log.Printf("Verifying checksum of %s", finalPath)
if d.verifyChecksum(checksumType, checksum, finalPath) {
if d.VerifyChecksum(checksumType, checksum, finalPath) {
log.Println("Initial checksum matched, no upload needed.")
return finalPath, nil
}
@ -141,7 +202,7 @@ func (d *ESX5Driver) UploadISO(localPath string, checksum string, checksumType s
}
func (d *ESX5Driver) RemoveCache(localPath string) error {
finalPath := d.cachePath(localPath)
finalPath := d.CachePath(localPath)
log.Printf("Removing remote cache path %s (local %s)", finalPath, localPath)
return d.sh("rm", "-f", strconv.Quote(finalPath))
}
@ -375,14 +436,18 @@ func (ESX5Driver) UpdateVMX(_, password string, port uint, data map[string]strin
}
func (d *ESX5Driver) CommHost(state multistep.StateBag) (string, error) {
config := state.Get("config").(*Config)
sshc := config.SSHConfig.Comm
sshc := state.Get("sshConfig").(*SSHConfig).Comm
port := sshc.SSHPort
if sshc.Type == "winrm" {
port = sshc.WinRMPort
}
if address := config.CommConfig.Host(); address != "" {
if address, ok := state.GetOk("vm_address"); ok {
return address.(string), nil
}
if address := d.CommConfig.Host(); address != "" {
state.Put("vm_address", address)
return address, nil
}
@ -396,7 +461,12 @@ func (d *ESX5Driver) CommHost(state multistep.StateBag) (string, error) {
var displayName string
if v, ok := state.GetOk("display_name"); ok {
displayName = v.(string)
} else {
displayName = strings.Replace(d.VMName, " ", "_", -1)
log.Printf("No display_name set; falling back to using VMName %s "+
"to look for SSH IP", displayName)
}
record, err := r.find("Name", displayName)
if err != nil {
return "", err
@ -432,14 +502,12 @@ func (d *ESX5Driver) CommHost(state multistep.StateBag) (string, error) {
if e.Timeout() {
log.Printf("Timeout connecting to %s", record["IPAddress"])
continue
} else if strings.Contains(e.Error(), "connection refused") {
log.Printf("Connection refused when connecting to: %s", record["IPAddress"])
continue
}
}
} else {
defer conn.Close()
address := record["IPAddress"]
state.Put("vm_address", address)
return address, nil
}
}
@ -456,7 +524,7 @@ func (d *ESX5Driver) DirExists() (bool, error) {
}
func (d *ESX5Driver) ListFiles() ([]string, error) {
stdout, err := d.ssh("ls -1p "+d.outputDir, nil)
stdout, err := d.ssh("ls -1p "+strconv.Quote(d.outputDir), nil)
if err != nil {
return nil, err
}
@ -503,7 +571,7 @@ func (d *ESX5Driver) datastorePath(path string) string {
return filepath.ToSlash(filepath.Join("/vmfs/volumes", d.Datastore, dirPath, filepath.Base(path)))
}
func (d *ESX5Driver) cachePath(path string) string {
func (d *ESX5Driver) CachePath(path string) string {
return filepath.ToSlash(filepath.Join("/vmfs/volumes", d.CacheDatastore, d.CacheDirectory, filepath.Base(path)))
}
@ -592,7 +660,16 @@ func (d *ESX5Driver) upload(dst, src string) error {
return d.comm.Upload(dst, f, nil)
}
func (d *ESX5Driver) verifyChecksum(ctype string, hash string, file string) bool {
func (d *ESX5Driver) Download(src, dst string) error {
file, err := os.Create(dst)
if err != nil {
return err
}
defer file.Close()
return d.comm.Download(d.datastorePath(src), file)
}
func (d *ESX5Driver) VerifyChecksum(ctype string, hash string, file string) bool {
if ctype == "none" {
if err := d.sh("stat", strconv.Quote(file)); err != nil {
return false
@ -661,7 +738,7 @@ func (d *ESX5Driver) esxcli(args ...string) (*esxcliReader, error) {
return &esxcliReader{r, header}, nil
}
func (d *ESX5Driver) GetVmwareDriver() vmwcommon.VmwareDriver {
func (d *ESX5Driver) GetVmwareDriver() VmwareDriver {
return d.base
}

View File

@ -1,16 +1,17 @@
package iso
package common
import (
"fmt"
"net"
"testing"
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
)
func TestESX5Driver_implDriver(t *testing.T) {
var _ vmwcommon.Driver = new(ESX5Driver)
var _ Driver = new(ESX5Driver)
}
func TestESX5Driver_UpdateVMX(t *testing.T) {
@ -30,11 +31,11 @@ func TestESX5Driver_UpdateVMX(t *testing.T) {
}
func TestESX5Driver_implOutputDir(t *testing.T) {
var _ vmwcommon.OutputDir = new(ESX5Driver)
var _ OutputDir = new(ESX5Driver)
}
func TestESX5Driver_implVNCAddressFinder(t *testing.T) {
var _ vmwcommon.VNCAddressFinder = new(ESX5Driver)
var _ VNCAddressFinder = new(ESX5Driver)
}
func TestESX5Driver_implRemoteDriver(t *testing.T) {
@ -60,28 +61,19 @@ func TestESX5Driver_HostIP(t *testing.T) {
func TestESX5Driver_CommHost(t *testing.T) {
const expected_host = "127.0.0.1"
config := testConfig()
config["communicator"] = "winrm"
config["winrm_username"] = "username"
config["winrm_password"] = "password"
config["winrm_host"] = expected_host
var b Builder
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if host := b.config.CommConfig.Host(); host != expected_host {
t.Fatalf("setup failed, bad host name: %s", host)
}
conf := make(map[string]interface{})
conf["communicator"] = "winrm"
conf["winrm_username"] = "username"
conf["winrm_password"] = "password"
conf["winrm_host"] = expected_host
var commConfig communicator.Config
err := config.Decode(&commConfig, nil, conf)
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
sshConfig := SSHConfig{Comm: commConfig}
state.Put("sshConfig", &sshConfig)
driver := ESX5Driver{CommConfig: *(&sshConfig.Comm)}
var driver ESX5Driver
host, err := driver.CommHost(state)
if err != nil {
t.Fatalf("should not have error: %s", err)
@ -89,4 +81,11 @@ func TestESX5Driver_CommHost(t *testing.T) {
if host != expected_host {
t.Errorf("bad host name: %s", host)
}
address, ok := state.GetOk("vm_address")
if !ok {
t.Error("state not updated with vm_address")
}
if address.(string) != expected_host {
t.Errorf("bad vm_address: %s", address.(string))
}
}

View File

@ -0,0 +1,26 @@
package common
import (
"fmt"
"github.com/hashicorp/packer/template/interpolate"
)
type ExportConfig struct {
Format string `mapstructure:"format"`
OVFToolOptions []string `mapstructure:"ovftool_options"`
SkipExport bool `mapstructure:"skip_export"`
KeepRegistered bool `mapstructure:"keep_registered"`
SkipCompaction bool `mapstructure:"skip_compaction"`
}
func (c *ExportConfig) Prepare(ctx *interpolate.Context) []error {
var errs []error
if c.Format != "" {
if !(c.Format == "ova" || c.Format == "ovf" || c.Format == "vmx") {
errs = append(
errs, fmt.Errorf("format must be one of ova, ovf, or vmx"))
}
}
return errs
}

View File

@ -4,6 +4,7 @@ package common
// of the output directory for VMware-based products. The abstraction is made
// so that the output directory can be properly made on remote (ESXi) based
// VMware products as well as local.
// For remote builds, OutputDir interface is satisfied by the ESX5Driver.
type OutputDir interface {
DirExists() (bool, error)
ListFiles() ([]string, error)

View File

@ -1,11 +1,7 @@
package iso
import (
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
)
package common
type RemoteDriver interface {
vmwcommon.Driver
Driver
// UploadISO uploads a local ISO to the remote side and returns the
// new path that should be used in the VMX along with an error if it
@ -30,6 +26,9 @@ type RemoteDriver interface {
// Uploads a local file to remote side.
upload(dst, src string) error
// Download a remote file to a local file.
Download(src, dst string) error
// Reload VM on remote side.
ReloadVM() error
}

View File

@ -1,11 +1,7 @@
package iso
import (
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
)
package common
type RemoteDriverMock struct {
vmwcommon.DriverMock
DriverMock
UploadISOCalled bool
UploadISOPath string
@ -27,7 +23,8 @@ type RemoteDriverMock struct {
IsDestroyedResult bool
IsDestroyedErr error
uploadErr error
UploadErr error
DownloadErr error
ReloadVMErr error
}
@ -61,7 +58,11 @@ func (d *RemoteDriverMock) IsDestroyed() (bool, error) {
}
func (d *RemoteDriverMock) upload(dst, src string) error {
return d.uploadErr
return d.UploadErr
}
func (d *RemoteDriverMock) Download(src, dst string) error {
return d.DownloadErr
}
func (d *RemoteDriverMock) RemoveCache(localPath string) error {

View File

@ -0,0 +1,10 @@
package common
import (
"testing"
)
func TestRemoteDriverMock_impl(t *testing.T) {
var _ Driver = new(RemoteDriverMock)
var _ RemoteDriver = new(RemoteDriverMock)
}

View File

@ -22,12 +22,16 @@ import (
type StepConfigureVMX struct {
CustomData map[string]string
SkipFloppy bool
VMName string
}
func (s *StepConfigureVMX) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vmxPath := state.Get("vmx_path").(string)
log.Printf("Configuring VMX...\n")
var err error
ui := state.Get("ui").(packer.Ui)
vmxPath := state.Get("vmx_path").(string)
vmxData, err := ReadVMX(vmxPath)
if err != nil {
err := fmt.Errorf("Error reading VMX file: %s", err)
@ -69,7 +73,9 @@ func (s *StepConfigureVMX) Run(_ context.Context, state multistep.StateBag) mult
}
}
if err := WriteVMX(vmxPath, vmxData); err != nil {
err = WriteVMX(vmxPath, vmxData)
if err != nil {
err := fmt.Errorf("Error writing VMX file: %s", err)
state.Put("error", err)
ui.Error(err.Error())

View File

@ -1,4 +1,4 @@
package iso
package common
import (
"bytes"
@ -21,10 +21,12 @@ import (
type StepExport struct {
Format string
SkipExport bool
VMName string
OVFToolOptions []string
OutputDir string
}
func (s *StepExport) generateArgs(c *Config, displayName string, hidePassword bool) []string {
func (s *StepExport) generateArgs(c *DriverConfig, displayName string, hidePassword bool) []string {
password := url.QueryEscape(c.RemotePassword)
if hidePassword {
password = "****"
@ -33,18 +35,19 @@ func (s *StepExport) generateArgs(c *Config, displayName string, hidePassword bo
"--noSSLVerify=true",
"--skipManifestCheck",
"-tt=" + s.Format,
"vi://" + c.RemoteUser + ":" + password + "@" + c.RemoteHost + "/" + displayName,
s.OutputDir,
}
return append(c.OVFToolOptions, args...)
return append(s.OVFToolOptions, args...)
}
func (s *StepExport) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
c := state.Get("config").(*Config)
c := state.Get("driverConfig").(*DriverConfig)
ui := state.Get("ui").(packer.Ui)
// Skip export if requested
if c.SkipExport {
if s.SkipExport {
ui.Say("Skipping export of virtual machine...")
return multistep.ActionContinue
}
@ -60,7 +63,7 @@ func (s *StepExport) Run(_ context.Context, state multistep.StateBag) multistep.
}
if _, err := exec.LookPath(ovftool); err != nil {
err := fmt.Errorf("Error %s not found: %s", ovftool, err)
err = fmt.Errorf("Error %s not found: %s", ovftool, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
@ -68,7 +71,7 @@ func (s *StepExport) Run(_ context.Context, state multistep.StateBag) multistep.
// Export the VM
if s.OutputDir == "" {
s.OutputDir = c.VMName + "." + s.Format
s.OutputDir = s.VMName + "." + s.Format
}
if s.Format == "ova" {

View File

@ -1,4 +1,4 @@
package iso
package common
import (
"context"
@ -15,9 +15,9 @@ func testStepExport_wrongtype_impl(t *testing.T, remoteType string) {
state := testState(t)
step := new(StepExport)
var config Config
var config DriverConfig
config.RemoteType = "foo"
state.Put("config", &config)
state.Put("driverConfig", &config)
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)

View File

@ -1,11 +1,10 @@
package iso
package common
import (
"context"
"fmt"
"time"
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@ -13,11 +12,14 @@ import (
type StepRegister struct {
registeredPath string
Format string
KeepRegistered bool
SkipExport bool
}
func (s *StepRegister) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(vmwcommon.Driver)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmxPath := state.Get("vmx_path").(string)
if remoteDriver, ok := driver.(RemoteDriver); ok {
@ -40,19 +42,18 @@ func (s *StepRegister) Cleanup(state multistep.StateBag) {
return
}
driver := state.Get("driver").(vmwcommon.Driver)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(*Config)
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if (config.KeepRegistered) && (!cancelled && !halted) {
if (s.KeepRegistered) && (!cancelled && !halted) {
ui.Say("Keeping virtual machine registered with ESX host (keep_registered = true)")
return
}
if remoteDriver, ok := driver.(RemoteDriver); ok {
if s.Format == "" || config.SkipExport {
if s.SkipExport {
ui.Say("Unregistering virtual machine...")
if err := remoteDriver.Unregister(s.registeredPath); err != nil {
ui.Error(fmt.Sprintf("Error unregistering VM: %s", err))
@ -70,7 +71,7 @@ func (s *StepRegister) Cleanup(state multistep.StateBag) {
if destroyed {
break
}
time.Sleep(150 * time.Millisecond)
time.Sleep(1 * time.Second)
}
}
}

View File

@ -1,4 +1,4 @@
package iso
package common
import (
"context"
@ -31,12 +31,12 @@ func TestStepRegister_regularDriver(t *testing.T) {
func TestStepRegister_remoteDriver(t *testing.T) {
state := testState(t)
step := new(StepRegister)
step := &StepRegister{
KeepRegistered: false,
SkipExport: true,
}
driver := new(RemoteDriverMock)
var config Config
config.KeepRegistered = false
state.Put("config", &config)
state.Put("driver", driver)
state.Put("vmx_path", "foo")
@ -71,12 +71,9 @@ func TestStepRegister_remoteDriver(t *testing.T) {
}
func TestStepRegister_WithoutUnregister_remoteDriver(t *testing.T) {
state := testState(t)
step := new(StepRegister)
step := &StepRegister{KeepRegistered: true}
driver := new(RemoteDriverMock)
var config Config
config.KeepRegistered = true
state.Put("config", &config)
state.Put("driver", driver)
state.Put("vmx_path", "foo")

View File

@ -70,6 +70,7 @@ func (s *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag)
// Connect to VNC
ui.Say(fmt.Sprintf("Connecting to VM via VNC (%s:%d)", vncIp, vncPort))
nc, err := net.Dial("tcp", fmt.Sprintf("%s:%d", vncIp, vncPort))
if err != nil {
err := fmt.Errorf("Error connecting to VNC: %s", err)

View File

@ -1,11 +1,10 @@
package iso
package common
import (
"context"
"fmt"
"path/filepath"
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@ -24,7 +23,7 @@ type StepUploadVMX struct {
}
func (c *StepUploadVMX) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(vmwcommon.Driver)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmxPath := state.Get("vmx_path").(string)

View File

@ -1,45 +0,0 @@
package iso
import (
"fmt"
)
const (
ArtifactConfFormat = "artifact.conf.format"
ArtifactConfKeepRegistered = "artifact.conf.keep_registered"
ArtifactConfSkipExport = "artifact.conf.skip_export"
)
// Artifact is the result of running the VMware builder, namely a set
// of files associated with the resulting machine.
type Artifact struct {
builderId string
id string
dir OutputDir
f []string
config map[string]string
}
func (a *Artifact) BuilderId() string {
return a.builderId
}
func (a *Artifact) Files() []string {
return a.f
}
func (a *Artifact) Id() string {
return a.id
}
func (a *Artifact) String() string {
return fmt.Sprintf("VM files in directory: %s", a.dir)
}
func (a *Artifact) State(name string) interface{} {
return a.config[name]
}
func (a *Artifact) Destroy() error {
return a.dir.RemoveAll()
}

View File

@ -1,15 +0,0 @@
package iso
import (
"testing"
"github.com/hashicorp/packer/packer"
)
func TestArtifact_Impl(t *testing.T) {
var raw interface{}
raw = &Artifact{}
if _, ok := raw.(packer.Artifact); !ok {
t.Fatal("Artifact must be a proper artifact")
}
}

View File

@ -6,7 +6,6 @@ import (
"io/ioutil"
"log"
"os"
"strconv"
"time"
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
@ -19,8 +18,6 @@ import (
"github.com/hashicorp/packer/template/interpolate"
)
const BuilderIdESX = "mitchellh.vmware-esx"
type Builder struct {
config Config
runner multistep.Runner
@ -39,6 +36,7 @@ type Config struct {
vmwcommon.SSHConfig `mapstructure:",squash"`
vmwcommon.ToolsConfig `mapstructure:",squash"`
vmwcommon.VMXConfig `mapstructure:",squash"`
vmwcommon.ExportConfig `mapstructure:",squash"`
// disk drives
AdditionalDiskSize []uint `mapstructure:"disk_additional_size"`
@ -68,27 +66,9 @@ type Config struct {
Serial string `mapstructure:"serial"`
Parallel string `mapstructure:"parallel"`
// booting a guest
KeepRegistered bool `mapstructure:"keep_registered"`
OVFToolOptions []string `mapstructure:"ovftool_options"`
SkipCompaction bool `mapstructure:"skip_compaction"`
SkipExport bool `mapstructure:"skip_export"`
VMXDiskTemplatePath string `mapstructure:"vmx_disk_template_path"`
VMXTemplatePath string `mapstructure:"vmx_template_path"`
// remote vsphere
RemoteType string `mapstructure:"remote_type"`
RemoteDatastore string `mapstructure:"remote_datastore"`
RemoteCacheDatastore string `mapstructure:"remote_cache_datastore"`
RemoteCacheDirectory string `mapstructure:"remote_cache_directory"`
RemoteHost string `mapstructure:"remote_host"`
RemotePort uint `mapstructure:"remote_port"`
RemoteUser string `mapstructure:"remote_username"`
RemotePassword string `mapstructure:"remote_password"`
RemotePrivateKey string `mapstructure:"remote_private_key_file"`
CommConfig communicator.Config `mapstructure:",squash"`
ctx interpolate.Context
}
@ -125,6 +105,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
errs = packer.MultiErrorAppend(errs, b.config.VMXConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.VNCConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.ExportConfig.Prepare(&b.config.ctx)...)
if b.config.DiskName == "" {
b.config.DiskName = "disk"
@ -175,26 +156,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.Version = "9"
}
if b.config.RemoteUser == "" {
b.config.RemoteUser = "root"
}
if b.config.RemoteDatastore == "" {
b.config.RemoteDatastore = "datastore1"
}
if b.config.RemoteCacheDatastore == "" {
b.config.RemoteCacheDatastore = b.config.RemoteDatastore
}
if b.config.RemoteCacheDirectory == "" {
b.config.RemoteCacheDirectory = "packer_cache"
}
if b.config.RemotePort == 0 {
b.config.RemotePort = 22
}
if b.config.VMXTemplatePath != "" {
if err := b.validateVMXTemplatePath(); err != nil {
errs = packer.MultiErrorAppend(
@ -221,6 +182,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("remote_host must be specified"))
}
if b.config.RemoteType != "esx5" {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Only 'esx5' value is accepted for remote_type"))
@ -262,20 +224,22 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
}
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
driver, err := NewDriver(&b.config)
driver, err := vmwcommon.NewDriver(&b.config.DriverConfig, &b.config.SSHConfig, b.config.VMName)
if err != nil {
return nil, fmt.Errorf("Failed creating VMware driver: %s", err)
}
// Determine the output dir implementation
var dir OutputDir
var dir vmwcommon.OutputDir
switch d := driver.(type) {
case OutputDir:
case vmwcommon.OutputDir:
dir = d
default:
dir = new(vmwcommon.LocalOutputDir)
}
// The OutputDir will track remote esxi output; exportOutputPath preserves
// the path to the output on the machine running Packer.
exportOutputPath := b.config.OutputDir
if b.config.RemoteType != "" {
@ -292,6 +256,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
state.Put("driver", driver)
state.Put("hook", hook)
state.Put("ui", ui)
state.Put("sshConfig", &b.config.SSHConfig)
state.Put("driverConfig", &b.config.DriverConfig)
steps := []multistep.Step{
&vmwcommon.StepPrepareTools{
@ -327,6 +293,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&stepCreateVMX{},
&vmwcommon.StepConfigureVMX{
CustomData: b.config.VMXData,
VMName: b.config.VMName,
},
&vmwcommon.StepSuppressMessages{},
&common.StepHTTPServer{
@ -341,8 +308,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
VNCPortMax: b.config.VNCPortMax,
VNCDisablePassword: b.config.VNCDisablePassword,
},
&StepRegister{
&vmwcommon.StepRegister{
Format: b.config.Format,
KeepRegistered: b.config.KeepRegistered,
SkipExport: b.config.SkipExport,
},
&vmwcommon.StepRun{
DurationBeforeStop: 5 * time.Second,
@ -382,17 +351,20 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&vmwcommon.StepConfigureVMX{
CustomData: b.config.VMXDataPost,
SkipFloppy: true,
VMName: b.config.VMName,
},
&vmwcommon.StepCleanVMX{
RemoveEthernetInterfaces: b.config.VMXConfig.VMXRemoveEthernet,
VNCEnabled: !b.config.DisableVNC,
},
&StepUploadVMX{
&vmwcommon.StepUploadVMX{
RemoteType: b.config.RemoteType,
},
&StepExport{
&vmwcommon.StepExport{
Format: b.config.Format,
SkipExport: b.config.SkipExport,
VMName: b.config.VMName,
OVFToolOptions: b.config.OVFToolOptions,
OutputDir: exportOutputPath,
},
}
@ -416,36 +388,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
// Compile the artifact list
var files []string
if b.config.RemoteType != "" && b.config.Format != "" && !b.config.SkipExport {
dir = new(vmwcommon.LocalOutputDir)
dir.SetOutputDir(exportOutputPath)
files, err = dir.ListFiles()
} else {
files, err = state.Get("dir").(OutputDir).ListFiles()
}
if err != nil {
return nil, err
}
// Set the proper builder ID
builderId := vmwcommon.BuilderId
if b.config.RemoteType != "" {
builderId = BuilderIdESX
}
config := make(map[string]string)
config[ArtifactConfKeepRegistered] = strconv.FormatBool(b.config.KeepRegistered)
config[ArtifactConfFormat] = b.config.Format
config[ArtifactConfSkipExport] = strconv.FormatBool(b.config.SkipExport)
return &Artifact{
builderId: builderId,
id: b.config.VMName,
dir: dir,
f: files,
config: config,
}, nil
return vmwcommon.NewArtifact(b.config.RemoteType, b.config.Format, exportOutputPath,
b.config.VMName, b.config.SkipExport, b.config.KeepRegistered, state)
}
func (b *Builder) Cancel() {

View File

@ -459,13 +459,13 @@ func TestBuilderPrepare_CommConfig(t *testing.T) {
t.Fatalf("should not have error: %s", err)
}
if b.config.CommConfig.WinRMUser != "username" {
t.Errorf("bad winrm_username: %s", b.config.CommConfig.WinRMUser)
if b.config.SSHConfig.Comm.WinRMUser != "username" {
t.Errorf("bad winrm_username: %s", b.config.SSHConfig.Comm.WinRMUser)
}
if b.config.CommConfig.WinRMPassword != "password" {
t.Errorf("bad winrm_password: %s", b.config.CommConfig.WinRMPassword)
if b.config.SSHConfig.Comm.WinRMPassword != "password" {
t.Errorf("bad winrm_password: %s", b.config.SSHConfig.Comm.WinRMPassword)
}
if host := b.config.CommConfig.Host(); host != "1.2.3.4" {
if host := b.config.SSHConfig.Comm.Host(); host != "1.2.3.4" {
t.Errorf("bad host: %s", host)
}
}
@ -487,13 +487,13 @@ func TestBuilderPrepare_CommConfig(t *testing.T) {
t.Fatalf("should not have error: %s", err)
}
if b.config.CommConfig.SSHUsername != "username" {
t.Errorf("bad ssh_username: %s", b.config.CommConfig.SSHUsername)
if b.config.SSHConfig.Comm.SSHUsername != "username" {
t.Errorf("bad ssh_username: %s", b.config.SSHConfig.Comm.SSHUsername)
}
if b.config.CommConfig.SSHPassword != "password" {
t.Errorf("bad ssh_password: %s", b.config.CommConfig.SSHPassword)
if b.config.SSHConfig.Comm.SSHPassword != "password" {
t.Errorf("bad ssh_password: %s", b.config.SSHConfig.Comm.SSHPassword)
}
if host := b.config.CommConfig.Host(); host != "1.2.3.4" {
if host := b.config.SSHConfig.Comm.Host(); host != "1.2.3.4" {
t.Errorf("bad host: %s", host)
}
}

View File

@ -1,44 +0,0 @@
package iso
import (
"fmt"
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
)
// NewDriver returns a new driver implementation for this operating
// system, or an error if the driver couldn't be initialized.
func NewDriver(config *Config) (vmwcommon.Driver, error) {
drivers := []vmwcommon.Driver{}
if config.RemoteType == "" {
return vmwcommon.NewDriver(&config.DriverConfig, &config.SSHConfig)
}
drivers = []vmwcommon.Driver{
&ESX5Driver{
Host: config.RemoteHost,
Port: config.RemotePort,
Username: config.RemoteUser,
Password: config.RemotePassword,
PrivateKeyFile: config.RemotePrivateKey,
Datastore: config.RemoteDatastore,
CacheDatastore: config.RemoteCacheDatastore,
CacheDirectory: config.RemoteCacheDirectory,
},
}
errs := ""
for _, driver := range drivers {
err := driver.Verify()
if err == nil {
return driver, nil
}
errs += "* " + err.Error() + "\n"
}
return nil, fmt.Errorf(
"Unable to initialize any driver for this platform. The errors\n"+
"from each driver are shown below. Please fix at least one driver\n"+
"to continue:\n%s", errs)
}

View File

@ -1,14 +0,0 @@
package iso
// OutputDir is an interface type that abstracts the creation and handling
// of the output directory for VMware-based products. The abstraction is made
// so that the output directory can be properly made on remote (ESXi) based
// VMware products as well as local.
type OutputDir interface {
DirExists() (bool, error)
ListFiles() ([]string, error)
MkdirAll() error
Remove(string) error
RemoveAll() error
SetOutputDir(string)
}

View File

@ -1,12 +0,0 @@
package iso
import (
"testing"
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
)
func TestRemoteDriverMock_impl(t *testing.T) {
var _ vmwcommon.Driver = new(RemoteDriverMock)
var _ RemoteDriver = new(RemoteDriverMock)
}

View File

@ -22,7 +22,7 @@ func (s *stepRemoteUpload) Run(_ context.Context, state multistep.StateBag) mult
driver := state.Get("driver").(vmwcommon.Driver)
ui := state.Get("ui").(packer.Ui)
remote, ok := driver.(RemoteDriver)
remote, ok := driver.(vmwcommon.RemoteDriver)
if !ok {
return multistep.ActionContinue
}
@ -36,10 +36,10 @@ func (s *stepRemoteUpload) Run(_ context.Context, state multistep.StateBag) mult
checksum := config.ISOChecksum
checksumType := config.ISOChecksumType
if esx5, ok := remote.(*ESX5Driver); ok {
remotePath := esx5.cachePath(path)
if esx5, ok := remote.(*vmwcommon.ESX5Driver); ok {
remotePath := esx5.CachePath(path)
if esx5.verifyChecksum(checksumType, checksum, remotePath) {
if esx5.VerifyChecksum(checksumType, checksum, remotePath) {
ui.Say("Remote cache was verified skipping remote upload...")
state.Put(s.Key, remotePath)
return multistep.ActionContinue
@ -68,7 +68,7 @@ func (s *stepRemoteUpload) Cleanup(state multistep.StateBag) {
driver := state.Get("driver").(vmwcommon.Driver)
remote, ok := driver.(RemoteDriver)
remote, ok := driver.(vmwcommon.RemoteDriver)
if !ok {
return
}

View File

@ -34,13 +34,27 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
// Run executes a Packer build and returns a packer.Artifact representing
// a VMware image.
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
driver, err := vmwcommon.NewDriver(&b.config.DriverConfig, &b.config.SSHConfig)
driver, err := vmwcommon.NewDriver(&b.config.DriverConfig, &b.config.SSHConfig, b.config.VMName)
if err != nil {
return nil, fmt.Errorf("Failed creating VMware driver: %s", err)
}
// Setup the directory
dir := new(vmwcommon.LocalOutputDir)
// Determine the output dir implementation
var dir vmwcommon.OutputDir
switch d := driver.(type) {
case vmwcommon.OutputDir:
dir = d
default:
dir = new(vmwcommon.LocalOutputDir)
}
// The OutputDir will track remote esxi output; exportOutputPath preserves
// the path to the output on the machine running Packer.
exportOutputPath := b.config.OutputDir
if b.config.RemoteType != "" {
b.config.OutputDir = b.config.VMName
}
dir.SetOutputDir(b.config.OutputDir)
// Set up the state.
@ -51,6 +65,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
state.Put("driver", driver)
state.Put("hook", hook)
state.Put("ui", ui)
state.Put("sshConfig", &b.config.SSHConfig)
state.Put("driverConfig", &b.config.DriverConfig)
// Build the steps.
steps := []multistep.Step{
@ -73,6 +89,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
},
&vmwcommon.StepConfigureVMX{
CustomData: b.config.VMXData,
VMName: b.config.VMName,
},
&vmwcommon.StepSuppressMessages{},
&common.StepHTTPServer{
@ -80,6 +97,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
},
&vmwcommon.StepUploadVMX{
RemoteType: b.config.RemoteType,
},
&vmwcommon.StepConfigureVNC{
Enabled: !b.config.DisableVNC,
VNCBindAddress: b.config.VNCBindAddress,
@ -87,6 +107,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
VNCPortMax: b.config.VNCPortMax,
VNCDisablePassword: b.config.VNCDisablePassword,
},
&vmwcommon.StepRegister{
Format: b.config.Format,
KeepRegistered: b.config.KeepRegistered,
SkipExport: b.config.SkipExport,
},
&vmwcommon.StepRun{
DurationBeforeStop: 5 * time.Second,
Headless: b.config.Headless,
@ -125,11 +150,22 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&vmwcommon.StepConfigureVMX{
CustomData: b.config.VMXDataPost,
SkipFloppy: true,
VMName: b.config.VMName,
},
&vmwcommon.StepCleanVMX{
RemoveEthernetInterfaces: b.config.VMXConfig.VMXRemoveEthernet,
VNCEnabled: !b.config.DisableVNC,
},
&vmwcommon.StepUploadVMX{
RemoteType: b.config.RemoteType,
},
&vmwcommon.StepExport{
Format: b.config.Format,
SkipExport: b.config.SkipExport,
VMName: b.config.VMName,
OVFToolOptions: b.config.OVFToolOptions,
OutputDir: exportOutputPath,
},
}
// Run the steps.
@ -150,7 +186,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
return nil, errors.New("Build was halted.")
}
return vmwcommon.NewLocalArtifact(b.config.VMName, b.config.OutputDir)
// Artifact
log.Printf("Generating artifact...")
return vmwcommon.NewArtifact(b.config.RemoteType, b.config.Format, exportOutputPath,
b.config.VMName, b.config.SkipExport, b.config.KeepRegistered, state)
}
// Cancel.

View File

@ -25,10 +25,10 @@ type Config struct {
vmwcommon.SSHConfig `mapstructure:",squash"`
vmwcommon.ToolsConfig `mapstructure:",squash"`
vmwcommon.VMXConfig `mapstructure:",squash"`
vmwcommon.ExportConfig `mapstructure:",squash"`
Linked bool `mapstructure:"linked"`
RemoteType string `mapstructure:"remote_type"`
SkipCompaction bool `mapstructure:"skip_compaction"`
SourcePath string `mapstructure:"source_path"`
VMName string `mapstructure:"vm_name"`
@ -69,7 +69,9 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
errs = packer.MultiErrorAppend(errs, c.VMXConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.FloppyConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.VNCConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.ExportConfig.Prepare(&c.ctx)...)
if c.RemoteType == "" {
if c.SourcePath == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is blank, but is required"))
} else {
@ -78,6 +80,27 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
fmt.Errorf("source_path is invalid: %s", err))
}
}
} else {
// Remote configuration validation
if c.RemoteHost == "" {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("remote_host must be specified"))
}
if c.RemoteType != "esx5" {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Only 'esx5' value is accepted for remote_type"))
}
}
if c.Format == "" {
c.Format = "ovf"
}
if !(c.Format == "ova" || c.Format == "ovf" || c.Format == "vmx") {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("format must be one of ova, ovf, or vmx"))
}
// Warnings
var warnings []string

View File

@ -3,7 +3,9 @@ package vmx
import (
"context"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
@ -18,9 +20,15 @@ type StepCloneVMX struct {
Path string
VMName string
Linked bool
tempDir string
}
func (s *StepCloneVMX) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
halt := func(err error) multistep.StepAction {
state.Put("error", err)
return multistep.ActionHalt
}
driver := state.Get("driver").(vmwcommon.Driver)
ui := state.Get("ui").(packer.Ui)
@ -29,9 +37,11 @@ func (s *StepCloneVMX) Run(_ context.Context, state multistep.StateBag) multiste
ui.Say("Cloning source VM...")
log.Printf("Cloning from: %s", s.Path)
log.Printf("Cloning to: %s", vmxPath)
if err := driver.Clone(vmxPath, s.Path, s.Linked); err != nil {
state.Put("error", err)
return multistep.ActionHalt
return halt(err)
}
// Read in the machine configuration from the cloned VMX file
@ -40,10 +50,22 @@ func (s *StepCloneVMX) Run(_ context.Context, state multistep.StateBag) multiste
// network type so that it can work out things like IP's and MAC
// addresses
// * The disk compaction step needs the paths to all attached disks
if remoteDriver, ok := driver.(vmwcommon.RemoteDriver); ok {
remoteVmxPath := vmxPath
tempDir, err := ioutil.TempDir("", "packer-vmx")
if err != nil {
return halt(err)
}
s.tempDir = tempDir
vmxPath = filepath.Join(tempDir, s.VMName+".vmx")
if err = remoteDriver.Download(remoteVmxPath, vmxPath); err != nil {
return halt(err)
}
}
vmxData, err := vmwcommon.ReadVMX(vmxPath)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
return halt(err)
}
var diskFilenames []string
@ -104,4 +126,7 @@ func (s *StepCloneVMX) Run(_ context.Context, state multistep.StateBag) multiste
}
func (s *StepCloneVMX) Cleanup(state multistep.StateBag) {
if s.tempDir != "" {
os.RemoveAll(s.tempDir)
}
}

View File

@ -8,7 +8,7 @@ import (
"strings"
"time"
"github.com/hashicorp/packer/builder/vmware/iso"
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
@ -20,7 +20,7 @@ import (
var builtins = map[string]string{
vsphere.BuilderId: "vmware",
iso.BuilderIdESX: "vmware",
vmwcommon.BuilderIdESX: "vmware",
}
type Config struct {
@ -96,9 +96,9 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
"Artifact type %s does not fit this requirement", artifact.BuilderId())
}
f := artifact.State(iso.ArtifactConfFormat)
k := artifact.State(iso.ArtifactConfKeepRegistered)
s := artifact.State(iso.ArtifactConfSkipExport)
f := artifact.State(vmwcommon.ArtifactConfFormat)
k := artifact.State(vmwcommon.ArtifactConfKeepRegistered)
s := artifact.State(vmwcommon.ArtifactConfSkipExport)
if f != "" && k != "true" && s == "false" {
return nil, false, errors.New("To use this post-processor with exporting behavior you need set keep_registered as true")

View File

@ -56,7 +56,8 @@ builder.
### Required:
- `source_path` (string) - Path to the source VMX file to clone.
- `source_path` (string) - Path to the source VMX file to clone. If
`remote_type` is enabled then this specifies a path on the `remote_host`.
### Optional:
@ -124,6 +125,40 @@ builder.
the builder. By default this is `output-BUILDNAME` where "BUILDNAME" is the
name of the build.
- `remote_cache_datastore` (string) - The path to the datastore where
supporting files will be stored during the build on the remote machine. By
default this is the same as the `remote_datastore` option. This only has an
effect if `remote_type` is enabled.
- `remote_cache_directory` (string) - The path where the ISO and/or floppy
files will be stored during the build on the remote machine. The path is
relative to the `remote_cache_datastore` on the remote machine. By default
this is "packer\_cache". This only has an effect if `remote_type`
is enabled.
- `remote_datastore` (string) - The path to the datastore where the resulting
VM will be stored when it is built on the remote machine. By default this
is "datastore1". This only has an effect if `remote_type` is enabled.
- `remote_host` (string) - The host of the remote machine used for access.
This is only required if `remote_type` is enabled.
- `remote_password` (string) - The SSH password for the user used to access
the remote machine. By default this is empty. This only has an effect if
`remote_type` is enabled.
- `remote_private_key_file` (string) - The path to the PEM encoded private key
file for the user used to access the remote machine. By default this is empty.
This only has an effect if `remote_type` is enabled.
- `remote_type` (string) - The type of remote machine that will be used to
build this VM rather than a local desktop product. The only value accepted
for this currently is "esx5". If this is not set, a desktop product will
be used. By default, this is not set.
- `remote_username` (string) - The username for the SSH user that will access
the remote machine. This is required if `remote_type` is enabled.
- `shutdown_command` (string) - The command to use to gracefully shut down the
machine once all the provisioning is done. By default this is an empty
string, which tells Packer to just forcefully shut down the machine unless a
@ -157,6 +192,25 @@ builder.
slightly larger. If you find this to be the case, you can disable compaction
using this configuration value. Defaults to `false`.
- `skip_export` (boolean) - Defaults to `false`. When enabled, Packer will
not export the VM. Useful if the build output is not the resultant image,
but created inside the VM.
- `keep_registered` (boolean) - Set this to `true` if you would like to keep
the VM registered with the remote ESXi server. This is convenient if you
use packer to provision VMs on ESXi and don't want to use ovftool to
deploy the resulting artifact (VMX or OVA or whatever you used as `format`).
Defaults to `false`.
- `ovftool_options` (array of strings) - Extra options to pass to ovftool
during export. Each item in the array is a new argument. The options
`--noSSLVerify`, `--skipManifestCheck`, and `--targetType` are reserved,
and should not be passed to this argument.
- `format` (string) - Either "ovf", "ova" or "vmx", this specifies the output
format of the exported virtual machine. This defaults to "ovf".
Before using this option, you need to install `ovftool`.
- `tools_upload_flavor` (string) - The flavor of the VMware Tools ISO to
upload into the VM. Valid values are `darwin`, `linux`, and `windows`. By
default, this is empty, which means VMware tools won't be uploaded.
@ -230,6 +284,46 @@ contention. You can tune this delay on a per-builder basis by specifying
}
```
- `<f1>` - `<f12>` - Simulates pressing a function key.
- `<up>` `<down>` `<left>` `<right>` - Simulates pressing an arrow key.
- `<spacebar>` - Simulates pressing the spacebar.
- `<insert>` - Simulates pressing the insert key.
- `<home>` `<end>` - Simulates pressing the home and end keys.
- `<pageUp>` `<pageDown>` - Simulates pressing the page up and page down keys.
- `<leftAlt>` `<rightAlt>` - Simulates pressing the alt key.
- `<leftCtrl>` `<rightCtrl>` - Simulates pressing the ctrl key.
- `<leftShift>` `<rightShift>` - Simulates pressing the shift key.
- `<leftAltOn>` `<rightAltOn>` - Simulates pressing and holding the alt key.
- `<leftCtrlOn>` `<rightCtrlOn>` - Simulates pressing and holding the ctrl
key.
- `<leftShiftOn>` `<rightShiftOn>` - Simulates pressing and holding the
shift key.
- `<leftAltOff>` `<rightAltOff>` - Simulates releasing a held alt key.
- `<leftCtrlOff>` `<rightCtrlOff>` - Simulates releasing a held ctrl key.
- `<leftShiftOff>` `<rightShiftOff>` - Simulates releasing a held shift 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:
<%= partial "partials/builders/boot-command" %>
Example boot command. This is actually a working boot command used to start an