Conflicts:
utils.go
utils_test.go
| ... | ... |
@@ -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> |
| ... | ... |
@@ -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 |
|
| ... | ... |
@@ -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, |
| 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 |
| ... | ... |
@@ -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 |
| 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) {
|