Browse Source

Merge pull request #1800 from gravitl/feature_netmaker_cli

Feature netmaker cli
dcarns 2 years ago
parent
commit
68992322ec
97 changed files with 3285 additions and 0 deletions
  1. 46 0
      cli/cmd/acl/allow.go
  2. 46 0
      cli/cmd/acl/deny.go
  3. 46 0
      cli/cmd/acl/list.go
  4. 38 0
      cli/cmd/acl/root.go
  5. 20 0
      cli/cmd/context/delete.go
  6. 20 0
      cli/cmd/context/list.go
  7. 38 0
      cli/cmd/context/root.go
  8. 44 0
      cli/cmd/context/set.go
  9. 20 0
      cli/cmd/context/use.go
  10. 33 0
      cli/cmd/dns/create.go
  11. 20 0
      cli/cmd/dns/delete.go
  12. 9 0
      cli/cmd/dns/flags.go
  13. 47 0
      cli/cmd/dns/list.go
  14. 22 0
      cli/cmd/dns/push.go
  15. 38 0
      cli/cmd/dns/root.go
  16. 22 0
      cli/cmd/ext_client/config.go
  17. 26 0
      cli/cmd/ext_client/create.go
  18. 20 0
      cli/cmd/ext_client/delete.go
  19. 20 0
      cli/cmd/ext_client/get.go
  20. 40 0
      cli/cmd/ext_client/list.go
  21. 38 0
      cli/cmd/ext_client/root.go
  22. 71 0
      cli/cmd/ext_client/update.go
  23. 35 0
      cli/cmd/keys/create.go
  24. 23 0
      cli/cmd/keys/delete.go
  25. 20 0
      cli/cmd/keys/list.go
  26. 38 0
      cli/cmd/keys/root.go
  27. 22 0
      cli/cmd/logs.go
  28. 20 0
      cli/cmd/metrics/all.go
  29. 20 0
      cli/cmd/metrics/network.go
  30. 20 0
      cli/cmd/metrics/network_ext.go
  31. 20 0
      cli/cmd/metrics/node.go
  32. 38 0
      cli/cmd/metrics/root.go
  33. 85 0
      cli/cmd/network/create.go
  34. 22 0
      cli/cmd/network/delete.go
  35. 22 0
      cli/cmd/network/flags.go
  36. 20 0
      cli/cmd/network/get.go
  37. 32 0
      cli/cmd/network/list.go
  38. 27 0
      cli/cmd/network/node_limit.go
  39. 20 0
      cli/cmd/network/refresh_keys.go
  40. 40 0
      cli/cmd/network/root.go
  41. 86 0
      cli/cmd/network/update.go
  42. 43 0
      cli/cmd/network_user/create.go
  43. 23 0
      cli/cmd/network_user/delete.go
  44. 10 0
      cli/cmd/network_user/flags.go
  45. 27 0
      cli/cmd/network_user/get.go
  46. 27 0
      cli/cmd/network_user/list.go
  47. 38 0
      cli/cmd/network_user/root.go
  48. 43 0
      cli/cmd/network_user/update.go
  49. 34 0
      cli/cmd/node/create_egress.go
  50. 21 0
      cli/cmd/node/create_ingress.go
  51. 22 0
      cli/cmd/node/create_relay.go
  52. 20 0
      cli/cmd/node/delete.go
  53. 20 0
      cli/cmd/node/delete_egress.go
  54. 20 0
      cli/cmd/node/delete_ingress.go
  55. 20 0
      cli/cmd/node/delete_relay.go
  56. 28 0
      cli/cmd/node/flags.go
  57. 20 0
      cli/cmd/node/get.go
  58. 47 0
      cli/cmd/node/list.go
  59. 38 0
      cli/cmd/node/root.go
  60. 22 0
      cli/cmd/node/uncordon.go
  61. 100 0
      cli/cmd/node/update.go
  62. 69 0
      cli/cmd/root.go
  63. 20 0
      cli/cmd/server/config.go
  64. 22 0
      cli/cmd/server/has_admin.go
  65. 20 0
      cli/cmd/server/health.go
  66. 20 0
      cli/cmd/server/info.go
  67. 38 0
      cli/cmd/server/root.go
  68. 37 0
      cli/cmd/user/create.go
  69. 20 0
      cli/cmd/user/delete.go
  70. 9 0
      cli/cmd/user/flags.go
  71. 20 0
      cli/cmd/user/get.go
  72. 30 0
      cli/cmd/user/list.go
  73. 38 0
      cli/cmd/user/root.go
  74. 35 0
      cli/cmd/user/update.go
  75. 23 0
      cli/cmd/usergroup/create.go
  76. 23 0
      cli/cmd/usergroup/delete.go
  77. 20 0
      cli/cmd/usergroup/get.go
  78. 38 0
      cli/cmd/usergroup/root.go
  79. 144 0
      cli/config/config.go
  80. 18 0
      cli/functions/acl.go
  81. 43 0
      cli/functions/dns.go
  82. 49 0
      cli/functions/ext_client.go
  83. 118 0
      cli/functions/http_client.go
  84. 23 0
      cli/functions/keys.go
  85. 28 0
      cli/functions/metrics.go
  86. 45 0
      cli/functions/network.go
  87. 44 0
      cli/functions/network_user.go
  88. 73 0
      cli/functions/node.go
  89. 16 0
      cli/functions/pretty_print.go
  90. 28 0
      cli/functions/server.go
  91. 37 0
      cli/functions/user.go
  92. 22 0
      cli/functions/usergroups.go
  93. 9 0
      cli/main.go
  94. 31 0
      cli/samples/network.json
  95. 101 0
      cli/samples/node.json
  96. 7 0
      go.mod
  97. 10 0
      go.sum

+ 46 - 0
cli/cmd/acl/allow.go

