From 5a1ba5937f28f8ee4002affb196e62b00df2edbc Mon Sep 17 00:00:00 2001
From: Florent Gluck <florent.gluck@hesge.ch>
Date: Mon, 18 Jul 2022 19:07:33 +0200
Subject: [PATCH] VMs can be deleted, started and stopped in bulk using regex
 expressions

---
 src/client_cli/cmdTemplate/helper.go       | 34 ++++----
 src/client_cli/cmdTemplate/templateList.go |  2 +-
 src/client_cli/cmdUser/helper.go           | 22 ++---
 src/client_cli/cmdUser/userList.go         |  2 +-
 src/client_cli/cmdVM/helper.go             | 94 ++++++++++++++--------
 src/client_cli/cmdVM/vmAttach.go           |  2 +-
 src/client_cli/cmdVM/vmCreate.go           | 14 ++--
 src/client_cli/cmdVM/vmDel.go              | 24 +++---
 src/client_cli/cmdVM/vmList.go             |  4 +-
 src/client_cli/cmdVM/vmListAttach.go       |  4 +-
 src/client_cli/cmdVM/vmListDel.go          |  4 +-
 src/client_cli/cmdVM/vmListEdit.go         |  4 +-
 src/client_cli/cmdVM/vmListEditAccess.go   |  4 +-
 src/client_cli/cmdVM/vmListStart.go        |  4 +-
 src/client_cli/cmdVM/vmListStop.go         |  4 +-
 src/client_cli/cmdVM/vmStart.go            | 37 ++++++---
 src/client_cli/cmdVM/vmStop.go             | 37 ++++++---
 17 files changed, 173 insertions(+), 123 deletions(-)

diff --git a/src/client_cli/cmdTemplate/helper.go b/src/client_cli/cmdTemplate/helper.go
index 1995b22..422db60 100644
--- a/src/client_cli/cmdTemplate/helper.go
+++ b/src/client_cli/cmdTemplate/helper.go
@@ -43,8 +43,9 @@ Regex examples:
 	u.PrintlnErr(usage)
 }
 
-// Call the given route and filter the results based on ID and regex.
-// Arguments (args) can either be:
+// Returns a list of filtered templates for a given route.
+// The filter is based on the specified IDs and regexes.
+// The filters argument can either be:
 // - any number of "template IDs" (UUID)
 // - any number of "template names"
 // - any combination of "template IDs" and "template names"
@@ -54,25 +55,24 @@ Regex examples:
 //   ""    -> matches everything
 //   "."   -> matches everything
 //   "bla" -> matches any template name containing "bla"
