소스 검색

wrap compose cobra command to set exitcode according to metrics status

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof 4 년 전
부모
커밋
d8aa00a766

+ 3 - 3
cli/cmd/compose/build.go

@@ -46,7 +46,7 @@ func buildCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "build [SERVICE...]",
 		Short: "Build or rebuild services",
-		RunE: func(cmd *cobra.Command, args []string) error {
+		RunE: Adapt(func(ctx context.Context, args []string) error {
 			if opts.memory != "" {
 				fmt.Println("WARNING --memory is ignored as not supported in buildkit.")
 			}
@@ -57,8 +57,8 @@ func buildCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 				}
 				os.Stdout = devnull
 			}
-			return runBuild(cmd.Context(), backend, opts, args)
-		},
+			return runBuild(ctx, backend, opts, args)
+		}),
 	}
 	cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Don't print anything to STDOUT")
 	cmd.Flags().BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image.")

+ 26 - 3
cli/cmd/compose/compose.go

@@ -17,12 +17,14 @@
 package compose
 
 import (
+	"context"
 	"fmt"
 	"os"
 	"strings"
 
 	"github.com/compose-spec/compose-go/cli"
 	"github.com/compose-spec/compose-go/types"
+	dockercli "github.com/docker/cli/cli"
 	"github.com/morikuni/aec"
 	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
@@ -34,6 +36,24 @@ import (
 	"github.com/docker/compose-cli/cli/metrics"
 )
 
+//Command defines a compose CLI command as a func with args
+type Command func(context.Context, []string) error
+
+//Adapt a Command func to cobra library
+func Adapt(fn Command) func(cmd *cobra.Command, args []string) error {
+	return func(cmd *cobra.Command, args []string) error {
+		err := fn(cmd.Context(), args)
+		var composeErr metrics.ComposeError
+		if errors.As(err, &composeErr) {
+			err = dockercli.StatusError{
+				StatusCode: composeErr.GetMetricsFailureCategory().ExitCode,
+				Status:     err.Error(),
+			}
+		}
+		return err
+	}
+}
+
 // Warning is a global warning to be displayed to user on command failure
 var Warning string
 
@@ -105,8 +125,8 @@ func (o *projectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.Proj
 			cli.WithName(o.ProjectName))...)
 }
 
