From 3eacccdc82edd5b8c10835bc8b81ab819273f667 Mon Sep 17 00:00:00 2001 From: Okean <76069640+Tinyblargon@users.noreply.github.com> Date: Wed, 13 Mar 2024 22:14:21 +0100 Subject: [PATCH] Feature: `vm_state` = `started` (#960) * refactor: control flow for start and stopping vm * feat: `vm_state` = `started` * refactor: use constants in schema --- docs/resources/vm_qemu.md | 2 +- proxmox/resource_vm_qemu.go | 98 +++++++++++++++++++++---------------- proxmox/validators.go | 5 +- 3 files changed, 60 insertions(+), 45 deletions(-) diff --git a/docs/resources/vm_qemu.md b/docs/resources/vm_qemu.md index 1d38e39e..b569e906 100644 --- a/docs/resources/vm_qemu.md +++ b/docs/resources/vm_qemu.md @@ -98,7 +98,7 @@ The following arguments are supported in the top level resource block. | `bios` | `str` | `"seabios"` | The BIOS to use, options are `seabios` or `ovmf` for UEFI. | | `onboot` | `bool` | `false` | Whether to have the VM startup after the PVE node starts. | | `startup` | `string` | `""` | The [startup and shutdown behaviour](https://pve.proxmox.com/pve-docs/pve-admin-guide.html#pct_startup_and_shutdown) | -| `vm_state` | `string` | `"running"` | The desired state of the VM, options are `running` or `stopped`. | +| `vm_state` | `string` | `"running"` | The desired state of the VM, options are `running`, `stopped` and `started`. Do note that `started` will only start the vm on creation and won't fully manage the power state unlike `running` and `stopped` do.| | `oncreate` | `bool` | `true` | Whether to have the VM startup after the VM is created (deprecated, use `vm_state` instead) | | `tablet` | `bool` | `true` | Enable/disable the USB tablet device. This device is usually needed to allow absolute mouse positioning with VNC. | | `boot` | `str` | | The boot order for the VM. For example: `order=scsi0;ide2;net0`. The deprecated `legacy=` syntax is no longer supported. See the `boot` option in the [Proxmox manual](https://pve.proxmox.com/wiki/Manual:_qm.conf#_options) for more information. | diff --git a/proxmox/resource_vm_qemu.go b/proxmox/resource_vm_qemu.go index 96a79d04..a6288621 100755 --- a/proxmox/resource_vm_qemu.go +++ b/proxmox/resource_vm_qemu.go @@ -29,6 +29,12 @@ import ( // so that we can print (debug) our ResourceData constructs var thisResource *schema.Resource +const ( + stateRunning string = "running" + stateStarted string = "started" + stateStopped string = "stopped" +) + func resourceVmQemu() *schema.Resource { thisResource = &schema.Resource{ CreateContext: resourceVmQemuCreate, @@ -117,9 +123,12 @@ func resourceVmQemu() *schema.Resource { "vm_state": { Type: schema.TypeString, Optional: true, - Default: "running", - Description: "The state of the VM (running or stopped)", + Default: stateRunning, + Description: "The state of the VM (" + stateRunning + ", " + stateStarted + ", " + stateStopped + ")", ValidateDiagFunc: VMStateValidator(), + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return new == stateStarted + }, }, "onboot": { Type: schema.TypeBool, @@ -1018,7 +1027,7 @@ func resourceVmQemuCreate(ctx context.Context, d *schema.ResourceData, meta inte // give sometime to proxmox to catchup time.Sleep(time.Duration(d.Get("additional_wait").(int)) * time.Second) - if d.Get("vm_state").(string) == "running" { + if d.Get("vm_state").(string) == "running" || d.Get("vm_state").(string) == "started" { log.Print("[DEBUG][QemuVmCreate] starting VM") _, err := client.StartVm(vmr) if err != nil { @@ -1252,56 +1261,61 @@ func resourceVmQemuUpdate(ctx context.Context, d *schema.ResourceData, meta inte // Try rebooting the VM is a reboot is required and automatic_reboot is // enabled. Attempt a graceful shutdown or if that fails, force power-off. vmState, err := client.GetVmState(vmr) - if err == nil && vmState["status"] != "stopped" && d.Get("vm_state").(string) == "stopped" { - log.Print("[DEBUG][QemuVmUpdate] shutting down VM to match `vm_state`") - _, err = client.ShutdownVm(vmr) - // note: the default timeout is 3 min, configurable per VM: Options/Start-Shutdown Order/Shutdown timeout - if err != nil { - log.Print("[DEBUG][QemuVmUpdate] shutdown failed, stopping VM forcefully") - _, err = client.StopVm(vmr) - if err != nil { + if err != nil { + diags = append(diags, diag.FromErr(err)...) + return diags + } + switch vmState["status"].(string) { // manage the VM state to match the `vm_state` attribute + // case stateStarted: does nothing during update as we don't enforce the VM state + case stateStopped: + if d.Get("vm_state").(string) == stateRunning { // start the VM + log.Print("[DEBUG][QemuVmUpdate] starting VM to match `vm_state`") + if _, err = client.StartVm(vmr); err != nil { return diag.FromErr(err) } } - } else if err == nil && vmState["status"] != "stopped" && d.Get("reboot_required").(bool) { - if d.Get("automatic_reboot").(bool) { - log.Print("[DEBUG][QemuVmUpdate] rebooting the VM to match the configuration changes") - _, err = client.RebootVm(vmr) + case stateRunning: + if d.Get("vm_state").(string) == stateStopped { // shutdown the VM + log.Print("[DEBUG][QemuVmUpdate] shutting down VM to match `vm_state`") + _, err = client.ShutdownVm(vmr) // note: the default timeout is 3 min, configurable per VM: Options/Start-Shutdown Order/Shutdown timeout if err != nil { - log.Print("[DEBUG][QemuVmUpdate] reboot failed, stopping VM forcefully") - - if _, err := client.StopVm(vmr); err != nil { + log.Print("[DEBUG][QemuVmUpdate] shutdown failed, stopping VM forcefully") + if _, err = client.StopVm(vmr); err != nil { return diag.FromErr(err) } - - // give sometime to proxmox to catchup - dur := time.Duration(d.Get("additional_wait").(int)) * time.Second - log.Printf("[DEBUG][QemuVmUpdate] waiting for (%v) before starting the VM again", dur) - time.Sleep(dur) - - if _, err := client.StartVm(vmr); err != nil { - return diag.FromErr(err) + } + } else if d.Get("reboot_required").(bool) { // reboot the VM + if d.Get("automatic_reboot").(bool) { // automatic reboots is enabled + log.Print("[DEBUG][QemuVmUpdate] rebooting the VM to match the configuration changes") + _, err = client.RebootVm(vmr) + // note: the default timeout is 3 min, configurable per VM: Options/Start-Shutdown Order/Shutdown timeout + if err != nil { + log.Print("[DEBUG][QemuVmUpdate] reboot failed, stopping VM forcefully") + if _, err := client.StopVm(vmr); err != nil { + return diag.FromErr(err) + } + // give sometime to proxmox to catchup + dur := time.Duration(d.Get("additional_wait").(int)) * time.Second + log.Printf("[DEBUG][QemuVmUpdate] waiting for (%v) before starting the VM again", dur) + time.Sleep(dur) + if _, err := client.StartVm(vmr); err != nil { + return diag.FromErr(err) + } } + } else { // automatic reboots is disabled + // Automatic reboots is not enabled, show the user a warning message that + // the VM needs a reboot for the changed parameters to take in effect. + diags = append(diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: "VM needs to be rebooted and automatic_reboot is disabled", + Detail: "One or more parameters are modified that only take effect after a reboot (shutdown & start).", + AttributePath: cty.Path{}, + }) } - } else { - // Automatic reboots is not enabled, show the user a warning message that - // the VM needs a reboot for the changed parameters to take in effect. - diags = append(diags, diag.Diagnostic{ - Severity: diag.Warning, - Summary: "VM needs to be rebooted and automatic_reboot is disabled", - Detail: "One or more parameters are modified that only take effect after a reboot (shutdown & start).", - AttributePath: cty.Path{}, - }) - } - } else if err != nil { - diags = append(diags, diag.FromErr(err)...) - return diags + } } - // if vmState["status"] == "running" && d.Get("vm_state").(string) == "running" { - // diags = append(diags, initConnInfo(ctx, d, pconf, client, vmr, &config, lock)...) - // } lock.unlock() // err = resourceVmQemuRead(ctx, d, meta) diff --git a/proxmox/validators.go b/proxmox/validators.go index b51280d3..5a4b3752 100644 --- a/proxmox/validators.go +++ b/proxmox/validators.go @@ -89,7 +89,8 @@ func BIOSValidator() schema.SchemaValidateDiagFunc { func VMStateValidator() schema.SchemaValidateDiagFunc { return validation.ToDiagFunc(validation.StringInSlice([]string{ - "running", - "stopped", + stateRunning, + stateStopped, + stateStarted, }, false)) }