Merge pull request #9825 from hashicorp/do_7165

Allow "export" to ovf/ova for local vmware builds in addition to esx …
This commit is contained in:
Megan Marsh 2020-08-26 10:43:49 -07:00 committed by GitHub
commit 60d124dcaf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 393 additions and 126 deletions

View File

@ -77,6 +77,9 @@ type Driver interface {
// Get the host ip address for the vm // Get the host ip address for the vm
HostIP(multistep.StateBag) (string, error) HostIP(multistep.StateBag) (string, error)
// Export the vm to ovf or ova format using ovftool
Export([]string) error
} }
// NewDriver returns a new driver implementation for this operating // NewDriver returns a new driver implementation for this operating
@ -600,3 +603,28 @@ func (d *VmwareDriver) HostIP(state multistep.StateBag) (string, error) {
} }
return "", fmt.Errorf("Unable to find host IP from devices %v, last error: %s", devices, lastError) return "", fmt.Errorf("Unable to find host IP from devices %v, last error: %s", devices, lastError)
} }
func GetOVFTool() string {
ovftool := "ovftool"
if runtime.GOOS == "windows" {
ovftool = "ovftool.exe"
}
if _, err := exec.LookPath(ovftool); err != nil {
return ""
}
return ovftool
}
func (d *VmwareDriver) Export(args []string) error {
ovftool := GetOVFTool()
if ovftool == "" {
return fmt.Errorf("Error: ovftool not found")
}
cmd := exec.Command(ovftool, args...)
if _, _, err := runAndLog(cmd); err != nil {
return err
}
return nil
}

View File