-// Command returns the compose command with its child commands
-func Command(contextType string, backend compose.Service) *cobra.Command {
+// RootCommand returns the compose command with its child commands
+func RootCommand(contextType string, backend compose.Service) *cobra.Command {
 	opts := projectOptions{}
 	var ansi string
 	var noAnsi bool
@@ -120,7 +140,10 @@ func Command(contextType string, backend compose.Service) *cobra.Command {
 				return cmd.Help()
 			}
 			_ = cmd.Help()
-			return fmt.Errorf("unknown docker command: %q", "compose "+args[0])
+			return dockercli.StatusError{
+				StatusCode: metrics.CommandSyntaxFailure.ExitCode,
+				Status:     fmt.Sprintf("unknown docker command: %q", "compose "+args[0]),
+			}
 		},
 		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
 			parent := cmd.Root()

+ 3 - 3
cli/cmd/compose/convert.go

@@ -58,7 +58,7 @@ func convertCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 		Aliases: []string{"config"},
 		Use:     "convert SERVICES",
 		Short:   "Converts the compose file to platform's canonical format",
-		RunE: func(cmd *cobra.Command, args []string) error {
+		RunE: Adapt(func(ctx context.Context, args []string) error {
 			if opts.quiet {
 				devnull, err := os.Open(os.DevNull)
 				if err != nil {
@@ -79,8 +79,8 @@ func convertCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 				return runProfiles(opts, args)
 			}
 
-			return runConvert(cmd.Context(), backend, opts, args)
-		},
+			return runConvert(ctx, backend, opts, args)
+		}),
 	}
 	flags := cmd.Flags()
 	flags.StringVar(&opts.Format, "format", "yaml", "Format the output. Values: [yaml | json]")

+ 4 - 3
cli/cmd/compose/create.go

@@ -17,6 +17,7 @@
 package compose
 
 import (
+	"context"
 	"fmt"
 
 	"github.com/spf13/cobra"
@@ -37,14 +38,14 @@ func createCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "create [SERVICE...]",
 		Short: "Creates containers for a service.",
-		RunE: func(cmd *cobra.Command, args []string) error {
+		RunE: Adapt(func(ctx context.Context, args []string) error {
 			if opts.Build && opts.noBuild {
 				return fmt.Errorf("--build and --no-build are incompatible")
 			}
 			if opts.forceRecreate && opts.noRecreate {
 				return fmt.Errorf("--force-recreate and --no-recreate are incompatible")
 			}
-			return runCreateStart(cmd.Context(), backend, upOptions{
+			return runCreateStart(ctx, backend, upOptions{
 				composeOptions: &composeOptions{
 					projectOptions: p,
 					Build:          opts.Build,
@@ -54,7 +55,7 @@ func createCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 				forceRecreate: opts.forceRecreate,
 				noRecreate:    opts.noRecreate,
 			}, args)
-		},
+		}),
 	}
 	flags := cmd.Flags()
 	flags.BoolVar(&opts.Build, "build", false, "Build images before starting containers.")

+ 5 - 3
cli/cmd/compose/down.go

@@ -45,15 +45,17 @@ func downCommand(p *projectOptions, contextType string, backend compose.Service)
 	downCmd := &cobra.Command{
 		Use:   "down",
 		Short: "Stop and remove containers, networks",
-		RunE: func(cmd *cobra.Command, args []string) error {
+		PreRun: func(cmd *cobra.Command, args []string) {
 			opts.timeChanged = cmd.Flags().Changed("timeout")
+		},
+		RunE: Adapt(func(ctx context.Context, args []string) error {
 			if opts.images != "" {
 				if opts.images != "all" && opts.images != "local" {
 					return fmt.Errorf("invalid value for --rmi: %q", opts.images)
 				}
 			}
-			return runDown(cmd.Context(), backend, opts)
-		},
+			return runDown(ctx, backend, opts)
+		}),
 	}
 	flags := downCmd.Flags()
 	flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")

+ 3 - 3
cli/cmd/compose/events.go

@@ -40,9 +40,9 @@ func eventsCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "events [options] [--] [SERVICE...]",
 		Short: "Receive real time events from containers.",
-		RunE: func(cmd *cobra.Command, args []string) error {
-			return runEvents(cmd.Context(), backend, opts, args)
-		},
+		RunE: Adapt(func(ctx context.Context, args []string) error {
+			return runEvents(ctx, backend, opts, args)
+		}),
 	}
 
 	cmd.Flags().BoolVar(&opts.json, "json", false, "Output events as a stream of json objects")

+ 3 - 3
cli/cmd/compose/exec.go

@@ -52,13 +52,13 @@ func execCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 		Use:   "exec [options] [-e KEY=VAL...] [--] SERVICE COMMAND [ARGS...]",
 		Short: "Execute a command in a running container.",
 		Args:  cobra.MinimumNArgs(2),
-		RunE: func(cmd *cobra.Command, args []string) error {
+		RunE: Adapt(func(ctx context.Context, args []string) error {
 			if len(args) > 1 {
 				opts.command = args[1:]
 			}
 			opts.service = args[0]
-			return runExec(cmd.Context(), backend, opts)
-		},
+			return runExec(ctx, backend, opts)
+		}),
 	}
 
 	runCmd.Flags().BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: Run command in the background.")

+ 3 - 3
cli/cmd/compose/images.go

@@ -46,9 +46,9 @@ func imagesCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 	imgCmd := &cobra.Command{
 		Use:   "images [SERVICE...]",
 		Short: "List images used by the created containers",
-		RunE: func(cmd *cobra.Command, args []string) error {
-			return runImages(cmd.Context(), backend, opts, args)
-		},
+		RunE: Adapt(func(ctx context.Context, args []string) error {
+			return runImages(ctx, backend, opts, args)
+		}),
 	}
 	imgCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
 	return imgCmd

+ 3 - 3
cli/cmd/compose/kill.go

@@ -36,9 +36,9 @@ func killCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "kill [options] [SERVICE...]",
 		Short: "Force stop service containers.",
-		RunE: func(cmd *cobra.Command, args []string) error {
-			return runKill(cmd.Context(), backend, opts, args)
-		},
+		RunE: Adapt(func(ctx context.Context, args []string) error {
+			return runKill(ctx, backend, opts, args)
+		}),
 	}
 
 	flags := cmd.Flags()

+ 3 - 3
cli/cmd/compose/list.go

@@ -43,9 +43,9 @@ func listCommand(contextType string, backend compose.Service) *cobra.Command {
 	lsCmd := &cobra.Command{
 		Use:   "ls",
 		Short: "List running compose projects",
-		RunE: func(cmd *cobra.Command, args []string) error {
-			return runList(cmd.Context(), backend, opts)
-		},
+		RunE: Adapt(func(ctx context.Context, args []string) error {
+			return runList(ctx, backend, opts)
+		}),
 	}
 	lsCmd.Flags().StringVar(&opts.Format, "format", "pretty", "Format the output. Values: [pretty | json].")
 	lsCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs.")

+ 3 - 3
cli/cmd/compose/logs.go

@@ -44,9 +44,9 @@ func logsCommand(p *projectOptions, contextType string, backend compose.Service)
 	logsCmd := &cobra.Command{
 		Use:   "logs [service...]",
 		Short: "View output from containers",
-		RunE: func(cmd *cobra.Command, args []string) error {
-			return runLogs(cmd.Context(), backend, opts, args)
-		},
+		RunE: Adapt(func(ctx context.Context, args []string) error {
+			return runLogs(ctx, backend, opts, args)
+		}),
 	}
 	flags := logsCmd.Flags()
 	flags.BoolVarP(&opts.follow, "follow", "f", false, "Follow log output.")

+ 6 - 6
cli/cmd/compose/pause.go

@@ -36,9 +36,9 @@ func pauseCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "pause [SERVICE...]",
 		Short: "pause services",
-		RunE: func(cmd *cobra.Command, args []string) error {
-			return runPause(cmd.Context(), backend, opts, args)
-		},
+		RunE: Adapt(func(ctx context.Context, args []string) error {
+			return runPause(ctx, backend, opts, args)
+		}),
 	}
 	return cmd
 }
