From 8183b83220bcc01b422fd6fea416a506670832ad Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Sun, 14 Jan 2018 00:46:46 -0800 Subject: [PATCH 1/9] ctlv3: support ETCDCTL_WATCH_KEY, ETCDCTL_WATCH_RANGE_END Signed-off-by: Gyuho Lee --- etcdctl/ctlv3/command/watch_command.go | 82 +++++++++-- etcdctl/ctlv3/command/watch_command_test.go | 154 +++++++++++++++++++- 2 files changed, 221 insertions(+), 15 deletions(-) diff --git a/etcdctl/ctlv3/command/watch_command.go b/etcdctl/ctlv3/command/watch_command.go index 453d3309638..e4596f78592 100644 --- a/etcdctl/ctlv3/command/watch_command.go +++ b/etcdctl/ctlv3/command/watch_command.go @@ -30,6 +30,7 @@ import ( var ( errBadArgsNum = errors.New("bad number of arguments") + errBadArgsNumConflictEnv = errors.New("bad number of arguments (found conflicting environment key)") errBadArgsNumSeparator = errors.New("bad number of arguments (found separator --, but no commands)") errBadArgsInteractiveWatch = errors.New("args[0] must be 'watch' for interactive calls") ) @@ -59,12 +60,17 @@ func NewWatchCommand() *cobra.Command { // watchCommandFunc executes the "watch" command. func watchCommandFunc(cmd *cobra.Command, args []string) { + envKey, envRange := os.Getenv("ETCDCTL_WATCH_KEY"), os.Getenv("ETCDCTL_WATCH_RANGE_END") + if envKey == "" && envRange != "" { + ExitWithError(ExitBadArgs, fmt.Errorf("ETCDCTL_WATCH_KEY is empty but got ETCDCTL_WATCH_RANGE_END=%q", envRange)) + } + if watchInteractive { - watchInteractiveFunc(cmd, os.Args) + watchInteractiveFunc(cmd, os.Args, envKey, envRange) return } - watchArgs, execArgs, err := parseWatchArgs(os.Args, args, false) + watchArgs, execArgs, err := parseWatchArgs(os.Args, args, envKey, envRange, false) if err != nil { ExitWithError(ExitBadArgs, err) } @@ -82,7 +88,7 @@ func watchCommandFunc(cmd *cobra.Command, args []string) { ExitWithError(ExitInterrupted, fmt.Errorf("watch is canceled by the server")) } -func watchInteractiveFunc(cmd *cobra.Command, osArgs []string) { +func watchInteractiveFunc(cmd *cobra.Command, osArgs []string, envKey, envRange string) { c := mustClientFromCmd(cmd) reader := bufio.NewReader(os.Stdin) @@ -95,7 +101,7 @@ func watchInteractiveFunc(cmd *cobra.Command, osArgs []string) { l = strings.TrimSuffix(l, "\n") args := argify(l) - if len(args) < 2 { + if len(args) < 2 && envKey == "" { fmt.Fprintf(os.Stderr, "Invalid command %s (command type or key is not provided)\n", l) continue } @@ -105,7 +111,7 @@ func watchInteractiveFunc(cmd *cobra.Command, osArgs []string) { continue } - watchArgs, execArgs, perr := parseWatchArgs(osArgs, args, true) + watchArgs, execArgs, perr := parseWatchArgs(osArgs, args, envKey, envRange, true) if perr != nil { ExitWithError(ExitBadArgs, perr) } @@ -165,7 +171,7 @@ func printWatchCh(c *clientv3.Client, ch clientv3.WatchChan, execArgs []string) // (e.g. ./bin/etcdctl watch foo --rev 1 bar). // "--" characters are invalid arguments for "spf13/cobra" library, // so no need to handle such cases. -func parseWatchArgs(osArgs, commandArgs []string, interactive bool) (watchArgs []string, execArgs []string, err error) { +func parseWatchArgs(osArgs, commandArgs []string, envKey, envRange string, interactive bool) (watchArgs []string, execArgs []string, err error) { watchArgs = commandArgs // remove preceding commands (e.g. "watch foo bar" in interactive mode) @@ -175,12 +181,54 @@ func parseWatchArgs(osArgs, commandArgs []string, interactive bool) (watchArgs [ break } } - if idx < len(watchArgs)-1 { - watchArgs = watchArgs[idx+1:] + if idx < len(watchArgs)-1 || envKey != "" { + if idx < len(watchArgs)-1 { + watchArgs = watchArgs[idx+1:] + } + + execIdx, execExist := 0, false + for execIdx = range osArgs { + v := osArgs[execIdx] + if v == "--" && execIdx != len(osArgs)-1 { + execExist = true + break + } + } + + if idx == len(watchArgs)-1 && envKey != "" { + if len(watchArgs) > 0 && !interactive { + // "watch --rev 1 -- echo Hello World" has no conflict + if !execExist { + // "watch foo" with ETCDCTL_WATCH_KEY=foo + // (watchArgs==["foo"]) + return nil, nil, errBadArgsNumConflictEnv + } + } + // otherwise, watch with no argument and environment key is set + // if interactive, first "watch" command string should be removed + if interactive { + watchArgs = []string{} + } + } + + // "watch foo -- echo hello" with ETCDCTL_WATCH_KEY=foo + // (watchArgs==["foo","echo","hello"]) + if envKey != "" && execExist { + widx, oidx := 0, len(osArgs)-1 + for widx = len(watchArgs) - 1; widx >= 0; widx-- { + if watchArgs[widx] == osArgs[oidx] { + oidx-- + continue + } + if oidx == execIdx { // watchArgs has extra + return nil, nil, errBadArgsNumConflictEnv + } + } + } } else if interactive { // "watch" not found return nil, nil, errBadArgsInteractiveWatch } - if len(watchArgs) < 1 { + if len(watchArgs) < 1 && envKey == "" { return nil, nil, errBadArgsNum } @@ -192,7 +240,7 @@ func parseWatchArgs(osArgs, commandArgs []string, interactive bool) (watchArgs [ } if idx < len(osArgs)-1 { osArgs = osArgs[idx+1:] - } else { + } else if envKey == "" { return nil, nil, errBadArgsNum } @@ -202,7 +250,7 @@ func parseWatchArgs(osArgs, commandArgs []string, interactive bool) (watchArgs [ } foundSep := false for idx = range argsWithSep { - if argsWithSep[idx] == "--" && idx > 0 { + if argsWithSep[idx] == "--" { foundSep = true break } @@ -214,6 +262,18 @@ func parseWatchArgs(osArgs, commandArgs []string, interactive bool) (watchArgs [ } watchArgs = flagset.Args() } + + // "watch -- echo hello" with ETCDCTL_WATCH_KEY=foo + // should be translated to "watch foo -- echo hello" + // (watchArgs=["echo","hello"] should be ["foo","echo","hello"]) + if envKey != "" { + tmp := []string{envKey} + if envRange != "" { + tmp = append(tmp, envRange) + } + watchArgs = append(tmp, watchArgs...) + } + if !foundSep { return watchArgs, nil, nil } diff --git a/etcdctl/ctlv3/command/watch_command_test.go b/etcdctl/ctlv3/command/watch_command_test.go index 0a65fdf3192..1456c971f0c 100644 --- a/etcdctl/ctlv3/command/watch_command_test.go +++ b/etcdctl/ctlv3/command/watch_command_test.go @@ -21,9 +21,10 @@ import ( func Test_parseWatchArgs(t *testing.T) { tt := []struct { - osArgs []string // raw arguments to "watch" command - commandArgs []string // arguments after "spf13/cobra" preprocessing - interactive bool + osArgs []string // raw arguments to "watch" command + commandArgs []string // arguments after "spf13/cobra" preprocessing + envKey, envRange string + interactive bool watchArgs []string execArgs []string @@ -45,6 +46,46 @@ func Test_parseWatchArgs(t *testing.T) { execArgs: nil, err: errBadArgsNumSeparator, }, + { + osArgs: []string{"./bin/etcdctl", "watch"}, + commandArgs: nil, + envKey: "foo", + envRange: "bar", + interactive: false, + watchArgs: []string{"foo", "bar"}, + execArgs: nil, + err: nil, + }, + { + osArgs: []string{"./bin/etcdctl", "watch", "foo"}, + commandArgs: []string{"foo"}, + envKey: "foo", + envRange: "", + interactive: false, + watchArgs: nil, + execArgs: nil, + err: errBadArgsNumConflictEnv, + }, + { + osArgs: []string{"./bin/etcdctl", "watch", "foo", "bar"}, + commandArgs: []string{"foo", "bar"}, + envKey: "foo", + envRange: "", + interactive: false, + watchArgs: nil, + execArgs: nil, + err: errBadArgsNumConflictEnv, + }, + { + osArgs: []string{"./bin/etcdctl", "watch", "foo", "bar"}, + commandArgs: []string{"foo", "bar"}, + envKey: "foo", + envRange: "bar", + interactive: false, + watchArgs: nil, + execArgs: nil, + err: errBadArgsNumConflictEnv, + }, { osArgs: []string{"./bin/etcdctl", "watch", "foo"}, commandArgs: []string{"foo"}, @@ -53,6 +94,15 @@ func Test_parseWatchArgs(t *testing.T) { execArgs: nil, err: nil, }, + { + osArgs: []string{"./bin/etcdctl", "watch"}, + commandArgs: nil, + envKey: "foo", + interactive: false, + watchArgs: []string{"foo"}, + execArgs: nil, + err: nil, + }, { osArgs: []string{"./bin/etcdctl", "watch", "--rev", "1", "foo"}, commandArgs: []string{"foo"}, @@ -61,6 +111,24 @@ func Test_parseWatchArgs(t *testing.T) { execArgs: nil, err: nil, }, + { + osArgs: []string{"./bin/etcdctl", "watch", "--rev", "1", "foo"}, + commandArgs: []string{"foo"}, + envKey: "foo", + interactive: false, + watchArgs: nil, + execArgs: nil, + err: errBadArgsNumConflictEnv, + }, + { + osArgs: []string{"./bin/etcdctl", "watch", "--rev", "1"}, + commandArgs: nil, + envKey: "foo", + interactive: false, + watchArgs: []string{"foo"}, + execArgs: nil, + err: nil, + }, { osArgs: []string{"./bin/etcdctl", "watch", "foo", "--rev", "1"}, commandArgs: []string{"foo"}, @@ -117,6 +185,35 @@ func Test_parseWatchArgs(t *testing.T) { execArgs: []string{"echo", "Hello", "World"}, err: nil, }, + { + osArgs: []string{"./bin/etcdctl", "watch", "--rev", "1", "--", "echo", "Hello", "World"}, + commandArgs: []string{"echo", "Hello", "World"}, + envKey: "foo", + envRange: "", + interactive: false, + watchArgs: []string{"foo"}, + execArgs: []string{"echo", "Hello", "World"}, + err: nil, + }, + { + osArgs: []string{"./bin/etcdctl", "watch", "--rev", "1", "--", "echo", "Hello", "World"}, + commandArgs: []string{"echo", "Hello", "World"}, + envKey: "foo", + envRange: "bar", + interactive: false, + watchArgs: []string{"foo", "bar"}, + execArgs: []string{"echo", "Hello", "World"}, + err: nil, + }, + { + osArgs: []string{"./bin/etcdctl", "watch", "foo", "bar", "--rev", "1", "--", "echo", "Hello", "World"}, + commandArgs: []string{"foo", "bar", "echo", "Hello", "World"}, + envKey: "foo", + interactive: false, + watchArgs: nil, + execArgs: nil, + err: errBadArgsNumConflictEnv, + }, { osArgs: []string{"./bin/etcdctl", "watch", "-i"}, commandArgs: []string{"foo", "bar", "--", "echo", "Hello", "World"}, @@ -141,6 +238,26 @@ func Test_parseWatchArgs(t *testing.T) { execArgs: nil, err: nil, }, + { + osArgs: []string{"./bin/etcdctl", "watch", "-i"}, + commandArgs: []string{"watch"}, + envKey: "foo", + envRange: "bar", + interactive: true, + watchArgs: []string{"foo", "bar"}, + execArgs: nil, + err: nil, + }, + { + osArgs: []string{"./bin/etcdctl", "watch", "-i"}, + commandArgs: []string{"watch"}, + envKey: "hello world!", + envRange: "bar", + interactive: true, + watchArgs: []string{"hello world!", "bar"}, + execArgs: nil, + err: nil, + }, { osArgs: []string{"./bin/etcdctl", "watch", "-i"}, commandArgs: []string{"watch", "foo", "--rev", "1"}, @@ -165,6 +282,25 @@ func Test_parseWatchArgs(t *testing.T) { execArgs: []string{"echo", "Hello", "World"}, err: nil, }, + { + osArgs: []string{"./bin/etcdctl", "watch", "-i"}, + commandArgs: []string{"watch", "--", "echo", "Hello", "World"}, + envKey: "foo", + interactive: true, + watchArgs: []string{"foo"}, + execArgs: []string{"echo", "Hello", "World"}, + err: nil, + }, + { + osArgs: []string{"./bin/etcdctl", "watch", "-i"}, + commandArgs: []string{"watch", "--", "echo", "Hello", "World"}, + envKey: "foo", + envRange: "bar", + interactive: true, + watchArgs: []string{"foo", "bar"}, + execArgs: []string{"echo", "Hello", "World"}, + err: nil, + }, { osArgs: []string{"./bin/etcdctl", "watch", "-i"}, commandArgs: []string{"watch", "foo", "bar", "--", "echo", "Hello", "World"}, @@ -181,6 +317,16 @@ func Test_parseWatchArgs(t *testing.T) { execArgs: []string{"echo", "Hello", "World"}, err: nil, }, + { + osArgs: []string{"./bin/etcdctl", "watch", "-i"}, + commandArgs: []string{"watch", "--rev", "1", "--", "echo", "Hello", "World"}, + envKey: "foo", + envRange: "bar", + interactive: true, + watchArgs: []string{"foo", "bar"}, + execArgs: []string{"echo", "Hello", "World"}, + err: nil, + }, { osArgs: []string{"./bin/etcdctl", "watch", "-i"}, commandArgs: []string{"watch", "foo", "--rev", "1", "bar", "--", "echo", "Hello", "World"}, @@ -199,7 +345,7 @@ func Test_parseWatchArgs(t *testing.T) { }, } for i, ts := range tt { - watchArgs, execArgs, err := parseWatchArgs(ts.osArgs, ts.commandArgs, ts.interactive) + watchArgs, execArgs, err := parseWatchArgs(ts.osArgs, ts.commandArgs, ts.envKey, ts.envRange, ts.interactive) if err != ts.err { t.Fatalf("#%d: error expected %v, got %v", i, ts.err, err) } From 503781e3a012d6179233e5214ebb7ceb2427ffda Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Sun, 14 Jan 2018 01:36:13 -0800 Subject: [PATCH 2/9] e2e: add watch tests with ETCDCTL_WATCH_* Signed-off-by: Gyuho Lee --- e2e/ctl_v3_watch_test.go | 112 +++++++++++++++++++++++++++++---------- 1 file changed, 83 insertions(+), 29 deletions(-) diff --git a/e2e/ctl_v3_watch_test.go b/e2e/ctl_v3_watch_test.go index 9356729b9f3..206783d028c 100644 --- a/e2e/ctl_v3_watch_test.go +++ b/e2e/ctl_v3_watch_test.go @@ -15,6 +15,7 @@ package e2e import ( + "os" "strings" "testing" ) @@ -45,55 +46,89 @@ type kvExec struct { func watchTest(cx ctlCtx) { tests := []struct { - puts []kv - args []string + puts []kv + envKey string + envRange string + args []string wkv []kvExec }{ { // watch 1 key - []kv{{"sample", "value"}}, - []string{"sample", "--rev", "1"}, - []kvExec{{key: "sample", val: "value"}}, + puts: []kv{{"sample", "value"}}, + args: []string{"sample", "--rev", "1"}, + wkv: []kvExec{{key: "sample", val: "value"}}, + }, + { // watch 1 key with env + puts: []kv{{"sample", "value"}}, + envKey: "sample", + args: []string{"--rev", "1"}, + wkv: []kvExec{{key: "sample", val: "value"}}, }, { // watch 1 key with "echo watch event received" - []kv{{"sample", "value"}}, - []string{"sample", "--rev", "1", "--", "echo", "watch event received"}, - []kvExec{{key: "sample", val: "value", execOutput: "watch event received"}}, + puts: []kv{{"sample", "value"}}, + args: []string{"sample", "--rev", "1", "--", "echo", "watch event received"}, + wkv: []kvExec{{key: "sample", val: "value", execOutput: "watch event received"}}, + }, + { // watch 1 key with "echo watch event received", with env + puts: []kv{{"sample", "value"}}, + envKey: "sample", + args: []string{"--rev", "1", "--", "echo", "watch event received"}, + wkv: []kvExec{{key: "sample", val: "value", execOutput: "watch event received"}}, }, { // watch 1 key with "echo watch event received" - []kv{{"sample", "value"}}, - []string{"--rev", "1", "sample", "--", "echo", "watch event received"}, - []kvExec{{key: "sample", val: "value", execOutput: "watch event received"}}, + puts: []kv{{"sample", "value"}}, + args: []string{"--rev", "1", "sample", "--", "echo", "watch event received"}, + wkv: []kvExec{{key: "sample", val: "value", execOutput: "watch event received"}}, }, { // watch 1 key with "echo \"Hello World!\"" - []kv{{"sample", "value"}}, - []string{"--rev", "1", "sample", "--", "echo", "\"Hello World!\""}, - []kvExec{{key: "sample", val: "value", execOutput: "Hello World!"}}, + puts: []kv{{"sample", "value"}}, + args: []string{"--rev", "1", "sample", "--", "echo", "\"Hello World!\""}, + wkv: []kvExec{{key: "sample", val: "value", execOutput: "Hello World!"}}, + }, + { // watch 1 key with "echo watch event received" + puts: []kv{{"sample", "value"}}, + args: []string{"sample", "samplx", "--rev", "1", "--", "echo", "watch event received"}, + wkv: []kvExec{{key: "sample", val: "value", execOutput: "watch event received"}}, }, { // watch 1 key with "echo watch event received" - []kv{{"sample", "value"}}, - []string{"sample", "samplx", "--rev", "1", "--", "echo", "watch event received"}, - []kvExec{{key: "sample", val: "value", execOutput: "watch event received"}}, + puts: []kv{{"sample", "value"}}, + envKey: "sample", + envRange: "samplx", + args: []string{"--rev", "1", "--", "echo", "watch event received"}, + wkv: []kvExec{{key: "sample", val: "value", execOutput: "watch event received"}}, }, { // watch 1 key with "echo watch event received" - []kv{{"sample", "value"}}, - []string{"sample", "--rev", "1", "samplx", "--", "echo", "watch event received"}, - []kvExec{{key: "sample", val: "value", execOutput: "watch event received"}}, + puts: []kv{{"sample", "value"}}, + args: []string{"sample", "--rev", "1", "samplx", "--", "echo", "watch event received"}, + wkv: []kvExec{{key: "sample", val: "value", execOutput: "watch event received"}}, }, { // watch 3 keys by prefix - []kv{{"key1", "val1"}, {"key2", "val2"}, {"key3", "val3"}}, - []string{"key", "--rev", "1", "--prefix"}, - []kvExec{{key: "key1", val: "val1"}, {key: "key2", val: "val2"}, {key: "key3", val: "val3"}}, + puts: []kv{{"key1", "val1"}, {"key2", "val2"}, {"key3", "val3"}}, + args: []string{"key", "--rev", "1", "--prefix"}, + wkv: []kvExec{{key: "key1", val: "val1"}, {key: "key2", val: "val2"}, {key: "key3", val: "val3"}}, + }, + { // watch 3 keys by prefix, with env + puts: []kv{{"key1", "val1"}, {"key2", "val2"}, {"key3", "val3"}}, + envKey: "key", + args: []string{"--rev", "1", "--prefix"}, + wkv: []kvExec{{key: "key1", val: "val1"}, {key: "key2", val: "val2"}, {key: "key3", val: "val3"}}, }, { // watch by revision - []kv{{"etcd", "revision_1"}, {"etcd", "revision_2"}, {"etcd", "revision_3"}}, - []string{"etcd", "--rev", "2"}, - []kvExec{{key: "etcd", val: "revision_2"}, {key: "etcd", val: "revision_3"}}, + puts: []kv{{"etcd", "revision_1"}, {"etcd", "revision_2"}, {"etcd", "revision_3"}}, + args: []string{"etcd", "--rev", "2"}, + wkv: []kvExec{{key: "etcd", val: "revision_2"}, {key: "etcd", val: "revision_3"}}, }, { // watch 3 keys by range - []kv{{"key1", "val1"}, {"key3", "val3"}, {"key2", "val2"}}, - []string{"key", "key3", "--rev", "1"}, - []kvExec{{key: "key1", val: "val1"}, {key: "key2", val: "val2"}}, + puts: []kv{{"key1", "val1"}, {"key3", "val3"}, {"key2", "val2"}}, + args: []string{"key", "key3", "--rev", "1"}, + wkv: []kvExec{{key: "key1", val: "val1"}, {key: "key2", val: "val2"}}, + }, + { // watch 3 keys by range, with env + puts: []kv{{"key1", "val1"}, {"key3", "val3"}, {"key2", "val2"}}, + envKey: "key", + envRange: "key3", + args: []string{"--rev", "1"}, + wkv: []kvExec{{key: "key1", val: "val1"}, {key: "key2", val: "val2"}}, }, } @@ -107,11 +142,30 @@ func watchTest(cx ctlCtx) { } close(donec) }(i, tt.puts) + + unsetEnv := func() {} + if tt.envKey != "" || tt.envRange != "" { + if tt.envKey != "" { + os.Setenv("ETCDCTL_WATCH_KEY", tt.envKey) + unsetEnv = func() { os.Unsetenv("ETCDCTL_WATCH_KEY") } + } + if tt.envRange != "" { + os.Setenv("ETCDCTL_WATCH_RANGE_END", tt.envRange) + unsetEnv = func() { os.Unsetenv("ETCDCTL_WATCH_RANGE_END") } + } + if tt.envKey != "" && tt.envRange != "" { + unsetEnv = func() { + os.Unsetenv("ETCDCTL_WATCH_KEY") + os.Unsetenv("ETCDCTL_WATCH_RANGE_END") + } + } + } if err := ctlV3Watch(cx, tt.args, tt.wkv...); err != nil { if cx.dialTimeout > 0 && !isGRPCTimedout(err) { cx.t.Errorf("watchTest #%d: ctlV3Watch error (%v)", i, err) } } + unsetEnv() <-donec } } From 57284aac28cb65ff4d9398023f4412f9721e964b Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Sun, 14 Jan 2018 01:46:21 -0800 Subject: [PATCH 3/9] etcdctl: document watch with ETCDCTL_WATCH_* Signed-off-by: Gyuho Lee --- etcdctl/README.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/etcdctl/README.md b/etcdctl/README.md index 17a14ce5cfb..1a0242a5ce5 100644 --- a/etcdctl/README.md +++ b/etcdctl/README.md @@ -378,6 +378,13 @@ watch [options] \n # bar ``` +```bash +ETCDCTL_WATCH_KEY=foo ./etcdctl watch +# PUT +# foo +# bar +``` + Receive events and execute `echo watch event received`: ```bash @@ -388,6 +395,27 @@ Receive events and execute `echo watch event received`: # watch event received ``` +Watch with environmental variables and execute `echo watch event received`: + +```bash +export ETCDCTL_WATCH_KEY=foo +./etcdctl watch -- echo watch event received +# PUT +# foo +# bar +# watch event received +``` + +```bash +export ETCDCTL_WATCH_KEY=foo +export ETCDCTL_WATCH_RANGE_END=foox +./etcdctl watch -- echo watch event received +# PUT +# fob +# bar +# watch event received +``` + ##### Interactive ```bash @@ -413,6 +441,29 @@ watch foo -- echo watch event received # watch event received ``` +Watch with environmental variables and execute `echo watch event received`: + +```bash +export ETCDCTL_WATCH_KEY=foo +./etcdctl watch -i +watch -- echo watch event received +# PUT +# foo +# bar +# watch event received +``` + +```bash +export ETCDCTL_WATCH_KEY=foo +export ETCDCTL_WATCH_RANGE_END=foox +./etcdctl watch -i +watch -- echo watch event received +# PUT +# fob +# bar +# watch event received +``` + ### LEASE \ LEASE provides commands for key lease management. From 388b7fece6c8f3c9479679c33e663648d014cdce Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Sun, 14 Jan 2018 01:59:46 -0800 Subject: [PATCH 4/9] ctlv3: handle pkg/flags warnings Signed-off-by: Gyuho Lee --- etcdctl/ctlv3/command/global.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/etcdctl/ctlv3/command/global.go b/etcdctl/ctlv3/command/global.go index ce607e82a0a..e52442ff814 100644 --- a/etcdctl/ctlv3/command/global.go +++ b/etcdctl/ctlv3/command/global.go @@ -101,8 +101,19 @@ type clientConfig struct { acfg *authCfg } +type discardValue struct{} + +func (*discardValue) String() string { return "" } +func (*discardValue) Set(string) error { return nil } +func (*discardValue) Type() string { return "" } + func clientConfigFromCmd(cmd *cobra.Command) *clientConfig { fs := cmd.InheritedFlags() + + // silence "pkg/flags: unrecognized environment variable ETCDCTL_WATCH_KEY=foo" warnings + // silence "pkg/flags: unrecognized environment variable ETCDCTL_WATCH_RANGE_END=bar" warnings + fs.AddFlag(&pflag.Flag{Name: "watch-key", Value: &discardValue{}}) + fs.AddFlag(&pflag.Flag{Name: "watch-range-end", Value: &discardValue{}}) flags.SetPflagsFromEnv("ETCDCTL", fs) debug, err := cmd.Flags().GetBool("debug") From 72a2a6671ead8cda7dc5624c8ea13155404b3eb2 Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Tue, 16 Jan 2018 09:03:10 -0800 Subject: [PATCH 5/9] ctlv3: set ETCD_WATCH_KEY, ETCD_WATCH_VALUE on exec watch Signed-off-by: Gyuho Lee --- etcdctl/ctlv3/command/watch_command.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/etcdctl/ctlv3/command/watch_command.go b/etcdctl/ctlv3/command/watch_command.go index e4596f78592..e546eb8dc3d 100644 --- a/etcdctl/ctlv3/command/watch_command.go +++ b/etcdctl/ctlv3/command/watch_command.go @@ -155,11 +155,15 @@ func printWatchCh(c *clientv3.Client, ch clientv3.WatchChan, execArgs []string) display.Watch(resp) if len(execArgs) > 0 { - cmd := exec.CommandContext(c.Ctx(), execArgs[0], execArgs[1:]...) - cmd.Env = os.Environ() - cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr - if err := cmd.Run(); err != nil { - fmt.Fprintf(os.Stderr, "command %q error (%v)\n", execArgs, err) + for _, ev := range resp.Events { + cmd := exec.CommandContext(c.Ctx(), execArgs[0], execArgs[1:]...) + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, fmt.Sprintf("ETCD_WATCH_KEY=%q", ev.Kv.Key)) + cmd.Env = append(cmd.Env, fmt.Sprintf("ETCD_WATCH_VALUE=%q", ev.Kv.Value)) + cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + if err := cmd.Run(); err != nil { + fmt.Fprintf(os.Stderr, "command %q error (%v)\n", execArgs, err) + } } } } From 2c347d715835af0ca584da0cfdac4d63b7bdb288 Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Tue, 16 Jan 2018 09:04:46 -0800 Subject: [PATCH 6/9] ctlv3: exit on exec watch error Signed-off-by: Gyuho Lee --- etcdctl/ctlv3/command/watch_command.go | 1 + 1 file changed, 1 insertion(+) diff --git a/etcdctl/ctlv3/command/watch_command.go b/etcdctl/ctlv3/command/watch_command.go index e546eb8dc3d..19b461e28e0 100644 --- a/etcdctl/ctlv3/command/watch_command.go +++ b/etcdctl/ctlv3/command/watch_command.go @@ -163,6 +163,7 @@ func printWatchCh(c *clientv3.Client, ch clientv3.WatchChan, execArgs []string) cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr if err := cmd.Run(); err != nil { fmt.Fprintf(os.Stderr, "command %q error (%v)\n", execArgs, err) + os.Exit(1) } } } From 5e0118d7ef14b74c0eed5de022f73207b1c82512 Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Tue, 16 Jan 2018 09:28:25 -0800 Subject: [PATCH 7/9] ctlv3: set ETCD_WATCH_* on watch exec Signed-off-by: Gyuho Lee --- etcdctl/ctlv3/command/watch_command.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/etcdctl/ctlv3/command/watch_command.go b/etcdctl/ctlv3/command/watch_command.go index 19b461e28e0..33447f649ee 100644 --- a/etcdctl/ctlv3/command/watch_command.go +++ b/etcdctl/ctlv3/command/watch_command.go @@ -158,6 +158,8 @@ func printWatchCh(c *clientv3.Client, ch clientv3.WatchChan, execArgs []string) for _, ev := range resp.Events { cmd := exec.CommandContext(c.Ctx(), execArgs[0], execArgs[1:]...) cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, fmt.Sprintf("ETCD_WATCH_REVISION=%d", resp.Header.Revision)) + cmd.Env = append(cmd.Env, fmt.Sprintf("ETCD_WATCH_EVENT_TYPE=%q", ev.Type)) cmd.Env = append(cmd.Env, fmt.Sprintf("ETCD_WATCH_KEY=%q", ev.Kv.Key)) cmd.Env = append(cmd.Env, fmt.Sprintf("ETCD_WATCH_VALUE=%q", ev.Kv.Value)) cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr From 6ba5682e641c3d8e663a8935c5dfc8644f718c10 Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Tue, 16 Jan 2018 09:35:37 -0800 Subject: [PATCH 8/9] e2e: test ETCD_WATCH_VALUE Signed-off-by: Gyuho Lee --- e2e/ctl_v3_watch_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/e2e/ctl_v3_watch_test.go b/e2e/ctl_v3_watch_test.go index 206783d028c..dbc2f081c7d 100644 --- a/e2e/ctl_v3_watch_test.go +++ b/e2e/ctl_v3_watch_test.go @@ -69,6 +69,11 @@ func watchTest(cx ctlCtx) { args: []string{"sample", "--rev", "1", "--", "echo", "watch event received"}, wkv: []kvExec{{key: "sample", val: "value", execOutput: "watch event received"}}, }, + { // watch 1 key with ${ETCD_WATCH_VALUE} + puts: []kv{{"sample", "value"}}, + args: []string{"sample", "--rev", "1", "--", "env"}, + wkv: []kvExec{{key: "sample", val: "value", execOutput: `ETCD_WATCH_VALUE="value"`}}, + }, { // watch 1 key with "echo watch event received", with env puts: []kv{{"sample", "value"}}, envKey: "sample", From b8a95d7a9b6b11b62e65b916d1917fc1a87fce52 Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Tue, 16 Jan 2018 09:39:05 -0800 Subject: [PATCH 9/9] etcdctl: document "ETCD_WATCH_*" Signed-off-by: Gyuho Lee --- etcdctl/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/etcdctl/README.md b/etcdctl/README.md index 1a0242a5ce5..1ca5922cf30 100644 --- a/etcdctl/README.md +++ b/etcdctl/README.md @@ -395,6 +395,20 @@ Receive events and execute `echo watch event received`: # watch event received ``` +Watch response is set via `ETCD_WATCH_*` environmental variables: + +```bash +./etcdctl watch foo -- sh -c "env | grep ETCD_WATCH_" + +# PUT +# foo +# bar +# ETCD_WATCH_REVISION=11 +# ETCD_WATCH_KEY="foo" +# ETCD_WATCH_EVENT_TYPE="PUT" +# ETCD_WATCH_VALUE="bar" +``` + Watch with environmental variables and execute `echo watch event received`: ```bash