Browse code

Merge branch 'master' into shykes-0.6.5-dm-plugin

Conflicts:
utils.go
utils_test.go

Guillaume J. Charmes authored on 2013/11/15 07:02:44
Showing 71 changed files
... ...
@@ -18,3 +18,4 @@ bundles/
18 18
 .hg/
19 19
 .git/
20 20
 vendor/pkg/
21
+pyenv
... ...
@@ -94,6 +94,7 @@ Jonathan Rudenberg <jonathan@titanous.com>
94 94
 Joost Cassee <joost@cassee.net>
95 95
 Jordan Arentsen <blissdev@gmail.com>
96 96
 Joseph Anthony Pasquale Holsten <joseph@josephholsten.com>
97
+Josh Poimboeuf <jpoimboe@redhat.com>
97 98
 Julien Barbier <write0@gmail.com>
98 99
 Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
99 100
 Karan Lyons <karan@karanlyons.com>
... ...
@@ -165,6 +166,7 @@ Sridatta Thatipamala <sthatipamala@gmail.com>
165 165
 Sridhar Ratnakumar <sridharr@activestate.com>
166 166
 Steeve Morin <steeve.morin@gmail.com>
167 167
 Stefan Praszalowicz <stefan@greplin.com>
168
+Sven Dowideit <SvenDowideit@home.org.au>
168 169
 Thatcher Peskens <thatcher@dotcloud.com>
169 170
 Thermionix <bond711@gmail.com>
170 171
 Thijs Terlouw <thijsterlouw@gmail.com>
... ...
@@ -17,7 +17,6 @@
17 17
 + Prevent DNS server conflicts in CreateBridgeIface
18 18
 + Validate bind mounts on the server side
19 19
 + Use parent image config in docker build
20
-* Fix regression in /etc/hosts
21 20
 
22 21
 #### Client
23 22
 
... ...
@@ -1,11 +1,14 @@
1 1
 # Contributing to Docker
2 2
 
3
-Want to hack on Docker? Awesome! Here are instructions to get you started. They are probably not perfect, please let us know if anything feels
4
-wrong or incomplete.
3
+Want to hack on Docker? Awesome! Here are instructions to get you
4
+started. They are probably not perfect, please let us know if anything
5
+feels wrong or incomplete.
5 6
 
6 7
 ## Build Environment
7 8
 
