Browse Source

Add status field in CLI metrics : success, failure, cancelled

Signed-off-by: Guillaume Tardif <[email protected]>
Guillaume Tardif 5 years ago
parent
commit
3ccc603461

+ 3 - 1
aci/context.go

@@ -21,6 +21,8 @@ import (
 	"fmt"
 	"os"
 
+	"github.com/docker/docker/errdefs"
+
 	"github.com/AlecAivazis/survey/v2/terminal"
 	"github.com/Azure/azure-sdk-for-go/profiles/preview/preview/subscription/mgmt/subscription"
 	"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2018-05-01/resources"
@@ -138,7 +140,7 @@ func (helper contextCreateACIHelper) chooseGroup(ctx context.Context, subscripti
 	group, err := helper.selector.Select("Select a resource group", groupNames)
 	if err != nil {
 		if err == terminal.InterruptErr {
-			os.Exit(0)
+			return resources.Group{}, errdefs.Cancelled(err)
 		}
 
 		return resources.Group{}, err

+ 1 - 1
cli/cmd/context/create.go

@@ -70,7 +70,7 @@ $ docker context create my-context --description "some description" --docker "ho
 		Use:   "create CONTEXT",
 		Short: "Create new context",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			mobycli.Exec()
+			mobycli.Exec(cmd.Root())
 			return nil
 		},
 		Long: longHelp,

+ 1 - 1
cli/cmd/context/inspect.go

@@ -27,7 +27,7 @@ func inspectCommand() *cobra.Command {
 		Use:   "inspect",
 		Short: "Display detailed information on one or more contexts",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			mobycli.Exec()
+			mobycli.Exec(cmd.Root())
 			return nil
 		},
 	}

+ 1 - 1
cli/cmd/context/ls.go

@@ -69,7 +69,7 @@ func runList(cmd *cobra.Command, opts lsOpts) error {
 		return err
 	}
 	if opts.format != "" {
-		mobycli.Exec()
+		mobycli.Exec(cmd.Root())
 		return nil
 	}
 

+ 1 - 1
cli/cmd/login/login.go

@@ -56,7 +56,7 @@ func runLogin(cmd *cobra.Command, args []string) error {
 		backend := args[0]
 		return errors.New("unknown backend type for cloud login: " + backend)
 	}
-	mobycli.Exec()
+	mobycli.Exec(cmd.Root())
 	return nil
 }
 

+ 1 - 1
cli/cmd/logout/logout.go

@@ -37,6 +37,6 @@ func Command() *cobra.Command {
 }
 
 func runLogout(cmd *cobra.Command, args []string) error {
-	mobycli.Exec()
+	mobycli.Exec(cmd.Root())
 	return nil
 }

+ 1 - 1
cli/cmd/version.go

