From a2b529ead21e6ab9eafcb1b1d2437c725c43a06a Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Wed, 15 Oct 2014 17:14:12 -0400 Subject: [PATCH 1/7] --help option and help command should print to stdout not stderr --help and help are successful commands so output should not go to error. QE teams have requested this change, also users doing docker help | less or docker run --help | less would expect this to work. Usage statement should only be printed when the user asks for it. Errors should print error message and then suggest the docker COMMAND --help command to see usage information. The current behaviour causes the user to have to search for the error message and sometimes scrolls right off the screen. For example a error on a "docker run" command is very difficult to diagnose. Finally erros should always exit with a non 0 exit code, if the user makes a CLI error. Docker-DCO-1.1-Signed-off-by: Dan Walsh (github: rhatdan) --- api/client/cli.go | 9 +- api/client/commands.go | 248 ++++++++++++++++------ docker/flags.go | 6 +- docs/man/docker-attach.1.md | 4 + docs/man/docker-build.1.md | 4 + docs/man/docker-commit.1.md | 4 + docs/man/docker-cp.1.md | 4 +- docs/man/docker-create.1.md | 4 + docs/man/docker-diff.1.md | 4 +- docs/man/docker-events.1.md | 4 + docs/man/docker-exec.1.md | 4 + docs/man/docker-export.1.md | 4 +- docs/man/docker-history.1.md | 4 + docs/man/docker-images.1.md | 4 + docs/man/docker-import.1.md | 4 +- docs/man/docker-info.1.md | 4 +- docs/man/docker-inspect.1.md | 4 + docs/man/docker-kill.1.md | 4 + docs/man/docker-load.1.md | 4 + docs/man/docker-login.1.md | 4 + docs/man/docker-logs.1.md | 4 + docs/man/docker-port.1.md | 4 +- docs/man/docker-ps.1.md | 4 + docs/man/docker-pull.1.md | 5 +- docs/man/docker-push.1.md | 4 +- docs/man/docker-restart.1.md | 4 + docs/man/docker-rm.1.md | 3 + docs/man/docker-rmi.1.md | 4 + docs/man/docker-run.1.md | 4 + docs/man/docker-save.1.md | 4 + docs/man/docker-search.1.md | 4 + docs/man/docker-start.1.md | 4 + docs/man/docker-stop.1.md | 4 + docs/man/docker-tag.1.md | 1 + docs/man/docker-top.1.md | 4 +- docs/man/docker-wait.1.md | 4 +- docs/man/docker.1.md | 3 + docs/sources/reference/commandline/cli.md | 13 ++ pkg/mflag/flag.go | 45 +++- runconfig/exec.go | 12 +- runconfig/parse.go | 14 +- 41 files changed, 390 insertions(+), 89 deletions(-) diff --git a/api/client/cli.go b/api/client/cli.go index e54eb8056..aec8ebb86 100644 --- a/api/client/cli.go +++ b/api/client/cli.go @@ -75,8 +75,8 @@ func (cli *DockerCli) Cmd(args ...string) error { if len(args) > 0 { method, exists := cli.getMethod(args[0]) if !exists { - fmt.Println("Error: Command not found:", args[0]) - return cli.CmdHelp() + fmt.Fprintf(cli.err, "docker: '%s' is not a docker command. See 'docker --help'.\n", args[0]) + os.Exit(1) } return method(args[1:]...) } @@ -90,9 +90,10 @@ func (cli *DockerCli) Subcmd(name, signature, description string) *flag.FlagSet if flags.FlagCountUndeprecated() > 0 { options = "[OPTIONS] " } - fmt.Fprintf(cli.err, "\nUsage: docker %s %s%s\n\n%s\n\n", name, options, signature, description) + fmt.Fprintf(cli.out, "\nUsage: docker %s %s%s\n\n%s\n\n", name, options, signature, description) + flags.SetOutput(cli.out) flags.PrintDefaults() - os.Exit(2) + os.Exit(0) } return flags } diff --git a/api/client/commands.go b/api/client/commands.go index e666d4320..14bcc8ffa 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -64,6 +64,8 @@ func (cli *DockerCli) CmdHelp(args ...string) error { method, exists := cli.getMethod(args[0]) if !exists { fmt.Fprintf(cli.err, "Error: Command not found: %s\n", args[0]) + fmt.Fprintf(cli.err, "docker: '%s' is not a docker command. See 'docker --help'.\n", args[0]) + os.Exit(1) } else { method("--help") return nil @@ -83,13 +85,18 @@ func (cli *DockerCli) CmdBuild(args ...string) error { rm := cmd.Bool([]string{"#rm", "-rm"}, true, "Remove intermediate containers after a successful build") forceRm := cmd.Bool([]string{"-force-rm"}, false, "Always remove intermediate containers, even after unsuccessful builds") pull := cmd.Bool([]string{"-pull"}, false, "Always attempt to pull a newer version of the image") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() != 1 { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Exact, 1) { + os.Exit(1) + } var ( context archive.Archive @@ -254,10 +261,16 @@ func (cli *DockerCli) CmdLogin(args ...string) error { cmd.StringVar(&username, []string{"u", "-username"}, "", "Username") cmd.StringVar(&password, []string{"p", "-password"}, "", "Password") cmd.StringVar(&email, []string{"e", "-email"}, "", "Email") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + err := cmd.Parse(args) if err != nil { return nil } + if *help { + cmd.Usage() + return nil + } serverAddress := registry.IndexServerAddress() if len(cmd.Args()) > 0 { serverAddress = cmd.Arg(0) @@ -390,13 +403,18 @@ 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.") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() < 1 { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Min, 1) { + os.Exit(1) + } var encounteredError error for _, name := range cmd.Args() { status, err := waitForExit(cli, name) @@ -416,10 +434,8 @@ func (cli *DockerCli) CmdVersion(args ...string) error { if err := cmd.Parse(args); err != nil { return nil } - - if cmd.NArg() > 0 { - cmd.Usage() - return nil + if cmd.BadArgs(flag.Exact, 0) { + os.Exit(1) } if dockerversion.VERSION != "" { fmt.Fprintf(cli.out, "Client version: %s\n", dockerversion.VERSION) @@ -462,9 +478,8 @@ func (cli *DockerCli) CmdInfo(args ...string) error { if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() > 0 { - cmd.Usage() - return nil + if cmd.BadArgs(flag.Exact, 0) { + os.Exit(1) } body, _, err := readBody(cli.call("GET", "/info", nil, false)) @@ -579,13 +594,18 @@ 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") nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Number of seconds to wait for the container to stop before killing it. Default is 10 seconds.") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() < 1 { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Min, 1) { + os.Exit(1) + } v := url.Values{} v.Set("t", strconv.Itoa(*nSeconds)) @@ -606,13 +626,18 @@ func (cli *DockerCli) CmdStop(args ...string) error { func (cli *DockerCli) CmdRestart(args ...string) error { cmd := cli.Subcmd("restart", "CONTAINER [CONTAINER...]", "Restart a running container") 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.") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() < 1 { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Min, 1) { + os.Exit(1) + } v := url.Values{} v.Set("t", strconv.Itoa(*nSeconds)) @@ -664,15 +689,19 @@ func (cli *DockerCli) CmdStart(args ...string) error { cmd = cli.Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container") 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") + help = cmd.Bool([]string{"#help", "-help"}, false, "Print usage") ) if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() < 1 { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Min, 1) { + os.Exit(1) + } hijacked := make(chan io.Closer) @@ -778,10 +807,8 @@ func (cli *DockerCli) CmdUnpause(args ...string) error { if err := cmd.Parse(args); err != nil { return nil } - - if cmd.NArg() != 1 { - cmd.Usage() - return nil + if cmd.BadArgs(flag.Exact, 1) { + os.Exit(1) } var encounteredError error @@ -801,10 +828,8 @@ func (cli *DockerCli) CmdPause(args ...string) error { if err := cmd.Parse(args); err != nil { return nil } - - if cmd.NArg() != 1 { - cmd.Usage() - return nil + if cmd.BadArgs(flag.Exact, 1) { + os.Exit(1) } var encounteredError error @@ -822,13 +847,18 @@ 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") tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template.") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() < 1 { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Min, 1) { + os.Exit(1) + } var tmpl *template.Template if *tmplStr != "" { @@ -901,13 +931,18 @@ 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") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() == 0 { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Min, 1) { + os.Exit(1) + } val := url.Values{} if cmd.NArg() > 1 { val.Set("ps_args", strings.Join(cmd.Args()[1:], " ")) @@ -936,13 +971,17 @@ 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") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() < 1 { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Min, 1) { + os.Exit(1) + } stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false) if err != nil { @@ -995,13 +1034,18 @@ func (cli *DockerCli) CmdRmi(args ...string) error { force = cmd.Bool([]string{"f", "-force"}, false, "Force removal of the image") noprune = cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents") ) + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() < 1 { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Min, 1) { + os.Exit(1) + } v := url.Values{} if *force { @@ -1040,14 +1084,18 @@ func (cli *DockerCli) CmdHistory(args ...string) error { cmd := cli.Subcmd("history", "IMAGE", "Show the history of an image") quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs") noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() != 1 { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Exact, 1) { + os.Exit(1) + } body, _, err := readBody(cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, false)) if err != nil { @@ -1098,14 +1146,18 @@ func (cli *DockerCli) CmdRm(args ...string) error { 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)") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() < 1 { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Min, 1) { + os.Exit(1) + } val := url.Values{} if *v { @@ -1136,14 +1188,18 @@ func (cli *DockerCli) CmdRm(args ...string) error { func (cli *DockerCli) CmdKill(args ...string) error { cmd := cli.Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container using SIGKILL or a specified signal") signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() < 1 { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Min, 1) { + os.Exit(1) + } var encounteredError error for _, name := range cmd.Args() { @@ -1159,15 +1215,18 @@ 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.") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() < 1 { + if *help { cmd.Usage() return nil } - + if cmd.BadArgs(flag.Min, 1) { + os.Exit(1) + } var ( v = url.Values{} src = cmd.Arg(0) @@ -1201,15 +1260,19 @@ 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") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + if err := cmd.Parse(args); err != nil { return nil } - name := cmd.Arg(0) - - if name == "" { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Exact, 1) { + os.Exit(1) + } + name := cmd.Arg(0) cli.LoadConfigFile() @@ -1267,14 +1330,19 @@ 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") allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + if err := cmd.Parse(args); err != nil { return nil } - - if cmd.NArg() != 1 { + if *help { cmd.Usage() return nil } + + if cmd.BadArgs(flag.Exact, 1) { + os.Exit(1) + } var ( v = url.Values{} remote = cmd.Arg(0) @@ -1338,6 +1406,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { // FIXME: --viz and --tree are deprecated. Remove them in a future version. flViz := cmd.Bool([]string{"#v", "#viz", "#-viz"}, false, "Output graph in graphviz format") flTree := cmd.Bool([]string{"#t", "#tree", "#-tree"}, false, "Output graph in tree format") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") flFilter := opts.NewListOpts(nil) cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values (i.e. 'dangling=true')") @@ -1345,10 +1414,13 @@ func (cli *DockerCli) CmdImages(args ...string) error { if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() > 1 { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Max, 1) { + os.Exit(1) + } // Consolidate all filter flags, and sanity check them early. // They'll get process in the daemon/server. @@ -1578,6 +1650,7 @@ func (cli *DockerCli) CmdPs(args ...string) error { 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.") + help = cmd.Bool([]string{"#help", "-help"}, false, "Print usage") noTrunc = cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") nLatest = cmd.Bool([]string{"l", "-latest"}, false, "Show only the latest created container, include non-running ones.") since = cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show only containers created since Id or Name, include non-running ones.") @@ -1591,7 +1664,10 @@ func (cli *DockerCli) CmdPs(args ...string) error { if err := cmd.Parse(args); err != nil { return nil } - + if *help { + cmd.Usage() + return nil + } if *last == -1 && *nLatest { *last = 1 } @@ -1732,20 +1808,28 @@ func (cli *DockerCli) CmdCommit(args ...string) error { flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (e.g., \"John Hannibal Smith \")") // FIXME: --run is deprecated, it will be replaced with inline Dockerfile commands. 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") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") if err := cmd.Parse(args); err != nil { return nil } + if *help { + cmd.Usage() + return nil + } + + if cmd.BadArgs(flag.Max, 2) { + os.Exit(1) + } + + if cmd.BadArgs(flag.Min, 1) { + os.Exit(1) + } var ( name = cmd.Arg(0) repository, tag = parsers.ParseRepositoryTag(cmd.Arg(1)) ) - if name == "" || len(cmd.Args()) > 2 { - cmd.Usage() - return nil - } - //Check if the given image name can be resolved if repository != "" { if _, _, err := registry.ResolveRepositoryName(repository); err != nil { @@ -1790,18 +1874,21 @@ func (cli *DockerCli) CmdEvents(args ...string) error { cmd := cli.Subcmd("events", "", "Get real time events from the server") 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')") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") if err := cmd.Parse(args); err != nil { return nil } - - if cmd.NArg() != 0 { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Exact, 0) { + os.Exit(1) + } + var ( v = url.Values{} loc = time.FixedZone(time.Now().Zone()) @@ -1849,14 +1936,18 @@ 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") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + if err := cmd.Parse(args); err != nil { return nil } - - if cmd.NArg() != 1 { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Exact, 1) { + os.Exit(1) + } if err := cli.stream("GET", "/containers/"+cmd.Arg(0)+"/export", nil, cli.out, nil); err != nil { return err @@ -1866,13 +1957,18 @@ 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") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() != 1 { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Exact, 1) { + os.Exit(1) + } body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, false)) @@ -1905,16 +2001,20 @@ func (cli *DockerCli) CmdLogs(args ...string) error { 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)") + help = cmd.Bool([]string{"#help", "-help"}, false, "Print usage") ) if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() != 1 { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Exact, 1) { + os.Exit(1) + } name := cmd.Arg(0) stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false) @@ -1948,16 +2048,19 @@ func (cli *DockerCli) CmdAttach(args ...string) error { cmd = cli.Subcmd("attach", "CONTAINER", "Attach to a running container") 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.") + help = cmd.Bool([]string{"#help", "-help"}, false, "Print usage") ) if err := cmd.Parse(args); err != nil { return nil } - - if cmd.NArg() != 1 { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Exact, 1) { + os.Exit(1) + } name := cmd.Arg(0) stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false) @@ -2027,13 +2130,18 @@ func (cli *DockerCli) CmdSearch(args ...string) error { 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") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() != 1 { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Exact, 1) { + os.Exit(1) + } v := url.Values{} v.Set("term", cmd.Arg(0)) @@ -2079,13 +2187,18 @@ 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") force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() != 2 { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Exact, 2) { + os.Exit(1) + } var ( repository, tag = parsers.ParseRepositoryTag(cmd.Arg(1)) @@ -2158,6 +2271,7 @@ func newCIDFile(path string) (*cidFile, error) { if _, err := os.Stat(path); err == nil { return nil, fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", path) } + f, err := os.Create(path) if err != nil { return nil, fmt.Errorf("Failed to create the container ID file: %s", err) @@ -2474,14 +2588,18 @@ 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") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + if err := cmd.Parse(args); err != nil { return nil } - - if cmd.NArg() != 2 { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Exact, 2) { + os.Exit(1) + } var copyData engine.Env info := strings.Split(cmd.Arg(0), ":") @@ -2514,16 +2632,20 @@ 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)") - outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT") if err := cmd.Parse(args); err != nil { return err } - if cmd.NArg() < 1 { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Min, 1) { + os.Exit(1) + } var ( output io.Writer = cli.out @@ -2558,15 +2680,18 @@ 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") infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN") + help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") if err := cmd.Parse(args); err != nil { return err } - - if cmd.NArg() != 0 { + if *help { cmd.Usage() return nil } + if cmd.BadArgs(flag.Exact, 0) { + os.Exit(1) + } var ( input io.Reader = cli.in @@ -2588,14 +2713,9 @@ func (cli *DockerCli) CmdExec(args ...string) error { cmd := cli.Subcmd("exec", "CONTAINER COMMAND [ARG...]", "Run a command in a running container") execConfig, err := runconfig.ParseExec(cmd, args) - if err != nil { - cmd.Usage() + if execConfig.Container == "" || err != nil { return err } - if execConfig.Container == "" { - cmd.Usage() - return nil - } stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, false) if err != nil { diff --git a/docker/flags.go b/docker/flags.go index 6601b4fe8..d6c9f3c19 100644 --- a/docker/flags.go +++ b/docker/flags.go @@ -36,6 +36,7 @@ var ( flLogLevel = flag.String([]string{"l", "-log-level"}, "info", "Set the logging level") flEnableCors = flag.Bool([]string{"#api-enable-cors", "-api-enable-cors"}, false, "Enable CORS headers in the remote API") flTls = flag.Bool([]string{"-tls"}, false, "Use TLS; implied by --tlsverify flag") + flHelp = flag.Bool([]string{"h", "-help"}, false, "Print usage") flTlsVerify = flag.Bool([]string{"-tlsverify"}, dockerTlsVerify, "Use TLS and verify the remote (daemon: verify client, client: verify daemon)") // these are initialized in init() below since their default values depend on dockerCertPath which isn't fully initialized until init() runs @@ -57,8 +58,9 @@ func init() { opts.HostListVar(&flHosts, []string{"H", "-host"}, "The socket(s) to bind to in daemon mode or connect to in client mode, specified using one or more tcp://host:port, unix:///path/to/socket, fd://* or fd://socketfd.") flag.Usage = func() { - fmt.Fprint(os.Stderr, "Usage: docker [OPTIONS] COMMAND [arg...]\n\nA self-sufficient runtime for linux containers.\n\nOptions:\n") + fmt.Fprint(os.Stdout, "Usage: docker [OPTIONS] COMMAND [arg...]\n\nA self-sufficient runtime for linux containers.\n\nOptions:\n") + flag.CommandLine.SetOutput(os.Stdout) flag.PrintDefaults() help := "\nCommands:\n" @@ -105,6 +107,6 @@ func init() { help += fmt.Sprintf(" %-10.10s%s\n", command[0], command[1]) } help += "\nRun 'docker COMMAND --help' for more information on a command." - fmt.Fprintf(os.Stderr, "%s\n", help) + fmt.Fprintf(os.Stdout, "%s\n", help) } } diff --git a/docs/man/docker-attach.1.md b/docs/man/docker-attach.1.md index 19fbaceb4..0d35a8a5b 100644 --- a/docs/man/docker-attach.1.md +++ b/docs/man/docker-attach.1.md @@ -6,6 +6,7 @@ docker-attach - Attach to a running container # SYNOPSIS **docker attach** +[**--help**]/ [**--no-stdin**[=*false*]] [**--sig-proxy**[=*true*]] CONTAINER @@ -24,6 +25,9 @@ It is forbidden to redirect the standard input of a docker attach command while attaching to a tty-enabled container (i.e.: launched with -t`). # OPTIONS +**--help** + Print usage statement + **--no-stdin**=*true*|*false* Do not attach STDIN. The default is *false*. diff --git a/docs/man/docker-build.1.md b/docs/man/docker-build.1.md index 3fed99640..c5dfa706c 100644 --- a/docs/man/docker-build.1.md +++ b/docs/man/docker-build.1.md @@ -6,6 +6,7 @@ docker-build - Build a new image from the source code at PATH # SYNOPSIS **docker build** +[**--help**] [**--force-rm**[=*false*]] [**--no-cache**[=*false*]] [**-q**|**--quiet**[=*false*]] @@ -36,6 +37,9 @@ as context. **--no-cache**=*true*|*false* Do not use cache when building the image. The default is *false*. +**--help** + Print usage statement + **-q**, **--quiet**=*true*|*false* Suppress the verbose output generated by the containers. The default is *false*. diff --git a/docs/man/docker-commit.1.md b/docs/man/docker-commit.1.md index 0d1d5406c..d7619133d 100644 --- a/docs/man/docker-commit.1.md +++ b/docs/man/docker-commit.1.md @@ -7,6 +7,7 @@ docker-commit - Create a new image from a container's changes # SYNOPSIS **docker commit** [**-a**|**--author**[=*AUTHOR*]] +[**--help**] [**-m**|**--message**[=*MESSAGE*]] [**-p**|**--pause**[=*true*]] CONTAINER [REPOSITORY[:TAG]] @@ -18,6 +19,9 @@ Using an existing container's name or ID you can create a new image. **-a**, **--author**="" Author (e.g., "John Hannibal Smith ") +**--help** + Print usage statement + **-m**, **--message**="" Commit message diff --git a/docs/man/docker-cp.1.md b/docs/man/docker-cp.1.md index dc8f295bb..ac49a47a5 100644 --- a/docs/man/docker-cp.1.md +++ b/docs/man/docker-cp.1.md @@ -6,6 +6,7 @@ docker-cp - Copy files/folders from the PATH to the HOSTPATH # SYNOPSIS **docker cp** +[**--help**] CONTAINER:PATH HOSTPATH # DESCRIPTION @@ -14,7 +15,8 @@ path. Paths are relative to the root of the filesystem. Files can be copied from a running or stopped container. # OPTIONS -There are no available options. +**--help** + Print usage statement # EXAMPLES An important shell script file, created in a bash shell, is copied from diff --git a/docs/man/docker-create.1.md b/docs/man/docker-create.1.md index 96a049672..18e582100 100644 --- a/docs/man/docker-create.1.md +++ b/docs/man/docker-create.1.md @@ -21,6 +21,7 @@ docker-create - Create a new container [**--env-file**[=*[]*]] [**--expose**[=*[]*]] [**-h**|**--hostname**[=*HOSTNAME*]] +[**--help**] [**-i**|**--interactive**[=*false*]] [**--ipc**[=*IPC*]] [**--link**[=*[]*]] @@ -87,6 +88,9 @@ IMAGE [COMMAND] [ARG...] **-h**, **--hostname**="" Container host name +**--help** + Print usage statement + **-i**, **--interactive**=*true*|*false* Keep STDIN open even if not attached. The default is *false*. diff --git a/docs/man/docker-diff.1.md b/docs/man/docker-diff.1.md index acf0911b0..6c6c50253 100644 --- a/docs/man/docker-diff.1.md +++ b/docs/man/docker-diff.1.md @@ -6,6 +6,7 @@ docker-diff - Inspect changes on a container's filesystem # SYNOPSIS **docker diff** +[**--help**] CONTAINER # DESCRIPTION @@ -14,7 +15,8 @@ shortened container ID or the container name set using **docker run --name** option. # OPTIONS -There are no available options. +**--help** + Print usage statement # EXAMPLES Inspect the changes to on a nginx container: diff --git a/docs/man/docker-events.1.md b/docs/man/docker-events.1.md index c88843970..5b056ecbc 100644 --- a/docs/man/docker-events.1.md +++ b/docs/man/docker-events.1.md @@ -6,6 +6,7 @@ docker-events - Get real time events from the server # SYNOPSIS **docker events** +[**--help**] [**--since**[=*SINCE*]] [**--until**[=*UNTIL*]] @@ -23,6 +24,9 @@ and Docker images will report: untag, delete # OPTIONS +**--help** + Print usage statement + **--since**="" Show all events created since timestamp diff --git a/docs/man/docker-exec.1.md b/docs/man/docker-exec.1.md index 3db296ed7..e7554419e 100644 --- a/docs/man/docker-exec.1.md +++ b/docs/man/docker-exec.1.md @@ -7,6 +7,7 @@ docker-exec - Run a command in a running container # SYNOPSIS **docker exec** [**-d**|**--detach**[=*false*]] +[**--help**] [**-i**|**--interactive**[=*false*]] [**-t**|**--tty**[=*false*]] CONTAINER COMMAND [ARG...] @@ -25,6 +26,9 @@ container is unpaused, and then run **-d**, **--detach**=*true*|*false* Detached mode: run command in the background. The default is *false*. +**--help** + Print usage statement + **-i**, **--interactive**=*true*|*false* Keep STDIN open even if not attached. The default is *false*. diff --git a/docs/man/docker-export.1.md b/docs/man/docker-export.1.md index 8fd7834a1..d2b22d221 100644 --- a/docs/man/docker-export.1.md +++ b/docs/man/docker-export.1.md @@ -6,6 +6,7 @@ docker-export - Export the contents of a filesystem as a tar archive to STDOUT # SYNOPSIS **docker export** +[**--help**] CONTAINER # DESCRIPTION @@ -14,7 +15,8 @@ container ID or container name. The output is exported to STDOUT and can be redirected to a tar file. # OPTIONS -There are no available options. +**--help** + Print usage statement # EXAMPLES Export the contents of the container called angry_bell to a tar file diff --git a/docs/man/docker-history.1.md b/docs/man/docker-history.1.md index 65ec9cd17..47350f887 100644 --- a/docs/man/docker-history.1.md +++ b/docs/man/docker-history.1.md @@ -6,6 +6,7 @@ docker-history - Show the history of an image # SYNOPSIS **docker history** +[**--help**] [**--no-trunc**[=*false*]] [**-q**|**--quiet**[=*false*]] IMAGE @@ -15,6 +16,9 @@ IMAGE Show the history of when and how an image was created. # OPTIONS +**--help** + Print usage statement + **--no-trunc**=*true*|*false* Don't truncate output. The default is *false*. diff --git a/docs/man/docker-images.1.md b/docs/man/docker-images.1.md index 6c9e6a60b..b0e8daddd 100644 --- a/docs/man/docker-images.1.md +++ b/docs/man/docker-images.1.md @@ -6,6 +6,7 @@ docker-images - List images # SYNOPSIS **docker images** +[**--help**] [**-a**|**--all**[=*false*]] [**-f**|**--filter**[=*[]*]] [**--no-trunc**[=*false*]] @@ -35,6 +36,9 @@ versions. **-f**, **--filter**=[] Provide filter values (i.e. 'dangling=true') +**--help** + Print usage statement + **--no-trunc**=*true*|*false* Don't truncate output. The default is *false*. diff --git a/docs/man/docker-import.1.md b/docs/man/docker-import.1.md index 2d67b8bc7..974288c72 100644 --- a/docs/man/docker-import.1.md +++ b/docs/man/docker-import.1.md @@ -6,6 +6,7 @@ docker-import - Create an empty filesystem image and import the contents of the # SYNOPSIS **docker import** +[**--help**] URL|- [REPOSITORY[:TAG]] # DESCRIPTION @@ -13,7 +14,8 @@ Create a new filesystem image from the contents of a tarball (`.tar`, `.tar.gz`, `.tgz`, `.bzip`, `.tar.xz`, `.txz`) into it, then optionally tag it. # OPTIONS -There are no available options. +**--help** + Print usage statement # EXAMPLES diff --git a/docs/man/docker-info.1.md b/docs/man/docker-info.1.md index 0547b44b0..346df866a 100644 --- a/docs/man/docker-info.1.md +++ b/docs/man/docker-info.1.md @@ -6,6 +6,7 @@ docker-info - Display system-wide information # SYNOPSIS **docker info** +[**--help**] # DESCRIPTION @@ -20,7 +21,8 @@ allocates a certain amount of data space and meta data space from the space available on the volume where `/var/lib/docker` is mounted. # OPTIONS -There are no available options. +**--help** + Print usage statement # EXAMPLES diff --git a/docs/man/docker-inspect.1.md b/docs/man/docker-inspect.1.md index a52d57c97..8cbef0f91 100644 --- a/docs/man/docker-inspect.1.md +++ b/docs/man/docker-inspect.1.md @@ -6,6 +6,7 @@ docker-inspect - Return low-level information on a container or image # SYNOPSIS **docker inspect** +[**--help**] [**-f**|**--format**[=*FORMAT*]] CONTAINER|IMAGE [CONTAINER|IMAGE...] @@ -17,6 +18,9 @@ array. If a format is specified, the given template will be executed for each result. # OPTIONS +**--help** + Print usage statement + **-f**, **--format**="" Format the output using the given go template. diff --git a/docs/man/docker-kill.1.md b/docs/man/docker-kill.1.md index d1d0ee7ad..cfab3f8e4 100644 --- a/docs/man/docker-kill.1.md +++ b/docs/man/docker-kill.1.md @@ -6,6 +6,7 @@ docker-kill - Kill a running container using SIGKILL or a specified signal # SYNOPSIS **docker kill** +[**--help**] [**-s**|**--signal**[=*"KILL"*]] CONTAINER [CONTAINER...] @@ -15,6 +16,9 @@ The main process inside each container specified will be sent SIGKILL, or any signal specified with option --signal. # OPTIONS +**--help** + Print usage statement + **-s**, **--signal**="KILL" Signal to send to the container diff --git a/docs/man/docker-load.1.md b/docs/man/docker-load.1.md index 07dac4613..71bd28adf 100644 --- a/docs/man/docker-load.1.md +++ b/docs/man/docker-load.1.md @@ -6,6 +6,7 @@ docker-load - Load an image from a tar archive on STDIN # SYNOPSIS **docker load** +[**--help**] [**-i**|**--input**[=*INPUT*]] @@ -15,6 +16,9 @@ Loads a tarred repository from a file or the standard input stream. Restores both images and tags. # OPTIONS +**--help** + Print usage statement + **-i**, **--input**="" Read from a tar archive file, instead of STDIN diff --git a/docs/man/docker-login.1.md b/docs/man/docker-login.1.md index e367050be..5ee6aa1c6 100644 --- a/docs/man/docker-login.1.md +++ b/docs/man/docker-login.1.md @@ -7,6 +7,7 @@ docker-login - Register or log in to a Docker registry server, if no server is s # SYNOPSIS **docker login** [**-e**|**--email**[=*EMAIL*]] +[**--help**] [**-p**|**--password**[=*PASSWORD*]] [**-u**|**--username**[=*USERNAME*]] [SERVER] @@ -20,6 +21,9 @@ login to a private registry you can specify this by adding the server name. **-e**, **--email**="" Email +**--help** + Print usage statement + **-p**, **--password**="" Password diff --git a/docs/man/docker-logs.1.md b/docs/man/docker-logs.1.md index 1fbd229d5..c89652672 100644 --- a/docs/man/docker-logs.1.md +++ b/docs/man/docker-logs.1.md @@ -7,6 +7,7 @@ docker-logs - Fetch the logs of a container # SYNOPSIS **docker logs** [**-f**|**--follow**[=*false*]] +[**--help**] [**-t**|**--timestamps**[=*false*]] [**--tail**[=*"all"*]] CONTAINER @@ -22,6 +23,9 @@ The **docker logs --follow** command combines commands **docker logs** and then continue streaming new output from the container’s stdout and stderr. # OPTIONS +**--help** + Print usage statement + **-f**, **--follow**=*true*|*false* Follow log output. The default is *false*. diff --git a/docs/man/docker-port.1.md b/docs/man/docker-port.1.md index 8c4c870dc..a297c3921 100644 --- a/docs/man/docker-port.1.md +++ b/docs/man/docker-port.1.md @@ -6,13 +6,15 @@ docker-port - List port mappings for the CONTAINER, or lookup the public-facing # SYNOPSIS **docker port** +[**--help**] CONTAINER [PRIVATE_PORT[/PROTO]] # DESCRIPTION List port mappings for the CONTAINER, or lookup the public-facing port that is NAT-ed to the PRIVATE_PORT # OPTIONS -There are no available options. +**--help** + Print usage statement # EXAMPLES You can find out all the ports mapped by not specifying a `PRIVATE_PORT`, or diff --git a/docs/man/docker-ps.1.md b/docs/man/docker-ps.1.md index d34d98396..4c94545e3 100644 --- a/docs/man/docker-ps.1.md +++ b/docs/man/docker-ps.1.md @@ -8,6 +8,7 @@ docker-ps - List containers **docker ps** [**-a**|**--all**[=*false*]] [**--before**[=*BEFORE*]] +[**--help**] [**-f**|**--filter**[=*[]*]] [**-l**|**--latest**[=*false*]] [**-n**[=*-1*]] @@ -29,6 +30,9 @@ the running containers. **--before**="" Show only container created before Id or Name, include non-running ones. +**--help** + Print usage statement + **-f**, **--filter**=[] Provide filter values. Valid filters: exited= - containers with exit code of diff --git a/docs/man/docker-pull.1.md b/docs/man/docker-pull.1.md index 01c664f56..f1963df55 100644 --- a/docs/man/docker-pull.1.md +++ b/docs/man/docker-pull.1.md @@ -7,6 +7,7 @@ docker-pull - Pull an image or a repository from the registry # SYNOPSIS **docker pull** [**-a**|**--all-tags**[=*false*]] +[**--help**] NAME[:TAG] # DESCRIPTION @@ -19,8 +20,10 @@ It is also possible to specify a non-default registry to pull from. # OPTIONS **-a**, **--all-tags**=*true*|*false* Download all tagged images in the repository. The default is *false*. +**--help** + Print usage statement -# EXAMPLES +# EXAMPLE # Pull a repository with multiple images # Note that if the image is previously downloaded then the status would be diff --git a/docs/man/docker-push.1.md b/docs/man/docker-push.1.md index 8523cb539..2d4dc8f89 100644 --- a/docs/man/docker-push.1.md +++ b/docs/man/docker-push.1.md @@ -6,6 +6,7 @@ docker-push - Push an image or a repository to the registry # SYNOPSIS **docker push** +[**--help**] NAME[:TAG] # DESCRIPTION @@ -15,7 +16,8 @@ image can be pushed to another, perhaps private, registry as demonstrated in the example below. # OPTIONS -There are no available options. +**--help** + Print usage statement # EXAMPLES diff --git a/docs/man/docker-restart.1.md b/docs/man/docker-restart.1.md index 9a2268800..77f99d51a 100644 --- a/docs/man/docker-restart.1.md +++ b/docs/man/docker-restart.1.md @@ -6,6 +6,7 @@ docker-restart - Restart a running container # SYNOPSIS **docker restart** +[**--help**] [**-t**|**--time**[=*10*]] CONTAINER [CONTAINER...] @@ -13,6 +14,9 @@ CONTAINER [CONTAINER...] Restart each container listed. # OPTIONS +**--help** + Print usage statement + **-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. diff --git a/docs/man/docker-rm.1.md b/docs/man/docker-rm.1.md index b8f31bd68..82850a395 100644 --- a/docs/man/docker-rm.1.md +++ b/docs/man/docker-rm.1.md @@ -19,6 +19,9 @@ remove a running container unless you use the \fB-f\fR option. To see all containers on a host use the **docker ps -a** command. # OPTIONS +**--help** + Print usage statement + **-f**, **--force**=*true*|*false* Force the removal of a running container (uses SIGKILL). The default is *false*. diff --git a/docs/man/docker-rmi.1.md b/docs/man/docker-rmi.1.md index 08d740a3b..c1f131f40 100644 --- a/docs/man/docker-rmi.1.md +++ b/docs/man/docker-rmi.1.md @@ -7,6 +7,7 @@ docker-rmi - Remove one or more images # SYNOPSIS **docker rmi** [**-f**|**--force**[=*false*]] +[**--help**] [**--no-prune**[=*false*]] IMAGE [IMAGE...] @@ -21,6 +22,9 @@ use the **docker images** command. **-f**, **--force**=*true*|*false* Force removal of the image. The default is *false*. +**--help** + Print usage statement + **--no-prune**=*true*|*false* Do not delete untagged parents. The default is *false*. diff --git a/docs/man/docker-run.1.md b/docs/man/docker-run.1.md index b9571dbe2..6c8bd59c4 100644 --- a/docs/man/docker-run.1.md +++ b/docs/man/docker-run.1.md @@ -22,6 +22,7 @@ docker-run - Run a command in a new container [**--env-file**[=*[]*]] [**--expose**[=*[]*]] [**-h**|**--hostname**[=*HOSTNAME*]] +[**--help**] [**-i**|**--interactive**[=*false*]] [**--ipc**[=*IPC*]] [**--link**[=*[]*]] @@ -153,6 +154,9 @@ ENTRYPOINT. Sets the container host name that is available inside the container. +**--help** + Print usage statement + **-i**, **--interactive**=*true*|*false* Keep STDIN open even if not attached. The default is *false*. diff --git a/docs/man/docker-save.1.md b/docs/man/docker-save.1.md index c02ffb101..987d18b84 100644 --- a/docs/man/docker-save.1.md +++ b/docs/man/docker-save.1.md @@ -6,6 +6,7 @@ docker-save - Save an image(s) to a tar archive (streamed to STDOUT by default) # SYNOPSIS **docker save** +[**--help**] [**-o**|**--output**[=*OUTPUT*]] IMAGE [IMAGE...] @@ -16,6 +17,9 @@ parent layers, and all tags + versions, or specified repo:tag. Stream to a file instead of STDOUT by using **-o**. # OPTIONS +**--help** + Print usage statement + **-o**, **--output**="" Write to a file, instead of STDOUT diff --git a/docs/man/docker-search.1.md b/docs/man/docker-search.1.md index 3937b870a..2d23e34cf 100644 --- a/docs/man/docker-search.1.md +++ b/docs/man/docker-search.1.md @@ -7,6 +7,7 @@ docker-search - Search the Docker Hub for images # SYNOPSIS **docker search** [**--automated**[=*false*]] +[**--help**] [**--no-trunc**[=*false*]] [**-s**|**--stars**[=*0*]] TERM @@ -22,6 +23,9 @@ is automated. **--automated**=*true*|*false* Only show automated builds. The default is *false*. +**--help** + Print usage statement + **--no-trunc**=*true*|*false* Don't truncate output. The default is *false*. diff --git a/docs/man/docker-start.1.md b/docs/man/docker-start.1.md index e23fd70ab..965c5bcaf 100644 --- a/docs/man/docker-start.1.md +++ b/docs/man/docker-start.1.md @@ -7,6 +7,7 @@ docker-start - Restart a stopped container # SYNOPSIS **docker start** [**-a**|**--attach**[=*false*]] +[**--help**] [**-i**|**--interactive**[=*false*]] CONTAINER [CONTAINER...] @@ -18,6 +19,9 @@ Start a stopped container. **-a**, **--attach**=*true*|*false* Attach container's STDOUT and STDERR and forward all signals to the process. The default is *false*. +**--help** + Print usage statement + **-i**, **--interactive**=*true*|*false* Attach container's STDIN. The default is *false*. diff --git a/docs/man/docker-stop.1.md b/docs/man/docker-stop.1.md index 1b73e387e..09972347a 100644 --- a/docs/man/docker-stop.1.md +++ b/docs/man/docker-stop.1.md @@ -6,6 +6,7 @@ docker-stop - Stop a running container by sending SIGTERM and then SIGKILL after # SYNOPSIS **docker stop** +[**--help**] [**-t**|**--time**[=*10*]] CONTAINER [CONTAINER...] @@ -14,6 +15,9 @@ Stop a running container (Send SIGTERM, and then SIGKILL after grace period) # OPTIONS +**--help** + Print usage statement + **-t**, **--time**=10 Number of seconds to wait for the container to stop before killing it. Default is 10 seconds. diff --git a/docs/man/docker-tag.1.md b/docs/man/docker-tag.1.md index e8550ec55..20125e5df 100644 --- a/docs/man/docker-tag.1.md +++ b/docs/man/docker-tag.1.md @@ -7,6 +7,7 @@ docker-tag - Tag an image into a repository # SYNOPSIS **docker tag** [**-f**|**--force**[=*false*]] +[**--help**] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG] # DESCRIPTION diff --git a/docs/man/docker-top.1.md b/docs/man/docker-top.1.md index 9781739cd..be2bed221 100644 --- a/docs/man/docker-top.1.md +++ b/docs/man/docker-top.1.md @@ -6,6 +6,7 @@ docker-top - Display the running processes of a container # SYNOPSIS **docker top** +[**--help**] CONTAINER [ps OPTIONS] # DESCRIPTION @@ -14,7 +15,8 @@ Look up the running process of the container. ps-OPTION can be any of the options you would pass to a Linux ps command. # OPTIONS -There are no available options. +**--help** + Print usage statement # EXAMPLES diff --git a/docs/man/docker-wait.1.md b/docs/man/docker-wait.1.md index 798f6d652..a1e2aa212 100644 --- a/docs/man/docker-wait.1.md +++ b/docs/man/docker-wait.1.md @@ -6,6 +6,7 @@ docker-wait - Block until a container stops, then print its exit code. # SYNOPSIS **docker wait** +[**--help**] CONTAINER [CONTAINER...] # DESCRIPTION @@ -13,7 +14,8 @@ CONTAINER [CONTAINER...] Block until a container stops, then print its exit code. # OPTIONS -There are no available options. +**--help** + Print usage statement # EXAMPLES diff --git a/docs/man/docker.1.md b/docs/man/docker.1.md index e07687c18..f3bcdb671 100644 --- a/docs/man/docker.1.md +++ b/docs/man/docker.1.md @@ -26,6 +26,9 @@ To see the man page for a command run **man docker **. **-D**=*true*|*false* Enable debug mode. Default is false. +**--help** + Print usage statement + **-H**, **--host**=[unix:///var/run/docker.sock]: tcp://[host:port] to bind or unix://[/path/to/socket] to use. The socket(s) to bind to in daemon mode specified using one or more diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index e48a393b7..8d10bcf9c 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -15,6 +15,19 @@ or execute `docker help`: ... +## Help +To list the help on any command just execute the command, followed by the `--help` option. + + $ sudo docker run --help + + Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...] + + Run a command in a new container + + -a, --attach=[] Attach to STDIN, STDOUT or STDERR. + -c, --cpu-shares=0 CPU shares (relative weight) + ... + ## Option types Single character commandline options can be combined, so rather than diff --git a/pkg/mflag/flag.go b/pkg/mflag/flag.go index a30c41b04..ffc509a59 100644 --- a/pkg/mflag/flag.go +++ b/pkg/mflag/flag.go @@ -410,6 +410,47 @@ func IsSet(name string) bool { return CommandLine.IsSet(name) } +// Indicator used to pass to BadArgs function +const ( + Exact = 1 + Max = 2 + Min = 3 +) + +// Bad Args takes two arguments. +// The first one indicates whether the number of arguments should, be +// A Minimal number of arguments, a maximum number of arguments or +// The exact number of arguments required +// If the actuall number of arguments is not valid and error message +// prints and true is returned, otherwise false is returned +func (f *FlagSet) BadArgs(arg_type, nargs int) bool { + if arg_type == Max && f.NArg() > nargs { + if nargs == 1 { + fmt.Fprintf(f.out(), "docker: '%s' requires a maximum of 1 argument. See 'docker %s --help'.\n", f.name, f.name) + } else { + fmt.Fprintf(f.out(), "docker: '%s' requires a maximum of %d arguments. See 'docker %s --help'.\n", f.name, nargs, f.name) + } + return true + } + if arg_type == Exact && f.NArg() != nargs { + if nargs == 1 { + fmt.Fprintf(f.out(), "docker: '%s' requires 1 argument. See 'docker %s --help'.\n", f.name, f.name) + } else { + fmt.Fprintf(f.out(), "docker: '%s' requires %d arguments. See 'docker %s --help'.\n", f.name, nargs, f.name) + } + return true + } + if arg_type == Min && f.NArg() < nargs { + if nargs == 1 { + fmt.Fprintf(f.out(), "docker: '%s' requires a minimum of 1 argument. See 'docker %s --help'.\n", f.name, f.name) + } else { + fmt.Fprintf(f.out(), "docker: '%s' requires a minimum of %d arguments. See 'docker %s --help'.\n", f.name, nargs, f.name) + } + return true + } + return false +} + // Set sets the value of the named flag. func (f *FlagSet) Set(name, value string) error { flag, ok := f.formal[name] @@ -483,7 +524,7 @@ func defaultUsage(f *FlagSet) { // Usage prints to standard error a usage message documenting all defined command-line flags. // The function is a variable that may be changed to point to a custom function. var Usage = func() { - fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) + fmt.Fprintf(CommandLine.output, "Usage of %s:\n", os.Args[0]) PrintDefaults() } @@ -789,7 +830,7 @@ func Var(value Value, names []string, usage string) { func (f *FlagSet) failf(format string, a ...interface{}) error { err := fmt.Errorf(format, a...) fmt.Fprintln(f.out(), err) - f.usage() + fmt.Fprintf(f.out(), "See 'docker %s --help'.\n", f.name) return err } diff --git a/runconfig/exec.go b/runconfig/exec.go index 1ced70a86..4e23481e5 100644 --- a/runconfig/exec.go +++ b/runconfig/exec.go @@ -5,6 +5,7 @@ import ( "github.com/docker/docker/engine" flag "github.com/docker/docker/pkg/mflag" + "os" ) type ExecConfig struct { @@ -45,17 +46,22 @@ func ParseExec(cmd *flag.FlagSet, args []string) (*ExecConfig, error) { flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached") flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY") flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run command in the background") + help = cmd.Bool([]string{"#help", "-help"}, false, "Print usage") execCmd []string container string ) if err := cmd.Parse(args); err != nil { return nil, err } - parsedArgs := cmd.Args() - if len(parsedArgs) < 2 { - return nil, fmt.Errorf("not enough arguments to create exec command") + if *help { + cmd.Usage() + return nil, nil + } + if cmd.BadArgs(flag.Min, 2) { + os.Exit(1) } container = cmd.Arg(0) + parsedArgs := cmd.Args() execCmd = parsedArgs[1:] execConfig := &ExecConfig{ diff --git a/runconfig/parse.go b/runconfig/parse.go index 5c684e346..d2e4c7384 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -2,6 +2,7 @@ package runconfig import ( "fmt" + "os" "path" "strconv" "strings" @@ -61,6 +62,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe flMacAddress = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)") flIpcMode = cmd.String([]string{"-ipc"}, "", "Default is to create a private IPC namespace (POSIX SysV IPC) for the container\n'container:': reuses another container shared memory, semaphores and message queues\n'host': use the host shared memory,semaphores and message queues inside the container. Note: the host mode gives the container full access to local shared memory and is therefore considered insecure.") flRestartPolicy = cmd.String([]string{"-restart"}, "", "Restart policy to apply when a container exits (no, on-failure[:max-retry], always)") + help = cmd.Bool([]string{"#help", "-help"}, false, "Print usage") ) cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR.") @@ -86,6 +88,13 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe if err := cmd.Parse(args); err != nil { return nil, nil, cmd, err } + if *help { + cmd.Usage() + return nil, nil, cmd, nil + } + if cmd.BadArgs(flag.Min, 1) { + os.Exit(1) + } // Validate input params if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) { @@ -156,11 +165,8 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe parsedArgs = cmd.Args() runCmd []string entrypoint []string - image string + image = cmd.Arg(0) ) - if len(parsedArgs) >= 1 { - image = cmd.Arg(0) - } if len(parsedArgs) > 1 { runCmd = parsedArgs[1:] } From 4dc962d09a05b5422c692f4762a32c24cb506e6b Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Thu, 4 Sep 2014 16:49:01 -0400 Subject: [PATCH 2/7] Remove TestUsage, since Usage will no longer be shown on failure to parse Docker-DCO-1.1-Signed-off-by: Dan Walsh (github: rhatdan) --- pkg/mflag/flag_test.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pkg/mflag/flag_test.go b/pkg/mflag/flag_test.go index 622e8a9bf..b8c0b305d 100644 --- a/pkg/mflag/flag_test.go +++ b/pkg/mflag/flag_test.go @@ -151,17 +151,6 @@ func TestGet(t *testing.T) { VisitAll(visitor) } -func TestUsage(t *testing.T) { - called := false - ResetForTesting(func() { called = true }) - if CommandLine.Parse([]string{"-x"}) == nil { - t.Error("parse did not fail for unknown flag") - } - if !called { - t.Error("did not call Usage for unknown flag") - } -} - func testParse(f *FlagSet, t *testing.T) { if f.Parsed() { t.Error("f.Parse() = true before Parse") From 41be2f73c7ce2cbb5450ee8bb35bf7235125ca63 Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Wed, 5 Nov 2014 11:57:51 -0500 Subject: [PATCH 3/7] refactor redundant code around calls to cmd.Parse Signed-off-by: Tibor Vass --- api/client/commands.go | 355 +++++++++-------------------------------- pkg/mflag/flag.go | 114 +++++++------ runconfig/exec.go | 13 +- runconfig/parse.go | 13 +- utils/flags.go | 34 ++++ 5 files changed, 184 insertions(+), 345 deletions(-) create mode 100644 utils/flags.go diff --git a/api/client/commands.go b/api/client/commands.go index 14bcc8ffa..cfbedc835 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -85,18 +85,11 @@ func (cli *DockerCli) CmdBuild(args ...string) error { rm := cmd.Bool([]string{"#rm", "-rm"}, true, "Remove intermediate containers after a successful build") forceRm := cmd.Bool([]string{"-force-rm"}, false, "Always remove intermediate containers, even after unsuccessful builds") pull := cmd.Bool([]string{"-pull"}, false, "Always attempt to pull a newer version of the image") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + cmd.Require(flag.Exact, 1) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Exact, 1) { - os.Exit(1) - } var ( context archive.Archive @@ -255,22 +248,18 @@ 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.Require(flag.Max, 1) var username, password, email string cmd.StringVar(&username, []string{"u", "-username"}, "", "Username") cmd.StringVar(&password, []string{"p", "-password"}, "", "Password") cmd.StringVar(&email, []string{"e", "-email"}, "", "Email") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") - err := cmd.Parse(args) - if err != nil { - return nil - } - if *help { - cmd.Usage() + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } + serverAddress := registry.IndexServerAddress() if len(cmd.Args()) > 0 { serverAddress = cmd.Arg(0) @@ -377,8 +366,9 @@ 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.Require(flag.Max, 1) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, false); err != nil { return nil } serverAddress := registry.IndexServerAddress() @@ -403,18 +393,12 @@ 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.") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + cmd.Require(flag.Min, 1) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Min, 1) { - os.Exit(1) - } + var encounteredError error for _, name := range cmd.Args() { status, err := waitForExit(cli, name) @@ -431,12 +415,12 @@ 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.") - if err := cmd.Parse(args); err != nil { + cmd.Require(flag.Exact, 0) + + if err := utils.ParseFlags(cmd, args, false); err != nil { return nil } - if cmd.BadArgs(flag.Exact, 0) { - os.Exit(1) - } + if dockerversion.VERSION != "" { fmt.Fprintf(cli.out, "Client version: %s\n", dockerversion.VERSION) } @@ -475,12 +459,10 @@ 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") - if err := cmd.Parse(args); err != nil { + cmd.Require(flag.Exact, 0) + if err := utils.ParseFlags(cmd, args, false); err != nil { return nil } - if cmd.BadArgs(flag.Exact, 0) { - os.Exit(1) - } body, _, err := readBody(cli.call("GET", "/info", nil, false)) if err != nil { @@ -594,18 +576,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") nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Number of seconds to wait for the container to stop before killing it. Default is 10 seconds.") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + cmd.Require(flag.Min, 1) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Min, 1) { - os.Exit(1) - } v := url.Values{} v.Set("t", strconv.Itoa(*nSeconds)) @@ -626,18 +601,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") 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.") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + cmd.Require(flag.Min, 1) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Min, 1) { - os.Exit(1) - } v := url.Values{} v.Set("t", strconv.Itoa(*nSeconds)) @@ -689,19 +657,12 @@ func (cli *DockerCli) CmdStart(args ...string) error { cmd = cli.Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container") 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") - help = cmd.Bool([]string{"#help", "-help"}, false, "Print usage") ) - if err := cmd.Parse(args); err != nil { + cmd.Require(flag.Min, 1) + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Min, 1) { - os.Exit(1) - } hijacked := make(chan io.Closer) @@ -804,12 +765,10 @@ 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") - if err := cmd.Parse(args); err != nil { + cmd.Require(flag.Exact, 1) + if err := utils.ParseFlags(cmd, args, false); err != nil { return nil } - if cmd.BadArgs(flag.Exact, 1) { - os.Exit(1) - } var encounteredError error for _, name := range cmd.Args() { @@ -825,12 +784,10 @@ 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") - if err := cmd.Parse(args); err != nil { + cmd.Require(flag.Exact, 1) + if err := utils.ParseFlags(cmd, args, false); err != nil { return nil } - if cmd.BadArgs(flag.Exact, 1) { - os.Exit(1) - } var encounteredError error for _, name := range cmd.Args() { @@ -847,18 +804,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") tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template.") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + cmd.Require(flag.Min, 1) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Min, 1) { - os.Exit(1) - } var tmpl *template.Template if *tmplStr != "" { @@ -931,18 +881,12 @@ 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") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + cmd.Require(flag.Min, 1) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Min, 1) { - os.Exit(1) - } + val := url.Values{} if cmd.NArg() > 1 { val.Set("ps_args", strings.Join(cmd.Args()[1:], " ")) @@ -971,17 +915,10 @@ 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") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") - if err := cmd.Parse(args); err != nil { + cmd.Require(flag.Min, 1) + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Min, 1) { - os.Exit(1) - } stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false) if err != nil { @@ -1034,18 +971,11 @@ func (cli *DockerCli) CmdRmi(args ...string) error { force = cmd.Bool([]string{"f", "-force"}, false, "Force removal of the image") noprune = cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents") ) - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + cmd.Require(flag.Min, 1) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Min, 1) { - os.Exit(1) - } v := url.Values{} if *force { @@ -1084,18 +1014,11 @@ func (cli *DockerCli) CmdHistory(args ...string) error { cmd := cli.Subcmd("history", "IMAGE", "Show the history of an image") quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs") noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + cmd.Require(flag.Exact, 1) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Exact, 1) { - os.Exit(1) - } body, _, err := readBody(cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, false)) if err != nil { @@ -1146,18 +1069,11 @@ func (cli *DockerCli) CmdRm(args ...string) error { 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)") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + cmd.Require(flag.Min, 1) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Min, 1) { - os.Exit(1) - } val := url.Values{} if *v { @@ -1188,18 +1104,11 @@ func (cli *DockerCli) CmdRm(args ...string) error { func (cli *DockerCli) CmdKill(args ...string) error { cmd := cli.Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container using SIGKILL or a specified signal") signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + cmd.Require(flag.Min, 1) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Min, 1) { - os.Exit(1) - } var encounteredError error for _, name := range cmd.Args() { @@ -1215,18 +1124,12 @@ 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.") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + cmd.Require(flag.Min, 1) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Min, 1) { - os.Exit(1) - } + var ( v = url.Values{} src = cmd.Arg(0) @@ -1260,18 +1163,12 @@ 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") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + cmd.Require(flag.Exact, 1) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Exact, 1) { - os.Exit(1) - } + name := cmd.Arg(0) cli.LoadConfigFile() @@ -1330,19 +1227,12 @@ 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") allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + cmd.Require(flag.Exact, 1) - if err := cmd.Parse(args); err != nil { - return nil - } - if *help { - cmd.Usage() + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if cmd.BadArgs(flag.Exact, 1) { - os.Exit(1) - } var ( v = url.Values{} remote = cmd.Arg(0) @@ -1406,21 +1296,14 @@ func (cli *DockerCli) CmdImages(args ...string) error { // FIXME: --viz and --tree are deprecated. Remove them in a future version. flViz := cmd.Bool([]string{"#v", "#viz", "#-viz"}, false, "Output graph in graphviz format") flTree := cmd.Bool([]string{"#t", "#tree", "#-tree"}, false, "Output graph in tree format") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") flFilter := opts.NewListOpts(nil) cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values (i.e. 'dangling=true')") + cmd.Require(flag.Max, 1) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Max, 1) { - os.Exit(1) - } // Consolidate all filter flags, and sanity check them early. // They'll get process in the daemon/server. @@ -1650,7 +1533,6 @@ func (cli *DockerCli) CmdPs(args ...string) error { 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.") - help = cmd.Bool([]string{"#help", "-help"}, false, "Print usage") noTrunc = cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") nLatest = cmd.Bool([]string{"l", "-latest"}, false, "Show only the latest created container, include non-running ones.") since = cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show only containers created since Id or Name, include non-running ones.") @@ -1658,14 +1540,11 @@ func (cli *DockerCli) CmdPs(args ...string) error { last = cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running ones.") flFilter = opts.NewListOpts(nil) ) + cmd.Require(flag.Exact, 0) cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values. Valid filters:\nexited= - containers with exit code of \nstatus=(restarting|running|paused|exited)") - if err := cmd.Parse(args); err != nil { - return nil - } - if *help { - cmd.Usage() + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } if *last == -1 && *nLatest { @@ -1808,22 +1687,11 @@ func (cli *DockerCli) CmdCommit(args ...string) error { flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (e.g., \"John Hannibal Smith \")") // FIXME: --run is deprecated, it will be replaced with inline Dockerfile commands. 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") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") - if err := cmd.Parse(args); err != nil { + cmd.Require(flag.Max, 2) + cmd.Require(flag.Min, 1) + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - - if cmd.BadArgs(flag.Max, 2) { - os.Exit(1) - } - - if cmd.BadArgs(flag.Min, 1) { - os.Exit(1) - } var ( name = cmd.Arg(0) @@ -1876,18 +1744,11 @@ func (cli *DockerCli) CmdEvents(args ...string) error { 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')") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + cmd.Require(flag.Exact, 0) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Exact, 0) { - os.Exit(1) - } var ( v = url.Values{} @@ -1936,18 +1797,11 @@ 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") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + cmd.Require(flag.Exact, 1) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Exact, 1) { - os.Exit(1) - } if err := cli.stream("GET", "/containers/"+cmd.Arg(0)+"/export", nil, cli.out, nil); err != nil { return err @@ -1957,18 +1811,11 @@ 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") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + cmd.Require(flag.Exact, 1) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Exact, 1) { - os.Exit(1) - } body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, false)) @@ -2001,20 +1848,13 @@ func (cli *DockerCli) CmdLogs(args ...string) error { 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)") - help = cmd.Bool([]string{"#help", "-help"}, false, "Print usage") ) + cmd.Require(flag.Exact, 1) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Exact, 1) { - os.Exit(1) - } name := cmd.Arg(0) stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false) @@ -2048,19 +1888,12 @@ func (cli *DockerCli) CmdAttach(args ...string) error { cmd = cli.Subcmd("attach", "CONTAINER", "Attach to a running container") 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.") - help = cmd.Bool([]string{"#help", "-help"}, false, "Print usage") ) + cmd.Require(flag.Exact, 1) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Exact, 1) { - os.Exit(1) - } name := cmd.Arg(0) stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false) @@ -2130,18 +1963,11 @@ func (cli *DockerCli) CmdSearch(args ...string) error { 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") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + cmd.Require(flag.Exact, 1) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Exact, 1) { - os.Exit(1) - } v := url.Values{} v.Set("term", cmd.Arg(0)) @@ -2187,18 +2013,11 @@ 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") force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + cmd.Require(flag.Exact, 2) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Exact, 2) { - os.Exit(1) - } var ( repository, tag = parsers.ParseRepositoryTag(cmd.Arg(1)) @@ -2588,18 +2407,11 @@ 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") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + cmd.Require(flag.Exact, 2) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Exact, 2) { - os.Exit(1) - } var copyData engine.Env info := strings.Split(cmd.Arg(0), ":") @@ -2632,21 +2444,13 @@ 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)") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT") + cmd.Require(flag.Min, 1) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, true); err != nil { return err } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Min, 1) { - os.Exit(1) - } - var ( output io.Writer = cli.out err error @@ -2680,18 +2484,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") infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN") - help := cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + cmd.Require(flag.Exact, 0) - if err := cmd.Parse(args); err != nil { + if err := utils.ParseFlags(cmd, args, true); err != nil { return err } - if *help { - cmd.Usage() - return nil - } - if cmd.BadArgs(flag.Exact, 0) { - os.Exit(1) - } var ( input io.Reader = cli.in diff --git a/pkg/mflag/flag.go b/pkg/mflag/flag.go index ffc509a59..42711b763 100644 --- a/pkg/mflag/flag.go +++ b/pkg/mflag/flag.go @@ -284,13 +284,14 @@ type FlagSet struct { // a custom error handler. Usage func() - name string - parsed bool - actual map[string]*Flag - formal map[string]*Flag - args []string // arguments after flags - errorHandling ErrorHandling - output io.Writer // nil means stderr; use out() accessor + name string + parsed bool + actual map[string]*Flag + formal map[string]*Flag + args []string // arguments after flags + errorHandling ErrorHandling + output io.Writer // nil means stderr; use Out() accessor + nArgRequirements []nArgRequirement } // A Flag represents the state of a flag. @@ -348,7 +349,13 @@ func sortFlags(flags map[string]*Flag) []*Flag { return result } -func (f *FlagSet) out() io.Writer { +// Name returns the name of the FlagSet. +func (f *FlagSet) Name() string { + return f.name +} + +// Out returns the destination for usage and error messages. +func (f *FlagSet) Out() io.Writer { if f.output == nil { return os.Stderr } @@ -410,45 +417,60 @@ func IsSet(name string) bool { return CommandLine.IsSet(name) } +type nArgRequirementType int + // Indicator used to pass to BadArgs function const ( - Exact = 1 - Max = 2 - Min = 3 + Exact nArgRequirementType = iota + Max + Min ) -// Bad Args takes two arguments. -// The first one indicates whether the number of arguments should, be -// A Minimal number of arguments, a maximum number of arguments or -// The exact number of arguments required -// If the actuall number of arguments is not valid and error message -// prints and true is returned, otherwise false is returned -func (f *FlagSet) BadArgs(arg_type, nargs int) bool { - if arg_type == Max && f.NArg() > nargs { - if nargs == 1 { - fmt.Fprintf(f.out(), "docker: '%s' requires a maximum of 1 argument. See 'docker %s --help'.\n", f.name, f.name) +type nArgRequirement struct { + Type nArgRequirementType + N int +} + +// Require adds a requirement about the number of arguments for the FlagSet. +// The first parameter can be Exact, Max, or Min to respectively specify the exact, +// the maximum, or the minimal number of arguments required. +// The actual check is done in FlagSet.CheckArgs(). +func (f *FlagSet) Require(nArgRequirementType nArgRequirementType, nArg int) { + f.nArgRequirements = append(f.nArgRequirements, nArgRequirement{nArgRequirementType, nArg}) +} + +// CheckArgs uses the requirements set by FlagSet.Require() to validate +// the number of arguments. If the requirements are not met, +// an error message string is returned. +func (f *FlagSet) CheckArgs() (message string) { + for _, req := range f.nArgRequirements { + var arguments string + if req.N == 1 { + arguments = "1 argument" } else { - fmt.Fprintf(f.out(), "docker: '%s' requires a maximum of %d arguments. See 'docker %s --help'.\n", f.name, nargs, f.name) + arguments = fmt.Sprintf("%d arguments", req.N) } - return true - } - if arg_type == Exact && f.NArg() != nargs { - if nargs == 1 { - fmt.Fprintf(f.out(), "docker: '%s' requires 1 argument. See 'docker %s --help'.\n", f.name, f.name) - } else { - fmt.Fprintf(f.out(), "docker: '%s' requires %d arguments. See 'docker %s --help'.\n", f.name, nargs, f.name) + + str := func(kind string) string { + return fmt.Sprintf("%q requires %s%s", f.name, kind, arguments) } - return true - } - if arg_type == Min && f.NArg() < nargs { - if nargs == 1 { - fmt.Fprintf(f.out(), "docker: '%s' requires a minimum of 1 argument. See 'docker %s --help'.\n", f.name, f.name) - } else { - fmt.Fprintf(f.out(), "docker: '%s' requires a minimum of %d arguments. See 'docker %s --help'.\n", f.name, nargs, f.name) + + switch req.Type { + case Exact: + if f.NArg() != req.N { + return str("") + } + case Max: + if f.NArg() > req.N { + return str("a maximum of ") + } + case Min: + if f.NArg() < req.N { + return str("a minimum of ") + } } - return true } - return false + return "" } // Set sets the value of the named flag. @@ -476,7 +498,7 @@ func Set(name, value string) error { // PrintDefaults prints, to standard error unless configured // otherwise, the default values of all defined flags in the set. func (f *FlagSet) PrintDefaults() { - writer := tabwriter.NewWriter(f.out(), 20, 1, 3, ' ', 0) + writer := tabwriter.NewWriter(f.Out(), 20, 1, 3, ' ', 0) f.VisitAll(func(flag *Flag) { format := " -%s=%s" if _, ok := flag.Value.(*stringValue); ok { @@ -510,9 +532,9 @@ func PrintDefaults() { // defaultUsage is the default function to print a usage message. func defaultUsage(f *FlagSet) { if f.name == "" { - fmt.Fprintf(f.out(), "Usage:\n") + fmt.Fprintf(f.Out(), "Usage:\n") } else { - fmt.Fprintf(f.out(), "Usage of %s:\n", f.name) + fmt.Fprintf(f.Out(), "Usage of %s:\n", f.name) } f.PrintDefaults() } @@ -805,7 +827,7 @@ func (f *FlagSet) Var(value Value, names []string, usage string) { } else { msg = fmt.Sprintf("%s flag redefined: %s", f.name, name) } - fmt.Fprintln(f.out(), msg) + fmt.Fprintln(f.Out(), msg) panic(msg) // Happens only if flags are declared with identical names } if f.formal == nil { @@ -829,8 +851,8 @@ func Var(value Value, names []string, usage string) { // returns the error. func (f *FlagSet) failf(format string, a ...interface{}) error { err := fmt.Errorf(format, a...) - fmt.Fprintln(f.out(), err) - fmt.Fprintf(f.out(), "See 'docker %s --help'.\n", f.name) + fmt.Fprintln(f.Out(), err) + fmt.Fprintf(f.Out(), "See 'docker %s --help'.\n", f.name) return err } @@ -956,9 +978,9 @@ func (f *FlagSet) parseOne() (bool, string, error) { } } if replacement != "" { - fmt.Fprintf(f.out(), "Warning: '-%s' is deprecated, it will be replaced by '-%s' soon. See usage.\n", name, replacement) + fmt.Fprintf(f.Out(), "Warning: '-%s' is deprecated, it will be replaced by '-%s' soon. See usage.\n", name, replacement) } else { - fmt.Fprintf(f.out(), "Warning: '-%s' is deprecated, it will be removed soon. See usage.\n", name) + fmt.Fprintf(f.Out(), "Warning: '-%s' is deprecated, it will be removed soon. See usage.\n", name) } } } diff --git a/runconfig/exec.go b/runconfig/exec.go index 4e23481e5..9390781a4 100644 --- a/runconfig/exec.go +++ b/runconfig/exec.go @@ -5,7 +5,7 @@ import ( "github.com/docker/docker/engine" flag "github.com/docker/docker/pkg/mflag" - "os" + "github.com/docker/docker/utils" ) type ExecConfig struct { @@ -46,20 +46,13 @@ func ParseExec(cmd *flag.FlagSet, args []string) (*ExecConfig, error) { flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached") flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY") flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run command in the background") - help = cmd.Bool([]string{"#help", "-help"}, false, "Print usage") execCmd []string container string ) - if err := cmd.Parse(args); err != nil { + cmd.Require(flag.Min, 2) + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil, err } - if *help { - cmd.Usage() - return nil, nil - } - if cmd.BadArgs(flag.Min, 2) { - os.Exit(1) - } container = cmd.Arg(0) parsedArgs := cmd.Args() execCmd = parsedArgs[1:] diff --git a/runconfig/parse.go b/runconfig/parse.go index d2e4c7384..911ab7e52 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -2,7 +2,6 @@ package runconfig import ( "fmt" - "os" "path" "strconv" "strings" @@ -62,7 +61,6 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe flMacAddress = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)") flIpcMode = cmd.String([]string{"-ipc"}, "", "Default is to create a private IPC namespace (POSIX SysV IPC) for the container\n'container:': reuses another container shared memory, semaphores and message queues\n'host': use the host shared memory,semaphores and message queues inside the container. Note: the host mode gives the container full access to local shared memory and is therefore considered insecure.") flRestartPolicy = cmd.String([]string{"-restart"}, "", "Restart policy to apply when a container exits (no, on-failure[:max-retry], always)") - help = cmd.Bool([]string{"#help", "-help"}, false, "Print usage") ) cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR.") @@ -85,16 +83,11 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe cmd.Var(&flCapDrop, []string{"-cap-drop"}, "Drop Linux capabilities") cmd.Var(&flSecurityOpt, []string{"-security-opt"}, "Security Options") - if err := cmd.Parse(args); err != nil { + cmd.Require(flag.Min, 1) + + if err := utils.ParseFlags(cmd, args, true); err != nil { return nil, nil, cmd, err } - if *help { - cmd.Usage() - return nil, nil, cmd, nil - } - if cmd.BadArgs(flag.Min, 1) { - os.Exit(1) - } // Validate input params if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) { diff --git a/utils/flags.go b/utils/flags.go new file mode 100644 index 000000000..d88104b63 --- /dev/null +++ b/utils/flags.go @@ -0,0 +1,34 @@ +package utils + +import ( + "fmt" + "os" + + flag "github.com/docker/docker/pkg/mflag" +) + +// 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. +// TODO: move this to a better package than utils +func ParseFlags(cmd *flag.FlagSet, args []string, withHelp bool) error { + var help *bool + if withHelp { + help = cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + } + if err := cmd.Parse(args); err != nil { + return err + } + if help != nil && *help { + cmd.Usage() + // just in case Usage does not exit + os.Exit(0) + } + if str := cmd.CheckArgs(); str != "" { + if withHelp { + str += ". See 'docker " + cmd.Name() + " --help'" + } + fmt.Fprintf(cmd.Out(), "docker: %s.\n", str) + os.Exit(1) + } + return nil +} From 1d09fc22ca14b0b83bdc81cba06d3d7ac886a3e6 Mon Sep 17 00:00:00 2001 From: Michal Minar Date: Thu, 13 Nov 2014 15:39:51 +0100 Subject: [PATCH 4/7] Handle bad options better * Do not log bad options error message twice, e.g.: $ docker run --pouet flag provided but not defined: --pouet See 'docker run --help'. 2014/11/05 21:41:23 flag provided but not defined: --pouet With this patch just the first two lines will be produced. * Print 'docker' just once when run without a command, e.g.: $ docker --hel flag provided but not defined: --hel See 'docker docker --help'. Signed-off-by: Michal Minar --- api/client/commands.go | 6 +++--- pkg/mflag/flag.go | 6 +++++- utils/flags.go | 6 +++++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/api/client/commands.go b/api/client/commands.go index cfbedc835..6c919e106 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -2187,7 +2187,7 @@ func (cli *DockerCli) CmdCreate(args ...string) error { config, hostConfig, cmd, err := runconfig.Parse(cmd, args) if err != nil { - return err + return &utils.StatusError{StatusCode: 1} } if config.Image == "" { cmd.Usage() @@ -2223,7 +2223,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { config, hostConfig, cmd, err := runconfig.Parse(cmd, args) if err != nil { - return err + return &utils.StatusError{StatusCode: 1} } if config.Image == "" { cmd.Usage() @@ -2511,7 +2511,7 @@ func (cli *DockerCli) CmdExec(args ...string) error { execConfig, err := runconfig.ParseExec(cmd, args) if execConfig.Container == "" || err != nil { - return err + return &utils.StatusError{StatusCode: 1} } stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, false) diff --git a/pkg/mflag/flag.go b/pkg/mflag/flag.go index 42711b763..72addae63 100644 --- a/pkg/mflag/flag.go +++ b/pkg/mflag/flag.go @@ -852,7 +852,11 @@ func Var(value Value, names []string, usage string) { func (f *FlagSet) failf(format string, a ...interface{}) error { err := fmt.Errorf(format, a...) fmt.Fprintln(f.Out(), err) - fmt.Fprintf(f.Out(), "See 'docker %s --help'.\n", f.name) + if os.Args[0] == f.name { + fmt.Fprintf(f.Out(), "See '%s --help'.\n", os.Args[0]) + } else { + fmt.Fprintf(f.Out(), "See '%s %s --help'.\n", os.Args[0], f.name) + } return err } diff --git a/utils/flags.go b/utils/flags.go index d88104b63..fdf57b1b2 100644 --- a/utils/flags.go +++ b/utils/flags.go @@ -25,7 +25,11 @@ func ParseFlags(cmd *flag.FlagSet, args []string, withHelp bool) error { } if str := cmd.CheckArgs(); str != "" { if withHelp { - str += ". See 'docker " + cmd.Name() + " --help'" + if os.Args[0] == cmd.Name() { + str += ". See '" + os.Args[0] + " " + cmd.Name() + " --help'" + } else { + str += ". See '" + os.Args[0] + " --help'" + } } fmt.Fprintf(cmd.Out(), "docker: %s.\n", str) os.Exit(1) From 08f0f1ee1db2dea79e622764891cb8b1432bcab1 Mon Sep 17 00:00:00 2001 From: Michal Minar Date: Wed, 19 Nov 2014 09:42:10 +0100 Subject: [PATCH 5/7] Fixed error reporting Removed redundant print line and fixed handling of command-less docker invocation. Signed-off-by: Michal Minar --- api/client/commands.go | 1 - utils/flags.go | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/api/client/commands.go b/api/client/commands.go index 6c919e106..5b0ee1175 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -63,7 +63,6 @@ func (cli *DockerCli) CmdHelp(args ...string) error { if len(args) > 0 { method, exists := cli.getMethod(args[0]) if !exists { - fmt.Fprintf(cli.err, "Error: Command not found: %s\n", args[0]) fmt.Fprintf(cli.err, "docker: '%s' is not a docker command. See 'docker --help'.\n", args[0]) os.Exit(1) } else { diff --git a/utils/flags.go b/utils/flags.go index fdf57b1b2..92449070e 100644 --- a/utils/flags.go +++ b/utils/flags.go @@ -26,9 +26,9 @@ func ParseFlags(cmd *flag.FlagSet, args []string, withHelp bool) error { if str := cmd.CheckArgs(); str != "" { if withHelp { if os.Args[0] == cmd.Name() { - str += ". See '" + os.Args[0] + " " + cmd.Name() + " --help'" - } else { str += ". See '" + os.Args[0] + " --help'" + } else { + str += ". See '" + os.Args[0] + " " + cmd.Name() + " --help'" } } fmt.Fprintf(cmd.Out(), "docker: %s.\n", str) From 8a785792cd3c3fb4494bced475263aef5fa4534b Mon Sep 17 00:00:00 2001 From: Michal Minar Date: Mon, 24 Nov 2014 17:34:13 +0100 Subject: [PATCH 6/7] 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 --- api/client/cli.go | 10 +- api/client/commands.go | 212 ++++++++++++++--------------------------- utils/flags.go | 5 +- 3 files changed, 85 insertions(+), 142 deletions(-) diff --git a/api/client/cli.go b/api/client/cli.go index aec8ebb86..9f034d6c4 100644 --- a/api/client/cli.go +++ b/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 { diff --git a/api/client/commands.go b/api/client/commands.go index 5b0ee1175..fa2f63df7 100644 --- a/api/client/commands.go +++ b/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= - containers with exit code of \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 \")") @@ -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} } diff --git a/utils/flags.go b/utils/flags.go index 92449070e..8f47780bc 100644 --- a/utils/flags.go +++ b/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 From 1db927113f3e7af83fac548c0e2da05bccbd8d56 Mon Sep 17 00:00:00 2001 From: Michal Minar Date: Tue, 6 Jan 2015 16:44:36 +0100 Subject: [PATCH 7/7] Amended TestExecParseError test Usage string isn't printed anymore. User is adviced to see help instead - according to coreutils standard. Signed-off-by: Michal Minar --- integration-cli/docker_cli_exec_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index 5bb2dc4bb..2ff39fc83 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/integration-cli/docker_cli_exec_test.go @@ -362,10 +362,10 @@ func TestExecParseError(t *testing.T) { // Test normal (non-detached) case first cmd := exec.Command(dockerBinary, "exec", "top") - if out, _, err := runCommandWithOutput(cmd); err == nil || !strings.Contains(out, "Usage:") { - t.Fatalf("Should have thrown error & given usage: %s", out) + if _, stderr, code, err := runCommandWithStdoutStderr(cmd); err == nil || !strings.Contains(stderr, "See '"+dockerBinary+" exec --help'") || code == 0 { + t.Fatalf("Should have thrown error & point to help: %s", stderr) } - logDone("exec - error on parseExec should return usage") + logDone("exec - error on parseExec should point to help") } func TestExecStopNotHanging(t *testing.T) {