@ -84,13 +84,28 @@ func (c *DriverConfig) Prepare(ctx *interpolate.Context) []error {
} }
func (c *DriverConfig) Validate(SkipExport bool) error { func (c *DriverConfig) Validate(SkipExport bool) error {
if c.RemoteType == "" || SkipExport == true { if SkipExport {
return nil return nil
} }
if c.RemotePassword == "" {
return fmt.Errorf("exporting the vm (with ovftool) requires that " + if c.RemoteType != "" && c.RemotePassword == "" {
"you set a value for remote_password") return fmt.Errorf("exporting the vm from esxi with ovftool requires " +
"that you set a value for remote_password")
} }
if c.RemoteType == "" {
// Validate that tool exists, but no need to validate credentials.
ovftool := GetOVFTool()
if ovftool != "" {
return nil
} else {
return fmt.Errorf("Couldn't find ovftool in path! Please either " +
"set `skip_export = true` and remove the `format` option " +
"from your template, or make sure ovftool is installed on " +
"your build system. ")
}
}
if c.SkipValidateCredentials { if c.SkipValidateCredentials {
return nil return nil
} }

View File

@ -699,6 +699,10 @@ func (d *ESX5Driver) Download(src, dst string) error {
return d.comm.Download(d.datastorePath(src), file) return d.comm.Download(d.datastorePath(src), file)
} }
func (d *ESX5Driver) Export(args []string) error {
return d.base.Export(args)
}
// VerifyChecksum checks that file on the esxi instance matches hash // VerifyChecksum checks that file on the esxi instance matches hash
func (d *ESX5Driver) VerifyChecksum(hash string, file string) bool { func (d *ESX5Driver) VerifyChecksum(hash string, file string) bool {
if hash == "none" { if hash == "none" {

View File

@ -27,6 +27,9 @@ type DriverMock struct {
CreateDiskTypeId string CreateDiskTypeId string
CreateDiskErr error CreateDiskErr error
ExportCalled bool
ExportArgs []string
IsRunningCalled bool IsRunningCalled bool
IsRunningPath string IsRunningPath string
IsRunningResult bool IsRunningResult bool
@ -254,6 +257,12 @@ func (d *DriverMock) Verify() error {
return d.VerifyErr return d.VerifyErr
} }
func (d *DriverMock) Export(args []string) error {
d.ExportCalled = true
d.ExportArgs = args
return nil
}
func (d *DriverMock) GetVmwareDriver() VmwareDriver { func (d *DriverMock) GetVmwareDriver() VmwareDriver {
var state VmwareDriver var state VmwareDriver
state.DhcpLeasesPath = func(string) string { state.DhcpLeasesPath = func(string) string {

View File

@ -10,33 +10,30 @@ import (
type ExportConfig struct { type ExportConfig struct {
// Either "ovf", "ova" or "vmx", this specifies the output // Either "ovf", "ova" or "vmx", this specifies the output
// format of the exported virtual machine. This defaults to "ovf". // format of the exported virtual machine. This defaults to "ovf" for
// Before using this option, you need to install ovftool. This option // remote (esx) builds, and "vmx" for local builds.
// currently only works when option remote_type is set to "esx5". // Before using this option, you need to install ovftool.
// Since ovftool is only capable of password based authentication // Since ovftool is only capable of password based authentication
// remote_password must be set when exporting the VM. // remote_password must be set when exporting the VM from a remote instance.
// If you are building locally, Packer will create a vmx and then
// export that vm to an ovf or ova. Packer will not delete the vmx and vmdk
// files; this is left up to the user if you don't want to keep those
// files.
Format string `mapstructure:"format" required:"false"` Format string `mapstructure:"format" required:"false"`
// Extra options to pass to ovftool during export. Each item in the array // Extra options to pass to ovftool during export. Each item in the array
// is a new argument. The options `--noSSLVerify`, `--skipManifestCheck`, // is a new argument. The options `--noSSLVerify`, `--skipManifestCheck`,
// and `--targetType` are reserved, and should not be passed to this // and `--targetType` are used by Packer for remote exports, and should not
// argument. Currently, exporting the build VM (with ovftool) is only // be passed to this argument. For ovf/ova exports from local builds, Packer
// supported when building on ESXi e.g. when `remote_type` is set to // does not automatically set any ovftool options.
// `esx5`. See the [Building on a Remote vSphere
// Hypervisor](/docs/builders/vmware-iso#building-on-a-remote-vsphere-hypervisor)
// section below for more info.
OVFToolOptions []string `mapstructure:"ovftool_options" required:"false"` OVFToolOptions []string `mapstructure:"ovftool_options" required:"false"`
// Defaults to `false`. When enabled, Packer will not export the VM. Useful // Defaults to `false`. When true, Packer will not export the VM. This can
// if the build output is not the resultant image, but created inside the // be useful if the build output is not the resultant image, but created
// VM. Currently, exporting the build VM is only supported when building on // inside the VM.
// ESXi e.g. when `remote_type` is set to `esx5`. See the [Building on a
// Remote vSphere
// Hypervisor](/docs/builders/vmware-iso#building-on-a-remote-vsphere-hypervisor)
// section below for more info.
SkipExport bool `mapstructure:"skip_export" required:"false"` SkipExport bool `mapstructure:"skip_export" required:"false"`
// Set this to true if you would like to keep // Set this to true if you would like to keep a remotely-built
// the VM registered with the remote ESXi server. If you do not need to export // VM registered with the remote ESXi server. If you do not need to export
// the vm, then also set skip_export: true in order to avoid an unnecessary // the vm, then also set `skip_export: true` in order to avoid unnecessarily
// step of using ovftool to export the vm. Defaults to false. // using ovftool to export the vm. Defaults to false.
KeepRegistered bool `mapstructure:"keep_registered" required:"false"` KeepRegistered bool `mapstructure:"keep_registered" required:"false"`
// VMware-created disks are defragmented and // VMware-created disks are defragmented and
// compacted at the end of the build process using vmware-vdiskmanager or // compacted at the end of the build process using vmware-vdiskmanager or
@ -56,5 +53,6 @@ func (c *ExportConfig) Prepare(ctx *interpolate.Context) []error {
errs, fmt.Errorf("format must be one of ova, ovf, or vmx")) errs, fmt.Errorf("format must be one of ova, ovf, or vmx"))
} }
} }
return errs return errs
} }

View File

@ -1,13 +1,11 @@
package common package common
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"net/url" "net/url"
"os" "os"
"os/exec" "path/filepath"
"runtime"
"strings" "strings"
"github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/helper/multistep"
@ -15,30 +13,15 @@ import (
) )
// This step exports a VM built on ESXi using ovftool // This step exports a VM built on ESXi using ovftool
//
// Uses:
// display_name string
type StepExport struct { type StepExport struct {
Format string Format string
SkipExport bool SkipExport bool
VMName string VMName string
OVFToolOptions []string OVFToolOptions []string
OutputDir string OutputDir *string
} }
func GetOVFTool() string { func (s *StepExport) generateRemoteExportArgs(c *DriverConfig, displayName string, hidePassword bool, exportOutputPath string) ([]string, error) {
ovftool := "ovftool"
if runtime.GOOS == "windows" {
ovftool = "ovftool.exe"
}
if _, err := exec.LookPath(ovftool); err != nil {
return ""
}
return ovftool
}
func (s *StepExport) generateArgs(c *DriverConfig, displayName string, hidePassword bool) ([]string, error) {
ovftool_uri := fmt.Sprintf("vi://%s/%s", c.RemoteHost, displayName) ovftool_uri := fmt.Sprintf("vi://%s/%s", c.RemoteHost, displayName)
u, err := url.Parse(ovftool_uri) u, err := url.Parse(ovftool_uri)
@ -57,7 +40,15 @@ func (s *StepExport) generateArgs(c *DriverConfig, displayName string, hidePassw
"--skipManifestCheck", "--skipManifestCheck",
"-tt=" + s.Format, "-tt=" + s.Format,
u.String(), u.String(),
s.OutputDir, filepath.Join(exportOutputPath, s.VMName+"."+s.Format),
}
return append(s.OVFToolOptions, args...), nil
}
func (s *StepExport) generateLocalExportArgs(exportOutputPath string) ([]string, error) {
args := []string{
filepath.Join(exportOutputPath, s.VMName+".vmx"),
filepath.Join(exportOutputPath, s.VMName+"."+s.Format),
} }
return append(s.OVFToolOptions, args...), nil return append(s.OVFToolOptions, args...), nil
} }
@ -65,6 +56,7 @@ func (s *StepExport) generateArgs(c *DriverConfig, displayName string, hidePassw
func (s *StepExport) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { func (s *StepExport) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
c := state.Get("driverConfig").(*DriverConfig) c := state.Get("driverConfig").(*DriverConfig)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
driver := state.Get("driver").(Driver)
// Skip export if requested // Skip export if requested
if s.SkipExport { if s.SkipExport {
@ -72,57 +64,72 @@ func (s *StepExport) Run(ctx context.Context, state multistep.StateBag) multiste
return multistep.ActionContinue return multistep.ActionContinue
} }
if c.RemoteType != "esx5" { // load output path from state. If it doesn't exist, just use the local
ui.Say("Skipping export of virtual machine (export is allowed only for ESXi)...") // outputdir.
return multistep.ActionContinue exportOutputPath, ok := state.Get("export_output_path").(string)
if !ok || exportOutputPath == "" {
if *s.OutputDir != "" {
exportOutputPath = *s.OutputDir
} else {
exportOutputPath = s.VMName
}
} }
ovftool := GetOVFTool() err := os.MkdirAll(exportOutputPath, 0755)
if ovftool == "" { if err != nil {
err := fmt.Errorf("Error ovftool not found") state.Put("error creating export directory", err)
state.Put("error", err)
ui.Error(err.Error()) ui.Error(err.Error())
return multistep.ActionHalt return multistep.ActionHalt
} }
// Export the VM
if s.OutputDir == "" {
s.OutputDir = s.VMName + "." + s.Format
}
os.MkdirAll(s.OutputDir, 0755)
ui.Say("Exporting virtual machine...") ui.Say("Exporting virtual machine...")
var displayName string var displayName string
if v, ok := state.GetOk("display_name"); ok { if v, ok := state.GetOk("display_name"); ok {
displayName = v.(string) displayName = v.(string)
} }
ui_args, err := s.generateArgs(c, displayName, true)
if err != nil { var args, ui_args []string
err := fmt.Errorf("Couldn't generate ovftool uri: %s", err)
state.Put("error", err) ovftool := GetOVFTool()
ui.Error(err.Error()) if c.RemoteType == "esx5" {
return multistep.ActionHalt // Generate arguments for the ovftool command, but obfuscating the
// password that we can log the command to the UI for debugging.
ui_args, err := s.generateRemoteExportArgs(c, displayName, true, exportOutputPath)
if err != nil {
err := fmt.Errorf("Couldn't generate ovftool export args: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Message(fmt.Sprintf("Executing: %s %s", ovftool,
strings.Join(ui_args, " ")))
// Re-run the generate command, this time without obfuscating the
// password, so we can actually use it.
args, err = s.generateRemoteExportArgs(c, displayName, false, exportOutputPath)
if err != nil {
err := fmt.Errorf("Couldn't generate ovftool export args: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
} else {
args, err = s.generateLocalExportArgs(exportOutputPath)
ui.Message(fmt.Sprintf("Executing: %s %s", ovftool,
strings.Join(ui_args, " ")))
} }
ui.Message(fmt.Sprintf("Executing: %s %s", ovftool, strings.Join(ui_args, " ")))
var out bytes.Buffer
args, err := s.generateArgs(c, displayName, false)
if err != nil { if err != nil {
err := fmt.Errorf("Couldn't generate ovftool uri: %s", err) err := fmt.Errorf("Couldn't generate ovftool export args: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
cmd := exec.Command(ovftool, args...)
cmd.Stdout = &out
if err := cmd.Run(); err != nil {
err := fmt.Errorf("Error exporting virtual machine: %s\n%s\n", err, out.String())
state.Put("error", err) state.Put("error", err)
ui.Error(err.Error()) ui.Error(err.Error())
return multistep.ActionHalt return multistep.ActionHalt
} }
ui.Message(out.String()) if err := driver.Export(args); err != nil {
err := fmt.Errorf("Error performing ovftool export: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue return multistep.ActionContinue
} }

View File

@ -2,22 +2,41 @@ package common
import ( import (
"context" "context"
"path/filepath"
"testing" "testing"
"github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/helper/multistep"
"github.com/stretchr/testify/assert"
) )
func TestStepExport_impl(t *testing.T) { func TestStepExport_impl(t *testing.T) {
var _ multistep.Step = new(StepExport) var _ multistep.Step = new(StepExport)
} }
func testStepExport_wrongtype_impl(t *testing.T, remoteType string) { func stringPointer(s string) *string {
return &s
}
func remoteExportTestState(t *testing.T) multistep.StateBag {
state := testState(t) state := testState(t)
driverConfig := &DriverConfig{
RemoteHost: "123.45.67.8",
RemotePassword: "password",
RemoteUser: "user",
RemoteType: "esx5",
}
state.Put("driverConfig", driverConfig)
state.Put("display_name", "vm_name")
return state
}
func TestStepExport_ReturnIfSkip(t *testing.T) {
state := testState(t)
driverConfig := &DriverConfig{}
state.Put("driverConfig", driverConfig)
step := new(StepExport) step := new(StepExport)
var config DriverConfig step.SkipExport = true
config.RemoteType = "foo"
state.Put("driverConfig", &config)
if action := step.Run(context.Background(), state); action != multistep.ActionContinue { if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action) t.Fatalf("bad action: %#v", action)
@ -26,11 +45,189 @@ func testStepExport_wrongtype_impl(t *testing.T, remoteType string) {
t.Fatal("should NOT have error") t.Fatal("should NOT have error")
} }
// We told step to skip so it should not have reached the driver's Export
// func.
d := state.Get("driver").(*DriverMock)
if d.ExportCalled {
t.Fatal("Should not have called the driver export func")
}
// Cleanup // Cleanup
step.Cleanup(state) step.Cleanup(state)
} }
func TestStepExport_wrongtype_impl(t *testing.T) { func TestStepExport_localArgs(t *testing.T) {
testStepExport_wrongtype_impl(t, "foo") // even though we aren't overriding the remote args and they are present,
testStepExport_wrongtype_impl(t, "") // test shouldn't use them since remoteType is not set to esx.
state := testState(t)
driverConfig := &DriverConfig{}
state.Put("driverConfig", driverConfig)
step := new(StepExport)
step.SkipExport = false
step.OutputDir = stringPointer("test-output")
step.VMName = "test-name"
step.Format = "ova"
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
// Check that step ran, and called Export with the expected args.
d := state.Get("driver").(*DriverMock)
if !d.ExportCalled {
t.Fatal("Should have called the driver export func")
}
assert.Equal(t, d.ExportArgs,
[]string{
filepath.Join("test-output", "test-name.vmx"),
filepath.Join("test-output", "test-name.ova")})
// Cleanup
step.Cleanup(state)
}
func TestStepExport_localArgsExportOutputPath(t *testing.T) {
// even though we aren't overriding the remote args and they are present,
// test shouldn't use them since remoteType is not set to esx.
state := testState(t)
driverConfig := &DriverConfig{}
state.Put("driverConfig", driverConfig)
state.Put("export_output_path", "local_output")
step := new(StepExport)
step.SkipExport = false
step.OutputDir = stringPointer("test-output")
step.VMName = "test-name"
step.Format = "ova"
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
// Check that step ran, and called Export with the expected args.
d := state.Get("driver").(*DriverMock)
if !d.ExportCalled {
t.Fatal("Should have called the driver export func")
}
assert.Equal(t, d.ExportArgs,
[]string{
filepath.Join("local_output", "test-name.vmx"),
filepath.Join("local_output", "test-name.ova")})
// Cleanup
step.Cleanup(state)
}
func TestStepExport_localArgs_OvftoolOptions(t *testing.T) {
// even though we aren't overriding the remote args and they are present,
// test shouldn't use them since remoteType is not set to esx.
state := testState(t)
driverConfig := &DriverConfig{}
state.Put("driverConfig", driverConfig)
step := new(StepExport)
step.SkipExport = false
step.OutputDir = stringPointer("test-output")
step.VMName = "test-name"
step.Format = "ova"
step.OVFToolOptions = []string{"--option=value", "--second-option=\"quoted value\""}
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
// Check that step ran, and called Export with the expected args.
d := state.Get("driver").(*DriverMock)
if !d.ExportCalled {
t.Fatal("Should have called the driver export func")
}
assert.Equal(t, d.ExportArgs, []string{"--option=value",
"--second-option=\"quoted value\"",
filepath.Join("test-output", "test-name.vmx"),
filepath.Join("test-output", "test-name.ova")})
// Cleanup
step.Cleanup(state)
}
func TestStepExport_RemoteArgs(t *testing.T) {
// Even though we aren't overriding the remote args and they are present,
// test shouldn't use them since remoteType is not set to esx.
state := remoteExportTestState(t)
step := new(StepExport)
step.SkipExport = false
step.OutputDir = stringPointer("test-output")
step.VMName = "test-name"
step.Format = "ova"
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
// Check that step ran, and called Export with the expected args.
d := state.Get("driver").(*DriverMock)
if !d.ExportCalled {
t.Fatal("Should have called the driver export func")
}
assert.Equal(t, d.ExportArgs, []string{"--noSSLVerify=true",
"--skipManifestCheck",
"-tt=ova",
"vi://user:password@123.45.67.8/vm_name",
filepath.Join("test-output", "test-name.ova")})
// Cleanup
step.Cleanup(state)
}
func TestStepExport_RemoteArgsWithExportOutputPath(t *testing.T) {
// Even though we aren't overriding the remote args and they are present,
// test shouldn't use them since remoteType is not set to esx.
state := remoteExportTestState(t)
state.Put("export_output_path", "local_output")
step := new(StepExport)
step.SkipExport = false
step.OutputDir = stringPointer("test-output")
step.VMName = "test-name"
step.Format = "ova"
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
// Check that step ran, and called Export with the expected args.
d := state.Get("driver").(*DriverMock)
if !d.ExportCalled {
t.Fatal("Should have called the driver export func")
}
assert.Equal(t, d.ExportArgs, []string{"--noSSLVerify=true",
"--skipManifestCheck",
"-tt=ova",
"vi://user:password@123.45.67.8/vm_name",
filepath.Join("local_output", "test-name.ova")})
// Cleanup
step.Cleanup(state)
} }

View File

@ -171,6 +171,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
SkipExport: b.config.SkipExport, SkipExport: b.config.SkipExport,
VMName: b.config.VMName, VMName: b.config.VMName,
OVFToolOptions: b.config.OVFToolOptions, OVFToolOptions: b.config.OVFToolOptions,
OutputDir: &b.config.OutputConfig.OutputDir,
}, },
} }

View File

@ -127,6 +127,7 @@ func TestBuilderPrepare_InvalidFloppies(t *testing.T) {
var b Builder var b Builder
config := testConfig() config := testConfig()
config["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"} config["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"}
b = Builder{} b = Builder{}
_, _, errs := b.Prepare(config) _, _, errs := b.Prepare(config)
if errs == nil { if errs == nil {

View File

@ -176,18 +176,19 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
} }
} }
if c.Format != "" { if c.Format == "" {
if c.RemoteType != "esx5" { if c.RemoteType != "esx5" {
errs = packer.MultiErrorAppend(errs, c.Format = "vmx"
fmt.Errorf("format is only valid when RemoteType=esx5")) } else {
c.Format = "ovf"
} }
} else {
c.Format = "ovf"
} }
if !(c.Format == "ova" || c.Format == "ovf" || c.Format == "vmx") { if c.RemoteType != "esx5" && c.Format == "vmx" {
errs = packer.MultiErrorAppend(errs, // if we're building locally and want a vmx, there's nothing to export.
fmt.Errorf("format must be one of ova, ovf, or vmx")) // Set skip export flag here to keep the export step from attempting
// an unneded export
c.SkipExport = true
} }
err = c.DriverConfig.Validate(c.SkipExport) err = c.DriverConfig.Validate(c.SkipExport)

View File

@ -170,6 +170,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
SkipExport: b.config.SkipExport, SkipExport: b.config.SkipExport,
VMName: b.config.VMName, VMName: b.config.VMName,
OVFToolOptions: b.config.OVFToolOptions, OVFToolOptions: b.config.OVFToolOptions,
OutputDir: &b.config.OutputConfig.OutputDir,
}, },
} }

