Browse code

Merge pull request #1842 from dotcloud/split_stdout_stderr

* Runtime: Split stdout stderr

Guillaume J. Charmes authored on 2013/09/27 10:05:24
Showing 7 changed files
... ...
@@ -21,10 +21,12 @@ import (
21 21
 	"strings"
22 22
 )
23 23
 
24
-const APIVERSION = 1.5
25
-const DEFAULTHTTPHOST = "127.0.0.1"
26
-const DEFAULTHTTPPORT = 4243
27
-const DEFAULTUNIXSOCKET = "/var/run/docker.sock"
24
+const (
25
+	APIVERSION        = 1.6
26
+	DEFAULTHTTPHOST   = "127.0.0.1"
27
+	DEFAULTHTTPPORT   = 4243
28
+	DEFAULTUNIXSOCKET = "/var/run/docker.sock"
29
+)
28 30
 
29 31
 type HttpApiFunc func(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error
30 32
 
... ...
@@ -710,32 +712,43 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r
710 710
 	}
711 711
 	name := vars["name"]
712 712
 
713
-	if _, err := srv.ContainerInspect(name); err != nil {
713
+	c, err := srv.ContainerInspect(name)
714
+	if err != nil {
714 715
 		return err
715 716
 	}
716 717
 
717
-	in, out, err := hijackServer(w)
718
+	inStream, outStream, err := hijackServer(w)
718 719
 	if err != nil {
719 720
 		return err
720 721
 	}
721 722
 	defer func() {
722
-		if tcpc, ok := in.(*net.TCPConn); ok {
723
+		if tcpc, ok := inStream.(*net.TCPConn); ok {
723 724
 			tcpc.CloseWrite()
724 725
 		} else {
725
-			in.Close()
726
+			inStream.Close()
726 727
 		}
727 728
 	}()
728 729
 	defer func() {
729
-		if tcpc, ok := out.(*net.TCPConn); ok {
730
+		if tcpc, ok := outStream.(*net.TCPConn); ok {
730 731
 			tcpc.CloseWrite()
731
-		} else if closer, ok := out.(io.Closer); ok {
732
+		} else if closer, ok := outStream.(io.Closer); ok {
732 733
 			closer.Close()
733 734
 		}
734 735
 	}()
735 736
 
736
-	fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
737
-	if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, in, out); err != nil {
738
-		fmt.Fprintf(out, "Error: %s\n", err)
737
+	var errStream io.Writer
738
+
739
+	fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
740
+
741
+	if !c.Config.Tty && version >= 1.6 {
742
+		errStream = utils.NewStdWriter(outStream, utils.Stderr)
743
+		outStream = utils.NewStdWriter(outStream, utils.Stdout)
744
+	} else {
745
+		errStream = outStream
746
+	}
747
+
748
+	if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, inStream, outStream, errStream); err != nil {
749
+		fmt.Fprintf(outStream, "Error: %s\n", err)
739 750
 	}
740 751
 	return nil
741 752
 }
... ...
@@ -778,7 +791,7 @@ func wsContainersAttach(srv *Server, version float64, w http.ResponseWriter, r *
778 778
 	h := websocket.Handler(func(ws *websocket.Conn) {
779 779
 		defer ws.Close()
780 780
 
781
-		if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, ws, ws); err != nil {
781
+		if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, ws, ws, ws); err != nil {
782 782
 			utils.Debugf("Error: %s", err)
783 783
 		}
784 784
 	})
... ...
@@ -566,7 +566,6 @@ func TestPostCommit(t *testing.T) {
566 566
 
567 567
 	srv := &Server{runtime: runtime}
568 568
 
569
-
570 569
 	// Create a container and remove a file
571 570
 	container, err := runtime.Create(
572 571
 		&Config{
... ...
@@ -952,7 +951,96 @@ func TestPostContainersAttach(t *testing.T) {
952 952
 	})
953 953
 
954 954
 	setTimeout(t, "read/write assertion timed out", 2*time.Second, func() {
955
-		if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
955
+		if err := assertPipe("hello\n", string([]byte{1, 0, 0, 0, 0, 0, 0, 6})+"hello", stdout, stdinPipe, 15); err != nil {
956
+			t.Fatal(err)
957
+		}
958
+	})
959
+
960
+	// Close pipes (client disconnects)
961
+	if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
962
+		t.Fatal(err)
963
+	}
964
+
965
+	// Wait for attach to finish, the client disconnected, therefore, Attach finished his job
966
+	setTimeout(t, "Waiting for CmdAttach timed out", 10*time.Second, func() {
967
+		<-c1
968
+	})
969
+
970
+	// We closed stdin, expect /bin/cat to still be running
971
+	// Wait a little bit to make sure container.monitor() did his thing
972
+	err = container.WaitTimeout(500 * time.Millisecond)
973
+	if err == nil || !container.State.Running {
974
+		t.Fatalf("/bin/cat is not running after closing stdin")
975
+	}
976
+
977
+	// Try to avoid the timeout in destroy. Best effort, don't check error
978
+	cStdin, _ := container.StdinPipe()
979
+	cStdin.Close()
980
+	container.Wait()
981
+}
982
+
983
+func TestPostContainersAttachStderr(t *testing.T) {
984
+	runtime := mkRuntime(t)
985
+	defer nuke(runtime)
986
+
987
+	srv := &Server{runtime: runtime}
988
+
989
+	container, err := runtime.Create(
990
+		&Config{
991
+			Image:     GetTestImage(runtime).ID,
992
+			Cmd:       []string{"/bin/sh", "-c", "/bin/cat >&2"},
993
+			OpenStdin: true,
994
+		},
995
+	)
996
+	if err != nil {
997
+		t.Fatal(err)
998
+	}
999
+	defer runtime.Destroy(container)
1000
+
1001
+	// Start the process
1002
+	hostConfig := &HostConfig{}
1003
+	if err := container.Start(hostConfig); err != nil {
1004
+		t.Fatal(err)
1005
+	}
1006
+
1007
+	stdin, stdinPipe := io.Pipe()
1008
+	stdout, stdoutPipe := io.Pipe()
1009
+
1010
+	// Try to avoid the timeout in destroy. Best effort, don't check error
1011
+	defer func() {
1012
+		closeWrap(stdin, stdinPipe, stdout, stdoutPipe)
1013
+		container.Kill()
1014
+	}()
1015
+
1016
+	// Attach to it
1017
+	c1 := make(chan struct{})
1018
+	go func() {
1019
+		defer close(c1)
1020
+
1021
+		r := &hijackTester{
1022
+			ResponseRecorder: httptest.NewRecorder(),
1023
+			in:               stdin,
1024
+			out:              stdoutPipe,
1025
+		}
1026
+
1027
+		req, err := http.NewRequest("POST", "/containers/"+container.ID+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{}))
1028
+		if err != nil {
1029
+			t.Fatal(err)
1030
+		}
1031
+
1032
+		if err := postContainersAttach(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil {
1033
+			t.Fatal(err)
1034
+		}
1035
+	}()
1036
+
1037
+	// Acknowledge hijack
1038
+	setTimeout(t, "hijack acknowledge timed out", 2*time.Second, func() {
1039
+		stdout.Read([]byte{})
1040
+		stdout.Read(make([]byte, 4096))
1041
+	})
1042
+
1043
+	setTimeout(t, "read/write assertion timed out", 2*time.Second, func() {
1044
+		if err := assertPipe("hello\n", string([]byte{2, 0, 0, 0, 0, 0, 0, 6})+"hello", stdout, stdinPipe, 15); err != nil {
956 1045
 			t.Fatal(err)
957 1046
 		}
958 1047
 	})
... ...
@@ -1230,7 +1230,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
1230 1230
 		return nil
1231 1231
 	}
1232 1232
 
1233
-	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out); err != nil {
1233
+	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out, cli.err); err != nil {
1234 1234
 		return err
1235 1235
 	}
