From 0d3f7186c572f0ac0ca86f21f34bd17a2706bdab Mon Sep 17 00:00:00 2001 From: Guillaume Tardif Date: Wed, 7 Oct 2020 23:29:55 +0200 Subject: [PATCH 1/4] Hardcoded list of commands and simplified metrics gathering. Signed-off-by: Guillaume Tardif --- cli/main.go | 22 ++-- cli/mobycli/exec.go | 4 +- metrics/commands.go | 148 ++++++++++++++++++++++++ metrics/generatecommands/main.go | 89 ++++++++++++++ metrics/metrics.go | 191 +++++-------------------------- metrics/metrics_test.go | 99 ++++++++-------- tests/e2e/e2e_test.go | 4 +- 7 files changed, 333 insertions(+), 224 deletions(-) create mode 100644 metrics/commands.go create mode 100644 metrics/generatecommands/main.go diff --git a/cli/main.go b/cli/main.go index 316479921..ea6d3e4cd 100644 --- a/cli/main.go +++ b/cli/main.go @@ -198,37 +198,37 @@ func main() { if err = root.ExecuteContext(ctx); err != nil { // if user canceled request, simply exit without any error message if errdefs.IsErrCanceled(err) || errors.Is(ctx.Err(), context.Canceled) { - metrics.Track(ctype, os.Args[1:], root.PersistentFlags(), metrics.CanceledStatus) + metrics.Track(ctype, os.Args[1:], metrics.CanceledStatus) os.Exit(130) } if ctype == store.AwsContextType { - exit(root, currentContext, errors.Errorf(`%q context type has been renamed. Recreate the context by running: + exit(currentContext, errors.Errorf(`%q context type has been renamed. Recreate the context by running: $ docker context create %s `, cc.Type(), store.EcsContextType), ctype) } // Context should always be handled by new CLI requiredCmd, _, _ := root.Find(os.Args[1:]) if requiredCmd != nil && isContextAgnosticCommand(requiredCmd) { - exit(root, currentContext, err, ctype) + exit(currentContext, err, ctype) } mobycli.ExecIfDefaultCtxType(ctx, root) - checkIfUnknownCommandExistInDefaultContext(err, currentContext, root, ctype) + checkIfUnknownCommandExistInDefaultContext(err, currentContext, ctype) - exit(root, currentContext, err, ctype) + exit(currentContext, err, ctype) } - metrics.Track(ctype, os.Args[1:], root.PersistentFlags(), metrics.SuccessStatus) + metrics.Track(ctype, os.Args[1:], metrics.SuccessStatus) } -func exit(root *cobra.Command, ctx string, err error, ctype string) { - metrics.Track(ctype, os.Args[1:], root.PersistentFlags(), metrics.FailureStatus) +func exit(ctx string, err error, ctype string) { + metrics.Track(ctype, os.Args[1:], metrics.FailureStatus) if errors.Is(err, errdefs.ErrLoginRequired) { fmt.Fprintln(os.Stderr, err) os.Exit(errdefs.ExitCodeLoginRequired) } if errors.Is(err, errdefs.ErrNotImplemented) { - name := metrics.GetCommand(os.Args[1:], root.PersistentFlags()) + name := metrics.GetCommand(os.Args[1:]) fmt.Fprintf(os.Stderr, "Command %q not available in current context (%s)\n", name, ctx) os.Exit(1) @@ -242,14 +242,14 @@ func fatal(err error) { os.Exit(1) } -func checkIfUnknownCommandExistInDefaultContext(err error, currentContext string, root *cobra.Command, contextType string) { +func checkIfUnknownCommandExistInDefaultContext(err error, currentContext string, contextType string) { submatch := unknownCommandRegexp.FindSubmatch([]byte(err.Error())) if len(submatch) == 2 { dockerCommand := string(submatch[1]) if mobycli.IsDefaultContextCommand(dockerCommand) { fmt.Fprintf(os.Stderr, "Command %q not available in current context (%s), you can use the \"default\" context to run this command\n", dockerCommand, currentContext) - metrics.Track(contextType, os.Args[1:], root.PersistentFlags(), metrics.FailureStatus) + metrics.Track(contextType, os.Args[1:], metrics.FailureStatus) os.Exit(1) } } diff --git a/cli/mobycli/exec.go b/cli/mobycli/exec.go index 5b89cd1c3..8c4500050 100644 --- a/cli/mobycli/exec.go +++ b/cli/mobycli/exec.go @@ -86,7 +86,7 @@ func Exec(root *cobra.Command) { err := cmd.Run() childExit <- true if err != nil { - metrics.Track(store.DefaultContextType, os.Args[1:], root.PersistentFlags(), metrics.FailureStatus) + metrics.Track(store.DefaultContextType, os.Args[1:], metrics.FailureStatus) if exiterr, ok := err.(*exec.ExitError); ok { os.Exit(exiterr.ExitCode()) @@ -94,7 +94,7 @@ func Exec(root *cobra.Command) { fmt.Fprintln(os.Stderr, err) os.Exit(1) } - metrics.Track(store.DefaultContextType, os.Args[1:], root.PersistentFlags(), metrics.SuccessStatus) + metrics.Track(store.DefaultContextType, os.Args[1:], metrics.SuccessStatus) os.Exit(0) } diff --git a/metrics/commands.go b/metrics/commands.go new file mode 100644 index 000000000..1350de16e --- /dev/null +++ b/metrics/commands.go @@ -0,0 +1,148 @@ +/* + Copyright 2020 Docker Compose CLI authors + + 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 metrics + +var commandFlags = []string{ + //added to catch scan details + "--version", "--login", + "--help", "-h"} + +// Generated with generatecommands/main.go +var managementCommands = []string{ + "ecs", + "assemble", + "registry", + "template", + "cluster", + "app", + "builder", + "buildx", + "imagetools", + "checkpoint", + "config", + "container", + "context", + "image", + "manifest", + "network", + "node", + "plugin", + "scan", + "secret", + "service", + "stack", + "swarm", + "system", + "trust", + "key", + "signer", + "volume", + "login", + "create", + "compose", +} + +var commands = []string{ + "bundle", + "completion", + "init", + "inspect", + "install", + "list", + "merge", + "pull", + "push", + "render", + "split", + "status", + "uninstall", + "upgrade", + "validate", + "version", + "build", + "prune", + "create", + "bake", + "du", + "ls", + "rm", + "stop", + "use", + "attach", + "commit", + "cp", + "diff", + "exec", + "export", + "kill", + "logs", + "pause", + "port", + "rename", + "restart", + "run", + "start", + "stats", + "top", + "unpause", + "update", + "wait", + "show", + "history", + "import", + "load", + "save", + "tag", + "annotate", + "connect", + "disconnect", + "demote", + "promote", + "ps", + "disable", + "enable", + "set", + "rollback", + "scale", + "deploy", + "services", + "ca", + "join", + "join-token", + "leave", + "unlock", + "unlock-key", + "df", + "events", + "info", + "generate", + "add", + "remove", + "revoke", + "sign", + "images", + "login", + "logout", + "rmi", + "search", + "azure", + "aci", + "ecs", + "convert", + "down", + "up", +} diff --git a/metrics/generatecommands/main.go b/metrics/generatecommands/main.go new file mode 100644 index 000000000..d4c57b5cf --- /dev/null +++ b/metrics/generatecommands/main.go @@ -0,0 +1,89 @@ +/* + Copyright 2020 Docker Compose CLI authors + + 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 main + +import ( + "fmt" + "os/exec" + "strings" + + "github.com/docker/compose-cli/utils" +) + +var managementCommands = []string{"ecs", "assemble", "registry", "template", "cluster"} + +var commands = []string{} + +func main() { + getCommands() + getCommands("login") + getCommands("context", "create") + getCommands("compose") + + fmt.Printf(` +var managementCommands = []string{ + "%s", +} + +var commands = []string{ + "%s", +} +`, strings.Join(managementCommands, "\", \n\t\""), strings.Join(commands, "\", \n\t\"")) +} + +func getCommands(execCommands ...string) { + if len(execCommands) > 0 { + managementCommands = append(managementCommands, execCommands[len(execCommands)-1]) + } + withHelp := append(execCommands, "--help") + cmd := exec.Command("docker", withHelp...) + output, err := cmd.Output() + if err != nil { + return + } + text := string(output) + lines := strings.Split(text, "\n") + mgtCommandsStarted := false + commandsStarted := false + for _, line := range lines { + trimmedLine := strings.TrimSpace(line) + if strings.HasPrefix(trimmedLine, "Management Commands:") { + mgtCommandsStarted = true + continue + } + if strings.HasPrefix(trimmedLine, "Commands:") || strings.HasPrefix(trimmedLine, "Available Commands:") { + mgtCommandsStarted = false + commandsStarted = true + continue + } + if trimmedLine == "" { + mgtCommandsStarted = false + commandsStarted = false + continue + } + tokens := strings.Split(trimmedLine, " ") + command := strings.Replace(tokens[0], "*", "", 1) + if mgtCommandsStarted { + getCommands(append(execCommands, command)...) + } + if commandsStarted { + if !utils.StringContains(commands, command) { + commands = append(commands, command) + } + } + } +} diff --git a/metrics/metrics.go b/metrics/metrics.go index 05892cd4a..edc048fc7 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -17,68 +17,12 @@ package metrics import ( - "strings" - - flag "github.com/spf13/pflag" - "github.com/docker/compose-cli/utils" ) -var managementCommands = []string{ - "app", - "assemble", - "builder", - "buildx", - "ecs", - "ecs compose", - "cluster", - "compose", - "config", - "container", - "context", - // We add "context create" as a management command to be able to catch - // calls to "context create aci" - "context create", - "help", - "image", - // Adding "login" as a management command so that the system can catch - // commands like `docker login azure` - "login", - "manifest", - "network", - "node", - "plugin", - "registry", - "secret", - "service", - "stack", - "swarm", - "system", - "template", - "trust", - "volume", -} - -// managementSubCommands holds a list of allowed subcommands of a management -// command. For example we want to send an event for "docker login azure" but -// we don't wat to send the name of the registry when the user does a -// "docker login my-registry", we only want to send "login" -var managementSubCommands = map[string][]string{ - "login": { - "azure", - }, - "context create": { - "aci", - }, -} - -const ( - scanCommand = "scan" -) - // Track sends the tracking analytics to Docker Desktop -func Track(context string, args []string, flags *flag.FlagSet, status string) { - command := GetCommand(args, flags) +func Track(context string, args []string, status string) { + command := GetCommand(args) if command != "" { c := NewClient() c.Send(Command{ @@ -90,115 +34,36 @@ func Track(context string, args []string, flags *flag.FlagSet, status string) { } } +func isCommand(word string) bool { + return utils.StringContains(commands, word) || isManagementCommand(word) +} + +func isManagementCommand(word string) bool { + return utils.StringContains(managementCommands, word) +} + +func isCommandFlag(word string) bool { + return utils.StringContains(commandFlags, word) +} + // GetCommand get the invoked command -func GetCommand(args []string, flags *flag.FlagSet) string { - command := "" - strippedArgs := stripFlags(args, flags) - - if len(strippedArgs) != 0 { - command = strippedArgs[0] - - if command == scanCommand { - return getScanCommand(args) - } - - for { - if utils.StringContains(managementCommands, command) { - if sub := getSubCommand(command, strippedArgs[1:]); sub != "" { - command += " " + sub - strippedArgs = strippedArgs[1:] - continue - } - } +func GetCommand(args []string) string { + result := "" + onlyFlags := false + for _, arg := range args { + if arg == "--" { break } - } - - return command -} - -func getScanCommand(args []string) string { - command := args[0] - - if utils.StringContains(args, "--auth") { - return command + " auth" - } - - if utils.StringContains(args, "--version") { - return command + " version" - } - - return command -} - -func getSubCommand(command string, args []string) string { - if len(args) == 0 { - return "" - } - - if val, ok := managementSubCommands[command]; ok { - if utils.StringContains(val, args[0]) { - return args[0] - } - return "" - } - - if isArg(args[0]) { - return args[0] - } - - return "" -} - -func stripFlags(args []string, flags *flag.FlagSet) []string { - commands := []string{} - - for len(args) > 0 { - s := args[0] - args = args[1:] - - if s == "--" { - return commands - } - - if flagArg(s, flags) { - if len(args) <= 1 { - return commands + if isCommandFlag(arg) || (!onlyFlags && isCommand(arg)) { + if result == "" { + result = arg + } else { + result = result + " " + arg + } + if !isManagementCommand(arg) { + onlyFlags = true } - args = args[1:] - } - - if isArg(s) { - commands = append(commands, s) } } - - return commands -} - -func flagArg(s string, flags *flag.FlagSet) bool { - return strings.HasPrefix(s, "--") && !strings.Contains(s, "=") && !hasNoOptDefVal(s[2:], flags) || - strings.HasPrefix(s, "-") && !strings.Contains(s, "=") && len(s) == 2 && !shortHasNoOptDefVal(s[1:], flags) -} - -func isArg(s string) bool { - return s != "" && !strings.HasPrefix(s, "-") -} - -func hasNoOptDefVal(name string, fs *flag.FlagSet) bool { - flag := fs.Lookup(name) - if flag == nil { - return false - } - - return flag.NoOptDefVal != "" -} - -func shortHasNoOptDefVal(name string, fs *flag.FlagSet) bool { - flag := fs.ShorthandLookup(name[:1]) - if flag == nil { - return false - } - - return flag.NoOptDefVal != "" + return result } diff --git a/metrics/metrics_test.go b/metrics/metrics_test.go index cb79f9a57..4e19df738 100644 --- a/metrics/metrics_test.go +++ b/metrics/metrics_test.go @@ -19,15 +19,10 @@ package metrics import ( "testing" - "github.com/spf13/cobra" "gotest.tools/v3/assert" ) -func TestFlag(t *testing.T) { - root := &cobra.Command{} - root.PersistentFlags().BoolP("debug", "D", false, "debug") - root.PersistentFlags().String("str", "str", "str") - +func TestGetCommand(t *testing.T) { testCases := []struct { name string args []string @@ -58,26 +53,11 @@ func TestFlag(t *testing.T) { args: []string{"--debug", "--str", "str-value"}, expected: "", }, - { - name: "with unknown short flag", - args: []string{"-f", "run"}, - expected: "", - }, - { - name: "with unknown long flag", - args: []string{"--unknown-flag", "run"}, - expected: "", - }, { name: "management command", args: []string{"image", "ls"}, expected: "image ls", }, - { - name: "management command with flag", - args: []string{"image", "--test", "ls"}, - expected: "image", - }, { name: "management subcommand with flag", args: []string{"image", "ls", "-q"}, @@ -93,14 +73,9 @@ func TestFlag(t *testing.T) { args: []string{"login", "-u", "test", "azure"}, expected: "login azure", }, - { - name: "azure login with azure user", - args: []string{"login", "-u", "azure"}, - expected: "login", - }, { name: "login to a registry", - args: []string{"login", "registry"}, + args: []string{"login", "myregistry"}, expected: "login", }, { @@ -119,9 +94,9 @@ func TestFlag(t *testing.T) { expected: "create", }, { - name: "create a container named aci", - args: []string{"create", "aci"}, - expected: "create", + name: "start a container named aci", + args: []string{"start", "aci"}, + expected: "start", }, { name: "create a container named test-container", @@ -152,15 +127,49 @@ func TestFlag(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - result := GetCommand(testCase.args, root.PersistentFlags()) + result := GetCommand(testCase.args) + assert.Equal(t, testCase.expected, result) + }) + } +} + +func TestFlags(t *testing.T) { + testCases := []struct { + name string + args []string + expected string + }{ + { + name: "help", + args: []string{"--help"}, + expected: "--help", + }, + { + name: "help on run", + args: []string{"run", "--help"}, + expected: "run --help", + }, + { + name: "-h on run", + args: []string{"run", "-h"}, + expected: "run -h", + }, + { + name: "help on compose up", + args: []string{"compose", "up", "--help"}, + expected: "compose up --help", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + result := GetCommand(testCase.args) assert.Equal(t, testCase.expected, result) }) } } func TestEcs(t *testing.T) { - root := &cobra.Command{} - testCases := []struct { name string args []string @@ -217,23 +226,21 @@ func TestEcs(t *testing.T) { expected: "ecs compose logs", }, { - name: "setup", - args: []string{"ecs", "setup"}, - expected: "ecs setup", + name: "ecs", + args: []string{"ecs", "anything"}, + expected: "ecs", }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - result := GetCommand(testCase.args, root.PersistentFlags()) + result := GetCommand(testCase.args) assert.Equal(t, testCase.expected, result) }) } } func TestScan(t *testing.T) { - root := &cobra.Command{} - testCases := []struct { name string args []string @@ -246,34 +253,34 @@ func TestScan(t *testing.T) { }, { name: "scan image with long flags", - args: []string{"scan", "--file", "file", "image"}, + args: []string{"scan", "--file", "file", "myimage"}, expected: "scan", }, { name: "scan image with short flags", - args: []string{"scan", "-f", "file", "image"}, + args: []string{"scan", "-f", "file", "myimage"}, expected: "scan", }, { name: "scan with long flag", - args: []string{"scan", "--dependency-tree", "image"}, + args: []string{"scan", "--dependency-tree", "myimage"}, expected: "scan", }, { name: "auth", - args: []string{"scan", "--auth"}, - expected: "scan auth", + args: []string{"scan", "--login"}, + expected: "scan --login", }, { name: "version", args: []string{"scan", "--version"}, - expected: "scan version", + expected: "scan --version", }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - result := GetCommand(testCase.args, root.PersistentFlags()) + result := GetCommand(testCase.args) assert.Equal(t, testCase.expected, result) }) } diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index 18dc00e3f..051eb1343 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -185,7 +185,7 @@ func TestContextMetrics(t *testing.T) { c.RunDockerCmd("ps") c.RunDockerCmd("context", "use", "test-example") c.RunDockerCmd("ps") - c.RunDockerOrExitError("error") + c.RunDockerOrExitError("stop", "unknown") c.RunDockerCmd("context", "use", "default") c.RunDockerCmd("--context", "test-example", "ps") @@ -195,7 +195,7 @@ func TestContextMetrics(t *testing.T) { assert.Equal(t, `{"command":"ps","context":"moby","source":"cli","status":"success"}`, usage[1]) assert.Equal(t, `{"command":"context use","context":"moby","source":"cli","status":"success"}`, usage[2]) assert.Equal(t, `{"command":"ps","context":"example","source":"cli","status":"success"}`, usage[3]) - assert.Equal(t, `{"command":"error","context":"example","source":"cli","status":"failure"}`, usage[4]) + assert.Equal(t, `{"command":"stop","context":"example","source":"cli","status":"failure"}`, usage[4]) assert.Equal(t, `{"command":"context use","context":"example","source":"cli","status":"success"}`, usage[5]) assert.Equal(t, `{"command":"ps","context":"example","source":"cli","status":"success"}`, usage[6]) }) From 3e93a690d2a9e9a530767f6370b0ecf87349cc7f Mon Sep 17 00:00:00 2001 From: Guillaume Tardif Date: Thu, 8 Oct 2020 13:12:36 +0200 Subject: [PATCH 2/4] =?UTF-8?q?Adding=20command=20aliases,=20was=20missing?= =?UTF-8?q?=20only=20=E2=80=9Cf=E2=80=9D=20and=20=E2=80=9Cb=E2=80=9D=20(fr?= =?UTF-8?q?om=20`build=20bake`=20and=20`build=20build`)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Guillaume Tardif --- metrics/commands.go | 35 +++++++++++---------- metrics/generatecommands/main.go | 52 +++++++++++++++++++++----------- metrics/metrics_test.go | 15 +++++++++ 3 files changed, 68 insertions(+), 34 deletions(-) diff --git a/metrics/commands.go b/metrics/commands.go index 1350de16e..b9a13a08b 100644 --- a/metrics/commands.go +++ b/metrics/commands.go @@ -28,31 +28,32 @@ var managementCommands = []string{ "registry", "template", "cluster", + "scan", "app", "builder", - "buildx", "imagetools", + "buildx", "checkpoint", "config", "container", "context", + "create", "image", "manifest", "network", "node", "plugin", - "scan", "secret", "service", "stack", "swarm", "system", - "trust", "key", "signer", + "trust", "volume", "login", - "create", + "logout", "compose", } @@ -62,7 +63,9 @@ var commands = []string{ "init", "inspect", "install", + "deploy", "list", + "ls", "merge", "pull", "push", @@ -77,11 +80,13 @@ var commands = []string{ "prune", "create", "bake", + "f", + "b", "du", - "ls", "rm", "stop", "use", + "remove", "attach", "commit", "cp", @@ -90,6 +95,7 @@ var commands = []string{ "export", "kill", "logs", + "ps", "pause", "port", "rename", @@ -101,10 +107,14 @@ var commands = []string{ "unpause", "update", "wait", + "aci", + "ecs", "show", "history", "import", "load", + "images", + "rmi", "save", "tag", "annotate", @@ -112,13 +122,13 @@ var commands = []string{ "disconnect", "demote", "promote", - "ps", "disable", "enable", "set", "rollback", "scale", - "deploy", + "up", + "down", "services", "ca", "join", @@ -131,18 +141,11 @@ var commands = []string{ "info", "generate", "add", - "remove", "revoke", "sign", - "images", "login", - "logout", - "rmi", - "search", "azure", - "aci", - "ecs", + "logout", + "search", "convert", - "down", - "up", } diff --git a/metrics/generatecommands/main.go b/metrics/generatecommands/main.go index d4c57b5cf..494b5875f 100644 --- a/metrics/generatecommands/main.go +++ b/metrics/generatecommands/main.go @@ -24,14 +24,12 @@ import ( "github.com/docker/compose-cli/utils" ) -var managementCommands = []string{"ecs", "assemble", "registry", "template", "cluster"} +var managementCommands = []string{"ecs", "assemble", "registry", "template", "cluster", "scan"} var commands = []string{} func main() { getCommands() - getCommands("login") - getCommands("context", "create") getCommands("compose") fmt.Printf(` @@ -45,10 +43,13 @@ var commands = []string{ `, strings.Join(managementCommands, "\", \n\t\""), strings.Join(commands, "\", \n\t\"")) } +const ( + mgtCommandsSection = "Management Commands:" + commandsSection = "Commands:" + aliasesSection = "Aliases:" +) + func getCommands(execCommands ...string) { - if len(execCommands) > 0 { - managementCommands = append(managementCommands, execCommands[len(execCommands)-1]) - } withHelp := append(execCommands, "--help") cmd := exec.Command("docker", withHelp...) output, err := cmd.Output() @@ -57,33 +58,48 @@ func getCommands(execCommands ...string) { } text := string(output) lines := strings.Split(text, "\n") - mgtCommandsStarted := false - commandsStarted := false + section := "" for _, line := range lines { trimmedLine := strings.TrimSpace(line) - if strings.HasPrefix(trimmedLine, "Management Commands:") { - mgtCommandsStarted = true + if strings.HasPrefix(trimmedLine, mgtCommandsSection) { + section = mgtCommandsSection continue } - if strings.HasPrefix(trimmedLine, "Commands:") || strings.HasPrefix(trimmedLine, "Available Commands:") { - mgtCommandsStarted = false - commandsStarted = true + if strings.HasPrefix(trimmedLine, commandsSection) || strings.HasPrefix(trimmedLine, "Available Commands:") { + section = commandsSection + if len(execCommands) > 0 { + command := execCommands[len(execCommands)-1] + managementCommands = append(managementCommands, command) + } + continue + } + if strings.HasPrefix(trimmedLine, aliasesSection) { + section = aliasesSection continue } if trimmedLine == "" { - mgtCommandsStarted = false - commandsStarted = false + section = "" continue } + tokens := strings.Split(trimmedLine, " ") command := strings.Replace(tokens[0], "*", "", 1) - if mgtCommandsStarted { + switch section { + case mgtCommandsSection: getCommands(append(execCommands, command)...) - } - if commandsStarted { + case commandsSection: if !utils.StringContains(commands, command) { commands = append(commands, command) } + getCommands(append(execCommands, command)...) + case aliasesSection: + aliases := strings.Split(trimmedLine, ",") + for _, alias := range aliases { + trimmedAlias := strings.TrimSpace(alias) + if !utils.StringContains(commands, trimmedAlias) { + commands = append(commands, trimmedAlias) + } + } } } } diff --git a/metrics/metrics_test.go b/metrics/metrics_test.go index 4e19df738..12846baf1 100644 --- a/metrics/metrics_test.go +++ b/metrics/metrics_test.go @@ -68,6 +68,11 @@ func TestGetCommand(t *testing.T) { args: []string{"login", "azure"}, expected: "login azure", }, + { + name: "azure logout", + args: []string{"logout", "azure"}, + expected: "logout azure", + }, { name: "azure login with flags", args: []string{"login", "-u", "test", "azure"}, @@ -78,11 +83,21 @@ func TestGetCommand(t *testing.T) { args: []string{"login", "myregistry"}, expected: "login", }, + { + name: "logout from a registry", + args: []string{"logout", "myregistry"}, + expected: "logout", + }, { name: "context create aci", args: []string{"context", "create", "aci"}, expected: "context create aci", }, + { + name: "context create ecs", + args: []string{"context", "create", "ecs"}, + expected: "context create ecs", + }, { name: "create a context from another context", args: []string{"context", "create", "test-context", "--from=default"}, From a09983b47455c8e9ce9a9f1aec68aeef6d61f0cb Mon Sep 17 00:00:00 2001 From: Guillaume Tardif Date: Thu, 8 Oct 2020 14:36:40 +0200 Subject: [PATCH 3/4] Do not send metrics for help commands Signed-off-by: Guillaume Tardif --- metrics/commands.go | 2 +- metrics/metrics.go | 3 +++ metrics/metrics_test.go | 18 +++++++++--------- tests/e2e/e2e_test.go | 11 +++++++++++ 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/metrics/commands.go b/metrics/commands.go index b9a13a08b..e07c02419 100644 --- a/metrics/commands.go +++ b/metrics/commands.go @@ -19,7 +19,7 @@ package metrics var commandFlags = []string{ //added to catch scan details "--version", "--login", - "--help", "-h"} +} // Generated with generatecommands/main.go var managementCommands = []string{ diff --git a/metrics/metrics.go b/metrics/metrics.go index edc048fc7..3f6c3931d 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -51,6 +51,9 @@ func GetCommand(args []string) string { result := "" onlyFlags := false for _, arg := range args { + if arg == "--help" { + return "" + } if arg == "--" { break } diff --git a/metrics/metrics_test.go b/metrics/metrics_test.go index 12846baf1..cd66e18a4 100644 --- a/metrics/metrics_test.go +++ b/metrics/metrics_test.go @@ -58,6 +58,11 @@ func TestGetCommand(t *testing.T) { args: []string{"image", "ls"}, expected: "image ls", }, + { + name: "management command with flag", + args: []string{"image", "--test", "ls"}, + expected: "image ls", + }, { name: "management subcommand with flag", args: []string{"image", "ls", "-q"}, @@ -148,7 +153,7 @@ func TestGetCommand(t *testing.T) { } } -func TestFlags(t *testing.T) { +func TestIgnoreHelpCommands(t *testing.T) { testCases := []struct { name string args []string @@ -157,22 +162,17 @@ func TestFlags(t *testing.T) { { name: "help", args: []string{"--help"}, - expected: "--help", + expected: "", }, { name: "help on run", args: []string{"run", "--help"}, - expected: "run --help", - }, - { - name: "-h on run", - args: []string{"run", "-h"}, - expected: "run -h", + expected: "", }, { name: "help on compose up", args: []string{"compose", "up", "--help"}, - expected: "compose up --help", + expected: "", }, } diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index 051eb1343..97cdee5eb 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -164,6 +164,17 @@ func TestContextMetrics(t *testing.T) { s.Start() defer s.Stop() + t.Run("do not send metrics on help commands", func(t *testing.T) { + s.ResetUsage() + + c.RunDockerCmd("--help") + c.RunDockerCmd("ps", "--help") + c.RunDockerCmd("run", "--help") + + usage := s.GetUsage() + assert.Equal(t, 0, len(usage)) + }) + t.Run("metrics on default context", func(t *testing.T) { s.ResetUsage() From 7ff9162dc7a871fec15777fd47b0b0ce651b7f42 Mon Sep 17 00:00:00 2001 From: Guillaume Tardif Date: Thu, 8 Oct 2020 14:50:27 +0200 Subject: [PATCH 4/4] Remove enterprise specific commands Signed-off-by: Guillaume Tardif --- metrics/commands.go | 4 ---- metrics/generatecommands/main.go | 3 ++- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/metrics/commands.go b/metrics/commands.go index e07c02419..bcf9ee91f 100644 --- a/metrics/commands.go +++ b/metrics/commands.go @@ -24,10 +24,6 @@ var commandFlags = []string{ // Generated with generatecommands/main.go var managementCommands = []string{ "ecs", - "assemble", - "registry", - "template", - "cluster", "scan", "app", "builder", diff --git a/metrics/generatecommands/main.go b/metrics/generatecommands/main.go index 494b5875f..9491c4e8a 100644 --- a/metrics/generatecommands/main.go +++ b/metrics/generatecommands/main.go @@ -24,11 +24,12 @@ import ( "github.com/docker/compose-cli/utils" ) -var managementCommands = []string{"ecs", "assemble", "registry", "template", "cluster", "scan"} +var managementCommands = []string{"ecs", "scan"} var commands = []string{} func main() { + fmt.Println("Walking through docker help to list commands...") getCommands() getCommands("compose")