Browse code

Merge branch 'master' into 1237-improve_docker_top-feature

Conflicts:
docs/sources/api/docker_remote_api.rst

Victor Vieux authored on 2013/07/23 01:22:11
Showing 26 changed files
... ...
@@ -7,6 +7,7 @@ import (
7 7
 	"github.com/dotcloud/docker/utils"
8 8
 	"io"
9 9
 	"io/ioutil"
10
+	"net/url"
10 11
 	"os"
11 12
 	"path"
12 13
 	"reflect"
... ...
@@ -201,6 +202,24 @@ func (b *buildFile) addRemote(container *Container, orig, dest string) error {
201 201
 	}
202 202
 	defer file.Body.Close()
203 203
 
204
+	// If the destination is a directory, figure out the filename.
205
+	if strings.HasSuffix(dest, "/") {
206
+		u, err := url.Parse(orig)
207
+		if err != nil {
208
+			return err
209
+		}
210
+		path := u.Path
211
+		if strings.HasSuffix(path, "/") {
212
+			path = path[:len(path)-1]
213
+		}
214
+		parts := strings.Split(path, "/")
215
+		filename := parts[len(parts)-1]
216
+		if filename == "" {
217
+			return fmt.Errorf("cannot determine filename from url: %s", u)
218
+		}
219
+		dest = dest + filename
220
+	}
221
+
204 222
 	return container.Inject(file.Body, dest)
205 223
 }
206 224
 
