From f40a6006fb243e157e90edd91cb5ed47336cc48d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 23 Dec 2013 14:38:54 -0800 Subject: [PATCH] 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 +}