diff --git a/cli/cobra.go b/cli/cobra.go new file mode 100644 index 00000000..a45b7a08 --- /dev/null +++ b/cli/cobra.go @@ -0,0 +1,94 @@ +package cli + +import ( + "crypto/tls" + "fmt" + "io/ioutil" + "os" + "regexp" + + "github.com/Telmate/proxmox-api-go/proxmox" + "github.com/spf13/cobra" +) + +// Global else the nested folders dont work +var RootCmd = &cobra.Command{ + Use: "proxmox-api-go", + Short: "Application to configure Proxmox from the Api", +} + +func init() { + RootCmd.PersistentFlags().BoolP("insecure", "i", false, "TLS insecure mode") + RootCmd.PersistentFlags().BoolP("debug", "d", false, "debug mode") + RootCmd.PersistentFlags().IntP("timeout", "t", 300, "api task timeout in seconds") + RootCmd.PersistentFlags().StringP("file", "f", "", "file to get the config from") + RootCmd.PersistentFlags().StringP("proxyurl", "p", "", "proxy url to connect to") +} + +func Execute() (err error) { + if err = RootCmd.Execute(); err != nil { + return + } + return +} + +func NewClient()(c *proxmox.Client) { + c, err := Client("","","","") + LogFatalError(err) + return +} + +func Client(apiUlr, userID, password, otp string) (c *proxmox.Client, err error) { + insecure, _ := RootCmd.Flags().GetBool("insecure") + timeout, _ := RootCmd.Flags().GetInt("timeout") + proxyUrl, _ := RootCmd.Flags().GetString("proxyurl") + + tlsconf := &tls.Config{InsecureSkipVerify: true} + if !insecure { + tlsconf = nil + } + if apiUlr == "" { + apiUlr = os.Getenv("PM_API_URL") + } + if userID == "" { + userID = os.Getenv("PM_USER") + } + if password == "" { + password = os.Getenv("PM_PASS") + } + if otp == "" { + otp = os.Getenv("PM_OTP") + } + c, err = proxmox.NewClient(apiUlr, nil, tlsconf, proxyUrl, timeout) + LogFatalError(err) + if userRequiresAPIToken(userID) { + c.SetAPIToken(userID, password) + // As test, get the version of the server + _, err = c.GetVersion() + if err != nil { + err = fmt.Errorf("login error: %s", err) + } + } else { + err = c.Login(userID, password, otp) + } + return +} + +var rxUserRequiresToken = regexp.MustCompile("[a-z0-9]+@[a-z0-9]+![a-z0-9]+") + +func userRequiresAPIToken(userID string) bool { + return rxUserRequiresToken.MatchString(userID) +} + +func NewConfig()(configSource []byte) { + var err error + file, _ := RootCmd.Flags().GetString("file") + if file != "" { + configSource, err = ioutil.ReadFile(file) + LogFatalError(err) + } else { + configSource, err = ioutil.ReadAll(RootCmd.InOrStdin()) + LogFatalError(err) + } + return +} \ No newline at end of file diff --git a/cli/command/commands/commands.go b/cli/command/commands/commands.go new file mode 100644 index 00000000..1c563367 --- /dev/null +++ b/cli/command/commands/commands.go @@ -0,0 +1,11 @@ +package commands + +import ( + _ "github.com/Telmate/proxmox-api-go/cli/command/create" + _ "github.com/Telmate/proxmox-api-go/cli/command/delete" + _ "github.com/Telmate/proxmox-api-go/cli/command/example" + _ "github.com/Telmate/proxmox-api-go/cli/command/get" + _ "github.com/Telmate/proxmox-api-go/cli/command/list" + _ "github.com/Telmate/proxmox-api-go/cli/command/set" + _ "github.com/Telmate/proxmox-api-go/cli/command/update" +) \ No newline at end of file diff --git a/cli/command/create/create-pool.go b/cli/command/create/create-pool.go new file mode 100644 index 00000000..97a20070 --- /dev/null +++ b/cli/command/create/create-pool.go @@ -0,0 +1,29 @@ +package create + +import ( + "github.com/Telmate/proxmox-api-go/cli" + "github.com/spf13/cobra" +) + +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") + var comment string + if len(args) > 1 { + comment = args[1] + } + c := cli.NewClient() + err = c.CreatePool(id, comment) + if err != nil { + return + } + cli.PrintItemCreated(createCmd.OutOrStdout() ,id, "Pool") + return + }, +} + +func init() { + createCmd.AddCommand(create_poolCmd) +} diff --git a/cli/command/create/create.go b/cli/command/create/create.go new file mode 100644 index 00000000..a98a6eb9 --- /dev/null +++ b/cli/command/create/create.go @@ -0,0 +1,15 @@ +package create + +import ( + "github.com/Telmate/proxmox-api-go/cli" + "github.com/spf13/cobra" +) + +var createCmd = &cobra.Command{ + Use: "create", + Short: "With this command you can create new items in proxmox", +} + +func init() { + cli.RootCmd.AddCommand(createCmd) +} diff --git a/cli/command/delete/delete-metricserver.go b/cli/command/delete/delete-metricserver.go new file mode 100644 index 00000000..1163b4a0 --- /dev/null +++ b/cli/command/delete/delete-metricserver.go @@ -0,0 +1,17 @@ +package delete + +import ( + "github.com/spf13/cobra" +) + +var delete_metricserverCmd = &cobra.Command{ + Use: "metricserver METRICSID", + Short: "Deletes the speciefied MetricServer", + RunE: func(cmd *cobra.Command, args []string) error { + return DeleteID(args, "MetricServer") + }, +} + +func init() { + deleteCmd.AddCommand(delete_metricserverCmd) +} diff --git a/cli/command/delete/delete-pool.go b/cli/command/delete/delete-pool.go new file mode 100644 index 00000000..69de767d --- /dev/null +++ b/cli/command/delete/delete-pool.go @@ -0,0 +1,18 @@ +package delete + +import ( + "github.com/spf13/cobra" +) + +var delete_poolCmd = &cobra.Command{ + Use: "pool POOLID", + Short: "Deletes the Speciefied pool", + RunE: func(cmd *cobra.Command, args []string) (err error) { + err = DeleteID(args, "Pool") + return + }, +} + +func init() { + deleteCmd.AddCommand(delete_poolCmd) +} diff --git a/cli/command/delete/delete-user.go b/cli/command/delete/delete-user.go new file mode 100644 index 00000000..57a70e5e --- /dev/null +++ b/cli/command/delete/delete-user.go @@ -0,0 +1,18 @@ +package delete + +import ( + "github.com/spf13/cobra" +) + +var delete_userCmd = &cobra.Command{ + Use: "user USERID", + Short: "Deletes the speciefied User", + RunE: func(cmd *cobra.Command, args []string) (err error) { + err = DeleteID(args, "User") + return + }, +} + +func init() { + deleteCmd.AddCommand(delete_userCmd) +} diff --git a/cli/command/delete/delete.go b/cli/command/delete/delete.go new file mode 100644 index 00000000..87bb3674 --- /dev/null +++ b/cli/command/delete/delete.go @@ -0,0 +1,33 @@ +package delete + +import ( + "github.com/Telmate/proxmox-api-go/cli" + "github.com/spf13/cobra" +) + +var deleteCmd = &cobra.Command{ + Use: "delete", + Short: "With this command you can delete existing items from proxmox", +} + +func init() { + cli.RootCmd.AddCommand(deleteCmd) +} + +func DeleteID(args []string, IDtype string) (err error){ + id := cli.ValidateIDset(args, 0, IDtype+"ID") + c := cli.NewClient() + switch IDtype { + case "MetricServer" : + err = c.DeleteMetricServer(id) + case "Pool" : + err = c.DeletePool(id) + case "User" : + err = c.DeleteUser(id) + } + if err != nil { + return + } + cli.PrintItemDeleted(deleteCmd.OutOrStdout(), id, IDtype) + return +} \ No newline at end of file diff --git a/cli/command/example/example.go b/cli/command/example/example.go new file mode 100644 index 00000000..6fd9a7d4 --- /dev/null +++ b/cli/command/example/example.go @@ -0,0 +1,15 @@ +package example + +import ( + "github.com/Telmate/proxmox-api-go/cli" + "github.com/spf13/cobra" +) + +var exampleCmd = &cobra.Command{ + Use: "example", + Short: "This function show examples of fully populated config files", +} + +func init() { + cli.RootCmd.AddCommand(exampleCmd) +} diff --git a/cli/command/get/get-pool.go b/cli/command/get/get-pool.go new file mode 100644 index 00000000..59ae68ce --- /dev/null +++ b/cli/command/get/get-pool.go @@ -0,0 +1,31 @@ +package get + +import ( + "encoding/json" + "fmt" + "github.com/Telmate/proxmox-api-go/cli" + "github.com/spf13/cobra" +) + +var get_poolCmd = &cobra.Command{ + Use: "pool POOLID", + Short: "Gets the configuration of the specified Pool", + RunE: func(cmd *cobra.Command, args []string) (err error) { + id := cli.ValidateIDset(args, 0, "PoolID") + c := cli.NewClient() + poolinfo, err := c.GetPoolInfo(id) + if err != nil { + return + } + poolList, err := json.Marshal(poolinfo) + if err != nil { + return + } + fmt.Fprintln(getCmd.OutOrStdout(),string(poolList)) + return + }, +} + +func init() { + getCmd.AddCommand(get_poolCmd) +} diff --git a/cli/command/get/get-user.go b/cli/command/get/get-user.go new file mode 100644 index 00000000..77800232 --- /dev/null +++ b/cli/command/get/get-user.go @@ -0,0 +1,17 @@ +package get + +import ( + "github.com/spf13/cobra" +) + +var get_userCmd = &cobra.Command{ + Use: "user USERID", + Short: "Gets the configuration of the specified User", + RunE: func(cmd *cobra.Command, args []string) error { + return GetConfig(args, "User") + }, +} + +func init() { + getCmd.AddCommand(get_userCmd) +} diff --git a/cli/command/get/get.go b/cli/command/get/get.go new file mode 100644 index 00000000..e4269f3b --- /dev/null +++ b/cli/command/get/get.go @@ -0,0 +1,31 @@ +package get + +import ( + "github.com/Telmate/proxmox-api-go/proxmox" + "github.com/Telmate/proxmox-api-go/cli" + "github.com/spf13/cobra" +) + +var getCmd = &cobra.Command{ + Use: "get", + Short: "get shows the current configuration an item in proxmox", +} + +func init() { + cli.RootCmd.AddCommand(getCmd) +} + +func GetConfig(args []string, IDtype string) (err error) { + id := cli.ValidateIDset(args, 0, IDtype+"ID") + c := cli.NewClient() + var config interface{} + switch IDtype { + case "User" : + config, err = proxmox.NewConfigUserFromApi(id, c) + } + if err != nil { + return + } + cli.PrintFormattedJson(getCmd.OutOrStdout(),config) + return +} diff --git a/cli/command/list/list-acmeaccounts.go b/cli/command/list/list-acmeaccounts.go new file mode 100644 index 00000000..e90cb893 --- /dev/null +++ b/cli/command/list/list-acmeaccounts.go @@ -0,0 +1,18 @@ +package list + +import ( + "github.com/spf13/cobra" +) + +var list_acmeaccountsCmd = &cobra.Command{ + Use: "acmeaccounts", + Short: "Prints a list of AcmeAccounts in raw json format", + Run: func(cmd *cobra.Command, args []string) { + ListRaw("AcmeAccounts") + }, +} + + +func init() { + listCmd.AddCommand(list_acmeaccountsCmd) +} diff --git a/cli/command/list/list-acmeplugins.go b/cli/command/list/list-acmeplugins.go new file mode 100644 index 00000000..36ed89fe --- /dev/null +++ b/cli/command/list/list-acmeplugins.go @@ -0,0 +1,18 @@ +package list + +import ( + "github.com/spf13/cobra" +) + +var list_acmepluginsCmd = &cobra.Command{ + Use: "acmeplugins", + Short: "Prints a list of AcmePlugins in raw json format", + Run: func(cmd *cobra.Command, args []string) { + ListRaw("AcmePlugins") + }, +} + + +func init() { + listCmd.AddCommand(list_acmepluginsCmd) +} diff --git a/cli/command/list/list-guests.go b/cli/command/list/list-guests.go new file mode 100644 index 00000000..9c1be139 --- /dev/null +++ b/cli/command/list/list-guests.go @@ -0,0 +1,17 @@ +package list + +import ( + "github.com/spf13/cobra" +) + +var list_qemuguestsCmd = &cobra.Command{ + Use: "guests", + Short: "Prints a list of Qemu/Lxc Guests in raw json format", + Run: func(cmd *cobra.Command, args []string) { + ListRaw("Guests") + }, +} + +func init() { + listCmd.AddCommand(list_qemuguestsCmd) +} diff --git a/cli/command/list/list-metricservers.go b/cli/command/list/list-metricservers.go new file mode 100644 index 00000000..5a1af818 --- /dev/null +++ b/cli/command/list/list-metricservers.go @@ -0,0 +1,18 @@ +package list + +import ( + "github.com/spf13/cobra" +) + +var list_metricserversCmd = &cobra.Command{ + Use: "metricservers", + Short: "Prints a list of MetricServers in raw json format", + Run: func(cmd *cobra.Command, args []string) { + ListRaw("MetricServers") + }, +} + + +func init() { + listCmd.AddCommand(list_metricserversCmd) +} diff --git a/cli/command/list/list-nodes.go b/cli/command/list/list-nodes.go new file mode 100644 index 00000000..03f8346d --- /dev/null +++ b/cli/command/list/list-nodes.go @@ -0,0 +1,17 @@ +package list + +import ( + "github.com/spf13/cobra" +) + +var list_nodesCmd = &cobra.Command{ + Use: "nodes", + Short: "Prints a list of Nodes in raw json format", + Run: func(cmd *cobra.Command, args []string) { + ListRaw("Nodes") + }, +} + +func init() { + listCmd.AddCommand(list_nodesCmd) +} diff --git a/cli/command/list/list-pools.go b/cli/command/list/list-pools.go new file mode 100644 index 00000000..c4f58568 --- /dev/null +++ b/cli/command/list/list-pools.go @@ -0,0 +1,18 @@ +package list + +import ( + "github.com/spf13/cobra" +) + +var list_poolsCmd = &cobra.Command{ + Use: "pools", + Short: "Prints a list of Pools in raw json format", + Run: func(cmd *cobra.Command, args []string) { + ListRaw("Pools") + }, +} + + +func init() { + listCmd.AddCommand(list_poolsCmd) +} diff --git a/cli/command/list/list-snapshots.go b/cli/command/list/list-snapshots.go new file mode 100644 index 00000000..2c2698ae --- /dev/null +++ b/cli/command/list/list-snapshots.go @@ -0,0 +1,37 @@ +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) + } + } + } + }, +} + +func init() { + listCmd.AddCommand(list_snapshotsCmd) +} diff --git a/cli/command/list/list-storages.go b/cli/command/list/list-storages.go new file mode 100644 index 00000000..116c575c --- /dev/null +++ b/cli/command/list/list-storages.go @@ -0,0 +1,18 @@ +package list + +import ( + "github.com/spf13/cobra" +) + +var list_storagesCmd = &cobra.Command{ + Use: "storages", + Short: "Prints a list of Storages in raw json format", + Run: func(cmd *cobra.Command, args []string) { + ListRaw("Storages") + }, +} + + +func init() { + listCmd.AddCommand(list_storagesCmd) +} diff --git a/cli/command/list/list-users.go b/cli/command/list/list-users.go new file mode 100644 index 00000000..b0d21e05 --- /dev/null +++ b/cli/command/list/list-users.go @@ -0,0 +1,17 @@ +package list + +import ( + "github.com/spf13/cobra" +) + +var list_usersCmd = &cobra.Command{ + Use: "users", + Short: "Prints a list of Users in raw json format", + Run: func(cmd *cobra.Command, args []string) { + ListRaw("Users") + }, +} + +func init() { + listCmd.AddCommand(list_usersCmd) +} diff --git a/cli/command/list/list.go b/cli/command/list/list.go new file mode 100644 index 00000000..6264eef4 --- /dev/null +++ b/cli/command/list/list.go @@ -0,0 +1,41 @@ +package list + +import ( + "github.com/Telmate/proxmox-api-go/cli" + "github.com/spf13/cobra" +) + +var listCmd = &cobra.Command{ + Use: "list", + Short: "Lists all items of the same kind from proxmox", +} + +func init() { + cli.RootCmd.AddCommand(listCmd) +} + +func ListRaw(IDtype string){ + c := cli.NewClient() + var list map[string]interface{} + var err error + switch IDtype { + case "AcmeAccounts": + list, err = c.GetAcmeAccountList() + case "AcmePlugins": + list, err = c.GetAcmePluginList() + case "Guests": + list, err = c.GetVmList() + case "MetricServers": + list, err = c.GetMetricsServerList() + case "Nodes": + list, err = c.GetNodeList() + case "Pools": + list, err = c.GetPoolList() + case "Storages": + list, err = c.GetStorageList() + case "Users": + list, err = c.GetUserList() + } + cli.LogFatalListing(IDtype, err) + cli.PrintRawJson(listCmd.OutOrStdout(),list) +} \ No newline at end of file diff --git a/cli/command/set/set-user.go b/cli/command/set/set-user.go new file mode 100644 index 00000000..155003db --- /dev/null +++ b/cli/command/set/set-user.go @@ -0,0 +1,38 @@ +package set + +import ( + "github.com/Telmate/proxmox-api-go/cli" + "github.com/Telmate/proxmox-api-go/proxmox" + "github.com/spf13/cobra" +) + +var set_userCmd = &cobra.Command{ + Use: "user USERID PASSWORD", + Short: "Sets the current state of a user", + Long: `Sets the current state of a user. +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") + config, err := proxmox.NewConfigUserFromJson(cli.NewConfig()) + if err != nil { + return + } + var password string + if len(args) > 1 { + password = args[1] + } + c := cli.NewClient() + err = config.SetUser(id, password, c) + if err != nil { + return + } + cli.PrintItemSet(setCmd.OutOrStdout() ,id ,"User") + return + }, +} + +func init() { + setCmd.AddCommand(set_userCmd) +} diff --git a/cli/command/set/set.go b/cli/command/set/set.go new file mode 100644 index 00000000..cfacd43b --- /dev/null +++ b/cli/command/set/set.go @@ -0,0 +1,17 @@ +package set + +import ( + "github.com/Telmate/proxmox-api-go/cli" + "github.com/spf13/cobra" +) + +var setCmd = &cobra.Command{ + Use: "set", + Short: "This command sets the current configuration of an item", + Long: `This command sets the current configuration of an item. +Depending on if the item already exists the item will be created or updated.`, +} + +func init() { + cli.RootCmd.AddCommand(setCmd) +} \ No newline at end of file diff --git a/cli/command/update/update-poolcomment.go b/cli/command/update/update-poolcomment.go new file mode 100644 index 00000000..541d0747 --- /dev/null +++ b/cli/command/update/update-poolcomment.go @@ -0,0 +1,29 @@ +package update + +import ( + "github.com/Telmate/proxmox-api-go/cli" + "github.com/spf13/cobra" +) + +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){ + var comment string + id := cli.ValidateIDset(args, 0, "PoolID") + if len(args) > 1 { + comment = args[1] + } + c := cli.NewClient() + err = c.UpdatePoolComment(id, comment) + if err != nil { + return + } + cli.PrintItemUpdated(updateCmd.OutOrStdout() ,id, "PoolComment") + return + }, +} + +func init() { + updateCmd.AddCommand(update_poolCmd) +} diff --git a/cli/command/update/update.go b/cli/command/update/update.go new file mode 100644 index 00000000..a5715375 --- /dev/null +++ b/cli/command/update/update.go @@ -0,0 +1,15 @@ +package update + +import ( + "github.com/Telmate/proxmox-api-go/cli" + "github.com/spf13/cobra" +) + +var updateCmd = &cobra.Command{ + Use: "update", + Short: "With this command you can update existing items within proxmox", +} + +func init() { + cli.RootCmd.AddCommand(updateCmd) +} \ No newline at end of file diff --git a/cli/log.go b/cli/log.go new file mode 100644 index 00000000..4b5e4ade --- /dev/null +++ b/cli/log.go @@ -0,0 +1,17 @@ +package cli + +import ( + "log" +) + +func LogFatalError(err error) { + if err != nil { + log.Fatal(err) + } +} + +func LogFatalListing(text string, err error){ + if err != nil { + log.Fatalf("error listing %s %+v\n",text ,err) + } +} \ No newline at end of file diff --git a/cli/print.go b/cli/print.go new file mode 100644 index 00000000..8f61d8af --- /dev/null +++ b/cli/print.go @@ -0,0 +1,35 @@ +package cli + +import ( + "fmt" + "io" + "encoding/json" +) + +func PrintItemCreated(out io.Writer, id, text string){ + fmt.Fprintf(out, "%s (%s) has been created\n", text, id) +} + +func PrintItemUpdated(out io.Writer, id, text string){ + fmt.Fprintf(out, "%s (%s) has been updated\n", text, id) +} + +func PrintItemDeleted(out io.Writer, id, text string){ + fmt.Fprintf(out, "%s (%s) has been deleted\n", text, id) +} + +func PrintItemSet(out io.Writer, id, text string){ + fmt.Fprintf(out, "%s (%s) has been configured\n", text, id) +} + +func PrintRawJson(out io.Writer, input interface{}){ + list, err := json.Marshal(input) + LogFatalError(err) + fmt.Fprintln(out,string(list)) +} + +func PrintFormattedJson(out io.Writer, input interface{}){ + list, err := json.MarshalIndent(input, "", " ") + LogFatalError(err) + fmt.Fprintln(out,string(list)) +} \ No newline at end of file diff --git a/cli/validate.go b/cli/validate.go new file mode 100644 index 00000000..7e4cc85c --- /dev/null +++ b/cli/validate.go @@ -0,0 +1,22 @@ +package cli + +import ( + "strconv" + "fmt" + "log" +) + +func ValidateIDset(args []string, indexPos int, text string) (string){ + if indexPos+1 > len(args) { + log.Fatal(fmt.Errorf("error: no %s has been provided", text)) + } + return args[indexPos] +} + +func ValidateExistinGuestID(args []string, indexPos int) (int){ + id, err := strconv.Atoi(ValidateIDset(args, indexPos, "GuestID")) + if err != nil || id < 100 { + log.Fatal(fmt.Errorf("error: GuestID must be a positive integer of 100 or greater")) + } + return id +} \ No newline at end of file diff --git a/go.mod b/go.mod index 47e96922..b291a9ea 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,15 @@ module github.com/Telmate/proxmox-api-go go 1.17 -require github.com/stretchr/testify v1.6.1 +require ( + github.com/spf13/cobra v1.4.0 + github.com/stretchr/testify v1.6.1 +) require ( github.com/davecgh/go-spew v1.1.0 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect ) diff --git a/go.sum b/go.sum index afe7890c..16c6dd47 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,20 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= +github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 38713e63..ae0ed583 100644 --- a/main.go +++ b/main.go @@ -12,11 +12,19 @@ import ( "strconv" "github.com/Telmate/proxmox-api-go/proxmox" + "github.com/Telmate/proxmox-api-go/cli" + _ "github.com/Telmate/proxmox-api-go/cli/command/commands" ) func main() { - var insecure *bool - insecure = flag.Bool("insecure", false, "TLS insecure mode") + if os.Getenv("NEW_CLI") == "true" { + err := cli.Execute() + if err != nil { + failError(err) + } + os.Exit(0) + } + insecure := flag.Bool("insecure", false, "TLS insecure mode") proxmox.Debug = flag.Bool("debug", false, "debug mode") fConfigFile := flag.String("file", "", "file to get the config from") taskTimeout := flag.Int("timeout", 300, "api task timeout in seconds") diff --git a/test/cli/Nodes/list_nodes_test.go b/test/cli/Nodes/list_nodes_test.go new file mode 100644 index 00000000..e5d63467 --- /dev/null +++ b/test/cli/Nodes/list_nodes_test.go @@ -0,0 +1,23 @@ +package cli_node_test + +import ( + "testing" + cliTest "github.com/Telmate/proxmox-api-go/test/cli" +) + +func Test_List_Nodes(t *testing.T) { + cliTest.SetEnvironmentVariables() + tests := []struct { + name string + args []string + expected string + }{{ + name: "List_User_root@pam", + args: []string{"-i","list","nodes"}, + expected: `"id":"node/pve"`, + }} + + for _, test := range tests { + cliTest.ListTest(t,test.args,test.expected) + } +} \ No newline at end of file diff --git a/test/cli/Pools/list_pools_test.go b/test/cli/Pools/list_pools_test.go new file mode 100644 index 00000000..becb9d36 --- /dev/null +++ b/test/cli/Pools/list_pools_test.go @@ -0,0 +1,152 @@ +package cli_pool_test + +import ( + "testing" + cliTest "github.com/Telmate/proxmox-api-go/test/cli" +) + +// Test0 +func Test_Pool_0_Cleanup(t *testing.T){ + Test := cliTest.Test{ + Expected: "", + ReqErr: true, + Args: []string{"-i","delete","pool","test-pool0"}, + } + Test.StandardTest(t) +} + +func Test_Pool_0_Create_Without_Comment(t *testing.T){ + Test := cliTest.Test{ + Expected: "", + ReqErr: false, + Args: []string{"-i","create","pool","test-pool0"}, + } + Test.StandardTest(t) +} + +func Test_Pool_0_List(t *testing.T){ + Test := cliTest.Test{ + Expected: `"test-pool0"`, + ReqErr: false, + Contains: true, + Args: []string{"-i","list","pools"}, + } + Test.StandardTest(t) +} + +func Test_Pool_0_Get_Without_Comment(t *testing.T){ + Test := cliTest.Test{ + NotExpected: `"comment"`, + ReqErr: false, + NotContains: true, + Args: []string{"-i","get","pool","test-pool0"}, + } + Test.StandardTest(t) +} + +func Test_Pool_0_Update_Comment(t *testing.T){ + Test := cliTest.Test{ + ReqErr: false, + Contains: true, + Expected: "(test-pool0)", + Args: []string{"-i","update","poolcomment","test-pool0","this is a comment"}, + } + Test.StandardTest(t) +} + +func Test_Pool_0_Get_With_Comment(t *testing.T){ + Test := cliTest.Test{ + Expected: `"this is a comment"`, + ReqErr: false, + Contains: true, + Args: []string{"-i","get","pool","test-pool0"}, + } + Test.StandardTest(t) +} + +func Test_Pool_0_Delete(t *testing.T){ + Test := cliTest.Test{ + Expected: "", + ReqErr: false, + Args: []string{"-i","delete","pool","test-pool0"}, + } + Test.StandardTest(t) +} + +func Test_Pool_0_Removed(t *testing.T){ + Test := cliTest.Test{ + NotExpected: `"test-pool0"`, + ReqErr: false, + NotContains: true, + Args: []string{"-i","list","pools"}, + } + Test.StandardTest(t) +} + +// Test1 +func Test_Pool_1_Cleanup(t *testing.T){ + Test := cliTest.Test{ + Expected: "", + ReqErr: true, + Args: []string{"-i","delete","pool","test-pool1"}, + } + Test.StandardTest(t) +} + +func Test_Pool_1_Create_With_Comment(t *testing.T){ + Test := cliTest.Test{ + Expected: "", + ReqErr: false, + Args: []string{"-i","create","pool","test-pool1","This is a comment"}, + } + Test.StandardTest(t) +} + +func Test_Pool_1_Get_With_Comment(t *testing.T){ + Test := cliTest.Test{ + Expected: `"This is a comment"`, + ReqErr: false, + Contains: true, + Args: []string{"-i","get","pool","test-pool1"}, + } + Test.StandardTest(t) +} + +func Test_Pool_1_Update_Comment(t *testing.T){ + Test := cliTest.Test{ + ReqErr: false, + Contains: true, + Expected: "(test-pool1)", + Args: []string{"-i","update","poolcomment","test-pool1"}, + } + Test.StandardTest(t) +} + +func Test_Pool_1_Get_Without_Comment(t *testing.T){ + Test := cliTest.Test{ + NotExpected: `"comment"`, + ReqErr: false, + NotContains: true, + Args: []string{"-i","get","pool","test-pool1"}, + } + Test.StandardTest(t) +} + +func Test_Pool_1_Delete(t *testing.T){ + Test := cliTest.Test{ + Expected: "", + ReqErr: false, + Args: []string{"-i","delete","pool","test-pool1"}, + } + Test.StandardTest(t) +} + +func Test_Pool_1_Removed(t *testing.T){ + Test := cliTest.Test{ + NotExpected: `"test-pool1"`, + ReqErr: false, + NotContains: true, + Args: []string{"-i","list","pools"}, + } + Test.StandardTest(t) +} \ No newline at end of file diff --git a/test/cli/Users/User_0_test.go b/test/cli/Users/User_0_test.go new file mode 100644 index 00000000..9359db7f --- /dev/null +++ b/test/cli/Users/User_0_test.go @@ -0,0 +1,112 @@ +package cli_user_test + +import ( + "testing" + _ "github.com/Telmate/proxmox-api-go/cli/command/commands" + cliTest "github.com/Telmate/proxmox-api-go/test/cli" +) + +func Test_User_0_Cleanup(t *testing.T){ + Test := cliTest.Test{ + ReqErr: true, + ErrContains: "test-user0@pve", + Args: []string{"-i","delete","user","test-user0@pve"}, + } + Test.StandardTest(t) +} + +// Set groups + +func Test_User_0_Set_Full_With_Password_Set(t *testing.T){ + Test := cliTest.Test{ + InputJson: ` +{ + "comment": "this is a comment", + "email": "b.wayne@proxmox.com", + "enable": true, + "expire": 99999999, + "firstname": "Bruce", + "lastname": "Wayne", + "groups": [ + ], + "keys": "2fa key" +}`, + Expected: "(test-user0@pve)", + Contains: true, + Args: []string{"-i","set","user","test-user0@pve","Enter123!"}, + } + Test.StandardTest(t) +} + +func Test_User_0_Login_Password_Set(t *testing.T) { + cliTest.SetEnvironmentVariables() + Test := cliTest.LoginTest{ + UserID: "test-user0@pve", + Password: "Enter123!", + ReqErr: false, + } + Test.Login(t) +} + +func Test_User_0_Get_Full(t *testing.T) { + cliTest.SetEnvironmentVariables() + Test := cliTest.Test{ + OutputJson: ` +{ + "comment": "this is a comment", + "userid": "test-user0@pve", + "email": "b.wayne@proxmox.com", + "enable": true, + "expire": 99999999, + "firstname": "Bruce", + "keys": "2fa key", + "lastname": "Wayne" +}`, + Args: []string{"-i","get","user","test-user0@pve"}, + } + Test.StandardTest(t) +} + +func Test_User_0_Set_Empty(t *testing.T){ + Test := cliTest.Test{ + InputJson: ` +{ + "comment": "", + "email": "", + "enable": false, + "expire": 0, + "firstname": "", + "lastname": "", + "groups": [ + ], + "keys": "" +}`, + Expected: "(test-user0@pve)", + Contains: true, + Args: []string{"-i","set","user","test-user0@pve"}, + } + Test.StandardTest(t) +} + +func Test_User_0_Get_Empty(t *testing.T) { + cliTest.SetEnvironmentVariables() + Test := cliTest.Test{ + OutputJson: ` +{ + "userid": "test-user0@pve", + "enable": false, + "expire": 0 +}`, + Args: []string{"-i","get","user","test-user0@pve"}, + } + Test.StandardTest(t) +} + +func Test_User_0_Delete(t *testing.T){ + Test := cliTest.Test{ + Expected: "", + ReqErr: false, + Args: []string{"-i","delete","user","test-user0@pve"}, + } + Test.StandardTest(t) +} diff --git a/test/cli/Users/User_1_test.go b/test/cli/Users/User_1_test.go new file mode 100644 index 00000000..f4dab7c0 --- /dev/null +++ b/test/cli/Users/User_1_test.go @@ -0,0 +1,113 @@ +package cli_user_test + +import ( + "testing" + _ "github.com/Telmate/proxmox-api-go/cli/command/commands" + cliTest "github.com/Telmate/proxmox-api-go/test/cli" +) + +func Test_User_1_Cleanup(t *testing.T){ + Test := cliTest.Test{ + ReqErr: true, + ErrContains: "test-user1@pve", + Args: []string{"-i","delete","user","test-user1@pve"}, + } + Test.StandardTest(t) +} + +func Test_User_1_Set_Empty_Without_Password(t *testing.T){ + Test := cliTest.Test{ + InputJson: ` +{ + "enable": false, + "expire": 0 +}`, + Expected: "(test-user1@pve)", + Contains: true, + Args: []string{"-i","set","user","test-user1@pve"}, + } + Test.StandardTest(t) +} + +func Test_User_1_Login_Password_Not_Set(t *testing.T) { + cliTest.SetEnvironmentVariables() + Test := cliTest.LoginTest{ + UserID: "test-user1@pve", + Password: "Enter123!", + ReqErr: true, + } + Test.Login(t) +} + +func Test_User_1_Get_Empty(t *testing.T) { + cliTest.SetEnvironmentVariables() + Test := cliTest.Test{ + OutputJson: ` +{ + "userid": "test-user1@pve", + "enable": false, + "expire": 0 +}`, + Args: []string{"-i","get","user","test-user1@pve"}, + } + Test.StandardTest(t) +} + +func Test_User_1_Set_Full_With_Password(t *testing.T){ + Test := cliTest.Test{ + InputJson: ` +{ + "comment": "this is a comment", + "email": "b.wayne@proxmox.com", + "enable": true, + "expire": 99999999, + "firstname": "Bruce", + "lastname": "Wayne", + "groups": [ + ], + "keys": "2fa key" +}`, + Expected: "(test-user1@pve)", + Contains: true, + Args: []string{"-i","set","user","test-user1@pve","Enter123!"}, + } + Test.StandardTest(t) +} + +func Test_User_1_Login_Password_Set(t *testing.T) { + cliTest.SetEnvironmentVariables() + Test := cliTest.LoginTest{ + UserID: "test-user1@pve", + Password: "Enter123!", + ReqErr: false, + } + Test.Login(t) +} + +func Test_User_1_Get_Full(t *testing.T) { + cliTest.SetEnvironmentVariables() + Test := cliTest.Test{ + OutputJson: ` +{ + "comment": "this is a comment", + "userid": "test-user1@pve", + "email": "b.wayne@proxmox.com", + "enable": true, + "expire": 99999999, + "firstname": "Bruce", + "lastname": "Wayne", + "keys": "2fa key" +}`, + Args: []string{"-i","get","user","test-user1@pve"}, + } + Test.StandardTest(t) +} + +func Test_User_1_Delete(t *testing.T){ + Test := cliTest.Test{ + Expected: "", + ReqErr: false, + Args: []string{"-i","delete","user","test-user1@pve"}, + } + Test.StandardTest(t) +} \ No newline at end of file diff --git a/test/cli/Users/User_global_test.go b/test/cli/Users/User_global_test.go new file mode 100644 index 00000000..6502203f --- /dev/null +++ b/test/cli/Users/User_global_test.go @@ -0,0 +1,17 @@ +package cli_user_test + +import ( + "testing" + _ "github.com/Telmate/proxmox-api-go/cli/command/commands" + cliTest "github.com/Telmate/proxmox-api-go/test/cli" +) + +func Test_User_List(t *testing.T){ + Test := cliTest.Test{ + Expected: `"userid":"root@pam"`, + ReqErr: false, + Contains: true, + Args: []string{"-i","list","users"}, + } + Test.StandardTest(t) +} \ No newline at end of file diff --git a/test/cli/preperations.go b/test/cli/preperations.go new file mode 100644 index 00000000..ae7d5454 --- /dev/null +++ b/test/cli/preperations.go @@ -0,0 +1,12 @@ +package test + +import ( + "os" +) + + +func SetEnvironmentVariables() { + os.Setenv("PM_API_URL","https://192.168.67.4:8006/api2/json") + os.Setenv("PM_USER","root@pam") + os.Setenv("PM_PASS","Enter123!") +} diff --git a/test/cli/shared_tests.go b/test/cli/shared_tests.go new file mode 100644 index 00000000..728f92ad --- /dev/null +++ b/test/cli/shared_tests.go @@ -0,0 +1,97 @@ +package test + +import ( + "testing" + "bytes" + "io/ioutil" + "strings" + + "github.com/Telmate/proxmox-api-go/cli" + _ "github.com/Telmate/proxmox-api-go/cli/command/commands" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type Test struct { + InputJson string //the inputted json + OutputJson string //the outputted json + + Expected string //the output that is expected + Contains bool //if the output contains (expected) or qeuals it + + NotExpected string //the output that is notexpected + NotContains bool //if the output contains (notexpected) or qeuals it + + ReqErr bool //if an error is expected as output + ErrContains string //the string the error should contain + + Args []string //cli arguments +} + +func ListTest(t *testing.T, args []string, expected string) { + cli.RootCmd.SetArgs(append(args)) + + buffer := new(bytes.Buffer) + + cli.RootCmd.SetOut(buffer) + err := cli.RootCmd.Execute() + require.NoError(t, err) + + out, _ := ioutil.ReadAll(buffer) + assert.Contains(t, string(out), expected) +} + +func (test *Test) StandardTest(t *testing.T) { + SetEnvironmentVariables() + cli.RootCmd.SetArgs(test.Args) + buffer := new(bytes.Buffer) + cli.RootCmd.SetOut(buffer) + cli.RootCmd.SetIn(strings.NewReader(test.InputJson)) + err := cli.RootCmd.Execute() + + if test.ReqErr { + require.Error(t, err) + if test.ErrContains != "" { + assert.Contains(t, err.Error(), test.ErrContains) + } + } else { + require.NoError(t, err) + } + if test.Expected != "" { + out, _ := ioutil.ReadAll(buffer) + if test.Contains { + assert.Contains(t, string(out), test.Expected) + } else { + assert.Equal(t, string(out), test.Expected) + } + } + if test.NotExpected != "" { + out, _ := ioutil.ReadAll(buffer) + if test.NotContains { + assert.NotContains(t, string(out), test.NotExpected) + } else { + assert.NotEqual(t, string(out), test.NotExpected) + } + } + if test.OutputJson != "" { + out, _ := ioutil.ReadAll(buffer) + require.JSONEq(t, test.OutputJson ,string(out)) + } +} + +type LoginTest struct { + APIurl string + UserID string + Password string + OTP string + ReqErr bool //if an error is expected as output +} + +func (test *LoginTest) Login(t *testing.T){ + _, err := cli.Client(test.APIurl,test.UserID,test.Password,test.OTP) + if test.ReqErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } +} \ No newline at end of file