diff --git a/etcdctlv3/README.md b/etcdctlv3/README.md index b13d3fb13..cf7ada7ed 100644 --- a/etcdctlv3/README.md +++ b/etcdctlv3/README.md @@ -108,3 +108,41 @@ OK 1 ./etcdctl range foo ``` + + +### 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. + +#### Options + +- hex -- print out key and value as hex encode string + +- i -- begins an interactive watch session + +- prefix -- watch on a prefix if prefix is set. + +- rev -- the revision to start watching. Specifying a revision is useful for observing past events. + +#### Return value + +Simple reply + +- \\\r\n\\r\n\\\r\n\... + +- Additional error string if WATCH failed. Exit code is non-zero. + +TODO: probably json and binary encoded proto + +#### Examples + +``` bash +./etcdctl watch foo +PUT +foo +bar +``` + +#### Notes + +TODO: doc interactive mode \ No newline at end of file diff --git a/etcdctlv3/command/get_command.go b/etcdctlv3/command/get_command.go index 8c0ed1c2f..167e8db06 100644 --- a/etcdctlv3/command/get_command.go +++ b/etcdctlv3/command/get_command.go @@ -15,7 +15,6 @@ package command import ( - "encoding/hex" "fmt" "strings" @@ -104,22 +103,6 @@ func getCommandFunc(cmd *cobra.Command, args []string) { } 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) + printKV(getHex, kv) } } - -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/util.go b/etcdctlv3/command/util.go new file mode 100644 index 000000000..9fc36ccde --- /dev/null +++ b/etcdctlv3/command/util.go @@ -0,0 +1,42 @@ +// 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" + + pb "github.com/coreos/etcd/storage/storagepb" +) + +func printKV(isHex bool, kv *pb.KeyValue) { + k, v := string(kv.Key), string(kv.Value) + if isHex { + 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/watch_command.go b/etcdctlv3/command/watch_command.go index 6ff080ce6..86717614f 100644 --- a/etcdctlv3/command/watch_command.go +++ b/etcdctlv3/command/watch_command.go @@ -24,20 +24,67 @@ 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 ( + watchRev int64 + watchPrefix bool + watchHex bool + watchInteractive bool +) + // NewWatchCommand returns the cobra command for "watch". func NewWatchCommand() *cobra.Command { - return &cobra.Command{ - Use: "watch", - Short: "Watch watches the events happening or happened.", + cmd := &cobra.Command{ + Use: "watch [key or prefix]", + Short: "Watch watches events stream on keys or prefixes.", Run: watchCommandFunc, } + + 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().BoolVar(&watchPrefix, "prefix", false, "watch on a prefix if prefix is set") + cmd.Flags().Int64Var(&watchRev, "rev", 0, "revision to start watching") + + return cmd } // watchCommandFunc executes the "watch" command. func watchCommandFunc(cmd *cobra.Command, args []string) { + if watchInteractive { + watchInteractiveFunc(cmd, args) + return + } + + if len(args) != 1 { + ExitWithError(ExitBadArgs, fmt.Errorf("watch in non-interactive mode requires an argument as key or prefix")) + } + + c := mustClientFromCmd(cmd) + w := clientv3.NewWatcher(c) + + var wc clientv3.WatchChan + if !watchPrefix { + wc = w.Watch(context.TODO(), args[0], watchRev) + } else { + wc = w.Watch(context.TODO(), args[0], watchRev) + } + for resp := range wc { + for _, e := range resp.Events { + fmt.Printf("%s\r\n", e.Type) + printKV(watchHex, e.Kv) + } + } + err := w.Close() + if err == nil { + ExitWithError(ExitInterrupted, fmt.Errorf("watch is canceled by the server")) + } + ExitWithError(ExitBadConnection, err) +} + +func watchInteractiveFunc(cmd *cobra.Command, args []string) { wStream, err := mustClientFromCmd(cmd).Watch.Watch(context.TODO()) if err != nil { ExitWithError(ExitBadConnection, err)