From 54d15256e78e945ad5889966f6a169ea4cef96e6 Mon Sep 17 00:00:00 2001 From: Anthony Romano Date: Fri, 19 Feb 2016 14:46:52 -0800 Subject: [PATCH 1/3] etcdctlv3: use clientv3 api for txn command --- etcdctlv3/command/txn_command.go | 229 ++++++++++++------------------- 1 file changed, 88 insertions(+), 141 deletions(-) diff --git a/etcdctlv3/command/txn_command.go b/etcdctlv3/command/txn_command.go index 0360f2ece..61a0c1a87 100644 --- a/etcdctlv3/command/txn_command.go +++ b/etcdctlv3/command/txn_command.go @@ -23,7 +23,7 @@ import ( "github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra" "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context" - pb "github.com/coreos/etcd/etcdserver/etcdserverpb" + "github.com/coreos/etcd/clientv3" ) // NewTxnCommand returns the cobra command for "txn". @@ -43,13 +43,15 @@ func txnCommandFunc(cmd *cobra.Command, args []string) { reader := bufio.NewReader(os.Stdin) - next := compareState - txn := &pb.TxnRequest{} - for next != nil { - next = next(txn, reader) - } + txn := clientv3.NewKV(mustClientFromCmd(cmd)).Txn(context.Background()) + fmt.Println("entry comparison[key target expected_result compare_value] (end with empty line):") + txn.If(readCompares(reader)...) + fmt.Println("entry success request[method key value(end_range)] (end with empty line):") + txn.Then(readOps(reader)...) + fmt.Println("entry failure request[method key value(end_range)] (end with empty line):") + txn.Else(readOps(reader)...) - resp, err := mustClientFromCmd(cmd).KV.Txn(context.Background(), txn) + resp, err := txn.Commit() if err != nil { ExitWithError(ExitError, err) } @@ -60,179 +62,124 @@ func txnCommandFunc(cmd *cobra.Command, args []string) { } } -type stateFunc func(txn *pb.TxnRequest, r *bufio.Reader) stateFunc +func readCompares(r *bufio.Reader) (cmps []clientv3.Cmp) { + for { + line, err := r.ReadString('\n') + if err != nil { + ExitWithError(ExitInvalidInput, err) + } + if len(line) == 1 { + break + } -func compareState(txn *pb.TxnRequest, r *bufio.Reader) stateFunc { - fmt.Println("entry comparison[key target expected_result compare_value] (end with empty line):") - - line, err := r.ReadString('\n') - if err != nil { - ExitWithError(ExitInvalidInput, err) + // remove trialling \n + line = line[:len(line)-1] + cmp, err := parseCompare(line) + if err != nil { + ExitWithError(ExitInvalidInput, err) + } + cmps = append(cmps, *cmp) } - if len(line) == 1 { - return successState - } - - // remove trialling \n - line = line[:len(line)-1] - c, err := parseCompare(line) - if err != nil { - ExitWithError(ExitInvalidInput, err) - } - - txn.Compare = append(txn.Compare, c) - - return compareState + return cmps } -func successState(txn *pb.TxnRequest, r *bufio.Reader) stateFunc { - fmt.Println("entry success request[method key value(end_range)] (end with empty line):") +func readOps(r *bufio.Reader) (ops []clientv3.Op) { + for { + line, err := r.ReadString('\n') + if err != nil { + ExitWithError(ExitInvalidInput, err) + } + if len(line) == 1 { + break + } - line, err := r.ReadString('\n') - if err != nil { - ExitWithError(ExitInvalidInput, err) + // remove trialling \n + line = line[:len(line)-1] + op, err := parseRequestUnion(line) + if err != nil { + ExitWithError(ExitInvalidInput, err) + } + ops = append(ops, *op) } - if len(line) == 1 { - return failureState - } - - // remove trialling \n - line = line[:len(line)-1] - ru, err := parseRequestUnion(line) - if err != nil { - ExitWithError(ExitInvalidInput, err) - } - - txn.Success = append(txn.Success, ru) - - return successState + return ops } -func failureState(txn *pb.TxnRequest, r *bufio.Reader) stateFunc { - fmt.Println("entry failure request[method key value(end_range)] (end with empty line):") - - line, err := r.ReadString('\n') - if err != nil { - ExitWithError(ExitInvalidInput, err) - } - - if len(line) == 1 { - return nil - } - - // remove trialling \n - line = line[:len(line)-1] - ru, err := parseRequestUnion(line) - if err != nil { - ExitWithError(ExitInvalidInput, err) - } - - txn.Failure = append(txn.Failure, ru) - - return failureState -} - -func parseRequestUnion(line string) (*pb.RequestUnion, error) { +func parseRequestUnion(line string) (*clientv3.Op, error) { parts := strings.Split(line, " ") if len(parts) < 2 { return nil, fmt.Errorf("invalid txn compare request: %s", line) } - ru := &pb.RequestUnion{} - key := []byte(parts[1]) + op := &clientv3.Op{} + key := parts[1] switch parts[0] { case "r", "range": if len(parts) == 3 { - ru.Request = &pb.RequestUnion_RequestRange{ - RequestRange: &pb.RangeRequest{ - Key: key, - RangeEnd: []byte(parts[2]), - }} + *op = clientv3.OpGet(key, clientv3.WithRange(parts[2])) } else { - ru.Request = &pb.RequestUnion_RequestRange{ - RequestRange: &pb.RangeRequest{ - Key: key, - }} + *op = clientv3.OpGet(key) } case "p", "put": - ru.Request = &pb.RequestUnion_RequestPut{ - RequestPut: &pb.PutRequest{ - Key: key, - Value: []byte(parts[2]), - }} + *op = clientv3.OpPut(key, parts[2]) case "d", "deleteRange": if len(parts) == 3 { - ru.Request = &pb.RequestUnion_RequestDeleteRange{ - RequestDeleteRange: &pb.DeleteRangeRequest{ - Key: key, - RangeEnd: []byte(parts[2]), - }} + *op = clientv3.OpDelete(key, clientv3.WithRange(parts[2])) } else { - ru.Request = &pb.RequestUnion_RequestDeleteRange{ - RequestDeleteRange: &pb.DeleteRangeRequest{ - Key: key, - }} + *op = clientv3.OpDelete(key) } default: return nil, fmt.Errorf("invalid txn request: %s", line) } - return ru, nil + return op, nil } -func parseCompare(line string) (*pb.Compare, error) { +func parseCompare(line string) (*clientv3.Cmp, error) { parts := strings.Split(line, " ") if len(parts) != 4 { return nil, fmt.Errorf("invalid txn compare request: %s", line) } - var err error - c := &pb.Compare{} - c.Key = []byte(parts[0]) - switch parts[1] { - case "ver", "version": - tv, _ := c.TargetUnion.(*pb.Compare_Version) - if tv != nil { - tv.Version, err = strconv.ParseInt(parts[3], 10, 64) - if err != nil { - return nil, fmt.Errorf("invalid txn compare request: %s", line) - } - } - case "c", "create": - tv, _ := c.TargetUnion.(*pb.Compare_CreateRevision) - if tv != nil { - tv.CreateRevision, err = strconv.ParseInt(parts[3], 10, 64) - if err != nil { - return nil, fmt.Errorf("invalid txn compare request: %s", line) - } - } - case "m", "mod": - tv, _ := c.TargetUnion.(*pb.Compare_ModRevision) - if tv != nil { - tv.ModRevision, err = strconv.ParseInt(parts[3], 10, 64) - if err != nil { - return nil, fmt.Errorf("invalid txn compare request: %s", line) - } - } - case "val", "value": - tv, _ := c.TargetUnion.(*pb.Compare_Value) - if tv != nil { - tv.Value = []byte(parts[3]) - } + cmpType := "" + switch parts[2] { + case "g", "greater": + cmpType = ">" + case "e", "equal": + cmpType = "=" + case "l", "less": + cmpType = "<" default: return nil, fmt.Errorf("invalid txn compare request: %s", line) } - switch parts[2] { - case "g", "greater": - c.Result = pb.Compare_GREATER - case "e", "equal": - c.Result = pb.Compare_EQUAL - case "l", "less": - c.Result = pb.Compare_LESS - default: + var ( + v int64 + err error + cmp clientv3.Cmp + ) + + key := parts[0] + switch parts[1] { + case "ver", "version": + if v, err = strconv.ParseInt(parts[3], 10, 64); err != nil { + cmp = clientv3.Compare(clientv3.Version(key), cmpType, v) + } + case "c", "create": + if v, err = strconv.ParseInt(parts[3], 10, 64); err != nil { + cmp = clientv3.Compare(clientv3.CreatedRevision(key), cmpType, v) + } + case "m", "mod": + if v, err = strconv.ParseInt(parts[3], 10, 64); err != nil { + cmp = clientv3.Compare(clientv3.ModifiedRevision(key), cmpType, v) + } + case "val", "value": + cmp = clientv3.Compare(clientv3.Value(key), cmpType, parts[3]) + } + + if err != nil { return nil, fmt.Errorf("invalid txn compare request: %s", line) } - return c, nil + + return &cmp, nil } From 87dcb2adea5b168242ec62a135fae56b8a62e5ce Mon Sep 17 00:00:00 2001 From: Anthony Romano Date: Fri, 19 Feb 2016 15:48:29 -0800 Subject: [PATCH 2/3] etcdctlv3: unify txn interactive mode input with get/put/delete --- etcdctlv3/README.md | 70 +++++++++++++- etcdctlv3/command/del_command.go | 21 +++-- etcdctlv3/command/get_command.go | 23 +++-- etcdctlv3/command/put_command.go | 25 ++++- etcdctlv3/command/txn_command.go | 155 ++++++++++++++++++++----------- 5 files changed, 218 insertions(+), 76 deletions(-) diff --git a/etcdctlv3/README.md b/etcdctlv3/README.md index d3b132532..99611d9fd 100644 --- a/etcdctlv3/README.md +++ b/etcdctlv3/README.md @@ -15,7 +15,7 @@ PUT assigns the specified value with the specified key. If key already holds a v Simple reply -- OK if PUT executed correctly. Exit code is zero. +- OK if PUT executed correctly. Exit code is zero. - Error string if PUT failed. Exit code is non-zero. @@ -93,7 +93,7 @@ TODO: --prefix, --from Simple reply -- The number of keys that were removed in decimal if DEL executed correctly. Exit code is zero. +- 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. @@ -109,6 +109,72 @@ OK ./etcdctl range foo ``` +### TXN [options] + +TXN applies multiple etcd requests as a single atomic transaction. A transaction consists of list of conditions, a list of requests to apply if all the conditions are true, and a list of requests to apply if any condition is false. + +#### Options + +- hex -- print out keys and values as hex encoded string + +- interactive -- input transaction with interactive mode + +#### Input Format +Interactive mode: +```ebnf + ::= * "\n" "\n" "\n" + ::= (|||) "\n" + ::= "<" | "=" | ">" + := ("c"|"create")"("")" + ::= ("m"|"mod")"("")" + ::= ("val"|"value")"("")" + ::= ("ver"|"version")"("")" + ::= * + ::= * + ::= ((see put, get, del etcdctl command syntax)) "\n" + ::= (%q formatted string) + ::= (%q formatted string) + ::= "\""[0-9]+"\"" + ::= "\""[0-9]+"\"" +``` + +TODO: non-interactive mode + +#### Return value + +Simple reply + +- SUCCESS if etcd processed the transaction success list, FAILURE if etcd processed the transaction failure list. + +- Simple reply for each command executed request list, each separated by a blank line. + +- Additional error string if TXN failed. Exit code is non-zero. + +TODO: probably json and binary encoded proto + +#### Examples + +``` bash +./etcdctl txn -i +mod("key1") > "0" + +put key1 "overwrote-key1" + +put key1 "created-key1" +put key2 "some extra key" + +FAILURE + +OK + +OK +``` + +#### Notes + +TODO: non-interactive mode + + ### WATCH [options] [key or prefix] Watch watches events stream on keys or prefixes. The watch command runs until it encounters an error or is terminated by the user. diff --git a/etcdctlv3/command/del_command.go b/etcdctlv3/command/del_command.go index dca38bb3d..3193602d1 100644 --- a/etcdctlv3/command/del_command.go +++ b/etcdctlv3/command/del_command.go @@ -33,22 +33,29 @@ func NewDelCommand() *cobra.Command { // delCommandFunc executes the "del" command. func delCommandFunc(cmd *cobra.Command, args []string) { + key, opts := getDelOp(cmd, args) + c := mustClientFromCmd(cmd) + kvapi := clientv3.NewKV(c) + resp, err := kvapi.Delete(context.TODO(), key, opts...) + if err != nil { + ExitWithError(ExitError, err) + } + printDeleteResponse(*resp) +} + +func getDelOp(cmd *cobra.Command, args []string) (string, []clientv3.OpOption) { if len(args) == 0 || len(args) > 2 { ExitWithError(ExitBadArgs, fmt.Errorf("del command needs one argument as key and an optional argument as range_end.")) } - opts := []clientv3.OpOption{} key := args[0] if len(args) > 1 { opts = append(opts, clientv3.WithRange(args[1])) } + return key, opts +} - c := mustClientFromCmd(cmd) - kvapi := clientv3.NewKV(c) - _, err := kvapi.Delete(context.TODO(), key, opts...) - if err != nil { - ExitWithError(ExitError, err) - } +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 167e8db06..12b41948a 100644 --- a/etcdctlv3/command/get_command.go +++ b/etcdctlv3/command/get_command.go @@ -50,6 +50,17 @@ func NewGetCommand() *cobra.Command { // getCommandFunc executes the "get" command. func getCommandFunc(cmd *cobra.Command, args []string) { + key, opts := getGetOp(cmd, args) + c := mustClientFromCmd(cmd) + kvapi := clientv3.NewKV(c) + resp, err := kvapi.Get(context.TODO(), key, opts...) + if err != nil { + ExitWithError(ExitError, err) + } + printGetResponse(*resp, getHex) +} + +func getGetOp(cmd *cobra.Command, args []string) (string, []clientv3.OpOption) { if len(args) == 0 { ExitWithError(ExitBadArgs, fmt.Errorf("range command needs arguments.")) } @@ -94,15 +105,11 @@ func getCommandFunc(cmd *cobra.Command, args []string) { } opts = append(opts, clientv3.WithSort(sortByTarget, sortByOrder)) + return key, opts +} - c := mustClientFromCmd(cmd) - kvapi := clientv3.NewKV(c) - resp, err := kvapi.Get(context.TODO(), key, opts...) - if err != nil { - ExitWithError(ExitError, err) - } - +func printGetResponse(resp clientv3.GetResponse, isHex bool) { for _, kv := range resp.Kvs { - printKV(getHex, kv) + printKV(isHex, kv) } } diff --git a/etcdctlv3/command/put_command.go b/etcdctlv3/command/put_command.go index d36689d58..5f21351de 100644 --- a/etcdctlv3/command/put_command.go +++ b/etcdctlv3/command/put_command.go @@ -56,6 +56,18 @@ will store the content of the file to . // putCommandFunc executes the "put" command. func putCommandFunc(cmd *cobra.Command, args []string) { + key, value, opts := getPutOp(cmd, args) + + c := mustClientFromCmd(cmd) + kvapi := clientv3.NewKV(c) + resp, err := kvapi.Put(context.TODO(), key, value, opts...) + if err != nil { + ExitWithError(ExitError, err) + } + printPutResponse(*resp) +} + +func getPutOp(cmd *cobra.Command, args []string) (string, string, []clientv3.OpOption) { if len(args) == 0 { ExitWithError(ExitBadArgs, fmt.Errorf("put command needs 1 argument and input from stdin or 2 arguments.")) } @@ -71,11 +83,14 @@ func putCommandFunc(cmd *cobra.Command, args []string) { ExitWithError(ExitBadArgs, fmt.Errorf("bad lease ID (%v), expecting ID in Hex", err)) } - c := mustClientFromCmd(cmd) - kvapi := clientv3.NewKV(c) - _, err = kvapi.Put(context.TODO(), key, value, clientv3.WithLease(lease.LeaseID(id))) - if err != nil { - ExitWithError(ExitError, err) + opts := []clientv3.OpOption{} + if id != 0 { + opts = append(opts, clientv3.WithLease(lease.LeaseID(id))) } + + 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 61a0c1a87..91acf99a7 100644 --- a/etcdctlv3/command/txn_command.go +++ b/etcdctlv3/command/txn_command.go @@ -18,21 +18,31 @@ import ( "bufio" "fmt" "os" + "regexp" "strconv" "strings" "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". func NewTxnCommand() *cobra.Command { - return &cobra.Command{ - Use: "txn", + cmd := &cobra.Command{ + Use: "txn [options]", Short: "Txn processes all the requests in one transaction.", 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 } // txnCommandFunc executes the "txn" command. @@ -41,25 +51,26 @@ func txnCommandFunc(cmd *cobra.Command, args []string) { ExitWithError(ExitBadArgs, fmt.Errorf("txn command does not accept argument.")) } + if !txnInteractive { + ExitWithError(ExitBadFeature, fmt.Errorf("txn command only supports interactive mode")) + } + reader := bufio.NewReader(os.Stdin) txn := clientv3.NewKV(mustClientFromCmd(cmd)).Txn(context.Background()) - fmt.Println("entry comparison[key target expected_result compare_value] (end with empty line):") + fmt.Println("compares:") txn.If(readCompares(reader)...) - fmt.Println("entry success request[method key value(end_range)] (end with empty line):") + fmt.Println("success requests (get, put, delete):") txn.Then(readOps(reader)...) - fmt.Println("entry failure request[method key value(end_range)] (end with empty line):") + fmt.Println("failure requests (get, put, delete):") txn.Else(readOps(reader)...) resp, err := txn.Commit() if err != nil { ExitWithError(ExitError, err) } - if resp.Succeeded { - fmt.Println("executed success request list") - } else { - fmt.Println("executed failure request list") - } + + printTxnResponse(*resp, txnHex) } func readCompares(r *bufio.Reader) (cmps []clientv3.Cmp) { @@ -106,51 +117,65 @@ func readOps(r *bufio.Reader) (ops []clientv3.Op) { return ops } +func argify(s string) []string { + r := regexp.MustCompile("'.+'|\".+\"|\\S+") + return r.FindAllString(s, -1) +} + func parseRequestUnion(line string) (*clientv3.Op, error) { - parts := strings.Split(line, " ") - if len(parts) < 2 { + args := argify(line) + if len(args) < 2 { return nil, fmt.Errorf("invalid txn compare request: %s", line) } - op := &clientv3.Op{} - key := parts[1] - switch parts[0] { - case "r", "range": - if len(parts) == 3 { - *op = clientv3.OpGet(key, clientv3.WithRange(parts[2])) - } else { - *op = clientv3.OpGet(key) - } - case "p", "put": - *op = clientv3.OpPut(key, parts[2]) - case "d", "deleteRange": - if len(parts) == 3 { - *op = clientv3.OpDelete(key, clientv3.WithRange(parts[2])) - } else { - *op = clientv3.OpDelete(key) - } - default: + opc := make(chan clientv3.Op, 1) + + put := NewPutCommand() + put.Run = func(cmd *cobra.Command, args []string) { + key, value, opts := getPutOp(cmd, args) + opc <- clientv3.OpPut(key, value, opts...) + } + get := NewGetCommand() + get.Run = func(cmd *cobra.Command, args []string) { + key, opts := getGetOp(cmd, args) + opc <- clientv3.OpGet(key, opts...) + } + del := NewDelCommand() + del.Run = func(cmd *cobra.Command, args []string) { + key, opts := getDelOp(cmd, args) + opc <- clientv3.OpDelete(key, opts...) + } + cmds := &cobra.Command{SilenceErrors: true} + cmds.AddCommand(put, get, del) + + cmds.SetArgs(args) + if err := cmds.Execute(); err != nil { return nil, fmt.Errorf("invalid txn request: %s", line) } - return op, nil + + op := <-opc + return &op, nil } func parseCompare(line string) (*clientv3.Cmp, error) { - parts := strings.Split(line, " ") - if len(parts) != 4 { - return nil, fmt.Errorf("invalid txn compare request: %s", line) + var ( + key string + op string + val string + ) + + lparenSplit := strings.SplitN(line, "(", 2) + if len(lparenSplit) != 2 { + return nil, fmt.Errorf("malformed comparison: %s", line) } - cmpType := "" - switch parts[2] { - case "g", "greater": - cmpType = ">" - case "e", "equal": - cmpType = "=" - case "l", "less": - cmpType = "<" - default: - return nil, fmt.Errorf("invalid txn compare request: %s", line) + target := lparenSplit[0] + n, serr := fmt.Sscanf(lparenSplit[1], "%q) %s %q", &key, &op, &val) + if n != 3 { + return nil, fmt.Errorf("malformed comparison: %s; got %s(%q) %s %q", line, target, key, op, val) + } + if serr != nil { + return nil, fmt.Errorf("malformed comparison: %s (%v)", line, serr) } var ( @@ -158,23 +183,23 @@ func parseCompare(line string) (*clientv3.Cmp, error) { err error cmp clientv3.Cmp ) - - key := parts[0] - switch parts[1] { + switch target { case "ver", "version": - if v, err = strconv.ParseInt(parts[3], 10, 64); err != nil { - cmp = clientv3.Compare(clientv3.Version(key), cmpType, v) + if v, err = strconv.ParseInt(val, 10, 64); err == nil { + cmp = clientv3.Compare(clientv3.Version(key), op, v) } case "c", "create": - if v, err = strconv.ParseInt(parts[3], 10, 64); err != nil { - cmp = clientv3.Compare(clientv3.CreatedRevision(key), cmpType, v) + if v, err = strconv.ParseInt(val, 10, 64); err == nil { + cmp = clientv3.Compare(clientv3.CreatedRevision(key), op, v) } case "m", "mod": - if v, err = strconv.ParseInt(parts[3], 10, 64); err != nil { - cmp = clientv3.Compare(clientv3.ModifiedRevision(key), cmpType, v) + if v, err = strconv.ParseInt(val, 10, 64); err == nil { + cmp = clientv3.Compare(clientv3.ModifiedRevision(key), op, v) } case "val", "value": - cmp = clientv3.Compare(clientv3.Value(key), cmpType, parts[3]) + cmp = clientv3.Compare(clientv3.Value(key), op, val) + default: + return nil, fmt.Errorf("malformed comparison: %s (unknown target %s)", line, target) } if err != nil { @@ -183,3 +208,25 @@ 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) + } + } +} From b280291f9eb099d78b753f4e7c61b2dd4c6daea4 Mon Sep 17 00:00:00 2001 From: Anthony Romano Date: Mon, 22 Feb 2016 09:55:09 -0800 Subject: [PATCH 3/3] etcdctlv3: use --interactive for interactive mode in watch --- etcdctlv3/README.md | 2 +- etcdctlv3/command/watch_command.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/etcdctlv3/README.md b/etcdctlv3/README.md index 99611d9fd..33d278a0d 100644 --- a/etcdctlv3/README.md +++ b/etcdctlv3/README.md @@ -183,7 +183,7 @@ Watch watches events stream on keys or prefixes. The watch command runs until it - hex -- print out key and value as hex encode string -- i -- begins an interactive watch session +- interactive -- begins an interactive watch session - prefix -- watch on a prefix if prefix is set. diff --git a/etcdctlv3/command/watch_command.go b/etcdctlv3/command/watch_command.go index 86717614f..025e7bf04 100644 --- a/etcdctlv3/command/watch_command.go +++ b/etcdctlv3/command/watch_command.go @@ -44,7 +44,7 @@ func NewWatchCommand() *cobra.Command { } cmd.Flags().BoolVar(&watchHex, "hex", false, "print out key and value as hex encode string for text format") - cmd.Flags().BoolVar(&watchInteractive, "i", false, "interactive mode") + 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")