From b045746a94d2717b69fc48234b9ece101b53bdcd Mon Sep 17 00:00:00 2001 From: Pavel Boldyrev <627562+bpg@users.noreply.github.com> Date: Sat, 29 Jul 2023 22:55:12 -0400 Subject: [PATCH] feat(vm): add support for start/shutdown order configuration (#445) --- docs/resources/virtual_environment_vm.md | 13 +++ example/resource_virtual_environment_vm.tf | 12 +- proxmoxtf/resource/vm.go | 127 +++++++++++++++++++++ 3 files changed, 149 insertions(+), 3 deletions(-) diff --git a/docs/resources/virtual_environment_vm.md b/docs/resources/virtual_environment_vm.md index 9f81522d..d6223685 100644 --- a/docs/resources/virtual_environment_vm.md +++ b/docs/resources/virtual_environment_vm.md @@ -26,6 +26,12 @@ resource "proxmox_virtual_environment_vm" "ubuntu_vm" { enabled = true } + startup { + order = "3" + up_delay = "60" + down_delay = "60" + } + disk { datastore_id = "local-lvm" file_id = proxmox_virtual_environment_file.ubuntu_cloud_image.id @@ -410,6 +416,13 @@ output "ubuntu_vm_public_key" { - `pvscsi` - VMware Paravirtual SCSI. - `started` - (Optional) Whether to start the virtual machine (defaults to `true`). +- `startup` - (Optional) Defines startup and shutdown behavior of the VM. + - `order` - (Required) A non-negative number defining the general startup + order. + - `up` - (Optional) A non-negative number defining the delay in seconds + before the next VM is started. + - `down` - (Optional) A non-negative number defining the delay in seconds + before the next VM is shut down. - `tablet_device` - (Optional) Whether to enable the USB tablet device (defaults to `true`). - `tags` - (Optional) A list of tags of the VM. This is only meta information ( diff --git a/example/resource_virtual_environment_vm.tf b/example/resource_virtual_environment_vm.tf index 31cfdb33..6119ceb2 100644 --- a/example/resource_virtual_environment_vm.tf +++ b/example/resource_virtual_environment_vm.tf @@ -13,10 +13,16 @@ resource "proxmox_virtual_environment_vm" "example_template" { numa = true } + startup { + order = "3" + up_delay = "60" + down_delay = "60" + } + efi_disk { - datastore_id = local.datastore_id - file_format = "raw" - type = "4m" + datastore_id = local.datastore_id + file_format = "raw" + type = "4m" } # disk { diff --git a/proxmoxtf/resource/vm.go b/proxmoxtf/resource/vm.go index 168338ff..679a074d 100644 --- a/proxmoxtf/resource/vm.go +++ b/proxmoxtf/resource/vm.go @@ -104,6 +104,9 @@ const ( dvResourceVirtualEnvironmentVMPoolID = "" dvResourceVirtualEnvironmentVMSerialDeviceDevice = "socket" dvResourceVirtualEnvironmentVMStarted = true + dvResourceVirtualEnvironmentVMStartupOrder = -1 + dvResourceVirtualEnvironmentVMStartupUpDelay = -1 + dvResourceVirtualEnvironmentVMStartupDownDelay = -1 dvResourceVirtualEnvironmentVMTabletDevice = true dvResourceVirtualEnvironmentVMTemplate = false dvResourceVirtualEnvironmentVMTimeoutClone = 1800 @@ -232,6 +235,10 @@ const ( mkResourceVirtualEnvironmentVMSerialDevice = "serial_device" mkResourceVirtualEnvironmentVMSerialDeviceDevice = "device" mkResourceVirtualEnvironmentVMStarted = "started" + mkResourceVirtualEnvironmentVMStartup = "startup" + mkResourceVirtualEnvironmentVMStartupOrder = "order" + mkResourceVirtualEnvironmentVMStartupUpDelay = "up_delay" + mkResourceVirtualEnvironmentVMStartupDownDelay = "down_delay" mkResourceVirtualEnvironmentVMTabletDevice = "tablet_device" mkResourceVirtualEnvironmentVMTags = "tags" mkResourceVirtualEnvironmentVMTemplate = "template" @@ -1214,6 +1221,47 @@ func VM() *schema.Resource { return d.Get(mkResourceVirtualEnvironmentVMTemplate).(bool) }, }, + mkResourceVirtualEnvironmentVMStartup: { + Type: schema.TypeList, + Description: "Defines startup and shutdown behavior of the VM", + Optional: true, + DefaultFunc: func() (interface{}, error) { + return []interface{}{ + map[string]interface{}{ + mkResourceVirtualEnvironmentVMStartupOrder: dvResourceVirtualEnvironmentVMStartupOrder, + mkResourceVirtualEnvironmentVMVGAMemory: dvResourceVirtualEnvironmentVMVGAMemory, + mkResourceVirtualEnvironmentVMVGAType: dvResourceVirtualEnvironmentVMVGAType, + }, + }, nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentVMStartupOrder: { + Type: schema.TypeInt, + Description: "A non-negative number defining the general startup order", + Optional: true, + Default: dvResourceVirtualEnvironmentVMStartupOrder, + }, + mkResourceVirtualEnvironmentVMStartupUpDelay: { + Type: schema.TypeInt, + Description: "A non-negative number defining the delay in seconds before the next VM is started", + Optional: true, + Default: dvResourceVirtualEnvironmentVMStartupUpDelay, + ValidateDiagFunc: validation.ToDiagFunc(validation.IntAtLeast(0)), + }, + mkResourceVirtualEnvironmentVMStartupDownDelay: { + Type: schema.TypeInt, + Description: "A non-negative number defining the delay in seconds before the next VM is shut down", + Optional: true, + Default: dvResourceVirtualEnvironmentVMStartupDownDelay, + ValidateDiagFunc: validation.ToDiagFunc(validation.IntAtLeast(0)), + }, + }, + }, + MaxItems: 1, + MinItems: 0, + }, + mkResourceVirtualEnvironmentVMTabletDevice: { Type: schema.TypeBool, Description: "Whether to enable the USB tablet device", @@ -1778,6 +1826,8 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d updateBody.StartOnBoot = &onBoot + updateBody.StartupOrder = vmGetStartupOrder(d) + //nolint:gosimple if tabletDevice != dvResourceVirtualEnvironmentVMTabletDevice { updateBody.TabletDeviceEnabled = &tabletDevice @@ -2148,6 +2198,8 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{}) serialDevices := vmGetSerialDeviceList(d) + startupOrder := vmGetStartupOrder(d) + onBoot := types.CustomBool(d.Get(mkResourceVirtualEnvironmentVMOnBoot).(bool)) tabletDevice := types.CustomBool(d.Get(mkResourceVirtualEnvironmentVMTabletDevice).(bool)) template := types.CustomBool(d.Get(mkResourceVirtualEnvironmentVMTemplate).(bool)) @@ -2268,6 +2320,7 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{}) SerialDevices: serialDevices, SharedMemory: memorySharedObject, StartOnBoot: &onBoot, + StartupOrder: startupOrder, TabletDeviceEnabled: &tabletDevice, Template: &template, VGADevice: vgaDevice, @@ -3009,6 +3062,33 @@ func vmGetSerialDeviceList(d *schema.ResourceData) vms.CustomSerialDevices { return list } +func vmGetStartupOrder(d *schema.ResourceData) *vms.CustomStartupOrder { + startup := d.Get(mkResourceVirtualEnvironmentVMStartup).([]interface{}) + if len(startup) > 0 { + startupBlock := startup[0].(map[string]interface{}) + startupOrder := startupBlock[mkResourceVirtualEnvironmentVMStartupOrder].(int) + startupUpDelay := startupBlock[mkResourceVirtualEnvironmentVMStartupUpDelay].(int) + startupDownDelay := startupBlock[mkResourceVirtualEnvironmentVMStartupDownDelay].(int) + + order := vms.CustomStartupOrder{} + + if startupUpDelay >= 0 { + order.Up = &startupUpDelay + } + + if startupDownDelay >= 0 { + order.Down = &startupDownDelay + } + + if startupOrder >= 0 { + order.Order = &startupOrder + return &order + } + } + + return nil +} + func vmGetTagsString(d *schema.ResourceData) string { tags := d.Get(mkResourceVirtualEnvironmentVMTags).([]interface{}) var sanitizedTags []string @@ -4064,6 +4144,53 @@ func vmReadCustom( diags = append(diags, diag.FromErr(err)...) } + // Compare the startup order to the one stored in the state. + startup := map[string]interface{}{} + + //nolint:nestif + if vmConfig.StartupOrder != nil { + if vmConfig.StartupOrder.Order != nil { + startup[mkResourceVirtualEnvironmentVMStartupOrder] = *vmConfig.StartupOrder.Order + } else { + startup[mkResourceVirtualEnvironmentVMStartupOrder] = dvResourceVirtualEnvironmentVMStartupOrder + } + + if vmConfig.StartupOrder.Up != nil { + startup[mkResourceVirtualEnvironmentVMStartupUpDelay] = *vmConfig.StartupOrder.Up + } else { + startup[mkResourceVirtualEnvironmentVMStartupUpDelay] = dvResourceVirtualEnvironmentVMStartupUpDelay + } + + if vmConfig.StartupOrder.Down != nil { + startup[mkResourceVirtualEnvironmentVMStartupDownDelay] = *vmConfig.StartupOrder.Down + } else { + startup[mkResourceVirtualEnvironmentVMStartupDownDelay] = dvResourceVirtualEnvironmentVMStartupDownDelay + } + } else { + startup[mkResourceVirtualEnvironmentVMStartupOrder] = dvResourceVirtualEnvironmentVMStartupOrder + startup[mkResourceVirtualEnvironmentVMStartupUpDelay] = dvResourceVirtualEnvironmentVMStartupUpDelay + startup[mkResourceVirtualEnvironmentVMStartupDownDelay] = dvResourceVirtualEnvironmentVMStartupDownDelay + } + + currentStartup := d.Get(mkResourceVirtualEnvironmentVMStartup).([]interface{}) + + //nolint:gocritic + if len(clone) > 0 { + if len(currentStartup) > 0 { + err := d.Set(mkResourceVirtualEnvironmentVMStartup, []interface{}{startup}) + diags = append(diags, diag.FromErr(err)...) + } + } else if len(currentStartup) > 0 || + startup[mkResourceVirtualEnvironmentVMStartupOrder] != mkResourceVirtualEnvironmentVMStartupOrder || + startup[mkResourceVirtualEnvironmentVMStartupUpDelay] != dvResourceVirtualEnvironmentVMStartupUpDelay || + startup[mkResourceVirtualEnvironmentVMStartupDownDelay] != dvResourceVirtualEnvironmentVMStartupDownDelay { + err := d.Set(mkResourceVirtualEnvironmentVMStartup, []interface{}{startup}) + diags = append(diags, diag.FromErr(err)...) + } else { + err := d.Set(mkResourceVirtualEnvironmentVMStartup, []interface{}{}) + diags = append(diags, diag.FromErr(err)...) + } + // Compare the VGA configuration to the one stored in the state. vga := map[string]interface{}{}