Merge pull request #4597 from heyitsanthony/etcdctlv3-format

etcdctlv3: add formatting for json and protobuf
This commit is contained in:
Xiang Li 2016-02-23 13:08:19 -08:00
commit 86e04d5ff3
9 changed files with 219 additions and 70 deletions

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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))
}

View File

@ -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")
}

View File

@ -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)
}
}
}

View File

@ -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)
}
}

View File

@ -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")