diff --git a/builder/vmware/common/output_config.go b/builder/vmware/common/output_config.go index e99919dcb..5cc859a64 100644 --- a/builder/vmware/common/output_config.go +++ b/builder/vmware/common/output_config.go @@ -10,13 +10,39 @@ import ( ) type OutputConfig struct { - // This is the path to the directory where the - // resulting virtual machine will be created. This may be relative or absolute. - // If relative, the path is relative to the working directory when packer - // is executed. This directory must not exist or be empty prior to running - // the builder. By default this is output-BUILDNAME where "BUILDNAME" is the - // name of the build. + // This is the path on your local machine (the one running Packer) to the + // directory where the resulting virtual machine will be created. + // This may be relative or absolute. If relative, the path is relative to + // the working directory when packer is executed. + // + // If you are running a remote esx build, the output_dir is the path on your + // local machine (the machine running Packer) to which Packer will export + // the vm if you have `"skip_export": false`. If you want to manage the + // virtual machine's path on the remote datastore, use `remote_output_dir`. + // + // This directory must not exist or be empty prior to running + // the builder. + // + // By default this is output-BUILDNAME where "BUILDNAME" is the name of the + // build. OutputDir string `mapstructure:"output_directory" required:"false"` + // This is the directoy on your remote esx host where you will save your + // vm, relative to your remote_datastore. + // + // This option's default value is your `vm_name`, and the final path of your + // vm will be vmfs/volumes/$remote_datastore/$vm_name/$vm_name.vmx where + // `$remote_datastore` and `$vm_name` match their corresponding template + // options + // + // For example, setting `"remote_output_directory": "path/to/subdir` + // will create a directory `/vmfs/volumes/remote_datastore/path/to/subdir`. + // + // Packer will not create the remote datastore for you; it must already + // exist. However, Packer will create all directories defined in the option + // that do not currently exist. + // + // This option will be ignored unless you are building on a remote esx host. + RemoteOutputDir string `mapstructure:"remote_output_directory" required:"false"` } func (c *OutputConfig) Prepare(ctx *interpolate.Context, pc *common.PackerConfig) []error { diff --git a/builder/vmware/common/remote_driver_mock.go b/builder/vmware/common/remote_driver_mock.go index 8bcc1f8a8..f20e5e69f 100644 --- a/builder/vmware/common/remote_driver_mock.go +++ b/builder/vmware/common/remote_driver_mock.go @@ -33,7 +33,11 @@ type RemoteDriverMock struct { RemovedCachePath string CacheRemoved bool + ReturnValDirExists bool + ReloadVMErr error + + outputDir string } func (d *RemoteDriverMock) UploadISO(path string, checksum string, ui packer.Ui) (string, error) { @@ -81,3 +85,33 @@ func (d *RemoteDriverMock) RemoveCache(localPath string) error { func (d *RemoteDriverMock) ReloadVM() error { return d.ReloadVMErr } + +// the following functions satisfy the Outputdir interface + +func (d *RemoteDriverMock) DirExists() (bool, error) { + return d.ReturnValDirExists, nil +} + +func (d *RemoteDriverMock) ListFiles() ([]string, error) { + return []string{}, nil +} + +func (d *RemoteDriverMock) MkdirAll() error { + return nil +} + +func (d *RemoteDriverMock) Remove(string) error { + return nil +} + +func (d *RemoteDriverMock) RemoveAll() error { + return nil +} + +func (d *RemoteDriverMock) SetOutputDir(s string) { + d.outputDir = s +} + +func (d *RemoteDriverMock) String() string { + return d.outputDir +} diff --git a/builder/vmware/common/step_output_dir.go b/builder/vmware/common/step_output_dir.go index b7f45c5e1..f063e1f08 100644 --- a/builder/vmware/common/step_output_dir.go +++ b/builder/vmware/common/step_output_dir.go @@ -16,13 +16,62 @@ import ( type StepOutputDir struct { Force bool + OutputConfig *OutputConfig + VMName string + + RemoteType string + success bool } -func (s *StepOutputDir) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { - dir := state.Get("dir").(OutputDir) - ui := state.Get("ui").(packer.Ui) +func (s *StepOutputDir) SetOutputAndExportDirs(state multistep.StateBag) OutputDir { + driver := state.Get("driver") + // Hold on to your pants. The output configuration is a little more complex + // than you'd expect because of all the moving parts between local and + // remote output, and exports, and legacy behavior. + var dir OutputDir + switch d := driver.(type) { + case OutputDir: + // The driver fulfils the OutputDir interface so that it can create + // output files on the remote instance. + dir = d + default: + // The driver will be running the build and creating the output + // directory locally + dir = new(LocalOutputDir) + } + + // If remote type is esx, we need to track both the output dir on the remote + // instance and the output dir locally. exportOutputPath is where we track + // the local output dir. + exportOutputPath := s.OutputConfig.OutputDir + + if s.RemoteType != "" { + if s.OutputConfig.RemoteOutputDir != "" { + // User set the remote output dir. + s.OutputConfig.OutputDir = s.OutputConfig.RemoteOutputDir + } else { + // Default output dir to vm name. On remote esx instance, this will + // become something like /vmfs/volumes/mydatastore/vmname/vmname.vmx + s.OutputConfig.OutputDir = s.VMName + } + } + // Remember, this one's either the output from a local build, or the remote + // output from a remote build. Not the local export path for a remote build. + dir.SetOutputDir(s.OutputConfig.OutputDir) + + // Set dir in the state for use in file cleanup and artifact + state.Put("dir", dir) + state.Put("export_output_path", exportOutputPath) + return dir +} + +func (s *StepOutputDir) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + ui.Say("Configuring output and export directories...") + + dir := s.SetOutputAndExportDirs(state) exists, err := dir.DirExists() if err != nil { state.Put("error", err) @@ -31,7 +80,7 @@ func (s *StepOutputDir) Run(ctx context.Context, state multistep.StateBag) multi if exists { if s.Force { - ui.Say("Deleting previous output directory...") + ui.Message("Deleting previous output directory...") dir.RemoveAll() } else { state.Put("error", fmt.Errorf( diff --git a/builder/vmware/common/step_output_dir_test.go b/builder/vmware/common/step_output_dir_test.go index 3726a9354..51a5d9348 100644 --- a/builder/vmware/common/step_output_dir_test.go +++ b/builder/vmware/common/step_output_dir_test.go @@ -9,16 +9,14 @@ import ( "github.com/hashicorp/packer/helper/multistep" ) -func testOutputDir(t *testing.T) *LocalOutputDir { +func testOutputDir(t *testing.T) string { td, err := ioutil.TempDir("", "packer") if err != nil { t.Fatalf("err: %s", err) } os.RemoveAll(td) - result := new(LocalOutputDir) - result.SetOutputDir(td) - return result + return td } func TestStepOutputDir_impl(t *testing.T) { @@ -27,12 +25,20 @@ func TestStepOutputDir_impl(t *testing.T) { func TestStepOutputDir(t *testing.T) { state := testState(t) - step := new(StepOutputDir) + driver := new(DriverMock) + state.Put("driver", driver) - dir := testOutputDir(t) + td := testOutputDir(t) + outconfig := &OutputConfig{ + OutputDir: td, + } + + step := &StepOutputDir{ + OutputConfig: outconfig, + VMName: "testVM", + } // Delete the test output directory when done - defer os.RemoveAll(dir.dir) - state.Put("dir", dir) + defer os.RemoveAll(td) // Test the run if action := step.Run(context.Background(), state); action != multistep.ActionContinue { @@ -41,29 +47,37 @@ func TestStepOutputDir(t *testing.T) { if _, ok := state.GetOk("error"); ok { t.Fatal("should NOT have error") } - if _, err := os.Stat(dir.dir); err != nil { + if _, err := os.Stat(td); err != nil { t.Fatalf("err: %s", err) } // Test the cleanup step.Cleanup(state) - if _, err := os.Stat(dir.dir); err != nil { + if _, err := os.Stat(td); err != nil { t.Fatalf("err: %s", err) } } func TestStepOutputDir_existsNoForce(t *testing.T) { state := testState(t) - step := new(StepOutputDir) - dir := testOutputDir(t) - state.Put("dir", dir) + td := testOutputDir(t) + outconfig := &OutputConfig{ + OutputDir: td, + } + + step := &StepOutputDir{ + OutputConfig: outconfig, + VMName: "testVM", + } + // Delete the test output directory when done + defer os.RemoveAll(td) // Make sure the dir exists - if err := os.MkdirAll(dir.dir, 0755); err != nil { + if err := os.MkdirAll(td, 0755); err != nil { t.Fatalf("err: %s", err) } - defer os.RemoveAll(dir.dir) + defer os.RemoveAll(td) // Test the run if action := step.Run(context.Background(), state); action != multistep.ActionHalt { @@ -75,24 +89,33 @@ func TestStepOutputDir_existsNoForce(t *testing.T) { // Test the cleanup step.Cleanup(state) - if _, err := os.Stat(dir.dir); err != nil { + if _, err := os.Stat(td); err != nil { t.Fatal("should not delete dir") } } func TestStepOutputDir_existsForce(t *testing.T) { state := testState(t) - step := new(StepOutputDir) + + td := testOutputDir(t) + outconfig := &OutputConfig{ + OutputDir: td, + } + + step := &StepOutputDir{ + OutputConfig: outconfig, + VMName: "testVM", + } step.Force = true - dir := testOutputDir(t) - state.Put("dir", dir) + // Delete the test output directory when done + defer os.RemoveAll(td) // Make sure the dir exists - if err := os.MkdirAll(dir.dir, 0755); err != nil { + if err := os.MkdirAll(td, 0755); err != nil { t.Fatalf("err: %s", err) } - defer os.RemoveAll(dir.dir) + defer os.RemoveAll(td) // Test the run if action := step.Run(context.Background(), state); action != multistep.ActionContinue { @@ -101,17 +124,22 @@ func TestStepOutputDir_existsForce(t *testing.T) { if _, ok := state.GetOk("error"); ok { t.Fatal("should NOT have error") } - if _, err := os.Stat(dir.dir); err != nil { + if _, err := os.Stat(td); err != nil { t.Fatalf("err: %s", err) } } func TestStepOutputDir_cancel(t *testing.T) { state := testState(t) - step := new(StepOutputDir) + td := testOutputDir(t) + outconfig := &OutputConfig{ + OutputDir: td, + } - dir := testOutputDir(t) - state.Put("dir", dir) + step := &StepOutputDir{ + OutputConfig: outconfig, + VMName: "testVM", + } // Test the run if action := step.Run(context.Background(), state); action != multistep.ActionContinue { @@ -120,24 +148,29 @@ func TestStepOutputDir_cancel(t *testing.T) { if _, ok := state.GetOk("error"); ok { t.Fatal("should NOT have error") } - if _, err := os.Stat(dir.dir); err != nil { + if _, err := os.Stat(td); err != nil { t.Fatalf("err: %s", err) } // Test cancel/halt state.Put(multistep.StateCancelled, true) step.Cleanup(state) - if _, err := os.Stat(dir.dir); err == nil { + if _, err := os.Stat(td); err == nil { t.Fatal("directory should not exist") } } func TestStepOutputDir_halt(t *testing.T) { state := testState(t) - step := new(StepOutputDir) + td := testOutputDir(t) + outconfig := &OutputConfig{ + OutputDir: td, + } - dir := testOutputDir(t) - state.Put("dir", dir) + step := &StepOutputDir{ + OutputConfig: outconfig, + VMName: "testVM", + } // Test the run if action := step.Run(context.Background(), state); action != multistep.ActionContinue { @@ -146,14 +179,46 @@ func TestStepOutputDir_halt(t *testing.T) { if _, ok := state.GetOk("error"); ok { t.Fatal("should NOT have error") } - if _, err := os.Stat(dir.dir); err != nil { + if _, err := os.Stat(td); err != nil { t.Fatalf("err: %s", err) } // Test cancel/halt state.Put(multistep.StateHalted, true) step.Cleanup(state) - if _, err := os.Stat(dir.dir); err == nil { + if _, err := os.Stat(td); err == nil { t.Fatal("directory should not exist") } } + +func TestStepOutputDir_Remote(t *testing.T) { + // Tests remote driver + state := testState(t) + driver := new(RemoteDriverMock) + state.Put("driver", driver) + + td := testOutputDir(t) + outconfig := &OutputConfig{ + OutputDir: td, + RemoteOutputDir: "remote_path", + } + + step := &StepOutputDir{ + OutputConfig: outconfig, + VMName: "testVM", + RemoteType: "esx5", + } + // Delete the test output directory when done + defer os.RemoveAll(td) + + // Test the run + if action := step.Run(context.Background(), state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + + // We don't pre-create the output path for export but we do set it in state. + exportOutputPath := state.Get("export_output_path").(string) + if exportOutputPath != td { + t.Fatalf("err: should have set export_output_path!") + } +} diff --git a/builder/vmware/common/step_shutdown_test.go b/builder/vmware/common/step_shutdown_test.go index c59f495e6..952a0914e 100644 --- a/builder/vmware/common/step_shutdown_test.go +++ b/builder/vmware/common/step_shutdown_test.go @@ -12,8 +12,20 @@ import ( "github.com/hashicorp/packer/packer" ) +func testLocalOutputDir(t *testing.T) *LocalOutputDir { + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + os.RemoveAll(td) + + result := new(LocalOutputDir) + result.SetOutputDir(td) + return result +} + func testStepShutdownState(t *testing.T) multistep.StateBag { - dir := testOutputDir(t) + dir := testLocalOutputDir(t) if err := dir.MkdirAll(); err != nil { t.Fatalf("err: %s", err) } diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 0a492246d..adf1ff08a 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -36,29 +36,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack return nil, fmt.Errorf("Failed creating VMware driver: %s", err) } - // 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) - // Setup the state bag state := new(multistep.BasicStateBag) state.Put("config", &b.config) state.Put("debug", b.config.PackerDebug) - state.Put("dir", dir) state.Put("driver", driver) state.Put("hook", hook) state.Put("ui", ui) @@ -80,7 +61,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack Url: b.config.ISOUrls, }, &vmwcommon.StepOutputDir{ - Force: b.config.PackerForce, + Force: b.config.PackerForce, + OutputConfig: &b.config.OutputConfig, + RemoteType: b.config.RemoteType, + VMName: b.config.VMName, }, &common.StepCreateFloppy{ Files: b.config.FloppyConfig.FloppyFiles, @@ -179,7 +163,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack SkipExport: b.config.SkipExport, VMName: b.config.VMName, OVFToolOptions: b.config.OVFToolOptions, - OutputDir: exportOutputPath, }, } @@ -202,6 +185,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack } // Compile the artifact list + exportOutputPath := state.Get("export_output_path").(string) // set in StepOutputDir return vmwcommon.NewArtifact(b.config.RemoteType, b.config.Format, exportOutputPath, b.config.VMName, b.config.SkipExport, b.config.KeepRegistered, state) } diff --git a/builder/vmware/iso/config.hcl2spec.go b/builder/vmware/iso/config.hcl2spec.go index bfb5a1b26..394aa9da0 100644 --- a/builder/vmware/iso/config.hcl2spec.go +++ b/builder/vmware/iso/config.hcl2spec.go @@ -56,6 +56,7 @@ type FlatConfig struct { Serial *string `mapstructure:"serial" required:"false" cty:"serial" hcl:"serial"` Parallel *string `mapstructure:"parallel" required:"false" cty:"parallel" hcl:"parallel"` OutputDir *string `mapstructure:"output_directory" required:"false" cty:"output_directory" hcl:"output_directory"` + RemoteOutputDir *string `mapstructure:"remote_output_directory" required:"false" cty:"remote_output_directory" hcl:"remote_output_directory"` Headless *bool `mapstructure:"headless" required:"false" cty:"headless" hcl:"headless"` VNCBindAddress *string `mapstructure:"vnc_bind_address" required:"false" cty:"vnc_bind_address" hcl:"vnc_bind_address"` VNCPortMin *int `mapstructure:"vnc_port_min" required:"false" cty:"vnc_port_min" hcl:"vnc_port_min"` @@ -194,6 +195,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "serial": &hcldec.AttrSpec{Name: "serial", Type: cty.String, Required: false}, "parallel": &hcldec.AttrSpec{Name: "parallel", Type: cty.String, Required: false}, "output_directory": &hcldec.AttrSpec{Name: "output_directory", Type: cty.String, Required: false}, + "remote_output_directory": &hcldec.AttrSpec{Name: "remote_output_directory", Type: cty.String, Required: false}, "headless": &hcldec.AttrSpec{Name: "headless", Type: cty.Bool, Required: false}, "vnc_bind_address": &hcldec.AttrSpec{Name: "vnc_bind_address", Type: cty.String, Required: false}, "vnc_port_min": &hcldec.AttrSpec{Name: "vnc_port_min", Type: cty.Number, Required: false}, diff --git a/builder/vmware/vmx/builder.go b/builder/vmware/vmx/builder.go index ef5a47814..c43c90f63 100644 --- a/builder/vmware/vmx/builder.go +++ b/builder/vmware/vmx/builder.go @@ -41,28 +41,9 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack return nil, fmt.Errorf("Failed creating VMware driver: %s", err) } - // 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. state := new(multistep.BasicStateBag) state.Put("debug", b.config.PackerDebug) - state.Put("dir", dir) state.Put("driver", driver) state.Put("hook", hook) state.Put("ui", ui) @@ -77,7 +58,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack ToolsUploadFlavor: b.config.ToolsUploadFlavor, }, &vmwcommon.StepOutputDir{ - Force: b.config.PackerForce, + Force: b.config.PackerForce, + OutputConfig: &b.config.OutputConfig, + RemoteType: b.config.RemoteType, + VMName: b.config.VMName, }, &common.StepCreateFloppy{ Files: b.config.FloppyConfig.FloppyFiles, @@ -91,8 +75,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack Checksum: "none", }, &StepCloneVMX{ - OutputDir: b.config.OutputDir, Path: b.config.SourcePath, + OutputDir: &b.config.OutputDir, VMName: b.config.VMName, Linked: b.config.Linked, }, @@ -177,7 +161,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack SkipExport: b.config.SkipExport, VMName: b.config.VMName, OVFToolOptions: b.config.OVFToolOptions, - OutputDir: exportOutputPath, }, } @@ -201,6 +184,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack // Artifact log.Printf("Generating artifact...") + exportOutputPath := state.Get("export_output_path").(string) // set in StepOutputDir return vmwcommon.NewArtifact(b.config.RemoteType, b.config.Format, exportOutputPath, b.config.VMName, b.config.SkipExport, b.config.KeepRegistered, state) } diff --git a/builder/vmware/vmx/config.hcl2spec.go b/builder/vmware/vmx/config.hcl2spec.go index 6544a0081..2b817b80f 100644 --- a/builder/vmware/vmx/config.hcl2spec.go +++ b/builder/vmware/vmx/config.hcl2spec.go @@ -41,6 +41,7 @@ type FlatConfig struct { RemotePrivateKey *string `mapstructure:"remote_private_key_file" required:"false" cty:"remote_private_key_file" hcl:"remote_private_key_file"` SkipValidateCredentials *bool `mapstructure:"skip_validate_credentials" required:"false" cty:"skip_validate_credentials" hcl:"skip_validate_credentials"` OutputDir *string `mapstructure:"output_directory" required:"false" cty:"output_directory" hcl:"output_directory"` + RemoteOutputDir *string `mapstructure:"remote_output_directory" required:"false" cty:"remote_output_directory" hcl:"remote_output_directory"` Headless *bool `mapstructure:"headless" required:"false" cty:"headless" hcl:"headless"` VNCBindAddress *string `mapstructure:"vnc_bind_address" required:"false" cty:"vnc_bind_address" hcl:"vnc_bind_address"` VNCPortMin *int `mapstructure:"vnc_port_min" required:"false" cty:"vnc_port_min" hcl:"vnc_port_min"` @@ -156,6 +157,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "remote_private_key_file": &hcldec.AttrSpec{Name: "remote_private_key_file", Type: cty.String, Required: false}, "skip_validate_credentials": &hcldec.AttrSpec{Name: "skip_validate_credentials", Type: cty.Bool, Required: false}, "output_directory": &hcldec.AttrSpec{Name: "output_directory", Type: cty.String, Required: false}, + "remote_output_directory": &hcldec.AttrSpec{Name: "remote_output_directory", Type: cty.String, Required: false}, "headless": &hcldec.AttrSpec{Name: "headless", Type: cty.Bool, Required: false}, "vnc_bind_address": &hcldec.AttrSpec{Name: "vnc_bind_address", Type: cty.String, Required: false}, "vnc_port_min": &hcldec.AttrSpec{Name: "vnc_port_min", Type: cty.Number, Required: false}, diff --git a/builder/vmware/vmx/step_clone_vmx.go b/builder/vmware/vmx/step_clone_vmx.go index cb2b4aa2f..b7e65e7d5 100644 --- a/builder/vmware/vmx/step_clone_vmx.go +++ b/builder/vmware/vmx/step_clone_vmx.go @@ -16,7 +16,7 @@ import ( // StepCloneVMX takes a VMX file and clones the VM into the output directory. type StepCloneVMX struct { - OutputDir string + OutputDir *string Path string VMName string Linked bool @@ -33,7 +33,7 @@ func (s *StepCloneVMX) Run(ctx context.Context, state multistep.StateBag) multis ui := state.Get("ui").(packer.Ui) // Set the path we want for the new .vmx file and clone - vmxPath := filepath.Join(s.OutputDir, s.VMName+".vmx") + vmxPath := filepath.Join(*s.OutputDir, s.VMName+".vmx") ui.Say("Cloning source VM...") log.Printf("Cloning from: %s", s.Path) log.Printf("Cloning to: %s", vmxPath) @@ -96,7 +96,7 @@ func (s *StepCloneVMX) Run(ctx context.Context, state multistep.StateBag) multis var diskFullPaths []string for _, diskFilename := range diskFilenames { log.Printf("Found attached disk with filename: %s", diskFilename) - diskFullPaths = append(diskFullPaths, filepath.Join(s.OutputDir, diskFilename)) + diskFullPaths = append(diskFullPaths, filepath.Join(*s.OutputDir, diskFilename)) } if len(diskFullPaths) == 0 { diff --git a/builder/vmware/vmx/step_clone_vmx_test.go b/builder/vmware/vmx/step_clone_vmx_test.go index d781df687..da2828061 100644 --- a/builder/vmware/vmx/step_clone_vmx_test.go +++ b/builder/vmware/vmx/step_clone_vmx_test.go @@ -62,7 +62,7 @@ func TestStepCloneVMX(t *testing.T) { state := testState(t) step := new(StepCloneVMX) - step.OutputDir = td + step.OutputDir = &td step.Path = sourcePath step.VMName = "foo" diff --git a/website/pages/partials/builder/vmware/common/OutputConfig-not-required.mdx b/website/pages/partials/builder/vmware/common/OutputConfig-not-required.mdx index 6487377d5..8ad2776dc 100644 --- a/website/pages/partials/builder/vmware/common/OutputConfig-not-required.mdx +++ b/website/pages/partials/builder/vmware/common/OutputConfig-not-required.mdx @@ -1,8 +1,34 @@ -- `output_directory` (string) - This is the path to the directory where the - resulting virtual machine will be created. This may be relative or absolute. - If relative, the path is relative to the working directory when packer - is executed. This directory must not exist or be empty prior to running - the builder. By default this is output-BUILDNAME where "BUILDNAME" is the - name of the build. +- `output_directory` (string) - This is the path on your local machine (the one running Packer) to the + directory where the resulting virtual machine will be created. + This may be relative or absolute. If relative, the path is relative to + the working directory when packer is executed. + + If you are running a remote esx build, the output_dir is the path on your + local machine (the machine running Packer) to which Packer will export + the vm if you have `"skip_export": false`. If you want to manage the + virtual machine's path on the remote datastore, use `remote_output_dir`. + + This directory must not exist or be empty prior to running + the builder. + + By default this is output-BUILDNAME where "BUILDNAME" is the name of the + build. + +- `remote_output_directory` (string) - This is the directoy on your remote esx host where you will save your + vm, relative to your remote_datastore. + + This option's default value is your `vm_name`, and the final path of your + vm will be vmfs/volumes/$remote_datastore/$vm_name/$vm_name.vmx where + `$remote_datastore` and `$vm_name` match their corresponding template + options + + For example, setting `"remote_output_directory": "path/to/subdir` + will create a directory `/vmfs/volumes/remote_datastore/path/to/subdir`. + + Packer will not create the remote datastore for you; it must already + exist. However, Packer will create all directories defined in the option + that do not currently exist. + + This option will be ignored unless you are building on a remote esx host.