浏览代码

Exit with non-zero code on first argument parsing error

Ignoring return value of ParseFlags leads to exit code 0 if bad
arguments are supplied. This patch makes sure that subcommands exit
with non-zero code in such a case.

Signed-off-by: Michal Minar <miminar@redhat.com>
Michal Minar 10 年之前
父节点
当前提交
8a785792cd
共有 3 个文件被更改,包括 85 次插入142 次删除
  1. 8 2
      api/client/cli.go
  2. 73 139
      api/client/commands.go
  3. 4 1
      utils/flags.go

+ 8 - 2
api/client/cli.go

@@ -83,8 +83,14 @@ func (cli *DockerCli) Cmd(args ...string) error {
 	return cli.CmdHelp()
 }
 
-func (cli *DockerCli) Subcmd(name, signature, description string) *flag.FlagSet {
-	flags := flag.NewFlagSet(name, flag.ContinueOnError)
+func (cli *DockerCli) Subcmd(name, signature, description string, exitOnError bool) *flag.FlagSet {
+	var errorHandling flag.ErrorHandling
+	if exitOnError {
+		errorHandling = flag.ExitOnError
+	} else {
+		errorHandling = flag.ContinueOnError
+	}
+	flags := flag.NewFlagSet(name, errorHandling)
 	flags.Usage = func() {
 		options := ""
 		if flags.FlagCountUndeprecated() > 0 {

+ 73 - 139
api/client/commands.go

@@ -77,7 +77,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
 }
 
 func (cli *DockerCli) CmdBuild(args ...string) error {
-	cmd := cli.Subcmd("build", "PATH | URL | -", "Build a new image from the source code at PATH")
+	cmd := cli.Subcmd("build", "PATH | URL | -", "Build a new image from the source code at PATH", true)
 	tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) to be applied to the resulting image in case of success")
 	suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers")
 	noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image")
@@ -86,9 +86,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
 	pull := cmd.Bool([]string{"-pull"}, false, "Always attempt to pull a newer version of the image")
 	cmd.Require(flag.Exact, 1)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	var (
 		context  archive.Archive
@@ -246,7 +244,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
 
 // 'docker login': login / register a user to registry service.
 func (cli *DockerCli) CmdLogin(args ...string) error {
-	cmd := cli.Subcmd("login", "[SERVER]", "Register or log in to a Docker registry server, if no server is specified \""+registry.IndexServerAddress()+"\" is the default.")
+	cmd := cli.Subcmd("login", "[SERVER]", "Register or log in to a Docker registry server, if no server is specified \""+registry.IndexServerAddress()+"\" is the default.", true)
 	cmd.Require(flag.Max, 1)
 
 	var username, password, email string
@@ -255,9 +253,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
 	cmd.StringVar(&password, []string{"p", "-password"}, "", "Password")
 	cmd.StringVar(&email, []string{"e", "-email"}, "", "Email")
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	serverAddress := registry.IndexServerAddress()
 	if len(cmd.Args()) > 0 {
@@ -364,12 +360,10 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
 
 // log out from a Docker registry
 func (cli *DockerCli) CmdLogout(args ...string) error {
-	cmd := cli.Subcmd("logout", "[SERVER]", "Log out from a Docker registry, if no server is specified \""+registry.IndexServerAddress()+"\" is the default.")
+	cmd := cli.Subcmd("logout", "[SERVER]", "Log out from a Docker registry, if no server is specified \""+registry.IndexServerAddress()+"\" is the default.", true)
 	cmd.Require(flag.Max, 1)
 
-	if err := utils.ParseFlags(cmd, args, false); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, false)
 	serverAddress := registry.IndexServerAddress()
 	if len(cmd.Args()) > 0 {
 		serverAddress = cmd.Arg(0)
@@ -391,12 +385,10 @@ func (cli *DockerCli) CmdLogout(args ...string) error {
 
 // 'docker wait': block until a container stops
 func (cli *DockerCli) CmdWait(args ...string) error {
-	cmd := cli.Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.")
+	cmd := cli.Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.", true)
 	cmd.Require(flag.Min, 1)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	var encounteredError error
 	for _, name := range cmd.Args() {
@@ -413,12 +405,10 @@ func (cli *DockerCli) CmdWait(args ...string) error {
 
 // 'docker version': show version information
 func (cli *DockerCli) CmdVersion(args ...string) error {
-	cmd := cli.Subcmd("version", "", "Show the Docker version information.")
+	cmd := cli.Subcmd("version", "", "Show the Docker version information.", true)
 	cmd.Require(flag.Exact, 0)
 
-	if err := utils.ParseFlags(cmd, args, false); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, false)
 
 	if dockerversion.VERSION != "" {
 		fmt.Fprintf(cli.out, "Client version: %s\n", dockerversion.VERSION)
@@ -457,11 +447,9 @@ func (cli *DockerCli) CmdVersion(args ...string) error {
 
 // 'docker info': display system-wide information.
 func (cli *DockerCli) CmdInfo(args ...string) error {
-	cmd := cli.Subcmd("info", "", "Display system-wide information")
+	cmd := cli.Subcmd("info", "", "Display system-wide information", true)
 	cmd.Require(flag.Exact, 0)
-	if err := utils.ParseFlags(cmd, args, false); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, false)
 
 	body, _, err := readBody(cli.call("GET", "/info", nil, false))
 	if err != nil {
@@ -573,13 +561,11 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
 }
 
 func (cli *DockerCli) CmdStop(args ...string) error {
-	cmd := cli.Subcmd("stop", "CONTAINER [CONTAINER...]", "Stop a running container by sending SIGTERM and then SIGKILL after a grace period")
+	cmd := cli.Subcmd("stop", "CONTAINER [CONTAINER...]", "Stop a running container by sending SIGTERM and then SIGKILL after a grace period", true)
 	nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Number of seconds to wait for the container to stop before killing it. Default is 10 seconds.")
 	cmd.Require(flag.Min, 1)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	v := url.Values{}
 	v.Set("t", strconv.Itoa(*nSeconds))
@@ -598,13 +584,11 @@ func (cli *DockerCli) CmdStop(args ...string) error {
 }
 
 func (cli *DockerCli) CmdRestart(args ...string) error {
-	cmd := cli.Subcmd("restart", "CONTAINER [CONTAINER...]", "Restart a running container")
+	cmd := cli.Subcmd("restart", "CONTAINER [CONTAINER...]", "Restart a running container", true)
 	nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Number of seconds to try to stop for before killing the container. Once killed it will then be restarted. Default is 10 seconds.")
 	cmd.Require(flag.Min, 1)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	v := url.Values{}
 	v.Set("t", strconv.Itoa(*nSeconds))
@@ -653,15 +637,13 @@ func (cli *DockerCli) CmdStart(args ...string) error {
 		cErr chan error
 		tty  bool
 
-		cmd       = cli.Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container")
+		cmd       = cli.Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container", true)
 		attach    = cmd.Bool([]string{"a", "-attach"}, false, "Attach container's STDOUT and STDERR and forward all signals to the process")
 		openStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN")
 	)
 
 	cmd.Require(flag.Min, 1)
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	hijacked := make(chan io.Closer)
 
@@ -763,11 +745,9 @@ func (cli *DockerCli) CmdStart(args ...string) error {
 }
 
 func (cli *DockerCli) CmdUnpause(args ...string) error {
-	cmd := cli.Subcmd("unpause", "CONTAINER", "Unpause all processes within a container")
+	cmd := cli.Subcmd("unpause", "CONTAINER", "Unpause all processes within a container", true)
 	cmd.Require(flag.Exact, 1)
-	if err := utils.ParseFlags(cmd, args, false); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, false)
 
 	var encounteredError error
 	for _, name := range cmd.Args() {
@@ -782,11 +762,9 @@ func (cli *DockerCli) CmdUnpause(args ...string) error {
 }
 
 func (cli *DockerCli) CmdPause(args ...string) error {
-	cmd := cli.Subcmd("pause", "CONTAINER", "Pause all processes within a container")
+	cmd := cli.Subcmd("pause", "CONTAINER", "Pause all processes within a container", true)
 	cmd.Require(flag.Exact, 1)
-	if err := utils.ParseFlags(cmd, args, false); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, false)
 
 	var encounteredError error
 	for _, name := range cmd.Args() {
@@ -801,13 +779,11 @@ func (cli *DockerCli) CmdPause(args ...string) error {
 }
 
 func (cli *DockerCli) CmdInspect(args ...string) error {
-	cmd := cli.Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container or image")
+	cmd := cli.Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container or image", true)
 	tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template.")
 	cmd.Require(flag.Min, 1)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	var tmpl *template.Template
 	if *tmplStr != "" {
@@ -879,12 +855,10 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
 }
 
 func (cli *DockerCli) CmdTop(args ...string) error {
-	cmd := cli.Subcmd("top", "CONTAINER [ps OPTIONS]", "Display the running processes of a container")
+	cmd := cli.Subcmd("top", "CONTAINER [ps OPTIONS]", "Display the running processes of a container", true)
 	cmd.Require(flag.Min, 1)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	val := url.Values{}
 	if cmd.NArg() > 1 {
@@ -913,11 +887,9 @@ func (cli *DockerCli) CmdTop(args ...string) error {
 }
 
 func (cli *DockerCli) CmdPort(args ...string) error {
-	cmd := cli.Subcmd("port", "CONTAINER [PRIVATE_PORT[/PROTO]]", "List port mappings for the CONTAINER, or lookup the public-facing port that is NAT-ed to the PRIVATE_PORT")
+	cmd := cli.Subcmd("port", "CONTAINER [PRIVATE_PORT[/PROTO]]", "List port mappings for the CONTAINER, or lookup the public-facing port that is NAT-ed to the PRIVATE_PORT", true)
 	cmd.Require(flag.Min, 1)
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false)
 	if err != nil {
@@ -966,15 +938,13 @@ func (cli *DockerCli) CmdPort(args ...string) error {
 // 'docker rmi IMAGE' removes all images with the name IMAGE
 func (cli *DockerCli) CmdRmi(args ...string) error {
 	var (
-		cmd     = cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images")
+		cmd     = cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images", true)
 		force   = cmd.Bool([]string{"f", "-force"}, false, "Force removal of the image")
 		noprune = cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents")
 	)
 	cmd.Require(flag.Min, 1)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	v := url.Values{}
 	if *force {
@@ -1010,14 +980,12 @@ func (cli *DockerCli) CmdRmi(args ...string) error {
 }
 
 func (cli *DockerCli) CmdHistory(args ...string) error {
-	cmd := cli.Subcmd("history", "IMAGE", "Show the history of an image")
+	cmd := cli.Subcmd("history", "IMAGE", "Show the history of an image", true)
 	quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
 	noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
 	cmd.Require(flag.Exact, 1)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	body, _, err := readBody(cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, false))
 	if err != nil {
@@ -1064,15 +1032,13 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
 }
 
 func (cli *DockerCli) CmdRm(args ...string) error {
-	cmd := cli.Subcmd("rm", "CONTAINER [CONTAINER...]", "Remove one or more containers")
+	cmd := cli.Subcmd("rm", "CONTAINER [CONTAINER...]", "Remove one or more containers", true)
 	v := cmd.Bool([]string{"v", "-volumes"}, false, "Remove the volumes associated with the container")
 	link := cmd.Bool([]string{"l", "#link", "-link"}, false, "Remove the specified link and not the underlying container")
 	force := cmd.Bool([]string{"f", "-force"}, false, "Force the removal of a running container (uses SIGKILL)")
 	cmd.Require(flag.Min, 1)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	val := url.Values{}
 	if *v {
@@ -1101,13 +1067,11 @@ func (cli *DockerCli) CmdRm(args ...string) error {
 
 // 'docker kill NAME' kills a running container
 func (cli *DockerCli) CmdKill(args ...string) error {
-	cmd := cli.Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container using SIGKILL or a specified signal")
+	cmd := cli.Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container using SIGKILL or a specified signal", true)
 	signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container")
 	cmd.Require(flag.Min, 1)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	var encounteredError error
 	for _, name := range cmd.Args() {
@@ -1122,12 +1086,10 @@ func (cli *DockerCli) CmdKill(args ...string) error {
 }
 
 func (cli *DockerCli) CmdImport(args ...string) error {
-	cmd := cli.Subcmd("import", "URL|- [REPOSITORY[:TAG]]", "Create an empty filesystem image and import the contents of the tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then optionally tag it.")
+	cmd := cli.Subcmd("import", "URL|- [REPOSITORY[:TAG]]", "Create an empty filesystem image and import the contents of the tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then optionally tag it.", true)
 	cmd.Require(flag.Min, 1)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	var (
 		v          = url.Values{}
@@ -1161,12 +1123,10 @@ func (cli *DockerCli) CmdImport(args ...string) error {
 }
 
 func (cli *DockerCli) CmdPush(args ...string) error {
-	cmd := cli.Subcmd("push", "NAME[:TAG]", "Push an image or a repository to the registry")
+	cmd := cli.Subcmd("push", "NAME[:TAG]", "Push an image or a repository to the registry", true)
 	cmd.Require(flag.Exact, 1)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	name := cmd.Arg(0)
 
@@ -1224,13 +1184,11 @@ func (cli *DockerCli) CmdPush(args ...string) error {
 }
 
 func (cli *DockerCli) CmdPull(args ...string) error {
-	cmd := cli.Subcmd("pull", "NAME[:TAG]", "Pull an image or a repository from the registry")
+	cmd := cli.Subcmd("pull", "NAME[:TAG]", "Pull an image or a repository from the registry", true)
 	allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository")
 	cmd.Require(flag.Exact, 1)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	var (
 		v         = url.Values{}
@@ -1288,7 +1246,7 @@ func (cli *DockerCli) CmdPull(args ...string) error {
 }
 
 func (cli *DockerCli) CmdImages(args ...string) error {
-	cmd := cli.Subcmd("images", "[REPOSITORY]", "List images")
+	cmd := cli.Subcmd("images", "[REPOSITORY]", "List images", true)
 	quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
 	all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (by default filter out the intermediate image layers)")
 	noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
@@ -1300,9 +1258,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
 	cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values (i.e. 'dangling=true')")
 	cmd.Require(flag.Max, 1)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	// Consolidate all filter flags, and sanity check them early.
 	// They'll get process in the daemon/server.
@@ -1528,7 +1484,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
 		psFilterArgs = filters.Args{}
 		v            = url.Values{}
 
-		cmd      = cli.Subcmd("ps", "", "List containers")
+		cmd      = cli.Subcmd("ps", "", "List containers", true)
 		quiet    = cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
 		size     = cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes")
 		all      = cmd.Bool([]string{"a", "-all"}, false, "Show all containers. Only running containers are shown by default.")
@@ -1543,9 +1499,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
 
 	cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values. Valid filters:\nexited=<int> - containers with exit code of <int>\nstatus=(restarting|running|paused|exited)")
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 	if *last == -1 && *nLatest {
 		*last = 1
 	}
@@ -1680,7 +1634,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
 }
 
 func (cli *DockerCli) CmdCommit(args ...string) error {
-	cmd := cli.Subcmd("commit", "CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes")
+	cmd := cli.Subcmd("commit", "CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes", true)
 	flPause := cmd.Bool([]string{"p", "-pause"}, true, "Pause container during commit")
 	flComment := cmd.String([]string{"m", "-message"}, "", "Commit message")
 	flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (e.g., \"John Hannibal Smith <hannibal@a-team.com>\")")
@@ -1688,9 +1642,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
 	flConfig := cmd.String([]string{"#run", "#-run"}, "", "This option is deprecated and will be removed in a future version in favor of inline Dockerfile-compatible commands")
 	cmd.Require(flag.Max, 2)
 	cmd.Require(flag.Min, 1)
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	var (
 		name            = cmd.Arg(0)
@@ -1738,16 +1690,14 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
 }
 
 func (cli *DockerCli) CmdEvents(args ...string) error {
-	cmd := cli.Subcmd("events", "", "Get real time events from the server")
+	cmd := cli.Subcmd("events", "", "Get real time events from the server", true)
 	since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp")
 	until := cmd.String([]string{"-until"}, "", "Stream events until this timestamp")
 	flFilter := opts.NewListOpts(nil)
 	cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values (i.e. 'event=stop')")
 	cmd.Require(flag.Exact, 0)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	var (
 		v               = url.Values{}
@@ -1795,12 +1745,10 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
 }
 
 func (cli *DockerCli) CmdExport(args ...string) error {
-	cmd := cli.Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive to STDOUT")
+	cmd := cli.Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive to STDOUT", true)
 	cmd.Require(flag.Exact, 1)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	if err := cli.stream("GET", "/containers/"+cmd.Arg(0)+"/export", nil, cli.out, nil); err != nil {
 		return err
@@ -1809,12 +1757,10 @@ func (cli *DockerCli) CmdExport(args ...string) error {
 }
 
 func (cli *DockerCli) CmdDiff(args ...string) error {
-	cmd := cli.Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem")
+	cmd := cli.Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem", true)
 	cmd.Require(flag.Exact, 1)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, false))
 
@@ -1843,16 +1789,14 @@ func (cli *DockerCli) CmdDiff(args ...string) error {
 
 func (cli *DockerCli) CmdLogs(args ...string) error {
 	var (
-		cmd    = cli.Subcmd("logs", "CONTAINER", "Fetch the logs of a container")
+		cmd    = cli.Subcmd("logs", "CONTAINER", "Fetch the logs of a container", true)
 		follow = cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
 		times  = cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps")
 		tail   = cmd.String([]string{"-tail"}, "all", "Output the specified number of lines at the end of logs (defaults to all logs)")
 	)
 	cmd.Require(flag.Exact, 1)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	name := cmd.Arg(0)
 
@@ -1884,15 +1828,13 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
 
 func (cli *DockerCli) CmdAttach(args ...string) error {
 	var (
-		cmd     = cli.Subcmd("attach", "CONTAINER", "Attach to a running container")
+		cmd     = cli.Subcmd("attach", "CONTAINER", "Attach to a running container", true)
 		noStdin = cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach STDIN")
 		proxy   = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy all received signals to the process (non-TTY mode only). SIGCHLD, SIGKILL, and SIGSTOP are not proxied.")
 	)
 	cmd.Require(flag.Exact, 1)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 	name := cmd.Arg(0)
 
 	stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false)
@@ -1957,16 +1899,14 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
 }
 
 func (cli *DockerCli) CmdSearch(args ...string) error {
-	cmd := cli.Subcmd("search", "TERM", "Search the Docker Hub for images")
+	cmd := cli.Subcmd("search", "TERM", "Search the Docker Hub for images", true)
 	noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
 	trusted := cmd.Bool([]string{"#t", "#trusted", "#-trusted"}, false, "Only show trusted builds")
 	automated := cmd.Bool([]string{"-automated"}, false, "Only show automated builds")
 	stars := cmd.Int([]string{"s", "#stars", "-stars"}, 0, "Only displays with at least x stars")
 	cmd.Require(flag.Exact, 1)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	v := url.Values{}
 	v.Set("term", cmd.Arg(0))
@@ -2010,13 +1950,11 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
 type ports []int
 
 func (cli *DockerCli) CmdTag(args ...string) error {
-	cmd := cli.Subcmd("tag", "IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]", "Tag an image into a repository")
+	cmd := cli.Subcmd("tag", "IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]", "Tag an image into a repository", true)
 	force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force")
 	cmd.Require(flag.Exact, 2)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	var (
 		repository, tag = parsers.ParseRepositoryTag(cmd.Arg(1))
@@ -2177,7 +2115,7 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc
 }
 
 func (cli *DockerCli) CmdCreate(args ...string) error {
-	cmd := cli.Subcmd("create", "IMAGE [COMMAND] [ARG...]", "Create a new container")
+	cmd := cli.Subcmd("create", "IMAGE [COMMAND] [ARG...]", "Create a new container", true)
 
 	// These are flags not stored in Config/HostConfig
 	var (
@@ -2205,7 +2143,7 @@ func (cli *DockerCli) CmdCreate(args ...string) error {
 
 func (cli *DockerCli) CmdRun(args ...string) error {
 	// FIXME: just use runconfig.Parse already
-	cmd := cli.Subcmd("run", "IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
+	cmd := cli.Subcmd("run", "IMAGE [COMMAND] [ARG...]", "Run a command in a new container", true)
 
 	// These are flags not stored in Config/HostConfig
 	var (
@@ -2221,6 +2159,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 	)
 
 	config, hostConfig, cmd, err := runconfig.Parse(cmd, args)
+	// just in case the Parse does not exit
 	if err != nil {
 		return &utils.StatusError{StatusCode: 1}
 	}
@@ -2405,12 +2344,10 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 }
 
 func (cli *DockerCli) CmdCp(args ...string) error {
-	cmd := cli.Subcmd("cp", "CONTAINER:PATH HOSTPATH", "Copy files/folders from the PATH to the HOSTPATH")
+	cmd := cli.Subcmd("cp", "CONTAINER:PATH HOSTPATH", "Copy files/folders from the PATH to the HOSTPATH", true)
 	cmd.Require(flag.Exact, 2)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return nil
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	var copyData engine.Env
 	info := strings.Split(cmd.Arg(0), ":")
@@ -2442,13 +2379,11 @@ func (cli *DockerCli) CmdCp(args ...string) error {
 }
 
 func (cli *DockerCli) CmdSave(args ...string) error {
-	cmd := cli.Subcmd("save", "IMAGE [IMAGE...]", "Save an image(s) to a tar archive (streamed to STDOUT by default)")
+	cmd := cli.Subcmd("save", "IMAGE [IMAGE...]", "Save an image(s) to a tar archive (streamed to STDOUT by default)", true)
 	outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT")
 	cmd.Require(flag.Min, 1)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return err
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	var (
 		output io.Writer = cli.out
@@ -2481,13 +2416,11 @@ func (cli *DockerCli) CmdSave(args ...string) error {
 }
 
 func (cli *DockerCli) CmdLoad(args ...string) error {
-	cmd := cli.Subcmd("load", "", "Load an image from a tar archive on STDIN")
+	cmd := cli.Subcmd("load", "", "Load an image from a tar archive on STDIN", true)
 	infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN")
 	cmd.Require(flag.Exact, 0)
 
-	if err := utils.ParseFlags(cmd, args, true); err != nil {
-		return err
-	}
+	utils.ParseFlags(cmd, args, true)
 
 	var (
 		input io.Reader = cli.in
@@ -2506,9 +2439,10 @@ func (cli *DockerCli) CmdLoad(args ...string) error {
 }
 
 func (cli *DockerCli) CmdExec(args ...string) error {
-	cmd := cli.Subcmd("exec", "CONTAINER COMMAND [ARG...]", "Run a command in a running container")
+	cmd := cli.Subcmd("exec", "CONTAINER COMMAND [ARG...]", "Run a command in a running container", true)
 
 	execConfig, err := runconfig.ParseExec(cmd, args)
+	// just in case the ParseExec does not exit
 	if execConfig.Container == "" || err != nil {
 		return &utils.StatusError{StatusCode: 1}
 	}

+ 4 - 1
utils/flags.go

@@ -8,7 +8,10 @@ import (
 )
 
 // ParseFlags is a utility function that adds a help flag if withHelp is true,
-// calls cmd.Parse(args) and prints a relevant error message if there are incorrect number of arguments.
+// calls cmd.Parse(args) and prints a relevant error message if there are
+// incorrect number of arguments. It returns error only if error handling is
+// set to ContinueOnError and parsing fails. If error handling is set to
+// ExitOnError, it's safe to ignore the return value.
 // TODO: move this to a better package than utils
 func ParseFlags(cmd *flag.FlagSet, args []string, withHelp bool) error {
 	var help *bool