From 03c4bebf004da190897bd1ff34008715215033b8 Mon Sep 17 00:00:00 2001 From: Vladislav Rassokhin Date: Mon, 20 Apr 2020 20:56:53 +0300 Subject: [PATCH] Add disable_shutdown option to VSphere builders Also don't try to shut down VM if it's already off, otherwise VSphere would raise an error: "The attempted operation cannot be performed in the current state (Powered off)." --- builder/vsphere/clone/config.hcl2spec.go | 2 ++ builder/vsphere/common/step_shutdown.go | 23 +++++++++++++++---- .../vsphere/common/step_shutdown.hcl2spec.go | 6 +++-- builder/vsphere/driver/vm.go | 13 +++++++++-- builder/vsphere/iso/config.hcl2spec.go | 2 ++ .../common/ShutdownConfig-not-required.mdx | 11 +++++++-- 6 files changed, 47 insertions(+), 10 deletions(-) diff --git a/builder/vsphere/clone/config.hcl2spec.go b/builder/vsphere/clone/config.hcl2spec.go index 0cda209fa..4f80db918 100644 --- a/builder/vsphere/clone/config.hcl2spec.go +++ b/builder/vsphere/clone/config.hcl2spec.go @@ -92,6 +92,7 @@ type FlatConfig struct { WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"` Command *string `mapstructure:"shutdown_command" cty:"shutdown_command"` Timeout *string `mapstructure:"shutdown_timeout" cty:"shutdown_timeout"` + DisableShutdown *bool `mapstructure:"disable_shutdown" cty:"disable_shutdown"` CreateSnapshot *bool `mapstructure:"create_snapshot" cty:"create_snapshot"` ConvertToTemplate *bool `mapstructure:"convert_to_template" cty:"convert_to_template"` Export *common.FlatExportConfig `mapstructure:"export" cty:"export"` @@ -191,6 +192,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false}, "shutdown_command": &hcldec.AttrSpec{Name: "shutdown_command", Type: cty.String, Required: false}, "shutdown_timeout": &hcldec.AttrSpec{Name: "shutdown_timeout", Type: cty.String, Required: false}, + "disable_shutdown": &hcldec.AttrSpec{Name: "disable_shutdown", Type: cty.Bool, Required: false}, "create_snapshot": &hcldec.AttrSpec{Name: "create_snapshot", Type: cty.Bool, Required: false}, "convert_to_template": &hcldec.AttrSpec{Name: "convert_to_template", Type: cty.Bool, Required: false}, "export": &hcldec.BlockSpec{TypeName: "export", Nested: hcldec.ObjectSpec((*common.FlatExportConfig)(nil).HCL2Spec())}, diff --git a/builder/vsphere/common/step_shutdown.go b/builder/vsphere/common/step_shutdown.go index 98142d421..0666b9666 100644 --- a/builder/vsphere/common/step_shutdown.go +++ b/builder/vsphere/common/step_shutdown.go @@ -19,9 +19,16 @@ type ShutdownConfig struct { // Specify a VM guest shutdown command. VMware guest tools are used by // default. Command string `mapstructure:"shutdown_command"` - // Amount of time to wait for graceful VM shutdown. Examples 45s and 10m. - // Defaults to 5m(5 minutes). + // Amount of time to wait for graceful VM shutdown. + // Defaults to 5m or five minutes. Timeout time.Duration `mapstructure:"shutdown_timeout"` + // 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 five minutes until the virtual machine is shutdown. + // The timeout can be changed using `shutdown_timeout` option. + DisableShutdown bool `mapstructure:"disable_shutdown"` } func (c *ShutdownConfig) Prepare() []error { @@ -43,7 +50,15 @@ func (s *StepShutdown) Run(ctx context.Context, state multistep.StateBag) multis comm := state.Get("communicator").(packer.Communicator) vm := state.Get("vm").(*driver.VirtualMachine) - if s.Config.Command != "" { + if off, _ := vm.IsPoweredOff(); off { + // Probably power off initiated by last provisioner, though disable_shutdown is not set + ui.Say("VM is already powered off") + return multistep.ActionContinue + } + + if s.Config.DisableShutdown { + ui.Say("Automatic shutdown disabled. Please shutdown virtual machine.") + } else if s.Config.Command != "" { ui.Say("Executing shutdown command...") log.Printf("Shutdown command: %s", s.Config.Command) @@ -59,7 +74,7 @@ func (s *StepShutdown) Run(ctx context.Context, state multistep.StateBag) multis return multistep.ActionHalt } } else { - ui.Say("Shut down VM...") + ui.Say("Shutting down VM...") err := vm.StartShutdown() if err != nil { diff --git a/builder/vsphere/common/step_shutdown.hcl2spec.go b/builder/vsphere/common/step_shutdown.hcl2spec.go index 6bd80b695..84ccb1789 100644 --- a/builder/vsphere/common/step_shutdown.hcl2spec.go +++ b/builder/vsphere/common/step_shutdown.hcl2spec.go @@ -9,8 +9,9 @@ import ( // FlatShutdownConfig is an auto-generated flat version of ShutdownConfig. // Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. type FlatShutdownConfig struct { - Command *string `mapstructure:"shutdown_command" cty:"shutdown_command"` - Timeout *string `mapstructure:"shutdown_timeout" cty:"shutdown_timeout"` + Command *string `mapstructure:"shutdown_command" cty:"shutdown_command"` + Timeout *string `mapstructure:"shutdown_timeout" cty:"shutdown_timeout"` + DisableShutdown *bool `mapstructure:"disable_shutdown" cty:"disable_shutdown"` } // FlatMapstructure returns a new FlatShutdownConfig. @@ -27,6 +28,7 @@ func (*FlatShutdownConfig) HCL2Spec() map[string]hcldec.Spec { s := 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}, + "disable_shutdown": &hcldec.AttrSpec{Name: "disable_shutdown", Type: cty.Bool, Required: false}, } return s } diff --git a/builder/vsphere/driver/vm.go b/builder/vsphere/driver/vm.go index 1736bd892..778d75f9a 100644 --- a/builder/vsphere/driver/vm.go +++ b/builder/vsphere/driver/vm.go @@ -454,6 +454,15 @@ func (vm *VirtualMachine) PowerOff() error { return err } +func (vm *VirtualMachine) IsPoweredOff() (bool, error) { + state, err := vm.vm.PowerState(vm.driver.ctx) + if err != nil { + return false, err + } + + return state == types.VirtualMachinePowerStatePoweredOff, nil +} + func (vm *VirtualMachine) StartShutdown() error { err := vm.vm.ShutdownGuest(vm.driver.ctx) return err @@ -462,11 +471,11 @@ func (vm *VirtualMachine) StartShutdown() error { func (vm *VirtualMachine) WaitForShutdown(ctx context.Context, timeout time.Duration) error { shutdownTimer := time.After(timeout) for { - powerState, err := vm.vm.PowerState(vm.driver.ctx) + off, err := vm.IsPoweredOff() if err != nil { return err } - if powerState == "poweredOff" { + if off { break } diff --git a/builder/vsphere/iso/config.hcl2spec.go b/builder/vsphere/iso/config.hcl2spec.go index 17120f42a..d72caa339 100644 --- a/builder/vsphere/iso/config.hcl2spec.go +++ b/builder/vsphere/iso/config.hcl2spec.go @@ -118,6 +118,7 @@ type FlatConfig struct { WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"` Command *string `mapstructure:"shutdown_command" cty:"shutdown_command"` Timeout *string `mapstructure:"shutdown_timeout" cty:"shutdown_timeout"` + DisableShutdown *bool `mapstructure:"disable_shutdown" cty:"disable_shutdown"` CreateSnapshot *bool `mapstructure:"create_snapshot" cty:"create_snapshot"` ConvertToTemplate *bool `mapstructure:"convert_to_template" cty:"convert_to_template"` Export *common.FlatExportConfig `mapstructure:"export" cty:"export"` @@ -243,6 +244,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false}, "shutdown_command": &hcldec.AttrSpec{Name: "shutdown_command", Type: cty.String, Required: false}, "shutdown_timeout": &hcldec.AttrSpec{Name: "shutdown_timeout", Type: cty.String, Required: false}, + "disable_shutdown": &hcldec.AttrSpec{Name: "disable_shutdown", Type: cty.Bool, Required: false}, "create_snapshot": &hcldec.AttrSpec{Name: "create_snapshot", Type: cty.Bool, Required: false}, "convert_to_template": &hcldec.AttrSpec{Name: "convert_to_template", Type: cty.Bool, Required: false}, "export": &hcldec.BlockSpec{TypeName: "export", Nested: hcldec.ObjectSpec((*common.FlatExportConfig)(nil).HCL2Spec())}, diff --git a/website/pages/partials/builder/vsphere/common/ShutdownConfig-not-required.mdx b/website/pages/partials/builder/vsphere/common/ShutdownConfig-not-required.mdx index d88210484..eb0aedf64 100644 --- a/website/pages/partials/builder/vsphere/common/ShutdownConfig-not-required.mdx +++ b/website/pages/partials/builder/vsphere/common/ShutdownConfig-not-required.mdx @@ -3,6 +3,13 @@ - `shutdown_command` (string) - Specify a VM guest shutdown command. VMware guest tools are used by default. -- `shutdown_timeout` (duration string | ex: "1h5m2s") - Amount of time to wait for graceful VM shutdown. Examples 45s and 10m. - Defaults to 5m(5 minutes). +- `shutdown_timeout` (duration string | ex: "1h5m2s") - Amount of time to wait for graceful VM shutdown. + Defaults to 5m or five minutes. + +- `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 five minutes until the virtual machine is shutdown. + The timeout can be changed using `shutdown_timeout` option. \ No newline at end of file