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

feat(vm): efi disk, cpu numa (#384)

* efi disks support

* make size a string

* add cpu numa support

* add docs

* fix description

* string size fixes

(cherry picked from commit 254724d1d10c5191d294f0377adac49ad41dc880)

* fix lints

* fix(vm): replace `size` with `type` for efi disk

also
- add support for `pre-enrolled-keys`
- add example
- fix handling of `numa` flag when it's not in the template
- cleanup linter errors

---------

Co-authored-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
Leah 2023-07-01 14:50:21 +02:00 committed by GitHub
parent 23a519475d
commit e9a74e9037
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 468 additions and 58 deletions

View File

@ -165,6 +165,7 @@ output "ubuntu_vm_public_key" {
protection for AMD models.
- `hotplugged` - (Optional) The number of hotplugged vCPUs (defaults
to `0`).
- `numa` - (Boolean) Enable/disable NUMA. (default to `false`)
- `sockets` - (Optional) The number of CPU sockets (defaults to `1`).
- `type` - (Optional) The emulated CPU type (defaults to `qemu64`).
- `486` - Intel 486.
@ -241,6 +242,19 @@ output "ubuntu_vm_public_key" {
- `ssd` - (Optional) Whether to use an SSD emulation option for this disk (
defaults to `false`). Note that SSD emulation is not supported on VirtIO
Block drives.
- `efi_disk` - (Optional) The efi disk device (required if `bios` is set
to `ovmf`)
- `datastore_id` (Optional) The identifier for the datastore to create
the disk in (defaults to `local-lvm`).
- `file_format` (Optional) The file format.
- `type` (Optional) Size and type of the OVMF EFI disk. `4m` is newer and
recommended, and required for Secure Boot. For backwards compatibility
use `2m`. Ignored for VMs with cpu.architecture=`aarch64` (defaults
to `2m`).
- `pre_enrolled_keys` (Optional) Use am EFI vars template with
distribution-specific and Microsoft Standard keys enrolled, if used with
EFI type=`4m`. Ignored for VMs with cpu.architecture=`aarch64` (defaults
to `false`).
- `hostpci` - (Optional) A host PCI device mapping (multiple blocks supported).
- `device` - (Required) The PCI device name for Proxmox, in form
of `hostpciX` where `X` is a sequential number from 0 to 3.
@ -284,7 +298,7 @@ output "ubuntu_vm_public_key" {
- `user_data_file_id` - (Optional) The identifier for a file containing
custom user data (conflicts with `user_account`).
- `vendor_data_file_id` - (Optional) The identifier for a file containing
all vendor data passed to the VM via cloud-init.
all vendor data passed to the VM via cloud-init.
- `meta_data_file_id` - (Optional) The identifier for a file containing
all meta data passed to the VM via cloud-init.
- `keyboard_layout` - (Optional) The keyboard layout (defaults to `en-us`).

View File

@ -9,6 +9,16 @@ resource "proxmox_virtual_environment_vm" "example_template" {
description = "Managed by Terraform"
cpu {
numa = true
}
efi_disk {
datastore_id = local.datastore_id
file_format = "raw"
type = "4m"
}
# disk {
# datastore_id = local.datastore_id
# file_id = proxmox_virtual_environment_file.ubuntu_cloud_image.id

View File

@ -82,9 +82,10 @@ type CustomCPUEmulation struct {
// CustomEFIDisk handles QEMU EFI disk parameters.
type CustomEFIDisk struct {
Size *types.DiskSize `json:"size,omitempty" url:"size,omitempty"`
FileVolume string `json:"file" url:"file"`
Format *string `json:"format,omitempty" url:"format,omitempty"`
FileVolume string `json:"file" url:"file"`
Format *string `json:"format,omitempty" url:"format,omitempty"`
Type *string `json:"efitype,omitempty" url:"efitype,omitempty"`
PreEnrolledKeys *types2.CustomBool `json:"pre-enrolled-keys,omitempty" url:"pre-enrolled-keys,omitempty,int"`
}
// CustomNetworkDevice handles QEMU network device parameters.
@ -784,8 +785,16 @@ func (r CustomEFIDisk) EncodeValues(key string, v *url.Values) error {
values = append(values, fmt.Sprintf("format=%s", *r.Format))
}
if r.Size != nil {
values = append(values, fmt.Sprintf("size=%s", *r.Size))
if r.Type != nil {
values = append(values, fmt.Sprintf("efitype=%s", *r.Type))
}
if r.PreEnrolledKeys != nil {
if *r.PreEnrolledKeys {
values = append(values, "pre-enrolled-keys=1")
} else {
values = append(values, "pre-enrolled-keys=0")
}
}
v.Add(key, strings.Join(values, ","))
@ -1472,22 +1481,25 @@ func (r *CustomEFIDisk) UnmarshalJSON(b []byte) error {
pairs := strings.Split(s, ",")
for _, p := range pairs {
for i, p := range pairs {
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 1 && i == 0 {
r.FileVolume = v[0]
}
if len(v) == 2 {
switch v[0] {
case "format":
r.Format = &v[1]
case "file":
r.FileVolume = v[1]
case "size":
r.Size = new(types.DiskSize)
err := r.Size.UnmarshalJSON([]byte(v[1]))
if err != nil {
return fmt.Errorf("failed to unmarshal disk size: %w", err)
}
case "format":
r.Format = &v[1]
case "efitype":
t := strings.ToLower(v[1])
r.Type = &t
case "pre-enrolled-keys":
bv := types2.CustomBool(v[1] == "1")
r.PreEnrolledKeys = &bv
}
}
}

View File

@ -24,7 +24,12 @@ type DiskSize int64
// String returns the string representation of the disk size.
func (r DiskSize) String() string {
return formatDiskSize(int64(r))
return FormatDiskSize(r)
}
// InMegabytes returns the disk size in megabytes.
func (r DiskSize) InMegabytes() int {
return int(int64(r) / 1024 / 1024)
}
// InGigabytes returns the disk size in gigabytes.
@ -39,7 +44,7 @@ func DiskSizeFromGigabytes(size int) DiskSize {
// MarshalJSON marshals a disk size into a Proxmox API `<DiskSize>` string.
func (r DiskSize) MarshalJSON() ([]byte, error) {
bytes, err := json.Marshal(formatDiskSize(int64(r)))
bytes, err := json.Marshal(FormatDiskSize(r))
if err != nil {
return nil, fmt.Errorf("cannot marshal disk size: %w", err)
}
@ -51,27 +56,23 @@ func (r DiskSize) MarshalJSON() ([]byte, error) {
func (r *DiskSize) UnmarshalJSON(b []byte) error {
s := string(b)
size, err := parseDiskSize(&s)
size, err := ParseDiskSize(s)
if err != nil {
return err
}
*r = DiskSize(size)
*r = size
return nil
}
// parseDiskSize parses a disk size string into a number of bytes.
func parseDiskSize(size *string) (int64, error) {
if size == nil {
return 0, nil
}
matches := sizeRegex.FindStringSubmatch(*size)
// ParseDiskSize parses a disk size string into a number of bytes.
func ParseDiskSize(size string) (DiskSize, error) {
matches := sizeRegex.FindStringSubmatch(size)
if len(matches) > 0 {
fsize, err := strconv.ParseFloat(matches[1], 64)
if err != nil {
return -1, fmt.Errorf("cannot parse disk size \"%s\": %w", *size, err)
return -1, fmt.Errorf("cannot parse disk size \"%s\": %w", size, err)
}
switch strings.ToLower(matches[3]) {
@ -85,13 +86,14 @@ func parseDiskSize(size *string) (int64, error) {
fsize = fsize * 1024 * 1024 * 1024 * 1024
}
return int64(math.Ceil(fsize)), nil
return DiskSize(math.Ceil(fsize)), nil
}
return -1, fmt.Errorf("cannot parse disk size \"%s\"", *size)
return -1, fmt.Errorf("cannot parse disk size \"%s\"", size)
}
func formatDiskSize(size int64) string {
// FormatDiskSize turns a number of bytes into a disk size string.
func FormatDiskSize(size DiskSize) string {
if size < 0 {
return ""
}

View File

@ -17,37 +17,36 @@ func TestParseDiskSize(t *testing.T) {
tests := []struct {
name string
size *string
size string
want int64
wantErr bool
}{
{"handle null size", nil, 0, false},
{"parse TB", StrPtr("2TB"), 2199023255552, false},
{"parse T", StrPtr("2T"), 2199023255552, false},
{"parse fraction T", StrPtr("2.2T"), 2418925581108, false},
{"parse GB", StrPtr("2GB"), 2147483648, false},
{"parse G", StrPtr("2G"), 2147483648, false},
{"parse M", StrPtr("2048M"), 2147483648, false},
{"parse MB", StrPtr("2048MB"), 2147483648, false},
{"parse MiB", StrPtr("2048MiB"), 2147483648, false},
{"parse K", StrPtr("1K"), 1024, false},
{"parse KB", StrPtr("2KB"), 2048, false},
{"parse KiB", StrPtr("4KiB"), 4096, false},
{"parse no units as bytes", StrPtr("12345"), 12345, false},
{"error on bad format string", StrPtr("20l8G"), -1, true},
{"error on unknown unit string", StrPtr("2048W"), -1, true},
{"error on arbitrary string", StrPtr("something"), -1, true},
{"parse TB", "2TB", 2199023255552, false},
{"parse T", "2T", 2199023255552, false},
{"parse fraction T", "2.2T", 2418925581108, false},
{"parse GB", "2GB", 2147483648, false},
{"parse G", "2G", 2147483648, false},
{"parse M", "2048M", 2147483648, false},
{"parse MB", "2048MB", 2147483648, false},
{"parse MiB", "2048MiB", 2147483648, false},
{"parse K", "1K", 1024, false},
{"parse KB", "2KB", 2048, false},
{"parse KiB", "4KiB", 4096, false},
{"parse no units as bytes", "12345", 12345, false},
{"error on bad format string", "20l8G", -1, true},
{"error on unknown unit string", "2048W", -1, true},
{"error on arbitrary string", "something", -1, true},
}
for _, test := range tests {
tt := test
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := parseDiskSize(tt.size)
got, err := ParseDiskSize(tt.size)
if (err != nil) != tt.wantErr {
t.Errorf("parseDiskSize() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
if int64(got) != tt.want {
t.Errorf("parseDiskSize() got = %v, want %v", got, tt.want)
}
})
@ -73,7 +72,7 @@ func TestFormatDiskSize(t *testing.T) {
tt := test
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := formatDiskSize(tt.size); got != tt.want {
if got := FormatDiskSize(DiskSize(tt.size)); got != tt.want {
t.Errorf("formatDiskSize() = %v, want %v", got, tt.want)
}
})

View File

@ -23,6 +23,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/vms"
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
)
func getBIOSValidator() schema.SchemaValidateDiagFunc {
@ -186,6 +187,29 @@ func getFileIDValidator() schema.SchemaValidateDiagFunc {
})
}
//nolint:unused
func getFileSizeValidator() 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 v != "" {
_, err := types.ParseDiskSize(v)
if err != nil {
es = append(es, fmt.Errorf("expected %s to be a valid file size (100, 1M, 1G), got %s", k, v))
return nil, es
}
}
return []string{}, es
})
}
func getKeyboardLayoutValidator() schema.SchemaValidateDiagFunc {
return validation.ToDiagFunc(validation.StringInSlice([]string{
"da",
@ -506,6 +530,11 @@ func getDiskDatastores(vm *vms.GetResponseData, d *schema.ResourceData) []string
datastoresSet[fileIDParts[0]] = 1
}
if vm.EFIDisk != nil {
fileIDParts := strings.Split(vm.EFIDisk.FileVolume, ":")
datastoresSet[fileIDParts[0]] = 1
}
datastores := []string{}
for datastore := range datastoresSet {
datastores = append(datastores, datastore)

View File

@ -47,6 +47,7 @@ const (
dvResourceVirtualEnvironmentVMCPUArchitecture = "x86_64"
dvResourceVirtualEnvironmentVMCPUCores = 1
dvResourceVirtualEnvironmentVMCPUHotplugged = 0
dvResourceVirtualEnvironmentVMCPUNUMA = false
dvResourceVirtualEnvironmentVMCPUSockets = 1
dvResourceVirtualEnvironmentVMCPUType = "qemu64"
dvResourceVirtualEnvironmentVMCPUUnits = 1024
@ -63,6 +64,10 @@ const (
dvResourceVirtualEnvironmentVMDiskSpeedReadBurstable = 0
dvResourceVirtualEnvironmentVMDiskSpeedWrite = 0
dvResourceVirtualEnvironmentVMDiskSpeedWriteBurstable = 0
dvResourceVirtualEnvironmentVMEFIDiskDatastoreID = "local-lvm"
dvResourceVirtualEnvironmentVMEFIDiskFileFormat = "qcow2"
dvResourceVirtualEnvironmentVMEFIDiskType = "2m"
dvResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys = false
dvResourceVirtualEnvironmentVMInitializationDatastoreID = "local-lvm"
dvResourceVirtualEnvironmentVMInitializationDNSDomain = ""
dvResourceVirtualEnvironmentVMInitializationDNSServer = ""
@ -139,6 +144,7 @@ const (
mkResourceVirtualEnvironmentVMCPUCores = "cores"
mkResourceVirtualEnvironmentVMCPUFlags = "flags"
mkResourceVirtualEnvironmentVMCPUHotplugged = "hotplugged"
mkResourceVirtualEnvironmentVMCPUNUMA = "numa"
mkResourceVirtualEnvironmentVMCPUSockets = "sockets"
mkResourceVirtualEnvironmentVMCPUType = "type"
mkResourceVirtualEnvironmentVMCPUUnits = "units"
@ -157,6 +163,11 @@ const (
mkResourceVirtualEnvironmentVMDiskSpeedReadBurstable = "read_burstable"
mkResourceVirtualEnvironmentVMDiskSpeedWrite = "write"
mkResourceVirtualEnvironmentVMDiskSpeedWriteBurstable = "write_burstable"
mkResourceVirtualEnvironmentVMEFIDisk = "efi_disk"
mkResourceVirtualEnvironmentVMEFIDiskDatastoreID = "datastore_id"
mkResourceVirtualEnvironmentVMEFIDiskFileFormat = "file_format"
mkResourceVirtualEnvironmentVMEFIDiskType = "type"
mkResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys = "pre_enrolled_keys"
mkResourceVirtualEnvironmentVMHostPCI = "hostpci"
mkResourceVirtualEnvironmentVMHostPCIDevice = "device"
mkResourceVirtualEnvironmentVMHostPCIDeviceID = "id"
@ -449,6 +460,7 @@ func VM() *schema.Resource {
mkResourceVirtualEnvironmentVMCPUArchitecture: dvResourceVirtualEnvironmentVMCPUArchitecture,
mkResourceVirtualEnvironmentVMCPUCores: dvResourceVirtualEnvironmentVMCPUCores,
mkResourceVirtualEnvironmentVMCPUFlags: []interface{}{},
mkResourceVirtualEnvironmentVMCPUNUMA: dvResourceVirtualEnvironmentVMCPUNUMA,
mkResourceVirtualEnvironmentVMCPUHotplugged: dvResourceVirtualEnvironmentVMCPUHotplugged,
mkResourceVirtualEnvironmentVMCPUSockets: dvResourceVirtualEnvironmentVMCPUSockets,
mkResourceVirtualEnvironmentVMCPUType: dvResourceVirtualEnvironmentVMCPUType,
@ -488,6 +500,12 @@ func VM() *schema.Resource {
Default: dvResourceVirtualEnvironmentVMCPUHotplugged,
ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 2304)),
},
mkResourceVirtualEnvironmentVMCPUNUMA: {
Type: schema.TypeBool,
Description: "Enable/disable NUMA.",
Optional: true,
Default: dvResourceVirtualEnvironmentVMCPUNUMA,
},
mkResourceVirtualEnvironmentVMCPUSockets: {
Type: schema.TypeInt,
Description: "The number of CPU sockets",
@ -644,6 +662,61 @@ func VM() *schema.Resource {
MaxItems: 14,
MinItems: 0,
},
mkResourceVirtualEnvironmentVMEFIDisk: {
Type: schema.TypeList,
Description: "The efidisk device",
Optional: true,
ForceNew: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{
map[string]interface{}{
mkResourceVirtualEnvironmentVMEFIDiskDatastoreID: dvResourceVirtualEnvironmentVMEFIDiskDatastoreID,
mkResourceVirtualEnvironmentVMEFIDiskType: dvResourceVirtualEnvironmentVMEFIDiskType,
mkResourceVirtualEnvironmentVMEFIDiskFileFormat: dvResourceVirtualEnvironmentVMEFIDiskFileFormat,
mkResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys: dvResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys,
},
}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMEFIDiskDatastoreID: {
Type: schema.TypeString,
Description: "The datastore id",
Optional: true,
Default: dvResourceVirtualEnvironmentVMEFIDiskDatastoreID,
},
mkResourceVirtualEnvironmentVMEFIDiskFileFormat: {
Type: schema.TypeString,
Description: "The file format",
Optional: true,
ForceNew: true,
Computed: true,
ValidateDiagFunc: getFileFormatValidator(),
},
mkResourceVirtualEnvironmentVMEFIDiskType: {
Type: schema.TypeString,
Description: "Size and type of the OVMF EFI disk",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentVMEFIDiskType,
ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{
"2m",
"4m",
}, true)),
},
mkResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys: {
Type: schema.TypeBool,
Description: "Use an EFI vars template with distribution-specific and Microsoft Standard " +
"keys enrolled, if used with efi type=`4m`.",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys,
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkResourceVirtualEnvironmentVMInitialization: {
Type: schema.TypeList,
Description: "The cloud-init configuration",
@ -1544,6 +1617,7 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
cpuCores := cpuBlock[mkResourceVirtualEnvironmentVMCPUCores].(int)
cpuFlags := cpuBlock[mkResourceVirtualEnvironmentVMCPUFlags].([]interface{})
cpuHotplugged := cpuBlock[mkResourceVirtualEnvironmentVMCPUHotplugged].(int)
cpuNUMA := types2.CustomBool(cpuBlock[mkResourceVirtualEnvironmentVMCPUNUMA].(bool))
cpuSockets := cpuBlock[mkResourceVirtualEnvironmentVMCPUSockets].(int)
cpuType := cpuBlock[mkResourceVirtualEnvironmentVMCPUType].(string)
cpuUnits := cpuBlock[mkResourceVirtualEnvironmentVMCPUUnits].(int)
@ -1565,6 +1639,7 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
Flags: &cpuFlagsConverted,
Type: cpuType,
}
updateBody.NUMAEnabled = &cpuNUMA
updateBody.CPUSockets = &cpuSockets
updateBody.CPUUnits = &cpuUnits
@ -1694,6 +1769,7 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
}
disk := d.Get(mkResourceVirtualEnvironmentVMDisk).([]interface{})
efiDisk := d.Get(mkResourceVirtualEnvironmentVMEFIDisk).([]interface{})
vmConfig, e := vmAPI.GetVM(ctx)
if e != nil {
@ -1805,6 +1881,65 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
}
}
efiDiskInfo := vmGetEfiDisk(d, nil) // from the resource config
for i := range efiDisk {
diskBlock := efiDisk[i].(map[string]interface{})
diskInterface := "efidisk0"
dataStoreID := diskBlock[mkResourceVirtualEnvironmentVMEFIDiskDatastoreID].(string)
efiType := diskBlock[mkResourceVirtualEnvironmentVMEFIDiskType].(string)
currentDiskInfo := vmConfig.EFIDisk
configuredDiskInfo := efiDiskInfo
if currentDiskInfo == nil {
diskUpdateBody := &vms.UpdateRequestBody{}
diskUpdateBody.EFIDisk = configuredDiskInfo
e = vmAPI.UpdateVM(ctx, diskUpdateBody)
if e != nil {
return diag.FromErr(e)
}
continue
}
if &efiType != currentDiskInfo.Type {
return diag.Errorf(
"resizing of efidisks is not supported.",
)
}
deleteOriginalDisk := types2.CustomBool(true)
diskMoveBody := &vms.MoveDiskRequestBody{
DeleteOriginalDisk: &deleteOriginalDisk,
Disk: diskInterface,
TargetStorage: dataStoreID,
}
moveDisk := false
if dataStoreID != "" {
moveDisk = true
if allDiskInfo[diskInterface] != nil {
fileIDParts := strings.Split(allDiskInfo[diskInterface].FileVolume, ":")
moveDisk = dataStoreID != fileIDParts[0]
}
}
if moveDisk {
moveDiskTimeout := d.Get(mkResourceVirtualEnvironmentVMTimeoutMoveDisk).(int)
e = vmAPI.MoveVMDisk(ctx, diskMoveBody, moveDiskTimeout)
if e != nil {
return diag.FromErr(e)
}
}
}
return vmCreateStart(ctx, d, m)
}
@ -1879,6 +2014,7 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
cpuFlags := cpuBlock[mkResourceVirtualEnvironmentVMCPUFlags].([]interface{})
cpuHotplugged := cpuBlock[mkResourceVirtualEnvironmentVMCPUHotplugged].(int)
cpuSockets := cpuBlock[mkResourceVirtualEnvironmentVMCPUSockets].(int)
cpuNUMA := types2.CustomBool(cpuBlock[mkResourceVirtualEnvironmentVMCPUNUMA].(bool))
cpuType := cpuBlock[mkResourceVirtualEnvironmentVMCPUType].(string)
cpuUnits := cpuBlock[mkResourceVirtualEnvironmentVMCPUUnits].(int)
@ -1888,6 +2024,29 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
return diag.FromErr(err)
}
var efiDisk *vms.CustomEFIDisk
efiDiskBlock := d.Get(mkResourceVirtualEnvironmentVMEFIDisk).([]interface{})
if len(efiDiskBlock) > 0 {
block := efiDiskBlock[0].(map[string]interface{})
datastoreID, _ := block[mkResourceVirtualEnvironmentVMEFIDiskDatastoreID].(string)
fileFormat, _ := block[mkResourceVirtualEnvironmentVMEFIDiskFileFormat].(string)
efiType, _ := block[mkResourceVirtualEnvironmentVMEFIDiskType].(string)
preEnrolledKeys := types2.CustomBool(block[mkResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys].(bool))
if fileFormat == "" {
fileFormat = dvResourceVirtualEnvironmentVMEFIDiskFileFormat
}
efiDisk = &vms.CustomEFIDisk{
Type: &efiType,
FileVolume: fmt.Sprintf("%s:1", datastoreID),
Format: &fileFormat,
PreEnrolledKeys: &preEnrolledKeys,
}
}
virtioDeviceObjects := diskDeviceObjects["virtio"]
scsiDeviceObjects := diskDeviceObjects["scsi"]
// ideDeviceObjects := getOrderedDiskDeviceList(diskDeviceObjects, "ide")
@ -2055,10 +2214,12 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
CPUSockets: &cpuSockets,
CPUUnits: &cpuUnits,
DedicatedMemory: &memoryDedicated,
EFIDisk: efiDisk,
FloatingMemory: &memoryFloating,
IDEDevices: ideDevices,
KeyboardLayout: &keyboardLayout,
NetworkDevices: networkDeviceObjects,
NUMAEnabled: &cpuNUMA,
OSType: &operatingSystemType,
PCIDevices: pciDeviceObjects,
SCSIHardware: &scsiHardware,
@ -2616,6 +2777,69 @@ func vmGetDiskDeviceObjects(
return diskDeviceObjects, nil
}
func vmGetEfiDisk(d *schema.ResourceData, disk []interface{}) *vms.CustomEFIDisk {
var efiDisk []interface{}
if disk != nil {
efiDisk = disk
} else {
efiDisk = d.Get(mkResourceVirtualEnvironmentVMEFIDisk).([]interface{})
}
var efiDiskConfig *vms.CustomEFIDisk
if len(efiDisk) > 0 {
efiDiskConfig = &vms.CustomEFIDisk{}
block := efiDisk[0].(map[string]interface{})
datastoreID, _ := block[mkResourceVirtualEnvironmentVMEFIDiskDatastoreID].(string)
fileFormat, _ := block[mkResourceVirtualEnvironmentVMEFIDiskFileFormat].(string)
efiType, _ := block[mkResourceVirtualEnvironmentVMEFIDiskType].(string)
preEnrolledKeys := types2.CustomBool(block[mkResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys].(bool))
// special case for efi disk, the size is ignored, see docs for more info
efiDiskConfig.FileVolume = fmt.Sprintf("%s:1", datastoreID)
efiDiskConfig.Format = &fileFormat
efiDiskConfig.Type = &efiType
efiDiskConfig.PreEnrolledKeys = &preEnrolledKeys
}
return efiDiskConfig
}
func vmGetEfiDiskAsStorageDevice(d *schema.ResourceData, disk []interface{}) (*vms.CustomStorageDevice, error) {
efiDisk := vmGetEfiDisk(d, disk)
var storageDevice *vms.CustomStorageDevice
if efiDisk != nil {
id := "0"
baseDiskInterface := "efidisk"
diskInterface := fmt.Sprint(baseDiskInterface, id)
storageDevice = &vms.CustomStorageDevice{
Enabled: true,
FileVolume: efiDisk.FileVolume,
Format: efiDisk.Format,
Interface: &diskInterface,
ID: &id,
}
if efiDisk.Type != nil {
ds, err := types.ParseDiskSize(*efiDisk.Type)
if err != nil {
return nil, fmt.Errorf("invalid efi disk type: %s", err.Error())
}
sizeInt := ds.InMegabytes()
storageDevice.Size = &ds
storageDevice.SizeInt = &sizeInt
}
}
return storageDevice, nil
}
func vmGetHostPCIDeviceObjects(d *schema.ResourceData) vms.CustomPCIDevices {
pciDevice := d.Get(mkResourceVirtualEnvironmentVMHostPCI).([]interface{})
pciDeviceObjects := make(vms.CustomPCIDevices, len(pciDevice))
@ -3038,6 +3262,13 @@ func vmReadCustom(
cpu[mkResourceVirtualEnvironmentVMCPUHotplugged] = 0
}
if vmConfig.NUMAEnabled != nil {
cpu[mkResourceVirtualEnvironmentVMCPUNUMA] = *vmConfig.NUMAEnabled
} else {
// Default value of "numa" is "false" according to the API documentation.
cpu[mkResourceVirtualEnvironmentVMCPUNUMA] = false
}
if vmConfig.CPUSockets != nil {
cpu[mkResourceVirtualEnvironmentVMCPUSockets] = *vmConfig.CPUSockets
} else {
@ -3212,6 +3443,60 @@ func vmReadCustom(
diags = append(diags, diag.FromErr(err)...)
}
//nolint:nestif
if vmConfig.EFIDisk != nil {
efiDisk := map[string]interface{}{}
fileIDParts := strings.Split(vmConfig.EFIDisk.FileVolume, ":")
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskDatastoreID] = fileIDParts[0]
if vmConfig.EFIDisk.Format != nil {
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskFileFormat] = *vmConfig.EFIDisk.Format
} else {
// disk format may not be returned by config API if it is default for the storage, and that may be different
// from the default qcow2, so we need to read it from the storage API to make sure we have the correct value
files, err := api.Node(nodeName).ListDatastoreFiles(ctx, fileIDParts[0])
if err != nil {
diags = append(diags, diag.FromErr(err)...)
} else {
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskFileFormat] = ""
for _, v := range files {
if v.VolumeID == vmConfig.EFIDisk.FileVolume {
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskFileFormat] = v.FileFormat
break
}
}
}
}
if vmConfig.EFIDisk.Type != nil {
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskType] = *vmConfig.EFIDisk.Type
} else {
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskType] = dvResourceVirtualEnvironmentVMEFIDiskType
}
if vmConfig.EFIDisk.PreEnrolledKeys != nil {
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys] = *vmConfig.EFIDisk.PreEnrolledKeys
} else {
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys] = false
}
currentEfiDisk := d.Get(mkResourceVirtualEnvironmentVMEFIDisk).([]interface{})
if len(clone) > 0 && len(currentEfiDisk) > 0 {
err := d.Set(mkResourceVirtualEnvironmentVMEFIDisk, []interface{}{efiDisk})
diags = append(diags, diag.FromErr(err)...)
} else if len(currentEfiDisk) > 0 ||
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskDatastoreID] != dvResourceVirtualEnvironmentVMEFIDiskDatastoreID ||
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskType] != dvResourceVirtualEnvironmentVMEFIDiskType ||
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys] != dvResourceVirtualEnvironmentVMEFIDiskPreEnrolledKeys || //nolint:lll
efiDisk[mkResourceVirtualEnvironmentVMEFIDiskFileFormat] != dvResourceVirtualEnvironmentVMEFIDiskFileFormat {
err := d.Set(mkResourceVirtualEnvironmentVMEFIDisk, []interface{}{efiDisk})
diags = append(diags, diag.FromErr(err)...)
}
}
currentPCIList := d.Get(mkResourceVirtualEnvironmentVMHostPCI).([]interface{})
pciMap := map[string]interface{}{}
var orderedPCIList []interface{}
@ -4216,6 +4501,7 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
cpuCores := cpuBlock[mkResourceVirtualEnvironmentVMCPUCores].(int)
cpuFlags := cpuBlock[mkResourceVirtualEnvironmentVMCPUFlags].([]interface{})
cpuHotplugged := cpuBlock[mkResourceVirtualEnvironmentVMCPUHotplugged].(int)
cpuNUMA := types2.CustomBool(cpuBlock[mkResourceVirtualEnvironmentVMCPUNUMA].(bool))
cpuSockets := cpuBlock[mkResourceVirtualEnvironmentVMCPUSockets].(int)
cpuType := cpuBlock[mkResourceVirtualEnvironmentVMCPUType].(string)
cpuUnits := cpuBlock[mkResourceVirtualEnvironmentVMCPUUnits].(int)
@ -4229,6 +4515,7 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
updateBody.CPUCores = &cpuCores
updateBody.CPUSockets = &cpuSockets
updateBody.CPUUnits = &cpuUnits
updateBody.NUMAEnabled = &cpuNUMA
if cpuHotplugged > 0 {
updateBody.VirtualCPUCount = &cpuHotplugged
@ -4311,6 +4598,15 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
}
}
// Prepare the new efi disk configuration.
if d.HasChange(mkResourceVirtualEnvironmentVMEFIDisk) {
efiDisk := vmGetEfiDisk(d, nil)
updateBody.EFIDisk = efiDisk
rebootRequired = true
}
// Prepare the new cloud-init configuration.
if d.HasChange(mkResourceVirtualEnvironmentVMInitialization) {
initializationConfig := vmGetCloudInitConfig(d)
@ -4330,15 +4626,14 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
Media: &cdromMedia,
}
if vmConfig.IDEDevice2 != nil {
if strings.Contains(
if vmConfig.IDEDevice2 != nil &&
strings.Contains(
vmConfig.IDEDevice2.FileVolume,
fmt.Sprintf("vm-%d-cloudinit", vmID),
) {
tmp := updateBody.IDEDevices["ide2"]
tmp.Enabled = true
updateBody.IDEDevices["ide2"] = tmp
}
tmp := updateBody.IDEDevices["ide2"]
tmp.Enabled = true
updateBody.IDEDevices["ide2"] = tmp
}
}
@ -4535,6 +4830,37 @@ func vmUpdateDiskLocationAndSize(
return diag.FromErr(err)
}
// Add efidisk if it has changes
if d.HasChange(mkResourceVirtualEnvironmentVMEFIDisk) {
diskOld, diskNew := d.GetChange(mkResourceVirtualEnvironmentVMEFIDisk)
oldEfiDisk, e := vmGetEfiDiskAsStorageDevice(d, diskOld.([]interface{}))
if e != nil {
return diag.FromErr(e)
}
newEfiDisk, e := vmGetEfiDiskAsStorageDevice(d, diskNew.([]interface{}))
if e != nil {
return diag.FromErr(e)
}
if oldEfiDisk != nil {
baseDiskInterface := diskDigitPrefix(*oldEfiDisk.Interface)
diskOldEntries[baseDiskInterface][*oldEfiDisk.Interface] = *oldEfiDisk
}
if newEfiDisk != nil {
baseDiskInterface := diskDigitPrefix(*newEfiDisk.Interface)
diskNewEntries[baseDiskInterface][*newEfiDisk.Interface] = *newEfiDisk
}
if oldEfiDisk != nil && newEfiDisk != nil && oldEfiDisk.Size != newEfiDisk.Size {
return diag.Errorf(
"resizing of efidisks is not supported.",
)
}
}
var diskMoveBodies []*vms.MoveDiskRequestBody
var diskResizeBodies []*vms.ResizeDiskRequestBody
@ -4566,7 +4892,7 @@ func vmUpdateDiskLocationAndSize(
shutdownForDisksRequired = true
}
if *oldDisk.SizeInt <= *diskNewEntries[prefix][oldKey].SizeInt {
if *oldDisk.SizeInt < *diskNewEntries[prefix][oldKey].SizeInt {
diskResizeBodies = append(
diskResizeBodies,
&vms.ResizeDiskRequestBody{

View File

@ -45,6 +45,7 @@ func TestVMSchema(t *testing.T) {
mkResourceVirtualEnvironmentVMCPU,
mkResourceVirtualEnvironmentVMDescription,
mkResourceVirtualEnvironmentVMDisk,
mkResourceVirtualEnvironmentVMEFIDisk,
mkResourceVirtualEnvironmentVMInitialization,
mkResourceVirtualEnvironmentVMHostPCI,
mkResourceVirtualEnvironmentVMKeyboardLayout,
@ -80,6 +81,7 @@ func TestVMSchema(t *testing.T) {
mkResourceVirtualEnvironmentVMCPU: schema.TypeList,
mkResourceVirtualEnvironmentVMDescription: schema.TypeString,
mkResourceVirtualEnvironmentVMDisk: schema.TypeList,
mkResourceVirtualEnvironmentVMEFIDisk: schema.TypeList,
mkResourceVirtualEnvironmentVMHostPCI: schema.TypeList,
mkResourceVirtualEnvironmentVMInitialization: schema.TypeList,
mkResourceVirtualEnvironmentVMIPv4Addresses: schema.TypeList,
@ -165,6 +167,7 @@ func TestVMSchema(t *testing.T) {
mkResourceVirtualEnvironmentVMCPUCores,
mkResourceVirtualEnvironmentVMCPUFlags,
mkResourceVirtualEnvironmentVMCPUHotplugged,
mkResourceVirtualEnvironmentVMCPUNUMA,
mkResourceVirtualEnvironmentVMCPUSockets,
mkResourceVirtualEnvironmentVMCPUType,
mkResourceVirtualEnvironmentVMCPUUnits,
@ -175,6 +178,7 @@ func TestVMSchema(t *testing.T) {
mkResourceVirtualEnvironmentVMCPUCores: schema.TypeInt,
mkResourceVirtualEnvironmentVMCPUFlags: schema.TypeList,
mkResourceVirtualEnvironmentVMCPUHotplugged: schema.TypeInt,
mkResourceVirtualEnvironmentVMCPUNUMA: schema.TypeBool,
mkResourceVirtualEnvironmentVMCPUSockets: schema.TypeInt,
mkResourceVirtualEnvironmentVMCPUType: schema.TypeString,
mkResourceVirtualEnvironmentVMCPUUnits: schema.TypeInt,
@ -216,6 +220,20 @@ func TestVMSchema(t *testing.T) {
mkResourceVirtualEnvironmentVMDiskSpeedWriteBurstable: schema.TypeInt,
})
efiDiskSchema := test.AssertNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentVMEFIDisk)
test.AssertOptionalArguments(t, efiDiskSchema, []string{
mkResourceVirtualEnvironmentVMEFIDiskDatastoreID,
mkResourceVirtualEnvironmentVMEFIDiskFileFormat,
mkResourceVirtualEnvironmentVMEFIDiskType,
})
test.AssertValueTypes(t, efiDiskSchema, map[string]schema.ValueType{
mkResourceVirtualEnvironmentVMEFIDiskDatastoreID: schema.TypeString,
mkResourceVirtualEnvironmentVMEFIDiskFileFormat: schema.TypeString,
mkResourceVirtualEnvironmentVMEFIDiskType: schema.TypeString,
})
initializationSchema := test.AssertNestedSchemaExistence(
t,
s,