1236 1236
 	return nil
... ...
@@ -1273,7 +1273,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
1273 1273
 	v.Set("stdout", "1")
1274 1274
 	v.Set("stderr", "1")
1275 1275
 
1276
-	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, cli.in, cli.out); err != nil {
1276
+	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, cli.in, cli.out, cli.err); err != nil {
1277 1277
 		return err
1278 1278
 	}
1279 1279
 	return nil
... ...
@@ -1537,7 +1537,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
1537 1537
 		v := url.Values{}
1538 1538
 		v.Set("logs", "1")
1539 1539
 		v.Set("stream", "1")
1540
-		var out io.Writer
1540
+		var out, stderr io.Writer
1541 1541
 
1542 1542
 		if config.AttachStdin {
1543 1543
 			v.Set("stdin", "1")
... ...
@@ -1548,7 +1548,11 @@ func (cli *DockerCli) CmdRun(args ...string) error {
1548 1548
 		}
1549 1549
 		if config.AttachStderr {
1550 1550
 			v.Set("stderr", "1")
1551
-			out = cli.out
1551
+			if config.Tty {
1552
+				stderr = cli.out
1553
+			} else {
1554
+				stderr = cli.err
1555
+			}
1552 1556
 		}
1553 1557
 
1554 1558
 		signals := make(chan os.Signal, 1)
... ...
@@ -1562,7 +1566,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
1562 1562
 			}
1563 1563
 		}()
1564 1564
 
1565
-		if err := cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, cli.in, out); err != nil {
1565
+		if err := cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, cli.in, out, stderr); err != nil {
1566 1566
 			utils.Debugf("Error hijack: %s", err)
1567 1567
 			return err
1568 1568
 		}
... ...
@@ -1729,7 +1733,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h
1729 1729
 	return nil
1730 1730
 }
1731 1731
 
1732
-func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, out io.Writer) error {
1732
+func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer) error {
1733 1733
 
1734 1734
 	req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil)
