diff --git a/builder/virtualbox/common/shutdown_config_test.go b/builder/virtualbox/common/shutdown_config_test.go index b98b3a402..5da613a19 100644 --- a/builder/virtualbox/common/shutdown_config_test.go +++ b/builder/virtualbox/common/shutdown_config_test.go @@ -2,6 +2,7 @@ package common import ( "testing" + "time" ) func testShutdownConfig() *ShutdownConfig { @@ -38,4 +39,7 @@ func TestShutdownConfigPrepare_ShutdownTimeout(t *testing.T) { if len(errs) > 0 { t.Fatalf("err: %#v", errs) } + if c.ShutdownTimeout != 5*time.Second { + t.Fatalf("bad: %s", c.ShutdownTimeout) + } } diff --git a/builder/vmware/vmx/builder.go b/builder/vmware/vmx/builder.go index c401e0a9e..7a1912b31 100644 --- a/builder/vmware/vmx/builder.go +++ b/builder/vmware/vmx/builder.go @@ -53,6 +53,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &vmwcommon.StepOutputDir{ Force: b.config.PackerForce, }, + &StepCloneVMX{ + Path: b.config.SourcePath, + }, } // Run the steps. diff --git a/builder/vmware/vmx/config.go b/builder/vmware/vmx/config.go index b4f45c4af..92a05add0 100644 --- a/builder/vmware/vmx/config.go +++ b/builder/vmware/vmx/config.go @@ -2,6 +2,7 @@ package vmx import ( "fmt" + "os" vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/common" @@ -14,6 +15,9 @@ type Config struct { vmwcommon.OutputConfig `mapstructure:",squash"` vmwcommon.SSHConfig `mapstructure:",squash"` + SourcePath string `mapstructure:"source_path"` + VMName string `mapstructure:"vm_name"` + tpl *packer.ConfigTemplate } @@ -31,13 +35,19 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { c.tpl.UserVars = c.PackerUserVars // Defaults + if c.VMName == "" { + c.VMName = fmt.Sprintf("packer-%s-{{timestamp}}", c.PackerBuildName) + } // Prepare the errors errs := common.CheckUnusedConfig(md) errs = packer.MultiErrorAppend(errs, c.OutputConfig.Prepare(c.tpl, &c.PackerConfig)...) errs = packer.MultiErrorAppend(errs, c.SSHConfig.Prepare(c.tpl)...) - templates := map[string]*string{} + templates := map[string]*string{ + "source_path": &c.SourcePath, + "vm_name": &c.VMName, + } for n, ptr := range templates { var err error @@ -48,6 +58,15 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } } + if c.SourcePath == "" { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is required")) + } else { + if _, err := os.Stat(c.SourcePath); err != nil { + errs = packer.MultiErrorAppend(errs, + fmt.Errorf("source_path is invalid: %s", err)) + } + } + // Warnings var warnings []string diff --git a/builder/vmware/vmx/config_test.go b/builder/vmware/vmx/config_test.go index 7128d8a71..d1e8a7860 100644 --- a/builder/vmware/vmx/config_test.go +++ b/builder/vmware/vmx/config_test.go @@ -1,11 +1,15 @@ package vmx import ( + "io/ioutil" + "os" "testing" ) func testConfig(t *testing.T) map[string]interface{} { - return map[string]interface{}{} + return map[string]interface{}{ + "ssh_username": "foo", + } } func testConfigErr(t *testing.T, warns []string, err error) { @@ -25,3 +29,30 @@ func testConfigOk(t *testing.T, warns []string, err error) { t.Fatalf("bad: %s", err) } } + +func TestNewConfig_sourcePath(t *testing.T) { + // Bad + c := testConfig(t) + delete(c, "source_path") + _, warns, errs := NewConfig(c) + testConfigErr(t, warns, errs) + + // Bad + c = testConfig(t) + c["source_path"] = "/i/dont/exist" + _, warns, errs = NewConfig(c) + testConfigErr(t, warns, errs) + + // Good + tf, err := ioutil.TempFile("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + tf.Close() + defer os.Remove(tf.Name()) + + c = testConfig(t) + c["source_path"] = tf.Name() + _, warns, errs = NewConfig(c) + testConfigOk(t, warns, errs) +} diff --git a/builder/vmware/vmx/step_clone_vmx.go b/builder/vmware/vmx/step_clone_vmx.go new file mode 100644 index 000000000..431406ab1 --- /dev/null +++ b/builder/vmware/vmx/step_clone_vmx.go @@ -0,0 +1,53 @@ +package vmx + +import ( + "io" + "log" + "os" + "path/filepath" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +// StepCloneVMX takes a VMX file and clones the VM into the output directory. +type StepCloneVMX struct { + OutputDir string + Path string + VMName string +} + +func (s *StepCloneVMX) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + vmxPath := filepath.Join(s.OutputDir, s.VMName+".vmx") + + ui.Say("Cloning VMX...") + log.Printf("Cloning from: %s", s.Path) + log.Printf("Cloning to: %s", vmxPath) + + from, err := os.Open(s.Path) + if err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + defer from.Close() + + to, err := os.Create(vmxPath) + if err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + defer to.Close() + + if _, err := io.Copy(to, from); err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + + state.Put("vmx_path", vmxPath) + return multistep.ActionContinue +} + +func (s *StepCloneVMX) Cleanup(state multistep.StateBag) { +} diff --git a/builder/vmware/vmx/step_clone_vmx_test.go b/builder/vmware/vmx/step_clone_vmx_test.go new file mode 100644 index 000000000..2a4f00124 --- /dev/null +++ b/builder/vmware/vmx/step_clone_vmx_test.go @@ -0,0 +1,56 @@ +package vmx + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/mitchellh/multistep" +) + +func TestStepCloneVMX_impl(t *testing.T) { + var _ multistep.Step = new(StepCloneVMX) +} + +func TestStepCloneVMX(t *testing.T) { + // Setup some state + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(td) + + // Create the source + sourcePath := filepath.Join(td, "source.vmx") + if err := ioutil.WriteFile(sourcePath, []byte("foo"), 0644); err != nil { + t.Fatalf("err: %s", err) + } + + state := testState(t) + step := new(StepCloneVMX) + step.OutputDir = td + step.Path = sourcePath + step.VMName = "foo" + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test we have our VMX + if _, err := os.Stat(filepath.Join(td, "foo.vmx")); err != nil { + t.Fatalf("err: %s", err) + } + + data, err := ioutil.ReadFile(filepath.Join(td, "foo.vmx")) + if err != nil { + t.Fatalf("err: %s", err) + } + if string(data) != "foo" { + t.Fatalf("bad: %#v", string(data)) + } +} diff --git a/builder/vmware/vmx/step_test.go b/builder/vmware/vmx/step_test.go new file mode 100644 index 000000000..ad8075a61 --- /dev/null +++ b/builder/vmware/vmx/step_test.go @@ -0,0 +1,20 @@ +package vmx + +import ( + "bytes" + "testing" + + "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" + "github.com/mitchellh/packer/packer" +) + +func testState(t *testing.T) multistep.StateBag { + state := new(multistep.BasicStateBag) + state.Put("driver", new(vmwcommon.DriverMock)) + state.Put("ui", &packer.BasicUi{ + Reader: new(bytes.Buffer), + Writer: new(bytes.Buffer), + }) + return state +}