From 9ea8ed3a13c80facf4a8c75c12de4d56a3709641 Mon Sep 17 00:00:00 2001 From: Xiang Li Date: Thu, 18 Feb 2016 13:18:49 -0800 Subject: [PATCH] etcdctlv3: better get command --- etcdctlv3/README.md | 39 +++++++++ etcdctlv3/command/get_command.go | 125 +++++++++++++++++++++++++++++ etcdctlv3/command/range_command.go | 103 ------------------------ etcdctlv3/main.go | 2 +- 4 files changed, 165 insertions(+), 104 deletions(-) create mode 100644 etcdctlv3/command/get_command.go delete mode 100644 etcdctlv3/command/range_command.go diff --git a/etcdctlv3/README.md b/etcdctlv3/README.md index ac5fa662f..b13d3fb13 100644 --- a/etcdctlv3/README.md +++ b/etcdctlv3/README.md @@ -42,6 +42,45 @@ Insert '--' for workaround: ./etcdctl put -- ``` +### GET [options] \ [range_end] + +GET gets the key or a range of keys [key, range_end) if `range-end` is given. + +#### Options + +- hex -- print out key and value as hex encode string + +- limit -- maximum number of results + +- order -- order of results; ASCEND or DESCEND + +- sort-by -- sort target; CREATE, KEY, MODIFY, VALUE, or VERSION + +TODO: add consistency, from, prefix + +#### Return value + +Simple reply + +- \\r\n\\r\n\\r\n\... + +- Error string if GET failed. Exit code is non-zero. + +TODO: probably json and binary encoded proto + +#### Examples + +``` bash +./etcdctl get foo +foo +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. +Adding `--hex` to print key or value as hex encode string in text format can resolve this issue. + ### DEL [options] \ [range_end] Removes the specified key or range of keys [key, range_end) if `range-end` is given. diff --git a/etcdctlv3/command/get_command.go b/etcdctlv3/command/get_command.go new file mode 100644 index 000000000..8c0ed1c2f --- /dev/null +++ b/etcdctlv3/command/get_command.go @@ -0,0 +1,125 @@ +// 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 ( + "encoding/hex" + "fmt" + "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" +) + +var ( + getLimit int64 + getSortOrder string + getSortTarget string + getHex bool +) + +// NewGetCommand returns the cobra command for "get". +func NewGetCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "get [options] [range_end]", + Short: "Get gets the key or a range of keys.", + Run: getCommandFunc, + } + + 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. + return cmd +} + +// getCommandFunc executes the "get" command. +func getCommandFunc(cmd *cobra.Command, args []string) { + if len(args) == 0 { + ExitWithError(ExitBadArgs, fmt.Errorf("range command needs arguments.")) + } + + opts := []clientv3.OpOption{} + key := args[0] + if len(args) > 1 { + opts = append(opts, clientv3.WithRange(args[1])) + } + opts = append(opts, clientv3.WithLimit(getLimit)) + + sortByOrder := clientv3.SortNone + sortOrder := strings.ToUpper(getSortOrder) + switch { + case sortOrder == "ASCEND": + sortByOrder = clientv3.SortAscend + case sortOrder == "DESCEND": + sortByOrder = clientv3.SortDescend + case sortOrder == "": + // nothing + default: + ExitWithError(ExitBadFeature, fmt.Errorf("bad sort order %v", getSortOrder)) + } + + sortByTarget := clientv3.SortByKey + sortTarget := strings.ToUpper(getSortTarget) + switch { + case sortTarget == "CREATE": + sortByTarget = clientv3.SortByCreatedRev + case sortTarget == "KEY": + sortByTarget = clientv3.SortByKey + case sortTarget == "MODIFY": + sortByTarget = clientv3.SortByModifiedRev + case sortTarget == "VALUE": + sortByTarget = clientv3.SortByValue + case sortTarget == "VERSION": + sortByTarget = clientv3.SortByVersion + case sortTarget == "": + // nothing + default: + ExitWithError(ExitBadFeature, fmt.Errorf("bad sort target %v", getSortTarget)) + } + + opts = append(opts, clientv3.WithSort(sortByTarget, sortByOrder)) + + c := mustClientFromCmd(cmd) + kvapi := clientv3.NewKV(c) + resp, err := kvapi.Get(context.TODO(), key, opts...) + if err != nil { + ExitWithError(ExitError, err) + } + + for _, kv := range resp.Kvs { + k, v := string(kv.Key), string(kv.Value) + if getHex { + k = addHexPrefix(hex.EncodeToString(kv.Key)) + v = addHexPrefix(hex.EncodeToString(kv.Value)) + } + fmt.Printf("%s\r\n%s\r\n", k, v) + } +} + +func addHexPrefix(s string) string { + ns := make([]byte, len(s)*2) + for i := 0; i < len(s); i += 2 { + ns[i*2] = '\\' + ns[i*2+1] = 'x' + ns[i*2+2] = s[i] + ns[i*2+3] = s[i+1] + } + return string(ns) +} diff --git a/etcdctlv3/command/range_command.go b/etcdctlv3/command/range_command.go deleted file mode 100644 index 677131551..000000000 --- a/etcdctlv3/command/range_command.go +++ /dev/null @@ -1,103 +0,0 @@ -// 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" - "strings" - - "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" -) - -var ( - rangeLimit int - rangeSortOrder string - rangeSortTarget string -) - -// NewRangeCommand returns the cobra command for "range". -func NewRangeCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "range", - Short: "Range gets the keys in the range from the store.", - Run: rangeCommandFunc, - } - cmd.Flags().StringVar(&rangeSortOrder, "order", "", "order of results; ASCEND or DESCEND") - cmd.Flags().StringVar(&rangeSortTarget, "sort-by", "", "sort target; CREATE, KEY, MODIFY, VALUE, or VERSION") - cmd.Flags().IntVar(&rangeLimit, "limit", 0, "maximum number of results") - return cmd -} - -// rangeCommandFunc executes the "range" command. -func rangeCommandFunc(cmd *cobra.Command, args []string) { - if len(args) == 0 { - ExitWithError(ExitBadArgs, fmt.Errorf("range command needs arguments.")) - } - - var rangeEnd []byte - key := []byte(args[0]) - if len(args) > 1 { - rangeEnd = []byte(args[1]) - } - - sortByOrder := pb.RangeRequest_NONE - sortOrder := strings.ToUpper(rangeSortOrder) - switch { - case sortOrder == "ASCEND": - sortByOrder = pb.RangeRequest_ASCEND - case sortOrder == "DESCEND": - sortByOrder = pb.RangeRequest_DESCEND - case sortOrder == "": - sortByOrder = pb.RangeRequest_NONE - default: - ExitWithError(ExitBadFeature, fmt.Errorf("bad sort order %v", rangeSortOrder)) - } - - sortByTarget := pb.RangeRequest_KEY - sortTarget := strings.ToUpper(rangeSortTarget) - switch { - case sortTarget == "CREATE": - sortByTarget = pb.RangeRequest_CREATE - case sortTarget == "KEY": - sortByTarget = pb.RangeRequest_KEY - case sortTarget == "MODIFY": - sortByTarget = pb.RangeRequest_MOD - case sortTarget == "VALUE": - sortByTarget = pb.RangeRequest_VALUE - case sortTarget == "VERSION": - sortByTarget = pb.RangeRequest_VERSION - case sortTarget == "": - sortByTarget = pb.RangeRequest_KEY - default: - ExitWithError(ExitBadFeature, fmt.Errorf("bad sort target %v", rangeSortTarget)) - } - - req := &pb.RangeRequest{ - Key: key, - RangeEnd: rangeEnd, - SortOrder: sortByOrder, - SortTarget: sortByTarget, - Limit: int64(rangeLimit), - } - resp, err := mustClientFromCmd(cmd).KV.Range(context.Background(), req) - if err != nil { - ExitWithError(ExitError, err) - } - for _, kv := range resp.Kvs { - fmt.Printf("%s %s\n", string(kv.Key), string(kv.Value)) - } -} diff --git a/etcdctlv3/main.go b/etcdctlv3/main.go index a00f5354a..0ae77570a 100644 --- a/etcdctlv3/main.go +++ b/etcdctlv3/main.go @@ -48,7 +48,7 @@ func init() { rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.CAFile, "cacert", "", "verify certificates of TLS-enabled secure servers using this CA bundle") rootCmd.AddCommand( - command.NewRangeCommand(), + command.NewGetCommand(), command.NewPutCommand(), command.NewDelCommand(), command.NewTxnCommand(),