From baa203bb538f63e9db4baf524b169afedc6efb60 Mon Sep 17 00:00:00 2001 From: Sylvia Moss Date: Thu, 9 Jan 2020 08:36:19 -0800 Subject: [PATCH] Adds acpi_shutdown to virtualbox builder (#8587) --- builder/virtualbox/common/driver.go | 3 ++ builder/virtualbox/common/driver_4_2.go | 8 +++++ builder/virtualbox/common/driver_mock.go | 10 ++++-- builder/virtualbox/common/shutdown_config.go | 3 ++ builder/virtualbox/common/step_shutdown.go | 11 ++++++- .../virtualbox/common/step_shutdown_test.go | 33 +++++++++++++++++++ builder/virtualbox/iso/builder.go | 1 + builder/virtualbox/iso/builder.hcl2spec.go | 2 ++ builder/virtualbox/ovf/builder.go | 1 + builder/virtualbox/ovf/config.hcl2spec.go | 2 ++ builder/virtualbox/vm/builder.go | 1 + builder/virtualbox/vm/config.hcl2spec.go | 2 ++ .../_ShutdownConfig-not-required.html.md | 3 ++ 13 files changed, 77 insertions(+), 3 deletions(-) diff --git a/builder/virtualbox/common/driver.go b/builder/virtualbox/common/driver.go index 3cfd1f6b9..986443e66 100644 --- a/builder/virtualbox/common/driver.go +++ b/builder/virtualbox/common/driver.go @@ -40,6 +40,9 @@ type Driver interface { // Stop stops a running machine, forcefully. Stop(string) error + // ACPIStop stops a running machine via ACPI power button. + StopViaACPI(string) error + // SuppressMessages should do what needs to be done in order to // suppress any annoying popups from VirtualBox. SuppressMessages() error diff --git a/builder/virtualbox/common/driver_4_2.go b/builder/virtualbox/common/driver_4_2.go index 338aee4d1..302820c04 100644 --- a/builder/virtualbox/common/driver_4_2.go +++ b/builder/virtualbox/common/driver_4_2.go @@ -161,6 +161,14 @@ func (d *VBox42Driver) Stop(name string) error { return nil } +func (d *VBox42Driver) StopViaACPI(name string) error { + if err := d.VBoxManage("controlvm", name, "acpipowerbutton"); err != nil { + return err + } + + return nil +} + func (d *VBox42Driver) SuppressMessages() error { extraData := map[string]string{ "GUI/RegistrationData": "triesLeft=0", diff --git a/builder/virtualbox/common/driver_mock.go b/builder/virtualbox/common/driver_mock.go index 6ff9e80b8..775fca430 100644 --- a/builder/virtualbox/common/driver_mock.go +++ b/builder/virtualbox/common/driver_mock.go @@ -34,8 +34,9 @@ type DriverMock struct { IsRunningReturn bool IsRunningErr error - StopName string - StopErr error + StopViaACPIName string + StopName string + StopErr error SuppressMessagesCalled bool SuppressMessagesErr error @@ -112,6 +113,11 @@ func (d *DriverMock) Stop(name string) error { return d.StopErr } +func (d *DriverMock) StopViaACPI(name string) error { + d.StopViaACPIName = name + return d.StopErr +} + func (d *DriverMock) SuppressMessages() error { d.SuppressMessagesCalled = true return d.SuppressMessagesErr diff --git a/builder/virtualbox/common/shutdown_config.go b/builder/virtualbox/common/shutdown_config.go index 48ad9c61d..29f0b0b72 100644 --- a/builder/virtualbox/common/shutdown_config.go +++ b/builder/virtualbox/common/shutdown_config.go @@ -34,6 +34,9 @@ type ShutdownConfig struct { // 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"` + // If it's set to true, it will shutdown the VM via power button. It could be a good option + // when keeping the machine state is necessary after shutting it down. + ACPIShutdown bool `mapstructure:"acpi_shutdown" required:"false"` } func (c *ShutdownConfig) Prepare(ctx *interpolate.Context) []error { diff --git a/builder/virtualbox/common/step_shutdown.go b/builder/virtualbox/common/step_shutdown.go index fcf19b782..75d570977 100644 --- a/builder/virtualbox/common/step_shutdown.go +++ b/builder/virtualbox/common/step_shutdown.go @@ -27,6 +27,7 @@ type StepShutdown struct { Timeout time.Duration Delay time.Duration DisableShutdown bool + ACPIShutdown bool } func (s *StepShutdown) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { @@ -35,7 +36,15 @@ func (s *StepShutdown) Run(ctx context.Context, state multistep.StateBag) multis ui := state.Get("ui").(packer.Ui) vmName := state.Get("vmName").(string) - if !s.DisableShutdown { + if s.ACPIShutdown { + ui.Say("Shuting down the virtual machine via ACPI power button...") + if err := driver.StopViaACPI(vmName); err != nil { + err := fmt.Errorf("Error stopping VM: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } else if !s.DisableShutdown { if s.Command != "" { ui.Say("Gracefully halting virtual machine...") log.Printf("Executing shutdown command: %s", s.Command) diff --git a/builder/virtualbox/common/step_shutdown_test.go b/builder/virtualbox/common/step_shutdown_test.go index ca8970230..c0cec95ed 100644 --- a/builder/virtualbox/common/step_shutdown_test.go +++ b/builder/virtualbox/common/step_shutdown_test.go @@ -17,6 +17,7 @@ func TestStepShutdown_noShutdownCommand(t *testing.T) { state := testState(t) step := new(StepShutdown) step.DisableShutdown = false + step.ACPIShutdown = false comm := new(packer.MockCommunicator) state.Put("communicator", comm) @@ -47,6 +48,7 @@ func TestStepShutdown_shutdownCommand(t *testing.T) { step.Command = "poweroff" step.Timeout = 1 * time.Second step.DisableShutdown = false + step.ACPIShutdown = false comm := new(packer.MockCommunicator) state.Put("communicator", comm) @@ -85,6 +87,7 @@ func TestStepShutdown_shutdownTimeout(t *testing.T) { step.Command = "poweroff" step.Timeout = 1 * time.Second step.DisableShutdown = false + step.ACPIShutdown = false comm := new(packer.MockCommunicator) state.Put("communicator", comm) @@ -113,6 +116,7 @@ func TestStepShutdown_DisableShutdown(t *testing.T) { state := testState(t) step := new(StepShutdown) step.DisableShutdown = true + step.ACPIShutdown = false step.Timeout = 2 * time.Second comm := new(packer.MockCommunicator) @@ -137,3 +141,32 @@ func TestStepShutdown_DisableShutdown(t *testing.T) { t.Fatal("should NOT have error") } } + +func TestStepShutdown_ACPIShutdown(t *testing.T) { + state := testState(t) + step := new(StepShutdown) + step.ACPIShutdown = true + step.Timeout = 2 * time.Second + + comm := new(packer.MockCommunicator) + state.Put("communicator", comm) + state.Put("vmName", "foo") + + driver := state.Get("driver").(*DriverMock) + + // 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") + } + + // Test that Stop was just called + if driver.StopViaACPIName != "foo" { + t.Fatal("should call stop via ACPI") + } + if comm.StartCalled { + t.Fatal("comm start should not be called") + } +} diff --git a/builder/virtualbox/iso/builder.go b/builder/virtualbox/iso/builder.go index 2bf69b49f..9ca107f2d 100644 --- a/builder/virtualbox/iso/builder.go +++ b/builder/virtualbox/iso/builder.go @@ -377,6 +377,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack Timeout: b.config.ShutdownTimeout, Delay: b.config.PostShutdownDelay, DisableShutdown: b.config.DisableShutdown, + ACPIShutdown: b.config.ACPIShutdown, }, &vboxcommon.StepRemoveDevices{ Bundling: b.config.VBoxBundleConfig, diff --git a/builder/virtualbox/iso/builder.hcl2spec.go b/builder/virtualbox/iso/builder.hcl2spec.go index 8f7497ac8..c5dd51997 100644 --- a/builder/virtualbox/iso/builder.hcl2spec.go +++ b/builder/virtualbox/iso/builder.hcl2spec.go @@ -43,6 +43,7 @@ type FlatConfig struct { 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"` + ACPIShutdown *bool `mapstructure:"acpi_shutdown" required:"false" cty:"acpi_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"` @@ -159,6 +160,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "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}, + "acpi_shutdown": &hcldec.AttrSpec{Name: "acpi_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 3cbd16675..18bfb9bd1 100644 --- a/builder/virtualbox/ovf/builder.go +++ b/builder/virtualbox/ovf/builder.go @@ -145,6 +145,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack Timeout: b.config.ShutdownTimeout, Delay: b.config.PostShutdownDelay, DisableShutdown: b.config.DisableShutdown, + ACPIShutdown: b.config.ACPIShutdown, }, &vboxcommon.StepRemoveDevices{ GuestAdditionsInterface: b.config.GuestAdditionsInterface, diff --git a/builder/virtualbox/ovf/config.hcl2spec.go b/builder/virtualbox/ovf/config.hcl2spec.go index e518e3cf7..c76b2aa76 100644 --- a/builder/virtualbox/ovf/config.hcl2spec.go +++ b/builder/virtualbox/ovf/config.hcl2spec.go @@ -80,6 +80,7 @@ type FlatConfig struct { 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"` + ACPIShutdown *bool `mapstructure:"acpi_shutdown" required:"false" cty:"acpi_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"` @@ -182,6 +183,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "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}, + "acpi_shutdown": &hcldec.AttrSpec{Name: "acpi_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 3bab1edb7..4247d310e 100644 --- a/builder/virtualbox/vm/builder.go +++ b/builder/virtualbox/vm/builder.go @@ -127,6 +127,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack Timeout: b.config.ShutdownTimeout, Delay: b.config.PostShutdownDelay, DisableShutdown: b.config.DisableShutdown, + ACPIShutdown: b.config.ACPIShutdown, }, &vboxcommon.StepVBoxManage{ Commands: b.config.VBoxManagePost, diff --git a/builder/virtualbox/vm/config.hcl2spec.go b/builder/virtualbox/vm/config.hcl2spec.go index a180a65f6..8245059d7 100644 --- a/builder/virtualbox/vm/config.hcl2spec.go +++ b/builder/virtualbox/vm/config.hcl2spec.go @@ -80,6 +80,7 @@ type FlatConfig struct { 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"` + ACPIShutdown *bool `mapstructure:"acpi_shutdown" required:"false" cty:"acpi_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"` @@ -178,6 +179,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "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}, + "acpi_shutdown": &hcldec.AttrSpec{Name: "acpi_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/partials/builder/virtualbox/common/_ShutdownConfig-not-required.html.md b/website/source/partials/builder/virtualbox/common/_ShutdownConfig-not-required.html.md index c427ec65a..6b44dcafe 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 @@ -24,4 +24,7 @@ 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. + +- `acpi_shutdown` (bool) - If it's set to true, it will shutdown the VM via power button. It could be a good option + when keeping the machine state is necessary after shutting it down. \ No newline at end of file