From 938fbc2900f310bc69dd1614ae7c8e37738665b1 Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Fri, 14 Jul 2023 15:06:33 +0000 Subject: [PATCH 1/7] refactor: put in alphabetical order --- proxmox/config_qemu.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxmox/config_qemu.go b/proxmox/config_qemu.go index aeedcd42..11f080c3 100644 --- a/proxmox/config_qemu.go +++ b/proxmox/config_qemu.go @@ -48,7 +48,6 @@ type ConfigQemu struct { Description string `json:"description,omitempty"` Disks *QemuStorages `json:"disks,omitempty"` EFIDisk QemuDevice `json:"efidisk,omitempty"` // TODO should be a struct - RNGDrive QemuDevice `json:"rng0,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 @@ -80,6 +79,7 @@ type ConfigQemu 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? From bd6f10d241dedbbd8aa98b8e74a1a5b946b37866 Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Fri, 14 Jul 2023 16:44:45 +0000 Subject: [PATCH 2/7] feat: Add nil check --- proxmox/client.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/proxmox/client.go b/proxmox/client.go index 548c2c8f..81a959ba 100644 --- a/proxmox/client.go +++ b/proxmox/client.go @@ -34,6 +34,10 @@ type Client struct { TaskTimeout int } +const ( + VmRef_Error_Nil string = "vm reference may not be nil" +) + // VmRef - virtual machine ref parts // map[type:qemu node:proxmox1-xx id:qemu/132 diskread:5.57424738e+08 disk:0 netin:5.9297450593e+10 mem:3.3235968e+09 uptime:1.4567097e+07 vmid:132 template:0 maxcpu:2 netout:6.053310416e+09 maxdisk:3.4359738368e+10 maxmem:8.592031744e+09 diskwrite:1.49663619584e+12 status:running cpu:0.00386980694947209 name:appt-app1-dev.xxx.xx] type VmRef struct { @@ -177,6 +181,9 @@ func (c *Client) GetVmList() (map[string]interface{}, error) { } func (c *Client) CheckVmRef(vmr *VmRef) (err error) { + if vmr == nil { + return errors.New(VmRef_Error_Nil) + } if vmr.node == "" || vmr.vmType == "" { _, err = c.GetVmInfo(vmr) } From 7c6d46ce75b54afe7dc0854460704df06573a423 Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Fri, 14 Jul 2023 16:46:12 +0000 Subject: [PATCH 3/7] refactor: nil check implemented by `CheckVmRef` --- proxmox/config_guest.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/proxmox/config_guest.go b/proxmox/config_guest.go index c969b1ef..78491f06 100644 --- a/proxmox/config_guest.go +++ b/proxmox/config_guest.go @@ -138,11 +138,7 @@ func ListGuests(client *Client) ([]GuestResource, error) { } func pendingGuestConfigFromApi(vmr *VmRef, client *Client) ([]interface{}, error) { - err := vmr.nilCheck() - if err != nil { - return nil, err - } - if err = client.CheckVmRef(vmr); err != nil { + if err := client.CheckVmRef(vmr); err != nil { return nil, err } return client.GetItemConfigInterfaceArray("/nodes/"+vmr.node+"/"+vmr.vmType+"/"+strconv.Itoa(vmr.vmId)+"/pending", "Guest", "PENDING CONFIG") From 11feae1f09a04483dcd94bcbadad72a4a3fc1550 Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Fri, 14 Jul 2023 19:59:15 +0000 Subject: [PATCH 4/7] feat: obtain features the guest has enabled Depending on the node, storage, config of the guest the clone, copy, snaspshot features might not be availible --- proxmox/config_guest.go | 71 ++++++++++++++++++++++++++++++++++++ proxmox/config_guest_test.go | 59 ++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) diff --git a/proxmox/config_guest.go b/proxmox/config_guest.go index 78491f06..ffc183a2 100644 --- a/proxmox/config_guest.go +++ b/proxmox/config_guest.go @@ -1,6 +1,7 @@ package proxmox import ( + "errors" "strconv" "strings" ) @@ -102,6 +103,40 @@ func (GuestResource) mapToStruct(params []interface{}) []GuestResource { return resources } +// Enum +type GuestFeature string + +const ( + GuestFeature_Clone GuestFeature = "clone" + GuestFeature_Copy GuestFeature = "copy" + GuestFeature_Snapshot GuestFeature = "snapshot" +) + +func (GuestFeature) Error() error { + return errors.New("value should be one of (" + string(GuestFeature_Clone) + " ," + string(GuestFeature_Copy) + " ," + string(GuestFeature_Snapshot) + ")") +} + +func (GuestFeature) mapToStruct(params map[string]interface{}) bool { + if value, isSet := params["hasFeature"]; isSet { + return Itob(int(value.(float64))) + } + return false +} + +func (feature GuestFeature) Validate() error { + switch feature { + case GuestFeature_Copy, GuestFeature_Clone, GuestFeature_Snapshot: + return nil + } + return GuestFeature("").Error() +} + +type GuestFeatures struct { + Clone bool `json:"clone"` + Copy bool `json:"copy"` + Snapshot bool `json:"snapshot"` +} + type GuestType string const ( @@ -109,6 +144,24 @@ const ( GuestQemu GuestType = "qemu" ) +// check if the guest has the specified feature. +func GuestHasFeature(vmr *VmRef, client *Client, feature GuestFeature) (bool, error) { + err := client.CheckVmRef(vmr) + if err != nil { + return false, err + } + return guestHasFeature(vmr, client, feature) +} + +func guestHasFeature(vmr *VmRef, client *Client, feature GuestFeature) (bool, error) { + var params map[string]interface{} + params, err := client.GetItemConfigMapStringInterface("/nodes/"+vmr.node+"/"+vmr.vmType+"/"+strconv.Itoa(vmr.vmId)+"/feature?feature=snapshot", "guest", "FEATURES") + if err != nil { + return false, err + } + return GuestFeature("").mapToStruct(params), nil +} + // Check if there are any pending changes that require a reboot to be applied. func GuestHasPendingChanges(vmr *VmRef, client *Client) (bool, error) { params, err := pendingGuestConfigFromApi(vmr, client) @@ -128,6 +181,24 @@ func GuestReboot(vmr *VmRef, client *Client) (err error) { return } +// List all features the guest has. +func ListGuestFeatures(vmr *VmRef, client *Client) (features GuestFeatures, err error) { + err = client.CheckVmRef(vmr) + if err != nil { + return + } + features.Clone, err = guestHasFeature(vmr, client, GuestFeature_Clone) + if err != nil { + return + } + features.Copy, err = guestHasFeature(vmr, client, GuestFeature_Copy) + if err != nil { + return + } + features.Snapshot, err = guestHasFeature(vmr, client, GuestFeature_Snapshot) + return +} + // List all guest the user has viewing rights for in the cluster func ListGuests(client *Client) ([]GuestResource, error) { list, err := client.GetResourceList("vm") diff --git a/proxmox/config_guest_test.go b/proxmox/config_guest_test.go index c2328d47..d22f5eec 100644 --- a/proxmox/config_guest_test.go +++ b/proxmox/config_guest_test.go @@ -237,3 +237,62 @@ func Test_GuestResource_mapToStruct(t *testing.T) { }) } } + +func Test_GuestFeature_mapToStruct(t *testing.T) { + tests := []struct { + name string + input map[string]interface{} + output bool + }{ + {name: "false", + input: map[string]interface{}{"hasFeature": float64(0)}, + output: false, + }, + {name: "not set", + input: map[string]interface{}{}, + output: false, + }, + {name: "true", + input: map[string]interface{}{"hasFeature": float64(1)}, + output: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(*testing.T) { + require.Equal(t, test.output, GuestFeature("").mapToStruct(test.input), test.name) + }) + } +} + +func Test_GuestFeature_Validate(t *testing.T) { + tests := []struct { + name string + input GuestFeature + err error + }{ + // Invalid + {name: "Invalid empty", + input: "", + err: GuestFeature("").Error(), + }, + {name: "Invalid not enum", + input: "invalid", + err: GuestFeature("").Error(), + }, + // Valid + {name: "Valid GuestFeature_Clone", + input: GuestFeature_Clone, + }, + {name: "Valid GuestFeature_Copy", + input: GuestFeature_Copy, + }, + {name: "Valid GuestFeature_Snapshot", + input: GuestFeature_Snapshot, + }, + } + for _, test := range tests { + t.Run(test.name, func(*testing.T) { + require.Equal(t, test.err, test.input.Validate(), test.name) + }) + } +} From d5a6ce30b8c2ff636318d726aab2573ea65c618a Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Fri, 14 Jul 2023 20:02:24 +0000 Subject: [PATCH 5/7] refactor: nest config in guest command this is to allow for future guest commands --- cli/command/commands/commands.go | 1 + .../{get-guest.go => guest/get-guest-config.go} | 10 +++++----- cli/command/get/guest/get-guest.go | 15 +++++++++++++++ 3 files changed, 21 insertions(+), 5 deletions(-) rename cli/command/get/{get-guest.go => guest/get-guest-config.go} (81%) create mode 100644 cli/command/get/guest/get-guest.go diff --git a/cli/command/commands/commands.go b/cli/command/commands/commands.go index f8798517..2b04aa1d 100644 --- a/cli/command/commands/commands.go +++ b/cli/command/commands/commands.go @@ -9,6 +9,7 @@ import ( _ "github.com/Telmate/proxmox-api-go/cli/command/delete" _ "github.com/Telmate/proxmox-api-go/cli/command/example" _ "github.com/Telmate/proxmox-api-go/cli/command/get" + _ "github.com/Telmate/proxmox-api-go/cli/command/get/guest" _ "github.com/Telmate/proxmox-api-go/cli/command/get/id" _ "github.com/Telmate/proxmox-api-go/cli/command/guest" _ "github.com/Telmate/proxmox-api-go/cli/command/guest/qemu" diff --git a/cli/command/get/get-guest.go b/cli/command/get/guest/get-guest-config.go similarity index 81% rename from cli/command/get/get-guest.go rename to cli/command/get/guest/get-guest-config.go index ffce1db7..0941785a 100644 --- a/cli/command/get/get-guest.go +++ b/cli/command/get/guest/get-guest-config.go @@ -1,4 +1,4 @@ -package get +package guest import ( "github.com/Telmate/proxmox-api-go/cli" @@ -6,8 +6,8 @@ import ( "github.com/spf13/cobra" ) -var get_guestCmd = &cobra.Command{ - Use: "guest GUESTID", +var configCmd = &cobra.Command{ + Use: "config GUESTID", Short: "Gets the configuration of the specified guest", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) (err error) { @@ -29,11 +29,11 @@ var get_guestCmd = &cobra.Command{ if err != nil { return } - cli.PrintFormattedJson(GetCmd.OutOrStdout(), config) + cli.PrintFormattedJson(guestCmd.OutOrStdout(), config) return }, } func init() { - GetCmd.AddCommand(get_guestCmd) + guestCmd.AddCommand(configCmd) } diff --git a/cli/command/get/guest/get-guest.go b/cli/command/get/guest/get-guest.go new file mode 100644 index 00000000..81672e75 --- /dev/null +++ b/cli/command/get/guest/get-guest.go @@ -0,0 +1,15 @@ +package guest + +import ( + "github.com/Telmate/proxmox-api-go/cli/command/get" + "github.com/spf13/cobra" +) + +var guestCmd = &cobra.Command{ + Use: "guest", + Short: "Commands to get information of guests on Proxmox", +} + +func init() { + get.GetCmd.AddCommand(guestCmd) +} From ca58b91dcca44ff40c3ad92d8c91944b825b6d2c Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Fri, 14 Jul 2023 20:03:17 +0000 Subject: [PATCH 6/7] feat: add command to get guests enabled features --- cli/command/get/guest/get-guest-feature.go | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 cli/command/get/guest/get-guest-feature.go diff --git a/cli/command/get/guest/get-guest-feature.go b/cli/command/get/guest/get-guest-feature.go new file mode 100644 index 00000000..d19b87e0 --- /dev/null +++ b/cli/command/get/guest/get-guest-feature.go @@ -0,0 +1,32 @@ +package guest + +import ( + "github.com/Telmate/proxmox-api-go/cli" + "github.com/Telmate/proxmox-api-go/proxmox" + "github.com/spf13/cobra" +) + +var featureCmd = &cobra.Command{ + Use: "feature GUESTID", + Short: "Gets the available features of the specified guest", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) (err error) { + id := cli.ValidateIntIDset(args, "GuestID") + vmr := proxmox.NewVmRef(id) + c := cli.NewClient() + err = c.CheckVmRef(vmr) + if err != nil { + return + } + features, err := proxmox.ListGuestFeatures(vmr, c) + if err != nil { + return + } + cli.PrintFormattedJson(guestCmd.OutOrStdout(), features) + return + }, +} + +func init() { + guestCmd.AddCommand(featureCmd) +} From daeb78f5a0180f4d4af02f68d735f11c7ffa90eb Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Fri, 14 Jul 2023 20:07:15 +0000 Subject: [PATCH 7/7] feat: validate input is enum value --- proxmox/config_guest.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/proxmox/config_guest.go b/proxmox/config_guest.go index ffc183a2..db25050c 100644 --- a/proxmox/config_guest.go +++ b/proxmox/config_guest.go @@ -146,7 +146,11 @@ const ( // check if the guest has the specified feature. func GuestHasFeature(vmr *VmRef, client *Client, feature GuestFeature) (bool, error) { - err := client.CheckVmRef(vmr) + err := feature.Validate() + if err != nil { + return false, err + } + err = client.CheckVmRef(vmr) if err != nil { return false, err }