0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-06-30 02:31:10 +00:00
terraform-provider-proxmox/proxmoxtf/resource/vm.go
Pavel Boldyrev 5ecf135398
chore(code): fix proxmox package dependencies (#536)
move `types` back from `internal` to `proxmox` and adjust a few other types, to make sure `proxmox` package is not dependent on anything else, and therefore can be extracted to a separate repo (#423)
2023-09-03 00:40:47 +00:00

5839 lines
196 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package resource
import (
"context"
"encoding/base64"
"errors"
"fmt"
"sort"
"strconv"
"strings"
"time"
"unicode"
"github.com/google/go-cmp/cmp"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster"
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/vms"
"github.com/bpg/terraform-provider-proxmox/proxmox/pools"
types2 "github.com/bpg/terraform-provider-proxmox/proxmox/types"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/resource/validator"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/structure"
)
const (
dvResourceVirtualEnvironmentVMRebootAfterCreation = false
dvResourceVirtualEnvironmentVMOnBoot = true
dvResourceVirtualEnvironmentVMACPI = true
dvResourceVirtualEnvironmentVMAgentEnabled = false
dvResourceVirtualEnvironmentVMAgentTimeout = "15m"
dvResourceVirtualEnvironmentVMAgentTrim = false
dvResourceVirtualEnvironmentVMAgentType = "virtio"
dvResourceVirtualEnvironmentVMAudioDeviceDevice = "intel-hda"
dvResourceVirtualEnvironmentVMAudioDeviceDriver = "spice"
dvResourceVirtualEnvironmentVMAudioDeviceEnabled = true
dvResourceVirtualEnvironmentVMBIOS = "seabios"
dvResourceVirtualEnvironmentVMCDROMEnabled = false
dvResourceVirtualEnvironmentVMCDROMFileID = ""
dvResourceVirtualEnvironmentVMCDROMInterface = "ide3"
dvResourceVirtualEnvironmentVMCloneDatastoreID = ""
dvResourceVirtualEnvironmentVMCloneNodeName = ""
dvResourceVirtualEnvironmentVMCloneFull = true
dvResourceVirtualEnvironmentVMCloneRetries = 1
dvResourceVirtualEnvironmentVMCPUArchitecture = "x86_64"
dvResourceVirtualEnvironmentVMCPUCores = 1
dvResourceVirtualEnvironmentVMCPUHotplugged = 0
dvResourceVirtualEnvironmentVMCPUNUMA = false
dvResourceVirtualEnvironmentVMCPUSockets = 1
dvResourceVirtualEnvironmentVMCPUType = "qemu64"
dvResourceVirtualEnvironmentVMCPUUnits = 1024
dvResourceVirtualEnvironmentVMDescription = ""
dvResourceVirtualEnvironmentVMDiskInterface = "scsi0"
dvResourceVirtualEnvironmentVMDiskDatastoreID = "local-lvm"
dvResourceVirtualEnvironmentVMDiskFileFormat = "qcow2"
dvResourceVirtualEnvironmentVMDiskFileID = ""
dvResourceVirtualEnvironmentVMDiskSize = 8
dvResourceVirtualEnvironmentVMDiskIOThread = false
dvResourceVirtualEnvironmentVMDiskSSD = false
dvResourceVirtualEnvironmentVMDiskDiscard = ""
dvResourceVirtualEnvironmentVMDiskCache = "none"
dvResourceVirtualEnvironmentVMDiskSpeedRead = 0
dvResourceVirtualEnvironmentVMDiskSpeedReadBurstable = 0
dvResourceVirtualEnvironmentVMDiskSpeedWrite = 0
dvResourceVirtualEnvironmentVMDiskSpeedWriteBurstable = 0
dvResourceVirtualEnvironmentVMEFIDiskDatastoreID = "local-lvm"
dvResourceVirtualEnvironmentVMEFIDiskFileFormat = "qcow2"
dvResourceVirtualEnvironmentVMEFIDiskType = "2m"
dvResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys = false
dvResourceVirtualEnvironmentVMInitializationDatastoreID = "local-lvm"
dvResourceVirtualEnvironmentVMInitializationInterface = ""
dvResourceVirtualEnvironmentVMInitializationDNSDomain = ""
dvResourceVirtualEnvironmentVMInitializationDNSServer = ""
dvResourceVirtualEnvironmentVMInitializationIPConfigIPv4Address = ""
dvResourceVirtualEnvironmentVMInitializationIPConfigIPv4Gateway = ""
dvResourceVirtualEnvironmentVMInitializationIPConfigIPv6Address = ""
dvResourceVirtualEnvironmentVMInitializationIPConfigIPv6Gateway = ""
dvResourceVirtualEnvironmentVMInitializationUserAccountPassword = ""
dvResourceVirtualEnvironmentVMInitializationUserDataFileID = ""
dvResourceVirtualEnvironmentVMInitializationVendorDataFileID = ""
dvResourceVirtualEnvironmentVMInitializationNetworkDataFileID = ""
dvResourceVirtualEnvironmentVMInitializationMetaDataFileID = ""
dvResourceVirtualEnvironmentVMInitializationType = ""
dvResourceVirtualEnvironmentVMKeyboardLayout = "en-us"
dvResourceVirtualEnvironmentVMKVMArguments = ""
dvResourceVirtualEnvironmentVMMachineType = ""
dvResourceVirtualEnvironmentVMMemoryDedicated = 512
dvResourceVirtualEnvironmentVMMemoryFloating = 0
dvResourceVirtualEnvironmentVMMemoryShared = 0
dvResourceVirtualEnvironmentVMMigrate = false
dvResourceVirtualEnvironmentVMName = ""
dvResourceVirtualEnvironmentVMNetworkDeviceBridge = "vmbr0"
dvResourceVirtualEnvironmentVMNetworkDeviceEnabled = true
dvResourceVirtualEnvironmentVMNetworkDeviceFirewall = false
dvResourceVirtualEnvironmentVMNetworkDeviceModel = "virtio"
dvResourceVirtualEnvironmentVMNetworkDeviceRateLimit = 0
dvResourceVirtualEnvironmentVMNetworkDeviceVLANID = 0
dvResourceVirtualEnvironmentVMNetworkDeviceMTU = 0
dvResourceVirtualEnvironmentVMOperatingSystemType = "other"
dvResourceVirtualEnvironmentVMPoolID = ""
dvResourceVirtualEnvironmentVMSerialDeviceDevice = "socket"
dvResourceVirtualEnvironmentVMSMBIOSFamily = ""
dvResourceVirtualEnvironmentVMSMBIOSManufacturer = ""
dvResourceVirtualEnvironmentVMSMBIOSProduct = ""
dvResourceVirtualEnvironmentVMSMBIOSSKU = ""
dvResourceVirtualEnvironmentVMSMBIOSSerial = ""
dvResourceVirtualEnvironmentVMSMBIOSVersion = ""
dvResourceVirtualEnvironmentVMStarted = true
dvResourceVirtualEnvironmentVMStartupOrder = -1
dvResourceVirtualEnvironmentVMStartupUpDelay = -1
dvResourceVirtualEnvironmentVMStartupDownDelay = -1
dvResourceVirtualEnvironmentVMTabletDevice = true
dvResourceVirtualEnvironmentVMTemplate = false
dvResourceVirtualEnvironmentVMTimeoutClone = 1800
dvResourceVirtualEnvironmentVMTimeoutMoveDisk = 1800
dvResourceVirtualEnvironmentVMTimeoutMigrate = 1800
dvResourceVirtualEnvironmentVMTimeoutReboot = 1800
dvResourceVirtualEnvironmentVMTimeoutShutdownVM = 1800
dvResourceVirtualEnvironmentVMTimeoutStartVM = 1800
dvResourceVirtualEnvironmentVMTimeoutStopVM = 300
dvResourceVirtualEnvironmentVMVGAEnabled = true
dvResourceVirtualEnvironmentVMVGAMemory = 16
dvResourceVirtualEnvironmentVMVGAType = "std"
dvResourceVirtualEnvironmentVMSCSIHardware = "virtio-scsi-pci"
maxResourceVirtualEnvironmentVMAudioDevices = 1
maxResourceVirtualEnvironmentVMNetworkDevices = 8
maxResourceVirtualEnvironmentVMSerialDevices = 4
maxResourceVirtualEnvironmentVMHostPCIDevices = 8
mkResourceVirtualEnvironmentVMRebootAfterCreation = "reboot"
mkResourceVirtualEnvironmentVMOnBoot = "on_boot"
mkResourceVirtualEnvironmentVMBootOrder = "boot_order"
mkResourceVirtualEnvironmentVMACPI = "acpi"
mkResourceVirtualEnvironmentVMAgent = "agent"
mkResourceVirtualEnvironmentVMAgentEnabled = "enabled"
mkResourceVirtualEnvironmentVMAgentTimeout = "timeout"
mkResourceVirtualEnvironmentVMAgentTrim = "trim"
mkResourceVirtualEnvironmentVMAgentType = "type"
mkResourceVirtualEnvironmentVMAudioDevice = "audio_device"
mkResourceVirtualEnvironmentVMAudioDeviceDevice = "device"
mkResourceVirtualEnvironmentVMAudioDeviceDriver = "driver"
mkResourceVirtualEnvironmentVMAudioDeviceEnabled = "enabled"
mkResourceVirtualEnvironmentVMBIOS = "bios"
mkResourceVirtualEnvironmentVMCDROM = "cdrom"
mkResourceVirtualEnvironmentVMCDROMEnabled = "enabled"
mkResourceVirtualEnvironmentVMCDROMFileID = "file_id"
mkResourceVirtualEnvironmentVMCDROMInterface = "interface"
mkResourceVirtualEnvironmentVMClone = "clone"
mkResourceVirtualEnvironmentVMCloneRetries = "retries"
mkResourceVirtualEnvironmentVMCloneDatastoreID = "datastore_id"
mkResourceVirtualEnvironmentVMCloneNodeName = "node_name"
mkResourceVirtualEnvironmentVMCloneVMID = "vm_id"
mkResourceVirtualEnvironmentVMCloneFull = "full"
mkResourceVirtualEnvironmentVMCPU = "cpu"
mkResourceVirtualEnvironmentVMCPUArchitecture = "architecture"
mkResourceVirtualEnvironmentVMCPUCores = "cores"
mkResourceVirtualEnvironmentVMCPUFlags = "flags"
mkResourceVirtualEnvironmentVMCPUHotplugged = "hotplugged"
mkResourceVirtualEnvironmentVMCPUNUMA = "numa"
mkResourceVirtualEnvironmentVMCPUSockets = "sockets"
mkResourceVirtualEnvironmentVMCPUType = "type"
mkResourceVirtualEnvironmentVMCPUUnits = "units"
mkResourceVirtualEnvironmentVMDescription = "description"
mkResourceVirtualEnvironmentVMDisk = "disk"
mkResourceVirtualEnvironmentVMDiskInterface = "interface"
mkResourceVirtualEnvironmentVMDiskDatastoreID = "datastore_id"
mkResourceVirtualEnvironmentVMDiskFileFormat = "file_format"
mkResourceVirtualEnvironmentVMDiskFileID = "file_id"
mkResourceVirtualEnvironmentVMDiskSize = "size"
mkResourceVirtualEnvironmentVMDiskIOThread = "iothread"
mkResourceVirtualEnvironmentVMDiskSSD = "ssd"
mkResourceVirtualEnvironmentVMDiskDiscard = "discard"
mkResourceVirtualEnvironmentVMDiskCache = "cache"
mkResourceVirtualEnvironmentVMDiskSpeed = "speed"
mkResourceVirtualEnvironmentVMDiskSpeedRead = "read"
mkResourceVirtualEnvironmentVMDiskSpeedReadBurstable = "read_burstable"
mkResourceVirtualEnvironmentVMDiskSpeedWrite = "write"
mkResourceVirtualEnvironmentVMDiskSpeedWriteBurstable = "write_burstable"
mkResourceVirtualEnvironmentVMEFIDisk = "efi_disk"
mkResourceVirtualEnvironmentVMEFIDiskDatastoreID = "datastore_id"
mkResourceVirtualEnvironmentVMEFIDiskFileFormat = "file_format"
mkResourceVirtualEnvironmentVMEFIDiskType = "type"
mkResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys = "pre_enrolled_keys"
mkResourceVirtualEnvironmentVMHostPCI = "hostpci"
mkResourceVirtualEnvironmentVMHostPCIDevice = "device"
mkResourceVirtualEnvironmentVMHostPCIDeviceID = "id"
mkResourceVirtualEnvironmentVMHostPCIDeviceMapping = "mapping"
mkResourceVirtualEnvironmentVMHostPCIDeviceMDev = "mdev"
mkResourceVirtualEnvironmentVMHostPCIDevicePCIE = "pcie"
mkResourceVirtualEnvironmentVMHostPCIDeviceROMBAR = "rombar"
mkResourceVirtualEnvironmentVMHostPCIDeviceROMFile = "rom_file"
mkResourceVirtualEnvironmentVMHostPCIDeviceXVGA = "xvga"
mkResourceVirtualEnvironmentVMInitialization = "initialization"
mkResourceVirtualEnvironmentVMInitializationDatastoreID = "datastore_id"
mkResourceVirtualEnvironmentVMInitializationInterface = "interface"
mkResourceVirtualEnvironmentVMInitializationDNS = "dns"
mkResourceVirtualEnvironmentVMInitializationDNSDomain = "domain"
mkResourceVirtualEnvironmentVMInitializationDNSServer = "server"
mkResourceVirtualEnvironmentVMInitializationIPConfig = "ip_config"
mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4 = "ipv4"
mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Address = "address"
mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Gateway = "gateway"
mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6 = "ipv6"
mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6Address = "address"
mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6Gateway = "gateway"
mkResourceVirtualEnvironmentVMInitializationType = "type"
mkResourceVirtualEnvironmentVMInitializationUserAccount = "user_account"
mkResourceVirtualEnvironmentVMInitializationUserAccountKeys = "keys"
mkResourceVirtualEnvironmentVMInitializationUserAccountPassword = "password"
mkResourceVirtualEnvironmentVMInitializationUserAccountUsername = "username"
mkResourceVirtualEnvironmentVMInitializationUserDataFileID = "user_data_file_id"
mkResourceVirtualEnvironmentVMInitializationVendorDataFileID = "vendor_data_file_id"
mkResourceVirtualEnvironmentVMInitializationNetworkDataFileID = "network_data_file_id"
mkResourceVirtualEnvironmentVMInitializationMetaDataFileID = "meta_data_file_id"
mkResourceVirtualEnvironmentVMIPv4Addresses = "ipv4_addresses"
mkResourceVirtualEnvironmentVMIPv6Addresses = "ipv6_addresses"
mkResourceVirtualEnvironmentVMKeyboardLayout = "keyboard_layout"
mkResourceVirtualEnvironmentVMKVMArguments = "kvm_arguments"
mkResourceVirtualEnvironmentVMMachine = "machine"
mkResourceVirtualEnvironmentVMMACAddresses = "mac_addresses"
mkResourceVirtualEnvironmentVMMemory = "memory"
mkResourceVirtualEnvironmentVMMemoryDedicated = "dedicated"
mkResourceVirtualEnvironmentVMMemoryFloating = "floating"
mkResourceVirtualEnvironmentVMMemoryShared = "shared"
mkResourceVirtualEnvironmentVMMigrate = "migrate"
mkResourceVirtualEnvironmentVMName = "name"
mkResourceVirtualEnvironmentVMNetworkDevice = "network_device"
mkResourceVirtualEnvironmentVMNetworkDeviceBridge = "bridge"
mkResourceVirtualEnvironmentVMNetworkDeviceEnabled = "enabled"
mkResourceVirtualEnvironmentVMNetworkDeviceFirewall = "firewall"
mkResourceVirtualEnvironmentVMNetworkDeviceMACAddress = "mac_address"
mkResourceVirtualEnvironmentVMNetworkDeviceModel = "model"
mkResourceVirtualEnvironmentVMNetworkDeviceRateLimit = "rate_limit"
mkResourceVirtualEnvironmentVMNetworkDeviceVLANID = "vlan_id"
mkResourceVirtualEnvironmentVMNetworkDeviceMTU = "mtu"
mkResourceVirtualEnvironmentVMNetworkInterfaceNames = "network_interface_names"
mkResourceVirtualEnvironmentVMNodeName = "node_name"
mkResourceVirtualEnvironmentVMOperatingSystem = "operating_system"
mkResourceVirtualEnvironmentVMOperatingSystemType = "type"
mkResourceVirtualEnvironmentVMPoolID = "pool_id"
mkResourceVirtualEnvironmentVMSerialDevice = "serial_device"
mkResourceVirtualEnvironmentVMSerialDeviceDevice = "device"
mkResourceVirtualEnvironmentVMSMBIOS = "smbios"
mkResourceVirtualEnvironmentVMSMBIOSFamily = "family"
mkResourceVirtualEnvironmentVMSMBIOSManufacturer = "manufacturer"
mkResourceVirtualEnvironmentVMSMBIOSProduct = "product"
mkResourceVirtualEnvironmentVMSMBIOSSKU = "sku"
mkResourceVirtualEnvironmentVMSMBIOSSerial = "serial"
mkResourceVirtualEnvironmentVMSMBIOSUUID = "uuid"
mkResourceVirtualEnvironmentVMSMBIOSVersion = "version"
mkResourceVirtualEnvironmentVMStarted = "started"
mkResourceVirtualEnvironmentVMStartup = "startup"
mkResourceVirtualEnvironmentVMStartupOrder = "order"
mkResourceVirtualEnvironmentVMStartupUpDelay = "up_delay"
mkResourceVirtualEnvironmentVMStartupDownDelay = "down_delay"
mkResourceVirtualEnvironmentVMTabletDevice = "tablet_device"
mkResourceVirtualEnvironmentVMTags = "tags"
mkResourceVirtualEnvironmentVMTemplate = "template"
mkResourceVirtualEnvironmentVMTimeoutClone = "timeout_clone"
mkResourceVirtualEnvironmentVMTimeoutMoveDisk = "timeout_move_disk"
mkResourceVirtualEnvironmentVMTimeoutMigrate = "timeout_migrate"
mkResourceVirtualEnvironmentVMTimeoutReboot = "timeout_reboot"
mkResourceVirtualEnvironmentVMTimeoutShutdownVM = "timeout_shutdown_vm"
mkResourceVirtualEnvironmentVMTimeoutStartVM = "timeout_start_vm"
mkResourceVirtualEnvironmentVMTimeoutStopVM = "timeout_stop_vm"
mkResourceVirtualEnvironmentVMVGA = "vga"
mkResourceVirtualEnvironmentVMVGAEnabled = "enabled"
mkResourceVirtualEnvironmentVMVGAMemory = "memory"
mkResourceVirtualEnvironmentVMVGAType = "type"
mkResourceVirtualEnvironmentVMVMID = "vm_id"
mkResourceVirtualEnvironmentVMSCSIHardware = "scsi_hardware"
vmCreateTimeoutSeconds = 10
)
// VM returns a resource that manages VMs.
func VM() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMRebootAfterCreation: {
Type: schema.TypeBool,
Description: "Whether to reboot vm after creation",
Optional: true,
Default: dvResourceVirtualEnvironmentVMRebootAfterCreation,
},
mkResourceVirtualEnvironmentVMOnBoot: {
Type: schema.TypeBool,
Description: "Start VM on Node boot",
Optional: true,
Default: dvResourceVirtualEnvironmentVMOnBoot,
},
mkResourceVirtualEnvironmentVMBootOrder: {
Type: schema.TypeList,
Description: "The guest will attempt to boot from devices in the order they appear here",
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
DefaultFunc: func() (interface{}, error) {
return []interface{}{}, nil
},
},
mkResourceVirtualEnvironmentVMACPI: {
Type: schema.TypeBool,
Description: "Whether to enable ACPI",
Optional: true,
Default: dvResourceVirtualEnvironmentVMACPI,
},
mkResourceVirtualEnvironmentVMAgent: {
Type: schema.TypeList,
Description: "The QEMU agent configuration",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{
map[string]interface{}{
mkResourceVirtualEnvironmentVMAgentEnabled: dvResourceVirtualEnvironmentVMAgentEnabled,
mkResourceVirtualEnvironmentVMAgentTimeout: dvResourceVirtualEnvironmentVMAgentTimeout,
mkResourceVirtualEnvironmentVMAgentTrim: dvResourceVirtualEnvironmentVMAgentTrim,
mkResourceVirtualEnvironmentVMAgentType: dvResourceVirtualEnvironmentVMAgentType,
},
}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMAgentEnabled: {
Type: schema.TypeBool,
Description: "Whether to enable the QEMU agent",
Optional: true,
Default: dvResourceVirtualEnvironmentVMAgentEnabled,
},
mkResourceVirtualEnvironmentVMAgentTimeout: {
Type: schema.TypeString,
Description: "The maximum amount of time to wait for data from the QEMU agent to become available",
Optional: true,
Default: dvResourceVirtualEnvironmentVMAgentTimeout,
ValidateDiagFunc: validator.Timeout(),
},
mkResourceVirtualEnvironmentVMAgentTrim: {
Type: schema.TypeBool,
Description: "Whether to enable the FSTRIM feature in the QEMU agent",
Optional: true,
Default: dvResourceVirtualEnvironmentVMAgentTrim,
},
mkResourceVirtualEnvironmentVMAgentType: {
Type: schema.TypeString,
Description: "The QEMU agent interface type",
Optional: true,
Default: dvResourceVirtualEnvironmentVMAgentType,
ValidateDiagFunc: validator.QEMUAgentType(),
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkResourceVirtualEnvironmentVMKVMArguments: {
Type: schema.TypeString,
Description: "The args implementation",
Optional: true,
Default: dvResourceVirtualEnvironmentVMKVMArguments,
},
mkResourceVirtualEnvironmentVMAudioDevice: {
Type: schema.TypeList,
Description: "The audio devices",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMAudioDeviceDevice: {
Type: schema.TypeString,
Description: "The device",
Optional: true,
Default: dvResourceVirtualEnvironmentVMAudioDeviceDevice,
ValidateDiagFunc: vmGetAudioDeviceValidator(),
},
mkResourceVirtualEnvironmentVMAudioDeviceDriver: {
Type: schema.TypeString,
Description: "The driver",
Optional: true,
Default: dvResourceVirtualEnvironmentVMAudioDeviceDriver,
ValidateDiagFunc: vmGetAudioDriverValidator(),
},
mkResourceVirtualEnvironmentVMAudioDeviceEnabled: {
Type: schema.TypeBool,
Description: "Whether to enable the audio device",
Optional: true,
Default: dvResourceVirtualEnvironmentVMAudioDeviceEnabled,
},
},
},
MaxItems: maxResourceVirtualEnvironmentVMAudioDevices,
MinItems: 0,
},
mkResourceVirtualEnvironmentVMBIOS: {
Type: schema.TypeString,
Description: "The BIOS implementation",
Optional: true,
Default: dvResourceVirtualEnvironmentVMBIOS,
ValidateDiagFunc: validator.BIOS(),
},
mkResourceVirtualEnvironmentVMCDROM: {
Type: schema.TypeList,
Description: "The CDROM drive",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{
map[string]interface{}{
mkResourceVirtualEnvironmentVMCDROMEnabled: dvResourceVirtualEnvironmentVMCDROMEnabled,
mkResourceVirtualEnvironmentVMCDROMFileID: dvResourceVirtualEnvironmentVMCDROMFileID,
mkResourceVirtualEnvironmentVMCDROMInterface: dvResourceVirtualEnvironmentVMCDROMInterface,
},
}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMCDROMEnabled: {
Type: schema.TypeBool,
Description: "Whether to enable the CDROM drive",
Optional: true,
Default: dvResourceVirtualEnvironmentVMCDROMEnabled,
},
mkResourceVirtualEnvironmentVMCDROMFileID: {
Type: schema.TypeString,
Description: "The file id",
Optional: true,
Default: dvResourceVirtualEnvironmentVMCDROMFileID,
ValidateDiagFunc: validator.FileID(),
},
mkResourceVirtualEnvironmentVMCDROMInterface: {
Type: schema.TypeString,
Description: "The CDROM interface",
Optional: true,
Default: dvResourceVirtualEnvironmentVMCDROMInterface,
ValidateDiagFunc: validator.IDEInterface(),
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkResourceVirtualEnvironmentVMClone: {
Type: schema.TypeList,
Description: "The cloning configuration",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMCloneRetries: {
Type: schema.TypeInt,
Description: "The number of Retries to create a clone",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentVMCloneRetries,
},
mkResourceVirtualEnvironmentVMCloneDatastoreID: {
Type: schema.TypeString,
Description: "The ID of the target datastore",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentVMCloneDatastoreID,
},
mkResourceVirtualEnvironmentVMCloneNodeName: {
Type: schema.TypeString,
Description: "The name of the source node",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentVMCloneNodeName,
},
mkResourceVirtualEnvironmentVMCloneVMID: {
Type: schema.TypeInt,
Description: "The ID of the source VM",
Required: true,
ForceNew: true,
ValidateDiagFunc: validator.VMID(),
},
mkResourceVirtualEnvironmentVMCloneFull: {
Type: schema.TypeBool,
Description: "The Clone Type, create a Full Clone (true) or a linked Clone (false)",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentVMCloneFull,
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkResourceVirtualEnvironmentVMCPU: {
Type: schema.TypeList,
Description: "The CPU allocation",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{
map[string]interface{}{
mkResourceVirtualEnvironmentVMCPUArchitecture: dvResourceVirtualEnvironmentVMCPUArchitecture,
mkResourceVirtualEnvironmentVMCPUCores: dvResourceVirtualEnvironmentVMCPUCores,
mkResourceVirtualEnvironmentVMCPUFlags: []interface{}{},
mkResourceVirtualEnvironmentVMCPUNUMA: dvResourceVirtualEnvironmentVMCPUNUMA,
mkResourceVirtualEnvironmentVMCPUHotplugged: dvResourceVirtualEnvironmentVMCPUHotplugged,
mkResourceVirtualEnvironmentVMCPUSockets: dvResourceVirtualEnvironmentVMCPUSockets,
mkResourceVirtualEnvironmentVMCPUType: dvResourceVirtualEnvironmentVMCPUType,
mkResourceVirtualEnvironmentVMCPUUnits: dvResourceVirtualEnvironmentVMCPUUnits,
},
}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMCPUArchitecture: {
Type: schema.TypeString,
Description: "The CPU architecture",
Optional: true,
Default: dvResourceVirtualEnvironmentVMCPUArchitecture,
ValidateDiagFunc: vmGetCPUArchitectureValidator(),
},
mkResourceVirtualEnvironmentVMCPUCores: {
Type: schema.TypeInt,
Description: "The number of CPU cores",
Optional: true,
Default: dvResourceVirtualEnvironmentVMCPUCores,
ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(1, 2304)),
},
mkResourceVirtualEnvironmentVMCPUFlags: {
Type: schema.TypeList,
Description: "The CPU flags",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{}, nil
},
Elem: &schema.Schema{Type: schema.TypeString},
},
mkResourceVirtualEnvironmentVMCPUHotplugged: {
Type: schema.TypeInt,
Description: "The number of hotplugged vCPUs",
Optional: true,
Default: dvResourceVirtualEnvironmentVMCPUHotplugged,
ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 2304)),
},
mkResourceVirtualEnvironmentVMCPUNUMA: {
Type: schema.TypeBool,
Description: "Enable/disable NUMA.",
Optional: true,
Default: dvResourceVirtualEnvironmentVMCPUNUMA,
},
mkResourceVirtualEnvironmentVMCPUSockets: {
Type: schema.TypeInt,
Description: "The number of CPU sockets",
Optional: true,
Default: dvResourceVirtualEnvironmentVMCPUSockets,
ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(1, 16)),
},
mkResourceVirtualEnvironmentVMCPUType: {
Type: schema.TypeString,
Description: "The emulated CPU type",
Optional: true,
Default: dvResourceVirtualEnvironmentVMCPUType,
ValidateDiagFunc: validator.CPUType(),
},
mkResourceVirtualEnvironmentVMCPUUnits: {
Type: schema.TypeInt,
Description: "The CPU units",
Optional: true,
Default: dvResourceVirtualEnvironmentVMCPUUnits,
ValidateDiagFunc: validation.ToDiagFunc(
validation.IntBetween(2, 262144),
),
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkResourceVirtualEnvironmentVMDescription: {
Type: schema.TypeString,
Description: "The description",
Optional: true,
Default: dvResourceVirtualEnvironmentVMDescription,
},
mkResourceVirtualEnvironmentVMDisk: {
Type: schema.TypeList,
Description: "The disk devices",
Optional: true,
ForceNew: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{
map[string]interface{}{
mkResourceVirtualEnvironmentVMDiskDatastoreID: dvResourceVirtualEnvironmentVMDiskDatastoreID,
mkResourceVirtualEnvironmentVMDiskFileID: dvResourceVirtualEnvironmentVMDiskFileID,
mkResourceVirtualEnvironmentVMDiskInterface: dvResourceVirtualEnvironmentVMDiskInterface,
mkResourceVirtualEnvironmentVMDiskSize: dvResourceVirtualEnvironmentVMDiskSize,
mkResourceVirtualEnvironmentVMDiskIOThread: dvResourceVirtualEnvironmentVMDiskIOThread,
mkResourceVirtualEnvironmentVMDiskSSD: dvResourceVirtualEnvironmentVMDiskSSD,
mkResourceVirtualEnvironmentVMDiskDiscard: dvResourceVirtualEnvironmentVMDiskDiscard,
mkResourceVirtualEnvironmentVMDiskCache: dvResourceVirtualEnvironmentVMDiskCache,
},
}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMDiskInterface: {
Type: schema.TypeString,
Description: "The datastore name",
Required: true,
},
mkResourceVirtualEnvironmentVMDiskDatastoreID: {
Type: schema.TypeString,
Description: "The datastore id",
Optional: true,
Default: dvResourceVirtualEnvironmentVMDiskDatastoreID,
},
mkResourceVirtualEnvironmentVMDiskFileFormat: {
Type: schema.TypeString,
Description: "The file format",
Optional: true,
ForceNew: true,
Computed: true,
ValidateDiagFunc: validator.FileFormat(),
},
mkResourceVirtualEnvironmentVMDiskFileID: {
Type: schema.TypeString,
Description: "The file id for a disk image",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentVMDiskFileID,
ValidateDiagFunc: validator.FileID(),
},
mkResourceVirtualEnvironmentVMDiskSize: {
Type: schema.TypeInt,
Description: "The disk size in gigabytes",
Optional: true,
Default: dvResourceVirtualEnvironmentVMDiskSize,
ValidateDiagFunc: validation.ToDiagFunc(validation.IntAtLeast(1)),
},
mkResourceVirtualEnvironmentVMDiskIOThread: {
Type: schema.TypeBool,
Description: "Whether to use iothreads for this disk drive",
Optional: true,
Default: dvResourceVirtualEnvironmentVMDiskIOThread,
},
mkResourceVirtualEnvironmentVMDiskSSD: {
Type: schema.TypeBool,
Description: "Whether to use ssd for this disk drive",
Optional: true,
Default: dvResourceVirtualEnvironmentVMDiskSSD,
},
mkResourceVirtualEnvironmentVMDiskDiscard: {
Type: schema.TypeString,
Description: "Whether to pass discard/trim requests to the underlying storage.",
Optional: true,
Default: dvResourceVirtualEnvironmentVMDiskDiscard,
},
mkResourceVirtualEnvironmentVMDiskCache: {
Type: schema.TypeString,
Description: "The drives cache mode",
Optional: true,
Default: dvResourceVirtualEnvironmentVMDiskCache,
ValidateDiagFunc: validation.ToDiagFunc(
validation.StringInSlice([]string{
"none",
"writethrough",
"writeback",
"unsafe",
"directsync",
}, false),
),
},
mkResourceVirtualEnvironmentVMDiskSpeed: {
Type: schema.TypeList,
Description: "The speed limits",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{
map[string]interface{}{
mkResourceVirtualEnvironmentVMDiskSpeedRead: dvResourceVirtualEnvironmentVMDiskSpeedRead,
mkResourceVirtualEnvironmentVMDiskSpeedReadBurstable: dvResourceVirtualEnvironmentVMDiskSpeedReadBurstable,
mkResourceVirtualEnvironmentVMDiskSpeedWrite: dvResourceVirtualEnvironmentVMDiskSpeedWrite,
mkResourceVirtualEnvironmentVMDiskSpeedWriteBurstable: dvResourceVirtualEnvironmentVMDiskSpeedWriteBurstable,
},
}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMDiskSpeedRead: {
Type: schema.TypeInt,
Description: "The maximum read speed in megabytes per second",
Optional: true,
Default: dvResourceVirtualEnvironmentVMDiskSpeedRead,
},
mkResourceVirtualEnvironmentVMDiskSpeedReadBurstable: {
Type: schema.TypeInt,
Description: "The maximum burstable read speed in megabytes per second",
Optional: true,
Default: dvResourceVirtualEnvironmentVMDiskSpeedReadBurstable,
},
mkResourceVirtualEnvironmentVMDiskSpeedWrite: {
Type: schema.TypeInt,
Description: "The maximum write speed in megabytes per second",
Optional: true,
Default: dvResourceVirtualEnvironmentVMDiskSpeedWrite,
},
mkResourceVirtualEnvironmentVMDiskSpeedWriteBurstable: {
Type: schema.TypeInt,
Description: "The maximum burstable write speed in megabytes per second",
Optional: true,
Default: dvResourceVirtualEnvironmentVMDiskSpeedWriteBurstable,
},
},
},
MaxItems: 1,
MinItems: 0,
},
},
},
MaxItems: 14,
MinItems: 0,
},
mkResourceVirtualEnvironmentVMEFIDisk: {
Type: schema.TypeList,
Description: "The efidisk device",
Optional: true,
ForceNew: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMEFIDiskDatastoreID: {
Type: schema.TypeString,
Description: "The datastore id",
Optional: true,
Default: dvResourceVirtualEnvironmentVMEFIDiskDatastoreID,
},
mkResourceVirtualEnvironmentVMEFIDiskFileFormat: {
Type: schema.TypeString,
Description: "The file format",
Optional: true,
ForceNew: true,
Computed: true,
ValidateDiagFunc: validator.FileFormat(),
},
mkResourceVirtualEnvironmentVMEFIDiskType: {
Type: schema.TypeString,
Description: "Size and type of the OVMF EFI disk",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentVMEFIDiskType,
ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{
"2m",
"4m",
}, true)),
},
mkResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys: {
Type: schema.TypeBool,
Description: "Use an EFI vars template with distribution-specific and Microsoft Standard " +
"keys enrolled, if used with efi type=`4m`.",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys,
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkResourceVirtualEnvironmentVMInitialization: {
Type: schema.TypeList,
Description: "The cloud-init configuration",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMInitializationDatastoreID: {
Type: schema.TypeString,
Description: "The datastore id",
Optional: true,
Default: dvResourceVirtualEnvironmentVMInitializationDatastoreID,
},
mkResourceVirtualEnvironmentVMInitializationInterface: {
Type: schema.TypeString,
Description: "The IDE interface on which the CloudInit drive will be added",
Optional: true,
Default: dvResourceVirtualEnvironmentVMInitializationInterface,
ValidateDiagFunc: validator.IDEInterface(),
DiffSuppressFunc: func(k, oldValue, newValue string, d *schema.ResourceData) bool {
return newValue == ""
},
},
mkResourceVirtualEnvironmentVMInitializationDNS: {
Type: schema.TypeList,
Description: "The DNS configuration",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMInitializationDNSDomain: {
Type: schema.TypeString,
Description: "The DNS search domain",
Optional: true,
Default: dvResourceVirtualEnvironmentVMInitializationDNSDomain,
},
mkResourceVirtualEnvironmentVMInitializationDNSServer: {
Type: schema.TypeString,
Description: "The DNS server",
Optional: true,
Default: dvResourceVirtualEnvironmentVMInitializationDNSServer,
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkResourceVirtualEnvironmentVMInitializationIPConfig: {
Type: schema.TypeList,
Description: "The IP configuration",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4: {
Type: schema.TypeList,
Description: "The IPv4 configuration",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Address: {
Type: schema.TypeString,
Description: "The IPv4 address",
Optional: true,
Default: dvResourceVirtualEnvironmentVMInitializationIPConfigIPv4Address,
},
mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Gateway: {
Type: schema.TypeString,
Description: "The IPv4 gateway",
Optional: true,
Default: dvResourceVirtualEnvironmentVMInitializationIPConfigIPv4Gateway,
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6: {
Type: schema.TypeList,
Description: "The IPv6 configuration",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6Address: {
Type: schema.TypeString,
Description: "The IPv6 address",
Optional: true,
Default: dvResourceVirtualEnvironmentVMInitializationIPConfigIPv6Address,
},
mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6Gateway: {
Type: schema.TypeString,
Description: "The IPv6 gateway",
Optional: true,
Default: dvResourceVirtualEnvironmentVMInitializationIPConfigIPv6Gateway,
},
},
},
MaxItems: 1,
MinItems: 0,
},
},
},
MaxItems: 8,
MinItems: 0,
},
mkResourceVirtualEnvironmentVMInitializationUserAccount: {
Type: schema.TypeList,
Description: "The user account configuration",
Optional: true,
ForceNew: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMInitializationUserAccountKeys: {
Type: schema.TypeList,
Description: "The SSH keys",
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
mkResourceVirtualEnvironmentVMInitializationUserAccountPassword: {
Type: schema.TypeString,
Description: "The SSH password",
Optional: true,
ForceNew: true,
Sensitive: true,
Default: dvResourceVirtualEnvironmentVMInitializationUserAccountPassword,
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return len(old) > 0 &&
strings.ReplaceAll(old, "*", "") == ""
},
},
mkResourceVirtualEnvironmentVMInitializationUserAccountUsername: {
Type: schema.TypeString,
Description: "The SSH username",
Optional: true,
ForceNew: true,
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkResourceVirtualEnvironmentVMInitializationUserDataFileID: {
Type: schema.TypeString,
Description: "The ID of a file containing custom user data",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentVMInitializationUserDataFileID,
ValidateDiagFunc: validator.FileID(),
},
mkResourceVirtualEnvironmentVMInitializationVendorDataFileID: {
Type: schema.TypeString,
Description: "The ID of a file containing vendor data",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentVMInitializationVendorDataFileID,
ValidateDiagFunc: validator.FileID(),
},
mkResourceVirtualEnvironmentVMInitializationNetworkDataFileID: {
Type: schema.TypeString,
Description: "The ID of a file containing network config",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentVMInitializationNetworkDataFileID,
ValidateDiagFunc: validator.FileID(),
},
mkResourceVirtualEnvironmentVMInitializationMetaDataFileID: {
Type: schema.TypeString,
Description: "The ID of a file containing meta data config",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentVMInitializationMetaDataFileID,
ValidateDiagFunc: validator.FileID(),
},
mkResourceVirtualEnvironmentVMInitializationType: {
Type: schema.TypeString,
Description: "The cloud-init configuration format",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentVMInitializationType,
ValidateDiagFunc: validator.CloudInitType(),
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkResourceVirtualEnvironmentVMIPv4Addresses: {
Type: schema.TypeList,
Description: "The IPv4 addresses published by the QEMU agent",
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
mkResourceVirtualEnvironmentVMIPv6Addresses: {
Type: schema.TypeList,
Description: "The IPv6 addresses published by the QEMU agent",
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
mkResourceVirtualEnvironmentVMHostPCI: {
Type: schema.TypeList,
Description: "The Host PCI devices mapped to the VM",
Optional: true,
ForceNew: false,
DefaultFunc: func() (interface{}, error) {
return []interface{}{}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMHostPCIDevice: {
Type: schema.TypeString,
Description: "The PCI device name for Proxmox, in form of 'hostpciX' where X is a sequential number from 0 to 3",
Required: true,
},
mkResourceVirtualEnvironmentVMHostPCIDeviceID: {
Type: schema.TypeString,
Description: "The PCI ID of the device, for example 0000:00:1f.0 (or 0000:00:1f.0;0000:00:1f.1 for multiple " +
"device functions, or 0000:00:1f for all functions). Use either this or mapping.",
Optional: true,
},
mkResourceVirtualEnvironmentVMHostPCIDeviceMapping: {
Type: schema.TypeString,
Description: "The resource mapping name of the device, for example gpu. Use either this or id.",
Optional: true,
},
mkResourceVirtualEnvironmentVMHostPCIDeviceMDev: {
Type: schema.TypeString,
Description: "The the mediated device to use",
Optional: true,
},
mkResourceVirtualEnvironmentVMHostPCIDevicePCIE: {
Type: schema.TypeBool,
Description: "Tells Proxmox VE to use a PCIe or PCI port. Some guests/device combination require PCIe rather " +
"than PCI. PCIe is only available for q35 machine types.",
Optional: true,
},
mkResourceVirtualEnvironmentVMHostPCIDeviceROMBAR: {
Type: schema.TypeBool,
Description: "Makes the firmware ROM visible for the guest. Default is true",
Optional: true,
},
mkResourceVirtualEnvironmentVMHostPCIDeviceROMFile: {
Type: schema.TypeString,
Description: "A path to a ROM file for the device to use. This is a relative path under /usr/share/kvm/",
Optional: true,
},
mkResourceVirtualEnvironmentVMHostPCIDeviceXVGA: {
Type: schema.TypeBool,
Description: "Marks the PCI(e) device as the primary GPU of the VM. With this enabled the vga configuration argument will be ignored.",
Optional: true,
},
},
},
},
mkResourceVirtualEnvironmentVMKeyboardLayout: {
Type: schema.TypeString,
Description: "The keyboard layout",
Optional: true,
Default: dvResourceVirtualEnvironmentVMKeyboardLayout,
ValidateDiagFunc: validator.KeyboardLayout(),
},
mkResourceVirtualEnvironmentVMMachine: {
Type: schema.TypeString,
Description: "The VM machine type, either default i440fx or q35",
Optional: true,
Default: dvResourceVirtualEnvironmentVMMachineType,
},
mkResourceVirtualEnvironmentVMMACAddresses: {
Type: schema.TypeList,
Description: "The MAC addresses for the network interfaces",
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
mkResourceVirtualEnvironmentVMMemory: {
Type: schema.TypeList,
Description: "The memory allocation",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{
map[string]interface{}{
mkResourceVirtualEnvironmentVMMemoryDedicated: dvResourceVirtualEnvironmentVMMemoryDedicated,
mkResourceVirtualEnvironmentVMMemoryFloating: dvResourceVirtualEnvironmentVMMemoryFloating,
mkResourceVirtualEnvironmentVMMemoryShared: dvResourceVirtualEnvironmentVMMemoryShared,
},
}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMMemoryDedicated: {
Type: schema.TypeInt,
Description: "The dedicated memory in megabytes",
Optional: true,
Default: dvResourceVirtualEnvironmentVMMemoryDedicated,
ValidateDiagFunc: validation.ToDiagFunc(
validation.IntBetween(64, 268435456),
),
},
mkResourceVirtualEnvironmentVMMemoryFloating: {
Type: schema.TypeInt,
Description: "The floating memory in megabytes (balloon)",
Optional: true,
Default: dvResourceVirtualEnvironmentVMMemoryFloating,
ValidateDiagFunc: validation.ToDiagFunc(
validation.IntBetween(0, 268435456),
),
},
mkResourceVirtualEnvironmentVMMemoryShared: {
Type: schema.TypeInt,
Description: "The shared memory in megabytes",
Optional: true,
Default: dvResourceVirtualEnvironmentVMMemoryShared,
ValidateDiagFunc: validation.ToDiagFunc(
validation.IntBetween(0, 268435456),
),
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkResourceVirtualEnvironmentVMName: {
Type: schema.TypeString,
Description: "The name",
Optional: true,
Default: dvResourceVirtualEnvironmentVMName,
},
mkResourceVirtualEnvironmentVMNetworkDevice: {
Type: schema.TypeList,
Description: "The network devices",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return make([]interface{}, 1), nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMNetworkDeviceBridge: {
Type: schema.TypeString,
Description: "The bridge",
Optional: true,
Default: dvResourceVirtualEnvironmentVMNetworkDeviceBridge,
},
mkResourceVirtualEnvironmentVMNetworkDeviceEnabled: {
Type: schema.TypeBool,
Description: "Whether to enable the network device",
Optional: true,
Default: dvResourceVirtualEnvironmentVMNetworkDeviceEnabled,
},
mkResourceVirtualEnvironmentVMNetworkDeviceFirewall: {
Type: schema.TypeBool,
Description: "Whether this interface's firewall rules should be used",
Optional: true,
Default: dvResourceVirtualEnvironmentVMNetworkDeviceFirewall,
},
mkResourceVirtualEnvironmentVMNetworkDeviceMACAddress: {
Type: schema.TypeString,
Description: "The MAC address",
Optional: true,
Computed: true,
ValidateDiagFunc: validator.MACAddress(),
},
mkResourceVirtualEnvironmentVMNetworkDeviceModel: {
Type: schema.TypeString,
Description: "The model",
Optional: true,
Default: dvResourceVirtualEnvironmentVMNetworkDeviceModel,
ValidateDiagFunc: validator.NetworkDeviceModel(),
},
mkResourceVirtualEnvironmentVMNetworkDeviceRateLimit: {
Type: schema.TypeFloat,
Description: "The rate limit in megabytes per second",
Optional: true,
Default: dvResourceVirtualEnvironmentVMNetworkDeviceRateLimit,
},
mkResourceVirtualEnvironmentVMNetworkDeviceVLANID: {
Type: schema.TypeInt,
Description: "The VLAN identifier",
Optional: true,
Default: dvResourceVirtualEnvironmentVMNetworkDeviceVLANID,
},
mkResourceVirtualEnvironmentVMNetworkDeviceMTU: {
Type: schema.TypeInt,
Description: "Maximum transmission unit (MTU)",
Optional: true,
Default: dvResourceVirtualEnvironmentVMNetworkDeviceMTU,
},
},
},
MaxItems: maxResourceVirtualEnvironmentVMNetworkDevices,
MinItems: 0,
},
mkResourceVirtualEnvironmentVMNetworkInterfaceNames: {
Type: schema.TypeList,
Description: "The network interface names published by the QEMU agent",
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
mkResourceVirtualEnvironmentVMNodeName: {
Type: schema.TypeString,
Description: "The node name",
Required: true,
},
mkResourceVirtualEnvironmentVMMigrate: {
Type: schema.TypeBool,
Description: "Whether to migrate the VM on node change instead of re-creating it",
Optional: true,
Default: dvResourceVirtualEnvironmentVMMigrate,
},
mkResourceVirtualEnvironmentVMOperatingSystem: {
Type: schema.TypeList,
Description: "The operating system configuration",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{
map[string]interface{}{
mkResourceVirtualEnvironmentVMOperatingSystemType: dvResourceVirtualEnvironmentVMOperatingSystemType,
},
}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMOperatingSystemType: {
Type: schema.TypeString,
Description: "The type",
Optional: true,
Default: dvResourceVirtualEnvironmentVMOperatingSystemType,
ValidateDiagFunc: vmGetOperatingSystemTypeValidator(),
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkResourceVirtualEnvironmentVMPoolID: {
Type: schema.TypeString,
Description: "The ID of the pool to assign the virtual machine to",
Optional: true,
Default: dvResourceVirtualEnvironmentVMPoolID,
},
mkResourceVirtualEnvironmentVMSerialDevice: {
Type: schema.TypeList,
Description: "The serial devices",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{
map[string]interface{}{
mkResourceVirtualEnvironmentVMSerialDeviceDevice: dvResourceVirtualEnvironmentVMSerialDeviceDevice,
},
}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMSerialDeviceDevice: {
Type: schema.TypeString,
Description: "The device",
Optional: true,
Default: dvResourceVirtualEnvironmentVMSerialDeviceDevice,
ValidateDiagFunc: vmGetSerialDeviceValidator(),
},
},
},
MaxItems: maxResourceVirtualEnvironmentVMSerialDevices,
MinItems: 0,
},
mkResourceVirtualEnvironmentVMSMBIOS: {
Type: schema.TypeList,
Description: "Specifies SMBIOS (type1) settings for the VM",
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMSMBIOSFamily: {
Type: schema.TypeString,
Description: "Sets SMBIOS family string",
Optional: true,
Default: dvResourceVirtualEnvironmentVMSMBIOSFamily,
},
mkResourceVirtualEnvironmentVMSMBIOSManufacturer: {
Type: schema.TypeString,
Description: "Sets SMBIOS manufacturer",
Optional: true,
Default: dvResourceVirtualEnvironmentVMSMBIOSManufacturer,
},
mkResourceVirtualEnvironmentVMSMBIOSProduct: {
Type: schema.TypeString,
Description: "Sets SMBIOS product ID",
Optional: true,
Default: dvResourceVirtualEnvironmentVMSMBIOSProduct,
},
mkResourceVirtualEnvironmentVMSMBIOSSerial: {
Type: schema.TypeString,
Description: "Sets SMBIOS serial number",
Optional: true,
Default: dvResourceVirtualEnvironmentVMSMBIOSSerial,
},
mkResourceVirtualEnvironmentVMSMBIOSSKU: {
Type: schema.TypeString,
Description: "Sets SMBIOS SKU",
Optional: true,
Default: dvResourceVirtualEnvironmentVMSMBIOSSKU,
},
mkResourceVirtualEnvironmentVMSMBIOSUUID: {
Type: schema.TypeString,
Description: "Sets SMBIOS UUID",
Optional: true,
Computed: true,
},
mkResourceVirtualEnvironmentVMSMBIOSVersion: {
Type: schema.TypeString,
Description: "Sets SMBIOS version",
Optional: true,
Default: dvResourceVirtualEnvironmentVMSMBIOSVersion,
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkResourceVirtualEnvironmentVMStarted: {
Type: schema.TypeBool,
Description: "Whether to start the virtual machine",
Optional: true,
Default: dvResourceVirtualEnvironmentVMStarted,
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return d.Get(mkResourceVirtualEnvironmentVMTemplate).(bool)
},
},
mkResourceVirtualEnvironmentVMStartup: {
Type: schema.TypeList,
Description: "Defines startup and shutdown behavior of the VM",
Optional: true,
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,
},
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,
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkResourceVirtualEnvironmentVMTabletDevice: {
Type: schema.TypeBool,
Description: "Whether to enable the USB tablet device",
Optional: true,
Default: dvResourceVirtualEnvironmentVMTabletDevice,
},
mkResourceVirtualEnvironmentVMTags: {
Type: schema.TypeList,
Description: "Tags of the virtual machine. This is only meta information.",
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringIsNotEmpty,
},
DiffSuppressFunc: structure.SuppressIfListsAreEqualIgnoringOrder,
DiffSuppressOnRefresh: true,
},
mkResourceVirtualEnvironmentVMTemplate: {
Type: schema.TypeBool,
Description: "Whether to create a template",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentVMTemplate,
},
mkResourceVirtualEnvironmentVMTimeoutClone: {
Type: schema.TypeInt,
Description: "Clone VM timeout",
Optional: true,
Default: dvResourceVirtualEnvironmentVMTimeoutClone,
},
mkResourceVirtualEnvironmentVMTimeoutMoveDisk: {
Type: schema.TypeInt,
Description: "MoveDisk timeout",
Optional: true,
Default: dvResourceVirtualEnvironmentVMTimeoutMoveDisk,
},
mkResourceVirtualEnvironmentVMTimeoutMigrate: {
Type: schema.TypeInt,
Description: "Migrate VM timeout",
Optional: true,
Default: dvResourceVirtualEnvironmentVMTimeoutMigrate,
},
mkResourceVirtualEnvironmentVMTimeoutReboot: {
Type: schema.TypeInt,
Description: "Reboot timeout",
Optional: true,
Default: dvResourceVirtualEnvironmentVMTimeoutReboot,
},
mkResourceVirtualEnvironmentVMTimeoutShutdownVM: {
Type: schema.TypeInt,
Description: "Shutdown timeout",
Optional: true,
Default: dvResourceVirtualEnvironmentVMTimeoutShutdownVM,
},
mkResourceVirtualEnvironmentVMTimeoutStartVM: {
Type: schema.TypeInt,
Description: "Start VM timeout",
Optional: true,
Default: dvResourceVirtualEnvironmentVMTimeoutStartVM,
},
mkResourceVirtualEnvironmentVMTimeoutStopVM: {
Type: schema.TypeInt,
Description: "Stop VM timeout",
Optional: true,
Default: dvResourceVirtualEnvironmentVMTimeoutStopVM,
},
mkResourceVirtualEnvironmentVMVGA: {
Type: schema.TypeList,
Description: "The VGA configuration",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{
map[string]interface{}{
mkResourceVirtualEnvironmentVMVGAEnabled: dvResourceVirtualEnvironmentVMVGAEnabled,
mkResourceVirtualEnvironmentVMVGAMemory: dvResourceVirtualEnvironmentVMVGAMemory,
mkResourceVirtualEnvironmentVMVGAType: dvResourceVirtualEnvironmentVMVGAType,
},
}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMVGAEnabled: {
Type: schema.TypeBool,
Description: "Whether to enable the VGA device",
Optional: true,
Default: dvResourceVirtualEnvironmentVMVGAEnabled,
},
mkResourceVirtualEnvironmentVMVGAMemory: {
Type: schema.TypeInt,
Description: "The VGA memory in megabytes (4-512 MB)",
Optional: true,
Default: dvResourceVirtualEnvironmentVMVGAMemory,
ValidateDiagFunc: validator.VGAMemory(),
},
mkResourceVirtualEnvironmentVMVGAType: {
Type: schema.TypeString,
Description: "The VGA type",
Optional: true,
Default: dvResourceVirtualEnvironmentVMVGAType,
ValidateDiagFunc: validator.VGAType(),
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkResourceVirtualEnvironmentVMVMID: {
Type: schema.TypeInt,
Description: "The VM identifier",
Optional: true,
Computed: true,
// "ForceNew: true" handled in CustomizeDiff, making sure VMs with legacy configs with vm_id = -1
// do not require re-creation.
ValidateDiagFunc: validator.VMID(),
},
mkResourceVirtualEnvironmentVMSCSIHardware: {
Type: schema.TypeString,
Description: "The SCSI hardware type",
Optional: true,
Default: dvResourceVirtualEnvironmentVMSCSIHardware,
ValidateDiagFunc: validator.SCSIHardware(),
},
},
CreateContext: vmCreate,
ReadContext: vmRead,
UpdateContext: vmUpdate,
DeleteContext: vmDelete,
CustomizeDiff: customdiff.All(
customdiff.ComputedIf(
mkResourceVirtualEnvironmentVMIPv4Addresses,
func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) bool {
return d.HasChange(mkResourceVirtualEnvironmentVMStarted) ||
d.HasChange(mkResourceVirtualEnvironmentVMNetworkDevice)
},
),
customdiff.ComputedIf(
mkResourceVirtualEnvironmentVMIPv6Addresses,
func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) bool {
return d.HasChange(mkResourceVirtualEnvironmentVMStarted) ||
d.HasChange(mkResourceVirtualEnvironmentVMNetworkDevice)
},
),
customdiff.ComputedIf(
mkResourceVirtualEnvironmentVMNetworkInterfaceNames,
func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) bool {
return d.HasChange(mkResourceVirtualEnvironmentVMStarted) ||
d.HasChange(mkResourceVirtualEnvironmentVMNetworkDevice)
},
),
customdiff.ForceNewIf(
mkResourceVirtualEnvironmentVMVMID,
func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) bool {
newValue := d.Get(mkResourceVirtualEnvironmentVMVMID)
// 'vm_id' is ForceNew, except when changing 'vm_id' to existing correct id
// (automatic fix from -1 to actual vm_id must not re-create VM)
return strconv.Itoa(newValue.(int)) != d.Id()
},
),
customdiff.ForceNewIf(
mkResourceVirtualEnvironmentVMNodeName,
func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) bool {
return !d.Get(mkResourceVirtualEnvironmentVMMigrate).(bool)
},
),
),
Importer: &schema.ResourceImporter{
StateContext: func(ctx context.Context, d *schema.ResourceData, i interface{}) ([]*schema.ResourceData, error) {
node, id, err := parseImportIDWithNodeName(d.Id())
if err != nil {
return nil, err
}
d.SetId(id)
err = d.Set(mkResourceVirtualEnvironmentVMNodeName, node)
if err != nil {
return nil, fmt.Errorf("failed setting state during import: %w", err)
}
return []*schema.ResourceData{d}, nil
},
},
}
}
func vmCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
clone := d.Get(mkResourceVirtualEnvironmentVMClone).([]interface{})
if len(clone) > 0 {
return vmCreateClone(ctx, d, m)
}
return vmCreateCustom(ctx, d, m)
}
// Check for an existing CloudInit IDE drive. If no such drive is found, return the specified `defaultValue`.
func findExistingCloudInitDrive(vmConfig *vms.GetResponseData, vmID int, defaultValue string) string {
devices := []*vms.CustomStorageDevice{
vmConfig.IDEDevice0, vmConfig.IDEDevice1, vmConfig.IDEDevice2, vmConfig.IDEDevice3,
}
for i, device := range devices {
if device != nil && device.Enabled && device.Media != nil && *device.Media == "cdrom" && strings.Contains(
device.FileVolume,
fmt.Sprintf("vm-%d-cloudinit", vmID),
) {
return fmt.Sprintf("ide%d", i)
}
}
return defaultValue
}
// Return a pointer to the IDE device configuration based on its name. The device name is assumed to be a
// valid IDE interface name.
func getIdeDevice(vmConfig *vms.GetResponseData, deviceName string) *vms.CustomStorageDevice {
ideDevice := vmConfig.IDEDevice3
switch deviceName {
case "ide0":
ideDevice = vmConfig.IDEDevice0
case "ide1":
ideDevice = vmConfig.IDEDevice1
case "ide2":
ideDevice = vmConfig.IDEDevice2
}
return ideDevice
}
// Delete IDE interfaces that can then be used for CloudInit. The first interface will always
// be deleted. The second will be deleted only if it isn't empty and isn't the same as the
// first.
func deleteIdeDrives(ctx context.Context, vmAPI *vms.Client, itf1 string, itf2 string) diag.Diagnostics {
ddUpdateBody := &vms.UpdateRequestBody{}
ddUpdateBody.Delete = append(ddUpdateBody.Delete, itf1)
tflog.Debug(ctx, fmt.Sprintf("Deleting IDE interface '%s'", itf1))
if itf2 != "" && itf2 != itf1 {
ddUpdateBody.Delete = append(ddUpdateBody.Delete, itf2)
tflog.Debug(ctx, fmt.Sprintf("Deleting IDE interface '%s'", itf2))
}
e := vmAPI.UpdateVM(ctx, ddUpdateBody)
if e != nil {
return diag.FromErr(e)
}
return nil
}
// Start the VM, then wait for it to actually start; it may not be started immediately if running in HA mode.
func vmStart(ctx context.Context, vmAPI *vms.Client, d *schema.ResourceData) diag.Diagnostics {
tflog.Debug(ctx, "Starting VM")
startVMTimeout := d.Get(mkResourceVirtualEnvironmentVMTimeoutStartVM).(int)
e := vmAPI.StartVM(ctx, startVMTimeout)
if e != nil {
return diag.FromErr(e)
}
return diag.FromErr(vmAPI.WaitForVMState(ctx, "running", startVMTimeout, 1))
}
// Shutdown the VM, then wait for it to actually shut down (it may not be shut down immediately if
// running in HA mode).
func vmShutdown(ctx context.Context, vmAPI *vms.Client, d *schema.ResourceData) diag.Diagnostics {
tflog.Debug(ctx, "Shutting down VM")
forceStop := types2.CustomBool(true)
shutdownTimeout := d.Get(mkResourceVirtualEnvironmentVMTimeoutShutdownVM).(int)
e := vmAPI.ShutdownVM(ctx, &vms.ShutdownRequestBody{
ForceStop: &forceStop,
Timeout: &shutdownTimeout,
}, shutdownTimeout+30)
if e != nil {
return diag.FromErr(e)
}
return diag.FromErr(vmAPI.WaitForVMState(ctx, "stopped", shutdownTimeout, 1))
}
func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
config := m.(proxmoxtf.ProviderConfiguration)
api, e := config.GetClient()
if e != nil {
return diag.FromErr(e)
}
clone := d.Get(mkResourceVirtualEnvironmentVMClone).([]interface{})
cloneBlock := clone[0].(map[string]interface{})
cloneRetries := cloneBlock[mkResourceVirtualEnvironmentVMCloneRetries].(int)
cloneDatastoreID := cloneBlock[mkResourceVirtualEnvironmentVMCloneDatastoreID].(string)
cloneNodeName := cloneBlock[mkResourceVirtualEnvironmentVMCloneNodeName].(string)
cloneVMID := cloneBlock[mkResourceVirtualEnvironmentVMCloneVMID].(int)
cloneFull := cloneBlock[mkResourceVirtualEnvironmentVMCloneFull].(bool)
description := d.Get(mkResourceVirtualEnvironmentVMDescription).(string)
name := d.Get(mkResourceVirtualEnvironmentVMName).(string)
tags := d.Get(mkResourceVirtualEnvironmentVMTags).([]interface{})
nodeName := d.Get(mkResourceVirtualEnvironmentVMNodeName).(string)
poolID := d.Get(mkResourceVirtualEnvironmentVMPoolID).(string)
vmIDUntyped, hasVMID := d.GetOk(mkResourceVirtualEnvironmentVMVMID)
vmID := vmIDUntyped.(int)
if !hasVMID {
vmIDNew, err := api.Cluster().GetVMID(ctx)
if err != nil {
return diag.FromErr(err)
}
vmID = *vmIDNew
err = d.Set(mkResourceVirtualEnvironmentVMVMID, vmID)
if err != nil {
return diag.FromErr(err)
}
}
fullCopy := types2.CustomBool(cloneFull)
cloneBody := &vms.CloneRequestBody{
FullCopy: &fullCopy,
VMIDNew: vmID,
}
if cloneDatastoreID != "" {
cloneBody.TargetStorage = &cloneDatastoreID
}
if description != "" {
cloneBody.Description = &description
}
if name != "" {
cloneBody.Name = &name
}
if poolID != "" {
cloneBody.PoolID = &poolID
}
cloneTimeout := d.Get(mkResourceVirtualEnvironmentVMTimeoutClone).(int)
if cloneNodeName != "" && cloneNodeName != nodeName {
// Check if any used datastores of the source VM are not shared
vmConfig, err := api.Node(cloneNodeName).VM(cloneVMID).GetVM(ctx)
if err != nil {
return diag.FromErr(err)
}
datastores := getDiskDatastores(vmConfig, d)
onlySharedDatastores := true
for _, datastore := range datastores {
datastoreStatus, err2 := api.Node(cloneNodeName).GetDatastoreStatus(ctx, datastore)
if err2 != nil {
return diag.FromErr(err2)
}
if datastoreStatus.Shared != nil && !*datastoreStatus.Shared {
onlySharedDatastores = false
break
}
}
if onlySharedDatastores {
// If the source and the target node are not the same, only clone directly to the target node if
// all used datastores in the source VM are shared. Directly cloning to non-shared storage
// on a different node is currently not supported by proxmox.
cloneBody.TargetNodeName = &nodeName
err = api.Node(cloneNodeName).VM(cloneVMID).CloneVM(
ctx,
cloneRetries,
cloneBody,
cloneTimeout,
)
if err != nil {
return diag.FromErr(err)
}
} else {
// If the source and the target node are not the same and any used datastore in the source VM is
// not shared, clone to the source node and then migrate to the target node. This is a workaround
// for missing functionality in the proxmox api as recommended per
// https://forum.proxmox.com/threads/500-cant-clone-to-non-shared-storage-local.49078/#post-229727
// Temporarily clone to local node
err = api.Node(cloneNodeName).VM(cloneVMID).CloneVM(ctx, cloneRetries, cloneBody, cloneTimeout)
if err != nil {
return diag.FromErr(err)
}
// Wait for the virtual machine to be created and its configuration lock to be released before migrating.
err = api.Node(cloneNodeName).VM(vmID).WaitForVMConfigUnlock(ctx, 600, 5, true)
if err != nil {
return diag.FromErr(err)
}
// Migrate to target node
withLocalDisks := types2.CustomBool(true)
migrateBody := &vms.MigrateRequestBody{
TargetNode: nodeName,
WithLocalDisks: &withLocalDisks,
}
if cloneDatastoreID != "" {
migrateBody.TargetStorage = &cloneDatastoreID
}
err = api.Node(cloneNodeName).VM(vmID).MigrateVM(ctx, migrateBody, cloneTimeout)
if err != nil {
return diag.FromErr(err)
}
}
} else {
e = api.Node(nodeName).VM(cloneVMID).CloneVM(ctx, cloneRetries, cloneBody, cloneTimeout)
}
if e != nil {
return diag.FromErr(e)
}
d.SetId(strconv.Itoa(vmID))
vmAPI := api.Node(nodeName).VM(vmID)
// Wait for the virtual machine to be created and its configuration lock to be released.
e = vmAPI.WaitForVMConfigUnlock(ctx, 600, 5, true)
if e != nil {
return diag.FromErr(e)
}
// Now that the virtual machine has been cloned, we need to perform some modifications.
acpi := types2.CustomBool(d.Get(mkResourceVirtualEnvironmentVMACPI).(bool))
agent := d.Get(mkResourceVirtualEnvironmentVMAgent).([]interface{})
audioDevices := vmGetAudioDeviceList(d)
bios := d.Get(mkResourceVirtualEnvironmentVMBIOS).(string)
kvmArguments := d.Get(mkResourceVirtualEnvironmentVMKVMArguments).(string)
scsiHardware := d.Get(mkResourceVirtualEnvironmentVMSCSIHardware).(string)
cdrom := d.Get(mkResourceVirtualEnvironmentVMCDROM).([]interface{})
cpu := d.Get(mkResourceVirtualEnvironmentVMCPU).([]interface{})
initialization := d.Get(mkResourceVirtualEnvironmentVMInitialization).([]interface{})
hostPCI := d.Get(mkResourceVirtualEnvironmentVMHostPCI).([]interface{})
keyboardLayout := d.Get(mkResourceVirtualEnvironmentVMKeyboardLayout).(string)
memory := d.Get(mkResourceVirtualEnvironmentVMMemory).([]interface{})
networkDevice := d.Get(mkResourceVirtualEnvironmentVMNetworkDevice).([]interface{})
operatingSystem := d.Get(mkResourceVirtualEnvironmentVMOperatingSystem).([]interface{})
serialDevice := d.Get(mkResourceVirtualEnvironmentVMSerialDevice).([]interface{})
onBoot := types2.CustomBool(d.Get(mkResourceVirtualEnvironmentVMOnBoot).(bool))
tabletDevice := types2.CustomBool(d.Get(mkResourceVirtualEnvironmentVMTabletDevice).(bool))
template := types2.CustomBool(d.Get(mkResourceVirtualEnvironmentVMTemplate).(bool))
vga := d.Get(mkResourceVirtualEnvironmentVMVGA).([]interface{})
updateBody := &vms.UpdateRequestBody{
AudioDevices: audioDevices,
}
ideDevices := vms.CustomStorageDevices{}
var del []string
//nolint:gosimple
if acpi != dvResourceVirtualEnvironmentVMACPI {
updateBody.ACPI = &acpi
}
if len(agent) > 0 {
agentBlock := agent[0].(map[string]interface{})
agentEnabled := types2.CustomBool(
agentBlock[mkResourceVirtualEnvironmentVMAgentEnabled].(bool),
)
agentTrim := types2.CustomBool(agentBlock[mkResourceVirtualEnvironmentVMAgentTrim].(bool))
agentType := agentBlock[mkResourceVirtualEnvironmentVMAgentType].(string)
updateBody.Agent = &vms.CustomAgent{
Enabled: &agentEnabled,
TrimClonedDisks: &agentTrim,
Type: &agentType,
}
}
if kvmArguments != dvResourceVirtualEnvironmentVMKVMArguments {
updateBody.KVMArguments = &kvmArguments
}
if bios != dvResourceVirtualEnvironmentVMBIOS {
updateBody.BIOS = &bios
}
if scsiHardware != dvResourceVirtualEnvironmentVMSCSIHardware {
updateBody.SCSIHardware = &scsiHardware
}
if len(cdrom) > 0 || len(initialization) > 0 {
ideDevices = vms.CustomStorageDevices{
"ide0": vms.CustomStorageDevice{
Enabled: false,
},
"ide1": vms.CustomStorageDevice{
Enabled: false,
},
"ide2": vms.CustomStorageDevice{
Enabled: false,
},
"ide3": vms.CustomStorageDevice{
Enabled: false,
},
}
}
if len(cdrom) > 0 {
cdromBlock := cdrom[0].(map[string]interface{})
cdromEnabled := cdromBlock[mkResourceVirtualEnvironmentVMCDROMEnabled].(bool)
cdromFileID := cdromBlock[mkResourceVirtualEnvironmentVMCDROMFileID].(string)
cdromInterface := cdromBlock[mkResourceVirtualEnvironmentVMCDROMInterface].(string)
if cdromFileID == "" {
cdromFileID = "cdrom"
}
cdromMedia := "cdrom"
ideDevices[cdromInterface] = vms.CustomStorageDevice{
Enabled: cdromEnabled,
FileVolume: cdromFileID,
Media: &cdromMedia,
}
}
if len(cpu) > 0 {
cpuBlock := cpu[0].(map[string]interface{})
cpuArchitecture := cpuBlock[mkResourceVirtualEnvironmentVMCPUArchitecture].(string)
cpuCores := cpuBlock[mkResourceVirtualEnvironmentVMCPUCores].(int)
cpuFlags := cpuBlock[mkResourceVirtualEnvironmentVMCPUFlags].([]interface{})
cpuHotplugged := cpuBlock[mkResourceVirtualEnvironmentVMCPUHotplugged].(int)
cpuNUMA := types2.CustomBool(cpuBlock[mkResourceVirtualEnvironmentVMCPUNUMA].(bool))
cpuSockets := cpuBlock[mkResourceVirtualEnvironmentVMCPUSockets].(int)
cpuType := cpuBlock[mkResourceVirtualEnvironmentVMCPUType].(string)
cpuUnits := cpuBlock[mkResourceVirtualEnvironmentVMCPUUnits].(int)
cpuFlagsConverted := make([]string, len(cpuFlags))
for fi, flag := range cpuFlags {
cpuFlagsConverted[fi] = flag.(string)
}
// Only the root account is allowed to change the CPU architecture, which makes this check necessary.
if api.API().IsRootTicket() ||
cpuArchitecture != dvResourceVirtualEnvironmentVMCPUArchitecture {
updateBody.CPUArchitecture = &cpuArchitecture
}
updateBody.CPUCores = &cpuCores
updateBody.CPUEmulation = &vms.CustomCPUEmulation{
Flags: &cpuFlagsConverted,
Type: cpuType,
}
updateBody.NUMAEnabled = &cpuNUMA
updateBody.CPUSockets = &cpuSockets
updateBody.CPUUnits = &cpuUnits
if cpuHotplugged > 0 {
updateBody.VirtualCPUCount = &cpuHotplugged
}
}
if len(initialization) > 0 {
tflog.Trace(ctx, "Preparing the CloudInit configuration")
initializationBlock := initialization[0].(map[string]interface{})
initializationDatastoreID := initializationBlock[mkResourceVirtualEnvironmentVMInitializationDatastoreID].(string)
initializationInterface := initializationBlock[mkResourceVirtualEnvironmentVMInitializationInterface].(string)
vmConfig, err := vmAPI.GetVM(ctx)
if err != nil {
return diag.FromErr(err)
}
existingInterface := findExistingCloudInitDrive(vmConfig, vmID, "ide2")
if initializationInterface == "" {
initializationInterface = existingInterface
}
tflog.Trace(ctx, fmt.Sprintf("CloudInit IDE interface is '%s'", initializationInterface))
const cdromCloudInitEnabled = true
cdromCloudInitFileID := fmt.Sprintf("%s:cloudinit", initializationDatastoreID)
cdromCloudInitMedia := "cdrom"
ideDevices[initializationInterface] = vms.CustomStorageDevice{
Enabled: cdromCloudInitEnabled,
FileVolume: cdromCloudInitFileID,
Media: &cdromCloudInitMedia,
}
if err := deleteIdeDrives(ctx, vmAPI, initializationInterface, existingInterface); err != nil {
return err
}
updateBody.CloudInitConfig = vmGetCloudInitConfig(d)
}
if len(hostPCI) > 0 {
updateBody.PCIDevices = vmGetHostPCIDeviceObjects(d)
}
if len(cdrom) > 0 || len(initialization) > 0 {
updateBody.IDEDevices = ideDevices
}
if keyboardLayout != dvResourceVirtualEnvironmentVMKeyboardLayout {
updateBody.KeyboardLayout = &keyboardLayout
}
if len(memory) > 0 {
memoryBlock := memory[0].(map[string]interface{})
memoryDedicated := memoryBlock[mkResourceVirtualEnvironmentVMMemoryDedicated].(int)
memoryFloating := memoryBlock[mkResourceVirtualEnvironmentVMMemoryFloating].(int)
memoryShared := memoryBlock[mkResourceVirtualEnvironmentVMMemoryShared].(int)
updateBody.DedicatedMemory = &memoryDedicated
updateBody.FloatingMemory = &memoryFloating
if memoryShared > 0 {
memorySharedName := fmt.Sprintf("vm-%d-ivshmem", vmID)
updateBody.SharedMemory = &vms.CustomSharedMemory{
Name: &memorySharedName,
Size: memoryShared,
}
}
}
if len(networkDevice) > 0 {
updateBody.NetworkDevices = vmGetNetworkDeviceObjects(d)
for i := 0; i < len(updateBody.NetworkDevices); i++ {
if !updateBody.NetworkDevices[i].Enabled {
del = append(del, fmt.Sprintf("net%d", i))
}
}
for i := len(updateBody.NetworkDevices); i < maxResourceVirtualEnvironmentVMNetworkDevices; i++ {
del = append(del, fmt.Sprintf("net%d", i))
}
}
if len(operatingSystem) > 0 {
operatingSystemBlock := operatingSystem[0].(map[string]interface{})
operatingSystemType := operatingSystemBlock[mkResourceVirtualEnvironmentVMOperatingSystemType].(string)
updateBody.OSType = &operatingSystemType
}
if len(serialDevice) > 0 {
updateBody.SerialDevices = vmGetSerialDeviceList(d)
for i := len(updateBody.SerialDevices); i < maxResourceVirtualEnvironmentVMSerialDevices; i++ {
del = append(del, fmt.Sprintf("serial%d", i))
}
}
updateBody.StartOnBoot = &onBoot
updateBody.StartupOrder = vmGetStartupOrder(d)
//nolint:gosimple
if tabletDevice != dvResourceVirtualEnvironmentVMTabletDevice {
updateBody.TabletDeviceEnabled = &tabletDevice
}
if len(tags) > 0 {
tagString := vmGetTagsString(d)
updateBody.Tags = &tagString
}
//nolint:gosimple
if template != dvResourceVirtualEnvironmentVMTemplate {
updateBody.Template = &template
}
if len(vga) > 0 {
vgaDevice, err := vmGetVGADeviceObject(d)
if err != nil {
return diag.FromErr(err)
}
updateBody.VGADevice = vgaDevice
}
updateBody.Delete = del
e = vmAPI.UpdateVM(ctx, updateBody)
if e != nil {
return diag.FromErr(e)
}
disk := d.Get(mkResourceVirtualEnvironmentVMDisk).([]interface{})
efiDisk := d.Get(mkResourceVirtualEnvironmentVMEFIDisk).([]interface{})
vmConfig, e := vmAPI.GetVM(ctx)
if e != nil {
if strings.Contains(e.Error(), "HTTP 404") ||
(strings.Contains(e.Error(), "HTTP 500") && strings.Contains(e.Error(), "does not exist")) {
d.SetId("")
return nil
}
return diag.FromErr(e)
}
allDiskInfo := getDiskInfo(vmConfig, d) // from the cloned VM
diskDeviceObjects, e := vmGetDiskDeviceObjects(d, nil) // from the resource config
if e != nil {
return diag.FromErr(e)
}
for i := range disk {
diskBlock := disk[i].(map[string]interface{})
diskInterface := diskBlock[mkResourceVirtualEnvironmentVMDiskInterface].(string)
dataStoreID := diskBlock[mkResourceVirtualEnvironmentVMDiskDatastoreID].(string)
diskSize := diskBlock[mkResourceVirtualEnvironmentVMDiskSize].(int)
prefix := diskDigitPrefix(diskInterface)
currentDiskInfo := allDiskInfo[diskInterface]
configuredDiskInfo := diskDeviceObjects[prefix][diskInterface]
if currentDiskInfo == nil {
diskUpdateBody := &vms.UpdateRequestBody{}
switch prefix {
case "virtio":
if diskUpdateBody.VirtualIODevices == nil {
diskUpdateBody.VirtualIODevices = vms.CustomStorageDevices{}
}
diskUpdateBody.VirtualIODevices[diskInterface] = configuredDiskInfo
case "sata":
if diskUpdateBody.SATADevices == nil {
diskUpdateBody.SATADevices = vms.CustomStorageDevices{}
}
diskUpdateBody.SATADevices[diskInterface] = configuredDiskInfo
case "scsi":
if diskUpdateBody.SCSIDevices == nil {
diskUpdateBody.SCSIDevices = vms.CustomStorageDevices{}
}
diskUpdateBody.SCSIDevices[diskInterface] = configuredDiskInfo
}
e = vmAPI.UpdateVM(ctx, diskUpdateBody)
if e != nil {
return diag.FromErr(e)
}
continue
}
if diskSize < currentDiskInfo.Size.InGigabytes() {
return diag.Errorf(
"disk resize fails requests size (%dG) is lower than current size (%s)",
diskSize,
*currentDiskInfo.Size,
)
}
deleteOriginalDisk := types2.CustomBool(true)
diskMoveBody := &vms.MoveDiskRequestBody{
DeleteOriginalDisk: &deleteOriginalDisk,
Disk: diskInterface,
TargetStorage: dataStoreID,
}
diskResizeBody := &vms.ResizeDiskRequestBody{
Disk: diskInterface,
Size: types2.DiskSizeFromGigabytes(diskSize),
}
moveDisk := false
if dataStoreID != "" {
moveDisk = true
if allDiskInfo[diskInterface] != nil {
fileIDParts := strings.Split(allDiskInfo[diskInterface].FileVolume, ":")
moveDisk = dataStoreID != fileIDParts[0]
}
}
if moveDisk {
moveDiskTimeout := d.Get(mkResourceVirtualEnvironmentVMTimeoutMoveDisk).(int)
e = vmAPI.MoveVMDisk(ctx, diskMoveBody, moveDiskTimeout)
if e != nil {
return diag.FromErr(e)
}
}
if diskSize > currentDiskInfo.Size.InGigabytes() {
e = vmAPI.ResizeVMDisk(ctx, diskResizeBody)
if e != nil {
return diag.FromErr(e)
}
}
}
efiDiskInfo := vmGetEfiDisk(d, nil) // from the resource config
for i := range efiDisk {
diskBlock := efiDisk[i].(map[string]interface{})
diskInterface := "efidisk0"
dataStoreID := diskBlock[mkResourceVirtualEnvironmentVMEFIDiskDatastoreID].(string)
efiType := diskBlock[mkResourceVirtualEnvironmentVMEFIDiskType].(string)
currentDiskInfo := vmConfig.EFIDisk
configuredDiskInfo := efiDiskInfo
if currentDiskInfo == nil {
diskUpdateBody := &vms.UpdateRequestBody{}
diskUpdateBody.EFIDisk = configuredDiskInfo
e = vmAPI.UpdateVM(ctx, diskUpdateBody)
if e != nil {
return diag.FromErr(e)
}
continue
}
if &efiType != currentDiskInfo.Type {
return diag.Errorf(
"resizing of efidisks is not supported.",
)
}
deleteOriginalDisk := types2.CustomBool(true)
diskMoveBody := &vms.MoveDiskRequestBody{
DeleteOriginalDisk: &deleteOriginalDisk,
Disk: diskInterface,
TargetStorage: dataStoreID,
}
moveDisk := false
if dataStoreID != "" {
moveDisk = true
if allDiskInfo[diskInterface] != nil {
fileIDParts := strings.Split(allDiskInfo[diskInterface].FileVolume, ":")
moveDisk = dataStoreID != fileIDParts[0]
}
}
if moveDisk {
moveDiskTimeout := d.Get(mkResourceVirtualEnvironmentVMTimeoutMoveDisk).(int)
e = vmAPI.MoveVMDisk(ctx, diskMoveBody, moveDiskTimeout)
if e != nil {
return diag.FromErr(e)
}
}
}
return vmCreateStart(ctx, d, m)
}
func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
config := m.(proxmoxtf.ProviderConfiguration)
api, err := config.GetClient()
if err != nil {
return diag.FromErr(err)
}
resource := VM()
acpi := types2.CustomBool(d.Get(mkResourceVirtualEnvironmentVMACPI).(bool))
agentBlock, err := structure.GetSchemaBlock(
resource,
d,
[]string{mkResourceVirtualEnvironmentVMAgent},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
agentEnabled := types2.CustomBool(
agentBlock[mkResourceVirtualEnvironmentVMAgentEnabled].(bool),
)
agentTrim := types2.CustomBool(agentBlock[mkResourceVirtualEnvironmentVMAgentTrim].(bool))
agentType := agentBlock[mkResourceVirtualEnvironmentVMAgentType].(string)
kvmArguments := d.Get(mkResourceVirtualEnvironmentVMKVMArguments).(string)
audioDevices := vmGetAudioDeviceList(d)
bios := d.Get(mkResourceVirtualEnvironmentVMBIOS).(string)
cdromBlock, err := structure.GetSchemaBlock(
resource,
d,
[]string{mkResourceVirtualEnvironmentVMCDROM},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
cdromEnabled := cdromBlock[mkResourceVirtualEnvironmentVMCDROMEnabled].(bool)
cdromFileID := cdromBlock[mkResourceVirtualEnvironmentVMCDROMFileID].(string)
cdromInterface := cdromBlock[mkResourceVirtualEnvironmentVMCDROMInterface].(string)
cdromCloudInitEnabled := false
cdromCloudInitFileID := ""
cdromCloudInitInterface := ""
if cdromFileID == "" {
cdromFileID = "cdrom"
}
cpuBlock, err := structure.GetSchemaBlock(
resource,
d,
[]string{mkResourceVirtualEnvironmentVMCPU},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
cpuArchitecture := cpuBlock[mkResourceVirtualEnvironmentVMCPUArchitecture].(string)
cpuCores := cpuBlock[mkResourceVirtualEnvironmentVMCPUCores].(int)
cpuFlags := cpuBlock[mkResourceVirtualEnvironmentVMCPUFlags].([]interface{})
cpuHotplugged := cpuBlock[mkResourceVirtualEnvironmentVMCPUHotplugged].(int)
cpuSockets := cpuBlock[mkResourceVirtualEnvironmentVMCPUSockets].(int)
cpuNUMA := types2.CustomBool(cpuBlock[mkResourceVirtualEnvironmentVMCPUNUMA].(bool))
cpuType := cpuBlock[mkResourceVirtualEnvironmentVMCPUType].(string)
cpuUnits := cpuBlock[mkResourceVirtualEnvironmentVMCPUUnits].(int)
description := d.Get(mkResourceVirtualEnvironmentVMDescription).(string)
diskDeviceObjects, err := vmGetDiskDeviceObjects(d, nil)
if err != nil {
return diag.FromErr(err)
}
var efiDisk *vms.CustomEFIDisk
efiDiskBlock := d.Get(mkResourceVirtualEnvironmentVMEFIDisk).([]interface{})
if len(efiDiskBlock) > 0 {
block := efiDiskBlock[0].(map[string]interface{})
datastoreID, _ := block[mkResourceVirtualEnvironmentVMEFIDiskDatastoreID].(string)
fileFormat, _ := block[mkResourceVirtualEnvironmentVMEFIDiskFileFormat].(string)
efiType, _ := block[mkResourceVirtualEnvironmentVMEFIDiskType].(string)
preEnrolledKeys := types2.CustomBool(block[mkResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys].(bool))
if fileFormat == "" {
fileFormat = dvResourceVirtualEnvironmentVMEFIDiskFileFormat
}
efiDisk = &vms.CustomEFIDisk{
Type: &efiType,
FileVolume: fmt.Sprintf("%s:1", datastoreID),
Format: &fileFormat,
PreEnrolledKeys: &preEnrolledKeys,
}
}
virtioDeviceObjects := diskDeviceObjects["virtio"]
scsiDeviceObjects := diskDeviceObjects["scsi"]
// ideDeviceObjects := getOrderedDiskDeviceList(diskDeviceObjects, "ide")
sataDeviceObjects := diskDeviceObjects["sata"]
initializationConfig := vmGetCloudInitConfig(d)
if initializationConfig != nil {
initialization := d.Get(mkResourceVirtualEnvironmentVMInitialization).([]interface{})
initializationBlock := initialization[0].(map[string]interface{})
initializationDatastoreID := initializationBlock[mkResourceVirtualEnvironmentVMInitializationDatastoreID].(string)
cdromCloudInitEnabled = true
cdromCloudInitFileID = fmt.Sprintf("%s:cloudinit", initializationDatastoreID)
cdromCloudInitInterface = initializationBlock[mkResourceVirtualEnvironmentVMInitializationInterface].(string)
if cdromCloudInitInterface == "" {
cdromCloudInitInterface = "ide2"
}
}
pciDeviceObjects := vmGetHostPCIDeviceObjects(d)
keyboardLayout := d.Get(mkResourceVirtualEnvironmentVMKeyboardLayout).(string)
memoryBlock, err := structure.GetSchemaBlock(
resource,
d,
[]string{mkResourceVirtualEnvironmentVMMemory},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
memoryDedicated := memoryBlock[mkResourceVirtualEnvironmentVMMemoryDedicated].(int)
memoryFloating := memoryBlock[mkResourceVirtualEnvironmentVMMemoryFloating].(int)
memoryShared := memoryBlock[mkResourceVirtualEnvironmentVMMemoryShared].(int)
machine := d.Get(mkResourceVirtualEnvironmentVMMachine).(string)
name := d.Get(mkResourceVirtualEnvironmentVMName).(string)
tags := d.Get(mkResourceVirtualEnvironmentVMTags).([]interface{})
networkDeviceObjects := vmGetNetworkDeviceObjects(d)
nodeName := d.Get(mkResourceVirtualEnvironmentVMNodeName).(string)
operatingSystem, err := structure.GetSchemaBlock(
resource,
d,
[]string{mkResourceVirtualEnvironmentVMOperatingSystem},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
operatingSystemType := operatingSystem[mkResourceVirtualEnvironmentVMOperatingSystemType].(string)
poolID := d.Get(mkResourceVirtualEnvironmentVMPoolID).(string)
serialDevices := vmGetSerialDeviceList(d)
smbios := vmGetSMBIOS(d)
startupOrder := vmGetStartupOrder(d)
onBoot := types2.CustomBool(d.Get(mkResourceVirtualEnvironmentVMOnBoot).(bool))
tabletDevice := types2.CustomBool(d.Get(mkResourceVirtualEnvironmentVMTabletDevice).(bool))
template := types2.CustomBool(d.Get(mkResourceVirtualEnvironmentVMTemplate).(bool))
vgaDevice, err := vmGetVGADeviceObject(d)
if err != nil {
return diag.FromErr(err)
}
vmIDUntyped, hasVMID := d.GetOk(mkResourceVirtualEnvironmentVMVMID)
vmID := vmIDUntyped.(int)
if !hasVMID {
vmIDNew, e := api.Cluster().GetVMID(ctx)
if e != nil {
return diag.FromErr(e)
}
vmID = *vmIDNew
e = d.Set(mkResourceVirtualEnvironmentVMVMID, vmID)
if e != nil {
return diag.FromErr(e)
}
}
var memorySharedObject *vms.CustomSharedMemory
var bootOrderConverted []string
if cdromEnabled {
bootOrderConverted = []string{cdromInterface}
}
bootOrder := d.Get(mkResourceVirtualEnvironmentVMBootOrder).([]interface{})
//nolint:nestif
if len(bootOrder) == 0 {
if sataDeviceObjects != nil {
bootOrderConverted = append(bootOrderConverted, "sata0")
}
if scsiDeviceObjects != nil {
bootOrderConverted = append(bootOrderConverted, "scsi0")
}
if virtioDeviceObjects != nil {
bootOrderConverted = append(bootOrderConverted, "virtio0")
}
if networkDeviceObjects != nil {
bootOrderConverted = append(bootOrderConverted, "net0")
}
} else {
bootOrderConverted = make([]string, len(bootOrder))
for i, device := range bootOrder {
bootOrderConverted[i] = device.(string)
}
}
cpuFlagsConverted := make([]string, len(cpuFlags))
for fi, flag := range cpuFlags {
cpuFlagsConverted[fi] = flag.(string)
}
ideDevice2Media := "cdrom"
ideDevices := vms.CustomStorageDevices{
cdromCloudInitInterface: vms.CustomStorageDevice{
Enabled: cdromCloudInitEnabled,
FileVolume: cdromCloudInitFileID,
Media: &ideDevice2Media,
},
cdromInterface: vms.CustomStorageDevice{
Enabled: cdromEnabled,
FileVolume: cdromFileID,
Media: &ideDevice2Media,
},
}
if memoryShared > 0 {
memorySharedName := fmt.Sprintf("vm-%d-ivshmem", vmID)
memorySharedObject = &vms.CustomSharedMemory{
Name: &memorySharedName,
Size: memoryShared,
}
}
scsiHardware := d.Get(mkResourceVirtualEnvironmentVMSCSIHardware).(string)
createBody := &vms.CreateRequestBody{
ACPI: &acpi,
Agent: &vms.CustomAgent{
Enabled: &agentEnabled,
TrimClonedDisks: &agentTrim,
Type: &agentType,
},
AudioDevices: audioDevices,
BIOS: &bios,
Boot: &vms.CustomBoot{
Order: &bootOrderConverted,
},
CloudInitConfig: initializationConfig,
CPUCores: &cpuCores,
CPUEmulation: &vms.CustomCPUEmulation{
Flags: &cpuFlagsConverted,
Type: cpuType,
},
CPUSockets: &cpuSockets,
CPUUnits: &cpuUnits,
DedicatedMemory: &memoryDedicated,
EFIDisk: efiDisk,
FloatingMemory: &memoryFloating,
IDEDevices: ideDevices,
KeyboardLayout: &keyboardLayout,
NetworkDevices: networkDeviceObjects,
NUMAEnabled: &cpuNUMA,
OSType: &operatingSystemType,
PCIDevices: pciDeviceObjects,
SCSIHardware: &scsiHardware,
SerialDevices: serialDevices,
SharedMemory: memorySharedObject,
StartOnBoot: &onBoot,
SMBIOS: smbios,
StartupOrder: startupOrder,
TabletDeviceEnabled: &tabletDevice,
Template: &template,
VGADevice: vgaDevice,
VMID: &vmID,
}
if sataDeviceObjects != nil {
createBody.SATADevices = sataDeviceObjects
}
if scsiDeviceObjects != nil {
createBody.SCSIDevices = scsiDeviceObjects
}
if virtioDeviceObjects != nil {
createBody.VirtualIODevices = virtioDeviceObjects
}
// Only the root account is allowed to change the CPU architecture, which makes this check necessary.
if api.API().IsRootTicket() ||
cpuArchitecture != dvResourceVirtualEnvironmentVMCPUArchitecture {
createBody.CPUArchitecture = &cpuArchitecture
}
if cpuHotplugged > 0 {
createBody.VirtualCPUCount = &cpuHotplugged
}
if description != "" {
createBody.Description = &description
}
if len(tags) > 0 {
tagsString := vmGetTagsString(d)
createBody.Tags = &tagsString
}
if kvmArguments != "" {
createBody.KVMArguments = &kvmArguments
}
if machine != "" {
createBody.Machine = &machine
}
if name != "" {
createBody.Name = &name
}
if poolID != "" {
createBody.PoolID = &poolID
}
err = api.Node(nodeName).VM(0).CreateVM(ctx, createBody, vmCreateTimeoutSeconds)
if err != nil {
return diag.FromErr(err)
}
d.SetId(strconv.Itoa(vmID))
return vmCreateCustomDisks(ctx, d, m)
}
func vmCreateCustomDisks(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
vmID, err := strconv.Atoi(d.Id())
if err != nil {
return diag.FromErr(err)
}
var commands []string
// Determine the ID of the next disk.
disk := d.Get(mkResourceVirtualEnvironmentVMDisk).([]interface{})
diskCount := 0
for _, d := range disk {
block := d.(map[string]interface{})
fileID, _ := block[mkResourceVirtualEnvironmentVMDiskFileID].(string)
if fileID == "" {
diskCount++
}
}
// Retrieve some information about the disk schema.
resourceSchema := VM().Schema
diskSchemaElem := resourceSchema[mkResourceVirtualEnvironmentVMDisk].Elem
diskSchemaResource := diskSchemaElem.(*schema.Resource)
diskSpeedResource := diskSchemaResource.Schema[mkResourceVirtualEnvironmentVMDiskSpeed]
// Generate the commands required to import the specified disks.
importedDiskCount := 0
for _, d := range disk {
block := d.(map[string]interface{})
fileID, _ := block[mkResourceVirtualEnvironmentVMDiskFileID].(string)
if fileID == "" {
continue
}
datastoreID, _ := block[mkResourceVirtualEnvironmentVMDiskDatastoreID].(string)
fileFormat, _ := block[mkResourceVirtualEnvironmentVMDiskFileFormat].(string)
size, _ := block[mkResourceVirtualEnvironmentVMDiskSize].(int)
speed := block[mkResourceVirtualEnvironmentVMDiskSpeed].([]interface{})
diskInterface, _ := block[mkResourceVirtualEnvironmentVMDiskInterface].(string)
ioThread := types2.CustomBool(block[mkResourceVirtualEnvironmentVMDiskIOThread].(bool))
ssd := types2.CustomBool(block[mkResourceVirtualEnvironmentVMDiskSSD].(bool))
discard, _ := block[mkResourceVirtualEnvironmentVMDiskDiscard].(string)
cache, _ := block[mkResourceVirtualEnvironmentVMDiskCache].(string)
if fileFormat == "" {
fileFormat = dvResourceVirtualEnvironmentVMDiskFileFormat
}
if len(speed) == 0 {
diskSpeedDefault, err := diskSpeedResource.DefaultValue()
if err != nil {
return diag.FromErr(err)
}
speed = diskSpeedDefault.([]interface{})
}
speedBlock := speed[0].(map[string]interface{})
speedLimitRead := speedBlock[mkResourceVirtualEnvironmentVMDiskSpeedRead].(int)
speedLimitReadBurstable := speedBlock[mkResourceVirtualEnvironmentVMDiskSpeedReadBurstable].(int)
speedLimitWrite := speedBlock[mkResourceVirtualEnvironmentVMDiskSpeedWrite].(int)
speedLimitWriteBurstable := speedBlock[mkResourceVirtualEnvironmentVMDiskSpeedWriteBurstable].(int)
diskOptions := ""
if ioThread {
diskOptions += ",iothread=1"
}
if ssd {
diskOptions += ",ssd=1"
}
if discard != "" {
diskOptions += fmt.Sprintf(",discard=%s", discard)
}
if cache != "" {
diskOptions += fmt.Sprintf(",cache=%s", cache)
}
if speedLimitRead > 0 {
diskOptions += fmt.Sprintf(",mbps_rd=%d", speedLimitRead)
}
if speedLimitReadBurstable > 0 {
diskOptions += fmt.Sprintf(",mbps_rd_max=%d", speedLimitReadBurstable)
}
if speedLimitWrite > 0 {
diskOptions += fmt.Sprintf(",mbps_wr=%d", speedLimitWrite)
}
if speedLimitWriteBurstable > 0 {
diskOptions += fmt.Sprintf(",mbps_wr_max=%d", speedLimitWriteBurstable)
}
filePathTmp := fmt.Sprintf(
"/tmp/vm-%d-disk-%d.%s",
vmID,
diskCount+importedDiskCount,
fileFormat,
)
//nolint:lll
commands = append(
commands,
`set -e`,
fmt.Sprintf(`file_id="%s"`, fileID),
fmt.Sprintf(`file_format="%s"`, fileFormat),
fmt.Sprintf(`datastore_id_target="%s"`, datastoreID),
fmt.Sprintf(`disk_options="%s"`, diskOptions),
fmt.Sprintf(`disk_size="%d"`, size),
fmt.Sprintf(`disk_interface="%s"`, diskInterface),
fmt.Sprintf(`file_path_tmp="%s"`, filePathTmp),
fmt.Sprintf(`vm_id="%d"`, vmID),
`source_image=$(pvesm path "$file_id")`,
`cp "$source_image" "$file_path_tmp"`,
`qemu-img resize -f "$file_format" "$file_path_tmp" "${disk_size}G"`,
`imported_disk="$(qm importdisk "$vm_id" "$file_path_tmp" "$datastore_id_target" -format $file_format | grep "unused0" | cut -d ":" -f 3 | cut -d "'" -f 1)"`,
`disk_id="${datastore_id_target}:$imported_disk${disk_options}"`,
`qm set "$vm_id" "-${disk_interface}" "$disk_id"`,
`rm -f "$file_path_tmp"`,
)
importedDiskCount++
}
// 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 {
config := m.(proxmoxtf.ProviderConfiguration)
api, err := config.GetClient()
if err != nil {
return diag.FromErr(err)
}
nodeName := d.Get(mkResourceVirtualEnvironmentVMNodeName).(string)
err = api.SSH().ExecuteNodeCommands(ctx, nodeName, commands)
if err != nil {
return diag.FromErr(err)
}
}
return vmCreateStart(ctx, d, m)
}
func vmCreateStart(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
started := d.Get(mkResourceVirtualEnvironmentVMStarted).(bool)
template := d.Get(mkResourceVirtualEnvironmentVMTemplate).(bool)
reboot := d.Get(mkResourceVirtualEnvironmentVMRebootAfterCreation).(bool)
if !started || template {
return vmRead(ctx, d, m)
}
config := m.(proxmoxtf.ProviderConfiguration)
api, err := config.GetClient()
if err != nil {
return diag.FromErr(err)
}
nodeName := d.Get(mkResourceVirtualEnvironmentVMNodeName).(string)
vmID, err := strconv.Atoi(d.Id())
if err != nil {
return diag.FromErr(err)
}
vmAPI := api.Node(nodeName).VM(vmID)
// Start the virtual machine and wait for it to reach a running state before continuing.
if diags := vmStart(ctx, vmAPI, d); diags != nil {
return diags
}
if reboot {
rebootTimeout := d.Get(mkResourceVirtualEnvironmentVMTimeoutReboot).(int)
err := vmAPI.RebootVM(
ctx,
&vms.RebootRequestBody{
Timeout: &rebootTimeout,
},
rebootTimeout+30,
)
if err != nil {
return diag.FromErr(err)
}
}
return vmRead(ctx, d, m)
}
func vmGetAudioDeviceList(d *schema.ResourceData) vms.CustomAudioDevices {
devices := d.Get(mkResourceVirtualEnvironmentVMAudioDevice).([]interface{})
list := make(vms.CustomAudioDevices, len(devices))
for i, v := range devices {
block := v.(map[string]interface{})
device, _ := block[mkResourceVirtualEnvironmentVMAudioDeviceDevice].(string)
driver, _ := block[mkResourceVirtualEnvironmentVMAudioDeviceDriver].(string)
enabled, _ := block[mkResourceVirtualEnvironmentVMAudioDeviceEnabled].(bool)
list[i].Device = device
list[i].Driver = &driver
list[i].Enabled = enabled
}
return list
}
func vmGetAudioDeviceValidator() schema.SchemaValidateDiagFunc {
return validation.ToDiagFunc(validation.StringInSlice([]string{
"AC97",
"ich9-intel-hda",
"intel-hda",
}, false))
}
func vmGetAudioDriverValidator() schema.SchemaValidateDiagFunc {
return validation.ToDiagFunc(validation.StringInSlice([]string{
"spice",
}, false))
}
func vmGetCloudInitConfig(d *schema.ResourceData) *vms.CustomCloudInitConfig {
var initializationConfig *vms.CustomCloudInitConfig
initialization := d.Get(mkResourceVirtualEnvironmentVMInitialization).([]interface{})
if len(initialization) > 0 {
initializationBlock := initialization[0].(map[string]interface{})
initializationConfig = &vms.CustomCloudInitConfig{}
initializationDNS := initializationBlock[mkResourceVirtualEnvironmentVMInitializationDNS].([]interface{})
if len(initializationDNS) > 0 {
initializationDNSBlock := initializationDNS[0].(map[string]interface{})
domain := initializationDNSBlock[mkResourceVirtualEnvironmentVMInitializationDNSDomain].(string)
if domain != "" {
initializationConfig.SearchDomain = &domain
}
server := initializationDNSBlock[mkResourceVirtualEnvironmentVMInitializationDNSServer].(string)
if server != "" {
initializationConfig.Nameserver = &server
}
}
initializationIPConfig := initializationBlock[mkResourceVirtualEnvironmentVMInitializationIPConfig].([]interface{})
initializationConfig.IPConfig = make(
[]vms.CustomCloudInitIPConfig,
len(initializationIPConfig),
)
for i, c := range initializationIPConfig {
configBlock := c.(map[string]interface{})
ipv4 := configBlock[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4].([]interface{})
if len(ipv4) > 0 {
ipv4Block := ipv4[0].(map[string]interface{})
ipv4Address := ipv4Block[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Address].(string)
if ipv4Address != "" {
initializationConfig.IPConfig[i].IPv4 = &ipv4Address
}
ipv4Gateway := ipv4Block[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Gateway].(string)
if ipv4Gateway != "" {
initializationConfig.IPConfig[i].GatewayIPv4 = &ipv4Gateway
}
}
ipv6 := configBlock[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6].([]interface{})
if len(ipv6) > 0 {
ipv6Block := ipv6[0].(map[string]interface{})
ipv6Address := ipv6Block[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6Address].(string)
if ipv6Address != "" {
initializationConfig.IPConfig[i].IPv6 = &ipv6Address
}
ipv6Gateway := ipv6Block[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6Gateway].(string)
if ipv6Gateway != "" {
initializationConfig.IPConfig[i].GatewayIPv6 = &ipv6Gateway
}
}
}
initializationUserAccount := initializationBlock[mkResourceVirtualEnvironmentVMInitializationUserAccount].([]interface{})
if len(initializationUserAccount) > 0 {
initializationUserAccountBlock := initializationUserAccount[0].(map[string]interface{})
keys := initializationUserAccountBlock[mkResourceVirtualEnvironmentVMInitializationUserAccountKeys].([]interface{})
if len(keys) > 0 {
sshKeys := make(vms.CustomCloudInitSSHKeys, len(keys))
for i, k := range keys {
sshKeys[i] = k.(string)
}
initializationConfig.SSHKeys = &sshKeys
}
password := initializationUserAccountBlock[mkResourceVirtualEnvironmentVMInitializationUserAccountPassword].(string)
if password != "" {
initializationConfig.Password = &password
}
username := initializationUserAccountBlock[mkResourceVirtualEnvironmentVMInitializationUserAccountUsername].(string)
initializationConfig.Username = &username
}
initializationUserDataFileID := initializationBlock[mkResourceVirtualEnvironmentVMInitializationUserDataFileID].(string)
if initializationUserDataFileID != "" {
initializationConfig.Files = &vms.CustomCloudInitFiles{
UserVolume: &initializationUserDataFileID,
}
}
initializationVendorDataFileID := initializationBlock[mkResourceVirtualEnvironmentVMInitializationVendorDataFileID].(string)
if initializationVendorDataFileID != "" {
if initializationConfig.Files == nil {
initializationConfig.Files = &vms.CustomCloudInitFiles{}
}
initializationConfig.Files.VendorVolume = &initializationVendorDataFileID
}
initializationNetworkDataFileID := initializationBlock[mkResourceVirtualEnvironmentVMInitializationNetworkDataFileID].(string)
if initializationNetworkDataFileID != "" {
if initializationConfig.Files == nil {
initializationConfig.Files = &vms.CustomCloudInitFiles{}
}
initializationConfig.Files.NetworkVolume = &initializationNetworkDataFileID
}
//nolint:lll
initializationMetaDataFileID := initializationBlock[mkResourceVirtualEnvironmentVMInitializationMetaDataFileID].(string)
if initializationMetaDataFileID != "" {
if initializationConfig.Files == nil {
initializationConfig.Files = &vms.CustomCloudInitFiles{}
}
initializationConfig.Files.MetaVolume = &initializationMetaDataFileID
}
initializationType := initializationBlock[mkResourceVirtualEnvironmentVMInitializationType].(string)
if initializationType != "" {
initializationConfig.Type = &initializationType
}
}
return initializationConfig
}
func vmGetCPUArchitectureValidator() schema.SchemaValidateDiagFunc {
return validation.ToDiagFunc(validation.StringInSlice([]string{
"aarch64",
"x86_64",
}, false))
}
func vmGetDiskDeviceObjects(
d *schema.ResourceData,
disks []interface{},
) (map[string]map[string]vms.CustomStorageDevice, error) {
var diskDevice []interface{}
if disks != nil {
diskDevice = disks
} else {
diskDevice = d.Get(mkResourceVirtualEnvironmentVMDisk).([]interface{})
}
diskDeviceObjects := map[string]map[string]vms.CustomStorageDevice{}
resource := VM()
for _, diskEntry := range diskDevice {
diskDevice := vms.CustomStorageDevice{
Enabled: true,
}
block := diskEntry.(map[string]interface{})
datastoreID, _ := block[mkResourceVirtualEnvironmentVMDiskDatastoreID].(string)
fileFormat, _ := block[mkResourceVirtualEnvironmentVMDiskFileFormat].(string)
fileID, _ := block[mkResourceVirtualEnvironmentVMDiskFileID].(string)
size, _ := block[mkResourceVirtualEnvironmentVMDiskSize].(int)
diskInterface, _ := block[mkResourceVirtualEnvironmentVMDiskInterface].(string)
ioThread := types2.CustomBool(block[mkResourceVirtualEnvironmentVMDiskIOThread].(bool))
ssd := types2.CustomBool(block[mkResourceVirtualEnvironmentVMDiskSSD].(bool))
discard := block[mkResourceVirtualEnvironmentVMDiskDiscard].(string)
cache := block[mkResourceVirtualEnvironmentVMDiskCache].(string)
speedBlock, err := structure.GetSchemaBlock(
resource,
d,
[]string{mkResourceVirtualEnvironmentVMDisk, mkResourceVirtualEnvironmentVMDiskSpeed},
0,
false,
)
if err != nil {
return diskDeviceObjects, err
}
if fileFormat == "" {
fileFormat = dvResourceVirtualEnvironmentVMDiskFileFormat
}
if fileID != "" {
diskDevice.Enabled = false
} else {
diskDevice.FileVolume = fmt.Sprintf("%s:%d", datastoreID, size)
}
diskDevice.ID = &datastoreID
diskDevice.Interface = &diskInterface
diskDevice.Format = &fileFormat
diskDevice.FileID = &fileID
diskSize := types2.DiskSizeFromGigabytes(size)
diskDevice.Size = &diskSize
diskDevice.SizeInt = &size
diskDevice.IOThread = &ioThread
diskDevice.Discard = &discard
diskDevice.Cache = &cache
if !strings.HasPrefix(diskInterface, "virtio") {
diskDevice.SSD = &ssd
}
if len(speedBlock) > 0 {
speedLimitRead := speedBlock[mkResourceVirtualEnvironmentVMDiskSpeedRead].(int)
speedLimitReadBurstable := speedBlock[mkResourceVirtualEnvironmentVMDiskSpeedReadBurstable].(int)
speedLimitWrite := speedBlock[mkResourceVirtualEnvironmentVMDiskSpeedWrite].(int)
speedLimitWriteBurstable := speedBlock[mkResourceVirtualEnvironmentVMDiskSpeedWriteBurstable].(int)
if speedLimitRead > 0 {
diskDevice.MaxReadSpeedMbps = &speedLimitRead
}
if speedLimitReadBurstable > 0 {
diskDevice.BurstableReadSpeedMbps = &speedLimitReadBurstable
}
if speedLimitWrite > 0 {
diskDevice.MaxWriteSpeedMbps = &speedLimitWrite
}
if speedLimitWriteBurstable > 0 {
diskDevice.BurstableWriteSpeedMbps = &speedLimitWriteBurstable
}
}
baseDiskInterface := diskDigitPrefix(diskInterface)
if baseDiskInterface != "virtio" && baseDiskInterface != "scsi" &&
baseDiskInterface != "sata" {
errorMsg := fmt.Sprintf(
"Defined disk interface not supported. Interface was %s, but only virtio, sata and scsi are supported",
diskInterface,
)
return diskDeviceObjects, errors.New(errorMsg)
}
if _, present := diskDeviceObjects[baseDiskInterface]; !present {
diskDeviceObjects[baseDiskInterface] = map[string]vms.CustomStorageDevice{}
}
diskDeviceObjects[baseDiskInterface][diskInterface] = diskDevice
}
return diskDeviceObjects, nil
}
func vmGetEfiDisk(d *schema.ResourceData, disk []interface{}) *vms.CustomEFIDisk {
var efiDisk []interface{}
if disk != nil {
efiDisk = disk
} else {
efiDisk = d.Get(mkResourceVirtualEnvironmentVMEFIDisk).([]interface{})
}
var efiDiskConfig *vms.CustomEFIDisk
if len(efiDisk) > 0 {
efiDiskConfig = &vms.CustomEFIDisk{}
block := efiDisk[0].(map[string]interface{})
datastoreID, _ := block[mkResourceVirtualEnvironmentVMEFIDiskDatastoreID].(string)
fileFormat, _ := block[mkResourceVirtualEnvironmentVMEFIDiskFileFormat].(string)
efiType, _ := block[mkResourceVirtualEnvironmentVMEFIDiskType].(string)
preEnrolledKeys := types2.CustomBool(block[mkResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys].(bool))
// special case for efi disk, the size is ignored, see docs for more info
efiDiskConfig.FileVolume = fmt.Sprintf("%s:1", datastoreID)
efiDiskConfig.Format = &fileFormat
efiDiskConfig.Type = &efiType
efiDiskConfig.PreEnrolledKeys = &preEnrolledKeys
}
return efiDiskConfig
}
func vmGetEfiDiskAsStorageDevice(d *schema.ResourceData, disk []interface{}) (*vms.CustomStorageDevice, error) {
efiDisk := vmGetEfiDisk(d, disk)
var storageDevice *vms.CustomStorageDevice
if efiDisk != nil {
id := "0"
baseDiskInterface := "efidisk"
diskInterface := fmt.Sprint(baseDiskInterface, id)
storageDevice = &vms.CustomStorageDevice{
Enabled: true,
FileVolume: efiDisk.FileVolume,
Format: efiDisk.Format,
Interface: &diskInterface,
ID: &id,
}
if efiDisk.Type != nil {
ds, err := types2.ParseDiskSize(*efiDisk.Type)
if err != nil {
return nil, fmt.Errorf("invalid efi disk type: %s", err.Error())
}
sizeInt := ds.InMegabytes()
storageDevice.Size = &ds
storageDevice.SizeInt = &sizeInt
}
}
return storageDevice, nil
}
func vmGetHostPCIDeviceObjects(d *schema.ResourceData) vms.CustomPCIDevices {
pciDevice := d.Get(mkResourceVirtualEnvironmentVMHostPCI).([]interface{})
pciDeviceObjects := make(vms.CustomPCIDevices, len(pciDevice))
for i, pciDeviceEntry := range pciDevice {
block := pciDeviceEntry.(map[string]interface{})
ids, _ := block[mkResourceVirtualEnvironmentVMHostPCIDeviceID].(string)
mdev, _ := block[mkResourceVirtualEnvironmentVMHostPCIDeviceMDev].(string)
pcie := types2.CustomBool(block[mkResourceVirtualEnvironmentVMHostPCIDevicePCIE].(bool))
rombar := types2.CustomBool(
block[mkResourceVirtualEnvironmentVMHostPCIDeviceROMBAR].(bool),
)
romfile, _ := block[mkResourceVirtualEnvironmentVMHostPCIDeviceROMFile].(string)
xvga := types2.CustomBool(block[mkResourceVirtualEnvironmentVMHostPCIDeviceXVGA].(bool))
mapping, _ := block[mkResourceVirtualEnvironmentVMHostPCIDeviceMapping].(string)
device := vms.CustomPCIDevice{
PCIExpress: &pcie,
ROMBAR: &rombar,
XVGA: &xvga,
}
if ids != "" {
dIds := strings.Split(ids, ";")
device.DeviceIDs = &dIds
}
if mdev != "" {
device.MDev = &mdev
}
if romfile != "" {
device.ROMFile = &romfile
}
if mapping != "" {
device.Mapping = &mapping
}
pciDeviceObjects[i] = device
}
return pciDeviceObjects
}
func vmGetNetworkDeviceObjects(d *schema.ResourceData) vms.CustomNetworkDevices {
networkDevice := d.Get(mkResourceVirtualEnvironmentVMNetworkDevice).([]interface{})
networkDeviceObjects := make(vms.CustomNetworkDevices, len(networkDevice))
for i, networkDeviceEntry := range networkDevice {
block := networkDeviceEntry.(map[string]interface{})
bridge := block[mkResourceVirtualEnvironmentVMNetworkDeviceBridge].(string)
enabled := block[mkResourceVirtualEnvironmentVMNetworkDeviceEnabled].(bool)
firewall := types2.CustomBool(block[mkResourceVirtualEnvironmentVMNetworkDeviceFirewall].(bool))
macAddress := block[mkResourceVirtualEnvironmentVMNetworkDeviceMACAddress].(string)
model := block[mkResourceVirtualEnvironmentVMNetworkDeviceModel].(string)
rateLimit := block[mkResourceVirtualEnvironmentVMNetworkDeviceRateLimit].(float64)
vlanID := block[mkResourceVirtualEnvironmentVMNetworkDeviceVLANID].(int)
mtu := block[mkResourceVirtualEnvironmentVMNetworkDeviceMTU].(int)
device := vms.CustomNetworkDevice{
Enabled: enabled,
Firewall: &firewall,
Model: model,
}
if bridge != "" {
device.Bridge = &bridge
}
if macAddress != "" {
device.MACAddress = &macAddress
}
if rateLimit != 0 {
device.RateLimit = &rateLimit
}
if vlanID != 0 {
device.Tag = &vlanID
}
if mtu != 0 {
device.MTU = &mtu
}
networkDeviceObjects[i] = device
}
return networkDeviceObjects
}
func vmGetOperatingSystemTypeValidator() schema.SchemaValidateDiagFunc {
return validation.ToDiagFunc(validation.StringInSlice([]string{
"l24",
"l26",
"other",
"solaris",
"w2k",
"w2k3",
"w2k8",
"win7",
"win8",
"win10",
"wvista",
"wxp",
}, false))
}
func vmGetSerialDeviceList(d *schema.ResourceData) vms.CustomSerialDevices {
device := d.Get(mkResourceVirtualEnvironmentVMSerialDevice).([]interface{})
list := make(vms.CustomSerialDevices, len(device))
for i, v := range device {
block := v.(map[string]interface{})
device, _ := block[mkResourceVirtualEnvironmentVMSerialDeviceDevice].(string)
list[i] = device
}
return list
}
func vmGetSMBIOS(d *schema.ResourceData) *vms.CustomSMBIOS {
smbiosSections := d.Get(mkResourceVirtualEnvironmentVMSMBIOS).([]interface{})
//nolint:nestif
if len(smbiosSections) > 0 {
smbiosBlock := smbiosSections[0].(map[string]interface{})
b64 := types2.CustomBool(true)
family, _ := smbiosBlock[mkResourceVirtualEnvironmentVMSMBIOSFamily].(string)
manufacturer, _ := smbiosBlock[mkResourceVirtualEnvironmentVMSMBIOSManufacturer].(string)
product, _ := smbiosBlock[mkResourceVirtualEnvironmentVMSMBIOSProduct].(string)
serial, _ := smbiosBlock[mkResourceVirtualEnvironmentVMSMBIOSSerial].(string)
sku, _ := smbiosBlock[mkResourceVirtualEnvironmentVMSMBIOSSKU].(string)
version, _ := smbiosBlock[mkResourceVirtualEnvironmentVMSMBIOSVersion].(string)
uid, _ := smbiosBlock[mkResourceVirtualEnvironmentVMSMBIOSUUID].(string)
smbios := vms.CustomSMBIOS{
Base64: &b64,
}
if family != "" {
v := base64.StdEncoding.EncodeToString([]byte(family))
smbios.Family = &v
}
if manufacturer != "" {
v := base64.StdEncoding.EncodeToString([]byte(manufacturer))
smbios.Manufacturer = &v
}
if product != "" {
v := base64.StdEncoding.EncodeToString([]byte(product))
smbios.Product = &v
}
if serial != "" {
v := base64.StdEncoding.EncodeToString([]byte(serial))
smbios.Serial = &v
}
if sku != "" {
v := base64.StdEncoding.EncodeToString([]byte(sku))
smbios.SKU = &v
}
if version != "" {
v := base64.StdEncoding.EncodeToString([]byte(version))
smbios.Version = &v
}
if uid != "" {
smbios.UUID = &uid
}
if smbios.UUID == nil || *smbios.UUID == "" {
smbios.UUID = types2.StrPtr(uuid.New().String())
}
return &smbios
}
return nil
}
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
for i := 0; i < len(tags); i++ {
tag := strings.TrimSpace(tags[i].(string))
if len(tag) > 0 {
sanitizedTags = append(sanitizedTags, tag)
}
}
sort.Strings(sanitizedTags)
return strings.Join(sanitizedTags, ";")
}
func vmGetSerialDeviceValidator() schema.SchemaValidateDiagFunc {
return validation.ToDiagFunc(func(i interface{}, k string) (s []string, es []error) {
v, ok := i.(string)
if !ok {
es = append(es, fmt.Errorf("expected type of %s to be string", k))
return
}
if !strings.HasPrefix(v, "/dev/") && v != "socket" {
es = append(es, fmt.Errorf("expected %s to be '/dev/*' or 'socket'", k))
return
}
return
})
}
func vmGetVGADeviceObject(d *schema.ResourceData) (*vms.CustomVGADevice, error) {
resource := VM()
vgaBlock, err := structure.GetSchemaBlock(
resource,
d,
[]string{mkResourceVirtualEnvironmentVMVGA},
0,
true,
)
if err != nil {
return nil, err
}
vgaEnabled := types2.CustomBool(vgaBlock[mkResourceVirtualEnvironmentVMAgentEnabled].(bool))
vgaMemory := vgaBlock[mkResourceVirtualEnvironmentVMVGAMemory].(int)
vgaType := vgaBlock[mkResourceVirtualEnvironmentVMVGAType].(string)
vgaDevice := &vms.CustomVGADevice{}
if vgaEnabled {
if vgaMemory > 0 {
vgaDevice.Memory = &vgaMemory
}
vgaDevice.Type = &vgaType
} else {
vgaType = "none"
vgaDevice = &vms.CustomVGADevice{
Type: &vgaType,
}
}
return vgaDevice, nil
}
func vmRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
config := m.(proxmoxtf.ProviderConfiguration)
api, err := config.GetClient()
if err != nil {
return diag.FromErr(err)
}
vmID, err := strconv.Atoi(d.Id())
if err != nil {
return diag.FromErr(err)
}
vmNodeName, err := api.Cluster().GetVMNodeName(ctx, vmID)
if err != nil {
if errors.Is(err, cluster.ErrVMDoesNotExist) {
d.SetId("")
return nil
}
return diag.FromErr(err)
}
if vmNodeName != d.Get(mkResourceVirtualEnvironmentVMNodeName) {
err = d.Set(mkResourceVirtualEnvironmentVMNodeName, vmNodeName)
if err != nil {
return diag.FromErr(err)
}
}
nodeName := d.Get(mkResourceVirtualEnvironmentVMNodeName).(string)
vmAPI := api.Node(nodeName).VM(vmID)
// Retrieve the entire configuration in order to compare it to the state.
vmConfig, err := vmAPI.GetVM(ctx)
if err != nil {
if strings.Contains(err.Error(), "HTTP 404") ||
(strings.Contains(err.Error(), "HTTP 500") && strings.Contains(err.Error(), "does not exist")) {
d.SetId("")
return nil
}
return diag.FromErr(err)
}
vmStatus, err := vmAPI.GetVMStatus(ctx)
if err != nil {
return diag.FromErr(err)
}
return vmReadCustom(ctx, d, m, vmID, vmConfig, vmStatus)
}
// orderedListFromMap generates a list from a map's values. The values are sorted based on the map's keys.
func orderedListFromMap(inputMap map[string]interface{}) []interface{} {
itemCount := len(inputMap)
keyList := make([]string, itemCount)
i := 0
for key := range inputMap {
keyList[i] = key
i++
}
sort.Strings(keyList)
orderedList := make([]interface{}, itemCount)
for i, k := range keyList {
orderedList[i] = inputMap[k]
}
return orderedList
}
func vmReadCustom(
ctx context.Context,
d *schema.ResourceData,
m interface{},
vmID int,
vmConfig *vms.GetResponseData,
vmStatus *vms.GetStatusResponseData,
) diag.Diagnostics {
config := m.(proxmoxtf.ProviderConfiguration)
api, err := config.GetClient()
if err != nil {
return diag.FromErr(err)
}
diags := vmReadPrimitiveValues(d, vmConfig, vmStatus)
if diags.HasError() {
return diags
}
// Fix terraform.tfstate, by replacing '-1' (the old default value) with actual vm_id value
if storedVMID := d.Get(mkResourceVirtualEnvironmentVMVMID).(int); storedVMID == -1 {
diags = append(diags, diag.Diagnostic{
Severity: diag.Warning,
Summary: fmt.Sprintf("VM %s has stored legacy vm_id %d, setting vm_id to its correct value %d.",
d.Id(), storedVMID, vmID),
})
err = d.Set(mkResourceVirtualEnvironmentVMVMID, vmID)
diags = append(diags, diag.FromErr(err)...)
}
nodeName := d.Get(mkResourceVirtualEnvironmentVMNodeName).(string)
clone := d.Get(mkResourceVirtualEnvironmentVMClone).([]interface{})
// Compare the agent configuration to the one stored in the state.
currentAgent := d.Get(mkResourceVirtualEnvironmentVMAgent).([]interface{})
if len(clone) == 0 || len(currentAgent) > 0 {
if vmConfig.Agent != nil {
agent := map[string]interface{}{}
if vmConfig.Agent.Enabled != nil {
agent[mkResourceVirtualEnvironmentVMAgentEnabled] = bool(*vmConfig.Agent.Enabled)
} else {
agent[mkResourceVirtualEnvironmentVMAgentEnabled] = false
}
if vmConfig.Agent.TrimClonedDisks != nil {
agent[mkResourceVirtualEnvironmentVMAgentTrim] = bool(
*vmConfig.Agent.TrimClonedDisks,
)
} else {
agent[mkResourceVirtualEnvironmentVMAgentTrim] = false
}
if len(currentAgent) > 0 {
currentAgentBlock := currentAgent[0].(map[string]interface{})
currentAgentTimeout := currentAgentBlock[mkResourceVirtualEnvironmentVMAgentTimeout].(string)
if currentAgentTimeout != "" {
agent[mkResourceVirtualEnvironmentVMAgentTimeout] = currentAgentTimeout
} else {
agent[mkResourceVirtualEnvironmentVMAgentTimeout] = dvResourceVirtualEnvironmentVMAgentTimeout
}
} else {
agent[mkResourceVirtualEnvironmentVMAgentTimeout] = dvResourceVirtualEnvironmentVMAgentTimeout
}
if vmConfig.Agent.Type != nil {
agent[mkResourceVirtualEnvironmentVMAgentType] = *vmConfig.Agent.Type
} else {
agent[mkResourceVirtualEnvironmentVMAgentType] = ""
}
if len(clone) > 0 {
if len(currentAgent) > 0 {
err := d.Set(mkResourceVirtualEnvironmentVMAgent, []interface{}{agent})
diags = append(diags, diag.FromErr(err)...)
}
} else if len(currentAgent) > 0 ||
agent[mkResourceVirtualEnvironmentVMAgentEnabled] != dvResourceVirtualEnvironmentVMAgentEnabled ||
agent[mkResourceVirtualEnvironmentVMAgentTimeout] != dvResourceVirtualEnvironmentVMAgentTimeout ||
agent[mkResourceVirtualEnvironmentVMAgentTrim] != dvResourceVirtualEnvironmentVMAgentTrim ||
agent[mkResourceVirtualEnvironmentVMAgentType] != dvResourceVirtualEnvironmentVMAgentType {
err := d.Set(mkResourceVirtualEnvironmentVMAgent, []interface{}{agent})
diags = append(diags, diag.FromErr(err)...)
}
} else if len(clone) > 0 {
if len(currentAgent) > 0 {
err := d.Set(mkResourceVirtualEnvironmentVMAgent, []interface{}{})
diags = append(diags, diag.FromErr(err)...)
}
} else {
err := d.Set(mkResourceVirtualEnvironmentVMAgent, []interface{}{})
diags = append(diags, diag.FromErr(err)...)
}
}
// Compare the audio devices to those stored in the state.
currentAudioDevice := d.Get(mkResourceVirtualEnvironmentVMAudioDevice).([]interface{})
audioDevices := make([]interface{}, 1)
audioDevicesArray := []*vms.CustomAudioDevice{
vmConfig.AudioDevice,
}
audioDevicesCount := 0
for adi, ad := range audioDevicesArray {
m := map[string]interface{}{}
if ad != nil {
m[mkResourceVirtualEnvironmentVMAudioDeviceDevice] = ad.Device
if ad.Driver != nil {
m[mkResourceVirtualEnvironmentVMAudioDeviceDriver] = *ad.Driver
} else {
m[mkResourceVirtualEnvironmentVMAudioDeviceDriver] = ""
}
m[mkResourceVirtualEnvironmentVMAudioDeviceEnabled] = true
audioDevicesCount = adi + 1
} else {
m[mkResourceVirtualEnvironmentVMAudioDeviceDevice] = ""
m[mkResourceVirtualEnvironmentVMAudioDeviceDriver] = ""
m[mkResourceVirtualEnvironmentVMAudioDeviceEnabled] = false
}
audioDevices[adi] = m
}
if len(clone) == 0 || len(currentAudioDevice) > 0 {
err := d.Set(mkResourceVirtualEnvironmentVMAudioDevice, audioDevices[:audioDevicesCount])
diags = append(diags, diag.FromErr(err)...)
}
// Compare the IDE devices to the CDROM configurations stored in the state.
currentInterface := dvResourceVirtualEnvironmentVMCDROMInterface
currentCDROM := d.Get(mkResourceVirtualEnvironmentVMCDROM).([]interface{})
if len(currentCDROM) > 0 {
currentBlock := currentCDROM[0].(map[string]interface{})
currentInterface = currentBlock[mkResourceVirtualEnvironmentVMCDROMInterface].(string)
}
cdromIDEDevice := getIdeDevice(vmConfig, currentInterface)
//nolint:nestif
if cdromIDEDevice != nil {
cdrom := make([]interface{}, 1)
cdromBlock := map[string]interface{}{}
if len(clone) == 0 || len(currentCDROM) > 0 {
cdromBlock[mkResourceVirtualEnvironmentVMCDROMEnabled] = cdromIDEDevice.Enabled
cdromBlock[mkResourceVirtualEnvironmentVMCDROMFileID] = cdromIDEDevice.FileVolume
cdromBlock[mkResourceVirtualEnvironmentVMCDROMInterface] = currentInterface
if len(currentCDROM) > 0 {
currentBlock := currentCDROM[0].(map[string]interface{})
if currentBlock[mkResourceVirtualEnvironmentVMCDROMFileID] == "" {
cdromBlock[mkResourceVirtualEnvironmentVMCDROMFileID] = ""
}
if currentBlock[mkResourceVirtualEnvironmentVMCDROMEnabled] == false {
cdromBlock[mkResourceVirtualEnvironmentVMCDROMEnabled] = false
}
}
cdrom[0] = cdromBlock
err := d.Set(mkResourceVirtualEnvironmentVMCDROM, cdrom)
diags = append(diags, diag.FromErr(err)...)
}
} else {
err := d.Set(mkResourceVirtualEnvironmentVMCDROM, []interface{}{})
diags = append(diags, diag.FromErr(err)...)
}
// Compare the CPU configuration to the one stored in the state.
cpu := map[string]interface{}{}
if vmConfig.CPUArchitecture != nil {
cpu[mkResourceVirtualEnvironmentVMCPUArchitecture] = *vmConfig.CPUArchitecture
} else {
// Default value of "arch" is "" according to the API documentation.
// However, assume the provider's default value as a workaround when the root account is not being used.
if !api.API().IsRootTicket() {
cpu[mkResourceVirtualEnvironmentVMCPUArchitecture] = dvResourceVirtualEnvironmentVMCPUArchitecture
} else {
cpu[mkResourceVirtualEnvironmentVMCPUArchitecture] = ""
}
}
if vmConfig.CPUCores != nil {
cpu[mkResourceVirtualEnvironmentVMCPUCores] = *vmConfig.CPUCores
} else {
// Default value of "cores" is "1" according to the API documentation.
cpu[mkResourceVirtualEnvironmentVMCPUCores] = 1
}
if vmConfig.VirtualCPUCount != nil {
cpu[mkResourceVirtualEnvironmentVMCPUHotplugged] = *vmConfig.VirtualCPUCount
} else {
// Default value of "vcpus" is "1" according to the API documentation.
cpu[mkResourceVirtualEnvironmentVMCPUHotplugged] = 0
}
if vmConfig.NUMAEnabled != nil {
cpu[mkResourceVirtualEnvironmentVMCPUNUMA] = *vmConfig.NUMAEnabled
} else {
// Default value of "numa" is "false" according to the API documentation.
cpu[mkResourceVirtualEnvironmentVMCPUNUMA] = false
}
if vmConfig.CPUSockets != nil {
cpu[mkResourceVirtualEnvironmentVMCPUSockets] = *vmConfig.CPUSockets
} else {
// Default value of "sockets" is "1" according to the API documentation.
cpu[mkResourceVirtualEnvironmentVMCPUSockets] = 1
}
if vmConfig.CPUEmulation != nil {
if vmConfig.CPUEmulation.Flags != nil {
convertedFlags := make([]interface{}, len(*vmConfig.CPUEmulation.Flags))
for fi, fv := range *vmConfig.CPUEmulation.Flags {
convertedFlags[fi] = fv
}
cpu[mkResourceVirtualEnvironmentVMCPUFlags] = convertedFlags
} else {
cpu[mkResourceVirtualEnvironmentVMCPUFlags] = []interface{}{}
}
cpu[mkResourceVirtualEnvironmentVMCPUType] = vmConfig.CPUEmulation.Type
} else {
cpu[mkResourceVirtualEnvironmentVMCPUFlags] = []interface{}{}
// Default value of "cputype" is "qemu64" according to the QEMU documentation.
cpu[mkResourceVirtualEnvironmentVMCPUType] = "qemu64"
}
if vmConfig.CPUUnits != nil {
cpu[mkResourceVirtualEnvironmentVMCPUUnits] = *vmConfig.CPUUnits
} else {
// Default value of "cpuunits" is "1024" according to the API documentation.
cpu[mkResourceVirtualEnvironmentVMCPUUnits] = 1024
}
currentCPU := d.Get(mkResourceVirtualEnvironmentVMCPU).([]interface{})
if len(clone) > 0 {
if len(currentCPU) > 0 {
err := d.Set(mkResourceVirtualEnvironmentVMCPU, []interface{}{cpu})
diags = append(diags, diag.FromErr(err)...)
}
} else if len(currentCPU) > 0 ||
cpu[mkResourceVirtualEnvironmentVMCPUArchitecture] != dvResourceVirtualEnvironmentVMCPUArchitecture ||
cpu[mkResourceVirtualEnvironmentVMCPUCores] != dvResourceVirtualEnvironmentVMCPUCores ||
len(cpu[mkResourceVirtualEnvironmentVMCPUFlags].([]interface{})) > 0 ||
cpu[mkResourceVirtualEnvironmentVMCPUHotplugged] != dvResourceVirtualEnvironmentVMCPUHotplugged ||
cpu[mkResourceVirtualEnvironmentVMCPUSockets] != dvResourceVirtualEnvironmentVMCPUSockets ||
cpu[mkResourceVirtualEnvironmentVMCPUType] != dvResourceVirtualEnvironmentVMCPUType ||
cpu[mkResourceVirtualEnvironmentVMCPUUnits] != dvResourceVirtualEnvironmentVMCPUUnits {
err := d.Set(mkResourceVirtualEnvironmentVMCPU, []interface{}{cpu})
diags = append(diags, diag.FromErr(err)...)
}
currentDiskList := d.Get(mkResourceVirtualEnvironmentVMDisk).([]interface{})
diskMap := map[string]interface{}{}
diskObjects := getDiskInfo(vmConfig, d)
for di, dd := range diskObjects {
disk := map[string]interface{}{}
if dd == nil || dd.FileVolume == "none" || strings.HasPrefix(di, "ide") {
continue
}
fileIDParts := strings.Split(dd.FileVolume, ":")
disk[mkResourceVirtualEnvironmentVMDiskDatastoreID] = fileIDParts[0]
if dd.Format == nil {
disk[mkResourceVirtualEnvironmentVMDiskFileFormat] = dvResourceVirtualEnvironmentVMDiskFileFormat
// disk format may not be returned by config API if it is default for the storage, and that may be different
// from the default qcow2, so we need to read it from the storage API to make sure we have the correct value
files, err := api.Node(nodeName).ListDatastoreFiles(ctx, fileIDParts[0])
if err != nil {
diags = append(diags, diag.FromErr(err)...)
continue
}
for _, v := range files {
if v.VolumeID == dd.FileVolume {
disk[mkResourceVirtualEnvironmentVMDiskFileFormat] = v.FileFormat
break
}
}
} else {
disk[mkResourceVirtualEnvironmentVMDiskFileFormat] = dd.Format
}
if dd.FileID != nil {
disk[mkResourceVirtualEnvironmentVMDiskFileID] = dd.FileID
}
disk[mkResourceVirtualEnvironmentVMDiskInterface] = di
disk[mkResourceVirtualEnvironmentVMDiskSize] = dd.Size.InGigabytes()
if dd.BurstableReadSpeedMbps != nil ||
dd.BurstableWriteSpeedMbps != nil ||
dd.MaxReadSpeedMbps != nil ||
dd.MaxWriteSpeedMbps != nil {
speed := map[string]interface{}{}
if dd.MaxReadSpeedMbps != nil {
speed[mkResourceVirtualEnvironmentVMDiskSpeedRead] = *dd.MaxReadSpeedMbps
} else {
speed[mkResourceVirtualEnvironmentVMDiskSpeedRead] = 0
}
if dd.BurstableReadSpeedMbps != nil {
speed[mkResourceVirtualEnvironmentVMDiskSpeedReadBurstable] = *dd.BurstableReadSpeedMbps
} else {
speed[mkResourceVirtualEnvironmentVMDiskSpeedReadBurstable] = 0
}
if dd.MaxWriteSpeedMbps != nil {
speed[mkResourceVirtualEnvironmentVMDiskSpeedWrite] = *dd.MaxWriteSpeedMbps
} else {
speed[mkResourceVirtualEnvironmentVMDiskSpeedWrite] = 0
}
if dd.BurstableWriteSpeedMbps != nil {
speed[mkResourceVirtualEnvironmentVMDiskSpeedWriteBurstable] = *dd.BurstableWriteSpeedMbps
} else {
speed[mkResourceVirtualEnvironmentVMDiskSpeedWriteBurstable] = 0
}
disk[mkResourceVirtualEnvironmentVMDiskSpeed] = []interface{}{speed}
} else {
disk[mkResourceVirtualEnvironmentVMDiskSpeed] = []interface{}{}
}
if dd.IOThread != nil {
disk[mkResourceVirtualEnvironmentVMDiskIOThread] = *dd.IOThread
} else {
disk[mkResourceVirtualEnvironmentVMDiskIOThread] = false
}
if dd.SSD != nil {
disk[mkResourceVirtualEnvironmentVMDiskSSD] = *dd.SSD
} else {
disk[mkResourceVirtualEnvironmentVMDiskSSD] = false
}
if dd.Discard != nil {
disk[mkResourceVirtualEnvironmentVMDiskDiscard] = *dd.Discard
} else {
disk[mkResourceVirtualEnvironmentVMDiskDiscard] = dvResourceVirtualEnvironmentVMDiskDiscard
}
if dd.Cache != nil {
disk[mkResourceVirtualEnvironmentVMDiskCache] = *dd.Cache
} else {
disk[mkResourceVirtualEnvironmentVMDiskCache] = dvResourceVirtualEnvironmentVMDiskCache
}
diskMap[di] = disk
}
if len(currentDiskList) > 0 {
orderedDiskList := orderedListFromMap(diskMap)
err := d.Set(mkResourceVirtualEnvironmentVMDisk, orderedDiskList)
diags = append(diags, diag.FromErr(err)...)
}
//nolint:nestif
if vmConfig.EFIDisk != nil {
efiDisk := map[string]interface{}{}
fileIDParts := strings.Split(vmConfig.EFIDisk.FileVolume, ":")
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskDatastoreID] = fileIDParts[0]
if vmConfig.EFIDisk.Format != nil {
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskFileFormat] = *vmConfig.EFIDisk.Format
} else {
// disk format may not be returned by config API if it is default for the storage, and that may be different
// from the default qcow2, so we need to read it from the storage API to make sure we have the correct value
files, err := api.Node(nodeName).ListDatastoreFiles(ctx, fileIDParts[0])
if err != nil {
diags = append(diags, diag.FromErr(err)...)
} else {
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskFileFormat] = ""
for _, v := range files {
if v.VolumeID == vmConfig.EFIDisk.FileVolume {
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskFileFormat] = v.FileFormat
break
}
}
}
}
if vmConfig.EFIDisk.Type != nil {
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskType] = *vmConfig.EFIDisk.Type
} else {
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskType] = dvResourceVirtualEnvironmentVMEFIDiskType
}
if vmConfig.EFIDisk.PreEnrolledKeys != nil {
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys] = *vmConfig.EFIDisk.PreEnrolledKeys
} else {
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys] = false
}
currentEfiDisk := d.Get(mkResourceVirtualEnvironmentVMEFIDisk).([]interface{})
if len(clone) > 0 {
if len(currentEfiDisk) > 0 {
err := d.Set(mkResourceVirtualEnvironmentVMEFIDisk, []interface{}{efiDisk})
diags = append(diags, diag.FromErr(err)...)
}
} else if len(currentEfiDisk) > 0 ||
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskDatastoreID] != dvResourceVirtualEnvironmentVMEFIDiskDatastoreID ||
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskType] != dvResourceVirtualEnvironmentVMEFIDiskType ||
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys] != dvResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys || //nolint:lll
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskFileFormat] != dvResourceVirtualEnvironmentVMEFIDiskFileFormat {
err := d.Set(mkResourceVirtualEnvironmentVMEFIDisk, []interface{}{efiDisk})
diags = append(diags, diag.FromErr(err)...)
}
}
currentPCIList := d.Get(mkResourceVirtualEnvironmentVMHostPCI).([]interface{})
pciMap := map[string]interface{}{}
pciDevices := getPCIInfo(vmConfig, d)
for pi, pp := range pciDevices {
if (pp == nil) || (pp.DeviceIDs == nil && pp.Mapping == nil) {
continue
}
pci := map[string]interface{}{}
pci[mkResourceVirtualEnvironmentVMHostPCIDevice] = pi
if pp.DeviceIDs != nil {
pci[mkResourceVirtualEnvironmentVMHostPCIDeviceID] = strings.Join(*pp.DeviceIDs, ";")
} else {
pci[mkResourceVirtualEnvironmentVMHostPCIDeviceID] = ""
}
if pp.MDev != nil {
pci[mkResourceVirtualEnvironmentVMHostPCIDeviceMDev] = *pp.MDev
} else {
pci[mkResourceVirtualEnvironmentVMHostPCIDeviceMDev] = ""
}
if pp.PCIExpress != nil {
pci[mkResourceVirtualEnvironmentVMHostPCIDevicePCIE] = *pp.PCIExpress
} else {
pci[mkResourceVirtualEnvironmentVMHostPCIDevicePCIE] = false
}
if pp.ROMBAR != nil {
pci[mkResourceVirtualEnvironmentVMHostPCIDeviceROMBAR] = *pp.ROMBAR
} else {
pci[mkResourceVirtualEnvironmentVMHostPCIDeviceROMBAR] = false
}
if pp.ROMFile != nil {
pci[mkResourceVirtualEnvironmentVMHostPCIDeviceROMFile] = *pp.ROMFile
} else {
pci[mkResourceVirtualEnvironmentVMHostPCIDeviceROMFile] = ""
}
if pp.XVGA != nil {
pci[mkResourceVirtualEnvironmentVMHostPCIDeviceXVGA] = *pp.XVGA
} else {
pci[mkResourceVirtualEnvironmentVMHostPCIDeviceXVGA] = false
}
if pp.Mapping != nil {
pci[mkResourceVirtualEnvironmentVMHostPCIDeviceMapping] = *pp.Mapping
} else {
pci[mkResourceVirtualEnvironmentVMHostPCIDeviceMapping] = ""
}
pciMap[pi] = pci
}
if len(currentPCIList) > 0 {
// todo: reordering of devices by PVE may cause an issue here
orderedPCIList := orderedListFromMap(pciMap)
err := d.Set(mkResourceVirtualEnvironmentVMHostPCI, orderedPCIList)
diags = append(diags, diag.FromErr(err)...)
}
// Compare the initialization configuration to the one stored in the state.
initialization := map[string]interface{}{}
initializationInterface := findExistingCloudInitDrive(vmConfig, vmID, "")
if initializationInterface != "" {
initializationDevice := getIdeDevice(vmConfig, initializationInterface)
fileVolumeParts := strings.Split(initializationDevice.FileVolume, ":")
initialization[mkResourceVirtualEnvironmentVMInitializationInterface] = initializationInterface
initialization[mkResourceVirtualEnvironmentVMInitializationDatastoreID] = fileVolumeParts[0]
}
if vmConfig.CloudInitDNSDomain != nil || vmConfig.CloudInitDNSServer != nil {
initializationDNS := map[string]interface{}{}
if vmConfig.CloudInitDNSDomain != nil {
initializationDNS[mkResourceVirtualEnvironmentVMInitializationDNSDomain] = *vmConfig.CloudInitDNSDomain
} else {
initializationDNS[mkResourceVirtualEnvironmentVMInitializationDNSDomain] = ""
}
if vmConfig.CloudInitDNSServer != nil {
initializationDNS[mkResourceVirtualEnvironmentVMInitializationDNSServer] = *vmConfig.CloudInitDNSServer
} else {
initializationDNS[mkResourceVirtualEnvironmentVMInitializationDNSServer] = ""
}
initialization[mkResourceVirtualEnvironmentVMInitializationDNS] = []interface{}{
initializationDNS,
}
}
ipConfigLast := -1
ipConfigObjects := []*vms.CustomCloudInitIPConfig{
vmConfig.IPConfig0,
vmConfig.IPConfig1,
vmConfig.IPConfig2,
vmConfig.IPConfig3,
vmConfig.IPConfig4,
vmConfig.IPConfig5,
vmConfig.IPConfig6,
vmConfig.IPConfig7,
}
ipConfigList := make([]interface{}, len(ipConfigObjects))
for ipConfigIndex, ipConfig := range ipConfigObjects {
ipConfigItem := map[string]interface{}{}
if ipConfig != nil {
ipConfigLast = ipConfigIndex
if ipConfig.GatewayIPv4 != nil || ipConfig.IPv4 != nil {
ipv4 := map[string]interface{}{}
if ipConfig.IPv4 != nil {
ipv4[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Address] = *ipConfig.IPv4
} else {
ipv4[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Address] = ""
}
if ipConfig.GatewayIPv4 != nil {
ipv4[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Gateway] = *ipConfig.GatewayIPv4
} else {
ipv4[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Gateway] = ""
}
ipConfigItem[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4] = []interface{}{
ipv4,
}
} else {
ipConfigItem[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4] = []interface{}{}
}
if ipConfig.GatewayIPv6 != nil || ipConfig.IPv6 != nil {
ipv6 := map[string]interface{}{}
if ipConfig.IPv6 != nil {
ipv6[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6Address] = *ipConfig.IPv6
} else {
ipv6[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6Address] = ""
}
if ipConfig.GatewayIPv6 != nil {
ipv6[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6Gateway] = *ipConfig.GatewayIPv6
} else {
ipv6[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6Gateway] = ""
}
ipConfigItem[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6] = []interface{}{
ipv6,
}
} else {
ipConfigItem[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6] = []interface{}{}
}
} else {
ipConfigItem[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4] = []interface{}{}
ipConfigItem[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6] = []interface{}{}
}
ipConfigList[ipConfigIndex] = ipConfigItem
}
if ipConfigLast >= 0 {
initialization[mkResourceVirtualEnvironmentVMInitializationIPConfig] = ipConfigList[:ipConfigLast+1]
}
//nolint:nestif
if vmConfig.CloudInitPassword != nil || vmConfig.CloudInitSSHKeys != nil ||
vmConfig.CloudInitUsername != nil {
initializationUserAccount := map[string]interface{}{}
if vmConfig.CloudInitSSHKeys != nil {
initializationUserAccount[mkResourceVirtualEnvironmentVMInitializationUserAccountKeys] = []string(
*vmConfig.CloudInitSSHKeys,
)
} else {
initializationUserAccount[mkResourceVirtualEnvironmentVMInitializationUserAccountKeys] = []string{}
}
if vmConfig.CloudInitPassword != nil {
initializationUserAccount[mkResourceVirtualEnvironmentVMInitializationUserAccountPassword] = *vmConfig.CloudInitPassword
} else {
initializationUserAccount[mkResourceVirtualEnvironmentVMInitializationUserAccountPassword] = ""
}
if vmConfig.CloudInitUsername != nil {
initializationUserAccount[mkResourceVirtualEnvironmentVMInitializationUserAccountUsername] = *vmConfig.CloudInitUsername
} else {
initializationUserAccount[mkResourceVirtualEnvironmentVMInitializationUserAccountUsername] = ""
}
initialization[mkResourceVirtualEnvironmentVMInitializationUserAccount] = []interface{}{
initializationUserAccount,
}
}
if vmConfig.CloudInitFiles != nil {
if vmConfig.CloudInitFiles.UserVolume != nil {
initialization[mkResourceVirtualEnvironmentVMInitializationUserDataFileID] = *vmConfig.CloudInitFiles.UserVolume
} else {
initialization[mkResourceVirtualEnvironmentVMInitializationUserDataFileID] = ""
}
if vmConfig.CloudInitFiles.VendorVolume != nil {
initialization[mkResourceVirtualEnvironmentVMInitializationVendorDataFileID] = *vmConfig.CloudInitFiles.VendorVolume
} else {
initialization[mkResourceVirtualEnvironmentVMInitializationVendorDataFileID] = ""
}
if vmConfig.CloudInitFiles.NetworkVolume != nil {
initialization[mkResourceVirtualEnvironmentVMInitializationNetworkDataFileID] = *vmConfig.CloudInitFiles.NetworkVolume
} else {
initialization[mkResourceVirtualEnvironmentVMInitializationNetworkDataFileID] = ""
}
if vmConfig.CloudInitFiles.MetaVolume != nil {
initialization[mkResourceVirtualEnvironmentVMInitializationMetaDataFileID] = *vmConfig.CloudInitFiles.MetaVolume
} else {
initialization[mkResourceVirtualEnvironmentVMInitializationMetaDataFileID] = ""
}
} else if len(initialization) > 0 {
initialization[mkResourceVirtualEnvironmentVMInitializationUserDataFileID] = ""
initialization[mkResourceVirtualEnvironmentVMInitializationVendorDataFileID] = ""
initialization[mkResourceVirtualEnvironmentVMInitializationNetworkDataFileID] = ""
initialization[mkResourceVirtualEnvironmentVMInitializationMetaDataFileID] = ""
}
if vmConfig.CloudInitType != nil {
initialization[mkResourceVirtualEnvironmentVMInitializationType] = *vmConfig.CloudInitType
} else if len(initialization) > 0 {
initialization[mkResourceVirtualEnvironmentVMInitializationType] = ""
}
currentInitialization := d.Get(mkResourceVirtualEnvironmentVMInitialization).([]interface{})
if len(clone) > 0 {
if len(currentInitialization) > 0 {
if len(initialization) > 0 {
err := d.Set(
mkResourceVirtualEnvironmentVMInitialization,
[]interface{}{initialization},
)
diags = append(diags, diag.FromErr(err)...)
} else {
err := d.Set(mkResourceVirtualEnvironmentVMInitialization, []interface{}{})
diags = append(diags, diag.FromErr(err)...)
}
}
} else if len(initialization) > 0 {
err := d.Set(mkResourceVirtualEnvironmentVMInitialization, []interface{}{initialization})
diags = append(diags, diag.FromErr(err)...)
} else {
err := d.Set(mkResourceVirtualEnvironmentVMInitialization, []interface{}{})
diags = append(diags, diag.FromErr(err)...)
}
// Compare the operating system configuration to the one stored in the state.
kvmArguments := map[string]interface{}{}
if vmConfig.KVMArguments != nil {
kvmArguments[mkResourceVirtualEnvironmentVMKVMArguments] = *vmConfig.KVMArguments
} else {
kvmArguments[mkResourceVirtualEnvironmentVMKVMArguments] = ""
}
// Compare the memory configuration to the one stored in the state.
memory := map[string]interface{}{}
if vmConfig.DedicatedMemory != nil {
memory[mkResourceVirtualEnvironmentVMMemoryDedicated] = *vmConfig.DedicatedMemory
} else {
memory[mkResourceVirtualEnvironmentVMMemoryDedicated] = 0
}
if vmConfig.FloatingMemory != nil {
memory[mkResourceVirtualEnvironmentVMMemoryFloating] = *vmConfig.FloatingMemory
} else {
memory[mkResourceVirtualEnvironmentVMMemoryFloating] = 0
}
if vmConfig.SharedMemory != nil {
memory[mkResourceVirtualEnvironmentVMMemoryShared] = vmConfig.SharedMemory.Size
} else {
memory[mkResourceVirtualEnvironmentVMMemoryShared] = 0
}
currentMemory := d.Get(mkResourceVirtualEnvironmentVMMemory).([]interface{})
if len(clone) > 0 {
if len(currentMemory) > 0 {
err := d.Set(mkResourceVirtualEnvironmentVMMemory, []interface{}{memory})
diags = append(diags, diag.FromErr(err)...)
}
} else if len(currentMemory) > 0 ||
memory[mkResourceVirtualEnvironmentVMMemoryDedicated] != dvResourceVirtualEnvironmentVMMemoryDedicated ||
memory[mkResourceVirtualEnvironmentVMMemoryFloating] != dvResourceVirtualEnvironmentVMMemoryFloating ||
memory[mkResourceVirtualEnvironmentVMMemoryShared] != dvResourceVirtualEnvironmentVMMemoryShared {
err := d.Set(mkResourceVirtualEnvironmentVMMemory, []interface{}{memory})
diags = append(diags, diag.FromErr(err)...)
}
// Compare the network devices to those stored in the state.
currentNetworkDeviceList := d.Get(mkResourceVirtualEnvironmentVMNetworkDevice).([]interface{})
macAddresses := make([]interface{}, 8)
networkDeviceLast := -1
networkDeviceList := make([]interface{}, 8)
networkDeviceObjects := []*vms.CustomNetworkDevice{
vmConfig.NetworkDevice0,
vmConfig.NetworkDevice1,
vmConfig.NetworkDevice2,
vmConfig.NetworkDevice3,
vmConfig.NetworkDevice4,
vmConfig.NetworkDevice5,
vmConfig.NetworkDevice6,
vmConfig.NetworkDevice7,
}
for ni, nd := range networkDeviceObjects {
networkDevice := map[string]interface{}{}
if nd != nil {
networkDeviceLast = ni
if nd.Bridge != nil {
networkDevice[mkResourceVirtualEnvironmentVMNetworkDeviceBridge] = *nd.Bridge
} else {
networkDevice[mkResourceVirtualEnvironmentVMNetworkDeviceBridge] = ""
}
networkDevice[mkResourceVirtualEnvironmentVMNetworkDeviceEnabled] = nd.Enabled
if nd.Firewall != nil {
networkDevice[mkResourceVirtualEnvironmentVMNetworkDeviceFirewall] = *nd.Firewall
} else {
networkDevice[mkResourceVirtualEnvironmentVMNetworkDeviceFirewall] = false
}
if nd.MACAddress != nil {
macAddresses[ni] = *nd.MACAddress
} else {
macAddresses[ni] = ""
}
networkDevice[mkResourceVirtualEnvironmentVMNetworkDeviceMACAddress] = macAddresses[ni]
networkDevice[mkResourceVirtualEnvironmentVMNetworkDeviceModel] = nd.Model
if nd.RateLimit != nil {
networkDevice[mkResourceVirtualEnvironmentVMNetworkDeviceRateLimit] = *nd.RateLimit
} else {
networkDevice[mkResourceVirtualEnvironmentVMNetworkDeviceRateLimit] = 0
}
if nd.Tag != nil {
networkDevice[mkResourceVirtualEnvironmentVMNetworkDeviceVLANID] = nd.Tag
} else {
networkDevice[mkResourceVirtualEnvironmentVMNetworkDeviceVLANID] = 0
}
if nd.MTU != nil {
networkDevice[mkResourceVirtualEnvironmentVMNetworkDeviceMTU] = nd.MTU
} else {
networkDevice[mkResourceVirtualEnvironmentVMNetworkDeviceMTU] = 0
}
} else {
macAddresses[ni] = ""
networkDevice[mkResourceVirtualEnvironmentVMNetworkDeviceEnabled] = false
}
networkDeviceList[ni] = networkDevice
}
if len(clone) > 0 {
if len(currentNetworkDeviceList) > 0 {
err := d.Set(
mkResourceVirtualEnvironmentVMMACAddresses,
macAddresses[0:len(currentNetworkDeviceList)],
)
diags = append(diags, diag.FromErr(err)...)
err = d.Set(
mkResourceVirtualEnvironmentVMNetworkDevice,
networkDeviceList[:networkDeviceLast+1],
)
diags = append(diags, diag.FromErr(err)...)
}
} else {
err := d.Set(mkResourceVirtualEnvironmentVMMACAddresses, macAddresses[0:len(currentNetworkDeviceList)])
diags = append(diags, diag.FromErr(err)...)
if len(currentNetworkDeviceList) > 0 || networkDeviceLast > -1 {
err := d.Set(mkResourceVirtualEnvironmentVMNetworkDevice, networkDeviceList[:networkDeviceLast+1])
diags = append(diags, diag.FromErr(err)...)
}
}
// Compare the operating system configuration to the one stored in the state.
operatingSystem := map[string]interface{}{}
if vmConfig.OSType != nil {
operatingSystem[mkResourceVirtualEnvironmentVMOperatingSystemType] = *vmConfig.OSType
} else {
operatingSystem[mkResourceVirtualEnvironmentVMOperatingSystemType] = ""
}
currentOperatingSystem := d.Get(mkResourceVirtualEnvironmentVMOperatingSystem).([]interface{})
if len(clone) > 0 {
if len(currentOperatingSystem) > 0 {
err := d.Set(
mkResourceVirtualEnvironmentVMOperatingSystem,
[]interface{}{operatingSystem},
)
diags = append(diags, diag.FromErr(err)...)
}
} else if len(currentOperatingSystem) > 0 ||
operatingSystem[mkResourceVirtualEnvironmentVMOperatingSystemType] != dvResourceVirtualEnvironmentVMOperatingSystemType {
err := d.Set(mkResourceVirtualEnvironmentVMOperatingSystem, []interface{}{operatingSystem})
diags = append(diags, diag.FromErr(err)...)
} else {
err := d.Set(mkResourceVirtualEnvironmentVMOperatingSystem, []interface{}{})
diags = append(diags, diag.FromErr(err)...)
}
// Compare the pool ID to the value stored in the state.
currentPoolID := d.Get(mkResourceVirtualEnvironmentVMPoolID).(string)
if len(clone) == 0 || currentPoolID != dvResourceVirtualEnvironmentVMPoolID {
if vmConfig.PoolID != nil {
err := d.Set(mkResourceVirtualEnvironmentVMPoolID, *vmConfig.PoolID)
diags = append(diags, diag.FromErr(err)...)
}
}
// Compare the serial devices to those stored in the state.
serialDevices := make([]interface{}, 4)
serialDevicesArray := []*string{
vmConfig.SerialDevice0,
vmConfig.SerialDevice1,
vmConfig.SerialDevice2,
vmConfig.SerialDevice3,
}
serialDevicesCount := 0
for sdi, sd := range serialDevicesArray {
m := map[string]interface{}{}
if sd != nil {
m[mkResourceVirtualEnvironmentVMSerialDeviceDevice] = *sd
serialDevicesCount = sdi + 1
} else {
m[mkResourceVirtualEnvironmentVMSerialDeviceDevice] = ""
}
serialDevices[sdi] = m
}
currentSerialDevice := d.Get(mkResourceVirtualEnvironmentVMSerialDevice).([]interface{})
if len(clone) == 0 || len(currentSerialDevice) > 0 {
err := d.Set(mkResourceVirtualEnvironmentVMSerialDevice, serialDevices[:serialDevicesCount])
diags = append(diags, diag.FromErr(err)...)
}
// Compare the SMBIOS to the one stored in the state.
var smbios map[string]interface{}
//nolint:nestif
if vmConfig.SMBIOS != nil {
smbios = map[string]interface{}{}
if vmConfig.SMBIOS.Family != nil {
b, err := base64.StdEncoding.DecodeString(*vmConfig.SMBIOS.Family)
diags = append(diags, diag.FromErr(err)...)
smbios[mkResourceVirtualEnvironmentVMSMBIOSFamily] = string(b)
} else {
smbios[mkResourceVirtualEnvironmentVMSMBIOSFamily] = dvResourceVirtualEnvironmentVMSMBIOSFamily
}
if vmConfig.SMBIOS.Manufacturer != nil {
b, err := base64.StdEncoding.DecodeString(*vmConfig.SMBIOS.Manufacturer)
diags = append(diags, diag.FromErr(err)...)
smbios[mkResourceVirtualEnvironmentVMSMBIOSManufacturer] = string(b)
} else {
smbios[mkResourceVirtualEnvironmentVMSMBIOSManufacturer] = dvResourceVirtualEnvironmentVMSMBIOSManufacturer
}
if vmConfig.SMBIOS.Product != nil {
b, err := base64.StdEncoding.DecodeString(*vmConfig.SMBIOS.Product)
diags = append(diags, diag.FromErr(err)...)
smbios[mkResourceVirtualEnvironmentVMSMBIOSProduct] = string(b)
} else {
smbios[mkResourceVirtualEnvironmentVMSMBIOSProduct] = dvResourceVirtualEnvironmentVMSMBIOSProduct
}
if vmConfig.SMBIOS.Serial != nil {
b, err := base64.StdEncoding.DecodeString(*vmConfig.SMBIOS.Serial)
diags = append(diags, diag.FromErr(err)...)
smbios[mkResourceVirtualEnvironmentVMSMBIOSSerial] = string(b)
} else {
smbios[mkResourceVirtualEnvironmentVMSMBIOSSerial] = dvResourceVirtualEnvironmentVMSMBIOSSerial
}
if vmConfig.SMBIOS.SKU != nil {
b, err := base64.StdEncoding.DecodeString(*vmConfig.SMBIOS.SKU)
diags = append(diags, diag.FromErr(err)...)
smbios[mkResourceVirtualEnvironmentVMSMBIOSSKU] = string(b)
} else {
smbios[mkResourceVirtualEnvironmentVMSMBIOSSKU] = dvResourceVirtualEnvironmentVMSMBIOSSKU
}
if vmConfig.SMBIOS.Version != nil {
b, err := base64.StdEncoding.DecodeString(*vmConfig.SMBIOS.Version)
diags = append(diags, diag.FromErr(err)...)
smbios[mkResourceVirtualEnvironmentVMSMBIOSVersion] = string(b)
} else {
smbios[mkResourceVirtualEnvironmentVMSMBIOSVersion] = dvResourceVirtualEnvironmentVMSMBIOSVersion
}
if vmConfig.SMBIOS.UUID != nil {
smbios[mkResourceVirtualEnvironmentVMSMBIOSUUID] = *vmConfig.SMBIOS.UUID
} else {
smbios[mkResourceVirtualEnvironmentVMSMBIOSUUID] = nil
}
}
currentSMBIOS := d.Get(mkResourceVirtualEnvironmentVMSMBIOS).([]interface{})
//nolint:gocritic
if len(clone) > 0 {
if len(currentSMBIOS) > 0 {
err := d.Set(mkResourceVirtualEnvironmentVMSMBIOS, currentSMBIOS)
diags = append(diags, diag.FromErr(err)...)
}
} else if len(smbios) == 0 {
err := d.Set(mkResourceVirtualEnvironmentVMSMBIOS, []interface{}{})
diags = append(diags, diag.FromErr(err)...)
} else if len(currentSMBIOS) > 0 ||
smbios[mkResourceVirtualEnvironmentVMSMBIOSFamily] != dvResourceVirtualEnvironmentVMSMBIOSFamily ||
smbios[mkResourceVirtualEnvironmentVMSMBIOSManufacturer] != dvResourceVirtualEnvironmentVMSMBIOSManufacturer ||
smbios[mkResourceVirtualEnvironmentVMSMBIOSProduct] != dvResourceVirtualEnvironmentVMSMBIOSProduct ||
smbios[mkResourceVirtualEnvironmentVMSMBIOSSerial] != dvResourceVirtualEnvironmentVMSMBIOSSerial ||
smbios[mkResourceVirtualEnvironmentVMSMBIOSSKU] != dvResourceVirtualEnvironmentVMSMBIOSSKU ||
smbios[mkResourceVirtualEnvironmentVMSMBIOSVersion] != dvResourceVirtualEnvironmentVMSMBIOSVersion {
err := d.Set(mkResourceVirtualEnvironmentVMSMBIOS, []interface{}{smbios})
diags = append(diags, diag.FromErr(err)...)
}
// Compare the startup order to the one stored in the state.
var startup map[string]interface{}
//nolint:nestif
if vmConfig.StartupOrder != nil {
startup = map[string]interface{}{}
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
}
}
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(startup) == 0 {
err := d.Set(mkResourceVirtualEnvironmentVMStartup, []interface{}{})
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)...)
}
// Compare the VGA configuration to the one stored in the state.
vga := map[string]interface{}{}
if vmConfig.VGADevice != nil {
vgaEnabled := true
if vmConfig.VGADevice.Type != nil {
vgaEnabled = *vmConfig.VGADevice.Type != "none"
}
vga[mkResourceVirtualEnvironmentVMVGAEnabled] = vgaEnabled
if vmConfig.VGADevice.Memory != nil {
vga[mkResourceVirtualEnvironmentVMVGAMemory] = *vmConfig.VGADevice.Memory
} else {
vga[mkResourceVirtualEnvironmentVMVGAMemory] = 0
}
if vgaEnabled {
if vmConfig.VGADevice.Type != nil {
vga[mkResourceVirtualEnvironmentVMVGAType] = *vmConfig.VGADevice.Type
} else {
vga[mkResourceVirtualEnvironmentVMVGAType] = ""
}
}
} else {
vga[mkResourceVirtualEnvironmentVMVGAEnabled] = true
vga[mkResourceVirtualEnvironmentVMVGAMemory] = 0
vga[mkResourceVirtualEnvironmentVMVGAType] = ""
}
currentVGA := d.Get(mkResourceVirtualEnvironmentVMVGA).([]interface{})
if len(clone) > 0 {
if len(currentVGA) > 0 {
err := d.Set(mkResourceVirtualEnvironmentVMVGA, []interface{}{vga})
diags = append(diags, diag.FromErr(err)...)
}
} else if len(currentVGA) > 0 ||
vga[mkResourceVirtualEnvironmentVMVGAEnabled] != dvResourceVirtualEnvironmentVMVGAEnabled ||
vga[mkResourceVirtualEnvironmentVMVGAMemory] != dvResourceVirtualEnvironmentVMVGAMemory ||
vga[mkResourceVirtualEnvironmentVMVGAType] != dvResourceVirtualEnvironmentVMVGAType {
err := d.Set(mkResourceVirtualEnvironmentVMVGA, []interface{}{vga})
diags = append(diags, diag.FromErr(err)...)
} else {
err := d.Set(mkResourceVirtualEnvironmentVMVGA, []interface{}{})
diags = append(diags, diag.FromErr(err)...)
}
// Compare SCSI hardware type
scsiHardware := d.Get(mkResourceVirtualEnvironmentVMSCSIHardware).(string)
if len(clone) == 0 || scsiHardware != dvResourceVirtualEnvironmentVMSCSIHardware {
if vmConfig.SCSIHardware != nil {
err := d.Set(mkResourceVirtualEnvironmentVMSCSIHardware, *vmConfig.SCSIHardware)
diags = append(diags, diag.FromErr(err)...)
}
}
diags = append(
diags,
vmReadNetworkValues(ctx, d, m, vmID, vmConfig)...)
return diags
}
func vmReadNetworkValues(
ctx context.Context,
d *schema.ResourceData,
m interface{},
vmID int,
vmConfig *vms.GetResponseData,
) diag.Diagnostics {
var diags diag.Diagnostics
config := m.(proxmoxtf.ProviderConfiguration)
api, e := config.GetClient()
if e != nil {
return diag.FromErr(e)
}
nodeName := d.Get(mkResourceVirtualEnvironmentVMNodeName).(string)
vmAPI := api.Node(nodeName).VM(vmID)
started := d.Get(mkResourceVirtualEnvironmentVMStarted).(bool)
var ipv4Addresses []interface{}
var ipv6Addresses []interface{}
var networkInterfaceNames []interface{}
if started {
if vmConfig.Agent != nil && vmConfig.Agent.Enabled != nil && *vmConfig.Agent.Enabled {
resource := VM()
agentBlock, err := structure.GetSchemaBlock(
resource,
d,
[]string{mkResourceVirtualEnvironmentVMAgent},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
agentTimeout, err := time.ParseDuration(
agentBlock[mkResourceVirtualEnvironmentVMAgentTimeout].(string),
)
if err != nil {
return diag.FromErr(err)
}
var macAddresses []interface{}
networkInterfaces, err := vmAPI.WaitForNetworkInterfacesFromVMAgent(ctx, int(agentTimeout.Seconds()), 5, true)
if err == nil && networkInterfaces.Result != nil {
ipv4Addresses = make([]interface{}, len(*networkInterfaces.Result))
ipv6Addresses = make([]interface{}, len(*networkInterfaces.Result))
macAddresses = make([]interface{}, len(*networkInterfaces.Result))
networkInterfaceNames = make([]interface{}, len(*networkInterfaces.Result))
for ri, rv := range *networkInterfaces.Result {
var rvIPv4Addresses []interface{}
var rvIPv6Addresses []interface{}
if rv.IPAddresses != nil {
for _, ip := range *rv.IPAddresses {
switch ip.Type {
case "ipv4":
rvIPv4Addresses = append(rvIPv4Addresses, ip.Address)
case "ipv6":
rvIPv6Addresses = append(rvIPv6Addresses, ip.Address)
}
}
}
ipv4Addresses[ri] = rvIPv4Addresses
ipv6Addresses[ri] = rvIPv6Addresses
macAddresses[ri] = strings.ToUpper(rv.MACAddress)
networkInterfaceNames[ri] = rv.Name
}
}
err = d.Set(mkResourceVirtualEnvironmentVMMACAddresses, macAddresses)
diags = append(diags, diag.FromErr(err)...)
}
}
e = d.Set(mkResourceVirtualEnvironmentVMIPv4Addresses, ipv4Addresses)
diags = append(diags, diag.FromErr(e)...)
e = d.Set(mkResourceVirtualEnvironmentVMIPv6Addresses, ipv6Addresses)
diags = append(diags, diag.FromErr(e)...)
e = d.Set(mkResourceVirtualEnvironmentVMNetworkInterfaceNames, networkInterfaceNames)
diags = append(diags, diag.FromErr(e)...)
return diags
}
func vmReadPrimitiveValues(
d *schema.ResourceData,
vmConfig *vms.GetResponseData,
vmStatus *vms.GetStatusResponseData,
) diag.Diagnostics {
var diags diag.Diagnostics
var err error
clone := d.Get(mkResourceVirtualEnvironmentVMClone).([]interface{})
currentACPI := d.Get(mkResourceVirtualEnvironmentVMACPI).(bool)
//nolint:gosimple
if len(clone) == 0 || currentACPI != dvResourceVirtualEnvironmentVMACPI {
if vmConfig.ACPI != nil {
err = d.Set(mkResourceVirtualEnvironmentVMACPI, bool(*vmConfig.ACPI))
} else {
// Default value of "acpi" is "1" according to the API documentation.
err = d.Set(mkResourceVirtualEnvironmentVMACPI, true)
}
diags = append(diags, diag.FromErr(err)...)
}
currentkvmArguments := d.Get(mkResourceVirtualEnvironmentVMKVMArguments).(string)
if len(clone) == 0 || currentkvmArguments != dvResourceVirtualEnvironmentVMKVMArguments {
// PVE API returns "args" as " " if it is set to empty.
if vmConfig.KVMArguments != nil && len(strings.TrimSpace(*vmConfig.KVMArguments)) > 0 {
err = d.Set(mkResourceVirtualEnvironmentVMKVMArguments, *vmConfig.KVMArguments)
} else {
// Default value of "args" is "" according to the API documentation.
err = d.Set(mkResourceVirtualEnvironmentVMKVMArguments, "")
}
diags = append(diags, diag.FromErr(err)...)
}
currentBIOS := d.Get(mkResourceVirtualEnvironmentVMBIOS).(string)
if len(clone) == 0 || currentBIOS != dvResourceVirtualEnvironmentVMBIOS {
if vmConfig.BIOS != nil {
err = d.Set(mkResourceVirtualEnvironmentVMBIOS, *vmConfig.BIOS)
} else {
// Default value of "bios" is "seabios" according to the API documentation.
err = d.Set(mkResourceVirtualEnvironmentVMBIOS, "seabios")
}
diags = append(diags, diag.FromErr(err)...)
}
currentDescription := d.Get(mkResourceVirtualEnvironmentVMDescription).(string)
if len(clone) == 0 || currentDescription != dvResourceVirtualEnvironmentVMDescription {
if vmConfig.Description != nil {
err = d.Set(mkResourceVirtualEnvironmentVMDescription, *vmConfig.Description)
} else {
// Default value of "description" is "" according to the API documentation.
err = d.Set(mkResourceVirtualEnvironmentVMDescription, "")
}
diags = append(diags, diag.FromErr(err)...)
}
currentTags := d.Get(mkResourceVirtualEnvironmentVMTags).([]interface{})
if len(clone) == 0 || len(currentTags) > 0 {
var tags []string
if vmConfig.Tags != nil {
for _, tag := range strings.Split(*vmConfig.Tags, ";") {
t := strings.TrimSpace(tag)
if len(t) > 0 {
tags = append(tags, t)
}
}
sort.Strings(tags)
}
err = d.Set(mkResourceVirtualEnvironmentVMTags, tags)
diags = append(diags, diag.FromErr(err)...)
}
currentKeyboardLayout := d.Get(mkResourceVirtualEnvironmentVMKeyboardLayout).(string)
if len(clone) == 0 || currentKeyboardLayout != dvResourceVirtualEnvironmentVMKeyboardLayout {
if vmConfig.KeyboardLayout != nil {
err = d.Set(mkResourceVirtualEnvironmentVMKeyboardLayout, *vmConfig.KeyboardLayout)
} else {
// Default value of "keyboard" is "" according to the API documentation.
err = d.Set(mkResourceVirtualEnvironmentVMKeyboardLayout, "")
}
diags = append(diags, diag.FromErr(err)...)
}
currentMachine := d.Get(mkResourceVirtualEnvironmentVMMachine).(string)
if len(clone) == 0 || currentMachine != dvResourceVirtualEnvironmentVMMachineType {
if vmConfig.Machine != nil {
err = d.Set(mkResourceVirtualEnvironmentVMMachine, *vmConfig.Machine)
} else {
err = d.Set(mkResourceVirtualEnvironmentVMMachine, "")
}
diags = append(diags, diag.FromErr(err)...)
}
currentName := d.Get(mkResourceVirtualEnvironmentVMName).(string)
if len(clone) == 0 || currentName != dvResourceVirtualEnvironmentVMName {
if vmConfig.Name != nil {
err = d.Set(mkResourceVirtualEnvironmentVMName, *vmConfig.Name)
} else {
// Default value of "name" is "" according to the API documentation.
err = d.Set(mkResourceVirtualEnvironmentVMName, "")
}
diags = append(diags, diag.FromErr(err)...)
}
if !d.Get(mkResourceVirtualEnvironmentVMTemplate).(bool) {
err = d.Set(mkResourceVirtualEnvironmentVMStarted, vmStatus.Status == "running")
diags = append(diags, diag.FromErr(err)...)
}
currentTabletDevice := d.Get(mkResourceVirtualEnvironmentVMTabletDevice).(bool)
//nolint:gosimple
if len(clone) == 0 || currentTabletDevice != dvResourceVirtualEnvironmentVMTabletDevice {
if vmConfig.TabletDeviceEnabled != nil {
err = d.Set(
mkResourceVirtualEnvironmentVMTabletDevice,
bool(*vmConfig.TabletDeviceEnabled),
)
} else {
// Default value of "tablet" is "1" according to the API documentation.
err = d.Set(mkResourceVirtualEnvironmentVMTabletDevice, true)
}
diags = append(diags, diag.FromErr(err)...)
}
currentTemplate := d.Get(mkResourceVirtualEnvironmentVMTemplate).(bool)
//nolint:gosimple
if len(clone) == 0 || currentTemplate != dvResourceVirtualEnvironmentVMTemplate {
if vmConfig.Template != nil {
err = d.Set(mkResourceVirtualEnvironmentVMTemplate, bool(*vmConfig.Template))
} else {
// Default value of "template" is "0" according to the API documentation.
err = d.Set(mkResourceVirtualEnvironmentVMTemplate, false)
}
diags = append(diags, diag.FromErr(err)...)
}
return diags
}
// vmUpdatePool moves the VM to the pool it is supposed to be in if the pool ID changed.
func vmUpdatePool(
ctx context.Context,
d *schema.ResourceData,
api *pools.Client,
vmID int,
) error {
oldPoolValue, newPoolValue := d.GetChange(mkResourceVirtualEnvironmentVMPoolID)
if cmp.Equal(newPoolValue, oldPoolValue) {
return nil
}
oldPool := oldPoolValue.(string)
newPool := newPoolValue.(string)
vmList := (types2.CustomCommaSeparatedList)([]string{strconv.Itoa(vmID)})
tflog.Debug(ctx, fmt.Sprintf("Moving VM %d from pool '%s' to pool '%s'", vmID, oldPool, newPool))
if oldPool != "" {
trueValue := types2.CustomBool(true)
poolUpdate := &pools.PoolUpdateRequestBody{
VMs: &vmList,
Delete: &trueValue,
}
err := api.UpdatePool(ctx, oldPool, poolUpdate)
if err != nil {
return fmt.Errorf("while removing VM %d from pool %s: %w", vmID, oldPool, err)
}
}
if newPool != "" {
poolUpdate := &pools.PoolUpdateRequestBody{VMs: &vmList}
err := api.UpdatePool(ctx, newPool, poolUpdate)
if err != nil {
return fmt.Errorf("while adding VM %d to pool %s: %w", vmID, newPool, err)
}
}
return nil
}
func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
config := m.(proxmoxtf.ProviderConfiguration)
api, e := config.GetClient()
if e != nil {
return diag.FromErr(e)
}
nodeName := d.Get(mkResourceVirtualEnvironmentVMNodeName).(string)
rebootRequired := false
vmID, e := strconv.Atoi(d.Id())
if e != nil {
return diag.FromErr(e)
}
e = vmUpdatePool(ctx, d, api.Pool(), vmID)
if e != nil {
return diag.FromErr(e)
}
// If the node name has changed we need to migrate the VM to the new node before we do anything else.
if d.HasChange(mkResourceVirtualEnvironmentVMNodeName) {
oldNodeNameValue, _ := d.GetChange(mkResourceVirtualEnvironmentVMNodeName)
oldNodeName := oldNodeNameValue.(string)
vmAPI := api.Node(oldNodeName).VM(vmID)
migrateTimeout := d.Get(mkResourceVirtualEnvironmentVMTimeoutMigrate).(int)
trueValue := types2.CustomBool(true)
migrateBody := &vms.MigrateRequestBody{
TargetNode: nodeName,
WithLocalDisks: &trueValue,
OnlineMigration: &trueValue,
}
err := vmAPI.MigrateVM(ctx, migrateBody, migrateTimeout)
if err != nil {
return diag.FromErr(err)
}
}
vmAPI := api.Node(nodeName).VM(vmID)
updateBody := &vms.UpdateRequestBody{
IDEDevices: vms.CustomStorageDevices{
"ide0": vms.CustomStorageDevice{
Enabled: false,
},
"ide1": vms.CustomStorageDevice{
Enabled: false,
},
"ide2": vms.CustomStorageDevice{
Enabled: false,
},
"ide3": vms.CustomStorageDevice{
Enabled: false,
},
},
}
var del []string
resource := VM()
// Retrieve the entire configuration as we need to process certain values.
vmConfig, e := vmAPI.GetVM(ctx)
if e != nil {
return diag.FromErr(e)
}
// Prepare the new primitive configuration values.
if d.HasChange(mkResourceVirtualEnvironmentVMACPI) {
acpi := types2.CustomBool(d.Get(mkResourceVirtualEnvironmentVMACPI).(bool))
updateBody.ACPI = &acpi
rebootRequired = true
}
if d.HasChange(mkResourceVirtualEnvironmentVMKVMArguments) {
kvmArguments := d.Get(mkResourceVirtualEnvironmentVMKVMArguments).(string)
updateBody.KVMArguments = &kvmArguments
rebootRequired = true
}
if d.HasChange(mkResourceVirtualEnvironmentVMBIOS) {
bios := d.Get(mkResourceVirtualEnvironmentVMBIOS).(string)
updateBody.BIOS = &bios
rebootRequired = true
}
if d.HasChange(mkResourceVirtualEnvironmentVMDescription) {
description := d.Get(mkResourceVirtualEnvironmentVMDescription).(string)
updateBody.Description = &description
}
if d.HasChange(mkResourceVirtualEnvironmentVMOnBoot) {
startOnBoot := types2.CustomBool(d.Get(mkResourceVirtualEnvironmentVMOnBoot).(bool))
updateBody.StartOnBoot = &startOnBoot
}
if d.HasChange(mkResourceVirtualEnvironmentVMTags) {
tagString := vmGetTagsString(d)
updateBody.Tags = &tagString
}
if d.HasChange(mkResourceVirtualEnvironmentVMKeyboardLayout) {
keyboardLayout := d.Get(mkResourceVirtualEnvironmentVMKeyboardLayout).(string)
updateBody.KeyboardLayout = &keyboardLayout
rebootRequired = true
}
if d.HasChange(mkResourceVirtualEnvironmentVMMachine) {
machine := d.Get(mkResourceVirtualEnvironmentVMMachine).(string)
updateBody.Machine = &machine
rebootRequired = true
}
name := d.Get(mkResourceVirtualEnvironmentVMName).(string)
if name == "" {
del = append(del, "name")
} else {
updateBody.Name = &name
}
if d.HasChange(mkResourceVirtualEnvironmentVMTabletDevice) {
tabletDevice := types2.CustomBool(d.Get(mkResourceVirtualEnvironmentVMTabletDevice).(bool))
updateBody.TabletDeviceEnabled = &tabletDevice
rebootRequired = true
}
template := types2.CustomBool(d.Get(mkResourceVirtualEnvironmentVMTemplate).(bool))
if d.HasChange(mkResourceVirtualEnvironmentVMTemplate) {
updateBody.Template = &template
rebootRequired = true
}
// Prepare the new agent configuration.
if d.HasChange(mkResourceVirtualEnvironmentVMAgent) {
agentBlock, err := structure.GetSchemaBlock(
resource,
d,
[]string{mkResourceVirtualEnvironmentVMAgent},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
agentEnabled := types2.CustomBool(
agentBlock[mkResourceVirtualEnvironmentVMAgentEnabled].(bool),
)
agentTrim := types2.CustomBool(agentBlock[mkResourceVirtualEnvironmentVMAgentTrim].(bool))
agentType := agentBlock[mkResourceVirtualEnvironmentVMAgentType].(string)
updateBody.Agent = &vms.CustomAgent{
Enabled: &agentEnabled,
TrimClonedDisks: &agentTrim,
Type: &agentType,
}
rebootRequired = true
}
// Prepare the new audio devices.
if d.HasChange(mkResourceVirtualEnvironmentVMAudioDevice) {
updateBody.AudioDevices = vmGetAudioDeviceList(d)
for i := 0; i < len(updateBody.AudioDevices); i++ {
if !updateBody.AudioDevices[i].Enabled {
del = append(del, fmt.Sprintf("audio%d", i))
}
}
for i := len(updateBody.AudioDevices); i < maxResourceVirtualEnvironmentVMAudioDevices; i++ {
del = append(del, fmt.Sprintf("audio%d", i))
}
rebootRequired = true
}
// Prepare the new boot configuration.
if d.HasChange(mkResourceVirtualEnvironmentVMBootOrder) {
bootOrder := d.Get(mkResourceVirtualEnvironmentVMBootOrder).([]interface{})
bootOrderConverted := make([]string, len(bootOrder))
for i, device := range bootOrder {
bootOrderConverted[i] = device.(string)
}
updateBody.Boot = &vms.CustomBoot{
Order: &bootOrderConverted,
}
rebootRequired = true
}
// Prepare the new CD-ROM configuration.
if d.HasChange(mkResourceVirtualEnvironmentVMCDROM) {
cdromBlock, err := structure.GetSchemaBlock(
resource,
d,
[]string{mkResourceVirtualEnvironmentVMCDROM},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
cdromEnabled := cdromBlock[mkResourceVirtualEnvironmentVMCDROMEnabled].(bool)
cdromFileID := cdromBlock[mkResourceVirtualEnvironmentVMCDROMFileID].(string)
cdromInterface := cdromBlock[mkResourceVirtualEnvironmentVMCDROMInterface].(string)
old, _ := d.GetChange(mkResourceVirtualEnvironmentVMCDROM)
if len(old.([]interface{})) > 0 {
oldList := old.([]interface{})[0]
oldBlock := oldList.(map[string]interface{})
// If the interface is not set, use the default, for backward compatibility.
oldInterface, ok := oldBlock[mkResourceVirtualEnvironmentVMCDROMInterface].(string)
if !ok || oldInterface == "" {
oldInterface = dvResourceVirtualEnvironmentVMCDROMInterface
}
if oldInterface != cdromInterface {
del = append(del, oldInterface)
}
}
if !cdromEnabled && cdromFileID == "" {
del = append(del, cdromInterface)
}
if cdromFileID == "" {
cdromFileID = "cdrom"
}
cdromMedia := "cdrom"
updateBody.IDEDevices[cdromInterface] = vms.CustomStorageDevice{
Enabled: cdromEnabled,
FileVolume: cdromFileID,
Media: &cdromMedia,
}
}
// Prepare the new CPU configuration.
if d.HasChange(mkResourceVirtualEnvironmentVMCPU) {
cpuBlock, err := structure.GetSchemaBlock(
resource,
d,
[]string{mkResourceVirtualEnvironmentVMCPU},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
cpuArchitecture := cpuBlock[mkResourceVirtualEnvironmentVMCPUArchitecture].(string)
cpuCores := cpuBlock[mkResourceVirtualEnvironmentVMCPUCores].(int)
cpuFlags := cpuBlock[mkResourceVirtualEnvironmentVMCPUFlags].([]interface{})
cpuHotplugged := cpuBlock[mkResourceVirtualEnvironmentVMCPUHotplugged].(int)
cpuNUMA := types2.CustomBool(cpuBlock[mkResourceVirtualEnvironmentVMCPUNUMA].(bool))
cpuSockets := cpuBlock[mkResourceVirtualEnvironmentVMCPUSockets].(int)
cpuType := cpuBlock[mkResourceVirtualEnvironmentVMCPUType].(string)
cpuUnits := cpuBlock[mkResourceVirtualEnvironmentVMCPUUnits].(int)
// Only the root account is allowed to change the CPU architecture, which makes this check necessary.
if api.API().IsRootTicket() ||
cpuArchitecture != dvResourceVirtualEnvironmentVMCPUArchitecture {
updateBody.CPUArchitecture = &cpuArchitecture
}
updateBody.CPUCores = &cpuCores
updateBody.CPUSockets = &cpuSockets
updateBody.CPUUnits = &cpuUnits
updateBody.NUMAEnabled = &cpuNUMA
if cpuHotplugged > 0 {
updateBody.VirtualCPUCount = &cpuHotplugged
} else {
del = append(del, "vcpus")
}
cpuFlagsConverted := make([]string, len(cpuFlags))
for fi, flag := range cpuFlags {
cpuFlagsConverted[fi] = flag.(string)
}
updateBody.CPUEmulation = &vms.CustomCPUEmulation{
Flags: &cpuFlagsConverted,
Type: cpuType,
}
rebootRequired = true
}
// Prepare the new disk device configuration.
if d.HasChange(mkResourceVirtualEnvironmentVMDisk) {
diskDeviceObjects, err := vmGetDiskDeviceObjects(d, nil)
if err != nil {
return diag.FromErr(err)
}
diskDeviceInfo := getDiskInfo(vmConfig, d)
for prefix, diskMap := range diskDeviceObjects {
if diskMap == nil {
continue
}
for key, value := range diskMap {
if diskDeviceInfo[key] == nil {
return diag.Errorf("missing %s device %s", prefix, key)
}
tmp := *diskDeviceInfo[key]
tmp.BurstableReadSpeedMbps = value.BurstableReadSpeedMbps
tmp.BurstableWriteSpeedMbps = value.BurstableWriteSpeedMbps
tmp.MaxReadSpeedMbps = value.MaxReadSpeedMbps
tmp.MaxWriteSpeedMbps = value.MaxWriteSpeedMbps
tmp.Cache = value.Cache
switch prefix {
case "virtio":
{
if updateBody.VirtualIODevices == nil {
updateBody.VirtualIODevices = vms.CustomStorageDevices{}
}
updateBody.VirtualIODevices[key] = tmp
}
case "sata":
{
if updateBody.SATADevices == nil {
updateBody.SATADevices = vms.CustomStorageDevices{}
}
updateBody.SATADevices[key] = tmp
}
case "scsi":
{
if updateBody.SCSIDevices == nil {
updateBody.SCSIDevices = vms.CustomStorageDevices{}
}
updateBody.SCSIDevices[key] = tmp
}
case "ide":
{
// Investigate whether to support IDE mapping.
}
default:
return diag.Errorf("device prefix %s not supported", prefix)
}
}
}
}
// Prepare the new efi disk configuration.
if d.HasChange(mkResourceVirtualEnvironmentVMEFIDisk) {
efiDisk := vmGetEfiDisk(d, nil)
updateBody.EFIDisk = efiDisk
rebootRequired = true
}
// Prepare the new cloud-init configuration.
stoppedBeforeUpdate := false
if d.HasChange(mkResourceVirtualEnvironmentVMInitialization) {
initializationConfig := vmGetCloudInitConfig(d)
updateBody.CloudInitConfig = initializationConfig
if updateBody.CloudInitConfig != nil {
var fileVolume string
initialization := d.Get(mkResourceVirtualEnvironmentVMInitialization).([]interface{})
initializationBlock := initialization[0].(map[string]interface{})
initializationDatastoreID := initializationBlock[mkResourceVirtualEnvironmentVMInitializationDatastoreID].(string)
initializationInterface := initializationBlock[mkResourceVirtualEnvironmentVMInitializationInterface].(string)
cdromMedia := "cdrom"
existingInterface := findExistingCloudInitDrive(vmConfig, vmID, "")
if initializationInterface == "" && existingInterface == "" {
initializationInterface = "ide2"
} else if initializationInterface == "" {
initializationInterface = existingInterface
}
mustMove := existingInterface != "" && initializationInterface != existingInterface
if mustMove {
tflog.Debug(ctx, fmt.Sprintf("CloudInit must be moved from %s to %s", existingInterface, initializationInterface))
}
oldInit, _ := d.GetChange(mkResourceVirtualEnvironmentVMInitialization)
oldInitBlock := oldInit.([]interface{})[0].(map[string]interface{})
prevDatastoreID := oldInitBlock[mkResourceVirtualEnvironmentVMInitializationDatastoreID].(string)
mustChangeDatastore := prevDatastoreID != initializationDatastoreID
if mustChangeDatastore {
tflog.Debug(ctx, fmt.Sprintf("CloudInit must be moved from datastore %s to datastore %s",
prevDatastoreID, initializationDatastoreID))
}
if mustMove || mustChangeDatastore || existingInterface == "" {
// CloudInit must be moved, either from a device to another or from a datastore
// to another (or both). This requires the VM to be stopped.
if err := vmShutdown(ctx, vmAPI, d); err != nil {
return err
}
if err := deleteIdeDrives(ctx, vmAPI, initializationInterface, existingInterface); err != nil {
return err
}
stoppedBeforeUpdate = true
fileVolume = fmt.Sprintf("%s:cloudinit", initializationDatastoreID)
} else {
ideDevice := getIdeDevice(vmConfig, existingInterface)
fileVolume = ideDevice.FileVolume
}
updateBody.IDEDevices[initializationInterface] = vms.CustomStorageDevice{
Enabled: true,
FileVolume: fileVolume,
Media: &cdromMedia,
}
}
rebootRequired = true
}
// Prepare the new hostpci devices configuration.
if d.HasChange(mkResourceVirtualEnvironmentVMHostPCI) {
updateBody.PCIDevices = vmGetHostPCIDeviceObjects(d)
for i := len(updateBody.PCIDevices); i < maxResourceVirtualEnvironmentVMHostPCIDevices; i++ {
del = append(del, fmt.Sprintf("hostpci%d", i))
}
rebootRequired = true
}
// Prepare the new memory configuration.
if d.HasChange(mkResourceVirtualEnvironmentVMMemory) {
memoryBlock, err := structure.GetSchemaBlock(
resource,
d,
[]string{mkResourceVirtualEnvironmentVMMemory},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
memoryDedicated := memoryBlock[mkResourceVirtualEnvironmentVMMemoryDedicated].(int)
memoryFloating := memoryBlock[mkResourceVirtualEnvironmentVMMemoryFloating].(int)
memoryShared := memoryBlock[mkResourceVirtualEnvironmentVMMemoryShared].(int)
updateBody.DedicatedMemory = &memoryDedicated
updateBody.FloatingMemory = &memoryFloating
if memoryShared > 0 {
memorySharedName := fmt.Sprintf("vm-%d-ivshmem", vmID)
updateBody.SharedMemory = &vms.CustomSharedMemory{
Name: &memorySharedName,
Size: memoryShared,
}
}
rebootRequired = true
}
// Prepare the new network device configuration.
if d.HasChange(mkResourceVirtualEnvironmentVMNetworkDevice) {
updateBody.NetworkDevices = vmGetNetworkDeviceObjects(d)
for i := 0; i < len(updateBody.NetworkDevices); i++ {
if !updateBody.NetworkDevices[i].Enabled {
del = append(del, fmt.Sprintf("net%d", i))
}
}
for i := len(updateBody.NetworkDevices); i < maxResourceVirtualEnvironmentVMNetworkDevices; i++ {
del = append(del, fmt.Sprintf("net%d", i))
}
rebootRequired = true
}
// Prepare the new operating system configuration.
if d.HasChange(mkResourceVirtualEnvironmentVMOperatingSystem) {
operatingSystem, err := structure.GetSchemaBlock(
resource,
d,
[]string{mkResourceVirtualEnvironmentVMOperatingSystem},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
operatingSystemType := operatingSystem[mkResourceVirtualEnvironmentVMOperatingSystemType].(string)
updateBody.OSType = &operatingSystemType
rebootRequired = true
}
// Prepare the new serial devices.
if d.HasChange(mkResourceVirtualEnvironmentVMSerialDevice) {
updateBody.SerialDevices = vmGetSerialDeviceList(d)
for i := len(updateBody.SerialDevices); i < maxResourceVirtualEnvironmentVMSerialDevices; i++ {
del = append(del, fmt.Sprintf("serial%d", i))
}
rebootRequired = true
}
if d.HasChange(mkResourceVirtualEnvironmentVMSMBIOS) {
updateBody.SMBIOS = vmGetSMBIOS(d)
if updateBody.SMBIOS == nil {
del = append(del, "smbios1")
}
}
if d.HasChange(mkResourceVirtualEnvironmentVMStartup) {
updateBody.StartupOrder = vmGetStartupOrder(d)
if updateBody.StartupOrder == nil {
del = append(del, "startup")
}
}
// Prepare the new VGA configuration.
if d.HasChange(mkResourceVirtualEnvironmentVMVGA) {
updateBody.VGADevice, e = vmGetVGADeviceObject(d)
if e != nil {
return diag.FromErr(e)
}
rebootRequired = true
}
// Prepare the new SCSI hardware type
if d.HasChange(mkResourceVirtualEnvironmentVMSCSIHardware) {
scsiHardware := d.Get(mkResourceVirtualEnvironmentVMSCSIHardware).(string)
updateBody.SCSIHardware = &scsiHardware
rebootRequired = true
}
// Update the configuration now that everything has been prepared.
updateBody.Delete = del
e = vmAPI.UpdateVM(ctx, updateBody)
if e != nil {
return diag.FromErr(e)
}
// Determine if the state of the virtual machine state needs to be changed.
//nolint: nestif
if (d.HasChange(mkResourceVirtualEnvironmentVMStarted) || stoppedBeforeUpdate) && !bool(template) {
started := d.Get(mkResourceVirtualEnvironmentVMStarted).(bool)
if started {
if diags := vmStart(ctx, vmAPI, d); diags != nil {
return diags
}
} else {
if e := vmShutdown(ctx, vmAPI, d); e != nil {
return e
}
rebootRequired = false
}
}
// Change the disk locations and/or sizes, if necessary.
return vmUpdateDiskLocationAndSize(
ctx,
d,
m,
!bool(template) && rebootRequired,
)
}
func vmUpdateDiskLocationAndSize(
ctx context.Context,
d *schema.ResourceData,
m interface{},
reboot bool,
) diag.Diagnostics {
config := m.(proxmoxtf.ProviderConfiguration)
api, err := config.GetClient()
if err != nil {
return diag.FromErr(err)
}
nodeName := d.Get(mkResourceVirtualEnvironmentVMNodeName).(string)
started := d.Get(mkResourceVirtualEnvironmentVMStarted).(bool)
template := d.Get(mkResourceVirtualEnvironmentVMTemplate).(bool)
vmID, err := strconv.Atoi(d.Id())
if err != nil {
return diag.FromErr(err)
}
vmAPI := api.Node(nodeName).VM(vmID)
// Determine if any of the disks are changing location and/or size, and initiate the necessary actions.
if d.HasChange(mkResourceVirtualEnvironmentVMDisk) {
diskOld, diskNew := d.GetChange(mkResourceVirtualEnvironmentVMDisk)
diskOldEntries, err := vmGetDiskDeviceObjects(
d,
diskOld.([]interface{}),
)
if err != nil {
return diag.FromErr(err)
}
diskNewEntries, err := vmGetDiskDeviceObjects(
d,
diskNew.([]interface{}),
)
if err != nil {
return diag.FromErr(err)
}
// Add efidisk if it has changes
if d.HasChange(mkResourceVirtualEnvironmentVMEFIDisk) {
diskOld, diskNew := d.GetChange(mkResourceVirtualEnvironmentVMEFIDisk)
oldEfiDisk, e := vmGetEfiDiskAsStorageDevice(d, diskOld.([]interface{}))
if e != nil {
return diag.FromErr(e)
}
newEfiDisk, e := vmGetEfiDiskAsStorageDevice(d, diskNew.([]interface{}))
if e != nil {
return diag.FromErr(e)
}
if oldEfiDisk != nil {
baseDiskInterface := diskDigitPrefix(*oldEfiDisk.Interface)
diskOldEntries[baseDiskInterface][*oldEfiDisk.Interface] = *oldEfiDisk
}
if newEfiDisk != nil {
baseDiskInterface := diskDigitPrefix(*newEfiDisk.Interface)
diskNewEntries[baseDiskInterface][*newEfiDisk.Interface] = *newEfiDisk
}
if oldEfiDisk != nil && newEfiDisk != nil && oldEfiDisk.Size != newEfiDisk.Size {
return diag.Errorf(
"resizing of efidisks is not supported.",
)
}
}
var diskMoveBodies []*vms.MoveDiskRequestBody
var diskResizeBodies []*vms.ResizeDiskRequestBody
shutdownForDisksRequired := false
for prefix, diskMap := range diskOldEntries {
for oldKey, oldDisk := range diskMap {
if _, present := diskNewEntries[prefix][oldKey]; !present {
return diag.Errorf(
"deletion of disks not supported. Please delete disk by hand. Old Interface was %s",
*oldDisk.Interface,
)
}
if *oldDisk.ID != *diskNewEntries[prefix][oldKey].ID {
deleteOriginalDisk := types2.CustomBool(true)
diskMoveBodies = append(
diskMoveBodies,
&vms.MoveDiskRequestBody{
DeleteOriginalDisk: &deleteOriginalDisk,
Disk: *oldDisk.Interface,
TargetStorage: *diskNewEntries[prefix][oldKey].ID,
},
)
// Cannot be done while VM is running.
shutdownForDisksRequired = true
}
if *oldDisk.SizeInt < *diskNewEntries[prefix][oldKey].SizeInt {
diskResizeBodies = append(
diskResizeBodies,
&vms.ResizeDiskRequestBody{
Disk: *oldDisk.Interface,
Size: *diskNewEntries[prefix][oldKey].Size,
},
)
}
}
}
if shutdownForDisksRequired && !template {
if e := vmShutdown(ctx, vmAPI, d); e != nil {
return e
}
}
for _, reqBody := range diskMoveBodies {
moveDiskTimeout := d.Get(mkResourceVirtualEnvironmentVMTimeoutMoveDisk).(int)
err = vmAPI.MoveVMDisk(ctx, reqBody, moveDiskTimeout)
if err != nil {
return diag.FromErr(err)
}
}
for _, reqBody := range diskResizeBodies {
err = vmAPI.ResizeVMDisk(ctx, reqBody)
if err != nil {
return diag.FromErr(err)
}
}
if shutdownForDisksRequired && started && !template {
if diags := vmStart(ctx, vmAPI, d); diags != nil {
return diags
}
// This concludes an equivalent of a reboot, avoid doing another.
reboot = false
}
}
// Perform a regular reboot in case it's necessary and haven't already been done.
if reboot {
vmStatus, err := vmAPI.GetVMStatus(ctx)
if err != nil {
return diag.FromErr(err)
}
if vmStatus.Status != "stopped" {
rebootTimeout := d.Get(mkResourceVirtualEnvironmentVMTimeoutReboot).(int)
err := vmAPI.RebootVM(
ctx,
&vms.RebootRequestBody{
Timeout: &rebootTimeout,
},
rebootTimeout+30,
)
if err != nil {
return diag.FromErr(err)
}
}
}
return vmRead(ctx, d, m)
}
func vmDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
config := m.(proxmoxtf.ProviderConfiguration)
api, err := config.GetClient()
if err != nil {
return diag.FromErr(err)
}
nodeName := d.Get(mkResourceVirtualEnvironmentVMNodeName).(string)
vmID, err := strconv.Atoi(d.Id())
if err != nil {
return diag.FromErr(err)
}
vmAPI := api.Node(nodeName).VM(vmID)
// Shut down the virtual machine before deleting it.
status, err := vmAPI.GetVMStatus(ctx)
if err != nil {
return diag.FromErr(err)
}
if status.Status != "stopped" {
if e := vmShutdown(ctx, vmAPI, d); e != nil {
return e
}
}
err = vmAPI.DeleteVM(ctx)
if err != nil {
if strings.Contains(err.Error(), "HTTP 404") ||
(strings.Contains(err.Error(), "HTTP 500") && strings.Contains(err.Error(), "does not exist")) {
d.SetId("")
return nil
}
return diag.FromErr(err)
}
// Wait for the state to become unavailable as that clearly indicates the destruction of the VM.
err = vmAPI.WaitForVMState(ctx, "", 60, 2)
if err == nil {
return diag.Errorf("failed to delete VM \"%d\"", vmID)
}
d.SetId("")
return nil
}
func diskDigitPrefix(s string) string {
for i, r := range s {
if unicode.IsDigit(r) {
return s[:i]
}
}
return s
}
func getDiskInfo(resp *vms.GetResponseData, d *schema.ResourceData) map[string]*vms.CustomStorageDevice {
currentDisk := d.Get(mkResourceVirtualEnvironmentVMDisk)
currentDiskList := currentDisk.([]interface{})
currentDiskMap := map[string]map[string]interface{}{}
for _, v := range currentDiskList {
diskMap := v.(map[string]interface{})
diskInterface := diskMap[mkResourceVirtualEnvironmentVMDiskInterface].(string)
currentDiskMap[diskInterface] = diskMap
}
storageDevices := map[string]*vms.CustomStorageDevice{}
storageDevices["ide0"] = resp.IDEDevice0
storageDevices["ide1"] = resp.IDEDevice1
storageDevices["ide2"] = resp.IDEDevice2
storageDevices["ide3"] = resp.IDEDevice3
storageDevices["sata0"] = resp.SATADevice0
storageDevices["sata1"] = resp.SATADevice1
storageDevices["sata2"] = resp.SATADevice2
storageDevices["sata3"] = resp.SATADevice3
storageDevices["sata4"] = resp.SATADevice4
storageDevices["sata5"] = resp.SATADevice5
storageDevices["scsi0"] = resp.SCSIDevice0
storageDevices["scsi1"] = resp.SCSIDevice1
storageDevices["scsi2"] = resp.SCSIDevice2
storageDevices["scsi3"] = resp.SCSIDevice3
storageDevices["scsi4"] = resp.SCSIDevice4
storageDevices["scsi5"] = resp.SCSIDevice5
storageDevices["scsi6"] = resp.SCSIDevice6
storageDevices["scsi7"] = resp.SCSIDevice7
storageDevices["scsi8"] = resp.SCSIDevice8
storageDevices["scsi9"] = resp.SCSIDevice9
storageDevices["scsi10"] = resp.SCSIDevice10
storageDevices["scsi11"] = resp.SCSIDevice11
storageDevices["scsi12"] = resp.SCSIDevice12
storageDevices["scsi13"] = resp.SCSIDevice13
storageDevices["virtio0"] = resp.VirtualIODevice0
storageDevices["virtio1"] = resp.VirtualIODevice1
storageDevices["virtio2"] = resp.VirtualIODevice2
storageDevices["virtio3"] = resp.VirtualIODevice3
storageDevices["virtio4"] = resp.VirtualIODevice4
storageDevices["virtio5"] = resp.VirtualIODevice5
storageDevices["virtio6"] = resp.VirtualIODevice6
storageDevices["virtio7"] = resp.VirtualIODevice7
storageDevices["virtio8"] = resp.VirtualIODevice8
storageDevices["virtio9"] = resp.VirtualIODevice9
storageDevices["virtio10"] = resp.VirtualIODevice10
storageDevices["virtio11"] = resp.VirtualIODevice11
storageDevices["virtio12"] = resp.VirtualIODevice12
storageDevices["virtio13"] = resp.VirtualIODevice13
storageDevices["virtio14"] = resp.VirtualIODevice14
storageDevices["virtio15"] = resp.VirtualIODevice15
for k, v := range storageDevices {
if v != nil {
if currentDiskMap[k] != nil {
if currentDiskMap[k][mkResourceVirtualEnvironmentVMDiskFileID] != nil {
fileID := currentDiskMap[k][mkResourceVirtualEnvironmentVMDiskFileID].(string)
v.FileID = &fileID
}
}
// defensive copy of the loop variable
iface := k
v.Interface = &iface
}
}
return storageDevices
}
// getDiskDatastores returns a list of the used datastores in a VM.
func getDiskDatastores(vm *vms.GetResponseData, d *schema.ResourceData) []string {
storageDevices := getDiskInfo(vm, d)
datastoresSet := map[string]int{}
for _, diskInfo := range storageDevices {
// Ignore empty storage devices and storage devices (like ide) which may not have any media mounted
if diskInfo == nil || diskInfo.FileVolume == "none" {
continue
}
fileIDParts := strings.Split(diskInfo.FileVolume, ":")
datastoresSet[fileIDParts[0]] = 1
}
if vm.EFIDisk != nil {
fileIDParts := strings.Split(vm.EFIDisk.FileVolume, ":")
datastoresSet[fileIDParts[0]] = 1
}
datastores := []string{}
for datastore := range datastoresSet {
datastores = append(datastores, datastore)
}
return datastores
}
func getPCIInfo(resp *vms.GetResponseData, _ *schema.ResourceData) map[string]*vms.CustomPCIDevice {
pciDevices := map[string]*vms.CustomPCIDevice{}
pciDevices["hostpci0"] = resp.PCIDevice0
pciDevices["hostpci1"] = resp.PCIDevice1
pciDevices["hostpci2"] = resp.PCIDevice2
pciDevices["hostpci3"] = resp.PCIDevice3
return pciDevices
}
func parseImportIDWithNodeName(id string) (string, string, error) {
nodeName, id, found := strings.Cut(id, "/")
if !found {
return "", "", fmt.Errorf("unexpected format of ID (%s), expected node/id", id)
}
return nodeName, id, nil
}