Browse code

Merge branch 'master' into 0.6.5-dm-plugin

Michael Crosby authored on 2013/11/23 07:06:59
Showing 7 changed files
... ...
@@ -1,5 +1,43 @@
1 1
 # Changelog
2 2
 
3
+## 0.6.7 (2013-11-21)
4
+
5
+#### Runtime
6
+
7
+* Improved stability, fixes some race conditons
8
+* Skip the volumes mounted when deleting the volumes of container.
9
+* Fix layer size computation: handle hard links correctly
10
+* Use the work Path for docker cp CONTAINER:PATH
11
+* Fix tmp dir never cleanup
12
+* Speedup docker ps
13
+* More informative error message on name collisions
14
+* Fix nameserver regex
15
+* Always return long id's
16
+* Fix container restart race condition
17
+* Keep published ports on docker stop;docker start
18
+* Fix container networking on Fedora
19
+* Correctly express "any address" to iptables
20
+* Fix network setup when reconnecting to ghost container
21
+* Prevent deletion if image is used by a running container
22
+* Lock around read operations in graph
23
+
24
+#### RemoteAPI
25
+
26
+* Return full ID on docker rmi
27
+
28
+#### Client
29
+
30
++ Add -tree option to images
31
++ Offline image transfer
32
+* Exit with status 2 on usage error and display usage on stderr
33
+* Do not forward SIGCHLD to container
34
+* Use string timestamp for docker events -since
35
+
36
+#### Other
37
+
38
+* Update to go 1.2rc5
39
++ Add /etc/default/docker support to upstart
40
+
3 41
 ## 0.6.6 (2013-11-06)
4 42
 
5 43
 #### Runtime
... ...
@@ -17,6 +55,7 @@
17 17
 + Prevent DNS server conflicts in CreateBridgeIface
18 18
 + Validate bind mounts on the server side
19 19
 + Use parent image config in docker build
20
+* Fix regression in /etc/hosts
20 21
 
21 22
 #### Client
22 23
 
... ...
@@ -1 +1 @@
1
-0.6.6-dev
1
+0.6.7-dev
... ...
@@ -1736,60 +1736,60 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
1736 1736
 }
1737 1737
 
1738 1738
 func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
1739
+	var (
1740
+		// FIXME: use utils.ListOpts for attach and volumes?
1741
+		flAttach  = NewAttachOpts()
1742
+		flVolumes = NewPathOpts()
1743
+
1744
+		flPublish     utils.ListOpts
1745
+		flExpose      utils.ListOpts
1746
+		flEnv         utils.ListOpts
1747
+		flDns         utils.ListOpts
1748
+		flVolumesFrom utils.ListOpts
1749
+		flLxcOpts     utils.ListOpts
1750
+		flLinks       utils.ListOpts
1751
+
1752
+		flAutoRemove      = cmd.Bool("rm", false, "Automatically remove the container when it exits (incompatible with -d)")
1753
+		flDetach          = cmd.Bool("d", false, "Detached mode: Run container in the background, print new container id")
1754
+		flNetwork         = cmd.Bool("n", true, "Enable networking for this container")
1755
+		flPrivileged      = cmd.Bool("privileged", false, "Give extended privileges to this container")
1756
+		flPublishAll      = cmd.Bool("P", false, "Publish all exposed ports to the host interfaces")
1757
+		flStdin           = cmd.Bool("i", false, "Keep stdin open even if not attached")
1758
+		flTty             = cmd.Bool("t", false, "Allocate a pseudo-tty")
1759
+		flContainerIDFile = cmd.String("cidfile", "", "Write the container ID to the file")
1760
+		flEntrypoint      = cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image")
1761
+		flHostname        = cmd.String("h", "", "Container host name")
1762
+		flMemoryString    = cmd.String("m", "", "Memory limit (format: <number><optional unit>, where unit = b, k, m or g)")
1763
+		flUser            = cmd.String("u", "", "Username or UID")
1764
+		flWorkingDir      = cmd.String("w", "", "Working directory inside the container")
1765
+		flCpuShares       = cmd.Int64("c", 0, "CPU shares (relative weight)")
1766
+
1767
+		// For documentation purpose
1768
+		_ = cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)")
1769
+		_ = cmd.String("name", "", "Assign a name to the container")
1770
+	)
1739 1771
 
