diff --git a/Makefile b/Makefile index 351da9a20c..bb33e4885e 100644 --- a/Makefile +++ b/Makefile @@ -39,8 +39,8 @@ image: image-bins _build install: - go install $(BUILD_FLAGS) ./cmd/akash - go install $(BUILD_FLAGS) ./cmd/akashd + GO111MODULE=off go install $(BUILD_FLAGS) ./cmd/akash + GO111MODULE=off go install $(BUILD_FLAGS) ./cmd/akashd image-minikube: eval $$(minikube docker-env) && make image diff --git a/_run/multi/run.sh b/_run/multi/run.sh index 13df881e6c..d3a838caae 100755 --- a/_run/multi/run.sh +++ b/_run/multi/run.sh @@ -8,8 +8,8 @@ do_init(){ mkdir -p "$AKASH_DIR" mkdir -p "$AKASHD_DIR" - _akash key create master > "$DATA_ROOT/master.key" - _akash key create other > "$DATA_ROOT/other.key" + _akash key create master | grep "Public Key" | awk '{print $3}' > "$DATA_ROOT/master.key" + _akash key create other | grep "Public Key" | awk '{print $3}' > "$DATA_ROOT/other.key" _akashd init "$(cat "$DATA_ROOT/master.key")" -t helm -c "${HELM_NODE_COUNT:-4}" } diff --git a/_run/single/run.sh b/_run/single/run.sh index 3d1ae08585..1ac331f4d8 100755 --- a/_run/single/run.sh +++ b/_run/single/run.sh @@ -8,11 +8,11 @@ do_init() { mkdir -p "$AKASH_DIR" mkdir -p "$AKASHD_DIR" - akash key create master > "$DATA_ROOT/master.key" - akash key create other > "$DATA_ROOT/other.key" + akash key create master | grep "Public Key" | awk '{print $3}' > "$DATA_ROOT/master.key" + akash key create other | grep "Public Key" | awk '{print $3}' > "$DATA_ROOT/other.key" akashd init "$(cat "$DATA_ROOT/master.key")" - akash_provider key create master > "$DATA_ROOT/provider-master.key" + akash_provider key create master | grep "Public Key" | awk '{print $3}' > "$DATA_ROOT/provider-master.key" } case "$1" in diff --git a/cmd/akash/deployment.go b/cmd/akash/deployment.go index 0ada01ed06..576786b542 100644 --- a/cmd/akash/deployment.go +++ b/cmd/akash/deployment.go @@ -20,10 +20,9 @@ import ( ) func deploymentCommand() *cobra.Command { - cmd := &cobra.Command{ Use: "deployment", - Short: "manage deployments", + Short: "Manage deployments", } cmd.AddCommand(createDeploymentCommand()) @@ -40,7 +39,7 @@ func createDeploymentCommand() *cobra.Command { cmd := &cobra.Command{ Use: "create ", - Short: "create a deployment", + Short: "Create a deployment", Args: cobra.ExactArgs(1), RunE: session.WithSession( session.RequireKey(session.RequireNode(createDeployment))), diff --git a/cmd/akash/key.go b/cmd/akash/key.go index c963bea647..c0621af008 100644 --- a/cmd/akash/key.go +++ b/cmd/akash/key.go @@ -3,11 +3,14 @@ package main import ( "errors" "fmt" - "os" - "text/tabwriter" + "strings" + "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/cosmos/cosmos-sdk/crypto/keys/hd" + "github.com/gosuri/uitable" "github.com/ovrclk/akash/cmd/akash/session" "github.com/ovrclk/akash/cmd/common" + "github.com/ovrclk/akash/util/uiutil" "github.com/spf13/cobra" . "github.com/ovrclk/akash/util" @@ -16,28 +19,28 @@ import ( func keyCommand() *cobra.Command { cmd := &cobra.Command{ Use: "key", - Short: "Key commands", + Short: "Manage keys", } cmd.AddCommand(keyCreateCommand()) cmd.AddCommand(keyListCommand()) cmd.AddCommand(keyShowCommand()) + cmd.AddCommand(keyRecoverCommand()) return cmd } func keyCreateCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "create [name]", - Short: "Create new key", - RunE: session.WithSession(session.RequireRootDir(doKeyCreateCommand)), + Use: "create ", + Short: "create a new key locally", + Long: "Create a new key and store it locally.\nTo recover a key using the recovery codes generated by the command, see 'akash help key recover'", + Example: keyCreateHelp, + Args: cobra.ExactArgs(1), + RunE: session.WithSession(session.RequireRootDir(doKeyCreateCommand)), } session.AddFlagKeyType(cmd, cmd.Flags()) return cmd } func doKeyCreateCommand(session session.Session, cmd *cobra.Command, args []string) error { - if len(args) != 1 { - return errors.New("name argument required") - } - kmgr, err := session.KeyManager() if err != nil { return err @@ -53,12 +56,16 @@ func doKeyCreateCommand(session session.Session, cmd *cobra.Command, args []stri return err } - info, _, err := kmgr.CreateMnemonic(args[0], common.DefaultCodec, password, ktype) + info, seed, err := kmgr.CreateMnemonic(args[0], common.DefaultCodec, password, ktype) if err != nil { return err } - fmt.Println(X(info.GetPubKey().Address())) + fmt.Println(string(uiutil.NewTitle(fmt.Sprintf("Successfully created key for '%s'", args[0])).Bytes())) + table := uitable.New() + table.AddRow("Public Key:", X(info.GetPubKey().Address())) + table.AddRow("Recovery Codes:", seed) + fmt.Println(table) return nil } @@ -66,30 +73,68 @@ func doKeyCreateCommand(session session.Session, cmd *cobra.Command, args []stri func keyListCommand() *cobra.Command { return &cobra.Command{ Use: "list", - Short: "list keys", + Short: "list all the keys stored locally", RunE: session.WithSession(session.RequireKeyManager(doKeyListCommand)), } } -func doKeyListCommand(session session.Session, cmd *cobra.Command, args []string) error { +func keyRecoverCommand() *cobra.Command { + return &cobra.Command{ + Use: "recover ...", + Short: "recover a key using recovery codes", + Long: "Recover a key using the recovery code generated during key creation and store it locally. For help with creating a key, see 'akash help key create'", + Example: keyRecoverExample, + Args: cobra.ExactArgs(25), + RunE: session.WithSession(session.RequireKeyManager(doKeyRecoverCommand)), + } +} + +func doKeyRecoverCommand(session session.Session, cmd *cobra.Command, args []string) error { + // the first arg is the key name and the rest are mnemonic codes + name, args := args[0], args[1:] + seed := strings.Join(args, " ") + + password, err := session.Password() + if err != nil { + return err + } + kmgr, _ := session.KeyManager() + params := *hd.NewFundraiserParams(0, 0) + info, err := kmgr.Derive(name, seed, keys.DefaultBIP39Passphrase, password, params) + if err != nil { + return err + } + + title := uiutil.NewTitle(fmt.Sprintf("Successfully recovered key, stored locally as '%s'", name)) + fmt.Println(string(title.Bytes())) + table := uitable.New() + table.AddRow("Public Key:", X(info.GetPubKey().Address())) + fmt.Println(table) + return nil +} + +func doKeyListCommand(session session.Session, cmd *cobra.Command, args []string) error { + kmgr, _ := session.KeyManager() infos, err := kmgr.List() if err != nil { return err } - - tw := tabwriter.NewWriter(os.Stdout, 0, 4, 0, '\t', 0) + table := uitable.New() + table.MaxColWidth = 80 + table.Wrap = true + table.AddRow(uiutil.NewTitle("NAME").String(), uiutil.NewTitle("PUBLIC KEY").String()) for _, info := range infos { - fmt.Fprintf(tw, "%v\t%v\n", info.GetName(), X(info.GetPubKey().Address())) + table.AddRow(info.GetName(), X(info.GetPubKey().Address())) } - tw.Flush() + fmt.Println(table) return nil } func keyShowCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "show [name]", + Use: "show ", Short: "display a key", Args: cobra.ExactArgs(1), RunE: session.WithSession(session.RequireRootDir(doKeyShowCommand)), @@ -122,3 +167,27 @@ func doKeyShowCommand(session session.Session, cmd *cobra.Command, args []string fmt.Println(X(info.GetPubKey().Address())) return nil } + +var ( + keyCreateHelp = ` +- Create a key with the name 'greg': + + $ akash key create greg + + Successfully created key for 'greg' + =================================== + + Public Key: f4e03226c054b1adafaa2739bad720c095500a49 + Recovery Codes: figure share industry canal... +` + + keyRecoverExample = ` +- Recover a key with the name 'my-key': + + $ akash key recover my-key today napkin arch \ + picnic fox case thrive table journey ill \ + any enforce awesome desert chapter regret \ + narrow capable advice skull pipe giraffe \ + clown outside +` +) diff --git a/cmd/akash/logs.go b/cmd/akash/logs.go index 54c46e580a..42e1c2d6e3 100644 --- a/cmd/akash/logs.go +++ b/cmd/akash/logs.go @@ -19,7 +19,7 @@ func logsCommand() *cobra.Command { cmd := &cobra.Command{ Use: "logs ", - Short: "service logs", + Short: "Service logs", Args: cobra.ExactArgs(2), RunE: session.WithSession(session.RequireNode(logs)), } diff --git a/cmd/akash/marketplace.go b/cmd/akash/marketplace.go index 32a814eead..8aa84f9913 100644 --- a/cmd/akash/marketplace.go +++ b/cmd/akash/marketplace.go @@ -17,7 +17,7 @@ func marketplaceCommand() *cobra.Command { cmd := &cobra.Command{ Use: "marketplace", - Short: "monitor marketplace", + Short: "Monitor marketplace", Args: cobra.NoArgs, RunE: session.WithSession(session.RequireNode(doMarketplaceMonitorCommand)), } diff --git a/cmd/akash/provider.go b/cmd/akash/provider.go index ebdbb08bef..291ce26c9d 100644 --- a/cmd/akash/provider.go +++ b/cmd/akash/provider.go @@ -28,7 +28,7 @@ func providerCommand() *cobra.Command { cmd := &cobra.Command{ Use: "provider", - Short: "manage provider", + Short: "Manage provider", Args: cobra.ExactArgs(1), } diff --git a/cmd/akash/query/query.go b/cmd/akash/query/query.go index 6f075ef299..41031f5822 100644 --- a/cmd/akash/query/query.go +++ b/cmd/akash/query/query.go @@ -13,7 +13,7 @@ func QueryCommand() *cobra.Command { cmd := &cobra.Command{ Use: "query [something]", - Short: "query something", + Short: "Query something", Args: cobra.ExactArgs(1), } diff --git a/cmd/akash/send.go b/cmd/akash/send.go index 308c7507f5..9a450c7bf6 100644 --- a/cmd/akash/send.go +++ b/cmd/akash/send.go @@ -14,7 +14,7 @@ func sendCommand() *cobra.Command { cmd := &cobra.Command{ Use: "send [amount] [to account]", - Short: "send tokens", + Short: "Send tokens", Args: cobra.ExactArgs(2), RunE: session.WithSession( session.RequireKey(session.RequireNode(doSendCommand))), diff --git a/cmd/akash/session/base.go b/cmd/akash/session/base.go index 7bd18d4983..c4bff1f282 100644 --- a/cmd/akash/session/base.go +++ b/cmd/akash/session/base.go @@ -18,6 +18,7 @@ const ( flagNoWait = "no-wait" flagHost = "host" flagPassword = "password" + flagMode = "mode" keyDir = "keys" defaultKeyType = keys.Secp256k1 @@ -32,6 +33,7 @@ func SetupBaseCommand(cmd *cobra.Command) { return initCommandConfig(root) } cmd.PersistentFlags().StringP(flagRootDir, "d", defaultRootDir(), "data directory") + cmd.PersistentFlags().StringP(flagMode, "m", "interactive", "output mode (interactive|text|json)") } func initCommandConfig(root string) error { diff --git a/cmd/akash/session/flags.go b/cmd/akash/session/flags.go index 7d612e7dc4..aa17e88eb4 100644 --- a/cmd/akash/session/flags.go +++ b/cmd/akash/session/flags.go @@ -28,7 +28,7 @@ func AddFlagNonce(cmd *cobra.Command, flags *pflag.FlagSet) { } func AddFlagKeyType(cmd *cobra.Command, flags *pflag.FlagSet) { - flags.StringP(flagKeyType, "t", string(defaultKeyType), "Type of key (secp256k1|ed25519|ledger)") + flags.StringP(flagKeyType, "t", string(defaultKeyType), "type of key (secp256k1|ed25519|ledger)") } func AddFlagWait(cmd *cobra.Command, flags *pflag.FlagSet) { diff --git a/cmd/akash/session/session.go b/cmd/akash/session/session.go index 7c9296355b..824b0d85e2 100644 --- a/cmd/akash/session/session.go +++ b/cmd/akash/session/session.go @@ -11,6 +11,7 @@ import ( "github.com/ovrclk/akash/cmd/common" "github.com/ovrclk/akash/query" "github.com/ovrclk/akash/txutil" + "github.com/ovrclk/akash/util/uiutil" "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/spf13/cobra" @@ -20,6 +21,9 @@ import ( tmclient "github.com/tendermint/tendermint/rpc/client" ) +// KeybaseName is the default name of the Keybase +var KeybaseName = "akash" + type Session interface { RootDir() string KeyManager() (keys.Keybase, error) @@ -37,6 +41,7 @@ type Session interface { NoWait() bool Host() string Password() (string, error) + Printer() uiutil.Printer } type cmdRunner func(cmd *cobra.Command, args []string) error @@ -55,6 +60,13 @@ func WithSession(fn Runner) cmdRunner { } } +func WithPrinter(fn Runner) Runner { + return func(session Session, cmd *cobra.Command, args []string) error { + defer session.Printer().Flush() + return fn(session, cmd, args) + } +} + func RequireHost(fn Runner) Runner { return func(session Session, cmd *cobra.Command, args []string) error { if host := session.Host(); host == "" { @@ -105,12 +117,13 @@ func newSession(ctx context.Context, cmd *cobra.Command) *session { } type session struct { - cmd *cobra.Command - kmgr keys.Keybase - kdb tmdb.DB - log log.Logger - ctx context.Context - mtx sync.Mutex + cmd *cobra.Command + kmgr keys.Keybase + kdb tmdb.DB + log log.Logger + ctx context.Context + mtx sync.Mutex + printer uiutil.Printer } func (s *session) shutdown() { @@ -256,11 +269,8 @@ func (s *session) Ctx() context.Context { } func loadKeyManager(root string) (keys.Keybase, tmdb.DB, error) { - db := tmdb.NewDB(keyDir, tmdb.GoLevelDBBackend, root) - // TODO: Code review required - manager := keys.New("default", path.Join(root, keyDir)) - + manager := keys.New(KeybaseName, path.Join(root, keyDir)) return manager, db, nil } @@ -270,3 +280,10 @@ func (s *session) Host() string { } return viper.GetString(flagHost) } + +func (s *session) Printer() uiutil.Printer { + if s.printer == nil { + s.printer = uiutil.NewPrinter(nil) + } + return s.printer +} diff --git a/cmd/akash/status.go b/cmd/akash/status.go index bbe67ccee5..188053eabc 100644 --- a/cmd/akash/status.go +++ b/cmd/akash/status.go @@ -11,7 +11,7 @@ import ( func statusCommand() *cobra.Command { cmd := &cobra.Command{ Use: "status", - Short: "get remote node status", + Short: "Display node status", Args: cobra.NoArgs, RunE: session.WithSession(session.RequireNode(doStatusCommand)), } diff --git a/cmd/common/version.go b/cmd/common/version.go index 5cc47b0f53..41f9dd9cab 100644 --- a/cmd/common/version.go +++ b/cmd/common/version.go @@ -10,7 +10,7 @@ import ( func VersionCommand() *cobra.Command { return &cobra.Command{ Use: "version", - Short: "Print version", + Short: "Display version", Run: func(_ *cobra.Command, _ []string) { fmt.Println("version: ", version.Version()) fmt.Println("commit: ", version.Commit()) diff --git a/glide.lock b/glide.lock index bc9cd57c24..d99f068c05 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 7e07e90e851d9a398dfb9b35685afe38e61a57709c06bc530205dbdfa7c39d68 -updated: 2019-03-25T18:20:23.227634-07:00 +hash: 43f4eafc13312667ea7649e7d54a9c02c0c206373c9a6b4f9f369256280593ec +updated: 2019-04-05T15:16:03.041186-07:00 imports: - name: github.com/beorn7/perks version: 3ac7bf7a47d159a033b107610db8a1b6575507a4 @@ -22,7 +22,7 @@ imports: - name: github.com/caarlos0/env version: 518fcfb943252f77846f92ce5794086d9cb4a1b5 - name: github.com/cosmos/cosmos-sdk - version: 7b4104aced52aa5b59a96c28b5ebeea7877fc4f0 + version: dba33693c4b714db7c01eb3926edf780be12a8e5 subpackages: - codec - crypto @@ -102,6 +102,11 @@ imports: version: 53c1911da2b537f792e7cafcb446b05ffe33b996 - name: github.com/gorilla/websocket version: 66b9c49e59c6c48f0ffce28c2d8b8a5678502c6d +- name: github.com/gosuri/uitable + version: 36ee7e946282a3fb1cfecd476ddc9b35d8847e42 + subpackages: + - util/strutil + - util/wordwrap - name: github.com/gregjones/httpcache version: 787624de3eb7bd915c329cba748687a3b22666a6 subpackages: @@ -142,6 +147,8 @@ imports: version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0 - name: github.com/magiconair/properties version: c2353362d570a7bfa228149c62842019201cfb71 +- name: github.com/mattn/go-runewidth + version: 703b5e6b11ae25aeb2af9ebb5d5fdf8fa2575211 - name: github.com/matttproud/golang_protobuf_extensions version: c12348ce28de40eed0136aa2b644d0ee0650e56c subpackages: @@ -235,7 +242,7 @@ imports: - name: github.com/tendermint/iavl version: 2f35d309e69ffec33c044a910acd9e8d739eb095 - name: github.com/tendermint/tendermint - version: 0d985ede28bd6937fa9d3613618e42cab6fc871c + version: 6cc3f4d87cae6589aef0ac2b4707f1ebc8a669b2 subpackages: - abci/client - abci/example/code diff --git a/glide.yaml b/glide.yaml index e47e9d240f..4ccbacf563 100644 --- a/glide.yaml +++ b/glide.yaml @@ -9,20 +9,18 @@ import: - package: github.com/spf13/cobra - package: github.com/spf13/viper version: master - - package: github.com/prometheus/client_golang version: ae27198cdd90bf12cd134ad79d1366a6cf49f632 - -- package: github.com/cosmos/cosmos-sdk/crypto +- package: github.com/cosmos/cosmos-sdk version: ~0.33.0 - + subpackages: + - crypto - package: github.com/tendermint/go-amino version: ~0.14.1 - package: github.com/tendermint/iavl version: ~0.12.2 - package: github.com/tendermint/tendermint version: ~0.31.0 - - package: github.com/stretchr/testify version: ^1.2.1 subpackages: @@ -41,40 +39,29 @@ import: - trees/avltree - package: github.com/boz/go-lifecycle - package: github.com/googleapis/gnostic - # version: ~0.2.0 subpackages: - OpenAPIv2 - - package: github.com/golang/protobuf version: ~1.1.0 - package: github.com/grpc-ecosystem/grpc-gateway - package: google.golang.org/grpc version: ^1.13.0 - - package: github.com/juju/errors - - package: k8s.io/client-go - # version: ~8.0.0 version: kubernetes-1.12.2 subpackages: - '...' - - package: k8s.io/apiextensions-apiserver version: kubernetes-1.12.2 - - package: k8s.io/apimachinery version: kubernetes-1.12.2 subpackages: - '...' - - package: k8s.io/code-generator version: kubernetes-1.12.2 - - package: k8s.io/metrics version: kubernetes-1.12.2 - - package: k8s.io/gengo - - package: github.com/hashicorp/golang-lru - package: github.com/caarlos0/env version: ^3.3.0 @@ -82,12 +69,11 @@ import: version: ^3.5.1 - package: github.com/satori/go.uuid version: ~1.2.0 - - package: golang.org/x/sys version: 1c9583448a9c3aa0f9a6a5241bf73c0bd8aafded subpackages: - unix - - package: golang.org/x/crypto + version: 3764759f34a542a3aef74d6b02e35be7ab893bba repo: https://github.com/tendermint/crypto - version: "3764759f34a542a3aef74d6b02e35be7ab893bba" +- package: github.com/gosuri/uitable diff --git a/util/uiutil/printer.go b/util/uiutil/printer.go new file mode 100644 index 0000000000..2fa56f6030 --- /dev/null +++ b/util/uiutil/printer.go @@ -0,0 +1,68 @@ +package uiutil + +import ( + "bytes" + "fmt" + "io" + "os" +) + +// Out is the default out +var Out = os.Stdout + +// Component in the interface that UI components need to implement +type Component interface { + Bytes() []byte +} + +type Printer interface { + Component + AddTitle(string) Printer + Add(Component) Printer + Flush() error +} + +// printer represents a buffered container for components that can be flushed +type printer struct { + out io.Writer + comps []Component +} + +// NewPrinter returns a pointer to a new printer object +func NewPrinter(out io.Writer) Printer { + if out == nil { + out = Out + } + return &printer{out: out} +} + +// Add adds the components to the printer +func (p *printer) Add(c Component) Printer { + p.comps = append(p.comps, c) + return p +} + +// AddTitle adds a Title to the printer +func (p *printer) AddTitle(title string) Printer { + return p.Add(&Title{text: title}) +} + +// Bytes returns the formmated string of the output +func (p *printer) Bytes() []byte { + var buf bytes.Buffer + for _, c := range p.comps { + buf.Write(c.Bytes()) + buf.Write([]byte{'\n'}) + } + return buf.Bytes() +} + +// Flush prints the output to the writer and clears the buffer +func (p *printer) Flush() error { + _, err := fmt.Fprintln(p.out, p) + if err != nil { + return err + } + p.comps = nil + return nil +} diff --git a/util/uiutil/printer_test.go b/util/uiutil/printer_test.go new file mode 100644 index 0000000000..78b5ed28ef --- /dev/null +++ b/util/uiutil/printer_test.go @@ -0,0 +1,18 @@ +package uiutil + +import ( + "bytes" + "io" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPrinter(t *testing.T) { + var buf bytes.Buffer + w := io.MultiWriter(&buf) + p := NewPrinter(w) + p.Add(bytes.NewBufferString("foo bar")) + err := p.Flush() + require.NoError(t, err) +} diff --git a/util/uiutil/title.go b/util/uiutil/title.go new file mode 100644 index 0000000000..1ab953ac4f --- /dev/null +++ b/util/uiutil/title.go @@ -0,0 +1,32 @@ +package uiutil + +import ( + "bytes" +) + +// TitleUnderliner is the underline character for the title +var TitleUnderliner = "=" + +// Title is a UI component that renders a title. Title implements Component interface. +type Title struct { + text string +} + +func NewTitle(text string) *Title { + return &Title{text: text} +} + +// String returns the formated string of the title +func (t *Title) Bytes() []byte { + var buf bytes.Buffer + buf.WriteString(t.text + "\n") + for i := 0; i < len(t.text); i++ { + buf.Write([]byte(TitleUnderliner)) + } + buf.WriteString("\n") + return buf.Bytes() +} + +func (t *Title) String() string { + return string(t.Bytes()) +} diff --git a/util/uiutil/title_test.go b/util/uiutil/title_test.go new file mode 100644 index 0000000000..e4039b3ac9 --- /dev/null +++ b/util/uiutil/title_test.go @@ -0,0 +1,15 @@ +package uiutil + + +import ( + "os" + "testing" +) + +func TestTitle_String(t *testing.T) { + got := string(NewPrinter(os.Stdout).AddTitle("foo").AddTitle("bar").Bytes()) + expect := "foo\n===\n\nbar\n===\n\n" + if got != expect { + t.Fatal("== expected\n", expect, "== got\n", got) + } +}