From 39d060008544dd53a97a06d7bc2d6245cac5476b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 23 Dec 2013 14:21:28 -0800 Subject: [PATCH 01/45] builder/virtualbox: fmt --- builder/virtualbox/ovf/config.go | 2 +- builder/virtualbox/ovf/config_test.go | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/builder/virtualbox/ovf/config.go b/builder/virtualbox/ovf/config.go index 12141cc23..9e37659fc 100644 --- a/builder/virtualbox/ovf/config.go +++ b/builder/virtualbox/ovf/config.go @@ -57,7 +57,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { templates := map[string]*string{ "source_path": &c.SourcePath, - "vm_name": &c.VMName, + "vm_name": &c.VMName, } for n, ptr := range templates { diff --git a/builder/virtualbox/ovf/config_test.go b/builder/virtualbox/ovf/config_test.go index 56cb088f5..dbb156ce1 100644 --- a/builder/virtualbox/ovf/config_test.go +++ b/builder/virtualbox/ovf/config_test.go @@ -1,14 +1,14 @@ package ovf import ( - "testing" "io/ioutil" "os" + "testing" ) func testConfig(t *testing.T) map[string]interface{} { return map[string]interface{}{ - "ssh_username": "foo", + "ssh_username": "foo", "shutdown_command": "foo", } } @@ -31,7 +31,6 @@ func testConfigOk(t *testing.T, warns []string, err error) { } } - func TestNewConfig_sourcePath(t *testing.T) { // Bad c := testConfig(t) @@ -58,4 +57,3 @@ func TestNewConfig_sourcePath(t *testing.T) { _, warns, errs = NewConfig(c) testConfigOk(t, warns, errs) } - From 5ab83238bf55033aa4df3c5328052b371d10026f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 23 Dec 2013 14:38:54 -0800 Subject: [PATCH 02/45] builder/vmware: separate step to configure vmx --- builder/vmware/builder.go | 3 + builder/vmware/step_configure_vmx.go | 78 +++++++++ builder/vmware/step_configure_vmx_test.go | 183 ++++++++++++++++++++++ builder/vmware/step_create_vmx.go | 24 +-- builder/vmware/step_test.go | 17 ++ 5 files changed, 282 insertions(+), 23 deletions(-) create mode 100644 builder/vmware/step_configure_vmx.go create mode 100644 builder/vmware/step_configure_vmx_test.go create mode 100644 builder/vmware/step_test.go diff --git a/builder/vmware/builder.go b/builder/vmware/builder.go index a9fe6c6b9..5df26f0ac 100644 --- a/builder/vmware/builder.go +++ b/builder/vmware/builder.go @@ -405,6 +405,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe }, &stepCreateDisk{}, &stepCreateVMX{}, + &StepConfigureVMX{ + CustomData: b.config.VMXData, + }, &stepSuppressMessages{}, &stepHTTPServer{}, &stepConfigureVNC{}, diff --git a/builder/vmware/step_configure_vmx.go b/builder/vmware/step_configure_vmx.go new file mode 100644 index 000000000..a9aeabe31 --- /dev/null +++ b/builder/vmware/step_configure_vmx.go @@ -0,0 +1,78 @@ +package vmware + +import ( + "fmt" + "io/ioutil" + "log" + "regexp" + "strings" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +// This step configures a VMX by setting some default settings as well +// as taking in custom data to set, attaching a floppy if it exists, etc. +// +// Uses: +// vmx_path string +type StepConfigureVMX struct { + CustomData map[string]string +} + +func (s *StepConfigureVMX) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + vmxPath := state.Get("vmx_path").(string) + + vmxContents, err := ioutil.ReadFile(vmxPath) + if err != nil { + err := fmt.Errorf("Error reading VMX file: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + vmxData := ParseVMX(string(vmxContents)) + + // Set this so that no dialogs ever appear from Packer. + vmxData["msg.autoanswer"] = "true" + + // Create a new UUID for this VM, since it is a new VM + vmxData["uuid.action"] = "create" + + // Delete any generated addresses since we want to regenerate + // them. Conflicting MAC addresses is a bad time. + addrRegex := regexp.MustCompile(`(?i)^ethernet\d+\.generatedAddress`) + for k, _ := range vmxData { + if addrRegex.MatchString(k) { + delete(vmxData, k) + } + } + + // Set custom data + for k, v := range s.CustomData { + log.Printf("Setting VMX: '%s' = '%s'", k, v) + k = strings.ToLower(k) + vmxData[k] = v + } + + // Set a floppy disk if we have one + if floppyPathRaw, ok := state.GetOk("floppy_path"); ok { + log.Println("Floppy path present, setting in VMX") + vmxData["floppy0.present"] = "TRUE" + vmxData["floppy0.filetype"] = "file" + vmxData["floppy0.filename"] = floppyPathRaw.(string) + } + + if err := WriteVMX(vmxPath, vmxData); err != nil { + err := fmt.Errorf("Error writing VMX file: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *StepConfigureVMX) Cleanup(state multistep.StateBag) { +} diff --git a/builder/vmware/step_configure_vmx_test.go b/builder/vmware/step_configure_vmx_test.go new file mode 100644 index 000000000..e46812c9a --- /dev/null +++ b/builder/vmware/step_configure_vmx_test.go @@ -0,0 +1,183 @@ +package vmware + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/mitchellh/multistep" +) + +func testVMXFile(t *testing.T) string { + tf, err := ioutil.TempFile("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + tf.Close() + + return tf.Name() +} + +func TestStepConfigureVMX_impl(t *testing.T) { + var _ multistep.Step = new(StepConfigureVMX) +} + +func TestStepConfigureVMX(t *testing.T) { + state := testState(t) + step := new(StepConfigureVMX) + step.CustomData = map[string]string{ + "foo": "bar", + } + + vmxPath := testVMXFile(t) + defer os.Remove(vmxPath) + state.Put("vmx_path", vmxPath) + + // 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 the resulting data + vmxContents, err := ioutil.ReadFile(vmxPath) + if err != nil { + t.Fatalf("err: %s", err) + } + vmxData := ParseVMX(string(vmxContents)) + + cases := []struct { + Key string + Value string + }{ + // Stuff we set + {"msg.autoanswer", "true"}, + {"uuid.action", "create"}, + + // Custom data + {"foo", "bar"}, + + // Stuff that should NOT exist + {"floppy0.present", ""}, + } + + for _, tc := range cases { + if tc.Value == "" { + if _, ok := vmxData[tc.Key]; ok { + t.Fatalf("should not have key: %s", tc.Key) + } + } else { + if vmxData[tc.Key] != tc.Value { + t.Fatalf("bad: %s %#v", tc.Key, vmxData[tc.Key]) + } + } + } +} + +func TestStepConfigureVMX_floppyPath(t *testing.T) { + state := testState(t) + step := new(StepConfigureVMX) + + vmxPath := testVMXFile(t) + defer os.Remove(vmxPath) + + state.Put("floppy_path", "foo") + state.Put("vmx_path", vmxPath) + + // 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 the resulting data + vmxContents, err := ioutil.ReadFile(vmxPath) + if err != nil { + t.Fatalf("err: %s", err) + } + vmxData := ParseVMX(string(vmxContents)) + + cases := []struct { + Key string + Value string + }{ + {"floppy0.present", "TRUE"}, + {"floppy0.filetype", "file"}, + {"floppy0.filename", "foo"}, + } + + for _, tc := range cases { + if tc.Value == "" { + if _, ok := vmxData[tc.Key]; ok { + t.Fatalf("should not have key: %s", tc.Key) + } + } else { + if vmxData[tc.Key] != tc.Value { + t.Fatalf("bad: %s %#v", tc.Key, vmxData[tc.Key]) + } + } + } + +} + +func TestStepConfigureVMX_generatedAddresses(t *testing.T) { + state := testState(t) + step := new(StepConfigureVMX) + + vmxPath := testVMXFile(t) + defer os.Remove(vmxPath) + + err := WriteVMX(vmxPath, map[string]string{ + "foo": "bar", + "ethernet0.generatedAddress": "foo", + "ethernet1.generatedAddress": "foo", + "ethernet1.generatedAddressOffset": "foo", + }) + if err != nil { + t.Fatalf("err: %s", err) + } + + state.Put("vmx_path", vmxPath) + + // 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 the resulting data + vmxContents, err := ioutil.ReadFile(vmxPath) + if err != nil { + t.Fatalf("err: %s", err) + } + vmxData := ParseVMX(string(vmxContents)) + + cases := []struct { + Key string + Value string + }{ + {"foo", "bar"}, + {"ethernet0.generatedaddress", ""}, + {"ethernet1.generatedaddress", ""}, + {"ethernet1.generatedaddressoffset", ""}, + } + + for _, tc := range cases { + if tc.Value == "" { + if _, ok := vmxData[tc.Key]; ok { + t.Fatalf("should not have key: %s", tc.Key) + } + } else { + if vmxData[tc.Key] != tc.Value { + t.Fatalf("bad: %s %#v", tc.Key, vmxData[tc.Key]) + } + } + } + +} diff --git a/builder/vmware/step_create_vmx.go b/builder/vmware/step_create_vmx.go index 6039b3dfc..b35d2885c 100644 --- a/builder/vmware/step_create_vmx.go +++ b/builder/vmware/step_create_vmx.go @@ -5,10 +5,8 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "io/ioutil" - "log" "os" "path/filepath" - "strings" ) type vmxTemplateData struct { @@ -75,26 +73,6 @@ func (s *stepCreateVMX) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - vmxData := ParseVMX(vmxContents) - if config.VMXData != nil { - log.Println("Setting custom VMX data...") - for k, v := range config.VMXData { - log.Printf("Setting VMX: '%s' = '%s'", k, v) - k = strings.ToLower(k) - vmxData[k] = v - } - } - - if floppyPathRaw, ok := state.GetOk("floppy_path"); ok { - log.Println("Floppy path present, setting in VMX") - vmxData["floppy0.present"] = "TRUE" - vmxData["floppy0.filetype"] = "file" - vmxData["floppy0.filename"] = floppyPathRaw.(string) - } - - // Set this so that no dialogs ever appear from Packer. - vmxData["msg.autoanswer"] = "true" - vmxDir := config.OutputDir if config.RemoteType != "" { // For remote builds, we just put the VMX in a temporary @@ -112,7 +90,7 @@ func (s *stepCreateVMX) Run(state multistep.StateBag) multistep.StepAction { } vmxPath := filepath.Join(vmxDir, config.VMName+".vmx") - if err := WriteVMX(vmxPath, vmxData); err != nil { + if err := WriteVMX(vmxPath, ParseVMX(vmxContents)); err != nil { err := fmt.Errorf("Error creating VMX file: %s", err) state.Put("error", err) ui.Error(err.Error()) diff --git a/builder/vmware/step_test.go b/builder/vmware/step_test.go new file mode 100644 index 000000000..71bce40f8 --- /dev/null +++ b/builder/vmware/step_test.go @@ -0,0 +1,17 @@ +package vmware + +import ( + "bytes" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "testing" +) + +func testState(t *testing.T) multistep.StateBag { + state := new(multistep.BasicStateBag) + state.Put("ui", &packer.BasicUi{ + Reader: new(bytes.Buffer), + Writer: new(bytes.Buffer), + }) + return state +} From d1e68875a714301962184048ed6fe4b4c1f3b588 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 23 Dec 2013 22:58:13 -0700 Subject: [PATCH 03/45] builder/vmware: move to iso package --- builder/vmware/{ => iso}/artifact.go | 0 builder/vmware/{ => iso}/artifact_test.go | 0 builder/vmware/{ => iso}/builder.go | 0 builder/vmware/{ => iso}/builder_test.go | 0 builder/vmware/{ => iso}/driver.go | 0 builder/vmware/{ => iso}/driver_esx5.go | 0 builder/vmware/{ => iso}/driver_esx5_test.go | 0 builder/vmware/{ => iso}/driver_fusion5.go | 0 builder/vmware/{ => iso}/driver_player5.go | 0 builder/vmware/{ => iso}/driver_workstation9.go | 0 builder/vmware/{ => iso}/driver_workstation9_unix.go | 0 builder/vmware/{ => iso}/driver_workstation9_windows.go | 0 builder/vmware/{ => iso}/guest_ip.go | 0 builder/vmware/{ => iso}/host_ip.go | 0 builder/vmware/{ => iso}/host_ip_ifconfig.go | 0 builder/vmware/{ => iso}/host_ip_ifconfig_test.go | 0 builder/vmware/{ => iso}/host_ip_vmnetnatconf.go | 0 builder/vmware/{ => iso}/host_ip_vmnetnatconf_test.go | 0 builder/vmware/{ => iso}/output_dir.go | 0 builder/vmware/{ => iso}/remote_driver.go | 0 builder/vmware/{ => iso}/ssh.go | 0 builder/vmware/{ => iso}/step_clean_files.go | 0 builder/vmware/{ => iso}/step_clean_vmx.go | 0 builder/vmware/{ => iso}/step_compact_disk.go | 0 builder/vmware/{ => iso}/step_configure_vmx.go | 0 builder/vmware/{ => iso}/step_configure_vmx_test.go | 0 builder/vmware/{ => iso}/step_configure_vnc.go | 0 builder/vmware/{ => iso}/step_create_disk.go | 0 builder/vmware/{ => iso}/step_create_vmx.go | 0 builder/vmware/{ => iso}/step_http_server.go | 0 builder/vmware/{ => iso}/step_prepare_output_dir.go | 0 builder/vmware/{ => iso}/step_prepare_tools.go | 0 builder/vmware/{ => iso}/step_remote_upload.go | 0 builder/vmware/{ => iso}/step_run.go | 0 builder/vmware/{ => iso}/step_shutdown.go | 0 builder/vmware/{ => iso}/step_suppress_messages.go | 0 builder/vmware/{ => iso}/step_test.go | 0 builder/vmware/{ => iso}/step_type_boot_command.go | 0 builder/vmware/{ => iso}/step_upload_tools.go | 0 builder/vmware/{ => iso}/vmx.go | 0 builder/vmware/{ => iso}/vmx_test.go | 0 41 files changed, 0 insertions(+), 0 deletions(-) rename builder/vmware/{ => iso}/artifact.go (100%) rename builder/vmware/{ => iso}/artifact_test.go (100%) rename builder/vmware/{ => iso}/builder.go (100%) rename builder/vmware/{ => iso}/builder_test.go (100%) rename builder/vmware/{ => iso}/driver.go (100%) rename builder/vmware/{ => iso}/driver_esx5.go (100%) rename builder/vmware/{ => iso}/driver_esx5_test.go (100%) rename builder/vmware/{ => iso}/driver_fusion5.go (100%) rename builder/vmware/{ => iso}/driver_player5.go (100%) rename builder/vmware/{ => iso}/driver_workstation9.go (100%) rename builder/vmware/{ => iso}/driver_workstation9_unix.go (100%) rename builder/vmware/{ => iso}/driver_workstation9_windows.go (100%) rename builder/vmware/{ => iso}/guest_ip.go (100%) rename builder/vmware/{ => iso}/host_ip.go (100%) rename builder/vmware/{ => iso}/host_ip_ifconfig.go (100%) rename builder/vmware/{ => iso}/host_ip_ifconfig_test.go (100%) rename builder/vmware/{ => iso}/host_ip_vmnetnatconf.go (100%) rename builder/vmware/{ => iso}/host_ip_vmnetnatconf_test.go (100%) rename builder/vmware/{ => iso}/output_dir.go (100%) rename builder/vmware/{ => iso}/remote_driver.go (100%) rename builder/vmware/{ => iso}/ssh.go (100%) rename builder/vmware/{ => iso}/step_clean_files.go (100%) rename builder/vmware/{ => iso}/step_clean_vmx.go (100%) rename builder/vmware/{ => iso}/step_compact_disk.go (100%) rename builder/vmware/{ => iso}/step_configure_vmx.go (100%) rename builder/vmware/{ => iso}/step_configure_vmx_test.go (100%) rename builder/vmware/{ => iso}/step_configure_vnc.go (100%) rename builder/vmware/{ => iso}/step_create_disk.go (100%) rename builder/vmware/{ => iso}/step_create_vmx.go (100%) rename builder/vmware/{ => iso}/step_http_server.go (100%) rename builder/vmware/{ => iso}/step_prepare_output_dir.go (100%) rename builder/vmware/{ => iso}/step_prepare_tools.go (100%) rename builder/vmware/{ => iso}/step_remote_upload.go (100%) rename builder/vmware/{ => iso}/step_run.go (100%) rename builder/vmware/{ => iso}/step_shutdown.go (100%) rename builder/vmware/{ => iso}/step_suppress_messages.go (100%) rename builder/vmware/{ => iso}/step_test.go (100%) rename builder/vmware/{ => iso}/step_type_boot_command.go (100%) rename builder/vmware/{ => iso}/step_upload_tools.go (100%) rename builder/vmware/{ => iso}/vmx.go (100%) rename builder/vmware/{ => iso}/vmx_test.go (100%) diff --git a/builder/vmware/artifact.go b/builder/vmware/iso/artifact.go similarity index 100% rename from builder/vmware/artifact.go rename to builder/vmware/iso/artifact.go diff --git a/builder/vmware/artifact_test.go b/builder/vmware/iso/artifact_test.go similarity index 100% rename from builder/vmware/artifact_test.go rename to builder/vmware/iso/artifact_test.go diff --git a/builder/vmware/builder.go b/builder/vmware/iso/builder.go similarity index 100% rename from builder/vmware/builder.go rename to builder/vmware/iso/builder.go diff --git a/builder/vmware/builder_test.go b/builder/vmware/iso/builder_test.go similarity index 100% rename from builder/vmware/builder_test.go rename to builder/vmware/iso/builder_test.go diff --git a/builder/vmware/driver.go b/builder/vmware/iso/driver.go similarity index 100% rename from builder/vmware/driver.go rename to builder/vmware/iso/driver.go diff --git a/builder/vmware/driver_esx5.go b/builder/vmware/iso/driver_esx5.go similarity index 100% rename from builder/vmware/driver_esx5.go rename to builder/vmware/iso/driver_esx5.go diff --git a/builder/vmware/driver_esx5_test.go b/builder/vmware/iso/driver_esx5_test.go similarity index 100% rename from builder/vmware/driver_esx5_test.go rename to builder/vmware/iso/driver_esx5_test.go diff --git a/builder/vmware/driver_fusion5.go b/builder/vmware/iso/driver_fusion5.go similarity index 100% rename from builder/vmware/driver_fusion5.go rename to builder/vmware/iso/driver_fusion5.go diff --git a/builder/vmware/driver_player5.go b/builder/vmware/iso/driver_player5.go similarity index 100% rename from builder/vmware/driver_player5.go rename to builder/vmware/iso/driver_player5.go diff --git a/builder/vmware/driver_workstation9.go b/builder/vmware/iso/driver_workstation9.go similarity index 100% rename from builder/vmware/driver_workstation9.go rename to builder/vmware/iso/driver_workstation9.go diff --git a/builder/vmware/driver_workstation9_unix.go b/builder/vmware/iso/driver_workstation9_unix.go similarity index 100% rename from builder/vmware/driver_workstation9_unix.go rename to builder/vmware/iso/driver_workstation9_unix.go diff --git a/builder/vmware/driver_workstation9_windows.go b/builder/vmware/iso/driver_workstation9_windows.go similarity index 100% rename from builder/vmware/driver_workstation9_windows.go rename to builder/vmware/iso/driver_workstation9_windows.go diff --git a/builder/vmware/guest_ip.go b/builder/vmware/iso/guest_ip.go similarity index 100% rename from builder/vmware/guest_ip.go rename to builder/vmware/iso/guest_ip.go diff --git a/builder/vmware/host_ip.go b/builder/vmware/iso/host_ip.go similarity index 100% rename from builder/vmware/host_ip.go rename to builder/vmware/iso/host_ip.go diff --git a/builder/vmware/host_ip_ifconfig.go b/builder/vmware/iso/host_ip_ifconfig.go similarity index 100% rename from builder/vmware/host_ip_ifconfig.go rename to builder/vmware/iso/host_ip_ifconfig.go diff --git a/builder/vmware/host_ip_ifconfig_test.go b/builder/vmware/iso/host_ip_ifconfig_test.go similarity index 100% rename from builder/vmware/host_ip_ifconfig_test.go rename to builder/vmware/iso/host_ip_ifconfig_test.go diff --git a/builder/vmware/host_ip_vmnetnatconf.go b/builder/vmware/iso/host_ip_vmnetnatconf.go similarity index 100% rename from builder/vmware/host_ip_vmnetnatconf.go rename to builder/vmware/iso/host_ip_vmnetnatconf.go diff --git a/builder/vmware/host_ip_vmnetnatconf_test.go b/builder/vmware/iso/host_ip_vmnetnatconf_test.go similarity index 100% rename from builder/vmware/host_ip_vmnetnatconf_test.go rename to builder/vmware/iso/host_ip_vmnetnatconf_test.go diff --git a/builder/vmware/output_dir.go b/builder/vmware/iso/output_dir.go similarity index 100% rename from builder/vmware/output_dir.go rename to builder/vmware/iso/output_dir.go diff --git a/builder/vmware/remote_driver.go b/builder/vmware/iso/remote_driver.go similarity index 100% rename from builder/vmware/remote_driver.go rename to builder/vmware/iso/remote_driver.go diff --git a/builder/vmware/ssh.go b/builder/vmware/iso/ssh.go similarity index 100% rename from builder/vmware/ssh.go rename to builder/vmware/iso/ssh.go diff --git a/builder/vmware/step_clean_files.go b/builder/vmware/iso/step_clean_files.go similarity index 100% rename from builder/vmware/step_clean_files.go rename to builder/vmware/iso/step_clean_files.go diff --git a/builder/vmware/step_clean_vmx.go b/builder/vmware/iso/step_clean_vmx.go similarity index 100% rename from builder/vmware/step_clean_vmx.go rename to builder/vmware/iso/step_clean_vmx.go diff --git a/builder/vmware/step_compact_disk.go b/builder/vmware/iso/step_compact_disk.go similarity index 100% rename from builder/vmware/step_compact_disk.go rename to builder/vmware/iso/step_compact_disk.go diff --git a/builder/vmware/step_configure_vmx.go b/builder/vmware/iso/step_configure_vmx.go similarity index 100% rename from builder/vmware/step_configure_vmx.go rename to builder/vmware/iso/step_configure_vmx.go diff --git a/builder/vmware/step_configure_vmx_test.go b/builder/vmware/iso/step_configure_vmx_test.go similarity index 100% rename from builder/vmware/step_configure_vmx_test.go rename to builder/vmware/iso/step_configure_vmx_test.go diff --git a/builder/vmware/step_configure_vnc.go b/builder/vmware/iso/step_configure_vnc.go similarity index 100% rename from builder/vmware/step_configure_vnc.go rename to builder/vmware/iso/step_configure_vnc.go diff --git a/builder/vmware/step_create_disk.go b/builder/vmware/iso/step_create_disk.go similarity index 100% rename from builder/vmware/step_create_disk.go rename to builder/vmware/iso/step_create_disk.go diff --git a/builder/vmware/step_create_vmx.go b/builder/vmware/iso/step_create_vmx.go similarity index 100% rename from builder/vmware/step_create_vmx.go rename to builder/vmware/iso/step_create_vmx.go diff --git a/builder/vmware/step_http_server.go b/builder/vmware/iso/step_http_server.go similarity index 100% rename from builder/vmware/step_http_server.go rename to builder/vmware/iso/step_http_server.go diff --git a/builder/vmware/step_prepare_output_dir.go b/builder/vmware/iso/step_prepare_output_dir.go similarity index 100% rename from builder/vmware/step_prepare_output_dir.go rename to builder/vmware/iso/step_prepare_output_dir.go diff --git a/builder/vmware/step_prepare_tools.go b/builder/vmware/iso/step_prepare_tools.go similarity index 100% rename from builder/vmware/step_prepare_tools.go rename to builder/vmware/iso/step_prepare_tools.go diff --git a/builder/vmware/step_remote_upload.go b/builder/vmware/iso/step_remote_upload.go similarity index 100% rename from builder/vmware/step_remote_upload.go rename to builder/vmware/iso/step_remote_upload.go diff --git a/builder/vmware/step_run.go b/builder/vmware/iso/step_run.go similarity index 100% rename from builder/vmware/step_run.go rename to builder/vmware/iso/step_run.go diff --git a/builder/vmware/step_shutdown.go b/builder/vmware/iso/step_shutdown.go similarity index 100% rename from builder/vmware/step_shutdown.go rename to builder/vmware/iso/step_shutdown.go diff --git a/builder/vmware/step_suppress_messages.go b/builder/vmware/iso/step_suppress_messages.go similarity index 100% rename from builder/vmware/step_suppress_messages.go rename to builder/vmware/iso/step_suppress_messages.go diff --git a/builder/vmware/step_test.go b/builder/vmware/iso/step_test.go similarity index 100% rename from builder/vmware/step_test.go rename to builder/vmware/iso/step_test.go diff --git a/builder/vmware/step_type_boot_command.go b/builder/vmware/iso/step_type_boot_command.go similarity index 100% rename from builder/vmware/step_type_boot_command.go rename to builder/vmware/iso/step_type_boot_command.go diff --git a/builder/vmware/step_upload_tools.go b/builder/vmware/iso/step_upload_tools.go similarity index 100% rename from builder/vmware/step_upload_tools.go rename to builder/vmware/iso/step_upload_tools.go diff --git a/builder/vmware/vmx.go b/builder/vmware/iso/vmx.go similarity index 100% rename from builder/vmware/vmx.go rename to builder/vmware/iso/vmx.go diff --git a/builder/vmware/vmx_test.go b/builder/vmware/iso/vmx_test.go similarity index 100% rename from builder/vmware/vmx_test.go rename to builder/vmware/iso/vmx_test.go From 91392feb58a81dd96b36d0086a94a3d5c27499a1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 23 Dec 2013 22:58:41 -0700 Subject: [PATCH 04/45] builder/vmware/iso: fix package name --- builder/vmware/iso/artifact.go | 2 +- builder/vmware/iso/artifact_test.go | 2 +- builder/vmware/iso/builder.go | 2 +- builder/vmware/iso/builder_test.go | 2 +- builder/vmware/iso/driver.go | 2 +- builder/vmware/iso/driver_esx5.go | 2 +- builder/vmware/iso/driver_esx5_test.go | 2 +- builder/vmware/iso/driver_fusion5.go | 2 +- builder/vmware/iso/driver_player5.go | 2 +- builder/vmware/iso/driver_workstation9.go | 2 +- builder/vmware/iso/driver_workstation9_unix.go | 2 +- builder/vmware/iso/driver_workstation9_windows.go | 2 +- builder/vmware/iso/guest_ip.go | 2 +- builder/vmware/iso/host_ip.go | 2 +- builder/vmware/iso/host_ip_ifconfig.go | 2 +- builder/vmware/iso/host_ip_ifconfig_test.go | 2 +- builder/vmware/iso/host_ip_vmnetnatconf.go | 2 +- builder/vmware/iso/host_ip_vmnetnatconf_test.go | 2 +- builder/vmware/iso/output_dir.go | 2 +- builder/vmware/iso/remote_driver.go | 2 +- builder/vmware/iso/ssh.go | 2 +- builder/vmware/iso/step_clean_files.go | 2 +- builder/vmware/iso/step_clean_vmx.go | 2 +- builder/vmware/iso/step_compact_disk.go | 2 +- builder/vmware/iso/step_configure_vmx.go | 2 +- builder/vmware/iso/step_configure_vmx_test.go | 2 +- builder/vmware/iso/step_configure_vnc.go | 2 +- builder/vmware/iso/step_create_disk.go | 2 +- builder/vmware/iso/step_create_vmx.go | 2 +- builder/vmware/iso/step_http_server.go | 2 +- builder/vmware/iso/step_prepare_output_dir.go | 2 +- builder/vmware/iso/step_prepare_tools.go | 2 +- builder/vmware/iso/step_remote_upload.go | 2 +- builder/vmware/iso/step_run.go | 2 +- builder/vmware/iso/step_shutdown.go | 2 +- builder/vmware/iso/step_suppress_messages.go | 2 +- builder/vmware/iso/step_test.go | 2 +- builder/vmware/iso/step_type_boot_command.go | 2 +- builder/vmware/iso/step_upload_tools.go | 2 +- builder/vmware/iso/vmx.go | 2 +- builder/vmware/iso/vmx_test.go | 2 +- 41 files changed, 41 insertions(+), 41 deletions(-) diff --git a/builder/vmware/iso/artifact.go b/builder/vmware/iso/artifact.go index 7e6fddb92..56924c3df 100644 --- a/builder/vmware/iso/artifact.go +++ b/builder/vmware/iso/artifact.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "fmt" diff --git a/builder/vmware/iso/artifact_test.go b/builder/vmware/iso/artifact_test.go index ea96fe569..4394c2735 100644 --- a/builder/vmware/iso/artifact_test.go +++ b/builder/vmware/iso/artifact_test.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "github.com/mitchellh/packer/packer" diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 5df26f0ac..a65176d2c 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "errors" diff --git a/builder/vmware/iso/builder_test.go b/builder/vmware/iso/builder_test.go index 893970768..a722595cd 100644 --- a/builder/vmware/iso/builder_test.go +++ b/builder/vmware/iso/builder_test.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "github.com/mitchellh/packer/packer" diff --git a/builder/vmware/iso/driver.go b/builder/vmware/iso/driver.go index 7031aebd5..3e52af5a8 100644 --- a/builder/vmware/iso/driver.go +++ b/builder/vmware/iso/driver.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "bytes" diff --git a/builder/vmware/iso/driver_esx5.go b/builder/vmware/iso/driver_esx5.go index 83d326333..2fc86f70c 100644 --- a/builder/vmware/iso/driver_esx5.go +++ b/builder/vmware/iso/driver_esx5.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "bufio" diff --git a/builder/vmware/iso/driver_esx5_test.go b/builder/vmware/iso/driver_esx5_test.go index 2c3184199..577e68d71 100644 --- a/builder/vmware/iso/driver_esx5_test.go +++ b/builder/vmware/iso/driver_esx5_test.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "testing" diff --git a/builder/vmware/iso/driver_fusion5.go b/builder/vmware/iso/driver_fusion5.go index a857237ae..5674acafe 100644 --- a/builder/vmware/iso/driver_fusion5.go +++ b/builder/vmware/iso/driver_fusion5.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "fmt" diff --git a/builder/vmware/iso/driver_player5.go b/builder/vmware/iso/driver_player5.go index 7f68a80a2..56a8b0b06 100644 --- a/builder/vmware/iso/driver_player5.go +++ b/builder/vmware/iso/driver_player5.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "fmt" diff --git a/builder/vmware/iso/driver_workstation9.go b/builder/vmware/iso/driver_workstation9.go index 47ac14220..101572ada 100644 --- a/builder/vmware/iso/driver_workstation9.go +++ b/builder/vmware/iso/driver_workstation9.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "fmt" diff --git a/builder/vmware/iso/driver_workstation9_unix.go b/builder/vmware/iso/driver_workstation9_unix.go index 31d0f33dc..c3c6288cd 100644 --- a/builder/vmware/iso/driver_workstation9_unix.go +++ b/builder/vmware/iso/driver_workstation9_unix.go @@ -1,6 +1,6 @@ // +build !windows -package vmware +package iso import ( "errors" diff --git a/builder/vmware/iso/driver_workstation9_windows.go b/builder/vmware/iso/driver_workstation9_windows.go index 19a7679c9..3c4d91105 100644 --- a/builder/vmware/iso/driver_workstation9_windows.go +++ b/builder/vmware/iso/driver_workstation9_windows.go @@ -1,6 +1,6 @@ // +build windows -package vmware +package iso import ( "log" diff --git a/builder/vmware/iso/guest_ip.go b/builder/vmware/iso/guest_ip.go index 7f259b418..2b9ca53fe 100644 --- a/builder/vmware/iso/guest_ip.go +++ b/builder/vmware/iso/guest_ip.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "errors" diff --git a/builder/vmware/iso/host_ip.go b/builder/vmware/iso/host_ip.go index 452cca7dc..98a6d26df 100644 --- a/builder/vmware/iso/host_ip.go +++ b/builder/vmware/iso/host_ip.go @@ -1,4 +1,4 @@ -package vmware +package iso // Interface to help find the host IP that is available from within // the VMware virtual machines. diff --git a/builder/vmware/iso/host_ip_ifconfig.go b/builder/vmware/iso/host_ip_ifconfig.go index 28e434b33..31243fafa 100644 --- a/builder/vmware/iso/host_ip_ifconfig.go +++ b/builder/vmware/iso/host_ip_ifconfig.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "bytes" diff --git a/builder/vmware/iso/host_ip_ifconfig_test.go b/builder/vmware/iso/host_ip_ifconfig_test.go index 0f3d9266e..51ccc272c 100644 --- a/builder/vmware/iso/host_ip_ifconfig_test.go +++ b/builder/vmware/iso/host_ip_ifconfig_test.go @@ -1,4 +1,4 @@ -package vmware +package iso import "testing" diff --git a/builder/vmware/iso/host_ip_vmnetnatconf.go b/builder/vmware/iso/host_ip_vmnetnatconf.go index 888dfa81d..658a0abd4 100644 --- a/builder/vmware/iso/host_ip_vmnetnatconf.go +++ b/builder/vmware/iso/host_ip_vmnetnatconf.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "bufio" diff --git a/builder/vmware/iso/host_ip_vmnetnatconf_test.go b/builder/vmware/iso/host_ip_vmnetnatconf_test.go index a5fc26f27..873a7fa2b 100644 --- a/builder/vmware/iso/host_ip_vmnetnatconf_test.go +++ b/builder/vmware/iso/host_ip_vmnetnatconf_test.go @@ -1,4 +1,4 @@ -package vmware +package iso import "testing" diff --git a/builder/vmware/iso/output_dir.go b/builder/vmware/iso/output_dir.go index c88e3f41b..5013d6905 100644 --- a/builder/vmware/iso/output_dir.go +++ b/builder/vmware/iso/output_dir.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "os" diff --git a/builder/vmware/iso/remote_driver.go b/builder/vmware/iso/remote_driver.go index 07fb2f8e7..4a88ed1e6 100644 --- a/builder/vmware/iso/remote_driver.go +++ b/builder/vmware/iso/remote_driver.go @@ -1,4 +1,4 @@ -package vmware +package iso type RemoteDriver interface { Driver diff --git a/builder/vmware/iso/ssh.go b/builder/vmware/iso/ssh.go index 0b499db29..3c27c387b 100644 --- a/builder/vmware/iso/ssh.go +++ b/builder/vmware/iso/ssh.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( gossh "code.google.com/p/go.crypto/ssh" diff --git a/builder/vmware/iso/step_clean_files.go b/builder/vmware/iso/step_clean_files.go index 7b6eaa8b5..f6c3f0fa4 100644 --- a/builder/vmware/iso/step_clean_files.go +++ b/builder/vmware/iso/step_clean_files.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "fmt" diff --git a/builder/vmware/iso/step_clean_vmx.go b/builder/vmware/iso/step_clean_vmx.go index a40855d41..2b9d6f90c 100644 --- a/builder/vmware/iso/step_clean_vmx.go +++ b/builder/vmware/iso/step_clean_vmx.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "fmt" diff --git a/builder/vmware/iso/step_compact_disk.go b/builder/vmware/iso/step_compact_disk.go index 4a87b4a1a..352ecf56e 100644 --- a/builder/vmware/iso/step_compact_disk.go +++ b/builder/vmware/iso/step_compact_disk.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "fmt" diff --git a/builder/vmware/iso/step_configure_vmx.go b/builder/vmware/iso/step_configure_vmx.go index a9aeabe31..3b467eeaf 100644 --- a/builder/vmware/iso/step_configure_vmx.go +++ b/builder/vmware/iso/step_configure_vmx.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "fmt" diff --git a/builder/vmware/iso/step_configure_vmx_test.go b/builder/vmware/iso/step_configure_vmx_test.go index e46812c9a..851841311 100644 --- a/builder/vmware/iso/step_configure_vmx_test.go +++ b/builder/vmware/iso/step_configure_vmx_test.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "io/ioutil" diff --git a/builder/vmware/iso/step_configure_vnc.go b/builder/vmware/iso/step_configure_vnc.go index f6a41b48e..6e6c39a22 100644 --- a/builder/vmware/iso/step_configure_vnc.go +++ b/builder/vmware/iso/step_configure_vnc.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "fmt" diff --git a/builder/vmware/iso/step_create_disk.go b/builder/vmware/iso/step_create_disk.go index 3b526812e..8544313c3 100644 --- a/builder/vmware/iso/step_create_disk.go +++ b/builder/vmware/iso/step_create_disk.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "fmt" diff --git a/builder/vmware/iso/step_create_vmx.go b/builder/vmware/iso/step_create_vmx.go index b35d2885c..46bbf77e1 100644 --- a/builder/vmware/iso/step_create_vmx.go +++ b/builder/vmware/iso/step_create_vmx.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "fmt" diff --git a/builder/vmware/iso/step_http_server.go b/builder/vmware/iso/step_http_server.go index 084816f63..24da8dd9d 100644 --- a/builder/vmware/iso/step_http_server.go +++ b/builder/vmware/iso/step_http_server.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "fmt" diff --git a/builder/vmware/iso/step_prepare_output_dir.go b/builder/vmware/iso/step_prepare_output_dir.go index 2909712ef..abd2bbe29 100644 --- a/builder/vmware/iso/step_prepare_output_dir.go +++ b/builder/vmware/iso/step_prepare_output_dir.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "fmt" diff --git a/builder/vmware/iso/step_prepare_tools.go b/builder/vmware/iso/step_prepare_tools.go index e26bcf8ed..3dc1e3482 100644 --- a/builder/vmware/iso/step_prepare_tools.go +++ b/builder/vmware/iso/step_prepare_tools.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "fmt" diff --git a/builder/vmware/iso/step_remote_upload.go b/builder/vmware/iso/step_remote_upload.go index 47c9d04a7..00aca6e09 100644 --- a/builder/vmware/iso/step_remote_upload.go +++ b/builder/vmware/iso/step_remote_upload.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "fmt" diff --git a/builder/vmware/iso/step_run.go b/builder/vmware/iso/step_run.go index d21f34ba1..66ed76377 100644 --- a/builder/vmware/iso/step_run.go +++ b/builder/vmware/iso/step_run.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "fmt" diff --git a/builder/vmware/iso/step_shutdown.go b/builder/vmware/iso/step_shutdown.go index dae402763..67c6a29f4 100644 --- a/builder/vmware/iso/step_shutdown.go +++ b/builder/vmware/iso/step_shutdown.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "bytes" diff --git a/builder/vmware/iso/step_suppress_messages.go b/builder/vmware/iso/step_suppress_messages.go index e4e101933..a936a5e6e 100644 --- a/builder/vmware/iso/step_suppress_messages.go +++ b/builder/vmware/iso/step_suppress_messages.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "fmt" diff --git a/builder/vmware/iso/step_test.go b/builder/vmware/iso/step_test.go index 71bce40f8..04490cb29 100644 --- a/builder/vmware/iso/step_test.go +++ b/builder/vmware/iso/step_test.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "bytes" diff --git a/builder/vmware/iso/step_type_boot_command.go b/builder/vmware/iso/step_type_boot_command.go index 688103c09..c72beecb7 100644 --- a/builder/vmware/iso/step_type_boot_command.go +++ b/builder/vmware/iso/step_type_boot_command.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "fmt" diff --git a/builder/vmware/iso/step_upload_tools.go b/builder/vmware/iso/step_upload_tools.go index 630faef8d..95812a748 100644 --- a/builder/vmware/iso/step_upload_tools.go +++ b/builder/vmware/iso/step_upload_tools.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "fmt" diff --git a/builder/vmware/iso/vmx.go b/builder/vmware/iso/vmx.go index ff60d5477..c4fc6d724 100644 --- a/builder/vmware/iso/vmx.go +++ b/builder/vmware/iso/vmx.go @@ -1,4 +1,4 @@ -package vmware +package iso import ( "bytes" diff --git a/builder/vmware/iso/vmx_test.go b/builder/vmware/iso/vmx_test.go index d8d554150..4b189b67b 100644 --- a/builder/vmware/iso/vmx_test.go +++ b/builder/vmware/iso/vmx_test.go @@ -1,4 +1,4 @@ -package vmware +package iso import "testing" From d73844c3efb4973c19e4005ecb2e50ff49d6bd87 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 23 Dec 2013 22:59:11 -0700 Subject: [PATCH 05/45] plugin/builder-vmware: fix package --- plugin/builder-vmware/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/builder-vmware/main.go b/plugin/builder-vmware/main.go index 14a10cf5c..58121bb49 100644 --- a/plugin/builder-vmware/main.go +++ b/plugin/builder-vmware/main.go @@ -1,7 +1,7 @@ package main import ( - "github.com/mitchellh/packer/builder/vmware" + "github.com/mitchellh/packer/builder/vmware/iso" "github.com/mitchellh/packer/packer/plugin" ) @@ -10,6 +10,6 @@ func main() { if err != nil { panic(err) } - server.RegisterBuilder(new(vmware.Builder)) + server.RegisterBuilder(new(iso.Builder)) server.Serve() } From 33452c2dfd33ec0922b24449aa2778c2122f9654 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 23 Dec 2013 23:07:43 -0700 Subject: [PATCH 06/45] builder/vmware: add more stuff to common --- .../{iso => common}/step_configure_vmx.go | 2 +- .../step_configure_vmx_test.go | 2 +- builder/vmware/common/step_test.go | 17 +++++ builder/vmware/common/vmx.go | 70 +++++++++++++++++++ builder/vmware/common/vmx_test.go | 39 +++++++++++ builder/vmware/iso/builder.go | 3 +- 6 files changed, 130 insertions(+), 3 deletions(-) rename builder/vmware/{iso => common}/step_configure_vmx.go (99%) rename builder/vmware/{iso => common}/step_configure_vmx_test.go (99%) create mode 100644 builder/vmware/common/step_test.go create mode 100644 builder/vmware/common/vmx.go create mode 100644 builder/vmware/common/vmx_test.go diff --git a/builder/vmware/iso/step_configure_vmx.go b/builder/vmware/common/step_configure_vmx.go similarity index 99% rename from builder/vmware/iso/step_configure_vmx.go rename to builder/vmware/common/step_configure_vmx.go index 3b467eeaf..0d53c44e9 100644 --- a/builder/vmware/iso/step_configure_vmx.go +++ b/builder/vmware/common/step_configure_vmx.go @@ -1,4 +1,4 @@ -package iso +package common import ( "fmt" diff --git a/builder/vmware/iso/step_configure_vmx_test.go b/builder/vmware/common/step_configure_vmx_test.go similarity index 99% rename from builder/vmware/iso/step_configure_vmx_test.go rename to builder/vmware/common/step_configure_vmx_test.go index 851841311..293269e3f 100644 --- a/builder/vmware/iso/step_configure_vmx_test.go +++ b/builder/vmware/common/step_configure_vmx_test.go @@ -1,4 +1,4 @@ -package iso +package common import ( "io/ioutil" diff --git a/builder/vmware/common/step_test.go b/builder/vmware/common/step_test.go new file mode 100644 index 000000000..27c37495e --- /dev/null +++ b/builder/vmware/common/step_test.go @@ -0,0 +1,17 @@ +package common + +import ( + "bytes" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "testing" +) + +func testState(t *testing.T) multistep.StateBag { + state := new(multistep.BasicStateBag) + state.Put("ui", &packer.BasicUi{ + Reader: new(bytes.Buffer), + Writer: new(bytes.Buffer), + }) + return state +} diff --git a/builder/vmware/common/vmx.go b/builder/vmware/common/vmx.go new file mode 100644 index 000000000..455da4cec --- /dev/null +++ b/builder/vmware/common/vmx.go @@ -0,0 +1,70 @@ +package common + +import ( + "bytes" + "fmt" + "io" + "log" + "os" + "regexp" + "sort" + "strings" +) + +// ParseVMX parses the keys and values from a VMX file and returns +// them as a Go map. +func ParseVMX(contents string) map[string]string { + results := make(map[string]string) + + lineRe := regexp.MustCompile(`^(.+?)\s*=\s*"(.*?)"\s*$`) + + for _, line := range strings.Split(contents, "\n") { + matches := lineRe.FindStringSubmatch(line) + if matches == nil { + continue + } + + key := strings.ToLower(matches[1]) + results[key] = matches[2] + } + + return results +} + +// EncodeVMX takes a map and turns it into valid VMX contents. +func EncodeVMX(contents map[string]string) string { + var buf bytes.Buffer + + i := 0 + keys := make([]string, len(contents)) + for k, _ := range contents { + keys[i] = k + i++ + } + + sort.Strings(keys) + for _, k := range keys { + buf.WriteString(fmt.Sprintf("%s = \"%s\"\n", k, contents[k])) + } + + return buf.String() +} + +// WriteVMX takes a path to a VMX file and contents in the form of a +// map and writes it out. +func WriteVMX(path string, data map[string]string) (err error) { + log.Printf("Writing VMX to: %s", path) + f, err := os.Create(path) + if err != nil { + return + } + defer f.Close() + + var buf bytes.Buffer + buf.WriteString(EncodeVMX(data)) + if _, err = io.Copy(f, &buf); err != nil { + return + } + + return +} diff --git a/builder/vmware/common/vmx_test.go b/builder/vmware/common/vmx_test.go new file mode 100644 index 000000000..da07b2a10 --- /dev/null +++ b/builder/vmware/common/vmx_test.go @@ -0,0 +1,39 @@ +package common + +import "testing" + +func TestParseVMX(t *testing.T) { + contents := ` +.encoding = "UTF-8" +config.version = "8" +` + + results := ParseVMX(contents) + if len(results) != 2 { + t.Fatalf("not correct number of results: %d", len(results)) + } + + if results[".encoding"] != "UTF-8" { + t.Errorf("invalid .encoding: %s", results[".encoding"]) + } + + if results["config.version"] != "8" { + t.Errorf("invalid config.version: %s", results["config.version"]) + } +} + +func TestEncodeVMX(t *testing.T) { + contents := map[string]string{ + ".encoding": "UTF-8", + "config.version": "8", + } + + expected := `.encoding = "UTF-8" +config.version = "8" +` + + result := EncodeVMX(contents) + if result != expected { + t.Errorf("invalid results: %s", result) + } +} diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index a65176d2c..068a675dd 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/packer" "io/ioutil" @@ -405,7 +406,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe }, &stepCreateDisk{}, &stepCreateVMX{}, - &StepConfigureVMX{ + &vmwcommon.StepConfigureVMX{ CustomData: b.config.VMXData, }, &stepSuppressMessages{}, From 6d83ef54998cb5bafca1499200310b58f10e79f8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 23 Dec 2013 23:09:47 -0700 Subject: [PATCH 07/45] builder/vmware/iso: move VMX methods out to the common --- builder/vmware/iso/ssh.go | 3 +- builder/vmware/iso/step_clean_vmx.go | 5 +- builder/vmware/iso/step_configure_vnc.go | 5 +- builder/vmware/iso/step_create_vmx.go | 3 +- builder/vmware/iso/vmx.go | 70 ------------------------ builder/vmware/iso/vmx_test.go | 39 ------------- 6 files changed, 10 insertions(+), 115 deletions(-) delete mode 100644 builder/vmware/iso/vmx.go delete mode 100644 builder/vmware/iso/vmx_test.go diff --git a/builder/vmware/iso/ssh.go b/builder/vmware/iso/ssh.go index 3c27c387b..32c7cb962 100644 --- a/builder/vmware/iso/ssh.go +++ b/builder/vmware/iso/ssh.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/communicator/ssh" "io/ioutil" "log" @@ -28,7 +29,7 @@ func sshAddress(state multistep.StateBag) (string, error) { return "", err } - vmxData := ParseVMX(string(vmxBytes)) + vmxData := vmwcommon.ParseVMX(string(vmxBytes)) var ok bool macAddress := "" diff --git a/builder/vmware/iso/step_clean_vmx.go b/builder/vmware/iso/step_clean_vmx.go index 2b9d6f90c..119173a5b 100644 --- a/builder/vmware/iso/step_clean_vmx.go +++ b/builder/vmware/iso/step_clean_vmx.go @@ -3,6 +3,7 @@ package iso import ( "fmt" "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/packer" "io/ioutil" "log" @@ -66,7 +67,7 @@ func (s stepCleanVMX) Run(state multistep.StateBag) multistep.StepAction { } // Rewrite the VMX - if err := WriteVMX(vmxPath, vmxData); err != nil { + if err := vmwcommon.WriteVMX(vmxPath, vmxData); err != nil { state.Put("error", fmt.Errorf("Error writing VMX: %s", err)) return multistep.ActionHalt } @@ -88,5 +89,5 @@ func (stepCleanVMX) readVMX(vmxPath string) (map[string]string, error) { return nil, err } - return ParseVMX(string(vmxBytes)), nil + return vmwcommon.ParseVMX(string(vmxBytes)), nil } diff --git a/builder/vmware/iso/step_configure_vnc.go b/builder/vmware/iso/step_configure_vnc.go index 6e6c39a22..676439213 100644 --- a/builder/vmware/iso/step_configure_vnc.go +++ b/builder/vmware/iso/step_configure_vnc.go @@ -3,6 +3,7 @@ package iso import ( "fmt" "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/packer" "io/ioutil" "log" @@ -84,11 +85,11 @@ func (s *stepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction { log.Printf("Found available VNC port: %d", vncPort) - vmxData := ParseVMX(string(vmxBytes)) + vmxData := vmwcommon.ParseVMX(string(vmxBytes)) vmxData["remotedisplay.vnc.enabled"] = "TRUE" vmxData["remotedisplay.vnc.port"] = fmt.Sprintf("%d", vncPort) - if err := WriteVMX(vmxPath, vmxData); err != nil { + if err := vmwcommon.WriteVMX(vmxPath, vmxData); err != nil { err := fmt.Errorf("Error writing VMX data: %s", err) state.Put("error", err) ui.Error(err.Error()) diff --git a/builder/vmware/iso/step_create_vmx.go b/builder/vmware/iso/step_create_vmx.go index 46bbf77e1..8544a1405 100644 --- a/builder/vmware/iso/step_create_vmx.go +++ b/builder/vmware/iso/step_create_vmx.go @@ -3,6 +3,7 @@ package iso import ( "fmt" "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/packer" "io/ioutil" "os" @@ -90,7 +91,7 @@ func (s *stepCreateVMX) Run(state multistep.StateBag) multistep.StepAction { } vmxPath := filepath.Join(vmxDir, config.VMName+".vmx") - if err := WriteVMX(vmxPath, ParseVMX(vmxContents)); err != nil { + if err := vmwcommon.WriteVMX(vmxPath, vmwcommon.ParseVMX(vmxContents)); err != nil { err := fmt.Errorf("Error creating VMX file: %s", err) state.Put("error", err) ui.Error(err.Error()) diff --git a/builder/vmware/iso/vmx.go b/builder/vmware/iso/vmx.go deleted file mode 100644 index c4fc6d724..000000000 --- a/builder/vmware/iso/vmx.go +++ /dev/null @@ -1,70 +0,0 @@ -package iso - -import ( - "bytes" - "fmt" - "io" - "log" - "os" - "regexp" - "sort" - "strings" -) - -// ParseVMX parses the keys and values from a VMX file and returns -// them as a Go map. -func ParseVMX(contents string) map[string]string { - results := make(map[string]string) - - lineRe := regexp.MustCompile(`^(.+?)\s*=\s*"(.*?)"\s*$`) - - for _, line := range strings.Split(contents, "\n") { - matches := lineRe.FindStringSubmatch(line) - if matches == nil { - continue - } - - key := strings.ToLower(matches[1]) - results[key] = matches[2] - } - - return results -} - -// EncodeVMX takes a map and turns it into valid VMX contents. -func EncodeVMX(contents map[string]string) string { - var buf bytes.Buffer - - i := 0 - keys := make([]string, len(contents)) - for k, _ := range contents { - keys[i] = k - i++ - } - - sort.Strings(keys) - for _, k := range keys { - buf.WriteString(fmt.Sprintf("%s = \"%s\"\n", k, contents[k])) - } - - return buf.String() -} - -// WriteVMX takes a path to a VMX file and contents in the form of a -// map and writes it out. -func WriteVMX(path string, data map[string]string) (err error) { - log.Printf("Writing VMX to: %s", path) - f, err := os.Create(path) - if err != nil { - return - } - defer f.Close() - - var buf bytes.Buffer - buf.WriteString(EncodeVMX(data)) - if _, err = io.Copy(f, &buf); err != nil { - return - } - - return -} diff --git a/builder/vmware/iso/vmx_test.go b/builder/vmware/iso/vmx_test.go deleted file mode 100644 index 4b189b67b..000000000 --- a/builder/vmware/iso/vmx_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package iso - -import "testing" - -func TestParseVMX(t *testing.T) { - contents := ` -.encoding = "UTF-8" -config.version = "8" -` - - results := ParseVMX(contents) - if len(results) != 2 { - t.Fatalf("not correct number of results: %d", len(results)) - } - - if results[".encoding"] != "UTF-8" { - t.Errorf("invalid .encoding: %s", results[".encoding"]) - } - - if results["config.version"] != "8" { - t.Errorf("invalid config.version: %s", results["config.version"]) - } -} - -func TestEncodeVMX(t *testing.T) { - contents := map[string]string{ - ".encoding": "UTF-8", - "config.version": "8", - } - - expected := `.encoding = "UTF-8" -config.version = "8" -` - - result := EncodeVMX(contents) - if result != expected { - t.Errorf("invalid results: %s", result) - } -} From 29931cd0c0278e2cc66029fb758228a6378a55b1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 23 Dec 2013 23:27:01 -0700 Subject: [PATCH 08/45] builder/vmware/common: SSHConfig --- builder/vmware/common/config_test.go | 15 ++ builder/vmware/common/ssh.go | 29 ++++ builder/vmware/common/ssh_config.go | 67 +++++++++ builder/vmware/common/ssh_config_test.go | 155 ++++++++++++++++++++ builder/vmware/iso/builder.go | 44 +----- builder/vmware/iso/builder_test.go | 179 +---------------------- 6 files changed, 271 insertions(+), 218 deletions(-) create mode 100644 builder/vmware/common/config_test.go create mode 100644 builder/vmware/common/ssh.go create mode 100644 builder/vmware/common/ssh_config.go create mode 100644 builder/vmware/common/ssh_config_test.go diff --git a/builder/vmware/common/config_test.go b/builder/vmware/common/config_test.go new file mode 100644 index 000000000..a84c51bc1 --- /dev/null +++ b/builder/vmware/common/config_test.go @@ -0,0 +1,15 @@ +package common + +import ( + "github.com/mitchellh/packer/packer" + "testing" +) + +func testConfigTemplate(t *testing.T) *packer.ConfigTemplate { + result, err := packer.NewConfigTemplate() + if err != nil { + t.Fatalf("err: %s", err) + } + + return result +} diff --git a/builder/vmware/common/ssh.go b/builder/vmware/common/ssh.go new file mode 100644 index 000000000..985f2eb76 --- /dev/null +++ b/builder/vmware/common/ssh.go @@ -0,0 +1,29 @@ +package common + +import ( + gossh "code.google.com/p/go.crypto/ssh" + "io/ioutil" + "os" + + "github.com/mitchellh/packer/communicator/ssh" +) + +func sshKeyToKeyring(path string) (gossh.ClientKeyring, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + keyBytes, err := ioutil.ReadAll(f) + if err != nil { + return nil, err + } + + keyring := new(ssh.SimpleKeychain) + if err := keyring.AddPEMKey(string(keyBytes)); err != nil { + return nil, err + } + + return keyring, nil +} diff --git a/builder/vmware/common/ssh_config.go b/builder/vmware/common/ssh_config.go new file mode 100644 index 000000000..0eab2ac3c --- /dev/null +++ b/builder/vmware/common/ssh_config.go @@ -0,0 +1,67 @@ +package common + +import ( + "errors" + "fmt" + "os" + "time" + + "github.com/mitchellh/packer/packer" +) + +type SSHConfig struct { + SSHUser string `mapstructure:"ssh_username"` + SSHKeyPath string `mapstructure:"ssh_key_path"` + SSHPassword string `mapstructure:"ssh_password"` + SSHPort uint `mapstructure:"ssh_port"` + SSHSkipRequestPty bool `mapstructure:"ssh_skip_request_pty"` + RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"` + + SSHWaitTimeout time.Duration +} + +func (c *SSHConfig) Prepare(t *packer.ConfigTemplate) []error { + if c.SSHPort == 0 { + c.SSHPort = 22 + } + + if c.RawSSHWaitTimeout == "" { + c.RawSSHWaitTimeout = "20m" + } + + templates := map[string]*string{ + "ssh_key_path": &c.SSHKeyPath, + "ssh_password": &c.SSHPassword, + "ssh_username": &c.SSHUser, + "ssh_wait_timeout": &c.RawSSHWaitTimeout, + } + + errs := make([]error, 0) + for n, ptr := range templates { + var err error + *ptr, err = t.Process(*ptr, nil) + if err != nil { + errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + + if c.SSHKeyPath != "" { + if _, err := os.Stat(c.SSHKeyPath); err != nil { + errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err)) + } else if _, err := sshKeyToKeyring(c.SSHKeyPath); err != nil { + errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err)) + } + } + + if c.SSHUser == "" { + errs = append(errs, errors.New("An ssh_username must be specified.")) + } + + var err error + c.SSHWaitTimeout, err = time.ParseDuration(c.RawSSHWaitTimeout) + if err != nil { + errs = append(errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %s", err)) + } + + return errs +} diff --git a/builder/vmware/common/ssh_config_test.go b/builder/vmware/common/ssh_config_test.go new file mode 100644 index 000000000..a6c9e8ef5 --- /dev/null +++ b/builder/vmware/common/ssh_config_test.go @@ -0,0 +1,155 @@ +package common + +import ( + "io/ioutil" + "os" + "testing" +) + +func testSSHConfig() *SSHConfig { + return &SSHConfig{ + SSHUser: "foo", + } +} + +func TestSSHConfigPrepare(t *testing.T) { + c := testSSHConfig() + errs := c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + + if c.SSHPort != 22 { + t.Errorf("bad ssh port: %d", c.SSHPort) + } +} + +func TestSSHConfigPrepare_SSHKeyPath(t *testing.T) { + var c *SSHConfig + var errs []error + + c = testSSHConfig() + c.SSHKeyPath = "" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %#v", errs) + } + + c = testSSHConfig() + c.SSHKeyPath = "/i/dont/exist" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatal("should have error") + } + + // Test bad contents + tf, err := ioutil.TempFile("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.Remove(tf.Name()) + defer tf.Close() + + if _, err := tf.Write([]byte("HELLO!")); err != nil { + t.Fatalf("err: %s", err) + } + + c = testSSHConfig() + c.SSHKeyPath = tf.Name() + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatal("should have error") + } + + // Test good contents + tf.Seek(0, 0) + tf.Truncate(0) + tf.Write([]byte(testPem)) + c = testSSHConfig() + c.SSHKeyPath = tf.Name() + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %#v", errs) + } +} + +func TestSSHConfigPrepare_SSHUser(t *testing.T) { + var c *SSHConfig + var errs []error + + c = testSSHConfig() + c.SSHUser = "" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatalf("should have error") + } + + c = testSSHConfig() + c.SSHUser = "exists" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %#v", errs) + } +} + +func TestSSHConfigPrepare_SSHWaitTimeout(t *testing.T) { + var c *SSHConfig + var errs []error + + // Defaults + c = testSSHConfig() + c.RawSSHWaitTimeout = "" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %#v", errs) + } + if c.RawSSHWaitTimeout != "20m" { + t.Fatalf("bad value: %s", c.RawSSHWaitTimeout) + } + + // Test with a bad value + c = testSSHConfig() + c.RawSSHWaitTimeout = "this is not good" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatal("should have error") + } + + // Test with a good one + c = testSSHConfig() + c.RawSSHWaitTimeout = "5s" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %#v", errs) + } +} + +const testPem = ` +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAxd4iamvrwRJvtNDGQSIbNvvIQN8imXTRWlRY62EvKov60vqu +hh+rDzFYAIIzlmrJopvOe0clqmi3mIP9dtkjPFrYflq52a2CF5q+BdwsJXuRHbJW +LmStZUwW1khSz93DhvhmK50nIaczW63u4EO/jJb3xj+wxR1Nkk9bxi3DDsYFt8SN +AzYx9kjlEYQ/+sI4/ATfmdV9h78SVotjScupd9KFzzi76gWq9gwyCBLRynTUWlyD +2UOfJRkOvhN6/jKzvYfVVwjPSfA9IMuooHdScmC4F6KBKJl/zf/zETM0XyzIDNmH +uOPbCiljq2WoRM+rY6ET84EO0kVXbfx8uxUsqQIDAQABAoIBAQCkPj9TF0IagbM3 +5BSs/CKbAWS4dH/D4bPlxx4IRCNirc8GUg+MRb04Xz0tLuajdQDqeWpr6iLZ0RKV +BvreLF+TOdV7DNQ4XE4gSdJyCtCaTHeort/aordL3l0WgfI7mVk0L/yfN1PEG4YG +E9q1TYcyrB3/8d5JwIkjabxERLglCcP+geOEJp+QijbvFIaZR/n2irlKW4gSy6ko +9B0fgUnhkHysSg49ChHQBPQ+o5BbpuLrPDFMiTPTPhdfsvGGcyCGeqfBA56oHcSF +K02Fg8OM+Bd1lb48LAN9nWWY4WbwV+9bkN3Ym8hO4c3a/Dxf2N7LtAQqWZzFjvM3 +/AaDvAgBAoGBAPLD+Xn1IYQPMB2XXCXfOuJewRY7RzoVWvMffJPDfm16O7wOiW5+ +2FmvxUDayk4PZy6wQMzGeGKnhcMMZTyaq2g/QtGfrvy7q1Lw2fB1VFlVblvqhoJa +nMJojjC4zgjBkXMHsRLeTmgUKyGs+fdFbfI6uejBnnf+eMVUMIdJ+6I9AoGBANCn +kWO9640dttyXURxNJ3lBr2H3dJOkmD6XS+u+LWqCSKQe691Y/fZ/ZL0Oc4Mhy7I6 +hsy3kDQ5k2V0fkaNODQIFJvUqXw2pMewUk8hHc9403f4fe9cPrL12rQ8WlQw4yoC +v2B61vNczCCUDtGxlAaw8jzSRaSI5s6ax3K7enbdAoGBAJB1WYDfA2CoAQO6y9Sl +b07A/7kQ8SN5DbPaqrDrBdJziBQxukoMJQXJeGFNUFD/DXFU5Fp2R7C86vXT7HIR +v6m66zH+CYzOx/YE6EsUJms6UP9VIVF0Rg/RU7teXQwM01ZV32LQ8mswhTH20o/3 +uqMHmxUMEhZpUMhrfq0isyApAoGAe1UxGTXfj9AqkIVYylPIq2HqGww7+jFmVEj1 +9Wi6S6Sq72ffnzzFEPkIQL/UA4TsdHMnzsYKFPSbbXLIWUeMGyVTmTDA5c0e5XIR +lPhMOKCAzv8w4VUzMnEkTzkFY5JqFCD/ojW57KvDdNZPVB+VEcdxyAW6aKELXMAc +eHLc1nkCgYEApm/motCTPN32nINZ+Vvywbv64ZD+gtpeMNP3CLrbe1X9O+H52AXa +1jCoOldWR8i2bs2NVPcKZgdo6fFULqE4dBX7Te/uYEIuuZhYLNzRO1IKU/YaqsXG +3bfQ8hKYcSnTfE0gPtLDnqCIxTocaGLSHeG3TH9fTw+dA8FvWpUztI4= +-----END RSA PRIVATE KEY----- +` diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 068a675dd..2dd7f3776 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -26,6 +26,7 @@ type Builder struct { type config struct { common.PackerConfig `mapstructure:",squash"` + vmwcommon.SSHConfig `mapstructure:",squash"` DiskName string `mapstructure:"vmdk_name"` DiskSize uint `mapstructure:"disk_size"` @@ -44,11 +45,6 @@ type config struct { BootCommand []string `mapstructure:"boot_command"` SkipCompaction bool `mapstructure:"skip_compaction"` ShutdownCommand string `mapstructure:"shutdown_command"` - SSHUser string `mapstructure:"ssh_username"` - SSHKeyPath string `mapstructure:"ssh_key_path"` - SSHPassword string `mapstructure:"ssh_password"` - SSHPort uint `mapstructure:"ssh_port"` - SSHSkipRequestPty bool `mapstructure:"ssh_skip_request_pty"` ToolsUploadFlavor string `mapstructure:"tools_upload_flavor"` ToolsUploadPath string `mapstructure:"tools_upload_path"` VMXData map[string]string `mapstructure:"vmx_data"` @@ -66,11 +62,9 @@ type config struct { RawBootWait string `mapstructure:"boot_wait"` RawSingleISOUrl string `mapstructure:"iso_url"` RawShutdownTimeout string `mapstructure:"shutdown_timeout"` - RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"` bootWait time.Duration `` shutdownTimeout time.Duration `` - sshWaitTimeout time.Duration `` tpl *packer.ConfigTemplate } @@ -88,6 +82,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { // Accumulate any errors errs := common.CheckUnusedConfig(md) + errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(b.config.tpl)...) warnings := make([]string, 0) if b.config.DiskName == "" { @@ -155,10 +150,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.config.RemotePort = 22 } - if b.config.SSHPort == 0 { - b.config.SSHPort = 22 - } - if b.config.ToolsUploadPath == "" { b.config.ToolsUploadPath = "{{ .Flavor }}.iso" } @@ -173,14 +164,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { "iso_url": &b.config.RawSingleISOUrl, "output_directory": &b.config.OutputDir, "shutdown_command": &b.config.ShutdownCommand, - "ssh_key_path": &b.config.SSHKeyPath, - "ssh_password": &b.config.SSHPassword, - "ssh_username": &b.config.SSHUser, "tools_upload_flavor": &b.config.ToolsUploadFlavor, "vm_name": &b.config.VMName, "boot_wait": &b.config.RawBootWait, "shutdown_timeout": &b.config.RawShutdownTimeout, - "ssh_wait_timeout": &b.config.RawSSHWaitTimeout, "vmx_template_path": &b.config.VMXTemplatePath, "remote_type": &b.config.RemoteType, "remote_host": &b.config.RemoteHost, @@ -295,21 +282,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } - if b.config.SSHKeyPath != "" { - if _, err := os.Stat(b.config.SSHKeyPath); err != nil { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("ssh_key_path is invalid: %s", err)) - } else if _, err := sshKeyToKeyring(b.config.SSHKeyPath); err != nil { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("ssh_key_path is invalid: %s", err)) - } - } - - if b.config.SSHUser == "" { - errs = packer.MultiErrorAppend( - errs, errors.New("An ssh_username must be specified.")) - } - if b.config.RawBootWait != "" { b.config.bootWait, err = time.ParseDuration(b.config.RawBootWait) if err != nil { @@ -328,16 +300,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err)) } - if b.config.RawSSHWaitTimeout == "" { - b.config.RawSSHWaitTimeout = "20m" - } - - b.config.sshWaitTimeout, err = time.ParseDuration(b.config.RawSSHWaitTimeout) - if err != nil { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %s", err)) - } - if _, err := template.New("path").Parse(b.config.ToolsUploadPath); err != nil { errs = packer.MultiErrorAppend( errs, fmt.Errorf("tools_upload_path invalid: %s", err)) @@ -417,7 +379,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &common.StepConnectSSH{ SSHAddress: driver.SSHAddress, SSHConfig: sshConfig, - SSHWaitTimeout: b.config.sshWaitTimeout, + SSHWaitTimeout: b.config.SSHWaitTimeout, NoPty: b.config.SSHSkipRequestPty, }, &stepUploadTools{}, diff --git a/builder/vmware/iso/builder_test.go b/builder/vmware/iso/builder_test.go index a722595cd..463a4c5fe 100644 --- a/builder/vmware/iso/builder_test.go +++ b/builder/vmware/iso/builder_test.go @@ -9,36 +9,6 @@ import ( "time" ) -var testPem = ` ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEAxd4iamvrwRJvtNDGQSIbNvvIQN8imXTRWlRY62EvKov60vqu -hh+rDzFYAIIzlmrJopvOe0clqmi3mIP9dtkjPFrYflq52a2CF5q+BdwsJXuRHbJW -LmStZUwW1khSz93DhvhmK50nIaczW63u4EO/jJb3xj+wxR1Nkk9bxi3DDsYFt8SN -AzYx9kjlEYQ/+sI4/ATfmdV9h78SVotjScupd9KFzzi76gWq9gwyCBLRynTUWlyD -2UOfJRkOvhN6/jKzvYfVVwjPSfA9IMuooHdScmC4F6KBKJl/zf/zETM0XyzIDNmH -uOPbCiljq2WoRM+rY6ET84EO0kVXbfx8uxUsqQIDAQABAoIBAQCkPj9TF0IagbM3 -5BSs/CKbAWS4dH/D4bPlxx4IRCNirc8GUg+MRb04Xz0tLuajdQDqeWpr6iLZ0RKV -BvreLF+TOdV7DNQ4XE4gSdJyCtCaTHeort/aordL3l0WgfI7mVk0L/yfN1PEG4YG -E9q1TYcyrB3/8d5JwIkjabxERLglCcP+geOEJp+QijbvFIaZR/n2irlKW4gSy6ko -9B0fgUnhkHysSg49ChHQBPQ+o5BbpuLrPDFMiTPTPhdfsvGGcyCGeqfBA56oHcSF -K02Fg8OM+Bd1lb48LAN9nWWY4WbwV+9bkN3Ym8hO4c3a/Dxf2N7LtAQqWZzFjvM3 -/AaDvAgBAoGBAPLD+Xn1IYQPMB2XXCXfOuJewRY7RzoVWvMffJPDfm16O7wOiW5+ -2FmvxUDayk4PZy6wQMzGeGKnhcMMZTyaq2g/QtGfrvy7q1Lw2fB1VFlVblvqhoJa -nMJojjC4zgjBkXMHsRLeTmgUKyGs+fdFbfI6uejBnnf+eMVUMIdJ+6I9AoGBANCn -kWO9640dttyXURxNJ3lBr2H3dJOkmD6XS+u+LWqCSKQe691Y/fZ/ZL0Oc4Mhy7I6 -hsy3kDQ5k2V0fkaNODQIFJvUqXw2pMewUk8hHc9403f4fe9cPrL12rQ8WlQw4yoC -v2B61vNczCCUDtGxlAaw8jzSRaSI5s6ax3K7enbdAoGBAJB1WYDfA2CoAQO6y9Sl -b07A/7kQ8SN5DbPaqrDrBdJziBQxukoMJQXJeGFNUFD/DXFU5Fp2R7C86vXT7HIR -v6m66zH+CYzOx/YE6EsUJms6UP9VIVF0Rg/RU7teXQwM01ZV32LQ8mswhTH20o/3 -uqMHmxUMEhZpUMhrfq0isyApAoGAe1UxGTXfj9AqkIVYylPIq2HqGww7+jFmVEj1 -9Wi6S6Sq72ffnzzFEPkIQL/UA4TsdHMnzsYKFPSbbXLIWUeMGyVTmTDA5c0e5XIR -lPhMOKCAzv8w4VUzMnEkTzkFY5JqFCD/ojW57KvDdNZPVB+VEcdxyAW6aKELXMAc -eHLc1nkCgYEApm/motCTPN32nINZ+Vvywbv64ZD+gtpeMNP3CLrbe1X9O+H52AXa -1jCoOldWR8i2bs2NVPcKZgdo6fFULqE4dBX7Te/uYEIuuZhYLNzRO1IKU/YaqsXG -3bfQ8hKYcSnTfE0gPtLDnqCIxTocaGLSHeG3TH9fTw+dA8FvWpUztI4= ------END RSA PRIVATE KEY----- -` - func testConfig() map[string]interface{} { return map[string]interface{}{ "iso_checksum": "foo", @@ -188,8 +158,8 @@ func TestBuilderPrepare_Defaults(t *testing.T) { t.Errorf("bad output dir: %s", b.config.OutputDir) } - if b.config.sshWaitTimeout != (20 * time.Minute) { - t.Errorf("bad wait timeout: %s", b.config.sshWaitTimeout) + if b.config.SSHWaitTimeout != (20 * time.Minute) { + t.Errorf("bad wait timeout: %s", b.config.SSHWaitTimeout) } if b.config.VMName != "packer-foo" { @@ -460,151 +430,6 @@ func TestBuilderPrepare_ShutdownTimeout(t *testing.T) { } } -func TestBuilderPrepare_sshKeyPath(t *testing.T) { - var b Builder - config := testConfig() - - config["ssh_key_path"] = "" - b = Builder{} - warns, err := b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("should not have error: %s", err) - } - - config["ssh_key_path"] = "/i/dont/exist" - b = Builder{} - warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err == nil { - t.Fatal("should have error") - } - - // Test bad contents - tf, err := ioutil.TempFile("", "packer") - if err != nil { - t.Fatalf("err: %s", err) - } - defer os.Remove(tf.Name()) - defer tf.Close() - - if _, err := tf.Write([]byte("HELLO!")); err != nil { - t.Fatalf("err: %s", err) - } - - config["ssh_key_path"] = tf.Name() - b = Builder{} - warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err == nil { - t.Fatal("should have error") - } - - // Test good contents - tf.Seek(0, 0) - tf.Truncate(0) - tf.Write([]byte(testPem)) - config["ssh_key_path"] = tf.Name() - b = Builder{} - warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestBuilderPrepare_SSHUser(t *testing.T) { - var b Builder - config := testConfig() - - config["ssh_username"] = "" - warns, err := b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err == nil { - t.Fatal("should have error") - } - - config["ssh_username"] = "exists" - b = Builder{} - warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("should not have error: %s", err) - } -} - -func TestBuilderPrepare_SSHPort(t *testing.T) { - var b Builder - config := testConfig() - - // Test with a bad value - delete(config, "ssh_port") - warns, err := b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("bad err: %s", err) - } - - if b.config.SSHPort != 22 { - t.Fatalf("bad ssh port: %d", b.config.SSHPort) - } - - // Test with a good one - config["ssh_port"] = 44 - b = Builder{} - warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("should not have error: %s", err) - } - - if b.config.SSHPort != 44 { - t.Fatalf("bad ssh port: %d", b.config.SSHPort) - } -} - -func TestBuilderPrepare_SSHWaitTimeout(t *testing.T) { - var b Builder - config := testConfig() - - // Test with a bad value - config["ssh_wait_timeout"] = "this is not good" - warns, err := b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err == nil { - t.Fatal("should have error") - } - - // Test with a good one - config["ssh_wait_timeout"] = "5s" - b = Builder{} - warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("should not have error: %s", err) - } -} - func TestBuilderPrepare_ToolsUploadPath(t *testing.T) { var b Builder config := testConfig() From 8bd3ca4470c454353591d0c0c79ef1783602c17a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Dec 2013 11:00:51 -0700 Subject: [PATCH 09/45] builder/vmware/common: shuffling stuff around --- builder/vmware/common/driver.go | 43 ++++++++++++ builder/vmware/common/guest_ip.go | 89 +++++++++++++++++++++++++ builder/vmware/common/ssh.go | 77 ++++++++++++++++++++++ builder/vmware/iso/ssh.go | 105 ------------------------------ 4 files changed, 209 insertions(+), 105 deletions(-) create mode 100644 builder/vmware/common/driver.go create mode 100644 builder/vmware/common/guest_ip.go delete mode 100644 builder/vmware/iso/ssh.go diff --git a/builder/vmware/common/driver.go b/builder/vmware/common/driver.go new file mode 100644 index 000000000..55de3ff14 --- /dev/null +++ b/builder/vmware/common/driver.go @@ -0,0 +1,43 @@ +package common + +import ( + "github.com/mitchellh/multistep" +) + +// A driver is able to talk to VMware, control virtual machines, etc. +type Driver interface { + // CompactDisk compacts a virtual disk. + CompactDisk(string) error + + // CreateDisk creates a virtual disk with the given size. + CreateDisk(string, string, string) error + + // Checks if the VMX file at the given path is running. + IsRunning(string) (bool, error) + + // SSHAddress returns the SSH address for the VM that is being + // managed by this driver. + SSHAddress(multistep.StateBag) (string, error) + + // Start starts a VM specified by the path to the VMX given. + Start(string, bool) error + + // Stop stops a VM specified by the path to the VMX given. + Stop(string) error + + // SuppressMessages modifies the VMX or surrounding directory so that + // VMware doesn't show any annoying messages. + SuppressMessages(string) error + + // Get the path to the VMware ISO for the given flavor. + ToolsIsoPath(string) string + + // Get the path to the DHCP leases file for the given device. + DhcpLeasesPath(string) string + + // Verify checks to make sure that this driver should function + // properly. This should check that all the files it will use + // appear to exist and so on. If everything is okay, this doesn't + // return an error. Otherwise, this returns an error. + Verify() error +} diff --git a/builder/vmware/common/guest_ip.go b/builder/vmware/common/guest_ip.go new file mode 100644 index 000000000..ad345d435 --- /dev/null +++ b/builder/vmware/common/guest_ip.go @@ -0,0 +1,89 @@ +package common + +import ( + "errors" + "io/ioutil" + "log" + "os" + "regexp" + "strings" + "time" +) + +// Interface to help find the IP address of a running virtual machine. +type GuestIPFinder interface { + GuestIP() (string, error) +} + +// DHCPLeaseGuestLookup looks up the IP address of a guest using DHCP +// lease information from the VMware network devices. +type DHCPLeaseGuestLookup struct { + // Driver that is being used (to find leases path) + Driver Driver + + // Device that the guest is connected to. + Device string + + // MAC address of the guest. + MACAddress string +} + +func (f *DHCPLeaseGuestLookup) GuestIP() (string, error) { + dhcpLeasesPath := f.Driver.DhcpLeasesPath(f.Device) + log.Printf("DHCP leases path: %s", dhcpLeasesPath) + if dhcpLeasesPath == "" { + return "", errors.New("no DHCP leases path found.") + } + + fh, err := os.Open(dhcpLeasesPath) + if err != nil { + return "", err + } + defer fh.Close() + + dhcpBytes, err := ioutil.ReadAll(fh) + if err != nil { + return "", err + } + + var lastIp string + var lastLeaseEnd time.Time + + var curIp string + var curLeaseEnd time.Time + + ipLineRe := regexp.MustCompile(`^lease (.+?) {$`) + endTimeLineRe := regexp.MustCompile(`^\s*ends \d (.+?);$`) + macLineRe := regexp.MustCompile(`^\s*hardware ethernet (.+?);$`) + + for _, line := range strings.Split(string(dhcpBytes), "\n") { + // Need to trim off CR character when running in windows + line = strings.TrimRight(line, "\r") + + matches := ipLineRe.FindStringSubmatch(line) + if matches != nil { + lastIp = matches[1] + continue + } + + matches = endTimeLineRe.FindStringSubmatch(line) + if matches != nil { + lastLeaseEnd, _ = time.Parse("2006/01/02 15:04:05", matches[1]) + continue + } + + // If the mac address matches and this lease ends farther in the + // future than the last match we might have, then choose it. + matches = macLineRe.FindStringSubmatch(line) + if matches != nil && matches[1] == f.MACAddress && curLeaseEnd.Before(lastLeaseEnd) { + curIp = lastIp + curLeaseEnd = lastLeaseEnd + } + } + + if curIp == "" { + return "", errors.New("IP not found for MAC in DHCP leases") + } + + return curIp, nil +} diff --git a/builder/vmware/common/ssh.go b/builder/vmware/common/ssh.go index 985f2eb76..7923dc376 100644 --- a/builder/vmware/common/ssh.go +++ b/builder/vmware/common/ssh.go @@ -2,12 +2,89 @@ package common import ( gossh "code.google.com/p/go.crypto/ssh" + "errors" + "fmt" "io/ioutil" + "log" "os" + "github.com/mitchellh/multistep" "github.com/mitchellh/packer/communicator/ssh" ) +func sshAddress(config *SSHConfig) func(multistep.StateBag) (string, error) { + return func(state multistep.StateBag) (string, error) { + driver := state.Get("driver").(Driver) + vmxPath := state.Get("vmx_path").(string) + + log.Println("Lookup up IP information...") + f, err := os.Open(vmxPath) + if err != nil { + return "", err + } + defer f.Close() + + vmxBytes, err := ioutil.ReadAll(f) + if err != nil { + return "", err + } + + vmxData := ParseVMX(string(vmxBytes)) + + var ok bool + macAddress := "" + if macAddress, ok = vmxData["ethernet0.address"]; !ok || macAddress == "" { + if macAddress, ok = vmxData["ethernet0.generatedaddress"]; !ok || macAddress == "" { + return "", errors.New("couldn't find MAC address in VMX") + } + } + + ipLookup := &DHCPLeaseGuestLookup{ + Driver: driver, + Device: "vmnet8", + MACAddress: macAddress, + } + + ipAddress, err := ipLookup.GuestIP() + if err != nil { + log.Printf("IP lookup failed: %s", err) + return "", fmt.Errorf("IP lookup failed: %s", err) + } + + if ipAddress == "" { + log.Println("IP is blank, no IP yet.") + return "", errors.New("IP is blank") + } + + log.Printf("Detected IP: %s", ipAddress) + return fmt.Sprintf("%s:%d", ipAddress, config.SSHPort), nil + } +} + +func sshConfig(config *SSHConfig) func(multistep.StateBag) (*gossh.ClientConfig, error) { + return func(state multistep.StateBag) (*gossh.ClientConfig, error) { + auth := []gossh.ClientAuth{ + gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)), + gossh.ClientAuthKeyboardInteractive( + ssh.PasswordKeyboardInteractive(config.SSHPassword)), + } + + if config.SSHKeyPath != "" { + keyring, err := sshKeyToKeyring(config.SSHKeyPath) + if err != nil { + return nil, err + } + + auth = append(auth, gossh.ClientAuthKeyring(keyring)) + } + + return &gossh.ClientConfig{ + User: config.SSHUser, + Auth: auth, + }, nil + } +} + func sshKeyToKeyring(path string) (gossh.ClientKeyring, error) { f, err := os.Open(path) if err != nil { diff --git a/builder/vmware/iso/ssh.go b/builder/vmware/iso/ssh.go deleted file mode 100644 index 32c7cb962..000000000 --- a/builder/vmware/iso/ssh.go +++ /dev/null @@ -1,105 +0,0 @@ -package iso - -import ( - gossh "code.google.com/p/go.crypto/ssh" - "errors" - "fmt" - "github.com/mitchellh/multistep" - vmwcommon "github.com/mitchellh/packer/builder/vmware/common" - "github.com/mitchellh/packer/communicator/ssh" - "io/ioutil" - "log" - "os" -) - -func sshAddress(state multistep.StateBag) (string, error) { - config := state.Get("config").(*config) - driver := state.Get("driver").(Driver) - vmxPath := state.Get("vmx_path").(string) - - log.Println("Lookup up IP information...") - f, err := os.Open(vmxPath) - if err != nil { - return "", err - } - defer f.Close() - - vmxBytes, err := ioutil.ReadAll(f) - if err != nil { - return "", err - } - - vmxData := vmwcommon.ParseVMX(string(vmxBytes)) - - var ok bool - macAddress := "" - if macAddress, ok = vmxData["ethernet0.address"]; !ok || macAddress == "" { - if macAddress, ok = vmxData["ethernet0.generatedaddress"]; !ok || macAddress == "" { - return "", errors.New("couldn't find MAC address in VMX") - } - } - - ipLookup := &DHCPLeaseGuestLookup{ - Driver: driver, - Device: "vmnet8", - MACAddress: macAddress, - } - - ipAddress, err := ipLookup.GuestIP() - if err != nil { - log.Printf("IP lookup failed: %s", err) - return "", fmt.Errorf("IP lookup failed: %s", err) - } - - if ipAddress == "" { - log.Println("IP is blank, no IP yet.") - return "", errors.New("IP is blank") - } - - log.Printf("Detected IP: %s", ipAddress) - return fmt.Sprintf("%s:%d", ipAddress, config.SSHPort), nil -} - -func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) { - config := state.Get("config").(*config) - - auth := []gossh.ClientAuth{ - gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)), - gossh.ClientAuthKeyboardInteractive( - ssh.PasswordKeyboardInteractive(config.SSHPassword)), - } - - if config.SSHKeyPath != "" { - keyring, err := sshKeyToKeyring(config.SSHKeyPath) - if err != nil { - return nil, err - } - - auth = append(auth, gossh.ClientAuthKeyring(keyring)) - } - - return &gossh.ClientConfig{ - User: config.SSHUser, - Auth: auth, - }, nil -} - -func sshKeyToKeyring(path string) (gossh.ClientKeyring, error) { - f, err := os.Open(path) - if err != nil { - return nil, err - } - defer f.Close() - - keyBytes, err := ioutil.ReadAll(f) - if err != nil { - return nil, err - } - - keyring := new(ssh.SimpleKeychain) - if err := keyring.AddPEMKey(string(keyBytes)); err != nil { - return nil, err - } - - return keyring, nil -} From 50f8b2c1a35ca9e2096b5142b3934faf758c03fd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Dec 2013 11:21:02 -0700 Subject: [PATCH 10/45] builder/vmware: move outputdir stuff to common --- builder/vmware/common/output_dir.go | 15 ++ builder/vmware/common/output_dir_local.go | 53 +++++++ .../vmware/common/output_dir_local_test.go | 9 ++ builder/vmware/common/step_output_dir.go | 74 +++++++++ builder/vmware/common/step_output_dir_test.go | 150 ++++++++++++++++++ builder/vmware/iso/builder.go | 31 ++-- builder/vmware/iso/step_prepare_output_dir.go | 84 ---------- 7 files changed, 323 insertions(+), 93 deletions(-) create mode 100644 builder/vmware/common/output_dir.go create mode 100644 builder/vmware/common/output_dir_local.go create mode 100644 builder/vmware/common/output_dir_local_test.go create mode 100644 builder/vmware/common/step_output_dir.go create mode 100644 builder/vmware/common/step_output_dir_test.go delete mode 100644 builder/vmware/iso/step_prepare_output_dir.go diff --git a/builder/vmware/common/output_dir.go b/builder/vmware/common/output_dir.go new file mode 100644 index 000000000..8af513a19 --- /dev/null +++ b/builder/vmware/common/output_dir.go @@ -0,0 +1,15 @@ +package common + +// OutputDir is an interface type that abstracts the creation and handling +// of the output directory for VMware-based products. The abstraction is made +// so that the output directory can be properly made on remote (ESXi) based +// VMware products as well as local. +type OutputDir interface { + DirExists() (bool, error) + ListFiles() ([]string, error) + MkdirAll() error + Remove(string) error + RemoveAll() error + SetOutputDir(string) + String() string +} diff --git a/builder/vmware/common/output_dir_local.go b/builder/vmware/common/output_dir_local.go new file mode 100644 index 000000000..e864ca316 --- /dev/null +++ b/builder/vmware/common/output_dir_local.go @@ -0,0 +1,53 @@ +package common + +import ( + "os" + "path/filepath" +) + +// LocalOutputDir is an OutputDir implementation where the directory +// is on the local machine. +type LocalOutputDir struct { + Dir string +} + +func (d *LocalOutputDir) DirExists() (bool, error) { + _, err := os.Stat(d.Dir) + return err == nil, nil +} + +func (d *LocalOutputDir) ListFiles() ([]string, error) { + files := make([]string, 0, 10) + + visit := func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + files = append(files, path) + } + return nil + } + + return files, filepath.Walk(d.Dir, visit) +} + +func (d *LocalOutputDir) MkdirAll() error { + return os.MkdirAll(d.Dir, 0755) +} + +func (d *LocalOutputDir) Remove(path string) error { + return os.Remove(path) +} + +func (d *LocalOutputDir) RemoveAll() error { + return os.RemoveAll(d.Dir) +} + +func (d *LocalOutputDir) SetOutputDir(path string) { + d.Dir = path +} + +func (d *LocalOutputDir) String() string { + return d.Dir +} diff --git a/builder/vmware/common/output_dir_local_test.go b/builder/vmware/common/output_dir_local_test.go new file mode 100644 index 000000000..c3197117e --- /dev/null +++ b/builder/vmware/common/output_dir_local_test.go @@ -0,0 +1,9 @@ +package common + +import ( + "testing" +) + +func TestLocalOuputDir_impl(t *testing.T) { + var _ OutputDir = new(LocalOutputDir) +} diff --git a/builder/vmware/common/step_output_dir.go b/builder/vmware/common/step_output_dir.go new file mode 100644 index 000000000..17f13d5d3 --- /dev/null +++ b/builder/vmware/common/step_output_dir.go @@ -0,0 +1,74 @@ +package common + +import ( + "fmt" + "log" + "time" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +// StepOutputDir sets up the output directory by creating it if it does +// not exist, deleting it if it does exist and we're forcing, and cleaning +// it up when we're done with it. +type StepOutputDir struct { + Force bool + + success bool +} + +func (s *StepOutputDir) Run(state multistep.StateBag) multistep.StepAction { + dir := state.Get("dir").(OutputDir) + ui := state.Get("ui").(packer.Ui) + + exists, err := dir.DirExists() + if err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + + if exists { + if s.Force { + ui.Say("Deleting previous output directory...") + dir.RemoveAll() + } else { + state.Put("error", fmt.Errorf( + "Output directory '%s' already exists.", dir.String())) + return multistep.ActionHalt + } + } + + if err := dir.MkdirAll(); err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + + s.success = true + return multistep.ActionContinue +} + +func (s *StepOutputDir) Cleanup(state multistep.StateBag) { + if !s.success { + return + } + + _, cancelled := state.GetOk(multistep.StateCancelled) + _, halted := state.GetOk(multistep.StateHalted) + + if cancelled || halted { + dir := state.Get("dir").(OutputDir) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Deleting output directory...") + for i := 0; i < 5; i++ { + err := dir.RemoveAll() + if err == nil { + break + } + + log.Printf("Error removing output dir: %s", err) + time.Sleep(2 * time.Second) + } + } +} diff --git a/builder/vmware/common/step_output_dir_test.go b/builder/vmware/common/step_output_dir_test.go new file mode 100644 index 000000000..bacc1d85c --- /dev/null +++ b/builder/vmware/common/step_output_dir_test.go @@ -0,0 +1,150 @@ +package common + +import ( + "github.com/mitchellh/multistep" + "io/ioutil" + "os" + "testing" +) + +func testOutputDir(t *testing.T) *LocalOutputDir { + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + os.RemoveAll(td) + return &LocalOutputDir{Dir: td} +} + +func TestStepOutputDir_impl(t *testing.T) { + var _ multistep.Step = new(StepOutputDir) +} + +func TestStepOutputDir(t *testing.T) { + state := testState(t) + step := new(StepOutputDir) + + dir := testOutputDir(t) + state.Put("dir", dir) + + // 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") + } + if _, err := os.Stat(dir.Dir); err != nil { + t.Fatalf("err: %s", err) + } + + // Test the cleanup + step.Cleanup(state) + if _, err := os.Stat(dir.Dir); 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) + + // Make sure the dir exists + if err := os.MkdirAll(dir.Dir, 0755); err != nil { + t.Fatalf("err: %s", err) + } + + // Test the run + if action := step.Run(state); action != multistep.ActionHalt { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); !ok { + t.Fatal("should have error") + } + + // Test the cleanup + step.Cleanup(state) + if _, err := os.Stat(dir.Dir); err != nil { + t.Fatal("should not delete dir") + } +} + +func TestStepOutputDir_existsForce(t *testing.T) { + state := testState(t) + step := new(StepOutputDir) + step.Force = true + + dir := testOutputDir(t) + state.Put("dir", dir) + + // Make sure the dir exists + if err := os.MkdirAll(dir.Dir, 0755); err != nil { + t.Fatalf("err: %s", err) + } + + // 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") + } + if _, err := os.Stat(dir.Dir); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestStepOutputDir_cancel(t *testing.T) { + state := testState(t) + step := new(StepOutputDir) + + dir := testOutputDir(t) + state.Put("dir", dir) + + // 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") + } + if _, err := os.Stat(dir.Dir); 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 { + t.Fatal("directory should not exist") + } +} + +func TestStepOutputDir_halt(t *testing.T) { + state := testState(t) + step := new(StepOutputDir) + + dir := testOutputDir(t) + state.Put("dir", dir) + + // 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") + } + if _, err := os.Stat(dir.Dir); 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 { + t.Fatal("directory should not exist") + } +} diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 2dd7f3776..173a8fc7c 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -346,6 +346,25 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe return nil, fmt.Errorf("Failed creating VMware driver: %s", err) } + // Determine the output dir implementation + var dir OutputDir + switch d := driver.(type) { + case OutputDir: + dir = d + default: + dir = new(vmwcommon.LocalOutputDir) + } + dir.SetOutputDir(b.config.OutputDir) + + // Setup the state bag + state := new(multistep.BasicStateBag) + state.Put("cache", cache) + state.Put("config", &b.config) + state.Put("dir", dir) + state.Put("driver", driver) + state.Put("hook", hook) + state.Put("ui", ui) + // Seed the random number generator rand.Seed(time.Now().UTC().UnixNano()) @@ -358,7 +377,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ResultKey: "iso_path", Url: b.config.ISOUrls, }, - &stepPrepareOutputDir{}, + &vmwcommon.StepOutputDir{ + Force: b.config.PackerForce, + }, &common.StepCreateFloppy{ Files: b.config.FloppyFiles, }, @@ -390,14 +411,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &stepCompactDisk{}, } - // Setup the state bag - state := new(multistep.BasicStateBag) - state.Put("cache", cache) - state.Put("config", &b.config) - state.Put("driver", driver) - state.Put("hook", hook) - state.Put("ui", ui) - // Run! if b.config.PackerDebug { b.runner = &multistep.DebugRunner{ diff --git a/builder/vmware/iso/step_prepare_output_dir.go b/builder/vmware/iso/step_prepare_output_dir.go deleted file mode 100644 index abd2bbe29..000000000 --- a/builder/vmware/iso/step_prepare_output_dir.go +++ /dev/null @@ -1,84 +0,0 @@ -package iso - -import ( - "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - "log" - "time" -) - -type stepPrepareOutputDir struct { - dir OutputDir -} - -func (s *stepPrepareOutputDir) Run(state multistep.StateBag) multistep.StepAction { - config := state.Get("config").(*config) - ui := state.Get("ui").(packer.Ui) - - dir := s.outputDir(state) - dir.SetOutputDir(config.OutputDir) - - exists, err := dir.DirExists() - if err != nil { - state.Put("error", err) - return multistep.ActionHalt - } - - if exists { - if config.PackerForce { - ui.Say("Deleting previous output directory...") - dir.RemoveAll() - } else { - state.Put("error", fmt.Errorf( - "Output directory '%s' already exists.", config.OutputDir)) - return multistep.ActionHalt - } - } - - if err := dir.MkdirAll(); err != nil { - state.Put("error", err) - return multistep.ActionHalt - } - - s.dir = dir - state.Put("dir", dir) - return multistep.ActionContinue -} - -func (s *stepPrepareOutputDir) Cleanup(state multistep.StateBag) { - _, cancelled := state.GetOk(multistep.StateCancelled) - _, halted := state.GetOk(multistep.StateHalted) - - if cancelled || halted { - ui := state.Get("ui").(packer.Ui) - - if s.dir != nil { - ui.Say("Deleting output directory...") - for i := 0; i < 5; i++ { - err := s.dir.RemoveAll() - if err == nil { - break - } - - log.Printf("Error removing output dir: %s", err) - time.Sleep(2 * time.Second) - } - } - } -} - -func (s *stepPrepareOutputDir) outputDir(state multistep.StateBag) (dir OutputDir) { - driver := state.Get("driver").(Driver) - - switch d := driver.(type) { - case OutputDir: - log.Printf("Using driver as the OutputDir implementation") - dir = d - default: - log.Printf("Using localOutputDir implementation") - dir = new(localOutputDir) - } - - return -} From d73cbd374410aefb1da7d800e799de47b05c6064 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Dec 2013 11:22:22 -0700 Subject: [PATCH 11/45] builder/vmware/common: make the dir private for LocalOutputDir --- builder/vmware/common/output_dir_local.go | 14 +++++------ builder/vmware/common/step_output_dir_test.go | 25 +++++++++++-------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/builder/vmware/common/output_dir_local.go b/builder/vmware/common/output_dir_local.go index e864ca316..39810efa9 100644 --- a/builder/vmware/common/output_dir_local.go +++ b/builder/vmware/common/output_dir_local.go @@ -8,11 +8,11 @@ import ( // LocalOutputDir is an OutputDir implementation where the directory // is on the local machine. type LocalOutputDir struct { - Dir string + dir string } func (d *LocalOutputDir) DirExists() (bool, error) { - _, err := os.Stat(d.Dir) + _, err := os.Stat(d.dir) return err == nil, nil } @@ -29,11 +29,11 @@ func (d *LocalOutputDir) ListFiles() ([]string, error) { return nil } - return files, filepath.Walk(d.Dir, visit) + return files, filepath.Walk(d.dir, visit) } func (d *LocalOutputDir) MkdirAll() error { - return os.MkdirAll(d.Dir, 0755) + return os.MkdirAll(d.dir, 0755) } func (d *LocalOutputDir) Remove(path string) error { @@ -41,13 +41,13 @@ func (d *LocalOutputDir) Remove(path string) error { } func (d *LocalOutputDir) RemoveAll() error { - return os.RemoveAll(d.Dir) + return os.RemoveAll(d.dir) } func (d *LocalOutputDir) SetOutputDir(path string) { - d.Dir = path + d.dir = path } func (d *LocalOutputDir) String() string { - return d.Dir + return d.dir } diff --git a/builder/vmware/common/step_output_dir_test.go b/builder/vmware/common/step_output_dir_test.go index bacc1d85c..fcd64ca7e 100644 --- a/builder/vmware/common/step_output_dir_test.go +++ b/builder/vmware/common/step_output_dir_test.go @@ -13,7 +13,10 @@ func testOutputDir(t *testing.T) *LocalOutputDir { t.Fatalf("err: %s", err) } os.RemoveAll(td) - return &LocalOutputDir{Dir: td} + + result := new(LocalOutputDir) + result.SetOutputDir(td) + return result } func TestStepOutputDir_impl(t *testing.T) { @@ -34,13 +37,13 @@ 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(dir.dir); 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(dir.dir); err != nil { t.Fatalf("err: %s", err) } } @@ -53,7 +56,7 @@ func TestStepOutputDir_existsNoForce(t *testing.T) { state.Put("dir", dir) // Make sure the dir exists - if err := os.MkdirAll(dir.Dir, 0755); err != nil { + if err := os.MkdirAll(dir.dir, 0755); err != nil { t.Fatalf("err: %s", err) } @@ -67,7 +70,7 @@ func TestStepOutputDir_existsNoForce(t *testing.T) { // Test the cleanup step.Cleanup(state) - if _, err := os.Stat(dir.Dir); err != nil { + if _, err := os.Stat(dir.dir); err != nil { t.Fatal("should not delete dir") } } @@ -81,7 +84,7 @@ func TestStepOutputDir_existsForce(t *testing.T) { state.Put("dir", dir) // Make sure the dir exists - if err := os.MkdirAll(dir.Dir, 0755); err != nil { + if err := os.MkdirAll(dir.dir, 0755); err != nil { t.Fatalf("err: %s", err) } @@ -92,7 +95,7 @@ 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(dir.dir); err != nil { t.Fatalf("err: %s", err) } } @@ -111,14 +114,14 @@ 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(dir.dir); 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(dir.dir); err == nil { t.Fatal("directory should not exist") } } @@ -137,14 +140,14 @@ 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(dir.dir); 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(dir.dir); err == nil { t.Fatal("directory should not exist") } } From 458bfd186fd54d46818933897311965b2c4bc324 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Dec 2013 11:31:57 -0700 Subject: [PATCH 12/45] builder/vmware: move driver out of ISO --- builder/vmware/common/ssh.go | 4 +- builder/vmware/iso/builder.go | 2 +- builder/vmware/iso/driver.go | 53 +++----------------- builder/vmware/iso/driver_esx5_test.go | 3 +- builder/vmware/iso/driver_fusion5.go | 9 +++- builder/vmware/iso/driver_player5.go | 9 +++- builder/vmware/iso/driver_workstation9.go | 9 +++- builder/vmware/iso/guest_ip.go | 4 +- builder/vmware/iso/remote_driver.go | 6 ++- builder/vmware/iso/step_compact_disk.go | 3 +- builder/vmware/iso/step_configure_vnc.go | 2 +- builder/vmware/iso/step_create_disk.go | 3 +- builder/vmware/iso/step_prepare_tools.go | 3 +- builder/vmware/iso/step_remote_upload.go | 3 +- builder/vmware/iso/step_run.go | 5 +- builder/vmware/iso/step_shutdown.go | 3 +- builder/vmware/iso/step_suppress_messages.go | 3 +- builder/vmware/iso/step_type_boot_command.go | 3 +- 18 files changed, 60 insertions(+), 67 deletions(-) diff --git a/builder/vmware/common/ssh.go b/builder/vmware/common/ssh.go index 7923dc376..7bf8bac72 100644 --- a/builder/vmware/common/ssh.go +++ b/builder/vmware/common/ssh.go @@ -12,7 +12,7 @@ import ( "github.com/mitchellh/packer/communicator/ssh" ) -func sshAddress(config *SSHConfig) func(multistep.StateBag) (string, error) { +func SSHAddressFunc(config *SSHConfig) func(multistep.StateBag) (string, error) { return func(state multistep.StateBag) (string, error) { driver := state.Get("driver").(Driver) vmxPath := state.Get("vmx_path").(string) @@ -61,7 +61,7 @@ func sshAddress(config *SSHConfig) func(multistep.StateBag) (string, error) { } } -func sshConfig(config *SSHConfig) func(multistep.StateBag) (*gossh.ClientConfig, error) { +func SSHConfigFunc(config *SSHConfig) func(multistep.StateBag) (*gossh.ClientConfig, error) { return func(state multistep.StateBag) (*gossh.ClientConfig, error) { auth := []gossh.ClientAuth{ gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)), diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 173a8fc7c..ea19bf24a 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -399,7 +399,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &stepTypeBootCommand{}, &common.StepConnectSSH{ SSHAddress: driver.SSHAddress, - SSHConfig: sshConfig, + SSHConfig: vmwcommon.SSHConfigFunc(&b.config.SSHConfig), SSHWaitTimeout: b.config.SSHWaitTimeout, NoPty: b.config.SSHSkipRequestPty, }, diff --git a/builder/vmware/iso/driver.go b/builder/vmware/iso/driver.go index 3e52af5a8..0fcc77fc6 100644 --- a/builder/vmware/iso/driver.go +++ b/builder/vmware/iso/driver.go @@ -3,58 +3,21 @@ package iso import ( "bytes" "fmt" - "github.com/mitchellh/multistep" "log" "os/exec" "runtime" "strings" + + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" ) -// A driver is able to talk to VMware, control virtual machines, etc. -type Driver interface { - // CompactDisk compacts a virtual disk. - CompactDisk(string) error - - // CreateDisk creates a virtual disk with the given size. - CreateDisk(string, string, string) error - - // Checks if the VMX file at the given path is running. - IsRunning(string) (bool, error) - - // SSHAddress returns the SSH address for the VM that is being - // managed by this driver. - SSHAddress(multistep.StateBag) (string, error) - - // Start starts a VM specified by the path to the VMX given. - Start(string, bool) error - - // Stop stops a VM specified by the path to the VMX given. - Stop(string) error - - // SuppressMessages modifies the VMX or surrounding directory so that - // VMware doesn't show any annoying messages. - SuppressMessages(string) error - - // Get the path to the VMware ISO for the given flavor. - ToolsIsoPath(string) string - - // Get the path to the DHCP leases file for the given device. - DhcpLeasesPath(string) string - - // Verify checks to make sure that this driver should function - // properly. This should check that all the files it will use - // appear to exist and so on. If everything is okay, this doesn't - // return an error. Otherwise, this returns an error. - Verify() error -} - // NewDriver returns a new driver implementation for this operating // system, or an error if the driver couldn't be initialized. -func NewDriver(config *config) (Driver, error) { - drivers := []Driver{} +func NewDriver(config *config) (vmwcommon.Driver, error) { + drivers := []vmwcommon.Driver{} if config.RemoteType != "" { - drivers = []Driver{ + drivers = []vmwcommon.Driver{ &ESX5Driver{ Host: config.RemoteHost, Port: config.RemotePort, @@ -66,18 +29,18 @@ func NewDriver(config *config) (Driver, error) { } else { switch runtime.GOOS { case "darwin": - drivers = []Driver{ + drivers = []vmwcommon.Driver{ &Fusion5Driver{ AppPath: "/Applications/VMware Fusion.app", }, } case "linux": - drivers = []Driver{ + drivers = []vmwcommon.Driver{ new(Workstation9Driver), new(Player5LinuxDriver), } case "windows": - drivers = []Driver{ + drivers = []vmwcommon.Driver{ new(Workstation9Driver), } default: diff --git a/builder/vmware/iso/driver_esx5_test.go b/builder/vmware/iso/driver_esx5_test.go index 577e68d71..c7e4f776d 100644 --- a/builder/vmware/iso/driver_esx5_test.go +++ b/builder/vmware/iso/driver_esx5_test.go @@ -1,11 +1,12 @@ package iso import ( + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "testing" ) func TestESX5Driver_implDriver(t *testing.T) { - var _ Driver = new(ESX5Driver) + var _ vmwcommon.Driver = new(ESX5Driver) } func TestESX5Driver_implRemoteDriver(t *testing.T) { diff --git a/builder/vmware/iso/driver_fusion5.go b/builder/vmware/iso/driver_fusion5.go index 5674acafe..9366c1755 100644 --- a/builder/vmware/iso/driver_fusion5.go +++ b/builder/vmware/iso/driver_fusion5.go @@ -2,18 +2,23 @@ package iso import ( "fmt" - "github.com/mitchellh/multistep" "io/ioutil" "os" "os/exec" "path/filepath" "strings" + + "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" ) // Fusion5Driver is a driver that can run VMWare Fusion 5. type Fusion5Driver struct { // This is the path to the "VMware Fusion.app" AppPath string + + // SSHConfig are the SSH settings for the Fusion VM + SSHConfig *vmwcommon.SSHConfig } func (d *Fusion5Driver) CompactDisk(diskPath string) error { @@ -61,7 +66,7 @@ func (d *Fusion5Driver) IsRunning(vmxPath string) (bool, error) { } func (d *Fusion5Driver) SSHAddress(state multistep.StateBag) (string, error) { - return sshAddress(state) + return vmwcommon.SSHAddressFunc(d.SSHConfig)(state) } func (d *Fusion5Driver) Start(vmxPath string, headless bool) error { diff --git a/builder/vmware/iso/driver_player5.go b/builder/vmware/iso/driver_player5.go index 56a8b0b06..a317d0c59 100644 --- a/builder/vmware/iso/driver_player5.go +++ b/builder/vmware/iso/driver_player5.go @@ -2,11 +2,13 @@ package iso import ( "fmt" - "github.com/mitchellh/multistep" "os" "os/exec" "path/filepath" "strings" + + "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" ) // Player5LinuxDriver is a driver that can run VMware Player 5 on Linux. @@ -15,6 +17,9 @@ type Player5LinuxDriver struct { VdiskManagerPath string QemuImgPath string VmrunPath string + + // SSHConfig are the SSH settings for the Fusion VM + SSHConfig *vmwcommon.SSHConfig } func (d *Player5LinuxDriver) CompactDisk(diskPath string) error { @@ -88,7 +93,7 @@ func (d *Player5LinuxDriver) IsRunning(vmxPath string) (bool, error) { } func (d *Player5LinuxDriver) SSHAddress(state multistep.StateBag) (string, error) { - return sshAddress(state) + return vmwcommon.SSHAddressFunc(d.SSHConfig)(state) } func (d *Player5LinuxDriver) Start(vmxPath string, headless bool) error { diff --git a/builder/vmware/iso/driver_workstation9.go b/builder/vmware/iso/driver_workstation9.go index 101572ada..e87c3b2e2 100644 --- a/builder/vmware/iso/driver_workstation9.go +++ b/builder/vmware/iso/driver_workstation9.go @@ -2,12 +2,14 @@ package iso import ( "fmt" - "github.com/mitchellh/multistep" "log" "os" "os/exec" "path/filepath" "strings" + + "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" ) // Workstation9Driver is a driver that can run VMware Workstation 9 @@ -16,6 +18,9 @@ type Workstation9Driver struct { AppPath string VdiskManagerPath string VmrunPath string + + // SSHConfig are the SSH settings for the Fusion VM + SSHConfig *vmwcommon.SSHConfig } func (d *Workstation9Driver) CompactDisk(diskPath string) error { @@ -63,7 +68,7 @@ func (d *Workstation9Driver) IsRunning(vmxPath string) (bool, error) { } func (d *Workstation9Driver) SSHAddress(state multistep.StateBag) (string, error) { - return sshAddress(state) + return vmwcommon.SSHAddressFunc(d.SSHConfig)(state) } func (d *Workstation9Driver) Start(vmxPath string, headless bool) error { diff --git a/builder/vmware/iso/guest_ip.go b/builder/vmware/iso/guest_ip.go index 2b9ca53fe..9d9713096 100644 --- a/builder/vmware/iso/guest_ip.go +++ b/builder/vmware/iso/guest_ip.go @@ -8,6 +8,8 @@ import ( "regexp" "strings" "time" + + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" ) // Interface to help find the IP address of a running virtual machine. @@ -19,7 +21,7 @@ type GuestIPFinder interface { // lease information from the VMware network devices. type DHCPLeaseGuestLookup struct { // Driver that is being used (to find leases path) - Driver Driver + Driver vmwcommon.Driver // Device that the guest is connected to. Device string diff --git a/builder/vmware/iso/remote_driver.go b/builder/vmware/iso/remote_driver.go index 4a88ed1e6..d020cb800 100644 --- a/builder/vmware/iso/remote_driver.go +++ b/builder/vmware/iso/remote_driver.go @@ -1,7 +1,11 @@ package iso +import ( + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" +) + type RemoteDriver interface { - Driver + vmwcommon.Driver // UploadISO uploads a local ISO to the remote side and returns the // new path that should be used in the VMX along with an error if it diff --git a/builder/vmware/iso/step_compact_disk.go b/builder/vmware/iso/step_compact_disk.go index 352ecf56e..8b4cf6593 100644 --- a/builder/vmware/iso/step_compact_disk.go +++ b/builder/vmware/iso/step_compact_disk.go @@ -3,6 +3,7 @@ package iso import ( "fmt" "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/packer" "log" ) @@ -22,7 +23,7 @@ type stepCompactDisk struct{} func (stepCompactDisk) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*config) - driver := state.Get("driver").(Driver) + driver := state.Get("driver").(vmwcommon.Driver) ui := state.Get("ui").(packer.Ui) full_disk_path := state.Get("full_disk_path").(string) diff --git a/builder/vmware/iso/step_configure_vnc.go b/builder/vmware/iso/step_configure_vnc.go index 676439213..1f194be76 100644 --- a/builder/vmware/iso/step_configure_vnc.go +++ b/builder/vmware/iso/step_configure_vnc.go @@ -47,7 +47,7 @@ func (stepConfigureVNC) VNCAddress(portMin, portMax uint) (string, uint) { func (s *stepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*config) - driver := state.Get("driver").(Driver) + driver := state.Get("driver").(vmwcommon.Driver) ui := state.Get("ui").(packer.Ui) vmxPath := state.Get("vmx_path").(string) diff --git a/builder/vmware/iso/step_create_disk.go b/builder/vmware/iso/step_create_disk.go index 8544313c3..cade63f75 100644 --- a/builder/vmware/iso/step_create_disk.go +++ b/builder/vmware/iso/step_create_disk.go @@ -3,6 +3,7 @@ package iso import ( "fmt" "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/packer" "path/filepath" ) @@ -20,7 +21,7 @@ type stepCreateDisk struct{} func (stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*config) - driver := state.Get("driver").(Driver) + driver := state.Get("driver").(vmwcommon.Driver) ui := state.Get("ui").(packer.Ui) ui.Say("Creating virtual machine disk") diff --git a/builder/vmware/iso/step_prepare_tools.go b/builder/vmware/iso/step_prepare_tools.go index 3dc1e3482..69ff81690 100644 --- a/builder/vmware/iso/step_prepare_tools.go +++ b/builder/vmware/iso/step_prepare_tools.go @@ -3,6 +3,7 @@ package iso import ( "fmt" "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "os" ) @@ -10,7 +11,7 @@ type stepPrepareTools struct{} func (*stepPrepareTools) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*config) - driver := state.Get("driver").(Driver) + driver := state.Get("driver").(vmwcommon.Driver) if config.ToolsUploadFlavor == "" { return multistep.ActionContinue diff --git a/builder/vmware/iso/step_remote_upload.go b/builder/vmware/iso/step_remote_upload.go index 00aca6e09..717b3dc13 100644 --- a/builder/vmware/iso/step_remote_upload.go +++ b/builder/vmware/iso/step_remote_upload.go @@ -3,6 +3,7 @@ package iso import ( "fmt" "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/packer" "log" ) @@ -15,7 +16,7 @@ type stepRemoteUpload struct { } func (s *stepRemoteUpload) Run(state multistep.StateBag) multistep.StepAction { - driver := state.Get("driver").(Driver) + driver := state.Get("driver").(vmwcommon.Driver) ui := state.Get("ui").(packer.Ui) remote, ok := driver.(RemoteDriver) diff --git a/builder/vmware/iso/step_run.go b/builder/vmware/iso/step_run.go index 66ed76377..835ff06ab 100644 --- a/builder/vmware/iso/step_run.go +++ b/builder/vmware/iso/step_run.go @@ -3,6 +3,7 @@ package iso import ( "fmt" "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/packer" "time" ) @@ -26,7 +27,7 @@ type stepRun struct { func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*config) - driver := state.Get("driver").(Driver) + driver := state.Get("driver").(vmwcommon.Driver) ui := state.Get("ui").(packer.Ui) vmxPath := state.Get("vmx_path").(string) vncIp := state.Get("vnc_ip").(string) @@ -84,7 +85,7 @@ func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction { } func (s *stepRun) Cleanup(state multistep.StateBag) { - driver := state.Get("driver").(Driver) + driver := state.Get("driver").(vmwcommon.Driver) ui := state.Get("ui").(packer.Ui) // If we started the machine... stop it. diff --git a/builder/vmware/iso/step_shutdown.go b/builder/vmware/iso/step_shutdown.go index 67c6a29f4..fea2f129a 100644 --- a/builder/vmware/iso/step_shutdown.go +++ b/builder/vmware/iso/step_shutdown.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/packer" "log" "path/filepath" @@ -30,7 +31,7 @@ type stepShutdown struct{} func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { comm := state.Get("communicator").(packer.Communicator) config := state.Get("config").(*config) - driver := state.Get("driver").(Driver) + driver := state.Get("driver").(vmwcommon.Driver) ui := state.Get("ui").(packer.Ui) vmxPath := state.Get("vmx_path").(string) diff --git a/builder/vmware/iso/step_suppress_messages.go b/builder/vmware/iso/step_suppress_messages.go index a936a5e6e..e2044fdcd 100644 --- a/builder/vmware/iso/step_suppress_messages.go +++ b/builder/vmware/iso/step_suppress_messages.go @@ -3,6 +3,7 @@ package iso import ( "fmt" "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/packer" "log" ) @@ -11,7 +12,7 @@ import ( type stepSuppressMessages struct{} func (s *stepSuppressMessages) Run(state multistep.StateBag) multistep.StepAction { - driver := state.Get("driver").(Driver) + driver := state.Get("driver").(vmwcommon.Driver) ui := state.Get("ui").(packer.Ui) vmxPath := state.Get("vmx_path").(string) diff --git a/builder/vmware/iso/step_type_boot_command.go b/builder/vmware/iso/step_type_boot_command.go index c72beecb7..89e7ac432 100644 --- a/builder/vmware/iso/step_type_boot_command.go +++ b/builder/vmware/iso/step_type_boot_command.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/mitchellh/go-vnc" "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/packer" "log" "net" @@ -36,7 +37,7 @@ type stepTypeBootCommand struct{} func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*config) - driver := state.Get("driver").(Driver) + driver := state.Get("driver").(vmwcommon.Driver) httpPort := state.Get("http_port").(uint) ui := state.Get("ui").(packer.Ui) vncIp := state.Get("vnc_ip").(string) From e5f674a8c2f127efd7cb31492afa35251f86b507 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Dec 2013 11:55:44 -0700 Subject: [PATCH 13/45] builder/vmware/common: Mock driver and test for DHCP Lease IP lookup --- builder/vmware/common/driver_mock.go | 113 ++++++++++++++++++++++ builder/vmware/common/driver_mock_test.go | 9 ++ builder/vmware/common/guest_ip_test.go | 82 ++++++++++++++++ builder/vmware/iso/guest_ip.go | 91 ----------------- 4 files changed, 204 insertions(+), 91 deletions(-) create mode 100644 builder/vmware/common/driver_mock.go create mode 100644 builder/vmware/common/driver_mock_test.go create mode 100644 builder/vmware/common/guest_ip_test.go delete mode 100644 builder/vmware/iso/guest_ip.go diff --git a/builder/vmware/common/driver_mock.go b/builder/vmware/common/driver_mock.go new file mode 100644 index 000000000..8e73990fa --- /dev/null +++ b/builder/vmware/common/driver_mock.go @@ -0,0 +1,113 @@ +package common + +import ( + "github.com/mitchellh/multistep" +) + +type DriverMock struct { + CompactDiskCalled bool + CompactDiskPath string + CompactDiskErr error + + CreateDiskCalled bool + CreateDiskOutput string + CreateDiskSize string + CreateDiskTypeId string + CreateDiskErr error + + IsRunningCalled bool + IsRunningPath string + IsRunningResult bool + IsRunningErr error + + SSHAddressCalled bool + SSHAddressState multistep.StateBag + SSHAddressResult string + SSHAddressErr error + + StartCalled bool + StartPath string + StartHeadless bool + StartErr error + + StopCalled bool + StopPath string + StopErr error + + SuppressMessagesCalled bool + SuppressMessagesPath string + SuppressMessagesErr error + + ToolsIsoPathCalled bool + ToolsIsoPathFlavor string + ToolsIsoPathResult string + + DhcpLeasesPathCalled bool + DhcpLeasesPathDevice string + DhcpLeasesPathResult string + + VerifyCalled bool + VerifyErr error +} + +func (d *DriverMock) CompactDisk(path string) error { + d.CompactDiskCalled = true + d.CompactDiskPath = path + return d.CompactDiskErr +} + +func (d *DriverMock) CreateDisk(output string, size string, typeId string) error { + d.CreateDiskCalled = true + d.CreateDiskOutput = output + d.CreateDiskSize = size + d.CreateDiskTypeId = typeId + return d.CreateDiskErr +} + +func (d *DriverMock) IsRunning(path string) (bool, error) { + d.IsRunningCalled = true + d.IsRunningPath = path + return d.IsRunningResult, d.IsRunningErr +} + +func (d *DriverMock) SSHAddress(state multistep.StateBag) (string, error) { + d.SSHAddressCalled = true + d.SSHAddressState = state + return d.SSHAddressResult, d.SSHAddressErr +} + +func (d *DriverMock) Start(path string, headless bool) error { + d.StartCalled = true + d.StartPath = path + d.StartHeadless = headless + return d.StartErr +} + +func (d *DriverMock) Stop(path string) error { + d.StopCalled = true + d.StopPath = path + return d.StopErr +} + +func (d *DriverMock) SuppressMessages(path string) error { + d.SuppressMessagesCalled = true + d.SuppressMessagesPath = path + return d.SuppressMessagesErr +} + +func (d *DriverMock) ToolsIsoPath(flavor string) string { + d.ToolsIsoPathCalled = true + d.ToolsIsoPathFlavor = flavor + return d.ToolsIsoPathResult +} + +func (d *DriverMock) DhcpLeasesPath(device string) string { + d.DhcpLeasesPathCalled = true + d.DhcpLeasesPathDevice = device + return d.DhcpLeasesPathResult +} + +func (d *DriverMock) Verify() error { + d.VerifyCalled = true + return d.VerifyErr +} diff --git a/builder/vmware/common/driver_mock_test.go b/builder/vmware/common/driver_mock_test.go new file mode 100644 index 000000000..5a056e149 --- /dev/null +++ b/builder/vmware/common/driver_mock_test.go @@ -0,0 +1,9 @@ +package common + +import ( + "testing" +) + +func TestDriverMock_impl(t *testing.T) { + var _ Driver = new(DriverMock) +} diff --git a/builder/vmware/common/guest_ip_test.go b/builder/vmware/common/guest_ip_test.go new file mode 100644 index 000000000..fdd6b4c9c --- /dev/null +++ b/builder/vmware/common/guest_ip_test.go @@ -0,0 +1,82 @@ +package common + +import ( + "io/ioutil" + "os" + "testing" +) + +func TestDHCPLeaseGuestLookup_impl(t *testing.T) { + var _ GuestIPFinder = new(DHCPLeaseGuestLookup) +} + +func TestDHCPLeaseGuestLookup(t *testing.T) { + tf, err := ioutil.TempFile("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + if _, err := tf.Write([]byte(testLeaseContents)); err != nil { + t.Fatalf("err: %s", err) + } + tf.Close() + defer os.Remove(tf.Name()) + + driver := new(DriverMock) + driver.DhcpLeasesPathResult = tf.Name() + + finder := &DHCPLeaseGuestLookup{ + Driver: driver, + Device: "vmnet8", + MACAddress: "00:0c:29:59:91:02", + } + + ip, err := finder.GuestIP() + if err != nil { + t.Fatalf("err: %s", err) + } + + if !driver.DhcpLeasesPathCalled { + t.Fatal("should ask for DHCP leases path") + } + if driver.DhcpLeasesPathDevice != "vmnet8" { + t.Fatal("should be vmnet8") + } + + if ip != "192.168.126.130" { + t.Fatalf("bad: %#v", ip) + } +} + +const testLeaseContents = ` +# All times in this file are in UTC (GMT), not your local timezone. This is +# not a bug, so please don't ask about it. There is no portable way to +# store leases in the local timezone, so please don't request this as a +# feature. If this is inconvenient or confusing to you, we sincerely +# apologize. Seriously, though - don't ask. +# The format of this file is documented in the dhcpd.leases(5) manual page. + +lease 192.168.126.129 { + starts 0 2013/09/15 23:58:51; + ends 1 2013/09/16 00:28:51; + hardware ethernet 00:0c:29:59:91:02; + client-hostname "precise64"; +} +lease 192.168.126.130 { + starts 2 2013/09/17 21:39:07; + ends 2 2013/09/17 22:09:07; + hardware ethernet 00:0c:29:59:91:02; + client-hostname "precise64"; +} +lease 192.168.126.128 { + starts 0 2013/09/15 20:09:59; + ends 0 2013/09/15 20:21:58; + hardware ethernet 00:0c:29:59:91:02; + client-hostname "precise64"; +} +lease 192.168.126.127 { + starts 0 2013/09/15 20:09:59; + ends 0 2013/09/15 20:21:58; + hardware ethernet 01:0c:29:59:91:02; + client-hostname "precise64"; + +` diff --git a/builder/vmware/iso/guest_ip.go b/builder/vmware/iso/guest_ip.go deleted file mode 100644 index 9d9713096..000000000 --- a/builder/vmware/iso/guest_ip.go +++ /dev/null @@ -1,91 +0,0 @@ -package iso - -import ( - "errors" - "io/ioutil" - "log" - "os" - "regexp" - "strings" - "time" - - vmwcommon "github.com/mitchellh/packer/builder/vmware/common" -) - -// Interface to help find the IP address of a running virtual machine. -type GuestIPFinder interface { - GuestIP() (string, error) -} - -// DHCPLeaseGuestLookup looks up the IP address of a guest using DHCP -// lease information from the VMware network devices. -type DHCPLeaseGuestLookup struct { - // Driver that is being used (to find leases path) - Driver vmwcommon.Driver - - // Device that the guest is connected to. - Device string - - // MAC address of the guest. - MACAddress string -} - -func (f *DHCPLeaseGuestLookup) GuestIP() (string, error) { - dhcpLeasesPath := f.Driver.DhcpLeasesPath(f.Device) - log.Printf("DHCP leases path: %s", dhcpLeasesPath) - if dhcpLeasesPath == "" { - return "", errors.New("no DHCP leases path found.") - } - - fh, err := os.Open(dhcpLeasesPath) - if err != nil { - return "", err - } - defer fh.Close() - - dhcpBytes, err := ioutil.ReadAll(fh) - if err != nil { - return "", err - } - - var lastIp string - var lastLeaseEnd time.Time - - var curIp string - var curLeaseEnd time.Time - - ipLineRe := regexp.MustCompile(`^lease (.+?) {$`) - endTimeLineRe := regexp.MustCompile(`^\s*ends \d (.+?);$`) - macLineRe := regexp.MustCompile(`^\s*hardware ethernet (.+?);$`) - - for _, line := range strings.Split(string(dhcpBytes), "\n") { - // Need to trim off CR character when running in windows - line = strings.TrimRight(line, "\r") - - matches := ipLineRe.FindStringSubmatch(line) - if matches != nil { - lastIp = matches[1] - continue - } - - matches = endTimeLineRe.FindStringSubmatch(line) - if matches != nil { - lastLeaseEnd, _ = time.Parse("2006/01/02 15:04:05", matches[1]) - continue - } - - // If the mac address matches and this lease ends farther in the - // future than the last match we might have, then choose it. - matches = macLineRe.FindStringSubmatch(line) - if matches != nil && matches[1] == f.MACAddress && curLeaseEnd.Before(lastLeaseEnd) { - curIp = lastIp - curLeaseEnd = lastLeaseEnd - } - } - - if curIp == "" { - return "", errors.New("IP not found for MAC in DHCP leases") - } - - return curIp, nil -} From 6cf8d9b3194b3c2a80dcf2585f772f83d430da64 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Dec 2013 14:26:44 -0700 Subject: [PATCH 14/45] builder/vmware/common: StepSuppressMessages --- .../{iso => common}/step_suppress_messages.go | 11 +++--- .../common/step_suppress_messages_test.go | 36 +++++++++++++++++++ builder/vmware/common/step_test.go | 1 + builder/vmware/iso/builder.go | 2 +- 4 files changed, 43 insertions(+), 7 deletions(-) rename builder/vmware/{iso => common}/step_suppress_messages.go (65%) create mode 100644 builder/vmware/common/step_suppress_messages_test.go diff --git a/builder/vmware/iso/step_suppress_messages.go b/builder/vmware/common/step_suppress_messages.go similarity index 65% rename from builder/vmware/iso/step_suppress_messages.go rename to builder/vmware/common/step_suppress_messages.go index e2044fdcd..a02b69336 100644 --- a/builder/vmware/iso/step_suppress_messages.go +++ b/builder/vmware/common/step_suppress_messages.go @@ -1,18 +1,17 @@ -package iso +package common import ( "fmt" "github.com/mitchellh/multistep" - vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/packer" "log" ) // This step suppresses any messages that VMware product might show. -type stepSuppressMessages struct{} +type StepSuppressMessages struct{} -func (s *stepSuppressMessages) Run(state multistep.StateBag) multistep.StepAction { - driver := state.Get("driver").(vmwcommon.Driver) +func (s *StepSuppressMessages) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) vmxPath := state.Get("vmx_path").(string) @@ -27,4 +26,4 @@ func (s *stepSuppressMessages) Run(state multistep.StateBag) multistep.StepActio return multistep.ActionContinue } -func (s *stepSuppressMessages) Cleanup(state multistep.StateBag) {} +func (s *StepSuppressMessages) Cleanup(state multistep.StateBag) {} diff --git a/builder/vmware/common/step_suppress_messages_test.go b/builder/vmware/common/step_suppress_messages_test.go new file mode 100644 index 000000000..998cfdb24 --- /dev/null +++ b/builder/vmware/common/step_suppress_messages_test.go @@ -0,0 +1,36 @@ +package common + +import ( + "testing" + + "github.com/mitchellh/multistep" +) + +func TestStepSuppressMessages_impl(t *testing.T) { + var _ multistep.Step = new(StepSuppressMessages) +} + +func TestStepSuppressMessages(t *testing.T) { + state := testState(t) + step := new(StepSuppressMessages) + + state.Put("vmx_path", "foo") + + driver := state.Get("driver").(*DriverMock) + + // 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 the driver + if !driver.SuppressMessagesCalled { + t.Fatal("should've called") + } + if driver.SuppressMessagesPath != "foo" { + t.Fatal("should call with right path") + } +} diff --git a/builder/vmware/common/step_test.go b/builder/vmware/common/step_test.go index 27c37495e..9bf6d5d67 100644 --- a/builder/vmware/common/step_test.go +++ b/builder/vmware/common/step_test.go @@ -9,6 +9,7 @@ import ( func testState(t *testing.T) multistep.StateBag { state := new(multistep.BasicStateBag) + state.Put("driver", new(DriverMock)) state.Put("ui", &packer.BasicUi{ Reader: new(bytes.Buffer), Writer: new(bytes.Buffer), diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index ea19bf24a..351a8de51 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -392,7 +392,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &vmwcommon.StepConfigureVMX{ CustomData: b.config.VMXData, }, - &stepSuppressMessages{}, + &vmwcommon.StepSuppressMessages{}, &stepHTTPServer{}, &stepConfigureVNC{}, &stepRun{}, From 7f86fa5fefbbeb5bf8140edd8ed9ef5aeec06ffa Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Dec 2013 14:39:52 -0700 Subject: [PATCH 15/45] builder/vmware/iso: Move remote registration out to separate step --- builder/vmware/iso/builder.go | 1 + builder/vmware/iso/remote_driver_mock.go | 40 ++++++++++++ builder/vmware/iso/remote_driver_mock_test.go | 12 ++++ builder/vmware/iso/step_register.go | 52 +++++++++++++++ builder/vmware/iso/step_register_test.go | 65 +++++++++++++++++++ builder/vmware/iso/step_run.go | 22 ------- builder/vmware/iso/step_test.go | 2 + 7 files changed, 172 insertions(+), 22 deletions(-) create mode 100644 builder/vmware/iso/remote_driver_mock.go create mode 100644 builder/vmware/iso/remote_driver_mock_test.go create mode 100644 builder/vmware/iso/step_register.go create mode 100644 builder/vmware/iso/step_register_test.go diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 351a8de51..361048c90 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -395,6 +395,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &vmwcommon.StepSuppressMessages{}, &stepHTTPServer{}, &stepConfigureVNC{}, + &StepRegister{}, &stepRun{}, &stepTypeBootCommand{}, &common.StepConnectSSH{ diff --git a/builder/vmware/iso/remote_driver_mock.go b/builder/vmware/iso/remote_driver_mock.go new file mode 100644 index 000000000..fe494fd86 --- /dev/null +++ b/builder/vmware/iso/remote_driver_mock.go @@ -0,0 +1,40 @@ +package iso + +import ( + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" +) + +type RemoteDriverMock struct { + vmwcommon.DriverMock + + UploadISOCalled bool + UploadISOPath string + UploadISOResult string + UploadISOErr error + + RegisterCalled bool + RegisterPath string + RegisterErr error + + UnregisterCalled bool + UnregisterPath string + UnregisterErr error +} + +func (d *RemoteDriverMock) UploadISO(path string) (string, error) { + d.UploadISOCalled = true + d.UploadISOPath = path + return d.UploadISOResult, d.UploadISOErr +} + +func (d *RemoteDriverMock) Register(path string) error { + d.RegisterCalled = true + d.RegisterPath = path + return d.RegisterErr +} + +func (d *RemoteDriverMock) Unregister(path string) error { + d.UnregisterCalled = true + d.UnregisterPath = path + return d.UnregisterErr +} diff --git a/builder/vmware/iso/remote_driver_mock_test.go b/builder/vmware/iso/remote_driver_mock_test.go new file mode 100644 index 000000000..86779a847 --- /dev/null +++ b/builder/vmware/iso/remote_driver_mock_test.go @@ -0,0 +1,12 @@ +package iso + +import ( + "testing" + + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" +) + +func TestRemoteDriverMock_impl(t *testing.T) { + var _ vmwcommon.Driver = new(RemoteDriverMock) + var _ RemoteDriver = new(RemoteDriverMock) +} diff --git a/builder/vmware/iso/step_register.go b/builder/vmware/iso/step_register.go new file mode 100644 index 000000000..6710cbd74 --- /dev/null +++ b/builder/vmware/iso/step_register.go @@ -0,0 +1,52 @@ +package iso + +import ( + "fmt" + + "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" + "github.com/mitchellh/packer/packer" +) + +type StepRegister struct { + registeredPath string +} + +func (s *StepRegister) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(vmwcommon.Driver) + ui := state.Get("ui").(packer.Ui) + vmxPath := state.Get("vmx_path").(string) + + if remoteDriver, ok := driver.(RemoteDriver); ok { + ui.Say("Registering remote VM...") + if err := remoteDriver.Register(vmxPath); err != nil { + err := fmt.Errorf("Error registering VM: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + s.registeredPath = vmxPath + } + + return multistep.ActionContinue +} + +func (s *StepRegister) Cleanup(state multistep.StateBag) { + if s.registeredPath == "" { + return + } + + driver := state.Get("driver").(vmwcommon.Driver) + ui := state.Get("ui").(packer.Ui) + + if remoteDriver, ok := driver.(RemoteDriver); ok { + ui.Say("Unregistering virtual machine...") + if err := remoteDriver.Unregister(s.registeredPath); err != nil { + ui.Error(fmt.Sprintf("Error unregistering VM: %s", err)) + } + + s.registeredPath = "" + } + +} diff --git a/builder/vmware/iso/step_register_test.go b/builder/vmware/iso/step_register_test.go new file mode 100644 index 000000000..c654f836d --- /dev/null +++ b/builder/vmware/iso/step_register_test.go @@ -0,0 +1,65 @@ +package iso + +import ( + "github.com/mitchellh/multistep" + "testing" +) + +func TestStepRegister_impl(t *testing.T) { + var _ multistep.Step = new(StepRegister) +} + +func TestStepRegister_regularDriver(t *testing.T) { + state := testState(t) + step := new(StepRegister) + + state.Put("vmx_path", "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") + } + + // Cleanup + step.Cleanup(state) +} + +func TestStepRegister_remoteDriver(t *testing.T) { + state := testState(t) + step := new(StepRegister) + + driver := new(RemoteDriverMock) + state.Put("driver", driver) + state.Put("vmx_path", "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") + } + + // verify + if !driver.RegisterCalled { + t.Fatal("register should be called") + } + if driver.RegisterPath != "foo" { + t.Fatal("should call with correct path") + } + if driver.UnregisterCalled { + t.Fatal("unregister should not be called") + } + + // cleanup + step.Cleanup(state) + if !driver.UnregisterCalled { + t.Fatal("unregister should be called") + } + if driver.UnregisterPath != "foo" { + t.Fatal("should unregister proper path") + } +} diff --git a/builder/vmware/iso/step_run.go b/builder/vmware/iso/step_run.go index 835ff06ab..6cbd6a171 100644 --- a/builder/vmware/iso/step_run.go +++ b/builder/vmware/iso/step_run.go @@ -21,8 +21,6 @@ import ( type stepRun struct { bootTime time.Time vmxPath string - - registered bool } func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction { @@ -45,17 +43,6 @@ func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction { "%s:%d", vncIp, vncPort)) } - if remoteDriver, ok := driver.(RemoteDriver); ok { - if err := remoteDriver.Register(vmxPath); err != nil { - err := fmt.Errorf("Error registering VM: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - s.registered = true - } - if err := driver.Start(vmxPath, config.Headless); err != nil { err := fmt.Errorf("Error starting VM: %s", err) state.Put("error", err) @@ -107,14 +94,5 @@ func (s *stepRun) Cleanup(state multistep.StateBag) { ui.Error(fmt.Sprintf("Error stopping VM: %s", err)) } } - - if remoteDriver, ok := driver.(RemoteDriver); ok && s.registered { - ui.Say("Unregistering virtual machine...") - if err := remoteDriver.Unregister(s.vmxPath); err != nil { - ui.Error(fmt.Sprintf("Error unregistering VM: %s", err)) - } - - s.registered = false - } } } diff --git a/builder/vmware/iso/step_test.go b/builder/vmware/iso/step_test.go index 04490cb29..aad099af5 100644 --- a/builder/vmware/iso/step_test.go +++ b/builder/vmware/iso/step_test.go @@ -3,12 +3,14 @@ package iso import ( "bytes" "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/packer" "testing" ) 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), From 95e0e465cfafbc21778fc76b610aaace12b11c13 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Dec 2013 18:12:43 -0700 Subject: [PATCH 16/45] builder/vmware: move StepRun to common --- builder/vmware/{iso => common}/step_run.go | 56 +++++++++------ builder/vmware/common/step_run_test.go | 82 ++++++++++++++++++++++ builder/vmware/iso/builder.go | 6 +- 3 files changed, 121 insertions(+), 23 deletions(-) rename builder/vmware/{iso => common}/step_run.go (54%) create mode 100644 builder/vmware/common/step_run_test.go diff --git a/builder/vmware/iso/step_run.go b/builder/vmware/common/step_run.go similarity index 54% rename from builder/vmware/iso/step_run.go rename to builder/vmware/common/step_run.go index 6cbd6a171..4e996d833 100644 --- a/builder/vmware/iso/step_run.go +++ b/builder/vmware/common/step_run.go @@ -1,9 +1,8 @@ -package iso +package common import ( "fmt" "github.com/mitchellh/multistep" - vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/packer" "time" ) @@ -11,39 +10,51 @@ import ( // This step runs the created virtual machine. // // Uses: -// config *config // driver Driver // ui packer.Ui // vmx_path string // // Produces: // -type stepRun struct { +type StepRun struct { + BootWait time.Duration + DurationBeforeStop time.Duration + Headless bool + bootTime time.Time vmxPath string } -func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction { - config := state.Get("config").(*config) - driver := state.Get("driver").(vmwcommon.Driver) +func (s *StepRun) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) vmxPath := state.Get("vmx_path").(string) - vncIp := state.Get("vnc_ip").(string) - vncPort := state.Get("vnc_port").(uint) // Set the VMX path so that we know we started the machine s.bootTime = time.Now() s.vmxPath = vmxPath ui.Say("Starting virtual machine...") - if config.Headless { - ui.Message(fmt.Sprintf( - "The VM will be run headless, without a GUI. If you want to\n"+ - "view the screen of the VM, connect via VNC without a password to\n"+ - "%s:%d", vncIp, vncPort)) + if s.Headless { + vncIpRaw, vncIpOk := state.GetOk("vnc_ip") + vncPortRaw, vncPortOk := state.GetOk("vnc_port") + + if vncIpOk && vncPortOk { + vncIp := vncIpRaw.(string) + vncPort := vncPortRaw.(uint) + + ui.Message(fmt.Sprintf( + "The VM will be run headless, without a GUI. If you want to\n"+ + "view the screen of the VM, connect via VNC without a password to\n"+ + "%s:%d", vncIp, vncPort)) + } else { + ui.Message("The VM will be run headless, without a GUI, as configured.\n" + + "If the run isn't succeeding as you expect, please enable the GUI\n" + + "to inspect the progress of the build.") + } } - if err := driver.Start(vmxPath, config.Headless); err != nil { + if err := driver.Start(vmxPath, s.Headless); err != nil { err := fmt.Errorf("Error starting VM: %s", err) state.Put("error", err) ui.Error(err.Error()) @@ -51,9 +62,9 @@ func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction { } // Wait the wait amount - if int64(config.bootWait) > 0 { - ui.Say(fmt.Sprintf("Waiting %s for boot...", config.bootWait.String())) - wait := time.After(config.bootWait) + if int64(s.BootWait) > 0 { + ui.Say(fmt.Sprintf("Waiting %s for boot...", s.BootWait.String())) + wait := time.After(s.BootWait) WAITLOOP: for { select { @@ -71,18 +82,19 @@ func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionContinue } -func (s *stepRun) Cleanup(state multistep.StateBag) { - driver := state.Get("driver").(vmwcommon.Driver) +func (s *StepRun) Cleanup(state multistep.StateBag) { + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) // If we started the machine... stop it. if s.vmxPath != "" { // If we started it less than 5 seconds ago... wait. sinceBootTime := time.Since(s.bootTime) - waitBootTime := 5 * time.Second + waitBootTime := s.DurationBeforeStop if sinceBootTime < waitBootTime { sleepTime := waitBootTime - sinceBootTime - ui.Say(fmt.Sprintf("Waiting %s to give VMware time to clean up...", sleepTime.String())) + ui.Say(fmt.Sprintf( + "Waiting %s to give VMware time to clean up...", sleepTime.String())) time.Sleep(sleepTime) } diff --git a/builder/vmware/common/step_run_test.go b/builder/vmware/common/step_run_test.go new file mode 100644 index 000000000..9888cdf10 --- /dev/null +++ b/builder/vmware/common/step_run_test.go @@ -0,0 +1,82 @@ +package common + +import ( + "testing" + + "github.com/mitchellh/multistep" +) + +func TestStepRun_impl(t *testing.T) { + var _ multistep.Step = new(StepRun) +} + +func TestStepRun(t *testing.T) { + state := testState(t) + step := new(StepRun) + + state.Put("vmx_path", "foo") + + driver := state.Get("driver").(*DriverMock) + + // 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 the driver + if !driver.StartCalled { + t.Fatal("start should be called") + } + if driver.StartPath != "foo" { + t.Fatalf("bad: %#v", driver.StartPath) + } + if driver.StartHeadless { + t.Fatal("bad") + } + + // Test cleanup + step.Cleanup(state) + if driver.StopCalled { + t.Fatal("stop should not be called if not running") + } +} + +func TestStepRun_cleanupRunning(t *testing.T) { + state := testState(t) + step := new(StepRun) + + state.Put("vmx_path", "foo") + + driver := state.Get("driver").(*DriverMock) + + // 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 the driver + if !driver.StartCalled { + t.Fatal("start should be called") + } + if driver.StartPath != "foo" { + t.Fatalf("bad: %#v", driver.StartPath) + } + if driver.StartHeadless { + t.Fatal("bad") + } + + // Mark that it is running + driver.IsRunningResult = true + + // Test cleanup + step.Cleanup(state) + if !driver.StopCalled { + t.Fatal("stop should be called") + } +} diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 361048c90..da9d71dec 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -396,7 +396,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &stepHTTPServer{}, &stepConfigureVNC{}, &StepRegister{}, - &stepRun{}, + &vmwcommon.StepRun{ + BootWait: b.config.bootWait, + DurationBeforeStop: 5 * time.Second, + Headless: b.config.Headless, + }, &stepTypeBootCommand{}, &common.StepConnectSSH{ SSHAddress: driver.SSHAddress, From 8f8ea60b4fd569f90d67a4655d4f2498323b703c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Dec 2013 18:17:58 -0700 Subject: [PATCH 17/45] builder/vmware/common: StepCleanFiles --- builder/vmware/{iso => common}/step_clean_files.go | 12 +++++++----- builder/vmware/iso/builder.go | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) rename builder/vmware/{iso => common}/step_clean_files.go (79%) diff --git a/builder/vmware/iso/step_clean_files.go b/builder/vmware/common/step_clean_files.go similarity index 79% rename from builder/vmware/iso/step_clean_files.go rename to builder/vmware/common/step_clean_files.go index f6c3f0fa4..de3debccc 100644 --- a/builder/vmware/iso/step_clean_files.go +++ b/builder/vmware/common/step_clean_files.go @@ -1,4 +1,4 @@ -package iso +package common import ( "fmt" @@ -21,9 +21,9 @@ var KeepFileExtensions = []string{".nvram", ".vmdk", ".vmsd", ".vmx", ".vmxf"} // // Produces: // -type stepCleanFiles struct{} +type StepCleanFiles struct{} -func (stepCleanFiles) Run(state multistep.StateBag) multistep.StepAction { +func (StepCleanFiles) Run(state multistep.StateBag) multistep.StepAction { dir := state.Get("dir").(OutputDir) ui := state.Get("ui").(packer.Ui) @@ -49,7 +49,9 @@ func (stepCleanFiles) Run(state multistep.StateBag) multistep.StepAction { if !keep { ui.Message(fmt.Sprintf("Deleting: %s", path)) if err = dir.Remove(path); err != nil { - // Only report the error if the file still exists + // Only report the error if the file still exists. We do this + // because sometimes the files naturally get removed on their + // own as VMware does its own cleanup. if _, serr := os.Stat(path); serr == nil || !os.IsNotExist(serr) { state.Put("error", err) return multistep.ActionHalt @@ -61,4 +63,4 @@ func (stepCleanFiles) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionContinue } -func (stepCleanFiles) Cleanup(multistep.StateBag) {} +func (StepCleanFiles) Cleanup(multistep.StateBag) {} diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index da9d71dec..a829f1a93 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -411,7 +411,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &stepUploadTools{}, &common.StepProvision{}, &stepShutdown{}, - &stepCleanFiles{}, + &vmwcommon.StepCleanFiles{}, &stepCleanVMX{}, &stepCompactDisk{}, } From f01b21c610fdef7e3625c5a9b2b56e7f224d625b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Dec 2013 18:40:52 -0700 Subject: [PATCH 18/45] builder/vmware: StepCleanVMX --- builder/vmware/common/step_clean_vmx.go | 78 ++++++++++ builder/vmware/common/step_clean_vmx_test.go | 142 +++++++++++++++++++ builder/vmware/common/vmx.go | 11 ++ builder/vmware/iso/builder.go | 2 +- builder/vmware/iso/step_clean_vmx.go | 93 ------------ 5 files changed, 232 insertions(+), 94 deletions(-) create mode 100644 builder/vmware/common/step_clean_vmx.go create mode 100644 builder/vmware/common/step_clean_vmx_test.go delete mode 100644 builder/vmware/iso/step_clean_vmx.go diff --git a/builder/vmware/common/step_clean_vmx.go b/builder/vmware/common/step_clean_vmx.go new file mode 100644 index 000000000..b55bcd549 --- /dev/null +++ b/builder/vmware/common/step_clean_vmx.go @@ -0,0 +1,78 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" + "regexp" + "strings" +) + +// This step cleans up the VMX by removing or changing this prior to +// being ready for use. +// +// Uses: +// ui packer.Ui +// vmx_path string +// +// Produces: +// +type StepCleanVMX struct{} + +func (s StepCleanVMX) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + vmxPath := state.Get("vmx_path").(string) + + ui.Say("Cleaning VMX prior to finishing up...") + + vmxData, err := ReadVMX(vmxPath) + if err != nil { + state.Put("error", fmt.Errorf("Error reading VMX: %s", err)) + return multistep.ActionHalt + } + + if _, ok := state.GetOk("floppy_path"); ok { + // Delete the floppy0 entries so the floppy is no longer mounted + ui.Message("Unmounting floppy from VMX...") + for k, _ := range vmxData { + if strings.HasPrefix(k, "floppy0.") { + log.Printf("Deleting key: %s", k) + delete(vmxData, k) + } + } + vmxData["floppy0.present"] = "FALSE" + } + + if isoPathRaw, ok := state.GetOk("iso_path"); ok { + isoPath := isoPathRaw.(string) + + ui.Message("Detaching ISO from CD-ROM device...") + devRe := regexp.MustCompile(`^ide\d:\d\.`) + for k, _ := range vmxData { + match := devRe.FindString(k) + if match == "" { + continue + } + + filenameKey := match + "filename" + if filename, ok := vmxData[filenameKey]; ok { + if filename == isoPath { + // Change the CD-ROM device back to auto-detect to eject + vmxData[filenameKey] = "auto detect" + vmxData[match+"devicetype"] = "cdrom-raw" + } + } + } + } + + // Rewrite the VMX + if err := WriteVMX(vmxPath, vmxData); err != nil { + state.Put("error", fmt.Errorf("Error writing VMX: %s", err)) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (StepCleanVMX) Cleanup(multistep.StateBag) {} diff --git a/builder/vmware/common/step_clean_vmx_test.go b/builder/vmware/common/step_clean_vmx_test.go new file mode 100644 index 000000000..59877027a --- /dev/null +++ b/builder/vmware/common/step_clean_vmx_test.go @@ -0,0 +1,142 @@ +package common + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/mitchellh/multistep" +) + +func TestStepCleanVMX_impl(t *testing.T) { + var _ multistep.Step = new(StepCleanVMX) +} + +func TestStepCleanVMX(t *testing.T) { + state := testState(t) + step := new(StepCleanVMX) + + vmxPath := testVMXFile(t) + defer os.Remove(vmxPath) + state.Put("vmx_path", vmxPath) + + // 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") + } +} + +func TestStepCleanVMX_floppyPath(t *testing.T) { + state := testState(t) + step := new(StepCleanVMX) + + vmxPath := testVMXFile(t) + defer os.Remove(vmxPath) + if err := ioutil.WriteFile(vmxPath, []byte(testVMXFloppyPath), 0644); err != nil { + t.Fatalf("err: %s", err) + } + + state.Put("floppy_path", "foo") + state.Put("vmx_path", vmxPath) + + // 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 the resulting data + vmxContents, err := ioutil.ReadFile(vmxPath) + if err != nil { + t.Fatalf("err: %s", err) + } + vmxData := ParseVMX(string(vmxContents)) + + cases := []struct { + Key string + Value string + }{ + {"floppy0.present", "FALSE"}, + {"floppy0.filetype", ""}, + {"floppy0.filename", ""}, + } + + for _, tc := range cases { + if tc.Value == "" { + if _, ok := vmxData[tc.Key]; ok { + t.Fatalf("should not have key: %s", tc.Key) + } + } else { + if vmxData[tc.Key] != tc.Value { + t.Fatalf("bad: %s %#v", tc.Key, vmxData[tc.Key]) + } + } + } +} + +func TestStepCleanVMX_isoPath(t *testing.T) { + state := testState(t) + step := new(StepCleanVMX) + + vmxPath := testVMXFile(t) + defer os.Remove(vmxPath) + if err := ioutil.WriteFile(vmxPath, []byte(testVMXISOPath), 0644); err != nil { + t.Fatalf("err: %s", err) + } + + state.Put("iso_path", "foo") + state.Put("vmx_path", vmxPath) + + // 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 the resulting data + vmxContents, err := ioutil.ReadFile(vmxPath) + if err != nil { + t.Fatalf("err: %s", err) + } + vmxData := ParseVMX(string(vmxContents)) + + cases := []struct { + Key string + Value string + }{ + {"ide0:0.filename", "auto detect"}, + {"ide0:0.devicetype", "cdrom-raw"}, + {"ide0:1.filename", "bar"}, + {"foo", "bar"}, + } + + for _, tc := range cases { + if tc.Value == "" { + if _, ok := vmxData[tc.Key]; ok { + t.Fatalf("should not have key: %s", tc.Key) + } + } else { + if vmxData[tc.Key] != tc.Value { + t.Fatalf("bad: %s %#v", tc.Key, vmxData[tc.Key]) + } + } + } +} + +const testVMXFloppyPath = ` +floppy0.present = "TRUE" +floppy0.filetype = "file" +` + +const testVMXISOPath = ` +ide0:0.filename = "foo" +ide0:1.filename = "bar" +foo = "bar" +` diff --git a/builder/vmware/common/vmx.go b/builder/vmware/common/vmx.go index 455da4cec..e7cdb662f 100644 --- a/builder/vmware/common/vmx.go +++ b/builder/vmware/common/vmx.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io" + "io/ioutil" "log" "os" "regexp" @@ -68,3 +69,13 @@ func WriteVMX(path string, data map[string]string) (err error) { return } + +// ReadVMX takes a path to a VMX file and reads it into a k/v mapping. +func ReadVMX(path string) (map[string]string, error) { + data, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + return ParseVMX(string(data)), nil +} diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index a829f1a93..e65185afd 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -412,7 +412,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &common.StepProvision{}, &stepShutdown{}, &vmwcommon.StepCleanFiles{}, - &stepCleanVMX{}, + &vmwcommon.StepCleanVMX{}, &stepCompactDisk{}, } diff --git a/builder/vmware/iso/step_clean_vmx.go b/builder/vmware/iso/step_clean_vmx.go deleted file mode 100644 index 119173a5b..000000000 --- a/builder/vmware/iso/step_clean_vmx.go +++ /dev/null @@ -1,93 +0,0 @@ -package iso - -import ( - "fmt" - "github.com/mitchellh/multistep" - vmwcommon "github.com/mitchellh/packer/builder/vmware/common" - "github.com/mitchellh/packer/packer" - "io/ioutil" - "log" - "os" - "regexp" - "strings" -) - -// This step cleans up the VMX by removing or changing this prior to -// being ready for use. -// -// Uses: -// ui packer.Ui -// vmx_path string -// -// Produces: -// -type stepCleanVMX struct{} - -func (s stepCleanVMX) Run(state multistep.StateBag) multistep.StepAction { - isoPath := state.Get("iso_path").(string) - ui := state.Get("ui").(packer.Ui) - vmxPath := state.Get("vmx_path").(string) - - ui.Say("Cleaning VMX prior to finishing up...") - - vmxData, err := s.readVMX(vmxPath) - if err != nil { - state.Put("error", fmt.Errorf("Error reading VMX: %s", err)) - return multistep.ActionHalt - } - - if _, ok := state.GetOk("floppy_path"); ok { - // Delete the floppy0 entries so the floppy is no longer mounted - ui.Message("Unmounting floppy from VMX...") - for k, _ := range vmxData { - if strings.HasPrefix(k, "floppy0.") { - log.Printf("Deleting key: %s", k) - delete(vmxData, k) - } - } - vmxData["floppy0.present"] = "FALSE" - } - - ui.Message("Detaching ISO from CD-ROM device...") - devRe := regexp.MustCompile(`^ide\d:\d\.`) - for k, _ := range vmxData { - match := devRe.FindString(k) - if match == "" { - continue - } - - filenameKey := match + "filename" - if filename, ok := vmxData[filenameKey]; ok { - if filename == isoPath { - // Change the CD-ROM device back to auto-detect to eject - vmxData[filenameKey] = "auto detect" - vmxData[match+"devicetype"] = "cdrom-raw" - } - } - } - - // Rewrite the VMX - if err := vmwcommon.WriteVMX(vmxPath, vmxData); err != nil { - state.Put("error", fmt.Errorf("Error writing VMX: %s", err)) - return multistep.ActionHalt - } - - return multistep.ActionContinue -} - -func (stepCleanVMX) Cleanup(multistep.StateBag) {} - -func (stepCleanVMX) readVMX(vmxPath string) (map[string]string, error) { - vmxF, err := os.Open(vmxPath) - if err != nil { - return nil, err - } - defer vmxF.Close() - - vmxBytes, err := ioutil.ReadAll(vmxF) - if err != nil { - return nil, err - } - - return vmwcommon.ParseVMX(string(vmxBytes)), nil -} From 87ab914a3cf2dec6e679e6347099ace076bb9b94 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Dec 2013 23:09:22 -0700 Subject: [PATCH 19/45] builder/vmware: StepCompactDisk --- .../{iso => common}/step_compact_disk.go | 17 +++--- .../vmware/common/step_compact_disk_test.go | 59 +++++++++++++++++++ builder/vmware/iso/builder.go | 4 +- 3 files changed, 70 insertions(+), 10 deletions(-) rename builder/vmware/{iso => common}/step_compact_disk.go (65%) create mode 100644 builder/vmware/common/step_compact_disk_test.go diff --git a/builder/vmware/iso/step_compact_disk.go b/builder/vmware/common/step_compact_disk.go similarity index 65% rename from builder/vmware/iso/step_compact_disk.go rename to builder/vmware/common/step_compact_disk.go index 8b4cf6593..777e8aaea 100644 --- a/builder/vmware/iso/step_compact_disk.go +++ b/builder/vmware/common/step_compact_disk.go @@ -1,9 +1,8 @@ -package iso +package common import ( "fmt" "github.com/mitchellh/multistep" - vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/packer" "log" ) @@ -12,22 +11,22 @@ import ( // boolean is true. // // Uses: -// config *config // driver Driver // full_disk_path string // ui packer.Ui // // Produces: // -type stepCompactDisk struct{} +type StepCompactDisk struct { + Skip bool +} -func (stepCompactDisk) Run(state multistep.StateBag) multistep.StepAction { - config := state.Get("config").(*config) - driver := state.Get("driver").(vmwcommon.Driver) +func (s StepCompactDisk) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) full_disk_path := state.Get("full_disk_path").(string) - if config.SkipCompaction == true { + if s.Skip { log.Println("Skipping disk compaction step...") return multistep.ActionContinue } @@ -41,4 +40,4 @@ func (stepCompactDisk) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionContinue } -func (stepCompactDisk) Cleanup(multistep.StateBag) {} +func (StepCompactDisk) Cleanup(multistep.StateBag) {} diff --git a/builder/vmware/common/step_compact_disk_test.go b/builder/vmware/common/step_compact_disk_test.go new file mode 100644 index 000000000..e59a99a90 --- /dev/null +++ b/builder/vmware/common/step_compact_disk_test.go @@ -0,0 +1,59 @@ +package common + +import ( + "testing" + + "github.com/mitchellh/multistep" +) + +func TestStepCompactDisk_impl(t *testing.T) { + var _ multistep.Step = new(StepCompactDisk) +} + +func TestStepCompactDisk(t *testing.T) { + state := testState(t) + step := new(StepCompactDisk) + + state.Put("full_disk_path", "foo") + + driver := state.Get("driver").(*DriverMock) + + // 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 the driver + if !driver.CompactDiskCalled { + t.Fatal("should've called") + } + if driver.CompactDiskPath != "foo" { + t.Fatal("should call with right path") + } +} + +func TestStepCompactDisk_skip(t *testing.T) { + state := testState(t) + step := new(StepCompactDisk) + step.Skip = true + + state.Put("full_disk_path", "foo") + + driver := state.Get("driver").(*DriverMock) + + // 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 the driver + if driver.CompactDiskCalled { + t.Fatal("should not have called") + } +} diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index e65185afd..9bbbf851a 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -413,7 +413,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &stepShutdown{}, &vmwcommon.StepCleanFiles{}, &vmwcommon.StepCleanVMX{}, - &stepCompactDisk{}, + &vmwcommon.StepCompactDisk{ + Skip: b.config.SkipCompaction, + }, } // Run! From 6c4af2d75fbdd4e35baade7677f6a5bda057d615 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Dec 2013 23:16:13 -0700 Subject: [PATCH 20/45] builder/vmware/iso: convert stepShutdown to use OutputDir for cleanup --- builder/vmware/iso/step_shutdown.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/builder/vmware/iso/step_shutdown.go b/builder/vmware/iso/step_shutdown.go index fea2f129a..14810113c 100644 --- a/builder/vmware/iso/step_shutdown.go +++ b/builder/vmware/iso/step_shutdown.go @@ -8,7 +8,7 @@ import ( vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/packer" "log" - "path/filepath" + "regexp" "runtime" "strings" "time" @@ -31,6 +31,7 @@ type stepShutdown struct{} func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { comm := state.Get("communicator").(packer.Communicator) config := state.Get("config").(*config) + dir := state.Get("dir").(vmwcommon.OutputDir) driver := state.Get("driver").(vmwcommon.Driver) ui := state.Get("ui").(packer.Ui) vmxPath := state.Get("vmx_path").(string) @@ -95,12 +96,19 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { } ui.Message("Waiting for VMware to clean up after itself...") - lockPattern := filepath.Join(config.OutputDir, "*.lck") + lockRegex := regexp.MustCompile(`(?i)\.lck$`) timer := time.After(15 * time.Second) LockWaitLoop: for { - locks, err := filepath.Glob(lockPattern) + files, err := dir.ListFiles() if err == nil { + var locks []string + for _, file := range files { + if lockRegex.MatchString(file) { + locks = append(locks, file) + } + } + if len(locks) == 0 { log.Println("No more lock files found. VMware is clean.") break From 4f32692fd5abc61e64776e44677672d3e242a6bf Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Dec 2013 23:33:49 -0700 Subject: [PATCH 21/45] builder/vmware: StepShutdown --- builder/vmware/common/driver_mock.go | 7 + .../vmware/{iso => common}/step_shutdown.go | 37 ++-- builder/vmware/common/step_shutdown_test.go | 174 ++++++++++++++++++ builder/vmware/iso/builder.go | 5 +- 4 files changed, 205 insertions(+), 18 deletions(-) rename builder/vmware/{iso => common}/step_shutdown.go (80%) create mode 100644 builder/vmware/common/step_shutdown_test.go diff --git a/builder/vmware/common/driver_mock.go b/builder/vmware/common/driver_mock.go index 8e73990fa..edae482c5 100644 --- a/builder/vmware/common/driver_mock.go +++ b/builder/vmware/common/driver_mock.go @@ -1,10 +1,14 @@ package common import ( + "sync" + "github.com/mitchellh/multistep" ) type DriverMock struct { + sync.Mutex + CompactDiskCalled bool CompactDiskPath string CompactDiskErr error @@ -65,6 +69,9 @@ func (d *DriverMock) CreateDisk(output string, size string, typeId string) error } func (d *DriverMock) IsRunning(path string) (bool, error) { + d.Lock() + defer d.Unlock() + d.IsRunningCalled = true d.IsRunningPath = path return d.IsRunningResult, d.IsRunningErr diff --git a/builder/vmware/iso/step_shutdown.go b/builder/vmware/common/step_shutdown.go similarity index 80% rename from builder/vmware/iso/step_shutdown.go rename to builder/vmware/common/step_shutdown.go index 14810113c..05ae26dd1 100644 --- a/builder/vmware/iso/step_shutdown.go +++ b/builder/vmware/common/step_shutdown.go @@ -1,11 +1,10 @@ -package iso +package common import ( "bytes" "errors" "fmt" "github.com/mitchellh/multistep" - vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/packer" "log" "regexp" @@ -19,30 +18,32 @@ import ( // // Uses: // communicator packer.Communicator -// config *config +// dir OutputDir // driver Driver // ui packer.Ui // vmx_path string // // Produces: // -type stepShutdown struct{} +type StepShutdown struct { + Command string + Timeout time.Duration +} -func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { +func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction { comm := state.Get("communicator").(packer.Communicator) - config := state.Get("config").(*config) - dir := state.Get("dir").(vmwcommon.OutputDir) - driver := state.Get("driver").(vmwcommon.Driver) + dir := state.Get("dir").(OutputDir) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) vmxPath := state.Get("vmx_path").(string) - if config.ShutdownCommand != "" { + if s.Command != "" { ui.Say("Gracefully halting virtual machine...") - log.Printf("Executing shutdown command: %s", config.ShutdownCommand) + log.Printf("Executing shutdown command: %s", s.Command) var stdout, stderr bytes.Buffer cmd := &packer.RemoteCmd{ - Command: config.ShutdownCommand, + Command: s.Command, Stdout: &stdout, Stderr: &stderr, } @@ -68,8 +69,8 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { log.Printf("Shutdown stderr: %s", stderr.String()) // Wait for the machine to actually shut down - log.Printf("Waiting max %s for shutdown to complete", config.shutdownTimeout) - shutdownTimer := time.After(config.shutdownTimeout) + log.Printf("Waiting max %s for shutdown to complete", s.Timeout) + shutdownTimer := time.After(s.Timeout) for { running, _ := driver.IsRunning(vmxPath) if !running { @@ -83,7 +84,7 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { ui.Error(err.Error()) return multistep.ActionHalt default: - time.Sleep(1 * time.Second) + time.Sleep(150 * time.Millisecond) } } } else { @@ -101,7 +102,9 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { LockWaitLoop: for { files, err := dir.ListFiles() - if err == nil { + if err != nil { + log.Printf("Error listing files in outputdir: %s", err) + } else { var locks []string for _, file := range files { if lockRegex.MatchString(file) { @@ -126,7 +129,7 @@ LockWaitLoop: case <-timer: log.Println("Reached timeout on waiting for clean VMware. Assuming clean.") break LockWaitLoop - case <-time.After(1 * time.Second): + case <-time.After(150 * time.Millisecond): } } @@ -141,4 +144,4 @@ LockWaitLoop: return multistep.ActionContinue } -func (s *stepShutdown) Cleanup(state multistep.StateBag) {} +func (s *StepShutdown) Cleanup(state multistep.StateBag) {} diff --git a/builder/vmware/common/step_shutdown_test.go b/builder/vmware/common/step_shutdown_test.go new file mode 100644 index 000000000..531bfc34b --- /dev/null +++ b/builder/vmware/common/step_shutdown_test.go @@ -0,0 +1,174 @@ +package common + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + "time" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +func testStepShutdownState(t *testing.T) multistep.StateBag { + dir := testOutputDir(t) + if err := dir.MkdirAll(); err != nil { + t.Fatalf("err: %s", err) + } + + state := testState(t) + state.Put("communicator", new(packer.MockCommunicator)) + state.Put("dir", dir) + state.Put("vmx_path", "foo") + return state +} + +func TestStepShutdown_impl(t *testing.T) { + var _ multistep.Step = new(StepShutdown) +} + +func TestStepShutdown_command(t *testing.T) { + state := testStepShutdownState(t) + step := new(StepShutdown) + step.Command = "foo" + step.Timeout = 10 * time.Second + + comm := state.Get("communicator").(*packer.MockCommunicator) + driver := state.Get("driver").(*DriverMock) + driver.IsRunningResult = true + + // Set not running after some time + go func() { + time.Sleep(100 * time.Millisecond) + driver.Lock() + defer driver.Unlock() + driver.IsRunningResult = false + }() + + resultCh := make(chan multistep.StepAction, 1) + go func() { + resultCh <- step.Run(state) + }() + + select { + case <-resultCh: + t.Fatal("should not have returned so quickly") + case <-time.After(50 * time.Millisecond): + } + + var action multistep.StepAction + select { + case action = <-resultCh: + case <-time.After(300 * time.Millisecond): + t.Fatal("should've returned by now") + } + + // Test the run + if action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test the driver + if driver.StopCalled { + t.Fatal("stop should not be called") + } + + if !comm.StartCalled { + t.Fatal("start should be called") + } + if comm.StartCmd.Command != "foo" { + t.Fatalf("bad: %#v", comm.StartCmd.Command) + } +} + +func TestStepShutdown_noCommand(t *testing.T) { + state := testStepShutdownState(t) + step := new(StepShutdown) + + comm := state.Get("communicator").(*packer.MockCommunicator) + driver := state.Get("driver").(*DriverMock) + + // 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 the driver + if !driver.StopCalled { + t.Fatal("stop should be called") + } + if driver.StopPath != "foo" { + t.Fatal("should call with right path") + } + + if comm.StartCalled { + t.Fatal("start should not be called") + } +} + +func TestStepShutdown_locks(t *testing.T) { + state := testStepShutdownState(t) + step := new(StepShutdown) + + dir := state.Get("dir").(*LocalOutputDir) + comm := state.Get("communicator").(*packer.MockCommunicator) + driver := state.Get("driver").(*DriverMock) + + // Create some lock files + lockPath := filepath.Join(dir.dir, "nope.lck") + err := ioutil.WriteFile(lockPath, []byte("foo"), 0644) + if err != nil { + t.Fatalf("err: %s") + } + + // Remove the lock file after a certain time + go func() { + time.Sleep(100 * time.Millisecond) + os.Remove(lockPath) + }() + + resultCh := make(chan multistep.StepAction, 1) + go func() { + resultCh <- step.Run(state) + }() + + select { + case <-resultCh: + t.Fatal("should not have returned so quickly") + case <-time.After(50 * time.Millisecond): + } + + var action multistep.StepAction + select { + case action = <-resultCh: + case <-time.After(300 * time.Millisecond): + t.Fatal("should've returned by now") + } + + // Test the run + if action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test the driver + if !driver.StopCalled { + t.Fatal("stop should be called") + } + if driver.StopPath != "foo" { + t.Fatal("should call with right path") + } + + if comm.StartCalled { + t.Fatal("start should not be called") + } +} diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 9bbbf851a..30209eb34 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -410,7 +410,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe }, &stepUploadTools{}, &common.StepProvision{}, - &stepShutdown{}, + &vmwcommon.StepShutdown{ + Command: b.config.ShutdownCommand, + Timeout: b.config.shutdownTimeout, + }, &vmwcommon.StepCleanFiles{}, &vmwcommon.StepCleanVMX{}, &vmwcommon.StepCompactDisk{ From 8d963501d7ca25884fa8a5ebe0bf4533ee8c03d3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 25 Dec 2013 10:44:10 -0700 Subject: [PATCH 22/45] builder/vmware/iso: set SSHConfig --- builder/vmware/iso/driver.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/builder/vmware/iso/driver.go b/builder/vmware/iso/driver.go index 0fcc77fc6..402b1b363 100644 --- a/builder/vmware/iso/driver.go +++ b/builder/vmware/iso/driver.go @@ -31,17 +31,24 @@ func NewDriver(config *config) (vmwcommon.Driver, error) { case "darwin": drivers = []vmwcommon.Driver{ &Fusion5Driver{ - AppPath: "/Applications/VMware Fusion.app", + AppPath: "/Applications/VMware Fusion.app", + SSHConfig: &config.SSHConfig, }, } case "linux": drivers = []vmwcommon.Driver{ - new(Workstation9Driver), - new(Player5LinuxDriver), + &Workstation9Driver{ + SSHConfig: &config.SSHConfig, + }, + &Player5LinuxDriver{ + SSHConfig: &config.SSHConfig, + }, } case "windows": drivers = []vmwcommon.Driver{ - new(Workstation9Driver), + &Workstation9Driver{ + SSHConfig: &config.SSHConfig, + }, } default: return nil, fmt.Errorf("can't find driver for OS: %s", runtime.GOOS) From 51735cc8950e9a472f243e4419e767e6b9fba624 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 25 Dec 2013 11:01:59 -0700 Subject: [PATCH 23/45] post-processor/vagrant: fix alternate providers --- post-processor/vagrant/post-processor.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/post-processor/vagrant/post-processor.go b/post-processor/vagrant/post-processor.go index 78659488c..eae580151 100644 --- a/post-processor/vagrant/post-processor.go +++ b/post-processor/vagrant/post-processor.go @@ -208,8 +208,14 @@ func (p *PostProcessor) configureSingle(config *Config, raws ...interface{}) err func providerForName(name string) Provider { switch name { + case "aws": + return new(AWSProvider) + case "digitalocean": + return new(DigitalOceanProvider) case "virtualbox": return new(VBoxProvider) + case "vmware": + return new(VMwareProvider) default: return nil } From ca867cdbb1fca3ada05f31fe2698e0ab529e45eb Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 25 Dec 2013 11:12:09 -0700 Subject: [PATCH 24/45] rename vmware to vmware-iso --- config.go | 2 +- plugin/{builder-vmware => builder-vmware-iso}/main.go | 0 plugin/{builder-vmware => builder-vmware-iso}/main_test.go | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename plugin/{builder-vmware => builder-vmware-iso}/main.go (100%) rename plugin/{builder-vmware => builder-vmware-iso}/main_test.go (100%) diff --git a/config.go b/config.go index 15f3d764f..0748b6716 100644 --- a/config.go +++ b/config.go @@ -29,7 +29,7 @@ const defaultConfig = ` "qemu": "packer-builder-qemu", "virtualbox-iso": "packer-builder-virtualbox-iso", "virtualbox-ovf": "packer-builder-virtualbox-ovf", - "vmware": "packer-builder-vmware" + "vmware-iso": "packer-builder-vmware-iso" }, "commands": { diff --git a/plugin/builder-vmware/main.go b/plugin/builder-vmware-iso/main.go similarity index 100% rename from plugin/builder-vmware/main.go rename to plugin/builder-vmware-iso/main.go diff --git a/plugin/builder-vmware/main_test.go b/plugin/builder-vmware-iso/main_test.go similarity index 100% rename from plugin/builder-vmware/main_test.go rename to plugin/builder-vmware-iso/main_test.go From 87f9eca511ce0d176ac030f1d76f705a5891b896 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 25 Dec 2013 11:13:32 -0700 Subject: [PATCH 25/45] command/fix: rename vmware to vmware-iso --- command/fix/fixer.go | 2 + command/fix/fixer_vmware_rename.go | 46 +++++++++++++++++++++++ command/fix/fixer_vmware_rename_test.go | 49 +++++++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 command/fix/fixer_vmware_rename.go create mode 100644 command/fix/fixer_vmware_rename_test.go diff --git a/command/fix/fixer.go b/command/fix/fixer.go index b4dc2013c..8da82f48f 100644 --- a/command/fix/fixer.go +++ b/command/fix/fixer.go @@ -25,6 +25,7 @@ func init() { "pp-vagrant-override": new(FixerVagrantPPOverride), "virtualbox-gaattach": new(FixerVirtualBoxGAAttach), "virtualbox-rename": new(FixerVirtualBoxRename), + "vmware-rename": new(FixerVMwareRename), } FixerOrder = []string{ @@ -33,5 +34,6 @@ func init() { "virtualbox-gaattach", "pp-vagrant-override", "virtualbox-rename", + "vmware-rename", } } diff --git a/command/fix/fixer_vmware_rename.go b/command/fix/fixer_vmware_rename.go new file mode 100644 index 000000000..7a7b7b920 --- /dev/null +++ b/command/fix/fixer_vmware_rename.go @@ -0,0 +1,46 @@ +package fix + +import ( + "github.com/mitchellh/mapstructure" +) + +// FixerVMwareRename changes "virtualbox" builders to "virtualbox-iso" +type FixerVMwareRename struct{} + +func (FixerVMwareRename) Fix(input map[string]interface{}) (map[string]interface{}, error) { + // The type we'll decode into; we only care about builders + type template struct { + Builders []map[string]interface{} + } + + // Decode the input into our structure, if we can + var tpl template + if err := mapstructure.Decode(input, &tpl); err != nil { + return nil, err + } + + for _, builder := range tpl.Builders { + builderTypeRaw, ok := builder["type"] + if !ok { + continue + } + + builderType, ok := builderTypeRaw.(string) + if !ok { + continue + } + + if builderType != "vmware" { + continue + } + + builder["type"] = "vmware-iso" + } + + input["builders"] = tpl.Builders + return input, nil +} + +func (FixerVMwareRename) Synopsis() string { + return `Updates "vmware" builders to "vmware-iso"` +} diff --git a/command/fix/fixer_vmware_rename_test.go b/command/fix/fixer_vmware_rename_test.go new file mode 100644 index 000000000..d5ec8868a --- /dev/null +++ b/command/fix/fixer_vmware_rename_test.go @@ -0,0 +1,49 @@ +package fix + +import ( + "reflect" + "testing" +) + +func TestFixerVMwareRename_impl(t *testing.T) { + var _ Fixer = new(FixerVMwareRename) +} + +func TestFixerVMwareRename_Fix(t *testing.T) { + cases := []struct { + Input map[string]interface{} + Expected map[string]interface{} + }{ + // No attach field + { + Input: map[string]interface{}{ + "type": "vmware", + }, + + Expected: map[string]interface{}{ + "type": "vmware-iso", + }, + }, + } + + for _, tc := range cases { + var f FixerVMwareRename + + input := map[string]interface{}{ + "builders": []map[string]interface{}{tc.Input}, + } + + expected := map[string]interface{}{ + "builders": []map[string]interface{}{tc.Expected}, + } + + output, err := f.Fix(input) + if err != nil { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(output, expected) { + t.Fatalf("unexpected: %#v\nexpected: %#v\n", output, expected) + } + } +} From 6f449abf2ee07a23050f0d477af95fa847e98575 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 25 Dec 2013 11:16:26 -0700 Subject: [PATCH 26/45] command/fix: remove some useless comments --- command/fix/fixer_virtualbox_rename_test.go | 1 - command/fix/fixer_vmware_rename_test.go | 1 - 2 files changed, 2 deletions(-) diff --git a/command/fix/fixer_virtualbox_rename_test.go b/command/fix/fixer_virtualbox_rename_test.go index 1f529def1..78b7bccf2 100644 --- a/command/fix/fixer_virtualbox_rename_test.go +++ b/command/fix/fixer_virtualbox_rename_test.go @@ -14,7 +14,6 @@ func TestFixerVirtualBoxRename_Fix(t *testing.T) { Input map[string]interface{} Expected map[string]interface{} }{ - // No attach field { Input: map[string]interface{}{ "type": "virtualbox", diff --git a/command/fix/fixer_vmware_rename_test.go b/command/fix/fixer_vmware_rename_test.go index d5ec8868a..059b73ad1 100644 --- a/command/fix/fixer_vmware_rename_test.go +++ b/command/fix/fixer_vmware_rename_test.go @@ -14,7 +14,6 @@ func TestFixerVMwareRename_Fix(t *testing.T) { Input map[string]interface{} Expected map[string]interface{} }{ - // No attach field { Input: map[string]interface{}{ "type": "vmware", From bee879409af06d577b30b6df3ccf1b96fc05b1c7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 25 Dec 2013 11:27:53 -0700 Subject: [PATCH 27/45] builder/vmware: new artifact type that is common --- builder/vmware/common/artifact.go | 61 ++++++++++++++++++++++++++ builder/vmware/common/artifact_test.go | 43 ++++++++++++++++++ builder/vmware/iso/artifact.go | 5 +-- builder/vmware/iso/builder.go | 5 +-- builder/vmware/iso/output_dir.go | 4 ++ 5 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 builder/vmware/common/artifact.go create mode 100644 builder/vmware/common/artifact_test.go diff --git a/builder/vmware/common/artifact.go b/builder/vmware/common/artifact.go new file mode 100644 index 000000000..ddc2b1a35 --- /dev/null +++ b/builder/vmware/common/artifact.go @@ -0,0 +1,61 @@ +package common + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/mitchellh/packer/packer" +) + +// BuilderId for the local artifacts +const BuilderId = "mitchellh.vmware" + +// Artifact is the result of running the VMware builder, namely a set +// of files associated with the resulting machine. +type localArtifact struct { + dir string + f []string +} + +// NewLocalArtifact returns a VMware artifact containing the files +// in the given directory. +func NewLocalArtifact(dir string) (packer.Artifact, error) { + files := make([]string, 0, 5) + visit := func(path string, info os.FileInfo, err error) error { + if !info.IsDir() { + files = append(files, path) + } + + return err + } + + if err := filepath.Walk(dir, visit); err != nil { + return nil, err + } + + return &localArtifact{ + dir: dir, + f: files, + }, nil +} + +func (a *localArtifact) BuilderId() string { + return BuilderId +} + +func (a *localArtifact) Files() []string { + return a.f +} + +func (*localArtifact) Id() string { + return "VM" +} + +func (a *localArtifact) String() string { + return fmt.Sprintf("VM files in directory: %s", a.dir) +} + +func (a *localArtifact) Destroy() error { + return os.RemoveAll(a.dir) +} diff --git a/builder/vmware/common/artifact_test.go b/builder/vmware/common/artifact_test.go new file mode 100644 index 000000000..a0379b82d --- /dev/null +++ b/builder/vmware/common/artifact_test.go @@ -0,0 +1,43 @@ +package common + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/mitchellh/packer/packer" +) + +func TestLocalArtifact_impl(t *testing.T) { + var _ packer.Artifact = new(localArtifact) +} + +func TestNewLocalArtifact(t *testing.T) { + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(td) + + err = ioutil.WriteFile(filepath.Join(td, "a"), []byte("foo"), 0644) + if err != nil { + t.Fatalf("err: %s", err) + } + + if err := os.Mkdir(filepath.Join(td, "b"), 0755); err != nil { + t.Fatalf("err: %s", err) + } + + a, err := NewLocalArtifact(td) + if err != nil { + t.Fatalf("err: %s", err) + } + + if a.BuilderId() != BuilderId { + t.Fatalf("bad: %#v", a.BuilderId()) + } + if len(a.Files()) != 1 { + t.Fatalf("should length 1: %d", len(a.Files())) + } +} diff --git a/builder/vmware/iso/artifact.go b/builder/vmware/iso/artifact.go index 56924c3df..d6ddf8921 100644 --- a/builder/vmware/iso/artifact.go +++ b/builder/vmware/iso/artifact.go @@ -2,14 +2,13 @@ package iso import ( "fmt" - "os" ) // Artifact is the result of running the VMware builder, namely a set // of files associated with the resulting machine. type Artifact struct { builderId string - dir string + dir OutputDir f []string } @@ -30,5 +29,5 @@ func (a *Artifact) String() string { } func (a *Artifact) Destroy() error { - return os.RemoveAll(a.dir) + return a.dir.RemoveAll() } diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 30209eb34..52bf48daa 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -16,7 +16,6 @@ import ( "time" ) -const BuilderId = "mitchellh.vmware" const BuilderIdESX = "mitchellh.vmware-esx" type Builder struct { @@ -454,14 +453,14 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe } // Set the proper builder ID - builderId := BuilderId + builderId := vmwcommon.BuilderId if b.config.RemoteType != "" { builderId = BuilderIdESX } return &Artifact{ builderId: builderId, - dir: b.config.OutputDir, + dir: dir, f: files, }, nil } diff --git a/builder/vmware/iso/output_dir.go b/builder/vmware/iso/output_dir.go index 5013d6905..0c08a225f 100644 --- a/builder/vmware/iso/output_dir.go +++ b/builder/vmware/iso/output_dir.go @@ -60,3 +60,7 @@ func (d *localOutputDir) RemoveAll() error { func (d *localOutputDir) SetOutputDir(path string) { d.dir = path } + +func (d *localOutputDir) String() string { + return d.dir +} From 16911d75e937052ec656b5c0ae786efc59deae4f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 25 Dec 2013 15:50:12 -0700 Subject: [PATCH 28/45] builder/vmware: move drivers out to common --- builder/vmware/common/driver.go | 80 +++++++++++++++++++ .../vmware/{iso => common}/driver_fusion5.go | 7 +- .../vmware/{iso => common}/driver_player5.go | 7 +- .../{iso => common}/driver_workstation9.go | 7 +- .../driver_workstation9_unix.go | 2 +- .../driver_workstation9_windows.go | 2 +- builder/vmware/iso/driver.go | 80 +++---------------- builder/vmware/iso/host_ip_vmnetnatconf.go | 4 +- 8 files changed, 106 insertions(+), 83 deletions(-) rename builder/vmware/{iso => common}/driver_fusion5.go (95%) rename builder/vmware/{iso => common}/driver_player5.go (96%) rename builder/vmware/{iso => common}/driver_workstation9.go (95%) rename builder/vmware/{iso => common}/driver_workstation9_unix.go (98%) rename builder/vmware/{iso => common}/driver_workstation9_windows.go (99%) diff --git a/builder/vmware/common/driver.go b/builder/vmware/common/driver.go index 55de3ff14..f825334ec 100644 --- a/builder/vmware/common/driver.go +++ b/builder/vmware/common/driver.go @@ -1,6 +1,13 @@ package common import ( + "bytes" + "fmt" + "log" + "os/exec" + "runtime" + "strings" + "github.com/mitchellh/multistep" ) @@ -41,3 +48,76 @@ type Driver interface { // return an error. Otherwise, this returns an error. Verify() error } + +// NewDriver returns a new driver implementation for this operating +// system, or an error if the driver couldn't be initialized. +func NewDriver(config *SSHConfig) (Driver, error) { + drivers := []Driver{} + + switch runtime.GOOS { + case "darwin": + drivers = []Driver{ + &Fusion5Driver{ + AppPath: "/Applications/VMware Fusion.app", + SSHConfig: config, + }, + } + case "linux": + drivers = []Driver{ + &Workstation9Driver{ + SSHConfig: config, + }, + &Player5LinuxDriver{ + SSHConfig: config, + }, + } + case "windows": + drivers = []Driver{ + &Workstation9Driver{ + SSHConfig: config, + }, + } + default: + return nil, fmt.Errorf("can't find driver for OS: %s", runtime.GOOS) + } + + errs := "" + for _, driver := range drivers { + err := driver.Verify() + if err == nil { + return driver, nil + } + errs += "* " + err.Error() + "\n" + } + + return nil, fmt.Errorf( + "Unable to initialize any driver for this platform. The errors\n"+ + "from each driver are shown below. Please fix at least one driver\n"+ + "to continue:\n%s", errs) +} + +func runAndLog(cmd *exec.Cmd) (string, string, error) { + var stdout, stderr bytes.Buffer + + log.Printf("Executing: %s %v", cmd.Path, cmd.Args[1:]) + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + + stdoutString := strings.TrimSpace(stdout.String()) + stderrString := strings.TrimSpace(stderr.String()) + + if _, ok := err.(*exec.ExitError); ok { + err = fmt.Errorf("VMware error: %s", stderrString) + } + + log.Printf("stdout: %s", stdoutString) + log.Printf("stderr: %s", stderrString) + + // Replace these for Windows, we only want to deal with Unix + // style line endings. + returnStdout := strings.Replace(stdout.String(), "\r\n", "\n", -1) + returnStderr := strings.Replace(stderr.String(), "\r\n", "\n", -1) + + return returnStdout, returnStderr, err +} diff --git a/builder/vmware/iso/driver_fusion5.go b/builder/vmware/common/driver_fusion5.go similarity index 95% rename from builder/vmware/iso/driver_fusion5.go rename to builder/vmware/common/driver_fusion5.go index 9366c1755..7d75dc0c7 100644 --- a/builder/vmware/iso/driver_fusion5.go +++ b/builder/vmware/common/driver_fusion5.go @@ -1,4 +1,4 @@ -package iso +package common import ( "fmt" @@ -9,7 +9,6 @@ import ( "strings" "github.com/mitchellh/multistep" - vmwcommon "github.com/mitchellh/packer/builder/vmware/common" ) // Fusion5Driver is a driver that can run VMWare Fusion 5. @@ -18,7 +17,7 @@ type Fusion5Driver struct { AppPath string // SSHConfig are the SSH settings for the Fusion VM - SSHConfig *vmwcommon.SSHConfig + SSHConfig *SSHConfig } func (d *Fusion5Driver) CompactDisk(diskPath string) error { @@ -66,7 +65,7 @@ func (d *Fusion5Driver) IsRunning(vmxPath string) (bool, error) { } func (d *Fusion5Driver) SSHAddress(state multistep.StateBag) (string, error) { - return vmwcommon.SSHAddressFunc(d.SSHConfig)(state) + return SSHAddressFunc(d.SSHConfig)(state) } func (d *Fusion5Driver) Start(vmxPath string, headless bool) error { diff --git a/builder/vmware/iso/driver_player5.go b/builder/vmware/common/driver_player5.go similarity index 96% rename from builder/vmware/iso/driver_player5.go rename to builder/vmware/common/driver_player5.go index a317d0c59..6434c201f 100644 --- a/builder/vmware/iso/driver_player5.go +++ b/builder/vmware/common/driver_player5.go @@ -1,4 +1,4 @@ -package iso +package common import ( "fmt" @@ -8,7 +8,6 @@ import ( "strings" "github.com/mitchellh/multistep" - vmwcommon "github.com/mitchellh/packer/builder/vmware/common" ) // Player5LinuxDriver is a driver that can run VMware Player 5 on Linux. @@ -19,7 +18,7 @@ type Player5LinuxDriver struct { VmrunPath string // SSHConfig are the SSH settings for the Fusion VM - SSHConfig *vmwcommon.SSHConfig + SSHConfig *SSHConfig } func (d *Player5LinuxDriver) CompactDisk(diskPath string) error { @@ -93,7 +92,7 @@ func (d *Player5LinuxDriver) IsRunning(vmxPath string) (bool, error) { } func (d *Player5LinuxDriver) SSHAddress(state multistep.StateBag) (string, error) { - return vmwcommon.SSHAddressFunc(d.SSHConfig)(state) + return SSHAddressFunc(d.SSHConfig)(state) } func (d *Player5LinuxDriver) Start(vmxPath string, headless bool) error { diff --git a/builder/vmware/iso/driver_workstation9.go b/builder/vmware/common/driver_workstation9.go similarity index 95% rename from builder/vmware/iso/driver_workstation9.go rename to builder/vmware/common/driver_workstation9.go index e87c3b2e2..bfa5f5389 100644 --- a/builder/vmware/iso/driver_workstation9.go +++ b/builder/vmware/common/driver_workstation9.go @@ -1,4 +1,4 @@ -package iso +package common import ( "fmt" @@ -9,7 +9,6 @@ import ( "strings" "github.com/mitchellh/multistep" - vmwcommon "github.com/mitchellh/packer/builder/vmware/common" ) // Workstation9Driver is a driver that can run VMware Workstation 9 @@ -20,7 +19,7 @@ type Workstation9Driver struct { VmrunPath string // SSHConfig are the SSH settings for the Fusion VM - SSHConfig *vmwcommon.SSHConfig + SSHConfig *SSHConfig } func (d *Workstation9Driver) CompactDisk(diskPath string) error { @@ -68,7 +67,7 @@ func (d *Workstation9Driver) IsRunning(vmxPath string) (bool, error) { } func (d *Workstation9Driver) SSHAddress(state multistep.StateBag) (string, error) { - return vmwcommon.SSHAddressFunc(d.SSHConfig)(state) + return SSHAddressFunc(d.SSHConfig)(state) } func (d *Workstation9Driver) Start(vmxPath string, headless bool) error { diff --git a/builder/vmware/iso/driver_workstation9_unix.go b/builder/vmware/common/driver_workstation9_unix.go similarity index 98% rename from builder/vmware/iso/driver_workstation9_unix.go rename to builder/vmware/common/driver_workstation9_unix.go index c3c6288cd..f90c326f4 100644 --- a/builder/vmware/iso/driver_workstation9_unix.go +++ b/builder/vmware/common/driver_workstation9_unix.go @@ -1,6 +1,6 @@ // +build !windows -package iso +package common import ( "errors" diff --git a/builder/vmware/iso/driver_workstation9_windows.go b/builder/vmware/common/driver_workstation9_windows.go similarity index 99% rename from builder/vmware/iso/driver_workstation9_windows.go rename to builder/vmware/common/driver_workstation9_windows.go index 3c4d91105..17095badc 100644 --- a/builder/vmware/iso/driver_workstation9_windows.go +++ b/builder/vmware/common/driver_workstation9_windows.go @@ -1,6 +1,6 @@ // +build windows -package iso +package common import ( "log" diff --git a/builder/vmware/iso/driver.go b/builder/vmware/iso/driver.go index 402b1b363..22c6b6902 100644 --- a/builder/vmware/iso/driver.go +++ b/builder/vmware/iso/driver.go @@ -1,12 +1,7 @@ package iso import ( - "bytes" "fmt" - "log" - "os/exec" - "runtime" - "strings" vmwcommon "github.com/mitchellh/packer/builder/vmware/common" ) @@ -16,43 +11,18 @@ import ( func NewDriver(config *config) (vmwcommon.Driver, error) { drivers := []vmwcommon.Driver{} - if config.RemoteType != "" { - drivers = []vmwcommon.Driver{ - &ESX5Driver{ - Host: config.RemoteHost, - Port: config.RemotePort, - Username: config.RemoteUser, - Password: config.RemotePassword, - Datastore: config.RemoteDatastore, - }, - } - } else { - switch runtime.GOOS { - case "darwin": - drivers = []vmwcommon.Driver{ - &Fusion5Driver{ - AppPath: "/Applications/VMware Fusion.app", - SSHConfig: &config.SSHConfig, - }, - } - case "linux": - drivers = []vmwcommon.Driver{ - &Workstation9Driver{ - SSHConfig: &config.SSHConfig, - }, - &Player5LinuxDriver{ - SSHConfig: &config.SSHConfig, - }, - } - case "windows": - drivers = []vmwcommon.Driver{ - &Workstation9Driver{ - SSHConfig: &config.SSHConfig, - }, - } - default: - return nil, fmt.Errorf("can't find driver for OS: %s", runtime.GOOS) - } + if config.RemoteType == "" { + return vmwcommon.NewDriver(&config.SSHConfig) + } + + drivers = []vmwcommon.Driver{ + &ESX5Driver{ + Host: config.RemoteHost, + Port: config.RemotePort, + Username: config.RemoteUser, + Password: config.RemotePassword, + Datastore: config.RemoteDatastore, + }, } errs := "" @@ -69,29 +39,3 @@ func NewDriver(config *config) (vmwcommon.Driver, error) { "from each driver are shown below. Please fix at least one driver\n"+ "to continue:\n%s", errs) } - -func runAndLog(cmd *exec.Cmd) (string, string, error) { - var stdout, stderr bytes.Buffer - - log.Printf("Executing: %s %v", cmd.Path, cmd.Args[1:]) - cmd.Stdout = &stdout - cmd.Stderr = &stderr - err := cmd.Run() - - stdoutString := strings.TrimSpace(stdout.String()) - stderrString := strings.TrimSpace(stderr.String()) - - if _, ok := err.(*exec.ExitError); ok { - err = fmt.Errorf("VMware error: %s", stderrString) - } - - log.Printf("stdout: %s", stdoutString) - log.Printf("stderr: %s", stderrString) - - // Replace these for Windows, we only want to deal with Unix - // style line endings. - returnStdout := strings.Replace(stdout.String(), "\r\n", "\n", -1) - returnStderr := strings.Replace(stderr.String(), "\r\n", "\n", -1) - - return returnStdout, returnStderr, err -} diff --git a/builder/vmware/iso/host_ip_vmnetnatconf.go b/builder/vmware/iso/host_ip_vmnetnatconf.go index 658a0abd4..0ba0a3727 100644 --- a/builder/vmware/iso/host_ip_vmnetnatconf.go +++ b/builder/vmware/iso/host_ip_vmnetnatconf.go @@ -8,6 +8,8 @@ import ( "os" "regexp" "strings" + + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" ) // VMnetNatConfIPFinder finds the IP address of the host machine by @@ -16,7 +18,7 @@ import ( type VMnetNatConfIPFinder struct{} func (*VMnetNatConfIPFinder) HostIP() (string, error) { - driver := &Workstation9Driver{} + driver := &vmwcommon.Workstation9Driver{} vmnetnat := driver.VmnetnatConfPath() if vmnetnat == "" { From 6fdcb0f832388fd530aac12d881d56fcc33f7d38 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 25 Dec 2013 15:52:40 -0700 Subject: [PATCH 29/45] builder/vmware/vmx: initial stuff --- builder/vmware/vmx/builder.go | 83 ++++++++++++++++++++++++++++++ builder/vmware/vmx/builder_test.go | 1 + builder/vmware/vmx/config.go | 58 +++++++++++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 builder/vmware/vmx/builder.go create mode 100644 builder/vmware/vmx/builder_test.go create mode 100644 builder/vmware/vmx/config.go diff --git a/builder/vmware/vmx/builder.go b/builder/vmware/vmx/builder.go new file mode 100644 index 000000000..2ef4a418b --- /dev/null +++ b/builder/vmware/vmx/builder.go @@ -0,0 +1,83 @@ +package vmx + +import ( + "errors" + "fmt" + "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/packer" + "log" +) + +// Builder implements packer.Builder and builds the actual VirtualBox +// images. +type Builder struct { + config *Config + runner multistep.Runner +} + +// Prepare processes the build configuration parameters. +func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { + c, warnings, errs := NewConfig(raws...) + if errs != nil { + return warnings, errs + } + b.config = c + + return warnings, nil +} + +// Run executes a Packer build and returns a packer.Artifact representing +// a VirtualBox appliance. +func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { + driver, err := vmwcommon.NewDriver(&b.config.SSHConfig) + if err != nil { + return nil, fmt.Errorf("Failed creating VMware driver: %s", err) + } + + // Set up the state. + state := new(multistep.BasicStateBag) + state.Put("config", b.config) + state.Put("driver", driver) + state.Put("hook", hook) + state.Put("ui", ui) + + // Build the steps. + steps := []multistep.Step{} + + // Run the steps. + if b.config.PackerDebug { + b.runner = &multistep.DebugRunner{ + Steps: steps, + PauseFn: common.MultistepDebugFn(ui), + } + } else { + b.runner = &multistep.BasicRunner{Steps: steps} + } + b.runner.Run(state) + + // Report any errors. + if rawErr, ok := state.GetOk("error"); ok { + return nil, rawErr.(error) + } + + // If we were interrupted or cancelled, then just exit. + if _, ok := state.GetOk(multistep.StateCancelled); ok { + return nil, errors.New("Build was cancelled.") + } + + if _, ok := state.GetOk(multistep.StateHalted); ok { + return nil, errors.New("Build was halted.") + } + + return nil, nil +} + +// Cancel. +func (b *Builder) Cancel() { + if b.runner != nil { + log.Println("Cancelling the step runner...") + b.runner.Cancel() + } +} diff --git a/builder/vmware/vmx/builder_test.go b/builder/vmware/vmx/builder_test.go new file mode 100644 index 000000000..e6b128721 --- /dev/null +++ b/builder/vmware/vmx/builder_test.go @@ -0,0 +1 @@ +package vmx diff --git a/builder/vmware/vmx/config.go b/builder/vmware/vmx/config.go new file mode 100644 index 000000000..c31b7d11d --- /dev/null +++ b/builder/vmware/vmx/config.go @@ -0,0 +1,58 @@ +package vmx + +import ( + "fmt" + + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/packer" +) + +// Config is the configuration structure for the builder. +type Config struct { + common.PackerConfig `mapstructure:",squash"` + vmwcommon.SSHConfig `mapstructure:",squash"` + + tpl *packer.ConfigTemplate +} + +func NewConfig(raws ...interface{}) (*Config, []string, error) { + c := new(Config) + md, err := common.DecodeConfig(c, raws...) + if err != nil { + return nil, nil, err + } + + c.tpl, err = packer.NewConfigTemplate() + if err != nil { + return nil, nil, err + } + c.tpl.UserVars = c.PackerUserVars + + // Defaults + + // Prepare the errors + errs := common.CheckUnusedConfig(md) + errs = packer.MultiErrorAppend(errs, c.SSHConfig.Prepare(c.tpl)...) + + templates := map[string]*string{} + + for n, ptr := range templates { + var err error + *ptr, err = c.tpl.Process(*ptr, nil) + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + + // Warnings + var warnings []string + + // Check for any errors. + if errs != nil && len(errs.Errors) > 0 { + return nil, warnings, errs + } + + return c, warnings, nil +} From 141cfeb4bbadfcca4d926be363db8d7ff591837e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 25 Dec 2013 16:01:57 -0700 Subject: [PATCH 30/45] builder/vmware/vmx: outputdir --- builder/vmware/common/output_config.go | 40 +++++++++++++ builder/vmware/common/output_config_test.go | 65 +++++++++++++++++++++ builder/vmware/iso/builder.go | 21 ++----- builder/vmware/vmx/builder.go | 11 +++- builder/vmware/vmx/config.go | 6 +- builder/vmware/vmx/config_test.go | 27 +++++++++ 6 files changed, 151 insertions(+), 19 deletions(-) create mode 100644 builder/vmware/common/output_config.go create mode 100644 builder/vmware/common/output_config_test.go create mode 100644 builder/vmware/vmx/config_test.go diff --git a/builder/vmware/common/output_config.go b/builder/vmware/common/output_config.go new file mode 100644 index 000000000..19be1ba00 --- /dev/null +++ b/builder/vmware/common/output_config.go @@ -0,0 +1,40 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/packer" + "os" +) + +type OutputConfig struct { + OutputDir string `mapstructure:"output_directory"` +} + +func (c *OutputConfig) Prepare(t *packer.ConfigTemplate, pc *common.PackerConfig) []error { + if c.OutputDir == "" { + c.OutputDir = fmt.Sprintf("output-%s", pc.PackerBuildName) + } + + templates := map[string]*string{ + "output_directory": &c.OutputDir, + } + + errs := make([]error, 0) + for n, ptr := range templates { + var err error + *ptr, err = t.Process(*ptr, nil) + if err != nil { + errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + + if !pc.PackerForce { + if _, err := os.Stat(c.OutputDir); err == nil { + errs = append(errs, fmt.Errorf( + "Output directory '%s' already exists. It must not exist.", c.OutputDir)) + } + } + + return errs +} diff --git a/builder/vmware/common/output_config_test.go b/builder/vmware/common/output_config_test.go new file mode 100644 index 000000000..7fa039a16 --- /dev/null +++ b/builder/vmware/common/output_config_test.go @@ -0,0 +1,65 @@ +package common + +import ( + "github.com/mitchellh/packer/common" + "io/ioutil" + "os" + "testing" +) + +func TestOutputConfigPrepare(t *testing.T) { + c := new(OutputConfig) + if c.OutputDir != "" { + t.Fatalf("what: %s", c.OutputDir) + } + + pc := &common.PackerConfig{PackerBuildName: "foo"} + errs := c.Prepare(testConfigTemplate(t), pc) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + + if c.OutputDir == "" { + t.Fatal("should have output dir") + } +} + +func TestOutputConfigPrepare_exists(t *testing.T) { + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(td) + + c := new(OutputConfig) + c.OutputDir = td + + pc := &common.PackerConfig{ + PackerBuildName: "foo", + PackerForce: false, + } + errs := c.Prepare(testConfigTemplate(t), pc) + if len(errs) == 0 { + t.Fatal("should have errors") + } +} + +func TestOutputConfigPrepare_forceExists(t *testing.T) { + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(td) + + c := new(OutputConfig) + c.OutputDir = td + + pc := &common.PackerConfig{ + PackerBuildName: "foo", + PackerForce: true, + } + errs := c.Prepare(testConfigTemplate(t), pc) + if len(errs) > 0 { + t.Fatal("should not have errors") + } +} diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 52bf48daa..3272d0036 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -24,8 +24,9 @@ type Builder struct { } type config struct { - common.PackerConfig `mapstructure:",squash"` - vmwcommon.SSHConfig `mapstructure:",squash"` + common.PackerConfig `mapstructure:",squash"` + vmwcommon.OutputConfig `mapstructure:",squash"` + vmwcommon.SSHConfig `mapstructure:",squash"` DiskName string `mapstructure:"vmdk_name"` DiskSize uint `mapstructure:"disk_size"` @@ -36,7 +37,6 @@ type config struct { ISOChecksumType string `mapstructure:"iso_checksum_type"` ISOUrls []string `mapstructure:"iso_urls"` VMName string `mapstructure:"vm_name"` - OutputDir string `mapstructure:"output_directory"` Headless bool `mapstructure:"headless"` HTTPDir string `mapstructure:"http_directory"` HTTPPortMin uint `mapstructure:"http_port_min"` @@ -81,6 +81,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { // Accumulate any errors errs := common.CheckUnusedConfig(md) + errs = packer.MultiErrorAppend(errs, + b.config.OutputConfig.Prepare(b.config.tpl, &b.config.PackerConfig)...) errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(b.config.tpl)...) warnings := make([]string, 0) @@ -133,10 +135,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.config.VNCPortMax = 6000 } - if b.config.OutputDir == "" { - b.config.OutputDir = fmt.Sprintf("output-%s", b.config.PackerBuildName) - } - if b.config.RemoteUser == "" { b.config.RemoteUser = "root" } @@ -161,7 +159,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { "iso_checksum": &b.config.ISOChecksum, "iso_checksum_type": &b.config.ISOChecksumType, "iso_url": &b.config.RawSingleISOUrl, - "output_directory": &b.config.OutputDir, "shutdown_command": &b.config.ShutdownCommand, "tools_upload_flavor": &b.config.ToolsUploadFlavor, "vm_name": &b.config.VMName, @@ -273,14 +270,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } - if !b.config.PackerForce { - if _, err := os.Stat(b.config.OutputDir); err == nil { - errs = packer.MultiErrorAppend( - errs, - fmt.Errorf("Output directory '%s' already exists. It must not exist.", b.config.OutputDir)) - } - } - if b.config.RawBootWait != "" { b.config.bootWait, err = time.ParseDuration(b.config.RawBootWait) if err != nil { diff --git a/builder/vmware/vmx/builder.go b/builder/vmware/vmx/builder.go index 2ef4a418b..c401e0a9e 100644 --- a/builder/vmware/vmx/builder.go +++ b/builder/vmware/vmx/builder.go @@ -36,15 +36,24 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe return nil, fmt.Errorf("Failed creating VMware driver: %s", err) } + // Setup the directory + dir := new(vmwcommon.LocalOutputDir) + dir.SetOutputDir(b.config.OutputDir) + // Set up the state. state := new(multistep.BasicStateBag) state.Put("config", b.config) + state.Put("dir", dir) state.Put("driver", driver) state.Put("hook", hook) state.Put("ui", ui) // Build the steps. - steps := []multistep.Step{} + steps := []multistep.Step{ + &vmwcommon.StepOutputDir{ + Force: b.config.PackerForce, + }, + } // Run the steps. if b.config.PackerDebug { diff --git a/builder/vmware/vmx/config.go b/builder/vmware/vmx/config.go index c31b7d11d..b4f45c4af 100644 --- a/builder/vmware/vmx/config.go +++ b/builder/vmware/vmx/config.go @@ -10,8 +10,9 @@ import ( // Config is the configuration structure for the builder. type Config struct { - common.PackerConfig `mapstructure:",squash"` - vmwcommon.SSHConfig `mapstructure:",squash"` + common.PackerConfig `mapstructure:",squash"` + vmwcommon.OutputConfig `mapstructure:",squash"` + vmwcommon.SSHConfig `mapstructure:",squash"` tpl *packer.ConfigTemplate } @@ -33,6 +34,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { // 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{} diff --git a/builder/vmware/vmx/config_test.go b/builder/vmware/vmx/config_test.go new file mode 100644 index 000000000..7128d8a71 --- /dev/null +++ b/builder/vmware/vmx/config_test.go @@ -0,0 +1,27 @@ +package vmx + +import ( + "testing" +) + +func testConfig(t *testing.T) map[string]interface{} { + return map[string]interface{}{} +} + +func testConfigErr(t *testing.T, warns []string, err error) { + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should error") + } +} + +func testConfigOk(t *testing.T, warns []string, err error) { + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("bad: %s", err) + } +} From eeeaec35623ab0408be62e923b5e0660c778cc3b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Dec 2013 08:34:27 -0700 Subject: [PATCH 31/45] builder/vmware/vmx: StepCloneVMX --- .../virtualbox/common/shutdown_config_test.go | 4 ++ builder/vmware/vmx/builder.go | 3 + builder/vmware/vmx/config.go | 21 ++++++- builder/vmware/vmx/config_test.go | 33 ++++++++++- builder/vmware/vmx/step_clone_vmx.go | 53 ++++++++++++++++++ builder/vmware/vmx/step_clone_vmx_test.go | 56 +++++++++++++++++++ builder/vmware/vmx/step_test.go | 20 +++++++ 7 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 builder/vmware/vmx/step_clone_vmx.go create mode 100644 builder/vmware/vmx/step_clone_vmx_test.go create mode 100644 builder/vmware/vmx/step_test.go 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 +} From 82fad98b070df7a3395043f5d5f3d86ff38309c0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Dec 2013 08:36:00 -0700 Subject: [PATCH 32/45] builder/vmware/vmx: set the settings for cloning vmx --- builder/vmware/vmx/builder.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/builder/vmware/vmx/builder.go b/builder/vmware/vmx/builder.go index 7a1912b31..148187dbd 100644 --- a/builder/vmware/vmx/builder.go +++ b/builder/vmware/vmx/builder.go @@ -54,7 +54,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Force: b.config.PackerForce, }, &StepCloneVMX{ - Path: b.config.SourcePath, + OutputDir: b.config.OutputDir, + Path: b.config.SourcePath, + VMName: b.config.VMName, }, } From f23d66a1b9b8bee5f8622cbc5b2c5298771c9959 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Dec 2013 14:39:41 -0700 Subject: [PATCH 33/45] builder/vmware/vmx: clone vmx --- builder/vmware/common/driver.go | 5 +++ builder/vmware/common/driver_fusion5.go | 12 +++++-- builder/vmware/common/driver_mock.go | 12 +++++++ builder/vmware/common/driver_player5.go | 5 +++ builder/vmware/common/driver_workstation9.go | 5 +++ builder/vmware/vmx/step_clone_vmx.go | 22 +++--------- builder/vmware/vmx/step_clone_vmx_test.go | 36 ++++---------------- 7 files changed, 48 insertions(+), 49 deletions(-) diff --git a/builder/vmware/common/driver.go b/builder/vmware/common/driver.go index f825334ec..992d0939c 100644 --- a/builder/vmware/common/driver.go +++ b/builder/vmware/common/driver.go @@ -13,6 +13,11 @@ import ( // A driver is able to talk to VMware, control virtual machines, etc. type Driver interface { + // Clone clones the VMX and the disk to the destination path. The + // destination is a path to the VMX file. The disk will be copied + // to that same directory. + Clone(dst string, src string) error + // CompactDisk compacts a virtual disk. CompactDisk(string) error diff --git a/builder/vmware/common/driver_fusion5.go b/builder/vmware/common/driver_fusion5.go index 7d75dc0c7..40b62ad5c 100644 --- a/builder/vmware/common/driver_fusion5.go +++ b/builder/vmware/common/driver_fusion5.go @@ -1,6 +1,7 @@ package common import ( + "errors" "fmt" "io/ioutil" "os" @@ -20,6 +21,10 @@ type Fusion5Driver struct { SSHConfig *SSHConfig } +func (d *Fusion5Driver) Clone(dst, src string) error { + return errors.New("Cloning is not supported with Fusion 5. Please use Fusion 6+.") +} + func (d *Fusion5Driver) CompactDisk(diskPath string) error { defragCmd := exec.Command(d.vdiskManagerPath(), "-d", diskPath) if _, _, err := runAndLog(defragCmd); err != nil { @@ -111,7 +116,8 @@ func (d *Fusion5Driver) Verify() error { if _, err := os.Stat(d.vmrunPath()); err != nil { if os.IsNotExist(err) { - return fmt.Errorf("Critical application 'vmrun' not found at path: %s", d.vmrunPath()) + return fmt.Errorf( + "Critical application 'vmrun' not found at path: %s", d.vmrunPath()) } return err @@ -119,7 +125,9 @@ func (d *Fusion5Driver) Verify() error { if _, err := os.Stat(d.vdiskManagerPath()); err != nil { if os.IsNotExist(err) { - return fmt.Errorf("Critical application vdisk manager not found at path: %s", d.vdiskManagerPath()) + return fmt.Errorf( + "Critical application vdisk manager not found at path: %s", + d.vdiskManagerPath()) } return err diff --git a/builder/vmware/common/driver_mock.go b/builder/vmware/common/driver_mock.go index edae482c5..2f5486bac 100644 --- a/builder/vmware/common/driver_mock.go +++ b/builder/vmware/common/driver_mock.go @@ -9,6 +9,11 @@ import ( type DriverMock struct { sync.Mutex + CloneCalled bool + CloneDst string + CloneSrc string + CloneErr error + CompactDiskCalled bool CompactDiskPath string CompactDiskErr error @@ -54,6 +59,13 @@ type DriverMock struct { VerifyErr error } +func (d *DriverMock) Clone(dst string, src string) error { + d.CloneCalled = true + d.CloneDst = dst + d.CloneSrc = src + return d.CloneErr +} + func (d *DriverMock) CompactDisk(path string) error { d.CompactDiskCalled = true d.CompactDiskPath = path diff --git a/builder/vmware/common/driver_player5.go b/builder/vmware/common/driver_player5.go index 6434c201f..1bec4ea25 100644 --- a/builder/vmware/common/driver_player5.go +++ b/builder/vmware/common/driver_player5.go @@ -1,6 +1,7 @@ package common import ( + "errors" "fmt" "os" "os/exec" @@ -21,6 +22,10 @@ type Player5LinuxDriver struct { SSHConfig *SSHConfig } +func (d *Player5LinuxDriver) Clone(dst, src string) error { + return errors.New("Cloning is not supported with Player 5. Please use Player 6+.") +} + func (d *Player5LinuxDriver) CompactDisk(diskPath string) error { if d.QemuImgPath != "" { return d.qemuCompactDisk(diskPath) diff --git a/builder/vmware/common/driver_workstation9.go b/builder/vmware/common/driver_workstation9.go index bfa5f5389..8643d8b1f 100644 --- a/builder/vmware/common/driver_workstation9.go +++ b/builder/vmware/common/driver_workstation9.go @@ -1,6 +1,7 @@ package common import ( + "errors" "fmt" "log" "os" @@ -22,6 +23,10 @@ type Workstation9Driver struct { SSHConfig *SSHConfig } +func (d *Workstation9Driver) Clone(dst, src string) error { + return errors.New("Cloning is not supported with WS 9. Please use WS 10+.") +} + func (d *Workstation9Driver) CompactDisk(diskPath string) error { defragCmd := exec.Command(d.VdiskManagerPath, "-d", diskPath) if _, _, err := runAndLog(defragCmd); err != nil { diff --git a/builder/vmware/vmx/step_clone_vmx.go b/builder/vmware/vmx/step_clone_vmx.go index 431406ab1..075df0d63 100644 --- a/builder/vmware/vmx/step_clone_vmx.go +++ b/builder/vmware/vmx/step_clone_vmx.go @@ -1,12 +1,11 @@ package vmx import ( - "io" "log" - "os" "path/filepath" "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/packer" ) @@ -18,29 +17,16 @@ type StepCloneVMX struct { } func (s *StepCloneVMX) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(vmwcommon.Driver) ui := state.Get("ui").(packer.Ui) vmxPath := filepath.Join(s.OutputDir, s.VMName+".vmx") - ui.Say("Cloning VMX...") + ui.Say("Cloning source VM...") 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 { + if err := driver.Clone(vmxPath, s.Path); err != nil { state.Put("error", err) return multistep.ActionHalt } diff --git a/builder/vmware/vmx/step_clone_vmx_test.go b/builder/vmware/vmx/step_clone_vmx_test.go index 2a4f00124..4d6532724 100644 --- a/builder/vmware/vmx/step_clone_vmx_test.go +++ b/builder/vmware/vmx/step_clone_vmx_test.go @@ -1,12 +1,10 @@ package vmx import ( - "io/ioutil" - "os" - "path/filepath" "testing" "github.com/mitchellh/multistep" + vmwcommon "github.com/mitchellh/packer/builder/vmware/common" ) func TestStepCloneVMX_impl(t *testing.T) { @@ -14,25 +12,14 @@ func TestStepCloneVMX_impl(t *testing.T) { } 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.OutputDir = "/foo" + step.Path = "/bar/bar.vmx" step.VMName = "foo" + driver := state.Get("driver").(*vmwcommon.DriverMock) + // Test the run if action := step.Run(state); action != multistep.ActionContinue { t.Fatalf("bad action: %#v", action) @@ -41,16 +28,7 @@ func TestStepCloneVMX(t *testing.T) { 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)) + if !driver.CloneCalled { + t.Fatal("clone should be called") } } From 8fecdf179da38f140e53fbb193f58acca7f435d1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Dec 2013 14:54:26 -0700 Subject: [PATCH 34/45] builder/vmware/common: Fusion6Driver --- builder/vmware/common/driver.go | 6 +++ builder/vmware/common/driver_fusion6.go | 60 +++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 builder/vmware/common/driver_fusion6.go diff --git a/builder/vmware/common/driver.go b/builder/vmware/common/driver.go index 992d0939c..aee7997e1 100644 --- a/builder/vmware/common/driver.go +++ b/builder/vmware/common/driver.go @@ -62,6 +62,12 @@ func NewDriver(config *SSHConfig) (Driver, error) { switch runtime.GOOS { case "darwin": drivers = []Driver{ + &Fusion6Driver{ + Fusion5Driver: Fusion5Driver{ + AppPath: "/Applications/VMware Fusion.app", + SSHConfig: config, + }, + }, &Fusion5Driver{ AppPath: "/Applications/VMware Fusion.app", SSHConfig: config, diff --git a/builder/vmware/common/driver_fusion6.go b/builder/vmware/common/driver_fusion6.go new file mode 100644 index 000000000..0bb49e17f --- /dev/null +++ b/builder/vmware/common/driver_fusion6.go @@ -0,0 +1,60 @@ +package common + +import ( + "bytes" + "errors" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" +) + +// Fusion6Driver is a driver that can run VMWare Fusion 5. +type Fusion6Driver struct { + Fusion5Driver +} + +func (d *Fusion6Driver) Clone(dst, src string) error { + return errors.New("Cloning is not supported with Fusion 5. Please use Fusion 6+.") +} + +func (d *Fusion6Driver) Verify() error { + if err := d.Fusion5Driver.Verify(); err != nil { + return err + } + + vmxpath := filepath.Join(d.AppPath, "Contents", "Library", "vmware-vmx") + if _, err := os.Stat(vmxpath); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("vmware-vmx could not be found at path: %s", + vmxpath) + } + + return err + } + + var stderr bytes.Buffer + cmd := exec.Command(vmxpath, "-v") + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + return err + } + + versionRe := regexp.MustCompile(`(?i)VMware [a-z0-9-]+ (\d+\.\d+\.\d+)\s`) + matches := versionRe.FindStringSubmatch(stderr.String()) + if matches == nil { + return fmt.Errorf( + "Couldn't find VMware version in output: %s", stderr.String()) + } + log.Printf("Detected VMware version: %s", matches[1]) + + if !strings.HasPrefix(matches[1], "6.") { + return fmt.Errorf( + "Fusion 6 not detected. Got version: %s", matches[1]) + } + + return nil +} From b994b8c09e51dcf7d36ab3bf132f9a06a2bcac3a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Dec 2013 14:55:06 -0700 Subject: [PATCH 35/45] Add vmware-vmx plugin --- config.go | 3 ++- plugin/builder-vmware-vmx/main.go | 15 +++++++++++++++ plugin/builder-vmware-vmx/main_test.go | 1 + 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 plugin/builder-vmware-vmx/main.go create mode 100644 plugin/builder-vmware-vmx/main_test.go diff --git a/config.go b/config.go index 0748b6716..a2fe49297 100644 --- a/config.go +++ b/config.go @@ -29,7 +29,8 @@ const defaultConfig = ` "qemu": "packer-builder-qemu", "virtualbox-iso": "packer-builder-virtualbox-iso", "virtualbox-ovf": "packer-builder-virtualbox-ovf", - "vmware-iso": "packer-builder-vmware-iso" + "vmware-iso": "packer-builder-vmware-iso", + "vmware-vmx": "packer-builder-vmware-vmx" }, "commands": { diff --git a/plugin/builder-vmware-vmx/main.go b/plugin/builder-vmware-vmx/main.go new file mode 100644 index 000000000..f060f3c56 --- /dev/null +++ b/plugin/builder-vmware-vmx/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/mitchellh/packer/builder/vmware/vmx" + "github.com/mitchellh/packer/packer/plugin" +) + +func main() { + server, err := plugin.Server() + if err != nil { + panic(err) + } + server.RegisterBuilder(new(vmx.Builder)) + server.Serve() +} diff --git a/plugin/builder-vmware-vmx/main_test.go b/plugin/builder-vmware-vmx/main_test.go new file mode 100644 index 000000000..06ab7d0f9 --- /dev/null +++ b/plugin/builder-vmware-vmx/main_test.go @@ -0,0 +1 @@ +package main From 03fb5fb0f1da6e01f26f46d3f42ae7c28a80dd5c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Dec 2013 15:03:12 -0700 Subject: [PATCH 36/45] builder/vmware/common: Fusion6 driver can clone --- builder/vmware/common/driver_fusion6.go | 11 +++++++++-- builder/vmware/iso/driver_esx5.go | 4 ++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/builder/vmware/common/driver_fusion6.go b/builder/vmware/common/driver_fusion6.go index 0bb49e17f..98c466cb9 100644 --- a/builder/vmware/common/driver_fusion6.go +++ b/builder/vmware/common/driver_fusion6.go @@ -2,7 +2,6 @@ package common import ( "bytes" - "errors" "fmt" "log" "os" @@ -18,7 +17,15 @@ type Fusion6Driver struct { } func (d *Fusion6Driver) Clone(dst, src string) error { - return errors.New("Cloning is not supported with Fusion 5. Please use Fusion 6+.") + cmd := exec.Command(d.vmrunPath(), + "-T", "fusion", + "clone", src, dst, + "full") + if _, _, err := runAndLog(cmd); err != nil { + return err + } + + return nil } func (d *Fusion6Driver) Verify() error { diff --git a/builder/vmware/iso/driver_esx5.go b/builder/vmware/iso/driver_esx5.go index 2fc86f70c..09083d618 100644 --- a/builder/vmware/iso/driver_esx5.go +++ b/builder/vmware/iso/driver_esx5.go @@ -33,6 +33,10 @@ type ESX5Driver struct { outputDir string } +func (d *ESX5Driver) Clone(dst, src string) error { + return errors.New("Cloning is not supported with the ESX driver.") +} + func (d *ESX5Driver) CompactDisk(diskPathLocal string) error { return nil } From 286edcb2b49e2a5c6ae38c78de8078788a26e551 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Dec 2013 15:14:19 -0700 Subject: [PATCH 37/45] builder/vmware/vmx: configure VMX --- builder/vmware/common/vmx_config.go | 37 +++++++++++++ builder/vmware/common/vmx_config_test.go | 22 ++++++++ builder/vmware/iso/builder.go | 66 +++++++++--------------- builder/vmware/iso/builder_test.go | 22 -------- builder/vmware/vmx/builder.go | 4 ++ builder/vmware/vmx/config.go | 2 + 6 files changed, 88 insertions(+), 65 deletions(-) create mode 100644 builder/vmware/common/vmx_config.go create mode 100644 builder/vmware/common/vmx_config_test.go diff --git a/builder/vmware/common/vmx_config.go b/builder/vmware/common/vmx_config.go new file mode 100644 index 000000000..f876d584b --- /dev/null +++ b/builder/vmware/common/vmx_config.go @@ -0,0 +1,37 @@ +package common + +import ( + "fmt" + + "github.com/mitchellh/packer/packer" +) + +type VMXConfig struct { + VMXData map[string]string `mapstructure:"vmx_data"` +} + +func (c *VMXConfig) Prepare(t *packer.ConfigTemplate) []error { + errs := make([]error, 0) + newVMXData := make(map[string]string) + for k, v := range c.VMXData { + var err error + k, err = t.Process(k, nil) + if err != nil { + errs = append(errs, + fmt.Errorf("Error processing VMX data key %s: %s", k, err)) + continue + } + + v, err = t.Process(v, nil) + if err != nil { + errs = append(errs, + fmt.Errorf("Error processing VMX data value '%s': %s", v, err)) + continue + } + + newVMXData[k] = v + } + c.VMXData = newVMXData + + return errs +} diff --git a/builder/vmware/common/vmx_config_test.go b/builder/vmware/common/vmx_config_test.go new file mode 100644 index 000000000..4b8172cb0 --- /dev/null +++ b/builder/vmware/common/vmx_config_test.go @@ -0,0 +1,22 @@ +package common + +import ( + "testing" +) + +func TestVMXConfigPrepare(t *testing.T) { + c := new(VMXConfig) + c.VMXData = map[string]string{ + "one": "foo", + "two": "bar", + } + + errs := c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("bad: %#v", errs) + } + + if len(c.VMXData) != 2 { + t.Fatal("should have two items in VMXData") + } +} diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 3272d0036..63e0649a7 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -27,29 +27,29 @@ type config struct { common.PackerConfig `mapstructure:",squash"` vmwcommon.OutputConfig `mapstructure:",squash"` vmwcommon.SSHConfig `mapstructure:",squash"` + vmwcommon.VMXConfig `mapstructure:",squash"` - DiskName string `mapstructure:"vmdk_name"` - DiskSize uint `mapstructure:"disk_size"` - DiskTypeId string `mapstructure:"disk_type_id"` - FloppyFiles []string `mapstructure:"floppy_files"` - GuestOSType string `mapstructure:"guest_os_type"` - ISOChecksum string `mapstructure:"iso_checksum"` - ISOChecksumType string `mapstructure:"iso_checksum_type"` - ISOUrls []string `mapstructure:"iso_urls"` - VMName string `mapstructure:"vm_name"` - Headless bool `mapstructure:"headless"` - HTTPDir string `mapstructure:"http_directory"` - HTTPPortMin uint `mapstructure:"http_port_min"` - HTTPPortMax uint `mapstructure:"http_port_max"` - BootCommand []string `mapstructure:"boot_command"` - SkipCompaction bool `mapstructure:"skip_compaction"` - ShutdownCommand string `mapstructure:"shutdown_command"` - ToolsUploadFlavor string `mapstructure:"tools_upload_flavor"` - ToolsUploadPath string `mapstructure:"tools_upload_path"` - VMXData map[string]string `mapstructure:"vmx_data"` - VMXTemplatePath string `mapstructure:"vmx_template_path"` - VNCPortMin uint `mapstructure:"vnc_port_min"` - VNCPortMax uint `mapstructure:"vnc_port_max"` + DiskName string `mapstructure:"vmdk_name"` + DiskSize uint `mapstructure:"disk_size"` + DiskTypeId string `mapstructure:"disk_type_id"` + FloppyFiles []string `mapstructure:"floppy_files"` + GuestOSType string `mapstructure:"guest_os_type"` + ISOChecksum string `mapstructure:"iso_checksum"` + ISOChecksumType string `mapstructure:"iso_checksum_type"` + ISOUrls []string `mapstructure:"iso_urls"` + VMName string `mapstructure:"vm_name"` + Headless bool `mapstructure:"headless"` + HTTPDir string `mapstructure:"http_directory"` + HTTPPortMin uint `mapstructure:"http_port_min"` + HTTPPortMax uint `mapstructure:"http_port_max"` + BootCommand []string `mapstructure:"boot_command"` + SkipCompaction bool `mapstructure:"skip_compaction"` + ShutdownCommand string `mapstructure:"shutdown_command"` + ToolsUploadFlavor string `mapstructure:"tools_upload_flavor"` + ToolsUploadPath string `mapstructure:"tools_upload_path"` + VMXTemplatePath string `mapstructure:"vmx_template_path"` + VNCPortMin uint `mapstructure:"vnc_port_min"` + VNCPortMax uint `mapstructure:"vnc_port_max"` RemoteType string `mapstructure:"remote_type"` RemoteDatastore string `mapstructure:"remote_datastore"` @@ -84,6 +84,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs = packer.MultiErrorAppend(errs, b.config.OutputConfig.Prepare(b.config.tpl, &b.config.PackerConfig)...) errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(b.config.tpl)...) + errs = packer.MultiErrorAppend(errs, b.config.VMXConfig.Prepare(b.config.tpl)...) warnings := make([]string, 0) if b.config.DiskName == "" { @@ -207,27 +208,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } - newVMXData := make(map[string]string) - for k, v := range b.config.VMXData { - k, err = b.config.tpl.Process(k, nil) - if err != nil { - errs = packer.MultiErrorAppend(errs, - fmt.Errorf("Error processing VMX data key %s: %s", k, err)) - continue - } - - v, err = b.config.tpl.Process(v, nil) - if err != nil { - errs = packer.MultiErrorAppend(errs, - fmt.Errorf("Error processing VMX data value '%s': %s", v, err)) - continue - } - - newVMXData[k] = v - } - - b.config.VMXData = newVMXData - if b.config.HTTPPortMin > b.config.HTTPPortMax { errs = packer.MultiErrorAppend( errs, errors.New("http_port_min must be less than http_port_max")) diff --git a/builder/vmware/iso/builder_test.go b/builder/vmware/iso/builder_test.go index 463a4c5fe..46c6cb300 100644 --- a/builder/vmware/iso/builder_test.go +++ b/builder/vmware/iso/builder_test.go @@ -568,25 +568,3 @@ func TestBuilderPrepare_VNCPort(t *testing.T) { t.Fatalf("should not have error: %s", err) } } - -func TestBuilderPrepare_VMXData(t *testing.T) { - var b Builder - config := testConfig() - - config["vmx_data"] = map[interface{}]interface{}{ - "one": "foo", - "two": "bar", - } - - warns, err := b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("should not have error: %s", err) - } - - if len(b.config.VMXData) != 2 { - t.Fatal("should have two items in VMXData") - } -} diff --git a/builder/vmware/vmx/builder.go b/builder/vmware/vmx/builder.go index 148187dbd..a1e42a886 100644 --- a/builder/vmware/vmx/builder.go +++ b/builder/vmware/vmx/builder.go @@ -58,6 +58,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Path: b.config.SourcePath, VMName: b.config.VMName, }, + &vmwcommon.StepConfigureVMX{ + CustomData: b.config.VMXData, + }, + &vmwcommon.StepSuppressMessages{}, } // Run the steps. diff --git a/builder/vmware/vmx/config.go b/builder/vmware/vmx/config.go index 92a05add0..312c6a208 100644 --- a/builder/vmware/vmx/config.go +++ b/builder/vmware/vmx/config.go @@ -14,6 +14,7 @@ type Config struct { common.PackerConfig `mapstructure:",squash"` vmwcommon.OutputConfig `mapstructure:",squash"` vmwcommon.SSHConfig `mapstructure:",squash"` + vmwcommon.VMXConfig `mapstructure:",squash"` SourcePath string `mapstructure:"source_path"` VMName string `mapstructure:"vm_name"` @@ -43,6 +44,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { errs := common.CheckUnusedConfig(md) errs = packer.MultiErrorAppend(errs, c.OutputConfig.Prepare(c.tpl, &c.PackerConfig)...) errs = packer.MultiErrorAppend(errs, c.SSHConfig.Prepare(c.tpl)...) + errs = packer.MultiErrorAppend(errs, c.VMXConfig.Prepare(c.tpl)...) templates := map[string]*string{ "source_path": &c.SourcePath, From e11f655d2277ce753170a3aef6e04a11f4d90a5f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Dec 2013 15:26:09 -0700 Subject: [PATCH 38/45] builder/vmware/vmx: run the VMs --- builder/vmware/common/run_config.go | 44 ++++++++++++++++++++++++ builder/vmware/common/run_config_test.go | 36 +++++++++++++++++++ builder/vmware/iso/builder.go | 20 ++--------- builder/vmware/iso/builder_test.go | 40 --------------------- builder/vmware/vmx/builder.go | 9 ++++- builder/vmware/vmx/config.go | 2 ++ 6 files changed, 93 insertions(+), 58 deletions(-) create mode 100644 builder/vmware/common/run_config.go create mode 100644 builder/vmware/common/run_config_test.go diff --git a/builder/vmware/common/run_config.go b/builder/vmware/common/run_config.go new file mode 100644 index 000000000..ce3480e9e --- /dev/null +++ b/builder/vmware/common/run_config.go @@ -0,0 +1,44 @@ +package common + +import ( + "fmt" + "time" + + "github.com/mitchellh/packer/packer" +) + +type RunConfig struct { + Headless bool `mapstructure:"headless"` + RawBootWait string `mapstructure:"boot_wait"` + + BootWait time.Duration `` +} + +func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error { + if c.RawBootWait == "" { + c.RawBootWait = "10s" + } + + templates := map[string]*string{ + "boot_wait": &c.RawBootWait, + } + + var err error + errs := make([]error, 0) + for n, ptr := range templates { + *ptr, err = t.Process(*ptr, nil) + if err != nil { + errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + + if c.RawBootWait != "" { + c.BootWait, err = time.ParseDuration(c.RawBootWait) + if err != nil { + errs = append( + errs, fmt.Errorf("Failed parsing boot_wait: %s", err)) + } + } + + return errs +} diff --git a/builder/vmware/common/run_config_test.go b/builder/vmware/common/run_config_test.go new file mode 100644 index 000000000..d94e254f7 --- /dev/null +++ b/builder/vmware/common/run_config_test.go @@ -0,0 +1,36 @@ +package common + +import ( + "testing" +) + +func TestRunConfigPrepare(t *testing.T) { + var c *RunConfig + + // Test a default boot_wait + c = new(RunConfig) + c.RawBootWait = "" + errs := c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("bad: %#v", errs) + } + if c.RawBootWait != "10s" { + t.Fatalf("bad value: %s", c.RawBootWait) + } + + // Test with a bad boot_wait + c = new(RunConfig) + c.RawBootWait = "this is not good" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatal("should error") + } + + // Test with a good one + c = new(RunConfig) + c.RawBootWait = "5s" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("bad: %#v", errs) + } +} diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 63e0649a7..85c691a7a 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -26,6 +26,7 @@ type Builder struct { type config struct { common.PackerConfig `mapstructure:",squash"` vmwcommon.OutputConfig `mapstructure:",squash"` + vmwcommon.RunConfig `mapstructure:",squash"` vmwcommon.SSHConfig `mapstructure:",squash"` vmwcommon.VMXConfig `mapstructure:",squash"` @@ -38,7 +39,6 @@ type config struct { ISOChecksumType string `mapstructure:"iso_checksum_type"` ISOUrls []string `mapstructure:"iso_urls"` VMName string `mapstructure:"vm_name"` - Headless bool `mapstructure:"headless"` HTTPDir string `mapstructure:"http_directory"` HTTPPortMin uint `mapstructure:"http_port_min"` HTTPPortMax uint `mapstructure:"http_port_max"` @@ -58,11 +58,9 @@ type config struct { RemoteUser string `mapstructure:"remote_username"` RemotePassword string `mapstructure:"remote_password"` - RawBootWait string `mapstructure:"boot_wait"` RawSingleISOUrl string `mapstructure:"iso_url"` RawShutdownTimeout string `mapstructure:"shutdown_timeout"` - bootWait time.Duration `` shutdownTimeout time.Duration `` tpl *packer.ConfigTemplate } @@ -83,6 +81,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs := common.CheckUnusedConfig(md) errs = packer.MultiErrorAppend(errs, b.config.OutputConfig.Prepare(b.config.tpl, &b.config.PackerConfig)...) + errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.tpl)...) errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(b.config.tpl)...) errs = packer.MultiErrorAppend(errs, b.config.VMXConfig.Prepare(b.config.tpl)...) warnings := make([]string, 0) @@ -124,10 +123,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.config.HTTPPortMax = 9000 } - if b.config.RawBootWait == "" { - b.config.RawBootWait = "10s" - } - if b.config.VNCPortMin == 0 { b.config.VNCPortMin = 5900 } @@ -163,7 +158,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { "shutdown_command": &b.config.ShutdownCommand, "tools_upload_flavor": &b.config.ToolsUploadFlavor, "vm_name": &b.config.VMName, - "boot_wait": &b.config.RawBootWait, "shutdown_timeout": &b.config.RawShutdownTimeout, "vmx_template_path": &b.config.VMXTemplatePath, "remote_type": &b.config.RemoteType, @@ -250,14 +244,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } - if b.config.RawBootWait != "" { - b.config.bootWait, err = time.ParseDuration(b.config.RawBootWait) - if err != nil { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("Failed parsing boot_wait: %s", err)) - } - } - if b.config.RawShutdownTimeout == "" { b.config.RawShutdownTimeout = "5m" } @@ -365,7 +351,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &stepConfigureVNC{}, &StepRegister{}, &vmwcommon.StepRun{ - BootWait: b.config.bootWait, + BootWait: b.config.BootWait, DurationBeforeStop: 5 * time.Second, Headless: b.config.Headless, }, diff --git a/builder/vmware/iso/builder_test.go b/builder/vmware/iso/builder_test.go index 46c6cb300..e76632f6e 100644 --- a/builder/vmware/iso/builder_test.go +++ b/builder/vmware/iso/builder_test.go @@ -29,46 +29,6 @@ func TestBuilder_ImplementsBuilder(t *testing.T) { } } -func TestBuilderPrepare_BootWait(t *testing.T) { - var b Builder - config := testConfig() - - // Test a default boot_wait - delete(config, "boot_wait") - warns, err := b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("err: %s", err) - } - - if b.config.RawBootWait != "10s" { - t.Fatalf("bad value: %s", b.config.RawBootWait) - } - - // Test with a bad boot_wait - config["boot_wait"] = "this is not good" - warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err == nil { - t.Fatal("should have error") - } - - // Test with a good one - config["boot_wait"] = "5s" - b = Builder{} - warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("should not have error: %s", err) - } -} - func TestBuilderPrepare_ISOChecksum(t *testing.T) { var b Builder config := testConfig() diff --git a/builder/vmware/vmx/builder.go b/builder/vmware/vmx/builder.go index a1e42a886..002699d5f 100644 --- a/builder/vmware/vmx/builder.go +++ b/builder/vmware/vmx/builder.go @@ -3,11 +3,13 @@ package vmx import ( "errors" "fmt" + "log" + "time" + "github.com/mitchellh/multistep" vmwcommon "github.com/mitchellh/packer/builder/vmware/common" "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/packer" - "log" ) // Builder implements packer.Builder and builds the actual VirtualBox @@ -62,6 +64,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe CustomData: b.config.VMXData, }, &vmwcommon.StepSuppressMessages{}, + &vmwcommon.StepRun{ + BootWait: b.config.BootWait, + DurationBeforeStop: 5 * time.Second, + Headless: b.config.Headless, + }, } // Run the steps. diff --git a/builder/vmware/vmx/config.go b/builder/vmware/vmx/config.go index 312c6a208..c6a1576b8 100644 --- a/builder/vmware/vmx/config.go +++ b/builder/vmware/vmx/config.go @@ -13,6 +13,7 @@ import ( type Config struct { common.PackerConfig `mapstructure:",squash"` vmwcommon.OutputConfig `mapstructure:",squash"` + vmwcommon.RunConfig `mapstructure:",squash"` vmwcommon.SSHConfig `mapstructure:",squash"` vmwcommon.VMXConfig `mapstructure:",squash"` @@ -43,6 +44,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { // Prepare the errors errs := common.CheckUnusedConfig(md) errs = packer.MultiErrorAppend(errs, c.OutputConfig.Prepare(c.tpl, &c.PackerConfig)...) + errs = packer.MultiErrorAppend(errs, c.RunConfig.Prepare(c.tpl)...) errs = packer.MultiErrorAppend(errs, c.SSHConfig.Prepare(c.tpl)...) errs = packer.MultiErrorAppend(errs, c.VMXConfig.Prepare(c.tpl)...) From ac8354ad9c050b7ea087bfc3d34c76894e549bc9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Dec 2013 15:28:15 -0700 Subject: [PATCH 39/45] builder/vmware/vmx: step connect SSH --- builder/vmware/vmx/builder.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/builder/vmware/vmx/builder.go b/builder/vmware/vmx/builder.go index 002699d5f..cd930183e 100644 --- a/builder/vmware/vmx/builder.go +++ b/builder/vmware/vmx/builder.go @@ -69,6 +69,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe DurationBeforeStop: 5 * time.Second, Headless: b.config.Headless, }, + &common.StepConnectSSH{ + SSHAddress: driver.SSHAddress, + SSHConfig: vmwcommon.SSHConfigFunc(&b.config.SSHConfig), + SSHWaitTimeout: b.config.SSHWaitTimeout, + NoPty: b.config.SSHSkipRequestPty, + }, + &common.StepProvision{}, } // Run the steps. From 7f38cea9f30108c9a3e638b461e2b992ee83fa68 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Dec 2013 15:31:23 -0700 Subject: [PATCH 40/45] builder/vmware/vmx: shutdown --- builder/vmware/common/shutdown_config.go | 42 +++++++++++++++++ builder/vmware/common/shutdown_config_test.go | 45 +++++++++++++++++++ builder/vmware/iso/builder.go | 33 +++++--------- builder/vmware/iso/builder_test.go | 41 ----------------- builder/vmware/vmx/builder.go | 4 ++ builder/vmware/vmx/config.go | 17 ++++--- builder/vmware/vmx/config_test.go | 3 +- 7 files changed, 115 insertions(+), 70 deletions(-) create mode 100644 builder/vmware/common/shutdown_config.go create mode 100644 builder/vmware/common/shutdown_config_test.go diff --git a/builder/vmware/common/shutdown_config.go b/builder/vmware/common/shutdown_config.go new file mode 100644 index 000000000..05e5fdfeb --- /dev/null +++ b/builder/vmware/common/shutdown_config.go @@ -0,0 +1,42 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/packer/packer" + "time" +) + +type ShutdownConfig struct { + ShutdownCommand string `mapstructure:"shutdown_command"` + RawShutdownTimeout string `mapstructure:"shutdown_timeout"` + + ShutdownTimeout time.Duration `` +} + +func (c *ShutdownConfig) Prepare(t *packer.ConfigTemplate) []error { + if c.RawShutdownTimeout == "" { + c.RawShutdownTimeout = "5m" + } + + templates := map[string]*string{ + "shutdown_command": &c.ShutdownCommand, + "shutdown_timeout": &c.RawShutdownTimeout, + } + + errs := make([]error, 0) + for n, ptr := range templates { + var err error + *ptr, err = t.Process(*ptr, nil) + if err != nil { + errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + + var err error + c.ShutdownTimeout, err = time.ParseDuration(c.RawShutdownTimeout) + if err != nil { + errs = append(errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err)) + } + + return errs +} diff --git a/builder/vmware/common/shutdown_config_test.go b/builder/vmware/common/shutdown_config_test.go new file mode 100644 index 000000000..5da613a19 --- /dev/null +++ b/builder/vmware/common/shutdown_config_test.go @@ -0,0 +1,45 @@ +package common + +import ( + "testing" + "time" +) + +func testShutdownConfig() *ShutdownConfig { + return &ShutdownConfig{} +} + +func TestShutdownConfigPrepare_ShutdownCommand(t *testing.T) { + var c *ShutdownConfig + var errs []error + + c = testShutdownConfig() + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } +} + +func TestShutdownConfigPrepare_ShutdownTimeout(t *testing.T) { + var c *ShutdownConfig + var errs []error + + // Test with a bad value + c = testShutdownConfig() + c.RawShutdownTimeout = "this is not good" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatalf("should have error") + } + + // Test with a good one + c = testShutdownConfig() + c.RawShutdownTimeout = "5s" + errs = c.Prepare(testConfigTemplate(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/iso/builder.go b/builder/vmware/iso/builder.go index 85c691a7a..0a3f33b75 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -24,11 +24,12 @@ type Builder struct { } type config struct { - common.PackerConfig `mapstructure:",squash"` - vmwcommon.OutputConfig `mapstructure:",squash"` - vmwcommon.RunConfig `mapstructure:",squash"` - vmwcommon.SSHConfig `mapstructure:",squash"` - vmwcommon.VMXConfig `mapstructure:",squash"` + common.PackerConfig `mapstructure:",squash"` + vmwcommon.OutputConfig `mapstructure:",squash"` + vmwcommon.RunConfig `mapstructure:",squash"` + vmwcommon.ShutdownConfig `mapstructure:",squash"` + vmwcommon.SSHConfig `mapstructure:",squash"` + vmwcommon.VMXConfig `mapstructure:",squash"` DiskName string `mapstructure:"vmdk_name"` DiskSize uint `mapstructure:"disk_size"` @@ -44,7 +45,6 @@ type config struct { HTTPPortMax uint `mapstructure:"http_port_max"` BootCommand []string `mapstructure:"boot_command"` SkipCompaction bool `mapstructure:"skip_compaction"` - ShutdownCommand string `mapstructure:"shutdown_command"` ToolsUploadFlavor string `mapstructure:"tools_upload_flavor"` ToolsUploadPath string `mapstructure:"tools_upload_path"` VMXTemplatePath string `mapstructure:"vmx_template_path"` @@ -58,11 +58,9 @@ type config struct { RemoteUser string `mapstructure:"remote_username"` RemotePassword string `mapstructure:"remote_password"` - RawSingleISOUrl string `mapstructure:"iso_url"` - RawShutdownTimeout string `mapstructure:"shutdown_timeout"` + RawSingleISOUrl string `mapstructure:"iso_url"` - shutdownTimeout time.Duration `` - tpl *packer.ConfigTemplate + tpl *packer.ConfigTemplate } func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { @@ -82,6 +80,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs = packer.MultiErrorAppend(errs, b.config.OutputConfig.Prepare(b.config.tpl, &b.config.PackerConfig)...) errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.tpl)...) + errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(b.config.tpl)...) errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(b.config.tpl)...) errs = packer.MultiErrorAppend(errs, b.config.VMXConfig.Prepare(b.config.tpl)...) warnings := make([]string, 0) @@ -155,10 +154,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { "iso_checksum": &b.config.ISOChecksum, "iso_checksum_type": &b.config.ISOChecksumType, "iso_url": &b.config.RawSingleISOUrl, - "shutdown_command": &b.config.ShutdownCommand, "tools_upload_flavor": &b.config.ToolsUploadFlavor, "vm_name": &b.config.VMName, - "shutdown_timeout": &b.config.RawShutdownTimeout, "vmx_template_path": &b.config.VMXTemplatePath, "remote_type": &b.config.RemoteType, "remote_host": &b.config.RemoteHost, @@ -244,16 +241,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } - if b.config.RawShutdownTimeout == "" { - b.config.RawShutdownTimeout = "5m" - } - - b.config.shutdownTimeout, err = time.ParseDuration(b.config.RawShutdownTimeout) - if err != nil { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err)) - } - if _, err := template.New("path").Parse(b.config.ToolsUploadPath); err != nil { errs = packer.MultiErrorAppend( errs, fmt.Errorf("tools_upload_path invalid: %s", err)) @@ -366,7 +353,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &common.StepProvision{}, &vmwcommon.StepShutdown{ Command: b.config.ShutdownCommand, - Timeout: b.config.shutdownTimeout, + Timeout: b.config.ShutdownTimeout, }, &vmwcommon.StepCleanFiles{}, &vmwcommon.StepCleanVMX{}, diff --git a/builder/vmware/iso/builder_test.go b/builder/vmware/iso/builder_test.go index e76632f6e..f05f4a470 100644 --- a/builder/vmware/iso/builder_test.go +++ b/builder/vmware/iso/builder_test.go @@ -349,47 +349,6 @@ func TestBuilderPrepare_OutputDir(t *testing.T) { } } -func TestBuilderPrepare_ShutdownCommand(t *testing.T) { - var b Builder - config := testConfig() - delete(config, "shutdown_command") - - warns, err := b.Prepare(config) - if err != nil { - t.Fatalf("bad: %s", err) - } - - if len(warns) != 1 { - t.Fatalf("bad: %#v", warns) - } -} - -func TestBuilderPrepare_ShutdownTimeout(t *testing.T) { - var b Builder - config := testConfig() - - // Test with a bad value - config["shutdown_timeout"] = "this is not good" - warns, err := b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err == nil { - t.Fatal("should have error") - } - - // Test with a good one - config["shutdown_timeout"] = "5s" - b = Builder{} - warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("should not have error: %s", err) - } -} - func TestBuilderPrepare_ToolsUploadPath(t *testing.T) { var b Builder config := testConfig() diff --git a/builder/vmware/vmx/builder.go b/builder/vmware/vmx/builder.go index cd930183e..af27d70df 100644 --- a/builder/vmware/vmx/builder.go +++ b/builder/vmware/vmx/builder.go @@ -76,6 +76,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe NoPty: b.config.SSHSkipRequestPty, }, &common.StepProvision{}, + &vmwcommon.StepShutdown{ + Command: b.config.ShutdownCommand, + Timeout: b.config.ShutdownTimeout, + }, } // Run the steps. diff --git a/builder/vmware/vmx/config.go b/builder/vmware/vmx/config.go index c6a1576b8..3f38dfd8c 100644 --- a/builder/vmware/vmx/config.go +++ b/builder/vmware/vmx/config.go @@ -11,11 +11,12 @@ import ( // Config is the configuration structure for the builder. type Config struct { - common.PackerConfig `mapstructure:",squash"` - vmwcommon.OutputConfig `mapstructure:",squash"` - vmwcommon.RunConfig `mapstructure:",squash"` - vmwcommon.SSHConfig `mapstructure:",squash"` - vmwcommon.VMXConfig `mapstructure:",squash"` + common.PackerConfig `mapstructure:",squash"` + vmwcommon.OutputConfig `mapstructure:",squash"` + vmwcommon.RunConfig `mapstructure:",squash"` + vmwcommon.ShutdownConfig `mapstructure:",squash"` + vmwcommon.SSHConfig `mapstructure:",squash"` + vmwcommon.VMXConfig `mapstructure:",squash"` SourcePath string `mapstructure:"source_path"` VMName string `mapstructure:"vm_name"` @@ -45,6 +46,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { errs := common.CheckUnusedConfig(md) errs = packer.MultiErrorAppend(errs, c.OutputConfig.Prepare(c.tpl, &c.PackerConfig)...) errs = packer.MultiErrorAppend(errs, c.RunConfig.Prepare(c.tpl)...) + errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare(c.tpl)...) errs = packer.MultiErrorAppend(errs, c.SSHConfig.Prepare(c.tpl)...) errs = packer.MultiErrorAppend(errs, c.VMXConfig.Prepare(c.tpl)...) @@ -73,6 +75,11 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { // Warnings var warnings []string + if c.ShutdownCommand == "" { + warnings = append(warnings, + "A shutdown_command was not specified. Without a shutdown command, Packer\n"+ + "will forcibly halt the virtual machine, which may result in data loss.") + } // Check for any errors. if errs != nil && len(errs.Errors) > 0 { diff --git a/builder/vmware/vmx/config_test.go b/builder/vmware/vmx/config_test.go index d1e8a7860..ac49957e3 100644 --- a/builder/vmware/vmx/config_test.go +++ b/builder/vmware/vmx/config_test.go @@ -8,7 +8,8 @@ import ( func testConfig(t *testing.T) map[string]interface{} { return map[string]interface{}{ - "ssh_username": "foo", + "ssh_username": "foo", + "shutdown_command": "foo", } } From 2b3d98d48de9460a2b3bb875b82c5e3bcb2b5e2d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Dec 2013 15:32:38 -0700 Subject: [PATCH 41/45] builder/vmware/vmx: create artifact --- builder/vmware/vmx/builder.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/builder/vmware/vmx/builder.go b/builder/vmware/vmx/builder.go index af27d70df..6d76c4537 100644 --- a/builder/vmware/vmx/builder.go +++ b/builder/vmware/vmx/builder.go @@ -80,6 +80,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Command: b.config.ShutdownCommand, Timeout: b.config.ShutdownTimeout, }, + &vmwcommon.StepCleanFiles{}, + &vmwcommon.StepCleanVMX{}, } // Run the steps. @@ -107,7 +109,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe return nil, errors.New("Build was halted.") } - return nil, nil + return vmwcommon.NewLocalArtifact(b.config.OutputDir) } // Cancel. From f134bcc3f46028a3c796cda804ad96e6c071f092 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Dec 2013 15:34:53 -0700 Subject: [PATCH 42/45] builder/vmware/common: better UI when forcibly shutting down --- builder/vmware/common/step_shutdown.go | 1 + 1 file changed, 1 insertion(+) diff --git a/builder/vmware/common/step_shutdown.go b/builder/vmware/common/step_shutdown.go index 05ae26dd1..92516730e 100644 --- a/builder/vmware/common/step_shutdown.go +++ b/builder/vmware/common/step_shutdown.go @@ -88,6 +88,7 @@ func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction { } } } else { + ui.Say("Forcibly halting virtual machine...") if err := driver.Stop(vmxPath); err != nil { err := fmt.Errorf("Error stopping VM: %s", err) state.Put("error", err) From 8e75075ec9b1820c8040648ef0b308eac42edf63 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Dec 2013 15:35:37 -0700 Subject: [PATCH 43/45] builder/vmware/vmx: compact disk --- builder/vmware/vmx/builder.go | 3 +++ builder/vmware/vmx/config.go | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/builder/vmware/vmx/builder.go b/builder/vmware/vmx/builder.go index 6d76c4537..e799efd0b 100644 --- a/builder/vmware/vmx/builder.go +++ b/builder/vmware/vmx/builder.go @@ -82,6 +82,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe }, &vmwcommon.StepCleanFiles{}, &vmwcommon.StepCleanVMX{}, + &vmwcommon.StepCompactDisk{ + Skip: b.config.SkipCompaction, + }, } // Run the steps. diff --git a/builder/vmware/vmx/config.go b/builder/vmware/vmx/config.go index 3f38dfd8c..5f68d1f42 100644 --- a/builder/vmware/vmx/config.go +++ b/builder/vmware/vmx/config.go @@ -18,8 +18,9 @@ type Config struct { vmwcommon.SSHConfig `mapstructure:",squash"` vmwcommon.VMXConfig `mapstructure:",squash"` - SourcePath string `mapstructure:"source_path"` - VMName string `mapstructure:"vm_name"` + SkipCompaction bool `mapstructure:"skip_compaction"` + SourcePath string `mapstructure:"source_path"` + VMName string `mapstructure:"vm_name"` tpl *packer.ConfigTemplate } From 9362cb536487aa3e13800d6ae8a32575f3eedc2f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Dec 2013 15:44:39 -0700 Subject: [PATCH 44/45] builder/vmware/vmx: set the full_disk_path so compacting works --- builder/vmware/vmx/step_clone_vmx.go | 16 +++++++- builder/vmware/vmx/step_clone_vmx_test.go | 46 +++++++++++++++++++++-- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/builder/vmware/vmx/step_clone_vmx.go b/builder/vmware/vmx/step_clone_vmx.go index 075df0d63..c1edcff15 100644 --- a/builder/vmware/vmx/step_clone_vmx.go +++ b/builder/vmware/vmx/step_clone_vmx.go @@ -1,6 +1,7 @@ package vmx import ( + "fmt" "log" "path/filepath" @@ -25,12 +26,25 @@ func (s *StepCloneVMX) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Cloning source VM...") log.Printf("Cloning from: %s", s.Path) log.Printf("Cloning to: %s", vmxPath) - if err := driver.Clone(vmxPath, s.Path); err != nil { state.Put("error", err) return multistep.ActionHalt } + vmxData, err := vmwcommon.ReadVMX(vmxPath) + if err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + + diskName, ok := vmxData["scsi0:0.filename"] + if !ok { + err := fmt.Errorf("Root disk filename could not be found!") + state.Put("error", err) + return multistep.ActionHalt + } + + state.Put("full_disk_path", filepath.Join(s.OutputDir, diskName)) state.Put("vmx_path", vmxPath) return multistep.ActionContinue } diff --git a/builder/vmware/vmx/step_clone_vmx_test.go b/builder/vmware/vmx/step_clone_vmx_test.go index 4d6532724..8b8b5a056 100644 --- a/builder/vmware/vmx/step_clone_vmx_test.go +++ b/builder/vmware/vmx/step_clone_vmx_test.go @@ -1,6 +1,9 @@ package vmx import ( + "io/ioutil" + "os" + "path/filepath" "testing" "github.com/mitchellh/multistep" @@ -12,10 +15,29 @@ func TestStepCloneVMX_impl(t *testing.T) { } 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(testCloneVMX), 0644); err != nil { + t.Fatalf("err: %s", err) + } + + // Create the dest because the mock driver won't + destPath := filepath.Join(td, "foo.vmx") + if err := ioutil.WriteFile(destPath, []byte(testCloneVMX), 0644); err != nil { + t.Fatalf("err: %s", err) + } + state := testState(t) step := new(StepCloneVMX) - step.OutputDir = "/foo" - step.Path = "/bar/bar.vmx" + step.OutputDir = td + step.Path = sourcePath step.VMName = "foo" driver := state.Get("driver").(*vmwcommon.DriverMock) @@ -28,7 +50,25 @@ func TestStepCloneVMX(t *testing.T) { t.Fatal("should NOT have error") } + // Test we cloned if !driver.CloneCalled { - t.Fatal("clone should be called") + t.Fatal("should call clone") + } + + // Test that we have our paths + if vmxPath, ok := state.GetOk("vmx_path"); !ok { + t.Fatal("should set vmx_path") + } else if vmxPath != destPath { + t.Fatalf("bad: %#v", vmxPath) + } + + if diskPath, ok := state.GetOk("full_disk_path"); !ok { + t.Fatal("should set full_disk_path") + } else if diskPath != filepath.Join(td, "foo") { + t.Fatalf("bad: %#v", diskPath) } } + +const testCloneVMX = ` +scsi0:0.fileName = "foo" +` From 92810deffd292958ab827eff1c2b53d52c4c8caf Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Dec 2013 17:10:53 -0700 Subject: [PATCH 45/45] website: document new vmware-vmx builder --- .../docs/builders/vmware-iso.html.markdown | 333 +++++++++++++++++ .../docs/builders/vmware-vmx.html.markdown | 121 ++++++ .../source/docs/builders/vmware.html.markdown | 344 +----------------- 3 files changed, 473 insertions(+), 325 deletions(-) create mode 100644 website/source/docs/builders/vmware-iso.html.markdown create mode 100644 website/source/docs/builders/vmware-vmx.html.markdown diff --git a/website/source/docs/builders/vmware-iso.html.markdown b/website/source/docs/builders/vmware-iso.html.markdown new file mode 100644 index 000000000..0b03d6d55 --- /dev/null +++ b/website/source/docs/builders/vmware-iso.html.markdown @@ -0,0 +1,333 @@ +--- +layout: "docs" +page_title: "VMware Builder from ISO" +--- + +# VMware Builder (from ISO) + +Type: `vmware-iso` + +This VMware builder is able to create VMware virtual machines from an +ISO file as a source. It currently +supports building virtual machines on hosts running +[VMware Fusion](http://www.vmware.com/products/fusion/overview.html) for OS X, +[VMware Workstation](http://www.vmware.com/products/workstation/overview.html) +for Linux and Windows, and +[VMware Player](http://www.vmware.com/products/player/) on Linux. It can +also build machines directly on +[VMware vSphere Hypervisor](http://www.vmware.com/products/vsphere-hypervisor/) +using SSH as opposed to the vSphere API. + +The builder builds a virtual machine by creating a new virtual machine +from scratch, booting it, installing an OS, provisioning software within +the OS, then shutting it down. The result of the VMware builder is a directory +containing all the files necessary to run the virtual machine. + +## Basic Example + +Here is a basic example. This example is not functional. It will start the +OS installer but then fail because we don't provide the preseed file for +Ubuntu to self-install. Still, the example serves to show the basic configuration: + +
+{
+  "type": "vmware-iso",
+  "iso_url": "http://old-releases.ubuntu.com/releases/precise/ubuntu-12.04.2-server-amd64.iso",
+  "iso_checksum": "af5f788aee1b32c4b2634734309cc9e9",
+  "iso_checksum_type": "md5",
+  "ssh_username": "packer",
+  "ssh_wait_timeout": "30s",
+  "shutdown_command": "shutdown -P now"
+}
+
+ +## Configuration Reference + +There are many configuration options available for the VMware builder. +They are organized below into two categories: required and optional. Within +each category, the available options are alphabetized and described. + +Required: + +* `iso_checksum` (string) - The checksum for the OS ISO file. Because ISO + files are so large, this is required and Packer will verify it prior + to booting a virtual machine with the ISO attached. The type of the + checksum is specified with `iso_checksum_type`, documented below. + +* `iso_checksum_type` (string) - The type of the checksum specified in + `iso_checksum`. Valid values are "md5", "sha1", "sha256", or "sha512" currently. + +* `iso_url` (string) - A URL to the ISO containing the installation image. + This URL can be either an HTTP URL or a file URL (or path to a file). + If this is an HTTP URL, Packer will download it and cache it between + runs. + +* `ssh_username` (string) - The username to use to SSH into the machine + once the OS is installed. + +Optional: + +* `boot_command` (array of strings) - This is an array of commands to type + when the virtual machine is firsted booted. The goal of these commands should + be to type just enough to initialize the operating system installer. Special + keys can be typed as well, and are covered in the section below on the boot + command. If this is not specified, it is assumed the installer will start + itself. + +* `boot_wait` (string) - The time to wait after booting the initial virtual + machine before typing the `boot_command`. The value of this should be + a duration. Examples are "5s" and "1m30s" which will cause Packer to wait + five seconds and one minute 30 seconds, respectively. If this isn't specified, + the default is 10 seconds. + +* `disk_size` (int) - The size of the hard disk for the VM in megabytes. + The builder uses expandable, not fixed-size virtual hard disks, so the + actual file representing the disk will not use the full size unless it is full. + By default this is set to 40,000 (40 GB). + +* `disk_type_id` (string) - The type of VMware virtual disk to create. + The default is "1", which corresponds to a growable virtual disk split in + 2GB files. This option is for advanced usage, modify only if you + know what you're doing. For more information, please consult the + [Virtual Disk Manager User's Guide](http://www.vmware.com/pdf/VirtualDiskManager.pdf) + for desktop VMware clients. For ESXi, refer to the proper ESXi documentation. + +* `floppy_files` (array of strings) - A list of files to put onto a floppy + disk that is attached when the VM is booted for the first time. This is + most useful for unattended Windows installs, which look for an + `Autounattend.xml` file on removable media. By default no floppy will + be attached. The files listed in this configuration will all be put + into the root directory of the floppy disk; sub-directories are not supported. + +* `guest_os_type` (string) - The guest OS type being installed. This will be + set in the VMware VMX. By default this is "other". By specifying a more specific + OS type, VMware may perform some optimizations or virtual hardware changes + to better support the operating system running in the virtual machine. + +* `headless` (bool) - Packer defaults to building VMware + virtual machines by launching a GUI that shows the console of the + machine being built. When this value is set to true, the machine will + start without a console. For VMware machines, Packer will output VNC + connection information in case you need to connect to the console to + debug the build process. + +* `http_directory` (string) - Path to a directory to serve using an HTTP + server. The files in this directory will be available over HTTP that will + be requestable from the virtual machine. This is useful for hosting + kickstart files and so on. By default this is "", which means no HTTP + server will be started. The address and port of the HTTP server will be + available as variables in `boot_command`. This is covered in more detail + below. + +* `http_port_min` and `http_port_max` (int) - These are the minimum and + maximum port to use for the HTTP server started to serve the `http_directory`. + Because Packer often runs in parallel, Packer will choose a randomly available + port in this range to run the HTTP server. If you want to force the HTTP + server to be on one port, make this minimum and maximum port the same. + By default the values are 8000 and 9000, respectively. + +* `iso_urls` (array of strings) - Multiple URLs for the ISO to download. + Packer will try these in order. If anything goes wrong attempting to download + or while downloading a single URL, it will move on to the next. All URLs + must point to the same file (same checksum). By default this is empty + and `iso_url` is used. Only one of `iso_url` or `iso_urls` can be specified. + +* `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. + +* `remote_type` (string) - The type of remote machine that will be used to + build this VM rather than a local desktop product. The only value accepted + for this currently is "esx5". If this is not set, a desktop product will be + used. By default, this is not set. + +* `remote_datastore` (string) - The path to the datastore where the resulting + VM will be stored when it is built on the remote machine. By default this + is "datastore1". This only has an effect if `remote_type` is enabled. + +* `remote_host` (string) - The host of the remote machine used for access. + This is only required if `remote_type` is enabled. + +* `remote_password` (string) - The SSH password for the user used to + access the remote machine. By default this is empty. This only has an + effect if `remote_type` is enabled. + +* `remote_username` (string) - The username for the SSH user that will access + the remote machine. This is required if `remote_type` is enabled. + +* `skip_compaction` (bool) - VMware-created disks are defragmented + and compacted at the end of the build process using `vmware-vdiskmanager`. + In certain rare cases, this might actually end up making the resulting disks + slightly larger. If you find this to be the case, you can disable compaction + using this configuration value. + +* `shutdown_command` (string) - The command to use to gracefully shut down + the machine once all the provisioning is done. By default this is an empty + string, which tells Packer to just forcefully shut down the machine. + +* `shutdown_timeout` (string) - The amount of time to wait after executing + the `shutdown_command` for the virtual machine to actually shut down. + If it doesn't shut down in this time, it is an error. By default, the timeout + is "5m", or five minutes. + +* `ssh_key_path` (string) - Path to a private key to use for authenticating + with SSH. By default this is not set (key-based auth won't be used). + The associated public key is expected to already be configured on the + VM being prepared by some other process (kickstart, etc.). + +* `ssh_password` (string) - The password for `ssh_username` to use to + authenticate with SSH. By default this is the empty string. + +* `ssh_port` (int) - The port that SSH will listen on within the virtual + machine. By default this is 22. + +* `ssh_skip_request_pty` (bool) - If true, a pty will not be requested as + part of the SSH connection. By default, this is "false", so a pty + _will_ be requested. + +* `ssh_wait_timeout` (string) - The duration to wait for SSH to become + available. By default this is "20m", or 20 minutes. Note that this should + be quite long since the timer begins as soon as the virtual machine is booted. + +* `tools_upload_flavor` (string) - The flavor of the VMware Tools ISO to + upload into the VM. Valid values are "darwin", "linux", and "windows". + By default, this is empty, which means VMware tools won't be uploaded. + +* `tools_upload_path` (string) - The path in the VM to upload the VMware + tools. This only takes effect if `tools_upload_flavor` is non-empty. + This is a [configuration template](/docs/templates/configuration-templates.html) + that has a single valid variable: `Flavor`, which will be the value of + `tools_upload_flavor`. By default the upload path is set to + `{{.Flavor}}.iso`. + +* `vm_name` (string) - This is the name of the VMX file for the new virtual + machine, without the file extension. By default this is "packer-BUILDNAME", + where "BUILDNAME" is the name of the build. + +* `vmdk_name` (string) - The filename of the virtual disk that'll be created, + without the extension. This defaults to "packer". + +* `vmx_data` (object, string keys and string values) - Arbitrary key/values + to enter into the virtual machine VMX file. This is for advanced users + who want to set properties such as memory, CPU, etc. + +* `vnc_port_min` and `vnc_port_max` (int) - The minimum and maximum port to + use for VNC access to the virtual machine. The builder uses VNC to type + the initial `boot_command`. Because Packer generally runs in parallel, Packer + uses a randomly chosen port in this range that appears available. By default + this is 5900 to 6000. The minimum and maximum ports are inclusive. + +* `vmx_template_path` (string) - Path to a + [configuration template](/docs/templates/configuration-templates.html) that + defines the contents of the virtual machine VMX file for VMware. This is + for **advanced users only** as this can render the virtual machine + non-functional. See below for more information. For basic VMX modifications, + try `vmx_data` first. + +## Boot Command + +The `boot_command` configuration is very important: it specifies the keys +to type when the virtual machine is first booted in order to start the +OS installer. This command is typed after `boot_wait`, which gives the +virtual machine some time to actually load the ISO. + +As documented above, the `boot_command` is an array of strings. The +strings are all typed in sequence. It is an array only to improve readability +within the template. + +The boot command is "typed" character for character over a VNC connection +to the machine, simulating a human actually typing the keyboard. There are +a set of special keys available. If these are in your boot command, they +will be replaced by the proper key: + +* `` and `` - Simulates an actual "enter" or "return" keypress. + +* `` - Simulates pressing the escape key. + +* `` - Simulates pressing the tab key. + +* `` `` `` - Adds a 1, 5 or 10 second pause before sending any additional keys. This + is useful if you have to generally wait for the UI to update before typing more. + +In addition to the special keys, each command to type is treated as a +[configuration template](/docs/templates/configuration-templates.html). +The available variables are: + +* `HTTPIP` and `HTTPPort` - The IP and port, respectively of an HTTP server + that is started serving the directory specified by the `http_directory` + configuration parameter. If `http_directory` isn't specified, these will + be blank! + +Example boot command. This is actually a working boot command used to start +an Ubuntu 12.04 installer: + +
+[
+  "<esc><esc><enter><wait>",
+  "/install/vmlinuz noapic ",
+  "preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg ",
+  "debian-installer=en_US auto locale=en_US kbd-chooser/method=us ",
+  "hostname={{ .Name }} ",
+  "fb=false debconf/frontend=noninteractive ",
+  "keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ",
+  "keyboard-configuration/variant=USA console-setup/ask_detect=false ",
+  "initrd=/install/initrd.gz -- <enter>"
+]
+
+ +## VMX Template + +The heart of a VMware machine is the "vmx" file. This contains all the +virtual hardware metadata necessary for the VM to function. Packer by default +uses a [safe, flexible VMX file](https://github.com/mitchellh/packer/blob/20541a7eda085aa5cf35bfed5069592ca49d106e/builder/vmware/step_create_vmx.go#L84). +But for advanced users, this template can be customized. This allows +Packer to build virtual machines of effectively any guest operating system +type. + +
+

+This is an advanced feature. Modifying the VMX template +can easily cause your virtual machine to not boot properly. Please only +modify the template if you know what you're doing. +

+
+ +Within the template, a handful of variables are available so that your +template can continue working with the rest of the Packer machinery. Using +these variables isn't required, however. + +* `Name` - The name of the virtual machine. +* `GuestOS` - The VMware-valid guest OS type. +* `DiskName` - The filename (without the suffix) of the main virtual disk. +* `ISOPath` - The path to the ISO to use for the OS installation. + +## Building on a Remote vSphere Hypervisor + +In addition to using the desktop products of VMware locally to build +virtual machines, Packer can use a remote VMware Hypervisor to build +the virtual machine. + +When using a remote VMware Hypervisor, the builder still downloads the +ISO and various files locally, and uploads these to the remote machine. +Packer currently uses SSH to communicate to the ESXi machine rather than +the vSphere API. At some point, the vSphere API may be used. + +To use a remote VMware vSphere Hypervisor to build your virtual machine, +fill in the required `remote_*` configurations: + +* `remote_type` - This must be set to "esx5". + +* `remote_host` - The host of the remote machine. + +Additionally, there are some optional configurations that you'll likely +have to modify as well: + +* `remote_datastore` - The path to the datastore where the VM will be + stored on the ESXi machine. + +* `remote_username` - The SSH username used to access the remote machine. + +* `remote_password` - The SSH password for access to the remote machine. diff --git a/website/source/docs/builders/vmware-vmx.html.markdown b/website/source/docs/builders/vmware-vmx.html.markdown new file mode 100644 index 000000000..e62375514 --- /dev/null +++ b/website/source/docs/builders/vmware-vmx.html.markdown @@ -0,0 +1,121 @@ +--- +layout: "docs" +page_title: "VMware Builder from VMX" +--- + +# VMware Builder (from VMX) + +Type: `vmware-vmx` + +This VMware builder is able to create VMware virtual machines from an +existing VMware virtual machine (a VMX file). It currently +supports building virtual machines on hosts running +[VMware Fusion](http://www.vmware.com/products/fusion/overview.html) for OS X, +[VMware Workstation](http://www.vmware.com/products/workstation/overview.html) +for Linux and Windows, and +[VMware Player](http://www.vmware.com/products/player/) on Linux. + +The builder builds a virtual machine by cloning the VMX file using +the clone capabilities introduced in VMware Fusion 6, Workstation 10, +and Player 6. After cloning the VM, it provisions software within the +new machine, shuts it down, and compacts the disks. The resulting folder +contains a new VMware virtual machine. + +## Basic Example + +Here is an example. This example is fully functional as long as the source +path points to a real VMX file with the proper settings: + +
+{
+  "type": "vmware-vmx",
+  "source_path": "/path/to/a/vm.vmx",
+  "ssh_username": "root",
+  "ssh_password": "root",
+  "shutdown_command": "shutdown -P now"
+}
+
+ +## Configuration Reference + +There are many configuration options available for the VMware builder. +They are organized below into two categories: required and optional. Within +each category, the available options are alphabetized and described. + +Required: + +* `source_path` (string) - Path to the source VMX file to clone. + +* `ssh_username` (string) - The username to use to SSH into the machine + once the OS is installed. + +Optional: + +* `boot_wait` (string) - The time to wait after booting the initial virtual + machine before typing the `boot_command`. The value of this should be + a duration. Examples are "5s" and "1m30s" which will cause Packer to wait + five seconds and one minute 30 seconds, respectively. If this isn't specified, + the default is 10 seconds. + +* `floppy_files` (array of strings) - A list of files to put onto a floppy + disk that is attached when the VM is booted for the first time. This is + most useful for unattended Windows installs, which look for an + `Autounattend.xml` file on removable media. By default no floppy will + be attached. The files listed in this configuration will all be put + into the root directory of the floppy disk; sub-directories are not supported. + +* `headless` (bool) - Packer defaults to building VMware + virtual machines by launching a GUI that shows the console of the + machine being built. When this value is set to true, the machine will + start without a console. For VMware machines, Packer will output VNC + connection information in case you need to connect to the console to + debug the build process. + +* `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. + +* `skip_compaction` (bool) - VMware-created disks are defragmented + and compacted at the end of the build process using `vmware-vdiskmanager`. + In certain rare cases, this might actually end up making the resulting disks + slightly larger. If you find this to be the case, you can disable compaction + using this configuration value. + +* `shutdown_command` (string) - The command to use to gracefully shut down + the machine once all the provisioning is done. By default this is an empty + string, which tells Packer to just forcefully shut down the machine. + +* `shutdown_timeout` (string) - The amount of time to wait after executing + the `shutdown_command` for the virtual machine to actually shut down. + If it doesn't shut down in this time, it is an error. By default, the timeout + is "5m", or five minutes. + +* `ssh_key_path` (string) - Path to a private key to use for authenticating + with SSH. By default this is not set (key-based auth won't be used). + The associated public key is expected to already be configured on the + VM being prepared by some other process (kickstart, etc.). + +* `ssh_password` (string) - The password for `ssh_username` to use to + authenticate with SSH. By default this is the empty string. + +* `ssh_port` (int) - The port that SSH will listen on within the virtual + machine. By default this is 22. + +* `ssh_skip_request_pty` (bool) - If true, a pty will not be requested as + part of the SSH connection. By default, this is "false", so a pty + _will_ be requested. + +* `ssh_wait_timeout` (string) - The duration to wait for SSH to become + available. By default this is "20m", or 20 minutes. Note that this should + be quite long since the timer begins as soon as the virtual machine is booted. + +* `vm_name` (string) - This is the name of the VMX file for the new virtual + machine, without the file extension. By default this is "packer-BUILDNAME", + where "BUILDNAME" is the name of the build. + +* `vmx_data` (object, string keys and string values) - Arbitrary key/values + to enter into the virtual machine VMX file. This is for advanced users + who want to set properties such as memory, CPU, etc. diff --git a/website/source/docs/builders/vmware.html.markdown b/website/source/docs/builders/vmware.html.markdown index f59c92c18..c25172efc 100644 --- a/website/source/docs/builders/vmware.html.markdown +++ b/website/source/docs/builders/vmware.html.markdown @@ -1,331 +1,25 @@ --- layout: "docs" +page_title: "VMware Builder" --- # VMware Builder -Type: `vmware` - -The VMware builder is able to create VMware virtual machines. It currently -supports building virtual machines on hosts running -[VMware Fusion](http://www.vmware.com/products/fusion/overview.html) for OS X, -[VMware Workstation](http://www.vmware.com/products/workstation/overview.html) -for Linux and Windows, and -[VMware Player](http://www.vmware.com/products/player/) on Linux. It can -also build machines directly on -[VMware vSphere Hypervisor](http://www.vmware.com/products/vsphere-hypervisor/) -using SSH as opposed to the vSphere API. - -The builder builds a virtual machine by creating a new virtual machine -from scratch, booting it, installing an OS, provisioning software within -the OS, then shutting it down. The result of the VMware builder is a directory -containing all the files necessary to run the virtual machine. - -## Basic Example - -Here is a basic example. This example is not functional. It will start the -OS installer but then fail because we don't provide the preseed file for -Ubuntu to self-install. Still, the example serves to show the basic configuration: - -
-{
-  "type": "vmware",
-  "iso_url": "http://old-releases.ubuntu.com/releases/precise/ubuntu-12.04.2-server-amd64.iso",
-  "iso_checksum": "af5f788aee1b32c4b2634734309cc9e9",
-  "iso_checksum_type": "md5",
-  "ssh_username": "packer",
-  "ssh_wait_timeout": "30s",
-  "shutdown_command": "shutdown -P now"
-}
-
- -## Configuration Reference - -There are many configuration options available for the VMware builder. -They are organized below into two categories: required and optional. Within -each category, the available options are alphabetized and described. - -Required: - -* `iso_checksum` (string) - The checksum for the OS ISO file. Because ISO - files are so large, this is required and Packer will verify it prior - to booting a virtual machine with the ISO attached. The type of the - checksum is specified with `iso_checksum_type`, documented below. - -* `iso_checksum_type` (string) - The type of the checksum specified in - `iso_checksum`. Valid values are "md5", "sha1", "sha256", or "sha512" currently. - -* `iso_url` (string) - A URL to the ISO containing the installation image. - This URL can be either an HTTP URL or a file URL (or path to a file). - If this is an HTTP URL, Packer will download it and cache it between - runs. - -* `ssh_username` (string) - The username to use to SSH into the machine - once the OS is installed. - -Optional: - -* `boot_command` (array of strings) - This is an array of commands to type - when the virtual machine is firsted booted. The goal of these commands should - be to type just enough to initialize the operating system installer. Special - keys can be typed as well, and are covered in the section below on the boot - command. If this is not specified, it is assumed the installer will start - itself. - -* `boot_wait` (string) - The time to wait after booting the initial virtual - machine before typing the `boot_command`. The value of this should be - a duration. Examples are "5s" and "1m30s" which will cause Packer to wait - five seconds and one minute 30 seconds, respectively. If this isn't specified, - the default is 10 seconds. - -* `disk_size` (int) - The size of the hard disk for the VM in megabytes. - The builder uses expandable, not fixed-size virtual hard disks, so the - actual file representing the disk will not use the full size unless it is full. - By default this is set to 40,000 (40 GB). - -* `disk_type_id` (string) - The type of VMware virtual disk to create. - The default is "1", which corresponds to a growable virtual disk split in - 2GB files. This option is for advanced usage, modify only if you - know what you're doing. For more information, please consult the - [Virtual Disk Manager User's Guide](http://www.vmware.com/pdf/VirtualDiskManager.pdf) - for desktop VMware clients. For ESXi, refer to the proper ESXi documentation. - -* `floppy_files` (array of strings) - A list of files to put onto a floppy - disk that is attached when the VM is booted for the first time. This is - most useful for unattended Windows installs, which look for an - `Autounattend.xml` file on removable media. By default no floppy will - be attached. The files listed in this configuration will all be put - into the root directory of the floppy disk; sub-directories are not supported. - -* `guest_os_type` (string) - The guest OS type being installed. This will be - set in the VMware VMX. By default this is "other". By specifying a more specific - OS type, VMware may perform some optimizations or virtual hardware changes - to better support the operating system running in the virtual machine. - -* `headless` (bool) - Packer defaults to building VMware - virtual machines by launching a GUI that shows the console of the - machine being built. When this value is set to true, the machine will - start without a console. For VMware machines, Packer will output VNC - connection information in case you need to connect to the console to - debug the build process. - -* `http_directory` (string) - Path to a directory to serve using an HTTP - server. The files in this directory will be available over HTTP that will - be requestable from the virtual machine. This is useful for hosting - kickstart files and so on. By default this is "", which means no HTTP - server will be started. The address and port of the HTTP server will be - available as variables in `boot_command`. This is covered in more detail - below. - -* `http_port_min` and `http_port_max` (int) - These are the minimum and - maximum port to use for the HTTP server started to serve the `http_directory`. - Because Packer often runs in parallel, Packer will choose a randomly available - port in this range to run the HTTP server. If you want to force the HTTP - server to be on one port, make this minimum and maximum port the same. - By default the values are 8000 and 9000, respectively. - -* `iso_urls` (array of strings) - Multiple URLs for the ISO to download. - Packer will try these in order. If anything goes wrong attempting to download - or while downloading a single URL, it will move on to the next. All URLs - must point to the same file (same checksum). By default this is empty - and `iso_url` is used. Only one of `iso_url` or `iso_urls` can be specified. - -* `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. - -* `remote_type` (string) - The type of remote machine that will be used to - build this VM rather than a local desktop product. The only value accepted - for this currently is "esx5". If this is not set, a desktop product will be - used. By default, this is not set. - -* `remote_datastore` (string) - The path to the datastore where the resulting - VM will be stored when it is built on the remote machine. By default this - is "datastore1". This only has an effect if `remote_type` is enabled. - -* `remote_host` (string) - The host of the remote machine used for access. - This is only required if `remote_type` is enabled. - -* `remote_password` (string) - The SSH password for the user used to - access the remote machine. By default this is empty. This only has an - effect if `remote_type` is enabled. - -* `remote_username` (string) - The username for the SSH user that will access - the remote machine. This is required if `remote_type` is enabled. - -* `skip_compaction` (bool) - VMware-created disks are defragmented - and compacted at the end of the build process using `vmware-vdiskmanager`. - In certain rare cases, this might actually end up making the resulting disks - slightly larger. If you find this to be the case, you can disable compaction - using this configuration value. - -* `shutdown_command` (string) - The command to use to gracefully shut down - the machine once all the provisioning is done. By default this is an empty - string, which tells Packer to just forcefully shut down the machine. - -* `shutdown_timeout` (string) - The amount of time to wait after executing - the `shutdown_command` for the virtual machine to actually shut down. - If it doesn't shut down in this time, it is an error. By default, the timeout - is "5m", or five minutes. - -* `ssh_key_path` (string) - Path to a private key to use for authenticating - with SSH. By default this is not set (key-based auth won't be used). - The associated public key is expected to already be configured on the - VM being prepared by some other process (kickstart, etc.). - -* `ssh_password` (string) - The password for `ssh_username` to use to - authenticate with SSH. By default this is the empty string. - -* `ssh_port` (int) - The port that SSH will listen on within the virtual - machine. By default this is 22. - -* `ssh_skip_request_pty` (bool) - If true, a pty will not be requested as - part of the SSH connection. By default, this is "false", so a pty - _will_ be requested. - -* `ssh_wait_timeout` (string) - The duration to wait for SSH to become - available. By default this is "20m", or 20 minutes. Note that this should - be quite long since the timer begins as soon as the virtual machine is booted. - -* `tools_upload_flavor` (string) - The flavor of the VMware Tools ISO to - upload into the VM. Valid values are "darwin", "linux", and "windows". - By default, this is empty, which means VMware tools won't be uploaded. - -* `tools_upload_path` (string) - The path in the VM to upload the VMware - tools. This only takes effect if `tools_upload_flavor` is non-empty. - This is a [configuration template](/docs/templates/configuration-templates.html) - that has a single valid variable: `Flavor`, which will be the value of - `tools_upload_flavor`. By default the upload path is set to - `{{.Flavor}}.iso`. - -* `vm_name` (string) - This is the name of the VMX file for the new virtual - machine, without the file extension. By default this is "packer-BUILDNAME", - where "BUILDNAME" is the name of the build. - -* `vmdk_name` (string) - The filename of the virtual disk that'll be created, - without the extension. This defaults to "packer". - -* `vmx_data` (object, string keys and string values) - Arbitrary key/values - to enter into the virtual machine VMX file. This is for advanced users - who want to set properties such as memory, CPU, etc. - -* `vnc_port_min` and `vnc_port_max` (int) - The minimum and maximum port to - use for VNC access to the virtual machine. The builder uses VNC to type - the initial `boot_command`. Because Packer generally runs in parallel, Packer - uses a randomly chosen port in this range that appears available. By default - this is 5900 to 6000. The minimum and maximum ports are inclusive. - -* `vmx_template_path` (string) - Path to a - [configuration template](/docs/templates/configuration-templates.html) that - defines the contents of the virtual machine VMX file for VMware. This is - for **advanced users only** as this can render the virtual machine - non-functional. See below for more information. For basic VMX modifications, - try `vmx_data` first. - -## Boot Command - -The `boot_command` configuration is very important: it specifies the keys -to type when the virtual machine is first booted in order to start the -OS installer. This command is typed after `boot_wait`, which gives the -virtual machine some time to actually load the ISO. - -As documented above, the `boot_command` is an array of strings. The -strings are all typed in sequence. It is an array only to improve readability -within the template. - -The boot command is "typed" character for character over a VNC connection -to the machine, simulating a human actually typing the keyboard. There are -a set of special keys available. If these are in your boot command, they -will be replaced by the proper key: - -* `` and `` - Simulates an actual "enter" or "return" keypress. - -* `` - Simulates pressing the escape key. - -* `` - Simulates pressing the tab key. - -* `` `` `` - Adds a 1, 5 or 10 second pause before sending any additional keys. This - is useful if you have to generally wait for the UI to update before typing more. - -In addition to the special keys, each command to type is treated as a -[configuration template](/docs/templates/configuration-templates.html). -The available variables are: - -* `HTTPIP` and `HTTPPort` - The IP and port, respectively of an HTTP server - that is started serving the directory specified by the `http_directory` - configuration parameter. If `http_directory` isn't specified, these will - be blank! - -Example boot command. This is actually a working boot command used to start -an Ubuntu 12.04 installer: - -
-[
-  "<esc><esc><enter><wait>",
-  "/install/vmlinuz noapic ",
-  "preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg ",
-  "debian-installer=en_US auto locale=en_US kbd-chooser/method=us ",
-  "hostname={{ .Name }} ",
-  "fb=false debconf/frontend=noninteractive ",
-  "keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ",
-  "keyboard-configuration/variant=USA console-setup/ask_detect=false ",
-  "initrd=/install/initrd.gz -- <enter>"
-]
-
- -## VMX Template - -The heart of a VMware machine is the "vmx" file. This contains all the -virtual hardware metadata necessary for the VM to function. Packer by default -uses a [safe, flexible VMX file](https://github.com/mitchellh/packer/blob/20541a7eda085aa5cf35bfed5069592ca49d106e/builder/vmware/step_create_vmx.go#L84). -But for advanced users, this template can be customized. This allows -Packer to build virtual machines of effectively any guest operating system -type. - -
-

-This is an advanced feature. Modifying the VMX template -can easily cause your virtual machine to not boot properly. Please only -modify the template if you know what you're doing. -

-
- -Within the template, a handful of variables are available so that your -template can continue working with the rest of the Packer machinery. Using -these variables isn't required, however. - -* `Name` - The name of the virtual machine. -* `GuestOS` - The VMware-valid guest OS type. -* `DiskName` - The filename (without the suffix) of the main virtual disk. -* `ISOPath` - The path to the ISO to use for the OS installation. - -## Building on a Remote vSphere Hypervisor - -In addition to using the desktop products of VMware locally to build -virtual machines, Packer can use a remote VMware Hypervisor to build -the virtual machine. - -When using a remote VMware Hypervisor, the builder still downloads the -ISO and various files locally, and uploads these to the remote machine. -Packer currently uses SSH to communicate to the ESXi machine rather than -the vSphere API. At some point, the vSphere API may be used. - -To use a remote VMware vSphere Hypervisor to build your virtual machine, -fill in the required `remote_*` configurations: - -* `remote_type` - This must be set to "esx5". - -* `remote_host` - The host of the remote machine. - -Additionally, there are some optional configurations that you'll likely -have to modify as well: - -* `remote_datastore` - The path to the datastore where the VM will be - stored on the ESXi machine. - -* `remote_username` - The SSH username used to access the remote machine. - -* `remote_password` - The SSH password for access to the remote machine. +The VMware builder is able to create VMware virtual machines for use +with any VMware product. + +Packer actually comes with multiple builders able to create VMware +machines, depending on the strategy you want to use to build the image. +Packer supports the following VMware builders: + +* [vmware-iso](/docs/builders/vmware-iso.html) - Starts from + an ISO file, creates a brand new VMware VM, installs an OS, + provisions software within the OS, then exports that machine to create + an image. This is best for people who want to start from scratch. + +* [vmware-vmx](/docs/builders/vmware-vmx.html) - This builder + imports an existing VMware machine (from a VMX file), runs provisioners + on top of that VM, and exports that machine to create an image. + This is best if you have an existing VMware VM you want to use as the + source. As an additional benefit, you can feed the artifact of this + builder back into Packer to iterate on a machine.