diff --git a/builder/vmware/common/step_prepare_tools.go b/builder/vmware/common/step_prepare_tools.go index 7c9f992a3..bd64f8885 100644 --- a/builder/vmware/common/step_prepare_tools.go +++ b/builder/vmware/common/step_prepare_tools.go @@ -11,6 +11,7 @@ import ( type StepPrepareTools struct { RemoteType string ToolsUploadFlavor string + ToolsSourcePath string } func (c *StepPrepareTools) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { @@ -20,11 +21,15 @@ func (c *StepPrepareTools) Run(ctx context.Context, state multistep.StateBag) mu return multistep.ActionContinue } - if c.ToolsUploadFlavor == "" { + if c.ToolsUploadFlavor == "" && c.ToolsSourcePath == "" { return multistep.ActionContinue } - path := driver.ToolsIsoPath(c.ToolsUploadFlavor) + path := c.ToolsSourcePath + if path == "" { + path = driver.ToolsIsoPath(c.ToolsUploadFlavor) + } + if _, err := os.Stat(path); err != nil { state.Put("error", fmt.Errorf( "Couldn't find VMware tools for '%s'! VMware often downloads these\n"+ diff --git a/builder/vmware/common/step_prepare_tools_test.go b/builder/vmware/common/step_prepare_tools_test.go index cddf0ffba..e70f7d43b 100644 --- a/builder/vmware/common/step_prepare_tools_test.go +++ b/builder/vmware/common/step_prepare_tools_test.go @@ -114,3 +114,65 @@ func TestStepPrepareTools_nonExist(t *testing.T) { t.Fatal("should NOT have tools_upload_source") } } + +func TestStepPrepareTools_SourcePath(t *testing.T) { + state := testState(t) + step := &StepPrepareTools{ + RemoteType: "", + ToolsSourcePath: "/path/to/tool.iso", + } + + driver := state.Get("driver").(*DriverMock) + + // Mock results + driver.ToolsIsoPathResult = "foo" + + // Test the run + if action := step.Run(context.Background(), state); action != multistep.ActionHalt { + t.Fatalf("Should have failed when stat failed %#v", action) + } + if _, ok := state.GetOk("error"); !ok { + t.Fatal("should have error") + } + + // Test the driver + if driver.ToolsIsoPathCalled { + t.Fatal("tools iso path should not be called when ToolsSourcePath is set") + } + + // Test the resulting state + if _, ok := state.GetOk("tools_upload_source"); ok { + t.Fatal("should NOT have tools_upload_source") + } +} + +func TestStepPrepareTools_SourcePath_exists(t *testing.T) { + state := testState(t) + step := &StepPrepareTools{ + RemoteType: "", + ToolsSourcePath: "./step_prepare_tools.go", + } + + driver := state.Get("driver").(*DriverMock) + + // Mock results + driver.ToolsIsoPathResult = "foo" + + // Test the run + if action := step.Run(context.Background(), state); action != multistep.ActionContinue { + t.Fatalf("Step should succeed when stat succeeds: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + // Test the driver + if driver.ToolsIsoPathCalled { + t.Fatal("tools iso path should not be called when ToolsSourcePath is set") + } + + // Test the resulting state + if _, ok := state.GetOk("tools_upload_source"); !ok { + t.Fatal("should have tools_upload_source") + } +} diff --git a/builder/vmware/common/tools_config.go b/builder/vmware/common/tools_config.go index 02a54f375..dd37831bf 100644 --- a/builder/vmware/common/tools_config.go +++ b/builder/vmware/common/tools_config.go @@ -3,6 +3,8 @@ package common import ( + "fmt" + "github.com/hashicorp/packer/template/interpolate" ) @@ -18,12 +20,22 @@ type ToolsConfig struct { // the upload path is set to `{{.Flavor}}.iso`. This setting is not used // when `remote_type` is `esx5`. ToolsUploadPath string `mapstructure:"tools_upload_path" required:"false"` + // The path on your local machine to fetch the vmware tools from. If this + // is not set but the tools_upload_flavor is set, then Packer will try to + // load the VMWare tools from the VMWare installation directory. + ToolsSourcePath string `mapstructure:"tools_source_path" required:"false"` } func (c *ToolsConfig) Prepare(ctx *interpolate.Context) []error { + errs := []error{} if c.ToolsUploadPath == "" { + if c.ToolsSourcePath != "" && c.ToolsUploadFlavor == "" { + errs = append(errs, fmt.Errorf("If you provide a "+ + "tools_source_path, you must also provide either a "+ + "tools_upload_flavor or a tools_upload_path.")) + } c.ToolsUploadPath = "{{ .Flavor }}.iso" } - return nil + return errs } diff --git a/builder/vmware/common/tools_config_test.go b/builder/vmware/common/tools_config_test.go new file mode 100644 index 000000000..8294661ca --- /dev/null +++ b/builder/vmware/common/tools_config_test.go @@ -0,0 +1,65 @@ +package common + +import ( + "testing" + + "github.com/hashicorp/packer/template/interpolate" +) + +func TestToolsConfigPrepare_Empty(t *testing.T) { + c := &ToolsConfig{} + + errs := c.Prepare(interpolate.NewContext()) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + + if c.ToolsUploadPath != "{{ .Flavor }}.iso" { + t.Fatal("should have defaulted tools upload path") + } +} + +func TestToolsConfigPrepare_SetUploadPath(t *testing.T) { + c := &ToolsConfig{ + ToolsUploadPath: "path/to/tools.iso", + } + + errs := c.Prepare(interpolate.NewContext()) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + + if c.ToolsUploadPath != "path/to/tools.iso" { + t.Fatal("should have used given tools upload path") + } +} + +func TestToolsConfigPrepare_ErrorIfOnlySource(t *testing.T) { + c := &ToolsConfig{ + ToolsSourcePath: "path/to/tools.iso", + } + + errs := c.Prepare(interpolate.NewContext()) + if len(errs) != 1 { + t.Fatalf("Should have received an error because the flavor and " + + "upload path aren't set") + } +} + +func TestToolsConfigPrepare_SourceSuccess(t *testing.T) { + for _, c := range []*ToolsConfig{ + &ToolsConfig{ + ToolsSourcePath: "path/to/tools.iso", + ToolsUploadPath: "partypath.iso", + }, + &ToolsConfig{ + ToolsSourcePath: "path/to/tools.iso", + ToolsUploadFlavor: "linux", + }, + } { + errs := c.Prepare(interpolate.NewContext()) + if len(errs) != 0 { + t.Fatalf("Should not have received an error") + } + } +} diff --git a/builder/vmware/iso/config.hcl2spec.go b/builder/vmware/iso/config.hcl2spec.go index c51c87b2b..8a726b193 100644 --- a/builder/vmware/iso/config.hcl2spec.go +++ b/builder/vmware/iso/config.hcl2spec.go @@ -119,6 +119,7 @@ type FlatConfig struct { SSHSkipRequestPty *bool `mapstructure:"ssh_skip_request_pty" cty:"ssh_skip_request_pty" hcl:"ssh_skip_request_pty"` ToolsUploadFlavor *string `mapstructure:"tools_upload_flavor" required:"false" cty:"tools_upload_flavor" hcl:"tools_upload_flavor"` ToolsUploadPath *string `mapstructure:"tools_upload_path" required:"false" cty:"tools_upload_path" hcl:"tools_upload_path"` + ToolsSourcePath *string `mapstructure:"tools_source_path" required:"false" cty:"tools_source_path" hcl:"tools_source_path"` VMXData map[string]string `mapstructure:"vmx_data" required:"false" cty:"vmx_data" hcl:"vmx_data"` VMXDataPost map[string]string `mapstructure:"vmx_data_post" required:"false" cty:"vmx_data_post" hcl:"vmx_data_post"` VMXRemoveEthernet *bool `mapstructure:"vmx_remove_ethernet_interfaces" required:"false" cty:"vmx_remove_ethernet_interfaces" hcl:"vmx_remove_ethernet_interfaces"` @@ -263,6 +264,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "ssh_skip_request_pty": &hcldec.AttrSpec{Name: "ssh_skip_request_pty", Type: cty.Bool, Required: false}, "tools_upload_flavor": &hcldec.AttrSpec{Name: "tools_upload_flavor", Type: cty.String, Required: false}, "tools_upload_path": &hcldec.AttrSpec{Name: "tools_upload_path", Type: cty.String, Required: false}, + "tools_source_path": &hcldec.AttrSpec{Name: "tools_source_path", Type: cty.String, Required: false}, "vmx_data": &hcldec.AttrSpec{Name: "vmx_data", Type: cty.Map(cty.String), Required: false}, "vmx_data_post": &hcldec.AttrSpec{Name: "vmx_data_post", Type: cty.Map(cty.String), Required: false}, "vmx_remove_ethernet_interfaces": &hcldec.AttrSpec{Name: "vmx_remove_ethernet_interfaces", Type: cty.Bool, Required: false}, diff --git a/builder/vmware/vmx/config.hcl2spec.go b/builder/vmware/vmx/config.hcl2spec.go index 71216d690..bc4ce8802 100644 --- a/builder/vmware/vmx/config.hcl2spec.go +++ b/builder/vmware/vmx/config.hcl2spec.go @@ -104,6 +104,7 @@ type FlatConfig struct { SSHSkipRequestPty *bool `mapstructure:"ssh_skip_request_pty" cty:"ssh_skip_request_pty" hcl:"ssh_skip_request_pty"` ToolsUploadFlavor *string `mapstructure:"tools_upload_flavor" required:"false" cty:"tools_upload_flavor" hcl:"tools_upload_flavor"` ToolsUploadPath *string `mapstructure:"tools_upload_path" required:"false" cty:"tools_upload_path" hcl:"tools_upload_path"` + ToolsSourcePath *string `mapstructure:"tools_source_path" required:"false" cty:"tools_source_path" hcl:"tools_source_path"` VMXData map[string]string `mapstructure:"vmx_data" required:"false" cty:"vmx_data" hcl:"vmx_data"` VMXDataPost map[string]string `mapstructure:"vmx_data_post" required:"false" cty:"vmx_data_post" hcl:"vmx_data_post"` VMXRemoveEthernet *bool `mapstructure:"vmx_remove_ethernet_interfaces" required:"false" cty:"vmx_remove_ethernet_interfaces" hcl:"vmx_remove_ethernet_interfaces"` @@ -229,6 +230,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "ssh_skip_request_pty": &hcldec.AttrSpec{Name: "ssh_skip_request_pty", Type: cty.Bool, Required: false}, "tools_upload_flavor": &hcldec.AttrSpec{Name: "tools_upload_flavor", Type: cty.String, Required: false}, "tools_upload_path": &hcldec.AttrSpec{Name: "tools_upload_path", Type: cty.String, Required: false}, + "tools_source_path": &hcldec.AttrSpec{Name: "tools_source_path", Type: cty.String, Required: false}, "vmx_data": &hcldec.AttrSpec{Name: "vmx_data", Type: cty.Map(cty.String), Required: false}, "vmx_data_post": &hcldec.AttrSpec{Name: "vmx_data_post", Type: cty.Map(cty.String), Required: false}, "vmx_remove_ethernet_interfaces": &hcldec.AttrSpec{Name: "vmx_remove_ethernet_interfaces", Type: cty.Bool, Required: false}, diff --git a/website/pages/partials/builder/vmware/common/ToolsConfig-not-required.mdx b/website/pages/partials/builder/vmware/common/ToolsConfig-not-required.mdx index eda25becc..6e3d1b06a 100644 --- a/website/pages/partials/builder/vmware/common/ToolsConfig-not-required.mdx +++ b/website/pages/partials/builder/vmware/common/ToolsConfig-not-required.mdx @@ -10,3 +10,7 @@ `Flavor`, which will be the value of `tools_upload_flavor`. By default the upload path is set to `{{.Flavor}}.iso`. This setting is not used when `remote_type` is `esx5`. + +- `tools_source_path` (string) - The path on your local machine to fetch the vmware tools from. If this + is not set but the tools_upload_flavor is set, then Packer will try to + load the VMWare tools from the VMWare installation directory.