From 3a3996fc27186acccc4182c8f847c90afa8c1a0d Mon Sep 17 00:00:00 2001
From: Florent <florent.gluck@hesge.ch>
Date: Tue, 24 Dec 2024 10:14:39 +0100
Subject: [PATCH] libclient: WIP vm commands

---
 src/client/cmdTemplate/templateEdit.go  |  2 +-
 src/client/cmdUser/userDel.go           |  2 +-
 src/client/cmdUser/userResetPwd.go      |  2 +-
 src/client/cmdUser/userSetCaps.go       |  4 +-
 src/client/cmdUser/userUnlock.go        |  2 +-
 src/client/cmdUser/userUpdatePwd.go     |  2 +-
 src/client/cmdUser/userWhoami.go        |  2 +-
 src/client/cmdVM/vmAddAccess.go         | 30 +++----------
 src/client/cmdVM/vmAttachAsync.go       |  4 +-
 src/client/cmdVM/vmAttachAsyncSingle.go |  2 +-
 src/client/cmdVM/vmAttachFromPwd.go     |  2 +-
 src/client/cmdVM/vmAttachSync.go        | 56 -------------------------
 src/client/cmdVM/vmCreate.go            |  2 +-
 src/client/cmdVM/vmDel.go               | 12 +++---
 src/client/cmdVM/vmDelAccess.go         | 30 +++----------
 src/client/cmdVM/vmEdit.go              | 23 ++++------
 src/client/cmdVM/vmExportDir.go         | 25 ++---------
 src/client/cmdVM/vmImportDir.go         |  6 +--
 src/client/cmdVM/vmReboot.go            | 12 +++---
 src/client/cmdVM/vmShutdown.go          | 19 ++-------
 src/client/cmdVM/vmStart.go             | 19 ++-------
 src/client/cmdVM/vmStartAttach.go       |  6 +--
 src/client/cmdVM/vmStartWithCreds.go    | 20 +++------
 src/client/cmdVM/vmStop.go              | 11 +++--
 src/client/nexush/nexush.go             |  2 +-
 src/libclient/go.sum                    |  2 +
 src/libclient/vm/vmAddAccess.go         | 23 ++++++++++
 src/libclient/vm/vmDelAccess.go         | 22 ++++++++++
 src/libclient/vm/vmEdit.go              | 28 +++++++++++++
 src/libclient/vm/vmExportDir.go         | 30 +++++++++++++
 src/libclient/vm/vmShutdown.go          | 21 ++++++++++
 src/libclient/vm/vmStart.go             | 21 ++++++++++
 src/libclient/vm/vmStartWithCreds.go    | 22 ++++++++++
 33 files changed, 247 insertions(+), 219 deletions(-)
 delete mode 100644 src/client/cmdVM/vmAttachSync.go
 create mode 100644 src/libclient/go.sum
 create mode 100644 src/libclient/vm/vmAddAccess.go
 create mode 100644 src/libclient/vm/vmDelAccess.go
 create mode 100644 src/libclient/vm/vmEdit.go
 create mode 100644 src/libclient/vm/vmExportDir.go
 create mode 100644 src/libclient/vm/vmShutdown.go
 create mode 100644 src/libclient/vm/vmStart.go
 create mode 100644 src/libclient/vm/vmStartWithCreds.go

