From b2713e4e635edcf3e52df254bcc14f3489bf59a7 Mon Sep 17 00:00:00 2001 From: Dan Petersen Date: Sat, 28 Dec 2019 02:44:36 +0100 Subject: [PATCH] Continued work on VM resource --- proxmox/virtual_environment_vm_types.go | 273 ++++++++++++++++++- proxmoxtf/resource_virtual_environment_vm.go | 158 ++++++++++- 2 files changed, 421 insertions(+), 10 deletions(-) diff --git a/proxmox/virtual_environment_vm_types.go b/proxmox/virtual_environment_vm_types.go index 108be42c..90f7d5a2 100644 --- a/proxmox/virtual_environment_vm_types.go +++ b/proxmox/virtual_environment_vm_types.go @@ -5,6 +5,7 @@ package proxmox import ( + "encoding/json" "fmt" "net/url" "strconv" @@ -288,7 +289,9 @@ type VirtualEnvironmentVMGetResponseData struct { HookScript *string `json:"hookscript,omitempty"` Hotplug *CustomCommaSeparatedList `json:"hotplug,omitempty"` Hugepages *string `json:"hugepages,omitempty"` - IDEDevices *CustomStorageDevices `json:"ide,omitempty"` + IDEDevice0 *CustomStorageDevice `json:"ide0,omitempty"` + IDEDevice1 *CustomStorageDevice `json:"ide1,omitempty"` + IDEDevice2 *CustomStorageDevice `json:"ide2,omitempty"` KeyboardLayout *string `json:"keyboard,omitempty"` KVMArguments *CustomLineBreakSeparatedList `json:"args,omitempty"` KVMEnabled *CustomBool `json:"kvm,omitempty"` @@ -298,15 +301,41 @@ type VirtualEnvironmentVMGetResponseData struct { MigrateDowntime *float64 `json:"migrate_downtime,omitempty"` MigrateSpeed *int `json:"migrate_speed,omitempty"` Name *string `json:"name,omitempty"` - NetworkDevices *CustomNetworkDevices `json:"net,omitempty"` + NetworkDevice0 *CustomNetworkDevice `json:"net0,omitempty"` + NetworkDevice1 *CustomNetworkDevice `json:"net1,omitempty"` + NetworkDevice2 *CustomNetworkDevice `json:"net2,omitempty"` + NetworkDevice3 *CustomNetworkDevice `json:"net3,omitempty"` + NetworkDevice4 *CustomNetworkDevice `json:"net4,omitempty"` + NetworkDevice5 *CustomNetworkDevice `json:"net5,omitempty"` + NetworkDevice6 *CustomNetworkDevice `json:"net6,omitempty"` + NetworkDevice7 *CustomNetworkDevice `json:"net7,omitempty"` NUMADevices *CustomNUMADevices `json:"numa_devices,omitempty"` NUMAEnabled *CustomBool `json:"numa,omitempty"` OSType *string `json:"ostype,omitempty"` Overwrite *CustomBool `json:"force,omitempty"` PCIDevices *CustomPCIDevices `json:"hostpci,omitempty"` + PoolID *string `json:"pool,omitempty" url:"pool,omitempty"` Revert *string `json:"revert,omitempty"` - SATADevices *CustomStorageDevices `json:"sata,omitempty"` - SCSIDevices *CustomStorageDevices `json:"scsi,omitempty"` + SATADevice0 *CustomStorageDevice `json:"sata0,omitempty"` + SATADevice1 *CustomStorageDevice `json:"sata1,omitempty"` + SATADevice2 *CustomStorageDevice `json:"sata2,omitempty"` + SATADevice3 *CustomStorageDevice `json:"sata3,omitempty"` + SATADevice4 *CustomStorageDevice `json:"sata4,omitempty"` + SATADevice5 *CustomStorageDevice `json:"sata5,omitempty"` + SCSIDevice0 *CustomStorageDevice `json:"scsi0,omitempty"` + SCSIDevice1 *CustomStorageDevice `json:"scsi1,omitempty"` + SCSIDevice2 *CustomStorageDevice `json:"scsi2,omitempty"` + SCSIDevice3 *CustomStorageDevice `json:"scsi3,omitempty"` + SCSIDevice4 *CustomStorageDevice `json:"scsi4,omitempty"` + SCSIDevice5 *CustomStorageDevice `json:"scsi5,omitempty"` + SCSIDevice6 *CustomStorageDevice `json:"scsi6,omitempty"` + SCSIDevice7 *CustomStorageDevice `json:"scsi7,omitempty"` + SCSIDevice8 *CustomStorageDevice `json:"scsi8,omitempty"` + SCSIDevice9 *CustomStorageDevice `json:"scsi9,omitempty"` + SCSIDevice10 *CustomStorageDevice `json:"scsi10,omitempty"` + SCSIDevice11 *CustomStorageDevice `json:"scsi11,omitempty"` + SCSIDevice12 *CustomStorageDevice `json:"scsi12,omitempty"` + SCSIDevice13 *CustomStorageDevice `json:"scsi13,omitempty"` SCSIHardware *string `json:"scsihw,omitempty"` SerialDevices *CustomSerialDevices `json:"serial,omitempty"` SharedMemory *CustomSharedMemory `json:"ivshmem,omitempty"` @@ -908,3 +937,239 @@ func (r CustomWatchdogDevice) EncodeValues(key string, v *url.Values) error { return nil } + +// UnmarshalJSON converts a CustomAgent string to an object. +func (r *CustomAgent) UnmarshalJSON(b []byte) error { + var s string + + err := json.Unmarshal(b, &s) + + if err != nil { + return err + } + + pairs := strings.Split(s, ",") + + for _, p := range pairs { + v := strings.Split(strings.TrimSpace(p), "=") + + if len(v) == 1 { + enabled := CustomBool(v[0] == "1") + r.Enabled = &enabled + } else if len(v) == 2 { + switch v[0] { + case "enabled": + enabled := CustomBool(v[1] == "1") + r.Enabled = &enabled + case "fstrim_cloned_disks": + fstrim := CustomBool(v[1] == "1") + r.TrimClonedDisks = &fstrim + case "type": + r.Type = &v[1] + } + } + } + + return nil +} + +// UnmarshalJSON converts a CustomNetworkDevice string to an object. +func (r *CustomNetworkDevice) UnmarshalJSON(b []byte) error { + var s string + + err := json.Unmarshal(b, &s) + + if err != nil { + return err + } + + pairs := strings.Split(s, ",") + + for _, p := range pairs { + v := strings.Split(strings.TrimSpace(p), "=") + + if len(v) == 2 { + switch v[0] { + case "bridge": + r.Bridge = &v[1] + case "firewall": + bv := CustomBool(v[1] == "1") + r.Firewall = &bv + case "link_down": + bv := CustomBool(v[1] == "1") + r.LinkDown = &bv + case "macaddr": + r.MACAddress = &v[1] + case "model": + r.Model = v[1] + case "queues": + iv, err := strconv.Atoi(v[1]) + + if err != nil { + return err + } + + r.Queues = &iv + case "rate": + fv, err := strconv.ParseFloat(v[1], 64) + + if err != nil { + return err + } + + r.RateLimit = &fv + case "tag": + iv, err := strconv.Atoi(v[1]) + + if err != nil { + return err + } + + r.Tag = &iv + case "trunks": + trunks := strings.Split(v[1], ";") + r.Trunks = make([]int, len(trunks)) + + for i, trunk := range trunks { + iv, err := strconv.Atoi(trunk) + + if err != nil { + return err + } + + r.Trunks[i] = iv + } + } + } + } + + r.Enabled = true + + return nil +} + +// UnmarshalJSON converts a CustomSharedMemory string to an object. +func (r *CustomSharedMemory) UnmarshalJSON(b []byte) error { + var s string + + err := json.Unmarshal(b, &s) + + if err != nil { + return err + } + + pairs := strings.Split(s, ",") + + for _, p := range pairs { + v := strings.Split(strings.TrimSpace(p), "=") + + if len(v) == 2 { + switch v[0] { + case "name": + r.Name = &v[1] + case "size": + r.Size, err = strconv.Atoi(v[1]) + + if err != nil { + return err + } + } + } + } + + return nil +} + +// UnmarshalJSON converts a CustomSMBIOS string to an object. +func (r *CustomSMBIOS) UnmarshalJSON(b []byte) error { + var s string + + err := json.Unmarshal(b, &s) + + if err != nil { + return err + } + + pairs := strings.Split(s, ",") + + for _, p := range pairs { + v := strings.Split(strings.TrimSpace(p), "=") + + if len(v) == 2 { + switch v[0] { + case "base64": + base64 := CustomBool(v[1] == "1") + r.Base64 = &base64 + case "family": + r.Family = &v[1] + case "manufacturer": + r.Manufacturer = &v[1] + case "product": + r.Product = &v[1] + case "serial": + r.Serial = &v[1] + case "sku": + r.SKU = &v[1] + case "uuid": + r.UUID = &v[1] + case "version": + r.Version = &v[1] + } + } + } + + return nil +} + +// UnmarshalJSON converts a CustomStorageDevice string to an object. +func (r *CustomStorageDevice) UnmarshalJSON(b []byte) error { + var s string + + err := json.Unmarshal(b, &s) + + if err != nil { + return err + } + + pairs := strings.Split(s, ",") + + for _, p := range pairs { + v := strings.Split(strings.TrimSpace(p), "=") + + if len(v) == 1 { + r.FileVolume = v[0] + } else if len(v) == 2 { + switch v[0] { + case "aio": + r.AIO = &v[1] + case "backup": + bv := CustomBool(v[1] == "1") + r.BackupEnabled = &bv + case "file": + r.FileVolume = v[1] + case "mbps_rd": + iv, err := strconv.Atoi(v[1]) + + if err != nil { + return err + } + + r.MaxReadSpeedMbps = &iv + case "mbps_wr": + iv, err := strconv.Atoi(v[1]) + + if err != nil { + return err + } + + r.MaxWriteSpeedMbps = &iv + case "media": + r.Media = &v[1] + } + } + } + + r.Enabled = true + + return nil +} diff --git a/proxmoxtf/resource_virtual_environment_vm.go b/proxmoxtf/resource_virtual_environment_vm.go index edbca436..31ccfa9e 100644 --- a/proxmoxtf/resource_virtual_environment_vm.go +++ b/proxmoxtf/resource_virtual_environment_vm.go @@ -374,6 +374,7 @@ func resourceVirtualEnvironmentVM() *schema.Resource { Type: schema.TypeList, Description: "The disk devices", Optional: true, + ForceNew: true, DefaultFunc: func() (interface{}, error) { defaultList := make([]interface{}, 1) defaultMap := make(map[string]interface{}) @@ -392,18 +393,21 @@ func resourceVirtualEnvironmentVM() *schema.Resource { mkResourceVirtualEnvironmentVMDiskDatastoreID: { Type: schema.TypeString, Optional: true, + ForceNew: true, Description: "The datastore id", Default: dvResourceVirtualEnvironmentVMDiskDatastoreID, }, mkResourceVirtualEnvironmentVMDiskEnabled: { Type: schema.TypeBool, Optional: true, + ForceNew: true, Description: "Whether to enable the disk", Default: dvResourceVirtualEnvironmentVMDiskEnabled, }, mkResourceVirtualEnvironmentVMDiskFileFormat: { Type: schema.TypeString, Optional: true, + ForceNew: true, Description: "The file format", Default: dvResourceVirtualEnvironmentVMDiskFileFormat, ValidateFunc: getFileFormatValidator(), @@ -411,6 +415,7 @@ func resourceVirtualEnvironmentVM() *schema.Resource { mkResourceVirtualEnvironmentVMDiskFileID: { Type: schema.TypeString, Optional: true, + ForceNew: true, Description: "The file id for a disk image", Default: dvResourceVirtualEnvironmentVMDiskFileID, ValidateFunc: getFileIDValidator(), @@ -418,6 +423,7 @@ func resourceVirtualEnvironmentVM() *schema.Resource { mkResourceVirtualEnvironmentVMDiskSize: { Type: schema.TypeInt, Optional: true, + ForceNew: true, Description: "The disk size in gigabytes", Default: dvResourceVirtualEnvironmentVMDiskSize, ValidateFunc: validation.IntBetween(1, 8192), @@ -540,10 +546,17 @@ func resourceVirtualEnvironmentVM() *schema.Resource { Default: dvResourceVirtualEnvironmentVMNetworkDeviceEnabled, }, mkResourceVirtualEnvironmentVMNetworkDeviceMACAddress: { - Type: schema.TypeString, - Optional: true, - Description: "The MAC address", - Default: dvResourceVirtualEnvironmentVMNetworkDeviceMACAddress, + Type: schema.TypeString, + Optional: true, + Description: "The MAC address", + Default: dvResourceVirtualEnvironmentVMNetworkDeviceMACAddress, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if old == "" { + return true + } + + return false + }, ValidateFunc: getMACAddressValidator(), }, mkResourceVirtualEnvironmentVMNetworkDeviceModel: { @@ -571,6 +584,7 @@ func resourceVirtualEnvironmentVM() *schema.Resource { Type: schema.TypeString, Description: "The node name", Required: true, + ForceNew: true, }, mkResourceVirtualEnvironmentVMOSType: { Type: schema.TypeString, @@ -876,6 +890,7 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e nodeName := d.Get(mkResourceVirtualEnvironmentVMNodeName).(string) osType := d.Get(mkResourceVirtualEnvironmentVMOSType).(string) poolID := d.Get(mkResourceVirtualEnvironmentVMPoolID).(string) + started := proxmox.CustomBool(d.Get(mkResourceVirtualEnvironmentVMStarted).(bool)) vmID := d.Get(mkResourceVirtualEnvironmentVMVMID).(int) if vmID == -1 { @@ -921,7 +936,6 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e } scsiHardware := "virtio-scsi-pci" - startOnBoot := proxmox.CustomBool(true) tabletDeviceEnabled := proxmox.CustomBool(true) body := &proxmox.VirtualEnvironmentVMCreateRequestBody{ @@ -945,7 +959,7 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e SCSIHardware: &scsiHardware, SerialDevices: []string{"socket"}, SharedMemory: memorySharedObject, - StartOnBoot: &startOnBoot, + StartOnBoot: &started, TabletDeviceEnabled: &tabletDeviceEnabled, VMID: &vmID, } @@ -1136,6 +1150,138 @@ func resourceVirtualEnvironmentVMRead(d *schema.ResourceData, m interface{}) err return err } + // Retrieve the entire configuration in order to compare it to the state. + vmConfig, err := veClient.GetVM(nodeName, vmID) + + if err != nil { + if strings.Contains(err.Error(), "HTTP 404") { + d.SetId("") + + return nil + } + + return err + } + + if vmConfig.Agent != nil { + agent := make(map[string]interface{}) + + if vmConfig.Agent.Enabled != nil { + agent[mkResourceVirtualEnvironmentVMAgentEnabled] = bool(*vmConfig.Agent.Enabled) + } else { + agent[mkResourceVirtualEnvironmentVMAgentEnabled] = dvResourceVirtualEnvironmentVMAgentEnabled + } + + if vmConfig.Agent.TrimClonedDisks != nil { + agent[mkResourceVirtualEnvironmentVMAgentTrim] = bool(*vmConfig.Agent.TrimClonedDisks) + } else { + agent[mkResourceVirtualEnvironmentVMAgentTrim] = dvResourceVirtualEnvironmentVMAgentTrim + } + + if vmConfig.Agent.Type != nil { + agent[mkResourceVirtualEnvironmentVMAgentType] = *vmConfig.Agent.Type + } else { + agent[mkResourceVirtualEnvironmentVMAgentType] = dvResourceVirtualEnvironmentVMAgentType + } + + currentAgent := d.Get(mkResourceVirtualEnvironmentVMAgent).([]interface{}) + + if len(currentAgent) > 0 || + agent[mkResourceVirtualEnvironmentVMAgentEnabled] != dvResourceVirtualEnvironmentVMAgentEnabled || + agent[mkResourceVirtualEnvironmentVMAgentTrim] != dvResourceVirtualEnvironmentVMAgentTrim || + agent[mkResourceVirtualEnvironmentVMAgentType] != dvResourceVirtualEnvironmentVMAgentType { + d.Set(mkResourceVirtualEnvironmentVMAgent, []interface{}{agent}) + } + } else { + d.Set(mkResourceVirtualEnvironmentVMAgent, make([]interface{}, 0)) + } + + cpu := make(map[string]interface{}) + + if vmConfig.CPUCores != nil { + cpu[mkResourceVirtualEnvironmentVMCPUCores] = *vmConfig.CPUCores + } else { + cpu[mkResourceVirtualEnvironmentVMCPUCores] = dvResourceVirtualEnvironmentVMCPUCores + } + + if vmConfig.VirtualCPUCount != nil { + cpu[mkResourceVirtualEnvironmentVMCPUHotplugged] = *vmConfig.VirtualCPUCount + } else { + cpu[mkResourceVirtualEnvironmentVMCPUHotplugged] = dvResourceVirtualEnvironmentVMCPUHotplugged + } + + if vmConfig.CPUSockets != nil { + cpu[mkResourceVirtualEnvironmentVMCPUSockets] = *vmConfig.CPUSockets + } else { + cpu[mkResourceVirtualEnvironmentVMCPUSockets] = dvResourceVirtualEnvironmentVMCPUSockets + } + + currentCPU := d.Get(mkResourceVirtualEnvironmentVMCPU).([]interface{}) + + if len(currentCPU) > 0 || + cpu[mkResourceVirtualEnvironmentVMCPUCores] != dvResourceVirtualEnvironmentVMCPUCores || + cpu[mkResourceVirtualEnvironmentVMCPUHotplugged] != dvResourceVirtualEnvironmentVMCPUHotplugged || + cpu[mkResourceVirtualEnvironmentVMCPUSockets] != dvResourceVirtualEnvironmentVMCPUSockets { + d.Set(mkResourceVirtualEnvironmentVMCPU, []interface{}{cpu}) + } + + if vmConfig.Description != nil { + d.Set(mkResourceVirtualEnvironmentVMDescription, *vmConfig.Description) + } else { + d.Set(mkResourceVirtualEnvironmentVMDescription, "") + } + + if vmConfig.KeyboardLayout != nil { + d.Set(mkResourceVirtualEnvironmentVMKeyboardLayout, *vmConfig.KeyboardLayout) + } else { + d.Set(mkResourceVirtualEnvironmentVMKeyboardLayout, "") + } + + memory := make(map[string]interface{}) + + if vmConfig.DedicatedMemory != nil { + memory[mkResourceVirtualEnvironmentVMMemoryDedicated] = *vmConfig.DedicatedMemory + } else { + memory[mkResourceVirtualEnvironmentVMMemoryDedicated] = dvResourceVirtualEnvironmentVMMemoryDedicated + } + + if vmConfig.FloatingMemory != nil { + memory[mkResourceVirtualEnvironmentVMMemoryFloating] = *vmConfig.FloatingMemory + } else { + memory[mkResourceVirtualEnvironmentVMMemoryFloating] = dvResourceVirtualEnvironmentVMMemoryFloating + } + + if vmConfig.SharedMemory != nil { + memory[mkResourceVirtualEnvironmentVMMemoryShared] = vmConfig.SharedMemory.Size + } else { + memory[mkResourceVirtualEnvironmentVMMemoryShared] = dvResourceVirtualEnvironmentVMMemoryShared + } + + currentMemory := d.Get(mkResourceVirtualEnvironmentVMMemory).([]interface{}) + + if len(currentMemory) > 0 || + memory[mkResourceVirtualEnvironmentVMMemoryDedicated] != dvResourceVirtualEnvironmentVMMemoryDedicated || + memory[mkResourceVirtualEnvironmentVMMemoryFloating] != dvResourceVirtualEnvironmentVMMemoryFloating || + memory[mkResourceVirtualEnvironmentVMMemoryShared] != dvResourceVirtualEnvironmentVMMemoryShared { + d.Set(mkResourceVirtualEnvironmentVMMemory, []interface{}{memory}) + } + + if vmConfig.Name != nil { + d.Set(mkResourceVirtualEnvironmentVMName, *vmConfig.Name) + } else { + d.Set(mkResourceVirtualEnvironmentVMName, "") + } + + if vmConfig.OSType != nil { + d.Set(mkResourceVirtualEnvironmentVMOSType, *vmConfig.OSType) + } else { + d.Set(mkResourceVirtualEnvironmentVMOSType, "") + } + + if vmConfig.PoolID != nil { + d.Set(mkResourceVirtualEnvironmentVMPoolID, *vmConfig.PoolID) + } + // Determine the state of the virtual machine in order to update the "started" argument. status, err := veClient.GetVMStatus(nodeName, vmID)