8
-For instructions on setting up your development environment, please see our dedicated [dev environment setup docs](http://docs.docker.io/en/latest/contributing/devenvironment/).
9
+For instructions on setting up your development environment, please
10
+see our dedicated [dev environment setup
11
+docs](http://docs.docker.io/en/latest/contributing/devenvironment/).
9 12
 
10 13
 ## Contribution guidelines
11 14
 
... ...
@@ -36,7 +36,7 @@ run	apt-get install -y -q mercurial
36 36
 run	apt-get install -y -q build-essential libsqlite3-dev
37 37
 
38 38
 # Install Go
39
-run	curl -s https://go.googlecode.com/files/go1.2rc3.src.tar.gz | tar -v -C /usr/local -xz
39
+run	curl -s https://go.googlecode.com/files/go1.2rc4.src.tar.gz | tar -v -C /usr/local -xz
40 40
 env	PATH	/usr/local/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
41 41
 env	GOPATH	/go:/go/src/github.com/dotcloud/docker/vendor
42 42
 run	cd /usr/local/go/src && ./make.bash && go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std
... ...
@@ -8,35 +8,12 @@ by Keith Rarick, licensed under the MIT License.
8 8
 
9 9
 The following is courtesy of our legal counsel:
10 10
 
11
-Transfers of Docker shall be in accordance with applicable export
12
-controls of any country and all other applicable legal requirements.
13
-Docker shall not be distributed or downloaded to or in Cuba, Iran,
14
-North Korea, Sudan or Syria and shall not be distributed or downloaded
15
-to any person on the Denied Persons List administered by the U.S.
16
-Department of Commerce.
17 11
 
18
-What does that mean? 
19
-Here is a further explanation from our legal counsel:
12
+Use and transfer of Docker may be subject to certain restrictions by the
13
+United States and other governments.  
14
+It is your responsibility to ensure that your use and/or transfer does not
15
+violate applicable laws. 
20 16
 
21
-Like all software products that utilize cryptography, the export and
22
-use of Docker is subject to the U.S. Commerce Department's Export
23
-Administration Regulations (EAR) because it uses or contains
24
-cryptography (see
25
-http://www.bis.doc.gov/index.php/policy-guidance/encryption).  Certain
26
-free and open source software projects have a lightweight set of
27
-requirements, which can generally be met by providing email notice to
28
-the appropriate U.S. government agencies that their source code is
29
-available on a publicly available repository and making the
30
-appropriate statements in the README.
17
+For more information, please see http://www.bis.doc.gov
31 18
 
32
-The restrictions of the EAR apply to certain denied locations
33
-(currently Iran, Sudan, Syria, North Korea, or Cuba) and those
34
-individuals on the Denied Persons List, which is available here:
35
-http://www.bis.doc.gov/index.php/policy-guidance/lists-of-parties-of-concern/denied-persons-list.
36
-If you are incorporating Docker into a new open source project, the
37
-EAR restrictions apply to your incorporation of Docker into your
38
-project in the same manner as other cryptography-enabled projects,
39
-such as OpenSSL, almost all Linux distributions, etc.
40
-
41
-For more information, see http://www.apache.org/dev/crypto.html and/or
42
-seek legal counsel.
19
+See also http://www.apache.org/dev/crypto.html and/or seek legal counsel.
... ...
@@ -193,10 +193,9 @@ wrong or incomplete.
193 193
 *Brought to you courtesy of our legal counsel. For more context,
194 194
 please see the Notice document.*
195 195
 
196
-Transfers of Docker shall be in accordance with applicable export controls 
197
-of any country and all other applicable legal requirements. Without limiting the 
198
-foregoing, Docker shall not be distributed or downloaded to any individual or 
199
-location if such distribution or download would violate the applicable US 
200
-government export regulations. 
196
+Use and transfer of Docker may be subject to certain restrictions by the
197
+United States and other governments.  
198
+It is your responsibility to ensure that your use and/or transfer does not
199
+violate applicable laws. 
201 200
 
202 201
 For more information, please see http://www.bis.doc.gov
... ...
@@ -4,65 +4,135 @@
4 4
 BOX_NAME = ENV['BOX_NAME'] || "ubuntu"
5 5
 BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64.box"
6 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"
7 8
 AWS_REGION = ENV['AWS_REGION'] || "us-east-1"
8
-AWS_AMI    = ENV['AWS_AMI']    || "ami-d0f89fb9"
9
+AWS_AMI = ENV['AWS_AMI'] || "ami-69f5a900"
10
+AWS_INSTANCE_TYPE = ENV['AWS_INSTANCE_TYPE'] || 't1.micro'
11
+
9 12
 FORWARD_DOCKER_PORTS = ENV['FORWARD_DOCKER_PORTS']
10 13
 
14
+SSH_PRIVKEY_PATH = ENV["SSH_PRIVKEY_PATH"]
15
+
16
+# A script to upgrade from the 12.04 kernel to the raring backport kernel (3.8)
17
+# and install docker.
18
+$script = <<SCRIPT
19
+# The username to add to the docker group will be passed as the first argument
20
+# to the script.  If nothing is passed, default to "vagrant".
21
+user="$1"
22
+if [ -z "$user" ]; then
23
+    user=vagrant
24
+fi
25
+
26
+# Adding an apt gpg key is idempotent.
27
+wget -q -O - https://get.docker.io/gpg | apt-key add -
28
+
29
+# Creating the docker.list file is idempotent, but it may overrite desired
30
+# settings if it already exists.  This could be solved with md5sum but it
31
+# doesn't seem worth it.
32
+echo 'deb http://get.docker.io/ubuntu docker main' > \
33
+    /etc/apt/sources.list.d/docker.list
34
+
35
+# Update remote package metadata.  'apt-get update' is idempotent.
36
+apt-get update -q
37
+
38
+# Install docker.  'apt-get install' is idempotent.
39
+apt-get install -q -y lxc-docker
40
+
41
+usermod -a -G docker "$user"
42
+
43
+tmp=`mktemp -q` && {
44
+    # Only install the backport kernel, don't bother upgrade if the backport is
45
+    # already installed.  We want parse the output of apt so we need to save it
46
+    # with 'tee'.  NOTE: The installation of the kernel will trigger dkms to
47
+    # install vboxguest if needed.
48
+    apt-get install -q -y --no-upgrade linux-image-generic-lts-raring | \
49
+        tee "$tmp"
50
+
51
+    # Parse the number of installed packages from the output
52
+    NUM_INST=`awk '$2 == "upgraded," && $4 == "newly" { print $3 }' "$tmp"`
53
+    rm "$tmp"
54
+}
55
+
56
+# If the number of installed packages is greater than 0, we want to reboot (the
57
+# backport kernel was installed but is not running).
58
+if [ "$NUM_INST" -gt 0 ];
59
+then
60
+    echo "Rebooting down to activate new kernel."
61
+    echo "/vagrant will not be mounted.  Use 'vagrant halt' followed by"
62
+    echo "'vagrant up' to ensure /vagrant is mounted."
63
+    shutdown -r now
64
+fi
65
+SCRIPT
66
+
67
+# We need to install the virtualbox guest additions *before* we do the normal
68
+# docker installation.  As such this script is prepended to the common docker
69
+# install script above.  This allows the install of the backport kernel to
70
+# trigger dkms to build the virtualbox guest module install.
71
+$vbox_script = <<VBOX_SCRIPT + $script
72
+# Install the VirtualBox guest additions if they aren't already installed.
73
+if [ ! -d /opt/VBoxGuestAdditions-4.2.12/ ]; then
74
+    # Update remote package metadata.  'apt-get update' is idempotent.
75
+    apt-get update -q
76
+
77
+    # Kernel Headers and dkms are required to build the vbox guest kernel
78
+    # modules.
79
+    apt-get install -q -y linux-headers-generic-lts-raring dkms
80
+
81
+    echo 'Downloading VBox Guest Additions...'
82
+    wget -cq http://dlc.sun.com.edgesuite.net/virtualbox/4.2.12/VBoxGuestAdditions_4.2.12.iso
83
+
84
+    mount -o loop,ro /home/vagrant/VBoxGuestAdditions_4.2.12.iso /mnt
85
+    /mnt/VBoxLinuxAdditions.run --nox11
86
+    umount /mnt
87
+fi
88
+VBOX_SCRIPT
89
+
11 90
 Vagrant::Config.run do |config|
12 91
   # Setup virtual machine box. This VM configuration code is always executed.
13 92
   config.vm.box = BOX_NAME
14 93
   config.vm.box_url = BOX_URI
15 94
 
16
-  config.ssh.forward_agent = true
17
-
18
-  # Provision docker and new kernel if deployment was not done.
19
-  # It is assumed Vagrant can successfully launch the provider instance.
20
-  if Dir.glob("#{File.dirname(__FILE__)}/.vagrant/machines/default/*/id").empty?
21
-    # Add lxc-docker package
22
-    pkg_cmd = "wget -q -O - https://get.docker.io/gpg | apt-key add -;" \
23
-      "echo deb http://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list;" \
24
-      "apt-get update -qq; apt-get install -q -y --force-yes lxc-docker; "
25
-    # Add Ubuntu raring backported kernel
26
-    pkg_cmd << "apt-get update -qq; apt-get install -q -y linux-image-generic-lts-raring; "
27
-    # Add guest additions if local vbox VM. As virtualbox is the default provider,
28
-    # it is assumed it won't be explicitly stated.
29
-    if ENV["VAGRANT_DEFAULT_PROVIDER"].nil? && ARGV.none? { |arg| arg.downcase.start_with?("--provider") }
30
-      pkg_cmd << "apt-get install -q -y linux-headers-generic-lts-raring dkms; " \
31
-        "echo 'Downloading VBox Guest Additions...'; " \
32
-        "wget -q http://dlc.sun.com.edgesuite.net/virtualbox/4.2.12/VBoxGuestAdditions_4.2.12.iso; "
33
-      # Prepare the VM to add guest additions after reboot
34
-      pkg_cmd << "echo -e 'mount -o loop,ro /home/vagrant/VBoxGuestAdditions_4.2.12.iso /mnt\n" \
35
-        "echo yes | /mnt/VBoxLinuxAdditions.run\numount /mnt\n" \
36
-          "rm /root/guest_additions.sh; ' > /root/guest_additions.sh; " \
37
-        "chmod 700 /root/guest_additions.sh; " \
38
-        "sed -i -E 's#^exit 0#[ -x /root/guest_additions.sh ] \\&\\& /root/guest_additions.sh#' /etc/rc.local; " \
39
-        "echo 'Installation of VBox Guest Additions is proceeding in the background.'; " \
40
-        "echo '\"vagrant reload\" can be used in about 2 minutes to activate the new guest additions.'; "
41
-    end
42
-    # Add vagrant user to the docker group
43
-    pkg_cmd << "usermod -a -G docker vagrant; "
44
-    # Activate new kernel
45
-    pkg_cmd << "shutdown -r +1; "
46
-    config.vm.provision :shell, :inline => pkg_cmd
95
+  # Use the specified private key path if it is specified and not empty.
96
+  if SSH_PRIVKEY_PATH
97
+      config.ssh.private_key_path = SSH_PRIVKEY_PATH
47 98
   end
48
-end
49 99
 
100
+  config.ssh.forward_agent = true
101
+end
50 102
 
51 103
 # Providers were added on Vagrant >= 1.1.0
104
+#
105
+# NOTE: The vagrant "vm.provision" appends its arguments to a list and executes
106
+# them in order.  If you invoke "vm.provision :shell, :inline => $script"
107
+# twice then vagrant will run the script two times.  Unfortunately when you use
108
+# providers and the override argument to set up provisioners (like the vbox
109
+# guest extensions) they 1) don't replace the other provisioners (they append
110
+# to the end of the list) and 2) you can't control the order the provisioners
111
+# are executed (you can only append to the list).  If you want the virtualbox
112
+# only script to run before the other script, you have to jump through a lot of
113
+# hoops.
114
+#
115
+# Here is my only repeatable solution: make one script that is common ($script)
116
+# and another script that is the virtual box guest *prepended* to the common
117
+# script.  Only ever use "vm.provision" *one time* per provider.  That means
118
+# every single provider has an override, and every single one configures
119
+# "vm.provision".  Much saddness, but such is life.
52 120
 Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
53 121
   config.vm.provider :aws do |aws, override|
54
-    aws.access_key_id = ENV["AWS_ACCESS_KEY_ID"]
55
-    aws.secret_access_key = ENV["AWS_SECRET_ACCESS_KEY"]
122
+    username = "ubuntu"
123
+    override.vm.box_url = AWS_BOX_URI
124
+    override.vm.provision :shell, :inline => $script, :args => username
125
+    aws.access_key_id = ENV["AWS_ACCESS_KEY"]
126
+    aws.secret_access_key = ENV["AWS_SECRET_KEY"]
56 127
     aws.keypair_name = ENV["AWS_KEYPAIR_NAME"]
57
-    override.ssh.private_key_path = ENV["AWS_SSH_PRIVKEY"]
58
-    override.ssh.username = "ubuntu"
128
+    override.ssh.username = username
59 129
     aws.region = AWS_REGION
60 130
     aws.ami    = AWS_AMI
61
-    aws.instance_type = "t1.micro"
131
+    aws.instance_type = AWS_INSTANCE_TYPE
62 132
   end
63 133
 
64
-  config.vm.provider :rackspace do |rs|
65
-    config.ssh.private_key_path = ENV["RS_PRIVATE_KEY"]
134
+  config.vm.provider :rackspace do |rs, override|
135
+    override.vm.provision :shell, :inline => $script
66 136
     rs.username = ENV["RS_USERNAME"]
67 137
     rs.api_key  = ENV["RS_API_KEY"]
68 138
     rs.public_key_path = ENV["RS_PUBLIC_KEY"]
... ...
@@ -71,20 +141,25 @@ Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
71 71
   end
72 72
 
73 73
   config.vm.provider :vmware_fusion do |f, override|
74
-    override.vm.box = BOX_NAME
75 74
     override.vm.box_url = VF_BOX_URI
76 75
     override.vm.synced_folder ".", "/vagrant", disabled: true
76
+    override.vm.provision :shell, :inline => $script
77 77
     f.vmx["displayName"] = "docker"
78 78
   end
79 79
 
80
-  config.vm.provider :virtualbox do |vb|
81
-    config.vm.box = BOX_NAME
82
-    config.vm.box_url = BOX_URI
80
+  config.vm.provider :virtualbox do |vb, override|
81
+    override.vm.provision :shell, :inline => $vbox_script
83 82
     vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
84 83
     vb.customize ["modifyvm", :id, "--natdnsproxy1", "on"]
85 84
   end
86 85
 end
87 86
 
87
+# If this is a version 1 config, virtualbox is the only option.  A version 2
88
+# config would have already been set in the above provider section.
89
+Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config|
90
+  config.vm.provision :shell, :inline => $vbox_script
91
+end
92
+
88 93
 if !FORWARD_DOCKER_PORTS.nil?
89 94
   Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config|
90 95
     (49000..49900).each do |port|
... ...
@@ -479,15 +479,16 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht
479 479
 		w.Header().Set("Content-Type", "application/json")
480 480
 	}
481 481
 	sf := utils.NewStreamFormatter(version > 1.0)
482
-	imgID, err := srv.ImageInsert(name, url, path, w, sf)
482
+	err := srv.ImageInsert(name, url, path, w, sf)
483 483
 	if err != nil {
484 484
 		if sf.Used() {
485 485
 			w.Write(sf.FormatError(err))
486 486
 			return nil
487 487
 		}
488
+		return err
488 489
 	}
489 490
 
490
-	return writeJSON(w, http.StatusOK, &APIID{ID: imgID})
491
+	return nil
491 492
 }
492 493
 
493 494
 func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
... ...
@@ -540,43 +541,36 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r
540 540
 	if err := parseForm(r); err != nil {
541 541
 		return nil
542 542
 	}
543
-	config := &Config{}
544 543
 	out := &APIRun{}
545
-	name := r.Form.Get("name")
546
-
547
-	if err := json.NewDecoder(r.Body).Decode(config); err != nil {
544
+	job := srv.Eng.Job("create", r.Form.Get("name"))
545
+	if err := job.DecodeEnv(r.Body); err != nil {
548 546
 		return err
549 547
 	}
550
-
551 548
 	resolvConf, err := utils.GetResolvConf()
552 549
 	if err != nil {
553 550
 		return err
554 551
 	}
555
-
556
-	if !config.NetworkDisabled && len(config.Dns) == 0 && len(srv.runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
552
+	if !job.GetenvBool("NetworkDisabled") && len(job.Getenv("Dns")) == 0 && len(srv.runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
557 553
 		out.Warnings = append(out.Warnings, fmt.Sprintf("Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns))
558
-		config.Dns = defaultDns
554
+		job.SetenvList("Dns", defaultDns)
559 555
 	}
560
-
561
-	id, warnings, err := srv.ContainerCreate(config, name)
562
-	if err != nil {
556
+	// Read container ID from the first line of stdout
557
+	job.StdoutParseString(&out.ID)
558
+	// Read warnings from stderr
559
+	job.StderrParseLines(&out.Warnings, 0)
560
+	if err := job.Run(); err != nil {
563 561
 		return err
564 562
 	}
565
-	out.ID = id
566
-	for _, warning := range warnings {
567
-		out.Warnings = append(out.Warnings, warning)
568
-	}
569
-
570
-	if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
563
+	if job.GetenvInt("Memory") > 0 && !srv.runtime.capabilities.MemoryLimit {
571 564
 		log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.")
572 565
 		out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.")
573 566
 	}
574
-	if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit {
567
+	if job.GetenvInt("Memory") > 0 && !srv.runtime.capabilities.SwapLimit {
575 568
 		log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.")
576 569
 		out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.")
577 570
 	}
578 571
 
579
-	if !config.NetworkDisabled && srv.runtime.capabilities.IPv4ForwardingDisabled {
572
+	if !job.GetenvBool("NetworkDisabled") && srv.runtime.capabilities.IPv4ForwardingDisabled {
580 573
 		log.Println("Warning: IPv4 forwarding is disabled.")
581 574
 		out.Warnings = append(out.Warnings, "IPv4 forwarding is disabled.")
582 575
 	}
... ...
@@ -653,26 +647,23 @@ func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.R
653 653
 }
654 654
 
655 655
 func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
656
-	var hostConfig *HostConfig
656
+	if vars == nil {
657
+		return fmt.Errorf("Missing parameter")
658
+	}
659
+	name := vars["name"]
660
+	job := srv.Eng.Job("start", name)
661
+	if err := job.ImportEnv(HostConfig{}); err != nil {
662
+		return fmt.Errorf("Couldn't initialize host configuration")
663
+	}
657 664
 	// allow a nil body for backwards compatibility
658 665
 	if r.Body != nil {
659 666
 		if matchesContentType(r.Header.Get("Content-Type"), "application/json") {
660
-			hostConfig = &HostConfig{}
661
-			if err := json.NewDecoder(r.Body).Decode(hostConfig); err != nil {
667
+			if err := job.DecodeEnv(r.Body); err != nil {
662 668
 				return err
663 669
 			}
664 670
 		}
665 671
 	}
666
-
667
-	if vars == nil {
668
-		return fmt.Errorf("Missing parameter")
669
-	}
670
-	name := vars["name"]
671
-	// Register any links from the host config before starting the container
672
-	if err := srv.RegisterLinks(name, hostConfig); err != nil {
673
-		return err
674
-	}
675
-	if err := srv.ContainerStart(name, hostConfig); err != nil {
672
+	if err := job.Run(); err != nil {
676 673
 		return err
677 674
 	}
678 675
 	w.WriteHeader(http.StatusNoContent)
... ...
@@ -609,11 +609,11 @@ func TestPostCommit(t *testing.T) {
609 609
 }
610 610
 
611 611
 func TestPostContainersCreate(t *testing.T) {
612
-	runtime := mkRuntime(t)
612
+	eng := NewTestEngine(t)
613
+	srv := mkServerFromEngine(eng, t)
614
+	runtime := srv.runtime
613 615
 	defer nuke(runtime)
614 616
 
615
-	srv := &Server{runtime: runtime}
616
-
617 617
 	configJSON, err := json.Marshal(&Config{
618 618
 		Image:  GetTestImage(runtime).ID,
619 619
 		Memory: 33554432,
... ...
@@ -756,27 +756,23 @@ func TestPostContainersRestart(t *testing.T) {
756 756
 }
757 757
 
758 758
 func TestPostContainersStart(t *testing.T) {
759
-	runtime := mkRuntime(t)
759
+	eng := NewTestEngine(t)
760
+	srv := mkServerFromEngine(eng, t)
761
+	runtime := srv.runtime
760 762
 	defer nuke(runtime)
761 763
 
762
-	srv := &Server{runtime: runtime}
763
-
764
-	container, _, err := runtime.Create(
764
+	id := createTestContainer(
765
+		eng,
765 766
 		&Config{
766 767
 			Image:     GetTestImage(runtime).ID,
767 768
 			Cmd:       []string{"/bin/cat"},
768 769
 			OpenStdin: true,
769 770
 		},
770
-		"",
771
-	)
772
-	if err != nil {
773
-		t.Fatal(err)
774
-	}
775
-	defer runtime.Destroy(container)
771
+		t)
776 772
 
777 773
 	hostConfigJSON, err := json.Marshal(&HostConfig{})
778 774
 
779
-	req, err := http.NewRequest("POST", "/containers/"+container.ID+"/start", bytes.NewReader(hostConfigJSON))
775
+	req, err := http.NewRequest("POST", "/containers/"+id+"/start", bytes.NewReader(hostConfigJSON))
780 776
 	if err != nil {
781 777
 		t.Fatal(err)
782 778
 	}
... ...
@@ -784,22 +780,26 @@ func TestPostContainersStart(t *testing.T) {
784 784
 	req.Header.Set("Content-Type", "application/json")
785 785
 
786 786
 	r := httptest.NewRecorder()
787
-	if err := postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil {
787
+	if err := postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": id}); err != nil {
788 788
 		t.Fatal(err)
789 789
 	}
790 790
 	if r.Code != http.StatusNoContent {
791 791
 		t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code)
792 792
 	}
793 793
 
794
+	container := runtime.Get(id)
795
+	if container == nil {
796
+		t.Fatalf("Container %s was not created", id)
797
+	}
794 798
 	// Give some time to the process to start
799
+	// FIXME: use Wait once it's available as a job
795 800
 	container.WaitTimeout(500 * time.Millisecond)
796
-
797 801
 	if !container.State.Running {
798 802
 		t.Errorf("Container should be running")
799 803
 	}
800 804
 
801 805
 	r = httptest.NewRecorder()
802
-	if err = postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err == nil {
806
+	if err = postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": id}); err == nil {
803 807
 		t.Fatalf("A running container should be able to be started")
804 808
 	}
805 809
 
... ...
@@ -544,10 +544,7 @@ func TestBuildADDFileNotFound(t *testing.T) {
544 544
 }
545 545
 
546 546
 func TestBuildInheritance(t *testing.T) {
547
-	runtime, err := newTestRuntime("")
548
-	if err != nil {
549
-		t.Fatal(err)
550
-	}
547
+	runtime := mkRuntime(t)
551 548
 	defer nuke(runtime)
552 549
 
553 550
 	srv := &Server{
... ...
@@ -130,10 +130,7 @@ func (cli *DockerCli) CmdInsert(args ...string) error {
130 130
 	v.Set("url", cmd.Arg(1))
131 131
 	v.Set("path", cmd.Arg(2))
132 132
 
133
-	if err := cli.stream("POST", "/images/"+cmd.Arg(0)+"/insert?"+v.Encode(), nil, cli.out, nil); err != nil {
134
-		return err
135
-	}
136
-	return nil
133
+	return cli.stream("POST", "/images/"+cmd.Arg(0)+"/insert?"+v.Encode(), nil, cli.out, nil)
137 134
 }
138 135
 
139 136
 // mkBuildContext returns an archive of an empty context with the contents
... ...
@@ -376,15 +373,17 @@ func (cli *DockerCli) CmdWait(args ...string) error {
376 376
 		cmd.Usage()
377 377
 		return nil
378 378
 	}
379
+	var encounteredError error
379 380
 	for _, name := range cmd.Args() {
380 381
 		status, err := waitForExit(cli, name)
381 382
 		if err != nil {
382
-			fmt.Fprintf(cli.err, "%s", err)
383
+			fmt.Fprintf(cli.err, "%s\n", err)
384
+			encounteredError = fmt.Errorf("Error: failed to wait one or more containers")
383 385
 		} else {
384 386
 			fmt.Fprintf(cli.out, "%d\n", status)
385 387
 		}
386 388
 	}
387
-	return nil
389
+	return encounteredError
388 390
 }
389 391
 
390 392
 // 'docker version': show version information
... ...
@@ -505,15 +504,17 @@ func (cli *DockerCli) CmdStop(args ...string) error {
505 505
 	v := url.Values{}
506 506
 	v.Set("t", strconv.Itoa(*nSeconds))
507 507
 
508
+	var encounteredError error
508 509
 	for _, name := range cmd.Args() {
509 510
 		_, _, err := cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil)
510 511
 		if err != nil {
511 512
 			fmt.Fprintf(cli.err, "%s\n", err)
513
+			encounteredError = fmt.Errorf("Error: failed to stop one or more containers")
512 514
 		} else {
513 515
 			fmt.Fprintf(cli.out, "%s\n", name)
514 516
 		}
515 517
 	}
516
-	return nil
518
+	return encounteredError
517 519
 }
518 520
 
519 521
 func (cli *DockerCli) CmdRestart(args ...string) error {
... ...
@@ -530,15 +531,17 @@ func (cli *DockerCli) CmdRestart(args ...string) error {
530 530
 	v := url.Values{}
531 531
 	v.Set("t", strconv.Itoa(*nSeconds))
532 532
 
533
+	var encounteredError error
533 534
 	for _, name := range cmd.Args() {
534 535
 		_, _, err := cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil)
535 536
 		if err != nil {
536 537
 			fmt.Fprintf(cli.err, "%s\n", err)
538
+			encounteredError = fmt.Errorf("Error: failed to  restart one or more containers")
537 539
 		} else {
538 540
 			fmt.Fprintf(cli.out, "%s\n", name)
539 541
 		}
540 542
 	}
541
-	return nil
543
+	return encounteredError
542 544
 }
543 545
 
544 546
 func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
... ...
@@ -772,15 +775,19 @@ func (cli *DockerCli) CmdRmi(args ...string) error {
772 772
 		return nil
773 773
 	}
774 774
 
775
+	var encounteredError error
775 776
 	for _, name := range cmd.Args() {
776 777
 		body, _, err := cli.call("DELETE", "/images/"+name, nil)
777 778
 		if err != nil {
778
-			fmt.Fprintf(cli.err, "%s", err)
779
+			fmt.Fprintf(cli.err, "%s\n", err)
780
+			encounteredError = fmt.Errorf("Error: failed to remove one or more images")
779 781
 		} else {
780 782
 			var outs []APIRmi
781 783
 			err = json.Unmarshal(body, &outs)
782 784
 			if err != nil {
783
-				return err
785
+				fmt.Fprintf(cli.err, "%s\n", err)
786
+				encounteredError = fmt.Errorf("Error: failed to remove one or more images")
787
+				continue
784 788
 			}
785 789
 			for _, out := range outs {
786 790
 				if out.Deleted != "" {
... ...
@@ -791,7 +798,7 @@ func (cli *DockerCli) CmdRmi(args ...string) error {
791 791
 			}
792 792
 		}
793 793
 	}
794
-	return nil
794
+	return encounteredError
795 795
 }
796 796
 
797 797
 func (cli *DockerCli) CmdHistory(args ...string) error {
... ...
@@ -870,15 +877,18 @@ func (cli *DockerCli) CmdRm(args ...string) error {
870 870
 	if *link {
871 871
 		val.Set("link", "1")
872 872
 	}
873
+
874
+	var encounteredError error
873 875
 	for _, name := range cmd.Args() {
874 876
 		_, _, err := cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil)
875 877
 		if err != nil {
876 878
 			fmt.Fprintf(cli.err, "%s\n", err)
879
+			encounteredError = fmt.Errorf("Error: failed to remove one or more containers")
877 880
 		} else {
878 881
 			fmt.Fprintf(cli.out, "%s\n", name)
879 882
 		}
880 883
 	}
881
-	return nil
884
+	return encounteredError
882 885
 }
883 886
 
884 887
 // 'docker kill NAME' kills a running container
... ...
@@ -892,15 +902,16 @@ func (cli *DockerCli) CmdKill(args ...string) error {
892 892
 		return nil
893 893
 	}
894 894
 
895
+	var encounteredError error
895 896
 	for _, name := range args {
896
-		_, _, err := cli.call("POST", "/containers/"+name+"/kill", nil)
897
-		if err != nil {
897
+		if _, _, err := cli.call("POST", "/containers/"+name+"/kill", nil); err != nil {
898 898
 			fmt.Fprintf(cli.err, "%s\n", err)
899
+			encounteredError = fmt.Errorf("Error: failed to kill one or more containers")
899 900
 		} else {
900 901
 			fmt.Fprintf(cli.out, "%s\n", name)
901 902
 		}
902 903
 	}
903
-	return nil
904
+	return encounteredError
904 905
 }
905 906
 
906 907
 func (cli *DockerCli) CmdImport(args ...string) error {
... ...
@@ -913,8 +924,16 @@ func (cli *DockerCli) CmdImport(args ...string) error {
913 913
 		cmd.Usage()
914 914
 		return nil
915 915
 	}
916
-	src := cmd.Arg(0)
917
-	repository, tag := utils.ParseRepositoryTag(cmd.Arg(1))
916
+
917
+	var src, repository, tag string
918
+
919
+	if cmd.NArg() == 3 {
920
+		fmt.Fprintf(cli.err, "[DEPRECATED] The format 'URL|- [REPOSITORY [TAG]]' as been deprecated. Please use URL|- [REPOSITORY[:TAG]]\n")
921
+		src, repository, tag = cmd.Arg(0), cmd.Arg(1), cmd.Arg(2)
922
+	} else {
923
+		src = cmd.Arg(0)
924
+		repository, tag = utils.ParseRepositoryTag(cmd.Arg(1))
925
+	}
918 926
 	v := url.Values{}
919 927
 	v.Set("repo", repository)
920 928
 	v.Set("tag", tag)
... ...
@@ -1166,14 +1185,10 @@ func (cli *DockerCli) CmdImages(args ...string) error {
1166 1166
 			fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tSIZE")
1167 1167
 		}
1168 1168
 
1169
-		var repo string
1170
-		var tag string
1171 1169
 		for _, out := range outs {
1172 1170
 			for _, repotag := range out.RepoTags {
1173 1171
 
1174
-				components := strings.SplitN(repotag, ":", 2)
1175
-				repo = components[0]
1176
-				tag = components[1]
1172
+				repo, tag := utils.ParseRepositoryTag(repotag)
1177 1173
 
1178 1174
 				if !*noTrunc {
1179 1175
 					out.ID = utils.TruncateID(out.ID)
... ...
@@ -1235,7 +1250,7 @@ func PrintTreeNode(cli *DockerCli, noTrunc *bool, image APIImages, prefix string
1235 1235
 
1236 1236
 	fmt.Fprintf(cli.out, "%s%s Size: %s (virtual %s)", prefix, imageID, utils.HumanSize(image.Size), utils.HumanSize(image.VirtualSize))
1237 1237
 	if image.RepoTags[0] != "<none>:<none>" {
1238
-		fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.RepoTags, ","))
1238
+		fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.RepoTags, ", "))
1239 1239
 	} else {
1240 1240
 		fmt.Fprint(cli.out, "\n")
1241 1241
 	}
... ...
@@ -1351,8 +1366,16 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
1351 1351
 	if err := cmd.Parse(args); err != nil {
1352 1352
 		return nil
1353 1353
 	}
1354
-	name := cmd.Arg(0)
1355
-	repository, tag := utils.ParseRepositoryTag(cmd.Arg(1))
1354
+
1355
+	var name, repository, tag string
1356
+
1357
+	if cmd.NArg() == 3 {
1358
+		fmt.Fprintf(cli.err, "[DEPRECATED] The format 'CONTAINER [REPOSITORY [TAG]]' as been deprecated. Please use CONTAINER [REPOSITORY[:TAG]]\n")
1359
+		name, repository, tag = cmd.Arg(0), cmd.Arg(1), cmd.Arg(2)
1360
+	} else {
1361
+		name = cmd.Arg(0)
1362
+		repository, tag = utils.ParseRepositoryTag(cmd.Arg(1))
1363
+	}
1356 1364
 
1357 1365
 	if name == "" {
1358 1366
 		cmd.Usage()
... ...
@@ -1389,7 +1412,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
1389 1389
 
1390 1390
 func (cli *DockerCli) CmdEvents(args ...string) error {
1391 1391
 	cmd := Subcmd("events", "[OPTIONS]", "Get real time events from the server")
1392
-	since := cmd.String("since", "", "Show events previously created (used for polling).")
1392
+	since := cmd.String("since", "", "Show previously created events and then stream.")
1393 1393
 	if err := cmd.Parse(args); err != nil {
1394 1394
 		return nil
1395 1395
 	}
... ...
@@ -1401,7 +1424,17 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
1401 1401
 
1402 1402
 	v := url.Values{}
1403 1403
 	if *since != "" {
1404
-		v.Set("since", *since)
1404
+		loc := time.FixedZone(time.Now().Zone())
1405
+		format := "2006-01-02 15:04:05 -0700 MST"
1406
+		if len(*since) < len(format) {
1407
+			format = format[:len(*since)]
1408
+		}
1409
+
1410
+		if t, err := time.ParseInLocation(format, *since, loc); err == nil {
1411
+			v.Set("since", strconv.FormatInt(t.Unix(), 10))
1412
+		} else {
1413
+			v.Set("since", *since)
1414
+		}
1405 1415
 	}
1406 1416
 
1407 1417
 	if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out, nil); err != nil {
... ...
@@ -1658,9 +1691,16 @@ func (cli *DockerCli) CmdTag(args ...string) error {
1658 1658
 		return nil
1659 1659
 	}
1660 1660
 
1661
-	v := url.Values{}
1662
-	repository, tag := utils.ParseRepositoryTag(cmd.Arg(1))
1661
+	var repository, tag string
1663 1662
 
1663
+	if cmd.NArg() == 3 {
1664
+		fmt.Fprintf(cli.err, "[DEPRECATED] The format 'IMAGE [REPOSITORY [TAG]]' as been deprecated. Please use IMAGE [REPOSITORY[:TAG]]\n")
1665
+		repository, tag = cmd.Arg(1), cmd.Arg(2)
1666
+	} else {
1667
+		repository, tag = utils.ParseRepositoryTag(cmd.Arg(1))
1668
+	}
1669
+
1670
+	v := url.Values{}
1664 1671
 	v.Set("repo", repository)
1665 1672
 	v.Set("tag", tag)
1666 1673
 
... ...
@@ -1971,7 +2011,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
1971 1971
 		if len(body) == 0 {
1972 1972
 			return nil, resp.StatusCode, fmt.Errorf("Error: %s", http.StatusText(resp.StatusCode))
1973 1973
 		}
1974
-		return nil, resp.StatusCode, fmt.Errorf("Error: %s", body)
1974
+		return nil, resp.StatusCode, fmt.Errorf("Error: %s", bytes.TrimSpace(body))
1975 1975
 	}
1976 1976
 	return body, resp.StatusCode, nil
1977 1977
 }
... ...
@@ -2027,7 +2067,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h
2027 2027
 		if len(body) == 0 {
2028 2028
 			return fmt.Errorf("Error :%s", http.StatusText(resp.StatusCode))
2029 2029
 		}
2030
-		return fmt.Errorf("Error: %s", body)
2030
+		return fmt.Errorf("Error: %s", bytes.TrimSpace(body))
2031 2031
 	}
2032 2032
 
2033 2033
 	if matchesContentType(resp.Header.Get("Content-Type"), "application/json") {
... ...
@@ -6,6 +6,8 @@ import (
6 6
 	"github.com/dotcloud/docker/utils"
7 7
 	"io"
8 8
 	"io/ioutil"
9
+	"os"
10
+	"path"
9 11
 	"regexp"
10 12
 	"strings"
11 13
 	"testing"
... ...
@@ -381,8 +383,8 @@ func TestRunAttachStdin(t *testing.T) {
381 381
 		if err != nil {
382 382
 			t.Fatal(err)
383 383
 		}
384
-		if cmdOutput != container.ShortID()+"\n" {
385
-			t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortID()+"\n", cmdOutput)
384
+		if cmdOutput != container.ID+"\n" {
385
+			t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ID+"\n", cmdOutput)
386 386
 		}
387 387
 	})
388 388
 
... ...
@@ -459,7 +461,7 @@ func TestRunDetach(t *testing.T) {
459 459
 	})
460 460
 }
461 461
 
462
-// TestAttachDetach checks that attach in tty mode can be detached
462
+// TestAttachDetach checks that attach in tty mode can be detached using the long container ID
463 463
 func TestAttachDetach(t *testing.T) {
464 464
 	stdin, stdinPipe := io.Pipe()
465 465
 	stdout, stdoutPipe := io.Pipe()
... ...
@@ -486,8 +488,8 @@ func TestAttachDetach(t *testing.T) {
486 486
 
487 487
 		container = globalRuntime.List()[0]
488 488
 
489
-		if strings.Trim(string(buf[:n]), " \r\n") != container.ShortID() {
490
-			t.Fatalf("Wrong ID received. Expect %s, received %s", container.ShortID(), buf[:n])
489
+		if strings.Trim(string(buf[:n]), " \r\n") != container.ID {
490
+			t.Fatalf("Wrong ID received. Expect %s, received %s", container.ID, buf[:n])
491 491
 		}
492 492
 	})
493 493
 	setTimeout(t, "Starting container timed out", 10*time.Second, func() {
... ...
@@ -501,7 +503,69 @@ func TestAttachDetach(t *testing.T) {
501 501
 	ch = make(chan struct{})
502 502
 	go func() {
503 503
 		defer close(ch)
504
-		if err := cli.CmdAttach(container.ShortID()); err != nil {
504
+		if err := cli.CmdAttach(container.ID); err != nil {
505
+			if err != io.ErrClosedPipe {
506
+				t.Fatal(err)
507
+			}
508
+		}
509
+	}()
510
+
511
+	setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() {
512
+		if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
513
+			if err != io.ErrClosedPipe {
514
+				t.Fatal(err)
515
+			}
516
+		}
517
+	})
518
+
519
+	setTimeout(t, "Escape sequence timeout", 5*time.Second, func() {
520
+		stdinPipe.Write([]byte{16, 17})
521
+		if err := stdinPipe.Close(); err != nil {
522
+			t.Fatal(err)
523
+		}
524
+	})
525
+	closeWrap(stdin, stdinPipe, stdout, stdoutPipe)
526
+
527
+	// wait for CmdRun to return
528
+	setTimeout(t, "Waiting for CmdAttach timed out", 15*time.Second, func() {
529
+		<-ch
530
+	})
531
+
532
+	time.Sleep(500 * time.Millisecond)
533
+	if !container.State.Running {
534
+		t.Fatal("The detached container should be still running")
535
+	}
536
+
537
+	setTimeout(t, "Waiting for container to die timedout", 5*time.Second, func() {
538
+		container.Kill()
539
+	})
540
+}
541
+
542
+// TestAttachDetachTruncatedID checks that attach in tty mode can be detached
543
+func TestAttachDetachTruncatedID(t *testing.T) {
544
+	stdin, stdinPipe := io.Pipe()
545
+	stdout, stdoutPipe := io.Pipe()
546
+
547
+	cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
548
+	defer cleanup(globalRuntime)
549
+
550
+	go stdout.Read(make([]byte, 1024))
551
+	setTimeout(t, "Starting container timed out", 2*time.Second, func() {
552
+		if err := cli.CmdRun("-i", "-t", "-d", unitTestImageID, "cat"); err != nil {
553
+			t.Fatal(err)
554
+		}
555
+	})
556
+
557
+	container := globalRuntime.List()[0]
558
+
559
+	stdin, stdinPipe = io.Pipe()
560
+	stdout, stdoutPipe = io.Pipe()
561
+	cli = NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
562
+
563
+	ch := make(chan struct{})
564
+	go func() {
565
+		defer close(ch)
566
+		if err := cli.CmdAttach(utils.TruncateID(container.ID)); err != nil {
505 567
 			if err != io.ErrClosedPipe {
506 568
 				t.Fatal(err)
507 569
 			}
... ...
@@ -824,3 +888,55 @@ run    [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
824 824
 
825 825
 	return image
826 826
 }
827
+
828
+// #2098 - Docker cidFiles only contain short version of the containerId
829
+//sudo docker run -cidfile /tmp/docker_test.cid ubuntu echo "test"
830
+// TestRunCidFile tests that run -cidfile returns the longid
831
+func TestRunCidFile(t *testing.T) {
832
+	stdout, stdoutPipe := io.Pipe()
833
+
834
+	tmpDir, err := ioutil.TempDir("", "TestRunCidFile")
835
+	if err != nil {
836
+		t.Fatal(err)
837
+	}
838
+	tmpCidFile := path.Join(tmpDir, "cid")
839
+
840
+	cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
841
+	defer cleanup(globalRuntime)
842
+
843
+	c := make(chan struct{})
844
+	go func() {
845
+		defer close(c)
846
+		if err := cli.CmdRun("-cidfile", tmpCidFile, unitTestImageID, "ls"); err != nil {
847
+			t.Fatal(err)
848
+		}
849
+	}()
850
+
851
+	defer os.RemoveAll(tmpDir)
852
+	setTimeout(t, "Reading command output time out", 2*time.Second, func() {
853
+		cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
854
+		if err != nil {
855
+			t.Fatal(err)
856
+		}
857
+		if len(cmdOutput) < 1 {
858
+			t.Fatalf("'ls' should return something , not '%s'", cmdOutput)
859
+		}
860
+		//read the tmpCidFile
861
+		buffer, err := ioutil.ReadFile(tmpCidFile)
862
+		if err != nil {
863
+			t.Fatal(err)
864
+		}
865
+		id := string(buffer)
866
+
867
+		if len(id) != len("2bf44ea18873287bd9ace8a4cb536a7cbe134bed67e805fdf2f58a57f69b320c") {
868
+			t.Fatalf("-cidfile should be a long id, not '%s'", id)
869
+		}
870
+		//test that its a valid cid? (though the container is gone..)
871
+		//remove the file and dir.
872
+	})
873
+
874
+	setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
875
+		<-c
876
+	})
877
+
878
+}
... ...
@@ -9,7 +9,6 @@ import (
9 9
 type DaemonConfig struct {
10 10
 	Pidfile                     string
11 11
 	Root                        string
12
-	ProtoAddresses              []string
13 12
 	AutoRestart                 bool
14 13
 	EnableCors                  bool
15 14
 	Dns                         []string
... ...
@@ -36,7 +35,6 @@ func ConfigFromJob(job *engine.Job) *DaemonConfig {
36 36
 	} else {
37 37
 		config.BridgeIface = DefaultNetworkBridge
38 38
 	}
39
-	config.ProtoAddresses = job.GetenvList("ProtoAddresses")
40 39
 	config.DefaultIp = net.ParseIP(job.Getenv("DefaultIp"))
41 40
 	config.InterContainerCommunication = job.GetenvBool("InterContainerCommunication")
42 41
 	return &config
... ...
@@ -134,7 +134,11 @@ type PortBinding struct {
134 134
 type Port string
135 135
 
136 136
 func (p Port) Proto() string {
137
-	return strings.Split(string(p), "/")[1]
137
+	parts := strings.Split(string(p), "/")
138
+	if len(parts) == 1 {
139
+		return "tcp"
140
+	}
141
+	return parts[1]
138 142
 }
139 143
 
140 144
 func (p Port) Port() string {
... ...
@@ -168,7 +172,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
168 168
 	cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.")
169 169
 	flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
170 170
 	flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
171
-	flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)")
171
+	flMemoryString := cmd.String("m", "", "Memory limit (format: <number><optional unit>, where unit = b, k, m or g)")
172 172
 	flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file")
173 173
 	flNetwork := cmd.Bool("n", true, "Enable networking for this container")
174 174
 	flPrivileged := cmd.Bool("privileged", false, "Give extended privileges to this container")
... ...
@@ -177,9 +181,9 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
177 177
 	cmd.String("name", "", "Assign a name to the container")
178 178
 	flPublishAll := cmd.Bool("P", false, "Publish all exposed ports to the host interfaces")
179 179
 
180
-	if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit {
180
+	if capabilities != nil && *flMemoryString != "" && !capabilities.MemoryLimit {
181 181
 		//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
182
-		*flMemory = 0
182
+		*flMemoryString = ""
183 183
 	}
184 184
 
185 185
 	flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)")
... ...
@@ -200,7 +204,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
200 200
 	cmd.Var(flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)")
201 201
 
202 202
 	var flVolumesFrom utils.ListOpts
203
-	cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container")
203
+	cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
204 204
 
205 205
 	flEntrypoint := cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image")
206 206
 
... ...
@@ -246,6 +250,18 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
246 246
 		}
247 247
 	}
248 248
 
249
+	var flMemory int64
250
+
251
+	if *flMemoryString != "" {
252
+		parsedMemory, err := utils.RAMInBytes(*flMemoryString)
253
+
254
+		if err != nil {
255
+			return nil, nil, cmd, err
256
+		}
257
+
258
+		flMemory = parsedMemory
259
+	}
260
+
249 261
 	var binds []string
250 262
 
251 263
 	// add any bind targets to the list of container volumes
... ...
@@ -316,7 +332,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
316 316
 		Tty:             *flTty,
317 317
 		NetworkDisabled: !*flNetwork,
318 318
 		OpenStdin:       *flStdin,
319
-		Memory:          *flMemory,
319
+		Memory:          flMemory,
320 320
 		CpuShares:       *flCpuShares,
321 321
 		AttachStdin:     flAttach.Get("stdin"),
322 322
 		AttachStdout:    flAttach.Get("stdout"),
... ...
@@ -341,7 +357,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
341 341
 		PublishAllPorts: *flPublishAll,
342 342
 	}
343 343
 
344
-	if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
344
+	if capabilities != nil && flMemory > 0 && !capabilities.SwapLimit {
345 345
 		//fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
346 346
 		config.MemorySwap = -1
347 347
 	}
... ...
@@ -694,24 +710,25 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
694 694
 func (container *Container) Start() (err error) {
695 695
 	container.State.Lock()
696 696
 	defer container.State.Unlock()
697
+	if container.State.Running {
698
+		return fmt.Errorf("The container %s is already running.", container.ID)
699
+	}
697 700
 	defer func() {
698 701
 		if err != nil {
699 702
 			container.cleanup()
700 703
 		}
701 704
 	}()
702
-
703
-	if container.State.Running {
704
-		return fmt.Errorf("The container %s is already running.", container.ID)
705
-	}
706 705
 	if err := container.EnsureMounted(); err != nil {
707 706
 		return err
708 707
 	}
709 708
 	if container.runtime.networkManager.disabled {
710 709
 		container.Config.NetworkDisabled = true
710
+		container.buildHostnameAndHostsFiles("127.0.1.1")
711 711
 	} else {
712 712
 		if err := container.allocateNetwork(); err != nil {
713 713
 			return err
714 714
 		}
715
+		container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress)
715 716
 	}
716 717
 
717 718
 	// Make sure the config is compatible with the current kernel
... ...
@@ -771,9 +788,23 @@ func (container *Container) Start() (err error) {
771 771
 
772 772
 	// Apply volumes from another container if requested
773 773
 	if container.Config.VolumesFrom != "" {
774
-		volumes := strings.Split(container.Config.VolumesFrom, ",")
775
-		for _, v := range volumes {
776
-			c := container.runtime.Get(v)
774
+		containerSpecs := strings.Split(container.Config.VolumesFrom, ",")
775
+		for _, containerSpec := range containerSpecs {
776
+			mountRW := true
777
+			specParts := strings.SplitN(containerSpec, ":", 2)
778
+			switch len(specParts) {
779
+			case 0:
780
+				return fmt.Errorf("Malformed volumes-from specification: %s", container.Config.VolumesFrom)
781
+			case 2:
782
+				switch specParts[1] {
783
+				case "ro":
784
+					mountRW = false
785
+				case "rw": // mountRW is already true
786
+				default:
787
+					return fmt.Errorf("Malformed volumes-from speficication: %s", containerSpec)
788
+				}
789
+			}
790
+			c := container.runtime.Get(specParts[0])
777 791
 			if c == nil {
778 792
 				return fmt.Errorf("Container %s not found. Impossible to mount its volumes", container.ID)
779 793
 			}
... ...
@@ -786,7 +817,7 @@ func (container *Container) Start() (err error) {
786 786
 				}
787 787
 				container.Volumes[volPath] = id
788 788
 				if isRW, exists := c.VolumesRW[volPath]; exists {
789
-					container.VolumesRW[volPath] = isRW
789
+					container.VolumesRW[volPath] = isRW && mountRW
790 790
 				}
791 791
 			}
792 792
 
... ...
@@ -832,7 +863,7 @@ func (container *Container) Start() (err error) {
832 832
 		// Create the mountpoint
833 833
 		rootVolPath := path.Join(container.RootfsPath(), volPath)
834 834
 		if err := os.MkdirAll(rootVolPath, 0755); err != nil {
835
-			return nil
835
+			return err
836 836
 		}
837 837
 
838 838
 		// Do not copy or change permissions if we are mounting from the host
... ...
@@ -876,7 +907,13 @@ func (container *Container) Start() (err error) {
876 876
 		return err
877 877
 	}
878 878
 
879
+	var lxcStart string = "lxc-start"
880
+	if container.hostConfig.Privileged && container.runtime.capabilities.AppArmor {
881
+		lxcStart = path.Join(container.runtime.config.Root, "lxc-start-unconfined")
882
+	}
883
+
879 884
 	params := []string{
885
+		lxcStart,
880 886
 		"-n", container.ID,
881 887
 		"-f", container.lxcConfigPath(),
882 888
 		"--",
... ...
@@ -969,11 +1006,24 @@ func (container *Container) Start() (err error) {
969 969
 	params = append(params, "--", container.Path)
970 970
 	params = append(params, container.Args...)
971 971
 
972
-	var lxcStart string = "lxc-start"
973
-	if container.hostConfig.Privileged && container.runtime.capabilities.AppArmor {
974
-		lxcStart = path.Join(container.runtime.config.Root, "lxc-start-unconfined")
972
+	if RootIsShared() {
973
+		// lxc-start really needs / to be non-shared, or all kinds of stuff break
974
+		// when lxc-start unmount things and those unmounts propagate to the main
975
+		// mount namespace.
976
+		// What we really want is to clone into a new namespace and then
977
+		// mount / MS_REC|MS_SLAVE, but since we can't really clone or fork
978
+		// without exec in go we have to do this horrible shell hack...
979
+		shellString :=
980
+			"mount --make-rslave /; exec " +
981
+				utils.ShellQuoteArguments(params)
982
+
983
+		params = []string{
984
+			"unshare", "-m", "--", "/bin/sh", "-c", shellString,
985
+		}
975 986
 	}
976
-	container.cmd = exec.Command(lxcStart, params...)
987
+
988
+	container.cmd = exec.Command(params[0], params[1:]...)
989
+
977 990
 	// Setup logging of stdout and stderr to disk
978 991
 	if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil {
979 992
 		return err
... ...
@@ -1082,6 +1132,30 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) {
1082 1082
 	return utils.NewBufReader(reader), nil
1083 1083
 }
1084 1084
 
1085
+func (container *Container) buildHostnameAndHostsFiles(IP string) {
1086
+	container.HostnamePath = path.Join(container.root, "hostname")
1087
+	ioutil.WriteFile(container.HostnamePath, []byte(container.Config.Hostname+"\n"), 0644)
1088
+
1089
+	hostsContent := []byte(`
1090
+127.0.0.1	localhost
1091
+::1		localhost ip6-localhost ip6-loopback
1092
+fe00::0		ip6-localnet
1093
+ff00::0		ip6-mcastprefix
1094
+ff02::1		ip6-allnodes
1095
+ff02::2		ip6-allrouters
1096
+`)
1097
+
1098
+	container.HostsPath = path.Join(container.root, "hosts")
1099
+
1100
+	if container.Config.Domainname != "" {
1101
+		hostsContent = append([]byte(fmt.Sprintf("%s\t%s.%s %s\n", IP, container.Config.Hostname, container.Config.Domainname, container.Config.Hostname)), hostsContent...)
1102
+	} else {
1103
+		hostsContent = append([]byte(fmt.Sprintf("%s\t%s\n", IP, container.Config.Hostname)), hostsContent...)
1104
+	}
1105
+
1106
+	ioutil.WriteFile(container.HostsPath, hostsContent, 0644)
1107
+}
1108
+
1085 1109
 func (container *Container) allocateNetwork() error {
1086 1110
 	if container.Config.NetworkDisabled {
1087 1111
 		return nil
... ...
@@ -1230,7 +1304,7 @@ func (container *Container) monitor() {
1230 1230
 	container.State.setStopped(exitCode)
1231 1231
 
1232 1232
 	if container.runtime != nil && container.runtime.srv != nil {
1233
-		container.runtime.srv.LogEvent("die", container.ShortID(), container.runtime.repositories.ImageName(container.Image))
1233
+		container.runtime.srv.LogEvent("die", container.ID, container.runtime.repositories.ImageName(container.Image))
1234 1234
 	}
1235 1235
 
1236 1236
 	// Cleanup
... ...
@@ -1297,7 +1371,7 @@ func (container *Container) kill(sig int) error {
1297 1297
 	}
1298 1298
 
1299 1299
 	if output, err := exec.Command("lxc-kill", "-n", container.ID, strconv.Itoa(sig)).CombinedOutput(); err != nil {
1300
-		log.Printf("error killing container %s (%s, %s)", container.ShortID(), output, err)
1300
+		log.Printf("error killing container %s (%s, %s)", utils.TruncateID(container.ID), output, err)
1301 1301
 		return err
1302 1302
 	}
1303 1303
 
... ...
@@ -1317,9 +1391,9 @@ func (container *Container) Kill() error {
1317 1317
 	// 2. Wait for the process to die, in last resort, try to kill the process directly
1318 1318
 	if err := container.WaitTimeout(10 * time.Second); err != nil {
1319 1319
 		if container.cmd == nil {
1320
-			return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", container.ShortID())
1320
+			return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", utils.TruncateID(container.ID))
1321 1321
 		}
1322
-		log.Printf("Container %s failed to exit within 10 seconds of lxc-kill %s - trying direct SIGKILL", "SIGKILL", container.ShortID())
1322
+		log.Printf("Container %s failed to exit within 10 seconds of lxc-kill %s - trying direct SIGKILL", "SIGKILL", utils.TruncateID(container.ID))
1323 1323
 		if err := container.cmd.Process.Kill(); err != nil {
1324 1324
 			return err
1325 1325
 		}
... ...
@@ -1433,14 +1507,6 @@ func (container *Container) Unmount() error {
1433 1433
 	return container.runtime.Unmount(container)
1434 1434
 }
1435 1435
 
1436
-// ShortID returns a shorthand version of the container's id for convenience.
1437
-// A collision with other container shorthands is very unlikely, but possible.
1438
-// In case of a collision a lookup with Runtime.Get() will fail, and the caller
1439
-// will need to use a langer prefix, or the full-length container Id.
1440
-func (container *Container) ShortID() string {
1441
-	return utils.TruncateID(container.ID)
1442
-}
1443
-
1444 1436
 func (container *Container) logPath(name string) string {
1445 1437
 	return path.Join(container.root, fmt.Sprintf("%s-%s.log", container.ID, name))
1446 1438
 }
... ...
@@ -3,6 +3,7 @@ package docker
3 3
 import (
4 4
 	"bufio"
5 5
 	"fmt"
6
+	"github.com/dotcloud/docker/utils"
6 7
 	"io"
7 8
 	"io/ioutil"
8 9
 	"math/rand"
... ...
@@ -1005,7 +1006,7 @@ func TestEnv(t *testing.T) {
1005 1005
 		"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
1006 1006
 		"HOME=/",
1007 1007
 		"container=lxc",
1008
-		"HOSTNAME=" + container.ShortID(),
1008
+		"HOSTNAME=" + utils.TruncateID(container.ID),
1009 1009
 		"FALSE=true",
1010 1010
 		"TRUE=false",
1011 1011
 		"TRICKY=tri",
... ...
@@ -1338,6 +1339,67 @@ func TestBindMounts(t *testing.T) {
1338 1338
 	}
1339 1339
 }
1340 1340
 
1341
+// Test that -volumes-from supports both read-only mounts
1342
+func TestFromVolumesInReadonlyMode(t *testing.T) {
1343
+	runtime := mkRuntime(t)
1344
+	defer nuke(runtime)
1345
+	container, _, err := runtime.Create(
1346
+		&Config{
1347
+			Image:   GetTestImage(runtime).ID,
1348
+			Cmd:     []string{"/bin/echo", "-n", "foobar"},
1349
+			Volumes: map[string]struct{}{"/test": {}},
1350
+		},
1351
+		"",
1352
+	)
1353
+	if err != nil {
1354
+		t.Fatal(err)
1355
+	}
1356
+	defer runtime.Destroy(container)
1357
+	_, err = container.Output()
1358
+	if err != nil {
1359
+		t.Fatal(err)
1360
+	}
1361
+	if !container.VolumesRW["/test"] {
1362
+		t.Fail()
1363
+	}
1364
+
1365
+	container2, _, err := runtime.Create(
1366
+		&Config{
1367
+			Image:       GetTestImage(runtime).ID,
1368
+			Cmd:         []string{"/bin/echo", "-n", "foobar"},
1369
+			VolumesFrom: container.ID + ":ro",
1370
+		},
1371
+		"",
1372
+	)
1373
+	if err != nil {
1374
+		t.Fatal(err)
1375
+	}
1376
+	defer runtime.Destroy(container2)
1377
+
1378
+	_, err = container2.Output()
1379
+	if err != nil {
1380
+		t.Fatal(err)
1381
+	}
1382
+
1383
+	if container.Volumes["/test"] != container2.Volumes["/test"] {
1384
+		t.Logf("container volumes do not match: %s | %s ",
1385
+			container.Volumes["/test"],
1386
+			container2.Volumes["/test"])
1387
+		t.Fail()
1388
+	}
1389
+
1390
+	_, exists := container2.VolumesRW["/test"]
1391
+	if !exists {
1392
+		t.Logf("container2 is missing '/test' volume: %s", container2.VolumesRW)
1393
+		t.Fail()
1394
+	}
1395
+
1396
+	if container2.VolumesRW["/test"] != false {
1397
+		t.Log("'/test' volume mounted in read-write mode, expected read-only")
1398
+		t.Fail()
1399
+	}
1400
+}
1401
+
1341 1402
 // Test that VolumesRW values are copied to the new container.  Regression test for #1201
1342 1403
 func TestVolumesFromReadonlyMount(t *testing.T) {
1343 1404
 	runtime := mkRuntime(t)
... ...
@@ -29,7 +29,9 @@ if [ -f /etc/default/$BASE ]; then
29 29
 	. /etc/default/$BASE
30 30
 fi
31 31
 
32
-if [ "$1" = start ] && which initctl >/dev/null && initctl version | grep -q upstart; then
32
+# see also init_is_upstart in /lib/lsb/init-functions (which isn't available in Ubuntu 12.04, or we'd use it)
33
+if [ -x /sbin/initctl ] && /sbin/initctl version 2>/dev/null | /bin/grep -q upstart; then
34
+	log_failure_msg "Docker is managed via upstart, try using service $BASE $1"
33 35
 	exit 1
34 36
 fi
35 37
 
... ...
@@ -6,5 +6,10 @@ stop on runlevel [!2345]
6 6
 respawn
7 7
 
8 8
 script
9
-	/usr/bin/docker -d
9
+	DOCKER=/usr/bin/$UPSTART_JOB
10
+	DOCKER_OPTS=
11
+	if [ -f /etc/default/$UPSTART_JOB ]; then
12
+		. /etc/default/$UPSTART_JOB
13
+	fi
14
+	"$DOCKER" -d $DOCKER_OPTS
10 15
 end script
... ...
@@ -1,3 +1,19 @@
1
-# Vagrant-docker
1
+# Vagrant integration
2 2
 
3
-This is a placeholder for the official vagrant-docker, a plugin for Vagrant (http://vagrantup.com) which exposes Docker as a provider.
3
+Currently there are at least 4 different projects that we are aware of that deals
4
+with integration with [Vagrant](http://vagrantup.com/) at different levels. One
5
+approach is to use Docker as a [provisioner](http://docs.vagrantup.com/v2/provisioning/index.html)
6
+which means you can create containers and pull base images on VMs using Docker's
7
+CLI and the other is to use Docker as a [provider](http://docs.vagrantup.com/v2/providers/index.html),
8
+meaning you can use Vagrant to control Docker containers.
9
+
10
+
11
+### Provisioners
12
+
13
+* [Vocker](https://github.com/fgrehm/vocker)
14
+* [Ventriloquist](https://github.com/fgrehm/ventriloquist)
15
+
16
+### Providers
17
+
18
+* [docker-provider](https://github.com/fgrehm/docker-provider)
19
+* [vagrant-shell](https://github.com/destructuring/vagrant-shell)
... ...
@@ -71,7 +71,8 @@ func main() {
71 71
 		if err != nil {
72 72
 			log.Fatal(err)
73 73
 		}
74
-		job := eng.Job("serveapi")
74
+		// Load plugin: httpapi
75
+		job := eng.Job("initapi")
75 76
 		job.Setenv("Pidfile", *pidfile)
76 77
 		job.Setenv("Root", *flRoot)
77 78
 		job.SetenvBool("AutoRestart", *flAutoRestart)
... ...
@@ -79,12 +80,17 @@ func main() {
79 79
 		job.Setenv("Dns", *flDns)
80 80
 		job.SetenvBool("EnableIptables", *flEnableIptables)
81 81
 		job.Setenv("BridgeIface", *bridgeName)
82
-		job.SetenvList("ProtoAddresses", flHosts)
83 82
 		job.Setenv("DefaultIp", *flDefaultIp)
84 83
 		job.SetenvBool("InterContainerCommunication", *flInterContainerComm)
85 84
 		if err := job.Run(); err != nil {
86 85
 			log.Fatal(err)
87 86
 		}
87
+		// Serve api
88
+		job = eng.Job("serveapi", flHosts...)
89
+		job.SetenvBool("Logging", true)
90
+		if err := job.Run(); err != nil {
91
+			log.Fatal(err)
92
+		}
88 93
 	} else {
89 94
 		if len(flHosts) > 1 {
90 95
 			log.Fatal("Please specify only one -H")
... ...
@@ -121,8 +121,7 @@ Create a container
121 121
 		"AttachStdin":false,
122 122
 		"AttachStdout":true,
123 123
 		"AttachStderr":true,
124
-		"PortSpecs":null,
125
-		"Privileged": false,
124
+		"ExposedPorts":{},
126 125
 		"Tty":false,
127 126
 		"OpenStdin":false,
128 127
 		"StdinOnce":false,
... ...
@@ -135,7 +134,6 @@ Create a container
135 135
 		"Volumes":{},
136 136
 		"VolumesFrom":"",
137 137
 		"WorkingDir":""
138
-
139 138
 	   }
140 139
 	   
141 140
 	**Example response**:
... ...
@@ -242,7 +240,7 @@ Inspect a container
242 242
 				"AttachStdin": false,
243 243
 				"AttachStdout": true,
244 244
 				"AttachStderr": true,
245
-				"PortSpecs": null,
245
+				"ExposedPorts": {},
246 246
 				"Tty": false,
247 247
 				"OpenStdin": false,
248 248
 				"StdinOnce": false,
... ...
@@ -413,7 +411,12 @@ Start a container
413 413
 
414 414
            {
415 415
                 "Binds":["/tmp:/tmp"],
416
-                "LxcConf":{"lxc.utsname":"docker"}
416
+                "LxcConf":{"lxc.utsname":"docker"},
417
+                "ContainerIDFile": "",
418
+                "Privileged": false,
419
+                "PortBindings": {"22/tcp": [{HostIp:"", HostPort:""}]},
420
+                "Links": [],
421
+                "PublishAllPorts": false
417 422
            }
418 423
 
419 424
         **Example response**:
... ...
@@ -846,7 +849,7 @@ Inspect an image
846 846
 				"AttachStdin":false,
847 847
 				"AttachStdout":false,
848 848
 				"AttachStderr":false,
849
-				"PortSpecs":null,
849
+				"ExposedPorts":{},
850 850
 				"Tty":true,
851 851
 				"OpenStdin":true,
852 852
 				"StdinOnce":false,
... ...
@@ -1192,7 +1195,7 @@ Create a new image from a container's changes
1192 1192
        
1193 1193
        {
1194 1194
            "Cmd": ["cat", "/world"],
1195
-           "PortSpecs":["22"]
1195
+           "ExposedPorts":{"22/tcp":{}}
1196 1196
        }
1197 1197
 
1198 1198
     **Example response**:
... ...
@@ -914,7 +914,12 @@ Search images
914 914
 
915 915
 .. http:get:: /images/search
916 916
 
917
-	Search for an image in the docker index
917
+	Search for an image in the docker index.
918
+	
919
+	.. note::
920
+	
921
+	   The response keys have changed from API v1.6 to reflect the JSON 
922
+	   sent by the registry server to the docker daemon's request.
918 923
 	
919 924
 	**Example request**:
920 925
 
... ...
@@ -930,18 +935,28 @@ Search images
930 930
 	   Content-Type: application/json
931 931
 	   
932 932
 	   [
933
-		{
934
-			"Name":"cespare/sshd",
935
-			"Description":""
936
-		},
937
-		{
938
-			"Name":"johnfuller/sshd",
939
-			"Description":""
940
-		},
941
-		{
942
-			"Name":"dhrp/mongodb-sshd",
943
-			"Description":""
944
-		}
933
+		   {
934
+		       "description": "",
935
+		       "is_official": false,
936
+		       "is_trusted": false,
937
+		       "name": "wma55/u1210sshd",
938
+		       "star_count": 0
939
+		   },
940
+		   {
941
+		       "description": "",
942
+		       "is_official": false,
943
+		       "is_trusted": false,
944
+		       "name": "jdswinbank/sshd",
945
+		       "star_count": 0
946
+		   },
947
+		   {
948
+		       "description": "",
949
+		       "is_official": false,
950
+		       "is_trusted": false,
951
+		       "name": "vgauthier/sshd",
952
+		       "star_count": 0
953
+		   }
954
+	   ...
945 955
 	   ]
946 956
 
947 957
 	   :query term: term to search
... ...
@@ -12,26 +12,28 @@ compatibility. Please file issues with the library owners.  If you
12 12
 find more library implementations, please list them in Docker doc bugs
13 13
 and we will add the libraries here.
14 14
 
15
-+----------------------+----------------+--------------------------------------------+
16
-| Language/Framework   | Name           | Repository                                 |
17
-+======================+================+============================================+
18
-| Python               | docker-py      | https://github.com/dotcloud/docker-py      |
19
-+----------------------+----------------+--------------------------------------------+
20
-| Ruby                 | docker-client  | https://github.com/geku/docker-client      |
21
-+----------------------+----------------+--------------------------------------------+
22
-| Ruby                 | docker-api     | https://github.com/swipely/docker-api      |
23
-+----------------------+----------------+--------------------------------------------+
24
-| Javascript (NodeJS)  | docker.io      | https://github.com/appersonlabs/docker.io  |
25
-|                      |                | Install via NPM: `npm install docker.io`   |
26
-+----------------------+----------------+--------------------------------------------+
27
-| Javascript           | docker-js      | https://github.com/dgoujard/docker-js      |
28
-+----------------------+----------------+--------------------------------------------+
29
-| Javascript (Angular) | dockerui       | https://github.com/crosbymichael/dockerui  |
30
-| **WebUI**            |                |                                            |
31
-+----------------------+----------------+--------------------------------------------+
32
-| Java                 | docker-java    | https://github.com/kpelykh/docker-java     |
33
-+----------------------+----------------+--------------------------------------------+
34
-| Erlang               | erldocker      | https://github.com/proger/erldocker        |
35
-+----------------------+----------------+--------------------------------------------+
36
-| Go                   | go-dockerclient| https://github.com/fsouza/go-dockerclient  |
37
-+----------------------+----------------+--------------------------------------------+
15
++----------------------+----------------+--------------------------------------------+----------+
16
+| Language/Framework   | Name           | Repository                                 | Status   |
17
++======================+================+============================================+==========+
18
+| Python               | docker-py      | https://github.com/dotcloud/docker-py      | Active   |
19
++----------------------+----------------+--------------------------------------------+----------+
20
+| Ruby                 | docker-client  | https://github.com/geku/docker-client      | Outdated |
21
++----------------------+----------------+--------------------------------------------+----------+
22
+| Ruby                 | docker-api     | https://github.com/swipely/docker-api      | Active   |
23
++----------------------+----------------+--------------------------------------------+----------+
24
+| Javascript (NodeJS)  | docker.io      | https://github.com/appersonlabs/docker.io  | Active   |
25
+|                      |                | Install via NPM: `npm install docker.io`   |          |
26
++----------------------+----------------+--------------------------------------------+----------+
27
+| Javascript           | docker-js      | https://github.com/dgoujard/docker-js      | Active   |
28
++----------------------+----------------+--------------------------------------------+----------+
29
+| Javascript (Angular) | dockerui       | https://github.com/crosbymichael/dockerui  | Active   |
30
+| **WebUI**            |                |                                            |          |
31
++----------------------+----------------+--------------------------------------------+----------+
32
+| Java                 | docker-java    | https://github.com/kpelykh/docker-java     | Active   |
33
++----------------------+----------------+--------------------------------------------+----------+
34
+| Erlang               | erldocker      | https://github.com/proger/erldocker        | Active   |
35
++----------------------+----------------+--------------------------------------------+----------+
36
+| Go                   | go-dockerclient| https://github.com/fsouza/go-dockerclient  | Active   |
37
++----------------------+----------------+--------------------------------------------+----------+
38
+| PHP                  | Alvine         | http://pear.alvine.io/ (alpha)             | Active   |
39
++----------------------+----------------+--------------------------------------------+----------+
... ...
@@ -245,6 +245,9 @@ Full -run example
245 245
     Usage: docker events
246 246
 
247 247
     Get real time events from the server
248
+    
249
+    -since="": Show previously created events and then stream.
250
+               (either seconds since epoch, or date string as below)
248 251
 
249 252
 .. _cli_events_example:
250 253
 
... ...
@@ -277,6 +280,23 @@ Shell 1: (Again .. now showing events)
277 277
     [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die
278 278
     [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop
279 279
 
280
+Show events in the past from a specified time
281
+.............................................
282
+
283
+.. code-block:: bash
284
+
285
+    $ sudo docker events -since 1378216169
286
+    [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die
287
+    [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop
288
+
289
+    $ sudo docker events -since '2013-09-03'
290
+    [2013-09-03 15:49:26 +0200 CEST] 4386fb97867d: (from 12de384bfb10) start
291
+    [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die
292
+    [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop
293
+
294
+    $ sudo docker events -since '2013-09-03 15:49:29 +0200 CEST'
295
+    [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die
296
+    [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop
280 297
 
281 298
 .. _cli_export:
282 299
 
... ...
@@ -460,6 +480,12 @@ Insert file from github
460 460
 
461 461
 The main process inside the container will be sent SIGKILL.
462 462
 
463
+Known Issues (kill)
464
+~~~~~~~~~~~~~~~~~~~
465
+
466
+* :issue:`197` indicates that ``docker kill`` may leave directories
467
+  behind and make it difficult to remove the container.
468
+
463 469
 .. _cli_login:
464 470
 
465 471
 ``login``
... ...
@@ -568,6 +594,12 @@ The main process inside the container will be sent SIGKILL.
568 568
     Remove one or more containers
569 569
         -link="": Remove the link instead of the actual container
570 570
 
571
+Known Issues (rm)
572
+~~~~~~~~~~~~~~~~~~~
573
+
574
+* :issue:`197` indicates that ``docker kill`` may leave directories
575
+  behind and make it difficult to remove the container.
576
+
571 577
 
572 578
 Examples:
573 579
 ~~~~~~~~~
... ...
@@ -590,6 +622,15 @@ This will remove the container referenced under the link ``/redis``.
590 590
 This will remove the underlying link between ``/webapp`` and the ``/redis`` containers removing all
591 591
 network communication.
592 592
 
593
+.. code-block:: bash
594
+
595
+    $ docker rm `docker ps -a -q`
596
+
597
+
598
+This command will delete all stopped containers. The command ``docker ps -a -q`` will return all
599
+existing container IDs and pass them to the ``rm`` command which will delete them. Any running
600
+containers will not be deleted.
601
+
593 602
 .. _cli_rmi:
594 603
 
595 604
 ``rmi``
... ...
@@ -620,7 +661,7 @@ network communication.
620 620
       -h="": Container host name
621 621
       -i=false: Keep stdin open even if not attached
622 622
       -privileged=false: Give extended privileges to this container
623
-      -m=0: Memory limit (in bytes)
623
+      -m="": Memory limit (format: <number><optional unit>, where unit = b, k, m or g)
624 624
       -n=true: Enable networking for this container
625 625
       -p=[]: Map a network port to the container
626 626
       -rm=false: Automatically remove the container when it exits (incompatible with -d)
... ...
@@ -628,7 +669,7 @@ network communication.
628 628
       -u="": Username or UID
629 629
       -dns=[]: Set custom dns servers for the container
630 630
       -v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "container-dir" is missing, then docker creates a new volume.
631
-      -volumes-from="": Mount all volumes from the given container
631
+      -volumes-from="": Mount all volumes from the given container(s)
632 632
       -entrypoint="": Overwrite the default entrypoint set by the image
633 633
       -w="": Working directory inside the container
634 634
       -lxc-conf=[]: Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
... ...
@@ -720,6 +761,17 @@ can access the network and environment of the redis container via
720 720
 environment variables.  The ``-name`` flag will assign the name ``console``
721 721
 to the newly created container.
722 722
 
723
+.. code-block:: bash
724
+
725
+   docker run -volumes-from 777f7dc92da7,ba8c0c54f0f2:ro -i -t ubuntu pwd
726
+
727
+The ``-volumes-from`` flag mounts all the defined volumes from the
728
+refrence containers. Containers can be specified by a comma seperated
729
+list or by repetitions of the ``-volumes-from`` argument. The container
730
+id may be optionally suffixed with ``:ro`` or ``:rw`` to mount the volumes in
731
+read-only or read-write mode, respectively. By default, the volumes are mounted
732
+in the same mode (rw or ro) as the reference container.
733
+
723 734
 .. _cli_search:
724 735
 
725 736
 ``search``
... ...
@@ -40,7 +40,11 @@ html_additional_pages = {
40 40
 
41 41
 # Add any Sphinx extension module names here, as strings. They can be extensions
42 42
 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
43
-extensions = ['sphinxcontrib.httpdomain']
43
+extensions = ['sphinxcontrib.httpdomain', 'sphinx.ext.extlinks']
44
+
45
+# Configure extlinks
46
+extlinks = { 'issue': ('https://github.com/dotcloud/docker/issues/%s',
47
+	               'Issue ') }
44 48
 
45 49
 # Add any paths that contain templates here, relative to this directory.
46 50
 templates_path = ['_templates']
... ...
@@ -10,13 +10,16 @@ Want to hack on Docker? Awesome!
10 10
 The repository includes `all the instructions you need to get
11 11
 started <https://github.com/dotcloud/docker/blob/master/CONTRIBUTING.md>`_.
12 12
 
13
-The developer environment `Dockerfile <https://github.com/dotcloud/docker/blob/master/Dockerfile>`_
13
+The `developer environment Dockerfile
14
+<https://github.com/dotcloud/docker/blob/master/Dockerfile>`_
14 15
 specifies the tools and versions used to test and build Docker.
15 16
 
16 17
 If you're making changes to the documentation, see the
17 18
 `README.md <https://github.com/dotcloud/docker/blob/master/docs/README.md>`_.
18 19
 
19
-The documentation environment `Dockerfile <https://github.com/dotcloud/docker/blob/master/docs/Dockerfile>`_
20
+The `documentation environment Dockerfile
21
+<https://github.com/dotcloud/docker/blob/master/docs/Dockerfile>`_
20 22
 specifies the tools and versions used to build the Documentation.
21 23
 
22
-Further interesting details can be found in the `Packaging hints <https://github.com/dotcloud/docker/blob/master/hack/PACKAGERS.md>`_.
24
+Further interesting details can be found in the `Packaging hints
25
+<https://github.com/dotcloud/docker/blob/master/hack/PACKAGERS.md>`_.
... ...
@@ -86,7 +86,7 @@ http://0.0.0.0:5000/`` in the log output.
86 86
 
87 87
 .. code-block:: bash
88 88
 
89
-    WEB_PORT=$(sudo docker port $WEB_WORKER 5000)
89
+    WEB_PORT=$(sudo docker port $WEB_WORKER 5000 | awk -F: '{ print $2 }')
90 90
 
91 91
 Look up the public-facing port which is NAT-ed. Find the private port
92 92
 used by the container and store it inside of the ``WEB_PORT`` variable.
... ...
@@ -102,26 +102,45 @@ Docker that way too. Vagrant 1.1 or higher is required.
102 102
    we need to set them there first. Make sure you have everything on
103 103
    amazon aws setup so you can (manually) deploy a new image to EC2.
104 104
 
105
+   Note that where possible these variables are the same as those honored by
106
+   the ec2 api tools.
105 107
    ::
106 108
 
107
-       export AWS_ACCESS_KEY_ID=xxx
108
-       export AWS_SECRET_ACCESS_KEY=xxx
109
+       export AWS_ACCESS_KEY=xxx
110
+       export AWS_SECRET_KEY=xxx
109 111
        export AWS_KEYPAIR_NAME=xxx
110
-       export AWS_SSH_PRIVKEY=xxx
112
+       export SSH_PRIVKEY_PATH=xxx
111 113
 
112
-   The environment variables are:
114
+       export BOX_NAME=xxx
115
+       export AWS_REGION=xxx
116
+       export AWS_AMI=xxx
117
+       export AWS_INSTANCE_TYPE=xxx
113 118
 
114
-   * ``AWS_ACCESS_KEY_ID`` - The API key used to make requests to AWS
115
-   * ``AWS_SECRET_ACCESS_KEY`` - The secret key to make AWS API requests
119
+   The required environment variables are:
120
+
121
+   * ``AWS_ACCESS_KEY`` - The API key used to make requests to AWS
122
+   * ``AWS_SECRET_KEY`` - The secret key to make AWS API requests
116 123
    * ``AWS_KEYPAIR_NAME`` - The name of the keypair used for this EC2 instance
117
-   * ``AWS_SSH_PRIVKEY`` - The path to the private key for the named
124
+   * ``SSH_PRIVKEY_PATH`` - The path to the private key for the named
118 125
      keypair, for example ``~/.ssh/docker.pem``
119 126
 
127
+   There are a number of optional environment variables:
128
+
129
+   * ``BOX_NAME`` - The name of the vagrant box to use.  Defaults to
130
+     ``ubuntu``.
131
+   * ``AWS_REGION`` - The aws region to spawn the vm in.  Defaults to
132
+     ``us-east-1``.
133
+   * ``AWS_AMI`` - The aws AMI to start with as a base.  This must be
134
+     be an ubuntu 12.04 precise image.  You must change this value if
135
+     ``AWS_REGION`` is set to a value other than ``us-east-1``.
136
+     This is because AMIs are region specific.  Defaults to ``ami-69f5a900``.
137
+   * ``AWS_INSTANCE_TYPE`` - The aws instance type.  Defaults to ``t1.micro``.
138
+
120 139
    You can check if they are set correctly by doing something like
121 140
 
122 141
    ::
123 142
 
124
-      echo $AWS_ACCESS_KEY_ID
143
+      echo $AWS_ACCESS_KEY
125 144
 
126 145
 6. Do the magic!
127 146
 
... ...
@@ -38,3 +38,10 @@ was when the container was stopped.
38 38
 You can promote a container to an :ref:`image_def` with ``docker
39 39
 commit``. Once a container is an image, you can use it as a parent for
40 40
 new containers.
41
+
42
+Container IDs
43
+.............
44
+All containers are identified by a 64 hexadecimal digit string (internally a 256bit 
45
+value). To simplify their use, a short ID of the first 12 characters can be used 
46
+on the commandline. There is a small possibility of short id collisions, so the 
47
+docker server will always return the long ID.
... ...
@@ -36,3 +36,11 @@ Base Image
36 36
 ..........
37 37
 
38 38
 An image that has no parent is a **base image**.
39
+
40
+Image IDs
41
+.........
42
+All images are identified by a 64 hexadecimal digit string (internally a 256bit 
43
+value). To simplify their use, a short ID of the first 12 characters can be used 
44
+on the command line. There is a small possibility of short id collisions, so the 
45
+docker server will always return the long ID.
46
+
... ...
@@ -22,22 +22,37 @@ specify the path to it and manually start it.
22 22
     # Run docker in daemon mode
23 23
     sudo <path to>/docker -d &
24 24
 
25
-
26
-Running an interactive shell
25
+Download a pre-built image
26
+--------------------------
27 27
 
28 28
 .. code-block:: bash
29 29
 
30 30
   # Download an ubuntu image
31 31
   sudo docker pull ubuntu
32 32
 
33
+This will find the ``ubuntu`` image by name in the :ref:`Central Index 
34
+<searching_central_index>` and download it from the top-level Central 
35
+Repository to a local image cache.
36
+
37
+.. NOTE:: When the image has successfully downloaded, you will see a 12 
38
+character hash ``539c0211cd76: Download complete`` which is the short 
39
+form of the image ID. These short image IDs are the first 12 characters 
40
+of the full image ID - which can be found using ``docker inspect`` or 
41
+``docker images -notrunc=true``
42
+
43
+.. _dockergroup:
44
+
45
+Running an interactive shell
46
+----------------------------
47
+
48
+.. code-block:: bash
49
+
33 50
   # Run an interactive shell in the ubuntu image,
34 51
   # allocate a tty, attach stdin and stdout
35 52
   # To detach the tty without exiting the shell,
36 53
   # use the escape sequence Ctrl-p + Ctrl-q
37 54
   sudo docker run -i -t ubuntu /bin/bash
38 55
 
39
-.. _dockergroup:
40 56
 
41 57
 Why ``sudo``?
42 58
 -------------
... ...
@@ -116,6 +116,16 @@ core concepts of Docker where commits are cheap and containers can be
116 116
 created from any point in an image's history, much like source
117 117
 control.
118 118
 
119
+Known Issues (RUN)
120
+..................
121
+
122
+* :issue:`783` is about file permissions problems that can occur when
123
+  using the AUFS file system. You might notice it during an attempt to
124
+  ``rm`` a file, for example. The issue describes a workaround.
125
+* :issue:`2424` Locale will not be set automatically.
126
+
127
+
128
+
119 129
 3.4 CMD
120 130
 -------
121 131
 
... ...
@@ -211,8 +221,16 @@ destination container.
211 211
 All new files and directories are created with mode 0755, uid and gid
212 212
 0.
213 213
 
214
+.. note::
215
+   if you build using STDIN (``docker build - < somefile``), there is no build 
216
+   context, so the Dockerfile can only contain an URL based ADD statement.
217
+
214 218
 The copy obeys the following rules:
215 219
 
220
+* The ``<src>`` path must be inside the *context* of the build; you cannot 
221
+  ``ADD ../something /something``, because the first step of a 
222
+  ``docker build`` is to send the context directory (and subdirectories) to 
223
+  the docker daemon.
216 224
 * If ``<src>`` is a URL and ``<dest>`` does not end with a trailing slash,
217 225
   then a file is downloaded from the URL and copied to ``<dest>``.
218 226
 * If ``<src>`` is a URL and ``<dest>`` does end with a trailing slash,
... ...
@@ -20,3 +20,4 @@ Contents:
20 20
    puppet
21 21
    host_integration
22 22
    working_with_volumes
23
+   working_with_links_names
23 24
new file mode 100644
... ...
@@ -0,0 +1,104 @@
0
+:title: Working with Links and Names
1
+:description: How to create and use links and names
2
+:keywords: Examples, Usage, links, docker, documentation, examples, names, name, container naming
3
+
4
+.. _working_with_links_names:
5
+
6
+Working with Links and Names
7
+============================
8
+
9
+From version 0.6.5 you are now able to ``name`` a container and ``link`` it to another
10
+container by referring to its name. This will create a parent -> child relationship
11
+where the parent container can see selected information about its child.
12
+
13
+.. _run_name:
14
+
15
+Container Naming
16
+----------------
17
+
18
+.. versionadded:: v0.6.5
19
+
20
+You can now name your container by using the ``-name`` flag. If no name is provided, Docker
21
+will automatically generate a name. You can see this name using the ``docker ps`` command.
22
+
23
+.. code-block:: bash
24
+
25
+    # format is "sudo docker run -name <container_name> <image_name> <command>"
26
+    $ sudo docker run -name test ubuntu /bin/bash
27
+
28
+    # the flag "-a" Show all containers. Only running containers are shown by default.
29
+    $ sudo docker ps -a
30
+    CONTAINER ID        IMAGE                            COMMAND             CREATED             STATUS              PORTS               NAMES
31
+    2522602a0d99        ubuntu:12.04                     /bin/bash           14 seconds ago      Exit 0                                  test
32
+
33
+.. _run_link:
34
+
35
+Links: service discovery for docker
36
+-----------------------------------
37
+
38
+.. versionadded:: v0.6.5
39
+
40
+Links allow containers to discover and securely communicate with each other by using the
41
+flag ``-link name:alias``. Inter-container communication can be disabled with the daemon
42
+flag ``-icc=false``. With this flag set to false, Container A cannot access Container B
43
+unless explicitly allowed via a link. This is a huge win for securing your containers.
44
+When two containers are linked together Docker creates a parent child relationship
45
+between the containers. The parent container will be able to access information via
46
+environment variables of the child such as name, exposed ports, IP and other selected
47
+environment variables.
48
+
49
+When linking two containers Docker will use the exposed ports of the container to create
50
+a secure tunnel for the parent to access. If a database container only exposes port 8080
51
+then the linked container will only be allowed to access port 8080 and nothing else if
52
+inter-container communication is set to false.
53
+
54
+.. code-block:: bash
55
+
56
+    # Example: there is an image called redis-2.6 that exposes the port 6379 and starts redis-server.
57
+    # Let's name the container as "redis" based on that image and run it as daemon.
58
+    $ sudo docker run -d -name redis redis-2.6
59
+
60
+We can issue all the commands that you would expect using the name "redis"; start, stop,
61
+attach, using the name for our container. The name also allows us to link other containers
62
+into this one.
63
+
64
+Next, we can start a new web application that has a dependency on Redis and apply a link
65
+to connect both containers. If you noticed when running our Redis server we did not use
66
+the -p flag to publish the Redis port to the host system. Redis exposed port 6379 and
67
+this is all we need to establish a link.
68
+
69
+.. code-block:: bash
70
+
71
+    # Linking the redis container as a child
72
+    $ sudo docker run -t -i -link redis:db -name webapp ubuntu bash
73
+
74
+When you specified -link redis:db you are telling docker to link the container named redis
75
+into this new container with the alias db. Environment variables are prefixed with the alias
76
+so that the parent container can access network and environment information from the containers
77
+that are linked into it.
78
+
79
+If we inspect the environment variables of the second container, we would see all the information
80
+about the child container.
81
+
82
+.. code-block:: bash
83
+
84
+    $ root@4c01db0b339c:/# env
85
+
86
+    HOSTNAME=4c01db0b339c
87
+    DB_NAME=/webapp/db
88
+    TERM=xterm
89
+    DB_PORT=tcp://172.17.0.8:6379
90
+    DB_PORT_6379_TCP=tcp://172.17.0.8:6379
91
+    DB_PORT_6379_TCP_PROTO=tcp
92
+    DB_PORT_6379_TCP_ADDR=172.17.0.8
93
+    DB_PORT_6379_TCP_PORT=6379
94
+    PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
95
+    PWD=/
96
+    SHLVL=1
97
+    HOME=/
98
+    container=lxc
99
+    _=/usr/bin/env
100
+    root@4c01db0b339c:/#
101
+
102
+Accessing the network information along with the environment of the child container allows
103
+us to easily connect to the Redis service on the specific IP and port in the environment.
... ...
@@ -129,7 +129,8 @@
129 129
                 <div class="row footer">
130 130
                     <div class="span12 tbox">
131 131
                         <div class="tbox">
132
-                            <p>Docker is an open source project, sponsored by  <a href="https://dotcloud.com">dotCloud</a>, under the <a href="https://github.com/dotcloud/docker/blob/master/LICENSE" title="Docker licence, hosted in the Github repository">apache 2.0 licence</a></p>
132
+                            <p>Docker is an open source project, sponsored by  <a href="https://www.docker.com">Docker Inc.</a>, under the <a href="https://github.com/dotcloud/docker/blob/master/LICENSE" title="Docker licence, hosted in the Github repository">apache 2.0 licence</a></p>
133
+                            <p>Documentation proudly hosted by <a href="http://www.readthedocs.org">Read the Docs</a></p>
133 134
                         </div>
134 135
 
135 136
                         <div class="social links">
... ...
@@ -6,15 +6,21 @@ import (
6 6
 	"log"
7 7
 	"os"
8 8
 	"runtime"
9
+	"strings"
9 10
 )
10 11
 
11 12
 type Handler func(*Job) string
12 13
 
13 14
 var globalHandlers map[string]Handler
14 15
 
16
+func init() {
17
+	globalHandlers = make(map[string]Handler)
18
+}
19
+
15 20
 func Register(name string, handler Handler) error {
16
-	if globalHandlers == nil {
17
-		globalHandlers = make(map[string]Handler)
21
+	_, exists := globalHandlers[name]
22
+	if exists {
23
+		return fmt.Errorf("Can't overwrite global handler for command %s", name)
18 24
 	}
19 25
 	globalHandlers[name] = handler
20 26
 	return nil
... ...
@@ -26,6 +32,22 @@ func Register(name string, handler Handler) error {
26 26
 type Engine struct {
27 27
 	root     string
28 28
 	handlers map[string]Handler
29
+	hack     Hack // data for temporary hackery (see hack.go)
30
+	id       string
31
+}
32
+
33
+func (eng *Engine) Root() string {
34
+	return eng.root
35
+}
36
+
37
+func (eng *Engine) Register(name string, handler Handler) error {
38
+	eng.Logf("Register(%s) (handlers=%v)", name, eng.handlers)
39
+	_, exists := eng.handlers[name]
40
+	if exists {
41
+		return fmt.Errorf("Can't overwrite handler for command %s", name)
42
+	}
43
+	eng.handlers[name] = handler
44
+	return nil
29 45
 }
30 46
 
31 47
 // New initializes a new engine managing the directory specified at `root`.
... ...
@@ -56,16 +78,25 @@ func New(root string) (*Engine, error) {
56 56
 	}
57 57
 	eng := &Engine{
58 58
 		root:     root,
59
-		handlers: globalHandlers,
59
+		handlers: make(map[string]Handler),
60
+		id:       utils.RandomString(),
61
+	}
62
+	// Copy existing global handlers
63
+	for k, v := range globalHandlers {
64
+		eng.handlers[k] = v
60 65
 	}
61 66
 	return eng, nil
62 67
 }
63 68
 
69
+func (eng *Engine) String() string {
70
+	return fmt.Sprintf("%s|%s", eng.Root(), eng.id[:8])
71
+}
72
+
64 73
 // Job creates a new job which can later be executed.
65 74
 // This function mimics `Command` from the standard os/exec package.
66 75
 func (eng *Engine) Job(name string, args ...string) *Job {
67 76
 	job := &Job{
68
-		eng:    eng,
77
+		Eng:    eng,
69 78
 		Name:   name,
70 79
 		Args:   args,
71 80
 		Stdin:  os.Stdin,
... ...
@@ -78,3 +109,8 @@ func (eng *Engine) Job(name string, args ...string) *Job {
78 78
 	}
79 79
 	return job
80 80
 }
81
+
82
+func (eng *Engine) Logf(format string, args ...interface{}) (n int, err error) {
83
+	prefixedFormat := fmt.Sprintf("[%s] %s\n", eng, strings.TrimRight(format, "\n"))
84
+	return fmt.Fprintf(os.Stderr, prefixedFormat, args...)
85
+}
81 86
new file mode 100644
... ...
@@ -0,0 +1,21 @@
0
+package engine
1
+
2
+type Hack map[string]interface{}
3
+
4
+func (eng *Engine) Hack_GetGlobalVar(key string) interface{} {
5
+	if eng.hack == nil {
6
+		return nil
7
+	}
8
+	val, exists := eng.hack[key]
9
+	if !exists {
10
+		return nil
11
+	}
12
+	return val
13
+}
14
+
15
+func (eng *Engine) Hack_SetGlobalVar(key string, val interface{}) {
16
+	if eng.hack == nil {
17
+		eng.hack = make(Hack)
18
+	}
19
+	eng.hack[key] = val
20
+}
0 21
deleted file mode 100644
... ...
@@ -1,42 +0,0 @@
1
-package engine
2
-
3
-import (
4
-	"fmt"
5
-	"github.com/dotcloud/docker/utils"
6
-	"io/ioutil"
7
-	"runtime"
8
-	"strings"
9
-	"testing"
10
-)
11
-
12
-var globalTestID string
13
-
14
-func init() {
15
-	Register("dummy", func(job *Job) string { return "" })
16
-}
17
-
18
-func mkEngine(t *testing.T) *Engine {
19
-	// Use the caller function name as a prefix.
20
-	// This helps trace temp directories back to their test.
21
-	pc, _, _, _ := runtime.Caller(1)
22
-	callerLongName := runtime.FuncForPC(pc).Name()
23
-	parts := strings.Split(callerLongName, ".")
24
-	callerShortName := parts[len(parts)-1]
25
-	if globalTestID == "" {
26
-		globalTestID = utils.RandomString()[:4]
27
-	}
28
-	prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, callerShortName)
29
-	root, err := ioutil.TempDir("", prefix)
30
-	if err != nil {
31
-		t.Fatal(err)
32
-	}
33
-	eng, err := New(root)
34
-	if err != nil {
35
-		t.Fatal(err)
36
-	}
37
-	return eng
38
-}
39
-
40
-func mkJob(t *testing.T, name string, args ...string) *Job {
41
-	return mkEngine(t).Job(name, args...)
42
-}
... ...
@@ -1,11 +1,16 @@
1 1
 package engine
2 2
 
3 3
 import (
4
+	"bufio"
5
+	"bytes"
4 6
 	"encoding/json"
5 7
 	"fmt"
6
-	"github.com/dotcloud/docker/utils"
7 8
 	"io"
9
+	"io/ioutil"
10
+	"os"
11
+	"strconv"
8 12
 	"strings"
13
+	"sync"
9 14
 )
10 15
 
11 16
 // A job is the fundamental unit of work in the docker engine.
... ...
@@ -22,24 +27,43 @@ import (
22 22
 // This allows for richer error reporting.
23 23
 //
24 24
 type Job struct {
25
-	eng     *Engine
25
+	Eng     *Engine
26 26
 	Name    string
27 27
 	Args    []string
28 28
 	env     []string
29
-	Stdin   io.ReadCloser
30
-	Stdout  io.WriteCloser
31
-	Stderr  io.WriteCloser
29
+	Stdin   io.Reader
30
+	Stdout  io.Writer
31
+	Stderr  io.Writer
32 32
 	handler func(*Job) string
33 33
 	status  string
34
+	onExit  []func()
34 35
 }
35 36
 
36 37
 // Run executes the job and blocks until the job completes.
37 38
 // If the job returns a failure status, an error is returned
38 39
 // which includes the status.
39 40
 func (job *Job) Run() error {
40
-	randId := utils.RandomString()[:4]
41
-	fmt.Printf("Job #%s: %s\n", randId, job)
42
-	defer fmt.Printf("Job #%s: %s = '%s'", randId, job, job.status)
41
+	defer func() {
42
+		var wg sync.WaitGroup
43
+		for _, f := range job.onExit {
44
+			wg.Add(1)
45
+			go func(f func()) {
46
+				f()
47
+				wg.Done()
48
+			}(f)
49
+		}
50
+		wg.Wait()
51
+	}()
52
+	if job.Stdout != nil && job.Stdout != os.Stdout {
53
+		job.Stdout = io.MultiWriter(job.Stdout, os.Stdout)
54
+	}
55
+	if job.Stderr != nil && job.Stderr != os.Stderr {
56
+		job.Stderr = io.MultiWriter(job.Stderr, os.Stderr)
57
+	}
58
+	job.Eng.Logf("+job %s", job.CallString())
59
+	defer func() {
60
+		job.Eng.Logf("-job %s%s", job.CallString(), job.StatusString())
61
+	}()
43 62
 	if job.handler == nil {
44 63
 		job.status = "command not found"
45 64
 	} else {
... ...
@@ -51,9 +75,87 @@ func (job *Job) Run() error {
51 51
 	return nil
52 52
 }
53 53
 
54
+func (job *Job) StdoutParseLines(dst *[]string, limit int) {
55
+	job.parseLines(job.StdoutPipe(), dst, limit)
56
+}
57
+
58
+func (job *Job) StderrParseLines(dst *[]string, limit int) {
59
+	job.parseLines(job.StderrPipe(), dst, limit)
60
+}
61
+
62
+func (job *Job) parseLines(src io.Reader, dst *[]string, limit int) {
63
+	var wg sync.WaitGroup
64
+	wg.Add(1)
65
+	go func() {
66
+		defer wg.Done()
67
+		scanner := bufio.NewScanner(src)
68
+		for scanner.Scan() {
69
+			// If the limit is reached, flush the rest of the source and return
70
+			if limit > 0 && len(*dst) >= limit {
71
+				io.Copy(ioutil.Discard, src)
72
+				return
73
+			}
74
+			line := scanner.Text()
75
+			// Append the line (with delimitor removed)
76
+			*dst = append(*dst, line)
77
+		}
78
+	}()
79
+	job.onExit = append(job.onExit, wg.Wait)
80
+}
81
+
82
+func (job *Job) StdoutParseString(dst *string) {
83
+	lines := make([]string, 0, 1)
84
+	job.StdoutParseLines(&lines, 1)
85
+	job.onExit = append(job.onExit, func() {
86
+		if len(lines) >= 1 {
87
+			*dst = lines[0]
88
+		}
89
+	})
90
+}
91
+
92
+func (job *Job) StderrParseString(dst *string) {
93
+	lines := make([]string, 0, 1)
94
+	job.StderrParseLines(&lines, 1)
95
+	job.onExit = append(job.onExit, func() { *dst = lines[0] })
96
+}
97
+
98
+func (job *Job) StdoutPipe() io.ReadCloser {
99
+	r, w := io.Pipe()
100
+	job.Stdout = w
101
+	job.onExit = append(job.onExit, func() { w.Close() })
102
+	return r
103
+}
104
+
105
+func (job *Job) StderrPipe() io.ReadCloser {
106
+	r, w := io.Pipe()
107
+	job.Stderr = w
108
+	job.onExit = append(job.onExit, func() { w.Close() })
109
+	return r
110
+}
111
+
112
+func (job *Job) CallString() string {
113
+	return fmt.Sprintf("%s(%s)", job.Name, strings.Join(job.Args, ", "))
114
+}
115
+
116
+func (job *Job) StatusString() string {
117
+	// FIXME: if a job returns the empty string, it will be printed
118
+	// as not having returned.
119
+	// (this only affects String which is a convenience function).
120
+	if job.status != "" {
121
+		var okerr string
122
+		if job.status == "0" {
123
+			okerr = "OK"
124
+		} else {
125
+			okerr = "ERR"
126
+		}
127
+		return fmt.Sprintf(" = %s (%s)", okerr, job.status)
128
+	}
129
+	return ""
130
+}
131
+
54 132
 // String returns a human-readable description of `job`
55 133
 func (job *Job) String() string {
56
-	return strings.Join(append([]string{job.Name}, job.Args...), " ")
134
+	return fmt.Sprintf("%s.%s%s", job.Eng, job.CallString(), job.StatusString())
57 135
 }
58 136
 
59 137
 func (job *Job) Getenv(key string) (value string) {
... ...
@@ -90,6 +192,19 @@ func (job *Job) SetenvBool(key string, value bool) {
90 90
 	}
91 91
 }
92 92
 
93
+func (job *Job) GetenvInt(key string) int64 {
94
+	s := strings.Trim(job.Getenv(key), " \t")
95
+	val, err := strconv.ParseInt(s, 10, 64)
96
+	if err != nil {
97
+		return -1
98
+	}
99
+	return val
100
+}
101
+
102
+func (job *Job) SetenvInt(key string, value int64) {
103
+	job.Setenv(key, fmt.Sprintf("%d", value))
104
+}
105
+
93 106
 func (job *Job) GetenvList(key string) []string {
94 107
 	sval := job.Getenv(key)
95 108
 	l := make([]string, 0, 1)
... ...
@@ -111,3 +226,109 @@ func (job *Job) SetenvList(key string, value []string) error {
111 111
 func (job *Job) Setenv(key, value string) {
112 112
 	job.env = append(job.env, key+"="+value)
113 113
 }
114
+
115
+// DecodeEnv decodes `src` as a json dictionary, and adds
116
+// each decoded key-value pair to the environment.
117
+//
118
+// If `text` cannot be decoded as a json dictionary, an error
119
+// is returned.
120
+func (job *Job) DecodeEnv(src io.Reader) error {
121
+	m := make(map[string]interface{})
122
+	if err := json.NewDecoder(src).Decode(&m); err != nil {
123
+		return err
124
+	}
125
+	for k, v := range m {
126
+		// FIXME: we fix-convert float values to int, because
127
+		// encoding/json decodes integers to float64, but cannot encode them back.
128
+		// (See http://golang.org/src/pkg/encoding/json/decode.go#L46)
129
+		if fval, ok := v.(float64); ok {
130
+			job.SetenvInt(k, int64(fval))
131
+		} else if sval, ok := v.(string); ok {
132
+			job.Setenv(k, sval)
133
+		} else if val, err := json.Marshal(v); err == nil {
134
+			job.Setenv(k, string(val))
135
+		} else {
136
+			job.Setenv(k, fmt.Sprintf("%v", v))
137
+		}
138
+	}
139
+	return nil
140
+}
141
+
142
+func (job *Job) EncodeEnv(dst io.Writer) error {
143
+	m := make(map[string]interface{})
144
+	for k, v := range job.Environ() {
145
+		var val interface{}
146
+		if err := json.Unmarshal([]byte(v), &val); err == nil {
147
+			// FIXME: we fix-convert float values to int, because
148
+			// encoding/json decodes integers to float64, but cannot encode them back.
149
+			// (See http://golang.org/src/pkg/encoding/json/decode.go#L46)
150
+			if fval, isFloat := val.(float64); isFloat {
151
+				val = int(fval)
152
+			}
153
+			m[k] = val
154
+		} else {
155
+			m[k] = v
156
+		}
157
+	}
158
+	if err := json.NewEncoder(dst).Encode(&m); err != nil {
159
+		return err
160
+	}
161
+	return nil
162
+}
163
+
164
+func (job *Job) ExportEnv(dst interface{}) (err error) {
165
+	defer func() {
166
+		if err != nil {
167
+			err = fmt.Errorf("ExportEnv %s", err)
168
+		}
169
+	}()
170
+	var buf bytes.Buffer
171
+	// step 1: encode/marshal the env to an intermediary json representation
172
+	if err := job.EncodeEnv(&buf); err != nil {
173
+		return err
174
+	}
175
+	// step 2: decode/unmarshal the intermediary json into the destination object
176
+	if err := json.NewDecoder(&buf).Decode(dst); err != nil {
177
+		return err
178
+	}
179
+	return nil
180
+}
181
+
182
+func (job *Job) ImportEnv(src interface{}) (err error) {
183
+	defer func() {
184
+		if err != nil {
185
+			err = fmt.Errorf("ImportEnv: %s", err)
186
+		}
187
+	}()
188
+	var buf bytes.Buffer
189
+	if err := json.NewEncoder(&buf).Encode(src); err != nil {
190
+		return err
191
+	}
192
+	if err := job.DecodeEnv(&buf); err != nil {
193
+		return err
194
+	}
195
+	return nil
196
+}
197
+
198
+func (job *Job) Environ() map[string]string {
199
+	m := make(map[string]string)
200
+	for _, kv := range job.env {
201
+		parts := strings.SplitN(kv, "=", 2)
202
+		m[parts[0]] = parts[1]
203
+	}
204
+	return m
205
+}
206
+
207
+func (job *Job) Logf(format string, args ...interface{}) (n int, err error) {
208
+	prefixedFormat := fmt.Sprintf("[%s] %s\n", job, strings.TrimRight(format, "\n"))
209
+	return fmt.Fprintf(job.Stderr, prefixedFormat, args...)
210
+}
211
+
212
+func (job *Job) Printf(format string, args ...interface{}) (n int, err error) {
213
+	return fmt.Fprintf(job.Stdout, format, args...)
214
+}
215
+
216
+func (job *Job) Errorf(format string, args ...interface{}) (n int, err error) {
217
+	return fmt.Fprintf(job.Stderr, format, args...)
218
+
219
+}
114 220
new file mode 100644
... ...
@@ -0,0 +1,42 @@
0
+package engine
1
+
2
+import (
3
+	"fmt"
4
+	"github.com/dotcloud/docker/utils"
5
+	"io/ioutil"
6
+	"runtime"
7
+	"strings"
8
+	"testing"
9
+)
10
+
11
+var globalTestID string
12
+
13
+func init() {
14
+	Register("dummy", func(job *Job) string { return "" })
15
+}
16
+
17
+func newTestEngine(t *testing.T) *Engine {
18
+	// Use the caller function name as a prefix.
19
+	// This helps trace temp directories back to their test.
20
+	pc, _, _, _ := runtime.Caller(1)
21
+	callerLongName := runtime.FuncForPC(pc).Name()
22
+	parts := strings.Split(callerLongName, ".")
23
+	callerShortName := parts[len(parts)-1]
24
+	if globalTestID == "" {
25
+		globalTestID = utils.RandomString()[:4]
26
+	}
27
+	prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, callerShortName)
28
+	root, err := ioutil.TempDir("", prefix)
29
+	if err != nil {
30
+		t.Fatal(err)
31
+	}
32
+	eng, err := New(root)
33
+	if err != nil {
34
+		t.Fatal(err)
35
+	}
36
+	return eng
37
+}
38
+
39
+func mkJob(t *testing.T, name string, args ...string) *Job {
40
+	return newTestEngine(t).Job(name, args...)
41
+}
... ...
@@ -48,7 +48,7 @@ type WalkFunc func(fullPath string, entity *Entity) error
48 48
 // Graph database for storing entities and their relationships
49 49
 type Database struct {
50 50
 	conn *sql.DB
51
-	mux  sync.Mutex
51
+	mux  sync.RWMutex
52 52
 }
53 53
 
54 54
 // Create a new graph database initialized with a root entity
... ...
@@ -138,7 +138,14 @@ func (db *Database) Set(fullPath, id string) (*Entity, error) {
138 138
 
139 139
 // Return true if a name already exists in the database
140 140
 func (db *Database) Exists(name string) bool {
141
-	return db.Get(name) != nil
141
+	db.mux.RLock()
142
+	defer db.mux.RUnlock()
143
+
144
+	e, err := db.get(name)
145
+	if err != nil {
146
+		return false
147
+	}
148
+	return e != nil
142 149
 }
143 150
 
144 151
 func (db *Database) setEdge(parentPath, name string, e *Entity) error {
... ...
@@ -165,6 +172,9 @@ func (db *Database) RootEntity() *Entity {
165 165
 
166 166
 // Return the entity for a given path
167 167
 func (db *Database) Get(name string) *Entity {
168
+	db.mux.RLock()
169
+	defer db.mux.RUnlock()
170
+
168 171
 	e, err := db.get(name)
169 172
 	if err != nil {
170 173
 		return nil
... ...
@@ -200,23 +210,36 @@ func (db *Database) get(name string) (*Entity, error) {
200 200
 // List all entities by from the name
201 201
 // The key will be the full path of the entity
202 202
 func (db *Database) List(name string, depth int) Entities {
203
+	db.mux.RLock()
204
+	defer db.mux.RUnlock()
205
+
203 206
 	out := Entities{}
204 207
 	e, err := db.get(name)
205 208
 	if err != nil {
206 209
 		return out
207 210
 	}
208
-	for c := range db.children(e, name, depth) {
211
+
212
+	children, err := db.children(e, name, depth, nil)
213
+	if err != nil {
214
+		return out
215
+	}
216
+
217
+	for _, c := range children {
209 218
 		out[c.FullPath] = c.Entity
210 219
 	}
211 220
 	return out
212 221
 }
213 222
 
223
+// Walk through the child graph of an entity, calling walkFunc for each child entity.
224
+// It is safe for walkFunc to call graph functions.
214 225
 func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
215
-	e, err := db.get(name)
226
+	children, err := db.Children(name, depth)
216 227
 	if err != nil {
217 228
 		return err
218 229
 	}
219
-	for c := range db.children(e, name, depth) {
230
+
231
+	// Note: the database lock must not be held while calling walkFunc
232
+	for _, c := range children {
220 233
 		if err := walkFunc(c.FullPath, c.Entity); err != nil {
221 234
 			return err
222 235
 		}
... ...
@@ -224,8 +247,24 @@ func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
224 224
 	return nil
225 225
 }
226 226
 
227
+// Return the children of the specified entity
228
+func (db *Database) Children(name string, depth int) ([]WalkMeta, error) {
229
+	db.mux.RLock()
230
+	defer db.mux.RUnlock()
231
+
232
+	e, err := db.get(name)
233
+	if err != nil {
234
+		return nil, err
235
+	}
236
+
237
+	return db.children(e, name, depth, nil)
238
+}
239
+
227 240
 // Return the refrence count for a specified id
228 241
 func (db *Database) Refs(id string) int {
242
+	db.mux.RLock()
243
+	defer db.mux.RUnlock()
244
+
229 245
 	var count int
230 246
 	if err := db.conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil {
231 247
 		return 0
... ...
@@ -235,6 +274,9 @@ func (db *Database) Refs(id string) int {
235 235
 
236 236
 // Return all the id's path references
237 237
 func (db *Database) RefPaths(id string) Edges {
238
+	db.mux.RLock()
239
+	defer db.mux.RUnlock()
240
+
238 241
 	refs := Edges{}
239 242
 
240 243
 	rows, err := db.conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id)
... ...
@@ -356,56 +398,51 @@ type WalkMeta struct {
356 356
 	Edge     *Edge
357 357
 }
358 358
 
359
-func (db *Database) children(e *Entity, name string, depth int) <-chan WalkMeta {
360
-	out := make(chan WalkMeta)
359
+func (db *Database) children(e *Entity, name string, depth int, entities []WalkMeta) ([]WalkMeta, error) {
361 360
 	if e == nil {
362
-		close(out)
363
-		return out
361
+		return entities, nil
362
+	}
363
+
364
+	rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
365
+	if err != nil {
366
+		return nil, err
364 367
 	}
368
+	defer rows.Close()
365 369
 
366
-	go func() {
367
-		rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
368
-		if err != nil {
369
-			close(out)
370
+	for rows.Next() {
371
+		var entityId, entityName string
372
+		if err := rows.Scan(&entityId, &entityName); err != nil {
373
+			return nil, err
374
+		}
375
+		child := &Entity{entityId}
376
+		edge := &Edge{
377
+			ParentID: e.id,
378
+			Name:     entityName,
379
+			EntityID: child.id,
370 380
 		}
371
-		defer rows.Close()
372 381
 
373
-		for rows.Next() {
374
-			var entityId, entityName string
375
-			if err := rows.Scan(&entityId, &entityName); err != nil {
376
-				// Log error
377
-				continue
378
-			}
379
-			child := &Entity{entityId}
380
-			edge := &Edge{
381
-				ParentID: e.id,
382
-				Name:     entityName,
383
-				EntityID: child.id,
384
-			}
382
+		meta := WalkMeta{
383
+			Parent:   e,
384
+			Entity:   child,
385
+			FullPath: path.Join(name, edge.Name),
386
+			Edge:     edge,
387
+		}
385 388
 
386
-			meta := WalkMeta{
387
-				Parent:   e,
388
-				Entity:   child,
389
-				FullPath: path.Join(name, edge.Name),
390
-				Edge:     edge,
391
-			}
389
+		entities = append(entities, meta)
392 390
 
393
-			out <- meta
394
-			if depth == 0 {
395
-				continue
396
-			}
391
+		if depth != 0 {
397 392
 			nDepth := depth
398 393
 			if depth != -1 {
399 394
 				nDepth -= 1
400 395
 			}
401
-			sc := db.children(child, meta.FullPath, nDepth)
402
-			for c := range sc {
403
-				out <- c
396
+			entities, err = db.children(child, meta.FullPath, nDepth, entities)
397
+			if err != nil {
398
+				return nil, err
404 399
 			}
405 400
 		}
406
-		close(out)
407
-	}()
408
-	return out
401
+	}
402
+
403
+	return entities, nil
409 404
 }
410 405
 
411 406
 // Return the entity based on the parent path and name
... ...
@@ -5,7 +5,7 @@ It is a curated selection of planned improvements which are either important, di
5 5
 
6 6
 For a more complete view of planned and requested improvements, see [the Github issues](https://github.com/dotcloud/docker/issues).
7 7
 
8
-Tu suggest changes to the roadmap, including additions, please write the change as if it were already in effect, and make a pull request.
8
+To suggest changes to the roadmap, including additions, please write the change as if it were already in effect, and make a pull request.
9 9
 
10 10
 
11 11
 ## Container wiring and service discovery
... ...
@@ -1,14 +1,16 @@
1
-# VERSION:        0.22
2
-# DOCKER-VERSION  0.6.3
3
-# AUTHOR:         Daniel Mizyrycki <daniel@dotcloud.com>
4
-# DESCRIPTION:    Deploy docker-ci on Amazon EC2
1
+# VERSION:        0.25
2
+# DOCKER-VERSION  0.6.6
3
+# AUTHOR:         Daniel Mizyrycki <daniel@docker.com>
4
+# DESCRIPTION:    Deploy docker-ci on Digital Ocean
5 5
 # COMMENTS:
6 6
 #     CONFIG_JSON is an environment variable json string loaded as:
7 7
 #
8 8
 # export CONFIG_JSON='
9
-#     { "AWS_TAG":             "EC2_instance_name",
10
-#       "AWS_ACCESS_KEY":      "EC2_access_key",
11
-#       "AWS_SECRET_KEY":      "EC2_secret_key",
9
+#     { "DROPLET_NAME":        "docker-ci",
10
+#       "DO_CLIENT_ID":        "Digital_Ocean_client_id",
11
+#       "DO_API_KEY":          "Digital_Ocean_api_key",
12
+#       "DOCKER_KEY_ID":       "Digital_Ocean_ssh_key_id",
13
+#       "DOCKER_CI_KEY_PATH":  "docker-ci_private_key_path",
12 14
 #       "DOCKER_CI_PUB":       "$(cat docker-ci_ssh_public_key.pub)",
13 15
 #       "DOCKER_CI_KEY":       "$(cat docker-ci_ssh_private_key.key)",
14 16
 #       "BUILDBOT_PWD":        "Buildbot_server_password",
... ...
@@ -33,9 +35,11 @@
33 33
 
34 34
 from ubuntu:12.04
35 35
 
36
-run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > /etc/apt/sources.list
37
-run apt-get update; apt-get install -y python2.7 python-dev python-pip ssh rsync less vim
38
-run pip install boto fabric
36
+run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' \
37
+    > /etc/apt/sources.list
38
+run apt-get update; apt-get install -y git python2.7 python-dev libevent-dev \
39
+    python-pip ssh rsync less vim
40
+run pip install requests fabric
39 41
 
40 42
 # Add deployment code and set default container command
41 43
 add . /docker-ci
42 44
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+0.4.5
... ...
@@ -43,7 +43,7 @@ c['slavePortnum'] = PORT_MASTER
43 43
 
44 44
 # Schedulers
45 45
 c['schedulers'] = [ForceScheduler(name='trigger', builderNames=['docker',
46
-    'index','registry','coverage','nightlyrelease'])]
46
+    'index','registry','docker-coverage','registry-coverage','nightlyrelease'])]
47 47
 c['schedulers'] += [SingleBranchScheduler(name="all", treeStableTimer=None,
48 48
     change_filter=filter.ChangeFilter(branch='master',
49 49
     repository='https://github.com/dotcloud/docker'), builderNames=['docker'])]
... ...
@@ -51,7 +51,7 @@ c['schedulers'] += [SingleBranchScheduler(name='pullrequest',
51 51
     change_filter=filter.ChangeFilter(category='github_pullrequest'), treeStableTimer=None,
52 52
     builderNames=['pullrequest'])]
53 53
 c['schedulers'] += [Nightly(name='daily', branch=None, builderNames=['nightlyrelease',
54
-    'coverage'], hour=7, minute=00)]
54
+    'docker-coverage','registry-coverage'], hour=7, minute=00)]
55 55
 c['schedulers'] += [Nightly(name='every4hrs', branch=None, builderNames=['registry','index'],
56 56
     hour=range(0,24,4), minute=15)]
57 57
 
... ...
@@ -76,17 +76,25 @@ c['builders'] += [BuilderConfig(name='pullrequest',slavenames=['buildworker'],
76 76
 
77 77
 # Docker coverage test
78 78
 factory = BuildFactory()
79
-factory.addStep(ShellCommand(description='Coverage', logEnviron=False,
79
+factory.addStep(ShellCommand(description='docker-coverage', logEnviron=False,
80 80
     usePTY=True, command='{0}/docker-coverage/coverage-docker.sh'.format(
81 81
     DOCKER_CI_PATH)))
82
-c['builders'] += [BuilderConfig(name='coverage',slavenames=['buildworker'],
82
+c['builders'] += [BuilderConfig(name='docker-coverage',slavenames=['buildworker'],
83
+    factory=factory)]
84
+
85
+# Docker registry coverage test
86
+factory = BuildFactory()
87
+factory.addStep(ShellCommand(description='registry-coverage', logEnviron=False,
88
+    usePTY=True, command='docker run registry_coverage'.format(
89
+    DOCKER_CI_PATH)))
90
+c['builders'] += [BuilderConfig(name='registry-coverage',slavenames=['buildworker'],
83 91
     factory=factory)]
84 92
 
85 93
 # Registry functional test
86 94
 factory = BuildFactory()
87 95
 factory.addStep(ShellCommand(description='registry', logEnviron=False,
88 96
     command='. {0}/master/credentials.cfg; '
89
-    '/docker-ci/functionaltests/test_registry.sh'.format(BUILDBOT_PATH),
97
+    '{1}/functionaltests/test_registry.sh'.format(BUILDBOT_PATH, DOCKER_CI_PATH),
90 98
     usePTY=True))
91 99
 c['builders'] += [BuilderConfig(name='registry',slavenames=['buildworker'],
92 100
     factory=factory)]
... ...
@@ -95,16 +103,17 @@ c['builders'] += [BuilderConfig(name='registry',slavenames=['buildworker'],
95 95
 factory = BuildFactory()
96 96
 factory.addStep(ShellCommand(description='index', logEnviron=False,
97 97
     command='. {0}/master/credentials.cfg; '
98
-    '/docker-ci/functionaltests/test_index.py'.format(BUILDBOT_PATH),
98
+    '{1}/functionaltests/test_index.py'.format(BUILDBOT_PATH, DOCKER_CI_PATH),
99 99
     usePTY=True))
100 100
 c['builders'] += [BuilderConfig(name='index',slavenames=['buildworker'],
101 101
     factory=factory)]
102 102
 
103 103
 # Docker nightly release
104
+nightlyrelease_cmd = ('docker version; docker run -i -t -privileged -e AWS_S3_BUCKET='
105
+    'test.docker.io dockerbuilder hack/dind dockerbuild.sh')
104 106
 factory = BuildFactory()
105
-factory.addStep(ShellCommand(description='NightlyRelease', logEnviron=False,
106
-    usePTY=True, command='docker run -privileged'
107
-    ' -e AWS_S3_BUCKET=test.docker.io dockerbuilder'))
107
+factory.addStep(ShellCommand(description='NightlyRelease',logEnviron=False,
108
+    usePTY=True, command=nightlyrelease_cmd))
108 109
 c['builders'] += [BuilderConfig(name='nightlyrelease',slavenames=['buildworker'],
109 110
     factory=factory)]
110 111
 
... ...
@@ -1,11 +1,11 @@
1 1
 #!/usr/bin/env python
2 2
 
3
-import os, sys, re, json, base64
4
-from boto.ec2.connection import EC2Connection
3
+import os, sys, re, json, requests, base64
5 4
 from subprocess import call
6 5
 from fabric import api
7 6
 from fabric.api import cd, run, put, sudo
8 7
 from os import environ as env
8
+from datetime import datetime
9 9
 from time import sleep
10 10
 
11 11
 # Remove SSH private key as it needs more processing
... ...
@@ -20,42 +20,41 @@ for key in CONFIG:
20 20
 env['DOCKER_CI_KEY'] = re.sub('^.+"DOCKER_CI_KEY".+?"(.+?)".+','\\1',
21 21
     env['CONFIG_JSON'],flags=re.DOTALL)
22 22
 
23
-
24
-AWS_TAG = env.get('AWS_TAG','docker-ci')
25
-AWS_KEY_NAME = 'dotcloud-dev'       # Same as CONFIG_JSON['DOCKER_CI_PUB']
26
-AWS_AMI = 'ami-d582d6bc'            # Ubuntu 13.04
27
-AWS_REGION = 'us-east-1'
28
-AWS_TYPE = 'm1.small'
29
-AWS_SEC_GROUPS = 'gateway'
30
-AWS_IMAGE_USER = 'ubuntu'
23
+DROPLET_NAME = env.get('DROPLET_NAME','docker-ci')
24
+TIMEOUT = 120            # Seconds before timeout droplet creation
25
+IMAGE_ID = 1004145       # Docker on Ubuntu 13.04
26
+REGION_ID = 4            # New York 2
27
+SIZE_ID = 62             # memory 2GB
28
+DO_IMAGE_USER = 'root'   # Image user on Digital Ocean
29
+API_URL = 'https://api.digitalocean.com/'
31 30
 DOCKER_PATH = '/go/src/github.com/dotcloud/docker'
32 31
 DOCKER_CI_PATH = '/docker-ci'
33 32
 CFG_PATH = '{}/buildbot'.format(DOCKER_CI_PATH)
34 33
 
35 34
 
36
-class AWS_EC2:
37
-    '''Amazon EC2'''
38
-    def __init__(self, access_key, secret_key):
35
+class DigitalOcean():
36
+
37
+    def __init__(self, key, client):
39 38
         '''Set default API parameters'''
40
-        self.handler = EC2Connection(access_key, secret_key)
41
-    def create_instance(self, tag, instance_type):
42
-        reservation = self.handler.run_instances(**instance_type)
43
-        instance = reservation.instances[0]
44
-        sleep(10)
45
-        while instance.state != 'running':
46
-            sleep(5)
47
-            instance.update()
48
-            print "Instance state: %s" % (instance.state)
49
-        instance.add_tag("Name",tag)
50
-        print "instance %s done!" % (instance.id)
51
-        return instance.ip_address
52
-    def get_instances(self):
53
-        return self.handler.get_all_instances()
54
-    def get_tags(self):
55
-        return dict([(i.instances[0].id, i.instances[0].tags['Name'])
56
-            for i in self.handler.get_all_instances() if i.instances[0].tags])
57
-    def del_instance(self, instance_id):
58
-        self.handler.terminate_instances(instance_ids=[instance_id])
39
+        self.key = key
40
+        self.client = client
41
+        self.api_url = API_URL
42
+
43
+    def api(self, cmd_path, api_arg={}):
44
+        '''Make api call'''
45
+        api_arg.update({'api_key':self.key, 'client_id':self.client})
46
+        resp = requests.get(self.api_url + cmd_path, params=api_arg).text
47
+        resp = json.loads(resp)
48
+        if resp['status'] != 'OK':
49
+            raise Exception(resp['error_message'])
50
+        return resp
51
+
52
+    def droplet_data(self, name):
53
+        '''Get droplet data'''
54
+        data = self.api('droplets')
55
+        data = [droplet for droplet in data['droplets']
56
+            if droplet['name'] == name]
57
+        return data[0] if data else {}
59 58
 
60 59
 
61 60
 def json_fmt(data):
... ...
@@ -63,20 +62,36 @@ def json_fmt(data):
63 63
     return json.dumps(data, sort_keys = True, indent = 2)
64 64
 
65 65
 
66
-# Create EC2 API handler
67
-ec2 = AWS_EC2(env['AWS_ACCESS_KEY'], env['AWS_SECRET_KEY'])
66
+do = DigitalOcean(env['DO_API_KEY'], env['DO_CLIENT_ID'])
67
+
68
+# Get DROPLET_NAME data
69
+data = do.droplet_data(DROPLET_NAME)
68 70
 
69
-# Stop processing if AWS_TAG exists on EC2
70
-if AWS_TAG in ec2.get_tags().values():
71
-    print ('Instance: {} already deployed. Not further processing.'
72
-        .format(AWS_TAG))
71
+# Stop processing if DROPLET_NAME exists on Digital Ocean
72
+if data:
73
+    print ('Droplet: {} already deployed. Not further processing.'
74
+        .format(DROPLET_NAME))
73 75
     exit(1)
74 76
 
75
-ip = ec2.create_instance(AWS_TAG, {'image_id':AWS_AMI, 'instance_type':AWS_TYPE,
76
-    'security_groups':[AWS_SEC_GROUPS], 'key_name':AWS_KEY_NAME})
77
+# Create droplet
78
+do.api('droplets/new', {'name':DROPLET_NAME, 'region_id':REGION_ID,
79
+    'image_id':IMAGE_ID, 'size_id':SIZE_ID,
80
+    'ssh_key_ids':[env['DOCKER_KEY_ID']]})
77 81
 
78
-# Wait 30 seconds for the machine to boot
79
-sleep(30)
82
+# Wait for droplet to be created.
83
+start_time = datetime.now()
84
+while (data.get('status','') != 'active' and (
85
+ datetime.now()-start_time).seconds < TIMEOUT):
86
+    data = do.droplet_data(DROPLET_NAME)
87
+    print data['status']
88
+    sleep(3)
89
+
90
+# Wait for the machine to boot
91
+sleep(15)
92
+
93
+# Get droplet IP
94
+ip = str(data['ip_address'])
95
+print 'droplet: {}    ip: {}'.format(DROPLET_NAME, ip)
80 96
 
81 97
 # Create docker-ci ssh private key so docker-ci docker container can communicate
82 98
 # with its EC2 instance
... ...
@@ -86,7 +101,7 @@ os.chmod('/root/.ssh/id_rsa',0600)
86 86
 open('/root/.ssh/config','w').write('StrictHostKeyChecking no\n')
87 87
 
88 88
 api.env.host_string = ip
89
-api.env.user = AWS_IMAGE_USER
89
+api.env.user = DO_IMAGE_USER
90 90
 api.env.key_filename = '/root/.ssh/id_rsa'
91 91
 
92 92
 # Correct timezone
... ...
@@ -100,20 +115,17 @@ sudo("echo '{}' >> /root/.ssh/authorized_keys".format(env['DOCKER_CI_PUB']))
100 100
 credentials = {
101 101
     'AWS_ACCESS_KEY': env['PKG_ACCESS_KEY'],
102 102
     'AWS_SECRET_KEY': env['PKG_SECRET_KEY'],
103
-    'GPG_PASSPHRASE': env['PKG_GPG_PASSPHRASE'],
104
-    'INDEX_AUTH': env['INDEX_AUTH']}
103
+    'GPG_PASSPHRASE': env['PKG_GPG_PASSPHRASE']}
105 104
 open(DOCKER_CI_PATH + '/nightlyrelease/release_credentials.json', 'w').write(
106 105
     base64.b64encode(json.dumps(credentials)))
107 106
 
108 107
 # Transfer docker
109 108
 sudo('mkdir -p ' + DOCKER_CI_PATH)
110
-sudo('chown {}.{} {}'.format(AWS_IMAGE_USER, AWS_IMAGE_USER, DOCKER_CI_PATH))
111
-call('/usr/bin/rsync -aH {} {}@{}:{}'.format(DOCKER_CI_PATH, AWS_IMAGE_USER, ip,
109
+sudo('chown {}.{} {}'.format(DO_IMAGE_USER, DO_IMAGE_USER, DOCKER_CI_PATH))
110
+call('/usr/bin/rsync -aH {} {}@{}:{}'.format(DOCKER_CI_PATH, DO_IMAGE_USER, ip,
112 111
     os.path.dirname(DOCKER_CI_PATH)), shell=True)
113 112
 
114 113
 # Install Docker and Buildbot dependencies
115
-sudo('addgroup docker')
116
-sudo('usermod -a -G docker ubuntu')
117 114
 sudo('mkdir /mnt/docker; ln -s /mnt/docker /var/lib/docker')
118 115
 sudo('wget -q -O - https://get.docker.io/gpg | apt-key add -')
119 116
 sudo('echo deb https://get.docker.io/ubuntu docker main >'
... ...
@@ -123,7 +135,7 @@ sudo('echo -e "deb http://archive.ubuntu.com/ubuntu raring main universe\n'
123 123
     ' > /etc/apt/sources.list; apt-get update')
124 124
 sudo('DEBIAN_FRONTEND=noninteractive apt-get install -q -y wget python-dev'
125 125
     ' python-pip supervisor git mercurial linux-image-extra-$(uname -r)'
126
-    ' aufs-tools make libfontconfig libevent-dev')
126
+    ' aufs-tools make libfontconfig libevent-dev libsqlite3-dev libssl-dev')
127 127
 sudo('wget -O - https://go.googlecode.com/files/go1.1.2.linux-amd64.tar.gz | '
128 128
     'tar -v -C /usr/local -xz; ln -s /usr/local/go/bin/go /usr/bin/go')
129 129
 sudo('GOPATH=/go go get -d github.com/dotcloud/docker')
... ...
@@ -135,13 +147,13 @@ sudo('curl -s https://phantomjs.googlecode.com/files/'
135 135
     'phantomjs-1.9.1-linux-x86_64.tar.bz2 | tar jx -C /usr/bin'
136 136
     ' --strip-components=2 phantomjs-1.9.1-linux-x86_64/bin/phantomjs')
137 137
 
138
-# Preventively reboot docker-ci daily
139
-sudo('ln -s /sbin/reboot /etc/cron.daily')
140
-
141 138
 # Build docker-ci containers
142 139
 sudo('cd {}; docker build -t docker .'.format(DOCKER_PATH))
140
+sudo('cd {}; docker build -t docker-ci .'.format(DOCKER_CI_PATH))
143 141
 sudo('cd {}/nightlyrelease; docker build -t dockerbuilder .'.format(
144 142
     DOCKER_CI_PATH))
143
+sudo('cd {}/registry-coverage; docker build -t registry_coverage .'.format(
144
+    DOCKER_CI_PATH))
145 145
 
146 146
 # Download docker-ci testing container
147 147
 sudo('docker pull mzdaniel/test_docker')
... ...
@@ -154,3 +166,6 @@ sudo('{0}/setup.sh root {0} {1} {2} {3} {4} {5} {6} {7} {8} {9} {10}'
154 154
     env['SMTP_PWD'], env['EMAIL_RCP'], env['REGISTRY_USER'],
155 155
     env['REGISTRY_PWD'], env['REGISTRY_BUCKET'], env['REGISTRY_ACCESS_KEY'],
156 156
     env['REGISTRY_SECRET_KEY']))
157
+
158
+# Preventively reboot docker-ci daily
159
+sudo('ln -s /sbin/reboot /etc/cron.daily')
... ...
@@ -1,6 +1,6 @@
1
-# VERSION:        0.3
2
-# DOCKER-VERSION  0.6.3
3
-# AUTHOR:         Daniel Mizyrycki <daniel@dotcloud.com>
1
+# VERSION:        0.4
2
+# DOCKER-VERSION  0.6.6
3
+# AUTHOR:         Daniel Mizyrycki <daniel@docker.com>
4 4
 # DESCRIPTION:    Testing docker PRs and commits on top of master using
5 5
 # REFERENCES:     This code reuses the excellent implementation of
6 6
 #                 Docker in Docker made by Jerome Petazzoni.
... ...
@@ -15,15 +15,10 @@
15 15
 # TO_RUN:         docker run -privileged test_docker hack/dind test_docker.sh [commit] [repo] [branch]
16 16
 
17 17
 from docker
18
-maintainer Daniel Mizyrycki <daniel@dotcloud.com>
18
+maintainer Daniel Mizyrycki <daniel@docker.com>
19 19
 
20
-# Setup go environment. Extracted from /Dockerfile
21
-env     CGO_ENABLED 0
22
-env     GOROOT  /goroot
23
-env     PATH    $PATH:/goroot/bin
24
-env     GOPATH  /go:/go/src/github.com/dotcloud/docker/vendor
25
-volume  /var/lib/docker
26
-workdir /go/src/github.com/dotcloud/docker
20
+# Setup go in PATH. Extracted from /Dockerfile
21
+env PATH /usr/local/go/bin:$PATH
27 22
 
28 23
 # Add test_docker.sh
29 24
 add test_docker.sh /usr/bin/test_docker.sh
... ...
@@ -8,31 +8,26 @@ BRANCH=${3-master}
8 8
 # Compute test paths
9 9
 DOCKER_PATH=/go/src/github.com/dotcloud/docker
10 10
 
11
+# Timestamp
12
+echo
13
+date; echo
14
+
11 15
 # Fetch latest master
16
+cd /
12 17
 rm -rf /go
13
-mkdir -p $DOCKER_PATH
18
+git clone -q -b master http://github.com/dotcloud/docker $DOCKER_PATH
14 19
 cd $DOCKER_PATH
15
-git init .
16
-git fetch -q http://github.com/dotcloud/docker master
17
-git reset --hard FETCH_HEAD
18 20
 
19 21
 # Merge commit
20
-#echo FIXME. Temporarily skip TestPrivilegedCanMount until DinD works reliable on AWS
21
-git pull -q https://github.com/mzdaniel/docker.git dind-aws || exit 1
22
-
23
-# Merge commit in top of master
24 22
 git fetch -q "$REPO" "$BRANCH"
25
-git merge --no-edit $COMMIT || exit 1
23
+git merge --no-edit $COMMIT || exit 255
26 24
 
27 25
 # Test commit
28
-go test -v; exit_status=$?
26
+./hack/make.sh test; exit_status=$?
29 27
 
30 28
 # Display load if test fails
31
-if [ $exit_status -eq 1 ] ; then
29
+if [ $exit_status -ne 0 ] ; then
32 30
     uptime; echo; free
33 31
 fi
34 32
 
35
-# Cleanup testing directory
36
-rm -rf $BASE_PATH
37
-
38 33
 exit $exit_status
... ...
@@ -8,10 +8,12 @@ rm -rf docker-registry
8 8
 # Setup the environment
9 9
 export SETTINGS_FLAVOR=test
10 10
 export DOCKER_REGISTRY_CONFIG=config_test.yml
11
+export PYTHONPATH=$(pwd)/docker-registry/test
11 12
 
12 13
 # Get latest docker registry
13 14
 git clone -q https://github.com/dotcloud/docker-registry.git
14 15
 cd docker-registry
16
+sed -Ei "s#(boto_bucket: ).+#\1_env:S3_BUCKET#" config_test.yml
15 17
 
16 18
 # Get dependencies
17 19
 pip install -q -r requirements.txt
... ...
@@ -20,7 +22,6 @@ pip install -q tox
20 20
 
21 21
 # Run registry tests
22 22
 tox || exit 1
23
-export PYTHONPATH=$(pwd)/docker-registry
24 23
 python -m unittest discover -p s3.py -s test || exit 1
25 24
 python -m unittest discover -p workflow.py -s test
26 25
 
... ...
@@ -1,20 +1,19 @@
1
-# VERSION:        1.2
2
-# DOCKER-VERSION  0.6.3
3
-# AUTHOR:         Daniel Mizyrycki <daniel@dotcloud.com>
1
+# VERSION:        1.6
2
+# DOCKER-VERSION  0.6.6
3
+# AUTHOR:         Daniel Mizyrycki <daniel@docker.com>
4 4
 # DESCRIPTION:    Build docker nightly release using Docker in Docker.
5 5
 # REFERENCES:     This code reuses the excellent implementation of docker in docker
6 6
 #                 made by Jerome Petazzoni.  https://github.com/jpetazzo/dind
7 7
 # COMMENTS:
8 8
 #   release_credentials.json is a base64 json encoded file containing:
9 9
 #       { "AWS_ACCESS_KEY": "Test_docker_AWS_S3_bucket_id",
10
-#         "AWS_SECRET_KEY='Test_docker_AWS_S3_bucket_key'
11
-#         "GPG_PASSPHRASE='Test_docker_GPG_passphrase_signature'
12
-#         "INDEX_AUTH='Encripted_index_authentication' }
10
+#         "AWS_SECRET_KEY": "Test_docker_AWS_S3_bucket_key",
11
+#         "GPG_PASSPHRASE": "Test_docker_GPG_passphrase_signature" }
13 12
 # TO_BUILD:       docker build -t dockerbuilder .
14
-# TO_RELEASE:     docker run -i -t -privileged  -e AWS_S3_BUCKET="test.docker.io" dockerbuilder
13
+# TO_RELEASE:     docker run -i -t -privileged -e AWS_S3_BUCKET="test.docker.io" dockerbuilder hack/dind dockerbuild.sh
15 14
 
16 15
 from docker
17
-maintainer Daniel Mizyrycki <daniel@dotcloud.com>
16
+maintainer Daniel Mizyrycki <daniel@docker.com>
18 17
 
19 18
 # Add docker dependencies and downloading packages
20 19
 run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > /etc/apt/sources.list
... ...
@@ -24,11 +23,8 @@ run apt-get update; apt-get install -y -q wget python2.7
24 24
 run wget -q -O /usr/bin/docker http://get.docker.io/builds/Linux/x86_64/docker-latest; chmod +x /usr/bin/docker
25 25
 
26 26
 # Add proto docker builder
27
-add ./dockerbuild /usr/bin/dockerbuild
28
-run chmod +x /usr/bin/dockerbuild
27
+add ./dockerbuild.sh /usr/bin/dockerbuild.sh
28
+run chmod +x /usr/bin/dockerbuild.sh
29 29
 
30 30
 # Add release credentials
31 31
 add ./release_credentials.json /root/release_credentials.json
32
-
33
-# Launch build process in a container
34
-cmd dockerbuild
35 32
deleted file mode 100644
... ...
@@ -1,50 +0,0 @@
1
-#!/bin/bash
2
-
3
-# Variables AWS_ACCESS_KEY, AWS_SECRET_KEY, PG_PASSPHRASE and INDEX_AUTH
4
-# are decoded from /root/release_credentials.json
5
-# Variable AWS_S3_BUCKET is passed to the environment from docker run -e
6
-
7
-# Enable debugging
8
-set -x
9
-
10
-# Fetch docker master branch
11
-rm -rf  /go/src/github.com/dotcloud/docker
12
-cd /
13
-git clone -q http://github.com/dotcloud/docker /go/src/github.com/dotcloud/docker
14
-cd /go/src/github.com/dotcloud/docker
15
-
16
-# Launch docker daemon using dind inside the container
17
-./hack/dind /usr/bin/docker -d &
18
-sleep 5
19
-
20
-# Add an uncommitted change to generate a timestamped release
21
-date > timestamp
22
-
23
-# Build the docker package using /Dockerfile
24
-docker build -t docker .
25
-
26
-# Run Docker unittests binary and Ubuntu package
27
-docker run -privileged docker hack/make.sh
28
-exit_status=$?
29
-
30
-# Display load if test fails
31
-if [ $exit_status -eq 1 ] ; then
32
-    uptime; echo; free
33
-    exit 1
34
-fi
35
-
36
-# Commit binary and ubuntu bundles for release
37
-docker commit -run '{"Env": ["PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin"], "WorkingDir": "/go/src/github.com/dotcloud/docker"}' $(docker ps -l -q) release
38
-
39
-# Turn debug off to load credentials from the environment
40
-set +x
41
-eval $(cat /root/release_credentials.json  | python -c '
42
-import sys,json,base64;
43
-d=json.loads(base64.b64decode(sys.stdin.read()));
44
-exec("""for k in d: print "export {0}=\\"{1}\\"".format(k,d[k])""")')
45
-set -x
46
-
47
-# Push docker nightly
48
-echo docker run -i -t -privileged -e AWS_S3_BUCKET=$AWS_S3_BUCKET -e AWS_ACCESS_KEY=XXXXX -e AWS_SECRET_KEY=XXXXX -e GPG_PASSPHRASE=XXXXX release  hack/release.sh
49
-set +x
50
-docker run -i -t -privileged -e AWS_S3_BUCKET=$AWS_S3_BUCKET -e AWS_ACCESS_KEY=$AWS_ACCESS_KEY -e AWS_SECRET_KEY=$AWS_SECRET_KEY -e GPG_PASSPHRASE=$GPG_PASSPHRASE  release  hack/release.sh
51 1
new file mode 100644
... ...
@@ -0,0 +1,40 @@
0
+#!/bin/bash
1
+
2
+# Variables AWS_ACCESS_KEY, AWS_SECRET_KEY and PG_PASSPHRASE are decoded
3
+# from /root/release_credentials.json
4
+# Variable AWS_S3_BUCKET is passed to the environment from docker run -e
5
+
6
+# Turn debug off to load credentials from the environment
7
+set +x
8
+eval $(cat /root/release_credentials.json  | python -c '
9
+import sys,json,base64;
10
+d=json.loads(base64.b64decode(sys.stdin.read()));
11
+exec("""for k in d: print "export {0}=\\"{1}\\"".format(k,d[k])""")')
12
+
13
+# Fetch docker master branch
14
+set -x
15
+cd /
16
+rm -rf /go
17
+git clone -q -b master http://github.com/dotcloud/docker /go/src/github.com/dotcloud/docker
18
+cd /go/src/github.com/dotcloud/docker
19
+
20
+# Launch docker daemon using dind inside the container
21
+/usr/bin/docker version
22
+/usr/bin/docker -d &
23
+sleep 5
24
+
25
+# Build Docker release container
26
+docker build -t docker .
27
+
28
+# Test docker and if everything works well, release
29
+echo docker run -i -t -privileged -e AWS_S3_BUCKET=$AWS_S3_BUCKET -e AWS_ACCESS_KEY=XXXXX -e AWS_SECRET_KEY=XXXXX -e GPG_PASSPHRASE=XXXXX docker hack/release.sh
30
+set +x
31
+docker run -privileged -i -t -e AWS_S3_BUCKET=$AWS_S3_BUCKET -e AWS_ACCESS_KEY=$AWS_ACCESS_KEY -e AWS_SECRET_KEY=$AWS_SECRET_KEY -e GPG_PASSPHRASE=$GPG_PASSPHRASE docker hack/release.sh
32
+exit_status=$?
33
+
34
+# Display load if test fails
35
+set -x
36
+if [ $exit_status -ne 0 ] ; then
37
+    uptime; echo; free
38
+    exit 1
39
+fi
0 40
deleted file mode 100644
... ...
@@ -1 +0,0 @@
1
-eyAiQVdTX0FDQ0VTU19LRVkiOiAiIiwKICAiQVdTX1NFQ1JFVF9LRVkiOiAiIiwKICAiR1BHX1BBU1NQSFJBU0UiOiAiIiwKICAiSU5ERVhfQVVUSCI6ICIiIH0=
2 1
new file mode 100644
... ...
@@ -0,0 +1,18 @@
0
+# VERSION:        0.1
1
+# DOCKER-VERSION  0.6.4
2
+# AUTHOR:         Daniel Mizyrycki <daniel@dotcloud.com>
3
+# DESCRIPTION:    Docker registry coverage
4
+# COMMENTS:       Add registry coverage into the docker-ci image
5
+# TO_BUILD:       docker build -t registry_coverage .
6
+# TO_RUN:         docker run registry_coverage
7
+
8
+from docker-ci
9
+maintainer Daniel Mizyrycki <daniel@dotcloud.com>
10
+
11
+# Add registry_coverager.sh and dependencies
12
+run pip install coverage flask pyyaml requests simplejson python-glanceclient \
13
+    blinker redis boto gevent rsa mock
14
+add registry_coverage.sh /usr/bin/registry_coverage.sh
15
+run chmod +x /usr/bin/registry_coverage.sh
16
+
17
+cmd "/usr/bin/registry_coverage.sh"
0 18
new file mode 100755
... ...
@@ -0,0 +1,18 @@
0
+#!/bin/bash
1
+
2
+set -x
3
+
4
+# Setup the environment
5
+REGISTRY_PATH=/data/docker-registry
6
+export SETTINGS_FLAVOR=test
7
+export DOCKER_REGISTRY_CONFIG=config_test.yml
8
+export PYTHONPATH=$REGISTRY_PATH/test
9
+
10
+# Fetch latest docker-registry master
11
+rm -rf $REGISTRY_PATH
12
+git clone https://github.com/dotcloud/docker-registry -b master $REGISTRY_PATH
13
+cd $REGISTRY_PATH
14
+
15
+# Generate coverage
16
+coverage run -m unittest discover test || exit 1
17
+coverage report --include='./*' --omit='./test/*'
... ...
@@ -34,7 +34,7 @@ env['DOCKER_CI_KEY'] = open(env['DOCKER_CI_KEY_PATH']).read()
34 34
 
35 35
 DROPLET_NAME = env.get('DROPLET_NAME','report')
36 36
 TIMEOUT = 120            # Seconds before timeout droplet creation
37
-IMAGE_ID = 894856        # Docker on Ubuntu 13.04
37
+IMAGE_ID = 1004145       # Docker on Ubuntu 13.04
38 38
 REGION_ID = 4            # New York 2
39 39
 SIZE_ID = 66             # memory 512MB
40 40
 DO_IMAGE_USER = 'root'   # Image user on Digital Ocean
... ...
@@ -22,7 +22,12 @@ bundle_test() {
22 22
 		for test_dir in $(find_test_dirs); do (
23 23
 			set -x
24 24
 			cd $test_dir
25
+			
26
+			# Install packages that are dependencies of the tests.
27
+			#   Note: Does not run the tests.
25 28
 			go test -i -ldflags "$LDFLAGS" $BUILDFLAGS
29
+			
30
+			# Run the tests with the optional $TESTFLAGS.
26 31
 			export TEST_DOCKERINIT_PATH=$DEST/../dynbinary/dockerinit-$VERSION
27 32
 			go test -v -ldflags "$LDFLAGS -X github.com/dotcloud/docker/utils.INITSHA1 \"$DOCKER_INITSHA1\"" $BUILDFLAGS $TESTFLAGS
28 33
 		)  done
... ...
@@ -16,7 +16,12 @@ bundle_test() {
16 16
 		for test_dir in $(find_test_dirs); do (
17 17
 			set -x
18 18
 			cd $test_dir
19
+			
20
+			# Install packages that are dependencies of the tests.
21
+			#   Note: Does not run the tests.
19 22
 			go test -i -ldflags "$LDFLAGS $LDFLAGS_STATIC" $BUILDFLAGS
23
+			
24
+			# Run the tests with the optional $TESTFLAGS.
20 25
 			go test -v -ldflags "$LDFLAGS $LDFLAGS_STATIC" $BUILDFLAGS $TESTFLAGS
21 26
 		)  done
22 27
 	} 2>&1 | tee $DEST/test.log
... ...
@@ -10,7 +10,7 @@ fi
10 10
 PACKAGE_ARCHITECTURE="$(dpkg-architecture -qDEB_HOST_ARCH)"
11 11
 PACKAGE_URL="http://www.docker.io/"
12 12
 PACKAGE_MAINTAINER="docker@dotcloud.com"
13
-PACKAGE_DESCRIPTION="lxc-docker is a Linux container runtime
13
+PACKAGE_DESCRIPTION="Linux container runtime
14 14
 Docker complements LXC with a high-level API which operates at the process
15 15
 level. It runs unix processes with strong guarantees of isolation and
16 16
 repeatability across servers.
... ...
@@ -37,27 +37,51 @@ bundle_ubuntu() {
37 37
 	# This will fail if the binary bundle hasn't been built
38 38
 	cp $DEST/../binary/docker-$VERSION $DIR/usr/bin/docker
39 39
 
40
-	# Generate postinst/prerm scripts
41
-	cat >/tmp/postinst <<'EOF'
40
+	# Generate postinst/prerm/postrm scripts
41
+	cat > /tmp/postinst <<'EOF'
42 42
 #!/bin/sh
43
-service docker stop || true
44
-grep -q '^docker:' /etc/group || groupadd --system docker || true
45
-service docker start
43
+set -e
44
+set -u
45
+
46
+getent group docker > /dev/null || groupadd --system docker || true
47
+
48
+update-rc.d docker defaults > /dev/null || true
49
+if [ -n "$2" ]; then
50
+	_dh_action=restart
51
+else
52
+	_dh_action=start
53
+fi
54
+service docker $_dh_action 2>/dev/null || true
55
+
56
+#DEBHELPER#
46 57
 EOF
47
-	cat >/tmp/prerm <<'EOF'
58
+	cat > /tmp/prerm <<'EOF'
48 59
 #!/bin/sh
49
-service docker stop || true
50
-
51
-case "$1" in
52
-	purge|remove|abort-install)
53
-		groupdel docker || true
54
-		;;
55
-		
56
-	upgrade|failed-upgrade|abort-upgrade)
57
-		# don't touch docker group
58
-		;;
59
-esac
60
+set -e
61
+set -u
62
+
63
+service docker stop 2>/dev/null || true
64
+
65
+#DEBHELPER#
60 66
 EOF
67
+	cat > /tmp/postrm <<'EOF'
68
+#!/bin/sh
69
+set -e
70
+set -u
71
+
72
+if [ "$1" = "purge" ] ; then
73
+	update-rc.d docker remove > /dev/null || true
74
+fi
75
+
76
+# In case this system is running systemd, we make systemd reload the unit files
77
+# to pick up changes.
78
+if [ -d /run/systemd/system ] ; then
79
+	systemctl --system daemon-reload > /dev/null || true
80
+fi
81
+
82
+#DEBHELPER#
83
+EOF
84
+	# TODO swaths of these were borrowed from debhelper's auto-inserted stuff, because we're still using fpm - we need to use debhelper instead, and somehow reconcile Ubuntu that way
61 85
 	chmod +x /tmp/postinst /tmp/prerm
62 86
 
63 87
 	(
... ...
@@ -66,6 +90,7 @@ EOF
66 66
 		    --name lxc-docker-$VERSION --version $PKGVERSION \
67 67
 		    --after-install /tmp/postinst \
68 68
 		    --before-remove /tmp/prerm \
69
+		    --after-remove /tmp/postrm \
69 70
 		    --architecture "$PACKAGE_ARCHITECTURE" \
70 71
 		    --prefix / \
71 72
 		    --depends lxc \
... ...
@@ -82,6 +107,8 @@ EOF
82 82
 		    --vendor "$PACKAGE_VENDOR" \
83 83
 		    --config-files /etc/init/docker.conf \
84 84
 		    --config-files /etc/init.d/docker \
85
+		    --config-files /etc/default/docker \
86
+		    --deb-compression xz \
85 87
 		    -t deb .
86 88
 		mkdir empty
87 89
 		fpm -s dir -C empty \
... ...
@@ -92,7 +119,12 @@ EOF
92 92
 		    --maintainer "$PACKAGE_MAINTAINER" \
93 93
 		    --url "$PACKAGE_URL" \
94 94
 		    --vendor "$PACKAGE_VENDOR" \
95
+		    --config-files /etc/init/docker.conf \
96
+		    --config-files /etc/init.d/docker \
97
+		    --config-files /etc/default/docker \
98
+		    --deb-compression xz \
95 99
 		    -t deb .
100
+		# note: the --config-files lines have to be duplicated to stop overwrite on package upgrade (since we have to use this funky virtual package)
96 101
 	)
97 102
 }
98 103
 
... ...
@@ -97,7 +97,7 @@ write_to_s3() {
97 97
 	DEST=$1
98 98
 	F=`mktemp`
99 99
 	cat > $F
100
-	s3cmd --acl-public put $F $DEST
100
+	s3cmd --acl-public --mime-type='text/plain' put $F $DEST
101 101
 	rm -f $F
102 102
 }
103 103
 
... ...
@@ -107,14 +107,14 @@ s3_url() {
107 107
 			echo "https://$BUCKET"
108 108
 			;;
109 109
 		*)
110
-			echo "http://$BUCKET.s3.amazonaws.com"
110
+			s3cmd ws-info s3://$BUCKET | awk -v 'FS=: +' '/http:\/\/'$BUCKET'/ { gsub(/\/+$/, "", $2); print $2 }'
111 111
 			;;
112 112
 	esac
113 113
 }
114 114
 
115 115
 # Upload the 'ubuntu' bundle to S3:
116 116
 # 1. A full APT repository is published at $BUCKET/ubuntu/
117
-# 2. Instructions for using the APT repository are uploaded at $BUCKET/ubuntu/info
117
+# 2. Instructions for using the APT repository are uploaded at $BUCKET/ubuntu/index
118 118
 release_ubuntu() {
119 119
 	[ -e bundles/$VERSION/ubuntu ] || {
120 120
 		echo >&2 './hack/make.sh must be run before release_ubuntu'
... ...
@@ -168,7 +168,7 @@ EOF
168 168
 
169 169
 	# Upload repo
170 170
 	s3cmd --acl-public sync $APTDIR/ s3://$BUCKET/ubuntu/
171
-	cat <<EOF | write_to_s3 s3://$BUCKET/ubuntu/info
171
+	cat <<EOF | write_to_s3 s3://$BUCKET/ubuntu/index
172 172
 # Add the repository to your APT sources
173 173
 echo deb $(s3_url)/ubuntu docker main > /etc/apt/sources.list.d/docker.list
174 174
 # Then import the repository key
... ...
@@ -180,7 +180,12 @@ apt-get update ; apt-get install -y lxc-docker
180 180
 # Alternatively, just use the curl-able install.sh script provided at $(s3_url)
181 181
 #
182 182
 EOF
183
-	echo "APT repository uploaded. Instructions available at $(s3_url)/ubuntu/info"
183
+
184
+	# Add redirect at /ubuntu/info for URL-backwards-compatibility
185
+	rm -rf /tmp/emptyfile && touch /tmp/emptyfile
186
+	s3cmd --acl-public --add-header='x-amz-website-redirect-location:/ubuntu/' --mime-type='text/plain' put /tmp/emptyfile s3://$BUCKET/ubuntu/info
187
+
188
+	echo "APT repository uploaded. Instructions available at $(s3_url)/ubuntu"
184 189
 }
185 190
 
186 191
 # Upload a static binary to S3
... ...
@@ -189,14 +194,20 @@ release_binary() {
189 189
 		echo >&2 './hack/make.sh must be run before release_binary'
190 190
 		exit 1
191 191
 	}
192
+
192 193
 	S3DIR=s3://$BUCKET/builds/Linux/x86_64
193 194
 	s3cmd --acl-public put bundles/$VERSION/binary/docker-$VERSION $S3DIR/docker-$VERSION
194
-	cat <<EOF | write_to_s3 s3://$BUCKET/builds/info
195
+	cat <<EOF | write_to_s3 s3://$BUCKET/builds/index
195 196
 # To install, run the following command as root:
196 197
 curl -O $(s3_url)/builds/Linux/x86_64/docker-$VERSION && chmod +x docker-$VERSION && sudo mv docker-$VERSION /usr/local/bin/docker
197 198
 # Then start docker in daemon mode:
198 199
 sudo /usr/local/bin/docker -d
199 200
 EOF
201
+
202
+	# Add redirect at /builds/info for URL-backwards-compatibility
203
+	rm -rf /tmp/emptyfile && touch /tmp/emptyfile
204
+	s3cmd --acl-public --add-header='x-amz-website-redirect-location:/builds/' --mime-type='text/plain' put /tmp/emptyfile s3://$BUCKET/builds/info
205
+
200 206
 	if [ -z "$NOLATEST" ]; then
201 207
 		echo "Copying docker-$VERSION to docker-latest"
202 208
 		s3cmd --acl-public cp $S3DIR/docker-$VERSION $S3DIR/docker-latest
... ...
@@ -134,10 +134,6 @@ func (image *Image) TarLayer(compression archive.Compression) (archive.Archive,
134 134
 	return archive.Tar(layerPath, compression)
135 135
 }
136 136
 
137
-func (image *Image) ShortID() string {
138
-	return utils.TruncateID(image.ID)
139
-}
140
-
141 137
 func ValidateID(id string) error {
142 138
 	if id == "" {
143 139
 		return fmt.Errorf("Image id can't be empty")
... ...
@@ -55,9 +55,16 @@ func RemoveExistingChain(name string) error {
55 55
 }
56 56
 
57 57
 func (c *Chain) Forward(action Action, ip net.IP, port int, proto, dest_addr string, dest_port int) error {
58
+	daddr := ip.String()
59
+	if ip.IsUnspecified() {
60
+		// iptables interprets "0.0.0.0" as "0.0.0.0/32", whereas we
61
+		// want "0.0.0.0/0". "0/0" is correctly interpreted as "any
62
+		// value" by both iptables and ip6tables.
63
+		daddr = "0/0"
64
+	}
58 65
 	if output, err := Raw("-t", "nat", fmt.Sprint(action), c.Name,
59 66
 		"-p", proto,
60
-		"-d", ip.String(),
67
+		"-d", daddr,
61 68
 		"--dport", strconv.Itoa(port),
62 69
 		"!", "-i", c.Bridge,
63 70
 		"-j", "DNAT",
... ...
@@ -168,12 +168,28 @@ func CreateBridgeIface(config *DaemonConfig) error {
168 168
 	}
169 169
 
170 170
 	if config.EnableIptables {
171
+		// Enable NAT
171 172
 		if output, err := iptables.Raw("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr,
172 173
 			"!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil {
173 174
 			return fmt.Errorf("Unable to enable network bridge NAT: %s", err)
174 175
 		} else if len(output) != 0 {
175 176
 			return fmt.Errorf("Error iptables postrouting: %s", output)
176 177
 		}
178
+
179
+		// Accept incoming packets for existing connections
180
+		if output, err := iptables.Raw("-I", "FORWARD", "-o", config.BridgeIface, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"); err != nil {
181
+			return fmt.Errorf("Unable to allow incoming packets: %s", err)
182
+		} else if len(output) != 0 {
183
+			return fmt.Errorf("Error iptables allow incoming: %s", output)
184
+		}
185
+
186
+		// Accept all non-intercontainer outgoing packets
187
+		if output, err := iptables.Raw("-I", "FORWARD", "-i", config.BridgeIface, "!", "-o", config.BridgeIface, "-j", "ACCEPT"); err != nil {
188
+			return fmt.Errorf("Unable to allow outgoing packets: %s", err)
189
+		} else if len(output) != 0 {
190
+			return fmt.Errorf("Error iptables allow outgoing: %s", output)
191
+		}
192
+
177 193
 	}
178 194
 	return nil
179 195
 }
... ...
@@ -680,20 +696,30 @@ func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) {
680 680
 
681 681
 	// Configure iptables for link support
682 682
 	if config.EnableIptables {
683
-		args := []string{"FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j", "DROP"}
683
+		args := []string{"FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j"}
684
+		acceptArgs := append(args, "ACCEPT")
685
+		dropArgs := append(args, "DROP")
684 686
 
685 687
 		if !config.InterContainerCommunication {
686
-			if !iptables.Exists(args...) {
688
+			iptables.Raw(append([]string{"-D"}, acceptArgs...)...)
689
+			if !iptables.Exists(dropArgs...) {
687 690
 				utils.Debugf("Disable inter-container communication")
688
-				if output, err := iptables.Raw(append([]string{"-A"}, args...)...); err != nil {
691
+				if output, err := iptables.Raw(append([]string{"-I"}, dropArgs...)...); err != nil {
689 692
 					return nil, fmt.Errorf("Unable to prevent intercontainer communication: %s", err)
690 693
 				} else if len(output) != 0 {
691
-					return nil, fmt.Errorf("Error enabling iptables: %s", output)
694
+					return nil, fmt.Errorf("Error disabling intercontainer communication: %s", output)
692 695
 				}
693 696
 			}
694 697
 		} else {
695
-			utils.Debugf("Enable inter-container communication")
696
-			iptables.Raw(append([]string{"-D"}, args...)...)
698
+			iptables.Raw(append([]string{"-D"}, dropArgs...)...)
699
+			if !iptables.Exists(acceptArgs...) {
700
+				utils.Debugf("Enable inter-container communication")
701
+				if output, err := iptables.Raw(append([]string{"-I"}, acceptArgs...)...); err != nil {
702
+					return nil, fmt.Errorf("Unable to allow intercontainer communication: %s", err)
703
+				} else if len(output) != 0 {
704
+					return nil, fmt.Errorf("Error enabling intercontainer communication: %s", output)
705
+				}
706
+			}
697 707
 		}
698 708
 	}
699 709
 
... ...
@@ -186,6 +186,7 @@ func (runtime *Runtime) Register(container *Container) error {
186 186
 	if !container.State.Running {
187 187
 		close(container.waitLock)
188 188
 	} else if !nomonitor {
189
+		container.allocateNetwork()
189 190
 		go container.monitor()
190 191
 	}
191 192
 	return nil
... ...
@@ -195,7 +196,7 @@ func (runtime *Runtime) ensureName(container *Container) error {
195 195
 	if container.Name == "" {
196 196
 		name, err := generateRandomName(runtime)
197 197
 		if err != nil {
198
-			name = container.ShortID()
198
+			name = utils.TruncateID(container.ID)
199 199
 		}
200 200
 		container.Name = name
201 201
 
... ...
@@ -298,7 +299,7 @@ func (runtime *Runtime) restore() error {
298 298
 		// Try to set the default name for a container if it exists prior to links
299 299
 		container.Name, err = generateRandomName(runtime)
300 300
 		if err != nil {
301
-			container.Name = container.ShortID()
301
+			container.Name = utils.TruncateID(container.ID)
302 302
 		}
303 303
 
304 304
 		if _, err := runtime.containerGraph.Set(container.Name, container.ID); err != nil {
... ...
@@ -506,32 +507,7 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
506 506
 		return nil, nil, err
507 507
 	}
508 508
 
509
-	// Step 3: if hostname, build hostname and hosts files
510
-	container.HostnamePath = path.Join(container.root, "hostname")
511
-	ioutil.WriteFile(container.HostnamePath, []byte(container.Config.Hostname+"\n"), 0644)
512
-
513
-	hostsContent := []byte(`
514
-127.0.0.1	localhost
515
-::1		localhost ip6-localhost ip6-loopback
516
-fe00::0		ip6-localnet
517
-ff00::0		ip6-mcastprefix
518
-ff02::1		ip6-allnodes
519
-ff02::2		ip6-allrouters
520
-`)
521
-
522
-	container.HostsPath = path.Join(container.root, "hosts")
523
-
524
-	if container.Config.Domainname != "" {
525
-		hostsContent = append([]byte(fmt.Sprintf("::1\t\t%s.%s %s\n", container.Config.Hostname, container.Config.Domainname, container.Config.Hostname)), hostsContent...)
526
-		hostsContent = append([]byte(fmt.Sprintf("127.0.0.1\t%s.%s %s\n", container.Config.Hostname, container.Config.Domainname, container.Config.Hostname)), hostsContent...)
527
-	} else {
528
-		hostsContent = append([]byte(fmt.Sprintf("::1\t\t%s\n", container.Config.Hostname)), hostsContent...)
529
-		hostsContent = append([]byte(fmt.Sprintf("127.0.0.1\t%s\n", container.Config.Hostname)), hostsContent...)
530
-	}
531
-
532
-	ioutil.WriteFile(container.HostsPath, hostsContent, 0644)
533
-
534
-	// Step 4: register the container
509
+	// Step 3: register the container
535 510
 	if err := runtime.Register(container); err != nil {
536 511
 		return nil, nil, err
537 512
 	}
... ...
@@ -3,11 +3,13 @@ package docker
3 3
 import (
4 4
 	"bytes"
5 5
 	"fmt"
6
+	"github.com/dotcloud/docker/engine"
6 7
 	"github.com/dotcloud/docker/sysinit"
7 8
 	"github.com/dotcloud/docker/utils"
8 9
 	"io"
9 10
 	"log"
10 11
 	"net"
12
+	"net/url"
11 13
 	"os"
12 14
 	"path/filepath"
13 15
 	"runtime"
... ...
@@ -122,22 +124,19 @@ func init() {
122 122
 }
123 123
 
124 124
 func setupBaseImage() {
125
-	config := &DaemonConfig{
126
-		Root:        unitTestStoreBase,
127
-		AutoRestart: false,
128
-		BridgeIface: unitTestNetworkBridge,
129
-	}
130
-	runtime, err := NewRuntimeFromDirectory(config)
125
+	eng, err := engine.New(unitTestStoreBase)
131 126
 	if err != nil {
132
-		log.Fatalf("Unable to create a runtime for tests:", err)
127
+		log.Fatalf("Can't initialize engine at %s: %s", unitTestStoreBase, err)
133 128
 	}
134
-
135
-	// Create the "Server"
136
-	srv := &Server{
137
-		runtime:     runtime,
138
-		pullingPool: make(map[string]struct{}),
139
-		pushingPool: make(map[string]struct{}),
129
+	job := eng.Job("initapi")
130
+	job.Setenv("Root", unitTestStoreBase)
131
+	job.SetenvBool("Autorestart", false)
132
+	job.Setenv("BridgeIface", unitTestNetworkBridge)
133
+	if err := job.Run(); err != nil {
134
+		log.Fatalf("Unable to create a runtime for tests:", err)
140 135
 	}
136
+	srv := mkServerFromEngine(eng, log.New(os.Stderr, "", 0))
137
+	runtime := srv.runtime
141 138
 
142 139
 	// If the unit test is not found, try to download it.
143 140
 	if img, err := runtime.repositories.LookupImage(unitTestImageName); err != nil || img.ID != unitTestImageID {
... ...
@@ -153,18 +152,22 @@ func spawnGlobalDaemon() {
153 153
 		utils.Debugf("Global runtime already exists. Skipping.")
154 154
 		return
155 155
 	}
156
-	globalRuntime = mkRuntime(log.New(os.Stderr, "", 0))
157
-	srv := &Server{
158
-		runtime:     globalRuntime,
159
-		pullingPool: make(map[string]struct{}),
160
-		pushingPool: make(map[string]struct{}),
161
-	}
156
+	t := log.New(os.Stderr, "", 0)
157
+	eng := NewTestEngine(t)
158
+	srv := mkServerFromEngine(eng, t)
159
+	globalRuntime = srv.runtime
162 160
 
163 161
 	// Spawn a Daemon
164 162
 	go func() {
165 163
 		utils.Debugf("Spawning global daemon for integration tests")
166
-		if err := ListenAndServe(testDaemonProto, testDaemonAddr, srv, os.Getenv("DEBUG") != ""); err != nil {
167
-			log.Fatalf("Unable to spawn the test daemon:", err)
164
+		listenURL := &url.URL{
165
+			Scheme: testDaemonProto,
166
+			Host:   testDaemonAddr,
167
+		}
168
+		job := eng.Job("serveapi", listenURL.String())
169
+		job.SetenvBool("Logging", os.Getenv("DEBUG") != "")
170
+		if err := job.Run(); err != nil {
171
+			log.Fatalf("Unable to spawn the test daemon: %s", err)
168 172
 		}
169 173
 	}()
170 174
 	// Give some time to ListenAndServer to actually start
... ...
@@ -184,7 +187,7 @@ func GetTestImage(runtime *Runtime) *Image {
184 184
 			return image
185 185
 		}
186 186
 	}
187
-	log.Fatalf("Test image %v not found", unitTestImageID)
187
+	log.Fatalf("Test image %v not found in %s: %s", unitTestImageID, runtime.graph.Root, imgs)
188 188
 	return nil
189 189
 }
190 190
 
... ...
@@ -646,20 +649,17 @@ func TestReloadContainerLinks(t *testing.T) {
646 646
 }
647 647
 
648 648
 func TestDefaultContainerName(t *testing.T) {
649
-	runtime := mkRuntime(t)
649
+	eng := NewTestEngine(t)
650
+	srv := mkServerFromEngine(eng, t)
651
+	runtime := srv.runtime
650 652
 	defer nuke(runtime)
651
-	srv := &Server{runtime: runtime}
652 653
 
653 654
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
654 655
 	if err != nil {
655 656
 		t.Fatal(err)
656 657
 	}
657 658
 
658
-	shortId, _, err := srv.ContainerCreate(config, "some_name")
659
-	if err != nil {
660
-		t.Fatal(err)
661
-	}
662
-	container := runtime.Get(shortId)
659
+	container := runtime.Get(createNamedTestContainer(eng, config, t, "some_name"))
663 660
 	containerID := container.ID
664 661
 
665 662
 	if container.Name != "/some_name" {
... ...
@@ -683,20 +683,17 @@ func TestDefaultContainerName(t *testing.T) {
683 683
 }
684 684
 
685 685
 func TestRandomContainerName(t *testing.T) {
686
-	runtime := mkRuntime(t)
686
+	eng := NewTestEngine(t)
687
+	srv := mkServerFromEngine(eng, t)
688
+	runtime := srv.runtime
687 689
 	defer nuke(runtime)
688
-	srv := &Server{runtime: runtime}
689 690
 
690 691
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
691 692
 	if err != nil {
692 693
 		t.Fatal(err)
693 694
 	}
694 695
 
695
-	shortId, _, err := srv.ContainerCreate(config, "")
696
-	if err != nil {
697
-		t.Fatal(err)
698
-	}
699
-	container := runtime.Get(shortId)
696
+	container := runtime.Get(createTestContainer(eng, config, t))
700 697
 	containerID := container.ID
701 698
 
702 699
 	if container.Name == "" {
... ...
@@ -720,20 +717,17 @@ func TestRandomContainerName(t *testing.T) {
720 720
 }
721 721
 
722 722
 func TestLinkChildContainer(t *testing.T) {
723
-	runtime := mkRuntime(t)
723
+	eng := NewTestEngine(t)
724
+	srv := mkServerFromEngine(eng, t)
725
+	runtime := srv.runtime
724 726
 	defer nuke(runtime)
725
-	srv := &Server{runtime: runtime}
726 727
 
727 728
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
728 729
 	if err != nil {
729 730
 		t.Fatal(err)
730 731
 	}
731 732
 
732
-	shortId, _, err := srv.ContainerCreate(config, "/webapp")
733
-	if err != nil {
734
-		t.Fatal(err)
735
-	}
736
-	container := runtime.Get(shortId)
733
+	container := runtime.Get(createNamedTestContainer(eng, config, t, "/webapp"))
737 734
 
738 735
 	webapp, err := runtime.GetByName("/webapp")
739 736
 	if err != nil {
... ...
@@ -749,12 +743,7 @@ func TestLinkChildContainer(t *testing.T) {
749 749
 		t.Fatal(err)
750 750
 	}
751 751
 
752
-	shortId, _, err = srv.ContainerCreate(config, "")
753
-	if err != nil {
754
-		t.Fatal(err)
755
-	}
756
-
757
-	childContainer := runtime.Get(shortId)
752
+	childContainer := runtime.Get(createTestContainer(eng, config, t))
758 753
 
759 754
 	if err := runtime.RegisterLink(webapp, childContainer, "db"); err != nil {
760 755
 		t.Fatal(err)
... ...
@@ -771,20 +760,17 @@ func TestLinkChildContainer(t *testing.T) {
771 771
 }
772 772
 
773 773
 func TestGetAllChildren(t *testing.T) {
774
-	runtime := mkRuntime(t)
774
+	eng := NewTestEngine(t)
775
+	srv := mkServerFromEngine(eng, t)
776
+	runtime := srv.runtime
775 777
 	defer nuke(runtime)
776
-	srv := &Server{runtime: runtime}
777 778
 
778 779
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
779 780
 	if err != nil {
780 781
 		t.Fatal(err)
781 782
 	}
782 783
 
783
-	shortId, _, err := srv.ContainerCreate(config, "/webapp")
784
-	if err != nil {
785
-		t.Fatal(err)
786
-	}
787
-	container := runtime.Get(shortId)
784
+	container := runtime.Get(createNamedTestContainer(eng, config, t, "/webapp"))
788 785
 
789 786
 	webapp, err := runtime.GetByName("/webapp")
790 787
 	if err != nil {
... ...
@@ -800,12 +786,7 @@ func TestGetAllChildren(t *testing.T) {
800 800
 		t.Fatal(err)
801 801
 	}
802 802
 
803
-	shortId, _, err = srv.ContainerCreate(config, "")
804
-	if err != nil {
805
-		t.Fatal(err)
806
-	}
807
-
808
-	childContainer := runtime.Get(shortId)
803
+	childContainer := runtime.Get(createTestContainer(eng, config, t))
809 804
 
810 805
 	if err := runtime.RegisterLink(webapp, childContainer, "db"); err != nil {
811 806
 		t.Fatal(err)
... ...
@@ -33,30 +33,25 @@ func (srv *Server) Close() error {
33 33
 }
34 34
 
35 35
 func init() {
36
-	engine.Register("serveapi", JobServeApi)
36
+	engine.Register("initapi", jobInitApi)
37 37
 }
38 38
 
39
-func JobServeApi(job *engine.Job) string {
40
-	srv, err := NewServer(ConfigFromJob(job))
39
+// jobInitApi runs the remote api server `srv` as a daemon,
40
+// Only one api server can run at the same time - this is enforced by a pidfile.
41
+// The signals SIGINT, SIGKILL and SIGTERM are intercepted for cleanup.
42
+func jobInitApi(job *engine.Job) string {
43
+	job.Logf("Creating server")
44
+	srv, err := NewServer(job.Eng, ConfigFromJob(job))
41 45
 	if err != nil {
42 46
 		return err.Error()
43 47
 	}
44
-	defer srv.Close()
45
-	if err := srv.Daemon(); err != nil {
46
-		return err.Error()
47
-	}
48
-	return "0"
49
-}
50
-
51
-// Daemon runs the remote api server `srv` as a daemon,
52
-// Only one api server can run at the same time - this is enforced by a pidfile.
53
-// The signals SIGINT, SIGKILL and SIGTERM are intercepted for cleanup.
54
-func (srv *Server) Daemon() error {
55
-	if err := utils.CreatePidFile(srv.runtime.config.Pidfile); err != nil {
56
-		log.Fatal(err)
48
+	if srv.runtime.config.Pidfile != "" {
49
+		job.Logf("Creating pidfile")
50
+		if err := utils.CreatePidFile(srv.runtime.config.Pidfile); err != nil {
51
+			log.Fatal(err)
52
+		}
57 53
 	}
58
-	defer utils.RemovePidFile(srv.runtime.config.Pidfile)
59
-
54
+	job.Logf("Setting up signal traps")
60 55
 	c := make(chan os.Signal, 1)
61 56
 	signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM))
62 57
 	go func() {
... ...
@@ -66,8 +61,21 @@ func (srv *Server) Daemon() error {
66 66
 		srv.Close()
67 67
 		os.Exit(0)
68 68
 	}()
69
+	job.Eng.Hack_SetGlobalVar("httpapi.server", srv)
70
+	if err := job.Eng.Register("create", srv.ContainerCreate); err != nil {
71
+		return err.Error()
72
+	}
73
+	if err := job.Eng.Register("start", srv.ContainerStart); err != nil {
74
+		return err.Error()
75
+	}
76
+	if err := job.Eng.Register("serveapi", srv.ListenAndServe); err != nil {
77
+		return err.Error()
78
+	}
79
+	return "0"
80
+}
69 81
 
70
-	protoAddrs := srv.runtime.config.ProtoAddresses
82
+func (srv *Server) ListenAndServe(job *engine.Job) string {
83
+	protoAddrs := job.Args
71 84
 	chErrors := make(chan error, len(protoAddrs))
72 85
 	for _, protoAddr := range protoAddrs {
73 86
 		protoAddrParts := strings.SplitN(protoAddr, "://", 2)
... ...
@@ -81,19 +89,20 @@ func (srv *Server) Daemon() error {
81 81
 				log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
82 82
 			}
83 83
 		default:
84
-			return fmt.Errorf("Invalid protocol format.")
84
+			return "Invalid protocol format."
85 85
 		}
86 86
 		go func() {
87
-			chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], srv, true)
87
+			// FIXME: merge Server.ListenAndServe with ListenAndServe
88
+			chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], srv, job.GetenvBool("Logging"))
88 89
 		}()
89 90
 	}
90 91
 	for i := 0; i < len(protoAddrs); i += 1 {
91 92
 		err := <-chErrors
92 93
 		if err != nil {
93
-			return err
94
+			return err.Error()
94 95
 		}
95 96
 	}
96
-	return nil
97
+	return "0"
97 98
 }
98 99
 
99 100
 func (srv *Server) DockerVersion() APIVersion {
... ...
@@ -154,7 +163,7 @@ func (srv *Server) ContainerKill(name string, sig int) error {
154 154
 			if err := container.Kill(); err != nil {
155 155
 				return fmt.Errorf("Cannot kill container %s: %s", name, err)
156 156
 			}
157
-			srv.LogEvent("kill", container.ShortID(), srv.runtime.repositories.ImageName(container.Image))
157
+			srv.LogEvent("kill", container.ID, srv.runtime.repositories.ImageName(container.Image))
158 158
 		} else {
159 159
 			// Otherwise, just send the requested signal
160 160
 			if err := container.kill(sig); err != nil {
... ...
@@ -180,7 +189,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
180 180
 		if _, err := io.Copy(out, data); err != nil {
181 181
 			return err
182 182
 		}
183
-		srv.LogEvent("export", container.ShortID(), srv.runtime.repositories.ImageName(container.Image))
183
+		srv.LogEvent("export", container.ID, srv.runtime.repositories.ImageName(container.Image))
184 184
 		return nil
185 185
 	}
186 186
 	return fmt.Errorf("No such container: %s", name)
... ...
@@ -198,39 +207,39 @@ func (srv *Server) ImagesSearch(term string) ([]registry.SearchResult, error) {
198 198
 	return results.Results, nil
199 199
 }
200 200
 
201
-func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils.StreamFormatter) (string, error) {
201
+func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils.StreamFormatter) error {
202 202
 	out = utils.NewWriteFlusher(out)
203 203
 	img, err := srv.runtime.repositories.LookupImage(name)
204 204
 	if err != nil {
205
-		return "", err
205
+		return err
206 206
 	}
207 207
 
208 208
 	file, err := utils.Download(url, out)
209 209
 	if err != nil {
210
-		return "", err
210
+		return err
211 211
 	}
212 212
 	defer file.Body.Close()
213 213
 
214 214
 	config, _, _, err := ParseRun([]string{img.ID, "echo", "insert", url, path}, srv.runtime.capabilities)
215 215
 	if err != nil {
216
-		return "", err
216
+		return err
217 217
 	}
218 218
 
219 219
 	c, _, err := srv.runtime.Create(config, "")
220 220
 	if err != nil {
221
-		return "", err
221
+		return err
222 222
 	}
223 223
 
224
-	if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf.FormatProgress("", "Downloading", "%8v/%v (%v)"), sf, true), path); err != nil {
225
-		return "", err
224
+	if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf.FormatProgress("", "Downloading", "%8v/%v (%v)"), sf, false), path); err != nil {
225
+		return err
226 226
 	}
227 227
 	// FIXME: Handle custom repo, tag comment, author
228 228
 	img, err = srv.runtime.Commit(c, "", "", img.Comment, img.Author, nil)
229 229
 	if err != nil {
230
-		return "", err
230
+		return err
231 231
 	}
232
-	out.Write(sf.FormatStatus("", img.ID))
233
-	return img.ShortID(), nil
232
+	out.Write(sf.FormatStatus(img.ID, ""))
233
+	return nil
234 234
 }
235 235
 
236 236
 func (srv *Server) ImagesViz(out io.Writer) error {
... ...
@@ -250,9 +259,9 @@ func (srv *Server) ImagesViz(out io.Writer) error {
250 250
 			return fmt.Errorf("Error while getting parent image: %v", err)
251 251
 		}
252 252
 		if parentImage != nil {
253
-			out.Write([]byte(" \"" + parentImage.ShortID() + "\" -> \"" + image.ShortID() + "\"\n"))
253
+			out.Write([]byte(" \"" + parentImage.ID + "\" -> \"" + image.ID + "\"\n"))
254 254
 		} else {
255
-			out.Write([]byte(" base -> \"" + image.ShortID() + "\" [style=invis]\n"))
255
+			out.Write([]byte(" base -> \"" + image.ID + "\" [style=invis]\n"))
256 256
 		}
257 257
 	}
258 258
 
... ...
@@ -465,7 +474,7 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API
465 465
 			continue
466 466
 		}
467 467
 		if before != "" {
468
-			if container.ShortID() == before {
468
+			if container.ID == before || utils.TruncateID(container.ID) == before {
469 469
 				foundBefore = true
470 470
 				continue
471 471
 			}
... ...
@@ -476,7 +485,7 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API
476 476
 		if displayed == n {
477 477
 			break
478 478
 		}
479
-		if container.ShortID() == since {
479
+		if container.ID == since || utils.TruncateID(container.ID) == since {
480 480
 			break
481 481
 		}
482 482
 		displayed++
... ...
@@ -518,7 +527,7 @@ func (srv *Server) ContainerCommit(name, repo, tag, author, comment string, conf
518 518
 	if err != nil {
519 519
 		return "", err
520 520
 	}
521
-	return img.ShortID(), err
521
+	return img.ID, err
522 522
 }
523 523
 
524 524
 func (srv *Server) ContainerTag(name, repo, tag string, force bool) error {
... ...
@@ -1018,37 +1027,47 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
1018 1018
 			return err
1019 1019
 		}
1020 1020
 	}
1021
-	out.Write(sf.FormatStatus("", img.ShortID()))
1021
+	out.Write(sf.FormatStatus("", img.ID))
1022 1022
 	return nil
1023 1023
 }
1024 1024
 
1025
-func (srv *Server) ContainerCreate(config *Config, name string) (string, []string, error) {
1025
+func (srv *Server) ContainerCreate(job *engine.Job) string {
1026
+	var name string
1027
+	if len(job.Args) == 1 {
1028
+		name = job.Args[0]
1029
+	} else if len(job.Args) > 1 {
1030
+		return fmt.Sprintf("Usage: %s ", job.Name)
1031
+	}
1032
+	var config Config
1033
+	if err := job.ExportEnv(&config); err != nil {
1034
+		return err.Error()
1035
+	}
1026 1036
 	if config.Memory != 0 && config.Memory < 524288 {
1027
-		return "", nil, fmt.Errorf("Memory limit must be given in bytes (minimum 524288 bytes)")
1037
+		return "Minimum memory limit allowed is 512k"
1028 1038
 	}
1029
-
1030 1039
 	if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
1031 1040
 		config.Memory = 0
1032 1041
 	}
1033
-
1034 1042
 	if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit {
1035 1043
 		config.MemorySwap = -1
1036 1044
 	}
1037
-	container, buildWarnings, err := srv.runtime.Create(config, name)
1045
+	container, buildWarnings, err := srv.runtime.Create(&config, name)
1038 1046
 	if err != nil {
1039 1047
 		if srv.runtime.graph.IsNotExist(err) {
1040
-
1041 1048
 			_, tag := utils.ParseRepositoryTag(config.Image)
1042 1049
 			if tag == "" {
1043 1050
 				tag = DEFAULTTAG
1044 1051
 			}
1045
-
1046
-			return "", nil, fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag)
1052
+			return fmt.Sprintf("No such image: %s (tag: %s)", config.Image, tag)
1047 1053
 		}
1048
-		return "", nil, err
1054
+		return err.Error()
1055
+	}
1056
+	srv.LogEvent("create", container.ID, srv.runtime.repositories.ImageName(container.Image))
1057
+	job.Printf("%s\n", container.ID)
1058
+	for _, warning := range buildWarnings {
1059
+		job.Errorf("%s\n", warning)
1049 1060
 	}
1050
-	srv.LogEvent("create", container.ShortID(), srv.runtime.repositories.ImageName(container.Image))
1051
-	return container.ShortID(), buildWarnings, nil
1061
+	return "0"
1052 1062
 }
1053 1063
 
1054 1064
 func (srv *Server) ContainerRestart(name string, t int) error {
... ...
@@ -1056,7 +1075,7 @@ func (srv *Server) ContainerRestart(name string, t int) error {
1056 1056
 		if err := container.Restart(t); err != nil {
1057 1057
 			return fmt.Errorf("Cannot restart container %s: %s", name, err)
1058 1058
 		}
1059
-		srv.LogEvent("restart", container.ShortID(), srv.runtime.repositories.ImageName(container.Image))
1059
+		srv.LogEvent("restart", container.ID, srv.runtime.repositories.ImageName(container.Image))
1060 1060
 	} else {
1061 1061
 		return fmt.Errorf("No such container: %s", name)
1062 1062
 	}
... ...
@@ -1112,7 +1131,7 @@ func (srv *Server) ContainerDestroy(name string, removeVolume, removeLink bool)
1112 1112
 		if err := srv.runtime.Destroy(container); err != nil {
1113 1113
 			return fmt.Errorf("Cannot destroy container %s: %s", name, err)
1114 1114
 		}
1115
-		srv.LogEvent("destroy", container.ShortID(), srv.runtime.repositories.ImageName(container.Image))
1115
+		srv.LogEvent("destroy", container.ID, srv.runtime.repositories.ImageName(container.Image))
1116 1116
 
1117 1117
 		if removeVolume {
1118 1118
 			// Retrieve all volumes from all remaining containers
... ...
@@ -1229,8 +1248,8 @@ func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, erro
1229 1229
 			return nil, err
1230 1230
 		}
1231 1231
 		if tagDeleted {
1232
-			imgs = append(imgs, APIRmi{Untagged: img.ShortID()})
1233
-			srv.LogEvent("untag", img.ShortID(), "")
1232
+			imgs = append(imgs, APIRmi{Untagged: img.ID})
1233
+			srv.LogEvent("untag", img.ID, "")
1234 1234
 		}
1235 1235
 	}
1236 1236
 	if len(srv.runtime.repositories.ByID()[img.ID]) == 0 {
... ...
@@ -1258,6 +1277,26 @@ func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) {
1258 1258
 		}
1259 1259
 		return nil, nil
1260 1260
 	}
1261
+
1262
+	// Prevent deletion if image is used by a running container
1263
+	for _, container := range srv.runtime.List() {
1264
+		if container.State.Running {
1265
+			parent, err := srv.runtime.repositories.LookupImage(container.Image)
1266
+			if err != nil {
1267
+				return nil, err
1268
+			}
1269
+
1270
+			if err := parent.WalkHistory(func(p *Image) error {
1271
+				if img.ID == p.ID {
1272
+					return fmt.Errorf("Conflict, cannot delete %s because the running container %s is using it", name, container.ID)
1273
+				}
1274
+				return nil
1275
+			}); err != nil {
1276
+				return nil, err
1277
+			}
1278
+		}
1279
+	}
1280
+
1261 1281
 	if strings.Contains(img.ID, name) {
1262 1282
 		//delete via ID
1263 1283
 		return srv.deleteImage(img, "", "")
... ...
@@ -1303,7 +1342,6 @@ func (srv *Server) RegisterLinks(name string, hostConfig *HostConfig) error {
1303 1303
 		return fmt.Errorf("No such container: %s", name)
1304 1304
 	}
1305 1305
 
1306
-	// Register links
1307 1306
 	if hostConfig != nil && hostConfig.Links != nil {
1308 1307
 		for _, l := range hostConfig.Links {
1309 1308
 			parts, err := parseLink(l)
... ...
@@ -1317,7 +1355,6 @@ func (srv *Server) RegisterLinks(name string, hostConfig *HostConfig) error {
1317 1317
 			if child == nil {
1318 1318
 				return fmt.Errorf("Could not get container for %s", parts["name"])
1319 1319
 			}
1320
-
1321 1320
 			if err := runtime.RegisterLink(container, child, parts["alias"]); err != nil {
1322 1321
 				return err
1323 1322
 			}
... ...
@@ -1333,41 +1370,57 @@ func (srv *Server) RegisterLinks(name string, hostConfig *HostConfig) error {
1333 1333
 	return nil
1334 1334
 }
1335 1335
 
1336
-func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error {
1336
+func (srv *Server) ContainerStart(job *engine.Job) string {
1337
+	if len(job.Args) < 1 {
1338
+		return fmt.Sprintf("Usage: %s container_id", job.Name)
1339
+	}
1340
+	name := job.Args[0]
1337 1341
 	runtime := srv.runtime
1338 1342
 	container := runtime.Get(name)
1339 1343
 
1340
-	if hostConfig != nil {
1344
+	if container == nil {
1345
+		return fmt.Sprintf("No such container: %s", name)
1346
+	}
1347
+	// If no environment was set, then no hostconfig was passed.
1348
+	if len(job.Environ()) > 0 {
1349
+		var hostConfig HostConfig
1350
+		if err := job.ExportEnv(&hostConfig); err != nil {
1351
+			return err.Error()
1352
+		}
1353
+		// Validate the HostConfig binds. Make sure that:
1354
+		// 1) the source of a bind mount isn't /
1355
+		//         The bind mount "/:/foo" isn't allowed.
1356
+		// 2) Check that the source exists
1357
+		//        The source to be bind mounted must exist.
1341 1358
 		for _, bind := range hostConfig.Binds {
1342 1359
 			splitBind := strings.Split(bind, ":")
1343 1360
 			source := splitBind[0]
1344 1361
 
1345 1362
 			// refuse to bind mount "/" to the container
1346 1363
 			if source == "/" {
1347
-				return fmt.Errorf("Invalid bind mount '%s' : source can't be '/'", bind)
1364
+				return fmt.Sprintf("Invalid bind mount '%s' : source can't be '/'", bind)
1348 1365
 			}
1349 1366
 
1350 1367
 			// ensure the source exists on the host
1351 1368
 			_, err := os.Stat(source)
1352 1369
 			if err != nil && os.IsNotExist(err) {
1353
-				return fmt.Errorf("Invalid bind mount '%s' : source doesn't exist", bind)
1370
+				return fmt.Sprintf("Invalid bind mount '%s' : source doesn't exist", bind)
1354 1371
 			}
1355 1372
 		}
1356
-	}
1357
-
1358
-	if container == nil {
1359
-		return fmt.Errorf("No such container: %s", name)
1360
-	}
1361
-	if hostConfig != nil {
1362
-		container.hostConfig = hostConfig
1373
+		// Register any links from the host config before starting the container
1374
+		// FIXME: we could just pass the container here, no need to lookup by name again.
1375
+		if err := srv.RegisterLinks(name, &hostConfig); err != nil {
1376
+			return err.Error()
1377
+		}
1378
+		container.hostConfig = &hostConfig
1363 1379
 		container.ToDisk()
1364 1380
 	}
1365 1381
 	if err := container.Start(); err != nil {
1366
-		return fmt.Errorf("Cannot start container %s: %s", name, err)
1382
+		return fmt.Sprintf("Cannot start container %s: %s", name, err)
1367 1383
 	}
1368
-	srv.LogEvent("start", container.ShortID(), runtime.repositories.ImageName(container.Image))
1384
+	srv.LogEvent("start", container.ID, runtime.repositories.ImageName(container.Image))
1369 1385
 
1370
-	return nil
1386
+	return "0"
1371 1387
 }
1372 1388
 
1373 1389
 func (srv *Server) ContainerStop(name string, t int) error {
... ...
@@ -1375,7 +1428,7 @@ func (srv *Server) ContainerStop(name string, t int) error {
1375 1375
 		if err := container.Stop(t); err != nil {
1376 1376
 			return fmt.Errorf("Cannot stop container %s: %s", name, err)
1377 1377
 		}
1378
-		srv.LogEvent("stop", container.ShortID(), srv.runtime.repositories.ImageName(container.Image))
1378
+		srv.LogEvent("stop", container.ID, srv.runtime.repositories.ImageName(container.Image))
1379 1379
 	} else {
1380 1380
 		return fmt.Errorf("No such container: %s", name)
1381 1381
 	}
... ...
@@ -1518,12 +1571,13 @@ func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) er
1518 1518
 
1519 1519
 }
1520 1520
 
1521
-func NewServer(config *DaemonConfig) (*Server, error) {
1521
+func NewServer(eng *engine.Engine, config *DaemonConfig) (*Server, error) {
1522 1522
 	runtime, err := NewRuntime(config)
1523 1523
 	if err != nil {
1524 1524
 		return nil, err
1525 1525
 	}
1526 1526
 	srv := &Server{
1527
+		Eng:         eng,
1527 1528
 		runtime:     runtime,
1528 1529
 		pullingPool: make(map[string]struct{}),
1529 1530
 		pushingPool: make(map[string]struct{}),
... ...
@@ -1567,4 +1621,5 @@ type Server struct {
1567 1567
 	events      []utils.JSONMessage
1568 1568
 	listeners   map[string]chan utils.JSONMessage
1569 1569
 	reqFactory  *utils.HTTPRequestFactory
1570
+	Eng         *engine.Engine
1570 1571
 }
... ...
@@ -2,6 +2,7 @@ package docker
2 2
 
3 3
 import (
4 4
 	"github.com/dotcloud/docker/utils"
5
+	"io/ioutil"
5 6
 	"strings"
6 7
 	"testing"
7 8
 	"time"
... ...
@@ -79,20 +80,17 @@ func TestContainerTagImageDelete(t *testing.T) {
79 79
 }
80 80
 
81 81
 func TestCreateRm(t *testing.T) {
82
-	runtime := mkRuntime(t)
82
+	eng := NewTestEngine(t)
83
+	srv := mkServerFromEngine(eng, t)
84
+	runtime := srv.runtime
83 85
 	defer nuke(runtime)
84 86
 
85
-	srv := &Server{runtime: runtime}
86
-
87 87
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
88 88
 	if err != nil {
89 89
 		t.Fatal(err)
90 90
 	}
91 91
 
92
-	id, _, err := srv.ContainerCreate(config, "")
93
-	if err != nil {
94
-		t.Fatal(err)
95
-	}
92
+	id := createTestContainer(eng, config, t)
96 93
 
97 94
 	if len(runtime.List()) != 1 {
98 95
 		t.Errorf("Expected 1 container, %v found", len(runtime.List()))
... ...
@@ -109,27 +107,28 @@ func TestCreateRm(t *testing.T) {
109 109
 }
110 110
 
111 111
 func TestCreateRmVolumes(t *testing.T) {
112
-	runtime := mkRuntime(t)
113
-	defer nuke(runtime)
112
+	eng := NewTestEngine(t)
114 113
 
115
-	srv := &Server{runtime: runtime}
114
+	srv := mkServerFromEngine(eng, t)
115
+	runtime := srv.runtime
116
+	defer nuke(runtime)
116 117
 
117 118
 	config, hostConfig, _, err := ParseRun([]string{"-v", "/srv", GetTestImage(runtime).ID, "echo test"}, nil)
118 119
 	if err != nil {
119 120
 		t.Fatal(err)
120 121
 	}
121 122
 
122
-	id, _, err := srv.ContainerCreate(config, "")
123
-	if err != nil {
124
-		t.Fatal(err)
125
-	}
123
+	id := createTestContainer(eng, config, t)
126 124
 
127 125
 	if len(runtime.List()) != 1 {
128 126
 		t.Errorf("Expected 1 container, %v found", len(runtime.List()))
129 127
 	}
130 128
 
131
-	err = srv.ContainerStart(id, hostConfig)
132
-	if err != nil {
129
+	job := eng.Job("start", id)
130
+	if err := job.ImportEnv(hostConfig); err != nil {
131
+		t.Fatal(err)
132
+	}
133
+	if err := job.Run(); err != nil {
133 134
 		t.Fatal(err)
134 135
 	}
135 136
 
... ...
@@ -148,20 +147,17 @@ func TestCreateRmVolumes(t *testing.T) {
148 148
 }
149 149
 
150 150
 func TestCommit(t *testing.T) {
151
-	runtime := mkRuntime(t)
151
+	eng := NewTestEngine(t)
152
+	srv := mkServerFromEngine(eng, t)
153
+	runtime := srv.runtime
152 154
 	defer nuke(runtime)
153 155
 
154
-	srv := &Server{runtime: runtime}
155
-
156 156
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil)
157 157
 	if err != nil {
158 158
 		t.Fatal(err)
159 159
 	}
160 160
 
161
-	id, _, err := srv.ContainerCreate(config, "")
162
-	if err != nil {
163
-		t.Fatal(err)
164
-	}
161
+	id := createTestContainer(eng, config, t)
165 162
 
166 163
 	if _, err := srv.ContainerCommit(id, "testrepo", "testtag", "", "", config); err != nil {
167 164
 		t.Fatal(err)
... ...
@@ -169,26 +165,27 @@ func TestCommit(t *testing.T) {
169 169
 }
170 170
 
171 171
 func TestCreateStartRestartStopStartKillRm(t *testing.T) {
172
-	runtime := mkRuntime(t)
172
+	eng := NewTestEngine(t)
173
+	srv := mkServerFromEngine(eng, t)
174
+	runtime := srv.runtime
173 175
 	defer nuke(runtime)
174 176
 
175
-	srv := &Server{runtime: runtime}
176
-
177 177
 	config, hostConfig, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil)
178 178
 	if err != nil {
179 179
 		t.Fatal(err)
180 180
 	}
181 181
 
182
-	id, _, err := srv.ContainerCreate(config, "")
183
-	if err != nil {
184
-		t.Fatal(err)
185
-	}
182
+	id := createTestContainer(eng, config, t)
186 183
 
187 184
 	if len(runtime.List()) != 1 {
188 185
 		t.Errorf("Expected 1 container, %v found", len(runtime.List()))
189 186
 	}
190 187
 
191
-	if err := srv.ContainerStart(id, hostConfig); err != nil {
188
+	job := eng.Job("start", id)
189
+	if err := job.ImportEnv(hostConfig); err != nil {
190
+		t.Fatal(err)
191
+	}
192
+	if err := job.Run(); err != nil {
192 193
 		t.Fatal(err)
193 194
 	}
194 195
 
... ...
@@ -200,7 +197,11 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
200 200
 		t.Fatal(err)
201 201
 	}
202 202
 
203
-	if err := srv.ContainerStart(id, hostConfig); err != nil {
203
+	job = eng.Job("start", id)
204
+	if err := job.ImportEnv(hostConfig); err != nil {
205
+		t.Fatal(err)
206
+	}
207
+	if err := job.Run(); err != nil {
204 208
 		t.Fatal(err)
205 209
 	}
206 210
 
... ...
@@ -220,22 +221,22 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
220 220
 }
221 221
 
222 222
 func TestRunWithTooLowMemoryLimit(t *testing.T) {
223
-	runtime := mkRuntime(t)
223
+	eng := NewTestEngine(t)
224
+	srv := mkServerFromEngine(eng, t)
225
+	runtime := srv.runtime
224 226
 	defer nuke(runtime)
225 227
 
226 228
 	// Try to create a container with a memory limit of 1 byte less than the minimum allowed limit.
227
-	if _, _, err := (*Server).ContainerCreate(&Server{runtime: runtime},
228
-		&Config{
229
-			Image:     GetTestImage(runtime).ID,
230
-			Memory:    524287,
231
-			CpuShares: 1000,
232
-			Cmd:       []string{"/bin/cat"},
233
-		},
234
-		"",
235
-	); err == nil {
229
+	job := eng.Job("create")
230
+	job.Setenv("Image", GetTestImage(runtime).ID)
231
+	job.Setenv("Memory", "524287")
232
+	job.Setenv("CpuShares", "1000")
233
+	job.SetenvList("Cmd", []string{"/bin/cat"})
234
+	var id string
235
+	job.StdoutParseString(&id)
236
+	if err := job.Run(); err == nil {
236 237
 		t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!")
237 238
 	}
238
-
239 239
 }
240 240
 
241 241
 func TestContainerTop(t *testing.T) {
... ...
@@ -384,9 +385,10 @@ func TestLogEvent(t *testing.T) {
384 384
 }
385 385
 
386 386
 func TestRmi(t *testing.T) {
387
-	runtime := mkRuntime(t)
387
+	eng := NewTestEngine(t)
388
+	srv := mkServerFromEngine(eng, t)
389
+	runtime := srv.runtime
388 390
 	defer nuke(runtime)
389
-	srv := &Server{runtime: runtime}
390 391
 
391 392
 	initialImages, err := srv.Images(false, "")
392 393
 	if err != nil {
... ...
@@ -398,14 +400,14 @@ func TestRmi(t *testing.T) {
398 398
 		t.Fatal(err)
399 399
 	}
400 400
 
401
-	containerID, _, err := srv.ContainerCreate(config, "")
402
-	if err != nil {
403
-		t.Fatal(err)
404
-	}
401
+	containerID := createTestContainer(eng, config, t)
405 402
 
406 403
 	//To remove
407
-	err = srv.ContainerStart(containerID, hostConfig)
408
-	if err != nil {
404
+	job := eng.Job("start", containerID)
405
+	if err := job.ImportEnv(hostConfig); err != nil {
406
+		t.Fatal(err)
407
+	}
408
+	if err := job.Run(); err != nil {
409 409
 		t.Fatal(err)
410 410
 	}
411 411
 
... ...
@@ -419,14 +421,14 @@ func TestRmi(t *testing.T) {
419 419
 		t.Fatal(err)
420 420
 	}
421 421
 
422
-	containerID, _, err = srv.ContainerCreate(config, "")
423
-	if err != nil {
424
-		t.Fatal(err)
425
-	}
422
+	containerID = createTestContainer(eng, config, t)
426 423
 
427 424
 	//To remove
428
-	err = srv.ContainerStart(containerID, hostConfig)
429
-	if err != nil {
425
+	job = eng.Job("start", containerID)
426
+	if err := job.ImportEnv(hostConfig); err != nil {
427
+		t.Fatal(err)
428
+	}
429
+	if err := job.Run(); err != nil {
430 430
 		t.Fatal(err)
431 431
 	}
432 432
 
... ...
@@ -521,3 +523,25 @@ func TestImagesFilter(t *testing.T) {
521 521
 		t.Fatal("incorrect number of matches returned")
522 522
 	}
523 523
 }
524
+
525
+func TestImageInsert(t *testing.T) {
526
+	runtime := mkRuntime(t)
527
+	defer nuke(runtime)
528
+	srv := &Server{runtime: runtime}
529
+	sf := utils.NewStreamFormatter(true)
530
+
531
+	// bad image name fails
532
+	if err := srv.ImageInsert("foo", "https://www.docker.io/static/img/docker-top-logo.png", "/foo", ioutil.Discard, sf); err == nil {
533
+		t.Fatal("expected an error and got none")
534
+	}
535
+
536
+	// bad url fails
537
+	if err := srv.ImageInsert(GetTestImage(runtime).ID, "http://bad_host_name_that_will_totally_fail.com/", "/foo", ioutil.Discard, sf); err == nil {
538
+		t.Fatal("expected an error and got none")
539
+	}
540
+
541
+	// success returns nil
542
+	if err := srv.ImageInsert(GetTestImage(runtime).ID, "https://www.docker.io/static/img/docker-top-logo.png", "/foo", ioutil.Discard, sf); err != nil {
543
+		t.Fatalf("expected no error, but got %v", err)
544
+	}
545
+}
... ...
@@ -119,6 +119,15 @@ func MergeConfig(userConf, imageConf *Config) error {
119 119
 	}
120 120
 	if userConf.ExposedPorts == nil || len(userConf.ExposedPorts) == 0 {
121 121
 		userConf.ExposedPorts = imageConf.ExposedPorts
122
+	} else if imageConf.ExposedPorts != nil {
123
+		if userConf.ExposedPorts == nil {
124
+			userConf.ExposedPorts = make(map[Port]struct{})
125
+		}
126
+		for port := range imageConf.ExposedPorts {
127
+			if _, exists := userConf.ExposedPorts[port]; !exists {
128
+				userConf.ExposedPorts[port] = struct{}{}
129
+			}
130
+		}
122 131
 	}
123 132
 
124 133
 	if userConf.PortSpecs != nil && len(userConf.PortSpecs) > 0 {
... ...
@@ -325,20 +334,6 @@ func migratePortMappings(config *Config, hostConfig *HostConfig) error {
325 325
 	return nil
326 326
 }
327 327
 
328
-func RootIsShared() bool {
329
-	if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil {
330
-		for _, line := range strings.Split(string(data), "\n") {
331
-			cols := strings.Split(line, " ")
332
-			if len(cols) >= 6 && cols[4] == "/" {
333
-				return strings.HasPrefix(cols[6], "shared")
334
-			}
335
-		}
336
-	}
337
-
338
-	// No idea, probably safe to assume so
339
-	return true
340
-}
341
-
342 328
 func BtrfsReflink(fd_out, fd_in uintptr) error {
343 329
 	res := C.btrfs_reflink(C.int(fd_out), C.int(fd_in))
344 330
 	if res != 0 {
... ...
@@ -353,6 +348,20 @@ func parseLink(rawLink string) (map[string]string, error) {
353 353
 	return utils.PartParser("name:alias", rawLink)
354 354
 }
355 355
 
356
+func RootIsShared() bool {
357
+	if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil {
358
+		for _, line := range strings.Split(string(data), "\n") {
359
+			cols := strings.Split(line, " ")
360
+			if len(cols) >= 6 && cols[4] == "/" {
361
+				return strings.HasPrefix(cols[6], "shared")
362
+			}
363
+		}
364
+	}
365
+
366
+	// No idea, probably safe to assume so
367
+	return true
368
+}
369
+
356 370
 type checker struct {
357 371
 	runtime *Runtime
358 372
 }
... ...
@@ -28,6 +28,12 @@ var (
28 28
 	INITSHA1  string // sha1sum of separate static dockerinit, if Docker itself was compiled dynamically via ./hack/make.sh dynbinary
29 29
 )
30 30
 
31
+// A common interface to access the Fatal method of
32
+// both testing.B and testing.T.
33
+type Fataler interface {
34
+	Fatal(args ...interface{})
35
+}
36
+
31 37
 // ListOpts type
32 38
 type ListOpts []string
33 39
 
... ...
@@ -177,6 +183,40 @@ func HumanSize(size int64) string {
177 177
 	return fmt.Sprintf("%.4g %s", sizef, units[i])
178 178
 }
179 179
 
180
+// Parses a human-readable string representing an amount of RAM
181
+// in bytes, kibibytes, mebibytes or gibibytes, and returns the
182
+// number of bytes, or -1 if the string is unparseable.
183
+// Units are case-insensitive, and the 'b' suffix is optional.
184
+func RAMInBytes(size string) (bytes int64, err error) {
185
+	re, error := regexp.Compile("^(\\d+)([kKmMgG])?[bB]?$")
186
+	if error != nil {
187
+		return -1, error
188
+	}
189
+
190
+	matches := re.FindStringSubmatch(size)
191
+
192
+	if len(matches) != 3 {
193
+		return -1, fmt.Errorf("Invalid size: '%s'", size)
194
+	}
195
+
196
+	memLimit, error := strconv.ParseInt(matches[1], 10, 0)
197
+	if error != nil {
198
+		return -1, error
199
+	}
200
+
201
+	unit := strings.ToLower(matches[2])
202
+
203
+	if unit == "k" {
204
+		memLimit *= 1024
205
+	} else if unit == "m" {
206
+		memLimit *= 1024 * 1024
207
+	} else if unit == "g" {
208
+		memLimit *= 1024 * 1024 * 1024
209
+	}
210
+
211
+	return memLimit, nil
212
+}
213
+
180 214
 func Trunc(s string, maxlen int) string {
181 215
 	if len(s) <= maxlen {
182 216
 		return s
... ...
@@ -910,7 +950,7 @@ func StripComments(input []byte, commentMarker []byte) []byte {
910 910
 func GetNameserversAsCIDR(resolvConf []byte) []string {
911 911
 	var parsedResolvConf = StripComments(resolvConf, []byte("#"))
912 912
 	nameservers := []string{}
913
-	re := regexp.MustCompile(`^\s*nameserver\s*(([0-9]\.){3}([0-9]))\s*$`)
913
+	re := regexp.MustCompile(`^\s*nameserver\s*(([0-9]+\.){3}([0-9]+))\s*$`)
914 914
 	for _, line := range bytes.Split(parsedResolvConf, []byte("\n")) {
915 915
 		var ns = re.FindSubmatch(line)
916 916
 		if len(ns) > 0 {
... ...
@@ -265,6 +265,39 @@ func TestHumanSize(t *testing.T) {
265 265
 	}
266 266
 }
267 267
 
268
+func TestRAMInBytes(t *testing.T) {
269
+	assertRAMInBytes(t, "32", false, 32)
270
+	assertRAMInBytes(t, "32b", false, 32)
271
+	assertRAMInBytes(t, "32B", false, 32)
272
+	assertRAMInBytes(t, "32k", false, 32*1024)
273
+	assertRAMInBytes(t, "32K", false, 32*1024)
274
+	assertRAMInBytes(t, "32kb", false, 32*1024)
275
+	assertRAMInBytes(t, "32Kb", false, 32*1024)
276
+	assertRAMInBytes(t, "32Mb", false, 32*1024*1024)
277
+	assertRAMInBytes(t, "32Gb", false, 32*1024*1024*1024)
278
+
279
+	assertRAMInBytes(t, "", true, -1)
280
+	assertRAMInBytes(t, "hello", true, -1)
281
+	assertRAMInBytes(t, "-32", true, -1)
282
+	assertRAMInBytes(t, " 32 ", true, -1)
283
+	assertRAMInBytes(t, "32 mb", true, -1)
284
+	assertRAMInBytes(t, "32m b", true, -1)
285
+	assertRAMInBytes(t, "32bm", true, -1)
286
+}
287
+
288
+func assertRAMInBytes(t *testing.T, size string, expectError bool, expectedBytes int64) {
289
+	actualBytes, err := RAMInBytes(size)
290
+	if (err != nil) && !expectError {
291
+		t.Errorf("Unexpected error parsing '%s': %s", size, err)
292
+	}
293
+	if (err == nil) && expectError {
294
+		t.Errorf("Expected to get an error parsing '%s', but got none (bytes=%d)", size, actualBytes)
295
+	}
296
+	if actualBytes != expectedBytes {
297
+		t.Errorf("Expected '%s' to parse as %d bytes, got %d", size, expectedBytes, actualBytes)
298
+	}
299
+}
300
+
268 301
 func TestParseHost(t *testing.T) {
269 302
 	if addr, err := ParseHost("127.0.0.1", 4243, "0.0.0.0"); err != nil || addr != "tcp://0.0.0.0:4243" {
270 303
 		t.Errorf("0.0.0.0 -> expected tcp://0.0.0.0:4243, got %s", addr)
... ...
@@ -448,12 +481,12 @@ func TestParsePortMapping(t *testing.T) {
448 448
 func TestGetNameserversAsCIDR(t *testing.T) {
449 449
 	for resolv, result := range map[string][]string{`
450 450
 nameserver 1.2.3.4
451
-nameserver 4.3.2.1
452
-search example.com`: {"1.2.3.4/32", "4.3.2.1/32"},
451
+nameserver 40.3.200.10
452
+search example.com`: {"1.2.3.4/32", "40.3.200.10/32"},
453 453
 		`search example.com`: {},
454 454
 		`nameserver 1.2.3.4
455 455
 search example.com
456
-nameserver 4.3.2.1`: {"1.2.3.4/32", "4.3.2.1/32"},
456
+nameserver 4.30.20.100`: {"1.2.3.4/32", "4.30.20.100/32"},
457 457
 		``: {},
458 458
 		`  nameserver 1.2.3.4   `: {"1.2.3.4/32"},
459 459
 		`search example.com
... ...
@@ -2,6 +2,7 @@ package docker
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"github.com/dotcloud/docker/engine"
5 6
 	"github.com/dotcloud/docker/utils"
6 7
 	"io"
7 8
 	"io/ioutil"
... ...
@@ -20,64 +21,97 @@ var globalTestID string
20 20
 
21 21
 // Create a temporary runtime suitable for unit testing.
22 22
 // Call t.Fatal() at the first error.
23
-func mkRuntime(f Fataler) *Runtime {
24
-	// Use the caller function name as a prefix.
25
-	// This helps trace temp directories back to their test.
26
-	pc, _, _, _ := runtime.Caller(1)
27
-	callerLongName := runtime.FuncForPC(pc).Name()
28
-	parts := strings.Split(callerLongName, ".")
29
-	callerShortName := parts[len(parts)-1]
30
-	if globalTestID == "" {
31
-		globalTestID = GenerateID()[:4]
23
+func mkRuntime(f utils.Fataler) *Runtime {
24
+	root, err := newTestDirectory(unitTestStoreBase)
25
+	if err != nil {
26
+		f.Fatal(err)
32 27
 	}
33
-	prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, callerShortName)
34
-	utils.Debugf("prefix = '%s'", prefix)
35
-
36
-	runtime, err := newTestRuntime(prefix)
28
+	config := &DaemonConfig{
29
+		Root:        root,
30
+		AutoRestart: false,
31
+	}
32
+	r, err := NewRuntimeFromDirectory(config)
37 33
 	if err != nil {
38 34
 		f.Fatal(err)
39 35
 	}
40
-	return runtime
36
+	r.UpdateCapabilities(true)
37
+	return r
38
+}
39
+
40
+func createNamedTestContainer(eng *engine.Engine, config *Config, f utils.Fataler, name string) (shortId string) {
41
+	job := eng.Job("create", name)
42
+	if err := job.ImportEnv(config); err != nil {
43
+		f.Fatal(err)
44
+	}
45
+	job.StdoutParseString(&shortId)
46
+	if err := job.Run(); err != nil {
47
+		f.Fatal(err)
48
+	}
49
+	return
41 50
 }
42 51
 
43
-// A common interface to access the Fatal method of
44
-// both testing.B and testing.T.
45
-type Fataler interface {
46
-	Fatal(args ...interface{})
52
+func createTestContainer(eng *engine.Engine, config *Config, f utils.Fataler) (shortId string) {
53
+	return createNamedTestContainer(eng, config, f, "")
47 54
 }
48 55
 
49
-func newTestRuntime(prefix string) (runtime *Runtime, err error) {
50
-	if prefix == "" {
51
-		prefix = "docker-test-"
56
+func mkServerFromEngine(eng *engine.Engine, t utils.Fataler) *Server {
57
+	iSrv := eng.Hack_GetGlobalVar("httpapi.server")
58
+	if iSrv == nil {
59
+		panic("Legacy server field not set in engine")
52 60
 	}
53
-	utils.Debugf("prefix = %s", prefix)
54
-	utils.Debugf("newTestRuntime start")
55
-	root, err := ioutil.TempDir("", prefix)
56
-	defer func() {
57
-		utils.Debugf("newTestRuntime: %s", root)
58
-	}()
61
+	srv, ok := iSrv.(*Server)
62
+	if !ok {
63
+		panic("Legacy server field in engine does not cast to *Server")
64
+	}
65
+	return srv
66
+}
67
+
68
+func NewTestEngine(t utils.Fataler) *engine.Engine {
69
+	root, err := newTestDirectory(unitTestStoreBase)
59 70
 	if err != nil {
60
-		return nil, err
71
+		t.Fatal(err)
61 72
 	}
62
-	if err := os.Remove(root); err != nil {
63
-		return nil, err
73
+	eng, err := engine.New(root)
74
+	if err != nil {
75
+		t.Fatal(err)
64 76
 	}
65
-	utils.Debugf("Copying %s to %s", unitTestStoreBase, root)
66
-	if err := utils.CopyDirectory(unitTestStoreBase, root); err != nil {
67
-		utils.Debugf("ERROR: Copying %s to %s returned %s", unitTestStoreBase, root, err)
68
-		return nil, err
77
+	// Load default plugins
78
+	// (This is manually copied and modified from main() until we have a more generic plugin system)
79
+	job := eng.Job("initapi")
80
+	job.Setenv("Root", root)
81
+	job.SetenvBool("AutoRestart", false)
82
+	if err := job.Run(); err != nil {
83
+		t.Fatal(err)
69 84
 	}
85
+	return eng
86
+}
70 87
 
71
-	config := &DaemonConfig{
72
-		Root:        root,
73
-		AutoRestart: false,
88
+func newTestDirectory(templateDir string) (dir string, err error) {
89
+	if globalTestID == "" {
90
+		globalTestID = GenerateID()[:4]
74 91
 	}
75
-	runtime, err = NewRuntimeFromDirectory(config)
76
-	if err != nil {
77
-		return nil, err
92
+	prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, getCallerName(2))
93
+	if prefix == "" {
94
+		prefix = "docker-test-"
95
+	}
96
+	dir, err = ioutil.TempDir("", prefix)
97
+	if err = os.Remove(dir); err != nil {
98
+		return
99
+	}
100
+	if err = utils.CopyDirectory(templateDir, dir); err != nil {
101
+		return
78 102
 	}
79
-	runtime.UpdateCapabilities(true)
80
-	return runtime, nil
103
+	return
104
+}
105
+
106
+func getCallerName(depth int) string {
107
+	// Use the caller function name as a prefix.
108
+	// This helps trace temp directories back to their test.
109
+	pc, _, _, _ := runtime.Caller(depth + 1)
110
+	callerLongName := runtime.FuncForPC(pc).Name()
111
+	parts := strings.Split(callerLongName, ".")
112
+	callerShortName := parts[len(parts)-1]
113
+	return callerShortName
81 114
 }
82 115
 
83 116
 // Write `content` to the file at path `dst`, creating it if necessary,
... ...
@@ -249,7 +283,9 @@ func TestMergeConfig(t *testing.T) {
249 249
 		Volumes:   volumesUser,
250 250
 	}
251 251
 
252
-	MergeConfig(configUser, configImage)
252
+	if err := MergeConfig(configUser, configImage); err != nil {
253
+		t.Error(err)
254
+	}
253 255
 
254 256
 	if len(configUser.Dns) != 3 {
255 257
 		t.Fatalf("Expected 3 dns, 1.1.1.1, 2.2.2.2 and 3.3.3.3, found %d", len(configUser.Dns))
... ...
@@ -261,7 +297,7 @@ func TestMergeConfig(t *testing.T) {
261 261
 	}
262 262
 
263 263
 	if len(configUser.ExposedPorts) != 3 {
264
-		t.Fatalf("Expected 3 portSpecs, 1111, 2222 and 3333, found %d", len(configUser.PortSpecs))
264
+		t.Fatalf("Expected 3 ExposedPorts, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts))
265 265
 	}
266 266
 	for portSpecs := range configUser.ExposedPorts {
267 267
 		if portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" {
... ...
@@ -289,6 +325,28 @@ func TestMergeConfig(t *testing.T) {
289 289
 	if configUser.VolumesFrom != "1111" {
290 290
 		t.Fatalf("Expected VolumesFrom to be 1111, found %s", configUser.VolumesFrom)
291 291
 	}
292
+
293
+	ports, _, err := parsePortSpecs([]string{"0000"})
294
+	if err != nil {
295
+		t.Error(err)
296
+	}
297
+	configImage2 := &Config{
298
+		ExposedPorts: ports,
299
+	}
300
+
301
+	if err := MergeConfig(configUser, configImage2); err != nil {
302
+		t.Error(err)
303
+	}
304
+
305
+	if len(configUser.ExposedPorts) != 4 {
306
+		t.Fatalf("Expected 4 ExposedPorts, 0000, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts))
307
+	}
308
+	for portSpecs := range configUser.ExposedPorts {
309
+		if portSpecs.Port() != "0000" && portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" {
310
+			t.Fatalf("Expected 0000 or 1111 or 2222 or 3333, found %s", portSpecs)
311
+		}
312
+	}
313
+
292 314
 }
293 315
 
294 316
 func TestParseLxcConfOpt(t *testing.T) {