From 1b29c0fffa7dd52c4ed4196dc29e2dea9ebfd426 Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Mon, 15 Apr 2024 13:32:42 +0200 Subject: [PATCH 1/4] test: `QemuGuestAgent` --- proxmox/config_qemu_guestagent_test.go | 51 +++++++++++ proxmox/config_qemu_test.go | 115 +++++++++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 proxmox/config_qemu_guestagent_test.go diff --git a/proxmox/config_qemu_guestagent_test.go b/proxmox/config_qemu_guestagent_test.go new file mode 100644 index 00000000..7554fb71 --- /dev/null +++ b/proxmox/config_qemu_guestagent_test.go @@ -0,0 +1,51 @@ +package proxmox + +import ( + "errors" + "testing" + + "github.com/Telmate/proxmox-api-go/internal/util" + "github.com/stretchr/testify/require" +) + +func Test_QemuGuestAgent_Validate(t *testing.T) { + tests := []struct { + name string + input QemuGuestAgent + output error + }{ + {name: "Valid Type", + input: QemuGuestAgent{Type: util.Pointer(QemuGuestAgentType("isa"))}}, + {name: "Invalid Type", + input: QemuGuestAgent{Type: util.Pointer(QemuGuestAgentType("invalid"))}, + output: errors.New(QemuGuestAgentType_Error_Invalid)}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require.Equal(t, test.output, test.input.Validate()) + }) + } +} + +func Test_QemuGuestAgentType_Validate(t *testing.T) { + tests := []struct { + name string + input QemuGuestAgentType + output error + }{ + {name: `Valid ""`, + input: QemuGuestAgentType("")}, + {name: "Valid lowercase", + input: QemuGuestAgentType("virtio")}, + {name: "Valid UpperCase", + input: QemuGuestAgentType("VirtIO")}, + {name: `Invalid`, + input: QemuGuestAgentType("invalid"), + output: errors.New(QemuGuestAgentType_Error_Invalid)}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require.Equal(t, test.output, test.input.Validate()) + }) + } +} diff --git a/proxmox/config_qemu_test.go b/proxmox/config_qemu_test.go index 8686a5ce..77d36ec3 100644 --- a/proxmox/config_qemu_test.go +++ b/proxmox/config_qemu_test.go @@ -34,6 +34,30 @@ func Test_ConfigQemu_mapToApiValues(t *testing.T) { vmr *VmRef output map[string]interface{} }{ + // TODO add option for create and update, as the crate and update are the same for many settings like 'Agent' + // Create Agent + {name: `Create Agent=nil`, + config: &ConfigQemu{}, + output: map[string]interface{}{}}, + {name: `Create Agent Full`, + config: &ConfigQemu{Agent: &QemuGuestAgent{ + Enable: util.Pointer(true), + Type: util.Pointer(QemuGuestAgentType_VirtIO), + Freeze: util.Pointer(true), + FsTrim: util.Pointer(true)}}, + output: map[string]interface{}{"agent": "1,freeze-fs-on-backup=1,fstrim_cloned_disks=1,type=virtio"}}, + {name: `Create Agent.Enable`, + config: &ConfigQemu{Agent: &QemuGuestAgent{Enable: util.Pointer(true)}}, + output: map[string]interface{}{"agent": "1"}}, + {name: `Create Agent.Type`, + config: &ConfigQemu{Agent: &QemuGuestAgent{Type: util.Pointer(QemuGuestAgentType_VirtIO)}}, + output: map[string]interface{}{"agent": "0,type=virtio"}}, + {name: `Create Agent.Freeze`, + config: &ConfigQemu{Agent: &QemuGuestAgent{Freeze: util.Pointer(true)}}, + output: map[string]interface{}{"agent": "0,freeze-fs-on-backup=1"}}, + {name: `Create Agent.FsTrim`, + config: &ConfigQemu{Agent: &QemuGuestAgent{FsTrim: util.Pointer(true)}}, + output: map[string]interface{}{"agent": "0,fstrim_cloned_disks=1"}}, // Create Disks // Create Disks.Ide @@ -1291,6 +1315,70 @@ func Test_ConfigQemu_mapToApiValues(t *testing.T) { output: map[string]interface{}{"delete": "tpmstate0"}}, // Update + // Update agent + {name: `Update Agent !nil nil`, + config: &ConfigQemu{Agent: &QemuGuestAgent{}}, + output: map[string]interface{}{"agent": "0"}}, + {name: `Update Agent nil !nil`, + config: &ConfigQemu{}, + currentConfig: ConfigQemu{Agent: &QemuGuestAgent{ + Enable: util.Pointer(true), + Type: util.Pointer(QemuGuestAgentType_VirtIO), + Freeze: util.Pointer(true), + FsTrim: util.Pointer(true)}}, + output: map[string]interface{}{}}, + {name: `Update Agent nil nil `, + config: &ConfigQemu{}, + output: map[string]interface{}{}}, + {name: `Update Agent.Enable !nil nil`, + config: &ConfigQemu{Agent: &QemuGuestAgent{Enable: util.Pointer(true)}}, + currentConfig: ConfigQemu{Agent: &QemuGuestAgent{}}, + output: map[string]interface{}{"agent": "1"}}, + {name: `Update Agent.Enable nil !nil`, + config: &ConfigQemu{Agent: &QemuGuestAgent{}}, + currentConfig: ConfigQemu{Agent: &QemuGuestAgent{Enable: util.Pointer(true)}}, + output: map[string]interface{}{"agent": "1"}}, + {name: `Update Agent.Enable nil nil`, + config: &ConfigQemu{Agent: &QemuGuestAgent{}}, + currentConfig: ConfigQemu{Agent: &QemuGuestAgent{}}, + output: map[string]interface{}{"agent": "0"}}, + {name: `Update Agent.Type !nil nil`, + config: &ConfigQemu{Agent: &QemuGuestAgent{Type: util.Pointer(QemuGuestAgentType_VirtIO)}}, + currentConfig: ConfigQemu{Agent: &QemuGuestAgent{}}, + output: map[string]interface{}{"agent": "0,type=virtio"}}, + {name: `Update Agent.Type nil !nil`, + config: &ConfigQemu{Agent: &QemuGuestAgent{}}, + currentConfig: ConfigQemu{Agent: &QemuGuestAgent{Type: util.Pointer(QemuGuestAgentType_VirtIO)}}, + output: map[string]interface{}{"agent": "0,type=virtio"}}, + {name: `Update Agent.Type nil nil`, + config: &ConfigQemu{Agent: &QemuGuestAgent{}}, + currentConfig: ConfigQemu{Agent: &QemuGuestAgent{}}, + output: map[string]interface{}{"agent": "0"}}, + {name: `Update Agent.Freeze !nil nil`, + config: &ConfigQemu{Agent: &QemuGuestAgent{Freeze: util.Pointer(false)}}, + currentConfig: ConfigQemu{Agent: &QemuGuestAgent{}}, + output: map[string]interface{}{"agent": "0,freeze-fs-on-backup=0"}}, + {name: `Update Agent.Freeze nil !nil`, + config: &ConfigQemu{Agent: &QemuGuestAgent{}}, + currentConfig: ConfigQemu{Agent: &QemuGuestAgent{Freeze: util.Pointer(true)}}, + output: map[string]interface{}{"agent": "0,freeze-fs-on-backup=1"}}, + {name: `Update Agent.Freeze nil nil`, + config: &ConfigQemu{Agent: &QemuGuestAgent{}}, + currentConfig: ConfigQemu{Agent: &QemuGuestAgent{}}, + output: map[string]interface{}{"agent": "0"}}, + {name: `Update Agent.FsTrim !nil nil`, + config: &ConfigQemu{Agent: &QemuGuestAgent{FsTrim: util.Pointer(false)}}, + currentConfig: ConfigQemu{Agent: &QemuGuestAgent{}}, + output: map[string]interface{}{"agent": "0,fstrim_cloned_disks=0"}}, + {name: `Update Agent.FsTrim nil !nil`, + config: &ConfigQemu{Agent: &QemuGuestAgent{}}, + currentConfig: ConfigQemu{Agent: &QemuGuestAgent{FsTrim: util.Pointer(true)}}, + output: map[string]interface{}{"agent": "0,fstrim_cloned_disks=1"}}, + {name: `Update Agent.FsTrim nil nil`, + config: &ConfigQemu{Agent: &QemuGuestAgent{}}, + currentConfig: ConfigQemu{Agent: &QemuGuestAgent{}}, + output: map[string]interface{}{"agent": "0"}}, + // Update Disk // Update Disk.Ide {name: "Update Disk.Ide.Disk_X DELETE", currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{}}}}, @@ -3297,6 +3385,26 @@ func Test_ConfigQemu_mapToStruct(t *testing.T) { err error }{ // TODO add test cases for all other items of ConfigQemu{} + {name: `Agent ALL`, + input: map[string]interface{}{"agent": string("1,freeze-fs-on-backup=1,fstrim_cloned_disks=1,type=virtio")}, + output: &ConfigQemu{Agent: &QemuGuestAgent{ + Enable: util.Pointer(true), + Freeze: util.Pointer(true), + FsTrim: util.Pointer(true), + Type: util.Pointer(QemuGuestAgentType_VirtIO), + }}}, + {name: `Agent Enabled`, + input: map[string]interface{}{"agent": string("1")}, + output: &ConfigQemu{Agent: &QemuGuestAgent{Enable: util.Pointer(true)}}}, + {name: `Agent Freeze`, + input: map[string]interface{}{"agent": string("0,freeze-fs-on-backup=1")}, + output: &ConfigQemu{Agent: &QemuGuestAgent{Enable: util.Pointer(false), Freeze: util.Pointer(true)}}}, + {name: `Agent FsTrim`, + input: map[string]interface{}{"agent": string("0,fstrim_cloned_disks=1")}, + output: &ConfigQemu{Agent: &QemuGuestAgent{Enable: util.Pointer(false), FsTrim: util.Pointer(true)}}}, + {name: `Agent Type`, + input: map[string]interface{}{"agent": string("1,type=virtio")}, + output: &ConfigQemu{Agent: &QemuGuestAgent{Enable: util.Pointer(true), Type: util.Pointer(QemuGuestAgentType_VirtIO)}}}, // Disks Ide CdRom {name: "Disks Ide CdRom none", input: map[string]interface{}{"ide1": "none,media=cdrom"}, @@ -5934,6 +6042,9 @@ func Test_ConfigQemu_Validate(t *testing.T) { err error }{ // Valid + // Valid Agent + {name: "Valid Agent", + input: ConfigQemu{Agent: &QemuGuestAgent{Type: util.Pointer(QemuGuestAgentType("isa"))}}}, // Valid Disks {name: "Valid Disks Empty 0", input: ConfigQemu{Disks: &QemuStorages{}}, @@ -6052,6 +6163,10 @@ func Test_ConfigQemu_Validate(t *testing.T) { input: ConfigQemu{TPM: &TpmState{Storage: "test"}}, current: &ConfigQemu{TPM: &TpmState{Storage: "test", Version: util.Pointer(TpmVersion("v1.2"))}}}, // Invalid + // Invalid Agent + {name: "Invalid Agent", + input: ConfigQemu{Agent: &QemuGuestAgent{Type: util.Pointer(QemuGuestAgentType("test"))}}, + err: errors.New(QemuGuestAgentType_Error_Invalid)}, // Invalid Disks Mutually exclusive Ide {name: "Invalid Disks MutuallyExclusive Ide 0", input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{ From 7ab5a3320dbf374fab49a5cf77b9264985e71908 Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Mon, 15 Apr 2024 13:32:57 +0200 Subject: [PATCH 2/4] feat: func `boolToIntString()` --- proxmox/util.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/proxmox/util.go b/proxmox/util.go index c8168982..04beb558 100644 --- a/proxmox/util.go +++ b/proxmox/util.go @@ -184,6 +184,14 @@ func failError(err error) { } } +// Convert a bool to a string "1" or "0" +func boolToIntString(b bool) string { + if b { + return "1" + } + return "0" +} + func arrayToStringArray[T fmt.Stringer](arr []T) []string { strArr := make([]string, len(arr)) for i, v := range arr { From 1a187586392e6dcf7cb71bc2a2c80b2d99c33533 Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Mon, 15 Apr 2024 13:33:56 +0200 Subject: [PATCH 3/4] feat: `QemuGuestAgent` --- proxmox/config_qemu.go | 127 +++++++++++++++--------------- proxmox/config_qemu_guestagent.go | 92 ++++++++++++++++++++++ 2 files changed, 155 insertions(+), 64 deletions(-) create mode 100644 proxmox/config_qemu_guestagent.go diff --git a/proxmox/config_qemu.go b/proxmox/config_qemu.go index ae9e9220..a12cfc44 100644 --- a/proxmox/config_qemu.go +++ b/proxmox/config_qemu.go @@ -38,60 +38,60 @@ type AgentNetworkInterface struct { // ConfigQemu - Proxmox API QEMU options type ConfigQemu struct { - Agent int `json:"agent,omitempty"` // TODO should probably be a bool - Args string `json:"args,omitempty"` - Balloon int `json:"balloon,omitempty"` // TODO should probably be a bool - Bios string `json:"bios,omitempty"` - Boot string `json:"boot,omitempty"` // TODO should be an array of custom enums - BootDisk string `json:"bootdisk,omitempty"` // TODO discuss deprecation? Only returned as it's deprecated in the proxmox api - CIcustom string `json:"cicustom,omitempty"` // TODO should be part of a cloud-init struct (cloud-init option) - CIpassword string `json:"cipassword,omitempty"` // TODO should be part of a cloud-init struct (cloud-init option) - CIuser string `json:"ciuser,omitempty"` // TODO should be part of a cloud-init struct (cloud-init option) - Description string `json:"description,omitempty"` - Disks *QemuStorages `json:"disks,omitempty"` - EFIDisk QemuDevice `json:"efidisk,omitempty"` // TODO should be a struct - FullClone *int `json:"fullclone,omitempty"` // TODO should probably be a bool - HaGroup string `json:"hagroup,omitempty"` - HaState string `json:"hastate,omitempty"` // TODO should be custom type with enum - Hookscript string `json:"hookscript,omitempty"` - Hotplug string `json:"hotplug,omitempty"` // TODO should be a struct - Ipconfig IpconfigMap `json:"ipconfig,omitempty"` // TODO should be part of a cloud-init struct (cloud-init option) - Iso *IsoFile `json:"iso,omitempty"` // Same as Disks.Ide.Disk_2.CdRom.Iso - LinkedVmId uint `json:"linked_id,omitempty"` // Only returned setting it has no effect - Machine string `json:"machine,omitempty"` // TODO should be custom type with enum - Memory int `json:"memory,omitempty"` // TODO should be uint - Name string `json:"name,omitempty"` // TODO should be custom type as there are character and length limitations - Nameserver string `json:"nameserver,omitempty"` // TODO should be part of a cloud-init struct (cloud-init option) - Node string `json:"node,omitempty"` // Only returned setting it has no effect, set node in the VmRef instead - Onboot *bool `json:"onboot,omitempty"` - Pool string `json:"pool,omitempty"` // TODO should be custom type as there are character and length limitations - Protection *bool `json:"protection,omitempty"` - QemuCores int `json:"cores,omitempty"` // TODO should be uint - QemuCpu string `json:"cpu,omitempty"` // TODO should be custom type with enum - QemuDisks QemuDevices `json:"disk,omitempty"` // DEPRECATED use Disks *QemuStorages instead - QemuIso string `json:"qemuiso,omitempty"` // DEPRECATED use Iso *IsoFile instead - QemuKVM *bool `json:"kvm,omitempty"` - QemuNetworks QemuDevices `json:"network,omitempty"` // TODO should be a struct - QemuNuma *bool `json:"numa,omitempty"` - QemuOs string `json:"ostype,omitempty"` - QemuPCIDevices QemuDevices `json:"hostpci,omitempty"` // TODO should be a struct - QemuPxe bool `json:"pxe,omitempty"` - QemuSerials QemuDevices `json:"serial,omitempty"` // TODO should be a struct - QemuSockets int `json:"sockets,omitempty"` // TODO should be uint - QemuUnusedDisks QemuDevices `json:"unused,omitempty"` // TODO should be a struct - QemuUsbs QemuDevices `json:"usb,omitempty"` // TODO should be a struct - QemuVcpus int `json:"vcpus,omitempty"` // TODO should be uint - QemuVga QemuDevice `json:"vga,omitempty"` // TODO should be a struct - RNGDrive QemuDevice `json:"rng0,omitempty"` // TODO should be a struct - Scsihw string `json:"scsihw,omitempty"` // TODO should be custom type with enum - Searchdomain string `json:"searchdomain,omitempty"` // TODO should be part of a cloud-init struct (cloud-init option) - Smbios1 string `json:"smbios1,omitempty"` // TODO should be custom type with enum? - Sshkeys string `json:"sshkeys,omitempty"` // TODO should be an array of strings - Startup string `json:"startup,omitempty"` // TODO should be a struct? - TPM *TpmState `json:"tpm,omitempty"` - Tablet *bool `json:"tablet,omitempty"` - Tags string `json:"tags,omitempty"` // TODO should be an array of a custom type as there are character and length limitations - VmID int `json:"vmid,omitempty"` // TODO should be a custom type as there are limitations + Agent *QemuGuestAgent `json:"agent,omitempty"` + Args string `json:"args,omitempty"` + Balloon int `json:"balloon,omitempty"` // TODO should probably be a bool + Bios string `json:"bios,omitempty"` + Boot string `json:"boot,omitempty"` // TODO should be an array of custom enums + BootDisk string `json:"bootdisk,omitempty"` // TODO discuss deprecation? Only returned as it's deprecated in the proxmox api + CIcustom string `json:"cicustom,omitempty"` // TODO should be part of a cloud-init struct (cloud-init option) + CIpassword string `json:"cipassword,omitempty"` // TODO should be part of a cloud-init struct (cloud-init option) + CIuser string `json:"ciuser,omitempty"` // TODO should be part of a cloud-init struct (cloud-init option) + Description string `json:"description,omitempty"` + Disks *QemuStorages `json:"disks,omitempty"` + EFIDisk QemuDevice `json:"efidisk,omitempty"` // TODO should be a struct + FullClone *int `json:"fullclone,omitempty"` // TODO should probably be a bool + HaGroup string `json:"hagroup,omitempty"` + HaState string `json:"hastate,omitempty"` // TODO should be custom type with enum + Hookscript string `json:"hookscript,omitempty"` + Hotplug string `json:"hotplug,omitempty"` // TODO should be a struct + Ipconfig IpconfigMap `json:"ipconfig,omitempty"` // TODO should be part of a cloud-init struct (cloud-init option) + Iso *IsoFile `json:"iso,omitempty"` // Same as Disks.Ide.Disk_2.CdRom.Iso + LinkedVmId uint `json:"linked_id,omitempty"` // Only returned setting it has no effect + Machine string `json:"machine,omitempty"` // TODO should be custom type with enum + Memory int `json:"memory,omitempty"` // TODO should be uint + Name string `json:"name,omitempty"` // TODO should be custom type as there are character and length limitations + Nameserver string `json:"nameserver,omitempty"` // TODO should be part of a cloud-init struct (cloud-init option) + Node string `json:"node,omitempty"` // Only returned setting it has no effect, set node in the VmRef instead + Onboot *bool `json:"onboot,omitempty"` + Pool string `json:"pool,omitempty"` // TODO should be custom type as there are character and length limitations + Protection *bool `json:"protection,omitempty"` + QemuCores int `json:"cores,omitempty"` // TODO should be uint + QemuCpu string `json:"cpu,omitempty"` // TODO should be custom type with enum + QemuDisks QemuDevices `json:"disk,omitempty"` // DEPRECATED use Disks *QemuStorages instead + QemuIso string `json:"qemuiso,omitempty"` // DEPRECATED use Iso *IsoFile instead + QemuKVM *bool `json:"kvm,omitempty"` + QemuNetworks QemuDevices `json:"network,omitempty"` // TODO should be a struct + QemuNuma *bool `json:"numa,omitempty"` + QemuOs string `json:"ostype,omitempty"` + QemuPCIDevices QemuDevices `json:"hostpci,omitempty"` // TODO should be a struct + QemuPxe bool `json:"pxe,omitempty"` + QemuSerials QemuDevices `json:"serial,omitempty"` // TODO should be a struct + QemuSockets int `json:"sockets,omitempty"` // TODO should be uint + QemuUnusedDisks QemuDevices `json:"unused,omitempty"` // TODO should be a struct + QemuUsbs QemuDevices `json:"usb,omitempty"` // TODO should be a struct + QemuVcpus int `json:"vcpus,omitempty"` // TODO should be uint + QemuVga QemuDevice `json:"vga,omitempty"` // TODO should be a struct + RNGDrive QemuDevice `json:"rng0,omitempty"` // TODO should be a struct + Scsihw string `json:"scsihw,omitempty"` // TODO should be custom type with enum + Searchdomain string `json:"searchdomain,omitempty"` // TODO should be part of a cloud-init struct (cloud-init option) + Smbios1 string `json:"smbios1,omitempty"` // TODO should be custom type with enum? + Sshkeys string `json:"sshkeys,omitempty"` // TODO should be an array of strings + Startup string `json:"startup,omitempty"` // TODO should be a struct? + TPM *TpmState `json:"tpm,omitempty"` + Tablet *bool `json:"tablet,omitempty"` + Tags string `json:"tags,omitempty"` // TODO should be an array of a custom type as there are character and length limitations + VmID int `json:"vmid,omitempty"` // TODO should be a custom type as there are limitations } const ( @@ -188,8 +188,8 @@ func (config ConfigQemu) mapToApiValues(currentConfig ConfigQemu) (rebootRequire if config.Args != "" { params["args"] = config.Args } - if config.Agent != 0 { - params["agent"] = config.Agent + if config.Agent != nil { + params["agent"] = config.Agent.mapToAPI(currentConfig.Agent) } if config.Balloon >= 1 { params["balloon"] = config.Balloon @@ -366,14 +366,8 @@ func (ConfigQemu) mapToStruct(vmr *VmRef, params map[string]interface{}) (*Confi config.VmID = vmr.vmId } - if _, isSet := params["agent"]; isSet { - switch params["agent"].(type) { - case float64: - config.Agent = int(params["agent"].(float64)) - case string: - AgentConfList := strings.Split(params["agent"].(string), ",") - config.Agent, _ = strconv.Atoi(AgentConfList[0]) - } + if v, isSet := params["agent"]; isSet { + config.Agent = QemuGuestAgent{}.mapToSDK(v.(string)) } if _, isSet := params["args"]; isSet { config.Args = strings.TrimSpace(params["args"].(string)) @@ -894,6 +888,11 @@ func (newConfig ConfigQemu) setAdvanced(currentConfig *ConfigQemu, rebootIfNeede func (config ConfigQemu) Validate(current *ConfigQemu) (err error) { // TODO test all other use cases // TODO has no context about changes caused by updating the vm + if config.Agent != nil { + if err = config.Agent.Validate(); err != nil { + return + } + } if config.Disks != nil { err = config.Disks.Validate() if err != nil { diff --git a/proxmox/config_qemu_guestagent.go b/proxmox/config_qemu_guestagent.go new file mode 100644 index 00000000..d528a9b3 --- /dev/null +++ b/proxmox/config_qemu_guestagent.go @@ -0,0 +1,92 @@ +package proxmox + +import ( + "errors" + "strconv" + "strings" + + "github.com/Telmate/proxmox-api-go/internal/util" +) + +type QemuGuestAgent struct { + Enable *bool `json:"enable,omitempty"` // Optional + Type *QemuGuestAgentType `json:"type,omitempty"` // Optional + Freeze *bool `json:"freeze,omitempty"` // Optional + FsTrim *bool `json:"trim,omitempty"` // Optional +} + +func (newSetting QemuGuestAgent) mapToAPI(currentSettings *QemuGuestAgent) string { + var params string + tmpEnable := "0" + if newSetting.Enable != nil { + if *newSetting.Enable { + tmpEnable = "1" + } + } else if currentSettings != nil && currentSettings.Enable != nil { + if *currentSettings.Enable { + tmpEnable = "1" + } + } + if newSetting.Freeze != nil { + params += ",freeze-fs-on-backup=" + boolToIntString(*newSetting.Freeze) + } else if currentSettings != nil && currentSettings.Freeze != nil { + params += ",freeze-fs-on-backup=" + boolToIntString(*currentSettings.Freeze) + } + if newSetting.FsTrim != nil { + params += ",fstrim_cloned_disks=" + boolToIntString(*newSetting.FsTrim) + } else if currentSettings != nil && currentSettings.FsTrim != nil { + params += ",fstrim_cloned_disks=" + boolToIntString(*currentSettings.FsTrim) + } + if newSetting.Type != nil { + params += ",type=" + strings.ToLower(string(*newSetting.Type)) + } else if currentSettings != nil && currentSettings.Type != nil { + params += ",type=" + strings.ToLower(string(*currentSettings.Type)) + } + return tmpEnable + params +} + +func (QemuGuestAgent) mapToSDK(params string) *QemuGuestAgent { + config := QemuGuestAgent{} + tmpEnable, _ := strconv.ParseBool(params[0:1]) + config.Enable = &tmpEnable + tmpParams := splitStringOfSettings(params) + if v, isSet := tmpParams["freeze-fs-on-backup"]; isSet { + tmpBool, _ := strconv.ParseBool(v.(string)) + config.Freeze = &tmpBool + } + if v, isSet := tmpParams["fstrim_cloned_disks"]; isSet { + tmpBool, _ := strconv.ParseBool(v.(string)) + config.FsTrim = &tmpBool + } + if v, isSet := tmpParams["type"]; isSet { + config.Type = util.Pointer(QemuGuestAgentType(v.(string))) + } + return &config +} + +func (setting QemuGuestAgent) Validate() error { + if setting.Type != nil { + return setting.Type.Validate() + } + return nil +} + +type QemuGuestAgentType string // enum + +const ( + QemuGuestAgentType_Isa QemuGuestAgentType = "isa" + QemuGuestAgentType_VirtIO QemuGuestAgentType = "virtio" + QemuGuestAgentType_None QemuGuestAgentType = "" // Used to unset the value. Proxmox enforces the default. + QemuGuestAgentType_Error_Invalid string = `invalid qemu guest agent type, should one of [` + string(QemuGuestAgentType_Isa) + `, ` + string(QemuGuestAgentType_VirtIO) + `, ""]` +) + +func (q QemuGuestAgentType) Validate() error { + if q == QemuGuestAgentType_None { + return nil + } + switch QemuGuestAgentType(strings.ToLower(string(q))) { + case QemuGuestAgentType_Isa, QemuGuestAgentType_VirtIO: + return nil + } + return errors.New(QemuGuestAgentType_Error_Invalid) +} From ff84c237c6403c8cab45e04e1406e24888e361c3 Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Mon, 15 Apr 2024 13:44:59 +0200 Subject: [PATCH 4/4] fix: `QemuGuestAgentType` can be unset --- proxmox/config_qemu_guestagent.go | 4 +++- proxmox/config_qemu_test.go | 13 ++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/proxmox/config_qemu_guestagent.go b/proxmox/config_qemu_guestagent.go index d528a9b3..7337985c 100644 --- a/proxmox/config_qemu_guestagent.go +++ b/proxmox/config_qemu_guestagent.go @@ -38,7 +38,9 @@ func (newSetting QemuGuestAgent) mapToAPI(currentSettings *QemuGuestAgent) strin params += ",fstrim_cloned_disks=" + boolToIntString(*currentSettings.FsTrim) } if newSetting.Type != nil { - params += ",type=" + strings.ToLower(string(*newSetting.Type)) + if *newSetting.Type != QemuGuestAgentType_None { + params += ",type=" + strings.ToLower(string(*newSetting.Type)) + } } else if currentSettings != nil && currentSettings.Type != nil { params += ",type=" + strings.ToLower(string(*currentSettings.Type)) } diff --git a/proxmox/config_qemu_test.go b/proxmox/config_qemu_test.go index 77d36ec3..a5e15358 100644 --- a/proxmox/config_qemu_test.go +++ b/proxmox/config_qemu_test.go @@ -49,7 +49,10 @@ func Test_ConfigQemu_mapToApiValues(t *testing.T) { {name: `Create Agent.Enable`, config: &ConfigQemu{Agent: &QemuGuestAgent{Enable: util.Pointer(true)}}, output: map[string]interface{}{"agent": "1"}}, - {name: `Create Agent.Type`, + {name: `Create Agent.Type=""`, + config: &ConfigQemu{Agent: &QemuGuestAgent{Type: util.Pointer(QemuGuestAgentType_None)}}, + output: map[string]interface{}{"agent": "0"}}, + {name: `Create Agent.Type="virtio"`, config: &ConfigQemu{Agent: &QemuGuestAgent{Type: util.Pointer(QemuGuestAgentType_VirtIO)}}, output: map[string]interface{}{"agent": "0,type=virtio"}}, {name: `Create Agent.Freeze`, @@ -1346,6 +1349,14 @@ func Test_ConfigQemu_mapToApiValues(t *testing.T) { config: &ConfigQemu{Agent: &QemuGuestAgent{Type: util.Pointer(QemuGuestAgentType_VirtIO)}}, currentConfig: ConfigQemu{Agent: &QemuGuestAgent{}}, output: map[string]interface{}{"agent": "0,type=virtio"}}, + {name: `Update Agent.Type "" !nil`, + config: &ConfigQemu{Agent: &QemuGuestAgent{Type: util.Pointer(QemuGuestAgentType_None)}}, + currentConfig: ConfigQemu{Agent: &QemuGuestAgent{}}, + output: map[string]interface{}{"agent": "0"}}, + {name: `Update Agent.Type "" nil`, + config: &ConfigQemu{Agent: &QemuGuestAgent{Type: util.Pointer(QemuGuestAgentType_None)}}, + currentConfig: ConfigQemu{Agent: &QemuGuestAgent{Type: util.Pointer(QemuGuestAgentType_VirtIO)}}, + output: map[string]interface{}{"agent": "0"}}, {name: `Update Agent.Type nil !nil`, config: &ConfigQemu{Agent: &QemuGuestAgent{}}, currentConfig: ConfigQemu{Agent: &QemuGuestAgent{Type: util.Pointer(QemuGuestAgentType_VirtIO)}},