From 3273c30d8b70d5808c69d9541b99b8fc27eaf3ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Matos?= Date: Sat, 31 Oct 2020 23:29:06 +0000 Subject: [PATCH] MF-1268 - CLI improvements (#1274) * Prefix error messages in CLI with a bold "error: ". Signed-off-by: Joao Matos * Remove duplicated "Usage: " from groups command help. Signed-off-by: Joao Matos * Add a raw output mode for CLI and use it on logCreated. Signed-off-by: Joao Matos * Add CLI global flag for user auth token. Signed-off-by: Joao Matos * Add CLI config flag and parsing logic. Signed-off-by: Joao Matos * Refactor CLI users commands outside array structure. Signed-off-by: Joao Matos * Refactor CLI certificates commands using flags. Signed-off-by: Joao Matos * Refactor CLI things create command using flags. Signed-off-by: Joao Matos Co-authored-by: Drasko DRASKOVIC --- cli/certs.go | 49 ++++++++++++++++-------------- cli/config.go | 81 +++++++++++++++++++++++++++++++++++++++++++++++++ cli/groups.go | 2 +- cli/things.go | 7 +++-- cli/users.go | 37 +++++++++++++--------- cli/utils.go | 26 ++++++++++++++-- cmd/cli/main.go | 23 ++++++++++++++ 7 files changed, 181 insertions(+), 44 deletions(-) create mode 100644 cli/config.go diff --git a/cli/certs.go b/cli/certs.go index cb446460..0b9a5d23 100644 --- a/cli/certs.go +++ b/cli/certs.go @@ -1,54 +1,57 @@ package cli import ( - "errors" "strconv" "github.com/spf13/cobra" ) -var cmdCerts = []cobra.Command{ - cobra.Command{ +// NewCertsCmd returns certificate command. +func NewCertsCmd() *cobra.Command { + var keySize uint16 + var keyType string + var ttl uint32 + + issueCmd := cobra.Command{ Use: "issue", - Short: "issue ", + Short: "issue [--keysize=2048] [--keytype=rsa] [--ttl=8760]", Long: `Issues new certificate for a thing`, Run: func(cmd *cobra.Command, args []string) { - if len(args) != 5 { + if len(args) != 1 { logUsage(cmd.Short) return } + thingID := args[0] - keyBits, err := strconv.Atoi(args[1]) - if err != nil { - logError(errors.New("invalid format for keybits")) - return - } + valid := strconv.FormatUint(uint64(ttl), 10) + token := getUserAuthToken() - keyType := args[2] - valid := args[3] - token := args[4] - - c, err := sdk.IssueCert(thingID, keyBits, keyType, valid, token) + c, err := sdk.IssueCert(thingID, int(keySize), keyType, valid, token) if err != nil { logError(err) return } logJSON(c) }, - }, -} + } + + issueCmd.Flags().Uint16Var(&keySize, "keysize", 2048, "certificate key strength in bits: 2048, 4096 (RSA) or 224, 256, 384, 512 (EC)") + issueCmd.Flags().StringVar(&keyType, "keytype", "rsa", "certificate key type: RSA or EC") + issueCmd.Flags().Uint32Var(&ttl, "ttl", 8760, "certificate time to live in hours") -// NewCertsCmd returns certificate command. -func NewCertsCmd() *cobra.Command { cmd := cobra.Command{ - Use: "cert", - Short: "Certificate management", - Long: `Certificate management: create certificates for things"`, + Use: "certs", + Short: "Certificates management", + Long: `Certificates management: create certificates for things"`, Run: func(cmd *cobra.Command, args []string) { - logUsage("cert issue ") + logUsage("certs [issue]") }, } + cmdCerts := []cobra.Command{ + issueCmd, + } + for i := range cmdCerts { cmd.AddCommand(&cmdCerts[i]) } diff --git a/cli/config.go b/cli/config.go new file mode 100644 index 00000000..9aafcee3 --- /dev/null +++ b/cli/config.go @@ -0,0 +1,81 @@ +package cli + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "path" + + "github.com/mainflux/mainflux/pkg/errors" + "github.com/pelletier/go-toml" +) + +type Config struct { + AuthToken string `toml:"auth_token"` +} + +// save - store config in a file +func save(c Config, file string) error { + b, err := toml.Marshal(c) + if err != nil { + return errors.New(fmt.Sprintf("failed to read config file: %s", err)) + } + if err := ioutil.WriteFile(file, b, 0644); err != nil { + return errors.New(fmt.Sprintf("failed to write config TOML: %s", err)) + } + return nil +} + +// read - retrieve config from a file +func read(file string) (Config, error) { + data, err := ioutil.ReadFile(file) + c := Config{} + if err != nil { + return c, errors.New(fmt.Sprintf("failed to read config file: %s", err)) + } + + if err := toml.Unmarshal(data, &c); err != nil { + return Config{}, errors.New(fmt.Sprintf("failed to unmarshal config TOML: %s", err)) + } + return c, nil +} + +func getConfigPath() (string, error) { + // Check if a config path passed by user exists. + if ConfigPath != "" { + if _, err := os.Stat(ConfigPath); os.IsNotExist(err) { + errConfigNotFound := errors.Wrap(errors.New("config file was not found"), err) + logError(errConfigNotFound) + return "", err + } + } + + // If not, then read it from the user config directory. + if ConfigPath == "" { + userConfigDir, _ := os.UserConfigDir() + ConfigPath = path.Join(userConfigDir, "mainflux", "cli.toml") + } + + if _, err := os.Stat(ConfigPath); os.IsNotExist(err) { + return "", err + } + + return ConfigPath, nil +} + +func ParseConfig() { + path, err := getConfigPath() + if err != nil { + return + } + + config, err := read(path) + if err != nil { + log.Fatal(err) + } + + if UserAuthToken == "" { + UserAuthToken = config.AuthToken + } +} diff --git a/cli/groups.go b/cli/groups.go index d0c27e6c..2a2e4ac8 100644 --- a/cli/groups.go +++ b/cli/groups.go @@ -172,7 +172,7 @@ func NewGroupsCmd() *cobra.Command { Short: "Groups management", Long: `Groups management: create groups and assigns user to groups"`, Run: func(cmd *cobra.Command, args []string) { - logUsage("Usage: Groups [create | get | delete | assign | unassign | members | membership]") + logUsage("groups [create | get | delete | assign | unassign | members | membership]") }, } for i := range cmdGroups { diff --git a/cli/things.go b/cli/things.go index 9222e1fe..436f4ce9 100644 --- a/cli/things.go +++ b/cli/things.go @@ -13,10 +13,10 @@ import ( var cmdThings = []cobra.Command{ cobra.Command{ Use: "create", - Short: "create ", + Short: "create ", Long: `Create new thing, generate his UUID and store it`, Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { + if len(args) != 1 { logUsage(cmd.Short) return } @@ -27,7 +27,8 @@ var cmdThings = []cobra.Command{ return } - id, err := sdk.CreateThing(thing, args[1]) + token := getUserAuthToken() + id, err := sdk.CreateThing(thing, token) if err != nil { logError(err) return diff --git a/cli/users.go b/cli/users.go index b70f4317..ee00f05a 100644 --- a/cli/users.go +++ b/cli/users.go @@ -10,8 +10,9 @@ import ( "github.com/spf13/cobra" ) -var cmdUsers = []cobra.Command{ - cobra.Command{ +// NewUsersCmd returns users command. +func NewUsersCmd() *cobra.Command { + createCmd := cobra.Command{ Use: "create", Short: "create ", Long: `Creates new user`, @@ -33,8 +34,9 @@ var cmdUsers = []cobra.Command{ logCreated(id) }, - }, - cobra.Command{ + } + + getCmd := cobra.Command{ Use: "get", Short: "get ", Long: `Returns user object`, @@ -52,8 +54,9 @@ var cmdUsers = []cobra.Command{ logJSON(u) }, - }, - cobra.Command{ + } + + tokenCmd := cobra.Command{ Use: "token", Short: "token ", Long: `Creates new token`, @@ -74,9 +77,11 @@ var cmdUsers = []cobra.Command{ } logCreated(token) + }, - }, - cobra.Command{ + } + + updateCmd := cobra.Command{ Use: "update", Short: "update ", Long: `Update user metadata`, @@ -99,8 +104,9 @@ var cmdUsers = []cobra.Command{ logOK() }, - }, - cobra.Command{ + } + + passwordCmd := cobra.Command{ Use: "password", Short: "password ", Long: `Update user password`, @@ -117,20 +123,21 @@ var cmdUsers = []cobra.Command{ logOK() }, - }, -} + } -// NewUsersCmd returns users command. -func NewUsersCmd() *cobra.Command { cmd := cobra.Command{ Use: "users", Short: "Users management", Long: `Users management: create accounts and tokens"`, Run: func(cmd *cobra.Command, args []string) { - logUsage("Usage: users [create | get | update | token | password]") + logUsage("users [create | get | update | token | password]") }, } + cmdUsers := []cobra.Command{ + createCmd, getCmd, tokenCmd, updateCmd, passwordCmd, + } + for i := range cmdUsers { cmd.AddCommand(&cmdUsers[i]) } diff --git a/cli/utils.go b/cli/utils.go index 6c137c14..64011ef4 100644 --- a/cli/utils.go +++ b/cli/utils.go @@ -6,6 +6,7 @@ package cli import ( "encoding/json" "fmt" + "log" "github.com/fatih/color" prettyjson "github.com/hokaccha/go-prettyjson" @@ -18,6 +19,12 @@ var ( Offset uint = 0 // Name query parameter Name string = "" + // UserAuthToken user auth token parameter + UserAuthToken string = "" + // ConfigPath config path parameter + ConfigPath string = "" + // RawOutput raw output mode + RawOutput bool = false ) func logJSON(iList ...interface{}) { @@ -43,7 +50,10 @@ func logUsage(u string) { } func logError(err error) { - fmt.Printf("\n%s\n\n", color.RedString(err.Error())) + boldRed := color.New(color.FgRed, color.Bold) + boldRed.Print("\nerror: ") + + fmt.Printf("%s\n\n", color.RedString(err.Error())) } func logOK() { @@ -51,5 +61,17 @@ func logOK() { } func logCreated(e string) { - fmt.Printf(color.BlueString("\ncreated: %s\n\n"), e) + if RawOutput { + fmt.Println(e) + } else { + fmt.Printf(color.BlueString("\ncreated: %s\n\n"), e) + } +} + +func getUserAuthToken() string { + if UserAuthToken == "" { + log.Fatal("user auth token not valid, please pass using --user-auth-token flag or config file") + } + + return UserAuthToken } diff --git a/cmd/cli/main.go b/cmd/cli/main.go index f2e47441..ba29aa8b 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -32,6 +32,8 @@ func main() { var rootCmd = &cobra.Command{ Use: "mainflux-cli", PersistentPreRun: func(cmd *cobra.Command, args []string) { + cli.ParseConfig() + sdkConf.MsgContentType = sdk.ContentType(msgContentType) s := sdk.NewSDK(sdkConf) cli.SetSDK(s) @@ -117,6 +119,27 @@ func main() { "Do not check for TLS cert", ) + rootCmd.PersistentFlags().StringVar( + &cli.UserAuthToken, + "user-auth-token", + "", + "Mainflux user auth token", + ) + + rootCmd.PersistentFlags().StringVar( + &cli.ConfigPath, + "config", + "", + "Mainflux config path", + ) + + rootCmd.PersistentFlags().BoolVar( + &cli.RawOutput, + "raw", + false, + "Enables raw output mode for easier parsing of output", + ) + // Client and Channels Flags rootCmd.PersistentFlags().UintVarP( &cli.Limit,