0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-07-09 15:25:01 +00:00
terraform-provider-proxmox/proxmoxtf/resource/vm/disk.go
Pavel Boldyrev 87ff97ca58
more disk management refactoring
Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
2024-02-03 16:57:46 -05:00

880 lines
25 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

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

package vm
import (
"context"
"fmt"
"strconv"
"strings"
"github.com/bpg/terraform-provider-proxmox/proxmox"
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/vms"
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/resource/validator"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/structure"
"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"
)
const (
mkDisk = "disk"
mkDiskInterface = "interface"
mkDiskDatastoreID = "datastore_id"
mkDiskPathInDatastore = "path_in_datastore"
mkDiskFileFormat = "file_format"
mkDiskFileID = "file_id"
mkDiskSize = "size"
mkDiskIOThread = "iothread"
mkDiskSSD = "ssd"
mkDiskDiscard = "discard"
mkDiskCache = "cache"
mkDiskSpeed = "speed"
mkDiskSpeedRead = "read"
mkDiskSpeedReadBurstable = "read_burstable"
mkDiskSpeedWrite = "write"
mkDiskSpeedWriteBurstable = "write_burstable"
dvDiskInterface = "scsi0"
dvDiskDatastoreID = "local-lvm"
dvDiskFileFormat = "qcow2"
dvDiskFileID = ""
dvDiskSize = 8
dvDiskIOThread = false
dvDiskSSD = false
dvDiskDiscard = "ignore"
dvDiskCache = "none"
dvDiskSpeedRead = 0
dvDiskSpeedReadBurstable = 0
dvDiskSpeedWrite = 0
dvDiskSpeedWriteBurstable = 0
)
func diskSchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Description: "The disk devices",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{
map[string]interface{}{
mkDiskDatastoreID: dvDiskDatastoreID,
mkDiskPathInDatastore: nil,
mkDiskFileID: dvDiskFileID,
mkDiskInterface: dvDiskInterface,
mkDiskSize: dvDiskSize,
mkDiskIOThread: dvDiskIOThread,
mkDiskSSD: dvDiskSSD,
mkDiskDiscard: dvDiskDiscard,
mkDiskCache: dvDiskCache,
},
}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkDiskInterface: {
Type: schema.TypeString,
Description: "The datastore name",
Required: true,
},
mkDiskDatastoreID: {
Type: schema.TypeString,
Description: "The datastore id",
Optional: true,
Default: dvDiskDatastoreID,
},
mkDiskPathInDatastore: {
Type: schema.TypeString,
Description: "The in-datastore path to disk image",
Computed: true,
Optional: true,
Default: nil,
},
mkDiskFileFormat: {
Type: schema.TypeString,
Description: "The file format",
Optional: true,
ForceNew: true,
Computed: true,
ValidateDiagFunc: validator.FileFormat(),
},
mkDiskFileID: {
Type: schema.TypeString,
Description: "The file id for a disk image",
Optional: true,
ForceNew: true,
Default: dvDiskFileID,
ValidateDiagFunc: validator.FileID(),
},
mkDiskSize: {
Type: schema.TypeInt,
Description: "The disk size in gigabytes",
Optional: true,
Default: dvDiskSize,
ValidateDiagFunc: validation.ToDiagFunc(validation.IntAtLeast(1)),
},
mkDiskIOThread: {
Type: schema.TypeBool,
Description: "Whether to use iothreads for this disk drive",
Optional: true,
Default: dvDiskIOThread,
},
mkDiskSSD: {
Type: schema.TypeBool,
Description: "Whether to use ssd for this disk drive",
Optional: true,
Default: dvDiskSSD,
},
mkDiskDiscard: {
Type: schema.TypeString,
Description: "Whether to pass discard/trim requests to the underlying storage.",
Optional: true,
Default: dvDiskDiscard,
},
mkDiskCache: {
Type: schema.TypeString,
Description: "The drives cache mode",
Optional: true,
Default: dvDiskCache,
ValidateDiagFunc: validation.ToDiagFunc(
validation.StringInSlice([]string{
"none",
"writethrough",
"writeback",
"unsafe",
"directsync",
}, false),
),
},
mkDiskSpeed: {
Type: schema.TypeList,
Description: "The speed limits",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{
map[string]interface{}{
mkDiskSpeedRead: dvDiskSpeedRead,
mkDiskSpeedReadBurstable: dvDiskSpeedReadBurstable,
mkDiskSpeedWrite: dvDiskSpeedWrite,
mkDiskSpeedWriteBurstable: dvDiskSpeedWriteBurstable,
},
}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkDiskSpeedRead: {
Type: schema.TypeInt,
Description: "The maximum read speed in megabytes per second",
Optional: true,
Default: dvDiskSpeedRead,
},
mkDiskSpeedReadBurstable: {
Type: schema.TypeInt,
Description: "The maximum burstable read speed in megabytes per second",
Optional: true,
Default: dvDiskSpeedReadBurstable,
},
mkDiskSpeedWrite: {
Type: schema.TypeInt,
Description: "The maximum write speed in megabytes per second",
Optional: true,
Default: dvDiskSpeedWrite,
},
mkDiskSpeedWriteBurstable: {
Type: schema.TypeInt,
Description: "The maximum burstable write speed in megabytes per second",
Optional: true,
Default: dvDiskSpeedWriteBurstable,
},
},
},
MaxItems: 1,
MinItems: 0,
},
},
},
}
}
func createDisks(
ctx context.Context, vmConfig *vms.GetResponseData, d *schema.ResourceData, vmAPI *vms.Client,
) (map[string]*vms.CustomStorageDevice, error) {
// this is what VM has at the moment: map of interface name (virtio1) -> disk object
currentDisks := populateFileIDs(mapStorageDevices(vmConfig), d)
// map of interface name (virtio1) -> disk object
planDisks, e := getStorageDevicesFromResource(d)
if e != nil {
return nil, e
}
for diskInterface, planDisk := range planDisks {
currentDisk := currentDisks[diskInterface]
if currentDisk == nil {
// create disks that are not present in the current configuration
err := createDisk(ctx, planDisk, vmAPI)
if err != nil {
return nil, err
}
} else {
// disk is present, i.e. when cloned a template, but we need to check if it needs to be resized
if planDisk.Size.InGigabytes() < currentDisk.Size.InGigabytes() {
return nil, fmt.Errorf("disk resize fails requests size (%dG) is lower than current size (%s)",
planDisk.Size.InGigabytes(),
*currentDisk.Size,
)
}
moveDisk := false
if *planDisk.ID != "" {
fileIDParts := strings.Split(currentDisk.FileVolume, ":")
moveDisk = *planDisk.ID != fileIDParts[0]
}
if moveDisk {
moveDiskTimeout := d.Get(mkTimeoutMoveDisk).(int)
deleteOriginalDisk := types.CustomBool(true)
diskMoveBody := &vms.MoveDiskRequestBody{
DeleteOriginalDisk: &deleteOriginalDisk,
Disk: diskInterface,
TargetStorage: *planDisk.ID,
}
err := vmAPI.MoveVMDisk(ctx, diskMoveBody, moveDiskTimeout)
if err != nil {
return nil, err
}
}
if planDisk.Size.InGigabytes() > currentDisk.Size.InGigabytes() {
moveDiskTimeout := d.Get(mkTimeoutMoveDisk).(int)
diskResizeBody := &vms.ResizeDiskRequestBody{
Disk: diskInterface,
Size: *types.DiskSizeFromGigabytes(planDisk.Size.InGigabytes()),
}
err := vmAPI.ResizeVMDisk(ctx, diskResizeBody, moveDiskTimeout)
if err != nil {
return nil, err
}
}
}
}
return currentDisks, nil
}
func createDisk(ctx context.Context, disk *vms.CustomStorageDevice, vmAPI *vms.Client) error {
addToDevices := func(ds vms.CustomStorageDevices, disk *vms.CustomStorageDevice) vms.CustomStorageDevices {
if ds == nil {
ds = vms.CustomStorageDevices{}
}
ds[*disk.Interface] = disk
return ds
}
diskUpdateBody := &vms.UpdateRequestBody{}
switch disk.StorageInterface() {
case "virtio":
diskUpdateBody.VirtualIODevices = addToDevices(diskUpdateBody.VirtualIODevices, disk)
case "sata":
diskUpdateBody.SATADevices = addToDevices(diskUpdateBody.SATADevices, disk)
case "scsi":
diskUpdateBody.SCSIDevices = addToDevices(diskUpdateBody.SCSIDevices, disk)
}
err := vmAPI.UpdateVM(ctx, diskUpdateBody)
if err != nil {
return err
}
return nil
}
func vmCreateCustomDisks(ctx context.Context, d *schema.ResourceData, m interface{}) error {
vmID, err := strconv.Atoi(d.Id())
if err != nil {
return err
}
// Determine the ID of the next disk.
disk := d.Get(mkDisk).([]interface{})
diskCount := 0
for _, d := range disk {
block := d.(map[string]interface{})
fileID, _ := block[mkDiskFileID].(string)
if fileID == "" {
diskCount++
}
}
// Retrieve some information about the disk schema.
resourceSchema := VM().Schema
diskSchemaElem := resourceSchema[mkDisk].Elem
diskSchemaResource := diskSchemaElem.(*schema.Resource)
diskSpeedResource := diskSchemaResource.Schema[mkDiskSpeed]
// Generate the commands required to import the specified disks.
commands := []string{}
importedDiskCount := 0
for _, d := range disk {
block := d.(map[string]interface{})
fileID, _ := block[mkDiskFileID].(string)
if fileID == "" {
continue
}
datastoreID, _ := block[mkDiskDatastoreID].(string)
fileFormat, _ := block[mkDiskFileFormat].(string)
size, _ := block[mkDiskSize].(int)
speed := block[mkDiskSpeed].([]interface{})
diskInterface, _ := block[mkDiskInterface].(string)
ioThread := types.CustomBool(block[mkDiskIOThread].(bool))
ssd := types.CustomBool(block[mkDiskSSD].(bool))
discard, _ := block[mkDiskDiscard].(string)
cache, _ := block[mkDiskCache].(string)
if fileFormat == "" {
fileFormat = dvDiskFileFormat
}
if len(speed) == 0 {
diskSpeedDefault, err := diskSpeedResource.DefaultValue()
if err != nil {
return err
}
speed = diskSpeedDefault.([]interface{})
}
speedBlock := speed[0].(map[string]interface{})
speedLimitRead := speedBlock[mkDiskSpeedRead].(int)
speedLimitReadBurstable := speedBlock[mkDiskSpeedReadBurstable].(int)
speedLimitWrite := speedBlock[mkDiskSpeedWrite].(int)
speedLimitWriteBurstable := speedBlock[mkDiskSpeedWriteBurstable].(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 err
}
nodeName := d.Get(mkNodeName).(string)
out, err := api.SSH().ExecuteNodeCommands(ctx, nodeName, commands)
if err != nil {
if strings.Contains(err.Error(), "pvesm: not found") {
return fmt.Errorf("The configured SSH user '%s' does not have the required permissions to import disks. "+
"Make sure `sudo` is installed and the user is a member of sudoers.", api.SSH().Username())
}
return err
}
tflog.Debug(ctx, "vmCreateCustomDisks", map[string]interface{}{
"output": string(out),
})
}
return nil
}
func getStorageDevicesFromResource(d *schema.ResourceData) (vms.CustomStorageDevices, error) {
return getDiskDeviceObjects1(d, d.Get(mkDisk).([]interface{}))
}
func getDiskDeviceObjects1(d *schema.ResourceData, disks []interface{}) (vms.CustomStorageDevices, error) {
diskDeviceObjects := vms.CustomStorageDevices{}
resource := VM()
for _, diskEntry := range disks {
diskDevice := vms.CustomStorageDevice{
Enabled: true,
}
block := diskEntry.(map[string]interface{})
diskInterface, _ := block[mkDiskInterface].(string)
datastoreID, _ := block[mkDiskDatastoreID].(string)
size, _ := block[mkDiskSize].(int)
fileFormat, _ := block[mkDiskFileFormat].(string)
fileID, _ := block[mkDiskFileID].(string)
ioThread := types.CustomBool(block[mkDiskIOThread].(bool))
ssd := types.CustomBool(block[mkDiskSSD].(bool))
discard := block[mkDiskDiscard].(string)
cache := block[mkDiskCache].(string)
pathInDatastore := ""
if untyped, hasPathInDatastore := block[mkDiskPathInDatastore]; hasPathInDatastore {
pathInDatastore = untyped.(string)
}
speedBlock, err := structure.GetSchemaBlock(
resource,
d,
[]string{mkDisk, mkDiskSpeed},
0,
false,
)
if err != nil {
return diskDeviceObjects, err
}
if fileFormat == "" {
fileFormat = dvDiskFileFormat
}
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
diskDevice.Size = types.DiskSizeFromGigabytes(int64(size))
diskDevice.IOThread = &ioThread
diskDevice.Discard = &discard
diskDevice.Cache = &cache
if !strings.HasPrefix(diskInterface, "virtio") {
diskDevice.SSD = &ssd
}
if len(speedBlock) > 0 {
speedLimitRead := speedBlock[mkDiskSpeedRead].(int)
speedLimitReadBurstable := speedBlock[mkDiskSpeedReadBurstable].(int)
speedLimitWrite := speedBlock[mkDiskSpeedWrite].(int)
speedLimitWriteBurstable := speedBlock[mkDiskSpeedWriteBurstable].(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
}
}
storageInterface := diskDevice.StorageInterface()
if storageInterface != "virtio" && storageInterface != "scsi" && storageInterface != "sata" {
return diskDeviceObjects, fmt.Errorf(
"Defined disk interface not supported. Interface was '%s', but only 'virtio', 'sata' and 'scsi' are supported",
diskInterface,
)
}
diskDeviceObjects[diskInterface] = &diskDevice
}
return diskDeviceObjects, nil
}
func readDisk1(ctx context.Context, d *schema.ResourceData,
vmConfig *vms.GetResponseData, vmID int, api proxmox.Client, nodeName string, clone []interface{},
) diag.Diagnostics {
currentDiskList := d.Get(mkDisk).([]interface{})
diskMap := map[string]interface{}{}
diskObjects := populateFileIDs(mapStorageDevices(vmConfig), d)
var diags diag.Diagnostics
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[mkDiskDatastoreID] = datastoreID
disk[mkDiskPathInDatastore] = pathInDatastore
if dd.Format == nil {
disk[mkDiskFileFormat] = dvDiskFileFormat
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[mkDiskFileFormat] = volume.FileFormat
}
} else {
disk[mkDiskFileFormat] = dd.Format
}
if dd.FileID != nil {
disk[mkDiskFileID] = dd.FileID
}
disk[mkDiskInterface] = di
disk[mkDiskSize] = 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[mkDiskSpeedRead] = *dd.MaxReadSpeedMbps
} else {
speed[mkDiskSpeedRead] = 0
}
if dd.BurstableReadSpeedMbps != nil {
speed[mkDiskSpeedReadBurstable] = *dd.BurstableReadSpeedMbps
} else {
speed[mkDiskSpeedReadBurstable] = 0
}
if dd.MaxWriteSpeedMbps != nil {
speed[mkDiskSpeedWrite] = *dd.MaxWriteSpeedMbps
} else {
speed[mkDiskSpeedWrite] = 0
}
if dd.BurstableWriteSpeedMbps != nil {
speed[mkDiskSpeedWriteBurstable] = *dd.BurstableWriteSpeedMbps
} else {
speed[mkDiskSpeedWriteBurstable] = 0
}
disk[mkDiskSpeed] = []interface{}{speed}
} else {
disk[mkDiskSpeed] = []interface{}{}
}
if dd.IOThread != nil {
disk[mkDiskIOThread] = *dd.IOThread
} else {
disk[mkDiskIOThread] = false
}
if dd.SSD != nil {
disk[mkDiskSSD] = *dd.SSD
} else {
disk[mkDiskSSD] = false
}
if dd.Discard != nil {
disk[mkDiskDiscard] = *dd.Discard
} else {
disk[mkDiskDiscard] = dvDiskDiscard
}
if dd.Cache != nil {
disk[mkDiskCache] = *dd.Cache
} else {
disk[mkDiskCache] = dvDiskCache
}
diskMap[di] = disk
}
if len(clone) == 0 || len(currentDiskList) > 0 {
orderedDiskList := orderedListFromMap(diskMap)
err := d.Set(mkDisk, orderedDiskList)
diags = append(diags, diag.FromErr(err)...)
}
return diags
}
func updateDisk(d *schema.ResourceData, vmConfig *vms.GetResponseData, updateBody *vms.UpdateRequestBody) error {
// Prepare the new disk device configuration.
if !d.HasChange(mkDisk) {
return nil
}
currentDisks := populateFileIDs(mapStorageDevices(vmConfig), d)
planDisks, err := getStorageDevicesFromResource(d)
if err != nil {
return err
}
addToDevices := func(ds vms.CustomStorageDevices, disk *vms.CustomStorageDevice) vms.CustomStorageDevices {
if ds == nil {
ds = vms.CustomStorageDevices{}
}
ds[*disk.Interface] = disk
return ds
}
for diskInterface, disk := range planDisks {
if currentDisks[diskInterface] == nil {
// TODO: create a new disk here
return fmt.Errorf("missing device %s", diskInterface)
}
// copy the current disk and update the fields
tmp := *currentDisks[diskInterface]
tmp.BurstableReadSpeedMbps = disk.BurstableReadSpeedMbps
tmp.BurstableWriteSpeedMbps = disk.BurstableWriteSpeedMbps
tmp.MaxReadSpeedMbps = disk.MaxReadSpeedMbps
tmp.MaxWriteSpeedMbps = disk.MaxWriteSpeedMbps
tmp.Cache = disk.Cache
tmp.Discard = disk.Discard
tmp.IOThread = disk.IOThread
tmp.SSD = disk.SSD
switch disk.StorageInterface() {
case "virtio":
updateBody.VirtualIODevices = addToDevices(updateBody.VirtualIODevices, &tmp)
case "sata":
updateBody.SATADevices = addToDevices(updateBody.SATADevices, &tmp)
case "scsi":
updateBody.SCSIDevices = addToDevices(updateBody.SCSIDevices, &tmp)
case "ide":
{
// Investigate whether to support IDE mapping.
}
default:
return fmt.Errorf("device storage interface %s not supported", disk.StorageInterface())
}
}
return nil
}
// mapStorageDevices maps the current VM storage devices by their interface names.
func mapStorageDevices(resp *vms.GetResponseData) map[string]*vms.CustomStorageDevice {
storageDevices := map[string]*vms.CustomStorageDevice{}
fillMap := func(iface string, dev *vms.CustomStorageDevice) {
if dev != nil {
d := *dev
if d.Size == nil {
d.Size = new(types.DiskSize)
}
d.Interface = &iface
storageDevices[iface] = &d
}
}
fillMap("ide0", resp.IDEDevice0)
fillMap("ide1", resp.IDEDevice1)
fillMap("ide2", resp.IDEDevice2)
fillMap("ide3", resp.IDEDevice3)
fillMap("sata0", resp.SATADevice0)
fillMap("sata1", resp.SATADevice1)
fillMap("sata2", resp.SATADevice2)
fillMap("sata3", resp.SATADevice3)
fillMap("sata4", resp.SATADevice4)
fillMap("sata5", resp.SATADevice5)
fillMap("scsi0", resp.SCSIDevice0)
fillMap("scsi1", resp.SCSIDevice1)
fillMap("scsi2", resp.SCSIDevice2)
fillMap("scsi3", resp.SCSIDevice3)
fillMap("scsi4", resp.SCSIDevice4)
fillMap("scsi5", resp.SCSIDevice5)
fillMap("scsi6", resp.SCSIDevice6)
fillMap("scsi7", resp.SCSIDevice7)
fillMap("scsi8", resp.SCSIDevice8)
fillMap("scsi9", resp.SCSIDevice9)
fillMap("scsi10", resp.SCSIDevice10)
fillMap("scsi11", resp.SCSIDevice11)
fillMap("scsi12", resp.SCSIDevice12)
fillMap("scsi13", resp.SCSIDevice13)
fillMap("virtio0", resp.VirtualIODevice0)
fillMap("virtio1", resp.VirtualIODevice1)
fillMap("virtio2", resp.VirtualIODevice2)
fillMap("virtio3", resp.VirtualIODevice3)
fillMap("virtio4", resp.VirtualIODevice4)
fillMap("virtio5", resp.VirtualIODevice5)
fillMap("virtio6", resp.VirtualIODevice6)
fillMap("virtio7", resp.VirtualIODevice7)
fillMap("virtio8", resp.VirtualIODevice8)
fillMap("virtio9", resp.VirtualIODevice9)
fillMap("virtio10", resp.VirtualIODevice10)
fillMap("virtio11", resp.VirtualIODevice11)
fillMap("virtio12", resp.VirtualIODevice12)
fillMap("virtio13", resp.VirtualIODevice13)
fillMap("virtio14", resp.VirtualIODevice14)
fillMap("virtio15", resp.VirtualIODevice15)
return storageDevices
}
// mapStorageDevices maps the current VM storage devices by their interface names.
func populateFileIDs(devices map[string]*vms.CustomStorageDevice, d *schema.ResourceData) map[string]*vms.CustomStorageDevice {
planDisk := d.Get(mkDisk)
planDiskList := planDisk.([]interface{})
planDiskMap := map[string]map[string]interface{}{}
for _, v := range planDiskList {
dm := v.(map[string]interface{})
iface := dm[mkDiskInterface].(string)
planDiskMap[iface] = dm
}
for k, v := range devices {
if v != nil && planDiskMap[k] != nil {
if planDiskMap[k][mkDiskFileID] != nil {
fileID := planDiskMap[k][mkDiskFileID].(string)
v.FileID = &fileID
}
}
}
return devices
}
// getDiskDatastores returns a list of the used datastores in a VM.
func getDiskDatastores(vm *vms.GetResponseData, d *schema.ResourceData) []string {
storageDevices := populateFileIDs(mapStorageDevices(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
}