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:
commit
60d124dcaf
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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" {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue