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 dc9962f59..adf1ff08a 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -36,44 +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) } - // Hold on to your pants. The output configuration is a little complex - // because of all the moving parts between local and remote output, and - // exports, and legacy behavior. - var dir vmwcommon.OutputDir - switch d := driver.(type) { - case vmwcommon.OutputDir: - // Remote type is esx; the driver fulfils the OutputDir interface so - // that it can create output files on the remote instance. - dir = d - default: - // Remote type is ""; the driver will be running the build and creating - // the output directory locally - dir = new(vmwcommon.LocalOutputDir) - } - - // If remote type is esx, we need to track both the output dir on the remote - // instance and the output dir locally. This is where we track the local - // output dir. - exportOutputPath := b.config.OutputDir - - if b.config.RemoteType != "" { - if b.config.RemoteOutputDir != "" { - b.config.OutputDir = b.config.RemoteOutputDir - } else { - // Default output dir to vm name. On remote esx instance, this will - // become something like /vmfs/volumes/mydatastore/vmname/vmname.vmx - b.config.OutputDir = b.config.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(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) @@ -95,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, @@ -194,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, }, } @@ -217,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/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/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"