... ...
@@ -208,7 +227,7 @@ func (b *buildFile) addContext(container *Container, orig, dest string) error {
208 208
 	origPath := path.Join(b.context, orig)
209 209
 	destPath := path.Join(container.RootfsPath(), dest)
210 210
 	// Preserve the trailing '/'
211
-	if dest[len(dest)-1] == '/' {
211
+	if strings.HasSuffix(dest, "/") {
212 212
 		destPath = destPath + "/"
213 213
 	}
214 214
 	fi, err := os.Stat(origPath)
... ...
@@ -3,13 +3,17 @@ package docker
3 3
 import (
4 4
 	"fmt"
5 5
 	"io/ioutil"
6
+	"net"
7
+	"net/http"
8
+	"net/http/httptest"
9
+	"strings"
6 10
 	"testing"
7 11
 )
8 12
 
9 13
 // mkTestContext generates a build context from the contents of the provided dockerfile.
10 14
 // This context is suitable for use as an argument to BuildFile.Build()
11 15
 func mkTestContext(dockerfile string, files [][2]string, t *testing.T) Archive {
12
-	context, err := mkBuildContext(fmt.Sprintf(dockerfile, unitTestImageID), files)
16
+	context, err := mkBuildContext(dockerfile, files)
13 17
 	if err != nil {
14 18
 		t.Fatal(err)
15 19
 	}
... ...
@@ -22,6 +26,8 @@ type testContextTemplate struct {
22 22
 	dockerfile string
23 23
 	// Additional files in the context, eg [][2]string{"./passwd", "gordon"}
24 24
 	files [][2]string
25
+	// Additional remote files to host on a local HTTP server.
26
+	remoteFiles [][2]string
25 27
 }
26 28
 
27 29
 // A table of all the contexts to build and test.
... ...
@@ -29,27 +35,31 @@ type testContextTemplate struct {
29 29
 var testContexts = []testContextTemplate{
30 30
 	{
31 31
 		`
32
-from   %s
32
+from   {IMAGE}
33 33
 run    sh -c 'echo root:testpass > /tmp/passwd'
34 34
 run    mkdir -p /var/run/sshd
35 35
 run    [ "$(cat /tmp/passwd)" = "root:testpass" ]
36 36
 run    [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
37 37
 `,
38 38
 		nil,
39
+		nil,
39 40
 	},
40 41
 
41 42
 	{
42 43
 		`
43
-from %s
44
+from {IMAGE}
44 45
 add foo /usr/lib/bla/bar
45
-run [ "$(cat /usr/lib/bla/bar)" = 'hello world!' ]
46
+run [ "$(cat /usr/lib/bla/bar)" = 'hello' ]
47
+add http://{SERVERADDR}/baz /usr/lib/baz/quux
48
+run [ "$(cat /usr/lib/baz/quux)" = 'world!' ]
46 49
 `,
47
-		[][2]string{{"foo", "hello world!"}},
50
+		[][2]string{{"foo", "hello"}},
51
+		[][2]string{{"/baz", "world!"}},
48 52
 	},
49 53
 
50 54
 	{
51 55
 		`
52
-from %s
56
+from {IMAGE}
53 57
 add f /
54 58
 run [ "$(cat /f)" = "hello" ]
55 59
 add f /abc
... ...
@@ -71,38 +81,86 @@ run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ]
71 71
 			{"f", "hello"},
72 72
 			{"d/ga", "bu"},
73 73
 		},
74
+		nil,
75
+	},
76
+
77
+	{
78
+		`
79
+from {IMAGE}
80
+add http://{SERVERADDR}/x /a/b/c
81
+run [ "$(cat /a/b/c)" = "hello" ]
82
+add http://{SERVERADDR}/x?foo=bar /
83
+run [ "$(cat /x)" = "hello" ]
84
+add http://{SERVERADDR}/x /d/
85
+run [ "$(cat /d/x)" = "hello" ]
86
+add http://{SERVERADDR} /e
87
+run [ "$(cat /e)" = "blah" ]
88
+`,
89
+		nil,
90
+		[][2]string{{"/x", "hello"}, {"/", "blah"}},
74 91
 	},
75 92
 
76 93
 	{
77 94
 		`
78
-from %s
95
+from   {IMAGE}
79 96
 env    FOO BAR
80 97
 run    [ "$FOO" = "BAR" ]
81 98
 `,
82 99
 		nil,
100
+		nil,
83 101
 	},
84 102
 
85 103
 	{
86 104
 		`
87
-from %s
105
+from {IMAGE}
88 106
 ENTRYPOINT /bin/echo
89 107
 CMD Hello world
90 108
 `,
91 109
 		nil,
110
+		nil,
92 111
 	},
93 112
 
94 113
 	{
95 114
 		`
96
-from %s
115
+from {IMAGE}
97 116
 VOLUME /test
98 117
 CMD Hello world
99 118
 `,
100 119
 		nil,
120
+		nil,
101 121
 	},
102 122
 }
103 123
 
104 124
 // FIXME: test building with 2 successive overlapping ADD commands
105 125
 
126
+func constructDockerfile(template string, ip net.IP, port string) string {
127
+	serverAddr := fmt.Sprintf("%s:%s", ip, port)
128
+	replacer := strings.NewReplacer("{IMAGE}", unitTestImageID, "{SERVERADDR}", serverAddr)
129
+	return replacer.Replace(template)
130
+}
131
+
132
+func mkTestingFileServer(files [][2]string) (*httptest.Server, error) {
133
+	mux := http.NewServeMux()
134
+	for _, file := range files {
135
+		name, contents := file[0], file[1]
136
+		mux.HandleFunc(name, func(w http.ResponseWriter, r *http.Request) {
137
+			w.Write([]byte(contents))
138
+		})
139
+	}
140
+
141
+	// This is how httptest.NewServer sets up a net.Listener, except that our listener must accept remote
142
+	// connections (from the container).
143
+	listener, err := net.Listen("tcp", ":0")
144
+	if err != nil {
145
+		return nil, err
146
+	}
147
+
148
+	s := httptest.NewUnstartedServer(mux)
149
+	s.Listener = listener
150
+	s.Start()
151
+	return s, nil
152
+}
153
+
106 154
 func TestBuild(t *testing.T) {
107 155
 	for _, ctx := range testContexts {
108 156
 		buildImage(ctx, t)
... ...
@@ -121,9 +179,24 @@ func buildImage(context testContextTemplate, t *testing.T) *Image {
121 121
 		pullingPool: make(map[string]struct{}),
122 122
 		pushingPool: make(map[string]struct{}),
123 123
 	}
124
-	buildfile := NewBuildFile(srv, ioutil.Discard, false)
125 124
 
126
-	id, err := buildfile.Build(mkTestContext(context.dockerfile, context.files, t))
125
+	httpServer, err := mkTestingFileServer(context.remoteFiles)
126
+	if err != nil {
127
+		t.Fatal(err)
128
+	}
129
+	defer httpServer.Close()
130
+
131
+	idx := strings.LastIndex(httpServer.URL, ":")
132
+	if idx < 0 {
133
+		t.Fatalf("could not get port from test http server address %s", httpServer.URL)
134
+	}
135
+	port := httpServer.URL[idx+1:]
136
+
137
+	ip := runtime.networkManager.bridgeNetwork.IP
138
+	dockerfile := constructDockerfile(context.dockerfile, ip, port)
139
+
140
+	buildfile := NewBuildFile(srv, ioutil.Discard, false)
141
+	id, err := buildfile.Build(mkTestContext(dockerfile, context.files, t))
127 142
 	if err != nil {
128 143
 		t.Fatal(err)
129 144
 	}
... ...
@@ -137,10 +210,10 @@ func buildImage(context testContextTemplate, t *testing.T) *Image {
137 137
 
138 138
 func TestVolume(t *testing.T) {
139 139
 	img := buildImage(testContextTemplate{`
140
-        from %s
140
+        from {IMAGE}
141 141
         volume /test
142 142
         cmd Hello world
143
-    `, nil}, t)
143
+    `, nil, nil}, t)
144 144
 
145 145
 	if len(img.Config.Volumes) == 0 {
146 146
 		t.Fail()
... ...
@@ -154,9 +227,9 @@ func TestVolume(t *testing.T) {
154 154
 
155 155
 func TestBuildMaintainer(t *testing.T) {
156 156
 	img := buildImage(testContextTemplate{`
157
-        from %s
157
+        from {IMAGE}
158 158
         maintainer dockerio
159
-    `, nil}, t)
159
+    `, nil, nil}, t)
160 160
 
161 161
 	if img.Author != "dockerio" {
162 162
 		t.Fail()
... ...
@@ -165,10 +238,10 @@ func TestBuildMaintainer(t *testing.T) {
165 165
 
166 166
 func TestBuildEnv(t *testing.T) {
167 167
 	img := buildImage(testContextTemplate{`
168
-        from %s
168
+        from {IMAGE}
169 169
         env port 4243
170 170
         `,
171
-		nil}, t)
171
+		nil, nil}, t)
172 172
 
173 173
 	if img.Config.Env[0] != "port=4243" {
174 174
 		t.Fail()
... ...
@@ -177,10 +250,10 @@ func TestBuildEnv(t *testing.T) {
177 177
 
178 178
 func TestBuildCmd(t *testing.T) {
179 179
 	img := buildImage(testContextTemplate{`
180
-        from %s
180
+        from {IMAGE}
181 181
         cmd ["/bin/echo", "Hello World"]
182 182
         `,
183
-		nil}, t)
183
+		nil, nil}, t)
184 184
 
185 185
 	if img.Config.Cmd[0] != "/bin/echo" {
186 186
 		t.Log(img.Config.Cmd[0])
... ...
@@ -194,10 +267,10 @@ func TestBuildCmd(t *testing.T) {
194 194
 
195 195
 func TestBuildExpose(t *testing.T) {
196 196
 	img := buildImage(testContextTemplate{`
197
-        from %s
197
+        from {IMAGE}
198 198
         expose 4243
199 199
         `,
200
-		nil}, t)
200
+		nil, nil}, t)
201 201
 
202 202
 	if img.Config.PortSpecs[0] != "4243" {
203 203
 		t.Fail()
... ...
@@ -206,10 +279,10 @@ func TestBuildExpose(t *testing.T) {
206 206
 
207 207
 func TestBuildEntrypoint(t *testing.T) {
208 208
 	img := buildImage(testContextTemplate{`
209
-        from %s
209
+        from {IMAGE}
210 210
         entrypoint ["/bin/echo"]
211 211
         `,
212
-		nil}, t)
212
+		nil, nil}, t)
213 213
 
214 214
 	if img.Config.Entrypoint[0] != "/bin/echo" {
215 215
 	}
... ...
@@ -475,7 +475,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
475 475
 
476 476
 func (cli *DockerCli) CmdStop(args ...string) error {
477 477
 	cmd := Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container")
478
-	nSeconds := cmd.Int("t", 10, "Number of seconds to try to stop for before killing the container. Default=10")
478
+	nSeconds := cmd.Int("t", 10, "Number of seconds to wait for the container to stop before killing it.")
479 479
 	if err := cmd.Parse(args); err != nil {
480 480
 		return nil
481 481
 	}
... ...
@@ -1110,10 +1110,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
1110 1110
 		return nil
1111 1111
 	}
1112 1112
 
1113
-	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", false, nil, cli.out); err != nil {
1114
-		return err
1115
-	}
1116
-	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", false, nil, cli.err); err != nil {
1113
+	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out); err != nil {
1117 1114
 		return err
1118 1115
 	}
1119 1116
 	return nil
... ...
@@ -1316,6 +1313,18 @@ func (cli *DockerCli) CmdRun(args ...string) error {
1316 1316
 		return nil
1317 1317
 	}
1318 1318
 
1319
+	var containerIDFile *os.File
1320
+	if len(hostConfig.ContainerIDFile) > 0 {
1321
+		if _, err := ioutil.ReadFile(hostConfig.ContainerIDFile); err == nil {
1322
+			return fmt.Errorf("cid file found, make sure the other container isn't running or delete %s", hostConfig.ContainerIDFile)
1323
+		}
1324
+		containerIDFile, err = os.Create(hostConfig.ContainerIDFile)
1325
+		if err != nil {
1326
+			return fmt.Errorf("failed to create the container ID file: %s", err)
1327
+		}
1328
+		defer containerIDFile.Close()
1329
+	}
1330
+
1319 1331
 	//create the container
1320 1332
 	body, statusCode, err := cli.call("POST", "/containers/create", config)
1321 1333
 	//if image not found try to pull it
... ...
@@ -1346,6 +1355,11 @@ func (cli *DockerCli) CmdRun(args ...string) error {
1346 1346
 	for _, warning := range runResult.Warnings {
1347 1347
 		fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
1348 1348
 	}
1349
+	if len(hostConfig.ContainerIDFile) > 0 {
1350
+		if _, err = containerIDFile.WriteString(runResult.ID); err != nil {
1351
+			return fmt.Errorf("failed to write the container ID to the file: %s", err)
1352
+		}
1353
+	}
1349 1354
 
1350 1355
 	//start the container
1351 1356
 	if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig); err != nil {
... ...
@@ -59,7 +59,6 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error
59 59
 	return nil
60 60
 }
61 61
 
62
-
63 62
 // TestRunHostname checks that 'docker run -h' correctly sets a custom hostname
64 63
 func TestRunHostname(t *testing.T) {
65 64
 	stdout, stdoutPipe := io.Pipe()
... ...
@@ -91,7 +90,6 @@ func TestRunHostname(t *testing.T) {
91 91
 
92 92
 }
93 93
 
94
-
95 94
 // TestAttachStdin checks attaching to stdin without stdout and stderr.
96 95
 // 'docker run -i -a stdin' should sends the client's stdin to the command,
97 96
 // then detach from it and print the container id.
... ...
@@ -144,15 +142,17 @@ func TestRunAttachStdin(t *testing.T) {
144 144
 	})
145 145
 
146 146
 	// Check logs
147
-	if cmdLogs, err := container.ReadLog("stdout"); err != nil {
147
+	if cmdLogs, err := container.ReadLog("json"); err != nil {
148 148
 		t.Fatal(err)
149 149
 	} else {
150 150
 		if output, err := ioutil.ReadAll(cmdLogs); err != nil {
151 151
 			t.Fatal(err)
152 152
 		} else {
153
-			expectedLog := "hello\nhi there\n"
154
-			if string(output) != expectedLog {
155
-				t.Fatalf("Unexpected logs: should be '%s', not '%s'\n", expectedLog, output)
153
+			expectedLogs := []string{"{\"log\":\"hello\\n\",\"stream\":\"stdout\"", "{\"log\":\"hi there\\n\",\"stream\":\"stdout\""}
154
+			for _, expectedLog := range expectedLogs {
155
+				if !strings.Contains(string(output), expectedLog) {
156
+					t.Fatalf("Unexpected logs: should contains '%s', it is not '%s'\n", expectedLog, output)
157
+				}
156 158
 			}
157 159
 		}
158 160
 	}
... ...
@@ -80,7 +80,8 @@ type Config struct {
80 80
 }
81 81
 
82 82
 type HostConfig struct {
83
-	Binds []string
83
+	Binds           []string
84
+	ContainerIDFile string
84 85
 }
85 86
 
86 87
 type BindMap struct {
... ...
@@ -93,6 +94,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
93 93
 	cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
94 94
 	if len(args) > 0 && args[0] != "--help" {
95 95
 		cmd.SetOutput(ioutil.Discard)
96
+		cmd.Usage = nil
96 97
 	}
97 98
 
98 99
 	flHostname := cmd.String("h", "", "Container host name")
... ...
@@ -103,6 +105,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
103 103
 	flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
104 104
 	flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
105 105
 	flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)")
106
+	flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file")
106 107
 
107 108
 	if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit {
108 109
 		//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
... ...
@@ -190,7 +193,8 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
190 190
 		Entrypoint:   entrypoint,
191 191
 	}
192 192
 	hostConfig := &HostConfig{
193
-		Binds: binds,
193
+		Binds:           binds,
194
+		ContainerIDFile: *flContainerIDFile,
194 195
 	}
195 196
 
196 197
 	if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
... ...
@@ -637,6 +641,7 @@ func (container *Container) Start(hostConfig *HostConfig) error {
637 637
 	params = append(params,
638 638
 		"-e", "HOME=/",
639 639
 		"-e", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
640
+		"-e", "container=lxc",
640 641
 	)
641 642
 
642 643
 	for _, elem := range container.Config.Env {
... ...
@@ -650,10 +655,10 @@ func (container *Container) Start(hostConfig *HostConfig) error {
650 650
 	container.cmd = exec.Command("lxc-start", params...)
651 651
 
652 652
 	// Setup logging of stdout and stderr to disk
653
-	if err := container.runtime.LogToDisk(container.stdout, container.logPath("stdout")); err != nil {
653
+	if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil {
654 654
 		return err
655 655
 	}
656
-	if err := container.runtime.LogToDisk(container.stderr, container.logPath("stderr")); err != nil {
656
+	if err := container.runtime.LogToDisk(container.stderr, container.logPath("json"), "stderr"); err != nil {
657 657
 		return err
658 658
 	}
659 659
 
... ...
@@ -712,13 +717,13 @@ func (container *Container) StdinPipe() (io.WriteCloser, error) {
712 712
 
713 713
 func (container *Container) StdoutPipe() (io.ReadCloser, error) {
714 714
 	reader, writer := io.Pipe()
715
-	container.stdout.AddWriter(writer)
715
+	container.stdout.AddWriter(writer, "")
716 716
 	return utils.NewBufReader(reader), nil
717 717
 }
718 718
 
719 719
 func (container *Container) StderrPipe() (io.ReadCloser, error) {
720 720
 	reader, writer := io.Pipe()
721
-	container.stderr.AddWriter(writer)
721
+	container.stderr.AddWriter(writer, "")
722 722
 	return utils.NewBufReader(reader), nil
723 723
 }
724 724
 
... ...
@@ -39,16 +39,11 @@ func TestIDFormat(t *testing.T) {
39 39
 func TestMultipleAttachRestart(t *testing.T) {
40 40
 	runtime := mkRuntime(t)
41 41
 	defer nuke(runtime)
42
-	container, err := NewBuilder(runtime).Create(
43
-		&Config{
44
-			Image: GetTestImage(runtime).ID,
45
-			Cmd: []string{"/bin/sh", "-c",
46
-				"i=1; while [ $i -le 5 ]; do i=`expr $i + 1`;  echo hello; done"},
47
-		},
42
+	container, hostConfig, _ := mkContainer(
43
+		runtime,
44
+		[]string{"_", "/bin/sh", "-c", "i=1; while [ $i -le 5 ]; do i=`expr $i + 1`;  echo hello; done"},
45
+		t,
48 46
 	)
49
-	if err != nil {
50
-		t.Fatal(err)
51
-	}
52 47
 	defer runtime.Destroy(container)
53 48
 
54 49
 	// Simulate 3 client attaching to the container and stop/restart
... ...
@@ -65,7 +60,6 @@ func TestMultipleAttachRestart(t *testing.T) {
65 65
 	if err != nil {
66 66
 		t.Fatal(err)
67 67
 	}
68
-	hostConfig := &HostConfig{}
69 68
 	if err := container.Start(hostConfig); err != nil {
70 69
 		t.Fatal(err)
71 70
 	}
... ...
@@ -140,19 +134,8 @@ func TestMultipleAttachRestart(t *testing.T) {
140 140
 func TestDiff(t *testing.T) {
141 141
 	runtime := mkRuntime(t)
142 142
 	defer nuke(runtime)
143
-
144
-	builder := NewBuilder(runtime)
145
-
146 143
 	// Create a container and remove a file
147
-	container1, err := builder.Create(
148
-		&Config{
149
-			Image: GetTestImage(runtime).ID,
150
-			Cmd:   []string{"/bin/rm", "/etc/passwd"},
151
-		},
152
-	)
153
-	if err != nil {
154
-		t.Fatal(err)
155
-	}
144
+	container1, _, _ := mkContainer(runtime, []string{"_", "/bin/rm", "/etc/passwd"}, t)
156 145
 	defer runtime.Destroy(container1)
157 146
 
158 147
 	if err := container1.Run(); err != nil {
... ...
@@ -185,15 +168,7 @@ func TestDiff(t *testing.T) {
185 185
 	}
186 186
 
187 187
 	// Create a new container from the commited image
188
-	container2, err := builder.Create(
189
-		&Config{
190
-			Image: img.ID,
191
-			Cmd:   []string{"cat", "/etc/passwd"},
192
-		},
193
-	)
194
-	if err != nil {
195
-		t.Fatal(err)
196
-	}
188
+	container2, _, _ := mkContainer(runtime, []string{img.ID, "cat", "/etc/passwd"}, t)
197 189
 	defer runtime.Destroy(container2)
198 190
 
199 191
 	if err := container2.Run(); err != nil {
... ...
@@ -212,15 +187,7 @@ func TestDiff(t *testing.T) {
212 212
 	}
213 213
 
214 214
 	// Create a new containere
215
-	container3, err := builder.Create(
216
-		&Config{
217
-			Image: GetTestImage(runtime).ID,
218
-			Cmd:   []string{"rm", "/bin/httpd"},
219
-		},
220
-	)
221
-	if err != nil {
222
-		t.Fatal(err)
223
-	}
215
+	container3, _, _ := mkContainer(runtime, []string{"_", "rm", "/bin/httpd"}, t)
224 216
 	defer runtime.Destroy(container3)
225 217
 
226 218
 	if err := container3.Run(); err != nil {
... ...
@@ -246,17 +213,7 @@ func TestDiff(t *testing.T) {
246 246
 func TestCommitAutoRun(t *testing.T) {
247 247
 	runtime := mkRuntime(t)
248 248
 	defer nuke(runtime)
249
-
250
-	builder := NewBuilder(runtime)
251
-	container1, err := builder.Create(
252
-		&Config{
253
-			Image: GetTestImage(runtime).ID,
254
-			Cmd:   []string{"/bin/sh", "-c", "echo hello > /world"},
255
-		},
256
-	)
257
-	if err != nil {
258
-		t.Fatal(err)
259
-	}
249
+	container1, _, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
260 250
 	defer runtime.Destroy(container1)
261 251
 
262 252
 	if container1.State.Running {
... ...
@@ -279,14 +236,7 @@ func TestCommitAutoRun(t *testing.T) {
279 279
 	}
280 280
 
281 281
 	// FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world
282
-	container2, err := builder.Create(
283
-		&Config{
284
-			Image: img.ID,
285
-		},
286
-	)
287
-	if err != nil {
288
-		t.Fatal(err)
289
-	}
282
+	container2, hostConfig, _ := mkContainer(runtime, []string{img.ID}, t)
290 283
 	defer runtime.Destroy(container2)
291 284
 	stdout, err := container2.StdoutPipe()
292 285
 	if err != nil {
... ...
@@ -296,7 +246,6 @@ func TestCommitAutoRun(t *testing.T) {
296 296
 	if err != nil {
297 297
 		t.Fatal(err)
298 298
 	}
299
-	hostConfig := &HostConfig{}
300 299
 	if err := container2.Start(hostConfig); err != nil {
301 300
 		t.Fatal(err)
302 301
 	}
... ...
@@ -324,17 +273,7 @@ func TestCommitRun(t *testing.T) {
324 324
 	runtime := mkRuntime(t)
325 325
 	defer nuke(runtime)
326 326
 
327
-	builder := NewBuilder(runtime)
328
-
329
-	container1, err := builder.Create(
330
-		&Config{
331
-			Image: GetTestImage(runtime).ID,
332
-			Cmd:   []string{"/bin/sh", "-c", "echo hello > /world"},
333
-		},
334
-	)
335
-	if err != nil {
336
-		t.Fatal(err)
337
-	}
327
+	container1, hostConfig, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
338 328
 	defer runtime.Destroy(container1)
339 329
 
340 330
 	if container1.State.Running {
... ...
@@ -357,16 +296,7 @@ func TestCommitRun(t *testing.T) {
357 357
 	}
358 358
 
359 359
 	// FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world
360
-
361
-	container2, err := builder.Create(
362
-		&Config{
363
-			Image: img.ID,
364
-			Cmd:   []string{"cat", "/world"},
365
-		},
366
-	)
367
-	if err != nil {
368
-		t.Fatal(err)
369
-	}
360
+	container2, hostConfig, _ := mkContainer(runtime, []string{img.ID, "cat", "/world"}, t)
370 361
 	defer runtime.Destroy(container2)
371 362
 	stdout, err := container2.StdoutPipe()
372 363
 	if err != nil {
... ...
@@ -376,7 +306,6 @@ func TestCommitRun(t *testing.T) {
376 376
 	if err != nil {
377 377
 		t.Fatal(err)
378 378
 	}
379
-	hostConfig := &HostConfig{}
380 379
 	if err := container2.Start(hostConfig); err != nil {
381 380
 		t.Fatal(err)
382 381
 	}
... ...
@@ -403,18 +332,7 @@ func TestCommitRun(t *testing.T) {
403 403
 func TestStart(t *testing.T) {
404 404
 	runtime := mkRuntime(t)
405 405
 	defer nuke(runtime)
406
-	container, err := NewBuilder(runtime).Create(
407
-		&Config{
408
-			Image:     GetTestImage(runtime).ID,
409
-			Memory:    33554432,
410
-			CpuShares: 1000,
411
-			Cmd:       []string{"/bin/cat"},
412
-			OpenStdin: true,
413
-		},
414
-	)
415
-	if err != nil {
416
-		t.Fatal(err)
417
-	}
406
+	container, hostConfig, _ := mkContainer(runtime, []string{"-m", "33554432", "-c", "1000", "-i", "_", "/bin/cat"}, t)
418 407
 	defer runtime.Destroy(container)
419 408
 
420 409
 	cStdin, err := container.StdinPipe()
... ...
@@ -422,7 +340,6 @@ func TestStart(t *testing.T) {
422 422
 		t.Fatal(err)
423 423
 	}
424 424
 
425
-	hostConfig := &HostConfig{}
426 425
 	if err := container.Start(hostConfig); err != nil {
427 426
 		t.Fatal(err)
428 427
 	}
... ...
@@ -445,15 +362,7 @@ func TestStart(t *testing.T) {
445 445
 func TestRun(t *testing.T) {
446 446
 	runtime := mkRuntime(t)
447 447
 	defer nuke(runtime)
448
-	container, err := NewBuilder(runtime).Create(
449
-		&Config{
450
-			Image: GetTestImage(runtime).ID,
451
-			Cmd:   []string{"ls", "-al"},
452
-		},
453
-	)
454
-	if err != nil {
455
-		t.Fatal(err)
456
-	}
448
+	container, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
457 449
 	defer runtime.Destroy(container)
458 450
 
459 451
 	if container.State.Running {
... ...
@@ -2,6 +2,9 @@
2 2
 :description: API Documentation for Docker
3 3
 :keywords: API, Docker, rcli, REST, documentation
4 4
 
5
+.. COMMENT use http://pythonhosted.org/sphinxcontrib-httpdomain/ to
6
+.. document the REST API.
7
+
5 8
 =================
6 9
 Docker Remote API
7 10
 =================
... ...
@@ -13,15 +16,23 @@ Docker Remote API
13 13
 
14 14
 - The Remote API is replacing rcli
15 15
 - Default port in the docker deamon is 4243 
16
-- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
17
-- Since API version 1.2, the auth configuration is now handled client side, so the client has to send the authConfig as POST in /images/(name)/push
16
+- The API tends to be REST, but for some complex commands, like attach
17
+  or pull, the HTTP connection is hijacked to transport stdout stdin
18
+  and stderr
19
+- Since API version 1.2, the auth configuration is now handled client
20
+  side, so the client has to send the authConfig as POST in
21
+  /images/(name)/push
18 22
 
19 23
 2. Versions
20 24
 ===========
21 25
 
22
-The current verson of the API is 1.4
23
-Calling /images/<name>/insert is the same as calling /v1.4/images/<name>/insert
24
-You can still call an old version of the api using /v1.0/images/<name>/insert
26
+The current verson of the API is 1.3
27
+
28
+Calling /images/<name>/insert is the same as calling
29
+/v1.3/images/<name>/insert 
30
+
31
+You can still call an old version of the api using
32
+/v1.0/images/<name>/insert
25 33
 
26 34
 :doc:`docker_remote_api_v1.3`
27 35
 *****************************
... ...
@@ -29,9 +40,9 @@ You can still call an old version of the api using /v1.0/images/<name>/insert
29 29
 What's new
30 30
 ----------
31 31
 
32
-Listing processes (/top):
32
+.. http:get:: /containers/(id)/top
33 33
 
34
-- You can now use ps args with docker top, like `docker top <container_id> aux`
34
+   **New!** You can now use ps args with docker top, like `docker top <container_id> aux`
35 35
 
36 36
 :doc:`docker_remote_api_v1.3`
37 37
 *****************************
... ...
@@ -41,19 +52,21 @@ docker v0.5.0 51f6c4a_
41 41
 What's new
42 42
 ----------
43 43
 
44
-Listing processes (/top):
45
-
46
-- List the processes inside a container
44
+.. http:get:: /containers/(id)/top
47 45
 
46
+   List the processes running inside a container.
48 47
 
49 48
 Builder (/build):
50 49
 
51 50
 - Simplify the upload of the build context
52
-- Simply stream a tarball instead of multipart upload with 4 intermediary buffers
51
+- Simply stream a tarball instead of multipart upload with 4
52
+  intermediary buffers
53 53
 - Simpler, less memory usage, less disk usage and faster
54 54
 
55
-.. Note::
56
-The /build improvements are not reverse-compatible. Pre 1.3 clients will break on /build.
55
+.. Warning::
56
+
57
+  The /build improvements are not reverse-compatible. Pre 1.3 clients
58
+  will break on /build.
57 59
 
58 60
 List containers (/containers/json):
59 61
 
... ...
@@ -61,7 +74,8 @@ List containers (/containers/json):
61 61
 
62 62
 Start containers (/containers/<id>/start):
63 63
 
64
-- You can now pass host-specific configuration (e.g. bind mounts) in the POST body for start calls 
64
+- You can now pass host-specific configuration (e.g. bind mounts) in
65
+  the POST body for start calls
65 66
 
66 67
 :doc:`docker_remote_api_v1.2`
67 68
 *****************************
... ...
@@ -72,14 +86,25 @@ What's new
72 72
 ----------
73 73
 
74 74
 The auth configuration is now handled by the client.
75
-The client should send it's authConfig as POST on each call of /images/(name)/push
76 75
 
77
-.. http:get:: /auth is now deprecated
78
-.. http:post:: /auth only checks the configuration but doesn't store it on the server
76
+The client should send it's authConfig as POST on each call of
77
+/images/(name)/push
78
+
79
+.. http:get:: /auth 
80
+
81
+  **Deprecated.**
82
+
83
+.. http:post:: /auth 
84
+
85
+  Only checks the configuration but doesn't store it on the server
86
+
87
+  Deleting an image is now improved, will only untag the image if it
88
+  has chidren and remove all the untagged parents if has any.
79 89
 
80
-Deleting an image is now improved, will only untag the image if it has chidrens and remove all the untagged parents if has any.
90
+.. http:post:: /images/<name>/delete 
81 91
 
82
-.. http:post:: /images/<name>/delete now returns a JSON with the list of images deleted/untagged
92
+  Now returns a JSON structure with the list of images
93
+  deleted/untagged.
83 94
 
84 95
 
85 96
 :doc:`docker_remote_api_v1.1`
... ...
@@ -94,7 +119,7 @@ What's new
94 94
 .. http:post:: /images/(name)/insert
95 95
 .. http:post:: /images/(name)/push
96 96
 
97
-Uses json stream instead of HTML hijack, it looks like this:
97
+   Uses json stream instead of HTML hijack, it looks like this:
98 98
 
99 99
         .. sourcecode:: http
100 100
 
... ...
@@ -1,3 +1,8 @@
1
+.. use orphan to suppress "WARNING: document isn't included in any toctree"
2
+.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata
3
+
4
+:orphan:
5
+
1 6
 :title: Remote API v1.0
2 7
 :description: API Documentation for Docker
3 8
 :keywords: API, Docker, rcli, REST, documentation
... ...
@@ -300,8 +305,8 @@ Start a container
300 300
 	:statuscode 500: server error
301 301
 
302 302
 
303
-Stop a contaier
304
-***************
303
+Stop a container
304
+****************
305 305
 
306 306
 .. http:post:: /containers/(id)/stop
307 307
 
... ...
@@ -1,3 +1,7 @@
1
+.. use orphan to suppress "WARNING: document isn't included in any toctree"
2
+.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata
3
+
4
+:orphan:
1 5
 
2 6
 :title: Remote API v1.1
3 7
 :description: API Documentation for Docker
... ...
@@ -1,3 +1,8 @@
1
+.. use orphan to suppress "WARNING: document isn't included in any toctree"
2
+.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata
3
+
4
+:orphan:
5
+
1 6
 :title: Remote API v1.2
2 7
 :description: API Documentation for Docker
3 8
 :keywords: API, Docker, rcli, REST, documentation
... ...
@@ -1,3 +1,8 @@
1
+.. use orphan to suppress "WARNING: document isn't included in any toctree"
2
+.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata
3
+
4
+:orphan:
5
+
1 6
 :title: Remote API v1.3
2 7
 :description: API Documentation for Docker
3 8
 :keywords: API, Docker, rcli, REST, documentation
... ...
@@ -452,7 +452,7 @@ User Register
452 452
          "username": "foobar"'}
453 453
 
454 454
     :jsonparameter email: valid email address, that needs to be confirmed
455
-    :jsonparameter username: min 4 character, max 30 characters, must match the regular expression [a-z0-9_].
455
+    :jsonparameter username: min 4 character, max 30 characters, must match the regular expression [a-z0-9\_].
456 456
     :jsonparameter password: min 5 characters
457 457
 
458 458
     **Example Response**:
... ...
@@ -367,7 +367,8 @@ POST /v1/users
367 367
     {"email": "sam@dotcloud.com", "password": "toto42", "username": "foobar"'}
368 368
 
369 369
 **Validation**:
370
-    - **username** : min 4 character, max 30 characters, must match the regular expression [a-z0-9_].
370
+    - **username**: min 4 character, max 30 characters, must match the regular
371
+      expression [a-z0-9\_].
371 372
     - **password**: min 5 characters
372 373
 
373 374
 **Valid**: return HTTP 200
... ...
@@ -566,4 +567,4 @@ Next request::
566 566
 ---------------------
567 567
 
568 568
 - 1.0 : May 6th 2013 : initial release 
569
-- 1.1 : June 1st 2013 : Added Delete Repository and way to handle new source namespace.
570 569
\ No newline at end of file
570
+- 1.1 : June 1st 2013 : Added Delete Repository and way to handle new source namespace.
... ...
@@ -14,6 +14,7 @@
14 14
 
15 15
       -a=map[]: Attach to stdin, stdout or stderr.
16 16
       -c=0: CPU shares (relative weight)
17
+      -cidfile="": Write the container ID to the file
17 18
       -d=false: Detached mode: leave the container running in the background
18 19
       -e=[]: Set environment variables
19 20
       -h="": Container host name
... ...
@@ -26,3 +27,13 @@
26 26
       -v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "host-dir" is missing, then docker creates a new volume.
27 27
       -volumes-from="": Mount all volumes from the given container.
28 28
       -entrypoint="": Overwrite the default entrypoint set by the image.
29
+
30
+
31
+Examples
32
+--------
33
+
34
+.. code-block:: bash
35
+
36
+    docker run -cidfile /tmp/docker_test.cid ubuntu echo "test"
37
+
38
+| This will create a container and print "test" to the console. The cidfile flag makes docker attempt to create a new file and write the container ID to it. If the file exists already, docker will return an error. Docker will close this file when docker run exits.
... ...
@@ -8,6 +8,8 @@
8 8
 
9 9
 ::
10 10
 
11
-    Usage: docker stop [OPTIONS] NAME
11
+    Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
12 12
 
13 13
     Stop a running container
14
+
15
+      -t=10: Number of seconds to wait for the container to stop before killing it.
... ...
@@ -37,5 +37,6 @@ Contents:
37 37
   start   <command/start>
38 38
   stop    <command/stop>
39 39
   tag     <command/tag>
40
+  top     <command/top>
40 41
   version <command/version>
41
-  wait    <command/wait>
42 42
\ No newline at end of file
43
+  wait    <command/wait>
... ...
@@ -46,11 +46,13 @@ in a standard build environment.
46 46
 You can run an interactive session in the newly built container:
47 47
 
48 48
 ::
49
+
49 50
     docker run -i -t docker bash
50 51
 
51 52
 
52 53
 To extract the binaries from the container:
53 54
 
54 55
 ::
56
+
55 57
     docker run docker sh -c 'cat $(which docker)' > docker-build && chmod +x docker-build
56 58
 
... ...
@@ -2,8 +2,6 @@
2 2
 :description: An overview of the Docker Documentation
3 3
 :keywords: containers, lxc, concepts, explanation
4 4
 
5
-.. _introduction:
6
-
7 5
 Welcome
8 6
 =======
9 7
 
... ...
@@ -1,6 +1,6 @@
1
-:title: Dockerfile Builder
2
-:description: Docker Builder specifes a simple DSL which allows you to automate the steps you would normally manually take to create an image.
3
-:keywords: builder, docker, Docker Builder, automation, image creation
1
+:title: Dockerfiles for Images
2
+:description: Dockerfiles use a simple DSL which allows you to automate the steps you would normally manually take to create an image.
3
+:keywords: builder, docker, Dockerfile, automation, image creation
4 4
 
5 5
 ==================
6 6
 Dockerfile Builder
... ...
@@ -30,7 +30,7 @@ build succeeds:
30 30
 
31 31
     ``docker build -t shykes/myapp .``
32 32
 
33
-Docker will run your steps one-by-one, committing the result if necessary, 
33
+Docker will run your steps one-by-one, committing the result if necessary,
34 34
 before finally outputting the ID of your new image.
35 35
 
36 36
 2. Format
... ...
@@ -43,7 +43,7 @@ The Dockerfile format is quite simple:
43 43
     # Comment
44 44
     INSTRUCTION arguments
45 45
 
46
-The Instruction is not case-sensitive, however convention is for them to be 
46
+The Instruction is not case-sensitive, however convention is for them to be
47 47
 UPPERCASE in order to distinguish them from arguments more easily.
48 48
 
49 49
 Docker evaluates the instructions in a Dockerfile in order. **The first
... ...
@@ -106,7 +106,7 @@ The ``CMD`` instruction sets the command to be executed when running
106 106
 the image.  This is functionally equivalent to running ``docker commit
107 107
 -run '{"Cmd": <command>}'`` outside the builder.
108 108
 
109
-.. note:: 
109
+.. note::
110 110
     Don't confuse `RUN` with `CMD`. `RUN` actually runs a
111 111
     command and commits the result; `CMD` does not execute anything at
112 112
     build time, but specifies the intended command for the image.
... ...
@@ -131,7 +131,7 @@ value ``<value>``. This value will be passed to all future ``RUN``
131 131
 instructions. This is functionally equivalent to prefixing the command
132 132
 with ``<key>=<value>``
133 133
 
134
-.. note:: 
134
+.. note::
135 135
     The environment variables will persist when a container is run
136 136
     from the resulting image.
137 137
 
... ...
@@ -152,16 +152,24 @@ destination container.
152 152
 
153 153
 The copy obeys the following rules:
154 154
 
155
+* If ``<src>`` is a URL and ``<dest>`` does not end with a trailing slash,
156
+  then a file is downloaded from the URL and copied to ``<dest>``.
157
+* If ``<src>`` is a URL and ``<dest>`` does end with a trailing slash,
158
+  then the filename is inferred from the URL and the file is downloaded to
159
+  ``<dest>/<filename>``. For instance, ``ADD http://example.com/foobar /``
160
+  would create the file ``/foobar``. The URL must have a nontrivial path
161
+  so that an appropriate filename can be discovered in this case
162
+  (``http://example.com`` will not work).
155 163
 * If ``<src>`` is a directory, the entire directory is copied,
156 164
   including filesystem metadata.
157 165
 * If ``<src>``` is a tar archive in a recognized compression format
158 166
   (identity, gzip, bzip2 or xz), it is unpacked as a directory.
159 167
 
160 168
   When a directory is copied or unpacked, it has the same behavior as
161
-  ``tar -x``: the result is the union of 
169
+  ``tar -x``: the result is the union of
162 170
 
163 171
   1. whatever existed at the destination path and
164
-  2. the contents of the source tree, 
172
+  2. the contents of the source tree,
165 173
 
166 174
   with conflicts resolved in favor of 2) on a file-by-file basis.
167 175
 
... ...
@@ -177,7 +185,7 @@ The copy obeys the following rules:
177 177
   with mode 0700, uid and gid 0.
178 178
 
179 179
 3.8 ENTRYPOINT
180
+--------------
180 181
 
181 182
     ``ENTRYPOINT /bin/echo``
182 183
 
... ...
@@ -203,14 +211,14 @@ container created from the image.
203 203
     # Nginx
204 204
     #
205 205
     # VERSION               0.0.1
206
-    
206
+
207 207
     FROM      ubuntu
208 208
     MAINTAINER Guillaume J. Charmes "guillaume@dotcloud.com"
209
-    
209
+
210 210
     # make sure the package repository is up to date
211 211
     RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
212 212
     RUN apt-get update
213
-    
213
+
214 214
     RUN apt-get install -y inotify-tools nginx apache2 openssh-server
215 215
 
216 216
 .. code-block:: bash
... ...
@@ -218,12 +226,12 @@ container created from the image.
218 218
     # Firefox over VNC
219 219
     #
220 220
     # VERSION               0.3
221
-    
221
+
222 222
     FROM ubuntu
223 223
     # make sure the package repository is up to date
224 224
     RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
225 225
     RUN apt-get update
226
-    
226
+
227 227
     # Install vnc, xvfb in order to create a 'fake' display and firefox
228 228
     RUN apt-get install -y x11vnc xvfb firefox
229 229
     RUN mkdir /.vnc
... ...
@@ -231,7 +239,7 @@ container created from the image.
231 231
     RUN x11vnc -storepasswd 1234 ~/.vnc/passwd
232 232
     # Autostart firefox (might not be the best way, but it does the trick)
233 233
     RUN bash -c 'echo "firefox" >> /.bashrc'
234
-    
234
+
235 235
     EXPOSE 5900
236 236
     CMD    ["x11vnc", "-forever", "-usepw", "-create"]
237 237
 
... ...
@@ -119,7 +119,7 @@ your container to an image within your username namespace.
119 119
 
120 120
 
121 121
 Pushing a container to its repository
122
+-------------------------------------
122 123
 
123 124
 In order to push an image to its repository you need to have committed
124 125
 your container to a named image (see above)
... ...
@@ -167,12 +167,12 @@ func (runtime *Runtime) Register(container *Container) error {
167 167
 	return nil
168 168
 }
169 169
 
170
-func (runtime *Runtime) LogToDisk(src *utils.WriteBroadcaster, dst string) error {
170
+func (runtime *Runtime) LogToDisk(src *utils.WriteBroadcaster, dst, stream string) error {
171 171
 	log, err := os.OpenFile(dst, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600)
172 172
 	if err != nil {
173 173
 		return err
174 174
 	}
175
-	src.AddWriter(log)
175
+	src.AddWriter(log, stream)
176 176
 	return nil
177 177
 }
178 178
 
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"fmt"
6 6
 	"github.com/dotcloud/docker/utils"
7 7
 	"io"
8
-	"io/ioutil"
9 8
 	"log"
10 9
 	"net"
11 10
 	"os"
... ...
@@ -247,36 +246,13 @@ func TestGet(t *testing.T) {
247 247
 	runtime := mkRuntime(t)
248 248
 	defer nuke(runtime)
249 249
 
250
-	builder := NewBuilder(runtime)
251
-
252
-	container1, err := builder.Create(&Config{
253
-		Image: GetTestImage(runtime).ID,
254
-		Cmd:   []string{"ls", "-al"},
255
-	},
256
-	)
257
-	if err != nil {
258
-		t.Fatal(err)
259
-	}
250
+	container1, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
260 251
 	defer runtime.Destroy(container1)
261 252
 
262
-	container2, err := builder.Create(&Config{
263
-		Image: GetTestImage(runtime).ID,
264
-		Cmd:   []string{"ls", "-al"},
265
-	},
266
-	)
267
-	if err != nil {
268
-		t.Fatal(err)
269
-	}
253
+	container2, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
270 254
 	defer runtime.Destroy(container2)
271 255
 
272
-	container3, err := builder.Create(&Config{
273
-		Image: GetTestImage(runtime).ID,
274
-		Cmd:   []string{"ls", "-al"},
275
-	},
276
-	)
277
-	if err != nil {
278
-		t.Fatal(err)
279
-	}
256
+	container3, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
280 257
 	defer runtime.Destroy(container3)
281 258
 
282 259
 	if runtime.Get(container1.ID) != container1 {
... ...
@@ -431,46 +407,14 @@ func TestAllocateUDPPortLocalhost(t *testing.T) {
431 431
 }
432 432
 
433 433
 func TestRestore(t *testing.T) {
434
-
435
-	root, err := ioutil.TempDir("", "docker-test")
436
-	if err != nil {
437
-		t.Fatal(err)
438
-	}
439
-	if err := os.Remove(root); err != nil {
440
-		t.Fatal(err)
441
-	}
442
-	if err := utils.CopyDirectory(unitTestStoreBase, root); err != nil {
443
-		t.Fatal(err)
444
-	}
445
-
446
-	runtime1, err := NewRuntimeFromDirectory(root, false)
447
-	if err != nil {
448
-		t.Fatal(err)
449
-	}
450
-
451
-	builder := NewBuilder(runtime1)
452
-
434
+	runtime1 := mkRuntime(t)
435
+	defer nuke(runtime1)
453 436
 	// Create a container with one instance of docker
454
-	container1, err := builder.Create(&Config{
455
-		Image: GetTestImage(runtime1).ID,
456
-		Cmd:   []string{"ls", "-al"},
457
-	},
458
-	)
459
-	if err != nil {
460
-		t.Fatal(err)
461
-	}
437
+	container1, _, _ := mkContainer(runtime1, []string{"_", "ls", "-al"}, t)
462 438
 	defer runtime1.Destroy(container1)
463 439
 
464 440
 	// Create a second container meant to be killed
465
-	container2, err := builder.Create(&Config{
466
-		Image:     GetTestImage(runtime1).ID,
467
-		Cmd:       []string{"/bin/cat"},
468
-		OpenStdin: true,
469
-	},
470
-	)
471
-	if err != nil {
472
-		t.Fatal(err)
473
-	}
441
+	container2, _, _ := mkContainer(runtime1, []string{"-i", "_", "/bin/cat"}, t)
474 442
 	defer runtime1.Destroy(container2)
475 443
 
476 444
 	// Start the container non blocking
... ...
@@ -505,7 +449,7 @@ func TestRestore(t *testing.T) {
505 505
 
506 506
 	// Here are are simulating a docker restart - that is, reloading all containers
507 507
 	// from scratch
508
-	runtime2, err := NewRuntimeFromDirectory(root, false)
508
+	runtime2, err := NewRuntimeFromDirectory(runtime1.root, false)
509 509
 	if err != nil {
510 510
 		t.Fatal(err)
511 511
 	}
... ...
@@ -2,6 +2,7 @@ package docker
2 2
 
3 3
 import (
4 4
 	"bufio"
5
+	"encoding/json"
5 6
 	"errors"
6 7
 	"fmt"
7 8
 	"github.com/dotcloud/docker/auth"
... ...
@@ -1054,20 +1055,41 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
1054 1054
 	}
1055 1055
 	//logs
1056 1056
 	if logs {
1057
-		if stdout {
1058
-			cLog, err := container.ReadLog("stdout")
1059
-			if err != nil {
1060
-				utils.Debugf("Error reading logs (stdout): %s", err)
1061
-			} else if _, err := io.Copy(out, cLog); err != nil {
1062
-				utils.Debugf("Error streaming logs (stdout): %s", err)
1057
+		cLog, err := container.ReadLog("json")
1058
+		if err != nil && os.IsNotExist(err) {
1059
+			// Legacy logs
1060
+			utils.Debugf("Old logs format")
1061
+			if stdout {
1062
+				cLog, err := container.ReadLog("stdout")
1063
+				if err != nil {
1064
+					utils.Debugf("Error reading logs (stdout): %s", err)
1065
+				} else if _, err := io.Copy(out, cLog); err != nil {
1066
+					utils.Debugf("Error streaming logs (stdout): %s", err)
1067
+				}
1063 1068
 			}
1064
-		}
1065
-		if stderr {
1066
-			cLog, err := container.ReadLog("stderr")
1067
-			if err != nil {
1068
-				utils.Debugf("Error reading logs (stderr): %s", err)
1069
-			} else if _, err := io.Copy(out, cLog); err != nil {
1070
-				utils.Debugf("Error streaming logs (stderr): %s", err)
1069
+			if stderr {
1070
+				cLog, err := container.ReadLog("stderr")
1071
+				if err != nil {
1072
+					utils.Debugf("Error reading logs (stderr): %s", err)
1073
+				} else if _, err := io.Copy(out, cLog); err != nil {
1074
+					utils.Debugf("Error streaming logs (stderr): %s", err)
1075
+				}
1076
+			}
1077
+		} else if err != nil {
1078
+			utils.Debugf("Error reading logs (json): %s", err)
1079
+		} else {
1080
+			dec := json.NewDecoder(cLog)
1081
+			for {
1082
+				var l utils.JSONLog
1083
+				if err := dec.Decode(&l); err == io.EOF {
1084
+					break
1085
+				} else if err != nil {
1086
+					utils.Debugf("Error streaming logs: %s", err)
1087
+					break
1088
+				}
1089
+				if (l.Stream == "stdout" && stdout) || (l.Stream == "stderr" && stderr) {
1090
+					fmt.Fprintf(out, "%s", l.Log)
1091
+				}
1071 1092
 			}
1072 1093
 		}
1073 1094
 	}
... ...
@@ -248,30 +248,54 @@ func (r *bufReader) Close() error {
248 248
 
249 249
 type WriteBroadcaster struct {
250 250
 	sync.Mutex
251
-	writers map[io.WriteCloser]struct{}
251
+	buf     *bytes.Buffer
252
+	writers map[StreamWriter]bool
252 253
 }
253 254
 
254
-func (w *WriteBroadcaster) AddWriter(writer io.WriteCloser) {
255
-	w.Lock()
256
-	w.writers[writer] = struct{}{}
257
-	w.Unlock()
255
+type StreamWriter struct {
256
+	wc     io.WriteCloser
257
+	stream string
258 258
 }
259 259
 
260
-// FIXME: Is that function used?
261
-// FIXME: This relies on the concrete writer type used having equality operator
262
-func (w *WriteBroadcaster) RemoveWriter(writer io.WriteCloser) {
260
+func (w *WriteBroadcaster) AddWriter(writer io.WriteCloser, stream string) {
263 261
 	w.Lock()
264
-	delete(w.writers, writer)
262
+	sw := StreamWriter{wc: writer, stream: stream}
263
+	w.writers[sw] = true
265 264
 	w.Unlock()
266 265
 }
267 266
 
267
+type JSONLog struct {
268
+	Log     string    `json:"log,omitempty"`
269
+	Stream  string    `json:"stream,omitempty"`
270
+	Created time.Time `json:"time"`
271
+}
272
+
268 273
 func (w *WriteBroadcaster) Write(p []byte) (n int, err error) {
269 274
 	w.Lock()
270 275
 	defer w.Unlock()
271
-	for writer := range w.writers {
272
-		if n, err := writer.Write(p); err != nil || n != len(p) {
276
+	w.buf.Write(p)
277
+	for sw := range w.writers {
278
+		lp := p
279
+		if sw.stream != "" {
280
+			lp = nil
281
+			for {
282
+				line, err := w.buf.ReadString('\n')
283
+				if err != nil {
284
+					w.buf.Write([]byte(line))
285
+					break
286
+				}
287
+				b, err := json.Marshal(&JSONLog{Log: line, Stream: sw.stream, Created: time.Now()})
288
+				if err != nil {
289
+					// On error, evict the writer
290
+					delete(w.writers, sw)
291
+					continue
292
+				}
293
+				lp = append(lp, b...)
294
+			}
295
+		}
296
+		if n, err := sw.wc.Write(lp); err != nil || n != len(lp) {
273 297
 			// On error, evict the writer
274
-			delete(w.writers, writer)
298
+			delete(w.writers, sw)
275 299
 		}
276 300
 	}
277 301
 	return len(p), nil
... ...
@@ -280,15 +304,15 @@ func (w *WriteBroadcaster) Write(p []byte) (n int, err error) {
280 280
 func (w *WriteBroadcaster) CloseWriters() error {
281 281
 	w.Lock()
282 282
 	defer w.Unlock()
283
-	for writer := range w.writers {
284
-		writer.Close()
283
+	for sw := range w.writers {
284
+		sw.wc.Close()
285 285
 	}
286
-	w.writers = make(map[io.WriteCloser]struct{})
286
+	w.writers = make(map[StreamWriter]bool)
287 287
 	return nil
288 288
 }
289 289
 
290 290
 func NewWriteBroadcaster() *WriteBroadcaster {
291
-	return &WriteBroadcaster{writers: make(map[io.WriteCloser]struct{})}
291
+	return &WriteBroadcaster{writers: make(map[StreamWriter]bool), buf: bytes.NewBuffer(nil)}
292 292
 }
293 293
 
294 294
 func GetTotalUsedFds() int {
... ...
@@ -60,9 +60,9 @@ func TestWriteBroadcaster(t *testing.T) {
60 60
 
61 61
 	// Test 1: Both bufferA and bufferB should contain "foo"
62 62
 	bufferA := &dummyWriter{}
63
-	writer.AddWriter(bufferA)
63
+	writer.AddWriter(bufferA, "")
64 64
 	bufferB := &dummyWriter{}
65
-	writer.AddWriter(bufferB)
65
+	writer.AddWriter(bufferB, "")
66 66
 	writer.Write([]byte("foo"))
67 67
 
68 68
 	if bufferA.String() != "foo" {
... ...
@@ -76,7 +76,7 @@ func TestWriteBroadcaster(t *testing.T) {
76 76
 	// Test2: bufferA and bufferB should contain "foobar",
77 77
 	// while bufferC should only contain "bar"
78 78
 	bufferC := &dummyWriter{}
79
-	writer.AddWriter(bufferC)
79
+	writer.AddWriter(bufferC, "")
80 80
 	writer.Write([]byte("bar"))
81 81
 
82 82
 	if bufferA.String() != "foobar" {
... ...
@@ -91,35 +91,22 @@ func TestWriteBroadcaster(t *testing.T) {
91 91
 		t.Errorf("Buffer contains %v", bufferC.String())
92 92
 	}
93 93
 
94
-	// Test3: Test removal
95
-	writer.RemoveWriter(bufferB)
96
-	writer.Write([]byte("42"))
97
-	if bufferA.String() != "foobar42" {
98
-		t.Errorf("Buffer contains %v", bufferA.String())
99
-	}
100
-	if bufferB.String() != "foobar" {
101
-		t.Errorf("Buffer contains %v", bufferB.String())
102
-	}
103
-	if bufferC.String() != "bar42" {
104
-		t.Errorf("Buffer contains %v", bufferC.String())
105
-	}
106
-
107
-	// Test4: Test eviction on failure
94
+	// Test3: Test eviction on failure
108 95
 	bufferA.failOnWrite = true
109 96
 	writer.Write([]byte("fail"))
110
-	if bufferA.String() != "foobar42" {
97
+	if bufferA.String() != "foobar" {
111 98
 		t.Errorf("Buffer contains %v", bufferA.String())
112 99
 	}
113
-	if bufferC.String() != "bar42fail" {
100
+	if bufferC.String() != "barfail" {
114 101
 		t.Errorf("Buffer contains %v", bufferC.String())
115 102
 	}
116 103
 	// Even though we reset the flag, no more writes should go in there
117 104
 	bufferA.failOnWrite = false
118 105
 	writer.Write([]byte("test"))
119
-	if bufferA.String() != "foobar42" {
106
+	if bufferA.String() != "foobar" {
120 107
 		t.Errorf("Buffer contains %v", bufferA.String())
121 108
 	}
122
-	if bufferC.String() != "bar42failtest" {
109
+	if bufferC.String() != "barfailtest" {
123 110
 		t.Errorf("Buffer contains %v", bufferC.String())
124 111
 	}
125 112
 
... ...
@@ -141,7 +128,7 @@ func TestRaceWriteBroadcaster(t *testing.T) {
141 141
 	writer := NewWriteBroadcaster()
142 142
 	c := make(chan bool)
143 143
 	go func() {
144
-		writer.AddWriter(devNullCloser(0))
144
+		writer.AddWriter(devNullCloser(0), "")
145 145
 		c <- true
146 146
 	}()
147 147
 	writer.Write([]byte("hello"))
... ...
@@ -84,18 +84,25 @@ func readFile(src string, t *testing.T) (content string) {
84 84
 }
85 85
 
86 86
 // Create a test container from the given runtime `r` and run arguments `args`.
87
-// The image name (eg. the XXX in []string{"-i", "-t", "XXX", "bash"}, is dynamically replaced by the current test image.
87
+// If the image name is "_", (eg. []string{"-i", "-t", "_", "bash"}, it is
88
+// dynamically replaced by the current test image.
88 89
 // The caller is responsible for destroying the container.
89 90
 // Call t.Fatal() at the first error.
90 91
 func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, *HostConfig, error) {
91 92
 	config, hostConfig, _, err := ParseRun(args, nil)
93
+	defer func() {
94
+		if err != nil && t != nil {
95
+			t.Fatal(err)
96
+		}
97
+	}()
92 98
 	if err != nil {
93 99
 		return nil, nil, err
94 100
 	}
95
-	config.Image = GetTestImage(r).ID
101
+	if config.Image == "_" {
102
+		config.Image = GetTestImage(r).ID
103
+	}
96 104
 	c, err := NewBuilder(r).Create(config)
97 105
 	if err != nil {
98
-		t.Fatal(err)
99 106
 		return nil, nil, err
100 107
 	}
101 108
 	return c, hostConfig, nil