mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-07-09 15:25:01 +00:00
4762 lines
143 KiB
Go
4762 lines
143 KiB
Go
/*
|
|
* 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 vm
|
|
|
|
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/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"
|
|
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
|
"github.com/bpg/terraform-provider-proxmox/proxmoxtf"
|
|
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/structure"
|
|
"github.com/bpg/terraform-provider-proxmox/utils"
|
|
)
|
|
|
|
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.IsCloudInitDrive(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 {
|
|
var diags diag.Diagnostics
|
|
|
|
tflog.Debug(ctx, "Starting VM")
|
|
|
|
startVMTimeout := d.Get(mkResourceVirtualEnvironmentVMTimeoutStartVM).(int)
|
|
|
|
log, e := vmAPI.StartVM(ctx, startVMTimeout)
|
|
if e != nil {
|
|
return append(diags, diag.FromErr(e)...)
|
|
}
|
|
|
|
if len(log) > 0 {
|
|
lines := "\n\t| " + strings.Join(log, "\n\t| ")
|
|
diags = append(diags, diag.Diagnostic{
|
|
Severity: diag.Warning,
|
|
Summary: fmt.Sprintf("the VM startup task finished with a warning, task log:\n%s", lines),
|
|
})
|
|
}
|
|
|
|
return append(diags, 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 := types.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))
|
|
}
|
|
|
|
// Forcefully stop the VM, then wait for it to actually stop.
|
|
func vmStop(ctx context.Context, vmAPI *vms.Client, d *schema.ResourceData) diag.Diagnostics {
|
|
tflog.Debug(ctx, "Stopping VM")
|
|
|
|
stopTimeout := d.Get(mkResourceVirtualEnvironmentVMTimeoutStopVM).(int)
|
|
|
|
e := vmAPI.StopVM(ctx, stopTimeout+30)
|
|
if e != nil {
|
|
return diag.FromErr(e)
|
|
}
|
|
|
|
return diag.FromErr(vmAPI.WaitForVMState(ctx, "stopped", stopTimeout, 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 := types.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).Storage(datastore).GetDatastoreStatus(ctx)
|
|
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 := types.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 := types.CustomBool(d.Get(mkResourceVirtualEnvironmentVMACPI).(bool))
|
|
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{})
|
|
hostUSB := d.Get(mkResourceVirtualEnvironmentVMHostUSB).([]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 := types.CustomBool(d.Get(mkResourceVirtualEnvironmentVMOnBoot).(bool))
|
|
tabletDevice := types.CustomBool(d.Get(mkResourceVirtualEnvironmentVMTabletDevice).(bool))
|
|
template := types.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
|
|
}
|
|
|
|
createAgent(d, updateBody)
|
|
|
|
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)
|
|
cpuLimit := cpuBlock[mkResourceVirtualEnvironmentVMCPULimit].(int)
|
|
cpuNUMA := types.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 cpuLimit > 0 {
|
|
updateBody.CPULimit = &cpuLimit
|
|
}
|
|
}
|
|
|
|
vmConfig, err := vmAPI.GetVM(ctx)
|
|
if err != nil {
|
|
return diag.FromErr(err)
|
|
}
|
|
|
|
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)
|
|
|
|
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(hostUSB) > 0 {
|
|
updateBody.USBDevices = vmGetHostUSBDeviceObjects(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.SMBIOS = vmGetSMBIOS(d)
|
|
|
|
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
|
|
}
|
|
|
|
hookScript := d.Get(mkResourceVirtualEnvironmentVMHookScriptFileID).(string)
|
|
currentHookScript := vmConfig.HookScript
|
|
|
|
if len(hookScript) > 0 {
|
|
updateBody.HookScript = &hookScript
|
|
} else if currentHookScript != nil {
|
|
del = append(del, "hookscript")
|
|
}
|
|
|
|
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 := int64(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 := types.CustomBool(true)
|
|
|
|
diskMoveBody := &vms.MoveDiskRequestBody{
|
|
DeleteOriginalDisk: &deleteOriginalDisk,
|
|
Disk: diskInterface,
|
|
TargetStorage: dataStoreID,
|
|
}
|
|
|
|
diskResizeBody := &vms.ResizeDiskRequestBody{
|
|
Disk: diskInterface,
|
|
Size: types.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 := types.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)
|
|
}
|
|
}
|
|
}
|
|
|
|
tpmState := d.Get(mkResourceVirtualEnvironmentVMTPMState).([]interface{})
|
|
tpmStateInfo := vmGetTPMState(d, nil) // from the resource config
|
|
|
|
for i := range tpmState {
|
|
diskBlock := tpmState[i].(map[string]interface{})
|
|
diskInterface := "tpmstate0"
|
|
dataStoreID := diskBlock[mkResourceVirtualEnvironmentVMTPMStateDatastoreID].(string)
|
|
|
|
currentTPMState := vmConfig.TPMState
|
|
configuredTPMStateInfo := tpmStateInfo
|
|
|
|
if currentTPMState == nil {
|
|
diskUpdateBody := &vms.UpdateRequestBody{}
|
|
|
|
diskUpdateBody.TPMState = configuredTPMStateInfo
|
|
|
|
e = vmAPI.UpdateVM(ctx, diskUpdateBody)
|
|
if e != nil {
|
|
return diag.FromErr(e)
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
deleteOriginalDisk := types.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 := types.CustomBool(d.Get(mkResourceVirtualEnvironmentVMACPI).(bool))
|
|
|
|
customAgent, err := customAgent(d, resource)
|
|
if err != nil {
|
|
return diag.FromErr(err)
|
|
}
|
|
|
|
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)
|
|
cpuLimit := cpuBlock[mkResourceVirtualEnvironmentVMCPULimit].(int)
|
|
cpuSockets := cpuBlock[mkResourceVirtualEnvironmentVMCPUSockets].(int)
|
|
cpuNUMA := types.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 := types.CustomBool(block[mkResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys].(bool))
|
|
|
|
if fileFormat == "" {
|
|
fileFormat = dvResourceVirtualEnvironmentVMEFIDiskFileFormat
|
|
}
|
|
|
|
efiDisk = &vms.CustomEFIDisk{
|
|
Type: &efiType,
|
|
FileVolume: fmt.Sprintf("%s:1", datastoreID),
|
|
Format: &fileFormat,
|
|
PreEnrolledKeys: &preEnrolledKeys,
|
|
}
|
|
}
|
|
|
|
var tpmState *vms.CustomTPMState
|
|
|
|
tpmStateBlock := d.Get(mkResourceVirtualEnvironmentVMTPMState).([]interface{})
|
|
if len(tpmStateBlock) > 0 {
|
|
block := tpmStateBlock[0].(map[string]interface{})
|
|
|
|
datastoreID, _ := block[mkResourceVirtualEnvironmentVMTPMStateDatastoreID].(string)
|
|
version, _ := block[mkResourceVirtualEnvironmentVMTPMStateVersion].(string)
|
|
|
|
if version == "" {
|
|
version = dvResourceVirtualEnvironmentVMTPMStateVersion
|
|
}
|
|
|
|
tpmState = &vms.CustomTPMState{
|
|
FileVolume: fmt.Sprintf("%s:1", datastoreID),
|
|
Version: &version,
|
|
}
|
|
}
|
|
|
|
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)
|
|
|
|
usbDeviceObjects := vmGetHostUSBDeviceObjects(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 := types.CustomBool(d.Get(mkResourceVirtualEnvironmentVMOnBoot).(bool))
|
|
tabletDevice := types.CustomBool(d.Get(mkResourceVirtualEnvironmentVMTabletDevice).(bool))
|
|
template := types.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: customAgent,
|
|
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,
|
|
TPMState: tpmState,
|
|
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,
|
|
USBDevices: usbDeviceObjects,
|
|
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 cpuLimit > 0 {
|
|
createBody.CPULimit = &cpuLimit
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
hookScript := d.Get(mkResourceVirtualEnvironmentVMHookScriptFileID).(string)
|
|
if len(hookScript) > 0 {
|
|
createBody.HookScript = &hookScript
|
|
}
|
|
|
|
createTimeout := d.Get(mkResourceVirtualEnvironmentVMTimeoutClone).(int)
|
|
|
|
err = api.Node(nodeName).VM(0).CreateVM(ctx, createBody, createTimeout)
|
|
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 := types.CustomBool(block[mkResourceVirtualEnvironmentVMDiskIOThread].(bool))
|
|
ssd := types.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`,
|
|
`try_sudo(){ if [ $(sudo -n echo tfpve 2>&1 | grep "tfpve" | wc -l) -gt 0 ]; then sudo $1; else $1; fi }`,
|
|
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=$(try_sudo "pvesm path $file_id")`,
|
|
`imported_disk="$(try_sudo "qm importdisk $vm_id $source_image $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}"`,
|
|
`try_sudo "qm set $vm_id -${disk_interface} $disk_id"`,
|
|
`try_sudo "qm resize $vm_id ${disk_interface} ${disk_size}G"`,
|
|
)
|
|
|
|
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)
|
|
|
|
out, err := api.SSH().ExecuteNodeCommands(ctx, nodeName, commands)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "pvesm: not found") {
|
|
return diag.Errorf("The configured SSH user '%s' does not have the required permissions to import disks. "+
|
|
"Make sure `sudo` is installated and the user is a member of sudoers.", api.SSH().Username())
|
|
}
|
|
|
|
return diag.FromErr(err)
|
|
}
|
|
|
|
tflog.Debug(ctx, "vmCreateCustomDisks", map[string]interface{}{
|
|
"output": string(out),
|
|
})
|
|
}
|
|
|
|
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))
|
|
}
|
|
|
|
//nolint:lll
|
|
func vmGetCloudInitConfig(d *schema.ResourceData) *vms.CustomCloudInitConfig {
|
|
initialization := d.Get(mkResourceVirtualEnvironmentVMInitialization).([]interface{})
|
|
|
|
if len(initialization) == 0 || initialization[0] == nil {
|
|
return nil
|
|
}
|
|
|
|
var initializationConfig *vms.CustomCloudInitConfig
|
|
|
|
initializationBlock := initialization[0].(map[string]interface{})
|
|
initializationConfig = &vms.CustomCloudInitConfig{}
|
|
initializationDNS := initializationBlock[mkResourceVirtualEnvironmentVMInitializationDNS].([]interface{})
|
|
|
|
if len(initializationDNS) > 0 && initializationDNS[0] != nil {
|
|
initializationDNSBlock := initializationDNS[0].(map[string]interface{})
|
|
domain := initializationDNSBlock[mkResourceVirtualEnvironmentVMInitializationDNSDomain].(string)
|
|
|
|
if domain != "" {
|
|
initializationConfig.SearchDomain = &domain
|
|
}
|
|
|
|
servers := initializationDNSBlock[mkResourceVirtualEnvironmentVMInitializationDNSServers].([]interface{})
|
|
deprecatedServer := initializationDNSBlock[mkResourceVirtualEnvironmentVMInitializationDNSServer].(string)
|
|
|
|
if len(servers) > 0 {
|
|
nameserver := strings.Join(utils.ConvertToStringSlice(servers), " ")
|
|
|
|
initializationConfig.Nameserver = &nameserver
|
|
} else if deprecatedServer != "" {
|
|
initializationConfig.Nameserver = &deprecatedServer
|
|
}
|
|
}
|
|
|
|
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)
|
|
pathInDatastore := ""
|
|
|
|
if untyped, hasPathInDatastore := block[mkResourceVirtualEnvironmentVMDiskPathInDatastore]; hasPathInDatastore {
|
|
pathInDatastore = untyped.(string)
|
|
}
|
|
|
|
fileFormat, _ := block[mkResourceVirtualEnvironmentVMDiskFileFormat].(string)
|
|
fileID, _ := block[mkResourceVirtualEnvironmentVMDiskFileID].(string)
|
|
size, _ := block[mkResourceVirtualEnvironmentVMDiskSize].(int)
|
|
diskInterface, _ := block[mkResourceVirtualEnvironmentVMDiskInterface].(string)
|
|
ioThread := types.CustomBool(block[mkResourceVirtualEnvironmentVMDiskIOThread].(bool))
|
|
ssd := types.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
|
|
}
|
|
|
|
if pathInDatastore != "" {
|
|
if datastoreID != "" {
|
|
diskDevice.FileVolume = fmt.Sprintf("%s:%s", datastoreID, pathInDatastore)
|
|
} else {
|
|
// FileVolume is absolute path in the host filesystem
|
|
diskDevice.FileVolume = pathInDatastore
|
|
}
|
|
} else {
|
|
diskDevice.FileVolume = fmt.Sprintf("%s:%d", datastoreID, size)
|
|
}
|
|
|
|
diskDevice.ID = &datastoreID
|
|
diskDevice.Interface = &diskInterface
|
|
diskDevice.Format = &fileFormat
|
|
diskDevice.FileID = &fileID
|
|
diskSize := types.DiskSizeFromGigabytes(int64(size))
|
|
diskDevice.Size = &diskSize
|
|
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 := types.CustomBool(block[mkResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys].(bool))
|
|
|
|
// use the special syntax STORAGE_ID:SIZE_IN_GiB to allocate a new volume.
|
|
// NB SIZE_IN_GiB 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 := types.ParseDiskSize(*efiDisk.Type)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid efi disk type: %s", err.Error())
|
|
}
|
|
|
|
storageDevice.Size = &ds
|
|
}
|
|
}
|
|
|
|
return storageDevice, nil
|
|
}
|
|
|
|
func vmGetTPMState(d *schema.ResourceData, disk []interface{}) *vms.CustomTPMState {
|
|
var tpmState []interface{}
|
|
|
|
if disk != nil {
|
|
tpmState = disk
|
|
} else {
|
|
tpmState = d.Get(mkResourceVirtualEnvironmentVMTPMState).([]interface{})
|
|
}
|
|
|
|
var tpmStateConfig *vms.CustomTPMState
|
|
|
|
if len(tpmState) > 0 {
|
|
tpmStateConfig = &vms.CustomTPMState{}
|
|
|
|
block := tpmState[0].(map[string]interface{})
|
|
datastoreID, _ := block[mkResourceVirtualEnvironmentVMTPMStateDatastoreID].(string)
|
|
version, _ := block[mkResourceVirtualEnvironmentVMTPMStateVersion].(string)
|
|
|
|
// use the special syntax STORAGE_ID:SIZE_IN_GiB to allocate a new volume.
|
|
// NB SIZE_IN_GiB is ignored, see docs for more info.
|
|
tpmStateConfig.FileVolume = fmt.Sprintf("%s:1", datastoreID)
|
|
tpmStateConfig.Version = &version
|
|
}
|
|
|
|
return tpmStateConfig
|
|
}
|
|
|
|
func vmGetTPMStateAsStorageDevice(d *schema.ResourceData, disk []interface{}) *vms.CustomStorageDevice {
|
|
tpmState := vmGetTPMState(d, disk)
|
|
|
|
var storageDevice *vms.CustomStorageDevice
|
|
|
|
if tpmState != nil {
|
|
id := "0"
|
|
baseDiskInterface := "tpmstate"
|
|
diskInterface := fmt.Sprint(baseDiskInterface, id)
|
|
|
|
storageDevice = &vms.CustomStorageDevice{
|
|
Enabled: true,
|
|
FileVolume: tpmState.FileVolume,
|
|
Interface: &diskInterface,
|
|
ID: &id,
|
|
}
|
|
}
|
|
|
|
return storageDevice
|
|
}
|
|
|
|
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 := types.CustomBool(block[mkResourceVirtualEnvironmentVMHostPCIDevicePCIE].(bool))
|
|
rombar := types.CustomBool(
|
|
block[mkResourceVirtualEnvironmentVMHostPCIDeviceROMBAR].(bool),
|
|
)
|
|
romfile, _ := block[mkResourceVirtualEnvironmentVMHostPCIDeviceROMFile].(string)
|
|
xvga := types.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 vmGetHostUSBDeviceObjects(d *schema.ResourceData) vms.CustomUSBDevices {
|
|
usbDevice := d.Get(mkResourceVirtualEnvironmentVMHostUSB).([]interface{})
|
|
usbDeviceObjects := make(vms.CustomUSBDevices, len(usbDevice))
|
|
|
|
for i, usbDeviceEntry := range usbDevice {
|
|
block := usbDeviceEntry.(map[string]interface{})
|
|
|
|
host, _ := block[mkResourceVirtualEnvironmentVMHostUSBDevice].(string)
|
|
usb3 := types.CustomBool(block[mkResourceVirtualEnvironmentVMHostUSBDeviceUSB3].(bool))
|
|
mapping, _ := block[mkResourceVirtualEnvironmentVMHostUSBDeviceMapping].(string)
|
|
|
|
device := vms.CustomUSBDevice{
|
|
HostDevice: &host,
|
|
USB3: &usb3,
|
|
}
|
|
if mapping != "" {
|
|
device.Mapping = &mapping
|
|
}
|
|
|
|
usbDeviceObjects[i] = device
|
|
}
|
|
|
|
return usbDeviceObjects
|
|
}
|
|
|
|
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 := types.CustomBool(block[mkResourceVirtualEnvironmentVMNetworkDeviceFirewall].(bool))
|
|
macAddress := block[mkResourceVirtualEnvironmentVMNetworkDeviceMACAddress].(string)
|
|
model := block[mkResourceVirtualEnvironmentVMNetworkDeviceModel].(string)
|
|
queues := block[mkResourceVirtualEnvironmentVMNetworkDeviceQueues].(int)
|
|
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 queues != 0 {
|
|
device.Queues = &queues
|
|
}
|
|
|
|
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",
|
|
"win11",
|
|
"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 := types.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 = types.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 {
|
|
var sanitizedTags []string
|
|
|
|
tags := d.Get(mkResourceVirtualEnvironmentVMTags).([]interface{})
|
|
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) ([]string, []error) {
|
|
v, ok := i.(string)
|
|
|
|
var es []error
|
|
|
|
if !ok {
|
|
es = append(es, fmt.Errorf("expected type of %s to be string", k))
|
|
return nil, es
|
|
}
|
|
|
|
if !strings.HasPrefix(v, "/dev/") && v != "socket" {
|
|
es = append(es, fmt.Errorf("expected %s to be '/dev/*' or 'socket'", k))
|
|
return nil, es
|
|
}
|
|
|
|
return nil, es
|
|
})
|
|
}
|
|
|
|
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 := types.CustomBool(vgaBlock[mkResourceVirtualEnvironmentVMVGAEnabled].(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{})
|
|
|
|
err = setAgent(d, len(clone) > 0, vmConfig)
|
|
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 CD-ROM 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.CPULimit != nil {
|
|
cpu[mkResourceVirtualEnvironmentVMCPULimit] = *vmConfig.CPULimit
|
|
} else {
|
|
// Default value of "cpulimit" is "0" according to the API documentation.
|
|
cpu[mkResourceVirtualEnvironmentVMCPULimit] = 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[mkResourceVirtualEnvironmentVMCPULimit] != dvResourceVirtualEnvironmentVMCPULimit ||
|
|
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 {
|
|
if dd == nil || dd.FileVolume == "none" || strings.HasPrefix(di, "ide") {
|
|
continue
|
|
}
|
|
|
|
if dd.IsCloudInitDrive(vmID) {
|
|
continue
|
|
}
|
|
|
|
disk := map[string]interface{}{}
|
|
|
|
datastoreID, pathInDatastore, hasDatastoreID := strings.Cut(dd.FileVolume, ":")
|
|
if !hasDatastoreID {
|
|
// when no ':' separator is found, 'Cut' places the whole string to 'datastoreID',
|
|
// we want it in 'pathInDatastore' (it is absolute filesystem path)
|
|
pathInDatastore = datastoreID
|
|
datastoreID = ""
|
|
}
|
|
|
|
disk[mkResourceVirtualEnvironmentVMDiskDatastoreID] = datastoreID
|
|
disk[mkResourceVirtualEnvironmentVMDiskPathInDatastore] = pathInDatastore
|
|
|
|
if dd.Format == nil {
|
|
disk[mkResourceVirtualEnvironmentVMDiskFileFormat] = dvResourceVirtualEnvironmentVMDiskFileFormat
|
|
|
|
if datastoreID != "" {
|
|
// 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
|
|
volume, err := api.Node(nodeName).Storage(datastoreID).GetDatastoreFile(ctx, dd.FileVolume)
|
|
if err != nil {
|
|
diags = append(diags, diag.FromErr(err)...)
|
|
continue
|
|
}
|
|
|
|
disk[mkResourceVirtualEnvironmentVMDiskFileFormat] = volume.FileFormat
|
|
}
|
|
} 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(clone) == 0 || 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
|
|
volume, err := api.Node(nodeName).Storage(fileIDParts[0]).GetDatastoreFile(ctx, vmConfig.EFIDisk.FileVolume)
|
|
if err != nil {
|
|
diags = append(diags, diag.FromErr(err)...)
|
|
} else {
|
|
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskFileFormat] = volume.FileFormat
|
|
}
|
|
}
|
|
|
|
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)...)
|
|
}
|
|
}
|
|
|
|
if vmConfig.TPMState != nil {
|
|
tpmState := map[string]interface{}{}
|
|
|
|
fileIDParts := strings.Split(vmConfig.TPMState.FileVolume, ":")
|
|
|
|
tpmState[mkResourceVirtualEnvironmentVMTPMStateDatastoreID] = fileIDParts[0]
|
|
tpmState[mkResourceVirtualEnvironmentVMTPMStateVersion] = dvResourceVirtualEnvironmentVMTPMStateVersion
|
|
|
|
currentTPMState := d.Get(mkResourceVirtualEnvironmentVMTPMState).([]interface{})
|
|
|
|
if len(clone) > 0 {
|
|
if len(currentTPMState) > 0 {
|
|
err := d.Set(mkResourceVirtualEnvironmentVMTPMState, []interface{}{tpmState})
|
|
diags = append(diags, diag.FromErr(err)...)
|
|
}
|
|
} else if len(currentTPMState) > 0 ||
|
|
tpmState[mkResourceVirtualEnvironmentVMTPMStateDatastoreID] != dvResourceVirtualEnvironmentVMTPMStateDatastoreID ||
|
|
tpmState[mkResourceVirtualEnvironmentVMTPMStateVersion] != dvResourceVirtualEnvironmentVMTPMStateVersion {
|
|
err := d.Set(mkResourceVirtualEnvironmentVMTPMState, []interface{}{tpmState})
|
|
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] = true
|
|
}
|
|
|
|
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(clone) == 0 || len(currentPCIList) > 0 {
|
|
orderedPCIList := orderedListFromMap(pciMap)
|
|
err := d.Set(mkResourceVirtualEnvironmentVMHostPCI, orderedPCIList)
|
|
diags = append(diags, diag.FromErr(err)...)
|
|
}
|
|
|
|
currentUSBList := d.Get(mkResourceVirtualEnvironmentVMHostUSB).([]interface{})
|
|
usbMap := map[string]interface{}{}
|
|
|
|
usbDevices := getUSBInfo(vmConfig, d)
|
|
for pi, pp := range usbDevices {
|
|
if (pp == nil) || (pp.HostDevice == nil && pp.Mapping == nil) {
|
|
continue
|
|
}
|
|
|
|
usb := map[string]interface{}{}
|
|
|
|
if pp.HostDevice != nil {
|
|
usb[mkResourceVirtualEnvironmentVMHostUSBDevice] = *pp.HostDevice
|
|
} else {
|
|
usb[mkResourceVirtualEnvironmentVMHostUSBDevice] = ""
|
|
}
|
|
|
|
if pp.USB3 != nil {
|
|
usb[mkResourceVirtualEnvironmentVMHostUSBDeviceUSB3] = *pp.USB3
|
|
} else {
|
|
usb[mkResourceVirtualEnvironmentVMHostUSBDeviceUSB3] = false
|
|
}
|
|
|
|
if pp.Mapping != nil {
|
|
usb[mkResourceVirtualEnvironmentVMHostUSBDeviceMapping] = *pp.Mapping
|
|
} else {
|
|
usb[mkResourceVirtualEnvironmentVMHostUSBDeviceMapping] = ""
|
|
}
|
|
|
|
usbMap[pi] = usb
|
|
}
|
|
|
|
if len(clone) == 0 || len(currentUSBList) > 0 {
|
|
// todo: reordering of devices by PVE may cause an issue here
|
|
orderedUSBList := orderedListFromMap(usbMap)
|
|
err := d.Set(mkResourceVirtualEnvironmentVMHostUSB, orderedUSBList)
|
|
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] = ""
|
|
}
|
|
|
|
// check what we have in the plan
|
|
currentInitializationDNSBlock := map[string]interface{}{}
|
|
currentInitialization := d.Get(mkResourceVirtualEnvironmentVMInitialization).([]interface{})
|
|
|
|
if len(currentInitialization) > 0 {
|
|
currentInitializationBlock := currentInitialization[0].(map[string]interface{})
|
|
//nolint:lll
|
|
currentInitializationDNS := currentInitializationBlock[mkResourceVirtualEnvironmentVMInitializationDNS].([]interface{})
|
|
if len(currentInitializationDNS) > 0 {
|
|
currentInitializationDNSBlock = currentInitializationDNS[0].(map[string]interface{})
|
|
}
|
|
}
|
|
|
|
//nolint:lll
|
|
currentInitializationDNSServer, ok := currentInitializationDNSBlock[mkResourceVirtualEnvironmentVMInitializationDNSServer]
|
|
if vmConfig.CloudInitDNSServer != nil {
|
|
if ok && currentInitializationDNSServer != "" {
|
|
// the template is using deprecated attribute mkResourceVirtualEnvironmentVMInitializationDNSServer
|
|
initializationDNS[mkResourceVirtualEnvironmentVMInitializationDNSServer] = *vmConfig.CloudInitDNSServer
|
|
} else {
|
|
dnsServer := strings.Split(*vmConfig.CloudInitDNSServer, " ")
|
|
initializationDNS[mkResourceVirtualEnvironmentVMInitializationDNSServers] = dnsServer
|
|
}
|
|
} else {
|
|
initializationDNS[mkResourceVirtualEnvironmentVMInitializationDNSServer] = ""
|
|
initializationDNS[mkResourceVirtualEnvironmentVMInitializationDNSServers] = []string{}
|
|
}
|
|
|
|
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,
|
|
vmConfig.IPConfig7,
|
|
vmConfig.IPConfig8,
|
|
vmConfig.IPConfig9,
|
|
vmConfig.IPConfig10,
|
|
vmConfig.IPConfig11,
|
|
vmConfig.IPConfig12,
|
|
vmConfig.IPConfig13,
|
|
vmConfig.IPConfig14,
|
|
vmConfig.IPConfig15,
|
|
vmConfig.IPConfig16,
|
|
vmConfig.IPConfig17,
|
|
vmConfig.IPConfig18,
|
|
vmConfig.IPConfig19,
|
|
vmConfig.IPConfig20,
|
|
vmConfig.IPConfig21,
|
|
vmConfig.IPConfig22,
|
|
vmConfig.IPConfig23,
|
|
vmConfig.IPConfig24,
|
|
vmConfig.IPConfig25,
|
|
vmConfig.IPConfig26,
|
|
vmConfig.IPConfig27,
|
|
vmConfig.IPConfig28,
|
|
vmConfig.IPConfig29,
|
|
vmConfig.IPConfig30,
|
|
vmConfig.IPConfig31,
|
|
}
|
|
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] = int(*vmConfig.DedicatedMemory)
|
|
} else {
|
|
memory[mkResourceVirtualEnvironmentVMMemoryDedicated] = 0
|
|
}
|
|
|
|
if vmConfig.FloatingMemory != nil {
|
|
memory[mkResourceVirtualEnvironmentVMMemoryFloating] = int(*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{}, maxResourceVirtualEnvironmentVMNetworkDevices)
|
|
networkDeviceLast := -1
|
|
networkDeviceList := make([]interface{}, maxResourceVirtualEnvironmentVMNetworkDevices)
|
|
networkDeviceObjects := []*vms.CustomNetworkDevice{
|
|
vmConfig.NetworkDevice0,
|
|
vmConfig.NetworkDevice1,
|
|
vmConfig.NetworkDevice2,
|
|
vmConfig.NetworkDevice3,
|
|
vmConfig.NetworkDevice4,
|
|
vmConfig.NetworkDevice5,
|
|
vmConfig.NetworkDevice6,
|
|
vmConfig.NetworkDevice7,
|
|
vmConfig.NetworkDevice8,
|
|
vmConfig.NetworkDevice9,
|
|
vmConfig.NetworkDevice10,
|
|
vmConfig.NetworkDevice11,
|
|
vmConfig.NetworkDevice12,
|
|
vmConfig.NetworkDevice13,
|
|
vmConfig.NetworkDevice14,
|
|
vmConfig.NetworkDevice15,
|
|
vmConfig.NetworkDevice16,
|
|
vmConfig.NetworkDevice17,
|
|
vmConfig.NetworkDevice18,
|
|
vmConfig.NetworkDevice19,
|
|
vmConfig.NetworkDevice20,
|
|
vmConfig.NetworkDevice21,
|
|
vmConfig.NetworkDevice22,
|
|
vmConfig.NetworkDevice23,
|
|
vmConfig.NetworkDevice24,
|
|
vmConfig.NetworkDevice25,
|
|
vmConfig.NetworkDevice26,
|
|
vmConfig.NetworkDevice27,
|
|
vmConfig.NetworkDevice28,
|
|
vmConfig.NetworkDevice29,
|
|
vmConfig.NetworkDevice30,
|
|
vmConfig.NetworkDevice31,
|
|
}
|
|
|
|
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.Queues != nil {
|
|
networkDevice[mkResourceVirtualEnvironmentVMNetworkDeviceQueues] = *nd.Queues
|
|
} else {
|
|
networkDevice[mkResourceVirtualEnvironmentVMNetworkDeviceQueues] = 0
|
|
}
|
|
|
|
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)...)
|
|
|
|
// during import these core attributes might not be set, so set them explicitly here
|
|
d.SetId(strconv.Itoa(vmID))
|
|
e := d.Set(mkResourceVirtualEnvironmentVMVMID, vmID)
|
|
diags = append(diags, diag.FromErr(e)...)
|
|
e = d.Set(mkResourceVirtualEnvironmentVMNodeName, nodeName)
|
|
diags = append(diags, diag.FromErr(e)...)
|
|
|
|
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 := (types.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 := types.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 := types.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 := types.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 := types.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 := types.CustomBool(d.Get(mkResourceVirtualEnvironmentVMTabletDevice).(bool))
|
|
updateBody.TabletDeviceEnabled = &tabletDevice
|
|
rebootRequired = true
|
|
}
|
|
|
|
template := types.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 := types.CustomBool(
|
|
agentBlock[mkResourceVirtualEnvironmentVMAgentEnabled].(bool),
|
|
)
|
|
agentTrim := types.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)
|
|
cpuLimit := cpuBlock[mkResourceVirtualEnvironmentVMCPULimit].(int)
|
|
cpuNUMA := types.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")
|
|
}
|
|
|
|
if cpuLimit > 0 {
|
|
updateBody.CPULimit = &cpuLimit
|
|
} else {
|
|
del = append(del, "cpulimit")
|
|
}
|
|
|
|
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 tpm state configuration.
|
|
if d.HasChange(mkResourceVirtualEnvironmentVMTPMState) {
|
|
tpmState := vmGetTPMState(d, nil)
|
|
|
|
updateBody.TPMState = tpmState
|
|
|
|
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 usb devices configuration.
|
|
if d.HasChange(mkResourceVirtualEnvironmentVMHostUSB) {
|
|
updateBody.USBDevices = vmGetHostUSBDeviceObjects(d)
|
|
|
|
for i := len(updateBody.USBDevices); i < maxResourceVirtualEnvironmentVMHostUSBDevices; i++ {
|
|
del = append(del, fmt.Sprintf("usb%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
|
|
}
|
|
|
|
if d.HasChanges(mkResourceVirtualEnvironmentVMHookScriptFileID) {
|
|
hookScript := d.Get(mkResourceVirtualEnvironmentVMHookScriptFileID).(string)
|
|
if len(hookScript) > 0 {
|
|
updateBody.HookScript = &hookScript
|
|
} else {
|
|
del = append(del, "hookscript")
|
|
}
|
|
}
|
|
|
|
// 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.",
|
|
)
|
|
}
|
|
}
|
|
|
|
// Add tpm state if it has changes
|
|
if d.HasChange(mkResourceVirtualEnvironmentVMTPMState) {
|
|
diskOld, diskNew := d.GetChange(mkResourceVirtualEnvironmentVMTPMState)
|
|
|
|
oldTPMState := vmGetTPMStateAsStorageDevice(d, diskOld.([]interface{}))
|
|
newTPMState := vmGetTPMStateAsStorageDevice(d, diskNew.([]interface{}))
|
|
|
|
if oldTPMState != nil {
|
|
baseDiskInterface := diskDigitPrefix(*oldTPMState.Interface)
|
|
diskOldEntries[baseDiskInterface][*oldTPMState.Interface] = *oldTPMState
|
|
}
|
|
|
|
if newTPMState != nil {
|
|
baseDiskInterface := diskDigitPrefix(*newTPMState.Interface)
|
|
diskNewEntries[baseDiskInterface][*newTPMState.Interface] = *newTPMState
|
|
}
|
|
|
|
if oldTPMState != nil && newTPMState != nil && oldTPMState.Size != newTPMState.Size {
|
|
return diag.Errorf(
|
|
"resizing of tpm state 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 {
|
|
if oldDisk.IsOwnedBy(vmID) {
|
|
deleteOriginalDisk := types.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
|
|
} else {
|
|
return diag.Errorf(
|
|
"Cannot move %s:%s to datastore %s in VM %d configuration, it is not owned by this VM!",
|
|
*oldDisk.ID,
|
|
*oldDisk.PathInDatastore(),
|
|
*diskNewEntries[prefix][oldKey].ID,
|
|
vmID,
|
|
)
|
|
}
|
|
}
|
|
|
|
if *oldDisk.Size < *diskNewEntries[prefix][oldKey].Size {
|
|
if oldDisk.IsOwnedBy(vmID) {
|
|
diskResizeBodies = append(
|
|
diskResizeBodies,
|
|
&vms.ResizeDiskRequestBody{
|
|
Disk: *oldDisk.Interface,
|
|
Size: *diskNewEntries[prefix][oldKey].Size,
|
|
},
|
|
)
|
|
} else {
|
|
return diag.Errorf(
|
|
"Cannot resize %s:%s in VM %d configuration, it is not owned by this VM!",
|
|
*oldDisk.ID,
|
|
*oldDisk.PathInDatastore(),
|
|
vmID,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
|
|
// Stop or shut down the virtual machine before deleting it.
|
|
status, err := vmAPI.GetVMStatus(ctx)
|
|
if err != nil {
|
|
return diag.FromErr(err)
|
|
}
|
|
|
|
stop := d.Get(mkResourceVirtualEnvironmentVMStopOnDestroy).(bool)
|
|
|
|
if status.Status != "stopped" {
|
|
if stop {
|
|
if e := vmStop(ctx, vmAPI, d); e != nil {
|
|
return e
|
|
}
|
|
} else {
|
|
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
|
|
}
|
|
}
|
|
|
|
if v.Size == nil {
|
|
v.Size = new(types.DiskSize)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
if vm.TPMState != nil {
|
|
fileIDParts := strings.Split(vm.TPMState.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 getUSBInfo(resp *vms.GetResponseData, _ *schema.ResourceData) map[string]*vms.CustomUSBDevice {
|
|
usbDevices := map[string]*vms.CustomUSBDevice{}
|
|
|
|
usbDevices["usb0"] = resp.USBDevice0
|
|
usbDevices["usb1"] = resp.USBDevice1
|
|
usbDevices["usb2"] = resp.USBDevice2
|
|
usbDevices["usb3"] = resp.USBDevice3
|
|
|
|
return usbDevices
|
|
}
|
|
|
|
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
|
|
}
|