1740
-	flHostname := cmd.String("h", "", "Container host name")
1741
-	flWorkingDir := cmd.String("w", "", "Working directory inside the container")
1742
-	flUser := cmd.String("u", "", "Username or UID")
1743
-	flDetach := cmd.Bool("d", false, "Detached mode: Run container in the background, print new container id")
1744
-	flAttach := NewAttachOpts()
1745 1772
 	cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.")
1746
-	flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
1747
-	flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
1748
-	flMemoryString := cmd.String("m", "", "Memory limit (format: <number><optional unit>, where unit = b, k, m or g)")
1749
-	flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file")
1750
-	flNetwork := cmd.Bool("n", true, "Enable networking for this container")
1751
-	flPrivileged := cmd.Bool("privileged", false, "Give extended privileges to this container")
1752
-	flAutoRemove := cmd.Bool("rm", false, "Automatically remove the container when it exits (incompatible with -d)")
1753
-	cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)")
1754
-	cmd.String("name", "", "Assign a name to the container")
1755
-	flPublishAll := cmd.Bool("P", false, "Publish all exposed ports to the host interfaces")
1756
-
1757
-	if capabilities != nil && *flMemoryString != "" && !capabilities.MemoryLimit {
1758
-		//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
1759
-		*flMemoryString = ""
1760
-	}
1761
-
1762
-	flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)")
1763
-
1764
-	var flPublish utils.ListOpts
1765
-	cmd.Var(&flPublish, "p", "Publish a container's port to the host (use 'docker port' to see the actual mapping)")
1773
+	cmd.Var(flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)")
1766 1774
 
1767
-	var flExpose utils.ListOpts
1775
+	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))
1768 1776
 	cmd.Var(&flExpose, "expose", "Expose a port from the container without publishing it to your host")
1769
-
1770
-	var flEnv utils.ListOpts
1771 1777
 	cmd.Var(&flEnv, "e", "Set environment variables")
1772
-
1773
-	var flDns utils.ListOpts
1774 1778
 	cmd.Var(&flDns, "dns", "Set custom dns servers")
1775
-
1776
-	flVolumes := NewPathOpts()
1777
-	cmd.Var(flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)")
1778
-
1779
-	var flVolumesFrom utils.ListOpts
1780 1779
 	cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
1781
-
1782
-	flEntrypoint := cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image")
1783
-
1784
-	var flLxcOpts utils.ListOpts
1785 1780
 	cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
1786
-
1787
-	var flLinks utils.ListOpts
1788 1781
 	cmd.Var(&flLinks, "link", "Add link to another container (name:alias)")
1789 1782
 
1790 1783
 	if err := cmd.Parse(args); err != nil {
1791 1784
 		return nil, nil, cmd, err
1792 1785
 	}
1786
+
1787
+	// Check if the kernel supports memory limit cgroup.
1788
+	if capabilities != nil && *flMemoryString != "" && !capabilities.MemoryLimit {
1789
+		*flMemoryString = ""
1790
+	}
1791
+
1792
+	// Validate input params
1793 1793
 	if *flDetach && len(flAttach) > 0 {
1794 1794
 		return nil, nil, cmd, ErrConflictAttachDetach
1795 1795
 	}
... ...
@@ -1811,8 +1811,7 @@ func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Co
1811 1811
 		}
1812 1812
 	}
1813 1813
 
1814
-	envs := []string{}
1815
-
1814
+	var envs []string
1816 1815
 	for _, env := range flEnv {
1817 1816
 		arr := strings.Split(env, "=")
1818 1817
 		if len(arr) > 1 {
... ...
@@ -1824,19 +1823,15 @@ func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Co
1824 1824
 	}
1825 1825
 
1826 1826
 	var flMemory int64
1827
-
1828 1827
 	if *flMemoryString != "" {
1829 1828
 		parsedMemory, err := utils.RAMInBytes(*flMemoryString)
1830
-
1831 1829
 		if err != nil {
1832 1830
 			return nil, nil, cmd, err
1833 1831
 		}
1834
-
1835 1832
 		flMemory = parsedMemory
1836 1833
 	}
1837 1834
 
1838 1835
 	var binds []string
1839
-
1840 1836
 	// add any bind targets to the list of container volumes
1841 1837
 	for bind := range flVolumes {
1842 1838
 		arr := strings.Split(bind, ":")
... ...
@@ -1851,10 +1846,12 @@ func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Co
1851 1851
 		}
1852 1852
 	}
1853 1853
 
