diff --git a/etcdctl/ctlv3/command/watch_command.go b/etcdctl/ctlv3/command/watch_command.go index 33447f649eec..aa623eb9d29d 100644 --- a/etcdctl/ctlv3/command/watch_command.go +++ b/etcdctl/ctlv3/command/watch_command.go @@ -179,89 +179,108 @@ func printWatchCh(c *clientv3.Client, ch clientv3.WatchChan, execArgs []string) // "--" characters are invalid arguments for "spf13/cobra" library, // so no need to handle such cases. func parseWatchArgs(osArgs, commandArgs []string, envKey, envRange string, interactive bool) (watchArgs []string, execArgs []string, err error) { - watchArgs = commandArgs + rawArgs := make([]string, len(osArgs)) + copy(rawArgs, osArgs) + watchArgs = make([]string, len(commandArgs)) + copy(watchArgs, commandArgs) - // remove preceding commands (e.g. "watch foo bar" in interactive mode) - idx := 0 - for idx = range watchArgs { - if watchArgs[idx] == "watch" { + // remove preceding commands (e.g. ./bin/etcdctl watch) + // handle "./bin/etcdctl watch foo -- echo watch event" + for idx := range rawArgs { + if rawArgs[idx] == "watch" { + rawArgs = rawArgs[idx+1:] break } } - if idx < len(watchArgs)-1 || envKey != "" { - if idx < len(watchArgs)-1 { - watchArgs = watchArgs[idx+1:] + + // remove preceding commands (e.g. "watch foo bar" in interactive mode) + // handle "./bin/etcdctl watch foo -- echo watch event" + if interactive { + if watchArgs[0] != "watch" { + // "watch" not found + return nil, nil, errBadArgsInteractiveWatch } + watchArgs = watchArgs[1:] + } - execIdx, execExist := 0, false - for execIdx = range osArgs { - v := osArgs[execIdx] - if v == "--" && execIdx != len(osArgs)-1 { + execIdx, execExist := 0, false + if !interactive { + for execIdx = range rawArgs { + if rawArgs[execIdx] == "--" { 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{} - } + if execExist && execIdx == len(rawArgs)-1 { + // "watch foo bar --" should error + return nil, nil, errBadArgsNumSeparator } - - // "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-- + // "watch" with no argument should error + if !execExist && len(rawArgs) < 1 && envKey == "" { + return nil, nil, errBadArgsNum + } + if execExist && envKey != "" { + // "ETCDCTL_WATCH_KEY=foo watch foo -- echo 1" should error + // (watchArgs==["foo","echo","1"]) + widx, ridx := len(watchArgs)-1, len(rawArgs)-1 + for ; widx >= 0; widx-- { + if watchArgs[widx] == rawArgs[ridx] { + ridx-- continue } - if oidx == execIdx { // watchArgs has extra + // watchArgs has extra: + // ETCDCTL_WATCH_KEY=foo watch foo -- echo 1 + // watchArgs: foo echo 1 + if ridx == execIdx { return nil, nil, errBadArgsNumConflictEnv } } } - } else if interactive { // "watch" not found - return nil, nil, errBadArgsInteractiveWatch - } - if len(watchArgs) < 1 && envKey == "" { - return nil, nil, errBadArgsNum - } - - // remove preceding commands (e.g. ./bin/etcdctl watch) - for idx = range osArgs { - if osArgs[idx] == "watch" { - break + } else { + for execIdx = range watchArgs { + if watchArgs[execIdx] == "--" { + execExist = true + break + } + } + if execExist && execIdx == len(watchArgs)-1 { + // "watch foo bar --" should error + return nil, nil, errBadArgsNumSeparator + } + // "watch" with no argument should error + if !execExist && len(watchArgs) < 1 && envKey == "" { + return nil, nil, errBadArgsNum + } + if !execExist && envKey != "" { + watchArgs = []string{} } } - if idx < len(osArgs)-1 { - osArgs = osArgs[idx+1:] - } else if envKey == "" { - return nil, nil, errBadArgsNum + + // check conflicting arguments + // e.g. "watch --rev 1 -- echo Hello World" has no conflict + if !execExist && len(watchArgs) > 0 && envKey != "" { + // "ETCDCTL_WATCH_KEY=foo watch foo" should error + // (watchArgs==["foo"]) + return nil, nil, errBadArgsNumConflictEnv } - argsWithSep := osArgs - if interactive { // interactive mode pass "--" to the command args + argsWithSep := rawArgs + if interactive { + // interactive mode directly passes "--" to the command args argsWithSep = watchArgs } - foundSep := false + + idx, foundSep := 0, false for idx = range argsWithSep { if argsWithSep[idx] == "--" { foundSep = true break } } + if foundSep { + execArgs = argsWithSep[idx+1:] + } + if interactive { flagset := NewWatchCommand().Flags() if err := flagset.Parse(argsWithSep); err != nil { @@ -270,27 +289,21 @@ func parseWatchArgs(osArgs, commandArgs []string, envKey, envRange string, inter watchArgs = flagset.Args() } - // "watch -- echo hello" with ETCDCTL_WATCH_KEY=foo - // should be translated to "watch foo -- echo hello" + // "ETCDCTL_WATCH_KEY=foo watch -- echo hello" + // should translate "watch foo -- echo hello" // (watchArgs=["echo","hello"] should be ["foo","echo","hello"]) if envKey != "" { - tmp := []string{envKey} + ranges := []string{envKey} if envRange != "" { - tmp = append(tmp, envRange) + ranges = append(ranges, envRange) } - watchArgs = append(tmp, watchArgs...) + watchArgs = append(ranges, watchArgs...) } if !foundSep { return watchArgs, nil, nil } - if idx == len(argsWithSep)-1 { - // "watch foo bar --" should error - return nil, nil, errBadArgsNumSeparator - } - execArgs = argsWithSep[idx+1:] - // "watch foo bar --rev 1 -- echo hello" or "watch foo --rev 1 bar -- echo hello", // then "watchArgs" is "foo bar echo hello" // so need ignore args after "argsWithSep[idx]", which is "--" diff --git a/etcdctl/ctlv3/command/watch_command_test.go b/etcdctl/ctlv3/command/watch_command_test.go index 1456c971f0c8..320478292f02 100644 --- a/etcdctl/ctlv3/command/watch_command_test.go +++ b/etcdctl/ctlv3/command/watch_command_test.go @@ -145,6 +145,14 @@ func Test_parseWatchArgs(t *testing.T) { execArgs: []string{"echo", "Hello", "World"}, err: nil, }, + { + osArgs: []string{"./bin/etcdctl", "watch", "foo", "--", "echo", "watch", "event", "received"}, + commandArgs: []string{"foo", "echo", "watch", "event", "received"}, + interactive: false, + watchArgs: []string{"foo"}, + execArgs: []string{"echo", "watch", "event", "received"}, + err: nil, + }, { osArgs: []string{"./bin/etcdctl", "watch", "foo", "--rev", "1", "--", "echo", "Hello", "World"}, commandArgs: []string{"foo", "echo", "Hello", "World"}, @@ -153,6 +161,22 @@ func Test_parseWatchArgs(t *testing.T) { execArgs: []string{"echo", "Hello", "World"}, err: nil, }, + { + osArgs: []string{"./bin/etcdctl", "watch", "foo", "--rev", "1", "--", "echo", "watch", "event", "received"}, + commandArgs: []string{"foo", "echo", "watch", "event", "received"}, + interactive: false, + watchArgs: []string{"foo"}, + execArgs: []string{"echo", "watch", "event", "received"}, + err: nil, + }, + { + osArgs: []string{"./bin/etcdctl", "watch", "--rev", "1", "foo", "--", "echo", "watch", "event", "received"}, + commandArgs: []string{"foo", "echo", "watch", "event", "received"}, + interactive: false, + watchArgs: []string{"foo"}, + execArgs: []string{"echo", "watch", "event", "received"}, + err: nil, + }, { osArgs: []string{"./bin/etcdctl", "watch", "foo", "bar", "--", "echo", "Hello", "World"}, commandArgs: []string{"foo", "bar", "echo", "Hello", "World"}, @@ -185,6 +209,30 @@ func Test_parseWatchArgs(t *testing.T) { execArgs: []string{"echo", "Hello", "World"}, err: nil, }, + { + osArgs: []string{"./bin/etcdctl", "watch", "foo", "bar", "--rev", "1", "--", "echo", "watch", "event", "received"}, + commandArgs: []string{"foo", "bar", "echo", "watch", "event", "received"}, + interactive: false, + watchArgs: []string{"foo", "bar"}, + execArgs: []string{"echo", "watch", "event", "received"}, + err: nil, + }, + { + osArgs: []string{"./bin/etcdctl", "watch", "foo", "--rev", "1", "bar", "--", "echo", "Hello", "World"}, + commandArgs: []string{"foo", "bar", "echo", "Hello", "World"}, + interactive: false, + watchArgs: []string{"foo", "bar"}, + execArgs: []string{"echo", "Hello", "World"}, + err: nil, + }, + { + osArgs: []string{"./bin/etcdctl", "watch", "--rev", "1", "foo", "bar", "--", "echo", "Hello", "World"}, + commandArgs: []string{"foo", "bar", "echo", "Hello", "World"}, + interactive: false, + watchArgs: []string{"foo", "bar"}, + execArgs: []string{"echo", "Hello", "World"}, + err: nil, + }, { osArgs: []string{"./bin/etcdctl", "watch", "--rev", "1", "--", "echo", "Hello", "World"}, commandArgs: []string{"echo", "Hello", "World"},