From 8352730ceb048731a9b45c2f18d901a6909fa398 Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Sat, 22 Oct 2022 12:04:49 +0000 Subject: [PATCH 01/14] refactor: Deprecate old snapshot functions Deprecated all the snapshot functions and added new ones. As the qemu in the function name implies it doesn't work for LXC. --- proxmox/client.go | 57 +++++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/proxmox/client.go b/proxmox/client.go index 0ed3a978..d3b4f01a 100644 --- a/proxmox/client.go +++ b/proxmox/client.go @@ -628,6 +628,15 @@ func (c *Client) CloneQemuVm(vmr *VmRef, vmParams map[string]interface{}) (exitS return } +func (c *Client) CreateSnapshot(vmr *VmRef, params map[string]interface{}) (exitStatus string, err error) { + err = c.CheckVmRef(vmr) + if err != nil { + return + } + return c.CreateItemWithTask(params, "/nodes/"+vmr.node+"/"+vmr.vmType+"/"+strconv.Itoa(vmr.vmId)+"/snapshot/") +} + +// DEPRECATED superceded by (c *Client) CreateSnapshot() func (c *Client) CreateQemuSnapshot(vmr *VmRef, snapshotName string) (exitStatus string, err error) { err = c.CheckVmRef(vmr) snapshotParams := map[string]interface{}{ @@ -652,26 +661,28 @@ func (c *Client) CreateQemuSnapshot(vmr *VmRef, snapshotName string) (exitStatus return } -func (c *Client) DeleteQemuSnapshot(vmr *VmRef, snapshotName string) (exitStatus string, err error) { +func (c *Client) DeleteSnapshot(vmr *VmRef, snapshot string) (exitStatus string, err error) { err = c.CheckVmRef(vmr) if err != nil { - return "", err + return } - url := fmt.Sprintf("/nodes/%s/%s/%d/snapshot/%s", vmr.node, vmr.vmType, vmr.vmId, snapshotName) - resp, err := c.session.Delete(url, nil, nil) - if err == nil { - taskResponse, err := ResponseJSON(resp) - if err != nil { - return "", err - } - exitStatus, err = c.WaitForCompletion(taskResponse) - if err != nil { - return "", err - } + return c.DeleteUrlWithTask("/nodes/" + vmr.node + "/" + vmr.vmType + "/" + strconv.Itoa(vmr.vmId) + "/snapshot/" + snapshot) +} + +// DEPRECATED superceded by (c *Client) DeleteSnapshot() +func (c *Client) DeleteQemuSnapshot(vmr *VmRef, snapshotName string) (exitStatus string, err error) { + return c.DeleteSnapshot(vmr, snapshotName) +} + +func (c *Client) ListSnapshots(vmr *VmRef) (taskResponse []interface{}, err error) { + err = c.CheckVmRef(vmr) + if err != nil { + return } - return + return c.GetItemConfigInterfaceArray("/nodes/"+vmr.node+"/"+vmr.vmType+"/"+strconv.Itoa(vmr.vmId)+"/snapshot/", "Guest", "SNAPSHOTS") } +// DEPRECATED superceded by (c *Client) ListSnapshots() func (c *Client) ListQemuSnapshot(vmr *VmRef) (taskResponse map[string]interface{}, exitStatus string, err error) { err = c.CheckVmRef(vmr) if err != nil { @@ -689,19 +700,17 @@ func (c *Client) ListQemuSnapshot(vmr *VmRef) (taskResponse map[string]interface return } -func (c *Client) RollbackQemuVm(vmr *VmRef, snapshot string) (exitStatus string, err error) { +func (c *Client) RollbackSnapshot(vmr *VmRef, snapshot string) (exitStatus string, err error) { err = c.CheckVmRef(vmr) if err != nil { - return "", err - } - url := fmt.Sprintf("/nodes/%s/%s/%d/snapshot/%s/rollback", vmr.node, vmr.vmType, vmr.vmId, snapshot) - var taskResponse map[string]interface{} - _, err = c.session.PostJSON(url, nil, nil, nil, &taskResponse) - if err != nil { - return "", err + return } - exitStatus, err = c.WaitForCompletion(taskResponse) - return + return c.CreateItemWithTask(nil, "/nodes/"+vmr.node+"/"+vmr.vmType+"/"+strconv.Itoa(vmr.vmId)+"/snapshot/"+snapshot+"/rollback") +} + +// DEPRECATED superceded by (c *Client) RollbackSnapshot() +func (c *Client) RollbackQemuVm(vmr *VmRef, snapshot string) (exitStatus string, err error) { + return c.RollbackSnapshot(vmr, snapshot) } // SetVmConfig - send config options From 37f62b5e9cc9a222a1cca879495af1f70a9422d7 Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Sat, 22 Oct 2022 12:08:35 +0000 Subject: [PATCH 02/14] feat: Add way to update the description of a snapshot --- proxmox/client.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/proxmox/client.go b/proxmox/client.go index d3b4f01a..6ff52ae6 100644 --- a/proxmox/client.go +++ b/proxmox/client.go @@ -713,6 +713,15 @@ func (c *Client) RollbackQemuVm(vmr *VmRef, snapshot string) (exitStatus string, return c.RollbackSnapshot(vmr, snapshot) } +// Can only be used to update the description of an already existing snapshot +func (c *Client) UpdateSnapshotDescription(vmr *VmRef, snapshot, description string) (err error) { + err = c.CheckVmRef(vmr) + if err != nil { + return + } + return c.UpdateItem(map[string]interface{}{"description": description}, "/nodes/"+vmr.node+"/"+vmr.vmType+"/"+strconv.Itoa(vmr.vmId)+"/snapshot/"+snapshot+"/config") +} + // SetVmConfig - send config options func (c *Client) SetVmConfig(vmr *VmRef, params map[string]interface{}) (exitStatus interface{}, err error) { return c.CreateItemWithTask(params, "/nodes/"+vmr.node+"/"+vmr.vmType+"/"+strconv.Itoa(vmr.vmId)+"/config") From 0e0ebb86c51b38ee75fb345c220e1ee626b5b6d9 Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Sat, 22 Oct 2022 12:14:20 +0000 Subject: [PATCH 03/14] feat: Add CreateSnapshot function Adding a command that allows for all setting to be used regarding snapshots --- proxmox/snapshot.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 proxmox/snapshot.go diff --git a/proxmox/snapshot.go b/proxmox/snapshot.go new file mode 100644 index 00000000..891af86a --- /dev/null +++ b/proxmox/snapshot.go @@ -0,0 +1,30 @@ +package proxmox + +import ( + "encoding/json" + "fmt" +) + +type ConfigSnapshot struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + VmState bool `json:"ram,omitempty"` +} + +func (config *ConfigSnapshot) mapValues() map[string]interface{} { + return map[string]interface{}{ + "snapname": config.Name, + "description": config.Description, + "vmstate": config.VmState, + } +} + +func (config *ConfigSnapshot) CreateSnapshot(guestId uint, c *Client) (err error) { + params := config.mapValues() + _, err = c.CreateSnapshot(NewVmRef(int(guestId)), params) + if err != nil { + params, _ := json.Marshal(¶ms) + return fmt.Errorf("error creating Snapshot: %v, (params: %v)", err, string(params)) + } + return +} From 6551610be6b7ffbf261c57189364c09bee046f20 Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Sat, 22 Oct 2022 12:15:51 +0000 Subject: [PATCH 04/14] feat: Add function to format the snapshots as a json tree --- proxmox/snapshot.go | 46 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/proxmox/snapshot.go b/proxmox/snapshot.go index 891af86a..71502aa1 100644 --- a/proxmox/snapshot.go +++ b/proxmox/snapshot.go @@ -28,3 +28,49 @@ func (config *ConfigSnapshot) CreateSnapshot(guestId uint, c *Client) (err error } return } + +type snapshot struct { + Name string `json:"name"` + SnapTime uint `json:"time,omitempty"` + Description string `json:"description,omitempty"` + VmState bool `json:"ram,omitempty"` + Children []*snapshot `json:"children,omitempty"` + Parent string `json:"-"` +} + +func FormatSnapshotsTree(taskResponse []interface{}) (tree *snapshot) { + snapshotList := make([]*snapshot, len(taskResponse)) + for i, e := range taskResponse { + snapshotList[i] = &snapshot{} + if _, isSet := e.(map[string]interface{})["description"]; isSet { + snapshotList[i].Description = e.(map[string]interface{})["description"].(string) + } + if _, isSet := e.(map[string]interface{})["name"]; isSet { + snapshotList[i].Name = e.(map[string]interface{})["name"].(string) + } + if _, isSet := e.(map[string]interface{})["parent"]; isSet { + snapshotList[i].Parent = e.(map[string]interface{})["parent"].(string) + } + if _, isSet := e.(map[string]interface{})["snaptime"]; isSet { + snapshotList[i].SnapTime = uint(e.(map[string]interface{})["snaptime"].(float64)) + } + if _, isSet := e.(map[string]interface{})["vmstate"]; isSet { + snapshotList[i].VmState = Itob(int(e.(map[string]interface{})["vmstate"].(float64))) + } + } + for _, e := range snapshotList { + for _, ee := range snapshotList { + if e.Parent == ee.Name { + ee.Children = append(ee.Children, e) + break + } + } + } + for _, e := range snapshotList { + if e.Parent == "" { + tree = e + break + } + } + return +} From 0a23b40344d6edf0e8119299fa456f32206b16db Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Sat, 22 Oct 2022 12:20:01 +0000 Subject: [PATCH 05/14] test: FormatSnapshotsTree the implementation of building the tree structure in FormatSnapshotsTree() can probably be inproved. --- proxmox/snapshot_test.go | 149 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 proxmox/snapshot_test.go diff --git a/proxmox/snapshot_test.go b/proxmox/snapshot_test.go new file mode 100644 index 00000000..770dda9c --- /dev/null +++ b/proxmox/snapshot_test.go @@ -0,0 +1,149 @@ +package proxmox + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +// Test the formatting logic to build the tree of snapshots +func Test_FormatSnapshotsTree(t *testing.T) { + input := test_FormatSnapshotsTree_Input() + output := test_FormatSnapshotsTree_Output() + for i, e := range input { + result, _ := json.Marshal(FormatSnapshotsTree(e)) + require.JSONEq(t, output[i], string(result)) + } +} + +func test_FormatSnapshotsTree_Input() [][]interface{} { + return [][]interface{}{{map[string]interface{}{ + "name": "aa", + "snaptime": float64(1666361849), + "description": "", + "parent": "", + "vmstate": float64(0), + }, map[string]interface{}{ + "name": "aaa", + "snaptime": float64(1666361866), + "description": "", + "parent": "aa", + "vmstate": float64(1), + }, map[string]interface{}{ + "name": "aaaa", + "snaptime": float64(1666362071), + "description": "123456", + "parent": "aaa", + "vmstate": float64(0), + }, map[string]interface{}{ + "name": "aaab", + "snaptime": float64(1666362062), + "description": "", + "parent": "aaa", + "vmstate": float64(0), + }, map[string]interface{}{ + "name": "aaac", + "snaptime": float64(1666361873), + "description": "", + "parent": "aaa", + "vmstate": float64(0), + }, map[string]interface{}{ + "name": "aaad", + "snaptime": float64(1666361937), + "description": "abcdefg", + "parent": "aaa", + "vmstate": float64(1), + }, map[string]interface{}{ + "name": "aaae", + "snaptime": float64(1666362084), + "description": "", + "parent": "aaa", + "vmstate": float64(0), + }, map[string]interface{}{ + "name": "current", + "description": "You are here!", + "parent": "aaae", + }, map[string]interface{}{ + "name": "aab", + "snaptime": float64(1666361920), + "description": "", + "parent": "aa", + "vmstate": float64(0), + }, map[string]interface{}{ + "name": "aaba", + "snaptime": float64(1666361952), + "description": "", + "parent": "aab", + "vmstate": float64(0), + }, map[string]interface{}{ + "name": "aabaa", + "snaptime": float64(1666361960), + "description": "", + "parent": "", + "vmstate": float64(0), + }, map[string]interface{}{ + "name": "aac", + "snaptime": float64(1666361896), + "description": "", + "parent": "aa", + "vmstate": float64(0), + }, map[string]interface{}{ + "name": "aaca", + "snaptime": float64(1666361988), + "description": "!@#()&", + "parent": "aac", + "vmstate": float64(1), + }, map[string]interface{}{ + "name": "aacaa", + "snaptime": float64(1666362006), + "description": "", + "parent": "aaca", + "vmstate": float64(1), + }, map[string]interface{}{ + "name": "aacb", + "snaptime": float64(1666361977), + "description": "", + "parent": "aac", + "vmstate": float64(0), + }, map[string]interface{}{ + "name": "aacba", + "snaptime": float64(1666362021), + "description": "QWERTY", + "parent": "aacb", + "vmstate": float64(0), + }, map[string]interface{}{ + "name": "aacc", + "snaptime": float64(1666361904), + "description": "", + "parent": "aac", + "vmstate": float64(0), + }, map[string]interface{}{ + "name": "aacca", + "snaptime": float64(1666361910), + "description": "", + "parent": "aacc", + "vmstate": float64(0), + }}} +} + +func test_FormatSnapshotsTree_Output() []string { + return []string{`{ + "name":"aa","time":1666361849,"children":[{ + "name":"aaa","time":1666361866,"ram":true,"children":[{ + "name":"aaaa","time":1666362071,"description":"123456"},{ + "name":"aaab","time":1666362062},{ + "name":"aaac","time":1666361873},{ + "name":"aaad","time":1666361937,"description":"abcdefg","ram":true},{ + "name":"aaae","time":1666362084,"children":[{ + "name":"current","description":"You are here!"}]}]},{ + "name":"aab","time":1666361920,"children":[{ + "name":"aaba","time":1666361952}]},{ + "name":"aac","time":1666361896,"children":[{ + "name":"aaca","time":1666361988,"description":"!@#()\u0026","ram":true,"children":[{ + "name":"aacaa","time":1666362006,"ram":true}]},{ + "name":"aacb","time":1666361977,"children":[{ + "name":"aacba","time":1666362021,"description":"QWERTY"}]},{ + "name":"aacc","time":1666361904,"children":[{ + "name":"aacca","time":1666361910}]}]}]}`} +} From eb50349aa6f80b3d337ef5f84a948a7260307d68 Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Sat, 22 Oct 2022 13:16:49 +0000 Subject: [PATCH 06/14] feat: Formats snapsots as a list --- proxmox/snapshot.go | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/proxmox/snapshot.go b/proxmox/snapshot.go index 71502aa1..1b9b161e 100644 --- a/proxmox/snapshot.go +++ b/proxmox/snapshot.go @@ -35,38 +35,45 @@ type snapshot struct { Description string `json:"description,omitempty"` VmState bool `json:"ram,omitempty"` Children []*snapshot `json:"children,omitempty"` - Parent string `json:"-"` + Parent string `json:"parent,omitempty"` } -func FormatSnapshotsTree(taskResponse []interface{}) (tree *snapshot) { - snapshotList := make([]*snapshot, len(taskResponse)) +// Formats the taskResponse as a list of snapshots +func FormatSnapshotsList(taskResponse []interface{}) (list []*snapshot) { + list = make([]*snapshot, len(taskResponse)) for i, e := range taskResponse { - snapshotList[i] = &snapshot{} + list[i] = &snapshot{} if _, isSet := e.(map[string]interface{})["description"]; isSet { - snapshotList[i].Description = e.(map[string]interface{})["description"].(string) + list[i].Description = e.(map[string]interface{})["description"].(string) } if _, isSet := e.(map[string]interface{})["name"]; isSet { - snapshotList[i].Name = e.(map[string]interface{})["name"].(string) + list[i].Name = e.(map[string]interface{})["name"].(string) } if _, isSet := e.(map[string]interface{})["parent"]; isSet { - snapshotList[i].Parent = e.(map[string]interface{})["parent"].(string) + list[i].Parent = e.(map[string]interface{})["parent"].(string) } if _, isSet := e.(map[string]interface{})["snaptime"]; isSet { - snapshotList[i].SnapTime = uint(e.(map[string]interface{})["snaptime"].(float64)) + list[i].SnapTime = uint(e.(map[string]interface{})["snaptime"].(float64)) } if _, isSet := e.(map[string]interface{})["vmstate"]; isSet { - snapshotList[i].VmState = Itob(int(e.(map[string]interface{})["vmstate"].(float64))) + list[i].VmState = Itob(int(e.(map[string]interface{})["vmstate"].(float64))) } } - for _, e := range snapshotList { - for _, ee := range snapshotList { + return +} + +// Formats a list of snapshots as a tree of snapshots +func FormatSnapshotsTree(list []*snapshot) (tree *snapshot) { + for _, e := range list { + for _, ee := range list { if e.Parent == ee.Name { + e.Parent = "" ee.Children = append(ee.Children, e) break } } } - for _, e := range snapshotList { + for _, e := range list { if e.Parent == "" { tree = e break From 97e6af057281efac2b235d599849e2b851c4fdb2 Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Sat, 22 Oct 2022 13:18:11 +0000 Subject: [PATCH 07/14] test: validate output of FormatSnapshotsList() --- proxmox/snapshot_test.go | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/proxmox/snapshot_test.go b/proxmox/snapshot_test.go index 770dda9c..d8ebd9a1 100644 --- a/proxmox/snapshot_test.go +++ b/proxmox/snapshot_test.go @@ -9,15 +9,25 @@ import ( // Test the formatting logic to build the tree of snapshots func Test_FormatSnapshotsTree(t *testing.T) { - input := test_FormatSnapshotsTree_Input() + input := test_FormatSnapshots_Input() output := test_FormatSnapshotsTree_Output() for i, e := range input { - result, _ := json.Marshal(FormatSnapshotsTree(e)) + result, _ := json.Marshal(FormatSnapshotsTree(FormatSnapshotsList(e))) require.JSONEq(t, output[i], string(result)) } } -func test_FormatSnapshotsTree_Input() [][]interface{} { +// Test the formatting logic to build the list of snapshots +func Test_FormatSnapshotsList(t *testing.T) { + input := test_FormatSnapshots_Input() + output := test_FormatSnapshotsList_Output() + for i, e := range input { + result, _ := json.Marshal(FormatSnapshotsList(e)) + require.JSONEq(t, output[i], string(result)) + } +} + +func test_FormatSnapshots_Input() [][]interface{} { return [][]interface{}{{map[string]interface{}{ "name": "aa", "snaptime": float64(1666361849), @@ -147,3 +157,25 @@ func test_FormatSnapshotsTree_Output() []string { "name":"aacc","time":1666361904,"children":[{ "name":"aacca","time":1666361910}]}]}]}`} } + +func test_FormatSnapshotsList_Output() []string { + return []string{`[{ + "name":"aa","time":1666361849},{ + "name":"aaa","time":1666361866,"ram":true,"parent":"aa"},{ + "name":"aaaa","time":1666362071,"description":"123456","parent":"aaa"},{ + "name":"aaab","time":1666362062,"parent":"aaa"},{ + "name":"aaac","time":1666361873,"parent":"aaa"},{ + "name":"aaad","time":1666361937,"description":"abcdefg","ram":true,"parent":"aaa"},{ + "name":"aaae","time":1666362084,"parent":"aaa"},{ + "name":"current","description":"You are here!","parent":"aaae"},{ + "name":"aab","time":1666361920,"parent":"aa"},{ + "name":"aaba","time":1666361952,"parent":"aab"},{ + "name":"aabaa","time":1666361960},{ + "name":"aac","time":1666361896,"parent":"aa"},{ + "name":"aaca","time":1666361988,"description":"!@#()\u0026","ram":true,"parent":"aac"},{ + "name":"aacaa","time":1666362006,"ram":true,"parent":"aaca"},{ + "name":"aacb","time":1666361977,"parent":"aac"},{ + "name":"aacba","time":1666362021,"description":"QWERTY","parent":"aacb"},{ + "name":"aacc","time":1666361904,"parent":"aac"},{ + "name":"aacca","time":1666361910,"parent":"aacc"}]`} +} From 96dbb8a000a3f4b0b1be411b365b78b361d9c0bc Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Sat, 22 Oct 2022 13:42:49 +0000 Subject: [PATCH 08/14] feat: add function to return optional IDs --- cli/validate.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cli/validate.go b/cli/validate.go index ed12ea98..f663809f 100644 --- a/cli/validate.go +++ b/cli/validate.go @@ -13,6 +13,15 @@ func ValidateIDset(args []string, indexPos int, text string) string { return args[indexPos] } +// Should be used for Optional IDs. +// returns if the indexd arg if it is set. It returns an empty string when the indexed arg is not set. +func OptionalIDset(args []string, indexPos uint) (out string) { + if int(indexPos+1) <= len(args) { + out = args[indexPos] + } + return +} + func ValidateIntIDset(args []string, text string) int { id, err := strconv.Atoi(ValidateIDset(args, 0, text)) if err != nil && id <= 0 { From e33fb7b63ae53eda29fb20054c946340e7e0d3a0 Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Sat, 22 Oct 2022 13:49:03 +0000 Subject: [PATCH 09/14] refactor: Raname function and add comment Renamed ValidateIDset() to RequiredIDset() and added a description to the function. Changed the indexPos to uint as this value should never be negative. --- cli/command/create/create-acmeaccount.go | 2 +- cli/command/create/create-pool.go | 2 +- cli/command/create/create-storage.go | 2 +- cli/command/create/guest/create-guest.go | 2 +- cli/command/delete/delete.go | 2 +- cli/command/get/get.go | 2 +- cli/command/node/node-reboot.go | 2 +- cli/command/node/node-shutdown.go | 2 +- cli/command/set/set-metricserver.go | 4 ++-- cli/command/set/set-user.go | 4 ++-- cli/command/update/update-poolcomment.go | 6 +++--- cli/validate.go | 12 +++++++----- 12 files changed, 22 insertions(+), 20 deletions(-) diff --git a/cli/command/create/create-acmeaccount.go b/cli/command/create/create-acmeaccount.go index b749496f..76258d83 100644 --- a/cli/command/create/create-acmeaccount.go +++ b/cli/command/create/create-acmeaccount.go @@ -13,7 +13,7 @@ var create_acmeaccountCmd = &cobra.Command{ The config can be set with the --file flag or piped from stdin. For config examples see "example acmeaccount"`, RunE: func(cmd *cobra.Command, args []string) (err error) { - id := cli.ValidateIDset(args, 0, "AcmeAccountID") + id := cli.RequiredIDset(args, 0, "AcmeAccountID") config, err := proxmox.NewConfigAcmeAccountFromJson(cli.NewConfig()) if err != nil { return diff --git a/cli/command/create/create-pool.go b/cli/command/create/create-pool.go index bef36d27..8d53b400 100644 --- a/cli/command/create/create-pool.go +++ b/cli/command/create/create-pool.go @@ -9,7 +9,7 @@ var create_poolCmd = &cobra.Command{ Use: "pool POOLID [COMMENT]", Short: "Creates a new pool", RunE: func(cmd *cobra.Command, args []string) (err error) { - id := cli.ValidateIDset(args, 0, "PoolID") + id := cli.RequiredIDset(args, 0, "PoolID") var comment string if len(args) > 1 { comment = args[1] diff --git a/cli/command/create/create-storage.go b/cli/command/create/create-storage.go index 4273f773..6083b380 100644 --- a/cli/command/create/create-storage.go +++ b/cli/command/create/create-storage.go @@ -13,7 +13,7 @@ var create_storageCmd = &cobra.Command{ The config can be set with the --file flag or piped from stdin. For config examples see "example storage"`, RunE: func(cmd *cobra.Command, args []string) (err error) { - id := cli.ValidateIDset(args, 0, "StorageID") + id := cli.RequiredIDset(args, 0, "StorageID") config, err := proxmox.NewConfigStorageFromJson(cli.NewConfig()) if err != nil { return diff --git a/cli/command/create/guest/create-guest.go b/cli/command/create/guest/create-guest.go index eb5fc07e..2b7932af 100644 --- a/cli/command/create/guest/create-guest.go +++ b/cli/command/create/guest/create-guest.go @@ -20,7 +20,7 @@ func init() { func createGuest(args []string, IDtype string) (err error) { id := cli.ValidateIntIDset(args, IDtype+"ID") - node := cli.ValidateIDset(args, 1, "NodeID") + node := cli.RequiredIDset(args, 1, "NodeID") vmr := proxmox.NewVmRef(id) vmr.SetNode(node) c := cli.NewClient() diff --git a/cli/command/delete/delete.go b/cli/command/delete/delete.go index 9becf311..426a3246 100644 --- a/cli/command/delete/delete.go +++ b/cli/command/delete/delete.go @@ -18,7 +18,7 @@ func init() { func deleteID(args []string, IDtype string) (err error) { var exitStatus string - id := cli.ValidateIDset(args, 0, IDtype+"ID") + id := cli.RequiredIDset(args, 0, IDtype+"ID") c := cli.NewClient() switch IDtype { case "AcmeAccount": diff --git a/cli/command/get/get.go b/cli/command/get/get.go index 2cfe5f7f..7af994c5 100644 --- a/cli/command/get/get.go +++ b/cli/command/get/get.go @@ -16,7 +16,7 @@ func init() { } func getConfig(args []string, IDtype string) (err error) { - id := cli.ValidateIDset(args, 0, IDtype+"ID") + id := cli.RequiredIDset(args, 0, IDtype+"ID") c := cli.NewClient() var config interface{} switch IDtype { diff --git a/cli/command/node/node-reboot.go b/cli/command/node/node-reboot.go index 31b6db0f..c755cc6d 100644 --- a/cli/command/node/node-reboot.go +++ b/cli/command/node/node-reboot.go @@ -9,7 +9,7 @@ var reboot_nodeCmd = &cobra.Command{ Use: "reboot NODE", Short: "Reboots the specified node", RunE: func(cmd *cobra.Command, args []string) (err error) { - node := cli.ValidateIDset(args, 0, "node") + node := cli.RequiredIDset(args, 0, "node") c := cli.NewClient() _, err = c.RebootNode(node) if err != nil { diff --git a/cli/command/node/node-shutdown.go b/cli/command/node/node-shutdown.go index ef37359a..c4994620 100644 --- a/cli/command/node/node-shutdown.go +++ b/cli/command/node/node-shutdown.go @@ -9,7 +9,7 @@ var shutdown_nodeCmd = &cobra.Command{ Use: "shutdown NODE", Short: "Shuts the specified node down", RunE: func(cmd *cobra.Command, args []string) (err error) { - node := cli.ValidateIDset(args, 0, "node") + node := cli.RequiredIDset(args, 0, "node") c := cli.NewClient() _, err = c.ShutdownNode(node) if err != nil { diff --git a/cli/command/set/set-metricserver.go b/cli/command/set/set-metricserver.go index 0a5e82e5..b03ea719 100644 --- a/cli/command/set/set-metricserver.go +++ b/cli/command/set/set-metricserver.go @@ -14,7 +14,7 @@ Depending on the current state of the MetricServer, the MetricServer will be cre The config can be set with the --file flag or piped from stdin. For config examples see "example metricserver"`, RunE: func(cmd *cobra.Command, args []string) (err error) { - id := cli.ValidateIDset(args, 0,"MetricServerID") + id := cli.RequiredIDset(args, 0, "MetricServerID") config, err := proxmox.NewConfigMetricsFromJson(cli.NewConfig()) if err != nil { return @@ -24,7 +24,7 @@ For config examples see "example metricserver"`, if err != nil { return } - cli.PrintItemSet(setCmd.OutOrStdout() ,id ,"MericServer") + cli.PrintItemSet(setCmd.OutOrStdout(), id, "MericServer") return }, } diff --git a/cli/command/set/set-user.go b/cli/command/set/set-user.go index 155003db..65ef474f 100644 --- a/cli/command/set/set-user.go +++ b/cli/command/set/set-user.go @@ -14,7 +14,7 @@ Depending on the current state of the user, the user will be created or updated. The config can be set with the --file flag or piped from stdin. For config examples see "example user"`, RunE: func(cmd *cobra.Command, args []string) (err error) { - id := cli.ValidateIDset(args, 0, "UserID") + id := cli.RequiredIDset(args, 0, "UserID") config, err := proxmox.NewConfigUserFromJson(cli.NewConfig()) if err != nil { return @@ -28,7 +28,7 @@ For config examples see "example user"`, if err != nil { return } - cli.PrintItemSet(setCmd.OutOrStdout() ,id ,"User") + cli.PrintItemSet(setCmd.OutOrStdout(), id, "User") return }, } diff --git a/cli/command/update/update-poolcomment.go b/cli/command/update/update-poolcomment.go index 541d0747..db57dfba 100644 --- a/cli/command/update/update-poolcomment.go +++ b/cli/command/update/update-poolcomment.go @@ -8,9 +8,9 @@ import ( var update_poolCmd = &cobra.Command{ Use: "poolcomment POOLID [COMMENT]", Short: "Updates the comment on the speciefied pool", - RunE: func(cmd *cobra.Command, args []string) (err error){ + RunE: func(cmd *cobra.Command, args []string) (err error) { var comment string - id := cli.ValidateIDset(args, 0, "PoolID") + id := cli.RequiredIDset(args, 0, "PoolID") if len(args) > 1 { comment = args[1] } @@ -19,7 +19,7 @@ var update_poolCmd = &cobra.Command{ if err != nil { return } - cli.PrintItemUpdated(updateCmd.OutOrStdout() ,id, "PoolComment") + cli.PrintItemUpdated(updateCmd.OutOrStdout(), id, "PoolComment") return }, } diff --git a/cli/validate.go b/cli/validate.go index f663809f..714058ca 100644 --- a/cli/validate.go +++ b/cli/validate.go @@ -6,8 +6,10 @@ import ( "strconv" ) -func ValidateIDset(args []string, indexPos int, text string) string { - if indexPos+1 > len(args) { +// Should be used for Required IDs. +// returns if the indexd arg if it is set. It throws and error when the indexed arg is not set. +func RequiredIDset(args []string, indexPos uint, text string) string { + if int(indexPos+1) > len(args) { log.Fatal(fmt.Errorf("error: no %s has been provided", text)) } return args[indexPos] @@ -23,15 +25,15 @@ func OptionalIDset(args []string, indexPos uint) (out string) { } func ValidateIntIDset(args []string, text string) int { - id, err := strconv.Atoi(ValidateIDset(args, 0, text)) + id, err := strconv.Atoi(RequiredIDset(args, 0, text)) if err != nil && id <= 0 { log.Fatal(fmt.Errorf("error: %s must be a positive integer", text)) } return id } -func ValidateExistinGuestID(args []string, indexPos int) int { - id, err := strconv.Atoi(ValidateIDset(args, indexPos, "GuestID")) +func ValidateExistinGuestID(args []string, indexPos uint) int { + id, err := strconv.Atoi(RequiredIDset(args, indexPos, "GuestID")) if err != nil || id < 100 { log.Fatal(fmt.Errorf("error: GuestID must be a positive integer of 100 or greater")) } From 1db435f205c14cab1ba0dd6699abc262d922f228 Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Sat, 22 Oct 2022 13:54:52 +0000 Subject: [PATCH 10/14] Missing from previous commit --- cli/command/update/update-storage.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/command/update/update-storage.go b/cli/command/update/update-storage.go index ec7179c4..655e889d 100644 --- a/cli/command/update/update-storage.go +++ b/cli/command/update/update-storage.go @@ -13,7 +13,7 @@ var update_storageCmd = &cobra.Command{ The config can be set with the --file flag or piped from stdin. For config examples see "example storage"`, RunE: func(cmd *cobra.Command, args []string) (err error) { - id := cli.ValidateIDset(args, 0, "StorageID") + id := cli.RequiredIDset(args, 0, "StorageID") config, err := proxmox.NewConfigStorageFromJson(cli.NewConfig()) if err != nil { return From cb7bb3e1748f447df7b2c4d3b54fc3a91dcf828b Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Mon, 24 Oct 2022 10:34:14 +0000 Subject: [PATCH 11/14] fix: Fix output formatting and improve tests --- proxmox/snapshot.go | 19 ++++++++++--------- proxmox/snapshot_test.go | 39 ++++++++++++++++++++++++++++++++------- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/proxmox/snapshot.go b/proxmox/snapshot.go index 1b9b161e..f457177b 100644 --- a/proxmox/snapshot.go +++ b/proxmox/snapshot.go @@ -29,20 +29,21 @@ func (config *ConfigSnapshot) CreateSnapshot(guestId uint, c *Client) (err error return } -type snapshot struct { +// Used for formatting the output when retrieving snapshots +type Snapshot struct { Name string `json:"name"` SnapTime uint `json:"time,omitempty"` Description string `json:"description,omitempty"` VmState bool `json:"ram,omitempty"` - Children []*snapshot `json:"children,omitempty"` + Children []*Snapshot `json:"children,omitempty"` Parent string `json:"parent,omitempty"` } // Formats the taskResponse as a list of snapshots -func FormatSnapshotsList(taskResponse []interface{}) (list []*snapshot) { - list = make([]*snapshot, len(taskResponse)) +func FormatSnapshotsList(taskResponse []interface{}) (list []*Snapshot) { + list = make([]*Snapshot, len(taskResponse)) for i, e := range taskResponse { - list[i] = &snapshot{} + list[i] = &Snapshot{} if _, isSet := e.(map[string]interface{})["description"]; isSet { list[i].Description = e.(map[string]interface{})["description"].(string) } @@ -63,11 +64,11 @@ func FormatSnapshotsList(taskResponse []interface{}) (list []*snapshot) { } // Formats a list of snapshots as a tree of snapshots -func FormatSnapshotsTree(list []*snapshot) (tree *snapshot) { +func FormatSnapshotsTree(taskResponse []interface{}) (tree []*Snapshot) { + list := FormatSnapshotsList(taskResponse) for _, e := range list { for _, ee := range list { if e.Parent == ee.Name { - e.Parent = "" ee.Children = append(ee.Children, e) break } @@ -75,9 +76,9 @@ func FormatSnapshotsTree(list []*snapshot) (tree *snapshot) { } for _, e := range list { if e.Parent == "" { - tree = e - break + tree = append(tree, e) } + e.Parent = "" } return } diff --git a/proxmox/snapshot_test.go b/proxmox/snapshot_test.go index d8ebd9a1..2dce5a3e 100644 --- a/proxmox/snapshot_test.go +++ b/proxmox/snapshot_test.go @@ -12,7 +12,7 @@ func Test_FormatSnapshotsTree(t *testing.T) { input := test_FormatSnapshots_Input() output := test_FormatSnapshotsTree_Output() for i, e := range input { - result, _ := json.Marshal(FormatSnapshotsTree(FormatSnapshotsList(e))) + result, _ := json.Marshal(FormatSnapshotsTree(e)) require.JSONEq(t, output[i], string(result)) } } @@ -90,7 +90,7 @@ func test_FormatSnapshots_Input() [][]interface{} { "name": "aabaa", "snaptime": float64(1666361960), "description": "", - "parent": "", + "parent": "aaba", "vmstate": float64(0), }, map[string]interface{}{ "name": "aac", @@ -134,11 +134,29 @@ func test_FormatSnapshots_Input() [][]interface{} { "description": "", "parent": "aacc", "vmstate": float64(0), + }, map[string]interface{}{ + "name": "bb", + "snaptime": float64(1666361866), + "description": "aA1!", + "parent": "", + "vmstate": float64(1), + }, map[string]interface{}{ + "name": "bba", + "snaptime": float64(1666362071), + "description": "", + "parent": "bb", + "vmstate": float64(0), + }, map[string]interface{}{ + "name": "bbb", + "snaptime": float64(1666362062), + "description": "", + "parent": "bb", + "vmstate": float64(0), }}} } func test_FormatSnapshotsTree_Output() []string { - return []string{`{ + return []string{`[{ "name":"aa","time":1666361849,"children":[{ "name":"aaa","time":1666361866,"ram":true,"children":[{ "name":"aaaa","time":1666362071,"description":"123456"},{ @@ -148,14 +166,18 @@ func test_FormatSnapshotsTree_Output() []string { "name":"aaae","time":1666362084,"children":[{ "name":"current","description":"You are here!"}]}]},{ "name":"aab","time":1666361920,"children":[{ - "name":"aaba","time":1666361952}]},{ + "name":"aaba","time":1666361952,"children":[{ + "name":"aabaa","time":1666361960}]}]},{ "name":"aac","time":1666361896,"children":[{ "name":"aaca","time":1666361988,"description":"!@#()\u0026","ram":true,"children":[{ "name":"aacaa","time":1666362006,"ram":true}]},{ "name":"aacb","time":1666361977,"children":[{ "name":"aacba","time":1666362021,"description":"QWERTY"}]},{ "name":"aacc","time":1666361904,"children":[{ - "name":"aacca","time":1666361910}]}]}]}`} + "name":"aacca","time":1666361910}]}]}]},{ + "name":"bb","time":1666361866,"description":"aA1!","ram":true,"children":[{ + "name":"bba","time":1666362071},{ + "name":"bbb","time":1666362062}]}]`} } func test_FormatSnapshotsList_Output() []string { @@ -170,12 +192,15 @@ func test_FormatSnapshotsList_Output() []string { "name":"current","description":"You are here!","parent":"aaae"},{ "name":"aab","time":1666361920,"parent":"aa"},{ "name":"aaba","time":1666361952,"parent":"aab"},{ - "name":"aabaa","time":1666361960},{ + "name":"aabaa","time":1666361960,"parent":"aaba"},{ "name":"aac","time":1666361896,"parent":"aa"},{ "name":"aaca","time":1666361988,"description":"!@#()\u0026","ram":true,"parent":"aac"},{ "name":"aacaa","time":1666362006,"ram":true,"parent":"aaca"},{ "name":"aacb","time":1666361977,"parent":"aac"},{ "name":"aacba","time":1666362021,"description":"QWERTY","parent":"aacb"},{ "name":"aacc","time":1666361904,"parent":"aac"},{ - "name":"aacca","time":1666361910,"parent":"aacc"}]`} + "name":"aacca","time":1666361910,"parent":"aacc"},{ + "name":"bb","time":1666361866,"description":"aA1!","ram":true},{ + "name":"bba","time":1666362071,"parent":"bb"},{ + "name":"bbb","time":1666362062,"parent":"bb"}]`} } From cda8283aef1388d63c47801db7300f102b5bf25f Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Mon, 24 Oct 2022 22:10:03 +0000 Subject: [PATCH 12/14] feat: Cli snapshot commands --- cli/command/create/create-snapshot.go | 39 +++++++++++++ cli/command/delete/delete-snapshot.go | 30 ++++++++++ cli/command/guest/guest-rollback.go | 29 ++++++++++ cli/command/list/list-snapshots.go | 55 +++++++++++-------- .../update/update-snapshotdescription.go | 28 ++++++++++ 5 files changed, 157 insertions(+), 24 deletions(-) create mode 100644 cli/command/create/create-snapshot.go create mode 100644 cli/command/delete/delete-snapshot.go create mode 100644 cli/command/guest/guest-rollback.go create mode 100644 cli/command/update/update-snapshotdescription.go diff --git a/cli/command/create/create-snapshot.go b/cli/command/create/create-snapshot.go new file mode 100644 index 00000000..26cbbe80 --- /dev/null +++ b/cli/command/create/create-snapshot.go @@ -0,0 +1,39 @@ +package create + +import ( + "github.com/Telmate/proxmox-api-go/cli" + "github.com/Telmate/proxmox-api-go/proxmox" + "github.com/spf13/cobra" +) + +var ( + // flag needs to be reset, as this value will persist during tests + memory bool + create_snapshotCmd = &cobra.Command{ + Use: "snapshot GUESTID SNAPSHOTNAME [DESCRIPTION]", + Short: "Creates a new snapshot of the specefied guest", + TraverseChildren: true, + Args: cobra.RangeArgs(2, 3), + RunE: func(cmd *cobra.Command, args []string) (err error) { + id := cli.ValidateIntIDset(args, "GuestID") + snapName := cli.RequiredIDset(args, 1, "SnapshotName") + config := proxmox.ConfigSnapshot{ + Name: snapName, + Description: cli.OptionalIDset(args, 2), + VmState: memory, + } + memory = false + err = config.CreateSnapshot(uint(id), cli.NewClient()) + if err != nil { + return + } + cli.PrintItemCreated(CreateCmd.OutOrStdout(), snapName, "Snapshot") + return + }, + } +) + +func init() { + CreateCmd.AddCommand(create_snapshotCmd) + create_snapshotCmd.Flags().BoolVar(&memory, "memory", false, "Snapshot memory") +} diff --git a/cli/command/delete/delete-snapshot.go b/cli/command/delete/delete-snapshot.go new file mode 100644 index 00000000..5f9a3240 --- /dev/null +++ b/cli/command/delete/delete-snapshot.go @@ -0,0 +1,30 @@ +package delete + +import ( + "github.com/Telmate/proxmox-api-go/cli" + "github.com/Telmate/proxmox-api-go/proxmox" + "github.com/spf13/cobra" +) + +var ( + delete_snapshotCmd = &cobra.Command{ + Use: "snapshot GUESTID SNAPSHOTNAME", + Short: "Deletes the Speciefied snapshot", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) (err error) { + id := cli.ValidateIntIDset(args, "GuestID") + snapName := cli.RequiredIDset(args, 1, "SnapshotName") + c := cli.NewClient() + _, err = c.DeleteSnapshot(proxmox.NewVmRef(id), snapName) + if err != nil { + return + } + cli.PrintItemDeleted(deleteCmd.OutOrStdout(), snapName, "Snapshot") + return + }, + } +) + +func init() { + deleteCmd.AddCommand(delete_snapshotCmd) +} diff --git a/cli/command/guest/guest-rollback.go b/cli/command/guest/guest-rollback.go new file mode 100644 index 00000000..c01e5e28 --- /dev/null +++ b/cli/command/guest/guest-rollback.go @@ -0,0 +1,29 @@ +package guest + +import ( + "fmt" + + "github.com/Telmate/proxmox-api-go/cli" + "github.com/Telmate/proxmox-api-go/proxmox" + "github.com/spf13/cobra" +) + +var guest_rollbackCmd = &cobra.Command{ + Use: "rollback GUESTID SNAPSHOT", + Short: "Shuts the speciefid guest down", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) (err error) { + vmr := proxmox.NewVmRef(cli.ValidateIntIDset(args, "GuestID")) + snapName := cli.RequiredIDset(args, 1, "SnapshotName") + c := cli.NewClient() + _, err = c.RollbackSnapshot(vmr, snapName) + if err == nil { + fmt.Fprintf(GuestCmd.OutOrStdout(), "Guest with id (%d) has been rolled back to snapshot (%s)\n", vmr.VmId(), snapName) + } + return + }, +} + +func init() { + GuestCmd.AddCommand(guest_rollbackCmd) +} diff --git a/cli/command/list/list-snapshots.go b/cli/command/list/list-snapshots.go index 2c2698ae..386485f7 100644 --- a/cli/command/list/list-snapshots.go +++ b/cli/command/list/list-snapshots.go @@ -1,37 +1,44 @@ package list import ( - "fmt" "github.com/Telmate/proxmox-api-go/cli" "github.com/Telmate/proxmox-api-go/proxmox" "github.com/spf13/cobra" ) -var list_snapshotsCmd = &cobra.Command{ - Use: "snapshots GuestID", - Short: "Prints a list of QemuSnapshots in raw json format", - Run: func(cmd *cobra.Command, args []string) { - id := cli.ValidateExistinGuestID(args, 0) - c := cli.NewClient() - vmr := proxmox.NewVmRef(id) - _, err := c.GetVmInfo(vmr) - cli.LogFatalError(err) - jbody, _, err := c.ListQemuSnapshot(vmr) - cli.LogFatalError(err) - temp := jbody["data"].([]interface{}) - if len(temp) == 1 { - fmt.Printf("Guest with ID (%d) has no snapshots",id) - } else { - for _, e := range temp { - snapshotName := e.(map[string]interface{})["name"].(string) - if snapshotName != "current" { - fmt.Println(snapshotName) - } +var ( + // flag needs to be reset, as this value will persist during tests + noTree bool + list_snapshotsCmd = &cobra.Command{ + Use: "snapshots GuestID", + Short: "Prints a list of QemuSnapshots in json format", + TraverseChildren: true, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) (err error) { + id := cli.ValidateExistinGuestID(args, 0) + jbody, err := cli.NewClient().ListSnapshots(proxmox.NewVmRef(id)) + if err != nil { + noTree = false + return } - } - }, -} + var list []*proxmox.Snapshot + if noTree { + noTree = false + list = proxmox.FormatSnapshotsList(jbody) + } else { + list = proxmox.FormatSnapshotsTree(jbody) + } + if len(list) == 0 { + listCmd.Printf("Guest with ID (%d) has no snapshots", id) + } else { + cli.PrintFormattedJson(listCmd.OutOrStdout(), list) + } + return + }, + } +) func init() { listCmd.AddCommand(list_snapshotsCmd) + list_snapshotsCmd.Flags().BoolVar(&noTree, "no-tree", false, "Format output as list instead of a tree.") } diff --git a/cli/command/update/update-snapshotdescription.go b/cli/command/update/update-snapshotdescription.go new file mode 100644 index 00000000..3c18d123 --- /dev/null +++ b/cli/command/update/update-snapshotdescription.go @@ -0,0 +1,28 @@ +package update + +import ( + "github.com/Telmate/proxmox-api-go/cli" + "github.com/Telmate/proxmox-api-go/proxmox" + "github.com/spf13/cobra" +) + +var update_snapshotCmd = &cobra.Command{ + Use: "snapshot GUESTID SNAPSHOTNAME [DESCRIPTION]", + Short: "Updates the description on the speciefied snapshot", + Args: cobra.RangeArgs(2, 3), + RunE: func(cmd *cobra.Command, args []string) (err error) { + id := cli.ValidateIntIDset(args, "GuestID") + snapName := cli.RequiredIDset(args, 1, "SnapshotName") + des := cli.OptionalIDset(args, 2) + err = cli.NewClient().UpdateSnapshotDescription(proxmox.NewVmRef(id), snapName, des) + if err != nil { + return + } + cli.PrintItemUpdated(updateCmd.OutOrStdout(), snapName, "Snapshot") + return + }, +} + +func init() { + updateCmd.AddCommand(update_snapshotCmd) +} From bb171561005e3b4407b39fb6cdb9d2465fae73e4 Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Mon, 24 Oct 2022 22:13:38 +0000 Subject: [PATCH 13/14] test: snapshot CRUD actions --- test/cli/Snapshot/Snapshot_0_test.go | 203 +++++++++++++++++++++++++++ test/cli/shared_tests.go | 14 +- 2 files changed, 213 insertions(+), 4 deletions(-) create mode 100644 test/cli/Snapshot/Snapshot_0_test.go diff --git a/test/cli/Snapshot/Snapshot_0_test.go b/test/cli/Snapshot/Snapshot_0_test.go new file mode 100644 index 00000000..586dd8f3 --- /dev/null +++ b/test/cli/Snapshot/Snapshot_0_test.go @@ -0,0 +1,203 @@ +package cli_snapshot_test + +import ( + "encoding/json" + "testing" + + cliTest "github.com/Telmate/proxmox-api-go/test/cli" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Snapshot_0_GuestQemu_300_Cleanup(t *testing.T) { + Test := cliTest.Test{ + ReqErr: true, + ErrContains: "300", + Args: []string{"-i", "delete", "guest", "300"}, + } + Test.StandardTest(t) +} + +func Test_Snapshot_0_GuestQemu_300_Create(t *testing.T) { + Test := cliTest.Test{ + InputJson: ` +{ + "name": "test-qemu300", + "memory": 128, + "os": "l26", + "cores": 1, + "sockets": 1 +}`, + Expected: "(300)", + Contains: true, + Args: []string{"-i", "create", "guest", "qemu", "300", "pve"}, + } + Test.StandardTest(t) +} + +func Test_Snapshot_0_GuestQemu_300_Start(t *testing.T) { + Test := cliTest.Test{ + Expected: "(300)", + Contains: true, + Args: []string{"-i", "guest", "start", "300"}, + } + Test.StandardTest(t) +} + +// Create a snapshot with all settings populated +func Test_Snapshot_0_Create_Full(t *testing.T) { + Test := cliTest.Test{ + Expected: "(snap00)", + Contains: true, + Args: []string{"-i", "create", "snapshot", "300", "snap00", "description00", "--memory"}, + } + Test.StandardTest(t) +} + +// Check if the snapshot was made properly and the right json structure is returned (tree) +func Test_Snapshot_0_Get_Full(t *testing.T) { + Test := cliTest.Test{ + Return: true, + Args: []string{"-i", "list", "snapshots", "300"}, + } + var data []*snapshot + require.NoError(t, json.Unmarshal(Test.StandardTest(t), &data)) + assert.Equal(t, "snap00", data[0].Name) + assert.Equal(t, "description00", data[0].Description) + assert.Equal(t, true, data[0].VmState) + assert.GreaterOrEqual(t, data[0].SnapTime, uint(0)) +} + +// Remove the description of the snapshot +func Test_Snapshot_0_Update_Description_Empty(t *testing.T) { + Test := cliTest.Test{ + Args: []string{"-i", "update", "snapshot", "300", "snap00", ""}, + } + Test.StandardTest(t) +} + +// Check if description is removed and the right json structure is returned (no tree) +func Test_Snapshot_0_Get_Description_Empty(t *testing.T) { + Test := cliTest.Test{ + NotExpected: "description00", + NotContains: true, + Return: true, + Args: []string{"-i", "list", "snapshots", "300", "--no-tree"}, + } + var data []snapshot + require.NoError(t, json.Unmarshal(Test.StandardTest(t), &data)) +} + +// Create a snapshot with the least settings populated +func Test_Snapshot_0_Create_Empty(t *testing.T) { + // t.(time.Second*120, true) + // time.Sleep(time.Second * 20) + Test := cliTest.Test{ + Expected: "(snap01)", + Contains: true, + Args: []string{"-i", "create", "snapshot", "300", "snap01"}, + } + Test.StandardTest(t) +} + +// Check if the snapshot was made properly +func Test_Snapshot_0_Get_Empty(t *testing.T) { + // time.Sleep(time.Second * 5) + Test := cliTest.Test{ + Return: true, + Args: []string{"-i", "list", "snapshots", "300"}, + } + var data []*snapshot + require.NoError(t, json.Unmarshal(Test.StandardTest(t), &data)) + assert.Equal(t, "snap01", data[0].Children[0].Name) + assert.Equal(t, "", data[0].Children[0].Description) + assert.Equal(t, false, data[0].Children[0].VmState) + assert.GreaterOrEqual(t, data[0].Children[0].SnapTime, uint(0)) +} + +// Add the description to the snapshot +func Test_Snapshot_0_Update_Description_Full(t *testing.T) { + Test := cliTest.Test{ + Args: []string{"-i", "update", "snapshot", "300", "snap01", "description01"}, + } + Test.StandardTest(t) +} + +// Check if description is added +func Test_Snapshot_0_Get_Description_Full(t *testing.T) { + Test := cliTest.Test{ + Expected: "description01", + Contains: true, + Return: true, + Args: []string{"-i", "list", "snapshots", "300"}, + } + var data []*snapshot + require.NoError(t, json.Unmarshal(Test.StandardTest(t), &data)) +} + +// rollback snapshot +func Test_Snapshot_0_Set_Rollback(t *testing.T) { + Test := cliTest.Test{ + Expected: "(snap00)", + Contains: true, + Args: []string{"-i", "guest", "rollback", "300", "snap00"}, + } + Test.StandardTest(t) +} + +// Check if the snapshot was rolled back +func Test_Snapshot_0_Get_Rollback(t *testing.T) { + Test := cliTest.Test{ + Return: true, + Args: []string{"-i", "list", "snapshots", "300", "--no-tree"}, + } + var data []*snapshot + var nofail bool + require.NoError(t, json.Unmarshal(Test.StandardTest(t), &data)) + for _, e := range data { + if e.Name == "current" { + assert.Equal(t, "You are here!", e.Description) + assert.Equal(t, "snap00", e.Parent) + nofail = true + break + } + } + assert.Equal(t, true, nofail) +} + +// delete snapshot +func Test_Snapshot_0_Delete(t *testing.T) { + Test := cliTest.Test{ + Expected: "(snap00)", + Contains: true, + Args: []string{"-i", "delete", "snapshot", "300", "snap00"}, + } + Test.StandardTest(t) +} + +// Check if the snapshot was deleted +func Test_Snapshot_0_Get_Delete(t *testing.T) { + Test := cliTest.Test{ + NotExpected: "snap00", + NotContains: true, + Args: []string{"-i", "list", "snapshots", "300"}, + } + Test.StandardTest(t) +} + +func Test_Snapshot_0_GuestQemu_300_Delete(t *testing.T) { + Test := cliTest.Test{ + ReqErr: false, + Args: []string{"-i", "delete", "guest", "300"}, + } + Test.StandardTest(t) +} + +type snapshot struct { + Name string `json:"name"` + SnapTime uint `json:"time,omitempty"` + Description string `json:"description,omitempty"` + VmState bool `json:"ram,omitempty"` + Children []*snapshot `json:"children,omitempty"` + Parent string `json:"parent,omitempty"` +} diff --git a/test/cli/shared_tests.go b/test/cli/shared_tests.go index 2f45e33a..0bb4e3c1 100644 --- a/test/cli/shared_tests.go +++ b/test/cli/shared_tests.go @@ -25,6 +25,8 @@ type Test struct { ReqErr bool //if an error is expected as output ErrContains string //the string the error should contain + Return bool //if the output should be read and returned for more advanced prcessing + Args []string //cli arguments } @@ -41,7 +43,7 @@ func ListTest(t *testing.T, args []string, expected string) { assert.Contains(t, string(out), expected) } -func (test *Test) StandardTest(t *testing.T) { +func (test *Test) StandardTest(t *testing.T) (out []byte) { SetEnvironmentVariables() cli.RootCmd.SetArgs(test.Args) buffer := new(bytes.Buffer) @@ -58,7 +60,7 @@ func (test *Test) StandardTest(t *testing.T) { require.NoError(t, err) } if test.Expected != "" { - out, _ := io.ReadAll(buffer) + out, _ = io.ReadAll(buffer) if test.Contains { assert.Contains(t, string(out), test.Expected) } else { @@ -66,7 +68,7 @@ func (test *Test) StandardTest(t *testing.T) { } } if test.NotExpected != "" { - out, _ := io.ReadAll(buffer) + out, _ = io.ReadAll(buffer) if test.NotContains { assert.NotContains(t, string(out), test.NotExpected) } else { @@ -74,9 +76,13 @@ func (test *Test) StandardTest(t *testing.T) { } } if test.OutputJson != "" { - out, _ := io.ReadAll(buffer) + out, _ = io.ReadAll(buffer) require.JSONEq(t, test.OutputJson, string(out)) } + if test.Return && len(out) == 0 { + out, _ = io.ReadAll(buffer) + } + return } type LoginTest struct { From c57e33a777f9e2aa500fe387953b346e284eb5ce Mon Sep 17 00:00:00 2001 From: Tinyblargon <76069640+Tinyblargon@users.noreply.github.com> Date: Mon, 24 Oct 2022 22:22:33 +0000 Subject: [PATCH 14/14] Remove TODO items --- main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index a3aa02d3..d9b41bcc 100644 --- a/main.go +++ b/main.go @@ -235,13 +235,13 @@ func main() { failError(config.CloneVm(sourceVmr, vmr, c)) failError(config.UpdateConfig(vmr, c)) log.Println("Complete") - // TODO make createQemuSnapshot in new cli + case "createQemuSnapshot": sourceVmr, err := c.GetVmRefByName(flag.Args()[1]) failError(err) jbody, err = c.CreateQemuSnapshot(sourceVmr, flag.Args()[2]) failError(err) - // TODO make deleteQemuSnapshot in new cli + case "deleteQemuSnapshot": sourceVmr, err := c.GetVmRefByName(flag.Args()[1]) failError(err) @@ -285,7 +285,7 @@ func main() { } } failError(err) - // TODO make rollbackQemu in new cli + case "rollbackQemu": sourceVmr, err := c.GetVmRefByName(flag.Args()[1]) failError(err)