1854
-	parsedArgs := cmd.Args()
1855
-	runCmd := []string{}
1856
-	entrypoint := []string{}
1857
-	image := ""
1854
+	var (
1855
+		parsedArgs = cmd.Args()
1856
+		runCmd     []string
1857
+		entrypoint []string
1858
+		image      string
1859
+	)
1858 1860
 	if len(parsedArgs) >= 1 {
1859 1861
 		image = cmd.Arg(0)
1860 1862
 	}
... ...
@@ -1865,16 +1862,16 @@ func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Co
1865 1865
 		entrypoint = []string{*flEntrypoint}
1866 1866
 	}
1867 1867
 
1868
-	var lxcConf []KeyValuePair
1869 1868
 	lxcConf, err := parseLxcConfOpts(flLxcOpts)
1870 1869
 	if err != nil {
1871 1870
 		return nil, nil, cmd, err
1872 1871
 	}
1873 1872
 
1874
-	hostname := *flHostname
1875
-	domainname := ""
1876
-
1877
-	parts := strings.SplitN(hostname, ".", 2)
1873
+	var (
1874
+		domainname string
1875
+		hostname   = *flHostname
1876
+		parts      = strings.SplitN(hostname, ".", 2)
1877
+	)
1878 1878
 	if len(parts) > 1 {
1879 1879
 		hostname = parts[0]
1880 1880
 		domainname = parts[1]
... ...
@@ -1952,30 +1949,33 @@ func (cli *DockerCli) CmdRun(args ...string) error {
1952 1952
 		return nil
1953 1953
 	}
1954 1954
 
1955
-	flRm := cmd.Lookup("rm")
1956
-	autoRemove, _ := strconv.ParseBool(flRm.Value.String())
1955
+	// Retrieve relevant client-side config
1956
+	var (
1957
+		flName        = cmd.Lookup("name")
1958
+		flRm          = cmd.Lookup("rm")
1959
+		flSigProxy    = cmd.Lookup("sig-proxy")
1960
+		autoRemove, _ = strconv.ParseBool(flRm.Value.String())
1961
+		sigProxy, _   = strconv.ParseBool(flSigProxy.Value.String())
1962
+	)
1957 1963
 
1958
-	flSigProxy := cmd.Lookup("sig-proxy")
1959
-	sigProxy, _ := strconv.ParseBool(flSigProxy.Value.String())
1960
-	flName := cmd.Lookup("name")
1964
+	// Disable sigProxy in case on TTY
1961 1965
 	if config.Tty {
1962 1966
 		sigProxy = false
1963 1967
 	}
1964 1968
 
1965
-	var containerIDFile *os.File
1969
+	var containerIDFile io.WriteCloser
1966 1970
 	if len(hostConfig.ContainerIDFile) > 0 {
1967
-		if _, err := ioutil.ReadFile(hostConfig.ContainerIDFile); err == nil {
1971
+		if _, err := os.Stat(hostConfig.ContainerIDFile); err == nil {
1968 1972
 			return fmt.Errorf("cid file found, make sure the other container isn't running or delete %s", hostConfig.ContainerIDFile)
1969 1973
 		}
1970
-		containerIDFile, err = os.Create(hostConfig.ContainerIDFile)
1971
-		if err != nil {
1974
+		if containerIDFile, err = os.Create(hostConfig.ContainerIDFile); err != nil {
1972 1975
 			return fmt.Errorf("failed to create the container ID file: %s", err)
1973 1976
 		}
1974 1977
 		defer containerIDFile.Close()
1975 1978
 	}
1979
+
1976 1980
 	containerValues := url.Values{}
1977
-	name := flName.Value.String()
1978
-	if name != "" {
1981
+	if name := flName.Value.String(); name != "" {
1979 1982
 		containerValues.Set("name", name)
1980 1983
 	}
1981 1984
 
... ...
@@ -1996,8 +1996,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
1996 1996
 		v.Set("tag", tag)
1997 1997
 
1998 1998
 		// Resolve the Repository name from fqn to endpoint + name
1999
-		var endpoint string
2000
-		endpoint, _, err = registry.ResolveRepositoryName(repos)
1999
+		endpoint, _, err := registry.ResolveRepositoryName(repos)
2001 2000
 		if err != nil {
2002 2001
 			return err
2003 2002
 		}
... ...
@@ -2015,14 +2014,10 @@ func (cli *DockerCli) CmdRun(args ...string) error {
2015 2015
 		registryAuthHeader := []string{
2016 2016
 			base64.URLEncoding.EncodeToString(buf),
2017 2017
 		}
2018
-		err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err, map[string][]string{
2019
-			"X-Registry-Auth": registryAuthHeader,
2020
-		})
2021
-		if err != nil {
2018
+		if err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err, map[string][]string{"X-Registry-Auth": registryAuthHeader}); err != nil {
2022 2019
 			return err
2023 2020
 		}
2024
-		body, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config)
2025
-		if err != nil {
2021
+		if body, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config); err != nil {
2026 2022
 			return err
2027 2023
 		}
2028 2024
 	}
... ...
@@ -2030,17 +2025,17 @@ func (cli *DockerCli) CmdRun(args ...string) error {
2030 2030
 		return err
2031 2031
 	}
2032 2032
 
2033
-	runResult := &APIRun{}
2034
-	err = json.Unmarshal(body, runResult)
2035
-	if err != nil {
2033
+	var runResult APIRun
2034
+	if err := json.Unmarshal(body, &runResult); err != nil {
2036 2035
 		return err
2037 2036
 	}
2038 2037
 
2039 2038
 	for _, warning := range runResult.Warnings {
2040 2039
 		fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
2041 2040
 	}
2041
+
2042 2042
 	if len(hostConfig.ContainerIDFile) > 0 {
2043
-		if _, err = containerIDFile.WriteString(runResult.ID); err != nil {
2043
+		if _, err = containerIDFile.Write([]byte(runResult.ID)); err != nil {
2044 2044
 			return fmt.Errorf("failed to write the container ID to the file: %s", err)
2045 2045
 		}
2046 2046
 	}
... ...
@@ -2051,27 +2046,29 @@ func (cli *DockerCli) CmdRun(args ...string) error {
2051 2051
 	}
2052 2052
 
2053 2053
 	var (
2054
-		wait  chan struct{}
2055
-		errCh chan error
2054
+		waitDisplayId chan struct{}
2055
+		errCh         chan error
2056 2056
 	)
2057 2057
 
2058 2058
 	if !config.AttachStdout && !config.AttachStderr {
2059 2059
 		// Make this asynchrone in order to let the client write to stdin before having to read the ID
2060
-		wait = make(chan struct{})
2060
+		waitDisplayId = make(chan struct{})
2061 2061
 		go func() {
2062
-			defer close(wait)
2062
+			defer close(waitDisplayId)
2063 2063
 			fmt.Fprintf(cli.out, "%s\n", runResult.ID)
2064 2064
 		}()
2065 2065
 	}
2066 2066
 
2067
+	// We need to make the chan because the select needs to have a closing
2068
+	// chan, it can't be uninitialized
2067 2069
 	hijacked := make(chan bool)
2068
-
2069 2070
 	if config.AttachStdin || config.AttachStdout || config.AttachStderr {
2070
-
2071
-		v := url.Values{}
2071
+		var (
2072
+			out, stderr io.Writer
2073
+			in          io.ReadCloser
2074
+			v           = url.Values{}
2075
+		)
2072 2076
 		v.Set("stream", "1")
2073
-		var out, stderr io.Writer
2074
-		var in io.ReadCloser
2075 2077
 
2076 2078
 		if config.AttachStdin {
2077 2079
 			v.Set("stdin", "1")
... ...
@@ -2125,31 +2122,37 @@ func (cli *DockerCli) CmdRun(args ...string) error {
2125 2125
 		}
2126 2126
 	}
2127 2127
 
2128
+	// Detached mode: wait for the id to be displayed and return.
2128 2129
 	if !config.AttachStdout && !config.AttachStderr {
2129 2130
 		// Detached mode
2130
-		<-wait
2131
-	} else {
2132
-		running, status, err := getExitCode(cli, runResult.ID)
2133
-		if err != nil {
2131
+		<-waitDisplayId
2132
+		return nil
2133
+	}
2134
+
2135
+	var status int
2136
+
2137
+	// Attached mode
2138
+	if autoRemove {
2139
+		// Autoremove: wait for the container to finish, retrieve
2140
+		// the exit code and remove the container
2141
+		if _, _, err := cli.call("POST", "/containers/"+runResult.ID+"/wait", nil); err != nil {
2134 2142
 			return err
2135 2143
 		}
2136
-		if autoRemove {
2137
-			if running {
2138
-				return fmt.Errorf("Impossible to auto-remove a detached container")
2139
-			}
2140
-			// Wait for the process to
2141
-			if _, _, err := cli.call("POST", "/containers/"+runResult.ID+"/wait", nil); err != nil {
2142
-				return err
2143
-			}
2144
-			if _, _, err := cli.call("DELETE", "/containers/"+runResult.ID, nil); err != nil {
2145
-				return err
2146
-			}
2144
+		if _, status, err = getExitCode(cli, runResult.ID); err != nil {
2145
+			return err
2147 2146
 		}
2148
-		if status != 0 {
2149
-			return &utils.StatusError{Status: status}
2147
+		if _, _, err := cli.call("DELETE", "/containers/"+runResult.ID, nil); err != nil {
2148
+			return err
2149
+		}
2150
+	} else {
2151
+		// No Autoremove: Simply retrieve the exit code
2152
+		if _, status, err = getExitCode(cli, runResult.ID); err != nil {
2153
+			return err
2150 2154
 		}
2151 2155
 	}
2152
-
2156
+	if status != 0 {
2157
+		return &utils.StatusError{Status: status}
2158
+	}
2153 2159
 	return nil
2154 2160
 }
2155 2161
 
... ...
@@ -2334,7 +2337,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h
2334 2334
 	}
2335 2335
 
2336 2336
 	if matchesContentType(resp.Header.Get("Content-Type"), "application/json") {
2337
-		return utils.DisplayJSONMessagesStream(resp.Body, out)
2337
+		return utils.DisplayJSONMessagesStream(resp.Body, out, cli.isTerminal)
2338 2338
 	}
2339 2339
 	if _, err := io.Copy(out, resp.Body); err != nil {
2340 2340
 		return err
... ...
@@ -661,6 +661,9 @@ func (manager *NetworkManager) Allocate() (*NetworkInterface, error) {
661 661
 }
662 662
 
663 663
 func (manager *NetworkManager) Close() error {
664
+	if manager.disabled {
665
+		return nil
666
+	}
664 667
 	err1 := manager.tcpPortAllocator.Close()
665 668
 	err2 := manager.udpPortAllocator.Close()
666 669
 	err3 := manager.ipAllocator.Close()
... ...
@@ -63,7 +63,10 @@ func jobInitApi(job *engine.Job) string {
63 63
 	}()
64 64
 	job.Eng.Hack_SetGlobalVar("httpapi.server", srv)
65 65
 	job.Eng.Hack_SetGlobalVar("httpapi.runtime", srv.runtime)
66
-	job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", srv.runtime.networkManager.bridgeNetwork.IP)
66
+	// https://github.com/dotcloud/docker/issues/2768
67
+	if srv.runtime.networkManager.bridgeNetwork != nil {
68
+		job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", srv.runtime.networkManager.bridgeNetwork.IP)
69
+	}
67 70
 	if err := job.Eng.Register("create", srv.ContainerCreate); err != nil {
68 71
 		return err.Error()
69 72
 	}
... ...
@@ -235,14 +235,23 @@ func parseLxcOpt(opt string) (string, string, error) {
235 235
 	return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil
236 236
 }
237 237
 
238
+// FIXME: network related stuff (including parsing) should be grouped in network file
239
+const (
240
+	PortSpecTemplate       = "ip:hostPort:containerPort"
241
+	PortSpecTemplateFormat = "ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort"
242
+)
243
+
238 244
 // We will receive port specs in the format of ip:public:private/proto and these need to be
239 245
 // parsed in the internal types
240 246
 func parsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, error) {
241
-	exposedPorts := make(map[Port]struct{}, len(ports))
242
-	bindings := make(map[Port][]PortBinding)
247
+	var (
248
+		exposedPorts = make(map[Port]struct{}, len(ports))
249
+		bindings     = make(map[Port][]PortBinding)
250
+	)
243 251
 
244 252
 	for _, rawPort := range ports {
245 253
 		proto := "tcp"
254
+
246 255
 		if i := strings.LastIndex(rawPort, "/"); i != -1 {
247 256
 			proto = rawPort[i+1:]
248 257
 			rawPort = rawPort[:i]
... ...
@@ -253,13 +262,16 @@ func parsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding,
253 253
 			rawPort = fmt.Sprintf(":%s", rawPort)
254 254
 		}
255 255
 
256
-		parts, err := utils.PartParser("ip:hostPort:containerPort", rawPort)
256
+		parts, err := utils.PartParser(PortSpecTemplate, rawPort)
257 257
 		if err != nil {
258 258
 			return nil, nil, err
259 259
 		}
260
-		containerPort := parts["containerPort"]
261
-		rawIp := parts["ip"]
262
-		hostPort := parts["hostPort"]
260
+
261
+		var (
262
+			containerPort = parts["containerPort"]
263
+			rawIp         = parts["ip"]
264
+			hostPort      = parts["hostPort"]
265
+		)
263 266
 
264 267
 		if containerPort == "" {
265 268
 			return nil, nil, fmt.Errorf("No port specified: %s<empty>", rawPort)
... ...
@@ -779,14 +779,19 @@ func NewHTTPRequestError(msg string, res *http.Response) error {
779 779
 	}
780 780
 }
781 781
 
782
-func (jm *JSONMessage) Display(out io.Writer) error {
782
+func (jm *JSONMessage) Display(out io.Writer, isTerminal bool) error {
783 783
 	if jm.Error != nil {
784 784
 		if jm.Error.Code == 401 {
785 785
 			return fmt.Errorf("Authentication is required.")
786 786
 		}
787 787
 		return jm.Error
788 788
 	}
789
-	fmt.Fprintf(out, "%c[2K\r", 27)
789
+	endl := ""
790
+	if isTerminal {
791
+		// <ESC>[2K = erase entire current line
792
+		fmt.Fprintf(out, "%c[2K\r", 27)
793
+		endl = "\r"
794
+	}
790 795
 	if jm.Time != 0 {
791 796
 		fmt.Fprintf(out, "[%s] ", time.Unix(jm.Time, 0))
792 797
 	}
... ...
@@ -797,14 +802,14 @@ func (jm *JSONMessage) Display(out io.Writer) error {
797 797
 		fmt.Fprintf(out, "(from %s) ", jm.From)
798 798
 	}
799 799
 	if jm.Progress != "" {
800
-		fmt.Fprintf(out, "%s %s\r", jm.Status, jm.Progress)
800
+		fmt.Fprintf(out, "%s %s%s", jm.Status, jm.Progress, endl)
801 801
 	} else {
802
-		fmt.Fprintf(out, "%s\r\n", jm.Status)
802
+		fmt.Fprintf(out, "%s%s\n", jm.Status, endl)
803 803
 	}
804 804
 	return nil
805 805
 }
806 806
 
807
-func DisplayJSONMessagesStream(in io.Reader, out io.Writer) error {
807
+func DisplayJSONMessagesStream(in io.Reader, out io.Writer, isTerminal bool) error {
808 808
 	dec := json.NewDecoder(in)
809 809
 	ids := make(map[string]int)
810 810
 	diff := 0
... ...
@@ -825,11 +830,17 @@ func DisplayJSONMessagesStream(in io.Reader, out io.Writer) error {
825 825
 			} else {
826 826
 				diff = len(ids) - line
827 827
 			}
828
-			fmt.Fprintf(out, "%c[%dA", 27, diff)
828
+			if isTerminal {
829
+				// <ESC>[{diff}A = move cursor up diff rows
830
+				fmt.Fprintf(out, "%c[%dA", 27, diff)
831
+			}
829 832
 		}
830
-		err := jm.Display(out)
833
+		err := jm.Display(out, isTerminal)
831 834
 		if jm.ID != "" {
832
-			fmt.Fprintf(out, "%c[%dB", 27, diff)
835
+			if isTerminal {
836
+				// <ESC>[{diff}B = move cursor down diff rows
837
+				fmt.Fprintf(out, "%c[%dB", 27, diff)
838
+			}
833 839
 		}
834 840
 		if err != nil {
835 841
 			return err
... ...
@@ -1226,12 +1237,14 @@ func IsClosedError(err error) bool {
1226 1226
 
1227 1227
 func PartParser(template, data string) (map[string]string, error) {
1228 1228
 	// ip:public:private
1229
-	templateParts := strings.Split(template, ":")
1230
-	parts := strings.Split(data, ":")
1229
+	var (
1230
+		templateParts = strings.Split(template, ":")
1231
+		parts         = strings.Split(data, ":")
1232
+		out           = make(map[string]string, len(templateParts))
1233
+	)
1231 1234
 	if len(parts) != len(templateParts) {
1232 1235
 		return nil, fmt.Errorf("Invalid format to parse.  %s should match template %s", data, template)
1233 1236
 	}
1234
-	out := make(map[string]string, len(templateParts))
1235 1237
 
1236 1238
 	for i, t := range templateParts {
1237 1239
 		value := ""