Continues 11858 by:
- Making sure the exit code is always zero when we ask for help
- Making sure the exit code isn't zero when we print help on error cases
- Making sure both short and long usage go to the same stream (stdout vs stderr)
- Making sure all docker commands support --help
- Test that all cmds send --help to stdout, exit code 0, show full usage, no blank lines at end
- Test that all cmds (that support it) show short usage on bad arg to stderr, no blank line at end
- Test that all cmds complain about a bad option, no blank line at end
- Test that docker (w/o subcmd) does the same stuff mentioned above properly
Signed-off-by: Doug Davis <dug@us.ibm.com>
| ... | ... |
@@ -8,7 +8,6 @@ import ( |
| 8 | 8 |
"io" |
| 9 | 9 |
"net" |
| 10 | 10 |
"net/http" |
| 11 |
- "os" |
|
| 12 | 11 |
"path/filepath" |
| 13 | 12 |
"reflect" |
| 14 | 13 |
"strings" |
| ... | ... |
@@ -98,7 +97,7 @@ func (cli *DockerCli) Cmd(args ...string) error {
|
| 98 | 98 |
if len(args) > 0 {
|
| 99 | 99 |
method, exists := cli.getMethod(args[0]) |
| 100 | 100 |
if !exists {
|
| 101 |
- return fmt.Errorf("docker: '%s' is not a docker command. See 'docker --help'.", args[0])
|
|
| 101 |
+ return fmt.Errorf("docker: '%s' is not a docker command.\nSee 'docker --help'.", args[0])
|
|
| 102 | 102 |
} |
| 103 | 103 |
return method(args[1:]...) |
| 104 | 104 |
} |
| ... | ... |
@@ -124,15 +123,13 @@ func (cli *DockerCli) Subcmd(name, signature, description string, exitOnError bo |
| 124 | 124 |
flags.Usage = func() {
|
| 125 | 125 |
flags.ShortUsage() |
| 126 | 126 |
flags.PrintDefaults() |
| 127 |
- os.Exit(0) |
|
| 128 | 127 |
} |
| 129 | 128 |
flags.ShortUsage = func() {
|
| 130 | 129 |
options := "" |
| 131 | 130 |
if flags.FlagCountUndeprecated() > 0 {
|
| 132 | 131 |
options = " [OPTIONS]" |
| 133 | 132 |
} |
| 134 |
- fmt.Fprintf(cli.out, "\nUsage: docker %s%s%s\n\n%s\n\n", name, options, signature, description) |
|
| 135 |
- flags.SetOutput(cli.out) |
|
| 133 |
+ fmt.Fprintf(flags.Out(), "\nUsage: docker %s%s%s\n\n%s\n", name, options, signature, description) |
|
| 136 | 134 |
} |
| 137 | 135 |
return flags |
| 138 | 136 |
} |
| ... | ... |
@@ -15,7 +15,7 @@ import ( |
| 15 | 15 |
func (cli *DockerCli) CmdInfo(args ...string) error {
|
| 16 | 16 |
cmd := cli.Subcmd("info", "", "Display system-wide information", true)
|
| 17 | 17 |
cmd.Require(flag.Exact, 0) |
| 18 |
- cmd.ParseFlags(args, false) |
|
| 18 |
+ cmd.ParseFlags(args, true) |
|
| 19 | 19 |
|
| 20 | 20 |
rdr, _, err := cli.call("GET", "/info", nil, nil)
|
| 21 | 21 |
if err != nil {
|
| ... | ... |
@@ -16,7 +16,7 @@ func (cli *DockerCli) CmdLogout(args ...string) error {
|
| 16 | 16 |
cmd := cli.Subcmd("logout", "[SERVER]", "Log out from a Docker registry, if no server is\nspecified \""+registry.IndexServerAddress()+"\" is the default.", true)
|
| 17 | 17 |
cmd.Require(flag.Max, 1) |
| 18 | 18 |
|
| 19 |
- cmd.ParseFlags(args, false) |
|
| 19 |
+ cmd.ParseFlags(args, true) |
|
| 20 | 20 |
serverAddress := registry.IndexServerAddress() |
| 21 | 21 |
if len(cmd.Args()) > 0 {
|
| 22 | 22 |
serverAddress = cmd.Arg(0) |
| ... | ... |
@@ -12,7 +12,7 @@ import ( |
| 12 | 12 |
func (cli *DockerCli) CmdPause(args ...string) error {
|
| 13 | 13 |
cmd := cli.Subcmd("pause", "CONTAINER [CONTAINER...]", "Pause all processes within a container", true)
|
| 14 | 14 |
cmd.Require(flag.Min, 1) |
| 15 |
- cmd.ParseFlags(args, false) |
|
| 15 |
+ cmd.ParseFlags(args, true) |
|
| 16 | 16 |
|
| 17 | 17 |
var errNames []string |
| 18 | 18 |
for _, name := range cmd.Args() {
|
| ... | ... |
@@ -12,7 +12,7 @@ import ( |
| 12 | 12 |
func (cli *DockerCli) CmdUnpause(args ...string) error {
|
| 13 | 13 |
cmd := cli.Subcmd("unpause", "CONTAINER [CONTAINER...]", "Unpause all processes within a container", true)
|
| 14 | 14 |
cmd.Require(flag.Min, 1) |
| 15 |
- cmd.ParseFlags(args, false) |
|
| 15 |
+ cmd.ParseFlags(args, true) |
|
| 16 | 16 |
|
| 17 | 17 |
var errNames []string |
| 18 | 18 |
for _, name := range cmd.Args() {
|
| ... | ... |
@@ -20,7 +20,7 @@ func (cli *DockerCli) CmdVersion(args ...string) error {
|
| 20 | 20 |
cmd := cli.Subcmd("version", "", "Show the Docker version information.", true)
|
| 21 | 21 |
cmd.Require(flag.Exact, 0) |
| 22 | 22 |
|
| 23 |
- cmd.ParseFlags(args, false) |
|
| 23 |
+ cmd.ParseFlags(args, true) |
|
| 24 | 24 |
|
| 25 | 25 |
if dockerversion.VERSION != "" {
|
| 26 | 26 |
fmt.Fprintf(cli.out, "Client version: %s\n", dockerversion.VERSION) |
| ... | ... |
@@ -93,6 +93,8 @@ func (s *DockerSuite) TestHelpTextVerify(c *check.C) {
|
| 93 | 93 |
// Skip first line, its "Commands:" |
| 94 | 94 |
cmds := []string{}
|
| 95 | 95 |
for _, cmd := range strings.Split(out[i:], "\n")[1:] {
|
| 96 |
+ var stderr string |
|
| 97 |
+ |
|
| 96 | 98 |
// Stop on blank line or non-idented line |
| 97 | 99 |
if cmd == "" || !unicode.IsSpace(rune(cmd[0])) {
|
| 98 | 100 |
break |
| ... | ... |
@@ -102,12 +104,24 @@ func (s *DockerSuite) TestHelpTextVerify(c *check.C) {
|
| 102 | 102 |
cmd = strings.Split(strings.TrimSpace(cmd), " ")[0] |
| 103 | 103 |
cmds = append(cmds, cmd) |
| 104 | 104 |
|
| 105 |
+ // Check the full usage text |
|
| 105 | 106 |
helpCmd := exec.Command(dockerBinary, cmd, "--help") |
| 106 | 107 |
helpCmd.Env = newEnvs |
| 107 |
- out, ec, err := runCommandWithOutput(helpCmd) |
|
| 108 |
+ out, stderr, ec, err = runCommandWithStdoutStderr(helpCmd) |
|
| 109 |
+ if len(stderr) != 0 {
|
|
| 110 |
+ c.Fatalf("Error on %q help. non-empty stderr:%q", cmd, stderr)
|
|
| 111 |
+ } |
|
| 112 |
+ if strings.HasSuffix(out, "\n\n") {
|
|
| 113 |
+ c.Fatalf("Should not have blank line on %q\nout:%q", cmd, out)
|
|
| 114 |
+ } |
|
| 115 |
+ if !strings.Contains(out, "--help=false") {
|
|
| 116 |
+ c.Fatalf("Should show full usage on %q\nout:%q", cmd, out)
|
|
| 117 |
+ } |
|
| 108 | 118 |
if err != nil || ec != 0 {
|
| 109 | 119 |
c.Fatalf("Error on %q help: %s\nexit code:%d", cmd, out, ec)
|
| 110 | 120 |
} |
| 121 |
+ |
|
| 122 |
+ // Check each line for lots of stuff |
|
| 111 | 123 |
lines := strings.Split(out, "\n") |
| 112 | 124 |
for _, line := range lines {
|
| 113 | 125 |
if len(line) > 80 {
|
| ... | ... |
@@ -142,6 +156,77 @@ func (s *DockerSuite) TestHelpTextVerify(c *check.C) {
|
| 142 | 142 |
} |
| 143 | 143 |
|
| 144 | 144 |
} |
| 145 |
+ |
|
| 146 |
+ // For each command make sure we generate an error |
|
| 147 |
+ // if we give a bad arg |
|
| 148 |
+ dCmd := exec.Command(dockerBinary, cmd, "--badArg") |
|
| 149 |
+ out, stderr, ec, err = runCommandWithStdoutStderr(dCmd) |
|
| 150 |
+ if len(out) != 0 || len(stderr) == 0 || ec == 0 || err == nil {
|
|
| 151 |
+ c.Fatalf("Bad results from 'docker %s --badArg'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", cmd, ec, out, stderr, err)
|
|
| 152 |
+ } |
|
| 153 |
+ // Be really picky |
|
| 154 |
+ if strings.HasSuffix(stderr, "\n\n") {
|
|
| 155 |
+ c.Fatalf("Should not have a blank line at the end of 'docker rm'\n%s", stderr)
|
|
| 156 |
+ } |
|
| 157 |
+ |
|
| 158 |
+ // Now make sure that each command will print a short-usage |
|
| 159 |
+ // (not a full usage - meaning no opts section) if we |
|
| 160 |
+ // are missing a required arg or pass in a bad arg |
|
| 161 |
+ |
|
| 162 |
+ // These commands will never print a short-usage so don't test |
|
| 163 |
+ noShortUsage := map[string]string{
|
|
| 164 |
+ "images": "", |
|
| 165 |
+ "login": "", |
|
| 166 |
+ "logout": "", |
|
| 167 |
+ } |
|
| 168 |
+ |
|
| 169 |
+ if _, ok := noShortUsage[cmd]; !ok {
|
|
| 170 |
+ // For each command run it w/o any args. It will either return |
|
| 171 |
+ // valid output or print a short-usage |
|
| 172 |
+ var dCmd *exec.Cmd |
|
| 173 |
+ var stdout, stderr string |
|
| 174 |
+ var args []string |
|
| 175 |
+ |
|
| 176 |
+ // skipNoArgs are ones that we don't want to try w/o |
|
| 177 |
+ // any args. Either because it'll hang the test or |
|
| 178 |
+ // lead to incorrect test result (like false negative). |
|
| 179 |
+ // Whatever the reason, skip trying to run w/o args and |
|
| 180 |
+ // jump to trying with a bogus arg. |
|
| 181 |
+ skipNoArgs := map[string]string{
|
|
| 182 |
+ "events": "", |
|
| 183 |
+ "load": "", |
|
| 184 |
+ } |
|
| 185 |
+ |
|
| 186 |
+ ec = 0 |
|
| 187 |
+ if _, ok := skipNoArgs[cmd]; !ok {
|
|
| 188 |
+ args = []string{cmd}
|
|
| 189 |
+ dCmd = exec.Command(dockerBinary, args...) |
|
| 190 |
+ stdout, stderr, ec, err = runCommandWithStdoutStderr(dCmd) |
|
| 191 |
+ } |
|
| 192 |
+ |
|
| 193 |
+ // If its ok w/o any args then try again with an arg |
|
| 194 |
+ if ec == 0 {
|
|
| 195 |
+ args = []string{cmd, "badArg"}
|
|
| 196 |
+ dCmd = exec.Command(dockerBinary, args...) |
|
| 197 |
+ stdout, stderr, ec, err = runCommandWithStdoutStderr(dCmd) |
|
| 198 |
+ } |
|
| 199 |
+ |
|
| 200 |
+ if len(stdout) != 0 || len(stderr) == 0 || ec == 0 || err == nil {
|
|
| 201 |
+ c.Fatalf("Bad output from %q\nstdout:%q\nstderr:%q\nec:%d\nerr:%q", args, stdout, stderr, ec, err)
|
|
| 202 |
+ } |
|
| 203 |
+ // Should have just short usage |
|
| 204 |
+ if !strings.Contains(stderr, "\nUsage: ") {
|
|
| 205 |
+ c.Fatalf("Missing short usage on %q\nstderr:%q", args, stderr)
|
|
| 206 |
+ } |
|
| 207 |
+ // But shouldn't have full usage |
|
| 208 |
+ if strings.Contains(stderr, "--help=false") {
|
|
| 209 |
+ c.Fatalf("Should not have full usage on %q\nstderr:%q", args, stderr)
|
|
| 210 |
+ } |
|
| 211 |
+ if strings.HasSuffix(stderr, "\n\n") {
|
|
| 212 |
+ c.Fatalf("Should not have a blank line on %q\nstderr:%q", args, stderr)
|
|
| 213 |
+ } |
|
| 214 |
+ } |
|
| 215 |
+ |
|
| 145 | 216 |
} |
| 146 | 217 |
|
| 147 | 218 |
expected := 39 |
| ... | ... |
@@ -153,31 +238,92 @@ func (s *DockerSuite) TestHelpTextVerify(c *check.C) {
|
| 153 | 153 |
|
| 154 | 154 |
} |
| 155 | 155 |
|
| 156 |
-func (s *DockerSuite) TestHelpErrorStderr(c *check.C) {
|
|
| 157 |
- // If we had a generic CLI test file this one shoudl go in there |
|
| 156 |
+func (s *DockerSuite) TestHelpExitCodesHelpOutput(c *check.C) {
|
|
| 157 |
+ // Test to make sure the exit code and output (stdout vs stderr) of |
|
| 158 |
+ // various good and bad cases are what we expect |
|
| 158 | 159 |
|
| 159 |
- cmd := exec.Command(dockerBinary, "boogie") |
|
| 160 |
- out, ec, err := runCommandWithOutput(cmd) |
|
| 161 |
- if err == nil || ec == 0 {
|
|
| 162 |
- c.Fatalf("Boogie command should have failed")
|
|
| 160 |
+ // docker : stdout=all, stderr=empty, rc=0 |
|
| 161 |
+ cmd := exec.Command(dockerBinary) |
|
| 162 |
+ stdout, stderr, ec, err := runCommandWithStdoutStderr(cmd) |
|
| 163 |
+ if len(stdout) == 0 || len(stderr) != 0 || ec != 0 || err != nil {
|
|
| 164 |
+ c.Fatalf("Bad results from 'docker'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", ec, stdout, stderr, err)
|
|
| 165 |
+ } |
|
| 166 |
+ // Be really pick |
|
| 167 |
+ if strings.HasSuffix(stdout, "\n\n") {
|
|
| 168 |
+ c.Fatalf("Should not have a blank line at the end of 'docker'\n%s", stdout)
|
|
| 163 | 169 |
} |
| 164 | 170 |
|
| 165 |
- expected := "docker: 'boogie' is not a docker command. See 'docker --help'.\n" |
|
| 166 |
- if out != expected {
|
|
| 167 |
- c.Fatalf("Bad output from boogie\nGot:%s\nExpected:%s", out, expected)
|
|
| 171 |
+ // docker help: stdout=all, stderr=empty, rc=0 |
|
| 172 |
+ cmd = exec.Command(dockerBinary, "help") |
|
| 173 |
+ stdout, stderr, ec, err = runCommandWithStdoutStderr(cmd) |
|
| 174 |
+ if len(stdout) == 0 || len(stderr) != 0 || ec != 0 || err != nil {
|
|
| 175 |
+ c.Fatalf("Bad results from 'docker help'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", ec, stdout, stderr, err)
|
|
| 176 |
+ } |
|
| 177 |
+ // Be really pick |
|
| 178 |
+ if strings.HasSuffix(stdout, "\n\n") {
|
|
| 179 |
+ c.Fatalf("Should not have a blank line at the end of 'docker help'\n%s", stdout)
|
|
| 168 | 180 |
} |
| 169 | 181 |
|
| 170 |
- cmd = exec.Command(dockerBinary, "rename", "foo", "bar") |
|
| 171 |
- out, ec, err = runCommandWithOutput(cmd) |
|
| 172 |
- if err == nil || ec == 0 {
|
|
| 173 |
- c.Fatalf("Rename should have failed")
|
|
| 182 |
+ // docker --help: stdout=all, stderr=empty, rc=0 |
|
| 183 |
+ cmd = exec.Command(dockerBinary, "--help") |
|
| 184 |
+ stdout, stderr, ec, err = runCommandWithStdoutStderr(cmd) |
|
| 185 |
+ if len(stdout) == 0 || len(stderr) != 0 || ec != 0 || err != nil {
|
|
| 186 |
+ c.Fatalf("Bad results from 'docker --help'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", ec, stdout, stderr, err)
|
|
| 187 |
+ } |
|
| 188 |
+ // Be really pick |
|
| 189 |
+ if strings.HasSuffix(stdout, "\n\n") {
|
|
| 190 |
+ c.Fatalf("Should not have a blank line at the end of 'docker --help'\n%s", stdout)
|
|
| 174 | 191 |
} |
| 175 | 192 |
|
| 176 |
- expected = `Error response from daemon: no such id: foo |
|
| 177 |
-Error: failed to rename container named foo |
|
| 178 |
-` |
|
| 179 |
- if out != expected {
|
|
| 180 |
- c.Fatalf("Bad output from rename\nGot:%s\nExpected:%s", out, expected)
|
|
| 193 |
+ // docker inspect busybox: stdout=all, stderr=empty, rc=0 |
|
| 194 |
+ // Just making sure stderr is empty on valid cmd |
|
| 195 |
+ cmd = exec.Command(dockerBinary, "inspect", "busybox") |
|
| 196 |
+ stdout, stderr, ec, err = runCommandWithStdoutStderr(cmd) |
|
| 197 |
+ if len(stdout) == 0 || len(stderr) != 0 || ec != 0 || err != nil {
|
|
| 198 |
+ c.Fatalf("Bad results from 'docker inspect busybox'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", ec, stdout, stderr, err)
|
|
| 199 |
+ } |
|
| 200 |
+ // Be really pick |
|
| 201 |
+ if strings.HasSuffix(stdout, "\n\n") {
|
|
| 202 |
+ c.Fatalf("Should not have a blank line at the end of 'docker inspect busyBox'\n%s", stdout)
|
|
| 203 |
+ } |
|
| 204 |
+ |
|
| 205 |
+ // docker rm: stdout=empty, stderr=all, rc!=0 |
|
| 206 |
+ // testing the min arg error msg |
|
| 207 |
+ cmd = exec.Command(dockerBinary, "rm") |
|
| 208 |
+ stdout, stderr, ec, err = runCommandWithStdoutStderr(cmd) |
|
| 209 |
+ if len(stdout) != 0 || len(stderr) == 0 || ec == 0 || err == nil {
|
|
| 210 |
+ c.Fatalf("Bad results from 'docker rm'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", ec, stdout, stderr, err)
|
|
| 211 |
+ } |
|
| 212 |
+ // Should not contain full help text but should contain info about |
|
| 213 |
+ // # of args and Usage line |
|
| 214 |
+ if !strings.Contains(stderr, "requires a minimum") {
|
|
| 215 |
+ c.Fatalf("Missing # of args text from 'docker rm'\nstderr:%s", stderr)
|
|
| 216 |
+ } |
|
| 217 |
+ |
|
| 218 |
+ // docker rm NoSuchContainer: stdout=empty, stderr=all, rc=0 |
|
| 219 |
+ // testing to make sure no blank line on error |
|
| 220 |
+ cmd = exec.Command(dockerBinary, "rm", "NoSuchContainer") |
|
| 221 |
+ stdout, stderr, ec, err = runCommandWithStdoutStderr(cmd) |
|
| 222 |
+ if len(stdout) != 0 || len(stderr) == 0 || ec == 0 || err == nil {
|
|
| 223 |
+ c.Fatalf("Bad results from 'docker rm NoSuchContainer'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", ec, stdout, stderr, err)
|
|
| 224 |
+ } |
|
| 225 |
+ // Be really picky |
|
| 226 |
+ if strings.HasSuffix(stderr, "\n\n") {
|
|
| 227 |
+ c.Fatalf("Should not have a blank line at the end of 'docker rm'\n%s", stderr)
|
|
| 228 |
+ } |
|
| 229 |
+ |
|
| 230 |
+ // docker BadCmd: stdout=empty, stderr=all, rc=0 |
|
| 231 |
+ cmd = exec.Command(dockerBinary, "BadCmd") |
|
| 232 |
+ stdout, stderr, ec, err = runCommandWithStdoutStderr(cmd) |
|
| 233 |
+ if len(stdout) != 0 || len(stderr) == 0 || ec == 0 || err == nil {
|
|
| 234 |
+ c.Fatalf("Bad results from 'docker BadCmd'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", ec, stdout, stderr, err)
|
|
| 235 |
+ } |
|
| 236 |
+ if stderr != "docker: 'BadCmd' is not a docker command.\nSee 'docker --help'.\n" {
|
|
| 237 |
+ c.Fatalf("Unexcepted output for 'docker badCmd'\nstderr:%s", stderr)
|
|
| 238 |
+ } |
|
| 239 |
+ // Be really picky |
|
| 240 |
+ if strings.HasSuffix(stderr, "\n\n") {
|
|
| 241 |
+ c.Fatalf("Should not have a blank line at the end of 'docker rm'\n%s", stderr)
|
|
| 181 | 242 |
} |
| 182 | 243 |
|
| 183 | 244 |
} |
| ... | ... |
@@ -512,6 +512,12 @@ func (f *FlagSet) PrintDefaults() {
|
| 512 | 512 |
if runtime.GOOS != "windows" && home == "/" {
|
| 513 | 513 |
home = "" |
| 514 | 514 |
} |
| 515 |
+ |
|
| 516 |
+ // Add a blank line between cmd description and list of options |
|
| 517 |
+ if f.FlagCount() > 0 {
|
|
| 518 |
+ fmt.Fprintln(writer, "") |
|
| 519 |
+ } |
|
| 520 |
+ |
|
| 515 | 521 |
f.VisitAll(func(flag *Flag) {
|
| 516 | 522 |
format := " -%s=%s" |
| 517 | 523 |
names := []string{}
|
| ... | ... |
@@ -1074,11 +1080,12 @@ func (cmd *FlagSet) ParseFlags(args []string, withHelp bool) error {
|
| 1074 | 1074 |
return err |
| 1075 | 1075 |
} |
| 1076 | 1076 |
if help != nil && *help {
|
| 1077 |
+ cmd.SetOutput(os.Stdout) |
|
| 1077 | 1078 |
cmd.Usage() |
| 1078 |
- // just in case Usage does not exit |
|
| 1079 | 1079 |
os.Exit(0) |
| 1080 | 1080 |
} |
| 1081 | 1081 |
if str := cmd.CheckArgs(); str != "" {
|
| 1082 |
+ cmd.SetOutput(os.Stderr) |
|
| 1082 | 1083 |
cmd.ReportError(str, withHelp) |
| 1083 | 1084 |
cmd.ShortUsage() |
| 1084 | 1085 |
os.Exit(1) |
| ... | ... |
@@ -1089,9 +1096,9 @@ func (cmd *FlagSet) ParseFlags(args []string, withHelp bool) error {
|
| 1089 | 1089 |
func (cmd *FlagSet) ReportError(str string, withHelp bool) {
|
| 1090 | 1090 |
if withHelp {
|
| 1091 | 1091 |
if os.Args[0] == cmd.Name() {
|
| 1092 |
- str += ". See '" + os.Args[0] + " --help'" |
|
| 1092 |
+ str += ".\nSee '" + os.Args[0] + " --help'" |
|
| 1093 | 1093 |
} else {
|
| 1094 |
- str += ". See '" + os.Args[0] + " " + cmd.Name() + " --help'" |
|
| 1094 |
+ str += ".\nSee '" + os.Args[0] + " " + cmd.Name() + " --help'" |
|
| 1095 | 1095 |
} |
| 1096 | 1096 |
} |
| 1097 | 1097 |
fmt.Fprintf(cmd.Out(), "docker: %s.\n", str) |