@@ -68,9 +68,9 @@ func unpauseCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "unpause [SERVICE...]",
 		Short: "unpause services",
-		RunE: func(cmd *cobra.Command, args []string) error {
-			return runUnPause(cmd.Context(), backend, opts, args)
-		},
+		RunE: Adapt(func(ctx context.Context, args []string) error {
+			return runUnPause(ctx, backend, opts, args)
+		}),
 	}
 	return cmd
 }

+ 3 - 3
cli/cmd/compose/port.go

@@ -40,13 +40,13 @@ func portCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 		Use:   "port [options] [--] SERVICE PRIVATE_PORT",
 		Short: "Print the public port for a port binding.",
 		Args:  cobra.MinimumNArgs(2),
-		RunE: func(cmd *cobra.Command, args []string) error {
+		RunE: Adapt(func(ctx context.Context, args []string) error {
 			port, err := strconv.Atoi(args[1])
 			if err != nil {
 				return err
 			}
-			return runPort(cmd.Context(), backend, opts, args[0], port)
-		},
+			return runPort(ctx, backend, opts, args[0], port)
+		}),
 	}
 	cmd.Flags().StringVar(&opts.protocol, "protocol", "tcp", "tcp or udp")
 	cmd.Flags().IntVar(&opts.index, "index", 1, "index of the container if service has multiple replicas")

+ 3 - 3
cli/cmd/compose/ps.go

@@ -46,9 +46,9 @@ func psCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 	psCmd := &cobra.Command{
 		Use:   "ps",
 		Short: "List containers",
-		RunE: func(cmd *cobra.Command, args []string) error {
-			return runPs(cmd.Context(), backend, opts)
-		},
+		RunE: Adapt(func(ctx context.Context, args []string) error {
+			return runPs(ctx, backend, opts)
+		}),
 	}
 	psCmd.Flags().StringVar(&opts.Format, "format", "pretty", "Format the output. Values: [pretty | json].")
 	psCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")

+ 3 - 3
cli/cmd/compose/pull.go

@@ -46,12 +46,12 @@ func pullCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "pull [SERVICE...]",
 		Short: "Pull service images",
-		RunE: func(cmd *cobra.Command, args []string) error {
+		RunE: Adapt(func(ctx context.Context, args []string) error {
 			if opts.noParallel {
 				fmt.Fprint(os.Stderr, aec.Apply("option '--no-parallel' is DEPRECATED and will be ignored.\n", aec.RedF))
 			}
-			return runPull(cmd.Context(), backend, opts, args)
-		},
+			return runPull(ctx, backend, opts, args)
+		}),
 	}
 	flags := cmd.Flags()
 	flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Pull without printing progress information")

+ 3 - 3
cli/cmd/compose/push.go

@@ -39,9 +39,9 @@ func pushCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 	pushCmd := &cobra.Command{
 		Use:   "push [SERVICE...]",
 		Short: "Push service images",
-		RunE: func(cmd *cobra.Command, args []string) error {
-			return runPush(cmd.Context(), backend, opts, args)
-		},
+		RunE: Adapt(func(ctx context.Context, args []string) error {
+			return runPush(ctx, backend, opts, args)
+		}),
 	}
 	pushCmd.Flags().BoolVar(&opts.Ignorefailures, "ignore-push-failures", false, "Push what it can and ignores images with push failures")
 

