diff --git a/builder/virtualbox/common/shutdown_config.go b/builder/virtualbox/common/shutdown_config.go index 5dd70c69b..0a626c9c7 100644 --- a/builder/virtualbox/common/shutdown_config.go +++ b/builder/virtualbox/common/shutdown_config.go @@ -27,6 +27,13 @@ type ShutdownConfig struct { // Error removing floppy controller, you might need to set this to 5m // or so. By default, the delay is 0s or disabled. PostShutdownDelay time.Duration `mapstructure:"post_shutdown_delay" required:"false"` + // Packer normally halts the virtual machine after all provisioners have + // run when no `shutdown_command` is defined. If this is set to `true`, Packer + // *will not* halt the virtual machine but will assume that you will send the stop + // signal yourself through the preseed.cfg or your final provisioner. + // Packer will wait for a default of 5 minutes until the virtual machine is shutdown. + // The timeout can be changed using `shutdown_timeout` option. + DisableShutdown bool `mapstructure:"disable_shutdown" required:"false"` } func (c *ShutdownConfig) Prepare(ctx *interpolate.Context) []error { diff --git a/builder/virtualbox/common/shutdown_config_test.go b/builder/virtualbox/common/shutdown_config_test.go index 206e2f848..e5afd6f0b 100644 --- a/builder/virtualbox/common/shutdown_config_test.go +++ b/builder/virtualbox/common/shutdown_config_test.go @@ -64,3 +64,27 @@ func TestShutdownConfigPrepare_PostShutdownDelay(t *testing.T) { t.Fatalf("bad: %s", c.PostShutdownDelay) } } + +func TestShutdownConfigPrepare_DisableShutdown(t *testing.T) { + var c *ShutdownConfig + var errs []error + + // Test with default value + c = testShutdownConfig() + c.DisableShutdown = false + errs = c.Prepare(interpolate.NewContext()) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + + // Test with a good one + c = testShutdownConfig() + c.DisableShutdown = true + errs = c.Prepare(interpolate.NewContext()) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + if !c.DisableShutdown { + t.Fatalf("bad: %t", c.DisableShutdown) + } +} diff --git a/builder/virtualbox/common/step_shutdown.go b/builder/virtualbox/common/step_shutdown.go index f28e2deef..fcf19b782 100644 --- a/builder/virtualbox/common/step_shutdown.go +++ b/builder/virtualbox/common/step_shutdown.go @@ -23,9 +23,10 @@ import ( // Produces: // type StepShutdown struct { - Command string - Timeout time.Duration - Delay time.Duration + Command string + Timeout time.Duration + Delay time.Duration + DisableShutdown bool } func (s *StepShutdown) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { @@ -34,25 +35,29 @@ func (s *StepShutdown) Run(ctx context.Context, state multistep.StateBag) multis ui := state.Get("ui").(packer.Ui) vmName := state.Get("vmName").(string) - if s.Command != "" { - ui.Say("Gracefully halting virtual machine...") - log.Printf("Executing shutdown command: %s", s.Command) - cmd := &packer.RemoteCmd{Command: s.Command} - if err := cmd.RunWithUi(ctx, comm, ui); err != nil { - err := fmt.Errorf("Failed to send shutdown command: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } + if !s.DisableShutdown { + if s.Command != "" { + ui.Say("Gracefully halting virtual machine...") + log.Printf("Executing shutdown command: %s", s.Command) + cmd := &packer.RemoteCmd{Command: s.Command} + if err := cmd.RunWithUi(ctx, comm, ui); err != nil { + err := fmt.Errorf("Failed to send shutdown command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } - } else { - ui.Say("Halting the virtual machine...") - if err := driver.Stop(vmName); err != nil { - err := fmt.Errorf("Error stopping VM: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt + } else { + ui.Say("Halting the virtual machine...") + if err := driver.Stop(vmName); err != nil { + err := fmt.Errorf("Error stopping VM: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } } + } else { + ui.Say("Automatic shutdown disabled. Please shutdown virtual machine.") } // Wait for the machine to actually shut down @@ -72,7 +77,7 @@ func (s *StepShutdown) Run(ctx context.Context, state multistep.StateBag) multis select { case <-shutdownTimer: - err := errors.New("Timeout while waiting for machine to shut down.") + err := errors.New("Timeout while waiting for machine to shutdown.") state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt diff --git a/builder/virtualbox/common/step_shutdown_test.go b/builder/virtualbox/common/step_shutdown_test.go index e95020a1e..82e72fd1c 100644 --- a/builder/virtualbox/common/step_shutdown_test.go +++ b/builder/virtualbox/common/step_shutdown_test.go @@ -16,6 +16,7 @@ func TestStepShutdown_impl(t *testing.T) { func TestStepShutdown_noShutdownCommand(t *testing.T) { state := testState(t) step := new(StepShutdown) + step.DisableShutdown = false comm := new(packer.MockCommunicator) state.Put("communicator", comm) @@ -45,6 +46,7 @@ func TestStepShutdown_shutdownCommand(t *testing.T) { step := new(StepShutdown) step.Command = "poweroff" step.Timeout = 1 * time.Second + step.DisableShutdown = false comm := new(packer.MockCommunicator) state.Put("communicator", comm) @@ -82,6 +84,7 @@ func TestStepShutdown_shutdownTimeout(t *testing.T) { step := new(StepShutdown) step.Command = "poweroff" step.Timeout = 1 * time.Second + step.DisableShutdown = false comm := new(packer.MockCommunicator) state.Put("communicator", comm) @@ -112,6 +115,7 @@ func TestStepShutdown_shutdownDelay(t *testing.T) { step.Command = "poweroff" step.Timeout = 5 * time.Second step.Delay = 2 * time.Second + step.DisableShutdown = false comm := new(packer.MockCommunicator) state.Put("communicator", comm) @@ -168,3 +172,32 @@ func TestStepShutdown_shutdownDelay(t *testing.T) { } } + +func TestStepShutdown_DisableShutdown(t *testing.T) { + state := testState(t) + step := new(StepShutdown) + step.DisableShutdown = true + step.Timeout = 2 * time.Second + + comm := new(packer.MockCommunicator) + state.Put("communicator", comm) + state.Put("vmName", "foo") + + driver := state.Get("driver").(*DriverMock) + driver.IsRunningReturn = true + + go func() { + time.Sleep(1 * time.Second) + driver.Lock() + defer driver.Unlock() + driver.IsRunningReturn = false + }() + + // Test the run + if action := step.Run(context.Background(), state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } +} diff --git a/builder/virtualbox/iso/builder.go b/builder/virtualbox/iso/builder.go index 2f3c52a23..02c7aeeac 100644 --- a/builder/virtualbox/iso/builder.go +++ b/builder/virtualbox/iso/builder.go @@ -370,9 +370,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack Comm: &b.config.SSHConfig.Comm, }, &vboxcommon.StepShutdown{ - Command: b.config.ShutdownCommand, - Timeout: b.config.ShutdownTimeout, - Delay: b.config.PostShutdownDelay, + Command: b.config.ShutdownCommand, + Timeout: b.config.ShutdownTimeout, + Delay: b.config.PostShutdownDelay, + DisableShutdown: b.config.DisableShutdown, }, &vboxcommon.StepRemoveDevices{ Bundling: b.config.VBoxBundleConfig, diff --git a/builder/virtualbox/iso/builder.hcl2spec.go b/builder/virtualbox/iso/builder.hcl2spec.go index 6ab143d08..effec9e79 100644 --- a/builder/virtualbox/iso/builder.hcl2spec.go +++ b/builder/virtualbox/iso/builder.hcl2spec.go @@ -42,6 +42,7 @@ type FlatConfig struct { ShutdownCommand *string `mapstructure:"shutdown_command" required:"false" cty:"shutdown_command"` ShutdownTimeout *string `mapstructure:"shutdown_timeout" required:"false" cty:"shutdown_timeout"` PostShutdownDelay *string `mapstructure:"post_shutdown_delay" required:"false" cty:"post_shutdown_delay"` + DisableShutdown *bool `mapstructure:"disable_shutdown" required:"false" cty:"disable_shutdown"` Type *string `mapstructure:"communicator" cty:"communicator"` PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting"` SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host"` @@ -154,6 +155,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "shutdown_command": &hcldec.AttrSpec{Name: "shutdown_command", Type: cty.String, Required: false}, "shutdown_timeout": &hcldec.AttrSpec{Name: "shutdown_timeout", Type: cty.String, Required: false}, "post_shutdown_delay": &hcldec.AttrSpec{Name: "post_shutdown_delay", Type: cty.String, Required: false}, + "disable_shutdown": &hcldec.AttrSpec{Name: "disable_shutdown", Type: cty.Bool, Required: false}, "communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false}, "pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false}, "ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false}, diff --git a/builder/virtualbox/ovf/builder.go b/builder/virtualbox/ovf/builder.go index 260a4ce64..b20b5b663 100644 --- a/builder/virtualbox/ovf/builder.go +++ b/builder/virtualbox/ovf/builder.go @@ -139,9 +139,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack Comm: &b.config.SSHConfig.Comm, }, &vboxcommon.StepShutdown{ - Command: b.config.ShutdownCommand, - Timeout: b.config.ShutdownTimeout, - Delay: b.config.PostShutdownDelay, + Command: b.config.ShutdownCommand, + Timeout: b.config.ShutdownTimeout, + Delay: b.config.PostShutdownDelay, + DisableShutdown: b.config.DisableShutdown, }, &vboxcommon.StepRemoveDevices{ GuestAdditionsInterface: b.config.GuestAdditionsInterface, diff --git a/builder/virtualbox/ovf/config.hcl2spec.go b/builder/virtualbox/ovf/config.hcl2spec.go index 789b147a1..4ac16c25a 100644 --- a/builder/virtualbox/ovf/config.hcl2spec.go +++ b/builder/virtualbox/ovf/config.hcl2spec.go @@ -79,6 +79,7 @@ type FlatConfig struct { ShutdownCommand *string `mapstructure:"shutdown_command" required:"false" cty:"shutdown_command"` ShutdownTimeout *string `mapstructure:"shutdown_timeout" required:"false" cty:"shutdown_timeout"` PostShutdownDelay *string `mapstructure:"post_shutdown_delay" required:"false" cty:"post_shutdown_delay"` + DisableShutdown *bool `mapstructure:"disable_shutdown" required:"false" cty:"disable_shutdown"` VBoxManage [][]string `mapstructure:"vboxmanage" required:"false" cty:"vboxmanage"` VBoxManagePost [][]string `mapstructure:"vboxmanage_post" required:"false" cty:"vboxmanage_post"` VBoxVersionFile *string `mapstructure:"virtualbox_version_file" required:"false" cty:"virtualbox_version_file"` @@ -177,6 +178,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "shutdown_command": &hcldec.AttrSpec{Name: "shutdown_command", Type: cty.String, Required: false}, "shutdown_timeout": &hcldec.AttrSpec{Name: "shutdown_timeout", Type: cty.String, Required: false}, "post_shutdown_delay": &hcldec.AttrSpec{Name: "post_shutdown_delay", Type: cty.String, Required: false}, + "disable_shutdown": &hcldec.AttrSpec{Name: "disable_shutdown", Type: cty.Bool, Required: false}, "vboxmanage": &hcldec.BlockListSpec{TypeName: "vboxmanage", Nested: &hcldec.AttrSpec{Name: "vboxmanage", Type: cty.List(cty.String), Required: false}}, "vboxmanage_post": &hcldec.BlockListSpec{TypeName: "vboxmanage_post", Nested: &hcldec.AttrSpec{Name: "vboxmanage_post", Type: cty.List(cty.String), Required: false}}, "virtualbox_version_file": &hcldec.AttrSpec{Name: "virtualbox_version_file", Type: cty.String, Required: false}, diff --git a/builder/virtualbox/vm/builder.go b/builder/virtualbox/vm/builder.go index 25f825622..2336e58d5 100644 --- a/builder/virtualbox/vm/builder.go +++ b/builder/virtualbox/vm/builder.go @@ -123,9 +123,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack Comm: &b.config.SSHConfig.Comm, }, &vboxcommon.StepShutdown{ - Command: b.config.ShutdownCommand, - Timeout: b.config.ShutdownTimeout, - Delay: b.config.PostShutdownDelay, + Command: b.config.ShutdownCommand, + Timeout: b.config.ShutdownTimeout, + Delay: b.config.PostShutdownDelay, + DisableShutdown: b.config.DisableShutdown, }, &vboxcommon.StepVBoxManage{ Commands: b.config.VBoxManagePost, diff --git a/builder/virtualbox/vm/config.hcl2spec.go b/builder/virtualbox/vm/config.hcl2spec.go index da231ce2c..eafdf2428 100644 --- a/builder/virtualbox/vm/config.hcl2spec.go +++ b/builder/virtualbox/vm/config.hcl2spec.go @@ -79,6 +79,7 @@ type FlatConfig struct { ShutdownCommand *string `mapstructure:"shutdown_command" required:"false" cty:"shutdown_command"` ShutdownTimeout *string `mapstructure:"shutdown_timeout" required:"false" cty:"shutdown_timeout"` PostShutdownDelay *string `mapstructure:"post_shutdown_delay" required:"false" cty:"post_shutdown_delay"` + DisableShutdown *bool `mapstructure:"disable_shutdown" required:"false" cty:"disable_shutdown"` VBoxManage [][]string `mapstructure:"vboxmanage" required:"false" cty:"vboxmanage"` VBoxManagePost [][]string `mapstructure:"vboxmanage_post" required:"false" cty:"vboxmanage_post"` VBoxVersionFile *string `mapstructure:"virtualbox_version_file" required:"false" cty:"virtualbox_version_file"` @@ -173,6 +174,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "shutdown_command": &hcldec.AttrSpec{Name: "shutdown_command", Type: cty.String, Required: false}, "shutdown_timeout": &hcldec.AttrSpec{Name: "shutdown_timeout", Type: cty.String, Required: false}, "post_shutdown_delay": &hcldec.AttrSpec{Name: "post_shutdown_delay", Type: cty.String, Required: false}, + "disable_shutdown": &hcldec.AttrSpec{Name: "disable_shutdown", Type: cty.Bool, Required: false}, "vboxmanage": &hcldec.BlockListSpec{TypeName: "vboxmanage", Nested: &hcldec.AttrSpec{Name: "vboxmanage", Type: cty.List(cty.String), Required: false}}, "vboxmanage_post": &hcldec.BlockListSpec{TypeName: "vboxmanage_post", Nested: &hcldec.AttrSpec{Name: "vboxmanage_post", Type: cty.List(cty.String), Required: false}}, "virtualbox_version_file": &hcldec.AttrSpec{Name: "virtualbox_version_file", Type: cty.String, Required: false}, diff --git a/website/source/docs/builders/virtualbox-vm.html.md.erb b/website/source/docs/builders/virtualbox-vm.html.md.erb index 155b0c054..daa1d619a 100644 --- a/website/source/docs/builders/virtualbox-vm.html.md.erb +++ b/website/source/docs/builders/virtualbox-vm.html.md.erb @@ -221,24 +221,6 @@ builder. the builder. By default this is `output-BUILDNAME` where "BUILDNAME" is the name of the build. -- `post_shutdown_delay` (string) - The amount of time to wait after shutting - down the virtual machine. Defaults to `2s`. **Hint:** Don't specify a value - smaller than `2s` because otherwise the creation of a target snapshot might - corrupt the VM because not all locks has been released by VirtualBox. - -- `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 unless a - shutdown command takes place inside script so this may safely be omitted. If - one or more scripts require a reboot it is suggested to leave this blank - since reboots may fail and specify the final shutdown command in your - last script. - -- `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. - - `skip_export` (boolean) - Defaults to `false`. When enabled, Packer will not export the VM. Useful if the builder should be applied again on the created target snapshot. @@ -292,6 +274,13 @@ builder. port in this range that appears available. By default this is `5900` to `6000`. The minimum and maximum ports are inclusive. + +### Shutdown configuration + +#### Optional: + +<%= partial "partials/builder/virtualbox/common/ShutdownConfig-not-required" %> + ## Boot Command The `boot_command` configuration is very important: it specifies the keys to diff --git a/website/source/partials/builder/virtualbox/common/_ShutdownConfig-not-required.html.md b/website/source/partials/builder/virtualbox/common/_ShutdownConfig-not-required.html.md index 93da47ced..c427ec65a 100644 --- a/website/source/partials/builder/virtualbox/common/_ShutdownConfig-not-required.html.md +++ b/website/source/partials/builder/virtualbox/common/_ShutdownConfig-not-required.html.md @@ -17,4 +17,11 @@ down the virtual machine. If you get the error Error removing floppy controller, you might need to set this to 5m or so. By default, the delay is 0s or disabled. + +- `disable_shutdown` (bool) - Packer normally halts the virtual machine after all provisioners have + run when no `shutdown_command` is defined. If this is set to `true`, Packer + *will not* halt the virtual machine but will assume that you will send the stop + signal yourself through the preseed.cfg or your final provisioner. + Packer will wait for a default of 5 minutes until the virtual machine is shutdown. + The timeout can be changed using `shutdown_timeout` option. \ No newline at end of file