diff --git a/src/client/cmdTemplate/templateEdit.go b/src/client/cmdTemplate/templateEdit.go
index 1996d0c..075bba8 100644
--- a/src/client/cmdTemplate/templateEdit.go
+++ b/src/client/cmdTemplate/templateEdit.go
@@ -44,7 +44,7 @@ func (cmd *Edit) Run(args []string) int {
 
 	p, err := cmd.parseArgs(args[1:])
 	if err != nil {
-		u.PrintlnErr(err.Error())
+		u.PrintlnErr(err)
 		return 1
 	}
 
diff --git a/src/client/cmdUser/userDel.go b/src/client/cmdUser/userDel.go
index ec41cb4..3d1159e 100644
--- a/src/client/cmdUser/userDel.go
+++ b/src/client/cmdUser/userDel.go
@@ -103,7 +103,7 @@ func (cmd *Del) Run(args []string) int {
 		for i := range args {
 			email := args[i]
 			if err := libclient.UserDelete(email); err != nil {
-				u.PrintlnErr(err.Error())
+				u.PrintlnErr(err)
 				statusCode = 1
 			} else {
 				u.Println("Successfully deleted user " + email)
diff --git a/src/client/cmdUser/userResetPwd.go b/src/client/cmdUser/userResetPwd.go
index 73758b6..e981a17 100644
--- a/src/client/cmdUser/userResetPwd.go
+++ b/src/client/cmdUser/userResetPwd.go
@@ -40,7 +40,7 @@ func (cmd *ResetPwd) Run(args []string) int {
 		email := args[i]
 		newpwd, err := libclient.UserResetPwd(email)
 		if err != nil {
-			u.PrintlnErr(err.Error())
+			u.PrintlnErr(err)
 			return 1
 		} else {
 			u.Println("New password for user " + email + ": " + newpwd)
diff --git a/src/client/cmdUser/userSetCaps.go b/src/client/cmdUser/userSetCaps.go
index 387fbdf..44133e7 100644
--- a/src/client/cmdUser/userSetCaps.go
+++ b/src/client/cmdUser/userSetCaps.go
@@ -123,7 +123,7 @@ func (cmd *SetCaps) Run(args []string) int {
 			}
 
 			if err := libclient.UserSetCaps(email, userCaps); err != nil {
-				u.PrintlnErr(err.Error())
+				u.PrintlnErr(err)
 				statusCode = 1
 			} else {
 				u.Println("Successfully set capabilities for " + email)
@@ -139,7 +139,7 @@ func (cmd *SetCaps) Run(args []string) int {
 		}
 
 		if err := libclient.UserSetCaps(email, userCaps); err != nil {
-			u.PrintlnErr(err.Error())
+			u.PrintlnErr(err)
 			statusCode = 1
 		} else {
 			u.Println("Successfully set capabilities for " + email)
diff --git a/src/client/cmdUser/userUnlock.go b/src/client/cmdUser/userUnlock.go
index 50e7c24..9db7111 100644
--- a/src/client/cmdUser/userUnlock.go
+++ b/src/client/cmdUser/userUnlock.go
@@ -39,7 +39,7 @@ func (cmd *Unlock) Run(args []string) int {
 	for i := range args {
 		email := args[i]
 		if err := libclient.UserUnlock(email); err != nil {
-			u.PrintlnErr(err.Error())
+			u.PrintlnErr(err)
 			return 1
 		} else {
 			u.Println("Successfully unlocked user " + email)
diff --git a/src/client/cmdUser/userUpdatePwd.go b/src/client/cmdUser/userUpdatePwd.go
index be9669e..1bd34d0 100644
--- a/src/client/cmdUser/userUpdatePwd.go
+++ b/src/client/cmdUser/userUpdatePwd.go
@@ -60,7 +60,7 @@ func (cmd *UpdatePwd) Run(args []string) int {
 
 	err = libclient.UserUpdatePwd(string(newPwd))
 	if err != nil {
-		u.PrintlnErr(err.Error())
+		u.PrintlnErr(err)
 		return 1
 	}
 
diff --git a/src/client/cmdUser/userWhoami.go b/src/client/cmdUser/userWhoami.go
index 915d302..56bc506 100644
--- a/src/client/cmdUser/userWhoami.go
+++ b/src/client/cmdUser/userWhoami.go
@@ -35,7 +35,7 @@ func (cmd *Whoami) Run(args []string) int {
 
 	user, err := libclient.UserWhoAmI()
 	if err != nil {
-		u.PrintlnErr(err.Error())
+		u.PrintlnErr(err)
 		return 1
 	}
 
diff --git a/src/client/cmdVM/vmAddAccess.go b/src/client/cmdVM/vmAddAccess.go
index f5a9f13..dba53d5 100644
--- a/src/client/cmdVM/vmAddAccess.go
+++ b/src/client/cmdVM/vmAddAccess.go
@@ -123,8 +123,8 @@ func (cmd *AddAccess) Run(args []string) int {
 				}
 			}
 
-			if err := cmd.runRequest(vmID, vmID, email, vmAccessCaps); err != nil {
-				u.PrintlnErr(err.Error())
+			if err := libclient.VMAddAccess(vmID, vmID, email, vmAccessCaps); err != nil {
+				u.PrintlnErr(err)
 				statusCode = 1
 			} else {
 				u.Println("Successfully set VM access for " + email + " in VM \"" + vmID + "\"")
@@ -135,13 +135,13 @@ func (cmd *AddAccess) Run(args []string) int {
 
 		email, capabilities, patterns, err := cmd.parseArgs(args)
 		if err != nil {
-			u.PrintlnErr(err.Error())
+			u.PrintlnErr(err)
 			return 1
 		}
 
 		vms, err := getFilteredVMs(libclient.GetEditAccessVMs, patterns)
 		if err != nil {
-			u.PrintlnErr(err.Error())
+			u.PrintlnErr(err)
 			return 1
 		}
 
@@ -156,10 +156,8 @@ func (cmd *AddAccess) Run(args []string) int {
 		}
 
 		for _, vm := range vms {
-			vmID := vm.ID.String()
-			vmName := vm.Name
-			if err := cmd.runRequest(vmID, vmName, email, vmAccessCaps); err != nil {
-				u.PrintlnErr(err.Error())
+			if err := libclient.VMAddAccess(vm.ID.String(), vm.Name, email, vmAccessCaps); err != nil {
+				u.PrintlnErr(err)
 				statusCode = 1
 			} else {
 				u.Println("Successfully set VM access for " + email + " in VM \"" + vm.Name + "\"")
@@ -170,22 +168,6 @@ func (cmd *AddAccess) Run(args []string) int {
 	return statusCode
 }
 
-func (cmd *AddAccess) runRequest(vmID, vmName, email string, vmAccessCaps *params.VMAddAccess) error {
-	client := g.GetInstance().Client
-	host := g.GetInstance().Host
-
-	resp, err := client.R().SetBody(vmAccessCaps).Put(host + "/vms/" + vmID + "/access/" + email)
-	if err != nil {
-		return errors.New("Failed setting VM access for " + email + " in VM \"" + vmName + "\": " + err.Error())
-	}
-
-	if resp.IsSuccess() {
-		return nil
-	} else {
-		return errors.New("Failed setting VM access for " + email + " in VM \"" + vmName + "\": " + resp.Status() + ": " + resp.String())
-	}
-}
-
 func (cmd *AddAccess) parseArgs(args []string) (string, []string, []string, error) {
 	var patterns []string
 	var capabilities []string
diff --git a/src/client/cmdVM/vmAttachAsync.go b/src/client/cmdVM/vmAttachAsync.go
index da51835..d147716 100644
--- a/src/client/cmdVM/vmAttachAsync.go
+++ b/src/client/cmdVM/vmAttachAsync.go
@@ -26,7 +26,7 @@ func (cmd *AttachAsync) PrintUsage() {
 
 func (cmd *AttachAsync) Run(args []string) int {
 	if err := exec.CheckRemoteViewer(); err != nil {
-		u.PrintlnErr(err.Error())
+		u.PrintlnErr(err)
 		return 1
 	}
 
@@ -38,7 +38,7 @@ func (cmd *AttachAsync) Run(args []string) int {
 
 	creds, err := getFilteredVMCredentials("/vms/attach", args)
 	if err != nil {
-		u.PrintlnErr(err.Error())
+		u.PrintlnErr(err)
 		return 1
 	}
 
diff --git a/src/client/cmdVM/vmAttachAsyncSingle.go b/src/client/cmdVM/vmAttachAsyncSingle.go
index 0f2a5f2..6fce7a9 100644
--- a/src/client/cmdVM/vmAttachAsyncSingle.go
+++ b/src/client/cmdVM/vmAttachAsyncSingle.go
@@ -32,7 +32,7 @@ func (cmd *AttachAsyncSingle) PrintUsage() {
 
 func (cmd *AttachAsyncSingle) Run(args []string) int {
 	if err := exec.CheckRemoteViewer(); err != nil {
-		u.PrintlnErr(err.Error())
+		u.PrintlnErr(err)
 		return 1
 	}
 
diff --git a/src/client/cmdVM/vmAttachFromPwd.go b/src/client/cmdVM/vmAttachFromPwd.go
index d3e1fb8..48d75f3 100644
--- a/src/client/cmdVM/vmAttachFromPwd.go
+++ b/src/client/cmdVM/vmAttachFromPwd.go
@@ -37,7 +37,7 @@ func (cmd *AttachFromPwd) PrintUsage() {
 
 func (cmd *AttachFromPwd) Run(args []string) int {
 	if err := exec.CheckRemoteViewer(); err != nil {
-		u.PrintlnErr(err.Error())
+		u.PrintlnErr(err)
 		return 1
 	}
 
diff --git a/src/client/cmdVM/vmAttachSync.go b/src/client/cmdVM/vmAttachSync.go
deleted file mode 100644
index 28cda3b..0000000
--- a/src/client/cmdVM/vmAttachSync.go
+++ /dev/null
@@ -1,56 +0,0 @@
-package cmdVM
-
-import (
-	"nexus-client/exec"
-	u "nexus-client/utils"
-)
-
-type AttachSync struct {
-	Name string
-}
-
-func (cmd *AttachSync) GetName() string {
-	return cmd.Name
-}
-
-func (cmd *AttachSync) GetDesc() []string {
-	return []string{
-		"Attaches to one or more VMs in order to use their desktop environment.",
-		"If not the VM's owner: requires VM_ATTACH VM access capability or VM_ATTACH_ANY user capability."}
-}
-
-func (cmd *AttachSync) PrintUsage() {
-	printRegexUsage(cmd)
-	printRegexUsageDetails()
-}
-
-func (cmd *AttachSync) Run(args []string) int {
-	if err := exec.CheckRemoteViewer(); err != nil {
-		u.PrintlnErr(err.Error())
-		return 1
-	}
-
-	argc := len(args)
-	if argc < 1 {
-		cmd.PrintUsage()
-		return 1
-	}
-
-	creds, err := getFilteredVMCredentials("/vms/attach", args)
-	if err != nil {
-		u.PrintlnErr(err.Error())
-		return 1
-	}
-
-	if len(creds) == 0 {
-		u.PrintlnErr("Error: VM(s) not found.")
-		return 1
-	}
-
-	statusCode, err := AttachToVMs(creds, true)
-	if err != nil {
-		u.PrintlnErr(err)
-	}
-
-	return statusCode
-}
diff --git a/src/client/cmdVM/vmCreate.go b/src/client/cmdVM/vmCreate.go
index 073a453..57f8994 100644
--- a/src/client/cmdVM/vmCreate.go
+++ b/src/client/cmdVM/vmCreate.go
@@ -84,7 +84,7 @@ func (cmd *Create) Run(args []string) int {
 			csvFile := args[6]
 			csvEntries, err = u.ReadCSVColumn(csvFile, 0)
 			if err != nil {
-				u.PrintlnErr(err.Error())
+				u.PrintlnErr(err)
 				return 1
 			}
 			count = len(csvEntries)
diff --git a/src/client/cmdVM/vmDel.go b/src/client/cmdVM/vmDel.go
index 68caed1..68a392f 100644
--- a/src/client/cmdVM/vmDel.go
+++ b/src/client/cmdVM/vmDel.go
@@ -33,7 +33,7 @@ func (cmd *Del) Run(args []string) int {
 
 	vms, err := getFilteredVMs(libclient.GetDelVMs, args)
 	if err != nil {
-		u.PrintlnErr(err.Error())
+		u.PrintlnErr(err)
 		return 1
 	}
 
@@ -42,15 +42,17 @@ func (cmd *Del) Run(args []string) int {
 		return 1
 	}
 
-	for _, vm := range vms {
+	statusCode := 0
 
+	for _, vm := range vms {
 		err := libclient.VMDel(vm.ID.String())
 		if err != nil {
 			u.PrintlnErr(err)
-			return 1
+			statusCode = 1
+		} else {
+			u.Println("Deleted VM \"" + vm.Name + "\"")
 		}
-		u.Println("Deleted VM \"" + vm.Name + "\"")
 	}
 
-	return 0
+	return statusCode
 }
diff --git a/src/client/cmdVM/vmDelAccess.go b/src/client/cmdVM/vmDelAccess.go
index ee61f45..4bd7ef9 100644
--- a/src/client/cmdVM/vmDelAccess.go
+++ b/src/client/cmdVM/vmDelAccess.go
@@ -91,8 +91,8 @@ func (cmd *DelAccess) Run(args []string) int {
 				continue
 			}
 
-			if err := cmd.runRequest(vmID, vmID, email); err != nil {
-				u.PrintlnErr(err.Error())
+			if err := libclient.VMDelAccess(vmID, vmID, email); err != nil {
+				u.PrintlnErr(err)
 				statusCode = 1
 			} else {
 				u.Println("Successfully removed VM access for " + email + " in VM \"" + vmID + "\"")
@@ -103,13 +103,13 @@ func (cmd *DelAccess) Run(args []string) int {
 
 		email, patterns, err := cmd.parseArgs(args)
 		if err != nil {
-			u.PrintlnErr(err.Error())
+			u.PrintlnErr(err)
 			return 1
 		}
 
 		vms, err := getFilteredVMs(libclient.GetEditAccessVMs, patterns)
 		if err != nil {
-			u.PrintlnErr(err.Error())
+			u.PrintlnErr(err)
 			return 1
 		}
 
@@ -119,10 +119,8 @@ func (cmd *DelAccess) Run(args []string) int {
 		}
 
 		for _, vm := range vms {
-			vmID := vm.ID.String()
-			vmName := vm.Name
-			if err := cmd.runRequest(vmID, vmName, email); err != nil {
-				u.PrintlnErr(err.Error())
+			if err := libclient.VMDelAccess(vm.ID.String(), vm.Name, email); err != nil {
+				u.PrintlnErr(err)
 				statusCode = 1
 			} else {
 				u.Println("Successfully removed VM access for " + email + " in VM \"" + vm.Name + "\"")
@@ -133,22 +131,6 @@ func (cmd *DelAccess) Run(args []string) int {
 	return statusCode
 }
 
-func (cmd *DelAccess) runRequest(vmID, vmName, email string) error {
-	client := g.GetInstance().Client
-	host := g.GetInstance().Host
-
-	resp, err := client.R().Delete(host + "/vms/" + vmID + "/access/" + email)
-	if err != nil {
-		return errors.New("Failed removing VM access for " + email + " in VM \"" + vmName + "\": " + err.Error())
-	}
-
-	if resp.IsSuccess() {
-		return nil
-	} else {
-		return errors.New("Failed removing VM access for " + email + " in VM \"" + vmName + "\": " + resp.Status() + ": " + resp.String())
-	}
-}
-
 func (cmd *DelAccess) parseArgs(args []string) (string, []string, error) {
 	var patterns []string
 	emailFound := false
diff --git a/src/client/cmdVM/vmEdit.go b/src/client/cmdVM/vmEdit.go
index 13055fb..eced17e 100644
--- a/src/client/cmdVM/vmEdit.go
+++ b/src/client/cmdVM/vmEdit.go
@@ -5,7 +5,6 @@ import (
 	u "nexus-client/utils"
 	"nexus-common/params"
 	"nexus-common/vm"
-	g "nexus-libclient/globals"
 	libclient "nexus-libclient/vm"
 	"strconv"
 	"strings"
@@ -46,9 +45,6 @@ usb     List of USB devices exposed in the VM; either "none" or a list of comma
 }
 
 func (cmd *Edit) Run(args []string) int {
-	client := g.GetInstance().Client
-	host := g.GetInstance().Host
-
 	argc := len(args)
 	if argc < 1 {
 		cmd.PrintUsage()
@@ -57,7 +53,7 @@ func (cmd *Edit) Run(args []string) int {
 
 	vmParams, patterns, err := cmd.parseArgs(args)
 	if err != nil {
-		u.PrintlnErr(err.Error())
+		u.PrintlnErr(err)
 		return 1
 	}
 
@@ -68,7 +64,7 @@ func (cmd *Edit) Run(args []string) int {
 
 	vms, err := getFilteredVMs(libclient.GetEditVMs, patterns)
 	if err != nil {
-		u.PrintlnErr(err.Error())
+		u.PrintlnErr(err)
 		return 1
 	}
 
@@ -80,19 +76,14 @@ func (cmd *Edit) Run(args []string) int {
 	statusCode := 0
 
 	for _, vm := range vms {
-		uuid := vm.ID.String()
-		resp, err := client.R().SetBody(vmParams).Put(host + "/vms/" + uuid)
+		vmID := vm.ID.String()
+		vmEdited, err := libclient.VMEdit(vmID, *vmParams)
 		if err != nil {
-			u.PrintlnErr("Failed editing VM \"" + vm.Name + "\": " + err.Error())
+			u.PrintlnErr(err)
 			statusCode = 1
 		} else {
-			if resp.IsSuccess() {
-				u.Println("Successfully edited VM \"" + vm.ID.String() + "\":")
-				u.Println(resp.String())
-			} else {
-				u.PrintlnErr("Failed editing VM \"" + vm.Name + "\": " + resp.Status() + ": " + resp.String())
-				statusCode = 1
-			}
+			u.Println("Successfully edited VM \"" + vmID + "\":")
+			u.Println(vmEdited.String())
 		}
 	}
 
diff --git a/src/client/cmdVM/vmExportDir.go b/src/client/cmdVM/vmExportDir.go
index cb3f456..e174201 100644
--- a/src/client/cmdVM/vmExportDir.go
+++ b/src/client/cmdVM/vmExportDir.go
@@ -2,8 +2,6 @@ package cmdVM
 
 import (
 	u "nexus-client/utils"
-	"nexus-common/params"
-	g "nexus-libclient/globals"
 	libclient "nexus-libclient/vm"
 )
 
@@ -36,9 +34,6 @@ Remark: one archive, named after the VM's name, is created per VM.`
 }
 
 func (cmd *ExportDir) Run(args []string) int {
-	client := g.GetInstance().Client
-	host := g.GetInstance().Host
-
 	argc := len(args)
 	if argc < 2 {
 		cmd.PrintUsage()
@@ -49,7 +44,7 @@ func (cmd *ExportDir) Run(args []string) int {
 
 	vms, err := getFilteredVMs(libclient.GetExportDirVMs, args[:argc-1])
 	if err != nil {
-		u.PrintlnErr(err.Error())
+		u.PrintlnErr(err)
 		return 1
 	}
 
@@ -58,27 +53,15 @@ func (cmd *ExportDir) Run(args []string) int {
 		return 1
 	}
 
-	params := &params.VMExportDir{Dir: dir}
-
 	statusCode := 0
 
-	client.SetAllowGetMethodPayload(true)
-
 	for _, vm := range vms {
-		uuid := vm.ID.String()
-		outputFile := vm.Name + "_" + vm.ID.String() + ".tar.gz"
-		resp, err := client.R().SetOutput(outputFile).SetBody(params).Get(host + "/vms/" + uuid + "/exportdir")
+		outputFile, err := libclient.VMExportDir(vm.Name, vm.ID.String(), dir)
 		if err != nil {
-			u.PrintlnErr("Failed exporting " + dir + " from VM \"" + vm.Name + "\": " + err.Error())
+			u.PrintlnErr(err)
 			statusCode = 1
 		} else {
-			if resp.IsSuccess() {
-				u.Println("Exported " + dir + " from VM \"" + vm.Name + "\" into " + outputFile)
-			} else {
-				errorMsg, _ := u.FileToString(outputFile)
-				u.PrintlnErr("Failed exporting " + dir + " from VM \"" + vm.Name + "\": " + resp.Status() + ": " + errorMsg)
-				statusCode = 1
-			}
+			u.Println("Exported " + dir + " from VM \"" + vm.Name + "\" into " + outputFile)
 		}
 	}
 
diff --git a/src/client/cmdVM/vmImportDir.go b/src/client/cmdVM/vmImportDir.go
index 659fa4f..60e3fe8 100644
--- a/src/client/cmdVM/vmImportDir.go
+++ b/src/client/cmdVM/vmImportDir.go
@@ -52,7 +52,7 @@ func (cmd *ImportDir) Run(args []string) int {
 
 	vms, err := getFilteredVMs(libclient.GetImportFileVMs, args[:argc-2])
 	if err != nil {
-		u.PrintlnErr(err.Error())
+		u.PrintlnErr(err)
 		return 1
 	}
 
@@ -65,13 +65,13 @@ func (cmd *ImportDir) Run(args []string) int {
 
 	tmpTarGzFile, err := u.GetRandomTempFilename()
 	if err != nil {
-		u.PrintlnErr(err.Error())
+		u.PrintlnErr(err)
 		return 1
 	}
 	tmpTarGzFile += ".tar.gz"
 	defer os.Remove(tmpTarGzFile)
 	if err := u.TarGzDir(localDir, tmpTarGzFile); err != nil {
-		u.PrintlnErr(err.Error())
+		u.PrintlnErr(err)
 		return 1
 	}
 
diff --git a/src/client/cmdVM/vmReboot.go b/src/client/cmdVM/vmReboot.go
index e62c57d..ff8ed2a 100644
--- a/src/client/cmdVM/vmReboot.go
+++ b/src/client/cmdVM/vmReboot.go
@@ -34,7 +34,7 @@ func (cmd *Reboot) Run(args []string) int {
 
 	vms, err := getFilteredVMs(libclient.GetRebootVMs, args)
 	if err != nil {
-		u.PrintlnErr(err.Error())
+		u.PrintlnErr(err)
 		return 1
 	}
 
@@ -43,15 +43,17 @@ func (cmd *Reboot) Run(args []string) int {
 		return 1
 	}
 
+	statusCode := 0
+
 	for _, vm := range vms {
 		err := libclient.VMReboot(vm.ID.String())
 		if err != nil {
 			u.PrintlnErr(err)
-			return 1
+			statusCode = 1
+		} else {
+			u.Println("Sent reboot message to VM \"" + vm.Name + "\"")
 		}
-		u.Println("Sent reboot message to VM \"" + vm.Name + "\"")
-
 	}
 
-	return 0
+	return statusCode
 }
diff --git a/src/client/cmdVM/vmShutdown.go b/src/client/cmdVM/vmShutdown.go
index da07af9..4c51321 100644
--- a/src/client/cmdVM/vmShutdown.go
+++ b/src/client/cmdVM/vmShutdown.go
@@ -2,7 +2,6 @@ package cmdVM
 
 import (
 	u "nexus-client/utils"
-	g "nexus-libclient/globals"
 	libclient "nexus-libclient/vm"
 )
 
@@ -26,9 +25,6 @@ func (cmd *Shutdown) PrintUsage() {
 }
 
 func (cmd *Shutdown) Run(args []string) int {
-	client := g.GetInstance().Client
-	host := g.GetInstance().Host
-
 	argc := len(args)
 	if argc < 1 {
 		cmd.PrintUsage()
@@ -37,7 +33,7 @@ func (cmd *Shutdown) Run(args []string) int {
 
 	vms, err := getFilteredVMs(libclient.GetStopVMs, args)
 	if err != nil {
-		u.PrintlnErr(err.Error())
+		u.PrintlnErr(err)
 		return 1
 	}
 
@@ -49,20 +45,13 @@ func (cmd *Shutdown) Run(args []string) int {
 	statusCode := 0
 
 	for _, vm := range vms {
-		uuid := vm.ID.String()
-		resp, err := client.R().Put(host + "/vms/" + uuid + "/shutdown")
+		err := libclient.VMShutdown(vm.ID.String())
 		if err != nil {
-			u.PrintlnErr("Shutdown failed for VM \"" + vm.Name + "\": " + err.Error())
+			u.PrintlnErr(err)
 			statusCode = 1
 		} else {
-			if resp.IsSuccess() {
-				u.Println("Sent shutdown message to VM \"" + vm.Name + "\"")
-			} else {
-				u.PrintlnErr("Shutdown failed for VM \"" + vm.Name + "\": " + resp.Status() + ": " + resp.String())
-				statusCode = 1
-			}
+			u.Println("Sent shutdown message to VM \"" + vm.Name + "\"")
 		}
-
 	}
 
 	return statusCode
diff --git a/src/client/cmdVM/vmStart.go b/src/client/cmdVM/vmStart.go
index bf11948..5e4e31e 100644
--- a/src/client/cmdVM/vmStart.go
+++ b/src/client/cmdVM/vmStart.go
@@ -2,7 +2,6 @@ package cmdVM
 
 import (
 	u "nexus-client/utils"
-	g "nexus-libclient/globals"
 	libclient "nexus-libclient/vm"
 )
 
@@ -26,9 +25,6 @@ func (cmd *Start) PrintUsage() {
 }
 
 func (cmd *Start) Run(args []string) int {
-	client := g.GetInstance().Client
-	host := g.GetInstance().Host
-
 	argc := len(args)
 	if argc < 1 {
 		cmd.PrintUsage()
@@ -37,7 +33,7 @@ func (cmd *Start) Run(args []string) int {
 
 	vms, err := getFilteredVMs(libclient.GetStartVMs, args)
 	if err != nil {
-		u.PrintlnErr(err.Error())
+		u.PrintlnErr(err)
 		return 1
 	}
 
@@ -49,20 +45,13 @@ func (cmd *Start) Run(args []string) int {
 	statusCode := 0
 
 	for _, vm := range vms {
-		uuid := vm.ID.String()
-		resp, err := client.R().Put(host + "/vms/" + uuid + "/start")
+		err := libclient.VMStart(vm.ID.String())
 		if err != nil {
-			u.PrintlnErr("Failed starting VM \"" + vm.Name + "\": " + err.Error())
+			u.PrintlnErr(err)
 			statusCode = 1
 		} else {
-			if resp.IsSuccess() {
-				u.Println("Started VM \"" + vm.Name + "\"")
-			} else {
-				u.PrintlnErr("Failed starting VM \"" + vm.Name + "\": " + resp.Status() + ": " + resp.String())
-				statusCode = 1
-			}
+			u.Println("Started VM \"" + vm.Name + "\"")
 		}
-
 	}
 
 	return statusCode
diff --git a/src/client/cmdVM/vmStartAttach.go b/src/client/cmdVM/vmStartAttach.go
index 49b9a87..4cca859 100644
--- a/src/client/cmdVM/vmStartAttach.go
+++ b/src/client/cmdVM/vmStartAttach.go
@@ -38,13 +38,13 @@ func (cmd *StartAttach) Run(args []string) int {
 	}
 
 	if err := exec.CheckRemoteViewer(); err != nil {
-		u.PrintlnErr(err.Error())
+		u.PrintlnErr(err)
 		return 1
 	}
 
 	vms, err := getFilteredVMs(libclient.GetStartVMs, args)
 	if err != nil {
-		u.PrintlnErr(err.Error())
+		u.PrintlnErr(err)
 		return 1
 	}
 
@@ -75,7 +75,7 @@ func (cmd *StartAttach) Run(args []string) int {
 	// at this point, the returned filtered credentials only works for VMs that started successfully
 	creds, err := getFilteredVMCredentials("/vms/attach", args)
 	if err != nil {
-		u.PrintlnErr(err.Error())
+		u.PrintlnErr(err)
 		return 1
 	}
 
diff --git a/src/client/cmdVM/vmStartWithCreds.go b/src/client/cmdVM/vmStartWithCreds.go
index 67569d8..30663e5 100644
--- a/src/client/cmdVM/vmStartWithCreds.go
+++ b/src/client/cmdVM/vmStartWithCreds.go
@@ -4,7 +4,7 @@ import (
 	"errors"
 	u "nexus-client/utils"
 	"nexus-common/params"
-	g "nexus-libclient/globals"
+	libclient "nexus-libclient/vm"
 )
 
 type StartWithCreds struct {
@@ -52,9 +52,6 @@ func (cmd *StartWithCreds) parseCSVFile(csvFile string) ([]string, []string, err
 }
 
 func (cmd *StartWithCreds) Run(args []string) int {
-	client := g.GetInstance().Client
-	host := g.GetInstance().Host
-
 	argc := len(args)
 	if argc != 1 {
 		cmd.PrintUsage()
@@ -63,27 +60,22 @@ func (cmd *StartWithCreds) Run(args []string) int {
 
 	vmIDs, pwds, err := cmd.parseCSVFile(args[0])
 	if err != nil {
-		u.PrintlnErr(err.Error())
+		u.PrintlnErr(err)
 		return 1
 	}
 
 	statusCode := 0
 
-	vmArgs := &params.VMStartWithCreds{}
+	vmArgs := params.VMStartWithCreds{}
 
 	for i, vmID := range vmIDs {
 		vmArgs.Pwd = pwds[i]
-		resp, err := client.R().SetBody(vmArgs).Put(host + "/vms/" + vmID + "/startwithcreds")
+		err := libclient.VMStartWithCreds(vmID, vmArgs)
 		if err != nil {
-			u.PrintlnErr("Failed starting VM \"" + vmID + "\": " + err.Error())
+			u.PrintlnErr(err)
 			statusCode = 1
 		} else {
-			if resp.IsSuccess() {
-				u.Println("Started VM \"" + vmID + "\"")
-			} else {
-				u.PrintlnErr("Failed starting VM \"" + vmID + "\": " + resp.Status() + ": " + resp.String())
-				statusCode = 1
-			}
+			u.Println("Started VM \"" + vmID + "\"")
 		}
 	}
 
diff --git a/src/client/cmdVM/vmStop.go b/src/client/cmdVM/vmStop.go
index 22f86d7..eae7181 100644
--- a/src/client/cmdVM/vmStop.go
+++ b/src/client/cmdVM/vmStop.go
@@ -33,7 +33,7 @@ func (cmd *Stop) Run(args []string) int {
 
 	vms, err := getFilteredVMs(libclient.GetStopVMs, args)
 	if err != nil {
-		u.PrintlnErr(err.Error())
+		u.PrintlnErr(err)
 		return 1
 	}
 
@@ -42,14 +42,17 @@ func (cmd *Stop) Run(args []string) int {
 		return 1
 	}
 
+	statusCode := 0
+
 	for _, vm := range vms {
 		err := libclient.VMStop(vm.ID.String())
 		if err != nil {
 			u.PrintlnErr(err)
-			return 1
+			statusCode = 1
+		} else {
+			u.Println("Stopped VM \"" + vm.Name + "\"")
 		}
-		u.Println("Stopped VM \"" + vm.Name + "\"")
 	}
 
-	return 0
+	return statusCode
 }
diff --git a/src/client/nexush/nexush.go b/src/client/nexush/nexush.go
index 5488e9f..3a12c03 100644
--- a/src/client/nexush/nexush.go
+++ b/src/client/nexush/nexush.go
@@ -165,7 +165,7 @@ func run() int {
 	fmt.Printf(email + "'s password: ")
 	bytePwd, err := term.ReadPassword(int(syscall.Stdin))
 	if err != nil {
-		u.PrintlnErr(err.Error())
+		u.PrintlnErr(err)
 		return 1
 	}
 	pwd := string(bytePwd)
diff --git a/src/libclient/go.sum b/src/libclient/go.sum
new file mode 100644
index 0000000..7790d7c
--- /dev/null
+++ b/src/libclient/go.sum
@@ -0,0 +1,2 @@
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
diff --git a/src/libclient/vm/vmAddAccess.go b/src/libclient/vm/vmAddAccess.go
new file mode 100644
index 0000000..b03612f
--- /dev/null
+++ b/src/libclient/vm/vmAddAccess.go
@@ -0,0 +1,23 @@
+package vm
+
+import (
+	"nexus-common/params"
+	g "nexus-libclient/globals"
+	"nexus-libclient/response"
+)
+
+func VMAddAccess(vmID, vmName, email string, vmAccessCaps *params.VMAddAccess) error {
+	client := g.GetInstance().Client
+	host := g.GetInstance().Host
+
+	resp, err := client.R().SetBody(vmAccessCaps).Put(host + "/vms/" + vmID + "/access/" + email)
+	if err != nil {
+		return err
+	}
+
+	if resp.IsSuccess() {
+		return nil
+	} else {
+		return response.ErrorToMsg(resp)
+	}
+}
diff --git a/src/libclient/vm/vmDelAccess.go b/src/libclient/vm/vmDelAccess.go
new file mode 100644
index 0000000..4d69b60
--- /dev/null
+++ b/src/libclient/vm/vmDelAccess.go
@@ -0,0 +1,22 @@
+package vm
+
+import (
+	g "nexus-libclient/globals"
+	"nexus-libclient/response"
+)
+
+func VMDelAccess(vmID, vmName, email string) error {
+	client := g.GetInstance().Client
+	host := g.GetInstance().Host
+
+	resp, err := client.R().Delete(host + "/vms/" + vmID + "/access/" + email)
+	if err != nil {
+		return err
+	}
+
+	if resp.IsSuccess() {
+		return nil
+	} else {
+		return response.ErrorToMsg(resp)
+	}
+}
diff --git a/src/libclient/vm/vmEdit.go b/src/libclient/vm/vmEdit.go
new file mode 100644
index 0000000..191f510
--- /dev/null
+++ b/src/libclient/vm/vmEdit.go
@@ -0,0 +1,28 @@
+package vm
+
+import (
+	"encoding/json"
+	"nexus-common/params"
+	"nexus-common/vm"
+	g "nexus-libclient/globals"
+	"nexus-libclient/response"
+)
+
+func VMEdit(vmID string, p params.VMEdit) (*vm.VMNetworkSerialized, error) {
+	client := g.GetInstance().Client
+	host := g.GetInstance().Host
+
+	resp, err := client.R().SetBody(p).Put(host + "/vms/" + vmID)
+	if err != nil {
+		return nil, err
+	}
+	if resp.IsSuccess() {
+		var vm vm.VMNetworkSerialized
+		if err := json.Unmarshal(resp.Body(), &vm); err != nil {
+			return nil, err
+		}
+		return &vm, nil
+	} else {
+		return nil, response.ErrorToMsg(resp)
+	}
+}
diff --git a/src/libclient/vm/vmExportDir.go b/src/libclient/vm/vmExportDir.go
new file mode 100644
index 0000000..adf0236
--- /dev/null
+++ b/src/libclient/vm/vmExportDir.go
@@ -0,0 +1,30 @@
+package vm
+
+import (
+	"errors"
+	u "nexus-client/utils"
+	"nexus-common/params"
+	g "nexus-libclient/globals"
+)
+
+func VMExportDir(vmName, vmID, dirToExport string) (string, error) {
+	client := g.GetInstance().Client
+	host := g.GetInstance().Host
+
+	p := &params.VMExportDir{Dir: dirToExport}
+
+	client.SetAllowGetMethodPayload(true)
+
+	outputFile := vmName + "_" + vmID + ".tar.gz"
+	resp, err := client.R().SetOutput(outputFile).SetBody(p).Get(host + "/vms/" + vmID + "/exportdir")
+	if err != nil {
+		// u.PrintlnErr("Failed exporting " + dir + " from VM \"" + vm.Name + "\": " + err.Error())
+		return "", err
+	}
+	if resp.IsSuccess() {
+		return outputFile, nil
+	} else {
+		errorMsg, _ := u.FileToString(outputFile)
+		return "", errors.New("Failed exporting " + dirToExport + " from VM \"" + vmName + "\": " + resp.Status() + ": " + errorMsg)
+	}
+}
diff --git a/src/libclient/vm/vmShutdown.go b/src/libclient/vm/vmShutdown.go
new file mode 100644
index 0000000..19dc4e6
--- /dev/null
+++ b/src/libclient/vm/vmShutdown.go
@@ -0,0 +1,21 @@
+package vm
+
+import (
+	g "nexus-libclient/globals"
+	"nexus-libclient/response"
+)
+
+func VMShutdown(vmID string) error {
+	client := g.GetInstance().Client
+	host := g.GetInstance().Host
+
+	resp, err := client.R().Put(host + "/vms/" + vmID + "/shutdown")
+	if err != nil {
+		return err
+	}
+	if resp.IsSuccess() {
+		return nil
+	} else {
+		return response.ErrorToMsg(resp)
+	}
+}
diff --git a/src/libclient/vm/vmStart.go b/src/libclient/vm/vmStart.go
new file mode 100644
index 0000000..a46f068
--- /dev/null
+++ b/src/libclient/vm/vmStart.go
@@ -0,0 +1,21 @@
+package vm
+
+import (
+	g "nexus-libclient/globals"
+	"nexus-libclient/response"
+)
+
+func VMStart(vmID string) error {
+	client := g.GetInstance().Client
+	host := g.GetInstance().Host
+
+	resp, err := client.R().Put(host + "/vms/" + vmID + "/start")
+	if err != nil {
+		return err
+	}
+	if resp.IsSuccess() {
+		return nil
+	} else {
+		return response.ErrorToMsg(resp)
+	}
+}
diff --git a/src/libclient/vm/vmStartWithCreds.go b/src/libclient/vm/vmStartWithCreds.go
new file mode 100644
index 0000000..4cef89e
--- /dev/null
+++ b/src/libclient/vm/vmStartWithCreds.go
@@ -0,0 +1,22 @@
+package vm
+
+import (
+	"nexus-common/params"
+	g "nexus-libclient/globals"
+	"nexus-libclient/response"
+)
+
+func VMStartWithCreds(vmID string, p params.VMStartWithCreds) error {
+	client := g.GetInstance().Client
+	host := g.GetInstance().Host
+
+	resp, err := client.R().SetBody(p).Put(host + "/vms/" + vmID + "/startwithcreds")
+	if err != nil {
+		return err
+	}
+	if resp.IsSuccess() {
+		return nil
+	} else {
+		return response.ErrorToMsg(resp)
+	}
+}
-- 
GitLab