diff --git a/builder/virtualbox/common/shutdown_config.go b/builder/virtualbox/common/shutdown_config.go new file mode 100644 index 000000000..05e5fdfeb --- /dev/null +++ b/builder/virtualbox/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/virtualbox/common/shutdown_config_test.go b/builder/virtualbox/common/shutdown_config_test.go new file mode 100644 index 000000000..b98b3a402 --- /dev/null +++ b/builder/virtualbox/common/shutdown_config_test.go @@ -0,0 +1,41 @@ +package common + +import ( + "testing" +) + +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) + } +} diff --git a/builder/virtualbox/iso/step_shutdown.go b/builder/virtualbox/common/step_shutdown.go similarity index 69% rename from builder/virtualbox/iso/step_shutdown.go rename to builder/virtualbox/common/step_shutdown.go index e1f9d5028..3469dc7da 100644 --- a/builder/virtualbox/iso/step_shutdown.go +++ b/builder/virtualbox/common/step_shutdown.go @@ -1,10 +1,9 @@ -package iso +package common import ( "errors" "fmt" "github.com/mitchellh/multistep" - vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common" "github.com/mitchellh/packer/packer" "log" "time" @@ -15,26 +14,27 @@ import ( // // Uses: // communicator packer.Communicator -// config *config // driver Driver // ui packer.Ui // vmName 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) - driver := state.Get("driver").(vboxcommon.Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) vmName := state.Get("vmName").(string) - if config.ShutdownCommand != "" { + if s.Command != "" { ui.Say("Gracefully halting virtual machine...") - log.Printf("Executing shutdown command: %s", config.ShutdownCommand) - cmd := &packer.RemoteCmd{Command: config.ShutdownCommand} + log.Printf("Executing shutdown command: %s", s.Command) + cmd := &packer.RemoteCmd{Command: s.Command} if err := cmd.StartWithUi(comm, ui); err != nil { err := fmt.Errorf("Failed to send shutdown command: %s", err) state.Put("error", err) @@ -43,8 +43,8 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { } // 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(vmName) if !running { @@ -75,4 +75,4 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionContinue } -func (s *stepShutdown) Cleanup(state multistep.StateBag) {} +func (s *StepShutdown) Cleanup(state multistep.StateBag) {} diff --git a/builder/virtualbox/iso/builder.go b/builder/virtualbox/iso/builder.go index e5430bc1c..25c4aee05 100644 --- a/builder/virtualbox/iso/builder.go +++ b/builder/virtualbox/iso/builder.go @@ -31,6 +31,7 @@ type config struct { common.PackerConfig `mapstructure:",squash"` vboxcommon.FloppyConfig `mapstructure:",squash"` vboxcommon.OutputConfig `mapstructure:",squash"` + vboxcommon.ShutdownConfig `mapstructure:",squash"` vboxcommon.SSHConfig `mapstructure:",squash"` vboxcommon.VBoxManageConfig `mapstructure:",squash"` @@ -50,17 +51,14 @@ type config struct { ISOChecksum string `mapstructure:"iso_checksum"` ISOChecksumType string `mapstructure:"iso_checksum_type"` ISOUrls []string `mapstructure:"iso_urls"` - ShutdownCommand string `mapstructure:"shutdown_command"` VBoxVersionFile string `mapstructure:"virtualbox_version_file"` VMName string `mapstructure:"vm_name"` - RawBootWait string `mapstructure:"boot_wait"` - RawSingleISOUrl string `mapstructure:"iso_url"` - RawShutdownTimeout string `mapstructure:"shutdown_timeout"` + RawBootWait string `mapstructure:"boot_wait"` + RawSingleISOUrl string `mapstructure:"iso_url"` - bootWait time.Duration `` - shutdownTimeout time.Duration `` - tpl *packer.ConfigTemplate + bootWait time.Duration `` + tpl *packer.ConfigTemplate } func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { @@ -138,12 +136,10 @@ 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, "virtualbox_version_file": &b.config.VBoxVersionFile, "vm_name": &b.config.VMName, "format": &b.config.Format, "boot_wait": &b.config.RawBootWait, - "shutdown_timeout": &b.config.RawShutdownTimeout, } for n, ptr := range templates { @@ -264,16 +260,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs, fmt.Errorf("Failed parsing boot_wait: %s", err)) } - 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)) - } - // Warnings if b.config.ShutdownCommand == "" { warnings = append(warnings, @@ -336,7 +322,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe new(stepUploadVersion), new(stepUploadGuestAdditions), new(common.StepProvision), - new(stepShutdown), + &vboxcommon.StepShutdown{ + Command: b.config.ShutdownCommand, + Timeout: b.config.ShutdownTimeout, + }, new(stepRemoveDevices), new(stepExport), } diff --git a/builder/virtualbox/iso/builder_test.go b/builder/virtualbox/iso/builder_test.go index b8e2469a8..7e7bf4ead 100644 --- a/builder/virtualbox/iso/builder_test.go +++ b/builder/virtualbox/iso/builder_test.go @@ -532,47 +532,6 @@ func TestBuilderPrepare_ISOUrl(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_VBoxVersionFile(t *testing.T) { var b Builder config := testConfig() diff --git a/builder/virtualbox/ovf/builder.go b/builder/virtualbox/ovf/builder.go index 1afcdc1c5..765da1c94 100644 --- a/builder/virtualbox/ovf/builder.go +++ b/builder/virtualbox/ovf/builder.go @@ -75,8 +75,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe new(stepUploadGuestAdditions), */ new(common.StepProvision), + &vboxcommon.StepShutdown{ + Command: b.config.ShutdownCommand, + Timeout: b.config.ShutdownTimeout, + }, /* - new(stepShutdown), new(stepRemoveDevices), new(stepExport), */ diff --git a/builder/virtualbox/ovf/config.go b/builder/virtualbox/ovf/config.go index 3ac1c80e0..999173ac0 100644 --- a/builder/virtualbox/ovf/config.go +++ b/builder/virtualbox/ovf/config.go @@ -12,6 +12,7 @@ type Config struct { vboxcommon.FloppyConfig `mapstructure:",squash"` vboxcommon.OutputConfig `mapstructure:",squash"` vboxcommon.SSHConfig `mapstructure:",squash"` + vboxcommon.ShutdownConfig `mapstructure:",squash"` vboxcommon.VBoxManageConfig `mapstructure:",squash"` tpl *packer.ConfigTemplate @@ -37,10 +38,18 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { errs = packer.MultiErrorAppend(errs, c.SSHConfig.Prepare(c.tpl)...) errs = packer.MultiErrorAppend(errs, c.VBoxManageConfig.Prepare(c.tpl)...) + // 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 { - return nil, nil, errs + return nil, warnings, errs } - return c, nil, nil + return c, warnings, nil }