From b7647e0b55a8f4014cb65379a668525ec31ae147 Mon Sep 17 00:00:00 2001 From: Gyu-Ho Lee Date: Wed, 25 Nov 2015 14:06:52 -0800 Subject: [PATCH] etcdctlv3: use spf13/cobra for cli interface This replaces codegansta/cli with spf13/cobra base on this guideline: https://github.com/coreos/docs/blob/master/golang/README.md#cli. --- .../{compaction.go => compaction_command.go} | 34 ++-- etcdctlv3/command/delete_range_command.go | 38 ++-- etcdctlv3/command/error.go | 39 ++++ etcdctlv3/command/global.go | 21 +++ etcdctlv3/command/put_command.go | 34 ++-- etcdctlv3/command/range_command.go | 36 ++-- etcdctlv3/command/txn_command.go | 46 ++--- etcdctlv3/command/version_command.go | 35 ++++ etcdctlv3/command/watch_command.go | 34 ++-- etcdctlv3/help.go | 166 ++++++++++++++++++ etcdctlv3/main.go | 54 ++++-- 11 files changed, 421 insertions(+), 116 deletions(-) rename etcdctlv3/command/{compaction.go => compaction_command.go} (58%) create mode 100644 etcdctlv3/command/error.go create mode 100644 etcdctlv3/command/global.go create mode 100644 etcdctlv3/command/version_command.go create mode 100644 etcdctlv3/help.go diff --git a/etcdctlv3/command/compaction.go b/etcdctlv3/command/compaction_command.go similarity index 58% rename from etcdctlv3/command/compaction.go rename to etcdctlv3/command/compaction_command.go index ad8b49870..9844b4227 100644 --- a/etcdctlv3/command/compaction.go +++ b/etcdctlv3/command/compaction_command.go @@ -15,38 +15,42 @@ package command import ( + "fmt" "strconv" - "github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli" + "github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra" "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context" "github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc" pb "github.com/coreos/etcd/etcdserver/etcdserverpb" ) -// NewCompactionCommand returns the CLI command for "compaction". -func NewCompactionCommand() cli.Command { - return cli.Command{ - Name: "compaction", - Action: func(c *cli.Context) { - compactionCommandFunc(c) - }, +// NewCompactionCommand returns the cobra command for "compaction". +func NewCompactionCommand() *cobra.Command { + return &cobra.Command{ + Use: "compaction", + Short: "Compaction compacts the event history in etcd.", + Run: compactionCommandFunc, } } // compactionCommandFunc executes the "compaction" command. -func compactionCommandFunc(c *cli.Context) { - if len(c.Args()) != 1 { - panic("bad arg") +func compactionCommandFunc(cmd *cobra.Command, args []string) { + if len(args) != 1 { + ExitWithError(ExitBadArgs, fmt.Errorf("compaction command needs 1 argument.")) } - rev, err := strconv.ParseInt(c.Args()[0], 10, 64) + rev, err := strconv.ParseInt(args[0], 10, 64) if err != nil { - panic("bad arg") + ExitWithError(ExitError, err) } - conn, err := grpc.Dial(c.GlobalString("endpoint")) + endpoint, err := cmd.Flags().GetString("endpoint") if err != nil { - panic(err) + ExitWithError(ExitError, err) + } + conn, err := grpc.Dial(endpoint) + if err != nil { + ExitWithError(ExitBadConnection, err) } kv := pb.NewKVClient(conn) req := &pb.CompactionRequest{Revision: rev} diff --git a/etcdctlv3/command/delete_range_command.go b/etcdctlv3/command/delete_range_command.go index 741beefcd..874b235dd 100644 --- a/etcdctlv3/command/delete_range_command.go +++ b/etcdctlv3/command/delete_range_command.go @@ -17,36 +17,40 @@ package command import ( "fmt" - "github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli" + "github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra" "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context" "github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc" pb "github.com/coreos/etcd/etcdserver/etcdserverpb" ) -// NewDeleteRangeCommand returns the CLI command for "deleteRange". -func NewDeleteRangeCommand() cli.Command { - return cli.Command{ - Name: "delete-range", - Action: func(c *cli.Context) { - deleteRangeCommandFunc(c) - }, +// NewDeleteRangeCommand returns the cobra command for "deleteRange". +func NewDeleteRangeCommand() *cobra.Command { + return &cobra.Command{ + Use: "delete-range", + Short: "DeleteRange deletes the given range from the store.", + Run: deleteRangeCommandFunc, } } -// deleteRangeCommandFunc executes the "delegeRange" command. -func deleteRangeCommandFunc(c *cli.Context) { - if len(c.Args()) == 0 { - panic("bad arg") +// deleteRangeCommandFunc executes the "deleteRange" command. +func deleteRangeCommandFunc(cmd *cobra.Command, args []string) { + if len(args) == 0 { + ExitWithError(ExitBadArgs, fmt.Errorf("delete-range command needs arguments.")) } var rangeEnd []byte - key := []byte(c.Args()[0]) - if len(c.Args()) > 1 { - rangeEnd = []byte(c.Args()[1]) + key := []byte(args[0]) + if len(args) > 1 { + rangeEnd = []byte(args[1]) } - conn, err := grpc.Dial(c.GlobalString("endpoint")) + + endpoint, err := cmd.Flags().GetString("endpoint") if err != nil { - panic(err) + ExitWithError(ExitError, err) + } + conn, err := grpc.Dial(endpoint) + if err != nil { + ExitWithError(ExitBadConnection, err) } kv := pb.NewKVClient(conn) req := &pb.DeleteRangeRequest{Key: key, RangeEnd: rangeEnd} diff --git a/etcdctlv3/command/error.go b/etcdctlv3/command/error.go new file mode 100644 index 000000000..4662ac1ee --- /dev/null +++ b/etcdctlv3/command/error.go @@ -0,0 +1,39 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package command + +import ( + "fmt" + "os" + + "github.com/coreos/etcd/client" +) + +const ( + // http://tldp.org/LDP/abs/html/exitcodes.html + ExitSuccess = iota + ExitError + ExitBadConnection + ExitInvalidInput // for txn, watch command + ExitBadArgs = 128 +) + +func ExitWithError(code int, err error) { + fmt.Fprintln(os.Stderr, "Error: ", err) + if cerr, ok := err.(*client.ClusterError); ok { + fmt.Fprintln(os.Stderr, cerr.Detail()) + } + os.Exit(code) +} diff --git a/etcdctlv3/command/global.go b/etcdctlv3/command/global.go new file mode 100644 index 000000000..d5bb89977 --- /dev/null +++ b/etcdctlv3/command/global.go @@ -0,0 +1,21 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package command + +// GlobalFlags are flags that defined globally +// and are inherited to all sub-commands. +type GlobalFlags struct { + Endpoints string +} diff --git a/etcdctlv3/command/put_command.go b/etcdctlv3/command/put_command.go index 58b6faab7..334caf221 100644 --- a/etcdctlv3/command/put_command.go +++ b/etcdctlv3/command/put_command.go @@ -17,33 +17,37 @@ package command import ( "fmt" - "github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli" + "github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra" "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context" "github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc" pb "github.com/coreos/etcd/etcdserver/etcdserverpb" ) -// NewPutCommand returns the CLI command for "put". -func NewPutCommand() cli.Command { - return cli.Command{ - Name: "put", - Action: func(c *cli.Context) { - putCommandFunc(c) - }, +// NewPutCommand returns the cobra command for "put". +func NewPutCommand() *cobra.Command { + return &cobra.Command{ + Use: "put", + Short: "Put puts the given key into the store.", + Run: putCommandFunc, } } // putCommandFunc executes the "put" command. -func putCommandFunc(c *cli.Context) { - if len(c.Args()) != 2 { - panic("bad arg") +func putCommandFunc(cmd *cobra.Command, args []string) { + if len(args) != 2 { + ExitWithError(ExitBadArgs, fmt.Errorf("put command needs 2 arguments.")) } - key := []byte(c.Args()[0]) - value := []byte(c.Args()[1]) - conn, err := grpc.Dial(c.GlobalString("endpoint")) + key := []byte(args[0]) + value := []byte(args[1]) + + endpoint, err := cmd.Flags().GetString("endpoint") if err != nil { - panic(err) + ExitWithError(ExitError, err) + } + conn, err := grpc.Dial(endpoint) + if err != nil { + ExitWithError(ExitBadConnection, err) } kv := pb.NewKVClient(conn) req := &pb.PutRequest{Key: key, Value: value} diff --git a/etcdctlv3/command/range_command.go b/etcdctlv3/command/range_command.go index ba3849a85..8aeffd24b 100644 --- a/etcdctlv3/command/range_command.go +++ b/etcdctlv3/command/range_command.go @@ -17,36 +17,40 @@ package command import ( "fmt" - "github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli" + "github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra" "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context" "github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc" pb "github.com/coreos/etcd/etcdserver/etcdserverpb" ) -// NewRangeCommand returns the CLI command for "range". -func NewRangeCommand() cli.Command { - return cli.Command{ - Name: "range", - Action: func(c *cli.Context) { - rangeCommandFunc(c) - }, +// NewRangeCommand returns the cobra command for "range". +func NewRangeCommand() *cobra.Command { + return &cobra.Command{ + Use: "range", + Short: "Range gets the keys in the range from the store.", + Run: rangeCommandFunc, } } // rangeCommandFunc executes the "range" command. -func rangeCommandFunc(c *cli.Context) { - if len(c.Args()) == 0 { - panic("bad arg") +func rangeCommandFunc(cmd *cobra.Command, args []string) { + if len(args) == 0 { + ExitWithError(ExitBadArgs, fmt.Errorf("range command needs arguments.")) } var rangeEnd []byte - key := []byte(c.Args()[0]) - if len(c.Args()) > 1 { - rangeEnd = []byte(c.Args()[1]) + key := []byte(args[0]) + if len(args) > 1 { + rangeEnd = []byte(args[1]) } - conn, err := grpc.Dial(c.GlobalString("endpoint")) + + endpoint, err := cmd.Flags().GetString("endpoint") if err != nil { - panic(err) + ExitWithError(ExitError, err) + } + conn, err := grpc.Dial(endpoint) + if err != nil { + ExitWithError(ExitBadConnection, err) } kv := pb.NewKVClient(conn) req := &pb.RangeRequest{Key: key, RangeEnd: rangeEnd} diff --git a/etcdctlv3/command/txn_command.go b/etcdctlv3/command/txn_command.go index 759d6c45b..a773825ee 100644 --- a/etcdctlv3/command/txn_command.go +++ b/etcdctlv3/command/txn_command.go @@ -21,26 +21,25 @@ import ( "strconv" "strings" - "github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli" + "github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra" "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context" "github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc" pb "github.com/coreos/etcd/etcdserver/etcdserverpb" ) -// NewTxnCommand returns the CLI command for "txn". -func NewTxnCommand() cli.Command { - return cli.Command{ - Name: "txn", - Action: func(c *cli.Context) { - txnCommandFunc(c) - }, +// NewTxnCommand returns the cobra command for "txn". +func NewTxnCommand() *cobra.Command { + return &cobra.Command{ + Use: "txn", + Short: "Txn processes all the requests in one transaction.", + Run: txnCommandFunc, } } // txnCommandFunc executes the "txn" command. -func txnCommandFunc(c *cli.Context) { - if len(c.Args()) != 0 { - panic("unexpected args") +func txnCommandFunc(cmd *cobra.Command, args []string) { + if len(args) != 0 { + ExitWithError(ExitBadArgs, fmt.Errorf("txn command does not accept argument.")) } reader := bufio.NewReader(os.Stdin) @@ -51,15 +50,19 @@ func txnCommandFunc(c *cli.Context) { next = next(txn, reader) } - conn, err := grpc.Dial(c.GlobalString("endpoint")) + endpoint, err := cmd.Flags().GetString("endpoint") if err != nil { - panic(err) + ExitWithError(ExitError, err) + } + conn, err := grpc.Dial(endpoint) + if err != nil { + ExitWithError(ExitBadConnection, err) } kv := pb.NewKVClient(conn) resp, err := kv.Txn(context.Background(), txn) if err != nil { - fmt.Println(err) + ExitWithError(ExitError, err) } if resp.Succeeded { fmt.Println("executed success request list") @@ -75,7 +78,7 @@ func compareState(txn *pb.TxnRequest, r *bufio.Reader) stateFunc { line, err := r.ReadString('\n') if err != nil { - os.Exit(1) + ExitWithError(ExitInvalidInput, err) } if len(line) == 1 { @@ -86,8 +89,7 @@ func compareState(txn *pb.TxnRequest, r *bufio.Reader) stateFunc { line = line[:len(line)-1] c, err := parseCompare(line) if err != nil { - fmt.Println(err) - os.Exit(1) + ExitWithError(ExitInvalidInput, err) } txn.Compare = append(txn.Compare, c) @@ -100,7 +102,7 @@ func successState(txn *pb.TxnRequest, r *bufio.Reader) stateFunc { line, err := r.ReadString('\n') if err != nil { - os.Exit(1) + ExitWithError(ExitInvalidInput, err) } if len(line) == 1 { @@ -111,8 +113,7 @@ func successState(txn *pb.TxnRequest, r *bufio.Reader) stateFunc { line = line[:len(line)-1] ru, err := parseRequestUnion(line) if err != nil { - fmt.Println(err) - os.Exit(1) + ExitWithError(ExitInvalidInput, err) } txn.Success = append(txn.Success, ru) @@ -125,7 +126,7 @@ func failureState(txn *pb.TxnRequest, r *bufio.Reader) stateFunc { line, err := r.ReadString('\n') if err != nil { - os.Exit(1) + ExitWithError(ExitInvalidInput, err) } if len(line) == 1 { @@ -136,8 +137,7 @@ func failureState(txn *pb.TxnRequest, r *bufio.Reader) stateFunc { line = line[:len(line)-1] ru, err := parseRequestUnion(line) if err != nil { - fmt.Println(err) - os.Exit(1) + ExitWithError(ExitInvalidInput, err) } txn.Failure = append(txn.Failure, ru) diff --git a/etcdctlv3/command/version_command.go b/etcdctlv3/command/version_command.go new file mode 100644 index 000000000..b7db54a01 --- /dev/null +++ b/etcdctlv3/command/version_command.go @@ -0,0 +1,35 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package command + +import ( + "fmt" + + "github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra" + "github.com/coreos/etcd/version" +) + +// NewVersionCommand prints out the version of etcd. +func NewVersionCommand() *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "Print the version of etcdctlv3.", + Run: versionCommandFunc, + } +} + +func versionCommandFunc(cmd *cobra.Command, args []string) { + fmt.Println(version.Version) +} diff --git a/etcdctlv3/command/watch_command.go b/etcdctlv3/command/watch_command.go index 98926ab6d..4714e1961 100644 --- a/etcdctlv3/command/watch_command.go +++ b/etcdctlv3/command/watch_command.go @@ -21,33 +21,36 @@ import ( "os" "strings" - "github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli" + "github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra" "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context" "github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc" pb "github.com/coreos/etcd/etcdserver/etcdserverpb" ) -// NewWatchCommand returns the CLI command for "watch". -func NewWatchCommand() cli.Command { - return cli.Command{ - Name: "watch", - Action: func(c *cli.Context) { - watchCommandFunc(c) - }, +// NewWatchCommand returns the cobra command for "watch". +func NewWatchCommand() *cobra.Command { + return &cobra.Command{ + Use: "watch", + Short: "Watch watches the events happening or happened.", + Run: watchCommandFunc, } } // watchCommandFunc executes the "watch" command. -func watchCommandFunc(c *cli.Context) { - conn, err := grpc.Dial(c.GlobalString("endpoint")) +func watchCommandFunc(cmd *cobra.Command, args []string) { + endpoint, err := cmd.Flags().GetString("endpoint") if err != nil { - panic(err) + ExitWithError(ExitInvalidInput, err) + } + conn, err := grpc.Dial(endpoint) + if err != nil { + ExitWithError(ExitBadConnection, err) } wAPI := pb.NewWatchClient(conn) wStream, err := wAPI.Watch(context.TODO()) if err != nil { - panic(err) + ExitWithError(ExitBadConnection, err) } go recvLoop(wStream) @@ -57,8 +60,7 @@ func watchCommandFunc(c *cli.Context) { for { l, err := reader.ReadString('\n') if err != nil { - fmt.Fprintf(os.Stderr, "Error reading watch request line: %v", err) - os.Exit(1) + ExitWithError(ExitInvalidInput, fmt.Errorf("Error reading watch request line: %v", err)) } l = strings.TrimSuffix(l, "\n") @@ -91,10 +93,10 @@ func recvLoop(wStream pb.Watch_WatchClient) { for { resp, err := wStream.Recv() if err == io.EOF { - os.Exit(0) + os.Exit(ExitSuccess) } if err != nil { - panic(err) + ExitWithError(ExitError, err) } fmt.Printf("%s: %s %s\n", resp.Event.Type, string(resp.Event.Kv.Key), string(resp.Event.Kv.Value)) } diff --git a/etcdctlv3/help.go b/etcdctlv3/help.go new file mode 100644 index 000000000..90cdcb047 --- /dev/null +++ b/etcdctlv3/help.go @@ -0,0 +1,166 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// copied from https://github.com/coreos/rkt/blob/master/rkt/help.go + +package main + +import ( + "bytes" + "fmt" + "io" + "os" + "strings" + "text/tabwriter" + "text/template" + + "github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra" + "github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/pflag" + "github.com/coreos/etcd/version" +) + +var ( + commandUsageTemplate *template.Template + templFuncs = template.FuncMap{ + "descToLines": func(s string) []string { + // trim leading/trailing whitespace and split into slice of lines + return strings.Split(strings.Trim(s, "\n\t "), "\n") + }, + "cmdName": func(cmd *cobra.Command, startCmd *cobra.Command) string { + parts := []string{cmd.Name()} + for cmd.HasParent() && cmd.Parent().Name() != startCmd.Name() { + cmd = cmd.Parent() + parts = append([]string{cmd.Name()}, parts...) + } + return strings.Join(parts, " ") + }, + } +) + +func init() { + commandUsage := ` +{{ $cmd := .Cmd }}\ +{{ $cmdname := cmdName .Cmd .Cmd.Root }}\ +NAME: +{{ if not .Cmd.HasParent }}\ +{{printf "\t%s - %s" .Cmd.Name .Cmd.Short}} +{{else}}\ +{{printf "\t%s - %s" $cmdname .Cmd.Short}} +{{end}}\ + +USAGE: +{{printf "\t%s" .Cmd.UseLine}} +{{ if not .Cmd.HasParent }}\ + +VERSION: +{{printf "\t%s" .Version}} +{{end}}\ +{{if .Cmd.HasSubCommands}}\ + +COMMANDS: +{{range .SubCommands}}\ +{{ $cmdname := cmdName . $cmd }}\ +{{ if .Runnable }}\ +{{printf "\t%s\t%s" $cmdname .Short}} +{{end}}\ +{{end}}\ +{{end}}\ +{{ if .Cmd.Long }}\ + +DESCRIPTION: +{{range $line := descToLines .Cmd.Long}}{{printf "\t%s" $line}} +{{end}}\ +{{end}}\ +{{if .Cmd.HasLocalFlags}}\ + +OPTIONS: +{{.LocalFlags}}\ +{{end}}\ +{{if .Cmd.HasInheritedFlags}}\ + +GLOBAL OPTIONS: +{{.GlobalFlags}}\ +{{end}} +`[1:] + + commandUsageTemplate = template.Must(template.New("command_usage").Funcs(templFuncs).Parse(strings.Replace(commandUsage, "\\\n", "", -1))) +} + +func etcdFlagUsages(flagSet *pflag.FlagSet) string { + x := new(bytes.Buffer) + + flagSet.VisitAll(func(flag *pflag.Flag) { + if len(flag.Deprecated) > 0 { + return + } + format := "" + if len(flag.Shorthand) > 0 { + format = " -%s, --%s" + } else { + format = " %s --%s" + } + if len(flag.NoOptDefVal) > 0 { + format = format + "[" + } + if flag.Value.Type() == "string" { + // put quotes on the value + format = format + "=%q" + } else { + format = format + "=%s" + } + if len(flag.NoOptDefVal) > 0 { + format = format + "]" + } + format = format + "\t%s\n" + shorthand := flag.Shorthand + fmt.Fprintf(x, format, shorthand, flag.Name, flag.DefValue, flag.Usage) + }) + + return x.String() +} + +func getSubCommands(cmd *cobra.Command) []*cobra.Command { + var subCommands []*cobra.Command + for _, subCmd := range cmd.Commands() { + subCommands = append(subCommands, subCmd) + subCommands = append(subCommands, getSubCommands(subCmd)...) + } + return subCommands +} + +func usageFunc(cmd *cobra.Command) error { + subCommands := getSubCommands(cmd) + tabOut := getTabOutWithWriter(os.Stdout) + commandUsageTemplate.Execute(tabOut, struct { + Cmd *cobra.Command + LocalFlags string + GlobalFlags string + SubCommands []*cobra.Command + Version string + }{ + cmd, + etcdFlagUsages(cmd.LocalFlags()), + etcdFlagUsages(cmd.InheritedFlags()), + subCommands, + version.Version, + }) + tabOut.Flush() + return nil +} + +func getTabOutWithWriter(writer io.Writer) *tabwriter.Writer { + aTabOut := new(tabwriter.Writer) + aTabOut.Init(writer, 0, 8, 1, '\t', 0) + return aTabOut +} diff --git a/etcdctlv3/main.go b/etcdctlv3/main.go index 75e9c9d6d..04ed982c3 100644 --- a/etcdctlv3/main.go +++ b/etcdctlv3/main.go @@ -16,29 +16,55 @@ package main import ( - "os" + "text/tabwriter" - "github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli" + "github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra" "github.com/coreos/etcd/etcdctlv3/command" - "github.com/coreos/etcd/version" ) -func main() { - app := cli.NewApp() - app.Name = "etcdctlv3" - app.Version = version.Version - app.Usage = "A simple command line client for etcd3." - app.Flags = []cli.Flag{ - cli.StringFlag{Name: "endpoint", Value: "127.0.0.1:2378", Usage: "gRPC endpoint"}, +const ( + cliName = "etcdctlv3" + cliDescription = "A simple command line client for etcd3." +) + +var ( + tabOut *tabwriter.Writer + globalFlags = command.GlobalFlags{} +) + +var ( + rootCmd = &cobra.Command{ + Use: cliName, + Short: cliDescription, + SuggestFor: []string{"etcctlv3", "etcdcltv3", "etlctlv3"}, } - app.Commands = []cli.Command{ +) + +func init() { + rootCmd.PersistentFlags().StringVar(&globalFlags.Endpoints, "endpoint", "127.0.0.1:2378", "gRPC endpoint") + + rootCmd.AddCommand( command.NewRangeCommand(), command.NewPutCommand(), command.NewDeleteRangeCommand(), command.NewTxnCommand(), command.NewCompactionCommand(), command.NewWatchCommand(), - } - - app.Run(os.Args) + command.NewVersionCommand(), + ) +} + +func init() { + cobra.EnablePrefixMatching = true +} + +func main() { + rootCmd.SetUsageFunc(usageFunc) + + // Make help just show the usage + rootCmd.SetHelpTemplate(`{{.UsageString}}`) + + if err := rootCmd.Execute(); err != nil { + command.ExitWithError(command.ExitError, err) + } }