-func getFilteredTemplates(c cmd.Command, args []string, route string) int {
+func printFilteredTemplates(c cmd.Command, filters []string, route string) int {
 	client := g.GetInstance().Client
 	host := g.GetInstance().Host
 
-	argc := len(args)
-	if argc < 1 {
+	if len(filters) < 1 {
 		c.PrintUsage()
 		return 1
 	}
 
 	var ids []string
-	var namePatterns []string
+	var regexes []string
 
-	for _, arg := range args {
-		_, err := uuid.Parse(arg)
+	for _, filter := range filters {
+		_, err := uuid.Parse(filter)
 		if err != nil {
-			namePatterns = append(namePatterns, arg)
+			regexes = append(regexes, filter)
 		} else {
-			ids = append(ids, arg)
+			ids = append(ids, filter)
 		}
 	}
 
@@ -89,7 +89,7 @@ func getFilteredTemplates(c cmd.Command, args []string, route string) int {
 			return 1
 		}
 
-		for _, template := range *templates {
+		for _, template := range templates {
 			found := false
 			for _, id := range ids {
 				if id == template.ID.String() {
@@ -101,10 +101,10 @@ func getFilteredTemplates(c cmd.Command, args []string, route string) int {
 			if found {
 				continue
 			}
-			for _, namePattern := range namePatterns {
-				match, err := regexp.MatchString(strings.ToLower(namePattern), strings.ToLower(template.Name))
+			for _, regex := range regexes {
+				match, err := regexp.MatchString(strings.ToLower(regex), strings.ToLower(template.Name))
 				if err != nil {
-					u.PrintlnErr("Error matching \""+namePattern+"\": "+err.Error())
+					u.PrintlnErr("Error matching \""+regex+"\": "+err.Error())
 				} else {
 					if match {
 						u.Println(template.String())
@@ -120,10 +120,10 @@ func getFilteredTemplates(c cmd.Command, args []string, route string) int {
 	}
 }
 
-func getTemplates(resp *resty.Response) (*[]Template, error) {
-	var templates []Template
+func getTemplates(resp *resty.Response) ([]Template, error) {
+	templates := []Template{}
 	if err := json.Unmarshal(resp.Body(), &templates); err != nil {
 		return nil, err
 	}
-	return &templates, nil
+	return templates, nil
 }
\ No newline at end of file
diff --git a/src/client_cli/cmdTemplate/templateList.go b/src/client_cli/cmdTemplate/templateList.go
index a258e02..455afa3 100644
--- a/src/client_cli/cmdTemplate/templateList.go
+++ b/src/client_cli/cmdTemplate/templateList.go
@@ -17,5 +17,5 @@ func (cmd *List)PrintUsage() {
 }
 
 func (cmd *List)Run(args []string) int {
-	return getFilteredTemplates(cmd, args, "/templates")
+	return printFilteredTemplates(cmd, args, "/templates")
 }
diff --git a/src/client_cli/cmdUser/helper.go b/src/client_cli/cmdUser/helper.go
index ad36e0d..118b54d 100644
--- a/src/client_cli/cmdUser/helper.go
+++ b/src/client_cli/cmdUser/helper.go
@@ -45,18 +45,18 @@ Regex examples:
 	u.PrintlnErr(usage)
 }
 
-// Call the given route and filter the results based on a regex.
+// Prints a list of filtered users for a given route.
+// The filter is based on regexes.
 // Remark: the regex matches the user email, first name and last name and is case-insensitive.
 // Regular expression examples:
 //   ""    -> matches everything
 //   "."   -> matches everything
 //   "bla" -> matches any user containing "bla"
-func getFilteredUsers(c cmd.Command, args []string, route string) int {
+func printFilteredUsers(c cmd.Command, regexes []string, route string) int {
 	client := g.GetInstance().Client
 	host := g.GetInstance().Host
 
-	argc := len(args)
-	if argc < 1 {
+	if len(regexes) < 1 {
 		c.PrintUsage()
 		return 1
 	}
@@ -74,12 +74,12 @@ func getFilteredUsers(c cmd.Command, args []string, route string) int {
 			return 1
 		}
 
-		for _, user := range *users {
-			for _, pattern := range args {
+		for _, user := range users {
+			for _, regex := range regexes {
 				fieldsToMatch := user.Email+" "+user.FirstName+" "+user.LastName
-				match, err := regexp.MatchString(strings.ToLower(pattern), strings.ToLower(fieldsToMatch))
+				match, err := regexp.MatchString(strings.ToLower(regex), strings.ToLower(fieldsToMatch))
 				if err != nil {
-					u.PrintlnErr("Error matching \""+pattern+"\": "+err.Error())
+					u.PrintlnErr("Error matching \""+regex+"\": "+err.Error())
 				} else {
 					if match {
 						u.Println(user.String())
@@ -95,10 +95,10 @@ func getFilteredUsers(c cmd.Command, args []string, route string) int {
 	}
 }
 
-func getUsers(resp *resty.Response) (*[]User, error) {
-	var users []User
+func getUsers(resp *resty.Response) ([]User, error) {
+	users := []User{}
 	if err := json.Unmarshal(resp.Body(), &users); err != nil {
 		return nil, err
 	}
-	return &users, nil
+	return users, nil
 }
\ No newline at end of file
diff --git a/src/client_cli/cmdUser/userList.go b/src/client_cli/cmdUser/userList.go
index 8adbf62..55902fa 100644
--- a/src/client_cli/cmdUser/userList.go
+++ b/src/client_cli/cmdUser/userList.go
@@ -17,5 +17,5 @@ func (cmd *List)PrintUsage() {
 }
 
 func (cmd *List)Run(args []string) int {
-	return getFilteredUsers(cmd, args, "/users")
+	return printFilteredUsers(cmd, args, "/users")
 }
diff --git a/src/client_cli/cmdVM/helper.go b/src/client_cli/cmdVM/helper.go
index 06f6f06..48793cc 100644
--- a/src/client_cli/cmdVM/helper.go
+++ b/src/client_cli/cmdVM/helper.go
@@ -1,8 +1,9 @@
 package cmdVM
 
 import (
-	"strings"
 	"regexp"
+	"errors"
+	"strings"
 	"encoding/json"
 	"nexus-client/cmd"
 	u "nexus-client/utils"
@@ -51,11 +52,11 @@ func (vm *VM)String() string {
 	return string(output)
 }
 
-func printUsage(c cmd.Command) {
+func printUsage(c cmd.Command, action string) {
 	u.PrintlnErr(c.GetDesc())
 	u.PrintlnErr("Usage: ",c.GetName()," [ID ...] [regex ...]")
-	const usage string = `Only VMs matching the specified IDs or regexes will be listed.
-Any number of IDs or regexes can be specified.
+	u.PrintlnErr("Only VMs matching the specified IDs or regexes will be "+action+".")
+	const usage string = `Any number of IDs or regexes can be specified.
 The regex only matches the VM's name and is case-insensitive.
 Regex examples:
     ""    -> matches any VMs
@@ -64,8 +65,30 @@ Regex examples:
 	u.PrintlnErr(usage)
 }
 
-// Call the given route and filter the results based on ID and regex.
-// Arguments (args) can either be:
+// Prints a list of filtered VMs for a given route.
+// Return 0 if everything went well or 1 in case of failure.
+func printFilteredVMs(c cmd.Command, args []string, route string) int {
+	if len(args) < 1 {
+		c.PrintUsage()
+		return 1
+	}
+
+	vms, err := getFilteredVMs(route, args)
+	if err != nil {
+		u.PrintlnErr("Error: "+err.Error())
+		return 1
+	}
+
+	for _, vm := range vms {
+		u.Println(vm.String())
+	}
+
+	return 0
+}
+
+// Returns a list of filtered VMs for a given route.
+// The filter is based on the specified IDs and regexes.
+// The filters argument can either be:
 // - any number of "VM IDs" (UUID)
 // - any number of "VM names"
 // - any combination of "VM IDs" and "VM names"
@@ -75,46 +98,45 @@ Regex examples:
 //   ""    -> matches everything
 //   "."   -> matches everything
 //   "bla" -> matches any VM name containing "bla"
-func getFilteredVMs(c cmd.Command, args []string, route string) int {
+func getFilteredVMs(route string, filters []string) ([]VM, error) {
 	client := g.GetInstance().Client
 	host := g.GetInstance().Host
 
-	argc := len(args)
-	if argc < 1 {
-		c.PrintUsage()
-		return 1
-	}
-
 	var ids []string
-	var namePatterns []string
+	var regexes []string
 
-	for _, arg := range args {
-		_, err := uuid.Parse(arg)
+	for _, filter := range filters {
+		_, err := uuid.Parse(filter)
 		if err != nil {
-			namePatterns = append(namePatterns, arg)
+			regexes = append(regexes, filter)
 		} else {
-			ids = append(ids, arg)
+			ids = append(ids, filter)
 		}
 	}
 
 	resp, err := client.R().Get(host+route)
 	if err != nil {
-		u.PrintlnErr("Error: "+err.Error())
-		return 1
+		//u.PrintlnErr("Error: "+err.Error())
+		//return 1
+		return nil, err
 	}
 
+	vmsList := []VM{}
+
 	if resp.IsSuccess() {
 		vms, err := getVMs(resp)
 		if err != nil {
-			u.PrintlnErr("Error: "+err.Error())
-			return 1
+			//u.PrintlnErr("Error: "+err.Error())
+			//return 1
+			return nil, err
 		}
 
-		for _, vm := range *vms {
+		for _, vm := range vms {
 			found := false
 			for _, id := range ids {
 				if id == vm.ID.String() {
-					u.Println(vm.String())
+					vmsList = append(vmsList, vm)
+					//u.Println(vm.String())
 					found = true
 					break
 				}
@@ -122,29 +144,33 @@ func getFilteredVMs(c cmd.Command, args []string, route string) int {
 			if found {
 				continue
 			}
-			for _, namePattern := range namePatterns {
-				match, err := regexp.MatchString(strings.ToLower(namePattern), strings.ToLower(vm.Name))
+			for _, regex := range regexes {
+				match, err := regexp.MatchString(strings.ToLower(regex), strings.ToLower(vm.Name))
 				if err != nil {
-					u.PrintlnErr("Error matching \""+namePattern+"\": "+err.Error())
+					//u.PrintlnErr("Error matching \""+regex+"\": "+err.Error())
+					return nil, errors.New("Error matching \""+regex+"\": "+err.Error())
 				} else {
 					if match {
-						u.Println(vm.String())
+						//u.Println(vm.String())
+						vmsList = append(vmsList, vm)
 						break
 					}
 				}
 			}
 		}
-		return 0
+		return vmsList, nil
 	} else {
-		u.PrintlnErr("Error: "+resp.Status()+": "+resp.String())
-		return 1
+		return nil, errors.New("Error: "+resp.Status()+": "+resp.String())
+		//u.PrintlnErr("Error: "+resp.Status()+": "+resp.String())
+		//return 1
 	}
 }
 
-func getVMs(resp *resty.Response) (*[]VM, error) {
-	var vms []VM
+// Retrieves all VMs (no filtering).
+func getVMs(resp *resty.Response) ([]VM, error) {
+	vms := []VM{}
 	if err := json.Unmarshal(resp.Body(), &vms); err != nil {
 		return nil, err
 	}
-	return &vms, nil
+	return vms, nil
 }
\ No newline at end of file
diff --git a/src/client_cli/cmdVM/vmAttach.go b/src/client_cli/cmdVM/vmAttach.go
index f2e7039..d9c7708 100644
--- a/src/client_cli/cmdVM/vmAttach.go
+++ b/src/client_cli/cmdVM/vmAttach.go
@@ -21,7 +21,7 @@ func (cmd *Attach)GetName() string {
 }
  
 func (cmd *Attach)GetDesc() string {
-	return "Attach to a VM in order to see its desktop"
+	return "Attach to a VM in order to use its desktop environment"
 }
 
 func (cmd *Attach)PrintUsage() {
diff --git a/src/client_cli/cmdVM/vmCreate.go b/src/client_cli/cmdVM/vmCreate.go
index 851b698..203aa38 100644
--- a/src/client_cli/cmdVM/vmCreate.go
+++ b/src/client_cli/cmdVM/vmCreate.go
@@ -84,7 +84,7 @@ func (cmd *Create)Run(args []string) int {
 		TemplateID: template,
 	}
 
-	errors := false
+	statusCode := 0
 
 	for i := 1; i <= count; i++ {
 		if count > 1 {
@@ -94,20 +94,16 @@ func (cmd *Create)Run(args []string) int {
 		resp, err := client.R().SetBody(vmArgs).Post(host+"/vms")
 		if err != nil {
 			u.PrintlnErr("Failed creating VM \""+vmArgs.Name+"\": "+err.Error())
-			errors = true
+			statusCode = 1
 		} else {
 			if resp.IsSuccess() {
-				u.Println("Successfully created VM \""+vmArgs.Name+"\"")
+				u.Println("Created VM \""+vmArgs.Name+"\"")
 			} else {
 				u.PrintlnErr("Failed creating VM \""+vmArgs.Name+"\": "+resp.Status()+": "+resp.String())
-				errors = true
+				statusCode = 1
 			}
 		}
 	}
 
-	if errors {
-		return 1
-	} else {
-		return 0
-	}
+	return statusCode
 }
diff --git a/src/client_cli/cmdVM/vmDel.go b/src/client_cli/cmdVM/vmDel.go
index c9eedcf..ae4ba72 100644
--- a/src/client_cli/cmdVM/vmDel.go
+++ b/src/client_cli/cmdVM/vmDel.go
@@ -14,12 +14,11 @@ func (cmd *Del)GetName() string {
 }
  
 func (cmd *Del)GetDesc() string {
-	return "Delete one or more VMs"
+	return "Delete one or more VMs."
 }
 
 func (cmd *Del)PrintUsage() {
-	u.PrintlnErr(cmd.GetDesc())
-	u.PrintlnErr("Usage: "+cmd.Name+" vmID [vmID ...]")
+	printUsage(cmd, "deleted")
 }
 
 func (cmd *Del)Run(args []string) int {
@@ -32,23 +31,30 @@ func (cmd *Del)Run(args []string) int {
 		return 1
 	}
 
+	vms, err := getFilteredVMs("/vms/del", args)
+	if err != nil {
+		u.PrintlnErr(err.Error())
+		return 1
+	}
+
 	statusCode := 0
 
-	// Iterates through each uuid to delete
-	for i:= range(args) {
-		uuid := args[i]
+	for _, vm := range(vms) {
+		uuid := vm.ID.String()
 		resp, err := client.R().Delete(host+"/vms/"+uuid)
 		if err != nil {
-			u.PrintlnErr("Error: "+err.Error())
+			u.PrintlnErr("Failed deleting VM \""+vm.Name+"\" ("+uuid+"): "+err.Error())
 			statusCode = 1
 		} else {
 			if resp.IsSuccess() {
-				u.Println("VM "+uuid+" deleted.")
+				u.Println("Deleted VM \""+vm.Name+"\" ("+uuid+")")
 			} else {
-				u.PrintlnErr("Error: "+resp.Status()+": "+resp.String())
+				u.PrintlnErr("Failed deleting VM \""+vm.Name+"\" ("+uuid+"): "+resp.Status()+": "+resp.String())
 				statusCode = 1
 			}
 		}
+
 	}
+
 	return statusCode
 }
diff --git a/src/client_cli/cmdVM/vmList.go b/src/client_cli/cmdVM/vmList.go
index c069011..844604b 100644
--- a/src/client_cli/cmdVM/vmList.go
+++ b/src/client_cli/cmdVM/vmList.go
@@ -13,9 +13,9 @@ func (cmd *List)GetDesc() string {
 }
 
 func (cmd *List)PrintUsage() {
-	printUsage(cmd)
+	printUsage(cmd, "listed")
 }
 
 func (cmd *List)Run(args []string) int {
-	return getFilteredVMs(cmd, args, "/vms")
+	return printFilteredVMs(cmd, args, "/vms")
 }
diff --git a/src/client_cli/cmdVM/vmListAttach.go b/src/client_cli/cmdVM/vmListAttach.go
index c10ccfc..e8fb3e9 100644
--- a/src/client_cli/cmdVM/vmListAttach.go
+++ b/src/client_cli/cmdVM/vmListAttach.go
@@ -13,9 +13,9 @@ func (cmd *ListAttach)GetDesc() string {
 }
 
 func (cmd *ListAttach)PrintUsage() {
-	printUsage(cmd)
+	printUsage(cmd, "listed")
 }
 
 func (cmd *ListAttach)Run(args []string) int {
-	return getFilteredVMs(cmd, args, "/vms/attach")
+	return printFilteredVMs(cmd, args, "/vms/attach")
 }
diff --git a/src/client_cli/cmdVM/vmListDel.go b/src/client_cli/cmdVM/vmListDel.go
index 7701bac..8646047 100644
--- a/src/client_cli/cmdVM/vmListDel.go
+++ b/src/client_cli/cmdVM/vmListDel.go
@@ -13,9 +13,9 @@ func (cmd *ListDel)GetDesc() string {
 }
 
 func (cmd *ListDel)PrintUsage() {
-	printUsage(cmd)
+	printUsage(cmd, "listed")
 }
 
 func (cmd *ListDel)Run(args []string) int {
-	return getFilteredVMs(cmd, args, "/vms/del")
+	return printFilteredVMs(cmd, args, "/vms/del")
 }
diff --git a/src/client_cli/cmdVM/vmListEdit.go b/src/client_cli/cmdVM/vmListEdit.go
index 47b3356..ca02246 100644
--- a/src/client_cli/cmdVM/vmListEdit.go
+++ b/src/client_cli/cmdVM/vmListEdit.go
@@ -13,9 +13,9 @@ func (cmd *ListEdit)GetDesc() string {
 }
 
 func (cmd *ListEdit)PrintUsage() {
-	printUsage(cmd)
+	printUsage(cmd, "listed")
 }
 
 func (cmd *ListEdit)Run(args []string) int {
-	return getFilteredVMs(cmd, args, "/vms/edit")
+	return printFilteredVMs(cmd, args, "/vms/edit")
 }
diff --git a/src/client_cli/cmdVM/vmListEditAccess.go b/src/client_cli/cmdVM/vmListEditAccess.go
index d8fa17d..2d97891 100644
--- a/src/client_cli/cmdVM/vmListEditAccess.go
+++ b/src/client_cli/cmdVM/vmListEditAccess.go
@@ -13,9 +13,9 @@ func (cmd *ListEditAccess)GetDesc() string {
 }
 
 func (cmd *ListEditAccess)PrintUsage() {
-	printUsage(cmd)
+	printUsage(cmd, "listed")
 }
 
 func (cmd *ListEditAccess)Run(args []string) int {
-	return getFilteredVMs(cmd, args, "/vms/editaccess")
+	return printFilteredVMs(cmd, args, "/vms/editaccess")
 }
diff --git a/src/client_cli/cmdVM/vmListStart.go b/src/client_cli/cmdVM/vmListStart.go
index 66a486b..70a7daa 100644
--- a/src/client_cli/cmdVM/vmListStart.go
+++ b/src/client_cli/cmdVM/vmListStart.go
@@ -13,9 +13,9 @@ func (cmd *ListStart)GetDesc() string {
 }
 
 func (cmd *ListStart)PrintUsage() {
-	printUsage(cmd)
+	printUsage(cmd, "listed")
 }
 
 func (cmd *ListStart)Run(args []string) int {
-	return getFilteredVMs(cmd, args, "/vms/start")
+	return printFilteredVMs(cmd, args, "/vms/start")
 }
diff --git a/src/client_cli/cmdVM/vmListStop.go b/src/client_cli/cmdVM/vmListStop.go
index e7e8a2e..19b473d 100644
--- a/src/client_cli/cmdVM/vmListStop.go
+++ b/src/client_cli/cmdVM/vmListStop.go
@@ -13,9 +13,9 @@ func (cmd *ListStop)GetDesc() string {
 }
 
 func (cmd *ListStop)PrintUsage() {
-	printUsage(cmd)
+	printUsage(cmd, "listed")
 }
 
 func (cmd *ListStop)Run(args []string) int {
-	return getFilteredVMs(cmd, args, "/vms/stop")
+	return printFilteredVMs(cmd, args, "/vms/stop")
 }
diff --git a/src/client_cli/cmdVM/vmStart.go b/src/client_cli/cmdVM/vmStart.go
index 9c03729..49f751e 100644
--- a/src/client_cli/cmdVM/vmStart.go
+++ b/src/client_cli/cmdVM/vmStart.go
@@ -14,12 +14,11 @@ func (cmd *Start)GetName() string {
 }
  
 func (cmd *Start)GetDesc() string {
-	return "Start a VM"
+	return "Start one or more VMs."
 }
 
 func (cmd *Start)PrintUsage() {
-	u.PrintlnErr(cmd.GetDesc())
-	u.PrintlnErr("Usage: "+cmd.Name+" vmID")
+	printUsage(cmd, "started")
 }
 
 func (cmd *Start)Run(args []string) int {
@@ -27,23 +26,35 @@ func (cmd *Start)Run(args []string) int {
 	host := g.GetInstance().Host
 
 	argc := len(args)
-	if argc != 1 {
+	if argc < 1 {
 		cmd.PrintUsage()
 		return 1
 	}
 
-	uuid := args[0]
-	resp, err := client.R().Put(host+"/vms/"+uuid+"/start")
+	vms, err := getFilteredVMs("/vms/start", args)
 	if err != nil {
-		u.PrintlnErr("Error: "+err.Error())
+		u.PrintlnErr(err.Error())
 		return 1
 	}
 
-	if resp.IsSuccess() {
-		u.Println(resp)
-		return 0
-	} else {
-		u.PrintlnErr("Error: "+resp.Status()+": "+resp.String())
-		return 1
+	statusCode := 0
+
+	for _, vm := range(vms) {
+		uuid := vm.ID.String()
+		resp, err := client.R().Put(host+"/vms/"+uuid+"/start")
+		if err != nil {
+			u.PrintlnErr("Failed starting VM \""+vm.Name+"\" ("+uuid+"): "+err.Error())
+			statusCode = 1
+		} else {
+			if resp.IsSuccess() {
+				u.Println("Started VM \""+vm.Name+"\" ("+uuid+")")
+			} else {
+				u.PrintlnErr("Failed starting VM \""+vm.Name+"\" ("+uuid+"): "+resp.Status()+": "+resp.String())
+				statusCode = 1
+			}
+		}
+
 	}
+
+	return statusCode
 }
diff --git a/src/client_cli/cmdVM/vmStop.go b/src/client_cli/cmdVM/vmStop.go
index e15411a..072cb80 100644
--- a/src/client_cli/cmdVM/vmStop.go
+++ b/src/client_cli/cmdVM/vmStop.go
@@ -14,12 +14,11 @@ func (cmd *Stop)GetName() string {
 }
  
 func (cmd *Stop)GetDesc() string {
-	return "Stop a VM"
+	return "Stop one or more VMs."
 }
 
 func (cmd *Stop)PrintUsage() {
-	u.PrintlnErr(cmd.GetDesc())
-	u.PrintlnErr("Usage: "+cmd.Name+" vmID")
+	printUsage(cmd, "stopped")
 }
 
 func (cmd *Stop)Run(args []string) int {
@@ -27,23 +26,35 @@ func (cmd *Stop)Run(args []string) int {
 	host := g.GetInstance().Host
 
 	argc := len(args)
-	if argc != 1 {
+	if argc < 1 {
 		cmd.PrintUsage()
 		return 1
 	}
 
-	uuid := args[0]
-	resp, err := client.R().Put(host+"/vms/"+uuid+"/stop")
+	vms, err := getFilteredVMs("/vms/stop", args)
 	if err != nil {
-		u.PrintlnErr("Error: "+err.Error())
+		u.PrintlnErr(err.Error())
 		return 1
 	}
 
-	if resp.IsSuccess() {
-		u.Println(resp)
-		return 0
-	} else {
-		u.PrintlnErr("Error: "+resp.Status()+": "+resp.String())
-		return 1
+	statusCode := 0
+
+	for _, vm := range(vms) {
+		uuid := vm.ID.String()
+		resp, err := client.R().Put(host+"/vms/"+uuid+"/stop")
+		if err != nil {
+			u.PrintlnErr("Failed stopping VM \""+vm.Name+"\" ("+uuid+"): "+err.Error())
+			statusCode = 1
+		} else {
+			if resp.IsSuccess() {
+				u.Println("Stopped VM \""+vm.Name+"\" ("+uuid+")")
+			} else {
+				u.PrintlnErr("Failed stopping VM \""+vm.Name+"\" ("+uuid+"): "+resp.Status()+": "+resp.String())
+				statusCode = 1
+			}
+		}
+
 	}
+
+	return statusCode
 }
-- 
GitLab