@@ -51,7 +51,7 @@ func runVersion(cmd *cobra.Command, version string) error {
 	// we don't want to fail on error, there is an error if the engine is not available but it displays client version info
 	// Still, technically the [] byte versionResult could be nil, just let the original command display what it has to display
 	if versionResult == nil {
-		mobycli.Exec()
+		mobycli.Exec(cmd.Root())
 		return nil
 	}
 	var s string = string(versionResult)

+ 20 - 15
cli/main.go

@@ -31,6 +31,8 @@ import (
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 
+	dockererrdef "github.com/docker/docker/errdefs"
+
 	"github.com/docker/compose-cli/cli/cmd/compose"
 	"github.com/docker/compose-cli/cli/cmd/logout"
 	volume "github.com/docker/compose-cli/cli/cmd/volume"
@@ -102,7 +104,7 @@ func main() {
 		SilenceUsage:  true,
 		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
 			if !isContextAgnosticCommand(cmd) {
-				mobycli.ExecIfDefaultCtxType(cmd.Context())
+				mobycli.ExecIfDefaultCtxType(cmd.Context(), cmd.Root())
 			}
 			return nil
 		},
@@ -136,7 +138,7 @@ func main() {
 	helpFunc := root.HelpFunc()
 	root.SetHelpFunc(func(cmd *cobra.Command, args []string) {
 		if !isContextAgnosticCommand(cmd) {
-			mobycli.ExecIfDefaultCtxType(cmd.Context())
+			mobycli.ExecIfDefaultCtxType(cmd.Context(), cmd.Root())
 		}
 		helpFunc(cmd, args)
 	})
@@ -158,7 +160,7 @@ func main() {
 
 	// --host and --version should immediately be forwarded to the original cli
 	if opts.Host != "" || opts.Version {
-		mobycli.Exec()
+		mobycli.Exec(root)
 	}
 
 	if opts.Config == "" {
@@ -171,7 +173,7 @@ func main() {
 
 	s, err := store.New(configDir)
 	if err != nil {
-		mobycli.Exec()
+		mobycli.Exec(root)
 	}
 
 	ctype := store.DefaultContextType
@@ -185,41 +187,43 @@ func main() {
 		root.AddCommand(volume.ACICommand())
 	}
 
-	metrics.Track(ctype, os.Args[1:], root.PersistentFlags())
-
 	ctx = apicontext.WithCurrentContext(ctx, currentContext)
 	ctx = store.WithContextStore(ctx, s)
 
 	if err = root.ExecuteContext(ctx); err != nil {
 		// if user canceled request, simply exit without any error message
-		if errors.Is(ctx.Err(), context.Canceled) {
+		if dockererrdef.IsCancelled(err) || errors.Is(ctx.Err(), context.Canceled) {
+			metrics.Track(ctype, os.Args[1:], root.PersistentFlags(), metrics.CancelledStatus)
 			os.Exit(130)
 		}
 		if ctype == store.AwsContextType {
 			exit(root, currentContext, errors.Errorf(`%q context type has been renamed. Recreate the context by running: 
-$ docker context create %s <name>`, cc.Type(), store.EcsContextType))
+$ docker context create %s <name>`, 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)
+			exit(root, currentContext, err, ctype)
 		}
-		mobycli.ExecIfDefaultCtxType(ctx)
+		mobycli.ExecIfDefaultCtxType(ctx, root)
 
-		checkIfUnknownCommandExistInDefaultContext(err, currentContext)
+		checkIfUnknownCommandExistInDefaultContext(err, currentContext, root)
 
-		exit(root, currentContext, err)
+		exit(root, currentContext, err, ctype)
 	}
+	metrics.Track(ctype, os.Args[1:], root.PersistentFlags(), metrics.SuccessStatus)
 }
 
-func exit(cmd *cobra.Command, ctx string, err error) {
+func exit(root *cobra.Command, ctx string, err error, ctype string) {
+	metrics.Track(ctype, os.Args[1:], root.PersistentFlags(), metrics.FailureStatus)
+
 	if errors.Is(err, errdefs.ErrLoginRequired) {
 		fmt.Fprintln(os.Stderr, err)
 		os.Exit(errdefs.ExitCodeLoginRequired)
 	}
 	if errors.Is(err, errdefs.ErrNotImplemented) {
-		cmd, _, _ := cmd.Traverse(os.Args[1:])
+		cmd, _, _ := root.Traverse(os.Args[1:])
 		name := cmd.Name()
 		parent := cmd.Parent()
 		if parent != nil && parent.Parent() != nil {
@@ -237,13 +241,14 @@ func fatal(err error) {
 	os.Exit(1)
 }
 
-func checkIfUnknownCommandExistInDefaultContext(err error, currentContext string) {
+func checkIfUnknownCommandExistInDefaultContext(err error, currentContext string, root *cobra.Command) {
 	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(currentContext, os.Args[1:], root.PersistentFlags(), metrics.FailureStatus)
 			os.Exit(1)
 		}
 	}

+ 12 - 4
cli/mobycli/exec.go

@@ -24,6 +24,10 @@ import (
 	"os/signal"
 	"strings"
 
+	"github.com/spf13/cobra"
+
+	"github.com/docker/compose-cli/metrics"
+
 	apicontext "github.com/docker/compose-cli/context"
 	"github.com/docker/compose-cli/context/store"
 )
@@ -33,8 +37,8 @@ var delegatedContextTypes = []string{store.DefaultContextType}
 // ComDockerCli name of the classic cli binary
 const ComDockerCli = "com.docker.cli"
 
-// ExecIfDefaultCtxType delegates to com.docker.cli if on moby or AWS context (until there is an AWS backend)
-func ExecIfDefaultCtxType(ctx context.Context) {
+// ExecIfDefaultCtxType delegates to com.docker.cli if on moby context
+func ExecIfDefaultCtxType(ctx context.Context, root *cobra.Command) {
 	currentContext := apicontext.CurrentContext(ctx)
 
 	s := store.ContextStore(ctx)
@@ -42,7 +46,7 @@ func ExecIfDefaultCtxType(ctx context.Context) {
 	currentCtx, err := s.Get(currentContext)
 	// Only run original docker command if the current context is not ours.
 	if err != nil || mustDelegateToMoby(currentCtx.Type()) {
-		Exec()
+		Exec(root)
 	}
 }
 
@@ -56,7 +60,7 @@ func mustDelegateToMoby(ctxType string) bool {
 }
 
 // Exec delegates to com.docker.cli if on moby context
-func Exec() {
+func Exec(root *cobra.Command) {
 	cmd := exec.Command(ComDockerCli, os.Args[1:]...)
 	cmd.Stdin = os.Stdin
 	cmd.Stdout = os.Stdout
@@ -83,12 +87,16 @@ func Exec() {
 	err := cmd.Run()
 	childExit <- true
 	if err != nil {
+		metrics.Track(store.DefaultContextName, os.Args[1:], root.PersistentFlags(), metrics.FailureStatus)
+
 		if exiterr, ok := err.(*exec.ExitError); ok {
 			os.Exit(exiterr.ExitCode())
 		}
 		fmt.Fprintln(os.Stderr, err)
 		os.Exit(1)
 	}
+	metrics.Track(store.DefaultContextName, os.Args[1:], root.PersistentFlags(), metrics.SuccessStatus)
+
 	os.Exit(0)
 }
 

+ 3 - 1
ecs/context.go

@@ -23,6 +23,8 @@ import (
 	"reflect"
 	"strings"
 
+	"github.com/docker/docker/errdefs"
+
 	"github.com/AlecAivazis/survey/v2/terminal"
 	"github.com/aws/aws-sdk-go/aws/awserr"
 	"github.com/aws/aws-sdk-go/aws/credentials"
@@ -155,7 +157,7 @@ func (h contextCreateAWSHelper) chooseProfile(section map[string]ini.Section) (s
 	selected, err := h.user.Select("Select AWS Profile", profiles)
 	if err != nil {
 		if err == terminal.InterruptErr {
-			os.Exit(-1)
+			return "", errdefs.Cancelled(err)
 		}
 		return "", err
 	}

+ 7 - 1
metrics/client.go

@@ -33,6 +33,7 @@ type Command struct {
 	Command string `json:"command"`
 	Context string `json:"context"`
 	Source  string `json:"source"`
+	Status  string `json:"status"`
 }
 
 const (
@@ -40,6 +41,12 @@ const (
 	CLISource = "cli"
 	// APISource is sent for API metrics
 	APISource = "api"
+	// SuccessStatus is sent for API metrics
+	SuccessStatus = "success"
+	// FailureStatus is sent for API metrics
+	FailureStatus = "failure"
+	// CancelledStatus is sent for API metrics
+	CancelledStatus = "cancelled"
 )
 
 // Client sends metrics to Docker Desktopn
@@ -83,5 +90,4 @@ func (c *client) Send(command Command) {
 		_, _ = c.httpClient.Post("http://localhost/usage", "application/json", bytes.NewBuffer(req))
 	}()
 	<-wasIn
-
 }

+ 2 - 1
metrics/metrics.go

@@ -77,7 +77,7 @@ const (
 )
 
 // Track sends the tracking analytics to Docker Desktop
-func Track(context string, args []string, flags *flag.FlagSet) {
+func Track(context string, args []string, flags *flag.FlagSet, status string) {
 	command := getCommand(args, flags)
 	if command != "" {
 		c := NewClient()
@@ -85,6 +85,7 @@ func Track(context string, args []string, flags *flag.FlagSet) {
 			Command: command,
 			Context: context,
 			Source:  CLISource,
+			Status:  status,
 		})
 	}
 }