Browse code

Merge branch 'master' into add-libcontainer

Conflicts:
runtime.go

Docker-DCO-1.1-Signed-off-by: Michael Crosby <michael@crosbymichael.com> (github: crosbymichael)

Michael Crosby authored on 2014/02/25 13:35:12
Showing 36 changed files
... ...
@@ -22,3 +22,4 @@ bundles/
22 22
 .git/
23 23
 vendor/pkg/
24 24
 pyenv
25
+Vagrantfile
... ...
@@ -6,4 +6,3 @@ Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
6 6
 api.go: Victor Vieux <victor@dotcloud.com> (@vieux)
7 7
 Dockerfile: Tianon Gravi <admwiggin@gmail.com> (@tianon)
8 8
 Makefile: Tianon Gravi <admwiggin@gmail.com> (@tianon)
9
-Vagrantfile: Cristian Staretu <cristian.staretu@gmail.com> (@unclejack)
10 9
deleted file mode 100644
... ...
@@ -1,206 +0,0 @@
1
-# -*- mode: ruby -*-
2
-# vi: set ft=ruby :
3
-
4
-BOX_NAME = ENV['BOX_NAME'] || "ubuntu"
5
-BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64.box"
6
-VF_BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64_vmware_fusion.box"
7
-AWS_BOX_URI = ENV['BOX_URI'] || "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box"
8
-AWS_REGION = ENV['AWS_REGION'] || "us-east-1"
9
-AWS_AMI = ENV['AWS_AMI'] || "ami-69f5a900"
10
-AWS_INSTANCE_TYPE = ENV['AWS_INSTANCE_TYPE'] || 't1.micro'
11
-SSH_PRIVKEY_PATH = ENV['SSH_PRIVKEY_PATH']
12
-PRIVATE_NETWORK = ENV['PRIVATE_NETWORK']
13
-
14
-# Boolean that forwards the Docker dynamic ports 49000-49900
15
-# See http://docs.docker.io/en/latest/use/port_redirection/ for more
16
-# $ FORWARD_DOCKER_PORTS=1 vagrant [up|reload]
17
-FORWARD_DOCKER_PORTS = ENV['FORWARD_DOCKER_PORTS']
18
-VAGRANT_RAM = ENV['VAGRANT_RAM'] || 512
19
-VAGRANT_CORES = ENV['VAGRANT_CORES'] || 1
20
-
21
-# You may also provide a comma-separated list of ports
22
-# for Vagrant to forward. For example:
23
-# $ FORWARD_PORTS=8080,27017 vagrant [up|reload]
24
-FORWARD_PORTS = ENV['FORWARD_PORTS']
25
-
26
-# A script to upgrade from the 12.04 kernel to the raring backport kernel (3.8)
27
-# and install docker.
28
-$script = <<SCRIPT
29
-# The username to add to the docker group will be passed as the first argument
30
-# to the script.  If nothing is passed, default to "vagrant".
31
-user="$1"
32
-if [ -z "$user" ]; then
33
-    user=vagrant
34
-fi
35
-
36
-# Enable memory cgroup and swap accounting
37
-sed -i 's/GRUB_CMDLINE_LINUX=""/GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1"/g' /etc/default/grub
38
-update-grub
39
-
40
-# Adding an apt gpg key is idempotent.
41
-apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9
42
-
43
-# Creating the docker.list file is idempotent, but it may overwrite desired
44
-# settings if it already exists.  This could be solved with md5sum but it
45
-# doesn't seem worth it.
46
-echo 'deb http://get.docker.io/ubuntu docker main' > \
47
-    /etc/apt/sources.list.d/docker.list
48
-
49
-# Update remote package metadata.  'apt-get update' is idempotent.
50
-apt-get update -q
51
-
52
-# Install docker.  'apt-get install' is idempotent.
53
-apt-get install -q -y lxc-docker
54
-
55
-usermod -a -G docker "$user"
56
-
57
-tmp=`mktemp -q` && {
58
-    # Only install the backport kernel, don't bother upgrading if the backport is
59
-    # already installed.  We want parse the output of apt so we need to save it
60
-    # with 'tee'.  NOTE: The installation of the kernel will trigger dkms to
61
-    # install vboxguest if needed.
62
-    apt-get install -q -y --no-upgrade linux-image-generic-lts-raring | \
63
-        tee "$tmp"
64
-
65
-    # Parse the number of installed packages from the output
66
-    NUM_INST=`awk '$2 == "upgraded," && $4 == "newly" { print $3 }' "$tmp"`
67
-    rm "$tmp"
68
-}
69
-
70
-# If the number of installed packages is greater than 0, we want to reboot (the
71
-# backport kernel was installed but is not running).
72
-if [ "$NUM_INST" -gt 0 ];
73
-then
74
-    echo "Rebooting down to activate new kernel."
75
-    echo "/vagrant will not be mounted.  Use 'vagrant halt' followed by"
76
-    echo "'vagrant up' to ensure /vagrant is mounted."
77
-    shutdown -r now
78
-fi
79
-SCRIPT
80
-
81
-# We need to install the virtualbox guest additions *before* we do the normal
82
-# docker installation.  As such this script is prepended to the common docker
83
-# install script above.  This allows the install of the backport kernel to
84
-# trigger dkms to build the virtualbox guest module install.
85
-$vbox_script = <<VBOX_SCRIPT + $script
86
-# Install the VirtualBox guest additions if they aren't already installed.
87
-if [ ! -d /opt/VBoxGuestAdditions-4.3.6/ ]; then
88
-    # Update remote package metadata.  'apt-get update' is idempotent.
89
-    apt-get update -q
90
-
91
-    # Kernel Headers and dkms are required to build the vbox guest kernel
92
-    # modules.
93
-    apt-get install -q -y linux-headers-generic-lts-raring dkms
94
-
95
-    echo 'Downloading VBox Guest Additions...'
96
-    wget -cq http://dlc.sun.com.edgesuite.net/virtualbox/4.3.6/VBoxGuestAdditions_4.3.6.iso
97
-    echo "95648fcdb5d028e64145a2fe2f2f28c946d219da366389295a61fed296ca79f0  VBoxGuestAdditions_4.3.6.iso" | sha256sum --check || exit 1
98
-
99
-    mount -o loop,ro /home/vagrant/VBoxGuestAdditions_4.3.6.iso /mnt
100
-    /mnt/VBoxLinuxAdditions.run --nox11
101
-    umount /mnt
102
-fi
103
-VBOX_SCRIPT
104
-
105
-Vagrant::Config.run do |config|
106
-  # Setup virtual machine box. This VM configuration code is always executed.
107
-  config.vm.box = BOX_NAME
108
-  config.vm.box_url = BOX_URI
109
-
110
-  # Use the specified private key path if it is specified and not empty.
111
-  if SSH_PRIVKEY_PATH
112
-      config.ssh.private_key_path = SSH_PRIVKEY_PATH
113
-  end
114
-
115
-  config.ssh.forward_agent = true
116
-end
117
-
118
-# Providers were added on Vagrant >= 1.1.0
119
-#
120
-# NOTE: The vagrant "vm.provision" appends its arguments to a list and executes
121
-# them in order.  If you invoke "vm.provision :shell, :inline => $script"
122
-# twice then vagrant will run the script two times.  Unfortunately when you use
123
-# providers and the override argument to set up provisioners (like the vbox
124
-# guest extensions) they 1) don't replace the other provisioners (they append
125
-# to the end of the list) and 2) you can't control the order the provisioners
126
-# are executed (you can only append to the list).  If you want the virtualbox
127
-# only script to run before the other script, you have to jump through a lot of
128
-# hoops.
129
-#
130
-# Here is my only repeatable solution: make one script that is common ($script)
131
-# and another script that is the virtual box guest *prepended* to the common
132
-# script.  Only ever use "vm.provision" *one time* per provider.  That means
133
-# every single provider has an override, and every single one configures
134
-# "vm.provision".  Much saddness, but such is life.
135
-Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
136
-  config.vm.provider :aws do |aws, override|
137
-    username = "ubuntu"
138
-    override.vm.box_url = AWS_BOX_URI
139
-    override.vm.provision :shell, :inline => $script, :args => username
140
-    aws.access_key_id = ENV["AWS_ACCESS_KEY"]
141
-    aws.secret_access_key = ENV["AWS_SECRET_KEY"]
142
-    aws.keypair_name = ENV["AWS_KEYPAIR_NAME"]
143
-    override.ssh.username = username
144
-    aws.region = AWS_REGION
145
-    aws.ami    = AWS_AMI
146
-    aws.instance_type = AWS_INSTANCE_TYPE
147
-  end
148
-
149
-  config.vm.provider :rackspace do |rs, override|
150
-    override.vm.provision :shell, :inline => $script
151
-    rs.username = ENV["RS_USERNAME"]
152
-    rs.api_key  = ENV["RS_API_KEY"]
153
-    rs.public_key_path = ENV["RS_PUBLIC_KEY"]
154
-    rs.flavor   = /512MB/
155
-    rs.image    = /Ubuntu/
156
-  end
157
-
158
-  config.vm.provider :vmware_fusion do |f, override|
159
-    override.vm.box_url = VF_BOX_URI
160
-    override.vm.synced_folder ".", "/vagrant", disabled: true
161
-    override.vm.provision :shell, :inline => $script
162
-    f.vmx["displayName"] = "docker"
163
-  end
164
-
165
-  config.vm.provider :virtualbox do |vb, override|
166
-    override.vm.provision :shell, :inline => $vbox_script
167
-    vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
168
-    vb.customize ["modifyvm", :id, "--natdnsproxy1", "on"]
169
-    vb.customize ["modifyvm", :id, "--memory", VAGRANT_RAM]
170
-    vb.customize ["modifyvm", :id, "--cpus", VAGRANT_CORES]
171
-  end
172
-end
173
-
174
-# If this is a version 1 config, virtualbox is the only option.  A version 2
175
-# config would have already been set in the above provider section.
176
-Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config|
177
-  config.vm.provision :shell, :inline => $vbox_script
178
-end
179
-
180
-# Setup port forwarding per loaded environment variables
181
-forward_ports = FORWARD_DOCKER_PORTS.nil? ? [] : [*49153..49900]
182
-forward_ports += FORWARD_PORTS.split(',').map{|i| i.to_i } if FORWARD_PORTS
183
-if forward_ports.any?
184
-  Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config|
185
-    forward_ports.each do |port|
186
-      config.vm.forward_port port, port
187
-    end
188
-  end
189
-
190
-  Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
191
-    forward_ports.each do |port|
192
-      config.vm.network :forwarded_port, :host => port, :guest => port, auto_correct: true
193
-    end
194
-  end
195
-end
196
-
197
-if !PRIVATE_NETWORK.nil?
198
-  Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config|
199
-    config.vm.network :hostonly, PRIVATE_NETWORK
200
-  end
201
-
202
-  Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
203
-    config.vm.network "private_network", ip: PRIVATE_NETWORK
204
-  end
205
-end
206
-
207 1
deleted file mode 100644
... ...
@@ -1,1248 +0,0 @@
1
-package api
2
-
3
-import (
4
-	"bufio"
5
-	"bytes"
6
-	"code.google.com/p/go.net/websocket"
7
-	"encoding/base64"
8
-	"encoding/json"
9
-	"expvar"
10
-	"fmt"
11
-	"github.com/dotcloud/docker/auth"
12
-	"github.com/dotcloud/docker/engine"
13
-	"github.com/dotcloud/docker/pkg/listenbuffer"
14
-	"github.com/dotcloud/docker/pkg/systemd"
15
-	"github.com/dotcloud/docker/utils"
16
-	"github.com/gorilla/mux"
17
-	"io"
18
-	"io/ioutil"
19
-	"log"
20
-	"mime"
21
-	"net"
22
-	"net/http"
23
-	"net/http/pprof"
24
-	"os"
25
-	"regexp"
26
-	"strconv"
27
-	"strings"
28
-	"syscall"
29
-	"time"
30
-)
31
-
32
-// FIXME: move code common to client and server to common.go
33
-const (
34
-	APIVERSION        = 1.9
35
-	DEFAULTHTTPHOST   = "127.0.0.1"
36
-	DEFAULTUNIXSOCKET = "/var/run/docker.sock"
37
-)
38
-
39
-var (
40
-	activationLock chan struct{}
41
-)
42
-
43
-func ValidateHost(val string) (string, error) {
44
-	host, err := utils.ParseHost(DEFAULTHTTPHOST, DEFAULTUNIXSOCKET, val)
45
-	if err != nil {
46
-		return val, err
47
-	}
48
-	return host, nil
49
-}
50
-
51
-type HttpApiFunc func(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error
52
-
53
-func init() {
54
-	engine.Register("serveapi", ServeApi)
55
-}
56
-
57
-func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
58
-	conn, _, err := w.(http.Hijacker).Hijack()
59
-	if err != nil {
60
-		return nil, nil, err
61
-	}
62
-	// Flush the options to make sure the client sets the raw mode
63
-	conn.Write([]byte{})
64
-	return conn, conn, nil
65
-}
66
-
67
-//If we don't do this, POST method without Content-type (even with empty body) will fail
68
-func parseForm(r *http.Request) error {
69
-	if r == nil {
70
-		return nil
71
-	}
72
-	if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
73
-		return err
74
-	}
75
-	return nil
76
-}
77
-
78
-func parseMultipartForm(r *http.Request) error {
79
-	if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
80
-		return err
81
-	}
82
-	return nil
83
-}
84
-
85
-func httpError(w http.ResponseWriter, err error) {
86
-	statusCode := http.StatusInternalServerError
87
-	// FIXME: this is brittle and should not be necessary.
88
-	// If we need to differentiate between different possible error types, we should
89
-	// create appropriate error types with clearly defined meaning.
90
-	if strings.Contains(err.Error(), "No such") {
91
-		statusCode = http.StatusNotFound
92
-	} else if strings.Contains(err.Error(), "Bad parameter") {
93
-		statusCode = http.StatusBadRequest
94
-	} else if strings.Contains(err.Error(), "Conflict") {
95
-		statusCode = http.StatusConflict
96
-	} else if strings.Contains(err.Error(), "Impossible") {
97
-		statusCode = http.StatusNotAcceptable
98
-	} else if strings.Contains(err.Error(), "Wrong login/password") {
99
-		statusCode = http.StatusUnauthorized
100
-	} else if strings.Contains(err.Error(), "hasn't been activated") {
101
-		statusCode = http.StatusForbidden
102
-	}
103
-
104
-	if err != nil {
105
-		utils.Errorf("HTTP Error: statusCode=%d %s", statusCode, err.Error())
106
-		http.Error(w, err.Error(), statusCode)
107
-	}
108
-}
109
-
110
-func writeJSON(w http.ResponseWriter, code int, v engine.Env) error {
111
-	w.Header().Set("Content-Type", "application/json")
112
-	w.WriteHeader(code)
113
-	return v.Encode(w)
114
-}
115
-
116
-func streamJSON(job *engine.Job, w http.ResponseWriter, flush bool) {
117
-	w.Header().Set("Content-Type", "application/json")
118
-	if flush {
119
-		job.Stdout.Add(utils.NewWriteFlusher(w))
120
-	} else {
121
-		job.Stdout.Add(w)
122
-	}
123
-}
124
-
125
-func getBoolParam(value string) (bool, error) {
126
-	if value == "" {
127
-		return false, nil
128
-	}
129
-	ret, err := strconv.ParseBool(value)
130
-	if err != nil {
131
-		return false, fmt.Errorf("Bad parameter")
132
-	}
133
-	return ret, nil
134
-}
135
-
136
-//TODO remove, used on < 1.5 in getContainersJSON
137
-func displayablePorts(ports *engine.Table) string {
138
-	result := []string{}
139
-	for _, port := range ports.Data {
140
-		if port.Get("IP") == "" {
141
-			result = append(result, fmt.Sprintf("%d/%s", port.GetInt("PublicPort"), port.Get("Type")))
142
-		} else {
143
-			result = append(result, fmt.Sprintf("%s:%d->%d/%s", port.Get("IP"), port.GetInt("PublicPort"), port.GetInt("PrivatePort"), port.Get("Type")))
144
-		}
145
-	}
146
-	return strings.Join(result, ", ")
147
-}
148
-
149
-func MatchesContentType(contentType, expectedType string) bool {
150
-	mimetype, _, err := mime.ParseMediaType(contentType)
151
-	if err != nil {
152
-		utils.Errorf("Error parsing media type: %s error: %s", contentType, err.Error())
153
-	}
154
-	return err == nil && mimetype == expectedType
155
-}
156
-
157
-func postAuth(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
158
-	var (
159
-		authConfig, err = ioutil.ReadAll(r.Body)
160
-		job             = eng.Job("auth")
161
-		status          string
162
-	)
163
-	if err != nil {
164
-		return err
165
-	}
166
-	job.Setenv("authConfig", string(authConfig))
167
-	job.Stdout.AddString(&status)
168
-	if err = job.Run(); err != nil {
169
-		return err
170
-	}
171
-	if status != "" {
172
-		var env engine.Env
173
-		env.Set("Status", status)
174
-		return writeJSON(w, http.StatusOK, env)
175
-	}
176
-	w.WriteHeader(http.StatusNoContent)
177
-	return nil
178
-}
179
-
180
-func getVersion(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
181
-	w.Header().Set("Content-Type", "application/json")
182
-	eng.ServeHTTP(w, r)
183
-	return nil
184
-}
185
-
186
-func postContainersKill(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
187
-	if vars == nil {
188
-		return fmt.Errorf("Missing parameter")
189
-	}
190
-	if err := parseForm(r); err != nil {
191
-		return err
192
-	}
193
-	job := eng.Job("kill", vars["name"])
194
-	if sig := r.Form.Get("signal"); sig != "" {
195
-		job.Args = append(job.Args, sig)
196
-	}
197
-	if err := job.Run(); err != nil {
198
-		return err
199
-	}
200
-	w.WriteHeader(http.StatusNoContent)
201
-	return nil
202
-}
203
-
204
-func getContainersExport(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
205
-	if vars == nil {
206
-		return fmt.Errorf("Missing parameter")
207
-	}
208
-	job := eng.Job("export", vars["name"])
209
-	job.Stdout.Add(w)
210
-	if err := job.Run(); err != nil {
211
-		return err
212
-	}
213
-	return nil
214
-}
215
-
216
-func getImagesJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
217
-	if err := parseForm(r); err != nil {
218
-		return err
219
-	}
220
-
221
-	var (
222
-		err  error
223
-		outs *engine.Table
224
-		job  = eng.Job("images")
225
-	)
226
-
227
-	job.Setenv("filter", r.Form.Get("filter"))
228
-	job.Setenv("all", r.Form.Get("all"))
229
-
230
-	if version >= 1.7 {
231
-		streamJSON(job, w, false)
232
-	} else if outs, err = job.Stdout.AddListTable(); err != nil {
233
-		return err
234
-	}
235
-
236
-	if err := job.Run(); err != nil {
237
-		return err
238
-	}
239
-
240
-	if version < 1.7 && outs != nil { // Convert to legacy format
241
-		outsLegacy := engine.NewTable("Created", 0)
242
-		for _, out := range outs.Data {
243
-			for _, repoTag := range out.GetList("RepoTags") {
244
-				parts := strings.Split(repoTag, ":")
245
-				outLegacy := &engine.Env{}
246
-				outLegacy.Set("Repository", parts[0])
247
-				outLegacy.Set("Tag", parts[1])
248
-				outLegacy.Set("Id", out.Get("Id"))
249
-				outLegacy.SetInt64("Created", out.GetInt64("Created"))
250
-				outLegacy.SetInt64("Size", out.GetInt64("Size"))
251
-				outLegacy.SetInt64("VirtualSize", out.GetInt64("VirtualSize"))
252
-				outsLegacy.Add(outLegacy)
253
-			}
254
-		}
255
-		w.Header().Set("Content-Type", "application/json")
256
-		if _, err := outsLegacy.WriteListTo(w); err != nil {
257
-			return err
258
-		}
259
-	}
260
-	return nil
261
-}
262
-
263
-func getImagesViz(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
264
-	if version > 1.6 {
265
-		w.WriteHeader(http.StatusNotFound)
266
-		return fmt.Errorf("This is now implemented in the client.")
267
-	}
268
-	eng.ServeHTTP(w, r)
269
-	return nil
270
-}
271
-
272
-func getInfo(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
273
-	w.Header().Set("Content-Type", "application/json")
274
-	eng.ServeHTTP(w, r)
275
-	return nil
276
-}
277
-
278
-func getEvents(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
279
-	if err := parseForm(r); err != nil {
280
-		return err
281
-	}
282
-
283
-	var job = eng.Job("events", r.RemoteAddr)
284
-	streamJSON(job, w, true)
285
-	job.Setenv("since", r.Form.Get("since"))
286
-	return job.Run()
287
-}
288
-
289
-func getImagesHistory(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
290
-	if vars == nil {
291
-		return fmt.Errorf("Missing parameter")
292
-	}
293
-
294
-	var job = eng.Job("history", vars["name"])
295
-	streamJSON(job, w, false)
296
-
297
-	if err := job.Run(); err != nil {
298
-		return err
299
-	}
300
-	return nil
301
-}
302
-
303
-func getContainersChanges(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
304
-	if vars == nil {
305
-		return fmt.Errorf("Missing parameter")
306
-	}
307
-	var job = eng.Job("changes", vars["name"])
308
-	streamJSON(job, w, false)
309
-
310
-	return job.Run()
311
-}
312
-
313
-func getContainersTop(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
314
-	if version < 1.4 {
315
-		return fmt.Errorf("top was improved a lot since 1.3, Please upgrade your docker client.")
316
-	}
317
-	if vars == nil {
318
-		return fmt.Errorf("Missing parameter")
319
-	}
320
-	if err := parseForm(r); err != nil {
321
-		return err
322
-	}
323
-
324
-	job := eng.Job("top", vars["name"], r.Form.Get("ps_args"))
325
-	streamJSON(job, w, false)
326
-	return job.Run()
327
-}
328
-
329
-func getContainersJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
330
-	if err := parseForm(r); err != nil {
331
-		return err
332
-	}
333
-	var (
334
-		err  error
335
-		outs *engine.Table
336
-		job  = eng.Job("containers")
337
-	)
338
-
339
-	job.Setenv("all", r.Form.Get("all"))
340
-	job.Setenv("size", r.Form.Get("size"))
341
-	job.Setenv("since", r.Form.Get("since"))
342
-	job.Setenv("before", r.Form.Get("before"))
343
-	job.Setenv("limit", r.Form.Get("limit"))
344
-
345
-	if version >= 1.5 {
346
-		streamJSON(job, w, false)
347
-	} else if outs, err = job.Stdout.AddTable(); err != nil {
348
-		return err
349
-	}
350
-	if err = job.Run(); err != nil {
351
-		return err
352
-	}
353
-	if version < 1.5 { // Convert to legacy format
354
-		for _, out := range outs.Data {
355
-			ports := engine.NewTable("", 0)
356
-			ports.ReadListFrom([]byte(out.Get("Ports")))
357
-			out.Set("Ports", displayablePorts(ports))
358
-		}
359
-		w.Header().Set("Content-Type", "application/json")
360
-		if _, err = outs.WriteListTo(w); err != nil {
361
-			return err
362
-		}
363
-	}
364
-	return nil
365
-}
366
-
367
-func postImagesTag(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
368
-	if err := parseForm(r); err != nil {
369
-		return err
370
-	}
371
-	if vars == nil {
372
-		return fmt.Errorf("Missing parameter")
373
-	}
374
-
375
-	job := eng.Job("tag", vars["name"], r.Form.Get("repo"), r.Form.Get("tag"))
376
-	job.Setenv("force", r.Form.Get("force"))
377
-	if err := job.Run(); err != nil {
378
-		return err
379
-	}
380
-	w.WriteHeader(http.StatusCreated)
381
-	return nil
382
-}
383
-
384
-func postCommit(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
385
-	if err := parseForm(r); err != nil {
386
-		return err
387
-	}
388
-	var (
389
-		config engine.Env
390
-		env    engine.Env
391
-		job    = eng.Job("commit", r.Form.Get("container"))
392
-	)
393
-	if err := config.Decode(r.Body); err != nil {
394
-		utils.Errorf("%s", err)
395
-	}
396
-
397
-	job.Setenv("repo", r.Form.Get("repo"))
398
-	job.Setenv("tag", r.Form.Get("tag"))
399
-	job.Setenv("author", r.Form.Get("author"))
400
-	job.Setenv("comment", r.Form.Get("comment"))
401
-	job.SetenvSubEnv("config", &config)
402
-
403
-	var id string
404
-	job.Stdout.AddString(&id)
405
-	if err := job.Run(); err != nil {
406
-		return err
407
-	}
408
-	env.Set("Id", id)
409
-	return writeJSON(w, http.StatusCreated, env)
410
-}
411
-
412
-// Creates an image from Pull or from Import
413
-func postImagesCreate(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
414
-	if err := parseForm(r); err != nil {
415
-		return err
416
-	}
417
-
418
-	var (
419
-		image = r.Form.Get("fromImage")
420
-		tag   = r.Form.Get("tag")
421
-		job   *engine.Job
422
-	)
423
-	authEncoded := r.Header.Get("X-Registry-Auth")
424
-	authConfig := &auth.AuthConfig{}
425
-	if authEncoded != "" {
426
-		authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
427
-		if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
428
-			// for a pull it is not an error if no auth was given
429
-			// to increase compatibility with the existing api it is defaulting to be empty
430
-			authConfig = &auth.AuthConfig{}
431
-		}
432
-	}
433
-	if version > 1.0 {
434
-		w.Header().Set("Content-Type", "application/json")
435
-	}
436
-	if image != "" { //pull
437
-		metaHeaders := map[string][]string{}
438
-		for k, v := range r.Header {
439
-			if strings.HasPrefix(k, "X-Meta-") {
440
-				metaHeaders[k] = v
441
-			}
442
-		}
443
-		job = eng.Job("pull", r.Form.Get("fromImage"), tag)
444
-		job.SetenvBool("parallel", version > 1.3)
445
-		job.SetenvJson("metaHeaders", metaHeaders)
446
-		job.SetenvJson("authConfig", authConfig)
447
-	} else { //import
448
-		job = eng.Job("import", r.Form.Get("fromSrc"), r.Form.Get("repo"), tag)
449
-		job.Stdin.Add(r.Body)
450
-	}
451
-
452
-	if version > 1.0 {
453
-		job.SetenvBool("json", true)
454
-		streamJSON(job, w, true)
455
-	} else {
456
-		job.Stdout.Add(utils.NewWriteFlusher(w))
457
-	}
458
-	if err := job.Run(); err != nil {
459
-		if !job.Stdout.Used() {
460
-			return err
461
-		}
462
-		sf := utils.NewStreamFormatter(version > 1.0)
463
-		w.Write(sf.FormatError(err))
464
-	}
465
-
466
-	return nil
467
-}
468
-
469
-func getImagesSearch(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
470
-	if err := parseForm(r); err != nil {
471
-		return err
472
-	}
473
-	var (
474
-		authEncoded = r.Header.Get("X-Registry-Auth")
475
-		authConfig  = &auth.AuthConfig{}
476
-		metaHeaders = map[string][]string{}
477
-	)
478
-
479
-	if authEncoded != "" {
480
-		authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
481
-		if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
482
-			// for a search it is not an error if no auth was given
483
-			// to increase compatibility with the existing api it is defaulting to be empty
484
-			authConfig = &auth.AuthConfig{}
485
-		}
486
-	}
487
-	for k, v := range r.Header {
488
-		if strings.HasPrefix(k, "X-Meta-") {
489
-			metaHeaders[k] = v
490
-		}
491
-	}
492
-
493
-	var job = eng.Job("search", r.Form.Get("term"))
494
-	job.SetenvJson("metaHeaders", metaHeaders)
495
-	job.SetenvJson("authConfig", authConfig)
496
-	streamJSON(job, w, false)
497
-
498
-	return job.Run()
499
-}
500
-
501
-func postImagesInsert(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
502
-	if err := parseForm(r); err != nil {
503
-		return err
504
-	}
505
-	if vars == nil {
506
-		return fmt.Errorf("Missing parameter")
507
-	}
508
-	if version > 1.0 {
509
-		w.Header().Set("Content-Type", "application/json")
510
-	}
511
-
512
-	job := eng.Job("insert", vars["name"], r.Form.Get("url"), r.Form.Get("path"))
513
-	if version > 1.0 {
514
-		job.SetenvBool("json", true)
515
-		streamJSON(job, w, false)
516
-	} else {
517
-		job.Stdout.Add(w)
518
-	}
519
-	if err := job.Run(); err != nil {
520
-		if !job.Stdout.Used() {
521
-			return err
522
-		}
523
-		sf := utils.NewStreamFormatter(version > 1.0)
524
-		w.Write(sf.FormatError(err))
525
-	}
526
-
527
-	return nil
528
-}
529
-
530
-func postImagesPush(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
531
-	if vars == nil {
532
-		return fmt.Errorf("Missing parameter")
533
-	}
534
-
535
-	metaHeaders := map[string][]string{}
536
-	for k, v := range r.Header {
537
-		if strings.HasPrefix(k, "X-Meta-") {
538
-			metaHeaders[k] = v
539
-		}
540
-	}
541
-	if err := parseForm(r); err != nil {
542
-		return err
543
-	}
544
-	authConfig := &auth.AuthConfig{}
545
-
546
-	authEncoded := r.Header.Get("X-Registry-Auth")
547
-	if authEncoded != "" {
548
-		// the new format is to handle the authConfig as a header
549
-		authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
550
-		if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
551
-			// to increase compatibility to existing api it is defaulting to be empty
552
-			authConfig = &auth.AuthConfig{}
553
-		}
554
-	} else {
555
-		// the old format is supported for compatibility if there was no authConfig header
556
-		if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
557
-			return err
558
-		}
559
-	}
560
-
561
-	if version > 1.0 {
562
-		w.Header().Set("Content-Type", "application/json")
563
-	}
564
-	job := eng.Job("push", vars["name"])
565
-	job.SetenvJson("metaHeaders", metaHeaders)
566
-	job.SetenvJson("authConfig", authConfig)
567
-	if version > 1.0 {
568
-		job.SetenvBool("json", true)
569
-		streamJSON(job, w, true)
570
-	} else {
571
-		job.Stdout.Add(utils.NewWriteFlusher(w))
572
-	}
573
-
574
-	if err := job.Run(); err != nil {
575
-		if !job.Stdout.Used() {
576
-			return err
577
-		}
578
-		sf := utils.NewStreamFormatter(version > 1.0)
579
-		w.Write(sf.FormatError(err))
580
-	}
581
-	return nil
582
-}
583
-
584
-func getImagesGet(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
585
-	if vars == nil {
586
-		return fmt.Errorf("Missing parameter")
587
-	}
588
-	if version > 1.0 {
589
-		w.Header().Set("Content-Type", "application/x-tar")
590
-	}
591
-	job := eng.Job("image_export", vars["name"])
592
-	job.Stdout.Add(w)
593
-	return job.Run()
594
-}
595
-
596
-func postImagesLoad(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
597
-	job := eng.Job("load")
598
-	job.Stdin.Add(r.Body)
599
-	return job.Run()
600
-}
601
-
602
-func postContainersCreate(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
603
-	if err := parseForm(r); err != nil {
604
-		return nil
605
-	}
606
-	var (
607
-		out         engine.Env
608
-		job         = eng.Job("create", r.Form.Get("name"))
609
-		outWarnings []string
610
-		outId       string
611
-		warnings    = bytes.NewBuffer(nil)
612
-	)
613
-	if err := job.DecodeEnv(r.Body); err != nil {
614
-		return err
615
-	}
616
-	// Read container ID from the first line of stdout
617
-	job.Stdout.AddString(&outId)
618
-	// Read warnings from stderr
619
-	job.Stderr.Add(warnings)
620
-	if err := job.Run(); err != nil {
621
-		return err
622
-	}
623
-	// Parse warnings from stderr
624
-	scanner := bufio.NewScanner(warnings)
625
-	for scanner.Scan() {
626
-		outWarnings = append(outWarnings, scanner.Text())
627
-	}
628
-	out.Set("Id", outId)
629
-	out.SetList("Warnings", outWarnings)
630
-	return writeJSON(w, http.StatusCreated, out)
631
-}
632
-
633
-func postContainersRestart(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
634
-	if err := parseForm(r); err != nil {
635
-		return err
636
-	}
637
-	if vars == nil {
638
-		return fmt.Errorf("Missing parameter")
639
-	}
640
-	job := eng.Job("restart", vars["name"])
641
-	job.Setenv("t", r.Form.Get("t"))
642
-	if err := job.Run(); err != nil {
643
-		return err
644
-	}
645
-	w.WriteHeader(http.StatusNoContent)
646
-	return nil
647
-}
648
-
649
-func deleteContainers(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
650
-	if err := parseForm(r); err != nil {
651
-		return err
652
-	}
653
-	if vars == nil {
654
-		return fmt.Errorf("Missing parameter")
655
-	}
656
-	job := eng.Job("container_delete", vars["name"])
657
-	job.Setenv("removeVolume", r.Form.Get("v"))
658
-	job.Setenv("removeLink", r.Form.Get("link"))
659
-	if err := job.Run(); err != nil {
660
-		return err
661
-	}
662
-	w.WriteHeader(http.StatusNoContent)
663
-	return nil
664
-}
665
-
666
-func deleteImages(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
667
-	if err := parseForm(r); err != nil {
668
-		return err
669
-	}
670
-	if vars == nil {
671
-		return fmt.Errorf("Missing parameter")
672
-	}
673
-	var job = eng.Job("image_delete", vars["name"])
674
-	streamJSON(job, w, false)
675
-	job.SetenvBool("autoPrune", version > 1.1)
676
-
677
-	return job.Run()
678
-}
679
-
680
-func postContainersStart(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
681
-	if vars == nil {
682
-		return fmt.Errorf("Missing parameter")
683
-	}
684
-	name := vars["name"]
685
-	job := eng.Job("start", name)
686
-	// allow a nil body for backwards compatibility
687
-	if r.Body != nil {
688
-		if MatchesContentType(r.Header.Get("Content-Type"), "application/json") {
689
-			if err := job.DecodeEnv(r.Body); err != nil {
690
-				return err
691
-			}
692
-		}
693
-	}
694
-	if err := job.Run(); err != nil {
695
-		return err
696
-	}
697
-	w.WriteHeader(http.StatusNoContent)
698
-	return nil
699
-}
700
-
701
-func postContainersStop(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
702
-	if err := parseForm(r); err != nil {
703
-		return err
704
-	}
705
-	if vars == nil {
706
-		return fmt.Errorf("Missing parameter")
707
-	}
708
-	job := eng.Job("stop", vars["name"])
709
-	job.Setenv("t", r.Form.Get("t"))
710
-	if err := job.Run(); err != nil {
711
-		return err
712
-	}
713
-	w.WriteHeader(http.StatusNoContent)
714
-	return nil
715
-}
716
-
717
-func postContainersWait(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
718
-	if vars == nil {
719
-		return fmt.Errorf("Missing parameter")
720
-	}
721
-	var (
722
-		env    engine.Env
723
-		status string
724
-		job    = eng.Job("wait", vars["name"])
725
-	)
726
-	job.Stdout.AddString(&status)
727
-	if err := job.Run(); err != nil {
728
-		return err
729
-	}
730
-	// Parse a 16-bit encoded integer to map typical unix exit status.
731
-	_, err := strconv.ParseInt(status, 10, 16)
732
-	if err != nil {
733
-		return err
734
-	}
735
-	env.Set("StatusCode", status)
736
-	return writeJSON(w, http.StatusOK, env)
737
-}
738
-
739
-func postContainersResize(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
740
-	if err := parseForm(r); err != nil {
741
-		return err
742
-	}
743
-	if vars == nil {
744
-		return fmt.Errorf("Missing parameter")
745
-	}
746
-	if err := eng.Job("resize", vars["name"], r.Form.Get("h"), r.Form.Get("w")).Run(); err != nil {
747
-		return err
748
-	}
749
-	return nil
750
-}
751
-
752
-func postContainersAttach(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
753
-	if err := parseForm(r); err != nil {
754
-		return err
755
-	}
756
-	if vars == nil {
757
-		return fmt.Errorf("Missing parameter")
758
-	}
759
-
760
-	var (
761
-		job    = eng.Job("inspect", vars["name"], "container")
762
-		c, err = job.Stdout.AddEnv()
763
-	)
764
-	if err != nil {
765
-		return err
766
-	}
767
-	if err = job.Run(); err != nil {
768
-		return err
769
-	}
770
-
771
-	inStream, outStream, err := hijackServer(w)
772
-	if err != nil {
773
-		return err
774
-	}
775
-	defer func() {
776
-		if tcpc, ok := inStream.(*net.TCPConn); ok {
777
-			tcpc.CloseWrite()
778
-		} else {
779
-			inStream.Close()
780
-		}
781
-	}()
782
-	defer func() {
783
-		if tcpc, ok := outStream.(*net.TCPConn); ok {
784
-			tcpc.CloseWrite()
785
-		} else if closer, ok := outStream.(io.Closer); ok {
786
-			closer.Close()
787
-		}
788
-	}()
789
-
790
-	var errStream io.Writer
791
-
792
-	fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
793
-
794
-	if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && version >= 1.6 {
795
-		errStream = utils.NewStdWriter(outStream, utils.Stderr)
796
-		outStream = utils.NewStdWriter(outStream, utils.Stdout)
797
-	} else {
798
-		errStream = outStream
799
-	}
800
-
801
-	job = eng.Job("attach", vars["name"])
802
-	job.Setenv("logs", r.Form.Get("logs"))
803
-	job.Setenv("stream", r.Form.Get("stream"))
804
-	job.Setenv("stdin", r.Form.Get("stdin"))
805
-	job.Setenv("stdout", r.Form.Get("stdout"))
806
-	job.Setenv("stderr", r.Form.Get("stderr"))
807
-	job.Stdin.Add(inStream)
808
-	job.Stdout.Add(outStream)
809
-	job.Stderr.Set(errStream)
810
-	if err := job.Run(); err != nil {
811
-		fmt.Fprintf(outStream, "Error: %s\n", err)
812
-
813
-	}
814
-	return nil
815
-}
816
-
817
-func wsContainersAttach(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
818
-	if err := parseForm(r); err != nil {
819
-		return err
820
-	}
821
-	if vars == nil {
822
-		return fmt.Errorf("Missing parameter")
823
-	}
824
-
825
-	if err := eng.Job("inspect", vars["name"], "container").Run(); err != nil {
826
-		return err
827
-	}
828
-
829
-	h := websocket.Handler(func(ws *websocket.Conn) {
830
-		defer ws.Close()
831
-		job := eng.Job("attach", vars["name"])
832
-		job.Setenv("logs", r.Form.Get("logs"))
833
-		job.Setenv("stream", r.Form.Get("stream"))
834
-		job.Setenv("stdin", r.Form.Get("stdin"))
835
-		job.Setenv("stdout", r.Form.Get("stdout"))
836
-		job.Setenv("stderr", r.Form.Get("stderr"))
837
-		job.Stdin.Add(ws)
838
-		job.Stdout.Add(ws)
839
-		job.Stderr.Set(ws)
840
-		if err := job.Run(); err != nil {
841
-			utils.Errorf("Error: %s", err)
842
-		}
843
-	})
844
-	h.ServeHTTP(w, r)
845
-
846
-	return nil
847
-}
848
-
849
-func getContainersByName(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
850
-	if vars == nil {
851
-		return fmt.Errorf("Missing parameter")
852
-	}
853
-	var job = eng.Job("inspect", vars["name"], "container")
854
-	streamJSON(job, w, false)
855
-	job.SetenvBool("conflict", true) //conflict=true to detect conflict between containers and images in the job
856
-	return job.Run()
857
-}
858
-
859
-func getImagesByName(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
860
-	if vars == nil {
861
-		return fmt.Errorf("Missing parameter")
862
-	}
863
-	var job = eng.Job("inspect", vars["name"], "image")
864
-	streamJSON(job, w, false)
865
-	job.SetenvBool("conflict", true) //conflict=true to detect conflict between containers and images in the job
866
-	return job.Run()
867
-}
868
-
869
-func postBuild(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
870
-	if version < 1.3 {
871
-		return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.")
872
-	}
873
-	var (
874
-		authEncoded       = r.Header.Get("X-Registry-Auth")
875
-		authConfig        = &auth.AuthConfig{}
876
-		configFileEncoded = r.Header.Get("X-Registry-Config")
877
-		configFile        = &auth.ConfigFile{}
878
-		job               = eng.Job("build")
879
-	)
880
-
881
-	// This block can be removed when API versions prior to 1.9 are deprecated.
882
-	// Both headers will be parsed and sent along to the daemon, but if a non-empty
883
-	// ConfigFile is present, any value provided as an AuthConfig directly will
884
-	// be overridden. See BuildFile::CmdFrom for details.
885
-	if version < 1.9 && authEncoded != "" {
886
-		authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
887
-		if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
888
-			// for a pull it is not an error if no auth was given
889
-			// to increase compatibility with the existing api it is defaulting to be empty
890
-			authConfig = &auth.AuthConfig{}
891
-		}
892
-	}
893
-
894
-	if configFileEncoded != "" {
895
-		configFileJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(configFileEncoded))
896
-		if err := json.NewDecoder(configFileJson).Decode(configFile); err != nil {
897
-			// for a pull it is not an error if no auth was given
898
-			// to increase compatibility with the existing api it is defaulting to be empty
899
-			configFile = &auth.ConfigFile{}
900
-		}
901
-	}
902
-
903
-	if version >= 1.8 {
904
-		job.SetenvBool("json", true)
905
-		streamJSON(job, w, true)
906
-	} else {
907
-		job.Stdout.Add(utils.NewWriteFlusher(w))
908
-	}
909
-	job.Stdin.Add(r.Body)
910
-	job.Setenv("remote", r.FormValue("remote"))
911
-	job.Setenv("t", r.FormValue("t"))
912
-	job.Setenv("q", r.FormValue("q"))
913
-	job.Setenv("nocache", r.FormValue("nocache"))
914
-	job.Setenv("rm", r.FormValue("rm"))
915
-	job.SetenvJson("authConfig", authConfig)
916
-	job.SetenvJson("configFile", configFile)
917
-
918
-	if err := job.Run(); err != nil {
919
-		if !job.Stdout.Used() {
920
-			return err
921
-		}
922
-		sf := utils.NewStreamFormatter(version >= 1.8)
923
-		w.Write(sf.FormatError(err))
924
-	}
925
-	return nil
926
-}
927
-
928
-func postContainersCopy(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
929
-	if vars == nil {
930
-		return fmt.Errorf("Missing parameter")
931
-	}
932
-
933
-	var copyData engine.Env
934
-
935
-	if contentType := r.Header.Get("Content-Type"); contentType == "application/json" {
936
-		if err := copyData.Decode(r.Body); err != nil {
937
-			return err
938
-		}
939
-	} else {
940
-		return fmt.Errorf("Content-Type not supported: %s", contentType)
941
-	}
942
-
943
-	if copyData.Get("Resource") == "" {
944
-		return fmt.Errorf("Path cannot be empty")
945
-	}
946
-	if copyData.Get("Resource")[0] == '/' {
947
-		copyData.Set("Resource", copyData.Get("Resource")[1:])
948
-	}
949
-
950
-	job := eng.Job("container_copy", vars["name"], copyData.Get("Resource"))
951
-	streamJSON(job, w, false)
952
-	if err := job.Run(); err != nil {
953
-		utils.Errorf("%s", err.Error())
954
-		if strings.Contains(err.Error(), "No such container") {
955
-			w.WriteHeader(http.StatusNotFound)
956
-		}
957
-	}
958
-	return nil
959
-}
960
-
961
-func optionsHandler(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
962
-	w.WriteHeader(http.StatusOK)
963
-	return nil
964
-}
965
-func writeCorsHeaders(w http.ResponseWriter, r *http.Request) {
966
-	w.Header().Add("Access-Control-Allow-Origin", "*")
967
-	w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
968
-	w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS")
969
-}
970
-
971
-func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc, enableCors bool, dockerVersion string) http.HandlerFunc {
972
-	return func(w http.ResponseWriter, r *http.Request) {
973
-		// log the request
974
-		utils.Debugf("Calling %s %s", localMethod, localRoute)
975
-
976
-		if logging {
977
-			log.Println(r.Method, r.RequestURI)
978
-		}
979
-
980
-		if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
981
-			userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
982
-			if len(userAgent) == 2 && userAgent[1] != dockerVersion {
983
-				utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion)
984
-			}
985
-		}
986
-		version, err := strconv.ParseFloat(mux.Vars(r)["version"], 64)
987
-		if err != nil {
988
-			version = APIVERSION
989
-		}
990
-		if enableCors {
991
-			writeCorsHeaders(w, r)
992
-		}
993
-
994
-		if version == 0 || version > APIVERSION {
995
-			http.Error(w, fmt.Errorf("client and server don't have same version (client : %g, server: %g)", version, APIVERSION).Error(), http.StatusNotFound)
996
-			return
997
-		}
998
-
999
-		if err := handlerFunc(eng, version, w, r, mux.Vars(r)); err != nil {
1000
-			utils.Errorf("Error: %s", err)
1001
-			httpError(w, err)
1002
-		}
1003
-	}
1004
-}
1005
-
1006
-// Replicated from expvar.go as not public.
1007
-func expvarHandler(w http.ResponseWriter, r *http.Request) {
1008
-	w.Header().Set("Content-Type", "application/json; charset=utf-8")
1009
-	fmt.Fprintf(w, "{\n")
1010
-	first := true
1011
-	expvar.Do(func(kv expvar.KeyValue) {
1012
-		if !first {
1013
-			fmt.Fprintf(w, ",\n")
1014
-		}
1015
-		first = false
1016
-		fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
1017
-	})
1018
-	fmt.Fprintf(w, "\n}\n")
1019
-}
1020
-
1021
-func AttachProfiler(router *mux.Router) {
1022
-	router.HandleFunc("/debug/vars", expvarHandler)
1023
-	router.HandleFunc("/debug/pprof/", pprof.Index)
1024
-	router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
1025
-	router.HandleFunc("/debug/pprof/profile", pprof.Profile)
1026
-	router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
1027
-	router.HandleFunc("/debug/pprof/heap", pprof.Handler("heap").ServeHTTP)
1028
-	router.HandleFunc("/debug/pprof/goroutine", pprof.Handler("goroutine").ServeHTTP)
1029
-	router.HandleFunc("/debug/pprof/threadcreate", pprof.Handler("threadcreate").ServeHTTP)
1030
-}
1031
-
1032
-func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion string) (*mux.Router, error) {
1033
-	r := mux.NewRouter()
1034
-	if os.Getenv("DEBUG") != "" {
1035
-		AttachProfiler(r)
1036
-	}
1037
-	m := map[string]map[string]HttpApiFunc{
1038
-		"GET": {
1039
-			"/events":                         getEvents,
1040
-			"/info":                           getInfo,
1041
-			"/version":                        getVersion,
1042
-			"/images/json":                    getImagesJSON,
1043
-			"/images/viz":                     getImagesViz,
1044
-			"/images/search":                  getImagesSearch,
1045
-			"/images/{name:.*}/get":           getImagesGet,
1046
-			"/images/{name:.*}/history":       getImagesHistory,
1047
-			"/images/{name:.*}/json":          getImagesByName,
1048
-			"/containers/ps":                  getContainersJSON,
1049
-			"/containers/json":                getContainersJSON,
1050
-			"/containers/{name:.*}/export":    getContainersExport,
1051
-			"/containers/{name:.*}/changes":   getContainersChanges,
1052
-			"/containers/{name:.*}/json":      getContainersByName,
1053
-			"/containers/{name:.*}/top":       getContainersTop,
1054
-			"/containers/{name:.*}/attach/ws": wsContainersAttach,
1055
-		},
1056
-		"POST": {
1057
-			"/auth":                         postAuth,
1058
-			"/commit":                       postCommit,
1059
-			"/build":                        postBuild,
1060
-			"/images/create":                postImagesCreate,
1061
-			"/images/{name:.*}/insert":      postImagesInsert,
1062
-			"/images/load":                  postImagesLoad,
1063
-			"/images/{name:.*}/push":        postImagesPush,
1064
-			"/images/{name:.*}/tag":         postImagesTag,
1065
-			"/containers/create":            postContainersCreate,
1066
-			"/containers/{name:.*}/kill":    postContainersKill,
1067
-			"/containers/{name:.*}/restart": postContainersRestart,
1068
-			"/containers/{name:.*}/start":   postContainersStart,
1069
-			"/containers/{name:.*}/stop":    postContainersStop,
1070
-			"/containers/{name:.*}/wait":    postContainersWait,
1071
-			"/containers/{name:.*}/resize":  postContainersResize,
1072
-			"/containers/{name:.*}/attach":  postContainersAttach,
1073
-			"/containers/{name:.*}/copy":    postContainersCopy,
1074
-		},
1075
-		"DELETE": {
1076
-			"/containers/{name:.*}": deleteContainers,
1077
-			"/images/{name:.*}":     deleteImages,
1078
-		},
1079
-		"OPTIONS": {
1080
-			"": optionsHandler,
1081
-		},
1082
-	}
1083
-
1084
-	for method, routes := range m {
1085
-		for route, fct := range routes {
1086
-			utils.Debugf("Registering %s, %s", method, route)
1087
-			// NOTE: scope issue, make sure the variables are local and won't be changed
1088
-			localRoute := route
1089
-			localFct := fct
1090
-			localMethod := method
1091
-
1092
-			// build the handler function
1093
-			f := makeHttpHandler(eng, logging, localMethod, localRoute, localFct, enableCors, dockerVersion)
1094
-
1095
-			// add the new route
1096
-			if localRoute == "" {
1097
-				r.Methods(localMethod).HandlerFunc(f)
1098
-			} else {
1099
-				r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f)
1100
-				r.Path(localRoute).Methods(localMethod).HandlerFunc(f)
1101
-			}
1102
-		}
1103
-	}
1104
-
1105
-	return r, nil
1106
-}
1107
-
1108
-// ServeRequest processes a single http request to the docker remote api.
1109
-// FIXME: refactor this to be part of Server and not require re-creating a new
1110
-// router each time. This requires first moving ListenAndServe into Server.
1111
-func ServeRequest(eng *engine.Engine, apiversion float64, w http.ResponseWriter, req *http.Request) error {
1112
-	router, err := createRouter(eng, false, true, "")
1113
-	if err != nil {
1114
-		return err
1115
-	}
1116
-	// Insert APIVERSION into the request as a convenience
1117
-	req.URL.Path = fmt.Sprintf("/v%g%s", apiversion, req.URL.Path)
1118
-	router.ServeHTTP(w, req)
1119
-	return nil
1120
-}
1121
-
1122
-// ServeFD creates an http.Server and sets it up to serve given a socket activated
1123
-// argument.
1124
-func ServeFd(addr string, handle http.Handler) error {
1125
-	ls, e := systemd.ListenFD(addr)
1126
-	if e != nil {
1127
-		return e
1128
-	}
1129
-
1130
-	chErrors := make(chan error, len(ls))
1131
-
1132
-	// Since ListenFD will return one or more sockets we have
1133
-	// to create a go func to spawn off multiple serves
1134
-	for i := range ls {
1135
-		listener := ls[i]
1136
-		go func() {
1137
-			httpSrv := http.Server{Handler: handle}
1138
-			chErrors <- httpSrv.Serve(listener)
1139
-		}()
1140
-	}
1141
-
1142
-	for i := 0; i < len(ls); i += 1 {
1143
-		err := <-chErrors
1144
-		if err != nil {
1145
-			return err
1146
-		}
1147
-	}
1148
-
1149
-	return nil
1150
-}
1151
-
1152
-// ListenAndServe sets up the required http.Server and gets it listening for
1153
-// each addr passed in and does protocol specific checking.
1154
-func ListenAndServe(proto, addr string, eng *engine.Engine, logging, enableCors bool, dockerVersion string) error {
1155
-	r, err := createRouter(eng, logging, enableCors, dockerVersion)
1156
-	if err != nil {
1157
-		return err
1158
-	}
1159
-
1160
-	if proto == "fd" {
1161
-		return ServeFd(addr, r)
1162
-	}
1163
-
1164
-	if proto == "unix" {
1165
-		if err := syscall.Unlink(addr); err != nil && !os.IsNotExist(err) {
1166
-			return err
1167
-		}
1168
-	}
1169
-
1170
-	l, err := listenbuffer.NewListenBuffer(proto, addr, activationLock, 15*time.Minute)
1171
-	if err != nil {
1172
-		return err
1173
-	}
1174
-
1175
-	// Basic error and sanity checking
1176
-	switch proto {
1177
-	case "tcp":
1178
-		if !strings.HasPrefix(addr, "127.0.0.1") {
1179
-			log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
1180
-		}
1181
-	case "unix":
1182
-		if err := os.Chmod(addr, 0660); err != nil {
1183
-			return err
1184
-		}
1185
-
1186
-		groups, err := ioutil.ReadFile("/etc/group")
1187
-		if err != nil {
1188
-			return err
1189
-		}
1190
-		re := regexp.MustCompile("(^|\n)docker:.*?:([0-9]+)")
1191
-		if gidMatch := re.FindStringSubmatch(string(groups)); gidMatch != nil {
1192
-			gid, err := strconv.Atoi(gidMatch[2])
1193
-			if err != nil {
1194
-				return err
1195
-			}
1196
-			utils.Debugf("docker group found. gid: %d", gid)
1197
-			if err := os.Chown(addr, 0, gid); err != nil {
1198
-				return err
1199
-			}
1200
-		}
1201
-	default:
1202
-		return fmt.Errorf("Invalid protocol format.")
1203
-	}
1204
-
1205
-	httpSrv := http.Server{Addr: addr, Handler: r}
1206
-	return httpSrv.Serve(l)
1207
-}
1208
-
1209
-// ServeApi loops through all of the protocols sent in to docker and spawns
1210
-// off a go routine to setup a serving http.Server for each.
1211
-func ServeApi(job *engine.Job) engine.Status {
1212
-	var (
1213
-		protoAddrs = job.Args
1214
-		chErrors   = make(chan error, len(protoAddrs))
1215
-	)
1216
-	activationLock = make(chan struct{})
1217
-
1218
-	if err := job.Eng.Register("acceptconnections", AcceptConnections); err != nil {
1219
-		return job.Error(err)
1220
-	}
1221
-
1222
-	for _, protoAddr := range protoAddrs {
1223
-		protoAddrParts := strings.SplitN(protoAddr, "://", 2)
1224
-		go func() {
1225
-			log.Printf("Listening for HTTP on %s (%s)\n", protoAddrParts[0], protoAddrParts[1])
1226
-			chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("Version"))
1227
-		}()
1228
-	}
1229
-
1230
-	for i := 0; i < len(protoAddrs); i += 1 {
1231
-		err := <-chErrors
1232
-		if err != nil {
1233
-			return job.Error(err)
1234
-		}
1235
-	}
1236
-
1237
-	return engine.StatusOK
1238
-}
1239
-
1240
-func AcceptConnections(job *engine.Job) engine.Status {
1241
-	// Tell the init daemon we are accepting requests
1242
-	go systemd.SdNotify("READY=1")
1243
-
1244
-	// close the lock so the listeners start accepting connections
1245
-	close(activationLock)
1246
-
1247
-	return engine.StatusOK
1248
-}
1249 1
new file mode 100644
... ...
@@ -0,0 +1,44 @@
0
+package api
1
+
2
+import (
3
+	"fmt"
4
+	"github.com/dotcloud/docker/engine"
5
+	"github.com/dotcloud/docker/utils"
6
+	"mime"
7
+	"strings"
8
+)
9
+
10
+const (
11
+	APIVERSION        = 1.9
12
+	DEFAULTHTTPHOST   = "127.0.0.1"
13
+	DEFAULTUNIXSOCKET = "/var/run/docker.sock"
14
+)
15
+
16
+func ValidateHost(val string) (string, error) {
17
+	host, err := utils.ParseHost(DEFAULTHTTPHOST, DEFAULTUNIXSOCKET, val)
18
+	if err != nil {
19
+		return val, err
20
+	}
21
+	return host, nil
22
+}
23
+
24
+//TODO remove, used on < 1.5 in getContainersJSON
25
+func displayablePorts(ports *engine.Table) string {
26
+	result := []string{}
27
+	for _, port := range ports.Data {
28
+		if port.Get("IP") == "" {
29
+			result = append(result, fmt.Sprintf("%d/%s", port.GetInt("PublicPort"), port.Get("Type")))
30
+		} else {
31
+			result = append(result, fmt.Sprintf("%s:%d->%d/%s", port.Get("IP"), port.GetInt("PublicPort"), port.GetInt("PrivatePort"), port.Get("Type")))
32
+		}
33
+	}
34
+	return strings.Join(result, ", ")
35
+}
36
+
37
+func MatchesContentType(contentType, expectedType string) bool {
38
+	mimetype, _, err := mime.ParseMediaType(contentType)
39
+	if err != nil {
40
+		utils.Errorf("Error parsing media type: %s error: %s", contentType, err.Error())
41
+	}
42
+	return err == nil && mimetype == expectedType
43
+}
0 44
new file mode 100644
... ...
@@ -0,0 +1,1207 @@
0
+package api
1
+
2
+import (
3
+	"bufio"
4
+	"bytes"
5
+	"code.google.com/p/go.net/websocket"
6
+	"encoding/base64"
7
+	"encoding/json"
8
+	"expvar"
9
+	"fmt"
10
+	"github.com/dotcloud/docker/auth"
11
+	"github.com/dotcloud/docker/engine"
12
+	"github.com/dotcloud/docker/pkg/listenbuffer"
13
+	"github.com/dotcloud/docker/pkg/systemd"
14
+	"github.com/dotcloud/docker/utils"
15
+	"github.com/gorilla/mux"
16
+	"io"
17
+	"io/ioutil"
18
+	"log"
19
+	"net"
20
+	"net/http"
21
+	"net/http/pprof"
22
+	"os"
23
+	"regexp"
24
+	"strconv"
25
+	"strings"
26
+	"syscall"
27
+	"time"
28
+)
29
+
30
+var (
31
+	activationLock chan struct{}
32
+)
33
+
34
+type HttpApiFunc func(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error
35
+
36
+func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
37
+	conn, _, err := w.(http.Hijacker).Hijack()
38
+	if err != nil {
39
+		return nil, nil, err
40
+	}
41
+	// Flush the options to make sure the client sets the raw mode
42
+	conn.Write([]byte{})
43
+	return conn, conn, nil
44
+}
45
+
46
+//If we don't do this, POST method without Content-type (even with empty body) will fail
47
+func parseForm(r *http.Request) error {
48
+	if r == nil {
49
+		return nil
50
+	}
51
+	if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
52
+		return err
53
+	}
54
+	return nil
55
+}
56
+
57
+func parseMultipartForm(r *http.Request) error {
58
+	if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
59
+		return err
60
+	}
61
+	return nil
62
+}
63
+
64
+func httpError(w http.ResponseWriter, err error) {
65
+	statusCode := http.StatusInternalServerError
66
+	// FIXME: this is brittle and should not be necessary.
67
+	// If we need to differentiate between different possible error types, we should
68
+	// create appropriate error types with clearly defined meaning.
69
+	if strings.Contains(err.Error(), "No such") {
70
+		statusCode = http.StatusNotFound
71
+	} else if strings.Contains(err.Error(), "Bad parameter") {
72
+		statusCode = http.StatusBadRequest
73
+	} else if strings.Contains(err.Error(), "Conflict") {
74
+		statusCode = http.StatusConflict
75
+	} else if strings.Contains(err.Error(), "Impossible") {
76
+		statusCode = http.StatusNotAcceptable
77
+	} else if strings.Contains(err.Error(), "Wrong login/password") {
78
+		statusCode = http.StatusUnauthorized
79
+	} else if strings.Contains(err.Error(), "hasn't been activated") {
80
+		statusCode = http.StatusForbidden
81
+	}
82
+
83
+	if err != nil {
84
+		utils.Errorf("HTTP Error: statusCode=%d %s", statusCode, err.Error())
85
+		http.Error(w, err.Error(), statusCode)
86
+	}
87
+}
88
+
89
+func writeJSON(w http.ResponseWriter, code int, v engine.Env) error {
90
+	w.Header().Set("Content-Type", "application/json")
91
+	w.WriteHeader(code)
92
+	return v.Encode(w)
93
+}
94
+
95
+func streamJSON(job *engine.Job, w http.ResponseWriter, flush bool) {
96
+	w.Header().Set("Content-Type", "application/json")
97
+	if flush {
98
+		job.Stdout.Add(utils.NewWriteFlusher(w))
99
+	} else {
100
+		job.Stdout.Add(w)
101
+	}
102
+}
103
+
104
+func getBoolParam(value string) (bool, error) {
105
+	if value == "" {
106
+		return false, nil
107
+	}
108
+	ret, err := strconv.ParseBool(value)
109
+	if err != nil {
110
+		return false, fmt.Errorf("Bad parameter")
111
+	}
112
+	return ret, nil
113
+}
114
+
115
+func postAuth(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
116
+	var (
117
+		authConfig, err = ioutil.ReadAll(r.Body)
118
+		job             = eng.Job("auth")
119
+		status          string
120
+	)
121
+	if err != nil {
122
+		return err
123
+	}
124
+	job.Setenv("authConfig", string(authConfig))
125
+	job.Stdout.AddString(&status)
126
+	if err = job.Run(); err != nil {
127
+		return err
128
+	}
129
+	if status != "" {
130
+		var env engine.Env
131
+		env.Set("Status", status)
132
+		return writeJSON(w, http.StatusOK, env)
133
+	}
134
+	w.WriteHeader(http.StatusNoContent)
135
+	return nil
136
+}
137
+
138
+func getVersion(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
139
+	w.Header().Set("Content-Type", "application/json")
140
+	eng.ServeHTTP(w, r)
141
+	return nil
142
+}
143
+
144
+func postContainersKill(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
145
+	if vars == nil {
146
+		return fmt.Errorf("Missing parameter")
147
+	}
148
+	if err := parseForm(r); err != nil {
149
+		return err
150
+	}
151
+	job := eng.Job("kill", vars["name"])
152
+	if sig := r.Form.Get("signal"); sig != "" {
153
+		job.Args = append(job.Args, sig)
154
+	}
155
+	if err := job.Run(); err != nil {
156
+		return err
157
+	}
158
+	w.WriteHeader(http.StatusNoContent)
159
+	return nil
160
+}
161
+
162
+func getContainersExport(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
163
+	if vars == nil {
164
+		return fmt.Errorf("Missing parameter")
165
+	}
166
+	job := eng.Job("export", vars["name"])
167
+	job.Stdout.Add(w)
168
+	if err := job.Run(); err != nil {
169
+		return err
170
+	}
171
+	return nil
172
+}
173
+
174
+func getImagesJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
175
+	if err := parseForm(r); err != nil {
176
+		return err
177
+	}
178
+
179
+	var (
180
+		err  error
181
+		outs *engine.Table
182
+		job  = eng.Job("images")
183
+	)
184
+
185
+	job.Setenv("filter", r.Form.Get("filter"))
186
+	job.Setenv("all", r.Form.Get("all"))
187
+
188
+	if version >= 1.7 {
189
+		streamJSON(job, w, false)
190
+	} else if outs, err = job.Stdout.AddListTable(); err != nil {
191
+		return err
192
+	}
193
+
194
+	if err := job.Run(); err != nil {
195
+		return err
196
+	}
197
+
198
+	if version < 1.7 && outs != nil { // Convert to legacy format
199
+		outsLegacy := engine.NewTable("Created", 0)
200
+		for _, out := range outs.Data {
201
+			for _, repoTag := range out.GetList("RepoTags") {
202
+				parts := strings.Split(repoTag, ":")
203
+				outLegacy := &engine.Env{}
204
+				outLegacy.Set("Repository", parts[0])
205
+				outLegacy.Set("Tag", parts[1])
206
+				outLegacy.Set("Id", out.Get("Id"))
207
+				outLegacy.SetInt64("Created", out.GetInt64("Created"))
208
+				outLegacy.SetInt64("Size", out.GetInt64("Size"))
209
+				outLegacy.SetInt64("VirtualSize", out.GetInt64("VirtualSize"))
210
+				outsLegacy.Add(outLegacy)
211
+			}
212
+		}
213
+		w.Header().Set("Content-Type", "application/json")
214
+		if _, err := outsLegacy.WriteListTo(w); err != nil {
215
+			return err
216
+		}
217
+	}
218
+	return nil
219
+}
220
+
221
+func getImagesViz(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
222
+	if version > 1.6 {
223
+		w.WriteHeader(http.StatusNotFound)
224
+		return fmt.Errorf("This is now implemented in the client.")
225
+	}
226
+	eng.ServeHTTP(w, r)
227
+	return nil
228
+}
229
+
230
+func getInfo(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
231
+	w.Header().Set("Content-Type", "application/json")
232
+	eng.ServeHTTP(w, r)
233
+	return nil
234
+}
235
+
236
+func getEvents(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
237
+	if err := parseForm(r); err != nil {
238
+		return err
239
+	}
240
+
241
+	var job = eng.Job("events", r.RemoteAddr)
242
+	streamJSON(job, w, true)
243
+	job.Setenv("since", r.Form.Get("since"))
244
+	return job.Run()
245
+}
246
+
247
+func getImagesHistory(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
248
+	if vars == nil {
249
+		return fmt.Errorf("Missing parameter")
250
+	}
251
+
252
+	var job = eng.Job("history", vars["name"])
253
+	streamJSON(job, w, false)
254
+
255
+	if err := job.Run(); err != nil {
256
+		return err
257
+	}
258
+	return nil
259
+}
260
+
261
+func getContainersChanges(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
262
+	if vars == nil {
263
+		return fmt.Errorf("Missing parameter")
264
+	}
265
+	var job = eng.Job("changes", vars["name"])
266
+	streamJSON(job, w, false)
267
+
268
+	return job.Run()
269
+}
270
+
271
+func getContainersTop(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
272
+	if version < 1.4 {
273
+		return fmt.Errorf("top was improved a lot since 1.3, Please upgrade your docker client.")
274
+	}
275
+	if vars == nil {
276
+		return fmt.Errorf("Missing parameter")
277
+	}
278
+	if err := parseForm(r); err != nil {
279
+		return err
280
+	}
281
+
282
+	job := eng.Job("top", vars["name"], r.Form.Get("ps_args"))
283
+	streamJSON(job, w, false)
284
+	return job.Run()
285
+}
286
+
287
+func getContainersJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
288
+	if err := parseForm(r); err != nil {
289
+		return err
290
+	}
291
+	var (
292
+		err  error
293
+		outs *engine.Table
294
+		job  = eng.Job("containers")
295
+	)
296
+
297
+	job.Setenv("all", r.Form.Get("all"))
298
+	job.Setenv("size", r.Form.Get("size"))
299
+	job.Setenv("since", r.Form.Get("since"))
300
+	job.Setenv("before", r.Form.Get("before"))
301
+	job.Setenv("limit", r.Form.Get("limit"))
302
+
303
+	if version >= 1.5 {
304
+		streamJSON(job, w, false)
305
+	} else if outs, err = job.Stdout.AddTable(); err != nil {
306
+		return err
307
+	}
308
+	if err = job.Run(); err != nil {
309
+		return err
310
+	}
311
+	if version < 1.5 { // Convert to legacy format
312
+		for _, out := range outs.Data {
313
+			ports := engine.NewTable("", 0)
314
+			ports.ReadListFrom([]byte(out.Get("Ports")))
315
+			out.Set("Ports", displayablePorts(ports))
316
+		}
317
+		w.Header().Set("Content-Type", "application/json")
318
+		if _, err = outs.WriteListTo(w); err != nil {
319
+			return err
320
+		}
321
+	}
322
+	return nil
323
+}
324
+
325
+func postImagesTag(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
326
+	if err := parseForm(r); err != nil {
327
+		return err
328
+	}
329
+	if vars == nil {
330
+		return fmt.Errorf("Missing parameter")
331
+	}
332
+
333
+	job := eng.Job("tag", vars["name"], r.Form.Get("repo"), r.Form.Get("tag"))
334
+	job.Setenv("force", r.Form.Get("force"))
335
+	if err := job.Run(); err != nil {
336
+		return err
337
+	}
338
+	w.WriteHeader(http.StatusCreated)
339
+	return nil
340
+}
341
+
342
+func postCommit(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
343
+	if err := parseForm(r); err != nil {
344
+		return err
345
+	}
346
+	var (
347
+		config engine.Env
348
+		env    engine.Env
349
+		job    = eng.Job("commit", r.Form.Get("container"))
350
+	)
351
+	if err := config.Decode(r.Body); err != nil {
352
+		utils.Errorf("%s", err)
353
+	}
354
+
355
+	job.Setenv("repo", r.Form.Get("repo"))
356
+	job.Setenv("tag", r.Form.Get("tag"))
357
+	job.Setenv("author", r.Form.Get("author"))
358
+	job.Setenv("comment", r.Form.Get("comment"))
359
+	job.SetenvSubEnv("config", &config)
360
+
361
+	var id string
362
+	job.Stdout.AddString(&id)
363
+	if err := job.Run(); err != nil {
364
+		return err
365
+	}
366
+	env.Set("Id", id)
367
+	return writeJSON(w, http.StatusCreated, env)
368
+}
369
+
370
+// Creates an image from Pull or from Import
371
+func postImagesCreate(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
372
+	if err := parseForm(r); err != nil {
373
+		return err
374
+	}
375
+
376
+	var (
377
+		image = r.Form.Get("fromImage")
378
+		tag   = r.Form.Get("tag")
379
+		job   *engine.Job
380
+	)
381
+	authEncoded := r.Header.Get("X-Registry-Auth")
382
+	authConfig := &auth.AuthConfig{}
383
+	if authEncoded != "" {
384
+		authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
385
+		if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
386
+			// for a pull it is not an error if no auth was given
387
+			// to increase compatibility with the existing api it is defaulting to be empty
388
+			authConfig = &auth.AuthConfig{}
389
+		}
390
+	}
391
+	if version > 1.0 {
392
+		w.Header().Set("Content-Type", "application/json")
393
+	}
394
+	if image != "" { //pull
395
+		metaHeaders := map[string][]string{}
396
+		for k, v := range r.Header {
397
+			if strings.HasPrefix(k, "X-Meta-") {
398
+				metaHeaders[k] = v
399
+			}
400
+		}
401
+		job = eng.Job("pull", r.Form.Get("fromImage"), tag)
402
+		job.SetenvBool("parallel", version > 1.3)
403
+		job.SetenvJson("metaHeaders", metaHeaders)
404
+		job.SetenvJson("authConfig", authConfig)
405
+	} else { //import
406
+		job = eng.Job("import", r.Form.Get("fromSrc"), r.Form.Get("repo"), tag)
407
+		job.Stdin.Add(r.Body)
408
+	}
409
+
410
+	if version > 1.0 {
411
+		job.SetenvBool("json", true)
412
+		streamJSON(job, w, true)
413
+	} else {
414
+		job.Stdout.Add(utils.NewWriteFlusher(w))
415
+	}
416
+	if err := job.Run(); err != nil {
417
+		if !job.Stdout.Used() {
418
+			return err
419
+		}
420
+		sf := utils.NewStreamFormatter(version > 1.0)
421
+		w.Write(sf.FormatError(err))
422
+	}
423
+
424
+	return nil
425
+}
426
+
427
+func getImagesSearch(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
428
+	if err := parseForm(r); err != nil {
429
+		return err
430
+	}
431
+	var (
432
+		authEncoded = r.Header.Get("X-Registry-Auth")
433
+		authConfig  = &auth.AuthConfig{}
434
+		metaHeaders = map[string][]string{}
435
+	)
436
+
437
+	if authEncoded != "" {
438
+		authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
439
+		if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
440
+			// for a search it is not an error if no auth was given
441
+			// to increase compatibility with the existing api it is defaulting to be empty
442
+			authConfig = &auth.AuthConfig{}
443
+		}
444
+	}
445
+	for k, v := range r.Header {
446
+		if strings.HasPrefix(k, "X-Meta-") {
447
+			metaHeaders[k] = v
448
+		}
449
+	}
450
+
451
+	var job = eng.Job("search", r.Form.Get("term"))
452
+	job.SetenvJson("metaHeaders", metaHeaders)
453
+	job.SetenvJson("authConfig", authConfig)
454
+	streamJSON(job, w, false)
455
+
456
+	return job.Run()
457
+}
458
+
459
+func postImagesInsert(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
460
+	if err := parseForm(r); err != nil {
461
+		return err
462
+	}
463
+	if vars == nil {
464
+		return fmt.Errorf("Missing parameter")
465
+	}
466
+	if version > 1.0 {
467
+		w.Header().Set("Content-Type", "application/json")
468
+	}
469
+
470
+	job := eng.Job("insert", vars["name"], r.Form.Get("url"), r.Form.Get("path"))
471
+	if version > 1.0 {
472
+		job.SetenvBool("json", true)
473
+		streamJSON(job, w, false)
474
+	} else {
475
+		job.Stdout.Add(w)
476
+	}
477
+	if err := job.Run(); err != nil {
478
+		if !job.Stdout.Used() {
479
+			return err
480
+		}
481
+		sf := utils.NewStreamFormatter(version > 1.0)
482
+		w.Write(sf.FormatError(err))
483
+	}
484
+
485
+	return nil
486
+}
487
+
488
+func postImagesPush(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
489
+	if vars == nil {
490
+		return fmt.Errorf("Missing parameter")
491
+	}
492
+
493
+	metaHeaders := map[string][]string{}
494
+	for k, v := range r.Header {
495
+		if strings.HasPrefix(k, "X-Meta-") {
496
+			metaHeaders[k] = v
497
+		}
498
+	}
499
+	if err := parseForm(r); err != nil {
500
+		return err
501
+	}
502
+	authConfig := &auth.AuthConfig{}
503
+
504
+	authEncoded := r.Header.Get("X-Registry-Auth")
505
+	if authEncoded != "" {
506
+		// the new format is to handle the authConfig as a header
507
+		authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
508
+		if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
509
+			// to increase compatibility to existing api it is defaulting to be empty
510
+			authConfig = &auth.AuthConfig{}
511
+		}
512
+	} else {
513
+		// the old format is supported for compatibility if there was no authConfig header
514
+		if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
515
+			return err
516
+		}
517
+	}
518
+
519
+	if version > 1.0 {
520
+		w.Header().Set("Content-Type", "application/json")
521
+	}
522
+	job := eng.Job("push", vars["name"])
523
+	job.SetenvJson("metaHeaders", metaHeaders)
524
+	job.SetenvJson("authConfig", authConfig)
525
+	if version > 1.0 {
526
+		job.SetenvBool("json", true)
527
+		streamJSON(job, w, true)
528
+	} else {
529
+		job.Stdout.Add(utils.NewWriteFlusher(w))
530
+	}
531
+
532
+	if err := job.Run(); err != nil {
533
+		if !job.Stdout.Used() {
534
+			return err
535
+		}
536
+		sf := utils.NewStreamFormatter(version > 1.0)
537
+		w.Write(sf.FormatError(err))
538
+	}
539
+	return nil
540
+}
541
+
542
+func getImagesGet(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
543
+	if vars == nil {
544
+		return fmt.Errorf("Missing parameter")
545
+	}
546
+	if version > 1.0 {
547
+		w.Header().Set("Content-Type", "application/x-tar")
548
+	}
549
+	job := eng.Job("image_export", vars["name"])
550
+	job.Stdout.Add(w)
551
+	return job.Run()
552
+}
553
+
554
+func postImagesLoad(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
555
+	job := eng.Job("load")
556
+	job.Stdin.Add(r.Body)
557
+	return job.Run()
558
+}
559
+
560
+func postContainersCreate(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
561
+	if err := parseForm(r); err != nil {
562
+		return nil
563
+	}
564
+	var (
565
+		out         engine.Env
566
+		job         = eng.Job("create", r.Form.Get("name"))
567
+		outWarnings []string
568
+		outId       string
569
+		warnings    = bytes.NewBuffer(nil)
570
+	)
571
+	if err := job.DecodeEnv(r.Body); err != nil {
572
+		return err
573
+	}
574
+	// Read container ID from the first line of stdout
575
+	job.Stdout.AddString(&outId)
576
+	// Read warnings from stderr
577
+	job.Stderr.Add(warnings)
578
+	if err := job.Run(); err != nil {
579
+		return err
580
+	}
581
+	// Parse warnings from stderr
582
+	scanner := bufio.NewScanner(warnings)
583
+	for scanner.Scan() {
584
+		outWarnings = append(outWarnings, scanner.Text())
585
+	}
586
+	out.Set("Id", outId)
587
+	out.SetList("Warnings", outWarnings)
588
+	return writeJSON(w, http.StatusCreated, out)
589
+}
590
+
591
+func postContainersRestart(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
592
+	if err := parseForm(r); err != nil {
593
+		return err
594
+	}
595
+	if vars == nil {
596
+		return fmt.Errorf("Missing parameter")
597
+	}
598
+	job := eng.Job("restart", vars["name"])
599
+	job.Setenv("t", r.Form.Get("t"))
600
+	if err := job.Run(); err != nil {
601
+		return err
602
+	}
603
+	w.WriteHeader(http.StatusNoContent)
604
+	return nil
605
+}
606
+
607
+func deleteContainers(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
608
+	if err := parseForm(r); err != nil {
609
+		return err
610
+	}
611
+	if vars == nil {
612
+		return fmt.Errorf("Missing parameter")
613
+	}
614
+	job := eng.Job("container_delete", vars["name"])
615
+	job.Setenv("removeVolume", r.Form.Get("v"))
616
+	job.Setenv("removeLink", r.Form.Get("link"))
617
+	if err := job.Run(); err != nil {
618
+		return err
619
+	}
620
+	w.WriteHeader(http.StatusNoContent)
621
+	return nil
622
+}
623
+
624
+func deleteImages(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
625
+	if err := parseForm(r); err != nil {
626
+		return err
627
+	}
628
+	if vars == nil {
629
+		return fmt.Errorf("Missing parameter")
630
+	}
631
+	var job = eng.Job("image_delete", vars["name"])
632
+	streamJSON(job, w, false)
633
+	job.SetenvBool("autoPrune", version > 1.1)
634
+
635
+	return job.Run()
636
+}
637
+
638
+func postContainersStart(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
639
+	if vars == nil {
640
+		return fmt.Errorf("Missing parameter")
641
+	}
642
+	name := vars["name"]
643
+	job := eng.Job("start", name)
644
+	// allow a nil body for backwards compatibility
645
+	if r.Body != nil {
646
+		if MatchesContentType(r.Header.Get("Content-Type"), "application/json") {
647
+			if err := job.DecodeEnv(r.Body); err != nil {
648
+				return err
649
+			}
650
+		}
651
+	}
652
+	if err := job.Run(); err != nil {
653
+		return err
654
+	}
655
+	w.WriteHeader(http.StatusNoContent)
656
+	return nil
657
+}
658
+
659
+func postContainersStop(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
660
+	if err := parseForm(r); err != nil {
661
+		return err
662
+	}
663
+	if vars == nil {
664
+		return fmt.Errorf("Missing parameter")
665
+	}
666
+	job := eng.Job("stop", vars["name"])
667
+	job.Setenv("t", r.Form.Get("t"))
668
+	if err := job.Run(); err != nil {
669
+		return err
670
+	}
671
+	w.WriteHeader(http.StatusNoContent)
672
+	return nil
673
+}
674
+
675
+func postContainersWait(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
676
+	if vars == nil {
677
+		return fmt.Errorf("Missing parameter")
678
+	}
679
+	var (
680
+		env    engine.Env
681
+		status string
682
+		job    = eng.Job("wait", vars["name"])
683
+	)
684
+	job.Stdout.AddString(&status)
685
+	if err := job.Run(); err != nil {
686
+		return err
687
+	}
688
+	// Parse a 16-bit encoded integer to map typical unix exit status.
689
+	_, err := strconv.ParseInt(status, 10, 16)
690
+	if err != nil {
691
+		return err
692
+	}
693
+	env.Set("StatusCode", status)
694
+	return writeJSON(w, http.StatusOK, env)
695
+}
696
+
697
+func postContainersResize(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
698
+	if err := parseForm(r); err != nil {
699
+		return err
700
+	}
701
+	if vars == nil {
702
+		return fmt.Errorf("Missing parameter")
703
+	}
704
+	if err := eng.Job("resize", vars["name"], r.Form.Get("h"), r.Form.Get("w")).Run(); err != nil {
705
+		return err
706
+	}
707
+	return nil
708
+}
709
+
710
+func postContainersAttach(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
711
+	if err := parseForm(r); err != nil {
712
+		return err
713
+	}
714
+	if vars == nil {
715
+		return fmt.Errorf("Missing parameter")
716
+	}
717
+
718
+	var (
719
+		job    = eng.Job("inspect", vars["name"], "container")
720
+		c, err = job.Stdout.AddEnv()
721
+	)
722
+	if err != nil {
723
+		return err
724
+	}
725
+	if err = job.Run(); err != nil {
726
+		return err
727
+	}
728
+
729
+	inStream, outStream, err := hijackServer(w)
730
+	if err != nil {
731
+		return err
732
+	}
733
+	defer func() {
734
+		if tcpc, ok := inStream.(*net.TCPConn); ok {
735
+			tcpc.CloseWrite()
736
+		} else {
737
+			inStream.Close()
738
+		}
739
+	}()
740
+	defer func() {
741
+		if tcpc, ok := outStream.(*net.TCPConn); ok {
742
+			tcpc.CloseWrite()
743
+		} else if closer, ok := outStream.(io.Closer); ok {
744
+			closer.Close()
745
+		}
746
+	}()
747
+
748
+	var errStream io.Writer
749
+
750
+	fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
751
+
752
+	if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && version >= 1.6 {
753
+		errStream = utils.NewStdWriter(outStream, utils.Stderr)
754
+		outStream = utils.NewStdWriter(outStream, utils.Stdout)
755
+	} else {
756
+		errStream = outStream
757
+	}
758
+
759
+	job = eng.Job("attach", vars["name"])
760
+	job.Setenv("logs", r.Form.Get("logs"))
761
+	job.Setenv("stream", r.Form.Get("stream"))
762
+	job.Setenv("stdin", r.Form.Get("stdin"))
763
+	job.Setenv("stdout", r.Form.Get("stdout"))
764
+	job.Setenv("stderr", r.Form.Get("stderr"))
765
+	job.Stdin.Add(inStream)
766
+	job.Stdout.Add(outStream)
767
+	job.Stderr.Set(errStream)
768
+	if err := job.Run(); err != nil {
769
+		fmt.Fprintf(outStream, "Error: %s\n", err)
770
+
771
+	}
772
+	return nil
773
+}
774
+
775
+func wsContainersAttach(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
776
+	if err := parseForm(r); err != nil {
777
+		return err
778
+	}
779
+	if vars == nil {
780
+		return fmt.Errorf("Missing parameter")
781
+	}
782
+
783
+	if err := eng.Job("inspect", vars["name"], "container").Run(); err != nil {
784
+		return err
785
+	}
786
+
787
+	h := websocket.Handler(func(ws *websocket.Conn) {
788
+		defer ws.Close()
789
+		job := eng.Job("attach", vars["name"])
790
+		job.Setenv("logs", r.Form.Get("logs"))
791
+		job.Setenv("stream", r.Form.Get("stream"))
792
+		job.Setenv("stdin", r.Form.Get("stdin"))
793
+		job.Setenv("stdout", r.Form.Get("stdout"))
794
+		job.Setenv("stderr", r.Form.Get("stderr"))
795
+		job.Stdin.Add(ws)
796
+		job.Stdout.Add(ws)
797
+		job.Stderr.Set(ws)
798
+		if err := job.Run(); err != nil {
799
+			utils.Errorf("Error: %s", err)
800
+		}
801
+	})
802
+	h.ServeHTTP(w, r)
803
+
804
+	return nil
805
+}
806
+
807
+func getContainersByName(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
808
+	if vars == nil {
809
+		return fmt.Errorf("Missing parameter")
810
+	}
811
+	var job = eng.Job("inspect", vars["name"], "container")
812
+	streamJSON(job, w, false)
813
+	job.SetenvBool("conflict", true) //conflict=true to detect conflict between containers and images in the job
814
+	return job.Run()
815
+}
816
+
817
+func getImagesByName(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
818
+	if vars == nil {
819
+		return fmt.Errorf("Missing parameter")
820
+	}
821
+	var job = eng.Job("inspect", vars["name"], "image")
822
+	streamJSON(job, w, false)
823
+	job.SetenvBool("conflict", true) //conflict=true to detect conflict between containers and images in the job
824
+	return job.Run()
825
+}
826
+
827
+func postBuild(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
828
+	if version < 1.3 {
829
+		return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.")
830
+	}
831
+	var (
832
+		authEncoded       = r.Header.Get("X-Registry-Auth")
833
+		authConfig        = &auth.AuthConfig{}
834
+		configFileEncoded = r.Header.Get("X-Registry-Config")
835
+		configFile        = &auth.ConfigFile{}
836
+		job               = eng.Job("build")
837
+	)
838
+
839
+	// This block can be removed when API versions prior to 1.9 are deprecated.
840
+	// Both headers will be parsed and sent along to the daemon, but if a non-empty
841
+	// ConfigFile is present, any value provided as an AuthConfig directly will
842
+	// be overridden. See BuildFile::CmdFrom for details.
843
+	if version < 1.9 && authEncoded != "" {
844
+		authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
845
+		if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
846
+			// for a pull it is not an error if no auth was given
847
+			// to increase compatibility with the existing api it is defaulting to be empty
848
+			authConfig = &auth.AuthConfig{}
849
+		}
850
+	}
851
+
852
+	if configFileEncoded != "" {
853
+		configFileJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(configFileEncoded))
854
+		if err := json.NewDecoder(configFileJson).Decode(configFile); err != nil {
855
+			// for a pull it is not an error if no auth was given
856
+			// to increase compatibility with the existing api it is defaulting to be empty
857
+			configFile = &auth.ConfigFile{}
858
+		}
859
+	}
860
+
861
+	if version >= 1.8 {
862
+		job.SetenvBool("json", true)
863
+		streamJSON(job, w, true)
864
+	} else {
865
+		job.Stdout.Add(utils.NewWriteFlusher(w))
866
+	}
867
+	job.Stdin.Add(r.Body)
868
+	job.Setenv("remote", r.FormValue("remote"))
869
+	job.Setenv("t", r.FormValue("t"))
870
+	job.Setenv("q", r.FormValue("q"))
871
+	job.Setenv("nocache", r.FormValue("nocache"))
872
+	job.Setenv("rm", r.FormValue("rm"))
873
+	job.SetenvJson("authConfig", authConfig)
874
+	job.SetenvJson("configFile", configFile)
875
+
876
+	if err := job.Run(); err != nil {
877
+		if !job.Stdout.Used() {
878
+			return err
879
+		}
880
+		sf := utils.NewStreamFormatter(version >= 1.8)
881
+		w.Write(sf.FormatError(err))
882
+	}
883
+	return nil
884
+}
885
+
886
+func postContainersCopy(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
887
+	if vars == nil {
888
+		return fmt.Errorf("Missing parameter")
889
+	}
890
+
891
+	var copyData engine.Env
892
+
893
+	if contentType := r.Header.Get("Content-Type"); contentType == "application/json" {
894
+		if err := copyData.Decode(r.Body); err != nil {
895
+			return err
896
+		}
897
+	} else {
898
+		return fmt.Errorf("Content-Type not supported: %s", contentType)
899
+	}
900
+
901
+	if copyData.Get("Resource") == "" {
902
+		return fmt.Errorf("Path cannot be empty")
903
+	}
904
+	if copyData.Get("Resource")[0] == '/' {
905
+		copyData.Set("Resource", copyData.Get("Resource")[1:])
906
+	}
907
+
908
+	job := eng.Job("container_copy", vars["name"], copyData.Get("Resource"))
909
+	streamJSON(job, w, false)
910
+	if err := job.Run(); err != nil {
911
+		utils.Errorf("%s", err.Error())
912
+		if strings.Contains(err.Error(), "No such container") {
913
+			w.WriteHeader(http.StatusNotFound)
914
+		}
915
+	}
916
+	return nil
917
+}
918
+
919
+func optionsHandler(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
920
+	w.WriteHeader(http.StatusOK)
921
+	return nil
922
+}
923
+func writeCorsHeaders(w http.ResponseWriter, r *http.Request) {
924
+	w.Header().Add("Access-Control-Allow-Origin", "*")
925
+	w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
926
+	w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS")
927
+}
928
+
929
+func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc, enableCors bool, dockerVersion string) http.HandlerFunc {
930
+	return func(w http.ResponseWriter, r *http.Request) {
931
+		// log the request
932
+		utils.Debugf("Calling %s %s", localMethod, localRoute)
933
+
934
+		if logging {
935
+			log.Println(r.Method, r.RequestURI)
936
+		}
937
+
938
+		if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
939
+			userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
940
+			if len(userAgent) == 2 && userAgent[1] != dockerVersion {
941
+				utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion)
942
+			}
943
+		}
944
+		version, err := strconv.ParseFloat(mux.Vars(r)["version"], 64)
945
+		if err != nil {
946
+			version = APIVERSION
947
+		}
948
+		if enableCors {
949
+			writeCorsHeaders(w, r)
950
+		}
951
+
952
+		if version == 0 || version > APIVERSION {
953
+			http.Error(w, fmt.Errorf("client and server don't have same version (client : %g, server: %g)", version, APIVERSION).Error(), http.StatusNotFound)
954
+			return
955
+		}
956
+
957
+		if err := handlerFunc(eng, version, w, r, mux.Vars(r)); err != nil {
958
+			utils.Errorf("Error: %s", err)
959
+			httpError(w, err)
960
+		}
961
+	}
962
+}
963
+
964
+// Replicated from expvar.go as not public.
965
+func expvarHandler(w http.ResponseWriter, r *http.Request) {
966
+	w.Header().Set("Content-Type", "application/json; charset=utf-8")
967
+	fmt.Fprintf(w, "{\n")
968
+	first := true
969
+	expvar.Do(func(kv expvar.KeyValue) {
970
+		if !first {
971
+			fmt.Fprintf(w, ",\n")
972
+		}
973
+		first = false
974
+		fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
975
+	})
976
+	fmt.Fprintf(w, "\n}\n")
977
+}
978
+
979
+func AttachProfiler(router *mux.Router) {
980
+	router.HandleFunc("/debug/vars", expvarHandler)
981
+	router.HandleFunc("/debug/pprof/", pprof.Index)
982
+	router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
983
+	router.HandleFunc("/debug/pprof/profile", pprof.Profile)
984
+	router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
985
+	router.HandleFunc("/debug/pprof/heap", pprof.Handler("heap").ServeHTTP)
986
+	router.HandleFunc("/debug/pprof/goroutine", pprof.Handler("goroutine").ServeHTTP)
987
+	router.HandleFunc("/debug/pprof/threadcreate", pprof.Handler("threadcreate").ServeHTTP)
988
+}
989
+
990
+func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion string) (*mux.Router, error) {
991
+	r := mux.NewRouter()
992
+	if os.Getenv("DEBUG") != "" {
993
+		AttachProfiler(r)
994
+	}
995
+	m := map[string]map[string]HttpApiFunc{
996
+		"GET": {
997
+			"/events":                         getEvents,
998
+			"/info":                           getInfo,
999
+			"/version":                        getVersion,
1000
+			"/images/json":                    getImagesJSON,
1001
+			"/images/viz":                     getImagesViz,
1002
+			"/images/search":                  getImagesSearch,
1003
+			"/images/{name:.*}/get":           getImagesGet,
1004
+			"/images/{name:.*}/history":       getImagesHistory,
1005
+			"/images/{name:.*}/json":          getImagesByName,
1006
+			"/containers/ps":                  getContainersJSON,
1007
+			"/containers/json":                getContainersJSON,
1008
+			"/containers/{name:.*}/export":    getContainersExport,
1009
+			"/containers/{name:.*}/changes":   getContainersChanges,
1010
+			"/containers/{name:.*}/json":      getContainersByName,
1011
+			"/containers/{name:.*}/top":       getContainersTop,
1012
+			"/containers/{name:.*}/attach/ws": wsContainersAttach,
1013
+		},
1014
+		"POST": {
1015
+			"/auth":                         postAuth,
1016
+			"/commit":                       postCommit,
1017
+			"/build":                        postBuild,
1018
+			"/images/create":                postImagesCreate,
1019
+			"/images/{name:.*}/insert":      postImagesInsert,
1020
+			"/images/load":                  postImagesLoad,
1021
+			"/images/{name:.*}/push":        postImagesPush,
1022
+			"/images/{name:.*}/tag":         postImagesTag,
1023
+			"/containers/create":            postContainersCreate,
1024
+			"/containers/{name:.*}/kill":    postContainersKill,
1025
+			"/containers/{name:.*}/restart": postContainersRestart,
1026
+			"/containers/{name:.*}/start":   postContainersStart,
1027
+			"/containers/{name:.*}/stop":    postContainersStop,
1028
+			"/containers/{name:.*}/wait":    postContainersWait,
1029
+			"/containers/{name:.*}/resize":  postContainersResize,
1030
+			"/containers/{name:.*}/attach":  postContainersAttach,
1031
+			"/containers/{name:.*}/copy":    postContainersCopy,
1032
+		},
1033
+		"DELETE": {
1034
+			"/containers/{name:.*}": deleteContainers,
1035
+			"/images/{name:.*}":     deleteImages,
1036
+		},
1037
+		"OPTIONS": {
1038
+			"": optionsHandler,
1039
+		},
1040
+	}
1041
+
1042
+	for method, routes := range m {
1043
+		for route, fct := range routes {
1044
+			utils.Debugf("Registering %s, %s", method, route)
1045
+			// NOTE: scope issue, make sure the variables are local and won't be changed
1046
+			localRoute := route
1047
+			localFct := fct
1048
+			localMethod := method
1049
+
1050
+			// build the handler function
1051
+			f := makeHttpHandler(eng, logging, localMethod, localRoute, localFct, enableCors, dockerVersion)
1052
+
1053
+			// add the new route
1054
+			if localRoute == "" {
1055
+				r.Methods(localMethod).HandlerFunc(f)
1056
+			} else {
1057
+				r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f)
1058
+				r.Path(localRoute).Methods(localMethod).HandlerFunc(f)
1059
+			}
1060
+		}
1061
+	}
1062
+
1063
+	return r, nil
1064
+}
1065
+
1066
+// ServeRequest processes a single http request to the docker remote api.
1067
+// FIXME: refactor this to be part of Server and not require re-creating a new
1068
+// router each time. This requires first moving ListenAndServe into Server.
1069
+func ServeRequest(eng *engine.Engine, apiversion float64, w http.ResponseWriter, req *http.Request) error {
1070
+	router, err := createRouter(eng, false, true, "")
1071
+	if err != nil {
1072
+		return err
1073
+	}
1074
+	// Insert APIVERSION into the request as a convenience
1075
+	req.URL.Path = fmt.Sprintf("/v%g%s", apiversion, req.URL.Path)
1076
+	router.ServeHTTP(w, req)
1077
+	return nil
1078
+}
1079
+
1080
+// ServeFD creates an http.Server and sets it up to serve given a socket activated
1081
+// argument.
1082
+func ServeFd(addr string, handle http.Handler) error {
1083
+	ls, e := systemd.ListenFD(addr)
1084
+	if e != nil {
1085
+		return e
1086
+	}
1087
+
1088
+	chErrors := make(chan error, len(ls))
1089
+
1090
+	// Since ListenFD will return one or more sockets we have
1091
+	// to create a go func to spawn off multiple serves
1092
+	for i := range ls {
1093
+		listener := ls[i]
1094
+		go func() {
1095
+			httpSrv := http.Server{Handler: handle}
1096
+			chErrors <- httpSrv.Serve(listener)
1097
+		}()
1098
+	}
1099
+
1100
+	for i := 0; i < len(ls); i += 1 {
1101
+		err := <-chErrors
1102
+		if err != nil {
1103
+			return err
1104
+		}
1105
+	}
1106
+
1107
+	return nil
1108
+}
1109
+
1110
+// ListenAndServe sets up the required http.Server and gets it listening for
1111
+// each addr passed in and does protocol specific checking.
1112
+func ListenAndServe(proto, addr string, eng *engine.Engine, logging, enableCors bool, dockerVersion string) error {
1113
+	r, err := createRouter(eng, logging, enableCors, dockerVersion)
1114
+	if err != nil {
1115
+		return err
1116
+	}
1117
+
1118
+	if proto == "fd" {
1119
+		return ServeFd(addr, r)
1120
+	}
1121
+
1122
+	if proto == "unix" {
1123
+		if err := syscall.Unlink(addr); err != nil && !os.IsNotExist(err) {
1124
+			return err
1125
+		}
1126
+	}
1127
+
1128
+	l, err := listenbuffer.NewListenBuffer(proto, addr, activationLock, 15*time.Minute)
1129
+	if err != nil {
1130
+		return err
1131
+	}
1132
+
1133
+	// Basic error and sanity checking
1134
+	switch proto {
1135
+	case "tcp":
1136
+		if !strings.HasPrefix(addr, "127.0.0.1") {
1137
+			log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
1138
+		}
1139
+	case "unix":
1140
+		if err := os.Chmod(addr, 0660); err != nil {
1141
+			return err
1142
+		}
1143
+
1144
+		groups, err := ioutil.ReadFile("/etc/group")
1145
+		if err != nil {
1146
+			return err
1147
+		}
1148
+		re := regexp.MustCompile("(^|\n)docker:.*?:([0-9]+)")
1149
+		if gidMatch := re.FindStringSubmatch(string(groups)); gidMatch != nil {
1150
+			gid, err := strconv.Atoi(gidMatch[2])
1151
+			if err != nil {
1152
+				return err
1153
+			}
1154
+			utils.Debugf("docker group found. gid: %d", gid)
1155
+			if err := os.Chown(addr, 0, gid); err != nil {
1156
+				return err
1157
+			}
1158
+		}
1159
+	default:
1160
+		return fmt.Errorf("Invalid protocol format.")
1161
+	}
1162
+
1163
+	httpSrv := http.Server{Addr: addr, Handler: r}
1164
+	return httpSrv.Serve(l)
1165
+}
1166
+
1167
+// ServeApi loops through all of the protocols sent in to docker and spawns
1168
+// off a go routine to setup a serving http.Server for each.
1169
+func ServeApi(job *engine.Job) engine.Status {
1170
+	var (
1171
+		protoAddrs = job.Args
1172
+		chErrors   = make(chan error, len(protoAddrs))
1173
+	)
1174
+	activationLock = make(chan struct{})
1175
+
1176
+	if err := job.Eng.Register("acceptconnections", AcceptConnections); err != nil {
1177
+		return job.Error(err)
1178
+	}
1179
+
1180
+	for _, protoAddr := range protoAddrs {
1181
+		protoAddrParts := strings.SplitN(protoAddr, "://", 2)
1182
+		go func() {
1183
+			log.Printf("Listening for HTTP on %s (%s)\n", protoAddrParts[0], protoAddrParts[1])
1184
+			chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("Version"))
1185
+		}()
1186
+	}
1187
+
1188
+	for i := 0; i < len(protoAddrs); i += 1 {
1189
+		err := <-chErrors
1190
+		if err != nil {
1191
+			return job.Error(err)
1192
+		}
1193
+	}
1194
+
1195
+	return engine.StatusOK
1196
+}
1197
+
1198
+func AcceptConnections(job *engine.Job) engine.Status {
1199
+	// Tell the init daemon we are accepting requests
1200
+	go systemd.SdNotify("READY=1")
1201
+
1202
+	// close the lock so the listeners start accepting connections
1203
+	close(activationLock)
1204
+
1205
+	return engine.StatusOK
1206
+}
... ...
@@ -110,7 +110,7 @@ func (b *buildFile) CmdFrom(name string) error {
110 110
 		b.config = image.Config
111 111
 	}
