From 398ac046da7e1b23551e6a0d4405d3a7653eafad Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 23 Mar 2017 20:31:47 +0100 Subject: [PATCH] Reorganize cobra cmd to enable better reuse --- cmd/common.go | 119 +++++++++++++++++++++++++++++++++++++++++++++++ cmd/get.go | 2 +- cmd/keys/main.go | 11 ++++- cmd/list.go | 2 +- cmd/new.go | 2 +- cmd/root.go | 95 ++++--------------------------------- cmd/serve.go | 2 +- cmd/update.go | 2 +- cmd/utils.go | 5 +- 9 files changed, 146 insertions(+), 94 deletions(-) create mode 100644 cmd/common.go diff --git a/cmd/common.go b/cmd/common.go new file mode 100644 index 000000000..56c9afbab --- /dev/null +++ b/cmd/common.go @@ -0,0 +1,119 @@ +package cmd + +import ( + "fmt" + "os" + "strings" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" + data "github.com/tendermint/go-data" + "github.com/tendermint/go-data/base58" +) + +/******* + +TODO + +This file should move into go-common or the like as a basis for all cli tools. +It is here for experimentation of re-use between go-keys and light-client. + +*********/ + +const ( + RootFlag = "root" + OutputFlag = "output" +) + +func PrepareMainCmd(cmd *cobra.Command, envPrefix, defautRoot string) func() { + cobra.OnInitialize(func() { initEnv(envPrefix) }) + cmd.PersistentFlags().StringP(RootFlag, "r", defautRoot, "root directory for config and data") + cmd.PersistentFlags().StringP("encoding", "e", "hex", "Binary encoding (hex|b64|btc)") + cmd.PersistentFlags().StringP(OutputFlag, "o", "text", "Output format (text|json)") + cmd.PersistentPreRunE = multiE(bindFlags, setEncoding, validateOutput, cmd.PersistentPreRunE) + return func() { execute(cmd) } +} + +// initEnv sets to use ENV variables if set. +func initEnv(prefix string) { + // env variables with TM prefix (eg. TM_ROOT) + viper.SetEnvPrefix(prefix) + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + viper.AutomaticEnv() +} + +// execute adds all child commands to the root command sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func execute(cmd *cobra.Command) { + if err := cmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(-1) + } +} + +type wrapE func(cmd *cobra.Command, args []string) error + +func multiE(fs ...wrapE) wrapE { + return func(cmd *cobra.Command, args []string) error { + for _, f := range fs { + if f != nil { + if err := f(cmd, args); err != nil { + return err + } + } + } + return nil + } +} + +func bindFlags(cmd *cobra.Command, args []string) error { + // cmd.Flags() includes flags from this command and all persistent flags from the parent + if err := viper.BindPFlags(cmd.Flags()); err != nil { + return err + } + + // rootDir is command line flag, env variable, or default $HOME/.tlc + rootDir := viper.GetString("root") + viper.SetConfigName("config") // name of config file (without extension) + viper.AddConfigPath(rootDir) // search root directory + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + // stderr, so if we redirect output to json file, this doesn't appear + // fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) + } else if _, ok := err.(viper.ConfigFileNotFoundError); !ok { + // we ignore not found error, only parse error + // stderr, so if we redirect output to json file, this doesn't appear + fmt.Fprintf(os.Stderr, "%#v", err) + } + return nil +} + +// setEncoding reads the encoding flag +func setEncoding(cmd *cobra.Command, args []string) error { + // validate and set encoding + enc := viper.GetString("encoding") + switch enc { + case "hex": + data.Encoder = data.HexEncoder + case "b64": + data.Encoder = data.B64Encoder + case "btc": + data.Encoder = base58.BTCEncoder + default: + return errors.Errorf("Unsupported encoding: %s", enc) + } + return nil +} + +func validateOutput(cmd *cobra.Command, args []string) error { + // validate output format + output := viper.GetString(OutputFlag) + switch output { + case "text", "json": + default: + return errors.Errorf("Unsupported output format: %s", output) + } + return nil +} diff --git a/cmd/get.go b/cmd/get.go index d945809f9..10d528807 100644 --- a/cmd/get.go +++ b/cmd/get.go @@ -32,7 +32,7 @@ var getCmd = &cobra.Command{ } name := args[0] - info, err := manager.Get(name) + info, err := Manager.Get(name) if err != nil { fmt.Println(err.Error()) return diff --git a/cmd/keys/main.go b/cmd/keys/main.go index 8c880ea03..40bf31808 100644 --- a/cmd/keys/main.go +++ b/cmd/keys/main.go @@ -14,8 +14,15 @@ package main -import "github.com/tendermint/go-keys/cmd" +import ( + "os" + + "github.com/tendermint/go-keys/cmd" +) func main() { - cmd.Execute() + cmd.RootCmd.PersistentPreRunE = cmd.SetupKeys + cmd.PrepareMainCmd(cmd.RootCmd, "TM", os.ExpandEnv("$HOME/.tlc")) + cmd.RootCmd.Execute() + // exec() } diff --git a/cmd/list.go b/cmd/list.go index 44fefb8d6..bf4d14b68 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -27,7 +27,7 @@ var listCmd = &cobra.Command{ Long: `Return a list of all public keys stored by this key manager along with their associated name and address.`, Run: func(cmd *cobra.Command, args []string) { - infos, err := manager.List() + infos, err := Manager.List() if err != nil { fmt.Println(err.Error()) return diff --git a/cmd/new.go b/cmd/new.go index 27c7dce75..3564ddf90 100644 --- a/cmd/new.go +++ b/cmd/new.go @@ -50,7 +50,7 @@ func newPassword(cmd *cobra.Command, args []string) { return } - info, err := manager.Create(name, pass, algo) + info, err := Manager.Create(name, pass, algo) if err != nil { fmt.Println(err.Error()) return diff --git a/cmd/root.go b/cmd/root.go index 41803fc65..ffe5cc36b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -15,26 +15,19 @@ package cmd import ( - "fmt" - "os" "path/filepath" - "strings" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" - data "github.com/tendermint/go-data" - "github.com/tendermint/go-data/base58" keys "github.com/tendermint/go-keys" "github.com/tendermint/go-keys/cryptostore" "github.com/tendermint/go-keys/storage/filestorage" ) +const KeySubdir = "keys" + var ( - rootDir string - output string - keyDir string - manager keys.Manager + Manager keys.Manager ) // RootCmd represents the base command when called without any subcommands @@ -46,87 +39,19 @@ var RootCmd = &cobra.Command{ These keys may be in any format supported by go-crypto and can be used by light-clients, full nodes, or any other application that needs to sign with a private key.`, - PersistentPreRunE: bindFlags, -} - -// Execute adds all child commands to the root command sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - if err := RootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(-1) - } -} - -func init() { - cobra.OnInitialize(initEnv) - RootCmd.PersistentFlags().StringP("root", "r", os.ExpandEnv("$HOME/.tlc"), "root directory for config and data") - RootCmd.PersistentFlags().String("keydir", "keys", "Directory to store private keys (subdir of root)") - RootCmd.PersistentFlags().StringP("output", "o", "text", "Output format (text|json)") - RootCmd.PersistentFlags().StringP("encoding", "e", "hex", "Binary encoding (hex|b64|btc)") -} - -// initEnv sets to use ENV variables if set. -func initEnv() { - // env variables with TM prefix (eg. TM_ROOT) - viper.SetEnvPrefix("TM") - viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - viper.AutomaticEnv() } -func bindFlags(cmd *cobra.Command, args []string) error { - // cmd.Flags() includes flags from this command and all persistent flags from the parent - if err := viper.BindPFlags(cmd.Flags()); err != nil { - return err - } - - // rootDir is command line flag, env variable, or default $HOME/.tlc - rootDir = viper.GetString("root") - viper.SetConfigName("keys") // name of config file (without extension) - viper.AddConfigPath(rootDir) // search root directory - - // If a config file is found, read it in. - if err := viper.ReadInConfig(); err == nil { - // stderr, so if we redirect output to json file, this doesn't appear - fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) - } - - return validateFlags(cmd) -} - -// validateFlags asserts all RootCmd flags are valid -func validateFlags(cmd *cobra.Command) error { - // validate output format - output = viper.GetString("output") - switch output { - case "text", "json": - default: - return errors.Errorf("Unsupported output format: %s", output) - } - - // validate and set encoding - enc := viper.GetString("encoding") - switch enc { - case "hex": - data.Encoder = data.HexEncoder - case "b64": - data.Encoder = data.B64Encoder - case "btc": - data.Encoder = base58.BTCEncoder - default: - return errors.Errorf("Unsupported encoding: %s", enc) - } - +// SetupKeys must be registered in main() on the top level command +// here this is RootCmd, but if we embed keys in eg. light-client, +// that must be responsible for the update +func SetupKeys(cmd *cobra.Command, args []string) error { // store the keys directory - keyDir = viper.GetString("keydir") - if !filepath.IsAbs(keyDir) { - keyDir = filepath.Join(rootDir, keyDir) - } + rootDir := viper.GetString("root") + keyDir := filepath.Join(rootDir, KeySubdir) // and construct the key manager - manager = cryptostore.New( + Manager = cryptostore.New( cryptostore.SecretBox, filestorage.New(keyDir), ) - return nil } diff --git a/cmd/serve.go b/cmd/serve.go index 2717913fd..5268f0b92 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -64,7 +64,7 @@ func serveHTTP(cmd *cobra.Command, args []string) error { } router := mux.NewRouter() - ks := server.New(manager, viper.GetString("type")) + ks := server.New(Manager, viper.GetString("type")) ks.Register(router) // only set cors for tcp listener diff --git a/cmd/update.go b/cmd/update.go index 0340944be..63b6f84df 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -50,7 +50,7 @@ func updatePassword(cmd *cobra.Command, args []string) { return } - err = manager.Update(name, oldpass, newpass) + err = Manager.Update(name, oldpass, newpass) if err != nil { fmt.Println(err.Error()) } else { diff --git a/cmd/utils.go b/cmd/utils.go index 01f9801ae..31a3a8df3 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -5,6 +5,7 @@ import ( "github.com/bgentry/speakeasy" "github.com/pkg/errors" + "github.com/spf13/viper" data "github.com/tendermint/go-data" keys "github.com/tendermint/go-keys" ) @@ -39,7 +40,7 @@ func getCheckPassword(prompt, prompt2 string) (string, error) { } func printInfo(info keys.Info) { - switch output { + switch viper.Get(OutputFlag) { case "text": addr, err := data.ToText(info.Address) if err != nil { @@ -60,7 +61,7 @@ func printInfo(info keys.Info) { } func printInfos(infos keys.Infos) { - switch output { + switch viper.Get(OutputFlag) { case "text": fmt.Println("All keys:") for _, i := range infos {