From 60cb5f1a34147250a46b623964f88823a40a0280 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Tue, 19 Nov 2013 09:41:10 -0500 Subject: [PATCH 1/8] do not setup bridge ip if bridgeNetwork is nil. This is the case when -b='none' bridge is provided. issue #2768 https://bugzilla.redhat.com/show_bug.cgi?id=1032094 --- server.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server.go b/server.go index 79ead96a62..f9bd8844e7 100644 --- a/server.go +++ b/server.go @@ -63,7 +63,10 @@ func jobInitApi(job *engine.Job) string { }() job.Eng.Hack_SetGlobalVar("httpapi.server", srv) job.Eng.Hack_SetGlobalVar("httpapi.runtime", srv.runtime) - job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", srv.runtime.networkManager.bridgeNetwork.IP) + // https://github.com/dotcloud/docker/issues/2768 + if srv.runtime.networkManager.bridgeNetwork != nil { + job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", srv.runtime.networkManager.bridgeNetwork.IP) + } if err := job.Eng.Register("create", srv.ContainerCreate); err != nil { return err.Error() } From 53f1bf0f995d4c0e6af3eb14cd76a1b049c851a2 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 21 Nov 2013 17:51:28 -0800 Subject: [PATCH 2/8] Bump version to v0.6.7 --- CHANGELOG.md | 39 +++++++++++++++++++++++++++++++++++++++ VERSION | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c08cefaad..a1042ba53a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,43 @@ # Changelog +## 0.6.7 (2013-11-21) + +#### Runtime + +* Improved stability, fixes some race conditons +* Skip the volumes mounted when deleting the volumes of container. +* Fix layer size computation: handle hard links correctly +* Use the work Path for docker cp CONTAINER:PATH +* Fix tmp dir never cleanup +* Speedup docker ps +* More informative error message on name collisions +* Fix nameserver regex +* Always return long id's +* Fix container restart race condition +* Keep published ports on docker stop;docker start +* Fix container networking on Fedora +* Correctly express "any address" to iptables +* Fix network setup when reconnecting to ghost container +* Prevent deletion if image is used by a running container +* Lock around read operations in graph + +#### RemoteAPI + +* Return full ID on docker rmi + +#### Client + ++ Add -tree option to images ++ Offline image transfer +* Exit with status 2 on usage error and display usage on stderr +* Do not forward SIGCHLD to container +* Use string timestamp for docker events -since + +#### Other + +* Update to go 1.2rc5 ++ Add /etc/default/docker support to upstart + ## 0.6.6 (2013-11-06) #### Runtime @@ -17,6 +55,7 @@ + Prevent DNS server conflicts in CreateBridgeIface + Validate bind mounts on the server side + Use parent image config in docker build +* Fix regression in /etc/hosts #### Client diff --git a/VERSION b/VERSION index 3e8bed9e92..2228cad41f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.6-dev +0.6.7 From 8b0cd60019b33488f8819da5c7bdd28d1d3fc737 Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Tue, 12 Nov 2013 23:31:45 -0600 Subject: [PATCH 3/8] Pass terminal setting to display utils --- commands.go | 2 +- utils/utils.go | 27 +++++++++++++++++++-------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/commands.go b/commands.go index c514aa28a0..766350409d 100644 --- a/commands.go +++ b/commands.go @@ -2328,7 +2328,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h } if matchesContentType(resp.Header.Get("Content-Type"), "application/json") { - return utils.DisplayJSONMessagesStream(resp.Body, out) + return utils.DisplayJSONMessagesStream(resp.Body, out, cli.isTerminal) } if _, err := io.Copy(out, resp.Body); err != nil { return err diff --git a/utils/utils.go b/utils/utils.go index 5864add8e1..d1cdff31fd 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -779,14 +779,19 @@ func NewHTTPRequestError(msg string, res *http.Response) error { } } -func (jm *JSONMessage) Display(out io.Writer) error { +func (jm *JSONMessage) Display(out io.Writer, isTerminal bool) error { if jm.Error != nil { if jm.Error.Code == 401 { return fmt.Errorf("Authentication is required.") } return jm.Error } - fmt.Fprintf(out, "%c[2K\r", 27) + endl := "" + if isTerminal { + // [2K = erase entire current line + fmt.Fprintf(out, "%c[2K\r", 27) + endl = "\r" + } if jm.Time != 0 { fmt.Fprintf(out, "[%s] ", time.Unix(jm.Time, 0)) } @@ -797,14 +802,14 @@ func (jm *JSONMessage) Display(out io.Writer) error { fmt.Fprintf(out, "(from %s) ", jm.From) } if jm.Progress != "" { - fmt.Fprintf(out, "%s %s\r", jm.Status, jm.Progress) + fmt.Fprintf(out, "%s %s%s", jm.Status, jm.Progress, endl) } else { - fmt.Fprintf(out, "%s\r\n", jm.Status) + fmt.Fprintf(out, "%s%s\n", jm.Status, endl) } return nil } -func DisplayJSONMessagesStream(in io.Reader, out io.Writer) error { +func DisplayJSONMessagesStream(in io.Reader, out io.Writer, isTerminal bool) error { dec := json.NewDecoder(in) ids := make(map[string]int) diff := 0 @@ -825,11 +830,17 @@ func DisplayJSONMessagesStream(in io.Reader, out io.Writer) error { } else { diff = len(ids) - line } - fmt.Fprintf(out, "%c[%dA", 27, diff) + if isTerminal { + // [{diff}A = move cursor up diff rows + fmt.Fprintf(out, "%c[%dA", 27, diff) + } } - err := jm.Display(out) + err := jm.Display(out, isTerminal) if jm.ID != "" { - fmt.Fprintf(out, "%c[%dB", 27, diff) + if isTerminal { + // [{diff}B = move cursor down diff rows + fmt.Fprintf(out, "%c[%dB", 27, diff) + } } if err != nil { return err From 1e7c04fcfe0b1dbc93a8d70e7bf94e963aa764d8 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 22 Nov 2013 11:23:48 -0800 Subject: [PATCH 4/8] fix -rm --- commands.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/commands.go b/commands.go index c514aa28a0..d9759c2894 100644 --- a/commands.go +++ b/commands.go @@ -2123,21 +2123,24 @@ func (cli *DockerCli) CmdRun(args ...string) error { // Detached mode <-wait } else { - running, status, err := getExitCode(cli, runResult.ID) - if err != nil { - return err - } + var status int + if autoRemove { - if running { - return fmt.Errorf("Impossible to auto-remove a detached container") - } - // Wait for the process to if _, _, err := cli.call("POST", "/containers/"+runResult.ID+"/wait", nil); err != nil { return err } + _, status, err = getExitCode(cli, runResult.ID) + if err != nil { + return err + } if _, _, err := cli.call("DELETE", "/containers/"+runResult.ID, nil); err != nil { return err } + } else { + _, status, err = getExitCode(cli, runResult.ID) + if err != nil { + return err + } } if status != 0 { return &utils.StatusError{Status: status} From ef14aaf627377b24edb55fe2080e10f748c0e444 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Fri, 22 Nov 2013 14:28:49 -0500 Subject: [PATCH 5/8] fix the nil pointer panic on closing a disabled network manager Issue #2768 --- network.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/network.go b/network.go index 3864cca8cd..1397de0557 100644 --- a/network.go +++ b/network.go @@ -661,6 +661,9 @@ func (manager *NetworkManager) Allocate() (*NetworkInterface, error) { } func (manager *NetworkManager) Close() error { + if manager.disabled { + return nil + } err1 := manager.tcpPortAllocator.Close() err2 := manager.udpPortAllocator.Close() err3 := manager.ipAllocator.Close() From 476559458d49b6d4eacfdbe9025ddf69947ae2b7 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 22 Nov 2013 10:12:27 -0800 Subject: [PATCH 6/8] Reformatting parseRun and partParse --- commands.go | 112 ++++++++++++++++++++++++------------------------- utils.go | 16 ++++--- utils/utils.go | 8 ++-- 3 files changed, 70 insertions(+), 66 deletions(-) diff --git a/commands.go b/commands.go index ff14b6cb2b..858ae80a6b 100644 --- a/commands.go +++ b/commands.go @@ -1730,60 +1730,59 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, } func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) { + var ( + // FIXME: use utils.ListOpts for attach and volumes? + flAttach = NewAttachOpts() + flVolumes = NewPathOpts() - flHostname := cmd.String("h", "", "Container host name") - flWorkingDir := cmd.String("w", "", "Working directory inside the container") - flUser := cmd.String("u", "", "Username or UID") - flDetach := cmd.Bool("d", false, "Detached mode: Run container in the background, print new container id") - flAttach := NewAttachOpts() + flPublish utils.ListOpts + flExpose utils.ListOpts + flEnv utils.ListOpts + flDns utils.ListOpts + flVolumesFrom utils.ListOpts + flLxcOpts utils.ListOpts + flLinks utils.ListOpts + + flAutoRemove = cmd.Bool("rm", false, "Automatically remove the container when it exits (incompatible with -d)") + flDetach = cmd.Bool("d", false, "Detached mode: Run container in the background, print new container id") + flNetwork = cmd.Bool("n", true, "Enable networking for this container") + flPrivileged = cmd.Bool("privileged", false, "Give extended privileges to this container") + flPublishAll = cmd.Bool("P", false, "Publish all exposed ports to the host interfaces") + flStdin = cmd.Bool("i", false, "Keep stdin open even if not attached") + flTty = cmd.Bool("t", false, "Allocate a pseudo-tty") + flContainerIDFile = cmd.String("cidfile", "", "Write the container ID to the file") + flEntrypoint = cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image") + flHostname = cmd.String("h", "", "Container host name") + flMemoryString = cmd.String("m", "", "Memory limit (format: , where unit = b, k, m or g)") + flUser = cmd.String("u", "", "Username or UID") + flWorkingDir = cmd.String("w", "", "Working directory inside the container") + flCpuShares = cmd.Int64("c", 0, "CPU shares (relative weight)") + + // For documentation purpose + _ = cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)") + _ = cmd.String("name", "", "Assign a name to the container") + ) cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.") - flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached") - flTty := cmd.Bool("t", false, "Allocate a pseudo-tty") - flMemoryString := cmd.String("m", "", "Memory limit (format: , where unit = b, k, m or g)") - flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file") - flNetwork := cmd.Bool("n", true, "Enable networking for this container") - flPrivileged := cmd.Bool("privileged", false, "Give extended privileges to this container") - flAutoRemove := cmd.Bool("rm", false, "Automatically remove the container when it exits (incompatible with -d)") - cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)") - cmd.String("name", "", "Assign a name to the container") - flPublishAll := cmd.Bool("P", false, "Publish all exposed ports to the host interfaces") - - if capabilities != nil && *flMemoryString != "" && !capabilities.MemoryLimit { - //fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") - *flMemoryString = "" - } - - flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)") - - var flPublish utils.ListOpts - cmd.Var(&flPublish, "p", "Publish a container's port to the host (use 'docker port' to see the actual mapping)") - - var flExpose utils.ListOpts - cmd.Var(&flExpose, "expose", "Expose a port from the container without publishing it to your host") - - var flEnv utils.ListOpts - cmd.Var(&flEnv, "e", "Set environment variables") - - var flDns utils.ListOpts - cmd.Var(&flDns, "dns", "Set custom dns servers") - - flVolumes := NewPathOpts() cmd.Var(flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)") - var flVolumesFrom utils.ListOpts + cmd.Var(&flPublish, "p", "Publish a container's port to the host (use 'docker port' to see the actual mapping)") + cmd.Var(&flExpose, "expose", "Expose a port from the container without publishing it to your host") + cmd.Var(&flEnv, "e", "Set environment variables") + cmd.Var(&flDns, "dns", "Set custom dns servers") cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container(s)") - - flEntrypoint := cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image") - - var flLxcOpts utils.ListOpts cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"") - - var flLinks utils.ListOpts cmd.Var(&flLinks, "link", "Add link to another container (name:alias)") if err := cmd.Parse(args); err != nil { return nil, nil, cmd, err } + + // Check if the kernel supports memory limit cgroup. + if capabilities != nil && *flMemoryString != "" && !capabilities.MemoryLimit { + *flMemoryString = "" + } + + // Validate input params if *flDetach && len(flAttach) > 0 { return nil, nil, cmd, ErrConflictAttachDetach } @@ -1805,8 +1804,7 @@ func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Co } } - envs := []string{} - + var envs []string for _, env := range flEnv { arr := strings.Split(env, "=") if len(arr) > 1 { @@ -1818,19 +1816,15 @@ func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Co } var flMemory int64 - if *flMemoryString != "" { parsedMemory, err := utils.RAMInBytes(*flMemoryString) - if err != nil { return nil, nil, cmd, err } - flMemory = parsedMemory } var binds []string - // add any bind targets to the list of container volumes for bind := range flVolumes { arr := strings.Split(bind, ":") @@ -1845,10 +1839,12 @@ func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Co } } - parsedArgs := cmd.Args() - runCmd := []string{} - entrypoint := []string{} - image := "" + var ( + parsedArgs = cmd.Args() + runCmd []string + entrypoint []string + image string + ) if len(parsedArgs) >= 1 { image = cmd.Arg(0) } @@ -1859,16 +1855,16 @@ func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Co entrypoint = []string{*flEntrypoint} } - var lxcConf []KeyValuePair lxcConf, err := parseLxcConfOpts(flLxcOpts) if err != nil { return nil, nil, cmd, err } - hostname := *flHostname - domainname := "" - - parts := strings.SplitN(hostname, ".", 2) + var ( + domainname string + hostname = *flHostname + parts = strings.SplitN(hostname, ".", 2) + ) if len(parts) > 1 { hostname = parts[0] domainname = parts[1] diff --git a/utils.go b/utils.go index 82b163c608..367caa2342 100644 --- a/utils.go +++ b/utils.go @@ -209,11 +209,14 @@ func parseLxcOpt(opt string) (string, string, error) { // We will receive port specs in the format of ip:public:private/proto and these need to be // parsed in the internal types func parsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, error) { - exposedPorts := make(map[Port]struct{}, len(ports)) - bindings := make(map[Port][]PortBinding) + var ( + exposedPorts = make(map[Port]struct{}, len(ports)) + bindings = make(map[Port][]PortBinding) + ) for _, rawPort := range ports { proto := "tcp" + if i := strings.LastIndex(rawPort, "/"); i != -1 { proto = rawPort[i+1:] rawPort = rawPort[:i] @@ -228,9 +231,12 @@ func parsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, if err != nil { return nil, nil, err } - containerPort := parts["containerPort"] - rawIp := parts["ip"] - hostPort := parts["hostPort"] + + var ( + containerPort = parts["containerPort"] + rawIp = parts["ip"] + hostPort = parts["hostPort"] + ) if containerPort == "" { return nil, nil, fmt.Errorf("No port specified: %s", rawPort) diff --git a/utils/utils.go b/utils/utils.go index d1cdff31fd..a20cdc585a 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1237,12 +1237,14 @@ func IsClosedError(err error) bool { func PartParser(template, data string) (map[string]string, error) { // ip:public:private - templateParts := strings.Split(template, ":") - parts := strings.Split(data, ":") + var ( + templateParts = strings.Split(template, ":") + parts = strings.Split(data, ":") + out = make(map[string]string, len(templateParts)) + ) if len(parts) != len(templateParts) { return nil, fmt.Errorf("Invalid format to parse. %s should match template %s", data, template) } - out := make(map[string]string, len(templateParts)) for i, t := range templateParts { value := "" From 1f9223a7c2c0e5c04936c56a071830f8792c3dd7 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 22 Nov 2013 10:36:49 -0800 Subject: [PATCH 7/8] Use a constant for PortSpecTemplate + display the template in the CmdRun help --- commands.go | 3 ++- utils.go | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/commands.go b/commands.go index 858ae80a6b..7b1b664e94 100644 --- a/commands.go +++ b/commands.go @@ -1762,10 +1762,11 @@ func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Co _ = cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)") _ = cmd.String("name", "", "Assign a name to the container") ) + cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.") cmd.Var(flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)") - cmd.Var(&flPublish, "p", "Publish a container's port to the host (use 'docker port' to see the actual mapping)") + cmd.Var(&flPublish, "p", fmt.Sprintf("Publish a container's port to the host (format: %s) (use 'docker port' to see the actual mapping)", PortSpecTemplateFormat)) cmd.Var(&flExpose, "expose", "Expose a port from the container without publishing it to your host") cmd.Var(&flEnv, "e", "Set environment variables") cmd.Var(&flDns, "dns", "Set custom dns servers") diff --git a/utils.go b/utils.go index 367caa2342..45d5afe04d 100644 --- a/utils.go +++ b/utils.go @@ -206,6 +206,12 @@ func parseLxcOpt(opt string) (string, string, error) { return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil } +// FIXME: network related stuff (including parsing) should be grouped in network file +const ( + PortSpecTemplate = "ip:hostPort:containerPort" + PortSpecTemplateFormat = "ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort" +) + // We will receive port specs in the format of ip:public:private/proto and these need to be // parsed in the internal types func parsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, error) { @@ -227,7 +233,7 @@ func parsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, rawPort = fmt.Sprintf(":%s", rawPort) } - parts, err := utils.PartParser("ip:hostPort:containerPort", rawPort) + parts, err := utils.PartParser(PortSpecTemplate, rawPort) if err != nil { return nil, nil, err } From 076c0eab704e5c4819611a42b1551f1d759fb376 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 22 Nov 2013 12:14:34 -0800 Subject: [PATCH 8/8] Format CmdRun --- commands.go | 115 +++++++++++++++++++++++++++------------------------- 1 file changed, 59 insertions(+), 56 deletions(-) diff --git a/commands.go b/commands.go index 7b1b664e94..74cbe74600 100644 --- a/commands.go +++ b/commands.go @@ -1943,30 +1943,33 @@ func (cli *DockerCli) CmdRun(args ...string) error { return nil } - flRm := cmd.Lookup("rm") - autoRemove, _ := strconv.ParseBool(flRm.Value.String()) + // Retrieve relevant client-side config + var ( + flName = cmd.Lookup("name") + flRm = cmd.Lookup("rm") + flSigProxy = cmd.Lookup("sig-proxy") + autoRemove, _ = strconv.ParseBool(flRm.Value.String()) + sigProxy, _ = strconv.ParseBool(flSigProxy.Value.String()) + ) - flSigProxy := cmd.Lookup("sig-proxy") - sigProxy, _ := strconv.ParseBool(flSigProxy.Value.String()) - flName := cmd.Lookup("name") + // Disable sigProxy in case on TTY if config.Tty { sigProxy = false } - var containerIDFile *os.File + var containerIDFile io.WriteCloser if len(hostConfig.ContainerIDFile) > 0 { - if _, err := ioutil.ReadFile(hostConfig.ContainerIDFile); err == nil { + if _, err := os.Stat(hostConfig.ContainerIDFile); err == nil { return fmt.Errorf("cid file found, make sure the other container isn't running or delete %s", hostConfig.ContainerIDFile) } - containerIDFile, err = os.Create(hostConfig.ContainerIDFile) - if err != nil { + if containerIDFile, err = os.Create(hostConfig.ContainerIDFile); err != nil { return fmt.Errorf("failed to create the container ID file: %s", err) } defer containerIDFile.Close() } + containerValues := url.Values{} - name := flName.Value.String() - if name != "" { + if name := flName.Value.String(); name != "" { containerValues.Set("name", name) } @@ -1987,8 +1990,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { v.Set("tag", tag) // Resolve the Repository name from fqn to endpoint + name - var endpoint string - endpoint, _, err = registry.ResolveRepositoryName(repos) + endpoint, _, err := registry.ResolveRepositoryName(repos) if err != nil { return err } @@ -2006,14 +2008,10 @@ func (cli *DockerCli) CmdRun(args ...string) error { registryAuthHeader := []string{ base64.URLEncoding.EncodeToString(buf), } - err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err, map[string][]string{ - "X-Registry-Auth": registryAuthHeader, - }) - if err != nil { + if err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err, map[string][]string{"X-Registry-Auth": registryAuthHeader}); err != nil { return err } - body, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config) - if err != nil { + if body, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config); err != nil { return err } } @@ -2021,17 +2019,17 @@ func (cli *DockerCli) CmdRun(args ...string) error { return err } - runResult := &APIRun{} - err = json.Unmarshal(body, runResult) - if err != nil { + var runResult APIRun + if err := json.Unmarshal(body, &runResult); err != nil { return err } for _, warning := range runResult.Warnings { fmt.Fprintf(cli.err, "WARNING: %s\n", warning) } + if len(hostConfig.ContainerIDFile) > 0 { - if _, err = containerIDFile.WriteString(runResult.ID); err != nil { + if _, err = containerIDFile.Write([]byte(runResult.ID)); err != nil { return fmt.Errorf("failed to write the container ID to the file: %s", err) } } @@ -2042,27 +2040,29 @@ func (cli *DockerCli) CmdRun(args ...string) error { } var ( - wait chan struct{} - errCh chan error + waitDisplayId chan struct{} + errCh chan error ) if !config.AttachStdout && !config.AttachStderr { // Make this asynchrone in order to let the client write to stdin before having to read the ID - wait = make(chan struct{}) + waitDisplayId = make(chan struct{}) go func() { - defer close(wait) + defer close(waitDisplayId) fmt.Fprintf(cli.out, "%s\n", runResult.ID) }() } + // We need to make the chan because the select needs to have a closing + // chan, it can't be uninitialized hijacked := make(chan bool) - if config.AttachStdin || config.AttachStdout || config.AttachStderr { - - v := url.Values{} + var ( + out, stderr io.Writer + in io.ReadCloser + v = url.Values{} + ) v.Set("stream", "1") - var out, stderr io.Writer - var in io.ReadCloser if config.AttachStdin { v.Set("stdin", "1") @@ -2116,34 +2116,37 @@ func (cli *DockerCli) CmdRun(args ...string) error { } } + // Detached mode: wait for the id to be displayed and return. if !config.AttachStdout && !config.AttachStderr { // Detached mode - <-wait - } else { - var status int - - if autoRemove { - if _, _, err := cli.call("POST", "/containers/"+runResult.ID+"/wait", nil); err != nil { - return err - } - _, status, err = getExitCode(cli, runResult.ID) - if err != nil { - return err - } - if _, _, err := cli.call("DELETE", "/containers/"+runResult.ID, nil); err != nil { - return err - } - } else { - _, status, err = getExitCode(cli, runResult.ID) - if err != nil { - return err - } - } - if status != 0 { - return &utils.StatusError{Status: status} - } + <-waitDisplayId + return nil } + var status int + + // Attached mode + if autoRemove { + // Autoremove: wait for the container to finish, retrieve + // the exit code and remove the container + if _, _, err := cli.call("POST", "/containers/"+runResult.ID+"/wait", nil); err != nil { + return err + } + if _, status, err = getExitCode(cli, runResult.ID); err != nil { + return err + } + if _, _, err := cli.call("DELETE", "/containers/"+runResult.ID, nil); err != nil { + return err + } + } else { + // No Autoremove: Simply retrieve the exit code + if _, status, err = getExitCode(cli, runResult.ID); err != nil { + return err + } + } + if status != 0 { + return &utils.StatusError{Status: status} + } return nil }