View File

@ -46,7 +46,6 @@ func TestBuilderPrepare_FloppyFiles(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("should not have error: %s", err) t.Fatalf("should not have error: %s", err)
} }
expected := []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)} expected := []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
if !reflect.DeepEqual(b.config.FloppyFiles, expected) { if !reflect.DeepEqual(b.config.FloppyFiles, expected) {
t.Fatalf("bad: %#v", b.config.FloppyFiles) t.Fatalf("bad: %#v", b.config.FloppyFiles)

View File

@ -123,23 +123,32 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
} }
} }
if c.Format == "" {
if c.RemoteType != "esx5" {
c.Format = "vmx"
} else {
c.Format = "ovf"
}
}
if c.RemoteType != "esx5" && c.Format == "vmx" {
// if we're building locally and want a vmx, there's nothing to export.
// Set skip export flag here to keep the export step from attempting
// an unneded export
c.SkipExport = true
}
err = c.DriverConfig.Validate(c.SkipExport) err = c.DriverConfig.Validate(c.SkipExport)
if err != nil { if err != nil {
errs = packer.MultiErrorAppend(errs, err) errs = packer.MultiErrorAppend(errs, err)
} }
if c.Format != "" { if c.Format == "" {
if c.RemoteType != "esx5" { if c.RemoteType != "esx5" {
errs = packer.MultiErrorAppend(errs, c.Format = "vmx"
fmt.Errorf("format is only valid when remote_type=esx5")) } else {
c.Format = "ovf"
} }
} else {
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 // Warnings

View File

@ -1,33 +1,30 @@
<!-- Code generated from the comments of the ExportConfig struct in builder/vmware/common/export_config.go; DO NOT EDIT MANUALLY --> <!-- Code generated from the comments of the ExportConfig struct in builder/vmware/common/export_config.go; DO NOT EDIT MANUALLY -->
- `format` (string) - Either "ovf", "ova" or "vmx", this specifies the output - `format` (string) - Either "ovf", "ova" or "vmx", this specifies the output
format of the exported virtual machine. This defaults to "ovf". format of the exported virtual machine. This defaults to "ovf" for
Before using this option, you need to install ovftool. This option remote (esx) builds, and "vmx" for local builds.
currently only works when option remote_type is set to "esx5". Before using this option, you need to install ovftool.
Since ovftool is only capable of password based authentication Since ovftool is only capable of password based authentication
remote_password must be set when exporting the VM. remote_password must be set when exporting the VM from a remote instance.
If you are building locally, Packer will create a vmx and then
export that vm to an ovf or ova. Packer will not delete the vmx and vmdk
files; this is left up to the user if you don't want to keep those
files.
- `ovftool_options` ([]string) - Extra options to pass to ovftool during export. Each item in the array - `ovftool_options` ([]string) - Extra options to pass to ovftool during export. Each item in the array
is a new argument. The options `--noSSLVerify`, `--skipManifestCheck`, is a new argument. The options `--noSSLVerify`, `--skipManifestCheck`,
and `--targetType` are reserved, and should not be passed to this and `--targetType` are used by Packer for remote exports, and should not
argument. Currently, exporting the build VM (with ovftool) is only be passed to this argument. For ovf/ova exports from local builds, Packer
supported when building on ESXi e.g. when `remote_type` is set to does not automatically set any ovftool options.
`esx5`. See the [Building on a Remote vSphere
Hypervisor](/docs/builders/vmware-iso#building-on-a-remote-vsphere-hypervisor)
section below for more info.
- `skip_export` (bool) - Defaults to `false`. When enabled, Packer will not export the VM. Useful - `skip_export` (bool) - Defaults to `false`. When true, Packer will not export the VM. This can
if the build output is not the resultant image, but created inside the be useful if the build output is not the resultant image, but created
VM. Currently, exporting the build VM is only supported when building on inside the VM.
ESXi e.g. when `remote_type` is set to `esx5`. See the [Building on a
Remote vSphere
Hypervisor](/docs/builders/vmware-iso#building-on-a-remote-vsphere-hypervisor)
section below for more info.
- `keep_registered` (bool) - Set this to true if you would like to keep - `keep_registered` (bool) - Set this to true if you would like to keep a remotely-built
the VM registered with the remote ESXi server. If you do not need to export VM registered with the remote ESXi server. If you do not need to export
the vm, then also set skip_export: true in order to avoid an unnecessary the vm, then also set `skip_export: true` in order to avoid unnecessarily
step of using ovftool to export the vm. Defaults to false. using ovftool to export the vm. Defaults to false.
- `skip_compaction` (bool) - VMware-created disks are defragmented and - `skip_compaction` (bool) - VMware-created disks are defragmented and
compacted at the end of the build process using vmware-vdiskmanager or compacted at the end of the build process using vmware-vdiskmanager or