0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-08-22 19:38:35 +00:00

Continued work on VM resource

This commit is contained in:
Dan Petersen 2019-12-27 19:13:40 +01:00
parent 408da5f420
commit 08e389dac8
11 changed files with 343 additions and 28 deletions

View File

@ -314,6 +314,10 @@ This resource doesn't expose any additional attributes.
##### VM (proxmox_virtual_environment_vm) ##### VM (proxmox_virtual_environment_vm)
###### Arguments ###### 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 * `cdrom` - (Optional) The CDROM configuration
* `enabled` - (Optional) Whether to enable the CDROM drive (defaults to `false`) * `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) * `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 * `username` - (Required) The SSH username
* `cpu` - (Optional) The CPU configuration * `cpu` - (Optional) The CPU configuration
* `cores` - (Optional) The number of CPU cores (defaults to `1`) * `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`) * `sockets` - (Optional) The number of CPU sockets (defaults to `1`)
* `description` - (Optional) The description
* `disk` - (Optional) The disk configuration (multiple blocks supported) * `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`) * `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`) * `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`) * `dedicated` - (Optional) The dedicated memory in megabytes (defaults to `512`)
* `floating` - (Optional) The floating memory in megabytes (defaults to `0`) * `floating` - (Optional) The floating memory in megabytes (defaults to `0`)
* `shared` - (Optional) The shared 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) * `network_device` - (Optional) The network device configuration (multiple blocks supported)
* `bridge` - (Optional) The name of the network bridge (defaults to `vmbr0`) * `bridge` - (Optional) The name of the network bridge (defaults to `vmbr0`)
* `enabled` - (Optional) Whether to enable the network device (defaults to `true`) * `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 * `vlan_id` - (Optional) The VLAN identifier
* `node_name` - (Required) The name of the node to assign the virtual machine to * `node_name` - (Required) The name of the node to assign the virtual machine to
* `os_type` - (Optional) The OS type (defaults to `other`) * `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 ###### Attributes
This resource doesn't expose any additional attributes. This resource doesn't expose any additional attributes.

View File