112 112
 	if b.config.Env == nil || len(b.config.Env) == 0 {
113
-		b.config.Env = append(b.config.Env, "HOME=/", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
113
+		b.config.Env = append(b.config.Env, "HOME=/", "PATH="+defaultPathEnv)
114 114
 	}
115 115
 	// Process ONBUILD triggers if they exist
116 116
 	if nTriggers := len(b.config.OnBuild); nTriggers != 0 {
117 117
new file mode 100644
... ...
@@ -0,0 +1,40 @@
0
+package builtins
1
+
2
+import (
3
+	"github.com/dotcloud/docker/engine"
4
+
5
+	"github.com/dotcloud/docker"
6
+	"github.com/dotcloud/docker/api"
7
+	"github.com/dotcloud/docker/networkdriver/lxc"
8
+)
9
+
10
+func Register(eng *engine.Engine) {
11
+	daemon(eng)
12
+	remote(eng)
13
+}
14
+
15
+// remote: a RESTful api for cross-docker communication
16
+func remote(eng *engine.Engine) {
17
+	eng.Register("serveapi", api.ServeApi)
18
+}
19
+
20
+// daemon: a default execution and storage backend for Docker on Linux,
21
+// with the following underlying components:
22
+//
23
+// * Pluggable storage drivers including aufs, vfs, lvm and btrfs.
24
+// * Pluggable execution drivers including lxc and chroot.
25
+//
26
+// In practice `daemon` still includes most core Docker components, including:
27
+//
28
+// * The reference registry client implementation
29
+// * Image management
30
+// * The build facility
31
+// * Logging
32
+//
33
+// These components should be broken off into plugins of their own.
34
+//
35
+func daemon(eng *engine.Engine) {
36
+	eng.Register("initserver", docker.InitServer)
37
+	eng.Register("init_networkdriver", lxc.InitDriver)
38
+	eng.Register("version", docker.GetVersion)
39
+}
... ...
@@ -25,6 +25,7 @@ type DaemonConfig struct {
25 25
 	BridgeIP                    string
26 26
 	InterContainerCommunication bool
27 27
 	GraphDriver                 string
28
+	ExecDriver                  string
28 29
 	Mtu                         int
29 30
 	DisableNetwork              bool
30 31
 }
... ...
@@ -43,6 +44,7 @@ func DaemonConfigFromJob(job *engine.Job) *DaemonConfig {
43 43
 		DefaultIp:                   net.ParseIP(job.Getenv("DefaultIp")),
44 44
 		InterContainerCommunication: job.GetenvBool("InterContainerCommunication"),
45 45
 		GraphDriver:                 job.Getenv("GraphDriver"),
46
+		ExecDriver:                  job.Getenv("ExecDriver"),
46 47
 	}
47 48
 	if dns := job.GetenvList("Dns"); dns != nil {
48 49
 		config.Dns = dns
... ...
@@ -23,6 +23,8 @@ import (
23 23
 	"time"
24 24
 )
25 25
 
26
+const defaultPathEnv = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
27
+
26 28
 var (
27 29
 	ErrNotATTY               = errors.New("The PTY is not a file")
28 30
 	ErrNoTTY                 = errors.New("No PTY found")
... ...
@@ -447,7 +449,7 @@ func (container *Container) Start() (err error) {
447 447
 	// Setup environment
448 448
 	env := []string{
449 449
 		"HOME=/",
450
-		"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
450
+		"PATH=" + defaultPathEnv,
451 451
 		"HOSTNAME=" + container.Config.Hostname,
452 452
 	}
453 453
 
... ...
@@ -44,6 +44,8 @@ debianStable=wheezy
44 44
 debianUnstable=sid
45 45
 # this should match the name found at http://releases.ubuntu.com/
46 46
 ubuntuLatestLTS=precise
47
+# this should match the name found at http://releases.tanglu.org/
48
+tangluLatest=aequorea
47 49
 
48 50
 while getopts v:i:a:p:dst name; do
49 51
 	case "$name" in
... ...
@@ -201,6 +203,17 @@ if [ -z "$strictDebootstrap" ]; then
201 201
 					s/ $suite-updates main/ ${suite}-security main/
202 202
 				" etc/apt/sources.list
203 203
 				;;
204
+			Tanglu)
205
+				# add the updates repository
206
+				if [ "$suite" = "$tangluLatest" ]; then
207
+					# ${suite}-updates only applies to stable Tanglu versions
208
+					sudo sed -i "p; s/ $suite main$/ ${suite}-updates main/" etc/apt/sources.list
209
+				fi
210
+				;;
211
+			SteamOS)
212
+				# add contrib and non-free
213
+				sudo sed -i "s/ $suite main$/ $suite main contrib non-free/" etc/apt/sources.list
214
+				;;
204 215
 		esac
205 216
 	fi
206 217
 	
... ...
@@ -248,6 +261,28 @@ else
248 248
 					fi
249 249
 				fi
250 250
 				;;
251
+			Tanglu)
252
+				if [ "$suite" = "$tangluLatest" ]; then
253
+					# tag latest
254
+					$docker tag $repo:$suite $repo:latest
255
+				fi
256
+				if [ -r etc/lsb-release ]; then
257
+					lsbRelease="$(. etc/lsb-release && echo "$DISTRIB_RELEASE")"
258
+					if [ "$lsbRelease" ]; then
259
+						# tag specific Tanglu version number, if available (1.0, 2.0, etc.)
260
+						$docker tag $repo:$suite $repo:$lsbRelease
261
+					fi
262
+				fi
263
+				;;
264
+			SteamOS)
265
+				if [ -r etc/lsb-release ]; then
266
+					lsbRelease="$(. etc/lsb-release && echo "$DISTRIB_RELEASE")"
267
+					if [ "$lsbRelease" ]; then
268
+						# tag specific SteamOS version number, if available (1.0, 2.0, etc.)
269
+						$docker tag $repo:$suite $repo:$lsbRelease
270
+					fi
271
+				fi
272
+				;;
251 273
 		esac
252 274
 	fi
253 275
 fi
... ...
@@ -31,7 +31,7 @@ stop on runlevel [!2345]
31 31
 respawn
32 32
 
33 33
 script
34
-    /usr/bin/docker -d -H=tcp://0.0.0.0:4243/
34
+    /usr/bin/docker -d -H=tcp://0.0.0.0:4243
35 35
 end script
36 36
 ```
37 37
 
... ...
@@ -6,8 +6,8 @@ import (
6 6
 	"os"
7 7
 	"strings"
8 8
 
9
-	_ "github.com/dotcloud/docker"
10 9
 	"github.com/dotcloud/docker/api"
10
+	"github.com/dotcloud/docker/builtins"
11 11
 	"github.com/dotcloud/docker/dockerversion"
12 12
 	"github.com/dotcloud/docker/engine"
13 13
 	flag "github.com/dotcloud/docker/pkg/mflag"
... ...
@@ -39,6 +39,7 @@ func main() {
39 39
 		flDefaultIp          = flag.String([]string{"#ip", "-ip"}, "0.0.0.0", "Default IP address to use when binding container ports")
40 40
 		flInterContainerComm = flag.Bool([]string{"#icc", "-icc"}, true, "Enable inter-container communication")
41 41
 		flGraphDriver        = flag.String([]string{"s", "-storage-driver"}, "", "Force the docker runtime to use a specific storage driver")
42
+		flExecDriver         = flag.String([]string{"e", "-exec-driver"}, "", "Force the docker runtime to use a specific exec driver")
42 43
 		flHosts              = opts.NewListOpts(api.ValidateHost)
43 44
 		flMtu                = flag.Int([]string{"#mtu", "-mtu"}, 0, "Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if no default route is available")
44 45
 	)
... ...
@@ -81,6 +82,8 @@ func main() {
81 81
 		if err != nil {
82 82
 			log.Fatal(err)
83 83
 		}
84
+		// Load builtins
85
+		builtins.Register(eng)
84 86
 		// load the daemon in the background so we can immediately start
85 87
 		// the http api so that connections don't fail while the daemon
86 88
 		// is booting
... ...
@@ -98,6 +101,7 @@ func main() {
98 98
 			job.Setenv("DefaultIp", *flDefaultIp)
99 99
 			job.SetenvBool("InterContainerCommunication", *flInterContainerComm)
100 100
 			job.Setenv("GraphDriver", *flGraphDriver)
101
+			job.Setenv("ExecDriver", *flExecDriver)
101 102
 			job.SetenvInt("Mtu", *flMtu)
102 103
 			if err := job.Run(); err != nil {
103 104
 				log.Fatal(err)
... ...
@@ -92,14 +92,6 @@ To execute the test cases, run this command:
92 92
 
93 93
 	sudo make test
94 94
 
95
-
96
-Note: if you're running the tests in vagrant, you need to specify a dns entry in 
97
-the command (either edit the Makefile, or run the step manually): 
98
-
99
-.. code-block:: bash
100
-
101
-	sudo docker run -dns 8.8.8.8 -privileged -v `pwd`:/go/src/github.com/dotcloud/docker docker hack/make.sh test
102
-
103 95
 If the test are successful then the tail of the output should look something like this
104 96
 
105 97
 .. code-block:: bash
... ...
@@ -25,9 +25,9 @@ Does Docker run on Mac OS X or Windows?
25 25
 
26 26
    Not at this time, Docker currently only runs on Linux, but you can
27 27
    use VirtualBox to run Docker in a virtual machine on your box, and
28
-   get the best of both worlds. Check out the
29
-   :ref:`macosx` and :ref:`windows` installation
30
-   guides.
28
+   get the best of both worlds. Check out the :ref:`macosx` and
29
+   :ref:`windows` installation guides. The small Linux distribution boot2docker
30
+   can be run inside virtual machines on these two operating systems.
31 31
 
32 32
 How do containers compare to virtual machines?
33 33
 ..............................................
... ...
@@ -24,6 +24,6 @@ For a high-level overview of Docker, please see the `Introduction
24 24
 Docker, we have a `quick start <http://www.docker.io/gettingstarted>`_
25 25
 and a more in-depth guide to :ref:`ubuntu_linux` and other
26 26
 :ref:`installation_list` paths including prebuilt binaries,
27
-Vagrant-created VMs, Rackspace and Amazon instances.
27
+Rackspace and Amazon instances.
28 28
 
29 29
 Enough reading! :ref:`Try it out! <running_examples>`
... ...
@@ -10,8 +10,7 @@ Amazon EC2
10 10
 There are several ways to install Docker on AWS EC2:
11 11
 
12 12
 * :ref:`amazonquickstart` or
13
-* :ref:`amazonstandard` or
14
-* :ref:`amazonvagrant`
13
+* :ref:`amazonstandard`
15 14
 
16 15
 **You'll need an** `AWS account <http://aws.amazon.com/>`_ **first, of course.**
17 16
 
... ...
@@ -73,112 +72,4 @@ running Ubuntu. Just follow Step 1 from :ref:`amazonquickstart` to
73 73
 pick an image (or use one of your own) and skip the step with the
74 74
 *User Data*. Then continue with the :ref:`ubuntu_linux` instructions.
75 75
 
76
-.. _amazonvagrant:
77
-
78
-Use Vagrant
79
-
80
-.. include:: install_unofficial.inc
81
-  
82
-And finally, if you prefer to work through Vagrant, you can install
83
-Docker that way too. Vagrant 1.1 or higher is required.
84
-
85
-1. Install vagrant from http://www.vagrantup.com/ (or use your package manager)
86
-2. Install the vagrant aws plugin
87
-
88
-   ::
89
-
90
-       vagrant plugin install vagrant-aws
91
-
92
-
93
-3. Get the docker sources, this will give you the latest Vagrantfile.
94
-
95
-   ::
96
-
97
-      git clone https://github.com/dotcloud/docker.git
98
-
99
-
100
-4. Check your AWS environment.
101
-
102
-   Create a keypair specifically for EC2, give it a name and save it
103
-   to your disk. *I usually store these in my ~/.ssh/ folder*.
104
-
105
-   Check that your default security group has an inbound rule to
106
-   accept SSH (port 22) connections.
107
-
108
-5. Inform Vagrant of your settings
109
-
110
-   Vagrant will read your access credentials from your environment, so
111
-   we need to set them there first. Make sure you have everything on
112
-   amazon aws setup so you can (manually) deploy a new image to EC2.
113
-
114
-   Note that where possible these variables are the same as those honored by
115
-   the ec2 api tools.
116
-   ::
117
-
118
-       export AWS_ACCESS_KEY=xxx
119
-       export AWS_SECRET_KEY=xxx
120
-       export AWS_KEYPAIR_NAME=xxx
121
-       export SSH_PRIVKEY_PATH=xxx
122
-
123
-       export BOX_NAME=xxx
124
-       export AWS_REGION=xxx
125
-       export AWS_AMI=xxx
126
-       export AWS_INSTANCE_TYPE=xxx
127
-
128
-   The required environment variables are:
129
-
130
-   * ``AWS_ACCESS_KEY`` - The API key used to make requests to AWS
131
-   * ``AWS_SECRET_KEY`` - The secret key to make AWS API requests
132
-   * ``AWS_KEYPAIR_NAME`` - The name of the keypair used for this EC2 instance
133
-   * ``SSH_PRIVKEY_PATH`` - The path to the private key for the named
134
-     keypair, for example ``~/.ssh/docker.pem``
135
-
136
-   There are a number of optional environment variables:
137
-
138
-   * ``BOX_NAME`` - The name of the vagrant box to use.  Defaults to
139
-     ``ubuntu``.
140
-   * ``AWS_REGION`` - The aws region to spawn the vm in.  Defaults to
141
-     ``us-east-1``.
142
-   * ``AWS_AMI`` - The aws AMI to start with as a base.  This must be
143
-     be an ubuntu 12.04 precise image.  You must change this value if
144
-     ``AWS_REGION`` is set to a value other than ``us-east-1``.
145
-     This is because AMIs are region specific.  Defaults to ``ami-69f5a900``.
146
-   * ``AWS_INSTANCE_TYPE`` - The aws instance type.  Defaults to ``t1.micro``.
147
-
148
-   You can check if they are set correctly by doing something like
149
-
150
-   ::
151
-
152
-      echo $AWS_ACCESS_KEY
153
-
154
-6. Do the magic!
155
-
156
-   ::
157
-
158
-      vagrant up --provider=aws
159
-
160
-
161
-   If it stalls indefinitely on ``[default] Waiting for SSH to become
162
-   available...``, Double check your default security zone on AWS
163
-   includes rights to SSH (port 22) to your container.
164
-
165
-   If you have an advanced AWS setup, you might want to have a look at
166
-   `vagrant-aws <https://github.com/mitchellh/vagrant-aws>`_.
167
-
168
-7. Connect to your machine
169
-
170
-   .. code-block:: bash
171
-
172
-      vagrant ssh
173
-
174
-8. Your first command
175
-
176
-   Now you are in the VM, run docker
177
-
178
-   .. code-block:: bash
179
-
180
-      sudo docker
181
-
182
-
183 76
 Continue with the :ref:`hello_world` example.
... ...
@@ -1,223 +1,72 @@
1 1
 :title: Installation on Windows
2 2
 :description: Please note this project is currently under heavy development. It should not be used in production.
3
-:keywords: Docker, Docker documentation, Windows, requirements, virtualbox, vagrant, git, ssh, putty, cygwin
3
+:keywords: Docker, Docker documentation, Windows, requirements, virtualbox, boot2docker
4 4
 
5 5
 .. _windows:
6 6
 
7 7
 Windows
8 8
 =======
9 9
 
10
-Docker can run on Windows using a VM like VirtualBox. You then run
11
-Linux within the VM.
10
+Docker can run on Windows using a virtualization platform like VirtualBox. A Linux
11
+distribution is run inside a virtual machine and that's where Docker will run. 
12 12
 
13 13
 Installation
14 14
 ------------
15 15
 
16 16
 .. include:: install_header.inc
17 17
 
18
-.. include:: install_unofficial.inc
18
+1. Install virtualbox from https://www.virtualbox.org - or follow this `tutorial <http://www.slideshare.net/julienbarbier42/install-virtualbox-on-windows-7>`_.
19 19
 
20
-1. Install virtualbox from https://www.virtualbox.org - or follow this tutorial__
20
+2. Download the latest boot2docker.iso from https://github.com/boot2docker/boot2docker/releases.
21 21
 
22
-.. __: http://www.slideshare.net/julienbarbier42/install-virtualbox-on-windows-7
22
+3. Start VirtualBox.
23 23
 
24
-2. Install vagrant from http://www.vagrantup.com - or follow this tutorial__
24
+4. Create a new Virtual machine with the following settings:
25 25
 
26
-.. __: http://www.slideshare.net/julienbarbier42/install-vagrant-on-windows-7
26
+ - `Name: boot2docker`
27
+ - `Type: Linux`
28
+ - `Version: Linux 2.6 (64 bit)`
29
+ - `Memory size: 1024 MB`
30
+ - `Hard drive: Do not add a virtual hard drive`
27 31
 
28
-3. Install git with ssh from http://git-scm.com/downloads - or follow this tutorial__
32
+5. Open the settings of the virtual machine:
29 33
 
30
-.. __: http://www.slideshare.net/julienbarbier42/install-git-with-ssh-on-windows-7
34
+   5.1. go to Storage
31 35
 
36
+   5.2. click the empty slot below `Controller: IDE`
32 37
 
33
-We recommend having at least 2Gb of free disk space and 2Gb of RAM (or more).
38
+   5.3. click the disc icon on the right of `IDE Secondary Master`
34 39
 
35
-Opening a command prompt
40
+   5.4. click `Choose a virtual CD/DVD disk file`
36 41
 
37
-First open a cmd prompt. Press Windows key and then press “R”
38
-key. This will open the RUN dialog box for you. Type “cmd” and press
39
-Enter. Or you can click on Start, type “cmd” in the “Search programs
40
-and files” field, and click on cmd.exe.
42
+6. Browse to the path where you've saved the `boot2docker.iso`, select the `boot2docker.iso` and click open.
41 43
 
42
-.. image:: images/win/_01.gif
43
-   :alt: Git install
44
-   :align: center
44
+7. Click OK on the Settings dialog to save the changes and close the window.
45 45
 
46
-This should open a cmd prompt window.
46
+8. Start the virtual machine by clicking the green start button.
47 47
 
48
-.. image:: images/win/_02.gif
49
-   :alt: run docker
50
-   :align: center
51
-
52
-Alternatively, you can also use a Cygwin terminal, or Git Bash (or any
53
-other command line program you are usually using). The next steps
54
-would be the same.
55
-
56
-.. _launch_ubuntu:
57
-
58
-Launch an Ubuntu virtual server
59
-
60
-Let’s download and run an Ubuntu image with docker binaries already
61
-installed.
62
-
63
-.. code-block:: bash
64
-
65
-	git clone https://github.com/dotcloud/docker.git 
66
-	cd docker
67
-	vagrant up
68
-
69
-.. image:: images/win/run_02_.gif
70
-   :alt: run docker
71
-   :align: center
72
-
73
-Congratulations! You are running an Ubuntu server with docker
74
-installed on it. You do not see it though, because it is running in
75
-the background.
76
-
77
-Log onto your Ubuntu server
78
-
79
-Let’s log into your Ubuntu server now. To do so you have two choices:
80
-
81
-- Use Vagrant on Windows command prompt OR
82
-- Use SSH
83
-
84
-Using Vagrant on Windows Command Prompt
85
-```````````````````````````````````````
86
-
87
-Run the following command
88
-
89
-.. code-block:: bash
90
-
91
-	vagrant ssh
92
-
93
-You may see an error message starting with “`ssh` executable not
94
-found”. In this case it means that you do not have SSH in your
95
-PATH. If you do not have SSH in your PATH you can set it up with the
96
-“set” command. For instance, if your ssh.exe is in the folder named
97
-“C:\Program Files (x86)\Git\bin”, then you can run the following
98
-command:
99
-
100
-.. code-block:: bash
101
-
102
-	set PATH=%PATH%;C:\Program Files (x86)\Git\bin
103
-
104
-.. image:: images/win/run_03.gif
105
-   :alt: run docker
106
-   :align: center
107
-
108
-Using SSH
109
-`````````
110
-
111
-First step is to get the IP and port of your Ubuntu server. Simply run:
112
-
113
-.. code-block:: bash
114
-
115
-	vagrant ssh-config 
116
-
117
-You should see an output with HostName and Port information. In this
118
-example, HostName is 127.0.0.1 and port is 2222. And the User is
119
-“vagrant”. The password is not shown, but it is also “vagrant”.
120
-
121
-.. image:: images/win/ssh-config.gif
122
-   :alt: run docker
123
-   :align: center
124
-
125
-You can now use this information for connecting via SSH to your
126
-server. To do so you can:
127
-
128
-- Use putty.exe OR
129
-- Use SSH from a terminal
130
-
131
-Use putty.exe
132
-'''''''''''''
133
-
134
-You can download putty.exe from this page
135
-http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html Launch
136
-putty.exe and simply enter the information you got from last step.
137
-
138
-.. image:: images/win/putty.gif
139
-   :alt: run docker
140
-   :align: center
141
-
142
-Open, and enter user = vagrant and password = vagrant.
143
-
144
-.. image:: images/win/putty_2.gif
145
-   :alt: run docker
146
-   :align: center
147
-
148
-SSH from a terminal
149
-'''''''''''''''''''
150
-
151
-You can also run this command on your favorite terminal (windows
152
-prompt, cygwin, git-bash, …). Make sure to adapt the IP and port from
153
-what you got from the vagrant ssh-config command.
154
-
155
-.. code-block:: bash
156
-
157
-	ssh vagrant@127.0.0.1 –p 2222
158
-
159
-Enter user = vagrant and password = vagrant.
160
-
161
-.. image:: images/win/cygwin.gif
162
-   :alt: run docker
163
-   :align: center
164
-
165
-Congratulations, you are now logged onto your Ubuntu Server, running
166
-on top of your Windows machine !
48
+9. The boot2docker virtual machine should boot now.
167 49
 
168 50
 Running Docker
169 51
 --------------
170 52
 
171
-First you have to be root in order to run docker. Simply run the
172
-following command:
173
-
174
-.. code-block:: bash
53
+boot2docker will log you in automatically so you can start using Docker right
54
+away.
175 55
 
176
-	sudo su
177
-
178
-You are now ready for the docker’s “hello world” example. Run
56
+Let's try the “hello world” example. Run
179 57
 
180 58
 .. code-block:: bash
181 59
 
182 60
 	docker run busybox echo hello world
183 61
 
184
-.. image:: images/win/run_04.gif
185
-   :alt: run docker
186
-   :align: center
187
-
188
-All done!
189
-
190
-Now you can continue with the :ref:`hello_world` example.
62
+This will download the small busybox image and print hello world.
191 63
 
192
-Troubleshooting
193 64
 
194
-VM does not boot
195
-````````````````
196
-
197
-.. image:: images/win/ts_go_bios.JPG
198
-
199
-If you run into this error message "The VM failed to remain in the
200
-'running' state while attempting to boot", please check that your
201
-computer has virtualization technology available and activated by
202
-going to the BIOS. Here's an example for an HP computer (System
203
-configuration / Device configuration)
204
-
205
-.. image:: images/win/hp_bios_vm.JPG
206
-
207
-On some machines the BIOS menu can only be accessed before startup.
208
-To access BIOS in this scenario you should restart your computer and 
209
-press ESC/Enter when prompted to access the boot and BIOS controls. Typically
210
-the option to allow virtualization is contained within the BIOS/Security menu.
211
-
212
-Docker is not installed
213
-```````````````````````
65
+Observations
66
+------------
214 67
 
215
-.. image:: images/win/ts_no_docker.JPG
68
+Persistent storage
69
+``````````````````
216 70
 
217
-If you run into this error message "The program 'docker' is currently
218
-not installed", try deleting the docker folder and restart from
219
-:ref:`launch_ubuntu`
71
+The virtual machine created above lacks any persistent data storage. All images
72
+and containers will be lost when shutting down or rebooting the VM.
... ...
@@ -118,6 +118,7 @@ Create a container
118 118
                 "User":"",
119 119
                 "Memory":0,
120 120
                 "MemorySwap":0,
121
+                "CpuShares":0,
121 122
                 "AttachStdin":false,
122 123
                 "AttachStdout":true,
123 124
                 "AttachStderr":true,
... ...
@@ -153,7 +154,15 @@ Create a container
153 153
                 "Warnings":[]
154 154
            }
155 155
 
156
-        :jsonparam config: the container's configuration
156
+        :jsonparam Hostname: Container host name
157
+        :jsonparam User: Username or UID
158
+        :jsonparam Memory: Memory Limit in bytes
159
+        :jsonparam CpuShares: CPU shares (relative weight)
160
+        :jsonparam AttachStdin: 1/True/true or 0/False/false, attach to standard input. Default false
161
+        :jsonparam AttachStdout: 1/True/true or 0/False/false, attach to standard output. Default false
162
+        :jsonparam AttachStderr: 1/True/true or 0/False/false, attach to standard error. Default false
163
+        :jsonparam Tty: 1/True/true or 0/False/false, allocate a pseudo-tty. Default false
164
+        :jsonparam OpenStdin: 1/True/true or 0/False/false, keep stdin open even if not attached. Default false
157 165
         :query name: Assign the specified name to the container. Must match ``/?[a-zA-Z0-9_-]+``.
158 166
         :statuscode 201: no error
159 167
         :statuscode 404: no such container
... ...
@@ -394,7 +403,11 @@ Start a container
394 394
            HTTP/1.1 204 No Content
395 395
            Content-Type: text/plain
396 396
 
397
-        :jsonparam hostConfig: the container's host configuration (optional)
397
+        :jsonparam Binds: Create a bind mount to a directory or file with [host-path]:[container-path]:[rw|ro]. If a directory "container-path" is missing, then docker creates a new volume.
398
+        :jsonparam LxcConf: Map of custom lxc options
399
+        :jsonparam PortBindings: Expose ports from the container, optionally publishing them via the HostPort flag
400
+        :jsonparam PublishAllPorts: 1/True/true or 0/False/false, publish all exposed ports to the host interfaces. Default false
401
+        :jsonparam Privileged: 1/True/true or 0/False/false, give extended privileges to this container. Default false
398 402
         :statuscode 204: no error
399 403
         :statuscode 404: no such container
400 404
         :statuscode 500: server error
... ...
@@ -118,6 +118,7 @@ Create a container
118 118
                 "User":"",
119 119
                 "Memory":0,
120 120
                 "MemorySwap":0,
121
+                "CpuShares":0,
121 122
                 "AttachStdin":false,
122 123
                 "AttachStdout":true,
123 124
                 "AttachStderr":true,
... ...
@@ -153,7 +154,15 @@ Create a container
153 153
                 "Warnings":[]
154 154
            }
155 155
 
156
-        :jsonparam config: the container's configuration
156
+        :jsonparam Hostname: Container host name
157
+        :jsonparam User: Username or UID
158
+        :jsonparam Memory: Memory Limit in bytes
159
+        :jsonparam CpuShares: CPU shares (relative weight)
160
+        :jsonparam AttachStdin: 1/True/true or 0/False/false, attach to standard input. Default false
161
+        :jsonparam AttachStdout: 1/True/true or 0/False/false, attach to standard output. Default false
162
+        :jsonparam AttachStderr: 1/True/true or 0/False/false, attach to standard error. Default false
163
+        :jsonparam Tty: 1/True/true or 0/False/false, allocate a pseudo-tty. Default false
164
+        :jsonparam OpenStdin: 1/True/true or 0/False/false, keep stdin open even if not attached. Default false
157 165
         :query name: Assign the specified name to the container. Must match ``/?[a-zA-Z0-9_-]+``.
158 166
         :statuscode 201: no error
159 167
         :statuscode 404: no such container
... ...
@@ -394,7 +403,11 @@ Start a container
394 394
            HTTP/1.1 204 No Content
395 395
            Content-Type: text/plain
396 396
 
397
-        :jsonparam hostConfig: the container's host configuration (optional)
397
+        :jsonparam Binds: Create a bind mount to a directory or file with [host-path]:[container-path]:[rw|ro]. If a directory "container-path" is missing, then docker creates a new volume.
398
+        :jsonparam LxcConf: Map of custom lxc options
399
+        :jsonparam PortBindings: Expose ports from the container, optionally publishing them via the HostPort flag
400
+        :jsonparam PublishAllPorts: 1/True/true or 0/False/false, publish all exposed ports to the host interfaces. Default false
401
+        :jsonparam Privileged: 1/True/true or 0/False/false, give extended privileges to this container. Default false
398 402
         :statuscode 204: no error
399 403
         :statuscode 404: no such container
400 404
         :statuscode 500: server error
... ...
@@ -74,7 +74,7 @@ When you're done with your build, you're ready to look into
74 74
 2. Format
75 75
 =========
76 76
 
77
-The Dockerfile format is quite simple:
77
+Here is the format of the Dockerfile:
78 78
 
79 79
 ::
80 80
 
... ...
@@ -79,6 +79,7 @@ Commands
79 79
       -p, --pidfile="/var/run/docker.pid": Path to use for daemon PID file
80 80
       -r, --restart=true: Restart previously running containers
81 81
       -s, --storage-driver="": Force the docker runtime to use a specific storage driver
82
+      -e, --exec-driver="": Force the docker runtime to use a specific exec driver
82 83
       -v, --version=false: Print version information and quit
83 84
       --mtu=0: Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if no default route is available
84 85
 
... ...
@@ -1,6 +1,7 @@
1 1
 package engine
2 2
 
3 3
 import (
4
+	"bufio"
4 5
 	"fmt"
5 6
 	"github.com/dotcloud/docker/utils"
6 7
 	"io"
... ...
@@ -136,6 +137,48 @@ func (eng *Engine) Job(name string, args ...string) *Job {
136 136
 	return job
137 137
 }
138 138
 
139
+// ParseJob creates a new job from a text description using a shell-like syntax.
140
+//
141
+// The following syntax is used to parse `input`:
142
+//
143
+// * Words are separated using standard whitespaces as separators.
144
+// * Quotes and backslashes are not interpreted.
145
+// * Words of the form 'KEY=[VALUE]' are added to the job environment.
146
+// * All other words are added to the job arguments.
147
+//
148
+// For example:
149
+//
150
+// job, _ := eng.ParseJob("VERBOSE=1 echo hello TEST=true world")
151
+//
152
+// The resulting job will have:
153
+//	job.Args={"echo", "hello", "world"}
154
+//	job.Env={"VERBOSE":"1", "TEST":"true"}
155
+//
156
+func (eng *Engine) ParseJob(input string) (*Job, error) {
157
+	// FIXME: use a full-featured command parser
158
+	scanner := bufio.NewScanner(strings.NewReader(input))
159
+	scanner.Split(bufio.ScanWords)
160
+	var (
161
+		cmd []string
162
+		env Env
163
+	)
164
+	for scanner.Scan() {
165
+		word := scanner.Text()
166
+		kv := strings.SplitN(word, "=", 2)
167
+		if len(kv) == 2 {
168
+			env.Set(kv[0], kv[1])
169
+		} else {
170
+			cmd = append(cmd, word)
171
+		}
172
+	}
173
+	if len(cmd) == 0 {
174
+		return nil, fmt.Errorf("empty command: '%s'", input)
175
+	}
176
+	job := eng.Job(cmd[0], cmd[1:]...)
177
+	job.Env().Init(&env)
178
+	return job, nil
179
+}
180
+
139 181
 func (eng *Engine) Logf(format string, args ...interface{}) (n int, err error) {
140 182
 	if os.Getenv("TEST") == "" {
141 183
 		prefixedFormat := fmt.Sprintf("[%s] %s\n", eng, strings.TrimRight(format, "\n"))
... ...
@@ -5,6 +5,7 @@ import (
5 5
 	"os"
6 6
 	"path"
7 7
 	"path/filepath"
8
+	"strings"
8 9
 	"testing"
9 10
 )
10 11
 
... ...
@@ -114,3 +115,40 @@ func TestEngineLogf(t *testing.T) {
114 114
 		t.Fatalf("Test: Logf() should print at least as much as the input\ninput=%d\nprinted=%d", len(input), n)
115 115
 	}
116 116
 }
117
+
118
+func TestParseJob(t *testing.T) {
119
+	eng := newTestEngine(t)
120
+	defer os.RemoveAll(eng.Root())
121
+	// Verify that the resulting job calls to the right place
122
+	var called bool
123
+	eng.Register("echo", func(job *Job) Status {
124
+		called = true
125
+		return StatusOK
126
+	})
127
+	input := "echo DEBUG=1 hello world VERBOSITY=42"
128
+	job, err := eng.ParseJob(input)
129
+	if err != nil {
130
+		t.Fatal(err)
131
+	}
132
+	if job.Name != "echo" {
133
+		t.Fatalf("Invalid job name: %v", job.Name)
134
+	}
135
+	if strings.Join(job.Args, ":::") != "hello:::world" {
136
+		t.Fatalf("Invalid job args: %v", job.Args)
137
+	}
138
+	if job.Env().Get("DEBUG") != "1" {
139
+		t.Fatalf("Invalid job env: %v", job.Env)
140
+	}
141
+	if job.Env().Get("VERBOSITY") != "42" {
142
+		t.Fatalf("Invalid job env: %v", job.Env)
143
+	}
144
+	if len(job.Env().Map()) != 2 {
145
+		t.Fatalf("Invalid job env: %v", job.Env)
146
+	}
147
+	if err := job.Run(); err != nil {
148
+		t.Fatal(err)
149
+	}
150
+	if !called {
151
+		t.Fatalf("Job was not called")
152
+	}
153
+}
... ...
@@ -36,6 +36,13 @@ func (env *Env) Exists(key string) bool {
36 36
 	return exists
37 37
 }
38 38
 
39
+func (env *Env) Init(src *Env) {
40
+	(*env) = make([]string, 0, len(*src))
41
+	for _, val := range *src {
42
+		(*env) = append((*env), val)
43
+	}
44
+}
45
+
39 46
 func (env *Env) GetBool(key string) (value bool) {
40 47
 	s := strings.ToLower(strings.Trim(env.Get(key), " \t"))
41 48
 	if s == "" || s == "0" || s == "no" || s == "false" || s == "none" {
... ...
@@ -74,7 +74,7 @@ func (job *Job) Run() error {
74 74
 		return err
75 75
 	}
76 76
 	if job.status != 0 {
77
-		return fmt.Errorf("%s: %s", job.Name, errorMessage)
77
+		return fmt.Errorf("%s", errorMessage)
78 78
 	}
79 79
 	return nil
80 80
 }
... ...
@@ -102,6 +102,10 @@ func (job *Job) String() string {
102 102
 	return fmt.Sprintf("%s.%s%s", job.Eng, job.CallString(), job.StatusString())
103 103
 }
104 104
 
105
+func (job *Job) Env() *Env {
106
+	return job.env
107
+}
108
+
105 109
 func (job *Job) EnvExists(key string) (value bool) {
106 110
 	return job.env.Exists(key)
107 111
 }
... ...
@@ -197,11 +201,14 @@ func (job *Job) Printf(format string, args ...interface{}) (n int, err error) {
197 197
 }
198 198
 
199 199
 func (job *Job) Errorf(format string, args ...interface{}) Status {
200
+	if format[len(format)-1] != '\n' {
201
+		format = format + "\n"
202
+	}
200 203
 	fmt.Fprintf(job.Stderr, format, args...)
201 204
 	return StatusErr
202 205
 }
203 206
 
204 207
 func (job *Job) Error(err error) Status {
205
-	fmt.Fprintf(job.Stderr, "%s", err)
208
+	fmt.Fprintf(job.Stderr, "%s\n", err)
206 209
 	return StatusErr
207 210
 }
... ...
@@ -301,9 +301,8 @@ func (d *driver) Info(id string) execdriver.Info {
301 301
 func (d *driver) GetPidsForContainer(id string) ([]int, error) {
302 302
 	pids := []int{}
303 303
 
304
-	// memory is chosen randomly, any cgroup used by docker works
305
-	subsystem := "memory"
306
-
304
+	// cpu is chosen because it is the only non optional subsystem in cgroups
305
+	subsystem := "cpu"
307 306
 	cgroupRoot, err := cgroups.FindCgroupMountpoint(subsystem)
308 307
 	if err != nil {
309 308
 		return pids, err
... ...
@@ -12,5 +12,5 @@ bundle_test_integration() {
12 12
 # this "grep" hides some really irritating warnings that "go test -coverpkg"
13 13
 # spews when it is given packages that aren't used
14 14
 bundle_test_integration 2>&1 \
15
-	| grep -v '^warning: no packages being tested depend on ' \
15
+	| grep --line-buffered -v '^warning: no packages being tested depend on ' \
16 16
 	| tee $DEST/test.log
... ...
@@ -123,19 +123,8 @@ func init() {
123 123
 }
124 124
 
125 125
 func setupBaseImage() {
126
-	eng, err := engine.New(unitTestStoreBase)
127
-	if err != nil {
128
-		log.Fatalf("Can't initialize engine at %s: %s", unitTestStoreBase, err)
129
-	}
130
-	job := eng.Job("initserver")
131
-	job.Setenv("Root", unitTestStoreBase)
132
-	job.SetenvBool("Autorestart", false)
133
-	job.Setenv("BridgeIface", unitTestNetworkBridge)
134
-	if err := job.Run(); err != nil {
135
-		log.Fatalf("Unable to create a runtime for tests: %s", err)
136
-	}
137
-
138
-	job = eng.Job("inspect", unitTestImageName, "image")
126
+	eng := newTestEngine(log.New(os.Stderr, "", 0), false, unitTestStoreBase)
127
+	job := eng.Job("inspect", unitTestImageName, "image")
139 128
 	img, _ := job.Stdout.AddEnv()
140 129
 	// If the unit test is not found, try to download it.
141 130
 	if err := job.Run(); err != nil || img.Get("id") != unitTestImageID {
... ...
@@ -575,18 +564,7 @@ func TestRestore(t *testing.T) {
575 575
 
576 576
 	// Here are are simulating a docker restart - that is, reloading all containers
577 577
 	// from scratch
578
-	root := eng.Root()
579
-	eng, err := engine.New(root)
580
-	if err != nil {
581
-		t.Fatal(err)
582
-	}
583
-	job := eng.Job("initserver")
584
-	job.Setenv("Root", eng.Root())
585
-	job.SetenvBool("Autorestart", false)
586
-	if err := job.Run(); err != nil {
587
-		t.Fatal(err)
588
-	}
589
-
578
+	eng = newTestEngine(t, false, eng.Root())
590 579
 	runtime2 := mkRuntimeFromEngine(eng, t)
591 580
 	if len(runtime2.List()) != 2 {
592 581
 		t.Errorf("Expected 2 container, %v found", len(runtime2.List()))
... ...
@@ -612,22 +590,14 @@ func TestRestore(t *testing.T) {
612 612
 }
613 613
 
614 614
 func TestReloadContainerLinks(t *testing.T) {
615
-	// FIXME: here we don't use NewTestEngine because it calls initserver with Autorestart=false,
616
-	// and we want to set it to true.
617 615
 	root, err := newTestDirectory(unitTestStoreBase)
618 616
 	if err != nil {
619 617
 		t.Fatal(err)
620 618
 	}
621
-	eng, err := engine.New(root)
622
-	if err != nil {
623
-		t.Fatal(err)
624
-	}
625
-	job := eng.Job("initserver")
626
-	job.Setenv("Root", eng.Root())
627
-	job.SetenvBool("Autorestart", true)
628
-	if err := job.Run(); err != nil {
629
-		t.Fatal(err)
630
-	}
619
+	// FIXME: here we don't use NewTestEngine because it calls initserver with Autorestart=false,
620
+	// and we want to set it to true.
621
+
622
+	eng := newTestEngine(t, true, root)
631 623
 
632 624
 	runtime1 := mkRuntimeFromEngine(eng, t)
633 625
 	defer nuke(runtime1)
... ...
@@ -668,17 +638,7 @@ func TestReloadContainerLinks(t *testing.T) {
668 668
 
669 669
 	// Here are are simulating a docker restart - that is, reloading all containers
670 670
 	// from scratch
671
-	eng, err = engine.New(root)
672
-	if err != nil {
673
-		t.Fatal(err)
674
-	}
675
-	job = eng.Job("initserver")
676
-	job.Setenv("Root", eng.Root())
677
-	job.SetenvBool("Autorestart", false)
678
-	if err := job.Run(); err != nil {
679
-		t.Fatal(err)
680
-	}
681
-
671
+	eng = newTestEngine(t, false, root)
682 672
 	runtime2 := mkRuntimeFromEngine(eng, t)
683 673
 	if len(runtime2.List()) != 2 {
684 674
 		t.Errorf("Expected 2 container, %v found", len(runtime2.List()))
... ...
@@ -2,7 +2,6 @@ package docker
2 2
 
3 3
 import (
4 4
 	"github.com/dotcloud/docker"
5
-	"github.com/dotcloud/docker/engine"
6 5
 	"github.com/dotcloud/docker/runconfig"
7 6
 	"strings"
8 7
 	"testing"
... ...
@@ -258,20 +257,7 @@ func TestRestartKillWait(t *testing.T) {
258 258
 		t.Fatal(err)
259 259
 	}
260 260
 
261
-	eng, err = engine.New(eng.Root())
262
-	if err != nil {
263
-		t.Fatal(err)
264
-	}
265
-
266
-	job = eng.Job("initserver")
267
-	job.Setenv("Root", eng.Root())
268
-	job.SetenvBool("AutoRestart", false)
269
-	// TestGetEnabledCors and TestOptionsRoute require EnableCors=true
270
-	job.SetenvBool("EnableCors", true)
271
-	if err := job.Run(); err != nil {
272
-		t.Fatal(err)
273
-	}
274
-
261
+	eng = newTestEngine(t, false, eng.Root())
275 262
 	srv = mkServerFromEngine(eng, t)
276 263
 
277 264
 	job = srv.Eng.Job("containers")
... ...
@@ -15,6 +15,7 @@ import (
15 15
 	"time"
16 16
 
17 17
 	"github.com/dotcloud/docker"
18
+	"github.com/dotcloud/docker/builtins"
18 19
 	"github.com/dotcloud/docker/engine"
19 20
 	"github.com/dotcloud/docker/runconfig"
20 21
 	"github.com/dotcloud/docker/utils"
... ...
@@ -27,26 +28,12 @@ import (
27 27
 // Create a temporary runtime suitable for unit testing.
28 28
 // Call t.Fatal() at the first error.
29 29
 func mkRuntime(f utils.Fataler) *docker.Runtime {
30
-	root, err := newTestDirectory(unitTestStoreBase)
31
-	if err != nil {
32
-		f.Fatal(err)
33
-	}
34
-	config := &docker.DaemonConfig{
35
-		Root:        root,
36
-		AutoRestart: false,
37
-		Mtu:         docker.GetDefaultNetworkMtu(),
38
-	}
39
-
40
-	eng, err := engine.New(root)
41
-	if err != nil {
42
-		f.Fatal(err)
43
-	}
44
-
45
-	r, err := docker.NewRuntimeFromDirectory(config, eng)
46
-	if err != nil {
47
-		f.Fatal(err)
48
-	}
49
-	return r
30
+	eng := newTestEngine(f, false, "")
31
+	return mkRuntimeFromEngine(eng, f)
32
+	// FIXME:
33
+	// [...]
34
+	// Mtu:         docker.GetDefaultNetworkMtu(),
35
+	// [...]
50 36
 }
51 37
 
52 38
 func createNamedTestContainer(eng *engine.Engine, config *runconfig.Config, f utils.Fataler, name string) (shortId string) {
... ...
@@ -185,20 +172,24 @@ func mkRuntimeFromEngine(eng *engine.Engine, t utils.Fataler) *docker.Runtime {
185 185
 	return runtime
186 186
 }
187 187
 
188
-func NewTestEngine(t utils.Fataler) *engine.Engine {
189
-	root, err := newTestDirectory(unitTestStoreBase)
190
-	if err != nil {
191
-		t.Fatal(err)
188
+func newTestEngine(t utils.Fataler, autorestart bool, root string) *engine.Engine {
189
+	if root == "" {
190
+		if dir, err := newTestDirectory(unitTestStoreBase); err != nil {
191
+			t.Fatal(err)
192
+		} else {
193
+			root = dir
194
+		}
192 195
 	}
193 196
 	eng, err := engine.New(root)
194 197
 	if err != nil {
195 198
 		t.Fatal(err)
196 199
 	}
197 200
 	// Load default plugins
201
+	builtins.Register(eng)
198 202
 	// (This is manually copied and modified from main() until we have a more generic plugin system)
199 203
 	job := eng.Job("initserver")
200 204
 	job.Setenv("Root", root)
201
-	job.SetenvBool("AutoRestart", false)
205
+	job.SetenvBool("AutoRestart", autorestart)
202 206
 	// TestGetEnabledCors and TestOptionsRoute require EnableCors=true
203 207
 	job.SetenvBool("EnableCors", true)
204 208
 	if err := job.Run(); err != nil {
... ...
@@ -207,6 +198,10 @@ func NewTestEngine(t utils.Fataler) *engine.Engine {
207 207
 	return eng
208 208
 }
209 209
 
210
+func NewTestEngine(t utils.Fataler) *engine.Engine {
211
+	return newTestEngine(t, false, "")
212
+}
213
+
210 214
 func newTestDirectory(templateDir string) (dir string, err error) {
211 215
 	return utils.TestDirectory(templateDir)
212 216
 }
... ...
@@ -72,7 +72,7 @@ func (l *Link) ToEnv() []string {
72 72
 			if len(parts) != 2 {
73 73
 				continue
74 74
 			}
75
-			// Ignore a few variables that are added during docker build
75
+			// Ignore a few variables that are added during docker build (and not really relevant to linked containers)
76 76
 			if parts[0] == "HOME" || parts[0] == "PATH" {
77 77
 				continue
78 78
 			}
... ...
@@ -57,12 +57,6 @@ var (
57 57
 	currentInterfaces = make(map[string]*networkInterface)
58 58
 )
59 59
 
60
-func init() {
61
-	if err := engine.Register("init_networkdriver", InitDriver); err != nil {
62
-		panic(err)
63
-	}
64
-}
65
-
66 60
 func InitDriver(job *engine.Job) engine.Status {
67 61
 	var (
68 62
 		network        *net.IPNet
... ...
@@ -8,7 +8,7 @@ import (
8 8
 	"github.com/dotcloud/docker/engine"
9 9
 	"github.com/dotcloud/docker/execdriver"
10 10
 	"github.com/dotcloud/docker/execdriver/docker"
11
-	_ "github.com/dotcloud/docker/execdriver/lxc"
11
+	"github.com/dotcloud/docker/execdriver/lxc"
12 12
 	"github.com/dotcloud/docker/graphdriver"
13 13
 	"github.com/dotcloud/docker/graphdriver/aufs"
14 14
 	_ "github.com/dotcloud/docker/graphdriver/btrfs"
... ...
@@ -704,7 +704,16 @@ func NewRuntimeFromDirectory(config *DaemonConfig, eng *engine.Engine) (*Runtime
704 704
 
705 705
 	sysInfo := sysinfo.New(false)
706 706
 
707
-	ed, err := docker.NewDriver(config.Root)
707
+	var ed execdriver.Driver
708
+	utils.Debugf("execDriver: provided %s", config.ExecDriver)
709
+	if config.ExecDriver == "chroot" && false {
710
+		// chroot is presently a noop driver https://github.com/dotcloud/docker/pull/4189#issuecomment-35330655
711
+		ed, err = chroot.NewDriver()
712
+		utils.Debugf("execDriver: using chroot")
713
+	} else {
714
+		ed, err = lxc.NewDriver(config.Root, sysInfo.AppArmor)
715
+		utils.Debugf("execDriver: using lxc")
716
+	}
708 717
 	if err != nil {
709 718
 		return nil, err
710 719
 	}
... ...
@@ -34,14 +34,10 @@ func (srv *Server) Close() error {
34 34
 	return srv.runtime.Close()
35 35
 }
36 36
 
37
-func init() {
38
-	engine.Register("initserver", jobInitServer)
39
-}
40
-
41 37
 // jobInitApi runs the remote api server `srv` as a daemon,
42 38
 // Only one api server can run at the same time - this is enforced by a pidfile.
43 39
 // The signals SIGINT, SIGQUIT and SIGTERM are intercepted for cleanup.
44
-func jobInitServer(job *engine.Job) engine.Status {
40
+func InitServer(job *engine.Job) engine.Status {
45 41
 	job.Logf("Creating server")
46 42
 	srv, err := NewServer(job.Eng, DaemonConfigFromJob(job))
47 43
 	if err != nil {
... ...
@@ -7,11 +7,7 @@ import (
7 7
 	"runtime"
8 8
 )
9 9
 
10
-func init() {
11
-	engine.Register("version", jobVersion)
12
-}
13
-
14
-func jobVersion(job *engine.Job) engine.Status {
10
+func GetVersion(job *engine.Job) engine.Status {
15 11
 	if _, err := dockerVersion().WriteTo(job.Stdout); err != nil {
16 12
 		job.Errorf("%s", err)
17 13
 		return engine.StatusErr