@@ -0,0 +1,46 @@
+package acl
+
+import (
+	"fmt"
+	"log"
+	"strings"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/gravitl/netmaker/logic/acls"
+	"github.com/spf13/cobra"
+)
+
+var aclAllowCmd = &cobra.Command{
+	Use:   "allow [NETWORK NAME] [FROM_NODE_NAME] [TO_NODE_NAME]",
+	Args:  cobra.ExactArgs(3),
+	Short: "Allow access from one node to another",
+	Long:  `Allow access from one node to another`,
+	Run: func(cmd *cobra.Command, args []string) {
+		nameIDMap := make(map[string]string)
+		for _, node := range *functions.GetNodes(args[0]) {
+			nameIDMap[strings.ToLower(node.Name)] = node.ID
+		}
+		fromNodeID, ok := nameIDMap[strings.ToLower(args[1])]
+		if !ok {
+			log.Fatalf("Node %s doesn't exist", args[1])
+		}
+		toNodeID, ok := nameIDMap[strings.ToLower(args[2])]
+		if !ok {
+			log.Fatalf("Node %s doesn't exist", args[2])
+		}
+		payload := acls.ACLContainer(map[acls.AclID]acls.ACL{
+			acls.AclID(fromNodeID): map[acls.AclID]byte{
+				acls.AclID(toNodeID): acls.Allowed,
+			},
+			acls.AclID(toNodeID): map[acls.AclID]byte{
+				acls.AclID(fromNodeID): acls.Allowed,
+			},
+		})
+		functions.UpdateACL(args[0], &payload)
+		fmt.Println("Success")
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(aclAllowCmd)
+}

+ 46 - 0
cli/cmd/acl/deny.go

@@ -0,0 +1,46 @@
+package acl
+
+import (
+	"fmt"
+	"log"
+	"strings"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/gravitl/netmaker/logic/acls"
+	"github.com/spf13/cobra"
+)
+
+var aclDenyCmd = &cobra.Command{
+	Use:   "deny [NETWORK NAME] [FROM_NODE_NAME] [TO_NODE_NAME]",
+	Args:  cobra.ExactArgs(3),
+	Short: "Deny access from one node to another",
+	Long:  `Deny access from one node to another`,
+	Run: func(cmd *cobra.Command, args []string) {
+		nameIDMap := make(map[string]string)
+		for _, node := range *functions.GetNodes(args[0]) {
+			nameIDMap[strings.ToLower(node.Name)] = node.ID
+		}
+		fromNodeID, ok := nameIDMap[strings.ToLower(args[1])]
+		if !ok {
+			log.Fatalf("Node %s doesn't exist", args[1])
+		}
+		toNodeID, ok := nameIDMap[strings.ToLower(args[2])]
+		if !ok {
+			log.Fatalf("Node %s doesn't exist", args[2])
+		}
+		payload := acls.ACLContainer(map[acls.AclID]acls.ACL{
+			acls.AclID(fromNodeID): map[acls.AclID]byte{
+				acls.AclID(toNodeID): acls.NotAllowed,
+			},
+			acls.AclID(toNodeID): map[acls.AclID]byte{
+				acls.AclID(fromNodeID): acls.NotAllowed,
+			},
+		})
+		functions.UpdateACL(args[0], &payload)
+		fmt.Println("Success")
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(aclDenyCmd)
+}

+ 46 - 0
cli/cmd/acl/list.go

@@ -0,0 +1,46 @@
+package acl
+
+import (
+	"os"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/gravitl/netmaker/logic/acls"
+	"github.com/guumaster/tablewriter"
+	"github.com/spf13/cobra"
+)
+
+var aclListCmd = &cobra.Command{
+	Use:   "list [NETWORK NAME]",
+	Args:  cobra.ExactArgs(1),
+	Short: "List all ACLs associated with a network",
+	Long:  `List all ACLs associated with a network`,
+	Run: func(cmd *cobra.Command, args []string) {
+		aclSource := (map[acls.AclID]acls.ACL)(*functions.GetACL(args[0]))
+		nodes := functions.GetNodes(args[0])
+		idNameMap := make(map[string]string)
+		for _, node := range *nodes {
+			idNameMap[node.ID] = node.Name
+		}
+		table := tablewriter.NewWriter(os.Stdout)
+		table.SetHeader([]string{"From", "To", "Status"})
+		for id, acl := range aclSource {
+			for k, v := range (map[acls.AclID]byte)(acl) {
+				row := []string{idNameMap[string(id)], idNameMap[string(k)]}
+				switch v {
+				case acls.NotAllowed:
+					row = append(row, "Not Allowed")
+				case acls.NotPresent:
+					row = append(row, "Not Present")
+				case acls.Allowed:
+					row = append(row, "Allowed")
+				}
+				table.Append(row)
+			}
+		}
+		table.Render()
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(aclListCmd)
+}

+ 38 - 0
cli/cmd/acl/root.go

@@ -0,0 +1,38 @@
+package acl
+
+import (
+	"os"
+
+	"github.com/spf13/cobra"
+)
+
+// rootCmd represents the base command when called without any subcommands
+var rootCmd = &cobra.Command{
+	Use:   "acl",
+	Short: "Manage Access Control Lists (ACLs)",
+	Long:  `Manage Access Control Lists (ACLs)`,
+	// Run: func(cmd *cobra.Command, args []string) { },
+}
+
+// GetRoot returns the root subcommand
+func GetRoot() *cobra.Command {
+	return rootCmd
+}
+
+// Execute adds all child commands to the root command and sets flags appropriately.
+// This is called by main.main(). It only needs to happen once to the rootCmd.
+func Execute() {
+	err := rootCmd.Execute()
+	if err != nil {
+		os.Exit(1)
+	}
+}
+
+func init() {
+	// Here you will define your flags and configuration settings.
+	// Cobra supports persistent flags, which, if defined here,
+	// will be global for your application.
+	// Cobra also supports local flags, which will only run
+	// when this action is called directly.
+	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}

+ 20 - 0
cli/cmd/context/delete.go

@@ -0,0 +1,20 @@
+package context
+
+import (
+	"github.com/gravitl/netmaker/cli/config"
+	"github.com/spf13/cobra"
+)
+
+var contextDeleteCmd = &cobra.Command{
+	Use:   "delete [NAME]",
+	Args:  cobra.ExactArgs(1),
+	Short: "Delete a context",
+	Long:  `Delete a context`,
+	Run: func(cmd *cobra.Command, args []string) {
+		config.DeleteContext(args[0])
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(contextDeleteCmd)
+}

+ 20 - 0
cli/cmd/context/list.go

@@ -0,0 +1,20 @@
+package context
+
+import (
+	"github.com/gravitl/netmaker/cli/config"
+	"github.com/spf13/cobra"
+)
+
+var contextListCmd = &cobra.Command{
+	Use:   "list",
+	Args:  cobra.NoArgs,
+	Short: "List all contexts",
+	Long:  `List all contexts`,
+	Run: func(cmd *cobra.Command, args []string) {
+		config.ListAll()
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(contextListCmd)
+}

+ 38 - 0
cli/cmd/context/root.go

@@ -0,0 +1,38 @@
+package context
+
+import (
+	"os"
+
+	"github.com/spf13/cobra"
+)
+
+// rootCmd represents the base command when called without any subcommands
+var rootCmd = &cobra.Command{
+	Use:   "context",
+	Short: "Manage various netmaker server configurations",
+	Long:  `Manage various netmaker server configurations`,
+	// Run: func(cmd *cobra.Command, args []string) { },
+}
+
+// GetRoot returns the root subcommand
+func GetRoot() *cobra.Command {
+	return rootCmd
+}
+
+// Execute adds all child commands to the root command and sets flags appropriately.
+// This is called by main.main(). It only needs to happen once to the rootCmd.
+func Execute() {
+	err := rootCmd.Execute()
+	if err != nil {
+		os.Exit(1)
+	}
+}
+
+func init() {
+	// Here you will define your flags and configuration settings.
+	// Cobra supports persistent flags, which, if defined here,
+	// will be global for your application.
+	// Cobra also supports local flags, which will only run
+	// when this action is called directly.
+	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}

+ 44 - 0
cli/cmd/context/set.go

@@ -0,0 +1,44 @@
+package context
+
+import (
+	"log"
+
+	"github.com/gravitl/netmaker/cli/config"
+	"github.com/spf13/cobra"
+)
+
+var (
+	endpoint  string
+	username  string
+	password  string
+	masterKey string
+)
+
+var contextSetCmd = &cobra.Command{
+	Use:   "set [NAME]",
+	Args:  cobra.ExactArgs(1),
+	Short: "Create a context or update an existing one",
+	Long:  `Create a context or update an existing one`,
+	Run: func(cmd *cobra.Command, args []string) {
+		ctx := config.Context{
+			Endpoint:  endpoint,
+			Username:  username,
+			Password:  password,
+			MasterKey: masterKey,
+		}
+		if ctx.Username == "" && ctx.MasterKey == "" {
+			cmd.Usage()
+			log.Fatal("Either username/password or master key is required")
+		}
+		config.SetContext(args[0], ctx)
+	},
+}
+
+func init() {
+	contextSetCmd.Flags().StringVar(&endpoint, "endpoint", "", "Endpoint of the API Server")
+	contextSetCmd.Flags().StringVar(&username, "username", "", "Username")
+	contextSetCmd.Flags().StringVar(&password, "password", "", "Password")
+	contextSetCmd.MarkFlagsRequiredTogether("username", "password")
+	contextSetCmd.Flags().StringVar(&masterKey, "master_key", "", "Master Key")
+	rootCmd.AddCommand(contextSetCmd)
+}

+ 20 - 0
cli/cmd/context/use.go

@@ -0,0 +1,20 @@
+package context
+
+import (
+	"github.com/gravitl/netmaker/cli/config"
+	"github.com/spf13/cobra"
+)
+
+var contextUseCmd = &cobra.Command{
+	Use:   "use [NAME]",
+	Args:  cobra.ExactArgs(1),
+	Short: "Set the current context",
+	Long:  `Set the current context`,
+	Run: func(cmd *cobra.Command, args []string) {
+		config.SetCurrentContext(args[0])
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(contextUseCmd)
+}

+ 33 - 0
cli/cmd/dns/create.go

@@ -0,0 +1,33 @@
+package dns
+
+import (
+	"log"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/gravitl/netmaker/models"
+	"github.com/spf13/cobra"
+)
+
+var dnsCreateCmd = &cobra.Command{
+	Use:   "create",
+	Args:  cobra.NoArgs,
+	Short: "Create a DNS entry",
+	Long:  `Create a DNS entry`,
+	Run: func(cmd *cobra.Command, args []string) {
+		if address == "" && address6 == "" {
+			log.Fatal("Either IPv4 or IPv6 address is required")
+		}
+		dnsEntry := &models.DNSEntry{Name: dnsName, Address: address, Address6: address6, Network: networkName}
+		functions.PrettyPrint(functions.CreateDNS(networkName, dnsEntry))
+	},
+}
+
+func init() {
+	dnsCreateCmd.Flags().StringVar(&dnsName, "name", "", "Name of the DNS entry")
+	dnsCreateCmd.MarkFlagRequired("name")
+	dnsCreateCmd.Flags().StringVar(&networkName, "network", "", "Name of the Network")
+	dnsCreateCmd.MarkFlagRequired("network")
+	dnsCreateCmd.Flags().StringVar(&address, "ipv4_addr", "", "IPv4 Address")
+	dnsCreateCmd.Flags().StringVar(&address6, "ipv6_addr", "", "IPv6 Address")
+	rootCmd.AddCommand(dnsCreateCmd)
+}

+ 20 - 0
cli/cmd/dns/delete.go

@@ -0,0 +1,20 @@
+package dns
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var dnsDeleteCmd = &cobra.Command{
+	Use:   "delete [NETWORK NAME] [DOMAIN NAME]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Delete a DNS entry",
+	Long:  `Delete a DNS entry`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.DeleteDNS(args[0], args[1]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(dnsDeleteCmd)
+}

+ 9 - 0
cli/cmd/dns/flags.go

@@ -0,0 +1,9 @@
+package dns
+
+var (
+	dnsName     string
+	address     string
+	address6    string
+	networkName string
+	dnsType     string
+)

+ 47 - 0
cli/cmd/dns/list.go

@@ -0,0 +1,47 @@
+package dns
+
+import (
+	"fmt"
+	"os"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/gravitl/netmaker/models"
+	"github.com/guumaster/tablewriter"
+	"github.com/spf13/cobra"
+)
+
+var dnsListCmd = &cobra.Command{
+	Use:   "list",
+	Args:  cobra.NoArgs,
+	Short: "List DNS entries",
+	Long:  `List DNS entries`,
+	Run: func(cmd *cobra.Command, args []string) {
+		var data []models.DNSEntry
+		if networkName != "" {
+			switch dnsType {
+			case "node":
+				data = *functions.GetNodeDNS(networkName)
+			case "custom":
+				data = *functions.GetCustomDNS(networkName)
+			case "network", "":
+				data = *functions.GetNetworkDNS(networkName)
+			default:
+				fmt.Println("Invalid DNS type provided ", dnsType)
+			}
+		} else {
+			data = *functions.GetDNS()
+		}
+		table := tablewriter.NewWriter(os.Stdout)
+		table.SetHeader([]string{"Name", "Network", "IPv4 Address", "IPv6 Address"})
+		for _, d := range data {
+			table.Append([]string{d.Name, d.Network, d.Address, d.Address6})
+		}
+		table.Render()
+	},
+}
+
+func init() {
+	dnsListCmd.Flags().StringVar(&networkName, "network", "", "Network name")
+	dnsListCmd.Flags().StringVar(&dnsType, "type", "", "Type of DNS records to fetch ENUM(node, custom, network)")
+	rootCmd.AddCommand(dnsListCmd)
+}

+ 22 - 0
cli/cmd/dns/push.go

@@ -0,0 +1,22 @@
+package dns
+
+import (
+	"fmt"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var dnsPushCmd = &cobra.Command{
+	Use:   "push",
+	Args:  cobra.NoArgs,
+	Short: "Push latest DNS entries",
+	Long:  `Push latest DNS entries`,
+	Run: func(cmd *cobra.Command, args []string) {
+		fmt.Println(*functions.PushDNS())
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(dnsPushCmd)
+}

+ 38 - 0
cli/cmd/dns/root.go

@@ -0,0 +1,38 @@
+package dns
+
+import (
+	"os"
+
+	"github.com/spf13/cobra"
+)
+
+// rootCmd represents the base command when called without any subcommands
+var rootCmd = &cobra.Command{
+	Use:   "dns",
+	Short: "Manage DNS entries associated with a network",
+	Long:  `Manage DNS entries associated with a network`,
+	// Run: func(cmd *cobra.Command, args []string) { },
+}
+
+// GetRoot returns the root subcommand
+func GetRoot() *cobra.Command {
+	return rootCmd
+}
+
+// Execute adds all child commands to the root command and sets flags appropriately.
+// This is called by main.main(). It only needs to happen once to the rootCmd.
+func Execute() {
+	err := rootCmd.Execute()
+	if err != nil {
+		os.Exit(1)
+	}
+}
+
+func init() {
+	// Here you will define your flags and configuration settings.
+	// Cobra supports persistent flags, which, if defined here,
+	// will be global for your application.
+	// Cobra also supports local flags, which will only run
+	// when this action is called directly.
+	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}

+ 22 - 0
cli/cmd/ext_client/config.go

@@ -0,0 +1,22 @@
+package ext_client
+
+import (
+	"fmt"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var extClientConfigCmd = &cobra.Command{
+	Use:   "config [NETWORK NAME] [EXTERNAL CLIENT ID]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Get an External Client Configuration",
+	Long:  `Get an External Client Configuration`,
+	Run: func(cmd *cobra.Command, args []string) {
+		fmt.Println(functions.GetExtClientConfig(args[0], args[1]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(extClientConfigCmd)
+}

+ 26 - 0
cli/cmd/ext_client/create.go

@@ -0,0 +1,26 @@
+package ext_client
+
+import (
+	"fmt"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var extClientID string
+
+var extClientCreateCmd = &cobra.Command{
+	Use:   "create [NETWORK NAME] [NODE ID]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Create an External Client",
+	Long:  `Create an External Client`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.CreateExtClient(args[0], args[1], extClientID)
+		fmt.Println("Success")
+	},
+}
+
+func init() {
+	extClientCreateCmd.Flags().StringVar(&extClientID, "id", "", "ID of the external client")
+	rootCmd.AddCommand(extClientCreateCmd)
+}

+ 20 - 0
cli/cmd/ext_client/delete.go

@@ -0,0 +1,20 @@
+package ext_client
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var extClientDeleteCmd = &cobra.Command{
+	Use:   "delete [NETWORK NAME] [EXTERNAL CLIENT ID]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Delete an External Client",
+	Long:  `Delete an External Client`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.DeleteExtClient(args[0], args[1]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(extClientDeleteCmd)
+}

+ 20 - 0
cli/cmd/ext_client/get.go

@@ -0,0 +1,20 @@
+package ext_client
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var extClientGetCmd = &cobra.Command{
+	Use:   "get [NETWORK NAME] [EXTERNAL CLIENT ID]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Get an External Client",
+	Long:  `Get an External Client`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.GetExtClient(args[0], args[1]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(extClientGetCmd)
+}

+ 40 - 0
cli/cmd/ext_client/list.go

@@ -0,0 +1,40 @@
+package ext_client
+
+import (
+	"os"
+	"strconv"
+	"time"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/gravitl/netmaker/models"
+	"github.com/guumaster/tablewriter"
+	"github.com/spf13/cobra"
+)
+
+var networkName string
+
+var extClientListCmd = &cobra.Command{
+	Use:   "list",
+	Args:  cobra.NoArgs,
+	Short: "List External Clients",
+	Long:  `List External Clients`,
+	Run: func(cmd *cobra.Command, args []string) {
+		var data []models.ExtClient
+		if networkName != "" {
+			data = *functions.GetNetworkExtClients(networkName)
+		} else {
+			data = *functions.GetAllExtClients()
+		}
+		table := tablewriter.NewWriter(os.Stdout)
+		table.SetHeader([]string{"Client ID", "Network", "IPv4 Address", "IPv6 Address", "Enabled", "Last Modified"})
+		for _, d := range data {
+			table.Append([]string{d.ClientID, d.Network, d.Address, d.Address6, strconv.FormatBool(d.Enabled), time.Unix(d.LastModified, 0).String()})
+		}
+		table.Render()
+	},
+}
+
+func init() {
+	extClientListCmd.Flags().StringVar(&networkName, "network", "", "Network name")
+	rootCmd.AddCommand(extClientListCmd)
+}

+ 38 - 0
cli/cmd/ext_client/root.go

@@ -0,0 +1,38 @@
+package ext_client
+
+import (
+	"os"
+
+	"github.com/spf13/cobra"
+)
+
+// rootCmd represents the base command when called without any subcommands
+var rootCmd = &cobra.Command{
+	Use:   "ext_client",
+	Short: "Manage External Clients",
+	Long:  `Manage External Clients`,
+	// Run: func(cmd *cobra.Command, args []string) { },
+}
+
+// GetRoot returns the root subcommand
+func GetRoot() *cobra.Command {
+	return rootCmd
+}
+
+// Execute adds all child commands to the root command and sets flags appropriately.
+// This is called by main.main(). It only needs to happen once to the rootCmd.
+func Execute() {
+	err := rootCmd.Execute()
+	if err != nil {
+		os.Exit(1)
+	}
+}
+
+func init() {
+	// Here you will define your flags and configuration settings.
+	// Cobra supports persistent flags, which, if defined here,
+	// will be global for your application.
+	// Cobra also supports local flags, which will only run
+	// when this action is called directly.
+	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}

+ 71 - 0
cli/cmd/ext_client/update.go

@@ -0,0 +1,71 @@
+package ext_client
+
+import (
+	"encoding/json"
+	"log"
+	"os"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/gravitl/netmaker/models"
+	"github.com/spf13/cobra"
+)
+
+var (
+	extClientUpdateFile    string
+	description            string
+	privateKey             string
+	publicKey              string
+	address                string
+	address6               string
+	ingressGatewayID       string
+	ingressGatewayEndpoint string
+	ownerID                string
+)
+
+var extClientUpdateCmd = &cobra.Command{
+	Use:   "update [NETWORK NAME] [EXTERNAL CLIENT ID]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Update an External Client",
+	Long:  `Update an External Client`,
+	Run: func(cmd *cobra.Command, args []string) {
+		var (
+			network   = args[0]
+			clientID  = args[1]
+			extClient = &models.ExtClient{}
+		)
+		if extClientUpdateFile != "" {
+			content, err := os.ReadFile(extClientUpdateFile)
+			if err != nil {
+				log.Fatal("Error when opening file: ", err)
+			}
+			if err := json.Unmarshal(content, extClient); err != nil {
+				log.Fatal(err)
+			}
+		} else {
+			extClient.ClientID = clientID
+			extClient.Description = description
+			extClient.PrivateKey = privateKey
+			extClient.PublicKey = publicKey
+			extClient.Network = network
+			extClient.Address = address
+			extClient.Address6 = address6
+			extClient.IngressGatewayID = ingressGatewayID
+			extClient.IngressGatewayEndpoint = ingressGatewayEndpoint
+			extClient.OwnerID = ownerID
+		}
+		functions.PrettyPrint(functions.UpdateExtClient(network, clientID, extClient))
+	},
+}
+
+func init() {
+	extClientUpdateCmd.Flags().StringVar(&extClientUpdateFile, "file", "", "Filepath of updated external client definition in JSON")
+	extClientUpdateCmd.Flags().StringVar(&description, "desc", "", "Description of the external client")
+	extClientUpdateCmd.Flags().StringVar(&privateKey, "private_key", "", "Filepath of updated external client definition in JSON")
+	extClientUpdateCmd.Flags().StringVar(&publicKey, "public_key", "", "Filepath of updated external client definition in JSON")
+	extClientUpdateCmd.Flags().StringVar(&address, "ipv4_addr", "", "IPv4 address of the external client")
+	extClientUpdateCmd.Flags().StringVar(&address6, "ipv6_addr", "", "IPv6 address of the external client")
+	extClientUpdateCmd.Flags().StringVar(&ingressGatewayID, "ingress_gateway_id", "", "ID of the ingress gateway")
+	extClientUpdateCmd.Flags().StringVar(&ingressGatewayEndpoint, "ingress_gateway_endpoint", "", "Endpoint of the ingress gateway")
+	extClientUpdateCmd.Flags().StringVar(&ownerID, "owner_id", "", "External Client owner's ID")
+	rootCmd.AddCommand(extClientUpdateCmd)
+}

+ 35 - 0
cli/cmd/keys/create.go

@@ -0,0 +1,35 @@
+package keys
+
+import (
+	"log"
+	"strconv"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/gravitl/netmaker/models"
+	"github.com/spf13/cobra"
+)
+
+var keyName string
+
+var keysCreateCmd = &cobra.Command{
+	Use:   "create [NETWORK NAME] [NUM USES]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Create an access key",
+	Long:  `Create an access key`,
+	Run: func(cmd *cobra.Command, args []string) {
+		keyUses, err := strconv.ParseInt(args[1], 10, 64)
+		if err != nil {
+			log.Fatal(err)
+		}
+		key := &models.AccessKey{Uses: int(keyUses)}
+		if keyName != "" {
+			key.Name = keyName
+		}
+		functions.PrettyPrint(functions.CreateKey(args[0], key))
+	},
+}
+
+func init() {
+	keysCreateCmd.Flags().StringVar(&keyName, "name", "", "Name of the key")
+	rootCmd.AddCommand(keysCreateCmd)
+}

+ 23 - 0
cli/cmd/keys/delete.go

@@ -0,0 +1,23 @@
+package keys
+
+import (
+	"fmt"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var keysDeleteCmd = &cobra.Command{
+	Use:   "delete [NETWORK NAME] [KEY NAME]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Delete a key",
+	Long:  `Delete a key`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.DeleteKey(args[0], args[1])
+		fmt.Println("Success")
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(keysDeleteCmd)
+}

+ 20 - 0
cli/cmd/keys/list.go

@@ -0,0 +1,20 @@
+package keys
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var keysListCmd = &cobra.Command{
+	Use:   "list [NETWORK NAME]",
+	Args:  cobra.ExactArgs(1),
+	Short: "List all keys associated with a network",
+	Long:  `List all keys associated with a network`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.GetKeys(args[0]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(keysListCmd)
+}

+ 38 - 0
cli/cmd/keys/root.go

@@ -0,0 +1,38 @@
+package keys
+
+import (
+	"os"
+
+	"github.com/spf13/cobra"
+)
+
+// rootCmd represents the base command when called without any subcommands
+var rootCmd = &cobra.Command{
+	Use:   "keys",
+	Short: "Manage access keys associated with a network",
+	Long:  `Manage access keys associated with a network`,
+	// Run: func(cmd *cobra.Command, args []string) { },
+}
+
+// GetRoot returns the root subcommand
+func GetRoot() *cobra.Command {
+	return rootCmd
+}
+
+// Execute adds all child commands to the root command and sets flags appropriately.
+// This is called by main.main(). It only needs to happen once to the rootCmd.
+func Execute() {
+	err := rootCmd.Execute()
+	if err != nil {
+		os.Exit(1)
+	}
+}
+
+func init() {
+	// Here you will define your flags and configuration settings.
+	// Cobra supports persistent flags, which, if defined here,
+	// will be global for your application.
+	// Cobra also supports local flags, which will only run
+	// when this action is called directly.
+	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}

+ 22 - 0
cli/cmd/logs.go

@@ -0,0 +1,22 @@
+package cmd
+
+import (
+	"fmt"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var getLogsCmd = &cobra.Command{
+	Use:   "logs",
+	Args:  cobra.NoArgs,
+	Short: "Retrieve server logs",
+	Long:  `Retrieve server logs`,
+	Run: func(cmd *cobra.Command, args []string) {
+		fmt.Println(functions.GetLogs())
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(getLogsCmd)
+}

+ 20 - 0
cli/cmd/metrics/all.go

@@ -0,0 +1,20 @@
+package metrics
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var metricsAllCmd = &cobra.Command{
+	Use:   "all",
+	Args:  cobra.NoArgs,
+	Short: "Retrieve all metrics",
+	Long:  `Retrieve all metrics`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.GetAllMetrics())
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(metricsAllCmd)
+}

+ 20 - 0
cli/cmd/metrics/network.go

@@ -0,0 +1,20 @@
+package metrics
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var metricsNetworkCmd = &cobra.Command{
+	Use:   "network [NETWORK NAME]",
+	Args:  cobra.ExactArgs(1),
+	Short: "Retrieve network metrics",
+	Long:  `Retrieve network metrics`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.GetNetworkNodeMetrics(args[0]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(metricsNetworkCmd)
+}

+ 20 - 0
cli/cmd/metrics/network_ext.go

@@ -0,0 +1,20 @@
+package metrics
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var metricsNetworkExtCmd = &cobra.Command{
+	Use:   "network_ext [NETWORK NAME]",
+	Args:  cobra.ExactArgs(1),
+	Short: "Retrieve metrics of external clients on a given network",
+	Long:  `Retrieve metrics of external clients on a given network`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.GetNetworkExtMetrics(args[0]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(metricsNetworkExtCmd)
+}

+ 20 - 0
cli/cmd/metrics/node.go

@@ -0,0 +1,20 @@
+package metrics
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var metricsNodeCmd = &cobra.Command{
+	Use:   "node [NETWORK NAME] [NODE ID]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Retrieve node metrics",
+	Long:  `Retrieve node metrics`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.GetNodeMetrics(args[0], args[1]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(metricsNodeCmd)
+}

+ 38 - 0
cli/cmd/metrics/root.go

@@ -0,0 +1,38 @@
+package metrics
+
+import (
+	"os"
+
+	"github.com/spf13/cobra"
+)
+
+// rootCmd represents the base command when called without any subcommands
+var rootCmd = &cobra.Command{
+	Use:   "metrics",
+	Short: "Fetch metrics of nodes/networks",
+	Long:  `Fetch metrics of nodes/networks`,
+	// Run: func(cmd *cobra.Command, args []string) { },
+}
+
+// GetRoot returns the root subcommand
+func GetRoot() *cobra.Command {
+	return rootCmd
+}
+
+// Execute adds all child commands to the root command and sets flags appropriately.
+// This is called by main.main(). It only needs to happen once to the rootCmd.
+func Execute() {
+	err := rootCmd.Execute()
+	if err != nil {
+		os.Exit(1)
+	}
+}
+
+func init() {
+	// Here you will define your flags and configuration settings.
+	// Cobra supports persistent flags, which, if defined here,
+	// will be global for your application.
+	// Cobra also supports local flags, which will only run
+	// when this action is called directly.
+	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}

+ 85 - 0
cli/cmd/network/create.go

@@ -0,0 +1,85 @@
+package network
+
+import (
+	"encoding/json"
+	"log"
+	"os"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/gravitl/netmaker/models"
+	"github.com/spf13/cobra"
+)
+
+var networkCreateCmd = &cobra.Command{
+	Use:   "create",
+	Short: "Create a Network",
+	Long:  `Create a Network`,
+	Args:  cobra.NoArgs,
+	Run: func(cmd *cobra.Command, args []string) {
+		network := &models.Network{}
+		if networkDefinitionFilePath != "" {
+			content, err := os.ReadFile(networkDefinitionFilePath)
+			if err != nil {
+				log.Fatal("Error when opening file: ", err)
+			}
+			if err := json.Unmarshal(content, network); err != nil {
+				log.Fatal(err)
+			}
+		} else {
+			network.NetID = netID
+			network.AddressRange = address
+			if address6 != "" {
+				network.AddressRange6 = address6
+				network.IsIPv6 = "yes"
+			}
+			if udpHolePunch {
+				network.DefaultUDPHolePunch = "yes"
+			}
+			if localNetwork {
+				network.IsLocal = "yes"
+			}
+			if defaultACL {
+				network.DefaultACL = "yes"
+			}
+			if pointToSite {
+				network.IsPointToSite = "yes"
+			}
+			network.DefaultInterface = defaultInterface
+			network.DefaultListenPort = int32(defaultListenPort)
+			network.NodeLimit = int32(nodeLimit)
+			network.DefaultPostUp = defaultPostUp
+			network.DefaultPostDown = defaultPostDown
+			network.DefaultKeepalive = int32(defaultKeepalive)
+			if allowManualSignUp {
+				network.AllowManualSignUp = "yes"
+			}
+			network.LocalRange = localRange
+			network.DefaultExtClientDNS = defaultExtClientDNS
+			network.DefaultMTU = int32(defaultMTU)
+		}
+		functions.PrettyPrint(functions.CreateNetwork(network))
+	},
+}
+
+func init() {
+	networkCreateCmd.Flags().StringVar(&networkDefinitionFilePath, "file", "", "Path to network_definition.json")
+	networkCreateCmd.Flags().StringVar(&netID, "name", "", "Name of the network")
+	networkCreateCmd.MarkFlagsMutuallyExclusive("file", "name")
+	networkCreateCmd.Flags().StringVar(&address, "ipv4_addr", "", "IPv4 address of the network")
+	networkCreateCmd.Flags().StringVar(&address6, "ipv6_addr", "", "IPv6 address of the network")
+	networkCreateCmd.Flags().BoolVar(&udpHolePunch, "udp_hole_punch", false, "Enable UDP Hole Punching ?")
+	networkCreateCmd.Flags().BoolVar(&localNetwork, "local", false, "Is the network local (LAN) ?")
+	networkCreateCmd.Flags().BoolVar(&defaultACL, "default_acl", false, "Enable default Access Control List ?")
+	networkCreateCmd.Flags().BoolVar(&pointToSite, "point_to_site", false, "Enforce all clients to have only 1 central peer ?")
+	networkCreateCmd.Flags().StringVar(&defaultInterface, "interface", "", "Name of the network interface")
+	networkCreateCmd.Flags().StringVar(&defaultPostUp, "post_up", "", "Commands to run after server is up `;` separated")
+	networkCreateCmd.Flags().StringVar(&defaultPostDown, "post_down", "", "Commands to run after server is down `;` separated")
+	networkCreateCmd.Flags().StringVar(&localRange, "local_range", "", "Local CIDR range")
+	networkCreateCmd.Flags().StringVar(&defaultExtClientDNS, "ext_client_dns", "", "IPv4 address of DNS server to be used by external clients")
+	networkCreateCmd.Flags().IntVar(&defaultListenPort, "listen_port", 51821, "Default wireguard port each node will attempt to use")
+	networkCreateCmd.Flags().IntVar(&nodeLimit, "node_limit", 999999999, "Maximum number of nodes that can be associated with this network")
+	networkCreateCmd.Flags().IntVar(&defaultKeepalive, "keep_alive", 20, "Keep Alive in seconds")
+	networkCreateCmd.Flags().IntVar(&defaultMTU, "mtu", 1280, "MTU size")
+	networkCreateCmd.Flags().BoolVar(&allowManualSignUp, "manual_signup", false, "Allow manual signup ?")
+	rootCmd.AddCommand(networkCreateCmd)
+}

+ 22 - 0
cli/cmd/network/delete.go

@@ -0,0 +1,22 @@
+package network
+
+import (
+	"fmt"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var networkDeleteCmd = &cobra.Command{
+	Use:   "delete [NAME]",
+	Short: "Delete a Network",
+	Long:  `Delete a Network`,
+	Args:  cobra.ExactArgs(1),
+	Run: func(cmd *cobra.Command, args []string) {
+		fmt.Println(*functions.DeleteNetwork(args[0]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(networkDeleteCmd)
+}

+ 22 - 0
cli/cmd/network/flags.go

@@ -0,0 +1,22 @@
+package network
+
+var (
+	networkDefinitionFilePath string
+	netID                     string
+	address                   string
+	address6                  string
+	udpHolePunch              bool
+	localNetwork              bool
+	defaultACL                bool
+	pointToSite               bool
+	defaultInterface          string
+	defaultListenPort         int
+	nodeLimit                 int
+	defaultPostUp             string
+	defaultPostDown           string
+	defaultKeepalive          int
+	allowManualSignUp         bool
+	localRange                string
+	defaultExtClientDNS       string
+	defaultMTU                int
+)

+ 20 - 0
cli/cmd/network/get.go

@@ -0,0 +1,20 @@
+package network
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var networkGetCmd = &cobra.Command{
+	Use:   "get [NETWORK NAME]",
+	Short: "Get a Network",
+	Long:  `Get a Network`,
+	Args:  cobra.ExactArgs(1),
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.GetNetwork(args[0]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(networkGetCmd)
+}

+ 32 - 0
cli/cmd/network/list.go

@@ -0,0 +1,32 @@
+package network
+
+import (
+	"os"
+	"time"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/olekukonko/tablewriter"
+	"github.com/spf13/cobra"
+)
+
+var networkListCmd = &cobra.Command{
+	Use:   "list",
+	Short: "List all Networks",
+	Long:  `List all Networks`,
+	Args:  cobra.NoArgs,
+	Run: func(cmd *cobra.Command, args []string) {
+		networks := functions.GetNetworks()
+		table := tablewriter.NewWriter(os.Stdout)
+		table.SetHeader([]string{"NetId", "Address Range (IPv4)", "Address Range (IPv6)", "Network Last Modified", "Nodes Last Modified"})
+		for _, n := range *networks {
+			networkLastModified := time.Unix(n.NetworkLastModified, 0).Format(time.RFC3339)
+			nodesLastModified := time.Unix(n.NodesLastModified, 0).Format(time.RFC3339)
+			table.Append([]string{n.NetID, n.AddressRange, n.AddressRange6, networkLastModified, nodesLastModified})
+		}
+		table.Render()
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(networkListCmd)
+}

+ 27 - 0
cli/cmd/network/node_limit.go

@@ -0,0 +1,27 @@
+package network
+
+import (
+	"log"
+	"strconv"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var networkNodeLimitCmd = &cobra.Command{
+	Use:   "node_limit [NETWORK NAME] [NEW LIMIT]",
+	Short: "Update network nodel limit",
+	Long:  `Update network nodel limit`,
+	Args:  cobra.ExactArgs(2),
+	Run: func(cmd *cobra.Command, args []string) {
+		nodelimit, err := strconv.ParseInt(args[1], 10, 32)
+		if err != nil {
+			log.Fatal(err)
+		}
+		functions.PrettyPrint(functions.UpdateNetworkNodeLimit(args[0], int32(nodelimit)))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(networkNodeLimitCmd)
+}

+ 20 - 0
cli/cmd/network/refresh_keys.go

@@ -0,0 +1,20 @@
+package network
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var networkRefreshKeysCmd = &cobra.Command{
+	Use:   "refresh_keys [NETWORK NAME]",
+	Short: "Refresh public and private key pairs of a network",
+	Long:  `Refresh public and private key pairs of a network`,
+	Args:  cobra.ExactArgs(1),
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.RefreshKeys(args[0]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(networkRefreshKeysCmd)
+}

+ 40 - 0
cli/cmd/network/root.go

@@ -0,0 +1,40 @@
+package network
+
+import (
+	"os"
+
+	"github.com/spf13/cobra"
+)
+
+// rootCmd represents the base command when called without any subcommands
+var rootCmd = &cobra.Command{
+	Use:   "network",
+	Short: "Manage Netmaker Networks",
+	Long:  `Manage Netmaker Networks`,
+	// Uncomment the following line if your bare application
+	// has an action associated with it:
+	// Run: func(cmd *cobra.Command, args []string) { },
+}
+
+// GetRoot returns the root subcommand
+func GetRoot() *cobra.Command {
+	return rootCmd
+}
+
+// Execute adds all child commands to the root command and sets flags appropriately.
+// This is called by main.main(). It only needs to happen once to the rootCmd.
+func Execute() {
+	err := rootCmd.Execute()
+	if err != nil {
+		os.Exit(1)
+	}
+}
+
+func init() {
+	// Here you will define your flags and configuration settings.
+	// Cobra supports persistent flags, which, if defined here,
+	// will be global for your application.
+	// Cobra also supports local flags, which will only run
+	// when this action is called directly.
+	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}

+ 86 - 0
cli/cmd/network/update.go

@@ -0,0 +1,86 @@
+package network
+
+import (
+	"encoding/json"
+	"log"
+	"os"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/gravitl/netmaker/models"
+	"github.com/spf13/cobra"
+)
+
+var networkUpdateCmd = &cobra.Command{
+	Use:   "update [NETWORK NAME]",
+	Short: "Update a Network",
+	Long:  `Update a Network`,
+	Args:  cobra.ExactArgs(1),
+	Run: func(cmd *cobra.Command, args []string) {
+		var (
+			networkName = args[0]
+			network     = &models.Network{}
+		)
+		if networkDefinitionFilePath != "" {
+			content, err := os.ReadFile(networkDefinitionFilePath)
+			if err != nil {
+				log.Fatal("Error when opening file: ", err)
+			}
+			if err := json.Unmarshal(content, network); err != nil {
+				log.Fatal(err)
+			}
+		} else {
+			network.NetID = networkName
+			network.AddressRange = address
+			if address6 != "" {
+				network.AddressRange6 = address6
+				network.IsIPv6 = "yes"
+			}
+			if udpHolePunch {
+				network.DefaultUDPHolePunch = "yes"
+			}
+			if localNetwork {
+				network.IsLocal = "yes"
+			}
+			if defaultACL {
+				network.DefaultACL = "yes"
+			}
+			if pointToSite {
+				network.IsPointToSite = "yes"
+			}
+			network.DefaultInterface = defaultInterface
+			network.DefaultListenPort = int32(defaultListenPort)
+			network.NodeLimit = int32(nodeLimit)
+			network.DefaultPostUp = defaultPostUp
+			network.DefaultPostDown = defaultPostDown
+			network.DefaultKeepalive = int32(defaultKeepalive)
+			if allowManualSignUp {
+				network.AllowManualSignUp = "yes"
+			}
+			network.LocalRange = localRange
+			network.DefaultExtClientDNS = defaultExtClientDNS
+			network.DefaultMTU = int32(defaultMTU)
+		}
+		functions.PrettyPrint(functions.UpdateNetwork(networkName, network))
+	},
+}
+
+func init() {
+	networkUpdateCmd.Flags().StringVar(&networkDefinitionFilePath, "file", "", "Path to network_definition.json")
+	networkUpdateCmd.Flags().StringVar(&address, "ipv4_addr", "", "IPv4 address of the network")
+	networkUpdateCmd.Flags().StringVar(&address6, "ipv6_addr", "", "IPv6 address of the network")
+	networkUpdateCmd.Flags().BoolVar(&udpHolePunch, "udp_hole_punch", false, "Enable UDP Hole Punching ?")
+	networkUpdateCmd.Flags().BoolVar(&localNetwork, "local", false, "Is the network local (LAN) ?")
+	networkUpdateCmd.Flags().BoolVar(&defaultACL, "default_acl", false, "Enable default Access Control List ?")
+	networkUpdateCmd.Flags().BoolVar(&pointToSite, "point_to_site", false, "Enforce all clients to have only 1 central peer ?")
+	networkUpdateCmd.Flags().StringVar(&defaultInterface, "interface", "", "Name of the network interface")
+	networkUpdateCmd.Flags().StringVar(&defaultPostUp, "post_up", "", "Commands to run after server is up `;` separated")
+	networkUpdateCmd.Flags().StringVar(&defaultPostDown, "post_down", "", "Commands to run after server is down `;` separated")
+	networkUpdateCmd.Flags().StringVar(&localRange, "local_range", "", "Local CIDR range")
+	networkUpdateCmd.Flags().StringVar(&defaultExtClientDNS, "ext_client_dns", "", "IPv4 address of DNS server to be used by external clients")
+	networkUpdateCmd.Flags().IntVar(&defaultListenPort, "listen_port", 0, "Default wireguard port each node will attempt to use")
+	networkUpdateCmd.Flags().IntVar(&nodeLimit, "node_limit", 0, "Maximum number of nodes that can be associated with this network")
+	networkUpdateCmd.Flags().IntVar(&defaultKeepalive, "keep_alive", 0, "Keep Alive in seconds")
+	networkUpdateCmd.Flags().IntVar(&defaultMTU, "mtu", 0, "MTU size")
+	networkUpdateCmd.Flags().BoolVar(&allowManualSignUp, "manual_signup", false, "Allow manual signup ?")
+	rootCmd.AddCommand(networkUpdateCmd)
+}

+ 43 - 0
cli/cmd/network_user/create.go

@@ -0,0 +1,43 @@
+package network_user
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/gravitl/netmaker/models/promodels"
+	"github.com/spf13/cobra"
+)
+
+var networkuserCreateCmd = &cobra.Command{
+	Use:   "create [NETWORK NAME]",
+	Args:  cobra.ExactArgs(1),
+	Short: "Create a network user",
+	Long:  `Create a network user`,
+	Run: func(cmd *cobra.Command, args []string) {
+		user := &promodels.NetworkUser{
+			AccessLevel: accessLevel,
+			ClientLimit: clientLimit,
+			NodeLimit:   nodeLimit, ID: promodels.NetworkUserID(id),
+		}
+		if clients != "" {
+			user.Clients = strings.Split(clients, ",")
+		}
+		if nodes != "" {
+			user.Nodes = strings.Split(nodes, ",")
+		}
+		functions.CreateNetworkUser(args[0], user)
+		fmt.Println("Success")
+	},
+}
+
+func init() {
+	networkuserCreateCmd.Flags().IntVar(&accessLevel, "access_level", 0, "Custom access level")
+	networkuserCreateCmd.Flags().IntVar(&clientLimit, "client_limit", 0, "Maximum number of external clients that can be created")
+	networkuserCreateCmd.Flags().IntVar(&nodeLimit, "node_limit", 999999999, "Maximum number of nodes that can be attached to a network")
+	networkuserCreateCmd.Flags().StringVar(&clients, "clients", "", "Access to list of external clients (comma separated)")
+	networkuserCreateCmd.Flags().StringVar(&nodes, "nodes", "", "Access to list of nodes (comma separated)")
+	networkuserCreateCmd.Flags().StringVar(&id, "id", "", "ID of the network user")
+	networkuserCreateCmd.MarkFlagRequired("id")
+	rootCmd.AddCommand(networkuserCreateCmd)
+}

+ 23 - 0
cli/cmd/network_user/delete.go

@@ -0,0 +1,23 @@
+package network_user
+
+import (
+	"fmt"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var networkuserDeleteCmd = &cobra.Command{
+	Use:   "delete [NETWORK NAME] [NETWORK USER NAME]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Delete a network user",
+	Long:  `Delete a network user`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.DeleteNetworkUser(args[0], args[1])
+		fmt.Println("Success")
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(networkuserDeleteCmd)
+}

+ 10 - 0
cli/cmd/network_user/flags.go

@@ -0,0 +1,10 @@
+package network_user
+
+var (
+	accessLevel int
+	clientLimit int
+	nodeLimit   int
+	clients     string
+	nodes       string
+	id          string
+)

+ 27 - 0
cli/cmd/network_user/get.go

@@ -0,0 +1,27 @@
+package network_user
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var data bool
+
+var networkuserGetCmd = &cobra.Command{
+	Use:   "get [NETWORK NAME] [NETWORK USER NAME]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Fetch a network user",
+	Long:  `Fetch a network user`,
+	Run: func(cmd *cobra.Command, args []string) {
+		if data {
+			functions.PrettyPrint(functions.GetNetworkUserData(args[1]))
+		} else {
+			functions.PrettyPrint(functions.GetNetworkUser(args[0], args[1]))
+		}
+	},
+}
+
+func init() {
+	networkuserGetCmd.Flags().BoolVar(&data, "data", false, "Fetch entire data of a network user")
+	rootCmd.AddCommand(networkuserGetCmd)
+}

+ 27 - 0
cli/cmd/network_user/list.go

@@ -0,0 +1,27 @@
+package network_user
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var networkName string
+
+var networkuserListCmd = &cobra.Command{
+	Use:   "list",
+	Args:  cobra.NoArgs,
+	Short: "List network users",
+	Long:  `List network users`,
+	Run: func(cmd *cobra.Command, args []string) {
+		if networkName != "" {
+			functions.PrettyPrint(functions.GetNetworkUsers(networkName))
+		} else {
+			functions.PrettyPrint(functions.GetAllNetworkUsers())
+		}
+	},
+}
+
+func init() {
+	networkuserListCmd.Flags().StringVar(&networkName, "network", "", "Name of the network")
+	rootCmd.AddCommand(networkuserListCmd)
+}

+ 38 - 0
cli/cmd/network_user/root.go

@@ -0,0 +1,38 @@
+package network_user
+
+import (
+	"os"
+
+	"github.com/spf13/cobra"
+)
+
+// rootCmd represents the base command when called without any subcommands
+var rootCmd = &cobra.Command{
+	Use:   "network_user",
+	Short: "Manage Network Users",
+	Long:  `Manage Network Users`,
+	// Run: func(cmd *cobra.Command, args []string) { },
+}
+
+// GetRoot returns the root subcommand
+func GetRoot() *cobra.Command {
+	return rootCmd
+}
+
+// Execute adds all child commands to the root command and sets flags appropriately.
+// This is called by main.main(). It only needs to happen once to the rootCmd.
+func Execute() {
+	err := rootCmd.Execute()
+	if err != nil {
+		os.Exit(1)
+	}
+}
+
+func init() {
+	// Here you will define your flags and configuration settings.
+	// Cobra supports persistent flags, which, if defined here,
+	// will be global for your application.
+	// Cobra also supports local flags, which will only run
+	// when this action is called directly.
+	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}

+ 43 - 0
cli/cmd/network_user/update.go

@@ -0,0 +1,43 @@
+package network_user
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/gravitl/netmaker/models/promodels"
+	"github.com/spf13/cobra"
+)
+
+var networkuserUpdateCmd = &cobra.Command{
+	Use:   "update [NETWORK NAME]",
+	Args:  cobra.ExactArgs(1),
+	Short: "Update a network user",
+	Long:  `Update a network user`,
+	Run: func(cmd *cobra.Command, args []string) {
+		user := &promodels.NetworkUser{
+			AccessLevel: accessLevel,
+			ClientLimit: clientLimit,
+			NodeLimit:   nodeLimit, ID: promodels.NetworkUserID(id),
+		}
+		if clients != "" {
+			user.Clients = strings.Split(clients, ",")
+		}
+		if nodes != "" {
+			user.Nodes = strings.Split(nodes, ",")
+		}
+		functions.UpdateNetworkUser(args[0], user)
+		fmt.Println("Success")
+	},
+}
+
+func init() {
+	networkuserUpdateCmd.Flags().IntVar(&accessLevel, "access_level", 0, "Custom access level")
+	networkuserUpdateCmd.Flags().IntVar(&clientLimit, "client_limit", 0, "Maximum number of external clients that can be created")
+	networkuserUpdateCmd.Flags().IntVar(&nodeLimit, "node_limit", 999999999, "Maximum number of nodes that can be attached to a network")
+	networkuserUpdateCmd.Flags().StringVar(&clients, "clients", "", "Access to list of external clients (comma separated)")
+	networkuserUpdateCmd.Flags().StringVar(&nodes, "nodes", "", "Access to list of nodes (comma separated)")
+	networkuserUpdateCmd.Flags().StringVar(&id, "id", "", "ID of the network user")
+	networkuserUpdateCmd.MarkFlagRequired("id")
+	rootCmd.AddCommand(networkuserUpdateCmd)
+}

+ 34 - 0
cli/cmd/node/create_egress.go

@@ -0,0 +1,34 @@
+package node
+
+import (
+	"strings"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/gravitl/netmaker/models"
+	"github.com/spf13/cobra"
+)
+
+var nodeCreateEgressCmd = &cobra.Command{
+	Use:   "create_egress [NETWORK NAME] [NODE ID] [EGRESS GATEWAY ADDRESSES (comma separated)]",
+	Args:  cobra.ExactArgs(3),
+	Short: "Turn a Node into a Egress",
+	Long:  `Turn a Node into a Egress`,
+	Run: func(cmd *cobra.Command, args []string) {
+		egress := &models.EgressGatewayRequest{
+			NetID:     args[0],
+			NodeID:    args[1],
+			Interface: networkInterface,
+			Ranges:    strings.Split(args[2], ","),
+		}
+		if natEnabled {
+			egress.NatEnabled = "yes"
+		}
+		functions.PrettyPrint(functions.CreateEgress(args[0], args[1], egress))
+	},
+}
+
+func init() {
+	nodeCreateEgressCmd.Flags().StringVar(&networkInterface, "interface", "", "Network interface name (ex:- eth0)")
+	nodeCreateEgressCmd.Flags().BoolVar(&natEnabled, "nat", false, "Enable NAT for Egress Traffic ?")
+	rootCmd.AddCommand(nodeCreateEgressCmd)
+}

+ 21 - 0
cli/cmd/node/create_ingress.go

@@ -0,0 +1,21 @@
+package node
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var nodeCreateIngressCmd = &cobra.Command{
+	Use:   "create_ingress [NETWORK NAME] [NODE ID]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Turn a Node into a Ingress",
+	Long:  `Turn a Node into a Ingress`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.CreateIngress(args[0], args[1], failover))
+	},
+}
+
+func init() {
+	nodeCreateIngressCmd.Flags().BoolVar(&failover, "failover", false, "Enable FailOver ?")
+	rootCmd.AddCommand(nodeCreateIngressCmd)
+}

+ 22 - 0
cli/cmd/node/create_relay.go

@@ -0,0 +1,22 @@
+package node
+
+import (
+	"strings"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var nodeCreateRelayCmd = &cobra.Command{
+	Use:   "create_relay [NETWORK NAME] [NODE ID] [RELAY ADDRESSES (comma separated)]",
+	Args:  cobra.ExactArgs(3),
+	Short: "Turn a Node into a Relay",
+	Long:  `Turn a Node into a Relay`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.CreateRelay(args[0], args[1], strings.Split(args[2], ",")))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(nodeCreateRelayCmd)
+}

+ 20 - 0
cli/cmd/node/delete.go

@@ -0,0 +1,20 @@
+package node
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var nodeDeleteCmd = &cobra.Command{
+	Use:   "delete [NETWORK NAME] [NODE ID]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Delete a Node",
+	Long:  `Delete a Node`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.DeleteNode(args[0], args[1]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(nodeDeleteCmd)
+}

+ 20 - 0
cli/cmd/node/delete_egress.go

@@ -0,0 +1,20 @@
+package node
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var nodeDeleteEgressCmd = &cobra.Command{
+	Use:   "delete_egress [NETWORK NAME] [NODE ID]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Delete Egress role from a Node",
+	Long:  `Delete Egress role from a Node`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.DeleteEgress(args[0], args[1]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(nodeDeleteEgressCmd)
+}

+ 20 - 0
cli/cmd/node/delete_ingress.go

@@ -0,0 +1,20 @@
+package node
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var nodeDeleteIngressCmd = &cobra.Command{
+	Use:   "delete_ingress [NETWORK NAME] [NODE ID]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Delete Ingress role from a Node",
+	Long:  `Delete Ingress role from a Node`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.DeleteIngress(args[0], args[1]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(nodeDeleteIngressCmd)
+}

+ 20 - 0
cli/cmd/node/delete_relay.go

@@ -0,0 +1,20 @@
+package node
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var nodeDeleteRelayCmd = &cobra.Command{
+	Use:   "delete_relay [NETWORK NAME] [NODE ID]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Delete Relay role from a Node",
+	Long:  `Delete Relay role from a Node`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.DeleteRelay(args[0], args[1]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(nodeDeleteRelayCmd)
+}

+ 28 - 0
cli/cmd/node/flags.go

@@ -0,0 +1,28 @@
+package node
+
+var (
+	networkInterface       string
+	natEnabled             bool
+	failover               bool
+	networkName            string
+	nodeDefinitionFilePath string
+	endpoint               string
+	listenPort             int
+	address                string
+	address6               string
+	localAddress           string
+	name                   string
+	postUp                 string
+	postDown               string
+	allowedIPs             string
+	keepAlive              int
+	relayAddrs             string
+	egressGatewayRanges    string
+	localRange             string
+	mtu                    int
+	expirationDateTime     int
+	defaultACL             bool
+	dnsOn                  bool
+	disconnect             bool
+	networkHub             bool
+)

+ 20 - 0
cli/cmd/node/get.go

@@ -0,0 +1,20 @@
+package node
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var nodeGetCmd = &cobra.Command{
+	Use:   "get [NETWORK NAME] [NODE ID]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Get a node by ID",
+	Long:  `Get a node by ID`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.GetNodeByID(args[0], args[1]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(nodeGetCmd)
+}

+ 47 - 0
cli/cmd/node/list.go

@@ -0,0 +1,47 @@
+package node
+
+import (
+	"os"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/gravitl/netmaker/models"
+	"github.com/guumaster/tablewriter"
+	"github.com/spf13/cobra"
+)
+
+// nodeListCmd lists all nodes
+var nodeListCmd = &cobra.Command{
+	Use:   "list",
+	Args:  cobra.NoArgs,
+	Short: "List all nodes",
+	Long:  `List all nodes`,
+	Run: func(cmd *cobra.Command, args []string) {
+		var data []models.Node
+		if networkName != "" {
+			data = *functions.GetNodes(networkName)
+		} else {
+			data = *functions.GetNodes()
+		}
+		table := tablewriter.NewWriter(os.Stdout)
+		table.SetHeader([]string{"Name", "Addresses", "Version", "Network", "Egress", "Ingress", "Relay", "ID"})
+		for _, d := range data {
+			addresses := ""
+			if d.Address != "" {
+				addresses += d.Address
+			}
+			if d.Address6 != "" {
+				if d.Address != "" {
+					addresses += ", "
+				}
+				addresses += d.Address6
+			}
+			table.Append([]string{d.Name, addresses, d.Version, d.Network, d.IsEgressGateway, d.IsIngressGateway, d.IsRelay, d.ID})
+		}
+		table.Render()
+	},
+}
+
+func init() {
+	nodeListCmd.Flags().StringVar(&networkName, "network", "", "Network name specifier")
+	rootCmd.AddCommand(nodeListCmd)
+}

+ 38 - 0
cli/cmd/node/root.go

@@ -0,0 +1,38 @@
+package node
+
+import (
+	"os"
+
+	"github.com/spf13/cobra"
+)
+
+// rootCmd represents the base command when called without any subcommands
+var rootCmd = &cobra.Command{
+	Use:   "node",
+	Short: "Manage nodes associated with a network",
+	Long:  `Manage nodes associated with a network`,
+	// Run: func(cmd *cobra.Command, args []string) { },
+}
+
+// GetRoot returns the root subcommand
+func GetRoot() *cobra.Command {
+	return rootCmd
+}
+
+// Execute adds all child commands to the root command and sets flags appropriately.
+// This is called by main.main(). It only needs to happen once to the rootCmd.
+func Execute() {
+	err := rootCmd.Execute()
+	if err != nil {
+		os.Exit(1)
+	}
+}
+
+func init() {
+	// Here you will define your flags and configuration settings.
+	// Cobra supports persistent flags, which, if defined here,
+	// will be global for your application.
+	// Cobra also supports local flags, which will only run
+	// when this action is called directly.
+	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}

+ 22 - 0
cli/cmd/node/uncordon.go

@@ -0,0 +1,22 @@
+package node
+
+import (
+	"fmt"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var nodeUncordonCmd = &cobra.Command{
+	Use:   "uncordon [NETWORK NAME] [NODE ID]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Get a node by ID",
+	Long:  `Get a node by ID`,
+	Run: func(cmd *cobra.Command, args []string) {
+		fmt.Println(*functions.UncordonNode(args[0], args[1]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(nodeUncordonCmd)
+}

+ 100 - 0
cli/cmd/node/update.go

@@ -0,0 +1,100 @@
+package node
+
+import (
+	"encoding/json"
+	"log"
+	"os"
+	"strings"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/gravitl/netmaker/models"
+	"github.com/spf13/cobra"
+)
+
+var nodeUpdateCmd = &cobra.Command{
+	Use:   "update [NETWORK NAME] [NODE ID]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Update a Node",
+	Long:  `Update a Node`,
+	Run: func(cmd *cobra.Command, args []string) {
+		var (
+			node        = &models.Node{}
+			networkName = args[0]
+			nodeID      = args[1]
+		)
+		if nodeDefinitionFilePath != "" {
+			content, err := os.ReadFile(nodeDefinitionFilePath)
+			if err != nil {
+				log.Fatal("Error when opening file: ", err)
+			}
+			if err := json.Unmarshal(content, node); err != nil {
+				log.Fatal(err)
+			}
+		} else {
+			if endpoint != "" {
+				node.Endpoint = endpoint
+				node.IsStatic = "no"
+			}
+			node.ListenPort = int32(listenPort)
+			node.Address = address
+			node.Address6 = address6
+			node.LocalAddress = localAddress
+			node.Name = name
+			node.PostUp = postUp
+			node.PostDown = postDown
+			if allowedIPs != "" {
+				node.AllowedIPs = strings.Split(allowedIPs, ",")
+			}
+			node.PersistentKeepalive = int32(keepAlive)
+			if relayAddrs != "" {
+				node.RelayAddrs = strings.Split(relayAddrs, ",")
+			}
+			if egressGatewayRanges != "" {
+				node.EgressGatewayRanges = strings.Split(egressGatewayRanges, ",")
+			}
+			if localRange != "" {
+				node.LocalRange = localRange
+				node.IsLocal = "yes"
+			}
+			node.MTU = int32(mtu)
+			node.ExpirationDateTime = int64(expirationDateTime)
+			if defaultACL {
+				node.DefaultACL = "yes"
+			}
+			if dnsOn {
+				node.DNSOn = "yes"
+			}
+			if disconnect {
+				node.Connected = "no"
+			}
+			if networkHub {
+				node.IsHub = "yes"
+			}
+		}
+		functions.PrettyPrint(functions.UpdateNode(networkName, nodeID, node))
+	},
+}
+
+func init() {
+	nodeUpdateCmd.Flags().StringVar(&nodeDefinitionFilePath, "file", "", "Filepath of updated node definition in JSON")
+	nodeUpdateCmd.Flags().StringVar(&endpoint, "endpoint", "", "Public endpoint of the node")
+	nodeUpdateCmd.Flags().IntVar(&listenPort, "listen_port", 0, "Default wireguard port for the node")
+	nodeUpdateCmd.Flags().StringVar(&address, "ipv4_addr", "", "IPv4 address of the node")
+	nodeUpdateCmd.Flags().StringVar(&address6, "ipv6_addr", "", "IPv6 address of the node")
+	nodeUpdateCmd.Flags().StringVar(&localAddress, "local_addr", "", "Locally reachable address of the node")
+	nodeUpdateCmd.Flags().StringVar(&name, "name", "", "Node name")
+	nodeUpdateCmd.Flags().StringVar(&postUp, "post_up", "", "Commands to run after node is up `;` separated")
+	nodeUpdateCmd.Flags().StringVar(&postDown, "post_down", "", "Commands to run after node is down `;` separated")
+	nodeUpdateCmd.Flags().StringVar(&allowedIPs, "allowed_addrs", "", "Additional private addresses given to the node (comma separated)")
+	nodeUpdateCmd.Flags().IntVar(&keepAlive, "keep_alive", 0, "Interval in which packets are sent to keep connections open with peers")
+	nodeUpdateCmd.Flags().StringVar(&relayAddrs, "relay_addrs", "", "Addresses for relaying connections if node acts as a relay")
+	nodeUpdateCmd.Flags().StringVar(&egressGatewayRanges, "egress_addrs", "", "Addresses for egressing traffic if node acts as an egress")
+	nodeUpdateCmd.Flags().StringVar(&localRange, "local_range", "", "Local range in which the node will look for private addresses to use as an endpoint if `LocalNetwork` is enabled")
+	nodeUpdateCmd.Flags().IntVar(&mtu, "mtu", 0, "MTU size")
+	nodeUpdateCmd.Flags().IntVar(&expirationDateTime, "expiry", 0, "UNIX timestamp after which node will lose access to the network")
+	nodeUpdateCmd.Flags().BoolVar(&defaultACL, "acl", false, "Enable default ACL ?")
+	nodeUpdateCmd.Flags().BoolVar(&dnsOn, "dns", false, "Setup DNS entries for peers locally ?")
+	nodeUpdateCmd.Flags().BoolVar(&disconnect, "disconnect", false, "Disconnect from the network ?")
+	nodeUpdateCmd.Flags().BoolVar(&networkHub, "hub", false, "On a point to site network, this node is the only one which all peers connect to ?")
+	rootCmd.AddCommand(nodeUpdateCmd)
+}

+ 69 - 0
cli/cmd/root.go

@@ -0,0 +1,69 @@
+package cmd
+
+import (
+	"os"
+
+	"github.com/gravitl/netmaker/cli/cmd/acl"
+	"github.com/gravitl/netmaker/cli/cmd/context"
+	"github.com/gravitl/netmaker/cli/cmd/dns"
+	"github.com/gravitl/netmaker/cli/cmd/ext_client"
+	"github.com/gravitl/netmaker/cli/cmd/keys"
+	"github.com/gravitl/netmaker/cli/cmd/metrics"
+	"github.com/gravitl/netmaker/cli/cmd/network"
+	"github.com/gravitl/netmaker/cli/cmd/network_user"
+	"github.com/gravitl/netmaker/cli/cmd/node"
+	"github.com/gravitl/netmaker/cli/cmd/server"
+	"github.com/gravitl/netmaker/cli/cmd/user"
+	"github.com/gravitl/netmaker/cli/cmd/usergroup"
+	"github.com/spf13/cobra"
+)
+
+// rootCmd represents the base command when called without any subcommands
+var rootCmd = &cobra.Command{
+	Use:   "netmaker",
+	Short: "CLI for interacting with Netmaker Server",
+	Long:  `CLI for interacting with Netmaker Server`,
+	// Uncomment the following line if your bare application
+	// has an action associated with it:
+	// Run: func(cmd *cobra.Command, args []string) { },
+}
+
+// GetRoot returns the root of all subcommands
+func GetRoot() *cobra.Command {
+	return rootCmd
+}
+
+// Execute adds all child commands to the root command and sets flags appropriately.
+// This is called by main.main(). It only needs to happen once to the rootCmd.
+func Execute() {
+	err := rootCmd.Execute()
+	if err != nil {
+		os.Exit(1)
+	}
+}
+
+func init() {
+	// Here you will define your flags and configuration settings.
+	// Cobra supports persistent flags, which, if defined here,
+	// will be global for your application.
+
+	// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.tctl.yaml)")
+
+	// Cobra also supports local flags, which will only run
+	// when this action is called directly.
+	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+
+	// IMP: Bind subcommands here
+	rootCmd.AddCommand(network.GetRoot())
+	rootCmd.AddCommand(context.GetRoot())
+	rootCmd.AddCommand(keys.GetRoot())
+	rootCmd.AddCommand(acl.GetRoot())
+	rootCmd.AddCommand(node.GetRoot())
+	rootCmd.AddCommand(dns.GetRoot())
+	rootCmd.AddCommand(server.GetRoot())
+	rootCmd.AddCommand(ext_client.GetRoot())
+	rootCmd.AddCommand(user.GetRoot())
+	rootCmd.AddCommand(usergroup.GetRoot())
+	rootCmd.AddCommand(metrics.GetRoot())
+	rootCmd.AddCommand(network_user.GetRoot())
+}

+ 20 - 0
cli/cmd/server/config.go

@@ -0,0 +1,20 @@
+package server
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var serverConfigCmd = &cobra.Command{
+	Use:   "config",
+	Args:  cobra.NoArgs,
+	Short: "Retrieve server config",
+	Long:  `Retrieve server config`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.GetServerConfig())
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(serverConfigCmd)
+}

+ 22 - 0
cli/cmd/server/has_admin.go

@@ -0,0 +1,22 @@
+package server
+
+import (
+	"fmt"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var serverHasAdminCmd = &cobra.Command{
+	Use:   "has_admin",
+	Args:  cobra.NoArgs,
+	Short: "Check if server has an admin",
+	Long:  `Check if server has an admin`,
+	Run: func(cmd *cobra.Command, args []string) {
+		fmt.Println(*functions.HasAdmin())
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(serverHasAdminCmd)
+}

+ 20 - 0
cli/cmd/server/health.go

@@ -0,0 +1,20 @@
+package server
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var serverHealthCmd = &cobra.Command{
+	Use:   "health",
+	Args:  cobra.NoArgs,
+	Short: "View server health",
+	Long:  `View server health`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.GetServerHealth())
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(serverHealthCmd)
+}

+ 20 - 0
cli/cmd/server/info.go

@@ -0,0 +1,20 @@
+package server
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var serverInfoCmd = &cobra.Command{
+	Use:   "info",
+	Args:  cobra.NoArgs,
+	Short: "Retrieve server information",
+	Long:  `Retrieve server information`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.GetServerInfo())
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(serverInfoCmd)
+}

+ 38 - 0
cli/cmd/server/root.go

@@ -0,0 +1,38 @@
+package server
+
+import (
+	"os"
+
+	"github.com/spf13/cobra"
+)
+
+// rootCmd represents the base command when called without any subcommands
+var rootCmd = &cobra.Command{
+	Use:   "server",
+	Short: "Get netmaker server information",
+	Long:  `Get netmaker server information`,
+	// Run: func(cmd *cobra.Command, args []string) { },
+}
+
+// GetRoot returns the root subcommand
+func GetRoot() *cobra.Command {
+	return rootCmd
+}
+
+// Execute adds all child commands to the root command and sets flags appropriately.
+// This is called by main.main(). It only needs to happen once to the rootCmd.
+func Execute() {
+	err := rootCmd.Execute()
+	if err != nil {
+		os.Exit(1)
+	}
+}
+
+func init() {
+	// Here you will define your flags and configuration settings.
+	// Cobra supports persistent flags, which, if defined here,
+	// will be global for your application.
+	// Cobra also supports local flags, which will only run
+	// when this action is called directly.
+	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}

+ 37 - 0
cli/cmd/user/create.go

@@ -0,0 +1,37 @@
+package user
+
+import (
+	"strings"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/gravitl/netmaker/models"
+	"github.com/spf13/cobra"
+)
+
+var userCreateCmd = &cobra.Command{
+	Use:   "create",
+	Args:  cobra.NoArgs,
+	Short: "Create a new user",
+	Long:  `Create a new user`,
+	Run: func(cmd *cobra.Command, args []string) {
+		user := &models.User{UserName: username, Password: password, IsAdmin: admin}
+		if networks != "" {
+			user.Networks = strings.Split(networks, ",")
+		}
+		if groups != "" {
+			user.Groups = strings.Split(groups, ",")
+		}
+		functions.PrettyPrint(functions.CreateUser(user))
+	},
+}
+
+func init() {
+	userCreateCmd.Flags().StringVar(&username, "name", "", "Name of the user")
+	userCreateCmd.Flags().StringVar(&password, "password", "", "Password of the user")
+	userCreateCmd.MarkFlagRequired("name")
+	userCreateCmd.MarkFlagRequired("password")
+	userCreateCmd.Flags().BoolVar(&admin, "admin", false, "Make the user an admin ?")
+	userCreateCmd.Flags().StringVar(&networks, "networks", "", "List of networks the user will access to (comma separated)")
+	userCreateCmd.Flags().StringVar(&groups, "groups", "", "List of user groups the user will be part of (comma separated)")
+	rootCmd.AddCommand(userCreateCmd)
+}

+ 20 - 0
cli/cmd/user/delete.go

@@ -0,0 +1,20 @@
+package user
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var userDeleteCmd = &cobra.Command{
+	Use:   "delete [USER NAME]",
+	Args:  cobra.ExactArgs(1),
+	Short: "Delete a user",
+	Long:  `Delete a user`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(*functions.DeleteUser(args[0]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(userDeleteCmd)
+}

+ 9 - 0
cli/cmd/user/flags.go

@@ -0,0 +1,9 @@
+package user
+
+var (
+	username string
+	password string
+	admin    bool
+	networks string
+	groups   string
+)

+ 20 - 0
cli/cmd/user/get.go

@@ -0,0 +1,20 @@
+package user
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var userGetCmd = &cobra.Command{
+	Use:   "get [USER NAME]",
+	Args:  cobra.ExactArgs(1),
+	Short: "Get a user",
+	Long:  `Get a user`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.GetUser(args[0]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(userGetCmd)
+}

+ 30 - 0
cli/cmd/user/list.go

@@ -0,0 +1,30 @@
+package user
+
+import (
+	"os"
+	"strconv"
+	"strings"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/guumaster/tablewriter"
+	"github.com/spf13/cobra"
+)
+
+var userListCmd = &cobra.Command{
+	Use:   "list",
+	Args:  cobra.NoArgs,
+	Short: "List all users",
+	Long:  `List all users`,
+	Run: func(cmd *cobra.Command, args []string) {
+		table := tablewriter.NewWriter(os.Stdout)
+		table.SetHeader([]string{"Name", "Admin", "Networks", "Groups"})
+		for _, d := range *functions.ListUsers() {
+			table.Append([]string{d.UserName, strconv.FormatBool(d.IsAdmin), strings.Join(d.Networks, ", "), strings.Join(d.Groups, ", ")})
+		}
+		table.Render()
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(userListCmd)
+}

+ 38 - 0
cli/cmd/user/root.go

@@ -0,0 +1,38 @@
+package user
+
+import (
+	"os"
+
+	"github.com/spf13/cobra"
+)
+
+// rootCmd represents the base command when called without any subcommands
+var rootCmd = &cobra.Command{
+	Use:   "user",
+	Short: "Manage users and permissions",
+	Long:  `Manage users and permissions`,
+	// Run: func(cmd *cobra.Command, args []string) { },
+}
+
+// GetRoot returns the root subcommand
+func GetRoot() *cobra.Command {
+	return rootCmd
+}
+
+// Execute adds all child commands to the root command and sets flags appropriately.
+// This is called by main.main(). It only needs to happen once to the rootCmd.
+func Execute() {
+	err := rootCmd.Execute()
+	if err != nil {
+		os.Exit(1)
+	}
+}
+
+func init() {
+	// Here you will define your flags and configuration settings.
+	// Cobra supports persistent flags, which, if defined here,
+	// will be global for your application.
+	// Cobra also supports local flags, which will only run
+	// when this action is called directly.
+	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}

+ 35 - 0
cli/cmd/user/update.go

@@ -0,0 +1,35 @@
+package user
+
+import (
+	"strings"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/gravitl/netmaker/models"
+	"github.com/spf13/cobra"
+)
+
+var userUpdateCmd = &cobra.Command{
+	Use:   "update [USER NAME]",
+	Args:  cobra.ExactArgs(1),
+	Short: "Update a user",
+	Long:  `Update a user`,
+	Run: func(cmd *cobra.Command, args []string) {
+		user := &models.User{UserName: args[0], IsAdmin: admin}
+		if networks != "" {
+			user.Networks = strings.Split(networks, ",")
+		}
+		if groups != "" {
+			user.Groups = strings.Split(groups, ",")
+		} else {
+			user.Groups = []string{"*"}
+		}
+		functions.PrettyPrint(functions.UpdateUser(user))
+	},
+}
+
+func init() {
+	userUpdateCmd.Flags().BoolVar(&admin, "admin", false, "Make the user an admin ?")
+	userUpdateCmd.Flags().StringVar(&networks, "networks", "", "List of networks the user will access to (comma separated)")
+	userUpdateCmd.Flags().StringVar(&groups, "groups", "", "List of user groups the user will be part of (comma separated)")
+	rootCmd.AddCommand(userUpdateCmd)
+}

+ 23 - 0
cli/cmd/usergroup/create.go

@@ -0,0 +1,23 @@
+package usergroup
+
+import (
+	"fmt"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var usergroupCreateCmd = &cobra.Command{
+	Use:   "create [GROUP NAME]",
+	Args:  cobra.ExactArgs(1),
+	Short: "Create a usergroup",
+	Long:  `Create a usergroup`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.CreateUsergroup(args[0])
+		fmt.Println("Success")
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(usergroupCreateCmd)
+}

+ 23 - 0
cli/cmd/usergroup/delete.go

@@ -0,0 +1,23 @@
+package usergroup
+
+import (
+	"fmt"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var usergroupDeleteCmd = &cobra.Command{
+	Use:   "delete [GROUP NAME]",
+	Args:  cobra.ExactArgs(1),
+	Short: "Delete a usergroup",
+	Long:  `Delete a usergroup`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.DeleteUsergroup(args[0])
+		fmt.Println("Success")
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(usergroupDeleteCmd)
+}

+ 20 - 0
cli/cmd/usergroup/get.go

@@ -0,0 +1,20 @@
+package usergroup
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var usergroupGetCmd = &cobra.Command{
+	Use:   "get",
+	Args:  cobra.NoArgs,
+	Short: "Fetch all usergroups",
+	Long:  `Fetch all usergroups`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.GetUsergroups())
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(usergroupGetCmd)
+}

+ 38 - 0
cli/cmd/usergroup/root.go

@@ -0,0 +1,38 @@
+package usergroup
+
+import (
+	"os"
+
+	"github.com/spf13/cobra"
+)
+
+// rootCmd represents the base command when called without any subcommands
+var rootCmd = &cobra.Command{
+	Use:   "usergroup",
+	Short: "Manage User Groups",
+	Long:  `Manage User Groups`,
+	// Run: func(cmd *cobra.Command, args []string) { },
+}
+
+// GetRoot returns the root subcommand
+func GetRoot() *cobra.Command {
+	return rootCmd
+}
+
+// Execute adds all child commands to the root command and sets flags appropriately.
+// This is called by main.main(). It only needs to happen once to the rootCmd.
+func Execute() {
+	err := rootCmd.Execute()
+	if err != nil {
+		os.Exit(1)
+	}
+}
+
+func init() {
+	// Here you will define your flags and configuration settings.
+	// Cobra supports persistent flags, which, if defined here,
+	// will be global for your application.
+	// Cobra also supports local flags, which will only run
+	// when this action is called directly.
+	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}

+ 144 - 0
cli/config/config.go

@@ -0,0 +1,144 @@
+package config
+
+import (
+	"fmt"
+	"log"
+	"os"
+	"path/filepath"
+
+	"gopkg.in/yaml.v3"
+)
+
+// Context maintains configuration for interaction with Netmaker API
+type Context struct {
+	Endpoint  string `yaml:"endpoint"`
+	Username  string `yaml:"username,omitempty"`
+	Password  string `yaml:"password,omitempty"`
+	MasterKey string `yaml:"masterkey,omitempty"`
+	Current   bool   `yaml:"current,omitempty"`
+	AuthToken string `yaml:"auth_token,omitempty"`
+}
+
+var (
+	contextMap     = map[string]Context{}
+	configFilePath string
+	filename       string
+)
+
+func createConfigPathIfNotExists() {
+	homeDir, err := os.UserHomeDir()
+	if err != nil {
+		log.Fatal(err)
+	}
+	configFilePath = filepath.Join(homeDir, ".netmaker")
+	// create directory if not exists
+	if err := os.MkdirAll(configFilePath, os.ModePerm); err != nil {
+		log.Fatal(err)
+	}
+	filename = filepath.Join(configFilePath, "config.yml")
+	// create file if not exists
+	if _, err := os.Stat(filename); err != nil {
+		if os.IsNotExist(err) {
+			if _, err := os.Create(filename); err != nil {
+				log.Fatalf("Unable to create file filename: %s", err)
+			}
+		} else {
+			log.Fatal(err)
+		}
+	}
+}
+
+func loadConfig() {
+	content, err := os.ReadFile(filename)
+	if err != nil {
+		log.Fatalf("Error reading config file: %s", err)
+	}
+	if err := yaml.Unmarshal(content, &contextMap); err != nil {
+		log.Fatalf("Unable to decode YAML into struct: %s", err)
+	}
+}
+
+func saveContext() {
+	bodyBytes, err := yaml.Marshal(&contextMap)
+	if err != nil {
+		log.Fatalf("Error marshalling into YAML %s", err)
+	}
+	file, err := os.Create(filename)
+	if err != nil {
+		log.Fatal(err)
+	}
+	if _, err := file.Write(bodyBytes); err != nil {
+		log.Fatal(err)
+	}
+	if err := file.Close(); err != nil {
+		log.Fatal(err)
+	}
+}
+
+// GetCurrentContext - returns current set context
+func GetCurrentContext() (name string, ctx Context) {
+	for n, c := range contextMap {
+		if c.Current {
+			name, ctx = n, c
+			return
+		}
+	}
+	log.Fatalf("No current context set, do so via `netmaker context use <name>`")
+	return
+}
+
+// SetCurrentContext - sets a given context as current context
+func SetCurrentContext(ctxName string) {
+	if _, ok := contextMap[ctxName]; !ok {
+		log.Fatalf("No such context %s", ctxName)
+	}
+	for key, ctx := range contextMap {
+		ctx.Current = key == ctxName
+		contextMap[key] = ctx
+	}
+	saveContext()
+}
+
+// SetContext - updates an existing context or creates a new one
+func SetContext(ctxName string, ctx Context) {
+	if oldCtx, ok := contextMap[ctxName]; ok && oldCtx.Current {
+		ctx.Current = true
+	}
+	contextMap[ctxName] = ctx
+	saveContext()
+}
+
+// SetAuthToken - saves the auth token
+func SetAuthToken(authToken string) {
+	ctxName, _ := GetCurrentContext()
+	if ctx, ok := contextMap[ctxName]; ok {
+		ctx.AuthToken = authToken
+		contextMap[ctxName] = ctx
+		saveContext()
+	}
+}
+
+// DeleteContext - deletes a context
+func DeleteContext(ctxName string) {
+	if _, ok := contextMap[ctxName]; ok {
+		delete(contextMap, ctxName)
+		saveContext()
+	} else {
+		log.Fatalf("No such context %s", ctxName)
+	}
+}
+
+// ListAll - lists all contexts
+func ListAll() {
+	for key, ctx := range contextMap {
+		fmt.Print("\n", key, " -> ", ctx.Endpoint)
+		if ctx.Current {
+			fmt.Print(" (current)")
+		}
+	}
+}
+
+func init() {
+	createConfigPathIfNotExists()
+	loadConfig()
+}

+ 18 - 0
cli/functions/acl.go

@@ -0,0 +1,18 @@
+package functions
+
+import (
+	"fmt"
+	"net/http"
+
+	"github.com/gravitl/netmaker/logic/acls"
+)
+
+// GetACL - fetch all ACLs associated with a network
+func GetACL(networkName string) *acls.ACLContainer {
+	return request[acls.ACLContainer](http.MethodGet, fmt.Sprintf("/api/networks/%s/acls", networkName), nil)
+}
+
+// UpdateACL - update an ACL
+func UpdateACL(networkName string, payload *acls.ACLContainer) *acls.ACLContainer {
+	return request[acls.ACLContainer](http.MethodPut, fmt.Sprintf("/api/networks/%s/acls", networkName), payload)
+}

+ 43 - 0
cli/functions/dns.go

@@ -0,0 +1,43 @@
+package functions
+
+import (
+	"fmt"
+	"net/http"
+
+	"github.com/gravitl/netmaker/models"
+)
+
+// GetDNS - fetch all DNS entries
+func GetDNS() *[]models.DNSEntry {
+	return request[[]models.DNSEntry](http.MethodGet, "/api/dns", nil)
+}
+
+// GetNodeDNS - fetch all Node DNS entires
+func GetNodeDNS(networkName string) *[]models.DNSEntry {
+	return request[[]models.DNSEntry](http.MethodGet, fmt.Sprintf("/api/dns/adm/%s/nodes", networkName), nil)
+}
+
+// GetCustomDNS - fetch user defined DNS entriees
+func GetCustomDNS(networkName string) *[]models.DNSEntry {
+	return request[[]models.DNSEntry](http.MethodGet, fmt.Sprintf("/api/dns/adm/%s/custom", networkName), nil)
+}
+
+// GetNetworkDNS - fetch DNS entries associated with a network
+func GetNetworkDNS(networkName string) *[]models.DNSEntry {
+	return request[[]models.DNSEntry](http.MethodGet, "/api/dns/adm/"+networkName, nil)
+}
+
+// CreateDNS - create a DNS entry
+func CreateDNS(networkName string, payload *models.DNSEntry) *models.DNSEntry {
+	return request[models.DNSEntry](http.MethodPost, "/api/dns/"+networkName, payload)
+}
+
+// PushDNS - push a DNS entry to CoreDNS
+func PushDNS() *string {
+	return request[string](http.MethodPost, "/api/dns/adm/pushdns", nil)
+}
+
+// DeleteDNS - delete a DNS entry
+func DeleteDNS(networkName, domainName string) *string {
+	return request[string](http.MethodDelete, fmt.Sprintf("/api/dns/%s/%s", networkName, domainName), nil)
+}

+ 49 - 0
cli/functions/ext_client.go

@@ -0,0 +1,49 @@
+package functions
+
+import (
+	"fmt"
+	"net/http"
+
+	"github.com/gravitl/netmaker/models"
+)
+
+// GetAllExtClients - fetch all external clients
+func GetAllExtClients() *[]models.ExtClient {
+	return request[[]models.ExtClient](http.MethodGet, "/api/extclients", nil)
+}
+
+// GetNetworkExtClients - fetch external clients associated with a network
+func GetNetworkExtClients(networkName string) *[]models.ExtClient {
+	return request[[]models.ExtClient](http.MethodGet, "/api/extclients/"+networkName, nil)
+}
+
+// GetExtClient - fetch a single external client
+func GetExtClient(networkName, clientID string) *models.ExtClient {
+	return request[models.ExtClient](http.MethodGet, fmt.Sprintf("/api/extclients/%s/%s", networkName, clientID), nil)
+}
+
+// GetExtClientConfig - fetch a wireguard config of an external client
+func GetExtClientConfig(networkName, clientID string) string {
+	return get(fmt.Sprintf("/api/extclients/%s/%s/file", networkName, clientID))
+}
+
+// CreateExtClient - create an external client
+func CreateExtClient(networkName, nodeID, extClientID string) {
+	if extClientID != "" {
+		request[any](http.MethodPost, fmt.Sprintf("/api/extclients/%s/%s", networkName, nodeID), &models.CustomExtClient{
+			ClientID: extClientID,
+		})
+	} else {
+		request[any](http.MethodPost, fmt.Sprintf("/api/extclients/%s/%s", networkName, nodeID), nil)
+	}
+}
+
+// DeleteExtClient - delete an external client
+func DeleteExtClient(networkName, clientID string) *models.SuccessResponse {
+	return request[models.SuccessResponse](http.MethodDelete, fmt.Sprintf("/api/extclients/%s/%s", networkName, clientID), nil)
+}
+
+// UpdateExtClient - update an external client
+func UpdateExtClient(networkName, clientID string, payload *models.ExtClient) *models.ExtClient {
+	return request[models.ExtClient](http.MethodPut, fmt.Sprintf("/api/extclients/%s/%s", networkName, clientID), payload)
+}

+ 118 - 0
cli/functions/http_client.go

@@ -0,0 +1,118 @@
+package functions
+
+import (
+	"bytes"
+	"encoding/json"
+	"io"
+	"log"
+	"net/http"
+
+	"github.com/gravitl/netmaker/cli/config"
+	"github.com/gravitl/netmaker/models"
+)
+
+func getAuthToken(ctx config.Context, force bool) string {
+	if !force && ctx.AuthToken != "" {
+		return ctx.AuthToken
+	}
+	authParams := &models.UserAuthParams{UserName: ctx.Username, Password: ctx.Password}
+	payload, err := json.Marshal(authParams)
+	if err != nil {
+		log.Fatal(err)
+	}
+	res, err := http.Post(ctx.Endpoint+"/api/users/adm/authenticate", "application/json", bytes.NewReader(payload))
+	if err != nil {
+		log.Fatal(err)
+	}
+	resBodyBytes, err := io.ReadAll(res.Body)
+	if err != nil {
+		log.Fatalf("Client could not read response body: %s", err)
+	}
+	if res.StatusCode != http.StatusOK {
+		log.Fatalf("Error Status: %d Response: %s", res.StatusCode, string(resBodyBytes))
+	}
+	body := new(models.SuccessResponse)
+	if err := json.Unmarshal(resBodyBytes, body); err != nil {
+		log.Fatalf("Error unmarshalling JSON: %s", err)
+	}
+	authToken := body.Response.(map[string]any)["AuthToken"].(string)
+	config.SetAuthToken(authToken)
+	return authToken
+}
+
+func request[T any](method, route string, payload any) *T {
+	var (
+		_, ctx = config.GetCurrentContext()
+		req    *http.Request
+		err    error
+	)
+	if payload == nil {
+		req, err = http.NewRequest(method, ctx.Endpoint+route, nil)
+		if err != nil {
+			log.Fatalf("Client could not create request: %s", err)
+		}
+	} else {
+		payloadBytes, jsonErr := json.Marshal(payload)
+		if jsonErr != nil {
+			log.Fatalf("Error in request JSON marshalling: %s", err)
+		}
+		req, err = http.NewRequest(method, ctx.Endpoint+route, bytes.NewReader(payloadBytes))
+		if err != nil {
+			log.Fatalf("Client could not create request: %s", err)
+		}
+		req.Header.Set("Content-Type", "application/json")
+	}
+	if ctx.MasterKey != "" {
+		req.Header.Set("Authorization", "Bearer "+ctx.MasterKey)
+	} else {
+		req.Header.Set("Authorization", "Bearer "+getAuthToken(ctx, false))
+	}
+	retried := false
+retry:
+	res, err := http.DefaultClient.Do(req)
+	if err != nil {
+		log.Fatalf("Client error making http request: %s", err)
+	}
+	// refresh JWT token
+	if res.StatusCode == http.StatusUnauthorized && !retried && ctx.MasterKey == "" {
+		req.Header.Set("Authorization", "Bearer "+getAuthToken(ctx, true))
+		retried = true
+		goto retry
+	}
+	resBodyBytes, err := io.ReadAll(res.Body)
+	if err != nil {
+		log.Fatalf("Client could not read response body: %s", err)
+	}
+	if res.StatusCode != http.StatusOK {
+		log.Fatalf("Error Status: %d Response: %s", res.StatusCode, string(resBodyBytes))
+	}
+	body := new(T)
+	if len(resBodyBytes) > 0 {
+		if err := json.Unmarshal(resBodyBytes, body); err != nil {
+			log.Fatalf("Error unmarshalling JSON: %s", err)
+		}
+	}
+	return body
+}
+
+func get(route string) string {
+	_, ctx := config.GetCurrentContext()
+	req, err := http.NewRequest(http.MethodGet, ctx.Endpoint+route, nil)
+	if err != nil {
+		log.Fatal(err)
+	}
+	if ctx.MasterKey != "" {
+		req.Header.Set("Authorization", "Bearer "+ctx.MasterKey)
+	} else {
+		req.Header.Set("Authorization", "Bearer "+getAuthToken(ctx, true))
+	}
+	res, err := http.DefaultClient.Do(req)
+	if err != nil {
+		log.Fatal(err)
+	}
+	bodyBytes, err := io.ReadAll(res.Body)
+	if err != nil {
+		log.Fatal(err)
+	}
+	return string(bodyBytes)
+}

+ 23 - 0
cli/functions/keys.go

@@ -0,0 +1,23 @@
+package functions
+
+import (
+	"fmt"
+	"net/http"
+
+	"github.com/gravitl/netmaker/models"
+)
+
+// GetKeys - fetch all access keys of a network
+func GetKeys(networkName string) *[]models.AccessKey {
+	return request[[]models.AccessKey](http.MethodGet, fmt.Sprintf("/api/networks/%s/keys", networkName), nil)
+}
+
+// CreateKey - create an access key
+func CreateKey(networkName string, key *models.AccessKey) *models.AccessKey {
+	return request[models.AccessKey](http.MethodPost, fmt.Sprintf("/api/networks/%s/keys", networkName), key)
+}
+
+// DeleteKey - delete an access key
+func DeleteKey(networkName, keyName string) {
+	request[string](http.MethodDelete, fmt.Sprintf("/api/networks/%s/keys/%s", networkName, keyName), nil)
+}

+ 28 - 0
cli/functions/metrics.go

@@ -0,0 +1,28 @@
+package functions
+
+import (
+	"fmt"
+	"net/http"
+
+	"github.com/gravitl/netmaker/models"
+)
+
+// GetNodeMetrics - fetch a single node's metrics
+func GetNodeMetrics(networkName, nodeID string) *models.Metrics {
+	return request[models.Metrics](http.MethodGet, fmt.Sprintf("/api/metrics/%s/%s", networkName, nodeID), nil)
+}
+
+// GetNetworkNodeMetrics - fetch an entire network's metrics
+func GetNetworkNodeMetrics(networkName string) *models.NetworkMetrics {
+	return request[models.NetworkMetrics](http.MethodGet, "/api/metrics/"+networkName, nil)
+}
+
+// GetAllMetrics - fetch all metrics
+func GetAllMetrics() *models.NetworkMetrics {
+	return request[models.NetworkMetrics](http.MethodGet, "/api/metrics", nil)
+}
+
+// GetNetworkExtMetrics - fetch external client metrics belonging to a network
+func GetNetworkExtMetrics(networkName string) *map[string]models.Metric {
+	return request[map[string]models.Metric](http.MethodGet, "/api/metrics-ext/"+networkName, nil)
+}

+ 45 - 0
cli/functions/network.go

@@ -0,0 +1,45 @@
+package functions
+
+import (
+	"fmt"
+	"net/http"
+
+	"github.com/gravitl/netmaker/models"
+)
+
+// CreateNetwork - creates a network
+func CreateNetwork(payload *models.Network) *models.Network {
+	return request[models.Network](http.MethodPost, "/api/networks", payload)
+}
+
+// UpdateNetwork - updates a network
+func UpdateNetwork(name string, payload *models.Network) *models.Network {
+	return request[models.Network](http.MethodPut, "/api/networks/"+name, payload)
+}
+
+// UpdateNetworkNodeLimit - updates a network
+func UpdateNetworkNodeLimit(name string, nodeLimit int32) *models.Network {
+	return request[models.Network](http.MethodPut, fmt.Sprintf("/api/networks/%s/nodelimit", name), &models.Network{
+		NodeLimit: nodeLimit,
+	})
+}
+
+// GetNetworks - fetch all networks
+func GetNetworks() *[]models.Network {
+	return request[[]models.Network](http.MethodGet, "/api/networks", nil)
+}
+
+// GetNetwork - fetch a single network
+func GetNetwork(name string) *models.Network {
+	return request[models.Network](http.MethodGet, "/api/networks/"+name, nil)
+}
+
+// DeleteNetwork - delete a network
+func DeleteNetwork(name string) *string {
+	return request[string](http.MethodDelete, "/api/networks/"+name, nil)
+}
+
+// RefreshKeys - refresh public and private key pairs for a network
+func RefreshKeys(networkName string) *models.Network {
+	return request[models.Network](http.MethodPost, fmt.Sprintf("/api/networks/%s/keyupdate", networkName), nil)
+}

+ 44 - 0
cli/functions/network_user.go

@@ -0,0 +1,44 @@
+package functions
+
+import (
+	"fmt"
+	"net/http"
+
+	"github.com/gravitl/netmaker/ee/ee_controllers"
+	"github.com/gravitl/netmaker/models/promodels"
+)
+
+// GetAllNetworkUsers - fetch all network users
+func GetAllNetworkUsers() *map[string][]promodels.NetworkUser {
+	return request[map[string][]promodels.NetworkUser](http.MethodGet, "/api/networkusers", nil)
+}
+
+// GetNetworkUsers - fetch network users belonging to a particular network
+func GetNetworkUsers(networkName string) *promodels.NetworkUserMap {
+	return request[promodels.NetworkUserMap](http.MethodGet, "/api/networkusers/"+networkName, nil)
+}
+
+// GetNetworkUser - fetch a single network user
+func GetNetworkUser(networkName, networkUserName string) *promodels.NetworkUser {
+	return request[promodels.NetworkUser](http.MethodGet, fmt.Sprintf("/api/networkusers/%s/%s", networkName, networkUserName), nil)
+}
+
+// CreateNetworkUser - create a network user
+func CreateNetworkUser(networkName string, payload *promodels.NetworkUser) {
+	request[any](http.MethodPost, "/api/networkusers/"+networkName, payload)
+}
+
+// UpdateNetworkUser - update a network user
+func UpdateNetworkUser(networkName string, payload *promodels.NetworkUser) {
+	request[any](http.MethodPut, "/api/networkusers/"+networkName, payload)
+}
+
+// GetNetworkUserData - fetch a network user's complete data
+func GetNetworkUserData(networkUserName string) *ee_controllers.NetworkUserDataMap {
+	return request[ee_controllers.NetworkUserDataMap](http.MethodGet, fmt.Sprintf("/api/networkusers/data/%s/me", networkUserName), nil)
+}
+
+// DeleteNetworkUser - delete a network user
+func DeleteNetworkUser(networkName, networkUserName string) {
+	request[any](http.MethodDelete, fmt.Sprintf("/api/networkusers/%s/%s", networkName, networkUserName), nil)
+}

+ 73 - 0
cli/functions/node.go

@@ -0,0 +1,73 @@
+package functions
+
+import (
+	"fmt"
+	"net/http"
+
+	"github.com/gravitl/netmaker/models"
+)
+
+// GetNodes - fetch all nodes
+func GetNodes(networkName ...string) *[]models.Node {
+	if len(networkName) == 1 {
+		return request[[]models.Node](http.MethodGet, "/api/nodes/"+networkName[0], nil)
+	} else {
+		return request[[]models.Node](http.MethodGet, "/api/nodes", nil)
+	}
+}
+
+// GetNodeByID - fetch a single node by ID
+func GetNodeByID(networkName, nodeID string) *models.NodeGet {
+	return request[models.NodeGet](http.MethodGet, fmt.Sprintf("/api/nodes/%s/%s", networkName, nodeID), nil)
+}
+
+// UpdateNode - update a single node
+func UpdateNode(networkName, nodeID string, node *models.Node) *models.Node {
+	return request[models.Node](http.MethodPut, fmt.Sprintf("/api/nodes/%s/%s", networkName, nodeID), node)
+}
+
+// DeleteNode - delete a node
+func DeleteNode(networkName, nodeID string) *models.SuccessResponse {
+	return request[models.SuccessResponse](http.MethodDelete, fmt.Sprintf("/api/nodes/%s/%s", networkName, nodeID), nil)
+}
+
+// CreateRelay - turn a node into a relay
+func CreateRelay(networkName, nodeID string, relayAddresses []string) *models.Node {
+	return request[models.Node](http.MethodPost, fmt.Sprintf("/api/nodes/%s/%s/createrelay", networkName, nodeID), &models.RelayRequest{
+		NetID:      networkName,
+		NodeID:     nodeID,
+		RelayAddrs: relayAddresses,
+	})
+}
+
+// DeleteRelay - remove relay role from a node
+func DeleteRelay(networkName, nodeID string) *models.Node {
+	return request[models.Node](http.MethodDelete, fmt.Sprintf("/api/nodes/%s/%s/deleterelay", networkName, nodeID), nil)
+}
+
+// CreateEgress - turn a node into an egress
+func CreateEgress(networkName, nodeID string, payload *models.EgressGatewayRequest) *models.Node {
+	return request[models.Node](http.MethodPost, fmt.Sprintf("/api/nodes/%s/%s/creategateway", networkName, nodeID), payload)
+}
+
+// DeleteEgress - remove egress role from a node
+func DeleteEgress(networkName, nodeID string) *models.Node {
+	return request[models.Node](http.MethodDelete, fmt.Sprintf("/api/nodes/%s/%s/deletegateway", networkName, nodeID), nil)
+}
+
+// CreateIngress - turn a node into an ingress
+func CreateIngress(networkName, nodeID string, failover bool) *models.Node {
+	return request[models.Node](http.MethodPost, fmt.Sprintf("/api/nodes/%s/%s/createingress", networkName, nodeID), &struct {
+		Failover bool `json:"failover"`
+	}{Failover: failover})
+}
+
+// DeleteIngress - remove ingress role from a node
+func DeleteIngress(networkName, nodeID string) *models.Node {
+	return request[models.Node](http.MethodDelete, fmt.Sprintf("/api/nodes/%s/%s/deleteingress", networkName, nodeID), nil)
+}
+
+// UncordonNode - uncordon a node
+func UncordonNode(networkName, nodeID string) *string {
+	return request[string](http.MethodPost, fmt.Sprintf("/api/nodes/%s/%s/approve", networkName, nodeID), nil)
+}

+ 16 - 0
cli/functions/pretty_print.go

@@ -0,0 +1,16 @@
+package functions
+
+import (
+	"encoding/json"
+	"fmt"
+	"log"
+)
+
+// PrettyPrint - print JSON with indentation
+func PrettyPrint(data any) {
+	body, err := json.MarshalIndent(data, "", "  ")
+	if err != nil {
+		log.Fatal(err)
+	}
+	fmt.Println(string(body))
+}

+ 28 - 0
cli/functions/server.go

@@ -0,0 +1,28 @@
+package functions
+
+import (
+	"net/http"
+
+	cfg "github.com/gravitl/netmaker/config"
+	"github.com/gravitl/netmaker/models"
+)
+
+// GetLogs - fetch Netmaker server logs
+func GetLogs() string {
+	return get("/api/logs")
+}
+
+// GetServerInfo - fetch minimal server info
+func GetServerInfo() *models.ServerConfig {
+	return request[models.ServerConfig](http.MethodGet, "/api/server/getserverinfo", nil)
+}
+
+// GetServerConfig - fetch entire server config including secrets
+func GetServerConfig() *cfg.ServerConfig {
+	return request[cfg.ServerConfig](http.MethodGet, "/api/server/getconfig", nil)
+}
+
+// GetServerHealth - fetch server current health status
+func GetServerHealth() string {
+	return get("/api/server/health")
+}

+ 37 - 0
cli/functions/user.go

@@ -0,0 +1,37 @@
+package functions
+
+import (
+	"net/http"
+
+	"github.com/gravitl/netmaker/models"
+)
+
+// HasAdmin - check if server has an admin user
+func HasAdmin() *bool {
+	return request[bool](http.MethodGet, "/api/users/adm/hasadmin", nil)
+}
+
+// CreateUser - create a user
+func CreateUser(payload *models.User) *models.User {
+	return request[models.User](http.MethodPost, "/api/users/"+payload.UserName, payload)
+}
+
+// UpdateUser - update a user
+func UpdateUser(payload *models.User) *models.User {
+	return request[models.User](http.MethodPut, "/api/users/networks/"+payload.UserName, payload)
+}
+
+// DeleteUser - delete a user
+func DeleteUser(username string) *string {
+	return request[string](http.MethodDelete, "/api/users/"+username, nil)
+}
+
+// GetUser - fetch a single user
+func GetUser(username string) *models.User {
+	return request[models.User](http.MethodGet, "/api/users/"+username, nil)
+}
+
+// ListUsers - fetch all users
+func ListUsers() *[]models.ReturnUser {
+	return request[[]models.ReturnUser](http.MethodGet, "/api/users", nil)
+}

+ 22 - 0
cli/functions/usergroups.go

@@ -0,0 +1,22 @@
+package functions
+
+import (
+	"net/http"
+
+	"github.com/gravitl/netmaker/models/promodels"
+)
+
+// GetUsergroups - fetch all usergroups
+func GetUsergroups() *promodels.UserGroups {
+	return request[promodels.UserGroups](http.MethodGet, "/api/usergroups", nil)
+}
+
+// CreateUsergroup - create a usergroup
+func CreateUsergroup(usergroupName string) {
+	request[any](http.MethodPost, "/api/usergroups/"+usergroupName, nil)
+}
+
+// DeleteUsergroup - delete a usergroup
+func DeleteUsergroup(usergroupName string) {
+	request[any](http.MethodDelete, "/api/usergroups/"+usergroupName, nil)
+}

+ 9 - 0
cli/main.go

@@ -0,0 +1,9 @@
+package main
+
+import (
+	"github.com/gravitl/netmaker/cli/cmd"
+)
+
+func main() {
+	cmd.Execute()
+}

+ 31 - 0
cli/samples/network.json

@@ -0,0 +1,31 @@
+{
+    "addressrange": "10.120.130.0/24",
+    "addressrange6": "",
+    "netid": "test3",
+    "defaultlistenport": 51821,
+    "nodelimit": 999999999,
+    "defaultpostup": "",
+    "defaultpostdown": "",
+    "defaultkeepalive": 20,
+    "defaultinterface": "nm-test3",
+    "accesskeys": [],
+    "allowmanualsignup": "no",
+    "islocal": "no",
+    "isipv4": "yes",
+    "isipv6": "no",
+    "ispointtosite": "no",
+    "localrange": "",
+    "defaultudpholepunch": "yes",
+    "defaultextclientdns": "",
+    "defaultmtu": 1280,
+    "defaultacl": "yes",
+    "prosettings": {
+      "defaultaccesslevel": 3,
+      "defaultusernodelimit": 0,
+      "defaultuserclientlimit": 0,
+      "allowedusers": [],
+      "allowedgroups": [
+        "*"
+      ]
+    }
+  }

+ 101 - 0
cli/samples/node.json

@@ -0,0 +1,101 @@
+{
+    "id": "eda7720b-830c-4e9c-8716-9773e15da160",
+    "address": "",
+    "address6": "fd00::ffff:ffff:ffff:ffff",
+    "localaddress": "",
+    "name": "netmaker-1",
+    "networksettings": {
+      "addressrange": "",
+      "addressrange6": "fd00::/64",
+      "netid": "onaw",
+      "nodeslastmodified": 1668594644,
+      "networklastmodified": 1668594644,
+      "defaultinterface": "nm-onaw",
+      "defaultlistenport": 51821,
+      "nodelimit": 999999999,
+      "defaultpostup": "",
+      "defaultpostdown": "",
+      "defaultkeepalive": 20,
+      "accesskeys": [],
+      "allowmanualsignup": "no",
+      "islocal": "no",
+      "isipv4": "no",
+      "isipv6": "yes",
+      "ispointtosite": "no",
+      "localrange": "",
+      "defaultudpholepunch": "yes",
+      "defaultextclientdns": "",
+      "defaultmtu": 1280,
+      "defaultacl": "yes",
+      "prosettings": {
+        "defaultaccesslevel": 3,
+        "defaultusernodelimit": 0,
+        "defaultuserclientlimit": 0,
+        "allowedusers": [],
+        "allowedgroups": [
+          "*"
+        ]
+      }
+    },
+    "listenport": 51823,
+    "locallistenport": 0,
+    "publickey": "xuLSz/ady+E6hK36cJiCzFp5tByBjnoZid54kHp9MVY=",
+    "endpoint": "134.209.145.214",
+    "postup": "",
+    "postdown": "",
+    "allowedips": null,
+    "persistentkeepalive": 20,
+    "ishub": "no",
+    "accesskey": "",
+    "interface": "nm-onaw",
+    "lastmodified": 1669114997,
+    "expdatetime": 1968594644,
+    "lastpeerupdate": 1668594644,
+    "lastcheckin": 1669114997,
+    "macaddress": "netmaker-server-1",
+    "password": "$2a$05$CrRlIbQkuQFQBs1A4wdhX.ar90lTRV9x.4IapTZV.FBaDmVyMukIG",
+    "network": "onaw",
+    "isrelayed": "no",
+    "ispending": "no",
+    "isrelay": "no",
+    "isdocker": "no",
+    "isk8s": "no",
+    "isegressgateway": "no",
+    "isingressgateway": "no",
+    "egressgatewayranges": null,
+    "egressgatewaynatenabled": "",
+    "egressgatewayrequest": {
+      "nodeid": "",
+      "netid": "",
+      "natenabled": "",
+      "ranges": null,
+      "interface": "",
+      "postup": "",
+      "postdown": ""
+    },
+    "relayaddrs": null,
+    "failovernode": "",
+    "ingressgatewayrange": "",
+    "ingressgatewayrange6": "",
+    "isstatic": "yes",
+    "udpholepunch": "no",
+    "dnson": "no",
+    "isserver": "yes",
+    "action": "noop",
+    "islocal": "no",
+    "localrange": "",
+    "ipforwarding": "yes",
+    "os": "linux",
+    "mtu": 1280,
+    "version": "v0.16.1",
+    "server": "",
+    "traffickeys": {
+      "mine": null,
+      "server": null
+    },
+    "firewallinuse": "iptables",
+    "internetgateway": "",
+    "connected": "yes",
+    "defaultacl": "yes",
+    "failover": "no"
+  }

+ 7 - 0
go.mod

@@ -43,6 +43,9 @@ require (
 	github.com/agnivade/levenshtein v1.1.1
 	github.com/coreos/go-oidc/v3 v3.4.0
 	github.com/gorilla/websocket v1.5.0
+	github.com/guumaster/tablewriter v0.0.10
+	github.com/olekukonko/tablewriter v0.0.5
+	github.com/spf13/cobra v1.5.0
 	golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e
 	golang.org/x/term v0.0.0-20220722155259-a9ba230a4035
 )
@@ -74,10 +77,12 @@ require (
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/google/go-cmp v0.5.8 // indirect
 	github.com/gopherjs/gopherjs v1.17.2 // indirect
+	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/josharian/native v1.0.0 // indirect
 	github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
 	github.com/kr/text v0.2.0 // indirect
 	github.com/leodido/go-urn v1.2.1 // indirect
+	github.com/mattn/go-runewidth v0.0.10 // indirect
 	github.com/mdlayher/genetlink v1.2.0 // indirect
 	github.com/mdlayher/netlink v1.6.0 // indirect
 	github.com/mdlayher/socket v0.1.1 // indirect
@@ -85,10 +90,12 @@ require (
 	github.com/opencontainers/image-spec v1.0.2 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/rivo/uniseg v0.1.0 // indirect
 	github.com/rogpeppe/go-internal v1.9.0 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/sirupsen/logrus v1.9.0 // indirect
 	github.com/spf13/afero v1.9.2 // indirect
+	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect
 	github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect
 	github.com/tevino/abool v1.2.0 // indirect

+ 10 - 0
go.sum

@@ -289,6 +289,7 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFb
 github.com/guumaster/hostctl v1.1.3 h1:b/yR3svkYsbr5VBdvfdyLXUl2xaKopSzgE/Xi7+1WRo=
 github.com/guumaster/hostctl v1.1.3/go.mod h1:h5rDx5Z8Hj2bYZfDt/eX4BNS2RSq7iRcGVQqfROJyH8=
 github.com/guumaster/tablewriter v0.0.10 h1:A0HD94yMdt4usgxBjoEceNeE0XMJ027euoHAzsPqBQs=
+github.com/guumaster/tablewriter v0.0.10/go.mod h1:p4FRFhyfo0UD9ZLmMRbbJooTUsxo6b80qZTERVDWrH8=
 github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
 github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -311,6 +312,7 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI=
 github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=
@@ -343,7 +345,9 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
 github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
+github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
 github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
 github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
 github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
@@ -374,6 +378,8 @@ github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJE
 github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
+github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
 github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
@@ -394,6 +400,7 @@ github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0 h1:Y2hUrkfuM0on
 github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0/go.mod h1:oa2sAs9tGai3VldabTV0eWejt/O4/OOD7azP8GaikqU=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
+github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
@@ -429,9 +436,12 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
 github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
 github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
+github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
+github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+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/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
 github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=