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", } }