1735 1735
 	if err != nil {
... ...
@@ -1755,10 +1759,16 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
1755 1755
 	rwc, br := clientconn.Hijack()
1756 1756
 	defer rwc.Close()
1757 1757
 
1758
-	var receiveStdout (chan error)
1759
-	if out != nil {
1760
-		receiveStdout = utils.Go(func() error {
1761
-			_, err := io.Copy(out, br)
1758
+	var receiveStdout chan error
1759
+
1760
+	if stdout != nil {
1761
+		receiveStdout = utils.Go(func() (err error) {
1762
+			// When TTY is ON, use regular copy
1763
+			if setRawTerminal {
1764
+				_, err = io.Copy(stdout, br)
1765
+			} else {
1766
+				_, err = utils.StdCopy(stdout, stderr, br)
1767
+			}
1762 1768
 			utils.Debugf("[hijack] End of stdout")
1763 1769
 			return err
1764 1770
 		})
... ...
@@ -1790,7 +1800,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
1790 1790
 		return nil
1791 1791
 	})
1792 1792
 
1793
-	if out != nil {
1793
+	if stdout != nil {
1794 1794
 		if err := <-receiveStdout; err != nil {
1795 1795
 			utils.Debugf("Error receiveStdout: %s", err)
1796 1796
 			return err
... ...
@@ -27,14 +27,29 @@ Docker Remote API
27 27
 2. Versions
28 28
 ===========
29 29
 
30
-The current version of the API is 1.5
30
+The current version of the API is 1.6
31 31
 
32 32
 Calling /images/<name>/insert is the same as calling
33
-/v1.5/images/<name>/insert 
33
+/v1.6/images/<name>/insert
34 34
 
35 35
 You can still call an old version of the api using
36 36
 /v1.0/images/<name>/insert
37 37
 
38
+:doc:`docker_remote_api_v1.6`
39
+*****************************
40
+
41
+What's new
42
+----------
43
+
44
+.. http:post:: /containers/(id)/attach
45
+
46
+   **New!** You can now split stderr from stdout. This is done by prefixing
47
+   a header to each transmition. See :http:post:`/containers/(id)/attach`.
48
+   The WebSocket attach is unchanged.
49
+   Note that attach calls on the previous API version didn't change. Stdout and
50
+   stderr are merged.
51
+
52
+
38 53
 :doc:`docker_remote_api_v1.5`
39 54
 *****************************
40 55
 
41 56
new file mode 100644
... ...
@@ -0,0 +1,1218 @@
0
+:title: Remote API v1.6
1
+:description: API Documentation for Docker
2
+:keywords: API, Docker, rcli, REST, documentation
3
+
4
+:orphan:
5
+
6
+======================
7
+Docker Remote API v1.6
8
+======================
9
+
10
+.. contents:: Table of Contents
11
+
12
+1. Brief introduction
13
+=====================
14
+
15
+- The Remote API is replacing rcli
16
+- Default port in the docker daemon is 4243
17
+- 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
18
+
19
+2. Endpoints
20
+============
21
+
22
+2.1 Containers
23
+--------------
24
+
25
+List containers
26
+***************
27
+
28
+.. http:get:: /containers/json
29
+
30
+	List containers
31
+
32
+	**Example request**:
33
+
34
+	.. sourcecode:: http
35
+
36
+	   GET /containers/json?all=1&before=8dfafdbc3a40&size=1 HTTP/1.1
37
+	   
38
+	**Example response**:
39
+
40
+	.. sourcecode:: http
41
+
42
+	   HTTP/1.1 200 OK
43
+	   Content-Type: application/json
44
+	   
45
+	   [
46
+		{
47
+			"Id": "8dfafdbc3a40",
48
+			"Image": "base:latest",
49
+			"Command": "echo 1",
50
+			"Created": 1367854155,
51
+			"Status": "Exit 0",
52
+			"Ports":[{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}],
53
+			"SizeRw":12288,
54
+			"SizeRootFs":0
55
+		},
56
+		{
57
+			"Id": "9cd87474be90",
58
+			"Image": "base:latest",
59
+			"Command": "echo 222222",
60
+			"Created": 1367854155,
61
+			"Status": "Exit 0",
62
+			"Ports":[],
63
+			"SizeRw":12288,
64
+			"SizeRootFs":0
65
+		},
66
+		{
67
+			"Id": "3176a2479c92",
68
+			"Image": "base:latest",
69
+			"Command": "echo 3333333333333333",
70
+			"Created": 1367854154,
71
+			"Status": "Exit 0",
72
+			"Ports":[],
73
+			"SizeRw":12288,
74
+			"SizeRootFs":0
75
+		},
76
+		{
77
+			"Id": "4cb07b47f9fb",
78
+			"Image": "base:latest",
79
+			"Command": "echo 444444444444444444444444444444444",
80
+			"Created": 1367854152,
81
+			"Status": "Exit 0",
82
+			"Ports":[],
83
+			"SizeRw":12288,
84
+			"SizeRootFs":0
85
+		}
86
+	   ]
87
+ 
88
+	:query all: 1/True/true or 0/False/false, Show all containers. Only running containers are shown by default
89
+	:query limit: Show ``limit`` last created containers, include non-running ones.
90
+	:query since: Show only containers created since Id, include non-running ones.
91
+	:query before: Show only containers created before Id, include non-running ones.
92
+	:query size: 1/True/true or 0/False/false, Show the containers sizes
93
+	:statuscode 200: no error
94
+	:statuscode 400: bad parameter
95
+	:statuscode 500: server error
96
+
97
+
98
+Create a container
99
+******************
100
+
101
+.. http:post:: /containers/create
102
+
103
+	Create a container
104
+
105
+	**Example request**:
106
+
107
+	.. sourcecode:: http
108
+
109
+	   POST /containers/create HTTP/1.1
110
+	   Content-Type: application/json
111
+
112
+	   {
113
+		"Hostname":"",
114
+		"User":"",
115
+		"Memory":0,
116
+		"MemorySwap":0,
117
+		"AttachStdin":false,
118
+		"AttachStdout":true,
119
+		"AttachStderr":true,
120
+		"PortSpecs":null,
121
+		"Privileged": false,
122
+		"Tty":false,
123
+		"OpenStdin":false,
124
+		"StdinOnce":false,
125
+		"Env":null,
126
+		"Cmd":[
127
+			"date"
128
+		],
129
+		"Dns":null,
130
+		"Image":"base",
131
+		"Volumes":{},
132
+		"VolumesFrom":"",
133
+		"WorkingDir":""
134
+
135
+	   }
136
+	   
137
+	**Example response**:
138
+
139
+	.. sourcecode:: http
140
+
141
+	   HTTP/1.1 201 OK
142
+	   Content-Type: application/json
143
+
144
+	   {
145
+		"Id":"e90e34656806"
146
+		"Warnings":[]
147
+	   }
148
+	
149
+	:jsonparam config: the container's configuration
150
+	:statuscode 201: no error
151
+	:statuscode 404: no such container
152
+	:statuscode 406: impossible to attach (container not running)
153
+	:statuscode 500: server error
154
+
155
+
156
+Inspect a container
157
+*******************
158
+
159
+.. http:get:: /containers/(id)/json
160
+
161
+	Return low-level information on the container ``id``
162
+
163
+	**Example request**:
164
+
165
+	.. sourcecode:: http
166
+
167
+	   GET /containers/4fa6e0f0c678/json HTTP/1.1
168
+	   
169
+	**Example response**:
170
+
171
+	.. sourcecode:: http
172
+
173
+	   HTTP/1.1 200 OK
174
+	   Content-Type: application/json
175
+
176
+	   {
177
+			"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2",
178
+			"Created": "2013-05-07T14:51:42.041847+02:00",
179
+			"Path": "date",
180
+			"Args": [],
181
+			"Config": {
182
+				"Hostname": "4fa6e0f0c678",
183
+				"User": "",
184
+				"Memory": 0,
185
+				"MemorySwap": 0,
186
+				"AttachStdin": false,
187
+				"AttachStdout": true,
188
+				"AttachStderr": true,
189
+				"PortSpecs": null,
190
+				"Tty": false,
191
+				"OpenStdin": false,
192
+				"StdinOnce": false,
193
+				"Env": null,
194
+				"Cmd": [
195
+					"date"
196
+				],
197
+				"Dns": null,
198
+				"Image": "base",
199
+				"Volumes": {},
200
+				"VolumesFrom": "",
201
+				"WorkingDir":""
202
+
203
+			},
204
+			"State": {
205
+				"Running": false,
206
+				"Pid": 0,
207
+				"ExitCode": 0,
208
+				"StartedAt": "2013-05-07T14:51:42.087658+02:01360",
209
+				"Ghost": false
210
+			},
211
+			"Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
212
+			"NetworkSettings": {
213
+				"IpAddress": "",
214
+				"IpPrefixLen": 0,
215
+				"Gateway": "",
216
+				"Bridge": "",
217
+				"PortMapping": null
218
+			},
219
+			"SysInitPath": "/home/kitty/go/src/github.com/dotcloud/docker/bin/docker",
220
+			"ResolvConfPath": "/etc/resolv.conf",
221
+			"Volumes": {}
222
+	   }
223
+
224
+	:statuscode 200: no error
225
+	:statuscode 404: no such container
226
+	:statuscode 500: server error
227
+
228
+
229
+List processes running inside a container
230
+*****************************************
231
+
232
+.. http:get:: /containers/(id)/top
233
+
234
+	List processes running inside the container ``id``
235
+
236
+	**Example request**:
237
+
238
+	.. sourcecode:: http
239
+
240
+	   GET /containers/4fa6e0f0c678/top HTTP/1.1
241
+
242
+	**Example response**:
243
+
244
+	.. sourcecode:: http
245
+
246
+	   HTTP/1.1 200 OK
247
+	   Content-Type: application/json
248
+
249
+	   {
250
+		"Titles":[
251
+			"USER",
252
+			"PID",
253
+			"%CPU",
254
+			"%MEM",
255
+			"VSZ",
256
+			"RSS",
257
+			"TTY",
258
+			"STAT",
259
+			"START",
260
+			"TIME",
261
+			"COMMAND"
262
+			],
263
+		"Processes":[
264
+			["root","20147","0.0","0.1","18060","1864","pts/4","S","10:06","0:00","bash"],
265
+			["root","20271","0.0","0.0","4312","352","pts/4","S+","10:07","0:00","sleep","10"]
266
+		]
267
+	   }
268
+
269
+	:query ps_args: ps arguments to use (eg. aux)
270
+	:statuscode 200: no error
271
+	:statuscode 404: no such container
272
+	:statuscode 500: server error
273
+
274
+
275
+Inspect changes on a container's filesystem
276
+*******************************************
277
+
278
+.. http:get:: /containers/(id)/changes
279
+
280
+	Inspect changes on container ``id`` 's filesystem
281
+
282
+	**Example request**:
283
+
284
+	.. sourcecode:: http
285
+
286
+	   GET /containers/4fa6e0f0c678/changes HTTP/1.1
287
+
288
+	   
289
+	**Example response**:
290
+
291
+	.. sourcecode:: http
292
+
293
+	   HTTP/1.1 200 OK
294
+	   Content-Type: application/json
295
+	   
296
+	   [
297
+		{
298
+			"Path":"/dev",
299
+			"Kind":0
300
+		},
301
+		{
302
+			"Path":"/dev/kmsg",
303
+			"Kind":1
304
+		},
305
+		{
306
+			"Path":"/test",
307
+			"Kind":1
308
+		}
309
+	   ]
310
+
311
+	:statuscode 200: no error
312
+	:statuscode 404: no such container
313
+	:statuscode 500: server error
314
+
315
+
316
+Export a container
317
+******************
318
+
319
+.. http:get:: /containers/(id)/export
320
+
321
+	Export the contents of container ``id``
322
+
323
+	**Example request**:
324
+
325
+	.. sourcecode:: http
326
+
327
+	   GET /containers/4fa6e0f0c678/export HTTP/1.1
328
+
329
+	   
330
+	**Example response**:
331
+
332
+	.. sourcecode:: http
333
+
334
+	   HTTP/1.1 200 OK
335
+	   Content-Type: application/octet-stream
336
+	   
337
+	   {{ STREAM }}
338
+
339
+	:statuscode 200: no error
340
+	:statuscode 404: no such container
341
+	:statuscode 500: server error
342
+
343
+
344
+Start a container
345
+*****************
346
+
347
+.. http:post:: /containers/(id)/start
348
+
349
+        Start the container ``id``
350
+
351
+        **Example request**:
352
+
353
+        .. sourcecode:: http
354
+
355
+           POST /containers/(id)/start HTTP/1.1
356
+           Content-Type: application/json
357
+
358
+           {
359
+                "Binds":["/tmp:/tmp"],
360
+                "LxcConf":{"lxc.utsname":"docker"}
361
+           }
362
+
363
+        **Example response**:
364
+
365
+        .. sourcecode:: http
366
+
367
+           HTTP/1.1 204 No Content
368
+           Content-Type: text/plain
369
+
370
+        :jsonparam hostConfig: the container's host configuration (optional)
371
+        :statuscode 204: no error
372
+        :statuscode 404: no such container
373
+        :statuscode 500: server error
374
+
375
+
376
+Stop a container
377
+****************
378
+
379
+.. http:post:: /containers/(id)/stop
380
+
381
+	Stop the container ``id``
382
+
383
+	**Example request**:
384
+
385
+	.. sourcecode:: http
386
+
387
+	   POST /containers/e90e34656806/stop?t=5 HTTP/1.1
388
+	   
389
+	**Example response**:
390
+
391
+	.. sourcecode:: http
392
+
393
+	   HTTP/1.1 204 OK
394
+	   	
395
+	:query t: number of seconds to wait before killing the container
396
+	:statuscode 204: no error
397
+	:statuscode 404: no such container
398
+	:statuscode 500: server error
399
+
400
+
401
+Restart a container
402
+*******************
403
+
404
+.. http:post:: /containers/(id)/restart
405
+
406
+	Restart the container ``id``
407
+
408
+	**Example request**:
409
+
410
+	.. sourcecode:: http
411
+
412
+	   POST /containers/e90e34656806/restart?t=5 HTTP/1.1
413
+	   
414
+	**Example response**:
415
+
416
+	.. sourcecode:: http
417
+
418
+	   HTTP/1.1 204 OK
419
+	   	
420
+	:query t: number of seconds to wait before killing the container
421
+	:statuscode 204: no error
422
+	:statuscode 404: no such container
423
+	:statuscode 500: server error
424
+
425
+
426
+Kill a container
427
+****************
428
+
429
+.. http:post:: /containers/(id)/kill
430
+
431
+	Kill the container ``id``
432
+
433
+	**Example request**:
434
+
435
+	.. sourcecode:: http
436
+
437
+	   POST /containers/e90e34656806/kill HTTP/1.1
438
+	   
439
+	**Example response**:
440
+
441
+	.. sourcecode:: http
442
+
443
+	   HTTP/1.1 204 OK
444
+	   	
445
+	:statuscode 204: no error
446
+	:statuscode 404: no such container
447
+	:statuscode 500: server error
448
+
449
+
450
+Attach to a container
451
+*********************
452
+
453
+.. http:post:: /containers/(id)/attach
454
+
455
+	Attach to the container ``id``
456
+
457
+	**Example request**:
458
+
459
+	.. sourcecode:: http
460
+
461
+	   POST /containers/16253994b7c4/attach?logs=1&stream=0&stdout=1 HTTP/1.1
462
+	   
463
+	**Example response**:
464
+
465
+	.. sourcecode:: http
466
+
467
+	   HTTP/1.1 200 OK
468
+	   Content-Type: application/vnd.docker.raw-stream
469
+
470
+	   {{ STREAM }}
471
+	   	
472
+	:query logs: 1/True/true or 0/False/false, return logs. Default false
473
+	:query stream: 1/True/true or 0/False/false, return stream. Default false
474
+	:query stdin: 1/True/true or 0/False/false, if stream=true, attach to stdin. Default false
475
+	:query stdout: 1/True/true or 0/False/false, if logs=true, return stdout log, if stream=true, attach to stdout. Default false
476
+	:query stderr: 1/True/true or 0/False/false, if logs=true, return stderr log, if stream=true, attach to stderr. Default false
477
+	:statuscode 200: no error
478
+	:statuscode 400: bad parameter
479
+	:statuscode 404: no such container
480
+	:statuscode 500: server error
481
+
482
+	**Stream details**:
483
+
484
+	When using the TTY setting is enabled in
485
+	:http:post:`/containers/create`, the stream is the raw data
486
+	from the process PTY and client's stdin.  When the TTY is
487
+	disabled, then the stream is multiplexed to separate stdout
488
+	and stderr.
489
+
490
+	The format is a **Header** and a **Payload** (frame).
491
+
492
+	**HEADER**
493
+
494
+	The header will contain the information on which stream write
495
+	the stream (stdout or stderr). It also contain the size of
496
+	the associated frame encoded on the last 4 bytes (uint32).
497
+
498
+	It is encoded on the first 8 bytes like this::
499
+
500
+	    header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}
501
+
502
+	``STREAM_TYPE`` can be:
503
+
504
+	- 0: stdin (will be writen on stdout)
505
+	- 1: stdout
506
+	- 2: stderr
507
+
508
+	``SIZE1, SIZE2, SIZE3, SIZE4`` are the 4 bytes of the uint32 size encoded as big endian.
509
+
510
+	**PAYLOAD**
511
+
512
+	The payload is the raw stream.
513
+
514
+	**IMPLEMENTATION**
515
+
516
+	The simplest way to implement the Attach protocol is the following:
517
+
518
+	1) Read 8 bytes
519
+	2) chose stdout or stderr depending on the first byte
520
+	3) Extract the frame size from the last 4 byets
521
+	4) Read the extracted size and output it on the correct output
522
+	5) Goto 1)
523
+
524
+
525
+
526
+Wait a container
527
+****************
528
+
529
+.. http:post:: /containers/(id)/wait
530
+
531
+	Block until container ``id`` stops, then returns the exit code
532
+
533
+	**Example request**:
534
+
535
+	.. sourcecode:: http
536
+
537
+	   POST /containers/16253994b7c4/wait HTTP/1.1
538
+	   
539
+	**Example response**:
540
+
541
+	.. sourcecode:: http
542
+
543
+	   HTTP/1.1 200 OK
544
+	   Content-Type: application/json
545
+
546
+	   {"StatusCode":0}
547
+	   	
548
+	:statuscode 200: no error
549
+	:statuscode 404: no such container
550
+	:statuscode 500: server error
551
+
552
+
553
+Remove a container
554
+*******************
555
+
556
+.. http:delete:: /containers/(id)
557
+
558
+	Remove the container ``id`` from the filesystem
559
+
560
+	**Example request**:
561
+
562
+        .. sourcecode:: http
563
+
564
+           DELETE /containers/16253994b7c4?v=1 HTTP/1.1
565
+
566
+        **Example response**:
567
+
568
+        .. sourcecode:: http
569
+
570
+	   HTTP/1.1 204 OK
571
+
572
+	:query v: 1/True/true or 0/False/false, Remove the volumes associated to the container. Default false
573
+        :statuscode 204: no error
574
+	:statuscode 400: bad parameter
575
+        :statuscode 404: no such container
576
+        :statuscode 500: server error
577
+
578
+
579
+Copy files or folders from a container
580
+**************************************
581
+
582
+.. http:post:: /containers/(id)/copy
583
+
584
+	Copy files or folders of container ``id``
585
+
586
+	**Example request**:
587
+
588
+	.. sourcecode:: http
589
+
590
+	   POST /containers/4fa6e0f0c678/copy HTTP/1.1
591
+	   Content-Type: application/json
592
+
593
+	   {
594
+		"Resource":"test.txt"
595
+	   }
596
+
597
+	**Example response**:
598
+
599
+	.. sourcecode:: http
600
+
601
+	   HTTP/1.1 200 OK
602
+	   Content-Type: application/octet-stream
603
+	   
604
+	   {{ STREAM }}
605
+
606
+	:statuscode 200: no error
607
+	:statuscode 404: no such container
608
+	:statuscode 500: server error
609
+
610
+
611
+2.2 Images
612
+----------
613
+
614
+List Images
615
+***********
616
+
617
+.. http:get:: /images/(format)
618
+
619
+	List images ``format`` could be json or viz (json default)
620
+
621
+	**Example request**:
622
+
623
+	.. sourcecode:: http
624
+
625
+	   GET /images/json?all=0 HTTP/1.1
626
+
627
+	**Example response**:
628
+
629
+	.. sourcecode:: http
630
+
631
+	   HTTP/1.1 200 OK
632
+	   Content-Type: application/json
633
+	   
634
+	   [
635
+		{
636
+			"Repository":"base",
637
+			"Tag":"ubuntu-12.10",
638
+			"Id":"b750fe79269d",
639
+			"Created":1364102658,
640
+			"Size":24653,
641
+			"VirtualSize":180116135
642
+		},
643
+		{
644
+			"Repository":"base",
645
+			"Tag":"ubuntu-quantal",
646
+			"Id":"b750fe79269d",
647
+			"Created":1364102658,
648
+			"Size":24653,
649
+			"VirtualSize":180116135
650
+		}
651
+	   ]
652
+
653
+
654
+	**Example request**:
655
+
656
+	.. sourcecode:: http
657
+
658
+	   GET /images/viz HTTP/1.1
659
+
660
+	**Example response**:
661
+
662
+	.. sourcecode:: http
663
+
664
+	   HTTP/1.1 200 OK
665
+	   Content-Type: text/plain
666
+
667
+	   digraph docker {
668
+	   "d82cbacda43a" -> "074be284591f"
669
+	   "1496068ca813" -> "08306dc45919"
670
+	   "08306dc45919" -> "0e7893146ac2"
671
+	   "b750fe79269d" -> "1496068ca813"
672
+	   base -> "27cf78414709" [style=invis]
673
+	   "f71189fff3de" -> "9a33b36209ed"
674
+	   "27cf78414709" -> "b750fe79269d"
675
+	   "0e7893146ac2" -> "d6434d954665"
676
+	   "d6434d954665" -> "d82cbacda43a"
677
+	   base -> "e9aa60c60128" [style=invis]
678
+	   "074be284591f" -> "f71189fff3de"
679
+	   "b750fe79269d" [label="b750fe79269d\nbase",shape=box,fillcolor="paleturquoise",style="filled,rounded"];
680
+	   "e9aa60c60128" [label="e9aa60c60128\nbase2",shape=box,fillcolor="paleturquoise",style="filled,rounded"];
681
+	   "9a33b36209ed" [label="9a33b36209ed\ntest",shape=box,fillcolor="paleturquoise",style="filled,rounded"];
682
+	   base [style=invisible]
683
+	   }
684
+ 
685
+	:query all: 1/True/true or 0/False/false, Show all containers. Only running containers are shown by default
686
+	:statuscode 200: no error
687
+	:statuscode 400: bad parameter
688
+	:statuscode 500: server error
689
+
690
+
691
+Create an image
692
+***************
693
+
694
+.. http:post:: /images/create
695
+
696
+	Create an image, either by pull it from the registry or by importing it
697
+
698
+	**Example request**:
699
+
700
+        .. sourcecode:: http
701
+
702
+           POST /images/create?fromImage=base HTTP/1.1
703
+
704
+        **Example response**:
705
+
706
+        .. sourcecode:: http
707
+
708
+           HTTP/1.1 200 OK
709
+	   Content-Type: application/json
710
+
711
+	   {"status":"Pulling..."}
712
+	   {"status":"Pulling", "progress":"1/? (n/a)"}
713
+	   {"error":"Invalid..."}
714
+	   ...
715
+
716
+	When using this endpoint to pull an image from the registry,
717
+	the ``X-Registry-Auth`` header can be used to include a
718
+	base64-encoded AuthConfig object.
719
+
720
+        :query fromImage: name of the image to pull
721
+	:query fromSrc: source to import, - means stdin
722
+        :query repo: repository
723
+	:query tag: tag
724
+	:query registry: the registry to pull from
725
+        :statuscode 200: no error
726
+        :statuscode 500: server error
727
+
728
+
729
+Insert a file in an image
730
+*************************
731
+
732
+.. http:post:: /images/(name)/insert
733
+
734
+	Insert a file from ``url`` in the image ``name`` at ``path``
735
+
736
+	**Example request**:
737
+
738
+        .. sourcecode:: http
739
+
740
+           POST /images/test/insert?path=/usr&url=myurl HTTP/1.1
741
+
742
+	**Example response**:
743
+
744
+        .. sourcecode:: http
745
+
746
+           HTTP/1.1 200 OK
747
+	   Content-Type: application/json
748
+
749
+	   {"status":"Inserting..."}
750
+	   {"status":"Inserting", "progress":"1/? (n/a)"}
751
+	   {"error":"Invalid..."}
752
+	   ...
753
+
754
+	:statuscode 200: no error
755
+        :statuscode 500: server error
756
+
757
+
758
+Inspect an image
759
+****************
760
+
761
+.. http:get:: /images/(name)/json
762
+
763
+	Return low-level information on the image ``name``
764
+
765
+	**Example request**:
766
+
767
+	.. sourcecode:: http
768
+
769
+	   GET /images/base/json HTTP/1.1
770
+
771
+	**Example response**:
772
+
773
+        .. sourcecode:: http
774
+
775
+           HTTP/1.1 200 OK
776
+	   Content-Type: application/json
777
+
778
+	   {
779
+		"id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
780
+		"parent":"27cf784147099545",
781
+		"created":"2013-03-23T22:24:18.818426-07:00",
782
+		"container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0",
783
+		"container_config":
784
+			{
785
+				"Hostname":"",
786
+				"User":"",
787
+				"Memory":0,
788
+				"MemorySwap":0,
789
+				"AttachStdin":false,
790
+				"AttachStdout":false,
791
+				"AttachStderr":false,
792
+				"PortSpecs":null,
793
+				"Tty":true,
794
+				"OpenStdin":true,
795
+				"StdinOnce":false,
796
+				"Env":null,
797
+				"Cmd": ["/bin/bash"]
798
+				,"Dns":null,
799
+				"Image":"base",
800
+				"Volumes":null,
801
+				"VolumesFrom":"",
802
+				"WorkingDir":""
803
+			},
804
+		"Size": 6824592
805
+	   }
806
+
807
+	:statuscode 200: no error
808
+	:statuscode 404: no such image
809
+        :statuscode 500: server error
810
+
811
+
812
+Get the history of an image
813
+***************************
814
+
815
+.. http:get:: /images/(name)/history
816
+
817
+        Return the history of the image ``name``
818
+
819
+        **Example request**:
820
+
821
+        .. sourcecode:: http
822
+
823
+           GET /images/base/history HTTP/1.1
824
+
825
+        **Example response**:
826
+
827
+        .. sourcecode:: http
828
+
829
+           HTTP/1.1 200 OK
830
+	   Content-Type: application/json
831
+
832
+	   [
833
+		{
834
+			"Id":"b750fe79269d",
835
+			"Created":1364102658,
836
+			"CreatedBy":"/bin/bash"
837
+		},
838
+		{
839
+			"Id":"27cf78414709",
840
+			"Created":1364068391,
841
+			"CreatedBy":""
842
+		}
843
+	   ]
844
+
845
+        :statuscode 200: no error
846
+        :statuscode 404: no such image
847
+        :statuscode 500: server error
848
+
849
+
850
+Push an image on the registry
851
+*****************************
852
+
853
+.. http:post:: /images/(name)/push
854
+
855
+   Push the image ``name`` on the registry
856
+
857
+   **Example request**:
858
+
859
+   .. sourcecode:: http
860
+
861
+      POST /images/test/push HTTP/1.1
862
+
863
+   **Example response**:
864
+
865
+   .. sourcecode:: http
866
+
867
+    HTTP/1.1 200 OK
868
+    Content-Type: application/json
869
+
870
+   {"status":"Pushing..."}
871
+   {"status":"Pushing", "progress":"1/? (n/a)"}
872
+   {"error":"Invalid..."}
873
+   ...
874
+
875
+	The ``X-Registry-Auth`` header can be used to include a
876
+	base64-encoded AuthConfig object.
877
+
878
+   :query registry: the registry you wan to push, optional
879
+   :statuscode 200: no error
880
+        :statuscode 404: no such image
881
+        :statuscode 500: server error
882
+
883
+
884
+Tag an image into a repository
885
+******************************
886
+
887
+.. http:post:: /images/(name)/tag
888
+
889
+	Tag the image ``name`` into a repository
890
+
891
+        **Example request**:
892
+
893
+        .. sourcecode:: http
894
+			
895
+	   POST /images/test/tag?repo=myrepo&force=0 HTTP/1.1
896
+
897
+	**Example response**:
898
+
899
+        .. sourcecode:: http
900
+
901
+           HTTP/1.1 200 OK
902
+
903
+	:query repo: The repository to tag in
904
+	:query force: 1/True/true or 0/False/false, default false
905
+	:statuscode 200: no error
906
+	:statuscode 400: bad parameter
907
+	:statuscode 404: no such image
908
+	:statuscode 409: conflict
909
+        :statuscode 500: server error
910
+
911
+
912
+Remove an image
913
+***************
914
+
915
+.. http:delete:: /images/(name)
916
+
917
+	Remove the image ``name`` from the filesystem 
918
+	
919
+	**Example request**:
920
+
921
+	.. sourcecode:: http
922
+
923
+	   DELETE /images/test HTTP/1.1
924
+
925
+	**Example response**:
926
+
927
+        .. sourcecode:: http
928
+
929
+	   HTTP/1.1 200 OK
930
+	   Content-type: application/json
931
+
932
+	   [
933
+	    {"Untagged":"3e2f21a89f"},
934
+	    {"Deleted":"3e2f21a89f"},
935
+	    {"Deleted":"53b4f83ac9"}
936
+	   ]
937
+
938
+	:statuscode 200: no error
939
+        :statuscode 404: no such image
940
+	:statuscode 409: conflict
941
+        :statuscode 500: server error
942
+
943
+
944
+Search images
945
+*************
946
+
947
+.. http:get:: /images/search
948
+
949
+	Search for an image in the docker index
950
+	
951
+	**Example request**:
952
+
953
+        .. sourcecode:: http
954
+
955
+           GET /images/search?term=sshd HTTP/1.1
956
+
957
+	**Example response**:
958
+
959
+	.. sourcecode:: http
960
+
961
+	   HTTP/1.1 200 OK
962
+	   Content-Type: application/json
963
+	   
964
+	   [
965
+		{
966
+			"Name":"cespare/sshd",
967
+			"Description":""
968
+		},
969
+		{
970
+			"Name":"johnfuller/sshd",
971
+			"Description":""
972
+		},
973
+		{
974
+			"Name":"dhrp/mongodb-sshd",
975
+			"Description":""
976
+		}
977
+	   ]
978
+
979
+	   :query term: term to search
980
+	   :statuscode 200: no error
981
+	   :statuscode 500: server error
982
+
983
+
984
+2.3 Misc
985
+--------
986
+
987
+Build an image from Dockerfile via stdin
988
+****************************************
989
+
990
+.. http:post:: /build
991
+
992
+   Build an image from Dockerfile via stdin
993
+
994
+   **Example request**:
995
+
996
+   .. sourcecode:: http
997
+
998
+      POST /build HTTP/1.1
999
+
1000
+      {{ STREAM }}
1001
+
1002
+   **Example response**:
1003
+
1004
+   .. sourcecode:: http
1005
+
1006
+      HTTP/1.1 200 OK
1007
+
1008
+      {{ STREAM }}
1009
+
1010
+
1011
+       The stream must be a tar archive compressed with one of the following algorithms:
1012
+       identity (no compression), gzip, bzip2, xz. The archive must include a file called
1013
+       `Dockerfile` at its root. It may include any number of other files, which will be
1014
+       accessible in the build context (See the ADD build command).
1015
+
1016
+       The Content-type header should be set to "application/tar".
1017
+
1018
+	:query t: repository name (and optionally a tag) to be applied to the resulting image in case of success
1019
+	:query q: suppress verbose build output
1020
+    :query nocache: do not use the cache when building the image
1021
+	:statuscode 200: no error
1022
+    :statuscode 500: server error
1023
+
1024
+
1025
+Check auth configuration
1026
+************************
1027
+
1028
+.. http:post:: /auth
1029
+
1030
+        Get the default username and email
1031
+
1032
+        **Example request**:
1033
+
1034
+        .. sourcecode:: http
1035
+
1036
+           POST /auth HTTP/1.1
1037
+	   Content-Type: application/json
1038
+
1039
+	   {
1040
+		"username":"hannibal",
1041
+		"password:"xxxx",
1042
+		"email":"hannibal@a-team.com",
1043
+		"serveraddress":"https://index.docker.io/v1/"
1044
+	   }
1045
+
1046
+        **Example response**:
1047
+
1048
+        .. sourcecode:: http
1049
+
1050
+           HTTP/1.1 200 OK
1051
+
1052
+        :statuscode 200: no error
1053
+        :statuscode 204: no error
1054
+        :statuscode 500: server error
1055
+
1056
+
1057
+Display system-wide information
1058
+*******************************
1059
+
1060
+.. http:get:: /info
1061
+
1062
+	Display system-wide information
1063
+	
1064
+	**Example request**:
1065
+
1066
+        .. sourcecode:: http
1067
+
1068
+           GET /info HTTP/1.1
1069
+
1070
+        **Example response**:
1071
+
1072
+        .. sourcecode:: http
1073
+
1074
+           HTTP/1.1 200 OK
1075
+	   Content-Type: application/json
1076
+
1077
+	   {
1078
+		"Containers":11,
1079
+		"Images":16,
1080
+		"Debug":false,
1081
+		"NFd": 11,
1082
+		"NGoroutines":21,
1083
+		"MemoryLimit":true,
1084
+		"SwapLimit":false,
1085
+		"IPv4Forwarding":true
1086
+	   }
1087
+
1088
+        :statuscode 200: no error
1089
+        :statuscode 500: server error
1090
+
1091
+
1092
+Show the docker version information
1093
+***********************************
1094
+
1095
+.. http:get:: /version
1096
+
1097
+	Show the docker version information
1098
+
1099
+	**Example request**:
1100
+
1101
+        .. sourcecode:: http
1102
+
1103
+           GET /version HTTP/1.1
1104
+
1105
+        **Example response**:
1106
+
1107
+        .. sourcecode:: http
1108
+
1109
+           HTTP/1.1 200 OK
1110
+	   Content-Type: application/json
1111
+
1112
+	   {
1113
+		"Version":"0.2.2",
1114
+		"GitCommit":"5a2a5cc+CHANGES",
1115
+		"GoVersion":"go1.0.3"
1116
+	   }
1117
+
1118
+        :statuscode 200: no error
1119
+	:statuscode 500: server error
1120
+
1121
+
1122
+Create a new image from a container's changes
1123
+*********************************************
1124
+
1125
+.. http:post:: /commit
1126
+
1127
+    Create a new image from a container's changes
1128
+
1129
+    **Example request**:
1130
+
1131
+    .. sourcecode:: http
1132
+
1133
+        POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
1134
+
1135
+    **Example response**:
1136
+
1137
+    .. sourcecode:: http
1138
+
1139
+        HTTP/1.1 201 OK
1140
+	    Content-Type: application/vnd.docker.raw-stream
1141
+
1142
+        {"Id":"596069db4bf5"}
1143
+
1144
+    :query container: source container
1145
+    :query repo: repository
1146
+    :query tag: tag
1147
+    :query m: commit message
1148
+    :query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
1149
+    :query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
1150
+    :statuscode 201: no error
1151
+    :statuscode 404: no such container
1152
+    :statuscode 500: server error
1153
+
1154
+
1155
+Monitor Docker's events
1156
+***********************
1157
+
1158
+.. http:get:: /events
1159
+
1160
+	Get events from docker, either in real time via streaming, or via polling (using `since`)
1161
+
1162
+	**Example request**:
1163
+
1164
+	.. sourcecode:: http
1165
+
1166
+           POST /events?since=1374067924
1167
+
1168
+        **Example response**:
1169
+
1170
+        .. sourcecode:: http
1171
+
1172
+           HTTP/1.1 200 OK
1173
+	   Content-Type: application/json
1174
+
1175
+	   {"status":"create","id":"dfdf82bd3881","from":"base:latest","time":1374067924}
1176
+	   {"status":"start","id":"dfdf82bd3881","from":"base:latest","time":1374067924}
1177
+	   {"status":"stop","id":"dfdf82bd3881","from":"base:latest","time":1374067966}
1178
+	   {"status":"destroy","id":"dfdf82bd3881","from":"base:latest","time":1374067970}
1179
+
1180
+	:query since: timestamp used for polling
1181
+        :statuscode 200: no error
1182
+        :statuscode 500: server error
1183
+
1184
+
1185
+3. Going further
1186
+================
1187
+
1188
+3.1 Inside 'docker run'
1189
+-----------------------
1190
+
1191
+Here are the steps of 'docker run' :
1192
+
1193
+* Create the container
1194
+* If the status code is 404, it means the image doesn't exists:
1195
+        * Try to pull it
1196
+        * Then retry to create the container
1197
+* Start the container
1198
+* If you are not in detached mode:
1199
+        * Attach to the container, using logs=1 (to have stdout and stderr from the container's start) and stream=1
1200
+* If in detached mode or only stdin is attached:
1201
+	* Display the container's id
1202
+
1203
+
1204
+3.2 Hijacking
1205
+-------------
1206
+
1207
+In this version of the API, /attach, uses hijacking to transport stdin, stdout and stderr on the same socket. This might change in the future.
1208
+
1209
+3.3 CORS Requests
1210
+-----------------
1211
+
1212
+To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode.
1213
+
1214
+.. code-block:: bash
1215
+
1216
+   docker -d -H="192.168.1.9:4243" -api-enable-cors
1217
+
... ...
@@ -1177,11 +1177,12 @@ func (srv *Server) ContainerResize(name string, h, w int) error {
1177 1177
 	return fmt.Errorf("No such container: %s", name)
1178 1178
 }
1179 1179
 
1180
-func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, stderr bool, in io.ReadCloser, out io.Writer) error {
1180
+func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, stderr bool, inStream io.ReadCloser, outStream, errStream io.Writer) error {
1181 1181
 	container := srv.runtime.Get(name)
1182 1182
 	if container == nil {
1183 1183
 		return fmt.Errorf("No such container: %s", name)
1184 1184
 	}
1185
+
1185 1186
 	//logs
1186 1187
 	if logs {
1187 1188
 		cLog, err := container.ReadLog("json")
... ...
@@ -1192,7 +1193,7 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
1192 1192
 				cLog, err := container.ReadLog("stdout")
1193 1193
 				if err != nil {
1194 1194
 					utils.Debugf("Error reading logs (stdout): %s", err)
1195
-				} else if _, err := io.Copy(out, cLog); err != nil {
1195
+				} else if _, err := io.Copy(outStream, cLog); err != nil {
1196 1196
 					utils.Debugf("Error streaming logs (stdout): %s", err)
1197 1197
 				}
1198 1198
 			}
... ...
@@ -1200,7 +1201,7 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
1200 1200
 				cLog, err := container.ReadLog("stderr")
1201 1201
 				if err != nil {
1202 1202
 					utils.Debugf("Error reading logs (stderr): %s", err)
1203
-				} else if _, err := io.Copy(out, cLog); err != nil {
1203
+				} else if _, err := io.Copy(errStream, cLog); err != nil {
1204 1204
 					utils.Debugf("Error streaming logs (stderr): %s", err)
1205 1205
 				}
1206 1206
 			}
