diff --git a/etcdctlv3/README.md b/etcdctlv3/README.md index 1a6a5e0ce..f560d276d 100644 --- a/etcdctlv3/README.md +++ b/etcdctlv3/README.md @@ -13,13 +13,19 @@ PUT assigns the specified value with the specified key. If key already holds a v #### Return value -Simple reply +##### Simple reply - OK if PUT executed correctly. Exit code is zero. - Error string if PUT failed. Exit code is non-zero. -TODO: probably json and binary encoded proto +##### JSON reply + +The JSON encoding of the PUT [RPC response][etcdrpc]. + +##### Protobuf reply + +The protobuf encoding of the PUT [RPC response][etcdrpc]. #### Examples @@ -60,13 +66,19 @@ TODO: add consistency, from, prefix #### Return value -Simple reply +##### Simple reply - \\n\\n\\n\... - Error string if GET failed. Exit code is non-zero. -TODO: probably json and binary encoded proto +##### JSON reply + +The JSON encoding of the [RPC message][etcdrpc] for a key-value pair for each fetched key-value. + +##### Protobuf reply + +The protobuf encoding of the [RPC message][etcdrpc] for a key-value pair for each fetched key-value. #### Examples @@ -78,7 +90,7 @@ bar #### Notes -If any key or value contains non-printable characters or control characters, the output in text format (e.g. simple reply or JSON reply) might be ambiguous. +If any key or value contains non-printable characters or control characters, the output in text format (e.g. simple reply) might be ambiguous. Adding `--hex` to print key or value as hex encode string in text format can resolve this issue. ### DEL [options] \ [range_end] @@ -91,13 +103,19 @@ TODO: --prefix, --from #### Return value -Simple reply +##### Simple reply - The number of keys that were removed in decimal if DEL executed correctly. Exit code is zero. - Error string if DEL failed. Exit code is non-zero. -TODO: probably json and binary encoded proto +##### JSON reply + +The JSON encoding of the DeleteRange [RPC response][etcdrpc]. + +##### Protobuf reply + +The protobuf encoding of the DeleteRange [RPC response][etcdrpc]. #### Examples @@ -142,7 +160,7 @@ TODO: non-interactive mode #### Return value -Simple reply +##### Simple reply - SUCCESS if etcd processed the transaction success list, FAILURE if etcd processed the transaction failure list. @@ -150,7 +168,13 @@ Simple reply - Additional error string if TXN failed. Exit code is non-zero. -TODO: probably json and binary encoded proto +##### JSON reply + +The JSON encoding of the Txn [RPC response][etcdrpc]. + +##### Protobuf reply + +The protobuf encoding of the Txn [RPC response][etcdrpc]. #### Examples @@ -205,7 +229,13 @@ watch [options] \n - Additional error string if WATCH failed. Exit code is non-zero. -TODO: probably json and binary encoded proto +##### JSON reply + +The JSON encoding of the [RPC message][storagerpc] for each received Event. + +##### Protobuf reply + +The protobuf encoding of the [RPC message][storagerpc] for each received Event. #### Examples @@ -265,3 +295,12 @@ Simple reply ``` [mirror]: ./doc/mirror_maker.md + + +## Notes + +- JSON encoding for keys and values uses base64 since they are byte strings. + + +[etcdrpc]: ../etcdserver/etcdserverpb/rpc.proto +[storagerpc]: ../storage/storagepb/kv.proto diff --git a/etcdctlv3/command/del_command.go b/etcdctlv3/command/del_command.go index 3193602d1..cdabf8dd7 100644 --- a/etcdctlv3/command/del_command.go +++ b/etcdctlv3/command/del_command.go @@ -40,7 +40,7 @@ func delCommandFunc(cmd *cobra.Command, args []string) { if err != nil { ExitWithError(ExitError, err) } - printDeleteResponse(*resp) + display.Del(*resp) } func getDelOp(cmd *cobra.Command, args []string) (string, []clientv3.OpOption) { @@ -54,9 +54,3 @@ func getDelOp(cmd *cobra.Command, args []string) (string, []clientv3.OpOption) { } return key, opts } - -func printDeleteResponse(resp clientv3.DeleteResponse) { - // TODO: add number of key removed into the response of delete. - // TODO: print out the number of removed keys. - fmt.Println(0) -} diff --git a/etcdctlv3/command/get_command.go b/etcdctlv3/command/get_command.go index 12b41948a..43a5f167b 100644 --- a/etcdctlv3/command/get_command.go +++ b/etcdctlv3/command/get_command.go @@ -27,7 +27,6 @@ var ( getLimit int64 getSortOrder string getSortTarget string - getHex bool ) // NewGetCommand returns the cobra command for "get". @@ -41,7 +40,6 @@ func NewGetCommand() *cobra.Command { cmd.Flags().StringVar(&getSortOrder, "order", "", "order of results; ASCEND or DESCEND") cmd.Flags().StringVar(&getSortTarget, "sort-by", "", "sort target; CREATE, KEY, MODIFY, VALUE, or VERSION") cmd.Flags().Int64Var(&getLimit, "limit", 0, "maximum number of results") - cmd.Flags().BoolVar(&getHex, "hex", false, "print out key and value as hex encode string for text format") // TODO: add fromkey. // TODO: add prefix. // TODO: add consistency. @@ -57,7 +55,8 @@ func getCommandFunc(cmd *cobra.Command, args []string) { if err != nil { ExitWithError(ExitError, err) } - printGetResponse(*resp, getHex) + + display.Get(*resp) } func getGetOp(cmd *cobra.Command, args []string) (string, []clientv3.OpOption) { @@ -107,9 +106,3 @@ func getGetOp(cmd *cobra.Command, args []string) (string, []clientv3.OpOption) { opts = append(opts, clientv3.WithSort(sortByTarget, sortByOrder)) return key, opts } - -func printGetResponse(resp clientv3.GetResponse, isHex bool) { - for _, kv := range resp.Kvs { - printKV(isHex, kv) - } -} diff --git a/etcdctlv3/command/global.go b/etcdctlv3/command/global.go index 961f3c385..e93510647 100644 --- a/etcdctlv3/command/global.go +++ b/etcdctlv3/command/global.go @@ -30,8 +30,13 @@ import ( type GlobalFlags struct { Endpoints string TLS transport.TLSInfo + + OutputFormat string + IsHex bool } +var display printer = &simplePrinter{} + func mustClientFromCmd(cmd *cobra.Command) *clientv3.Client { endpoint, err := cmd.Flags().GetString("endpoint") if err != nil { @@ -57,6 +62,12 @@ func mustClientFromCmd(cmd *cobra.Command) *clientv3.Client { ExitWithError(ExitBadArgs, errors.New("empty string is passed to --cacert option")) } + isHex, _ := cmd.Flags().GetBool("hex") + outputType, _ := cmd.Flags().GetString("write-out") + if display = NewPrinter(outputType, isHex); display == nil { + ExitWithError(ExitBadFeature, errors.New("unsupported output format")) + } + return mustClient(endpoint, cert, key, cacert) } @@ -90,6 +101,7 @@ func mustClient(endpoint, cert, key, cacert string) *clientv3.Client { if err != nil { ExitWithError(ExitBadConnection, err) } + return client } diff --git a/etcdctlv3/command/printer.go b/etcdctlv3/command/printer.go new file mode 100644 index 000000000..1bf44a070 --- /dev/null +++ b/etcdctlv3/command/printer.go @@ -0,0 +1,146 @@ +// Copyright 2016 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 ( + "encoding/json" + "fmt" + "os" + + v3 "github.com/coreos/etcd/clientv3" + pb "github.com/coreos/etcd/etcdserver/etcdserverpb" + spb "github.com/coreos/etcd/storage/storagepb" +) + +type printer interface { + Del(v3.DeleteResponse) + Get(v3.GetResponse) + Put(v3.PutResponse) + Txn(v3.TxnResponse) + Watch(v3.WatchResponse) +} + +func NewPrinter(printerType string, isHex bool) printer { + switch printerType { + case "simple": + return &simplePrinter{isHex: isHex} + case "json": + return &jsonPrinter{} + case "protobuf": + return &pbPrinter{} + } + return nil +} + +type simplePrinter struct { + isHex bool +} + +func (s *simplePrinter) Del(v3.DeleteResponse) { + // TODO: add number of key removed into the response of delete. + // TODO: print out the number of removed keys. + fmt.Println(0) +} + +func (s *simplePrinter) Get(resp v3.GetResponse) { + for _, kv := range resp.Kvs { + printKV(s.isHex, kv) + } +} + +func (s *simplePrinter) Put(r v3.PutResponse) { fmt.Println("OK") } + +func (s *simplePrinter) Txn(resp v3.TxnResponse) { + if resp.Succeeded { + fmt.Println("SUCCESS") + } else { + fmt.Println("FAILURE") + } + + for _, r := range resp.Responses { + fmt.Println("") + switch v := r.Response.(type) { + case *pb.ResponseUnion_ResponseDeleteRange: + s.Del((v3.DeleteResponse)(*v.ResponseDeleteRange)) + case *pb.ResponseUnion_ResponsePut: + s.Put((v3.PutResponse)(*v.ResponsePut)) + case *pb.ResponseUnion_ResponseRange: + s.Get(((v3.GetResponse)(*v.ResponseRange))) + default: + fmt.Printf("unexpected response %+v\n", r) + } + } +} + +func (s *simplePrinter) Watch(resp v3.WatchResponse) { + for _, e := range resp.Events { + fmt.Println(e.Type) + printKV(s.isHex, e.Kv) + } +} + +type jsonPrinter struct{} + +func (p *jsonPrinter) Del(r v3.DeleteResponse) { printJSON(r) } +func (p *jsonPrinter) Get(r v3.GetResponse) { + for _, kv := range r.Kvs { + printJSON(kv) + } +} +func (p *jsonPrinter) Put(r v3.PutResponse) { printJSON(r) } +func (p *jsonPrinter) Txn(r v3.TxnResponse) { printJSON(r) } +func (p *jsonPrinter) Watch(r v3.WatchResponse) { printJSON(r) } + +func printJSON(v interface{}) { + b, err := json.Marshal(v) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + return + } + fmt.Println(string(b)) +} + +type pbPrinter struct{} + +type pbMarshal interface { + Marshal() ([]byte, error) +} + +func (p *pbPrinter) Del(r v3.DeleteResponse) { + printPB((*pb.DeleteRangeResponse)(&r)) +} +func (p *pbPrinter) Get(r v3.GetResponse) { + printPB((*pb.RangeResponse)(&r)) +} +func (p *pbPrinter) Put(r v3.PutResponse) { + printPB((*pb.PutResponse)(&r)) +} +func (p *pbPrinter) Txn(r v3.TxnResponse) { + printPB((*pb.TxnResponse)(&r)) +} +func (p *pbPrinter) Watch(r v3.WatchResponse) { + for _, ev := range r.Events { + printPB((*spb.Event)(ev)) + } +} + +func printPB(m pbMarshal) { + b, err := m.Marshal() + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + return + } + fmt.Printf(string(b)) +} diff --git a/etcdctlv3/command/put_command.go b/etcdctlv3/command/put_command.go index 5f21351de..709675937 100644 --- a/etcdctlv3/command/put_command.go +++ b/etcdctlv3/command/put_command.go @@ -64,7 +64,7 @@ func putCommandFunc(cmd *cobra.Command, args []string) { if err != nil { ExitWithError(ExitError, err) } - printPutResponse(*resp) + display.Put(*resp) } func getPutOp(cmd *cobra.Command, args []string) (string, string, []clientv3.OpOption) { @@ -90,7 +90,3 @@ func getPutOp(cmd *cobra.Command, args []string) (string, string, []clientv3.OpO return key, value, opts } - -func printPutResponse(resp clientv3.PutResponse) { - fmt.Println("OK") -} diff --git a/etcdctlv3/command/txn_command.go b/etcdctlv3/command/txn_command.go index 37c24aab5..13950166b 100644 --- a/etcdctlv3/command/txn_command.go +++ b/etcdctlv3/command/txn_command.go @@ -24,12 +24,10 @@ import ( "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/clientv3" - pb "github.com/coreos/etcd/etcdserver/etcdserverpb" ) var ( txnInteractive bool - txnHex bool ) // NewTxnCommand returns the cobra command for "txn". @@ -40,7 +38,6 @@ func NewTxnCommand() *cobra.Command { Run: txnCommandFunc, } cmd.Flags().BoolVarP(&txnInteractive, "interactive", "i", false, "input transaction in interactive mode") - cmd.Flags().BoolVar(&txnHex, "hex", false, "print out key-values as hex encoded strings") return cmd } @@ -69,7 +66,7 @@ func txnCommandFunc(cmd *cobra.Command, args []string) { ExitWithError(ExitError, err) } - printTxnResponse(*resp, txnHex) + display.Txn(*resp) } func readCompares(r *bufio.Reader) (cmps []clientv3.Cmp) { @@ -202,25 +199,3 @@ func parseCompare(line string) (*clientv3.Cmp, error) { return &cmp, nil } - -func printTxnResponse(resp clientv3.TxnResponse, isHex bool) { - if resp.Succeeded { - fmt.Println("SUCCESS") - } else { - fmt.Println("FAILURE") - } - - for _, r := range resp.Responses { - fmt.Println("") - switch v := r.Response.(type) { - case *pb.ResponseUnion_ResponseDeleteRange: - printDeleteResponse((clientv3.DeleteResponse)(*v.ResponseDeleteRange)) - case *pb.ResponseUnion_ResponsePut: - printPutResponse((clientv3.PutResponse)(*v.ResponsePut)) - case *pb.ResponseUnion_ResponseRange: - printGetResponse(((clientv3.GetResponse)(*v.ResponseRange)), isHex) - default: - fmt.Printf("unexpected response %+v\n", r) - } - } -} diff --git a/etcdctlv3/command/watch_command.go b/etcdctlv3/command/watch_command.go index 686574fed..58defbefa 100644 --- a/etcdctlv3/command/watch_command.go +++ b/etcdctlv3/command/watch_command.go @@ -28,7 +28,6 @@ import ( var ( watchRev int64 watchPrefix bool - watchHex bool watchInteractive bool ) @@ -40,7 +39,6 @@ func NewWatchCommand() *cobra.Command { Run: watchCommandFunc, } - cmd.Flags().BoolVar(&watchHex, "hex", false, "print out key and value as hex encode string for text format") cmd.Flags().BoolVarP(&watchInteractive, "interactive", "i", false, "interactive mode") cmd.Flags().BoolVar(&watchPrefix, "prefix", false, "watch on a prefix if prefix is set") cmd.Flags().Int64Var(&watchRev, "rev", 0, "revision to start watching") @@ -68,7 +66,7 @@ func watchCommandFunc(cmd *cobra.Command, args []string) { } else { wc = w.Watch(context.TODO(), args[0], watchRev) } - printWatchCh(wc, watchHex) + printWatchCh(wc) err := w.Close() if err == nil { ExitWithError(ExitInterrupted, fmt.Errorf("watch is canceled by the server")) @@ -122,19 +120,12 @@ func watchInteractiveFunc(cmd *cobra.Command, args []string) { } else { ch = w.Watch(context.TODO(), key, watchRev) } - go printWatchCh(ch, watchHex) + go printWatchCh(ch) } } -func printWatchCh(ch clientv3.WatchChan, hex bool) { +func printWatchCh(ch clientv3.WatchChan) { for resp := range ch { - printWatchResponse(resp, hex) - } -} - -func printWatchResponse(resp clientv3.WatchResponse, hex bool) { - for _, e := range resp.Events { - fmt.Println(e.Type) - printKV(hex, e.Kv) + display.Watch(resp) } } diff --git a/etcdctlv3/main.go b/etcdctlv3/main.go index 0ae77570a..3106a7a02 100644 --- a/etcdctlv3/main.go +++ b/etcdctlv3/main.go @@ -43,6 +43,9 @@ var ( func init() { rootCmd.PersistentFlags().StringVar(&globalFlags.Endpoints, "endpoint", "127.0.0.1:2378", "gRPC endpoint") + rootCmd.PersistentFlags().StringVarP(&globalFlags.OutputFormat, "write-out", "w", "simple", "set the output format (simple, json, protobuf)") + rootCmd.PersistentFlags().BoolVar(&globalFlags.IsHex, "hex", false, "print byte strings as hex encoded strings") + rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.CertFile, "cert", "", "identify secure client using this TLS certificate file") rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.KeyFile, "key", "", "identify secure client using this TLS key file") rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.CAFile, "cacert", "", "verify certificates of TLS-enabled secure servers using this CA bundle")