+ 3 - 3
cli/cmd/compose/remove.go

@@ -48,9 +48,9 @@ By default, anonymous volumes attached to containers will not be removed. You
 can override this with -v. To list all volumes, use "docker volume ls".
 
 Any data which is not in a volume will be lost.`,
-		RunE: func(cmd *cobra.Command, args []string) error {
-			return runRemove(cmd.Context(), backend, opts, args)
-		},
+		RunE: Adapt(func(ctx context.Context, args []string) error {
+			return runRemove(ctx, backend, opts, args)
+		}),
 	}
 	f := cmd.Flags()
 	f.BoolVarP(&opts.force, "force", "f", false, "Don't ask to confirm removal")

+ 3 - 3
cli/cmd/compose/restart.go

@@ -38,9 +38,9 @@ func restartCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 	restartCmd := &cobra.Command{
 		Use:   "restart",
 		Short: "Restart containers",
-		RunE: func(cmd *cobra.Command, args []string) error {
-			return runRestart(cmd.Context(), backend, opts, args)
-		},
+		RunE: Adapt(func(ctx context.Context, args []string) error {
+			return runRestart(ctx, backend, opts, args)
+		}),
 	}
 	flags := restartCmd.Flags()
 	flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")

+ 3 - 3
cli/cmd/compose/run.go

@@ -109,7 +109,7 @@ func runCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 		Use:   "run [options] [-v VOLUME...] [-p PORT...] [-e KEY=VAL...] [-l KEY=VALUE...] SERVICE [COMMAND] [ARGS...]",
 		Short: "Run a one-off command on a service.",
 		Args:  cobra.MinimumNArgs(1),
-		RunE: func(cmd *cobra.Command, args []string) error {
+		RunE: Adapt(func(ctx context.Context, args []string) error {
 			if len(args) > 1 {
 				opts.Command = args[1:]
 			}
@@ -117,8 +117,8 @@ func runCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 			if len(opts.publish) > 0 && opts.servicePorts {
 				return fmt.Errorf("--service-ports and --publish are incompatible")
 			}
-			return runRun(cmd.Context(), backend, opts)
-		},
+			return runRun(ctx, backend, opts)
+		}),
 	}
 	flags := cmd.Flags()
 	flags.BoolVarP(&opts.Detach, "detach", "d", false, "Run container in background and print container ID")

+ 3 - 3
cli/cmd/compose/start.go

@@ -36,9 +36,9 @@ func startCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 	startCmd := &cobra.Command{
 		Use:   "start [SERVICE...]",
 		Short: "Start services",
-		RunE: func(cmd *cobra.Command, args []string) error {
-			return runStart(cmd.Context(), backend, opts, args)
-		},
+		RunE: Adapt(func(ctx context.Context, args []string) error {
+			return runStart(ctx, backend, opts, args)
+		}),
 	}
 	return startCmd
 }

+ 4 - 2
cli/cmd/compose/stop.go

@@ -39,10 +39,12 @@ func stopCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "stop [SERVICE...]",
 		Short: "Stop services",
-		RunE: func(cmd *cobra.Command, args []string) error {
+		PreRun: func(cmd *cobra.Command, args []string) {
 			opts.timeChanged = cmd.Flags().Changed("timeout")
-			return runStop(cmd.Context(), backend, opts, args)
 		},
+		RunE: Adapt(func(ctx context.Context, args []string) error {
+			return runStop(ctx, backend, opts, args)
+		}),
 	}
 	flags := cmd.Flags()
 	flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")

+ 3 - 3
cli/cmd/compose/top.go

@@ -41,9 +41,9 @@ func topCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 	topCmd := &cobra.Command{
 		Use:   "top",
 		Short: "Display the running processes",
-		RunE: func(cmd *cobra.Command, args []string) error {
-			return runTop(cmd.Context(), backend, opts, args)
-		},
+		RunE: Adapt(func(ctx context.Context, args []string) error {
+			return runTop(ctx, backend, opts, args)
+		}),
 	}
 	return topCmd
 }

+ 6 - 4
cli/cmd/compose/up.go

@@ -148,8 +148,10 @@ func upCommand(p *projectOptions, contextType string, backend compose.Service) *
 	upCmd := &cobra.Command{
 		Use:   "up [SERVICE...]",
 		Short: "Create and start containers",
-		RunE: func(cmd *cobra.Command, args []string) error {
+		PreRun: func(cmd *cobra.Command, args []string) {
 			opts.timeChanged = cmd.Flags().Changed("timeout")
+		},
+		RunE: Adapt(func(ctx context.Context, args []string) error {
 			switch contextType {
 			case store.LocalContextType, store.DefaultContextType, store.EcsLocalSimulationContextType:
 				if opts.exitCodeFrom != "" {
@@ -167,11 +169,11 @@ func upCommand(p *projectOptions, contextType string, backend compose.Service) *
 				if opts.recreateDeps && opts.noRecreate {
 					return fmt.Errorf("--always-recreate-deps and --no-recreate are incompatible")
 				}
-				return runCreateStart(cmd.Context(), backend, opts, args)
+				return runCreateStart(ctx, backend, opts, args)
 			default:
-				return runUp(cmd.Context(), backend, opts, args)
+				return runUp(ctx, backend, opts, args)
 			}
-		},
+		}),
 	}
 	flags := upCmd.Flags()
 	flags.StringArrayVarP(&opts.Environment, "environment", "e", []string{}, "Environment variables")

+ 3 - 3
cli/main.go

@@ -218,7 +218,7 @@ func main() {
 
 	root.AddCommand(
 		run.Command(ctype),
-		compose.Command(ctype, service.ComposeService()),
+		compose.RootCommand(ctype, service.ComposeService()),
 		volume.Command(ctype),
 	)
 
@@ -294,7 +294,7 @@ func exit(ctx string, err error, ctype string) {
 
 	if errors.Is(err, errdefs.ErrNotImplemented) {
 		name := metrics.GetCommand(os.Args[1:])
-		fmt.Fprintf(os.Stderr, "Command %q not available in current context (%s)\n", name, ctx)
+		fmt.Fprintf(os.Stderr, "RootCommand %q not available in current context (%s)\n", name, ctx)
 
 		os.Exit(1)
 	}
@@ -314,7 +314,7 @@ func checkIfUnknownCommandExistInDefaultContext(err error, currentContext string
 		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)
+			fmt.Fprintf(os.Stderr, "RootCommand %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:], metrics.FailureStatus)
 			os.Exit(1)
 		}

+ 1 - 1
docs/yaml/main/generate.go

@@ -35,7 +35,7 @@ const descriptionSourcePath = "docs/reference/"
 
 func generateCliYaml(opts *options) error {
 	cmd := &cobra.Command{Use: "docker"}
-	cmd.AddCommand(compose.Command("local", nil))
+	cmd.AddCommand(compose.RootCommand("local", nil))
 	disableFlagsInUseLine(cmd)
 	source := filepath.Join(opts.source, descriptionSourcePath)
 	if err := loadLongDescription(cmd, source); err != nil {

+ 11 - 3
main.go

@@ -19,14 +19,16 @@ package main
 import (
 	"strings"
 
-	"github.com/spf13/cobra"
-
+	dockercli "github.com/docker/cli/cli"
 	"github.com/docker/cli/cli-plugins/manager"
 	"github.com/docker/cli/cli-plugins/plugin"
 	"github.com/docker/cli/cli/command"
+	"github.com/spf13/cobra"
+
 	api "github.com/docker/compose-cli/api/compose"
 	"github.com/docker/compose-cli/api/context/store"
 	"github.com/docker/compose-cli/cli/cmd/compose"
+	"github.com/docker/compose-cli/cli/metrics"
 	"github.com/docker/compose-cli/internal"
 	impl "github.com/docker/compose-cli/local/compose"
 )
@@ -36,7 +38,7 @@ func main() {
 		lazyInit := api.ServiceDelegator{
 			Delegate: api.NoImpl{},
 		}
-		cmd := compose.Command(store.DefaultContextType, &lazyInit)
+		cmd := compose.RootCommand(store.DefaultContextType, &lazyInit)
 		originalPreRun := cmd.PersistentPreRunE
 		cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
 			if err := plugin.PersistentPreRunE(cmd, args); err != nil {
@@ -48,6 +50,12 @@ func main() {
 			}
 			return nil
 		}
+		cmd.SetFlagErrorFunc(func(c *cobra.Command, err error) error {
+			return dockercli.StatusError{
+				StatusCode: metrics.CommandSyntaxFailure.ExitCode,
+				Status:     err.Error(),
+			}
+		})
 		return cmd
 	},
 		manager.Metadata{