... ...
@@ -1209,15 +1210,19 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
1209 1209
 		} else {
1210 1210
 			dec := json.NewDecoder(cLog)
1211 1211
 			for {
1212
-				var l utils.JSONLog
1213
-				if err := dec.Decode(&l); err == io.EOF {
1212
+				l := &utils.JSONLog{}
1213
+
1214
+				if err := dec.Decode(l); err == io.EOF {
1214 1215
 					break
1215 1216
 				} else if err != nil {
1216 1217
 					utils.Debugf("Error streaming logs: %s", err)
1217 1218
 					break
1218 1219
 				}
1219
-				if (l.Stream == "stdout" && stdout) || (l.Stream == "stderr" && stderr) {
1220
-					fmt.Fprintf(out, "%s", l.Log)
1220
+				if l.Stream == "stdout" && stdout {
1221
+					fmt.Fprintf(outStream, "%s", l.Log)
1222
+				}
1223
+				if l.Stream == "stderr" && stderr {
1224
+					fmt.Fprintf(errStream, "%s", l.Log)
1221 1225
 				}
1222 1226
 			}
1223 1227
 		}
... ...
@@ -1240,16 +1245,16 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
1240 1240
 			go func() {
1241 1241
 				defer w.Close()
1242 1242
 				defer utils.Debugf("Closing buffered stdin pipe")
1243
-				io.Copy(w, in)
1243
+				io.Copy(w, inStream)
1244 1244
 			}()
1245 1245
 			cStdin = r
1246
-			cStdinCloser = in
1246
+			cStdinCloser = inStream
1247 1247
 		}
1248 1248
 		if stdout {
1249
-			cStdout = out
1249
+			cStdout = outStream
1250 1250
 		}
1251 1251
 		if stderr {
1252
-			cStderr = out
1252
+			cStderr = errStream
1253 1253
 		}
1254 1254
 
1255 1255
 		<-container.Attach(cStdin, cStdinCloser, cStdout, cStderr)
1256 1256
new file mode 100644
... ...
@@ -0,0 +1,152 @@
0
+package utils
1
+
2
+import (
3
+	"encoding/binary"
4
+	"errors"
5
+	"io"
6
+)
7
+
8
+const (
9
+	StdWriterPrefixLen = 8
10
+	StdWriterFdIndex   = 0
11
+	StdWriterSizeIndex = 4
12
+)
13
+
14
+type StdType [StdWriterPrefixLen]byte
15
+
16
+var (
17
+	Stdin  StdType = StdType{0: 0}
18
+	Stdout StdType = StdType{0: 1}
19
+	Stderr StdType = StdType{0: 2}
20
+)
21
+
22
+type StdWriter struct {
23
+	io.Writer
24
+	prefix  StdType
25
+	sizeBuf []byte
26
+}
27
+
28
+func (w *StdWriter) Write(buf []byte) (n int, err error) {
29
+	if w == nil || w.Writer == nil {
30
+		return 0, errors.New("Writer not instanciated")
31
+	}
32
+	binary.BigEndian.PutUint32(w.prefix[4:], uint32(len(buf)))
33
+	buf = append(w.prefix[:], buf...)
34
+
35
+	n, err = w.Writer.Write(buf)
36
+	return n - StdWriterPrefixLen, err
37
+}
38
+
39
+// NewStdWriter instanciate a new Writer based on the given type `t`.
40
+// the utils package contains the valid parametres for `t`:
41
+func NewStdWriter(w io.Writer, t StdType) *StdWriter {
42
+	if len(t) != StdWriterPrefixLen {
43
+		return nil
44
+	}
45
+
46
+	return &StdWriter{
47
+		Writer:  w,
48
+		prefix:  t,
49
+		sizeBuf: make([]byte, 4),
50
+	}
51
+}
52
+
53
+var ErrInvalidStdHeader = errors.New("Unrecognized input header")
54
+
55
+// StdCopy is a modified version of io.Copy.
56
+//
57
+// StdCopy copies from src to dstout or dsterr until either EOF is reached
58
+// on src or an error occurs.  It returns the number of bytes
59
+// copied and the first error encountered while copying, if any.
60
+//
61
+// A successful Copy returns err == nil, not err == EOF.
62
+// Because Copy is defined to read from src until EOF, it does
63
+// not treat an EOF from Read as an error to be reported.
64
+//
65
+// The source needs to be writter via StdWriter, dstout or dsterr is selected
66
+// based on the prefix added by StdWriter
67
+func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) {
68
+	var (
69
+		buf       = make([]byte, 32*1024+StdWriterPrefixLen+1)
70
+		bufLen    = len(buf)
71
+		nr, nw    int
72
+		er, ew    error
73
+		out       io.Writer
74
+		frameSize int
75
+	)
76
+
77
+	for {
78
+		// Make sure we have at least a full header
79
+		for nr < StdWriterPrefixLen {
80
+			var nr2 int
81
+			nr2, er = src.Read(buf[nr:])
82
+			if er == io.EOF {
83
+				return written, nil
84
+			}
85
+			if er != nil {
86
+				return 0, er
87
+			}
88
+			nr += nr2
89
+		}
90
+
91
+		// Check the first byte to know where to write
92
+		switch buf[StdWriterFdIndex] {
93
+		case 0:
94
+			fallthrough
95
+		case 1:
96
+			// Write on stdout
97
+			out = dstout
98
+		case 2:
99
+			// Write on stderr
100
+			out = dsterr
101
+		default:
102
+			Debugf("Error selecting output fd: (%d)", buf[StdWriterFdIndex])
103
+			return 0, ErrInvalidStdHeader
104
+		}
105
+
106
+		// Retrieve the size of the frame
107
+		frameSize = int(binary.BigEndian.Uint32(buf[StdWriterSizeIndex : StdWriterSizeIndex+4]))
108
+
109
+		// Check if the buffer is big enough to read the frame.
110
+		// Extend it if necessary.
111
+		if frameSize+StdWriterPrefixLen > bufLen {
112
+			Debugf("Extending buffer cap.")
113
+			buf = append(buf, make([]byte, frameSize-len(buf)+1)...)
114
+			bufLen = len(buf)
115
+		}
116
+
117
+		// While the amount of bytes read is less than the size of the frame + header, we keep reading
118
+		for nr < frameSize+StdWriterPrefixLen {
119
+			var nr2 int
120
+			nr2, er = src.Read(buf[nr:])
121
+			if er == io.EOF {
122
+				return written, nil
123
+			}
124
+			if er != nil {
125
+				Debugf("Error reading frame: %s", er)
126
+				return 0, er
127
+			}
128
+			nr += nr2
129
+		}
130
+
131
+		// Write the retrieved frame (without header)
132
+		nw, ew = out.Write(buf[StdWriterPrefixLen : frameSize+StdWriterPrefixLen])
133
+		if nw > 0 {
134
+			written += int64(nw)
135
+		}
136
+		if ew != nil {
137
+			Debugf("Error writing frame: %s", ew)
138
+			return 0, ew
139
+		}
140
+		// If the frame has not been fully written: error
141
+		if nw != frameSize {
142
+			Debugf("Error Short Write: (%d on %d)", nw, frameSize)
143
+			return 0, io.ErrShortWrite
144
+		}
145
+
146
+		// Move the rest of the buffer to the beginning
147
+		copy(buf, buf[frameSize+StdWriterPrefixLen:])
148
+		// Move the index
149
+		nr -= frameSize + StdWriterPrefixLen
150
+	}
151
+}