diff --git a/README.md b/README.md index e4d2b034..f574b9cd 100644 --- a/README.md +++ b/README.md @@ -314,6 +314,10 @@ This resource doesn't expose any additional attributes. ##### VM (proxmox_virtual_environment_vm) ###### Arguments +* `agent` - (Optional) The QEMU agent configuration + * `enabled` - (Optional) Whether to enable the QEMU agent (defaults to `false`) + * `trim` - (Optional) Whether to enable the FSTRIM feature in the QEMU agent (defaults to `false`) + * `type` - (Optional) The QEMU agent interface type (defaults to `virtio`) * `cdrom` - (Optional) The CDROM configuration * `enabled` - (Optional) Whether to enable the CDROM drive (defaults to `false`) * `file_id` - (Optional) A file ID for an ISO file (defaults to `cdrom` as in the physical drive) @@ -333,7 +337,9 @@ This resource doesn't expose any additional attributes. * `username` - (Required) The SSH username * `cpu` - (Optional) The CPU configuration * `cores` - (Optional) The number of CPU cores (defaults to `1`) + * `hotplugged` - (Optional) The number of hotplugged vCPUs (defaults to `0`) * `sockets` - (Optional) The number of CPU sockets (defaults to `1`) +* `description` - (Optional) The description * `disk` - (Optional) The disk configuration (multiple blocks supported) * `datastore_id` - (Optional) The ID of the datastore to create the disk in (defaults to `local-lvm`) * `enabled` - (Optional) Whether to enable the disk (defaults to `true`) @@ -345,7 +351,7 @@ This resource doesn't expose any additional attributes. * `dedicated` - (Optional) The dedicated memory in megabytes (defaults to `512`) * `floating` - (Optional) The floating memory in megabytes (defaults to `0`) * `shared` - (Optional) The shared memory in megabytes (defaults to `0`) -* `name` - (Optional) The virtual machine name +* `name` - (Optional) The name * `network_device` - (Optional) The network device configuration (multiple blocks supported) * `bridge` - (Optional) The name of the network bridge (defaults to `vmbr0`) * `enabled` - (Optional) Whether to enable the network device (defaults to `true`) @@ -354,7 +360,8 @@ This resource doesn't expose any additional attributes. * `vlan_id` - (Optional) The VLAN identifier * `node_name` - (Required) The name of the node to assign the virtual machine to * `os_type` - (Optional) The OS type (defaults to `other`) -* `vm_id` - (Optional) The virtual machine ID +* `pool_id` - (Optional) The ID of a pool to assign the virtual machine to +* `vm_id` - (Optional) The ID ###### Attributes This resource doesn't expose any additional attributes. diff --git a/example/resource_virtual_environment_vm.tf b/example/resource_virtual_environment_vm.tf index 698c589b..3678eca2 100644 --- a/example/resource_virtual_environment_vm.tf +++ b/example/resource_virtual_environment_vm.tf @@ -25,6 +25,8 @@ resource "proxmox_virtual_environment_vm" "example" { node_name = "${data.proxmox_virtual_environment_nodes.example.names[0]}" os_type = "l26" + pool_id = "${proxmox_virtual_environment_pool.example.id}" + vm_id = 2038 } resource "local_file" "example_ssh_private_key" { diff --git a/proxmox/virtual_environment_vm.go b/proxmox/virtual_environment_vm.go index e7323ade..61ada9ee 100644 --- a/proxmox/virtual_environment_vm.go +++ b/proxmox/virtual_environment_vm.go @@ -8,19 +8,21 @@ import ( "errors" "fmt" "net/url" + "strings" + "time" ) -// CreateVM creates an virtual machine. +// CreateVM creates a virtual machine. func (c *VirtualEnvironmentClient) CreateVM(nodeName string, d *VirtualEnvironmentVMCreateRequestBody) error { return c.DoRequest(hmPOST, fmt.Sprintf("nodes/%s/qemu", url.PathEscape(nodeName)), d, nil) } -// DeleteVM deletes an virtual machine. +// DeleteVM deletes a virtual machine. func (c *VirtualEnvironmentClient) DeleteVM(nodeName string, vmID int) error { return c.DoRequest(hmDELETE, fmt.Sprintf("nodes/%s/qemu/%d", url.PathEscape(nodeName), vmID), nil, nil) } -// GetVM retrieves an virtual machine. +// GetVM retrieves a virtual machine. func (c *VirtualEnvironmentClient) GetVM(nodeName string, vmID int) (*VirtualEnvironmentVMGetResponseData, error) { resBody := &VirtualEnvironmentVMGetResponseBody{} err := c.DoRequest(hmGET, fmt.Sprintf("nodes/%s/qemu/%d/config", url.PathEscape(nodeName), vmID), nil, resBody) @@ -64,11 +66,42 @@ VMID: return nil, errors.New("Unable to retrieve the next available VM identifier") } +// GetVMStatus retrieves the status for a virtual machine. +func (c *VirtualEnvironmentClient) GetVMStatus(nodeName string, vmID int) (*VirtualEnvironmentVMGetStatusResponseData, error) { + resBody := &VirtualEnvironmentVMGetStatusResponseBody{} + err := c.DoRequest(hmGET, fmt.Sprintf("nodes/%s/qemu/%d/status/current", url.PathEscape(nodeName), vmID), nil, resBody) + + if err != nil { + return nil, err + } + + if resBody.Data == nil { + return nil, errors.New("The server did not include a data object in the response") + } + + return resBody.Data, nil +} + // ListVMs retrieves a list of virtual machines. func (c *VirtualEnvironmentClient) ListVMs() ([]*VirtualEnvironmentVMListResponseData, error) { return nil, errors.New("Not implemented") } +// ShutdownVM shuts down a virtual machine. +func (c *VirtualEnvironmentClient) ShutdownVM(nodeName string, vmID int, d *VirtualEnvironmentVMShutdownRequestBody) error { + return c.DoRequest(hmPOST, fmt.Sprintf("nodes/%s/qemu/%d/status/shutdown", url.PathEscape(nodeName), vmID), d, nil) +} + +// StartVM starts a virtual machine. +func (c *VirtualEnvironmentClient) StartVM(nodeName string, vmID int) error { + return c.DoRequest(hmPOST, fmt.Sprintf("nodes/%s/qemu/%d/status/start", url.PathEscape(nodeName), vmID), nil, nil) +} + +// StopVM stops a virtual machine immediately. +func (c *VirtualEnvironmentClient) StopVM(nodeName string, vmID int) error { + return c.DoRequest(hmPOST, fmt.Sprintf("nodes/%s/qemu/%d/status/stop", url.PathEscape(nodeName), vmID), nil, nil) +} + // UpdateVM updates a virtual machine. func (c *VirtualEnvironmentClient) UpdateVM(nodeName string, vmID int, d *VirtualEnvironmentVMUpdateRequestBody) error { return c.DoRequest(hmPUT, fmt.Sprintf("nodes/%s/qemu/%d/config", url.PathEscape(nodeName), vmID), d, nil) @@ -78,3 +111,35 @@ func (c *VirtualEnvironmentClient) UpdateVM(nodeName string, vmID int, d *Virtua func (c *VirtualEnvironmentClient) UpdateVMAsync(nodeName string, vmID int, d *VirtualEnvironmentVMUpdateRequestBody) error { return c.DoRequest(hmPOST, fmt.Sprintf("nodes/%s/qemu/%d/config", url.PathEscape(nodeName), vmID), d, nil) } + +// WaitForState waits for a virtual machine to reach a specific state. +func (c *VirtualEnvironmentClient) WaitForState(nodeName string, vmID int, state string, timeout int, delay int) error { + state = strings.ToLower(state) + + timeDelay := int64(delay) + timeMax := float64(timeout) + timeStart := time.Now() + timeElapsed := timeStart.Sub(timeStart) + + for timeElapsed.Seconds() < timeMax { + if int64(timeElapsed.Seconds())%timeDelay == 0 { + data, err := c.GetVMStatus(nodeName, vmID) + + if err != nil { + return err + } + + if data.Status == state { + return nil + } + + time.Sleep(1 * time.Second) + } + + time.Sleep(200 * time.Millisecond) + + timeElapsed = time.Now().Sub(timeStart) + } + + return fmt.Errorf("Failed to wait for VM \"%d\" to enter the state \"%s\"", vmID, state) +} diff --git a/proxmox/virtual_environment_vm_types.go b/proxmox/virtual_environment_vm_types.go index 49ca22f6..7df62f96 100644 --- a/proxmox/virtual_environment_vm_types.go +++ b/proxmox/virtual_environment_vm_types.go @@ -225,6 +225,7 @@ type VirtualEnvironmentVMCreateRequestBody struct { OSType *string `json:"ostype,omitempty" url:"ostype,omitempty"` Overwrite *CustomBool `json:"force,omitempty" url:"force,omitempty,int"` PCIDevices CustomPCIDevices `json:"hostpci,omitempty" url:"hostpci,omitempty"` + PoolID *string `json:"pool,omitempty" url:"pool,omitempty"` Revert *string `json:"revert,omitempty" url:"revert,omitempty"` SATADevices CustomStorageDevices `json:"sata,omitempty" url:"sata,omitempty"` SCSIDevices CustomStorageDevices `json:"scsi,omitempty" url:"scsi,omitempty"` @@ -326,6 +327,28 @@ type VirtualEnvironmentVMGetResponseData struct { WatchdogDevice *CustomWatchdogDevice `json:"watchdog,omitempty"` } +// VirtualEnvironmentVMGetStatusResponseBody contains the body from a VM get status response. +type VirtualEnvironmentVMGetStatusResponseBody struct { + Data *VirtualEnvironmentVMGetStatusResponseData `json:"data,omitempty"` +} + +// VirtualEnvironmentVMGetStatusResponseData contains the data from a VM get status response. +type VirtualEnvironmentVMGetStatusResponseData struct { + AgentEnabled *CustomBool `json:"agent,omitempty"` + CPUCount *float64 `json:"cpus,omitempty"` + Lock *string `json:"lock,omitempty"` + MemoryAllocation *int `json:"maxmem,omitempty"` + Name *string `json:"name,omitempty"` + PID *string `json:"pid,omitempty"` + QMPStatus *string `json:"qmpstatus,omitempty"` + RootDiskSize *int `json:"maxdisk,omitempty"` + SpiceSupport *CustomBool `json:"spice,omitempty"` + Status string `json:"status,omitempty"` + Tags *string `json:"tags,omitempty"` + Uptime *int `json:"uptime,omitempty"` + VMID string `json:"vmid,omitempty"` +} + // VirtualEnvironmentVMListResponseBody contains the body from an virtual machine list response. type VirtualEnvironmentVMListResponseBody struct { Data []*VirtualEnvironmentVMListResponseData `json:"data,omitempty"` @@ -336,6 +359,14 @@ type VirtualEnvironmentVMListResponseData struct { ACPI *CustomBool `json:"acpi,omitempty" url:"acpi,omitempty,int"` } +// VirtualEnvironmentVMShutdownRequestBody contains the body for a VM shutdown request. +type VirtualEnvironmentVMShutdownRequestBody struct { + ForceStop *CustomBool `json:"forceStop,omitempty,int" url:"forceStop,omitempty,int"` + KeepActive *CustomBool `json:"keepActive,omitempty,int" url:"keepActive,omitempty,int"` + SkipLock *CustomBool `json:"skipLock,omitempty,int" url:"skipLock,omitempty,int"` + Timeout *int `json:"timeout,omitempty" url:"timeout,omitempty"` +} + // VirtualEnvironmentVMUpdateRequestBody contains the data for an virtual machine update request. type VirtualEnvironmentVMUpdateRequestBody VirtualEnvironmentVMCreateRequestBody diff --git a/proxmoxtf/resource_virtual_environment_file.go b/proxmoxtf/resource_virtual_environment_file.go index 9f47a584..9f231347 100644 --- a/proxmoxtf/resource_virtual_environment_file.go +++ b/proxmoxtf/resource_virtual_environment_file.go @@ -24,6 +24,12 @@ import ( ) const ( + dvResourceVirtualEnvironmentFileContentType = "" + dvResourceVirtualEnvironmentFileOverrideFileName = "" + dvResourceVirtualEnvironmentFileSourceChanged = false + dvResourceVirtualEnvironmentFileSourceChecksum = "" + dvResourceVirtualEnvironmentFileSourceInsecure = false + mkResourceVirtualEnvironmentFileContentType = "content_type" mkResourceVirtualEnvironmentFileDatastoreID = "datastore_id" mkResourceVirtualEnvironmentFileFileModificationDate = "file_modification_date" @@ -46,7 +52,7 @@ func resourceVirtualEnvironmentFile() *schema.Resource { Description: "The content type", Optional: true, ForceNew: true, - Default: "", + Default: dvResourceVirtualEnvironmentFileContentType, }, mkResourceVirtualEnvironmentFileDatastoreID: &schema.Schema{ Type: schema.TypeString, @@ -82,7 +88,7 @@ func resourceVirtualEnvironmentFile() *schema.Resource { Description: "The file name to use instead of the source file name", Optional: true, ForceNew: true, - Default: "", + Default: dvResourceVirtualEnvironmentFileOverrideFileName, }, mkResourceVirtualEnvironmentFileNodeName: &schema.Schema{ Type: schema.TypeString, @@ -101,21 +107,21 @@ func resourceVirtualEnvironmentFile() *schema.Resource { Description: "Whether the source has changed since the last run", Optional: true, ForceNew: true, - Default: false, + Default: dvResourceVirtualEnvironmentFileSourceChanged, }, mkResourceVirtualEnvironmentFileSourceChecksum: &schema.Schema{ Type: schema.TypeString, Description: "The SHA256 checksum of the source file", Optional: true, ForceNew: true, - Default: "", + Default: dvResourceVirtualEnvironmentFileSourceChecksum, }, mkResourceVirtualEnvironmentFileSourceInsecure: &schema.Schema{ Type: schema.TypeBool, Description: "Whether to skip the TLS verification step for HTTPS sources", Optional: true, ForceNew: true, - Default: false, + Default: dvResourceVirtualEnvironmentFileSourceInsecure, }, }, Create: resourceVirtualEnvironmentFileCreate, diff --git a/proxmoxtf/resource_virtual_environment_group.go b/proxmoxtf/resource_virtual_environment_group.go index 7b01b3c5..03bb9442 100644 --- a/proxmoxtf/resource_virtual_environment_group.go +++ b/proxmoxtf/resource_virtual_environment_group.go @@ -12,6 +12,8 @@ import ( ) const ( + dvResourceVirtualEnvironmentGroupComment = "" + mkResourceVirtualEnvironmentGroupACL = "acl" mkResourceVirtualEnvironmentGroupACLPath = "path" mkResourceVirtualEnvironmentGroupACLPropagate = "propagate" @@ -56,7 +58,7 @@ func resourceVirtualEnvironmentGroup() *schema.Resource { Type: schema.TypeString, Description: "The group comment", Optional: true, - Default: "", + Default: dvResourceVirtualEnvironmentGroupComment, }, mkResourceVirtualEnvironmentGroupID: &schema.Schema{ Type: schema.TypeString, diff --git a/proxmoxtf/resource_virtual_environment_pool.go b/proxmoxtf/resource_virtual_environment_pool.go index c3235e70..14a50955 100644 --- a/proxmoxtf/resource_virtual_environment_pool.go +++ b/proxmoxtf/resource_virtual_environment_pool.go @@ -12,6 +12,8 @@ import ( ) const ( + dvResourceVirtualEnvironmentPoolComment = "" + mkResourceVirtualEnvironmentPoolComment = "comment" mkResourceVirtualEnvironmentPoolMembers = "members" mkResourceVirtualEnvironmentPoolMembersDatastoreID = "datastore_id" @@ -29,7 +31,7 @@ func resourceVirtualEnvironmentPool() *schema.Resource { Type: schema.TypeString, Description: "The pool comment", Optional: true, - Default: "", + Default: dvResourceVirtualEnvironmentPoolComment, }, mkResourceVirtualEnvironmentPoolMembers: &schema.Schema{ Type: schema.TypeList, diff --git a/proxmoxtf/resource_virtual_environment_user.go b/proxmoxtf/resource_virtual_environment_user.go index ae446e3e..a90c3b3b 100644 --- a/proxmoxtf/resource_virtual_environment_user.go +++ b/proxmoxtf/resource_virtual_environment_user.go @@ -14,6 +14,13 @@ import ( ) const ( + dvResourceVirtualEnvironmentUserComment = "" + dvResourceVirtualEnvironmentUserEmail = "" + dvResourceVirtualEnvironmentUserEnabled = true + dvResourceVirtualEnvironmentUserFirstName = "" + dvResourceVirtualEnvironmentUserKeys = "" + dvResourceVirtualEnvironmentUserLastName = "" + mkResourceVirtualEnvironmentUserACL = "acl" mkResourceVirtualEnvironmentUserACLPath = "path" mkResourceVirtualEnvironmentUserACLPropagate = "propagate" @@ -65,19 +72,19 @@ func resourceVirtualEnvironmentUser() *schema.Resource { Type: schema.TypeString, Description: "The user comment", Optional: true, - Default: "", + Default: dvResourceVirtualEnvironmentUserComment, }, mkResourceVirtualEnvironmentUserEmail: &schema.Schema{ Type: schema.TypeString, Description: "The user's email address", Optional: true, - Default: "", + Default: dvResourceVirtualEnvironmentUserEmail, }, mkResourceVirtualEnvironmentUserEnabled: &schema.Schema{ Type: schema.TypeBool, Description: "Whether the user account is enabled", Optional: true, - Default: true, + Default: dvResourceVirtualEnvironmentUserEnabled, }, mkResourceVirtualEnvironmentUserExpirationDate: &schema.Schema{ Type: schema.TypeString, @@ -90,7 +97,7 @@ func resourceVirtualEnvironmentUser() *schema.Resource { Type: schema.TypeString, Description: "The user's first name", Optional: true, - Default: "", + Default: dvResourceVirtualEnvironmentUserFirstName, }, mkResourceVirtualEnvironmentUserGroups: &schema.Schema{ Type: schema.TypeSet, @@ -105,13 +112,13 @@ func resourceVirtualEnvironmentUser() *schema.Resource { Type: schema.TypeString, Description: "The user's keys", Optional: true, - Default: "", + Default: dvResourceVirtualEnvironmentUserKeys, }, mkResourceVirtualEnvironmentUserLastName: &schema.Schema{ Type: schema.TypeString, Description: "The user's last name", Optional: true, - Default: "", + Default: dvResourceVirtualEnvironmentUserLastName, }, mkResourceVirtualEnvironmentUserPassword: &schema.Schema{ Type: schema.TypeString, diff --git a/proxmoxtf/resource_virtual_environment_vm.go b/proxmoxtf/resource_virtual_environment_vm.go index 0e9e5c48..bc0e7b3f 100644 --- a/proxmoxtf/resource_virtual_environment_vm.go +++ b/proxmoxtf/resource_virtual_environment_vm.go @@ -6,7 +6,6 @@ package proxmoxtf import ( "fmt" - "log" "strconv" "strings" @@ -16,12 +15,17 @@ import ( ) const ( + dvResourceVirtualEnvironmentVMAgentEnabled = false + dvResourceVirtualEnvironmentVMAgentTrim = false + dvResourceVirtualEnvironmentVMAgentType = "virtio" dvResourceVirtualEnvironmentVMCDROMEnabled = false dvResourceVirtualEnvironmentVMCDROMFileID = "" dvResourceVirtualEnvironmentVMCloudInitDNSDomain = "" dvResourceVirtualEnvironmentVMCloudInitDNSServer = "" dvResourceVirtualEnvironmentVMCPUCores = 1 + dvResourceVirtualEnvironmentVMCPUHotplugged = 0 dvResourceVirtualEnvironmentVMCPUSockets = 1 + dvResourceVirtualEnvironmentVMDescription = "" dvResourceVirtualEnvironmentVMDiskDatastoreID = "local-lvm" dvResourceVirtualEnvironmentVMDiskEnabled = true dvResourceVirtualEnvironmentVMDiskFileFormat = "qcow2" @@ -38,8 +42,13 @@ const ( dvResourceVirtualEnvironmentVMNetworkDeviceModel = "virtio" dvResourceVirtualEnvironmentVMNetworkDeviceVLANID = -1 dvResourceVirtualEnvironmentVMOSType = "other" + dvResourceVirtualEnvironmentVMPoolID = "" dvResourceVirtualEnvironmentVMVMID = -1 + mkResourceVirtualEnvironmentVMAgent = "agent" + mkResourceVirtualEnvironmentVMAgentEnabled = "enabled" + mkResourceVirtualEnvironmentVMAgentTrim = "trim" + mkResourceVirtualEnvironmentVMAgentType = "type" mkResourceVirtualEnvironmentVMCDROM = "cdrom" mkResourceVirtualEnvironmentVMCDROMEnabled = "enabled" mkResourceVirtualEnvironmentVMCDROMFileID = "file_id" @@ -59,7 +68,9 @@ const ( mkResourceVirtualEnvironmentVMCloudInitUserAccountUsername = "username" mkResourceVirtualEnvironmentVMCPU = "cpu" mkResourceVirtualEnvironmentVMCPUCores = "cores" + mkResourceVirtualEnvironmentVMCPUHotplugged = "hotplugged" mkResourceVirtualEnvironmentVMCPUSockets = "sockets" + mkResourceVirtualEnvironmentVMDescription = "description" mkResourceVirtualEnvironmentVMDisk = "disk" mkResourceVirtualEnvironmentVMDiskDatastoreID = "datastore_id" mkResourceVirtualEnvironmentVMDiskEnabled = "enabled" @@ -80,12 +91,55 @@ const ( mkResourceVirtualEnvironmentVMNetworkDeviceVLANID = "vlan_id" mkResourceVirtualEnvironmentVMNodeName = "node_name" mkResourceVirtualEnvironmentVMOSType = "os_type" + mkResourceVirtualEnvironmentVMPoolID = "pool_id" mkResourceVirtualEnvironmentVMVMID = "vm_id" ) func resourceVirtualEnvironmentVM() *schema.Resource { return &schema.Resource{ Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentVMAgent: &schema.Schema{ + Type: schema.TypeList, + Description: "The QEMU agent configuration", + Optional: true, + DefaultFunc: func() (interface{}, error) { + defaultList := make([]interface{}, 1) + defaultMap := make(map[string]interface{}) + + defaultMap[mkResourceVirtualEnvironmentVMAgentEnabled] = dvResourceVirtualEnvironmentVMAgentEnabled + defaultMap[mkResourceVirtualEnvironmentVMAgentTrim] = dvResourceVirtualEnvironmentVMAgentTrim + defaultMap[mkResourceVirtualEnvironmentVMAgentType] = dvResourceVirtualEnvironmentVMAgentType + + defaultList[0] = defaultMap + + return defaultList, nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentVMAgentEnabled: { + Type: schema.TypeBool, + Optional: true, + Description: "Whether to enable the QEMU agent", + Default: dvResourceVirtualEnvironmentVMAgentEnabled, + }, + mkResourceVirtualEnvironmentVMAgentTrim: { + Type: schema.TypeBool, + Optional: true, + Description: "Whether to enable the FSTRIM feature in the QEMU agent", + Default: dvResourceVirtualEnvironmentVMAgentTrim, + }, + mkResourceVirtualEnvironmentVMAgentType: { + Type: schema.TypeString, + Optional: true, + Description: "The QEMU agent interface type", + Default: dvResourceVirtualEnvironmentVMAgentType, + ValidateFunc: getQEMUAgentTypeValidator(), + }, + }, + }, + MaxItems: 1, + MinItems: 0, + }, mkResourceVirtualEnvironmentVMCDROM: &schema.Schema{ Type: schema.TypeList, Description: "The CDROM drive", @@ -261,6 +315,7 @@ func resourceVirtualEnvironmentVM() *schema.Resource { defaultMap := make(map[string]interface{}) defaultMap[mkResourceVirtualEnvironmentVMCPUCores] = dvResourceVirtualEnvironmentVMCPUCores + defaultMap[mkResourceVirtualEnvironmentVMCPUHotplugged] = dvResourceVirtualEnvironmentVMCPUHotplugged defaultMap[mkResourceVirtualEnvironmentVMCPUSockets] = dvResourceVirtualEnvironmentVMCPUSockets defaultList[0] = defaultMap @@ -276,6 +331,13 @@ func resourceVirtualEnvironmentVM() *schema.Resource { Default: dvResourceVirtualEnvironmentVMCPUCores, ValidateFunc: validation.IntBetween(1, 2304), }, + mkResourceVirtualEnvironmentVMCPUHotplugged: { + Type: schema.TypeInt, + Optional: true, + Description: "The number of hotplugged vCPUs", + Default: dvResourceVirtualEnvironmentVMCPUHotplugged, + ValidateFunc: validation.IntBetween(0, 2304), + }, mkResourceVirtualEnvironmentVMCPUSockets: { Type: schema.TypeInt, Optional: true, @@ -288,6 +350,12 @@ func resourceVirtualEnvironmentVM() *schema.Resource { MaxItems: 1, MinItems: 0, }, + mkResourceVirtualEnvironmentVMDescription: { + Type: schema.TypeString, + Optional: true, + Description: "The description", + Default: dvResourceVirtualEnvironmentVMDescription, + }, mkResourceVirtualEnvironmentVMDisk: &schema.Schema{ Type: schema.TypeList, Description: "The disk devices", @@ -461,6 +529,12 @@ func resourceVirtualEnvironmentVM() *schema.Resource { Default: dvResourceVirtualEnvironmentVMOSType, ValidateFunc: getOSTypeValidator(), }, + mkResourceVirtualEnvironmentVMPoolID: { + Type: schema.TypeString, + Optional: true, + Description: "The ID of the pool to assign the virtual machine to", + Default: dvResourceVirtualEnvironmentVMPoolID, + }, mkResourceVirtualEnvironmentVMVMID: { Type: schema.TypeInt, Optional: true, @@ -486,6 +560,23 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e } schema := resourceVirtualEnvironmentVM().Schema + agent := d.Get(mkResourceVirtualEnvironmentVMAgent).([]interface{}) + + if len(agent) == 0 { + agentDefault, err := schema[mkResourceVirtualEnvironmentVMAgent].DefaultValue() + + if err != nil { + return err + } + + agent = agentDefault.([]interface{}) + } + + agentBlock := agent[0].(map[string]interface{}) + agentEnabled := proxmox.CustomBool(agentBlock[mkResourceVirtualEnvironmentVMAgentEnabled].(bool)) + agentTrim := proxmox.CustomBool(agentBlock[mkResourceVirtualEnvironmentVMAgentTrim].(bool)) + agentType := agentBlock[mkResourceVirtualEnvironmentVMAgentType].(string) + cdrom := d.Get(mkResourceVirtualEnvironmentVMCDROM).([]interface{}) if len(cdrom) == 0 { @@ -609,8 +700,10 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e cpuBlock := cpu[0].(map[string]interface{}) cpuCores := cpuBlock[mkResourceVirtualEnvironmentVMCPUCores].(int) + cpuHotplugged := cpuBlock[mkResourceVirtualEnvironmentVMCPUHotplugged].(int) cpuSockets := cpuBlock[mkResourceVirtualEnvironmentVMCPUSockets].(int) + description := d.Get(mkResourceVirtualEnvironmentVMDescription).(string) disk := d.Get(mkResourceVirtualEnvironmentVMDisk).([]interface{}) scsiDevices := make(proxmox.CustomStorageDevices, len(disk)) @@ -689,6 +782,7 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e nodeName := d.Get(mkResourceVirtualEnvironmentVMNodeName).(string) osType := d.Get(mkResourceVirtualEnvironmentVMOSType).(string) + poolID := d.Get(mkResourceVirtualEnvironmentVMPoolID).(string) vmID := d.Get(mkResourceVirtualEnvironmentVMVMID).(int) if vmID == -1 { @@ -703,7 +797,6 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e var memorySharedObject *proxmox.CustomSharedMemory - agentEnabled := proxmox.CustomBool(true) bootDisk := "scsi0" bootOrder := "c" @@ -738,7 +831,11 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e tabletDeviceEnabled := proxmox.CustomBool(true) body := &proxmox.VirtualEnvironmentVMCreateRequestBody{ - Agent: &proxmox.CustomAgent{Enabled: &agentEnabled}, + Agent: &proxmox.CustomAgent{ + Enabled: &agentEnabled, + TrimClonedDisks: &agentTrim, + Type: &agentType, + }, BootDisk: &bootDisk, BootOrder: &bootOrder, CloudInitConfig: cloudInitConfig, @@ -758,10 +855,22 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e VMID: &vmID, } + if cpuHotplugged > 0 { + body.VirtualCPUCount = &cpuHotplugged + } + + if description != "" { + body.Description = &description + } + if name != "" { body.Name = &name } + if poolID != "" { + body.PoolID = &poolID + } + err = veClient.CreateVM(nodeName, body) if err != nil { @@ -770,10 +879,10 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e d.SetId(strconv.Itoa(vmID)) - return resourceVirtualEnvironmentVMImportDisks(d, m) + return resourceVirtualEnvironmentVMCreateImportedDisks(d, m) } -func resourceVirtualEnvironmentVMImportDisks(d *schema.ResourceData, m interface{}) error { +func resourceVirtualEnvironmentVMCreateImportedDisks(d *schema.ResourceData, m interface{}) error { config := m.(providerConfiguration) veClient, err := config.GetVEClient() @@ -838,10 +947,6 @@ func resourceVirtualEnvironmentVMImportDisks(d *schema.ResourceData, m interface // Execute the commands on the node and wait for the result. // This is a highly experimental approach to disk imports and is not recommended by Proxmox. if len(commands) > 0 { - for _, cmd := range commands { - log.Printf("[DEBUG] Node command: %s", cmd) - } - err = veClient.ExecuteNodeCommands(nodeName, commands) if err != nil { @@ -849,6 +954,37 @@ func resourceVirtualEnvironmentVMImportDisks(d *schema.ResourceData, m interface } } + return resourceVirtualEnvironmentVMCreateStart(d, m) +} + +func resourceVirtualEnvironmentVMCreateStart(d *schema.ResourceData, m interface{}) error { + config := m.(providerConfiguration) + veClient, err := config.GetVEClient() + + if err != nil { + return err + } + + nodeName := d.Get(mkResourceVirtualEnvironmentVMNodeName).(string) + vmID, err := strconv.Atoi(d.Id()) + + if err != nil { + return err + } + + // Start the virtual machine and wait for it to reach a running state before continuing. + err = veClient.StartVM(nodeName, vmID) + + if err != nil { + return err + } + + err = veClient.WaitForState(nodeName, vmID, "running", 120, 5) + + if err != nil { + return err + } + return resourceVirtualEnvironmentVMRead(d, m) } @@ -893,6 +1029,25 @@ func resourceVirtualEnvironmentVMDelete(d *schema.ResourceData, m interface{}) e return err } + // Shut down the virtual machine before deleting it. + forceStop := proxmox.CustomBool(true) + shutdownTimeout := 300 + + err = veClient.ShutdownVM(nodeName, vmID, &proxmox.VirtualEnvironmentVMShutdownRequestBody{ + ForceStop: &forceStop, + Timeout: &shutdownTimeout, + }) + + if err != nil { + return err + } + + err = veClient.WaitForState(nodeName, vmID, "stopped", 30, 5) + + if err != nil { + return err + } + err = veClient.DeleteVM(nodeName, vmID) if err != nil { @@ -905,6 +1060,13 @@ func resourceVirtualEnvironmentVMDelete(d *schema.ResourceData, m interface{}) e return err } + // Wait for the state to become unavailable as that clearly indicates the destruction of the VM. + err = veClient.WaitForState(nodeName, vmID, "", 30, 2) + + if err == nil { + return fmt.Errorf("Failed to delete VM \"%d\"", vmID) + } + d.SetId("") return nil diff --git a/proxmoxtf/resource_virtual_environment_vm_test.go b/proxmoxtf/resource_virtual_environment_vm_test.go index 5bd23093..162cfe7b 100644 --- a/proxmoxtf/resource_virtual_environment_vm_test.go +++ b/proxmoxtf/resource_virtual_environment_vm_test.go @@ -31,12 +31,14 @@ func TestResourceVirtualEnvironmentVMSchema(t *testing.T) { mkResourceVirtualEnvironmentVMCDROM, mkResourceVirtualEnvironmentVMCloudInit, mkResourceVirtualEnvironmentVMCPU, + mkResourceVirtualEnvironmentVMDescription, mkResourceVirtualEnvironmentVMDisk, mkResourceVirtualEnvironmentVMKeyboardLayout, mkResourceVirtualEnvironmentVMMemory, mkResourceVirtualEnvironmentVMName, mkResourceVirtualEnvironmentVMNetworkDevice, mkResourceVirtualEnvironmentVMOSType, + mkResourceVirtualEnvironmentVMPoolID, mkResourceVirtualEnvironmentVMVMID, }) @@ -44,26 +46,48 @@ func TestResourceVirtualEnvironmentVMSchema(t *testing.T) { mkResourceVirtualEnvironmentVMCDROM, mkResourceVirtualEnvironmentVMCloudInit, mkResourceVirtualEnvironmentVMCPU, + mkResourceVirtualEnvironmentVMDescription, mkResourceVirtualEnvironmentVMDisk, mkResourceVirtualEnvironmentVMKeyboardLayout, mkResourceVirtualEnvironmentVMMemory, mkResourceVirtualEnvironmentVMName, mkResourceVirtualEnvironmentVMNetworkDevice, mkResourceVirtualEnvironmentVMOSType, + mkResourceVirtualEnvironmentVMPoolID, mkResourceVirtualEnvironmentVMVMID, }, []schema.ValueType{ schema.TypeList, schema.TypeList, schema.TypeList, - schema.TypeList, schema.TypeString, schema.TypeList, schema.TypeString, schema.TypeList, schema.TypeString, + schema.TypeList, + schema.TypeString, + schema.TypeString, schema.TypeInt, }) + agentSchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentVMAgent) + + testOptionalArguments(t, agentSchema, []string{ + mkResourceVirtualEnvironmentVMAgentEnabled, + mkResourceVirtualEnvironmentVMAgentTrim, + mkResourceVirtualEnvironmentVMAgentType, + }) + + testSchemaValueTypes(t, agentSchema, []string{ + mkResourceVirtualEnvironmentVMAgentEnabled, + mkResourceVirtualEnvironmentVMAgentTrim, + mkResourceVirtualEnvironmentVMAgentType, + }, []schema.ValueType{ + schema.TypeBool, + schema.TypeBool, + schema.TypeString, + }) + cdromSchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentVMCDROM) testOptionalArguments(t, cdromSchema, []string{ @@ -179,15 +203,18 @@ func TestResourceVirtualEnvironmentVMSchema(t *testing.T) { testOptionalArguments(t, cpuSchema, []string{ mkResourceVirtualEnvironmentVMCPUCores, + mkResourceVirtualEnvironmentVMCPUHotplugged, mkResourceVirtualEnvironmentVMCPUSockets, }) testSchemaValueTypes(t, cpuSchema, []string{ mkResourceVirtualEnvironmentVMCPUCores, + mkResourceVirtualEnvironmentVMCPUHotplugged, mkResourceVirtualEnvironmentVMCPUSockets, }, []schema.ValueType{ schema.TypeInt, schema.TypeInt, + schema.TypeInt, }) diskSchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentVMDisk) diff --git a/proxmoxtf/utils.go b/proxmoxtf/utils.go index c01d4a21..dcc48a4d 100644 --- a/proxmoxtf/utils.go +++ b/proxmoxtf/utils.go @@ -114,6 +114,10 @@ func getOSTypeValidator() schema.SchemaValidateFunc { }, false) } +func getQEMUAgentTypeValidator() schema.SchemaValidateFunc { + return validation.StringInSlice([]string{"isa", "virtio"}, false) +} + func getVLANIDValidator() schema.SchemaValidateFunc { return func(i interface{}, k string) (ws []string, es []error) { min := 1