Browse Source

Merge pull request #4597 from heyitsanthony/etcdctlv3-format

etcdctlv3: add formatting for json and protobuf
Xiang Li 9 years ago
parent
commit
86e04d5ff3

+ 49 - 10
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
 
 - \<key\>\n\<value\>\n\<next_key\>\n\<next_value\>...
 
 - 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] \<key\> [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] <key or prefix>\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

+ 1 - 7
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)
-}

+ 2 - 9
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)
-	}
-}

+ 12 - 0
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
 }
 

+ 146 - 0
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))
+}

+ 1 - 5
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")
-}

+ 1 - 26
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)
-		}
-	}
-}

+ 4 - 13
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)
 	}
 }

+ 3 - 0
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")