@ -25,6 +25,8 @@ resource "proxmox_virtual_environment_vm" "example" {
node_name = "${data.proxmox_virtual_environment_nodes.example.names[0]}" node_name = "${data.proxmox_virtual_environment_nodes.example.names[0]}"
os_type = "l26" os_type = "l26"
pool_id = "${proxmox_virtual_environment_pool.example.id}"
vm_id = 2038
} }
resource "local_file" "example_ssh_private_key" { resource "local_file" "example_ssh_private_key" {

View File

@ -8,19 +8,21 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/url" "net/url"
"strings"
"time"
) )
// CreateVM creates an virtual machine. // CreateVM creates a virtual machine.
func (c *VirtualEnvironmentClient) CreateVM(nodeName string, d *VirtualEnvironmentVMCreateRequestBody) error { func (c *VirtualEnvironmentClient) CreateVM(nodeName string, d *VirtualEnvironmentVMCreateRequestBody) error {
return c.DoRequest(hmPOST, fmt.Sprintf("nodes/%s/qemu", url.PathEscape(nodeName)), d, nil) 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 { 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) 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) { func (c *VirtualEnvironmentClient) GetVM(nodeName string, vmID int) (*VirtualEnvironmentVMGetResponseData, error) {
resBody := &VirtualEnvironmentVMGetResponseBody{} resBody := &VirtualEnvironmentVMGetResponseBody{}
err := c.DoRequest(hmGET, fmt.Sprintf("nodes/%s/qemu/%d/config", url.PathEscape(nodeName), vmID), nil, resBody) 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") 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. // ListVMs retrieves a list of virtual machines.
func (c *VirtualEnvironmentClient) ListVMs() ([]*VirtualEnvironmentVMListResponseData, error) { func (c *VirtualEnvironmentClient) ListVMs() ([]*VirtualEnvironmentVMListResponseData, error) {
return nil, errors.New("Not implemented") 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. // UpdateVM updates a virtual machine.
func (c *VirtualEnvironmentClient) UpdateVM(nodeName string, vmID int, d *VirtualEnvironmentVMUpdateRequestBody) error { 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) 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 { 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) 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)
}

View File

@ -225,6 +225,7 @@ type VirtualEnvironmentVMCreateRequestBody struct {
OSType *string `json:"ostype,omitempty" url:"ostype,omitempty"` OSType *string `json:"ostype,omitempty" url:"ostype,omitempty"`
Overwrite *CustomBool `json:"force,omitempty" url:"force,omitempty,int"` Overwrite *CustomBool `json:"force,omitempty" url:"force,omitempty,int"`
PCIDevices CustomPCIDevices `json:"hostpci,omitempty" url:"hostpci,omitempty"` PCIDevices CustomPCIDevices `json:"hostpci,omitempty" url:"hostpci,omitempty"`
PoolID *string `json:"pool,omitempty" url:"pool,omitempty"`
Revert *string `json:"revert,omitempty" url:"revert,omitempty"` Revert *string `json:"revert,omitempty" url:"revert,omitempty"`
SATADevices CustomStorageDevices `json:"sata,omitempty" url:"sata,omitempty"` SATADevices CustomStorageDevices `json:"sata,omitempty" url:"sata,omitempty"`
SCSIDevices CustomStorageDevices `json:"scsi,omitempty" url:"scsi,omitempty"` SCSIDevices CustomStorageDevices `json:"scsi,omitempty" url:"scsi,omitempty"`
@ -326,6 +327,28 @@ type VirtualEnvironmentVMGetResponseData struct {
WatchdogDevice *CustomWatchdogDevice `json:"watchdog,omitempty"` 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. // VirtualEnvironmentVMListResponseBody contains the body from an virtual machine list response.
type VirtualEnvironmentVMListResponseBody struct { type VirtualEnvironmentVMListResponseBody struct {
Data []*VirtualEnvironmentVMListResponseData `json:"data,omitempty"` Data []*VirtualEnvironmentVMListResponseData `json:"data,omitempty"`
@ -336,6 +359,14 @@ type VirtualEnvironmentVMListResponseData struct {
ACPI *CustomBool `json:"acpi,omitempty" url:"acpi,omitempty,int"` 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. // VirtualEnvironmentVMUpdateRequestBody contains the data for an virtual machine update request.
type VirtualEnvironmentVMUpdateRequestBody VirtualEnvironmentVMCreateRequestBody type VirtualEnvironmentVMUpdateRequestBody VirtualEnvironmentVMCreateRequestBody

View File

@ -24,6 +24,12 @@ import (
) )
const ( const (
dvResourceVirtualEnvironmentFileContentType = ""
dvResourceVirtualEnvironmentFileOverrideFileName = ""
dvResourceVirtualEnvironmentFileSourceChanged = false
dvResourceVirtualEnvironmentFileSourceChecksum = ""
dvResourceVirtualEnvironmentFileSourceInsecure = false
mkResourceVirtualEnvironmentFileContentType = "content_type" mkResourceVirtualEnvironmentFileContentType = "content_type"
mkResourceVirtualEnvironmentFileDatastoreID = "datastore_id" mkResourceVirtualEnvironmentFileDatastoreID = "datastore_id"
mkResourceVirtualEnvironmentFileFileModificationDate = "file_modification_date" mkResourceVirtualEnvironmentFileFileModificationDate = "file_modification_date"
@ -46,7 +52,7 @@ func resourceVirtualEnvironmentFile() *schema.Resource {
Description: "The content type", Description: "The content type",
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
Default: "", Default: dvResourceVirtualEnvironmentFileContentType,
}, },
mkResourceVirtualEnvironmentFileDatastoreID: &schema.Schema{ mkResourceVirtualEnvironmentFileDatastoreID: &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
@ -82,7 +88,7 @@ func resourceVirtualEnvironmentFile() *schema.Resource {
Description: "The file name to use instead of the source file name", Description: "The file name to use instead of the source file name",
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
Default: "", Default: dvResourceVirtualEnvironmentFileOverrideFileName,
}, },
mkResourceVirtualEnvironmentFileNodeName: &schema.Schema{ mkResourceVirtualEnvironmentFileNodeName: &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
@ -101,21 +107,21 @@ func resourceVirtualEnvironmentFile() *schema.Resource {
Description: "Whether the source has changed since the last run", Description: "Whether the source has changed since the last run",
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
Default: false, Default: dvResourceVirtualEnvironmentFileSourceChanged,
}, },
mkResourceVirtualEnvironmentFileSourceChecksum: &schema.Schema{ mkResourceVirtualEnvironmentFileSourceChecksum: &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Description: "The SHA256 checksum of the source file", Description: "The SHA256 checksum of the source file",
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
Default: "", Default: dvResourceVirtualEnvironmentFileSourceChecksum,
}, },
mkResourceVirtualEnvironmentFileSourceInsecure: &schema.Schema{ mkResourceVirtualEnvironmentFileSourceInsecure: &schema.Schema{
Type: schema.TypeBool, Type: schema.TypeBool,
Description: "Whether to skip the TLS verification step for HTTPS sources", Description: "Whether to skip the TLS verification step for HTTPS sources",
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
Default: false, Default: dvResourceVirtualEnvironmentFileSourceInsecure,
}, },
}, },
Create: resourceVirtualEnvironmentFileCreate, Create: resourceVirtualEnvironmentFileCreate,

View File

@ -12,6 +12,8 @@ import (
) )
const ( const (
dvResourceVirtualEnvironmentGroupComment = ""
mkResourceVirtualEnvironmentGroupACL = "acl" mkResourceVirtualEnvironmentGroupACL = "acl"
mkResourceVirtualEnvironmentGroupACLPath = "path" mkResourceVirtualEnvironmentGroupACLPath = "path"
mkResourceVirtualEnvironmentGroupACLPropagate = "propagate" mkResourceVirtualEnvironmentGroupACLPropagate = "propagate"
@ -56,7 +58,7 @@ func resourceVirtualEnvironmentGroup() *schema.Resource {
Type: schema.TypeString, Type: schema.TypeString,
Description: "The group comment", Description: "The group comment",
Optional: true, Optional: true,
Default: "", Default: dvResourceVirtualEnvironmentGroupComment,
}, },
mkResourceVirtualEnvironmentGroupID: &schema.Schema{ mkResourceVirtualEnvironmentGroupID: &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,

View File

@ -12,6 +12,8 @@ import (
) )
const ( const (
dvResourceVirtualEnvironmentPoolComment = ""
mkResourceVirtualEnvironmentPoolComment = "comment" mkResourceVirtualEnvironmentPoolComment = "comment"
mkResourceVirtualEnvironmentPoolMembers = "members" mkResourceVirtualEnvironmentPoolMembers = "members"
mkResourceVirtualEnvironmentPoolMembersDatastoreID = "datastore_id" mkResourceVirtualEnvironmentPoolMembersDatastoreID = "datastore_id"
@ -29,7 +31,7 @@ func resourceVirtualEnvironmentPool() *schema.Resource {
Type: schema.TypeString, Type: schema.TypeString,
Description: "The pool comment", Description: "The pool comment",
Optional: true, Optional: true,
Default: "", Default: dvResourceVirtualEnvironmentPoolComment,
}, },
mkResourceVirtualEnvironmentPoolMembers: &schema.Schema{ mkResourceVirtualEnvironmentPoolMembers: &schema.Schema{
Type: schema.TypeList, Type: schema.TypeList,

View File

@ -14,6 +14,13 @@ import (
) )
const ( const (
dvResourceVirtualEnvironmentUserComment = ""
dvResourceVirtualEnvironmentUserEmail = ""
dvResourceVirtualEnvironmentUserEnabled = true
dvResourceVirtualEnvironmentUserFirstName = ""
dvResourceVirtualEnvironmentUserKeys = ""
dvResourceVirtualEnvironmentUserLastName = ""
mkResourceVirtualEnvironmentUserACL = "acl" mkResourceVirtualEnvironmentUserACL = "acl"
mkResourceVirtualEnvironmentUserACLPath = "path" mkResourceVirtualEnvironmentUserACLPath = "path"
mkResourceVirtualEnvironmentUserACLPropagate = "propagate" mkResourceVirtualEnvironmentUserACLPropagate = "propagate"
@ -65,19 +72,19 @@ func resourceVirtualEnvironmentUser() *schema.Resource {
Type: schema.TypeString, Type: schema.TypeString,
Description: "The user comment", Description: "The user comment",
Optional: true, Optional: true,
Default: "", Default: dvResourceVirtualEnvironmentUserComment,
}, },
mkResourceVirtualEnvironmentUserEmail: &schema.Schema{ mkResourceVirtualEnvironmentUserEmail: &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Description: "The user's email address", Description: "The user's email address",
Optional: true, Optional: true,
Default: "", Default: dvResourceVirtualEnvironmentUserEmail,
}, },
mkResourceVirtualEnvironmentUserEnabled: &schema.Schema{ mkResourceVirtualEnvironmentUserEnabled: &schema.Schema{
Type: schema.TypeBool, Type: schema.TypeBool,
Description: "Whether the user account is enabled", Description: "Whether the user account is enabled",
Optional: true, Optional: true,
Default: true, Default: dvResourceVirtualEnvironmentUserEnabled,
}, },
mkResourceVirtualEnvironmentUserExpirationDate: &schema.Schema{ mkResourceVirtualEnvironmentUserExpirationDate: &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
@ -90,7 +97,7 @@ func resourceVirtualEnvironmentUser() *schema.Resource {
Type: schema.TypeString, Type: schema.TypeString,
Description: "The user's first name", Description: "The user's first name",
Optional: true, Optional: true,
Default: "", Default: dvResourceVirtualEnvironmentUserFirstName,
}, },
mkResourceVirtualEnvironmentUserGroups: &schema.Schema{ mkResourceVirtualEnvironmentUserGroups: &schema.Schema{
Type: schema.TypeSet, Type: schema.TypeSet,
@ -105,13 +112,13 @@ func resourceVirtualEnvironmentUser() *schema.Resource {
Type: schema.TypeString, Type: schema.TypeString,
Description: "The user's keys", Description: "The user's keys",
Optional: true, Optional: true,
Default: "", Default: dvResourceVirtualEnvironmentUserKeys,
}, },
mkResourceVirtualEnvironmentUserLastName: &schema.Schema{ mkResourceVirtualEnvironmentUserLastName: &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Description: "The user's last name", Description: "The user's last name",
Optional: true, Optional: true,
Default: "", Default: dvResourceVirtualEnvironmentUserLastName,
}, },
mkResourceVirtualEnvironmentUserPassword: &schema.Schema{ mkResourceVirtualEnvironmentUserPassword: &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,

View File

@ -6,7 +6,6 @@ package proxmoxtf
import ( import (
"fmt" "fmt"
"log"
"strconv" "strconv"
"strings" "strings"
@ -16,12 +15,17 @@ import (
) )
const ( const (
dvResourceVirtualEnvironmentVMAgentEnabled = false
dvResourceVirtualEnvironmentVMAgentTrim = false
dvResourceVirtualEnvironmentVMAgentType = "virtio"
dvResourceVirtualEnvironmentVMCDROMEnabled = false dvResourceVirtualEnvironmentVMCDROMEnabled = false
dvResourceVirtualEnvironmentVMCDROMFileID = "" dvResourceVirtualEnvironmentVMCDROMFileID = ""
dvResourceVirtualEnvironmentVMCloudInitDNSDomain = "" dvResourceVirtualEnvironmentVMCloudInitDNSDomain = ""
dvResourceVirtualEnvironmentVMCloudInitDNSServer = "" dvResourceVirtualEnvironmentVMCloudInitDNSServer = ""
dvResourceVirtualEnvironmentVMCPUCores = 1 dvResourceVirtualEnvironmentVMCPUCores = 1
dvResourceVirtualEnvironmentVMCPUHotplugged = 0
dvResourceVirtualEnvironmentVMCPUSockets = 1 dvResourceVirtualEnvironmentVMCPUSockets = 1
dvResourceVirtualEnvironmentVMDescription = ""
dvResourceVirtualEnvironmentVMDiskDatastoreID = "local-lvm" dvResourceVirtualEnvironmentVMDiskDatastoreID = "local-lvm"
dvResourceVirtualEnvironmentVMDiskEnabled = true dvResourceVirtualEnvironmentVMDiskEnabled = true
dvResourceVirtualEnvironmentVMDiskFileFormat = "qcow2" dvResourceVirtualEnvironmentVMDiskFileFormat = "qcow2"
@ -38,8 +42,13 @@ const (
dvResourceVirtualEnvironmentVMNetworkDeviceModel = "virtio" dvResourceVirtualEnvironmentVMNetworkDeviceModel = "virtio"
dvResourceVirtualEnvironmentVMNetworkDeviceVLANID = -1 dvResourceVirtualEnvironmentVMNetworkDeviceVLANID = -1
dvResourceVirtualEnvironmentVMOSType = "other" dvResourceVirtualEnvironmentVMOSType = "other"
dvResourceVirtualEnvironmentVMPoolID = ""
dvResourceVirtualEnvironmentVMVMID = -1 dvResourceVirtualEnvironmentVMVMID = -1
mkResourceVirtualEnvironmentVMAgent = "agent"
mkResourceVirtualEnvironmentVMAgentEnabled = "enabled"
mkResourceVirtualEnvironmentVMAgentTrim = "trim"
mkResourceVirtualEnvironmentVMAgentType = "type"
mkResourceVirtualEnvironmentVMCDROM = "cdrom" mkResourceVirtualEnvironmentVMCDROM = "cdrom"
mkResourceVirtualEnvironmentVMCDROMEnabled = "enabled" mkResourceVirtualEnvironmentVMCDROMEnabled = "enabled"
mkResourceVirtualEnvironmentVMCDROMFileID = "file_id" mkResourceVirtualEnvironmentVMCDROMFileID = "file_id"
@ -59,7 +68,9 @@ const (
mkResourceVirtualEnvironmentVMCloudInitUserAccountUsername = "username" mkResourceVirtualEnvironmentVMCloudInitUserAccountUsername = "username"
mkResourceVirtualEnvironmentVMCPU = "cpu" mkResourceVirtualEnvironmentVMCPU = "cpu"
mkResourceVirtualEnvironmentVMCPUCores = "cores" mkResourceVirtualEnvironmentVMCPUCores = "cores"
mkResourceVirtualEnvironmentVMCPUHotplugged = "hotplugged"
mkResourceVirtualEnvironmentVMCPUSockets = "sockets" mkResourceVirtualEnvironmentVMCPUSockets = "sockets"
mkResourceVirtualEnvironmentVMDescription = "description"
mkResourceVirtualEnvironmentVMDisk = "disk" mkResourceVirtualEnvironmentVMDisk = "disk"
mkResourceVirtualEnvironmentVMDiskDatastoreID = "datastore_id" mkResourceVirtualEnvironmentVMDiskDatastoreID = "datastore_id"
mkResourceVirtualEnvironmentVMDiskEnabled = "enabled" mkResourceVirtualEnvironmentVMDiskEnabled = "enabled"
@ -80,12 +91,55 @@ const (
mkResourceVirtualEnvironmentVMNetworkDeviceVLANID = "vlan_id" mkResourceVirtualEnvironmentVMNetworkDeviceVLANID = "vlan_id"
mkResourceVirtualEnvironmentVMNodeName = "node_name" mkResourceVirtualEnvironmentVMNodeName = "node_name"
mkResourceVirtualEnvironmentVMOSType = "os_type" mkResourceVirtualEnvironmentVMOSType = "os_type"
mkResourceVirtualEnvironmentVMPoolID = "pool_id"
mkResourceVirtualEnvironmentVMVMID = "vm_id" mkResourceVirtualEnvironmentVMVMID = "vm_id"
) )
func resourceVirtualEnvironmentVM() *schema.Resource { func resourceVirtualEnvironmentVM() *schema.Resource {
return &schema.Resource{ return &schema.Resource{
Schema: map[string]*schema.Schema{ 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{ mkResourceVirtualEnvironmentVMCDROM: &schema.Schema{
Type: schema.TypeList, Type: schema.TypeList,
Description: "The CDROM drive", Description: "The CDROM drive",
@ -261,6 +315,7 @@ func resourceVirtualEnvironmentVM() *schema.Resource {
defaultMap := make(map[string]interface{}) defaultMap := make(map[string]interface{})
defaultMap[mkResourceVirtualEnvironmentVMCPUCores] = dvResourceVirtualEnvironmentVMCPUCores defaultMap[mkResourceVirtualEnvironmentVMCPUCores] = dvResourceVirtualEnvironmentVMCPUCores
defaultMap[mkResourceVirtualEnvironmentVMCPUHotplugged] = dvResourceVirtualEnvironmentVMCPUHotplugged
defaultMap[mkResourceVirtualEnvironmentVMCPUSockets] = dvResourceVirtualEnvironmentVMCPUSockets defaultMap[mkResourceVirtualEnvironmentVMCPUSockets] = dvResourceVirtualEnvironmentVMCPUSockets
defaultList[0] = defaultMap defaultList[0] = defaultMap
@ -276,6 +331,13 @@ func resourceVirtualEnvironmentVM() *schema.Resource {
Default: dvResourceVirtualEnvironmentVMCPUCores, Default: dvResourceVirtualEnvironmentVMCPUCores,
ValidateFunc: validation.IntBetween(1, 2304), 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: { mkResourceVirtualEnvironmentVMCPUSockets: {
Type: schema.TypeInt, Type: schema.TypeInt,
Optional: true, Optional: true,
@ -288,6 +350,12 @@ func resourceVirtualEnvironmentVM() *schema.Resource {
MaxItems: 1, MaxItems: 1,
MinItems: 0, MinItems: 0,
}, },
mkResourceVirtualEnvironmentVMDescription: {
Type: schema.TypeString,
Optional: true,
Description: "The description",
Default: dvResourceVirtualEnvironmentVMDescription,
},
mkResourceVirtualEnvironmentVMDisk: &schema.Schema{ mkResourceVirtualEnvironmentVMDisk: &schema.Schema{
Type: schema.TypeList, Type: schema.TypeList,
Description: "The disk devices", Description: "The disk devices",
@ -461,6 +529,12 @@ func resourceVirtualEnvironmentVM() *schema.Resource {
Default: dvResourceVirtualEnvironmentVMOSType, Default: dvResourceVirtualEnvironmentVMOSType,
ValidateFunc: getOSTypeValidator(), ValidateFunc: getOSTypeValidator(),
}, },
mkResourceVirtualEnvironmentVMPoolID: {
Type: schema.TypeString,
Optional: true,
Description: "The ID of the pool to assign the virtual machine to",
Default: dvResourceVirtualEnvironmentVMPoolID,
},
mkResourceVirtualEnvironmentVMVMID: { mkResourceVirtualEnvironmentVMVMID: {
Type: schema.TypeInt, Type: schema.TypeInt,
Optional: true, Optional: true,
@ -486,6 +560,23 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e
} }
schema := resourceVirtualEnvironmentVM().Schema 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{}) cdrom := d.Get(mkResourceVirtualEnvironmentVMCDROM).([]interface{})
if len(cdrom) == 0 { if len(cdrom) == 0 {
@ -609,8 +700,10 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e
cpuBlock := cpu[0].(map[string]interface{}) cpuBlock := cpu[0].(map[string]interface{})
cpuCores := cpuBlock[mkResourceVirtualEnvironmentVMCPUCores].(int) cpuCores := cpuBlock[mkResourceVirtualEnvironmentVMCPUCores].(int)
cpuHotplugged := cpuBlock[mkResourceVirtualEnvironmentVMCPUHotplugged].(int)
cpuSockets := cpuBlock[mkResourceVirtualEnvironmentVMCPUSockets].(int) cpuSockets := cpuBlock[mkResourceVirtualEnvironmentVMCPUSockets].(int)
description := d.Get(mkResourceVirtualEnvironmentVMDescription).(string)
disk := d.Get(mkResourceVirtualEnvironmentVMDisk).([]interface{}) disk := d.Get(mkResourceVirtualEnvironmentVMDisk).([]interface{})
scsiDevices := make(proxmox.CustomStorageDevices, len(disk)) scsiDevices := make(proxmox.CustomStorageDevices, len(disk))
@ -689,6 +782,7 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e
nodeName := d.Get(mkResourceVirtualEnvironmentVMNodeName).(string) nodeName := d.Get(mkResourceVirtualEnvironmentVMNodeName).(string)
osType := d.Get(mkResourceVirtualEnvironmentVMOSType).(string) osType := d.Get(mkResourceVirtualEnvironmentVMOSType).(string)
poolID := d.Get(mkResourceVirtualEnvironmentVMPoolID).(string)
vmID := d.Get(mkResourceVirtualEnvironmentVMVMID).(int) vmID := d.Get(mkResourceVirtualEnvironmentVMVMID).(int)
if vmID == -1 { if vmID == -1 {
@ -703,7 +797,6 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e
var memorySharedObject *proxmox.CustomSharedMemory var memorySharedObject *proxmox.CustomSharedMemory
agentEnabled := proxmox.CustomBool(true)
bootDisk := "scsi0" bootDisk := "scsi0"
bootOrder := "c" bootOrder := "c"
@ -738,7 +831,11 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e
tabletDeviceEnabled := proxmox.CustomBool(true) tabletDeviceEnabled := proxmox.CustomBool(true)
body := &proxmox.VirtualEnvironmentVMCreateRequestBody{ body := &proxmox.VirtualEnvironmentVMCreateRequestBody{
Agent: &proxmox.CustomAgent{Enabled: &agentEnabled}, Agent: &proxmox.CustomAgent{
Enabled: &agentEnabled,
TrimClonedDisks: &agentTrim,
Type: &agentType,
},
BootDisk: &bootDisk, BootDisk: &bootDisk,
BootOrder: &bootOrder, BootOrder: &bootOrder,
CloudInitConfig: cloudInitConfig, CloudInitConfig: cloudInitConfig,
@ -758,10 +855,22 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e
VMID: &vmID, VMID: &vmID,
} }
if cpuHotplugged > 0 {
body.VirtualCPUCount = &cpuHotplugged
}
if description != "" {
body.Description = &description
}
if name != "" { if name != "" {
body.Name = &name body.Name = &name
} }
if poolID != "" {
body.PoolID = &poolID
}
err = veClient.CreateVM(nodeName, body) err = veClient.CreateVM(nodeName, body)
if err != nil { if err != nil {
@ -770,10 +879,10 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e
d.SetId(strconv.Itoa(vmID)) 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) config := m.(providerConfiguration)
veClient, err := config.GetVEClient() 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. // 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. // This is a highly experimental approach to disk imports and is not recommended by Proxmox.
if len(commands) > 0 { if len(commands) > 0 {
for _, cmd := range commands {
log.Printf("[DEBUG] Node command: %s", cmd)
}
err = veClient.ExecuteNodeCommands(nodeName, commands) err = veClient.ExecuteNodeCommands(nodeName, commands)
if err != nil { 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) return resourceVirtualEnvironmentVMRead(d, m)
} }
@ -893,6 +1029,25 @@ func resourceVirtualEnvironmentVMDelete(d *schema.ResourceData, m interface{}) e
return err 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) err = veClient.DeleteVM(nodeName, vmID)
if err != nil { if err != nil {
@ -905,6 +1060,13 @@ func resourceVirtualEnvironmentVMDelete(d *schema.ResourceData, m interface{}) e
return err 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("") d.SetId("")
return nil return nil

View File

@ -31,12 +31,14 @@ func TestResourceVirtualEnvironmentVMSchema(t *testing.T) {
mkResourceVirtualEnvironmentVMCDROM, mkResourceVirtualEnvironmentVMCDROM,
mkResourceVirtualEnvironmentVMCloudInit, mkResourceVirtualEnvironmentVMCloudInit,
mkResourceVirtualEnvironmentVMCPU, mkResourceVirtualEnvironmentVMCPU,
mkResourceVirtualEnvironmentVMDescription,
mkResourceVirtualEnvironmentVMDisk, mkResourceVirtualEnvironmentVMDisk,
mkResourceVirtualEnvironmentVMKeyboardLayout, mkResourceVirtualEnvironmentVMKeyboardLayout,
mkResourceVirtualEnvironmentVMMemory, mkResourceVirtualEnvironmentVMMemory,
mkResourceVirtualEnvironmentVMName, mkResourceVirtualEnvironmentVMName,
mkResourceVirtualEnvironmentVMNetworkDevice, mkResourceVirtualEnvironmentVMNetworkDevice,
mkResourceVirtualEnvironmentVMOSType, mkResourceVirtualEnvironmentVMOSType,
mkResourceVirtualEnvironmentVMPoolID,
mkResourceVirtualEnvironmentVMVMID, mkResourceVirtualEnvironmentVMVMID,
}) })
@ -44,26 +46,48 @@ func TestResourceVirtualEnvironmentVMSchema(t *testing.T) {
mkResourceVirtualEnvironmentVMCDROM, mkResourceVirtualEnvironmentVMCDROM,
mkResourceVirtualEnvironmentVMCloudInit, mkResourceVirtualEnvironmentVMCloudInit,
mkResourceVirtualEnvironmentVMCPU, mkResourceVirtualEnvironmentVMCPU,
mkResourceVirtualEnvironmentVMDescription,
mkResourceVirtualEnvironmentVMDisk, mkResourceVirtualEnvironmentVMDisk,
mkResourceVirtualEnvironmentVMKeyboardLayout, mkResourceVirtualEnvironmentVMKeyboardLayout,
mkResourceVirtualEnvironmentVMMemory, mkResourceVirtualEnvironmentVMMemory,
mkResourceVirtualEnvironmentVMName, mkResourceVirtualEnvironmentVMName,
mkResourceVirtualEnvironmentVMNetworkDevice, mkResourceVirtualEnvironmentVMNetworkDevice,
mkResourceVirtualEnvironmentVMOSType, mkResourceVirtualEnvironmentVMOSType,
mkResourceVirtualEnvironmentVMPoolID,
mkResourceVirtualEnvironmentVMVMID, mkResourceVirtualEnvironmentVMVMID,
}, []schema.ValueType{ }, []schema.ValueType{
schema.TypeList, schema.TypeList,
schema.TypeList, schema.TypeList,
schema.TypeList, schema.TypeList,
schema.TypeList,
schema.TypeString, schema.TypeString,
schema.TypeList, schema.TypeList,
schema.TypeString, schema.TypeString,
schema.TypeList, schema.TypeList,
schema.TypeString, schema.TypeString,
schema.TypeList,
schema.TypeString,
schema.TypeString,
schema.TypeInt, 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) cdromSchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentVMCDROM)
testOptionalArguments(t, cdromSchema, []string{ testOptionalArguments(t, cdromSchema, []string{
@ -179,15 +203,18 @@ func TestResourceVirtualEnvironmentVMSchema(t *testing.T) {
testOptionalArguments(t, cpuSchema, []string{ testOptionalArguments(t, cpuSchema, []string{
mkResourceVirtualEnvironmentVMCPUCores, mkResourceVirtualEnvironmentVMCPUCores,
mkResourceVirtualEnvironmentVMCPUHotplugged,
mkResourceVirtualEnvironmentVMCPUSockets, mkResourceVirtualEnvironmentVMCPUSockets,
}) })
testSchemaValueTypes(t, cpuSchema, []string{ testSchemaValueTypes(t, cpuSchema, []string{
mkResourceVirtualEnvironmentVMCPUCores, mkResourceVirtualEnvironmentVMCPUCores,
mkResourceVirtualEnvironmentVMCPUHotplugged,
mkResourceVirtualEnvironmentVMCPUSockets, mkResourceVirtualEnvironmentVMCPUSockets,
}, []schema.ValueType{ }, []schema.ValueType{
schema.TypeInt, schema.TypeInt,
schema.TypeInt, schema.TypeInt,
schema.TypeInt,
}) })
diskSchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentVMDisk) diskSchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentVMDisk)

View File

@ -114,6 +114,10 @@ func getOSTypeValidator() schema.SchemaValidateFunc {
}, false) }, false)
} }
func getQEMUAgentTypeValidator() schema.SchemaValidateFunc {
return validation.StringInSlice([]string{"isa", "virtio"}, false)
}
func getVLANIDValidator() schema.SchemaValidateFunc { func getVLANIDValidator() schema.SchemaValidateFunc {
return func(i interface{}, k string) (ws []string, es []error) { return func(i interface{}, k string) (ws []string, es []error) {
min := 1 min := 1