mirror of
https://github.com/clearlinux/docker.git
synced 2026-08-21 13:18:00 +00:00
Compare commits
227 Commits
v0.6.7
..
v0.7.0-rc4
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c20a8cdc7 | |||
| d5b3299673 | |||
| 27140018c5 | |||
| fbf85e2e0a | |||
| d596a8a72c | |||
| bf25342e11 | |||
| 4f51ad7d0e | |||
| 2e181c5635 | |||
| 1db7cb2f01 | |||
| e6e6d89cc1 | |||
| 1484434196 | |||
| 34c7fb0c43 | |||
| 9268e5e42f | |||
| 04f8ffa140 | |||
| 7e3e662ba5 | |||
| 2b17d79e5b | |||
| 0fae30e8c3 | |||
| 7fff719cf3 | |||
| 6f8d479324 | |||
| 4c27690cdd | |||
| bc92933384 | |||
| e9ee860c91 | |||
| c2175ae736 | |||
| 34365ececa | |||
| 7591a69108 | |||
| 764ce78c1d | |||
| 2812baf395 | |||
| 572b1fd9be | |||
| 4756ad248a | |||
| dfb77274ce | |||
| 73545199a8 | |||
| 0ad35c6746 | |||
| 5e363072f5 | |||
| cad913c57b | |||
| 5f58a1fbe4 | |||
| 07e09d57af | |||
| 9f85a967bb | |||
| 85a36b3b53 | |||
| d3505d836a | |||
| c47e93fcbe | |||
| 5cd7de5de8 | |||
| d034aafac7 | |||
| 4bd6021806 | |||
| ed03dbfe82 | |||
| 330062ef72 | |||
| 31b883b076 | |||
| bdb3b2a88c | |||
| a14496ce89 | |||
| 8abcc8e713 | |||
| d733cdcebb | |||
| 0aee096fd7 | |||
| e6a73e65a2 | |||
| ad2fbd9e87 | |||
| e5d7472a0d | |||
| 7d3c7e2b29 | |||
| f1d07e2dbe | |||
| ad968ef3ef | |||
| c688e9b5a6 | |||
| f3e6d34df2 | |||
| ea04f3de72 | |||
| 153248b60f | |||
| 1711de4b09 | |||
| 11d695a297 | |||
| acf58362cb | |||
| 3a246ac3d1 | |||
| 1da335f784 | |||
| cd61fb2e6f | |||
| fdba1aeed8 | |||
| c9f3e54c31 | |||
| 3997b8a923 | |||
| ec885d9180 | |||
| 54b0cd7cd1 | |||
| 66db2ac9d8 | |||
| 5ebaca7e55 | |||
| 432ff7e3c3 | |||
| 80bd64245f | |||
| 4431e9edb7 | |||
| 7093411a8d | |||
| 5778ed7db2 | |||
| 3455c1a098 | |||
| 5dd12ba20a | |||
| 5892c8e469 | |||
| 20e3e8c07d | |||
| 79c0c4470f | |||
| 4dedd9a9aa | |||
| 063ebbab68 | |||
| ea92dc2e8c | |||
| 7f429e0ceb | |||
| bb42801cdc | |||
| 8e4b3a3390 | |||
| 48070274ee | |||
| c3f1bb3287 | |||
| 562e4f1e23 | |||
| a263e07678 | |||
| e7986da531 | |||
| b440ec0136 | |||
| c77697a45c | |||
| 1804fcba93 | |||
| f6913592a1 | |||
| aaf1f73bcc | |||
| 9b65c7cf49 | |||
| f7e374fb3a | |||
| f29c500d8d | |||
| b843998718 | |||
| 1a1be5a87c | |||
| 7b58e15b08 | |||
| 8b2f4aab23 | |||
| 06d0843a61 | |||
| deb05a36e8 | |||
| 55189307d0 | |||
| 55e1782d66 | |||
| 152302e379 | |||
| 72a08a5458 | |||
| aeb89ffbba | |||
| 0484b2c325 | |||
| be6fef0254 | |||
| 071cc18b58 | |||
| 6ec5585501 | |||
| 6f57e8025a | |||
| b0a9147fd5 | |||
| a7e876e357 | |||
| ecdbdfdaea | |||
| 2c71710b74 | |||
| bbc72c85f7 | |||
| 1a082ed245 | |||
| 86421e8b5e | |||
| 91c69fd353 | |||
| 43a7d3d0e9 | |||
| 60f552cac3 | |||
| ad402763e1 | |||
| 5d2ace3424 | |||
| 727e7fccca | |||
| 7d566b4f76 | |||
| fdbc2695fe | |||
| 429587779a | |||
| 145024c6cc | |||
| c138801073 | |||
| 0722786600 | |||
| 5f8e24f842 | |||
| 0d7ab8db03 | |||
| ed741f7b27 | |||
| 9e64ebb295 | |||
| d951911b23 | |||
| 7fb60caa5d | |||
| 76a2ab6e34 | |||
| 6094257b28 | |||
| 52294192b2 | |||
| 381ce94ef4 | |||
| 99393cf3cf | |||
| 30890c7763 | |||
| b0626f403b | |||
| fda6ff9c27 | |||
| b86f67126c | |||
| 1c5dc26a7c | |||
| 8e7cbbff50 | |||
| 074f38d493 | |||
| a9ec1dbc9b | |||
| d2ba3e2005 | |||
| 8f7361279c | |||
| ca2f7f955e | |||
| 1d36b8c7b7 | |||
| e6216793d9 | |||
| e368c8bb01 | |||
| 167601e858 | |||
| b8dc7b5f1a | |||
| 7fb3bfed03 | |||
| 374a5e9913 | |||
| 459bac7127 | |||
| 514886c73d | |||
| 75e958bf48 | |||
| ebfa24acb0 | |||
| 5e1d540209 | |||
| c1e25d7273 | |||
| d263aa6ca9 | |||
| 03320f0d1c | |||
| 6c7ae06435 | |||
| 395bce4c41 | |||
| 41399ac005 | |||
| 67788723c9 | |||
| f99f39abaa | |||
| 009d0f9d81 | |||
| ed65815613 | |||
| cc28829429 | |||
| 062a2b32e9 | |||
| cda8754013 | |||
| 5415804c9d | |||
| adae684987 | |||
| ad0a6a03e3 | |||
| 36603e68e3 | |||
| 99c7d129f4 | |||
| 02b5f1369c | |||
| d478a4bb54 | |||
| c199ed228b | |||
| e40f5c7cb9 | |||
| d80be57c15 | |||
| 20bac716b5 | |||
| 2566e2604c | |||
| e1c418cac3 | |||
| 3343b3f8f8 | |||
| c6e8813c97 | |||
| 251a7ed437 | |||
| 261b0b01df | |||
| a7fd1fce5d | |||
| 6938a36c69 | |||
| bc7fa7b957 | |||
| d47c18c5fb | |||
| 0e686fa2f4 | |||
| 3f3f5f0bba | |||
| 19ba0b851b | |||
| 94fa3c7bb5 | |||
| 223280f319 | |||
| 8f23945f7f | |||
| 8e8ef7cb5b | |||
| 8f343ea65a | |||
| b125f2334c | |||
| a89a51128e | |||
| fcd41fe51a | |||
| 53851474c0 | |||
| f317a6b6fe | |||
| 87e248f524 | |||
| ac194fc696 | |||
| 8637ba710e | |||
| 0f5ccf934e | |||
| 250bc3f615 | |||
| 2b1dc8a8a3 | |||
| 0b12702c0c | |||
| 739af0a17f |
@@ -17,5 +17,3 @@ docs/_templates
|
||||
bundles/
|
||||
.hg/
|
||||
.git/
|
||||
vendor/pkg/
|
||||
pyenv
|
||||
|
||||
@@ -44,7 +44,6 @@ Daniel Nordberg <dnordberg@gmail.com>
|
||||
Daniel Robinson <gottagetmac@gmail.com>
|
||||
Daniel Von Fange <daniel@leancoder.com>
|
||||
Daniel YC Lin <dlin.tw@gmail.com>
|
||||
Darren Coxall <darren@darrencoxall.com>
|
||||
David Calavera <david.calavera@gmail.com>
|
||||
David Sissitka <me@dsissitka.com>
|
||||
Deni Bertovic <deni@kset.org>
|
||||
@@ -70,14 +69,12 @@ Gabriel Monroy <gabriel@opdemand.com>
|
||||
Gareth Rushgrove <gareth@morethanseven.net>
|
||||
Greg Thornton <xdissent@me.com>
|
||||
Guillaume J. Charmes <guillaume.charmes@dotcloud.com>
|
||||
Gurjeet Singh <gurjeet@singh.im>
|
||||
Guruprasad <lgp171188@gmail.com>
|
||||
Harley Laue <losinggeneration@gmail.com>
|
||||
Hector Castro <hectcastro@gmail.com>
|
||||
Hunter Blanks <hunter@twilio.com>
|
||||
Isao Jonas <isao.jonas@gmail.com>
|
||||
James Carr <james.r.carr@gmail.com>
|
||||
James Turnbull <james@lovedthanlost.net>
|
||||
Jason McVetta <jason.mcvetta@gmail.com>
|
||||
Jean-Baptiste Barth <jeanbaptiste.barth@gmail.com>
|
||||
Jeff Lindsay <progrium@gmail.com>
|
||||
@@ -95,7 +92,6 @@ Jonathan Rudenberg <jonathan@titanous.com>
|
||||
Joost Cassee <joost@cassee.net>
|
||||
Jordan Arentsen <blissdev@gmail.com>
|
||||
Joseph Anthony Pasquale Holsten <joseph@josephholsten.com>
|
||||
Josh Poimboeuf <jpoimboe@redhat.com>
|
||||
Julien Barbier <write0@gmail.com>
|
||||
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
|
||||
Karan Lyons <karan@karanlyons.com>
|
||||
@@ -121,7 +117,6 @@ Marko Mikulicic <mmikulicic@gmail.com>
|
||||
Markus Fix <lispmeister@gmail.com>
|
||||
Martin Redmond <martin@tinychat.com>
|
||||
Matt Apperson <me@mattapperson.com>
|
||||
Mathieu Le Marec - Pasquet <kiorky@cryptelium.net>
|
||||
Matt Bachmann <bachmann.matt@gmail.com>
|
||||
Matthew Mueller <mattmuelle@gmail.com>
|
||||
Maxim Treskin <zerthurd@gmail.com>
|
||||
@@ -145,7 +140,6 @@ odk- <github@odkurzacz.org>
|
||||
Pascal Borreli <pascal@borreli.com>
|
||||
Paul Bowsher <pbowsher@globalpersonals.co.uk>
|
||||
Paul Hammond <paul@paulhammond.org>
|
||||
Paul Nasrat <pnasrat@gmail.com>
|
||||
Phil Spitler <pspitler@gmail.com>
|
||||
Piotr Bogdan <ppbogdan@gmail.com>
|
||||
pysqz <randomq@126.com>
|
||||
@@ -157,7 +151,6 @@ Roberto Hashioka <roberto_hashioka@hotmail.com>
|
||||
Ryan Fowler <rwfowler@gmail.com>
|
||||
Sam Alba <sam.alba@gmail.com>
|
||||
Sam J Sharpe <sam.sharpe@digital.cabinet-office.gov.uk>
|
||||
Scott Bessler <scottbessler@gmail.com>
|
||||
Sean P. Kane <skane@newrelic.com>
|
||||
Shawn Siefkas <shawn.siefkas@meredith.com>
|
||||
Shih-Yuan Lee <fourdollars@gmail.com>
|
||||
@@ -168,7 +161,6 @@ Sridatta Thatipamala <sthatipamala@gmail.com>
|
||||
Sridhar Ratnakumar <sridharr@activestate.com>
|
||||
Steeve Morin <steeve.morin@gmail.com>
|
||||
Stefan Praszalowicz <stefan@greplin.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au>
|
||||
Thatcher Peskens <thatcher@dotcloud.com>
|
||||
Thermionix <bond711@gmail.com>
|
||||
Thijs Terlouw <thijsterlouw@gmail.com>
|
||||
@@ -193,5 +185,4 @@ Vladimir Kirillov <proger@wilab.org.ua>
|
||||
Walter Stanish <walter@pratyeka.org>
|
||||
Wes Morgan <cap10morgan@gmail.com>
|
||||
Will Dietz <w@wdtz.org>
|
||||
Yang Bai <hamo.by@gmail.com>
|
||||
Zaiste! <oh@zaiste.net>
|
||||
|
||||
+403
-739
File diff suppressed because it is too large
Load Diff
+3
-6
@@ -1,14 +1,11 @@
|
||||
# Contributing to Docker
|
||||
|
||||
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 wrong or incomplete.
|
||||
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
|
||||
wrong or incomplete.
|
||||
|
||||
## Build Environment
|
||||
|
||||
For instructions on setting up your development environment, please
|
||||
see our dedicated [dev environment setup
|
||||
docs](http://docs.docker.io/en/latest/contributing/devenvironment/).
|
||||
For instructions on setting up your development environment, please see our dedicated [dev environment setup docs](http://docs.docker.io/en/latest/contributing/devenvironment/).
|
||||
|
||||
## Contribution guidelines
|
||||
|
||||
|
||||
+47
-32
@@ -4,63 +4,78 @@
|
||||
#
|
||||
# # Assemble the full dev environment. This is slow the first time.
|
||||
# docker build -t docker .
|
||||
# # Apparmor messes with privileged mode: disable it
|
||||
# /etc/init.d/apparmor stop ; /etc/init.d/apparmor teardown
|
||||
#
|
||||
# # Mount your source in an interactive container for quick testing:
|
||||
# docker run -v `pwd`:/go/src/github.com/dotcloud/docker -privileged -i -t docker bash
|
||||
# docker run -v `pwd`:/go/src/github.com/dotcloud/docker -privileged -lxc-conf=lxc.aa_profile=unconfined -i -t docker bash
|
||||
#
|
||||
#
|
||||
# # Run the test suite:
|
||||
# docker run -privileged docker hack/make.sh test
|
||||
# docker run -privileged -lxc-conf=lxc.aa_profile=unconfined docker hack/make.sh test
|
||||
#
|
||||
# # Publish a release:
|
||||
# docker run -privileged \
|
||||
# docker run -privileged -lxc-conf=lxc.aa_profile=unconfined \
|
||||
# -e AWS_S3_BUCKET=baz \
|
||||
# -e AWS_ACCESS_KEY=foo \
|
||||
# -e AWS_SECRET_KEY=bar \
|
||||
# -e GPG_PASSPHRASE=gloubiboulga \
|
||||
# docker hack/release.sh
|
||||
#
|
||||
# Note: Apparmor used to mess with privileged mode, but this is no longer
|
||||
# the case. Therefore, you don't have to disable it anymore.
|
||||
#
|
||||
|
||||
docker-version 0.6.1
|
||||
from ubuntu:12.04
|
||||
maintainer Solomon Hykes <solomon@dotcloud.com>
|
||||
from ubuntu:12.10
|
||||
maintainer Solomon Hykes <solomon@dotcloud.com>
|
||||
|
||||
# Build dependencies
|
||||
run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > /etc/apt/sources.list
|
||||
run apt-get update
|
||||
run apt-get install -y -q curl
|
||||
run apt-get install -y -q git
|
||||
run apt-get install -y -q mercurial
|
||||
run apt-get install -y -q build-essential libsqlite3-dev
|
||||
run apt-get update
|
||||
run apt-get install -y -q curl
|
||||
run apt-get install -y -q git
|
||||
run apt-get install -y -q mercurial
|
||||
run apt-get install -y -q build-essential
|
||||
run apt-get install -y -q libsqlite3-dev
|
||||
|
||||
# Install Go
|
||||
run curl -s https://go.googlecode.com/files/go1.2rc5.src.tar.gz | tar -v -C /usr/local -xz
|
||||
env PATH /usr/local/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
|
||||
env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor
|
||||
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
|
||||
# Install Go from source (for eventual cross-compiling)
|
||||
run curl -s https://go.googlecode.com/files/go1.2rc1.src.tar.gz | tar -v -C / -xz && mv /go /goroot
|
||||
run cd /goroot/src && ./make.bash
|
||||
env GOROOT /goroot
|
||||
env PATH $PATH:/goroot/bin
|
||||
env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor
|
||||
|
||||
# Create Go cache with tag netgo (for static compilation of Go while preserving CGO support)
|
||||
run go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std
|
||||
|
||||
# Get lvm2 source for compiling statically
|
||||
run git clone git://git.fedorahosted.org/git/lvm2.git /lvm2
|
||||
run cd /lvm2 && git checkout v2_02_102
|
||||
|
||||
# can't use git clone -b because it's not supported by git versions before 1.7.10
|
||||
run cd /lvm2 && ./configure --enable-static_link && make && make install_device-mapper
|
||||
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
|
||||
|
||||
# Ubuntu stuff
|
||||
run apt-get install -y -q ruby1.9.3 rubygems libffi-dev
|
||||
run gem install --no-rdoc --no-ri fpm
|
||||
run apt-get install -y -q reprepro dpkg-sig
|
||||
run apt-get install -y -q ruby1.9.3 rubygems libffi-dev
|
||||
run gem install --no-rdoc --no-ri fpm
|
||||
run apt-get install -y -q reprepro dpkg-sig
|
||||
|
||||
run apt-get install -y -q python-pip
|
||||
run pip install s3cmd==1.1.0-beta3
|
||||
run pip install python-magic==0.4.6
|
||||
run /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY\n' > /.s3cfg
|
||||
# Install s3cmd 1.0.1 (earlier versions don't support env variables in the config)
|
||||
run apt-get install -y -q python-pip
|
||||
run pip install s3cmd
|
||||
run pip install python-magic
|
||||
run /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY\n' > /.s3cfg
|
||||
|
||||
# Runtime dependencies
|
||||
run apt-get install -y -q iptables
|
||||
run apt-get install -y -q lxc
|
||||
run apt-get install -y -q aufs-tools
|
||||
run apt-get install -y -q iptables
|
||||
run dpkg-divert --local --rename --add /sbin/initctl && \
|
||||
ln -s /bin/true /sbin/initctl && \
|
||||
apt-get install -y -q lxc
|
||||
|
||||
volume /var/lib/docker
|
||||
workdir /go/src/github.com/dotcloud/docker
|
||||
volume /var/lib/docker
|
||||
workdir /go/src/github.com/dotcloud/docker
|
||||
|
||||
# Wrap all commands in the "docker-in-docker" script to allow nested containers
|
||||
entrypoint ["hack/dind"]
|
||||
|
||||
# Upload docker source
|
||||
add . /go/src/github.com/dotcloud/docker
|
||||
add . /go/src/github.com/dotcloud/docker
|
||||
|
||||
|
||||
@@ -1,19 +1,43 @@
|
||||
Docker
|
||||
Copyright 2012-2013 Docker, Inc.
|
||||
Copyright 2012-2013 dotCloud, inc.
|
||||
|
||||
This product includes software developed at Docker, Inc. (http://www.docker.com).
|
||||
This product includes software developed at dotCloud,
|
||||
inc. (http://www.dotcloud.com).
|
||||
|
||||
This product contains software (https://github.com/kr/pty) developed
|
||||
by Keith Rarick, licensed under the MIT License.
|
||||
|
||||
The following is courtesy of our legal counsel:
|
||||
|
||||
Transfers of Docker shall be in accordance with applicable export
|
||||
controls of any country and all other applicable legal requirements.
|
||||
Docker shall not be distributed or downloaded to or in Cuba, Iran,
|
||||
North Korea, Sudan or Syria and shall not be distributed or downloaded
|
||||
to any person on the Denied Persons List administered by the U.S.
|
||||
Department of Commerce.
|
||||
|
||||
Use and transfer of Docker may be subject to certain restrictions by the
|
||||
United States and other governments.
|
||||
It is your responsibility to ensure that your use and/or transfer does not
|
||||
violate applicable laws.
|
||||
What does that mean?
|
||||
Here is a further explanation from our legal counsel:
|
||||
|
||||
For more information, please see http://www.bis.doc.gov
|
||||
Like all software products that utilize cryptography, the export and
|
||||
use of Docker is subject to the U.S. Commerce Department's Export
|
||||
Administration Regulations (EAR) because it uses or contains
|
||||
cryptography (see
|
||||
http://www.bis.doc.gov/index.php/policy-guidance/encryption). Certain
|
||||
free and open source software projects have a lightweight set of
|
||||
requirements, which can generally be met by providing email notice to
|
||||
the appropriate U.S. government agencies that their source code is
|
||||
available on a publicly available repository and making the
|
||||
appropriate statements in the README.
|
||||
|
||||
See also http://www.apache.org/dev/crypto.html and/or seek legal counsel.
|
||||
The restrictions of the EAR apply to certain denied locations
|
||||
(currently Iran, Sudan, Syria, North Korea, or Cuba) and those
|
||||
individuals on the Denied Persons List, which is available here:
|
||||
http://www.bis.doc.gov/index.php/policy-guidance/lists-of-parties-of-concern/denied-persons-list.
|
||||
If you are incorporating Docker into a new open source project, the
|
||||
EAR restrictions apply to your incorporation of Docker into your
|
||||
project in the same manner as other cryptography-enabled projects,
|
||||
such as OpenSSL, almost all Linux distributions, etc.
|
||||
|
||||
For more information, see http://www.apache.org/dev/crypto.html and/or
|
||||
seek legal counsel.
|
||||
|
||||
@@ -193,9 +193,10 @@ wrong or incomplete.
|
||||
*Brought to you courtesy of our legal counsel. For more context,
|
||||
please see the Notice document.*
|
||||
|
||||
Use and transfer of Docker may be subject to certain restrictions by the
|
||||
United States and other governments.
|
||||
It is your responsibility to ensure that your use and/or transfer does not
|
||||
violate applicable laws.
|
||||
Transfers of Docker shall be in accordance with applicable export
|
||||
controls of any country and all other applicable legal requirements.
|
||||
Docker shall not be distributed or downloaded to or in Cuba, Iran,
|
||||
North Korea, Sudan or Syria and shall not be distributed or downloaded
|
||||
to any person on the Denied Persons List administered by the U.S.
|
||||
Department of Commerce.
|
||||
|
||||
For more information, please see http://www.bis.doc.gov
|
||||
|
||||
Vendored
+44
-119
@@ -4,135 +4,65 @@
|
||||
BOX_NAME = ENV['BOX_NAME'] || "ubuntu"
|
||||
BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64.box"
|
||||
VF_BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64_vmware_fusion.box"
|
||||
AWS_BOX_URI = ENV['BOX_URI'] || "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box"
|
||||
AWS_REGION = ENV['AWS_REGION'] || "us-east-1"
|
||||
AWS_AMI = ENV['AWS_AMI'] || "ami-69f5a900"
|
||||
AWS_INSTANCE_TYPE = ENV['AWS_INSTANCE_TYPE'] || 't1.micro'
|
||||
|
||||
AWS_AMI = ENV['AWS_AMI'] || "ami-d0f89fb9"
|
||||
FORWARD_DOCKER_PORTS = ENV['FORWARD_DOCKER_PORTS']
|
||||
|
||||
SSH_PRIVKEY_PATH = ENV["SSH_PRIVKEY_PATH"]
|
||||
|
||||
# A script to upgrade from the 12.04 kernel to the raring backport kernel (3.8)
|
||||
# and install docker.
|
||||
$script = <<SCRIPT
|
||||
# The username to add to the docker group will be passed as the first argument
|
||||
# to the script. If nothing is passed, default to "vagrant".
|
||||
user="$1"
|
||||
if [ -z "$user" ]; then
|
||||
user=vagrant
|
||||
fi
|
||||
|
||||
# Adding an apt gpg key is idempotent.
|
||||
wget -q -O - https://get.docker.io/gpg | apt-key add -
|
||||
|
||||
# Creating the docker.list file is idempotent, but it may overrite desired
|
||||
# settings if it already exists. This could be solved with md5sum but it
|
||||
# doesn't seem worth it.
|
||||
echo 'deb http://get.docker.io/ubuntu docker main' > \
|
||||
/etc/apt/sources.list.d/docker.list
|
||||
|
||||
# Update remote package metadata. 'apt-get update' is idempotent.
|
||||
apt-get update -q
|
||||
|
||||
# Install docker. 'apt-get install' is idempotent.
|
||||
apt-get install -q -y lxc-docker
|
||||
|
||||
usermod -a -G docker "$user"
|
||||
|
||||
tmp=`mktemp -q` && {
|
||||
# Only install the backport kernel, don't bother upgrade if the backport is
|
||||
# already installed. We want parse the output of apt so we need to save it
|
||||
# with 'tee'. NOTE: The installation of the kernel will trigger dkms to
|
||||
# install vboxguest if needed.
|
||||
apt-get install -q -y --no-upgrade linux-image-generic-lts-raring | \
|
||||
tee "$tmp"
|
||||
|
||||
# Parse the number of installed packages from the output
|
||||
NUM_INST=`awk '$2 == "upgraded," && $4 == "newly" { print $3 }' "$tmp"`
|
||||
rm "$tmp"
|
||||
}
|
||||
|
||||
# If the number of installed packages is greater than 0, we want to reboot (the
|
||||
# backport kernel was installed but is not running).
|
||||
if [ "$NUM_INST" -gt 0 ];
|
||||
then
|
||||
echo "Rebooting down to activate new kernel."
|
||||
echo "/vagrant will not be mounted. Use 'vagrant halt' followed by"
|
||||
echo "'vagrant up' to ensure /vagrant is mounted."
|
||||
shutdown -r now
|
||||
fi
|
||||
SCRIPT
|
||||
|
||||
# We need to install the virtualbox guest additions *before* we do the normal
|
||||
# docker installation. As such this script is prepended to the common docker
|
||||
# install script above. This allows the install of the backport kernel to
|
||||
# trigger dkms to build the virtualbox guest module install.
|
||||
$vbox_script = <<VBOX_SCRIPT + $script
|
||||
# Install the VirtualBox guest additions if they aren't already installed.
|
||||
if [ ! -d /opt/VBoxGuestAdditions-4.3.2/ ]; then
|
||||
# Update remote package metadata. 'apt-get update' is idempotent.
|
||||
apt-get update -q
|
||||
|
||||
# Kernel Headers and dkms are required to build the vbox guest kernel
|
||||
# modules.
|
||||
apt-get install -q -y linux-headers-generic-lts-raring dkms
|
||||
|
||||
echo 'Downloading VBox Guest Additions...'
|
||||
wget -cq http://dlc.sun.com.edgesuite.net/virtualbox/4.3.2/VBoxGuestAdditions_4.3.2.iso
|
||||
|
||||
mount -o loop,ro /home/vagrant/VBoxGuestAdditions_4.3.2.iso /mnt
|
||||
/mnt/VBoxLinuxAdditions.run --nox11
|
||||
umount /mnt
|
||||
fi
|
||||
VBOX_SCRIPT
|
||||
|
||||
Vagrant::Config.run do |config|
|
||||
# Setup virtual machine box. This VM configuration code is always executed.
|
||||
config.vm.box = BOX_NAME
|
||||
config.vm.box_url = BOX_URI
|
||||
|
||||
# Use the specified private key path if it is specified and not empty.
|
||||
if SSH_PRIVKEY_PATH
|
||||
config.ssh.private_key_path = SSH_PRIVKEY_PATH
|
||||
end
|
||||
|
||||
config.ssh.forward_agent = true
|
||||
|
||||
# Provision docker and new kernel if deployment was not done.
|
||||
# It is assumed Vagrant can successfully launch the provider instance.
|
||||
if Dir.glob("#{File.dirname(__FILE__)}/.vagrant/machines/default/*/id").empty?
|
||||
# Add lxc-docker package
|
||||
pkg_cmd = "wget -q -O - https://get.docker.io/gpg | apt-key add -;" \
|
||||
"echo deb http://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list;" \
|
||||
"apt-get update -qq; apt-get install -q -y --force-yes lxc-docker; "
|
||||
# Add Ubuntu raring backported kernel
|
||||
pkg_cmd << "apt-get update -qq; apt-get install -q -y linux-image-generic-lts-raring; "
|
||||
# Add guest additions if local vbox VM. As virtualbox is the default provider,
|
||||
# it is assumed it won't be explicitly stated.
|
||||
if ENV["VAGRANT_DEFAULT_PROVIDER"].nil? && ARGV.none? { |arg| arg.downcase.start_with?("--provider") }
|
||||
pkg_cmd << "apt-get install -q -y linux-headers-generic-lts-raring dkms; " \
|
||||
"echo 'Downloading VBox Guest Additions...'; " \
|
||||
"wget -q http://dlc.sun.com.edgesuite.net/virtualbox/4.2.12/VBoxGuestAdditions_4.2.12.iso; "
|
||||
# Prepare the VM to add guest additions after reboot
|
||||
pkg_cmd << "echo -e 'mount -o loop,ro /home/vagrant/VBoxGuestAdditions_4.2.12.iso /mnt\n" \
|
||||
"echo yes | /mnt/VBoxLinuxAdditions.run\numount /mnt\n" \
|
||||
"rm /root/guest_additions.sh; ' > /root/guest_additions.sh; " \
|
||||
"chmod 700 /root/guest_additions.sh; " \
|
||||
"sed -i -E 's#^exit 0#[ -x /root/guest_additions.sh ] \\&\\& /root/guest_additions.sh#' /etc/rc.local; " \
|
||||
"echo 'Installation of VBox Guest Additions is proceeding in the background.'; " \
|
||||
"echo '\"vagrant reload\" can be used in about 2 minutes to activate the new guest additions.'; "
|
||||
end
|
||||
# Add vagrant user to the docker group
|
||||
pkg_cmd << "usermod -a -G docker vagrant; "
|
||||
# Activate new kernel
|
||||
pkg_cmd << "shutdown -r +1; "
|
||||
config.vm.provision :shell, :inline => pkg_cmd
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Providers were added on Vagrant >= 1.1.0
|
||||
#
|
||||
# NOTE: The vagrant "vm.provision" appends its arguments to a list and executes
|
||||
# them in order. If you invoke "vm.provision :shell, :inline => $script"
|
||||
# twice then vagrant will run the script two times. Unfortunately when you use
|
||||
# providers and the override argument to set up provisioners (like the vbox
|
||||
# guest extensions) they 1) don't replace the other provisioners (they append
|
||||
# to the end of the list) and 2) you can't control the order the provisioners
|
||||
# are executed (you can only append to the list). If you want the virtualbox
|
||||
# only script to run before the other script, you have to jump through a lot of
|
||||
# hoops.
|
||||
#
|
||||
# Here is my only repeatable solution: make one script that is common ($script)
|
||||
# and another script that is the virtual box guest *prepended* to the common
|
||||
# script. Only ever use "vm.provision" *one time* per provider. That means
|
||||
# every single provider has an override, and every single one configures
|
||||
# "vm.provision". Much saddness, but such is life.
|
||||
Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
|
||||
config.vm.provider :aws do |aws, override|
|
||||
username = "ubuntu"
|
||||
override.vm.box_url = AWS_BOX_URI
|
||||
override.vm.provision :shell, :inline => $script, :args => username
|
||||
aws.access_key_id = ENV["AWS_ACCESS_KEY"]
|
||||
aws.secret_access_key = ENV["AWS_SECRET_KEY"]
|
||||
aws.access_key_id = ENV["AWS_ACCESS_KEY_ID"]
|
||||
aws.secret_access_key = ENV["AWS_SECRET_ACCESS_KEY"]
|
||||
aws.keypair_name = ENV["AWS_KEYPAIR_NAME"]
|
||||
override.ssh.username = username
|
||||
override.ssh.private_key_path = ENV["AWS_SSH_PRIVKEY"]
|
||||
override.ssh.username = "ubuntu"
|
||||
aws.region = AWS_REGION
|
||||
aws.ami = AWS_AMI
|
||||
aws.instance_type = AWS_INSTANCE_TYPE
|
||||
aws.instance_type = "t1.micro"
|
||||
end
|
||||
|
||||
config.vm.provider :rackspace do |rs, override|
|
||||
override.vm.provision :shell, :inline => $script
|
||||
config.vm.provider :rackspace do |rs|
|
||||
config.ssh.private_key_path = ENV["RS_PRIVATE_KEY"]
|
||||
rs.username = ENV["RS_USERNAME"]
|
||||
rs.api_key = ENV["RS_API_KEY"]
|
||||
rs.public_key_path = ENV["RS_PUBLIC_KEY"]
|
||||
@@ -141,25 +71,20 @@ Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
|
||||
end
|
||||
|
||||
config.vm.provider :vmware_fusion do |f, override|
|
||||
override.vm.box = BOX_NAME
|
||||
override.vm.box_url = VF_BOX_URI
|
||||
override.vm.synced_folder ".", "/vagrant", disabled: true
|
||||
override.vm.provision :shell, :inline => $script
|
||||
f.vmx["displayName"] = "docker"
|
||||
end
|
||||
|
||||
config.vm.provider :virtualbox do |vb, override|
|
||||
override.vm.provision :shell, :inline => $vbox_script
|
||||
config.vm.provider :virtualbox do |vb|
|
||||
config.vm.box = BOX_NAME
|
||||
config.vm.box_url = BOX_URI
|
||||
vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
|
||||
vb.customize ["modifyvm", :id, "--natdnsproxy1", "on"]
|
||||
end
|
||||
end
|
||||
|
||||
# If this is a version 1 config, virtualbox is the only option. A version 2
|
||||
# config would have already been set in the above provider section.
|
||||
Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config|
|
||||
config.vm.provision :shell, :inline => $vbox_script
|
||||
end
|
||||
|
||||
if !FORWARD_DOCKER_PORTS.nil?
|
||||
Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config|
|
||||
(49000..49900).each do |port|
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/archive"
|
||||
"github.com/dotcloud/docker/auth"
|
||||
"github.com/dotcloud/docker/gograph"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"github.com/gorilla/mux"
|
||||
"io"
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
APIVERSION = 1.7
|
||||
APIVERSION = 1.6
|
||||
DEFAULTHTTPHOST = "127.0.0.1"
|
||||
DEFAULTHTTPPORT = 4243
|
||||
DEFAULTUNIXSOCKET = "/var/run/docker.sock"
|
||||
@@ -43,9 +43,6 @@ func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
|
||||
|
||||
//If we don't do this, POST method without Content-type (even with empty body) will fail
|
||||
func parseForm(r *http.Request) error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
||||
return err
|
||||
}
|
||||
@@ -61,10 +58,7 @@ func parseMultipartForm(r *http.Request) error {
|
||||
|
||||
func httpError(w http.ResponseWriter, err error) {
|
||||
statusCode := http.StatusInternalServerError
|
||||
// FIXME: this is brittle and should not be necessary.
|
||||
// If we need to differentiate between different possible error types, we should
|
||||
// create appropriate error types with clearly defined meaning.
|
||||
if strings.Contains(err.Error(), "No such") {
|
||||
if strings.HasPrefix(err.Error(), "No such") {
|
||||
statusCode = http.StatusNotFound
|
||||
} else if strings.HasPrefix(err.Error(), "Bad parameter") {
|
||||
statusCode = http.StatusBadRequest
|
||||
@@ -142,22 +136,8 @@ func postContainersKill(srv *Server, version float64, w http.ResponseWriter, r *
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
name := vars["name"]
|
||||
|
||||
signal := 0
|
||||
if r != nil {
|
||||
if s := r.Form.Get("signal"); s != "" {
|
||||
s, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signal = s
|
||||
}
|
||||
}
|
||||
if err := srv.ContainerKill(name, signal); err != nil {
|
||||
if err := srv.ContainerKill(name); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
@@ -193,23 +173,10 @@ func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http.
|
||||
return err
|
||||
}
|
||||
|
||||
if version < 1.7 {
|
||||
outs2 := []APIImagesOld{}
|
||||
for _, ctnr := range outs {
|
||||
outs2 = append(outs2, ctnr.ToLegacy()...)
|
||||
}
|
||||
|
||||
return writeJSON(w, http.StatusOK, outs2)
|
||||
}
|
||||
return writeJSON(w, http.StatusOK, outs)
|
||||
}
|
||||
|
||||
func getImagesViz(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if version > 1.6 {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return fmt.Errorf("This is now implemented in the client.")
|
||||
}
|
||||
|
||||
if err := srv.ImagesViz(w); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -251,7 +218,6 @@ func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
wf := utils.NewWriteFlusher(w)
|
||||
wf.Flush()
|
||||
if since != 0 {
|
||||
// If since, send previous events that happened after the timestamp
|
||||
for _, event := range srv.events {
|
||||
@@ -314,10 +280,13 @@ func getContainersTop(srv *Server, version float64, w http.ResponseWriter, r *ht
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
procsStr, err := srv.ContainerTop(vars["name"], r.Form.Get("ps_args"))
|
||||
name := vars["name"]
|
||||
ps_args := r.Form.Get("ps_args")
|
||||
procsStr, err := srv.ContainerTop(name, ps_args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return writeJSON(w, http.StatusOK, procsStr)
|
||||
}
|
||||
|
||||
@@ -345,12 +314,13 @@ func getContainersJSON(srv *Server, version float64, w http.ResponseWriter, r *h
|
||||
if version < 1.5 {
|
||||
outs2 := []APIContainersOld{}
|
||||
for _, ctnr := range outs {
|
||||
outs2 = append(outs2, *ctnr.ToLegacy())
|
||||
outs2 = append(outs2, ctnr.ToLegacy())
|
||||
}
|
||||
|
||||
return writeJSON(w, http.StatusOK, outs2)
|
||||
} else {
|
||||
return writeJSON(w, http.StatusOK, outs)
|
||||
}
|
||||
return writeJSON(w, http.StatusOK, outs)
|
||||
}
|
||||
|
||||
func postImagesTag(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
@@ -476,16 +446,15 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
}
|
||||
sf := utils.NewStreamFormatter(version > 1.0)
|
||||
err := srv.ImageInsert(name, url, path, w, sf)
|
||||
imgID, err := srv.ImageInsert(name, url, path, w, sf)
|
||||
if err != nil {
|
||||
if sf.Used() {
|
||||
w.Write(sf.FormatError(err))
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return writeJSON(w, http.StatusOK, &APIID{ID: imgID})
|
||||
}
|
||||
|
||||
func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
@@ -534,52 +503,43 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
|
||||
return nil
|
||||
}
|
||||
|
||||
func getImagesGet(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
name := vars["name"]
|
||||
if version > 1.0 {
|
||||
w.Header().Set("Content-Type", "application/x-tar")
|
||||
}
|
||||
return srv.ImageExport(name, w)
|
||||
}
|
||||
|
||||
func postImagesLoad(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
return srv.ImageLoad(r.Body)
|
||||
}
|
||||
|
||||
func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return nil
|
||||
}
|
||||
config := &Config{}
|
||||
out := &APIRun{}
|
||||
job := srv.Eng.Job("create", r.Form.Get("name"))
|
||||
if err := job.DecodeEnv(r.Body); err != nil {
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resolvConf, err := utils.GetResolvConf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !job.GetenvBool("NetworkDisabled") && len(job.Getenv("Dns")) == 0 && len(srv.runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
|
||||
|
||||
if !config.NetworkDisabled && len(config.Dns) == 0 && len(srv.runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
|
||||
out.Warnings = append(out.Warnings, fmt.Sprintf("Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns))
|
||||
job.SetenvList("Dns", defaultDns)
|
||||
config.Dns = defaultDns
|
||||
}
|
||||
// Read container ID from the first line of stdout
|
||||
job.StdoutParseString(&out.ID)
|
||||
// Read warnings from stderr
|
||||
job.StderrParseLines(&out.Warnings, 0)
|
||||
if err := job.Run(); err != nil {
|
||||
|
||||
id, warnings, err := srv.ContainerCreate(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if job.GetenvInt("Memory") > 0 && !srv.runtime.capabilities.MemoryLimit {
|
||||
out.ID = id
|
||||
for _, warning := range warnings {
|
||||
out.Warnings = append(out.Warnings, warning)
|
||||
}
|
||||
|
||||
if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
|
||||
log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.")
|
||||
out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.")
|
||||
}
|
||||
if job.GetenvInt("Memory") > 0 && !srv.runtime.capabilities.SwapLimit {
|
||||
if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit {
|
||||
log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.")
|
||||
out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.")
|
||||
}
|
||||
|
||||
if !job.GetenvBool("NetworkDisabled") && srv.runtime.capabilities.IPv4ForwardingDisabled {
|
||||
if !config.NetworkDisabled && srv.runtime.capabilities.IPv4ForwardingDisabled {
|
||||
log.Println("Warning: IPv4 forwarding is disabled.")
|
||||
out.Warnings = append(out.Warnings, "IPv4 forwarding is disabled.")
|
||||
}
|
||||
@@ -646,28 +606,33 @@ func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.R
|
||||
if imgs != nil {
|
||||
if len(imgs) != 0 {
|
||||
return writeJSON(w, http.StatusOK, imgs)
|
||||
} else {
|
||||
return fmt.Errorf("Conflict, %s wasn't deleted", name)
|
||||
}
|
||||
return fmt.Errorf("Conflict, %s wasn't deleted", name)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
job := srv.Eng.Job("start", name)
|
||||
var hostConfig *HostConfig
|
||||
// allow a nil body for backwards compatibility
|
||||
if r.Body != nil {
|
||||
if matchesContentType(r.Header.Get("Content-Type"), "application/json") {
|
||||
if err := job.DecodeEnv(r.Body); err != nil {
|
||||
hostConfig = &HostConfig{}
|
||||
if err := json.NewDecoder(r.Body).Decode(hostConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := job.Run(); err != nil {
|
||||
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
if err := srv.ContainerStart(name, hostConfig); err != nil {
|
||||
utils.Debugf("error ContainerStart: %s", err)
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
@@ -916,7 +881,7 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
|
||||
return fmt.Errorf("Error trying to use git: %s (%s)", err, output)
|
||||
}
|
||||
|
||||
c, err := archive.Tar(root, archive.Bzip2)
|
||||
c, err := Tar(root, Bzip2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -931,7 +896,7 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c, err := MkBuildContext(string(dockerFile), nil)
|
||||
c, err := mkBuildContext(string(dockerFile), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -979,7 +944,7 @@ func postContainersCopy(srv *Server, version float64, w http.ResponseWriter, r *
|
||||
}
|
||||
|
||||
if copyData.Resource == "" {
|
||||
return fmt.Errorf("Path cannot be empty")
|
||||
return fmt.Errorf("Resource cannot be empty")
|
||||
}
|
||||
if copyData.Resource[0] == '/' {
|
||||
copyData.Resource = copyData.Resource[1:]
|
||||
@@ -1037,6 +1002,73 @@ func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute s
|
||||
}
|
||||
}
|
||||
|
||||
func getContainersLinks(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
runtime := srv.runtime
|
||||
all, err := getBoolParam(r.Form.Get("all"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out := []APILink{}
|
||||
err = runtime.containerGraph.Walk("/", func(p string, e *gograph.Entity) error {
|
||||
if container := runtime.Get(e.ID()); container != nil {
|
||||
if !all && strings.Contains(p, container.ID) {
|
||||
return nil
|
||||
}
|
||||
out = append(out, APILink{
|
||||
Path: p,
|
||||
ContainerID: container.ID,
|
||||
Image: runtime.repositories.ImageName(container.Image),
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}, -1)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeJSON(w, http.StatusOK, out)
|
||||
}
|
||||
|
||||
func postContainerLink(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
values := make(map[string]string)
|
||||
if matchesContentType(r.Header.Get("Content-Type"), "application/json") && r.Body != nil {
|
||||
defer r.Body.Close()
|
||||
|
||||
dec := json.NewDecoder(r.Body)
|
||||
if err := dec.Decode(&values); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Invalid json body")
|
||||
}
|
||||
currentName := values["currentName"]
|
||||
newName := values["newName"]
|
||||
|
||||
if currentName == "" {
|
||||
return fmt.Errorf("currentName cannot be empty")
|
||||
}
|
||||
if newName == "" {
|
||||
return fmt.Errorf("newName cannot be empty")
|
||||
}
|
||||
|
||||
if err := srv.runtime.RenameLink(currentName, newName); err != nil {
|
||||
if strings.HasSuffix(err.Error(), "name are not unique") {
|
||||
return fmt.Errorf("Conflict, %s already exists", newName)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
||||
r := mux.NewRouter()
|
||||
|
||||
@@ -1048,7 +1080,6 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
||||
"/images/json": getImagesJSON,
|
||||
"/images/viz": getImagesViz,
|
||||
"/images/search": getImagesSearch,
|
||||
"/images/{name:.*}/get": getImagesGet,
|
||||
"/images/{name:.*}/history": getImagesHistory,
|
||||
"/images/{name:.*}/json": getImagesByName,
|
||||
"/containers/ps": getContainersJSON,
|
||||
@@ -1058,6 +1089,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
||||
"/containers/{name:.*}/json": getContainersByName,
|
||||
"/containers/{name:.*}/top": getContainersTop,
|
||||
"/containers/{name:.*}/attach/ws": wsContainersAttach,
|
||||
"/containers/links": getContainersLinks,
|
||||
},
|
||||
"POST": {
|
||||
"/auth": postAuth,
|
||||
@@ -1065,7 +1097,6 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
||||
"/build": postBuild,
|
||||
"/images/create": postImagesCreate,
|
||||
"/images/{name:.*}/insert": postImagesInsert,
|
||||
"/images/load": postImagesLoad,
|
||||
"/images/{name:.*}/push": postImagesPush,
|
||||
"/images/{name:.*}/tag": postImagesTag,
|
||||
"/containers/create": postContainersCreate,
|
||||
@@ -1077,6 +1108,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
||||
"/containers/{name:.*}/resize": postContainersResize,
|
||||
"/containers/{name:.*}/attach": postContainersAttach,
|
||||
"/containers/{name:.*}/copy": postContainersCopy,
|
||||
"/containers/link": postContainerLink,
|
||||
},
|
||||
"DELETE": {
|
||||
"/containers/{name:.*}": deleteContainers,
|
||||
@@ -1111,20 +1143,6 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// ServeRequest processes a single http request to the docker remote api.
|
||||
// FIXME: refactor this to be part of Server and not require re-creating a new
|
||||
// router each time. This requires first moving ListenAndServe into Server.
|
||||
func ServeRequest(srv *Server, apiversion float64, w http.ResponseWriter, req *http.Request) error {
|
||||
router, err := createRouter(srv, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Insert APIVERSION into the request as a convenience
|
||||
req.URL.Path = fmt.Sprintf("/v%g%s", apiversion, req.URL.Path)
|
||||
router.ServeHTTP(w, req)
|
||||
return nil
|
||||
}
|
||||
|
||||
func ListenAndServe(proto, addr string, srv *Server, logging bool) error {
|
||||
log.Printf("Listening for HTTP on %s (%s)\n", addr, proto)
|
||||
|
||||
|
||||
+128
-142
@@ -1,148 +1,134 @@
|
||||
package docker
|
||||
|
||||
import "strings"
|
||||
|
||||
type (
|
||||
APIHistory struct {
|
||||
ID string `json:"Id"`
|
||||
Tags []string `json:",omitempty"`
|
||||
Created int64
|
||||
CreatedBy string `json:",omitempty"`
|
||||
Size int64
|
||||
}
|
||||
|
||||
APIImages struct {
|
||||
ID string `json:"Id"`
|
||||
RepoTags []string `json:",omitempty"`
|
||||
Created int64
|
||||
Size int64
|
||||
VirtualSize int64
|
||||
ParentId string `json:",omitempty"`
|
||||
}
|
||||
|
||||
APIImagesOld struct {
|
||||
Repository string `json:",omitempty"`
|
||||
Tag string `json:",omitempty"`
|
||||
ID string `json:"Id"`
|
||||
Created int64
|
||||
Size int64
|
||||
VirtualSize int64
|
||||
}
|
||||
|
||||
APIInfo struct {
|
||||
Debug bool
|
||||
Containers int
|
||||
Images int
|
||||
NFd int `json:",omitempty"`
|
||||
NGoroutines int `json:",omitempty"`
|
||||
MemoryLimit bool `json:",omitempty"`
|
||||
SwapLimit bool `json:",omitempty"`
|
||||
IPv4Forwarding bool `json:",omitempty"`
|
||||
LXCVersion string `json:",omitempty"`
|
||||
NEventsListener int `json:",omitempty"`
|
||||
KernelVersion string `json:",omitempty"`
|
||||
IndexServerAddress string `json:",omitempty"`
|
||||
}
|
||||
|
||||
APITop struct {
|
||||
Titles []string
|
||||
Processes [][]string
|
||||
}
|
||||
|
||||
APIRmi struct {
|
||||
Deleted string `json:",omitempty"`
|
||||
Untagged string `json:",omitempty"`
|
||||
}
|
||||
|
||||
APIContainers struct {
|
||||
ID string `json:"Id"`
|
||||
Image string
|
||||
Command string
|
||||
Created int64
|
||||
Status string
|
||||
Ports []APIPort
|
||||
SizeRw int64
|
||||
SizeRootFs int64
|
||||
Names []string
|
||||
}
|
||||
|
||||
APIContainersOld struct {
|
||||
ID string `json:"Id"`
|
||||
Image string
|
||||
Command string
|
||||
Created int64
|
||||
Status string
|
||||
Ports string
|
||||
SizeRw int64
|
||||
SizeRootFs int64
|
||||
}
|
||||
|
||||
APIID struct {
|
||||
ID string `json:"Id"`
|
||||
}
|
||||
|
||||
APIRun struct {
|
||||
ID string `json:"Id"`
|
||||
Warnings []string `json:",omitempty"`
|
||||
}
|
||||
|
||||
APIPort struct {
|
||||
PrivatePort int64
|
||||
PublicPort int64
|
||||
Type string
|
||||
IP string
|
||||
}
|
||||
|
||||
APIVersion struct {
|
||||
Version string
|
||||
GitCommit string `json:",omitempty"`
|
||||
GoVersion string `json:",omitempty"`
|
||||
}
|
||||
|
||||
APIWait struct {
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
APIAuth struct {
|
||||
Status string
|
||||
}
|
||||
|
||||
APIImageConfig struct {
|
||||
ID string `json:"Id"`
|
||||
*Config
|
||||
}
|
||||
|
||||
APICopy struct {
|
||||
Resource string
|
||||
HostPath string
|
||||
}
|
||||
)
|
||||
|
||||
func (api APIImages) ToLegacy() []APIImagesOld {
|
||||
outs := []APIImagesOld{}
|
||||
for _, repotag := range api.RepoTags {
|
||||
components := strings.SplitN(repotag, ":", 2)
|
||||
outs = append(outs, APIImagesOld{
|
||||
ID: api.ID,
|
||||
Repository: components[0],
|
||||
Tag: components[1],
|
||||
Created: api.Created,
|
||||
Size: api.Size,
|
||||
VirtualSize: api.VirtualSize,
|
||||
})
|
||||
}
|
||||
return outs
|
||||
type APIHistory struct {
|
||||
ID string `json:"Id"`
|
||||
Tags []string `json:",omitempty"`
|
||||
Created int64
|
||||
CreatedBy string `json:",omitempty"`
|
||||
}
|
||||
|
||||
func (api APIContainers) ToLegacy() *APIContainersOld {
|
||||
return &APIContainersOld{
|
||||
ID: api.ID,
|
||||
Image: api.Image,
|
||||
Command: api.Command,
|
||||
Created: api.Created,
|
||||
Status: api.Status,
|
||||
Ports: displayablePorts(api.Ports),
|
||||
SizeRw: api.SizeRw,
|
||||
SizeRootFs: api.SizeRootFs,
|
||||
type APIImages struct {
|
||||
Repository string `json:",omitempty"`
|
||||
Tag string `json:",omitempty"`
|
||||
ID string `json:"Id"`
|
||||
Created int64
|
||||
Size int64
|
||||
VirtualSize int64
|
||||
}
|
||||
|
||||
type APIInfo struct {
|
||||
Debug bool
|
||||
Containers int
|
||||
Images int
|
||||
NFd int `json:",omitempty"`
|
||||
NGoroutines int `json:",omitempty"`
|
||||
MemoryLimit bool `json:",omitempty"`
|
||||
SwapLimit bool `json:",omitempty"`
|
||||
IPv4Forwarding bool `json:",omitempty"`
|
||||
LXCVersion string `json:",omitempty"`
|
||||
NEventsListener int `json:",omitempty"`
|
||||
KernelVersion string `json:",omitempty"`
|
||||
IndexServerAddress string `json:",omitempty"`
|
||||
DevmapperPool string `json:",omitempty"`
|
||||
DevmapperDataUsed uint64 `json:",omitempty"`
|
||||
DevmapperDataTotal uint64 `json:",omitempty"`
|
||||
DevmapperMetadataUsed uint64 `json:",omitempty"`
|
||||
DevmapperMetadataTotal uint64 `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APITop struct {
|
||||
Titles []string
|
||||
Processes [][]string
|
||||
}
|
||||
|
||||
type APIRmi struct {
|
||||
Deleted string `json:",omitempty"`
|
||||
Untagged string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APIContainers struct {
|
||||
ID string `json:"Id"`
|
||||
Image string
|
||||
Command string
|
||||
Created int64
|
||||
Status string
|
||||
Ports []APIPort
|
||||
SizeRw int64
|
||||
SizeRootFs int64
|
||||
Names []string
|
||||
}
|
||||
|
||||
func (self *APIContainers) ToLegacy() APIContainersOld {
|
||||
return APIContainersOld{
|
||||
ID: self.ID,
|
||||
Image: self.Image,
|
||||
Command: self.Command,
|
||||
Created: self.Created,
|
||||
Status: self.Status,
|
||||
Ports: displayablePorts(self.Ports),
|
||||
SizeRw: self.SizeRw,
|
||||
SizeRootFs: self.SizeRootFs,
|
||||
}
|
||||
}
|
||||
|
||||
type APIContainersOld struct {
|
||||
ID string `json:"Id"`
|
||||
Image string
|
||||
Command string
|
||||
Created int64
|
||||
Status string
|
||||
Ports string
|
||||
SizeRw int64
|
||||
SizeRootFs int64
|
||||
}
|
||||
|
||||
type APISearch struct {
|
||||
Name string
|
||||
Description string
|
||||
}
|
||||
|
||||
type APIID struct {
|
||||
ID string `json:"Id"`
|
||||
}
|
||||
|
||||
type APIRun struct {
|
||||
ID string `json:"Id"`
|
||||
Warnings []string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APIPort struct {
|
||||
PrivatePort int64
|
||||
PublicPort int64
|
||||
Type string
|
||||
IP string
|
||||
}
|
||||
|
||||
type APIVersion struct {
|
||||
Version string
|
||||
GitCommit string `json:",omitempty"`
|
||||
GoVersion string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APIWait struct {
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
type APIAuth struct {
|
||||
Status string
|
||||
}
|
||||
|
||||
type APIImageConfig struct {
|
||||
ID string `json:"Id"`
|
||||
*Config
|
||||
}
|
||||
|
||||
type APICopy struct {
|
||||
Resource string
|
||||
HostPath string
|
||||
}
|
||||
|
||||
type APILink struct {
|
||||
Path string
|
||||
ContainerID string
|
||||
Image string
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,19 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestJsonContentType(t *testing.T) {
|
||||
if !matchesContentType("application/json", "application/json") {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if !matchesContentType("application/json; charset=utf-8", "application/json") {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if matchesContentType("dockerapplication/json", "application/json") {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package archive
|
||||
package docker
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
@@ -80,20 +80,74 @@ func (compression *Compression) Extension() string {
|
||||
// Tar creates an archive from the directory at `path`, and returns it as a
|
||||
// stream of bytes.
|
||||
func Tar(path string, compression Compression) (io.Reader, error) {
|
||||
return TarFilter(path, compression, nil)
|
||||
return TarFilter(path, compression, nil, true, nil)
|
||||
}
|
||||
|
||||
func escapeName(name string) string {
|
||||
escaped := make([]byte, 0)
|
||||
for i, c := range []byte(name) {
|
||||
if i == 0 && c == '/' {
|
||||
continue
|
||||
}
|
||||
// all printable chars except "-" which is 0x2d
|
||||
if (0x20 <= c && c <= 0x7E) && c != 0x2d {
|
||||
escaped = append(escaped, c)
|
||||
} else {
|
||||
escaped = append(escaped, fmt.Sprintf("\\%03o", c)...)
|
||||
}
|
||||
}
|
||||
return string(escaped)
|
||||
}
|
||||
|
||||
// Tar creates an archive from the directory at `path`, only including files whose relative
|
||||
// paths are included in `filter`. If `filter` is nil, then all files are included.
|
||||
func TarFilter(path string, compression Compression, filter []string) (io.Reader, error) {
|
||||
args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path}
|
||||
func TarFilter(path string, compression Compression, filter []string, recursive bool, createFiles []string) (io.Reader, error) {
|
||||
args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path, "-T", "-"}
|
||||
if filter == nil {
|
||||
filter = []string{"."}
|
||||
}
|
||||
for _, f := range filter {
|
||||
args = append(args, "-c"+compression.Flag(), f)
|
||||
args = append(args, "-c"+compression.Flag())
|
||||
|
||||
if !recursive {
|
||||
args = append(args, "--no-recursion")
|
||||
}
|
||||
return CmdStream(exec.Command(args[0], args[1:]...))
|
||||
|
||||
files := ""
|
||||
for _, f := range filter {
|
||||
files = files + escapeName(f) + "\n"
|
||||
}
|
||||
|
||||
tmpDir := ""
|
||||
|
||||
if createFiles != nil {
|
||||
var err error // Can't use := here or we override the outer tmpDir
|
||||
tmpDir, err = ioutil.TempDir("", "docker-tar")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files = files + "-C" + tmpDir + "\n"
|
||||
for _, f := range createFiles {
|
||||
path := filepath.Join(tmpDir, f)
|
||||
err := os.MkdirAll(filepath.Dir(path), 0600)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if file, err := os.OpenFile(path, os.O_CREATE, 0600); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
file.Close()
|
||||
}
|
||||
files = files + escapeName(f) + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
return CmdStream(exec.Command(args[0], args[1:]...), &files, func() {
|
||||
if tmpDir != "" {
|
||||
_ = os.RemoveAll(tmpDir)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Untar reads a stream of bytes from `archive`, parses it as a tar archive,
|
||||
@@ -109,17 +163,16 @@ func Untar(archive io.Reader, path string) error {
|
||||
buf := make([]byte, 10)
|
||||
totalN := 0
|
||||
for totalN < 10 {
|
||||
n, err := archive.Read(buf[totalN:])
|
||||
if err != nil {
|
||||
if n, err := archive.Read(buf[totalN:]); err != nil {
|
||||
if err == io.EOF {
|
||||
return fmt.Errorf("Tarball too short")
|
||||
}
|
||||
return err
|
||||
} else {
|
||||
totalN += n
|
||||
utils.Debugf("[tar autodetect] n: %d", n)
|
||||
}
|
||||
totalN += n
|
||||
utils.Debugf("[tar autodetect] n: %d", n)
|
||||
}
|
||||
|
||||
compression := DetectCompression(buf)
|
||||
|
||||
utils.Debugf("Archive compression detected: %s", compression.Extension())
|
||||
@@ -141,7 +194,7 @@ func Untar(archive io.Reader, path string) error {
|
||||
// TarUntar aborts and returns the error.
|
||||
func TarUntar(src string, filter []string, dst string) error {
|
||||
utils.Debugf("TarUntar(%s %s %s)", src, filter, dst)
|
||||
archive, err := TarFilter(src, Uncompressed, filter)
|
||||
archive, err := TarFilter(src, Uncompressed, filter, true, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -228,13 +281,33 @@ func CopyFileWithTar(src, dst string) error {
|
||||
// CmdStream executes a command, and returns its stdout as a stream.
|
||||
// If the command fails to run or doesn't complete successfully, an error
|
||||
// will be returned, including anything written on stderr.
|
||||
func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
|
||||
func CmdStream(cmd *exec.Cmd, input *string, atEnd func()) (io.Reader, error) {
|
||||
if input != nil {
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
if atEnd != nil {
|
||||
atEnd()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
// Write stdin if any
|
||||
go func() {
|
||||
_, _ = stdin.Write([]byte(*input))
|
||||
stdin.Close()
|
||||
}()
|
||||
}
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
if atEnd != nil {
|
||||
atEnd()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
if atEnd != nil {
|
||||
atEnd()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
pipeR, pipeW := io.Pipe()
|
||||
@@ -259,6 +332,9 @@ func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
|
||||
} else {
|
||||
pipeW.Close()
|
||||
}
|
||||
if atEnd != nil {
|
||||
atEnd()
|
||||
}
|
||||
}()
|
||||
// Run the command and return the pipe
|
||||
if err := cmd.Start(); err != nil {
|
||||
@@ -1 +0,0 @@
|
||||
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
|
||||
@@ -1,4 +1,4 @@
|
||||
package archive
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
func TestCmdStreamLargeStderr(t *testing.T) {
|
||||
cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello")
|
||||
out, err := CmdStream(cmd)
|
||||
out, err := CmdStream(cmd, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start command: %s", err)
|
||||
}
|
||||
@@ -35,7 +35,7 @@ func TestCmdStreamLargeStderr(t *testing.T) {
|
||||
|
||||
func TestCmdStreamBad(t *testing.T) {
|
||||
badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1")
|
||||
out, err := CmdStream(badCmd)
|
||||
out, err := CmdStream(badCmd, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start command: %s", err)
|
||||
}
|
||||
@@ -50,7 +50,7 @@ func TestCmdStreamBad(t *testing.T) {
|
||||
|
||||
func TestCmdStreamGood(t *testing.T) {
|
||||
cmd := exec.Command("/bin/sh", "-c", "echo hello; exit 0")
|
||||
out, err := CmdStream(cmd)
|
||||
out, err := CmdStream(cmd, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
+3
-2
@@ -196,9 +196,10 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e
|
||||
if loginAgainstOfficialIndex {
|
||||
return "", fmt.Errorf("Login: Your account hasn't been activated. " +
|
||||
"Please check your e-mail for a confirmation link.")
|
||||
} else {
|
||||
return "", fmt.Errorf("Login: Your account hasn't been activated. " +
|
||||
"Please see the documentation of the registry " + serverAddress + " for instructions how to activate it.")
|
||||
}
|
||||
return "", fmt.Errorf("Login: Your account hasn't been activated. " +
|
||||
"Please see the documentation of the registry " + serverAddress + " for instructions how to activate it.")
|
||||
} else if reqStatusCode == 400 {
|
||||
if string(reqBody) == "\"Username or email already exists\"" {
|
||||
req, err := factory.NewRequest("GET", serverAddress+"users/", nil)
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -26,6 +29,52 @@ func TestEncodeAuth(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogin(t *testing.T) {
|
||||
os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
|
||||
defer os.Setenv("DOCKER_INDEX_URL", "")
|
||||
authConfig := &AuthConfig{Username: "unittester", Password: "surlautrerivejetattendrai", Email: "noise+unittester@dotcloud.com"}
|
||||
status, err := Login(authConfig, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if status != "Login Succeeded" {
|
||||
t.Fatalf("Expected status \"Login Succeeded\", found \"%s\" instead", status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateAccount(t *testing.T) {
|
||||
os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
|
||||
defer os.Setenv("DOCKER_INDEX_URL", "")
|
||||
tokenBuffer := make([]byte, 16)
|
||||
_, err := rand.Read(tokenBuffer)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
token := hex.EncodeToString(tokenBuffer)[:12]
|
||||
username := "ut" + token
|
||||
authConfig := &AuthConfig{Username: username, Password: "test42", Email: "docker-ut+" + token + "@example.com"}
|
||||
status, err := Login(authConfig, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expectedStatus := "Account created. Please use the confirmation link we sent" +
|
||||
" to your e-mail to activate it."
|
||||
if status != expectedStatus {
|
||||
t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status)
|
||||
}
|
||||
|
||||
status, err = Login(authConfig, nil)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error but found nil instead")
|
||||
}
|
||||
|
||||
expectedError := "Login: Account is not Active"
|
||||
|
||||
if !strings.Contains(err.Error(), expectedError) {
|
||||
t.Fatalf("Expected message \"%s\" but found \"%s\" instead", expectedError, err)
|
||||
}
|
||||
}
|
||||
|
||||
func setupTempConfigFile() (*ConfigFile, error) {
|
||||
root, err := ioutil.TempDir("", "docker-test-auth")
|
||||
if err != nil {
|
||||
|
||||
+18
-26
@@ -3,7 +3,6 @@ package docker
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/archive"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -65,9 +64,6 @@ func (b *buildFile) CmdFrom(name string) error {
|
||||
}
|
||||
b.image = image.ID
|
||||
b.config = &Config{}
|
||||
if image.Config != nil {
|
||||
b.config = image.Config
|
||||
}
|
||||
if b.config.Env == nil || len(b.config.Env) == 0 {
|
||||
b.config.Env = append(b.config.Env, "HOME=/", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
|
||||
}
|
||||
@@ -180,6 +176,8 @@ func (b *buildFile) CmdEnv(args string) error {
|
||||
func (b *buildFile) CmdCmd(args string) error {
|
||||
var cmd []string
|
||||
if err := json.Unmarshal([]byte(args), &cmd); err != nil {
|
||||
// If the unmarshal fails, it is not an error, we just use the
|
||||
// args as a string.
|
||||
utils.Debugf("Error unmarshalling: %s, setting cmd to /bin/sh -c", err)
|
||||
cmd = []string{"/bin/sh", "-c", args}
|
||||
}
|
||||
@@ -191,6 +189,9 @@ func (b *buildFile) CmdCmd(args string) error {
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdExpose(args string) error {
|
||||
if strings.Contains(args, ":") {
|
||||
return fmt.Errorf("EXPOSE cannot be used to bind to a host ip or port")
|
||||
}
|
||||
ports := strings.Split(args, " ")
|
||||
b.config.PortSpecs = append(ports, b.config.PortSpecs...)
|
||||
return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports))
|
||||
@@ -295,17 +296,17 @@ func (b *buildFile) addContext(container *Container, orig, dest string) error {
|
||||
return fmt.Errorf("%s: no such file or directory", orig)
|
||||
}
|
||||
if fi.IsDir() {
|
||||
if err := archive.CopyWithTar(origPath, destPath); err != nil {
|
||||
if err := CopyWithTar(origPath, destPath); err != nil {
|
||||
return err
|
||||
}
|
||||
// First try to unpack the source as an archive
|
||||
} else if err := archive.UntarPath(origPath, destPath); err != nil {
|
||||
utils.Debugf("Couldn't untar %s to %s: %s", origPath, destPath, err)
|
||||
} else if err := UntarPath(origPath, destPath); err != nil {
|
||||
utils.Debugf("[tar] Not a directory nor a tar archive. Copying as a file. Untar error from %s to %s: %s", origPath, destPath, err)
|
||||
// If that fails, just copy it as a regular file
|
||||
if err := os.MkdirAll(path.Dir(destPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := archive.CopyWithTar(origPath, destPath); err != nil {
|
||||
if err := CopyWithTar(origPath, destPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -336,7 +337,7 @@ func (b *buildFile) CmdAdd(args string) error {
|
||||
|
||||
b.config.Image = b.image
|
||||
// Create the container and start it
|
||||
container, _, err := b.runtime.Create(b.config, "")
|
||||
container, _, err := b.runtime.Create(b.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -371,7 +372,7 @@ func (b *buildFile) run() (string, error) {
|
||||
b.config.Image = b.image
|
||||
|
||||
// Create the container and start it
|
||||
c, _, err := b.runtime.Create(b.config, "")
|
||||
c, _, err := b.runtime.Create(b.config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -382,21 +383,15 @@ func (b *buildFile) run() (string, error) {
|
||||
c.Path = b.config.Cmd[0]
|
||||
c.Args = b.config.Cmd[1:]
|
||||
|
||||
var errCh chan error
|
||||
|
||||
if b.verbose {
|
||||
errCh = utils.Go(func() error {
|
||||
return <-c.Attach(nil, nil, b.out, b.out)
|
||||
})
|
||||
}
|
||||
|
||||
//start the container
|
||||
if err := c.Start(); err != nil {
|
||||
hostConfig := &HostConfig{}
|
||||
if err := c.Start(hostConfig); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if errCh != nil {
|
||||
if err := <-errCh; err != nil {
|
||||
if b.verbose {
|
||||
err = <-c.Attach(nil, nil, b.out, b.out)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
@@ -433,13 +428,10 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
|
||||
}
|
||||
}
|
||||
|
||||
container, warnings, err := b.runtime.Create(b.config, "")
|
||||
container, _, err := b.runtime.Create(b.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, warning := range warnings {
|
||||
fmt.Fprintf(b.out, " ---> [Warning] %s\n", warning)
|
||||
}
|
||||
b.tmpContainers[container.ID] = struct{}{}
|
||||
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID))
|
||||
id = container.ID
|
||||
@@ -476,7 +468,7 @@ func (b *buildFile) Build(context io.Reader) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := archive.Untar(context, name); err != nil {
|
||||
if err := Untar(context, name); err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer os.RemoveAll(name)
|
||||
|
||||
@@ -2,9 +2,6 @@ package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker"
|
||||
"github.com/dotcloud/docker/archive"
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -15,8 +12,8 @@ import (
|
||||
|
||||
// mkTestContext generates a build context from the contents of the provided dockerfile.
|
||||
// This context is suitable for use as an argument to BuildFile.Build()
|
||||
func mkTestContext(dockerfile string, files [][2]string, t *testing.T) archive.Archive {
|
||||
context, err := docker.MkBuildContext(dockerfile, files)
|
||||
func mkTestContext(dockerfile string, files [][2]string, t *testing.T) Archive {
|
||||
context, err := mkBuildContext(dockerfile, files)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -230,15 +227,17 @@ func TestBuild(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, useCache bool) *docker.Image {
|
||||
if eng == nil {
|
||||
eng = NewTestEngine(t)
|
||||
runtime := mkRuntimeFromEngine(eng, t)
|
||||
// FIXME: we might not need runtime, why not simply nuke
|
||||
// the engine?
|
||||
func buildImage(context testContextTemplate, t *testing.T, srv *Server, useCache bool) *Image {
|
||||
if srv == nil {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv = &Server{
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
}
|
||||
}
|
||||
srv := mkServerFromEngine(eng, t)
|
||||
|
||||
httpServer, err := mkTestingFileServer(context.remoteFiles)
|
||||
if err != nil {
|
||||
@@ -252,17 +251,10 @@ func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, u
|
||||
}
|
||||
port := httpServer.URL[idx+1:]
|
||||
|
||||
iIP := eng.Hack_GetGlobalVar("httpapi.bridgeIP")
|
||||
if iIP == nil {
|
||||
t.Fatal("Legacy bridgeIP field not set in engine")
|
||||
}
|
||||
ip, ok := iIP.(net.IP)
|
||||
if !ok {
|
||||
panic("Legacy bridgeIP field in engine does not cast to net.IP")
|
||||
}
|
||||
ip := srv.runtime.networkManager.bridgeNetwork.IP
|
||||
dockerfile := constructDockerfile(context.dockerfile, ip, port)
|
||||
|
||||
buildfile := docker.NewBuildFile(srv, ioutil.Discard, false, useCache, false)
|
||||
buildfile := NewBuildFile(srv, ioutil.Discard, false, useCache, false)
|
||||
id, err := buildfile.Build(mkTestContext(dockerfile, context.files, t))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -375,14 +367,20 @@ func TestBuildEntrypoint(t *testing.T) {
|
||||
// testing #1405 - config.Cmd does not get cleaned up if
|
||||
// utilizing cache
|
||||
func TestBuildEntrypointRunCleanup(t *testing.T) {
|
||||
eng := NewTestEngine(t)
|
||||
defer nuke(mkRuntimeFromEngine(eng, t))
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
}
|
||||
|
||||
img := buildImage(testContextTemplate{`
|
||||
from {IMAGE}
|
||||
run echo "hello"
|
||||
`,
|
||||
nil, nil}, t, eng, true)
|
||||
nil, nil}, t, srv, true)
|
||||
|
||||
img = buildImage(testContextTemplate{`
|
||||
from {IMAGE}
|
||||
@@ -390,7 +388,7 @@ func TestBuildEntrypointRunCleanup(t *testing.T) {
|
||||
add foo /foo
|
||||
entrypoint ["/bin/echo"]
|
||||
`,
|
||||
[][2]string{{"foo", "HEYO"}}, nil}, t, eng, true)
|
||||
[][2]string{{"foo", "HEYO"}}, nil}, t, srv, true)
|
||||
|
||||
if len(img.Config.Cmd) != 0 {
|
||||
t.Fail()
|
||||
@@ -398,8 +396,14 @@ func TestBuildEntrypointRunCleanup(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBuildImageWithCache(t *testing.T) {
|
||||
eng := NewTestEngine(t)
|
||||
defer nuke(mkRuntimeFromEngine(eng, t))
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
}
|
||||
|
||||
template := testContextTemplate{`
|
||||
from {IMAGE}
|
||||
@@ -407,11 +411,11 @@ func TestBuildImageWithCache(t *testing.T) {
|
||||
`,
|
||||
nil, nil}
|
||||
|
||||
img := buildImage(template, t, eng, true)
|
||||
img := buildImage(template, t, srv, true)
|
||||
imageId := img.ID
|
||||
|
||||
img = nil
|
||||
img = buildImage(template, t, eng, true)
|
||||
img = buildImage(template, t, srv, true)
|
||||
|
||||
if imageId != img.ID {
|
||||
t.Logf("Image ids should match: %s != %s", imageId, img.ID)
|
||||
@@ -420,8 +424,14 @@ func TestBuildImageWithCache(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBuildImageWithoutCache(t *testing.T) {
|
||||
eng := NewTestEngine(t)
|
||||
defer nuke(mkRuntimeFromEngine(eng, t))
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
}
|
||||
|
||||
template := testContextTemplate{`
|
||||
from {IMAGE}
|
||||
@@ -429,11 +439,11 @@ func TestBuildImageWithoutCache(t *testing.T) {
|
||||
`,
|
||||
nil, nil}
|
||||
|
||||
img := buildImage(template, t, eng, true)
|
||||
img := buildImage(template, t, srv, true)
|
||||
imageId := img.ID
|
||||
|
||||
img = nil
|
||||
img = buildImage(template, t, eng, false)
|
||||
img = buildImage(template, t, srv, false)
|
||||
|
||||
if imageId == img.ID {
|
||||
t.Logf("Image ids should not match: %s == %s", imageId, img.ID)
|
||||
@@ -442,9 +452,14 @@ func TestBuildImageWithoutCache(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestForbiddenContextPath(t *testing.T) {
|
||||
eng := NewTestEngine(t)
|
||||
defer nuke(mkRuntimeFromEngine(eng, t))
|
||||
srv := mkServerFromEngine(eng, t)
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
}
|
||||
|
||||
context := testContextTemplate{`
|
||||
from {IMAGE}
|
||||
@@ -465,17 +480,10 @@ func TestForbiddenContextPath(t *testing.T) {
|
||||
}
|
||||
port := httpServer.URL[idx+1:]
|
||||
|
||||
iIP := eng.Hack_GetGlobalVar("httpapi.bridgeIP")
|
||||
if iIP == nil {
|
||||
t.Fatal("Legacy bridgeIP field not set in engine")
|
||||
}
|
||||
ip, ok := iIP.(net.IP)
|
||||
if !ok {
|
||||
panic("Legacy bridgeIP field in engine does not cast to net.IP")
|
||||
}
|
||||
ip := srv.runtime.networkManager.bridgeNetwork.IP
|
||||
dockerfile := constructDockerfile(context.dockerfile, ip, port)
|
||||
|
||||
buildfile := docker.NewBuildFile(srv, ioutil.Discard, false, true, false)
|
||||
buildfile := NewBuildFile(srv, ioutil.Discard, false, true, false)
|
||||
_, err = buildfile.Build(mkTestContext(dockerfile, context.files, t))
|
||||
|
||||
if err == nil {
|
||||
@@ -490,8 +498,14 @@ func TestForbiddenContextPath(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBuildADDFileNotFound(t *testing.T) {
|
||||
eng := NewTestEngine(t)
|
||||
defer nuke(mkRuntimeFromEngine(eng, t))
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
}
|
||||
|
||||
context := testContextTemplate{`
|
||||
from {IMAGE}
|
||||
@@ -511,17 +525,10 @@ func TestBuildADDFileNotFound(t *testing.T) {
|
||||
}
|
||||
port := httpServer.URL[idx+1:]
|
||||
|
||||
iIP := eng.Hack_GetGlobalVar("httpapi.bridgeIP")
|
||||
if iIP == nil {
|
||||
t.Fatal("Legacy bridgeIP field not set in engine")
|
||||
}
|
||||
ip, ok := iIP.(net.IP)
|
||||
if !ok {
|
||||
panic("Legacy bridgeIP field in engine does not cast to net.IP")
|
||||
}
|
||||
ip := srv.runtime.networkManager.bridgeNetwork.IP
|
||||
dockerfile := constructDockerfile(context.dockerfile, ip, port)
|
||||
|
||||
buildfile := docker.NewBuildFile(mkServerFromEngine(eng, t), ioutil.Discard, false, true, false)
|
||||
buildfile := NewBuildFile(srv, ioutil.Discard, false, true, false)
|
||||
_, err = buildfile.Build(mkTestContext(dockerfile, context.files, t))
|
||||
|
||||
if err == nil {
|
||||
@@ -534,30 +541,3 @@ func TestBuildADDFileNotFound(t *testing.T) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildInheritance(t *testing.T) {
|
||||
eng := NewTestEngine(t)
|
||||
defer nuke(mkRuntimeFromEngine(eng, t))
|
||||
|
||||
img := buildImage(testContextTemplate{`
|
||||
from {IMAGE}
|
||||
expose 4243
|
||||
`,
|
||||
nil, nil}, t, eng, true)
|
||||
|
||||
img2 := buildImage(testContextTemplate{fmt.Sprintf(`
|
||||
from %s
|
||||
entrypoint ["/bin/echo"]
|
||||
`, img.ID),
|
||||
nil, nil}, t, eng, true)
|
||||
|
||||
// from child
|
||||
if img2.Config.Entrypoint[0] != "/bin/echo" {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// from parent
|
||||
if img.Config.PortSpecs[0] != "4243" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
+259
-49
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type ChangeType int
|
||||
@@ -33,74 +34,283 @@ func (change *Change) String() string {
|
||||
return fmt.Sprintf("%s %s", kind, change.Path)
|
||||
}
|
||||
|
||||
func Changes(layers []string, rw string) ([]Change, error) {
|
||||
type FileInfo struct {
|
||||
parent *FileInfo
|
||||
name string
|
||||
stat syscall.Stat_t
|
||||
children map[string]*FileInfo
|
||||
}
|
||||
|
||||
func (root *FileInfo) LookUp(path string) *FileInfo {
|
||||
parent := root
|
||||
if path == "/" {
|
||||
return root
|
||||
}
|
||||
|
||||
pathElements := strings.Split(path, "/")
|
||||
for _, elem := range pathElements {
|
||||
if elem != "" {
|
||||
child := parent.children[elem]
|
||||
if child == nil {
|
||||
return nil
|
||||
}
|
||||
parent = child
|
||||
}
|
||||
}
|
||||
return parent
|
||||
}
|
||||
|
||||
func (info *FileInfo) path() string {
|
||||
if info.parent == nil {
|
||||
return "/"
|
||||
}
|
||||
return filepath.Join(info.parent.path(), info.name)
|
||||
}
|
||||
|
||||
func (info *FileInfo) unlink() {
|
||||
if info.parent != nil {
|
||||
delete(info.parent.children, info.name)
|
||||
}
|
||||
}
|
||||
|
||||
func (info *FileInfo) Remove(path string) bool {
|
||||
child := info.LookUp(path)
|
||||
if child != nil {
|
||||
child.unlink()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (info *FileInfo) isDir() bool {
|
||||
return info.parent == nil || info.stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR
|
||||
}
|
||||
|
||||
func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
|
||||
if oldInfo == nil {
|
||||
// add
|
||||
change := Change{
|
||||
Path: info.path(),
|
||||
Kind: ChangeAdd,
|
||||
}
|
||||
*changes = append(*changes, change)
|
||||
}
|
||||
|
||||
// We make a copy so we can modify it to detect additions
|
||||
// also, we only recurse on the old dir if the new info is a directory
|
||||
// otherwise any previous delete/change is considered recursive
|
||||
oldChildren := make(map[string]*FileInfo)
|
||||
if oldInfo != nil && info.isDir() {
|
||||
for k, v := range oldInfo.children {
|
||||
oldChildren[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
for name, newChild := range info.children {
|
||||
oldChild, _ := oldChildren[name]
|
||||
if oldChild != nil {
|
||||
// change?
|
||||
oldStat := &oldChild.stat
|
||||
newStat := &newChild.stat
|
||||
// Note: We can't compare inode or ctime or blocksize here, because these change
|
||||
// when copying a file into a container. However, that is not generally a problem
|
||||
// because any content change will change mtime, and any status change should
|
||||
// be visible when actually comparing the stat fields. The only time this
|
||||
// breaks down is if some code intentionally hides a change by setting
|
||||
// back mtime
|
||||
oldMtime := syscall.NsecToTimeval(oldStat.Mtim.Nano())
|
||||
newMtime := syscall.NsecToTimeval(oldStat.Mtim.Nano())
|
||||
if oldStat.Mode != newStat.Mode ||
|
||||
oldStat.Uid != newStat.Uid ||
|
||||
oldStat.Gid != newStat.Gid ||
|
||||
oldStat.Rdev != newStat.Rdev ||
|
||||
// Don't look at size for dirs, its not a good measure of change
|
||||
(oldStat.Size != newStat.Size && oldStat.Mode&syscall.S_IFDIR != syscall.S_IFDIR) ||
|
||||
oldMtime.Sec != newMtime.Sec ||
|
||||
oldMtime.Usec != newMtime.Usec {
|
||||
change := Change{
|
||||
Path: newChild.path(),
|
||||
Kind: ChangeModify,
|
||||
}
|
||||
*changes = append(*changes, change)
|
||||
}
|
||||
|
||||
// Remove from copy so we can detect deletions
|
||||
delete(oldChildren, name)
|
||||
}
|
||||
|
||||
newChild.addChanges(oldChild, changes)
|
||||
}
|
||||
for _, oldChild := range oldChildren {
|
||||
// delete
|
||||
change := Change{
|
||||
Path: oldChild.path(),
|
||||
Kind: ChangeDelete,
|
||||
}
|
||||
*changes = append(*changes, change)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (info *FileInfo) Changes(oldInfo *FileInfo) []Change {
|
||||
var changes []Change
|
||||
err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error {
|
||||
|
||||
info.addChanges(oldInfo, &changes)
|
||||
|
||||
return changes
|
||||
}
|
||||
|
||||
func newRootFileInfo() *FileInfo {
|
||||
root := &FileInfo{
|
||||
name: "/",
|
||||
children: make(map[string]*FileInfo),
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
func applyLayer(root *FileInfo, layer string) error {
|
||||
err := filepath.Walk(layer, func(layerPath string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Skip root
|
||||
if layerPath == layer {
|
||||
return nil
|
||||
}
|
||||
|
||||
// rebase path
|
||||
relPath, err := filepath.Rel(layer, layerPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
relPath = filepath.Join("/", relPath)
|
||||
|
||||
// Skip AUFS metadata
|
||||
if matched, err := filepath.Match("/.wh..wh.*", relPath); err != nil || matched {
|
||||
if err != nil || !f.IsDir() {
|
||||
return err
|
||||
}
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
var layerStat syscall.Stat_t
|
||||
err = syscall.Lstat(layerPath, &layerStat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file := filepath.Base(relPath)
|
||||
// If there is a whiteout, then the file was removed
|
||||
if strings.HasPrefix(file, ".wh.") {
|
||||
originalFile := file[len(".wh."):]
|
||||
deletePath := filepath.Join(filepath.Dir(relPath), originalFile)
|
||||
|
||||
root.Remove(deletePath)
|
||||
} else {
|
||||
// Added or changed file
|
||||
existing := root.LookUp(relPath)
|
||||
if existing != nil {
|
||||
// Changed file
|
||||
existing.stat = layerStat
|
||||
if !existing.isDir() {
|
||||
// Changed from dir to non-dir, delete all previous files
|
||||
existing.children = make(map[string]*FileInfo)
|
||||
}
|
||||
} else {
|
||||
// Added file
|
||||
parent := root.LookUp(filepath.Dir(relPath))
|
||||
if parent == nil {
|
||||
return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath)
|
||||
}
|
||||
|
||||
info := &FileInfo{
|
||||
name: filepath.Base(relPath),
|
||||
children: make(map[string]*FileInfo),
|
||||
parent: parent,
|
||||
stat: layerStat,
|
||||
}
|
||||
|
||||
parent.children[info.name] = info
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func collectFileInfo(sourceDir string) (*FileInfo, error) {
|
||||
root := newRootFileInfo()
|
||||
|
||||
err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rebase path
|
||||
path, err = filepath.Rel(rw, path)
|
||||
relPath, err := filepath.Rel(sourceDir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path = filepath.Join("/", path)
|
||||
relPath = filepath.Join("/", relPath)
|
||||
|
||||
// Skip root
|
||||
if path == "/" {
|
||||
if relPath == "/" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Skip AUFS metadata
|
||||
if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched {
|
||||
parent := root.LookUp(filepath.Dir(relPath))
|
||||
if parent == nil {
|
||||
return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath)
|
||||
}
|
||||
|
||||
info := &FileInfo{
|
||||
name: filepath.Base(relPath),
|
||||
children: make(map[string]*FileInfo),
|
||||
parent: parent,
|
||||
}
|
||||
|
||||
if err := syscall.Lstat(path, &info.stat); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
change := Change{
|
||||
Path: path,
|
||||
}
|
||||
parent.children[info.name] = info
|
||||
|
||||
// Find out what kind of modification happened
|
||||
file := filepath.Base(path)
|
||||
// If there is a whiteout, then the file was removed
|
||||
if strings.HasPrefix(file, ".wh.") {
|
||||
originalFile := file[len(".wh."):]
|
||||
change.Path = filepath.Join(filepath.Dir(path), originalFile)
|
||||
change.Kind = ChangeDelete
|
||||
} else {
|
||||
// Otherwise, the file was added
|
||||
change.Kind = ChangeAdd
|
||||
|
||||
// ...Unless it already existed in a top layer, in which case, it's a modification
|
||||
for _, layer := range layers {
|
||||
stat, err := os.Stat(filepath.Join(layer, path))
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if err == nil {
|
||||
// The file existed in the top layer, so that's a modification
|
||||
|
||||
// However, if it's a directory, maybe it wasn't actually modified.
|
||||
// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
|
||||
if stat.IsDir() && f.IsDir() {
|
||||
if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() {
|
||||
// Both directories are the same, don't record the change
|
||||
return nil
|
||||
}
|
||||
}
|
||||
change.Kind = ChangeModify
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Record change
|
||||
changes = append(changes, change)
|
||||
return nil
|
||||
})
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return changes, nil
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// Compare a directory with an array of layer directories it was based on and
|
||||
// generate an array of Change objects describing the changes
|
||||
func ChangesLayers(newDir string, layers []string) ([]Change, error) {
|
||||
newRoot, err := collectFileInfo(newDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oldRoot := newRootFileInfo()
|
||||
for i := len(layers) - 1; i >= 0; i-- {
|
||||
layer := layers[i]
|
||||
if err = applyLayer(oldRoot, layer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return newRoot.Changes(oldRoot), nil
|
||||
}
|
||||
|
||||
// Compare two directories and generate an array of Change objects describing the changes
|
||||
func ChangesDirs(newDir, oldDir string) ([]Change, error) {
|
||||
oldRoot, err := collectFileInfo(oldDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newRoot, err := collectFileInfo(newDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newRoot.Changes(oldRoot), nil
|
||||
}
|
||||
|
||||
+272
-802
File diff suppressed because it is too large
Load Diff
@@ -3,14 +3,9 @@ package docker
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker"
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -68,8 +63,8 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error
|
||||
func TestRunHostname(t *testing.T) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := docker.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
@@ -89,32 +84,18 @@ func TestRunHostname(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
container := globalRuntime.List()[0]
|
||||
|
||||
setTimeout(t, "CmdRun timed out", 10*time.Second, func() {
|
||||
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
|
||||
<-c
|
||||
|
||||
go func() {
|
||||
cli.CmdWait(container.ID)
|
||||
}()
|
||||
|
||||
if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
// Cleanup pipes
|
||||
if err := closeWrap(stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunWorkdir checks that 'docker run -w' correctly sets a custom working directory
|
||||
func TestRunWorkdir(t *testing.T) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := docker.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
@@ -134,32 +115,18 @@ func TestRunWorkdir(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
container := globalRuntime.List()[0]
|
||||
|
||||
setTimeout(t, "CmdRun timed out", 10*time.Second, func() {
|
||||
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
|
||||
<-c
|
||||
|
||||
go func() {
|
||||
cli.CmdWait(container.ID)
|
||||
}()
|
||||
|
||||
if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
// Cleanup pipes
|
||||
if err := closeWrap(stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunWorkdirExists checks that 'docker run -w' correctly sets a custom working directory, even if it exists
|
||||
func TestRunWorkdirExists(t *testing.T) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := docker.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
@@ -179,32 +146,18 @@ func TestRunWorkdirExists(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
container := globalRuntime.List()[0]
|
||||
|
||||
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
|
||||
<-c
|
||||
|
||||
go func() {
|
||||
cli.CmdWait(container.ID)
|
||||
}()
|
||||
|
||||
if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
// Cleanup pipes
|
||||
if err := closeWrap(stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunExit(t *testing.T) {
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
@@ -256,8 +209,8 @@ func TestRunDisconnect(t *testing.T) {
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
@@ -289,7 +242,7 @@ func TestRunDisconnect(t *testing.T) {
|
||||
setTimeout(t, "Waiting for /bin/cat to exit timed out", 2*time.Second, func() {
|
||||
container := globalRuntime.List()[0]
|
||||
container.Wait()
|
||||
if container.State.IsRunning() {
|
||||
if container.State.Running {
|
||||
t.Fatalf("/bin/cat is still running after closing stdin")
|
||||
}
|
||||
})
|
||||
@@ -301,15 +254,15 @@ func TestRunDisconnectTty(t *testing.T) {
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
// We're simulating a disconnect so the return value doesn't matter. What matters is the
|
||||
// fact that CmdRun returns.
|
||||
if err := cli.CmdRun("-i", "-t", unitTestImageID, "/bin/cat"); err != nil {
|
||||
utils.Debugf("Error CmdRun: %s", err)
|
||||
utils.Debugf("Error CmdRun: %s\n", err)
|
||||
}
|
||||
|
||||
close(c1)
|
||||
@@ -319,7 +272,7 @@ func TestRunDisconnectTty(t *testing.T) {
|
||||
for {
|
||||
// Client disconnect after run -i should keep stdin out in TTY mode
|
||||
l := globalRuntime.List()
|
||||
if len(l) == 1 && l[0].State.IsRunning() {
|
||||
if len(l) == 1 && l[0].State.Running {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
@@ -329,7 +282,7 @@ func TestRunDisconnectTty(t *testing.T) {
|
||||
// Client disconnect after run -i should keep stdin out in TTY mode
|
||||
container := globalRuntime.List()[0]
|
||||
|
||||
setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
|
||||
setTimeout(t, "Read/Write assertion timed out", 2000*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -345,7 +298,7 @@ func TestRunDisconnectTty(t *testing.T) {
|
||||
|
||||
// Give some time to monitor to do his thing
|
||||
container.WaitTimeout(500 * time.Millisecond)
|
||||
if !container.State.IsRunning() {
|
||||
if !container.State.Running {
|
||||
t.Fatalf("/bin/cat should still be running after closing stdin (tty mode)")
|
||||
}
|
||||
}
|
||||
@@ -358,8 +311,8 @@ func TestRunAttachStdin(t *testing.T) {
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
@@ -385,8 +338,8 @@ func TestRunAttachStdin(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cmdOutput != container.ID+"\n" {
|
||||
t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ID+"\n", cmdOutput)
|
||||
if cmdOutput != container.ShortID()+"\n" {
|
||||
t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortID()+"\n", cmdOutput)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -422,8 +375,8 @@ func TestRunDetach(t *testing.T) {
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
@@ -440,116 +393,35 @@ func TestRunDetach(t *testing.T) {
|
||||
container := globalRuntime.List()[0]
|
||||
|
||||
setTimeout(t, "Escape sequence timeout", 5*time.Second, func() {
|
||||
stdinPipe.Write([]byte{16, 17})
|
||||
stdinPipe.Write([]byte{'', ''})
|
||||
if err := stdinPipe.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
closeWrap(stdin, stdinPipe, stdout, stdoutPipe)
|
||||
|
||||
// wait for CmdRun to return
|
||||
setTimeout(t, "Waiting for CmdRun timed out", 15*time.Second, func() {
|
||||
<-ch
|
||||
})
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
if !container.State.IsRunning() {
|
||||
if !container.State.Running {
|
||||
t.Fatal("The detached container should be still running")
|
||||
}
|
||||
|
||||
setTimeout(t, "Waiting for container to die timed out", 20*time.Second, func() {
|
||||
container.Kill()
|
||||
container.Wait()
|
||||
})
|
||||
}
|
||||
|
||||
// TestAttachDetach checks that attach in tty mode can be detached using the long container ID
|
||||
// TestAttachDetach checks that attach in tty mode can be detached
|
||||
func TestAttachDetach(t *testing.T) {
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
defer close(ch)
|
||||
if err := cli.CmdRun("-i", "-t", "-d", unitTestImageID, "cat"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
var container *docker.Container
|
||||
|
||||
setTimeout(t, "Reading container's id timed out", 10*time.Second, func() {
|
||||
buf := make([]byte, 1024)
|
||||
n, err := stdout.Read(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
container = globalRuntime.List()[0]
|
||||
|
||||
if strings.Trim(string(buf[:n]), " \r\n") != container.ID {
|
||||
t.Fatalf("Wrong ID received. Expect %s, received %s", container.ID, buf[:n])
|
||||
}
|
||||
})
|
||||
setTimeout(t, "Starting container timed out", 10*time.Second, func() {
|
||||
<-ch
|
||||
})
|
||||
|
||||
stdin, stdinPipe = io.Pipe()
|
||||
stdout, stdoutPipe = io.Pipe()
|
||||
cli = docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
|
||||
ch = make(chan struct{})
|
||||
go func() {
|
||||
defer close(ch)
|
||||
if err := cli.CmdAttach(container.ID); err != nil {
|
||||
if err != io.ErrClosedPipe {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
if err != io.ErrClosedPipe {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(t, "Escape sequence timeout", 5*time.Second, func() {
|
||||
stdinPipe.Write([]byte{16, 17})
|
||||
if err := stdinPipe.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
closeWrap(stdin, stdinPipe, stdout, stdoutPipe)
|
||||
|
||||
// wait for CmdRun to return
|
||||
setTimeout(t, "Waiting for CmdAttach timed out", 15*time.Second, func() {
|
||||
<-ch
|
||||
})
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
if !container.State.IsRunning() {
|
||||
t.Fatal("The detached container should be still running")
|
||||
}
|
||||
|
||||
setTimeout(t, "Waiting for container to die timedout", 5*time.Second, func() {
|
||||
container.Kill()
|
||||
})
|
||||
}
|
||||
|
||||
// TestAttachDetachTruncatedID checks that attach in tty mode can be detached
|
||||
func TestAttachDetachTruncatedID(t *testing.T) {
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
go stdout.Read(make([]byte, 1024))
|
||||
setTimeout(t, "Starting container timed out", 2*time.Second, func() {
|
||||
@@ -562,33 +434,28 @@ func TestAttachDetachTruncatedID(t *testing.T) {
|
||||
|
||||
stdin, stdinPipe = io.Pipe()
|
||||
stdout, stdoutPipe = io.Pipe()
|
||||
cli = docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
cli = NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
defer close(ch)
|
||||
if err := cli.CmdAttach(utils.TruncateID(container.ID)); err != nil {
|
||||
if err != io.ErrClosedPipe {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := cli.CmdAttach(container.ShortID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
if err != io.ErrClosedPipe {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(t, "Escape sequence timeout", 5*time.Second, func() {
|
||||
stdinPipe.Write([]byte{16, 17})
|
||||
stdinPipe.Write([]byte{'', ''})
|
||||
if err := stdinPipe.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
closeWrap(stdin, stdinPipe, stdout, stdoutPipe)
|
||||
|
||||
// wait for CmdRun to return
|
||||
setTimeout(t, "Waiting for CmdAttach timed out", 15*time.Second, func() {
|
||||
@@ -596,12 +463,13 @@ func TestAttachDetachTruncatedID(t *testing.T) {
|
||||
})
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
if !container.State.IsRunning() {
|
||||
if !container.State.Running {
|
||||
t.Fatal("The detached container should be still running")
|
||||
}
|
||||
|
||||
setTimeout(t, "Waiting for container to die timedout", 5*time.Second, func() {
|
||||
container.Kill()
|
||||
container.Wait()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -610,13 +478,13 @@ func TestAttachDisconnect(t *testing.T) {
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
go func() {
|
||||
// Start a process in daemon mode
|
||||
if err := cli.CmdRun("-d", "-i", unitTestImageID, "/bin/cat"); err != nil {
|
||||
utils.Debugf("Error CmdRun: %s", err)
|
||||
utils.Debugf("Error CmdRun: %s\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -629,7 +497,7 @@ func TestAttachDisconnect(t *testing.T) {
|
||||
setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() {
|
||||
for {
|
||||
l := globalRuntime.List()
|
||||
if len(l) == 1 && l[0].State.IsRunning() {
|
||||
if len(l) == 1 && l[0].State.Running {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
@@ -665,7 +533,7 @@ func TestAttachDisconnect(t *testing.T) {
|
||||
// We closed stdin, expect /bin/cat to still be running
|
||||
// Wait a little bit to make sure container.monitor() did his thing
|
||||
err := container.WaitTimeout(500 * time.Millisecond)
|
||||
if err == nil || !container.State.IsRunning() {
|
||||
if err == nil || !container.State.Running {
|
||||
t.Fatalf("/bin/cat is not running after closing stdin")
|
||||
}
|
||||
|
||||
@@ -679,8 +547,8 @@ func TestAttachDisconnect(t *testing.T) {
|
||||
func TestRunAutoRemove(t *testing.T) {
|
||||
t.Skip("Fixme. Skipping test for now, race condition")
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
cli := docker.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
@@ -702,7 +570,7 @@ func TestRunAutoRemove(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(t, "CmdRun timed out", 10*time.Second, func() {
|
||||
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
|
||||
<-c
|
||||
})
|
||||
|
||||
@@ -712,232 +580,3 @@ func TestRunAutoRemove(t *testing.T) {
|
||||
t.Fatalf("failed to remove container automatically: container %s still exists", temporaryContainerID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCmdLogs(t *testing.T) {
|
||||
cli := docker.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
if err := cli.CmdRun(unitTestImageID, "sh", "-c", "ls -l"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := cli.CmdRun("-t", unitTestImageID, "sh", "-c", "ls -l"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := cli.CmdLogs(globalRuntime.List()[0].ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Expected behaviour: using / as a bind mount source should throw an error
|
||||
func TestRunErrorBindMountRootSource(t *testing.T) {
|
||||
|
||||
cli := docker.NewDockerCli(nil, nil, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
defer close(c)
|
||||
if err := cli.CmdRun("-v", "/:/tmp", unitTestImageID, "echo 'should fail'"); err == nil {
|
||||
t.Fatal("should have failed to run when using / as a source for the bind mount")
|
||||
}
|
||||
}()
|
||||
|
||||
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
|
||||
<-c
|
||||
})
|
||||
}
|
||||
|
||||
// Expected behaviour: error out when attempting to bind mount non-existing source paths
|
||||
func TestRunErrorBindNonExistingSource(t *testing.T) {
|
||||
|
||||
cli := docker.NewDockerCli(nil, nil, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
defer close(c)
|
||||
if err := cli.CmdRun("-v", "/i/dont/exist:/tmp", unitTestImageID, "echo 'should fail'"); err == nil {
|
||||
t.Fatal("should have failed to run when using /i/dont/exist as a source for the bind mount")
|
||||
}
|
||||
}()
|
||||
|
||||
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
|
||||
<-c
|
||||
})
|
||||
}
|
||||
|
||||
func TestImagesViz(t *testing.T) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := docker.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
image := buildTestImages(t, globalEngine)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
defer close(c)
|
||||
if err := cli.CmdImages("-viz"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stdoutPipe.Close()
|
||||
}()
|
||||
|
||||
setTimeout(t, "Reading command output time out", 2*time.Second, func() {
|
||||
cmdOutputBytes, err := ioutil.ReadAll(bufio.NewReader(stdout))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cmdOutput := string(cmdOutputBytes)
|
||||
|
||||
regexpStrings := []string{
|
||||
"digraph docker {",
|
||||
fmt.Sprintf("base -> \"%s\" \\[style=invis]", unitTestImageIDShort),
|
||||
fmt.Sprintf("label=\"%s\\\\n%s:latest\"", unitTestImageIDShort, unitTestImageName),
|
||||
fmt.Sprintf("label=\"%s\\\\n%s:%s\"", utils.TruncateID(image.ID), "test", "latest"),
|
||||
"base \\[style=invisible]",
|
||||
}
|
||||
|
||||
compiledRegexps := []*regexp.Regexp{}
|
||||
for _, regexpString := range regexpStrings {
|
||||
regexp, err := regexp.Compile(regexpString)
|
||||
if err != nil {
|
||||
fmt.Println("Error in regex string: ", err)
|
||||
return
|
||||
}
|
||||
compiledRegexps = append(compiledRegexps, regexp)
|
||||
}
|
||||
|
||||
for _, regexp := range compiledRegexps {
|
||||
if !regexp.MatchString(cmdOutput) {
|
||||
t.Fatalf("images -viz content '%s' did not match regexp '%s'", cmdOutput, regexp)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestImagesTree(t *testing.T) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := docker.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
image := buildTestImages(t, globalEngine)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
defer close(c)
|
||||
if err := cli.CmdImages("-tree"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stdoutPipe.Close()
|
||||
}()
|
||||
|
||||
setTimeout(t, "Reading command output time out", 2*time.Second, func() {
|
||||
cmdOutputBytes, err := ioutil.ReadAll(bufio.NewReader(stdout))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cmdOutput := string(cmdOutputBytes)
|
||||
|
||||
regexpStrings := []string{
|
||||
fmt.Sprintf("└─%s Size: (\\d+.\\d+ MB) \\(virtual \\d+.\\d+ MB\\) Tags: %s:latest", unitTestImageIDShort, unitTestImageName),
|
||||
"(?m)^ └─[0-9a-f]+",
|
||||
"(?m)^ └─[0-9a-f]+",
|
||||
"(?m)^ └─[0-9a-f]+",
|
||||
fmt.Sprintf(" └─%s Size: \\d+ B \\(virtual \\d+.\\d+ MB\\) Tags: test:latest", utils.TruncateID(image.ID)),
|
||||
}
|
||||
|
||||
compiledRegexps := []*regexp.Regexp{}
|
||||
for _, regexpString := range regexpStrings {
|
||||
regexp, err := regexp.Compile(regexpString)
|
||||
if err != nil {
|
||||
fmt.Println("Error in regex string: ", err)
|
||||
return
|
||||
}
|
||||
compiledRegexps = append(compiledRegexps, regexp)
|
||||
}
|
||||
|
||||
for _, regexp := range compiledRegexps {
|
||||
if !regexp.MatchString(cmdOutput) {
|
||||
t.Fatalf("images -tree content '%s' did not match regexp '%s'", cmdOutput, regexp)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func buildTestImages(t *testing.T, eng *engine.Engine) *docker.Image {
|
||||
|
||||
var testBuilder = testContextTemplate{
|
||||
`
|
||||
from {IMAGE}
|
||||
run sh -c 'echo root:testpass > /tmp/passwd'
|
||||
run mkdir -p /var/run/sshd
|
||||
run [ "$(cat /tmp/passwd)" = "root:testpass" ]
|
||||
run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
|
||||
`,
|
||||
nil,
|
||||
nil,
|
||||
}
|
||||
image := buildImage(testBuilder, t, eng, true)
|
||||
|
||||
err := mkServerFromEngine(eng, t).ContainerTag(image.ID, "test", "latest", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return image
|
||||
}
|
||||
|
||||
// #2098 - Docker cidFiles only contain short version of the containerId
|
||||
//sudo docker run -cidfile /tmp/docker_test.cid ubuntu echo "test"
|
||||
// TestRunCidFile tests that run -cidfile returns the longid
|
||||
func TestRunCidFile(t *testing.T) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
tmpDir, err := ioutil.TempDir("", "TestRunCidFile")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tmpCidFile := path.Join(tmpDir, "cid")
|
||||
|
||||
cli := docker.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
defer close(c)
|
||||
if err := cli.CmdRun("-cidfile", tmpCidFile, unitTestImageID, "ls"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
defer os.RemoveAll(tmpDir)
|
||||
setTimeout(t, "Reading command output time out", 2*time.Second, func() {
|
||||
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(cmdOutput) < 1 {
|
||||
t.Fatalf("'ls' should return something , not '%s'", cmdOutput)
|
||||
}
|
||||
//read the tmpCidFile
|
||||
buffer, err := ioutil.ReadFile(tmpCidFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
id := string(buffer)
|
||||
|
||||
if len(id) != len("2bf44ea18873287bd9ace8a4cb536a7cbe134bed67e805fdf2f58a57f69b320c") {
|
||||
t.Fatalf("-cidfile should be a long id, not '%s'", id)
|
||||
}
|
||||
//test that its a valid cid? (though the container is gone..)
|
||||
//remove the file and dir.
|
||||
})
|
||||
|
||||
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
|
||||
<-c
|
||||
})
|
||||
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"net"
|
||||
)
|
||||
|
||||
// FIXME: separate runtime configuration from http api configuration
|
||||
type DaemonConfig struct {
|
||||
Pidfile string
|
||||
Root string
|
||||
GraphPath string
|
||||
ProtoAddresses []string
|
||||
AutoRestart bool
|
||||
EnableCors bool
|
||||
Dns []string
|
||||
@@ -17,25 +16,3 @@ type DaemonConfig struct {
|
||||
DefaultIp net.IP
|
||||
InterContainerCommunication bool
|
||||
}
|
||||
|
||||
// ConfigFromJob creates and returns a new DaemonConfig object
|
||||
// by parsing the contents of a job's environment.
|
||||
func ConfigFromJob(job *engine.Job) *DaemonConfig {
|
||||
var config DaemonConfig
|
||||
config.Pidfile = job.Getenv("Pidfile")
|
||||
config.Root = job.Getenv("Root")
|
||||
config.AutoRestart = job.GetenvBool("AutoRestart")
|
||||
config.EnableCors = job.GetenvBool("EnableCors")
|
||||
if dns := job.Getenv("Dns"); dns != "" {
|
||||
config.Dns = []string{dns}
|
||||
}
|
||||
config.EnableIptables = job.GetenvBool("EnableIptables")
|
||||
if br := job.Getenv("BridgeIface"); br != "" {
|
||||
config.BridgeIface = br
|
||||
} else {
|
||||
config.BridgeIface = DefaultNetworkBridge
|
||||
}
|
||||
config.DefaultIp = net.ParseIP(job.Getenv("DefaultIp"))
|
||||
config.InterContainerCommunication = job.GetenvBool("InterContainerCommunication")
|
||||
return &config
|
||||
}
|
||||
|
||||
-149
@@ -1,149 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCompareConfig(t *testing.T) {
|
||||
volumes1 := make(map[string]struct{})
|
||||
volumes1["/test1"] = struct{}{}
|
||||
config1 := Config{
|
||||
Dns: []string{"1.1.1.1", "2.2.2.2"},
|
||||
PortSpecs: []string{"1111:1111", "2222:2222"},
|
||||
Env: []string{"VAR1=1", "VAR2=2"},
|
||||
VolumesFrom: "11111111",
|
||||
Volumes: volumes1,
|
||||
}
|
||||
config2 := Config{
|
||||
Dns: []string{"0.0.0.0", "2.2.2.2"},
|
||||
PortSpecs: []string{"1111:1111", "2222:2222"},
|
||||
Env: []string{"VAR1=1", "VAR2=2"},
|
||||
VolumesFrom: "11111111",
|
||||
Volumes: volumes1,
|
||||
}
|
||||
config3 := Config{
|
||||
Dns: []string{"1.1.1.1", "2.2.2.2"},
|
||||
PortSpecs: []string{"0000:0000", "2222:2222"},
|
||||
Env: []string{"VAR1=1", "VAR2=2"},
|
||||
VolumesFrom: "11111111",
|
||||
Volumes: volumes1,
|
||||
}
|
||||
config4 := Config{
|
||||
Dns: []string{"1.1.1.1", "2.2.2.2"},
|
||||
PortSpecs: []string{"0000:0000", "2222:2222"},
|
||||
Env: []string{"VAR1=1", "VAR2=2"},
|
||||
VolumesFrom: "22222222",
|
||||
Volumes: volumes1,
|
||||
}
|
||||
volumes2 := make(map[string]struct{})
|
||||
volumes2["/test2"] = struct{}{}
|
||||
config5 := Config{
|
||||
Dns: []string{"1.1.1.1", "2.2.2.2"},
|
||||
PortSpecs: []string{"0000:0000", "2222:2222"},
|
||||
Env: []string{"VAR1=1", "VAR2=2"},
|
||||
VolumesFrom: "11111111",
|
||||
Volumes: volumes2,
|
||||
}
|
||||
if CompareConfig(&config1, &config2) {
|
||||
t.Fatalf("CompareConfig should return false, Dns are different")
|
||||
}
|
||||
if CompareConfig(&config1, &config3) {
|
||||
t.Fatalf("CompareConfig should return false, PortSpecs are different")
|
||||
}
|
||||
if CompareConfig(&config1, &config4) {
|
||||
t.Fatalf("CompareConfig should return false, VolumesFrom are different")
|
||||
}
|
||||
if CompareConfig(&config1, &config5) {
|
||||
t.Fatalf("CompareConfig should return false, Volumes are different")
|
||||
}
|
||||
if !CompareConfig(&config1, &config1) {
|
||||
t.Fatalf("CompareConfig should return true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeConfig(t *testing.T) {
|
||||
volumesImage := make(map[string]struct{})
|
||||
volumesImage["/test1"] = struct{}{}
|
||||
volumesImage["/test2"] = struct{}{}
|
||||
configImage := &Config{
|
||||
Dns: []string{"1.1.1.1", "2.2.2.2"},
|
||||
PortSpecs: []string{"1111:1111", "2222:2222"},
|
||||
Env: []string{"VAR1=1", "VAR2=2"},
|
||||
VolumesFrom: "1111",
|
||||
Volumes: volumesImage,
|
||||
}
|
||||
|
||||
volumesUser := make(map[string]struct{})
|
||||
volumesUser["/test3"] = struct{}{}
|
||||
configUser := &Config{
|
||||
Dns: []string{"3.3.3.3"},
|
||||
PortSpecs: []string{"3333:2222", "3333:3333"},
|
||||
Env: []string{"VAR2=3", "VAR3=3"},
|
||||
Volumes: volumesUser,
|
||||
}
|
||||
|
||||
if err := MergeConfig(configUser, configImage); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if len(configUser.Dns) != 3 {
|
||||
t.Fatalf("Expected 3 dns, 1.1.1.1, 2.2.2.2 and 3.3.3.3, found %d", len(configUser.Dns))
|
||||
}
|
||||
for _, dns := range configUser.Dns {
|
||||
if dns != "1.1.1.1" && dns != "2.2.2.2" && dns != "3.3.3.3" {
|
||||
t.Fatalf("Expected 1.1.1.1 or 2.2.2.2 or 3.3.3.3, found %s", dns)
|
||||
}
|
||||
}
|
||||
|
||||
if len(configUser.ExposedPorts) != 3 {
|
||||
t.Fatalf("Expected 3 ExposedPorts, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts))
|
||||
}
|
||||
for portSpecs := range configUser.ExposedPorts {
|
||||
if portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" {
|
||||
t.Fatalf("Expected 1111 or 2222 or 3333, found %s", portSpecs)
|
||||
}
|
||||
}
|
||||
if len(configUser.Env) != 3 {
|
||||
t.Fatalf("Expected 3 env var, VAR1=1, VAR2=3 and VAR3=3, found %d", len(configUser.Env))
|
||||
}
|
||||
for _, env := range configUser.Env {
|
||||
if env != "VAR1=1" && env != "VAR2=3" && env != "VAR3=3" {
|
||||
t.Fatalf("Expected VAR1=1 or VAR2=3 or VAR3=3, found %s", env)
|
||||
}
|
||||
}
|
||||
|
||||
if len(configUser.Volumes) != 3 {
|
||||
t.Fatalf("Expected 3 volumes, /test1, /test2 and /test3, found %d", len(configUser.Volumes))
|
||||
}
|
||||
for v := range configUser.Volumes {
|
||||
if v != "/test1" && v != "/test2" && v != "/test3" {
|
||||
t.Fatalf("Expected /test1 or /test2 or /test3, found %s", v)
|
||||
}
|
||||
}
|
||||
|
||||
if configUser.VolumesFrom != "1111" {
|
||||
t.Fatalf("Expected VolumesFrom to be 1111, found %s", configUser.VolumesFrom)
|
||||
}
|
||||
|
||||
ports, _, err := parsePortSpecs([]string{"0000"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
configImage2 := &Config{
|
||||
ExposedPorts: ports,
|
||||
}
|
||||
|
||||
if err := MergeConfig(configUser, configImage2); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if len(configUser.ExposedPorts) != 4 {
|
||||
t.Fatalf("Expected 4 ExposedPorts, 0000, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts))
|
||||
}
|
||||
for portSpecs := range configUser.ExposedPorts {
|
||||
if portSpecs.Port() != "0000" && portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" {
|
||||
t.Fatalf("Expected 0000 or 1111 or 2222 or 3333, found %s", portSpecs)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+370
-313
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,161 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseLxcConfOpt(t *testing.T) {
|
||||
opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "}
|
||||
|
||||
for _, o := range opts {
|
||||
k, v, err := parseLxcOpt(o)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
if k != "lxc.utsname" {
|
||||
t.Fail()
|
||||
}
|
||||
if v != "docker" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNetworkOptsPrivateOnly(t *testing.T) {
|
||||
ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::80"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(ports) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(ports))
|
||||
t.FailNow()
|
||||
}
|
||||
if len(bindings) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(bindings))
|
||||
t.FailNow()
|
||||
}
|
||||
for k := range ports {
|
||||
if k.Proto() != "tcp" {
|
||||
t.Logf("Expected tcp got %s", k.Proto())
|
||||
t.Fail()
|
||||
}
|
||||
if k.Port() != "80" {
|
||||
t.Logf("Expected 80 got %s", k.Port())
|
||||
t.Fail()
|
||||
}
|
||||
b, exists := bindings[k]
|
||||
if !exists {
|
||||
t.Log("Binding does not exist")
|
||||
t.FailNow()
|
||||
}
|
||||
if len(b) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(b))
|
||||
t.FailNow()
|
||||
}
|
||||
s := b[0]
|
||||
if s.HostPort != "" {
|
||||
t.Logf("Expected \"\" got %s", s.HostPort)
|
||||
t.Fail()
|
||||
}
|
||||
if s.HostIp != "192.168.1.100" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNetworkOptsPublic(t *testing.T) {
|
||||
ports, bindings, err := parsePortSpecs([]string{"192.168.1.100:8080:80"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(ports) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(ports))
|
||||
t.FailNow()
|
||||
}
|
||||
if len(bindings) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(bindings))
|
||||
t.FailNow()
|
||||
}
|
||||
for k := range ports {
|
||||
if k.Proto() != "tcp" {
|
||||
t.Logf("Expected tcp got %s", k.Proto())
|
||||
t.Fail()
|
||||
}
|
||||
if k.Port() != "80" {
|
||||
t.Logf("Expected 80 got %s", k.Port())
|
||||
t.Fail()
|
||||
}
|
||||
b, exists := bindings[k]
|
||||
if !exists {
|
||||
t.Log("Binding does not exist")
|
||||
t.FailNow()
|
||||
}
|
||||
if len(b) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(b))
|
||||
t.FailNow()
|
||||
}
|
||||
s := b[0]
|
||||
if s.HostPort != "8080" {
|
||||
t.Logf("Expected 8080 got %s", s.HostPort)
|
||||
t.Fail()
|
||||
}
|
||||
if s.HostIp != "192.168.1.100" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNetworkOptsUdp(t *testing.T) {
|
||||
ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::6000/udp"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(ports) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(ports))
|
||||
t.FailNow()
|
||||
}
|
||||
if len(bindings) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(bindings))
|
||||
t.FailNow()
|
||||
}
|
||||
for k := range ports {
|
||||
if k.Proto() != "udp" {
|
||||
t.Logf("Expected udp got %s", k.Proto())
|
||||
t.Fail()
|
||||
}
|
||||
if k.Port() != "6000" {
|
||||
t.Logf("Expected 6000 got %s", k.Port())
|
||||
t.Fail()
|
||||
}
|
||||
b, exists := bindings[k]
|
||||
if !exists {
|
||||
t.Log("Binding does not exist")
|
||||
t.FailNow()
|
||||
}
|
||||
if len(b) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(b))
|
||||
t.FailNow()
|
||||
}
|
||||
s := b[0]
|
||||
if s.HostPort != "" {
|
||||
t.Logf("Expected \"\" got %s", s.HostPort)
|
||||
t.Fail()
|
||||
}
|
||||
if s.HostIp != "192.168.1.100" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFullName(t *testing.T) {
|
||||
name, err := getFullName("testing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if name != "/testing" {
|
||||
t.Fatalf("Expected /testing got %s", name)
|
||||
}
|
||||
if _, err := getFullName(""); err == nil {
|
||||
t.Fatal("Error should not be nil")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
*.pyc
|
||||
@@ -0,0 +1,78 @@
|
||||
# docker-brew
|
||||
|
||||
docker-brew is a command-line tool used to build the docker standard library.
|
||||
|
||||
## Install instructions
|
||||
|
||||
1. Install python if it isn't already available on your OS of choice
|
||||
1. Install the easy_install tool (`sudo apt-get install python-setuptools`
|
||||
for Debian)
|
||||
1. Install the python package manager, `pip` (`easy_install pip`)
|
||||
1. Run the following command: `sudo pip install -r requirements.txt`
|
||||
1. You should now be able to use the `docker-brew` script as such.
|
||||
|
||||
## Basics
|
||||
|
||||
./docker-brew -h
|
||||
|
||||
Display usage and help.
|
||||
|
||||
./docker-brew
|
||||
|
||||
Default build from the default repo/branch. Images will be created under the
|
||||
`library/` namespace. Does not perform a remote push.
|
||||
|
||||
./docker-brew -n mycorp.com -b stable --push git://github.com/mycorp/docker
|
||||
|
||||
Will fetch the library definition files in the `stable` branch of the
|
||||
`git://github.com/mycorp/docker` repository and create images under the
|
||||
`mycorp.com` namespace (e.g. `mycorp.com/ubuntu`). Created images will then
|
||||
be pushed to the official docker repository (pending: support for private
|
||||
repositories)
|
||||
|
||||
## Library definition files
|
||||
|
||||
The library definition files are plain text files found in the `library/`
|
||||
subfolder of the docker repository.
|
||||
|
||||
### File names
|
||||
|
||||
The name of a definition file will determine the name of the image(s) it
|
||||
creates. For example, the `library/ubuntu` file will create images in the
|
||||
`<namespace>/ubuntu` repository. If multiple instructions are present in
|
||||
a single file, all images are expected to be created under a different tag.
|
||||
|
||||
### Instruction format
|
||||
|
||||
Each line represents a build instruction.
|
||||
There are different formats that `docker-brew` is able to parse.
|
||||
|
||||
<git-url>
|
||||
git://github.com/dotcloud/hipache
|
||||
https://github.com/dotcloud/docker.git
|
||||
|
||||
The simplest format. `docker-brew` will fetch data from the provided git
|
||||
repository from the `HEAD`of its `master` branch. Generated image will be
|
||||
tagged as `latest`. Use of this format is discouraged because there is no
|
||||
way to ensure stability.
|
||||
|
||||
<docker-tag> <git-url>
|
||||
bleeding-edge git://github.com/dotcloud/docker
|
||||
unstable https://github.com/dotcloud/docker-redis.git
|
||||
|
||||
A more advanced format. `docker-brew` will fetch data from the provided git
|
||||
repository from the `HEAD`of its `master` branch. Generated image will be
|
||||
tagged as `<docker-tag>`. Recommended if we always want to provide a snapshot
|
||||
of the latest development. Again, no way to ensure stability.
|
||||
|
||||
<docker-tag> <git-url> T:<git-tag>
|
||||
2.4.0 git://github.com/dotcloud/docker-redis T:2.4.0
|
||||
<docker-tag> <git-url> B:<git-branch>
|
||||
zfs git://github.com/dotcloud/docker B:zfs-support
|
||||
<docker-tag> <git-url> C:<git-commit-id>
|
||||
2.2.0 https://github.com/dotcloud/docker-redis.git C:a4bf8923ee4ec566d3ddc212
|
||||
|
||||
The most complete format. `docker-brew` will fetch data from the provided git
|
||||
repository from the provided reference (if it's a branch, brew will fetch its
|
||||
`HEAD`). Generated image will be tagged as `<docker-tag>`. Recommended whenever
|
||||
possible.
|
||||
@@ -0,0 +1 @@
|
||||
from brew import build_library, DEFAULT_REPOSITORY, DEFAULT_BRANCH
|
||||
@@ -0,0 +1,185 @@
|
||||
import os
|
||||
import logging
|
||||
from shutil import rmtree
|
||||
|
||||
import docker
|
||||
|
||||
import git
|
||||
|
||||
DEFAULT_REPOSITORY = 'git://github.com/dotcloud/docker'
|
||||
DEFAULT_BRANCH = 'master'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
|
||||
level='INFO')
|
||||
client = docker.Client()
|
||||
processed = {}
|
||||
processed_folders = []
|
||||
|
||||
|
||||
def build_library(repository=None, branch=None, namespace=None, push=False,
|
||||
debug=False, prefill=True, registry=None):
|
||||
dst_folder = None
|
||||
summary = Summary()
|
||||
if repository is None:
|
||||
repository = DEFAULT_REPOSITORY
|
||||
if branch is None:
|
||||
branch = DEFAULT_BRANCH
|
||||
if debug:
|
||||
logger.setLevel('DEBUG')
|
||||
|
||||
if not (repository.startswith('https://') or repository.startswith('git://')):
|
||||
logger.info('Repository provided assumed to be a local path')
|
||||
dst_folder = repository
|
||||
|
||||
try:
|
||||
client.version()
|
||||
except Exception as e:
|
||||
logger.error('Could not reach the docker daemon. Please make sure it '
|
||||
'is running.')
|
||||
logger.warning('Also make sure you have access to the docker UNIX '
|
||||
'socket (use sudo)')
|
||||
return
|
||||
|
||||
#FIXME: set destination folder and only pull latest changes instead of
|
||||
# cloning the whole repo everytime
|
||||
if not dst_folder:
|
||||
logger.info('Cloning docker repo from {0}, branch: {1}'.format(
|
||||
repository, branch))
|
||||
try:
|
||||
rep, dst_folder = git.clone_branch(repository, branch)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
logger.error('Source repository could not be fetched. Check '
|
||||
'that the address is correct and the branch exists.')
|
||||
return
|
||||
try:
|
||||
dirlist = os.listdir(os.path.join(dst_folder, 'library'))
|
||||
except OSError as e:
|
||||
logger.error('The path provided ({0}) could not be found or didn\'t'
|
||||
'contain a library/ folder.'.format(dst_folder))
|
||||
return
|
||||
for buildfile in dirlist:
|
||||
if buildfile == 'MAINTAINERS':
|
||||
continue
|
||||
f = open(os.path.join(dst_folder, 'library', buildfile))
|
||||
linecnt = 0
|
||||
for line in f:
|
||||
linecnt = linecnt + 1
|
||||
logger.debug('{0} ---> {1}'.format(buildfile, line))
|
||||
args = line.split()
|
||||
try:
|
||||
if len(args) > 3:
|
||||
raise RuntimeError('Incorrect line format, '
|
||||
'please refer to the docs')
|
||||
|
||||
url = None
|
||||
ref = 'refs/heads/master'
|
||||
tag = None
|
||||
if len(args) == 1: # Just a URL, simple mode
|
||||
url = args[0]
|
||||
elif len(args) == 2 or len(args) == 3: # docker-tag url
|
||||
url = args[1]
|
||||
tag = args[0]
|
||||
|
||||
if len(args) == 3: # docker-tag url B:branch or T:tag
|
||||
ref = None
|
||||
if args[2].startswith('B:'):
|
||||
ref = 'refs/heads/' + args[2][2:]
|
||||
elif args[2].startswith('T:'):
|
||||
ref = 'refs/tags/' + args[2][2:]
|
||||
elif args[2].startswith('C:'):
|
||||
ref = args[2][2:]
|
||||
else:
|
||||
raise RuntimeError('Incorrect line format, '
|
||||
'please refer to the docs')
|
||||
if prefill:
|
||||
logger.debug('Pulling {0} from official repository (cache '
|
||||
'fill)'.format(buildfile))
|
||||
client.pull(buildfile)
|
||||
img = build_repo(url, ref, buildfile, tag, namespace, push,
|
||||
registry)
|
||||
summary.add_success(buildfile, (linecnt, line), img)
|
||||
processed['{0}@{1}'.format(url, ref)] = img
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
summary.add_exception(buildfile, (linecnt, line), e)
|
||||
|
||||
f.close()
|
||||
if dst_folder != repository:
|
||||
rmtree(dst_folder, True)
|
||||
for d in processed_folders:
|
||||
rmtree(d, True)
|
||||
summary.print_summary(logger)
|
||||
|
||||
|
||||
def build_repo(repository, ref, docker_repo, docker_tag, namespace, push, registry):
|
||||
docker_repo = '{0}/{1}'.format(namespace or 'library', docker_repo)
|
||||
img_id = None
|
||||
dst_folder = None
|
||||
if '{0}@{1}'.format(repository, ref) not in processed.keys():
|
||||
logger.info('Cloning {0} (ref: {1})'.format(repository, ref))
|
||||
if repository not in processed:
|
||||
rep, dst_folder = git.clone(repository, ref)
|
||||
processed[repository] = rep
|
||||
processed_folders.append(dst_folder)
|
||||
else:
|
||||
dst_folder = git.checkout(processed[repository], ref)
|
||||
if not 'Dockerfile' in os.listdir(dst_folder):
|
||||
raise RuntimeError('Dockerfile not found in cloned repository')
|
||||
logger.info('Building using dockerfile...')
|
||||
img_id, logs = client.build(path=dst_folder, quiet=True)
|
||||
else:
|
||||
img_id = processed['{0}@{1}'.format(repository, ref)]
|
||||
logger.info('Committing to {0}:{1}'.format(docker_repo,
|
||||
docker_tag or 'latest'))
|
||||
client.tag(img_id, docker_repo, docker_tag)
|
||||
if push:
|
||||
logger.info('Pushing result to registry {0}'.format(
|
||||
registry or "default"))
|
||||
if registry is not None:
|
||||
docker_repo = '{0}/{1}'.format(registry, docker_repo)
|
||||
logger.info('Also tagging {0}'.format(docker_repo))
|
||||
client.tag(img_id, docker_repo, docker_tag)
|
||||
client.push(docker_repo)
|
||||
return img_id
|
||||
|
||||
|
||||
class Summary(object):
|
||||
def __init__(self):
|
||||
self._summary = {}
|
||||
self._has_exc = False
|
||||
|
||||
def _add_data(self, image, linestr, data):
|
||||
if image not in self._summary:
|
||||
self._summary[image] = { linestr: data }
|
||||
else:
|
||||
self._summary[image][linestr] = data
|
||||
|
||||
def add_exception(self, image, line, exc):
|
||||
lineno, linestr = line
|
||||
self._add_data(image, linestr, { 'line': lineno, 'exc': str(exc) })
|
||||
self._has_exc = True
|
||||
|
||||
def add_success(self, image, line, img_id):
|
||||
lineno, linestr = line
|
||||
self._add_data(image, linestr, { 'line': lineno, 'id': img_id })
|
||||
|
||||
def print_summary(self, logger=None):
|
||||
linesep = ''.center(61, '-') + '\n'
|
||||
s = 'BREW BUILD SUMMARY\n' + linesep
|
||||
success = 'OVERALL SUCCESS: {}\n'.format(not self._has_exc)
|
||||
details = linesep
|
||||
for image, lines in self._summary.iteritems():
|
||||
details = details + '{}\n{}'.format(image, linesep)
|
||||
for linestr, data in lines.iteritems():
|
||||
details = details + '{0:2} | {1} | {2:50}\n'.format(
|
||||
data['line'],
|
||||
'KO' if 'exc' in data else 'OK',
|
||||
data['exc'] if 'exc' in data else data['id']
|
||||
)
|
||||
details = details + linesep
|
||||
if logger:
|
||||
logger.info(s + success + details)
|
||||
else:
|
||||
print s, success, details
|
||||
@@ -0,0 +1,63 @@
|
||||
import tempfile
|
||||
import logging
|
||||
|
||||
from dulwich import index
|
||||
from dulwich.client import get_transport_and_path
|
||||
from dulwich.repo import Repo
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def clone_branch(repo_url, branch="master", folder=None):
|
||||
return clone(repo_url, 'refs/heads/' + branch, folder)
|
||||
|
||||
|
||||
def clone_tag(repo_url, tag, folder=None):
|
||||
return clone(repo_url, 'refs/tags/' + tag, folder)
|
||||
|
||||
|
||||
def checkout(rep, ref=None):
|
||||
is_commit = False
|
||||
if ref is None:
|
||||
ref = 'refs/heads/master'
|
||||
elif not ref.startswith('refs/'):
|
||||
is_commit = True
|
||||
if is_commit:
|
||||
rep['HEAD'] = rep.commit(ref)
|
||||
else:
|
||||
rep['HEAD'] = rep.refs[ref]
|
||||
indexfile = rep.index_path()
|
||||
tree = rep["HEAD"].tree
|
||||
index.build_index_from_tree(rep.path, indexfile, rep.object_store, tree)
|
||||
return rep.path
|
||||
|
||||
def clone(repo_url, ref=None, folder=None):
|
||||
is_commit = False
|
||||
if ref is None:
|
||||
ref = 'refs/heads/master'
|
||||
elif not ref.startswith('refs/'):
|
||||
is_commit = True
|
||||
logger.debug("clone repo_url={0}, ref={1}".format(repo_url, ref))
|
||||
if folder is None:
|
||||
folder = tempfile.mkdtemp()
|
||||
logger.debug("folder = {0}".format(folder))
|
||||
rep = Repo.init(folder)
|
||||
client, relative_path = get_transport_and_path(repo_url)
|
||||
logger.debug("client={0}".format(client))
|
||||
|
||||
remote_refs = client.fetch(relative_path, rep)
|
||||
for k, v in remote_refs.iteritems():
|
||||
try:
|
||||
rep.refs.add_if_new(k, v)
|
||||
except:
|
||||
pass
|
||||
|
||||
if is_commit:
|
||||
rep['HEAD'] = rep.commit(ref)
|
||||
else:
|
||||
rep['HEAD'] = remote_refs[ref]
|
||||
indexfile = rep.index_path()
|
||||
tree = rep["HEAD"].tree
|
||||
index.build_index_from_tree(rep.path, indexfile, rep.object_store, tree)
|
||||
logger.debug("done")
|
||||
return rep, folder
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
try:
|
||||
import brew
|
||||
except ImportError as e:
|
||||
print str(e)
|
||||
print 'Please install the required dependencies first'
|
||||
print 'sudo pip install -r requirements.txt'
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser('Build the docker standard library')
|
||||
parser.add_argument('--push', action='store_true', default=False,
|
||||
help='Push generated repositories')
|
||||
parser.add_argument('--debug', default=False, action='store_true',
|
||||
help='Enable debugging output')
|
||||
parser.add_argument('--noprefill', default=True, action='store_false',
|
||||
dest='prefill', help='Disable cache prefill')
|
||||
parser.add_argument('-n', metavar='NAMESPACE', default='library',
|
||||
help='Namespace used for generated repositories.'
|
||||
' Default is library')
|
||||
parser.add_argument('-b', metavar='BRANCH', default=brew.DEFAULT_BRANCH,
|
||||
help='Branch in the repository where the library definition'
|
||||
' files will be fetched. Default is ' + brew.DEFAULT_BRANCH)
|
||||
parser.add_argument('repository', default=brew.DEFAULT_REPOSITORY,
|
||||
nargs='?', help='git repository containing the library definition'
|
||||
' files. Default is ' + brew.DEFAULT_REPOSITORY)
|
||||
parser.add_argument('--reg', default=None, help='Registry address to'
|
||||
' push build results to. Also sets push to true.')
|
||||
args = parser.parse_args()
|
||||
brew.build_library(args.repository, args.b, args.n,
|
||||
args.push or args.reg is not None, args.debug, args.prefill, args.reg)
|
||||
@@ -0,0 +1,2 @@
|
||||
dulwich==0.9.0
|
||||
-e git://github.com/dotcloud/docker-py.git#egg=docker-py
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
from setuptools import setup
|
||||
|
||||
ROOT_DIR = os.path.dirname(__file__)
|
||||
SOURCE_DIR = os.path.join(ROOT_DIR)
|
||||
|
||||
test_requirements = []
|
||||
setup(
|
||||
name="docker-brew",
|
||||
version='0.0.1',
|
||||
description="-",
|
||||
packages=['dockerbrew'],
|
||||
install_requires=['dulwich', 'docker'] + test_requirements,
|
||||
zip_safe=False,
|
||||
classifiers=['Development Status :: 3 - Alpha',
|
||||
'Environment :: Other Environment',
|
||||
'Intended Audience :: Developers',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Topic :: Utilities'],
|
||||
)
|
||||
@@ -426,7 +426,7 @@ _docker_run()
|
||||
|
||||
_docker_search()
|
||||
{
|
||||
COMPREPLY=( $( compgen -W "-notrunc" "-stars" "-trusted" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "-notrunc" -- "$cur" ) )
|
||||
}
|
||||
|
||||
_docker_start()
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
Desktop Integration
|
||||
===================
|
||||
|
||||
The ./contrib/desktop-integration contains examples of typical dockerized
|
||||
desktop applications.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
* Data container: ./data/Dockerfile creates a data image sharing /data volume
|
||||
* Firefox: ./firefox/Dockerfile shows a way to dockerize a common multimedia application
|
||||
@@ -1,38 +0,0 @@
|
||||
# VERSION: 0.1
|
||||
# DESCRIPTION: Create data image sharing /data volume
|
||||
# AUTHOR: Daniel Mizyrycki <daniel@dotcloud.com>
|
||||
# COMMENTS:
|
||||
# This image is used as base for all data containers.
|
||||
# /data volume is owned by sysadmin.
|
||||
# USAGE:
|
||||
# # Download data Dockerfile
|
||||
# wget http://raw.github.com/dotcloud/docker/master/contrib/desktop-integration/data/Dockerfile
|
||||
#
|
||||
# # Build data image
|
||||
# docker build -t data -rm .
|
||||
#
|
||||
# # Create a data container. (eg: firefox-data)
|
||||
# docker run -name firefox-data data true
|
||||
#
|
||||
# # List data from it
|
||||
# docker run -volumes-from firefox-data busybox ls -al /data
|
||||
|
||||
docker-version 0.6.5
|
||||
|
||||
# Smallest base image, just to launch a container
|
||||
from busybox
|
||||
maintainer Daniel Mizyrycki <daniel@docker.com>
|
||||
|
||||
# Create a regular user
|
||||
run echo 'sysadmin:x:1000:1000::/data:/bin/sh' >> /etc/passwd
|
||||
run echo 'sysadmin:x:1000:' >> /etc/group
|
||||
|
||||
# Create directory for that user
|
||||
run mkdir /data
|
||||
run chown sysadmin.sysadmin /data
|
||||
|
||||
# Add content to /data. This will keep sysadmin ownership
|
||||
run touch /data/init_volume
|
||||
|
||||
# Create /data volume
|
||||
VOLUME /data
|
||||
@@ -1,49 +0,0 @@
|
||||
# VERSION: 0.7
|
||||
# DESCRIPTION: Create firefox container with its dependencies
|
||||
# AUTHOR: Daniel Mizyrycki <daniel@dotcloud.com>
|
||||
# COMMENTS:
|
||||
# This file describes how to build a Firefox container with all
|
||||
# dependencies installed. It uses native X11 unix socket and alsa
|
||||
# sound devices. Tested on Debian 7.2
|
||||
# USAGE:
|
||||
# # Download Firefox Dockerfile
|
||||
# wget http://raw.github.com/dotcloud/docker/master/contrib/desktop-integration/firefox/Dockerfile
|
||||
#
|
||||
# # Build firefox image
|
||||
# docker build -t firefox -rm .
|
||||
#
|
||||
# # Run stateful data-on-host firefox. For ephemeral, remove -v /data/firefox:/data
|
||||
# docker run -v /data/firefox:/data -v /tmp/.X11-unix:/tmp/.X11-unix \
|
||||
# -v /dev/snd:/dev/snd -lxc-conf='lxc.cgroup.devices.allow = c 116:* rwm' \
|
||||
# -e DISPLAY=unix$DISPLAY firefox
|
||||
#
|
||||
# # To run stateful dockerized data containers
|
||||
# docker run -volumes-from firefox-data -v /tmp/.X11-unix:/tmp/.X11-unix \
|
||||
# -v /dev/snd:/dev/snd -lxc-conf='lxc.cgroup.devices.allow = c 116:* rwm' \
|
||||
# -e DISPLAY=unix$DISPLAY firefox
|
||||
|
||||
docker-version 0.6.5
|
||||
|
||||
# Base docker image
|
||||
from tianon/debian:wheezy
|
||||
maintainer Daniel Mizyrycki <daniel@docker.com>
|
||||
|
||||
# Install firefox dependencies
|
||||
run echo "deb http://ftp.debian.org/debian/ wheezy main contrib" > /etc/apt/sources.list
|
||||
run apt-get update
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y libXrender1 libasound2 \
|
||||
libdbus-glib-1-2 libgtk2.0-0 libpango1.0-0 libxt6 wget bzip2 sudo
|
||||
|
||||
# Install Firefox
|
||||
run mkdir /application
|
||||
run cd /application; wget -O - \
|
||||
http://ftp.mozilla.org/pub/mozilla.org/firefox/releases/25.0/linux-x86_64/en-US/firefox-25.0.tar.bz2 | tar jx
|
||||
|
||||
# create sysadmin account
|
||||
run useradd -m -d /data -p saIVpsc0EVTwA sysadmin
|
||||
run sed -Ei 's/sudo:x:27:/sudo:x:27:sysadmin/' /etc/group
|
||||
run sed -Ei 's/(\%sudo\s+ALL=\(ALL\:ALL\) )ALL/\1 NOPASSWD:ALL/' /etc/sudoers
|
||||
|
||||
# Autorun firefox. -no-remote is necessary to create a new container, as firefox
|
||||
# appears to communicate with itself through X11.
|
||||
cmd ["/bin/sh", "-c", "/usr/bin/sudo -u sysadmin -H -E /application/firefox/firefox -no-remote"]
|
||||
@@ -1,27 +0,0 @@
|
||||
#
|
||||
# This Dockerfile will create an image that allows to generate upstart and
|
||||
# systemd scripts (more to come)
|
||||
#
|
||||
# docker-version 0.6.2
|
||||
#
|
||||
|
||||
FROM ubuntu:12.10
|
||||
MAINTAINER Guillaume J. Charmes <guillaume@dotcloud.com>
|
||||
|
||||
RUN apt-get update && apt-get install -y wget git mercurial
|
||||
|
||||
# Install Go
|
||||
RUN wget --no-check-certificate https://go.googlecode.com/files/go1.1.2.linux-amd64.tar.gz -O go-1.1.2.tar.gz
|
||||
RUN tar -xzvf go-1.1.2.tar.gz && mv /go /goroot
|
||||
RUN mkdir /go
|
||||
|
||||
ENV GOROOT /goroot
|
||||
ENV GOPATH /go
|
||||
ENV PATH $GOROOT/bin:$PATH
|
||||
|
||||
RUN go get github.com/dotcloud/docker && cd /go/src/github.com/dotcloud/docker && git checkout v0.6.3
|
||||
ADD manager.go /manager/
|
||||
RUN cd /manager && go build -o /usr/bin/manager
|
||||
|
||||
ENTRYPOINT ["/usr/bin/manager"]
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
FROM busybox
|
||||
MAINTAINER Guillaume J. Charmes <guillaume@dotcloud.com>
|
||||
ADD manager /usr/bin/
|
||||
ENTRYPOINT ["/usr/bin/manager"]
|
||||
@@ -1,130 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker"
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var templates = map[string]string{
|
||||
|
||||
"upstart": `description "{{.description}}"
|
||||
author "{{.author}}"
|
||||
start on filesystem and started lxc-net and started docker
|
||||
stop on runlevel [!2345]
|
||||
respawn
|
||||
exec /home/vagrant/goroot/bin/docker start -a {{.container_id}}
|
||||
`,
|
||||
|
||||
"systemd": `[Unit]
|
||||
Description={{.description}}
|
||||
Author={{.author}}
|
||||
After=docker.service
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
ExecStart=/usr/bin/docker start -a {{.container_id}}
|
||||
ExecStop=/usr/bin/docker stop -t 2 {{.container_id}}
|
||||
|
||||
[Install]
|
||||
WantedBy=local.target
|
||||
`,
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Parse command line for custom options
|
||||
kind := flag.String("t", "upstart", "Type of manager requested")
|
||||
author := flag.String("a", "<none>", "Author of the image")
|
||||
description := flag.String("d", "<none>", "Description of the image")
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, "\nUsage: manager <container id>\n\n")
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
flag.Parse()
|
||||
|
||||
// We require at least the container ID
|
||||
if flag.NArg() != 1 {
|
||||
println(flag.NArg())
|
||||
flag.Usage()
|
||||
return
|
||||
}
|
||||
|
||||
// Check that the requested process manager is supported
|
||||
if _, exists := templates[*kind]; !exists {
|
||||
panic("Unkown script template")
|
||||
}
|
||||
|
||||
// Load the requested template
|
||||
tpl, err := template.New("processManager").Parse(templates[*kind])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Create stdout/stderr buffers
|
||||
bufOut := bytes.NewBuffer(nil)
|
||||
bufErr := bytes.NewBuffer(nil)
|
||||
|
||||
// Instanciate the Docker CLI
|
||||
cli := docker.NewDockerCli(nil, bufOut, bufErr, "unix", "/var/run/docker.sock")
|
||||
// Retrieve the container info
|
||||
if err := cli.CmdInspect(flag.Arg(0)); err != nil {
|
||||
// As of docker v0.6.3, CmdInspect always returns nil
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// If there is nothing in the error buffer, then the Docker daemon is there and the container has been found
|
||||
if bufErr.Len() == 0 {
|
||||
// Unmarshall the resulting container data
|
||||
c := []*docker.Container{{}}
|
||||
if err := json.Unmarshal(bufOut.Bytes(), &c); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Reset the buffers
|
||||
bufOut.Reset()
|
||||
bufErr.Reset()
|
||||
// Retrieve the info of the linked image
|
||||
if err := cli.CmdInspect(c[0].Image); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// If there is nothing in the error buffer, then the image has been found.
|
||||
if bufErr.Len() == 0 {
|
||||
// Unmarshall the resulting image data
|
||||
img := []*docker.Image{{}}
|
||||
if err := json.Unmarshal(bufOut.Bytes(), &img); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// If no author has been set, use the one from the image
|
||||
if *author == "<none>" && img[0].Author != "" {
|
||||
*author = strings.Replace(img[0].Author, "\"", "", -1)
|
||||
}
|
||||
// If no description has been set, use the comment from the image
|
||||
if *description == "<none>" && img[0].Comment != "" {
|
||||
*description = strings.Replace(img[0].Comment, "\"", "", -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Old version: Wrtie the resulting script to file
|
||||
// f, err := os.OpenFile(kind, os.O_CREATE|os.O_WRONLY, 0755)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// defer f.Close()
|
||||
|
||||
// Create a map with needed data
|
||||
data := map[string]string{
|
||||
"author": *author,
|
||||
"description": *description,
|
||||
"container_id": flag.Arg(0),
|
||||
}
|
||||
|
||||
// Process the template and output it on Stdout
|
||||
if err := tpl.Execute(os.Stdout, data); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
usage() {
|
||||
echo >&2 "usage: $0 [-a author] [-d description] container [manager]"
|
||||
echo >&2 " ie: $0 -a 'John Smith' 4ec9612a37cd systemd"
|
||||
echo >&2 " ie: $0 -d 'Super Cool System' 4ec9612a37cd # defaults to upstart"
|
||||
exit 1
|
||||
}
|
||||
|
||||
auth='<none>'
|
||||
desc='<none>'
|
||||
have_auth=
|
||||
have_desc=
|
||||
while getopts a:d: opt; do
|
||||
case "$opt" in
|
||||
a)
|
||||
auth="$OPTARG"
|
||||
have_auth=1
|
||||
;;
|
||||
d)
|
||||
desc="$OPTARG"
|
||||
have_desc=1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $(($OPTIND - 1))
|
||||
|
||||
[ $# -ge 1 -a $# -le 2 ] || usage
|
||||
|
||||
cid="$1"
|
||||
script="${2:-upstart}"
|
||||
if [ ! -e "manager/$script" ]; then
|
||||
echo >&2 "Error: manager type '$script' is unknown (PRs always welcome!)."
|
||||
echo >&2 'The currently supported types are:'
|
||||
echo >&2 " $(cd manager && echo *)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# TODO https://github.com/dotcloud/docker/issues/734 (docker inspect formatting)
|
||||
#if command -v docker > /dev/null 2>&1; then
|
||||
# image="$(docker inspect -f '{{.Image}}' "$cid")"
|
||||
# if [ "$image" ]; then
|
||||
# if [ -z "$have_auth" ]; then
|
||||
# auth="$(docker inspect -f '{{.Author}}' "$image")"
|
||||
# fi
|
||||
# if [ -z "$have_desc" ]; then
|
||||
# desc="$(docker inspect -f '{{.Comment}}' "$image")"
|
||||
# fi
|
||||
# fi
|
||||
#fi
|
||||
|
||||
exec "manager/$script" "$cid" "$auth" "$desc"
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
cid="$1"
|
||||
auth="$2"
|
||||
desc="$3"
|
||||
|
||||
cat <<-EOF
|
||||
[Unit]
|
||||
Description=$desc
|
||||
Author=$auth
|
||||
After=docker.service
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/docker start -a $cid
|
||||
ExecStop=/usr/bin/docker stop -t 2 $cid
|
||||
|
||||
[Install]
|
||||
WantedBy=local.target
|
||||
EOF
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
cid="$1"
|
||||
auth="$2"
|
||||
desc="$3"
|
||||
|
||||
cat <<-EOF
|
||||
description "$(echo "$desc" | sed 's/"/\\"/g')"
|
||||
author "$(echo "$auth" | sed 's/"/\\"/g')"
|
||||
start on filesystem and started lxc-net and started docker
|
||||
stop on runlevel [!2345]
|
||||
respawn
|
||||
exec /usr/bin/docker start -a "$cid"
|
||||
EOF
|
||||
@@ -1,13 +0,0 @@
|
||||
# /etc/conf.d/docker: config file for /etc/init.d/docker
|
||||
|
||||
# where the docker daemon output gets piped
|
||||
#DOCKER_LOGFILE="/var/log/docker.log"
|
||||
|
||||
# where docker's pid get stored
|
||||
#DOCKER_PIDFILE="/run/docker.pid"
|
||||
|
||||
# where the docker daemon itself is run from
|
||||
#DOCKER_BINARY="/usr/bin/docker"
|
||||
|
||||
# any other random options you want to pass to docker
|
||||
DOCKER_OPTS=""
|
||||
@@ -1,31 +0,0 @@
|
||||
#!/sbin/runscript
|
||||
# Copyright 1999-2013 Gentoo Foundation
|
||||
# Distributed under the terms of the GNU General Public License v2
|
||||
# $Header: $
|
||||
|
||||
DOCKER_LOGFILE=${DOCKER_LOGFILE:-/var/log/${SVCNAME}.log}
|
||||
DOCKER_PIDFILE=${DOCKER_PIDFILE:-/run/${SVCNAME}.pid}
|
||||
DOCKER_BINARY=${DOCKER_BINARY:-/usr/bin/docker}
|
||||
DOCKER_OPTS=${DOCKER_OPTS:-}
|
||||
|
||||
start() {
|
||||
checkpath -f -m 0644 -o root:docker "$DOCKER_LOGFILE"
|
||||
|
||||
ebegin "Starting docker daemon"
|
||||
start-stop-daemon --start --background \
|
||||
--exec "$DOCKER_BINARY" \
|
||||
--pidfile "$DOCKER_PIDFILE" \
|
||||
--stdout "$DOCKER_LOGFILE" \
|
||||
--stderr "$DOCKER_LOGFILE" \
|
||||
-- -d -p "$DOCKER_PIDFILE" \
|
||||
$DOCKER_OPTS
|
||||
eend $?
|
||||
}
|
||||
|
||||
stop() {
|
||||
ebegin "Stopping docker daemon"
|
||||
start-stop-daemon --stop \
|
||||
--exec "$DOCKER_BINARY" \
|
||||
--pidfile "$DOCKER_PIDFILE"
|
||||
eend $?
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
[Unit]
|
||||
Description=Docker Application Container Engine
|
||||
Documentation=http://docs.docker.io
|
||||
Requires=network.target
|
||||
After=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStartPre=/bin/mount --make-rprivate /
|
||||
ExecStart=/usr/bin/docker -d
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,96 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: docker
|
||||
# Required-Start: $syslog $remote_fs
|
||||
# Required-Stop: $syslog $remote_fs
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Create lightweight, portable, self-sufficient containers.
|
||||
# Description:
|
||||
# Docker is an open-source project to easily create lightweight, portable,
|
||||
# self-sufficient containers from any application. The same container that a
|
||||
# developer builds and tests on a laptop can run at scale, in production, on
|
||||
# VMs, bare metal, OpenStack clusters, public clouds and more.
|
||||
### END INIT INFO
|
||||
|
||||
BASE=$(basename $0)
|
||||
|
||||
DOCKER=/usr/bin/$BASE
|
||||
DOCKER_PIDFILE=/var/run/$BASE.pid
|
||||
DOCKER_OPTS=
|
||||
|
||||
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
|
||||
|
||||
# Get lsb functions
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
if [ -f /etc/default/$BASE ]; then
|
||||
. /etc/default/$BASE
|
||||
fi
|
||||
|
||||
# see also init_is_upstart in /lib/lsb/init-functions (which isn't available in Ubuntu 12.04, or we'd use it)
|
||||
if [ -x /sbin/initctl ] && /sbin/initctl version 2>/dev/null | /bin/grep -q upstart; then
|
||||
log_failure_msg "Docker is managed via upstart, try using service $BASE $1"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check docker is present
|
||||
if [ ! -x $DOCKER ]; then
|
||||
log_failure_msg "$DOCKER not present or not executable"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
fail_unless_root() {
|
||||
if [ "$(id -u)" != '0' ]; then
|
||||
log_failure_msg "Docker must be run as root"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
fail_unless_root
|
||||
log_begin_msg "Starting Docker: $BASE"
|
||||
mount | grep cgroup >/dev/null || mount -t cgroup none /sys/fs/cgroup 2>/dev/null
|
||||
start-stop-daemon --start --background \
|
||||
--exec "$DOCKER" \
|
||||
--pidfile "$DOCKER_PIDFILE" \
|
||||
-- -d -p "$DOCKER_PIDFILE" \
|
||||
$DOCKER_OPTS
|
||||
log_end_msg $?
|
||||
;;
|
||||
|
||||
stop)
|
||||
fail_unless_root
|
||||
log_begin_msg "Stopping Docker: $BASE"
|
||||
start-stop-daemon --stop \
|
||||
--pidfile "$DOCKER_PIDFILE"
|
||||
log_end_msg $?
|
||||
;;
|
||||
|
||||
restart)
|
||||
fail_unless_root
|
||||
docker_pid=`cat "$DOCKER_PIDFILE" 2>/dev/null`
|
||||
[ -n "$docker_pid" ] \
|
||||
&& ps -p $docker_pid > /dev/null 2>&1 \
|
||||
&& $0 stop
|
||||
$0 start
|
||||
;;
|
||||
|
||||
force-reload)
|
||||
fail_unless_root
|
||||
$0 restart
|
||||
;;
|
||||
|
||||
status)
|
||||
status_of_proc -p "$DOCKER_PIDFILE" "$DOCKER" docker
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart|status}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
@@ -1,15 +0,0 @@
|
||||
description "Docker daemon"
|
||||
|
||||
start on filesystem and started lxc-net
|
||||
stop on runlevel [!2345]
|
||||
|
||||
respawn
|
||||
|
||||
script
|
||||
DOCKER=/usr/bin/$UPSTART_JOB
|
||||
DOCKER_OPTS=
|
||||
if [ -f /etc/default/$UPSTART_JOB ]; then
|
||||
. /etc/default/$UPSTART_JOB
|
||||
fi
|
||||
"$DOCKER" -d $DOCKER_OPTS
|
||||
end script
|
||||
@@ -62,6 +62,6 @@ mknod -m 666 ${DEV}/full c 1 7
|
||||
mknod -m 600 ${DEV}/initctl p
|
||||
mknod -m 666 ${DEV}/ptmx c 5 2
|
||||
|
||||
tar --numeric-owner -C $ROOTFS -c . | docker import - archlinux
|
||||
tar -C $ROOTFS -c . | docker import - archlinux
|
||||
docker run -i -t archlinux echo Success.
|
||||
rm -rf $ROOTFS
|
||||
|
||||
@@ -35,5 +35,5 @@ do
|
||||
cp -a /dev/$X dev
|
||||
done
|
||||
|
||||
tar --numeric-owner -cf- . | docker import - busybox
|
||||
tar -cf- . | docker import - busybox
|
||||
docker run -i -u root busybox /bin/echo Success.
|
||||
|
||||
Executable
+83
@@ -0,0 +1,83 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# these should match the names found at http://www.debian.org/releases/
|
||||
stableSuite='wheezy'
|
||||
testingSuite='jessie'
|
||||
unstableSuite='sid'
|
||||
|
||||
variant='minbase'
|
||||
include='iproute,iputils-ping'
|
||||
|
||||
repo="$1"
|
||||
suite="$2"
|
||||
mirror="${3:-}" # stick to the default debootstrap mirror if one is not provided
|
||||
|
||||
if [ ! "$repo" ] || [ ! "$suite" ]; then
|
||||
echo >&2 "usage: $0 repo suite [mirror]"
|
||||
echo >&2
|
||||
echo >&2 " ie: $0 tianon/debian squeeze"
|
||||
echo >&2 " $0 tianon/debian squeeze http://ftp.uk.debian.org/debian/"
|
||||
echo >&2
|
||||
echo >&2 " ie: $0 tianon/ubuntu precise"
|
||||
echo >&2 " $0 tianon/ubuntu precise http://mirrors.melbourne.co.uk/ubuntu/"
|
||||
echo >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
target="/tmp/docker-rootfs-debian-$suite-$$-$RANDOM"
|
||||
|
||||
cd "$(dirname "$(readlink -f "$BASH_SOURCE")")"
|
||||
returnTo="$(pwd -P)"
|
||||
|
||||
set -x
|
||||
|
||||
# bootstrap
|
||||
mkdir -p "$target"
|
||||
sudo debootstrap --verbose --variant="$variant" --include="$include" "$suite" "$target" "$mirror"
|
||||
|
||||
cd "$target"
|
||||
|
||||
# prevent init scripts from running during install/update
|
||||
# policy-rc.d (for most scripts)
|
||||
echo $'#!/bin/sh\nexit 101' | sudo tee usr/sbin/policy-rc.d > /dev/null
|
||||
sudo chmod +x usr/sbin/policy-rc.d
|
||||
# initctl (for some pesky upstart scripts)
|
||||
sudo chroot . dpkg-divert --local --rename --add /sbin/initctl
|
||||
sudo ln -sf /bin/true sbin/initctl
|
||||
# see https://github.com/dotcloud/docker/issues/446#issuecomment-16953173
|
||||
|
||||
# shrink the image, since apt makes us fat (wheezy: ~157.5MB vs ~120MB)
|
||||
sudo chroot . apt-get clean
|
||||
|
||||
# while we're at it, apt is unnecessarily slow inside containers
|
||||
# this forces dpkg not to call sync() after package extraction and speeds up install
|
||||
# the benefit is huge on spinning disks, and the penalty is nonexistent on SSD or decent server virtualization
|
||||
echo 'force-unsafe-io' | sudo tee etc/dpkg/dpkg.cfg.d/02apt-speedup > /dev/null
|
||||
# we want to effectively run "apt-get clean" after every install to keep images small
|
||||
echo 'DPkg::Post-Invoke {"/bin/rm -f /var/cache/apt/archives/*.deb || true";};' | sudo tee etc/apt/apt.conf.d/no-cache > /dev/null
|
||||
|
||||
# helpful undo lines for each the above tweaks (for lack of a better home to keep track of them):
|
||||
# rm /usr/sbin/policy-rc.d
|
||||
# rm /sbin/initctl; dpkg-divert --rename --remove /sbin/initctl
|
||||
# rm /etc/dpkg/dpkg.cfg.d/02apt-speedup
|
||||
# rm /etc/apt/apt.conf.d/no-cache
|
||||
|
||||
# create the image (and tag $repo:$suite)
|
||||
sudo tar -c . | docker import - $repo $suite
|
||||
|
||||
# test the image
|
||||
docker run -i -t $repo:$suite echo success
|
||||
|
||||
if [ "$suite" = "$stableSuite" -o "$suite" = 'stable' ]; then
|
||||
# tag latest
|
||||
docker tag $repo:$suite $repo latest
|
||||
|
||||
# tag the specific debian release version
|
||||
ver=$(docker run $repo:$suite cat /etc/debian_version)
|
||||
docker tag $repo:$suite $repo $ver
|
||||
fi
|
||||
|
||||
# cleanup
|
||||
cd "$returnTo"
|
||||
sudo rm -rf "$target"
|
||||
@@ -1,233 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
variant='minbase'
|
||||
include='iproute,iputils-ping'
|
||||
arch='amd64' # intentionally undocumented for now
|
||||
skipDetection=
|
||||
strictDebootstrap=
|
||||
justTar=
|
||||
|
||||
usage() {
|
||||
echo >&2
|
||||
|
||||
echo >&2 "usage: $0 [options] repo suite [mirror]"
|
||||
|
||||
echo >&2
|
||||
echo >&2 'options: (not recommended)'
|
||||
echo >&2 " -p set an http_proxy for debootstrap"
|
||||
echo >&2 " -v $variant # change default debootstrap variant"
|
||||
echo >&2 " -i $include # change default package includes"
|
||||
echo >&2 " -d # strict debootstrap (do not apply any docker-specific tweaks)"
|
||||
echo >&2 " -s # skip version detection and tagging (ie, precise also tagged as 12.04)"
|
||||
echo >&2 " # note that this will also skip adding universe and/or security/updates to sources.list"
|
||||
echo >&2 " -t # just create a tarball, especially for dockerbrew (uses repo as tarball name)"
|
||||
|
||||
echo >&2
|
||||
echo >&2 " ie: $0 username/debian squeeze"
|
||||
echo >&2 " $0 username/debian squeeze http://ftp.uk.debian.org/debian/"
|
||||
|
||||
echo >&2
|
||||
echo >&2 " ie: $0 username/ubuntu precise"
|
||||
echo >&2 " $0 username/ubuntu precise http://mirrors.melbourne.co.uk/ubuntu/"
|
||||
|
||||
echo >&2
|
||||
echo >&2 " ie: $0 -t precise.tar.bz2 precise"
|
||||
echo >&2 " $0 -t wheezy.tgz wheezy"
|
||||
echo >&2 " $0 -t wheezy-uk.tar.xz wheezy http://ftp.uk.debian.org/debian/"
|
||||
|
||||
echo >&2
|
||||
}
|
||||
|
||||
# these should match the names found at http://www.debian.org/releases/
|
||||
debianStable=wheezy
|
||||
debianUnstable=sid
|
||||
# this should match the name found at http://releases.ubuntu.com/
|
||||
ubuntuLatestLTS=precise
|
||||
|
||||
while getopts v:i:a:p:dst name; do
|
||||
case "$name" in
|
||||
p)
|
||||
http_proxy="$OPTARG"
|
||||
;;
|
||||
v)
|
||||
variant="$OPTARG"
|
||||
;;
|
||||
i)
|
||||
include="$OPTARG"
|
||||
;;
|
||||
a)
|
||||
arch="$OPTARG"
|
||||
;;
|
||||
d)
|
||||
strictDebootstrap=1
|
||||
;;
|
||||
s)
|
||||
skipDetection=1
|
||||
;;
|
||||
t)
|
||||
justTar=1
|
||||
;;
|
||||
?)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $(($OPTIND - 1))
|
||||
|
||||
repo="$1"
|
||||
suite="$2"
|
||||
mirror="${3:-}" # stick to the default debootstrap mirror if one is not provided
|
||||
|
||||
if [ ! "$repo" ] || [ ! "$suite" ]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# some rudimentary detection for whether we need to "sudo" our docker calls
|
||||
docker=''
|
||||
if docker version > /dev/null 2>&1; then
|
||||
docker='docker'
|
||||
elif sudo docker version > /dev/null 2>&1; then
|
||||
docker='sudo docker'
|
||||
elif command -v docker > /dev/null 2>&1; then
|
||||
docker='docker'
|
||||
else
|
||||
echo >&2 "warning: either docker isn't installed, or your current user cannot run it;"
|
||||
echo >&2 " this script is not likely to work as expected"
|
||||
sleep 3
|
||||
docker='docker' # give us a command-not-found later
|
||||
fi
|
||||
|
||||
# make sure we have an absolute path to our final tarball so we can still reference it properly after we change directory
|
||||
if [ "$justTar" ]; then
|
||||
if [ ! -d "$(dirname "$repo")" ]; then
|
||||
echo >&2 "error: $(dirname "$repo") does not exist"
|
||||
exit 1
|
||||
fi
|
||||
repo="$(cd "$(dirname "$repo")" && pwd -P)/$(basename "$repo")"
|
||||
fi
|
||||
|
||||
# will be filled in later, if [ -z "$skipDetection" ]
|
||||
lsbDist=''
|
||||
|
||||
target="/tmp/docker-rootfs-debootstrap-$suite-$$-$RANDOM"
|
||||
|
||||
cd "$(dirname "$(readlink -f "$BASH_SOURCE")")"
|
||||
returnTo="$(pwd -P)"
|
||||
|
||||
set -x
|
||||
|
||||
# bootstrap
|
||||
mkdir -p "$target"
|
||||
sudo http_proxy=$http_proxy debootstrap --verbose --variant="$variant" --include="$include" --arch="$arch" "$suite" "$target" "$mirror"
|
||||
|
||||
cd "$target"
|
||||
|
||||
if [ -z "$strictDebootstrap" ]; then
|
||||
# prevent init scripts from running during install/update
|
||||
# policy-rc.d (for most scripts)
|
||||
echo $'#!/bin/sh\nexit 101' | sudo tee usr/sbin/policy-rc.d > /dev/null
|
||||
sudo chmod +x usr/sbin/policy-rc.d
|
||||
# initctl (for some pesky upstart scripts)
|
||||
sudo chroot . dpkg-divert --local --rename --add /sbin/initctl
|
||||
sudo ln -sf /bin/true sbin/initctl
|
||||
# see https://github.com/dotcloud/docker/issues/446#issuecomment-16953173
|
||||
|
||||
# shrink the image, since apt makes us fat (wheezy: ~157.5MB vs ~120MB)
|
||||
sudo chroot . apt-get clean
|
||||
|
||||
# while we're at it, apt is unnecessarily slow inside containers
|
||||
# this forces dpkg not to call sync() after package extraction and speeds up install
|
||||
# the benefit is huge on spinning disks, and the penalty is nonexistent on SSD or decent server virtualization
|
||||
echo 'force-unsafe-io' | sudo tee etc/dpkg/dpkg.cfg.d/02apt-speedup > /dev/null
|
||||
# we want to effectively run "apt-get clean" after every install to keep images small
|
||||
echo 'DPkg::Post-Invoke {"/bin/rm -f /var/cache/apt/archives/*.deb || true";};' | sudo tee etc/apt/apt.conf.d/no-cache > /dev/null
|
||||
|
||||
# helpful undo lines for each the above tweaks (for lack of a better home to keep track of them):
|
||||
# rm /usr/sbin/policy-rc.d
|
||||
# rm /sbin/initctl; dpkg-divert --rename --remove /sbin/initctl
|
||||
# rm /etc/dpkg/dpkg.cfg.d/02apt-speedup
|
||||
# rm /etc/apt/apt.conf.d/no-cache
|
||||
|
||||
if [ -z "$skipDetection" ]; then
|
||||
# see also rudimentary platform detection in hack/install.sh
|
||||
lsbDist=''
|
||||
if [ -r etc/lsb-release ]; then
|
||||
lsbDist="$(. etc/lsb-release && echo "$DISTRIB_ID")"
|
||||
fi
|
||||
if [ -z "$lsbDist" ] && [ -r etc/debian_version ]; then
|
||||
lsbDist='Debian'
|
||||
fi
|
||||
|
||||
case "$lsbDist" in
|
||||
Debian)
|
||||
# add the updates and security repositories
|
||||
if [ "$suite" != "$debianUnstable" -a "$suite" != 'unstable' ]; then
|
||||
# ${suite}-updates only applies to non-unstable
|
||||
sudo sed -i "p; s/ $suite main$/ ${suite}-updates main/" etc/apt/sources.list
|
||||
|
||||
# same for security updates
|
||||
echo "deb http://security.debian.org/ $suite/updates main" | sudo tee -a etc/apt/sources.list > /dev/null
|
||||
fi
|
||||
;;
|
||||
Ubuntu)
|
||||
# add the universe, updates, and security repositories
|
||||
sudo sed -i "
|
||||
s/ $suite main$/ $suite main universe/; p;
|
||||
s/ $suite main/ ${suite}-updates main/; p;
|
||||
s/ $suite-updates main/ ${suite}-security main/
|
||||
" etc/apt/sources.list
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$justTar" ]; then
|
||||
# create the tarball file so it has the right permissions (ie, not root)
|
||||
touch "$repo"
|
||||
|
||||
# fill the tarball
|
||||
sudo tar --numeric-owner -caf "$repo" .
|
||||
else
|
||||
# create the image (and tag $repo:$suite)
|
||||
sudo tar --numeric-owner -c . | $docker import - $repo:$suite
|
||||
|
||||
# test the image
|
||||
$docker run -i -t $repo:$suite echo success
|
||||
|
||||
if [ -z "$skipDetection" ]; then
|
||||
case "$lsbDist" in
|
||||
Debian)
|
||||
if [ "$suite" = "$debianStable" -o "$suite" = 'stable' ] && [ -r etc/debian_version ]; then
|
||||
# tag latest
|
||||
$docker tag $repo:$suite $repo:latest
|
||||
|
||||
if [ -r etc/debian_version ]; then
|
||||
# tag the specific debian release version (which is only reasonable to tag on debian stable)
|
||||
ver=$(cat etc/debian_version)
|
||||
$docker tag $repo:$suite $repo:$ver
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
Ubuntu)
|
||||
if [ "$suite" = "$ubuntuLatestLTS" ]; then
|
||||
# tag latest
|
||||
$docker tag $repo:$suite $repo:latest
|
||||
fi
|
||||
if [ -r etc/lsb-release ]; then
|
||||
lsbRelease="$(. etc/lsb-release && echo "$DISTRIB_RELEASE")"
|
||||
if [ "$lsbRelease" ]; then
|
||||
# tag specific Ubuntu version number, if available (12.04, etc.)
|
||||
$docker tag $repo:$suite $repo:$lsbRelease
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
|
||||
# cleanup
|
||||
cd "$returnTo"
|
||||
sudo rm -rf "$target"
|
||||
@@ -44,6 +44,6 @@ do
|
||||
done
|
||||
|
||||
chmod 0755 $ROOTFS # See #486
|
||||
tar --numeric-owner -cf- . | docker import - docker-ut
|
||||
tar -cf- . | docker import - docker-ut
|
||||
docker run -i -u root docker-ut /bin/echo Success.
|
||||
rm -rf $ROOTFS
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
# Vagrant integration
|
||||
# Vagrant-docker
|
||||
|
||||
Currently there are at least 4 different projects that we are aware of that deals
|
||||
with integration with [Vagrant](http://vagrantup.com/) at different levels. One
|
||||
approach is to use Docker as a [provisioner](http://docs.vagrantup.com/v2/provisioning/index.html)
|
||||
which means you can create containers and pull base images on VMs using Docker's
|
||||
CLI and the other is to use Docker as a [provider](http://docs.vagrantup.com/v2/providers/index.html),
|
||||
meaning you can use Vagrant to control Docker containers.
|
||||
|
||||
|
||||
### Provisioners
|
||||
|
||||
* [Vocker](https://github.com/fgrehm/vocker)
|
||||
* [Ventriloquist](https://github.com/fgrehm/ventriloquist)
|
||||
|
||||
### Providers
|
||||
|
||||
* [docker-provider](https://github.com/fgrehm/docker-provider)
|
||||
* [vagrant-shell](https://github.com/destructuring/vagrant-shell)
|
||||
This is a placeholder for the official vagrant-docker, a plugin for Vagrant (http://vagrantup.com) which exposes Docker as a provider.
|
||||
|
||||
@@ -20,5 +20,3 @@ highlight link dockerfileString String
|
||||
|
||||
syntax match dockerfileComment "\v^\s*#.*$"
|
||||
highlight link dockerfileComment Comment
|
||||
|
||||
set commentstring=#\ %s
|
||||
|
||||
@@ -0,0 +1,850 @@
|
||||
package devmapper
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024
|
||||
DefaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024
|
||||
DefaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024
|
||||
)
|
||||
|
||||
type DevInfo struct {
|
||||
Hash string `json:"-"`
|
||||
DeviceId int `json:"device_id"`
|
||||
Size uint64 `json:"size"`
|
||||
TransactionId uint64 `json:"transaction_id"`
|
||||
Initialized bool `json:"initialized"`
|
||||
devices *DeviceSet `json:"-"`
|
||||
}
|
||||
|
||||
type MetaData struct {
|
||||
Devices map[string]*DevInfo `json:devices`
|
||||
}
|
||||
|
||||
type DeviceSet struct {
|
||||
MetaData
|
||||
sync.Mutex
|
||||
initialized bool
|
||||
root string
|
||||
devicePrefix string
|
||||
TransactionId uint64
|
||||
NewTransactionId uint64
|
||||
nextFreeDevice int
|
||||
activeMounts map[string]int
|
||||
}
|
||||
|
||||
type DiskUsage struct {
|
||||
Used uint64
|
||||
Total uint64
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
PoolName string
|
||||
DataLoopback string
|
||||
MetadataLoopback string
|
||||
Data DiskUsage
|
||||
Metadata DiskUsage
|
||||
}
|
||||
|
||||
func getDevName(name string) string {
|
||||
return "/dev/mapper/" + name
|
||||
}
|
||||
|
||||
func (info *DevInfo) Name() string {
|
||||
hash := info.Hash
|
||||
if hash == "" {
|
||||
hash = "base"
|
||||
}
|
||||
return fmt.Sprintf("%s-%s", info.devices.devicePrefix, hash)
|
||||
}
|
||||
|
||||
func (info *DevInfo) DevName() string {
|
||||
return getDevName(info.Name())
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) loopbackDir() string {
|
||||
return path.Join(devices.root, "devicemapper")
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) jsonFile() string {
|
||||
return path.Join(devices.loopbackDir(), "json")
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) getPoolName() string {
|
||||
return devices.devicePrefix + "-pool"
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) getPoolDevName() string {
|
||||
return getDevName(devices.getPoolName())
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) hasImage(name string) bool {
|
||||
dirname := devices.loopbackDir()
|
||||
filename := path.Join(dirname, name)
|
||||
|
||||
_, err := os.Stat(filename)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// ensureImage creates a sparse file of <size> bytes at the path
|
||||
// <root>/devicemapper/<name>.
|
||||
// If the file already exists, it does nothing.
|
||||
// Either way it returns the full path.
|
||||
func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) {
|
||||
dirname := devices.loopbackDir()
|
||||
filename := path.Join(dirname, name)
|
||||
|
||||
if err := os.MkdirAll(dirname, 0700); err != nil && !os.IsExist(err) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filename); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
utils.Debugf("Creating loopback file %s for device-manage use", filename)
|
||||
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = file.Truncate(size); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return filename, nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) allocateDeviceId() int {
|
||||
// TODO: Add smarter reuse of deleted devices
|
||||
id := devices.nextFreeDevice
|
||||
devices.nextFreeDevice = devices.nextFreeDevice + 1
|
||||
return id
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) allocateTransactionId() uint64 {
|
||||
devices.NewTransactionId = devices.NewTransactionId + 1
|
||||
return devices.NewTransactionId
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) saveMetadata() error {
|
||||
jsonData, err := json.Marshal(devices.MetaData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error encoding metaadata to json: %s", err)
|
||||
}
|
||||
tmpFile, err := ioutil.TempFile(filepath.Dir(devices.jsonFile()), ".json")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating metadata file: %s", err)
|
||||
}
|
||||
|
||||
n, err := tmpFile.Write(jsonData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error writing metadata to %s: %s", tmpFile.Name(), err)
|
||||
}
|
||||
if n < len(jsonData) {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
if err := tmpFile.Sync(); err != nil {
|
||||
return fmt.Errorf("Error syncing metadata file %s: %s", tmpFile.Name(), err)
|
||||
}
|
||||
if err := tmpFile.Close(); err != nil {
|
||||
return fmt.Errorf("Error closing metadata file %s: %s", tmpFile.Name(), err)
|
||||
}
|
||||
if err := os.Rename(tmpFile.Name(), devices.jsonFile()); err != nil {
|
||||
return fmt.Errorf("Error committing metadata file", err)
|
||||
}
|
||||
|
||||
if devices.NewTransactionId != devices.TransactionId {
|
||||
if err = setTransactionId(devices.getPoolDevName(), devices.TransactionId, devices.NewTransactionId); err != nil {
|
||||
return fmt.Errorf("Error setting devmapper transition ID: %s", err)
|
||||
}
|
||||
devices.TransactionId = devices.NewTransactionId
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) registerDevice(id int, hash string, size uint64) (*DevInfo, error) {
|
||||
utils.Debugf("registerDevice(%v, %v)", id, hash)
|
||||
info := &DevInfo{
|
||||
Hash: hash,
|
||||
DeviceId: id,
|
||||
Size: size,
|
||||
TransactionId: devices.allocateTransactionId(),
|
||||
Initialized: false,
|
||||
devices: devices,
|
||||
}
|
||||
|
||||
devices.Devices[hash] = info
|
||||
if err := devices.saveMetadata(); err != nil {
|
||||
// Try to remove unused device
|
||||
delete(devices.Devices, hash)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) activateDeviceIfNeeded(hash string) error {
|
||||
utils.Debugf("activateDeviceIfNeeded(%v)", hash)
|
||||
info := devices.Devices[hash]
|
||||
if info == nil {
|
||||
return fmt.Errorf("Unknown device %s", hash)
|
||||
}
|
||||
|
||||
if devinfo, _ := getInfo(info.Name()); devinfo != nil && devinfo.Exists != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return activateDevice(devices.getPoolDevName(), info.Name(), info.DeviceId, info.Size)
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) createFilesystem(info *DevInfo) error {
|
||||
devname := info.DevName()
|
||||
|
||||
err := exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0,lazy_journal_init=0", devname).Run()
|
||||
if err != nil {
|
||||
err = exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0", devname).Run()
|
||||
}
|
||||
if err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) loadMetaData() error {
|
||||
utils.Debugf("loadMetadata()")
|
||||
defer utils.Debugf("loadMetadata END")
|
||||
_, _, _, params, err := getStatus(devices.getPoolName())
|
||||
if err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := fmt.Sscanf(params, "%d", &devices.TransactionId); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
devices.NewTransactionId = devices.TransactionId
|
||||
|
||||
jsonData, err := ioutil.ReadFile(devices.jsonFile())
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
devices.MetaData.Devices = make(map[string]*DevInfo)
|
||||
if jsonData != nil {
|
||||
if err := json.Unmarshal(jsonData, &devices.MetaData); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for hash, d := range devices.Devices {
|
||||
d.Hash = hash
|
||||
d.devices = devices
|
||||
|
||||
if d.DeviceId >= devices.nextFreeDevice {
|
||||
devices.nextFreeDevice = d.DeviceId + 1
|
||||
}
|
||||
|
||||
// If the transaction id is larger than the actual one we lost the device due to some crash
|
||||
if d.TransactionId > devices.TransactionId {
|
||||
utils.Debugf("Removing lost device %s with id %d", hash, d.TransactionId)
|
||||
delete(devices.Devices, hash)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) setupBaseImage() error {
|
||||
oldInfo := devices.Devices[""]
|
||||
if oldInfo != nil && oldInfo.Initialized {
|
||||
return nil
|
||||
}
|
||||
|
||||
if oldInfo != nil && !oldInfo.Initialized {
|
||||
utils.Debugf("Removing uninitialized base image")
|
||||
if err := devices.removeDevice(""); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
utils.Debugf("Initializing base device-manager snapshot")
|
||||
|
||||
id := devices.allocateDeviceId()
|
||||
|
||||
// Create initial device
|
||||
if err := createDevice(devices.getPoolDevName(), id); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
utils.Debugf("Registering base device (id %v) with FS size %v", id, DefaultBaseFsSize)
|
||||
info, err := devices.registerDevice(id, "", DefaultBaseFsSize)
|
||||
if err != nil {
|
||||
_ = deleteDevice(devices.getPoolDevName(), id)
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
utils.Debugf("Creating filesystem on base device-manager snapshot")
|
||||
|
||||
if err = devices.activateDeviceIfNeeded(""); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := devices.createFilesystem(info); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
info.Initialized = true
|
||||
if err = devices.saveMetadata(); err != nil {
|
||||
info.Initialized = false
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setCloseOnExec(name string) {
|
||||
fileInfos, _ := ioutil.ReadDir("/proc/self/fd")
|
||||
if fileInfos != nil {
|
||||
for _, i := range fileInfos {
|
||||
link, _ := os.Readlink(filepath.Join("/proc/self/fd", i.Name()))
|
||||
if link == name {
|
||||
fd, err := strconv.Atoi(i.Name())
|
||||
if err == nil {
|
||||
syscall.CloseOnExec(fd)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) log(level int, file string, line int, dmError int, message string) {
|
||||
if level >= 7 {
|
||||
return // Ignore _LOG_DEBUG
|
||||
}
|
||||
|
||||
utils.Debugf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message)
|
||||
}
|
||||
|
||||
func major(device uint64) uint64 {
|
||||
return (device >> 8) & 0xfff
|
||||
}
|
||||
|
||||
func minor(device uint64) uint64 {
|
||||
return (device & 0xff) | ((device >> 12) & 0xfff00)
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) initDevmapper() error {
|
||||
logInit(devices)
|
||||
|
||||
// Make sure the sparse images exist in <root>/devicemapper/data and
|
||||
// <root>/devicemapper/metadata
|
||||
|
||||
createdLoopback := !devices.hasImage("data") || !devices.hasImage("metadata")
|
||||
data, err := devices.ensureImage("data", DefaultDataLoopbackSize)
|
||||
if err != nil {
|
||||
utils.Debugf("Error device ensureImage (data): %s\n", err)
|
||||
return err
|
||||
}
|
||||
metadata, err := devices.ensureImage("metadata", DefaultMetaDataLoopbackSize)
|
||||
if err != nil {
|
||||
utils.Debugf("Error device ensureImage (metadata): %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the device prefix from the device id and inode of the docker root dir
|
||||
|
||||
st, err := os.Stat(devices.root)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error looking up dir %s: %s", devices.root, err)
|
||||
}
|
||||
sysSt := st.Sys().(*syscall.Stat_t)
|
||||
// "reg-" stands for "regular file".
|
||||
// In the future we might use "dev-" for "device file", etc.
|
||||
// docker-maj,min[-inode] stands for:
|
||||
// - Managed by docker
|
||||
// - The target of this device is at major <maj> and minor <min>
|
||||
// - If <inode> is defined, use that file inside the device as a loopback image. Otherwise use the device itself.
|
||||
devices.devicePrefix = fmt.Sprintf("docker-%d:%d-%d", major(sysSt.Dev), minor(sysSt.Dev), sysSt.Ino)
|
||||
utils.Debugf("Generated prefix: %s", devices.devicePrefix)
|
||||
|
||||
// Check for the existence of the device <prefix>-pool
|
||||
utils.Debugf("Checking for existence of the pool '%s'", devices.getPoolName())
|
||||
info, err := getInfo(devices.getPoolName())
|
||||
if info == nil {
|
||||
utils.Debugf("Error device getInfo: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// It seems libdevmapper opens this without O_CLOEXEC, and go exec will not close files
|
||||
// that are not Close-on-exec, and lxc-start will die if it inherits any unexpected files,
|
||||
// so we add this badhack to make sure it closes itself
|
||||
setCloseOnExec("/dev/mapper/control")
|
||||
|
||||
// If the pool doesn't exist, create it
|
||||
if info.Exists == 0 {
|
||||
utils.Debugf("Pool doesn't exist. Creating it.")
|
||||
|
||||
dataFile, err := AttachLoopDevice(data)
|
||||
if err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
defer dataFile.Close()
|
||||
|
||||
metadataFile, err := AttachLoopDevice(metadata)
|
||||
if err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
defer metadataFile.Close()
|
||||
|
||||
if err := createPool(devices.getPoolName(), dataFile, metadataFile); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't just create the data or metadata image, we need to
|
||||
// load the metadata from the existing file.
|
||||
if !createdLoopback {
|
||||
if err = devices.loadMetaData(); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Setup the base image
|
||||
if err := devices.setupBaseImage(); err != nil {
|
||||
utils.Debugf("Error device setupBaseImage: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) AddDevice(hash, baseHash string) error {
|
||||
devices.Lock()
|
||||
defer devices.Unlock()
|
||||
|
||||
if err := devices.ensureInit(); err != nil {
|
||||
utils.Debugf("Error init: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if devices.Devices[hash] != nil {
|
||||
return fmt.Errorf("hash %s already exists", hash)
|
||||
}
|
||||
|
||||
baseInfo := devices.Devices[baseHash]
|
||||
if baseInfo == nil {
|
||||
return fmt.Errorf("Error adding device for '%s': can't find device for parent '%s'", hash, baseHash)
|
||||
}
|
||||
|
||||
deviceId := devices.allocateDeviceId()
|
||||
|
||||
if err := devices.createSnapDevice(devices.getPoolDevName(), deviceId, baseInfo.Name(), baseInfo.DeviceId); err != nil {
|
||||
utils.Debugf("Error creating snap device: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := devices.registerDevice(deviceId, hash, baseInfo.Size); err != nil {
|
||||
deleteDevice(devices.getPoolDevName(), deviceId)
|
||||
utils.Debugf("Error registering device: %s\n", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) removeDevice(hash string) error {
|
||||
info := devices.Devices[hash]
|
||||
if info == nil {
|
||||
return fmt.Errorf("hash %s doesn't exists", hash)
|
||||
}
|
||||
|
||||
devinfo, _ := getInfo(info.Name())
|
||||
if devinfo != nil && devinfo.Exists != 0 {
|
||||
if err := removeDevice(info.Name()); err != nil {
|
||||
utils.Debugf("Error removing device: %s\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if info.Initialized {
|
||||
info.Initialized = false
|
||||
if err := devices.saveMetadata(); err != nil {
|
||||
utils.Debugf("Error saving meta data: %s\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := deleteDevice(devices.getPoolDevName(), info.DeviceId); err != nil {
|
||||
utils.Debugf("Error deleting device: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
devices.allocateTransactionId()
|
||||
delete(devices.Devices, info.Hash)
|
||||
|
||||
if err := devices.saveMetadata(); err != nil {
|
||||
devices.Devices[info.Hash] = info
|
||||
utils.Debugf("Error saving meta data: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) RemoveDevice(hash string) error {
|
||||
devices.Lock()
|
||||
defer devices.Unlock()
|
||||
|
||||
if err := devices.ensureInit(); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return devices.removeDevice(hash)
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) deactivateDevice(hash string) error {
|
||||
utils.Debugf("[devmapper] deactivateDevice(%s)", hash)
|
||||
defer utils.Debugf("[devmapper] deactivateDevice END")
|
||||
var devname string
|
||||
// FIXME: shouldn't we just register the pool into devices?
|
||||
devname, err := devices.byHash(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
devinfo, err := getInfo(devname)
|
||||
if err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
if devinfo.Exists != 0 {
|
||||
if err := removeDevice(devname); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
if err := devices.waitRemove(hash); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// waitRemove blocks until either:
|
||||
// a) the device registered at <device_set_prefix>-<hash> is removed,
|
||||
// or b) the 1 second timeout expires.
|
||||
func (devices *DeviceSet) waitRemove(hash string) error {
|
||||
utils.Debugf("[deviceset %s] waitRemove(%s)", devices.devicePrefix, hash)
|
||||
defer utils.Debugf("[deviceset %s] waitRemove END", devices.devicePrefix, hash)
|
||||
devname, err := devices.byHash(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i := 0
|
||||
for ; i < 1000; i += 1 {
|
||||
devinfo, err := getInfo(devname)
|
||||
if err != nil {
|
||||
// If there is an error we assume the device doesn't exist.
|
||||
// The error might actually be something else, but we can't differentiate.
|
||||
return nil
|
||||
}
|
||||
utils.Debugf("Waiting for removal of %s: exists=%d", devname, devinfo.Exists)
|
||||
if devinfo.Exists == 0 {
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
if i == 1000 {
|
||||
return fmt.Errorf("Timeout while waiting for device %s to be removed", devname)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// waitClose blocks until either:
|
||||
// a) the device registered at <device_set_prefix>-<hash> is closed,
|
||||
// or b) the 1 second timeout expires.
|
||||
func (devices *DeviceSet) waitClose(hash string) error {
|
||||
devname, err := devices.byHash(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i := 0
|
||||
for ; i < 1000; i += 1 {
|
||||
devinfo, err := getInfo(devname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
utils.Debugf("Waiting for unmount of %s: opencount=%d", devname, devinfo.OpenCount)
|
||||
if devinfo.OpenCount == 0 {
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
if i == 1000 {
|
||||
return fmt.Errorf("Timeout while waiting for device %s to close", devname)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// byHash is a hack to allow looking up the deviceset's pool by the hash "pool".
|
||||
// FIXME: it seems probably cleaner to register the pool in devices.Devices,
|
||||
// but I am afraid of arcane implications deep in the devicemapper code,
|
||||
// so this will do.
|
||||
func (devices *DeviceSet) byHash(hash string) (devname string, err error) {
|
||||
if hash == "pool" {
|
||||
return devices.getPoolDevName(), nil
|
||||
}
|
||||
info := devices.Devices[hash]
|
||||
if info == nil {
|
||||
return "", fmt.Errorf("hash %s doesn't exists", hash)
|
||||
}
|
||||
return info.Name(), nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) Shutdown() error {
|
||||
utils.Debugf("[deviceset %s] shutdown()", devices.devicePrefix)
|
||||
defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix)
|
||||
devices.Lock()
|
||||
utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root)
|
||||
defer devices.Unlock()
|
||||
|
||||
if !devices.initialized {
|
||||
return nil
|
||||
}
|
||||
|
||||
for path, count := range devices.activeMounts {
|
||||
for i := count; i > 0; i-- {
|
||||
if err := syscall.Unmount(path, 0); err != nil {
|
||||
utils.Debugf("Shutdown unmounting %s, error: %s\n", path, err)
|
||||
}
|
||||
}
|
||||
delete(devices.activeMounts, path)
|
||||
}
|
||||
|
||||
for _, d := range devices.Devices {
|
||||
if err := devices.waitClose(d.Hash); err != nil {
|
||||
utils.Errorf("Warning: error waiting for device %s to unmount: %s\n", d.Hash, err)
|
||||
}
|
||||
if err := devices.deactivateDevice(d.Hash); err != nil {
|
||||
utils.Debugf("Shutdown deactivate %s , error: %s\n", d.Hash, err)
|
||||
}
|
||||
}
|
||||
|
||||
pool := devices.getPoolDevName()
|
||||
if devinfo, err := getInfo(pool); err == nil && devinfo.Exists != 0 {
|
||||
if err := devices.deactivateDevice("pool"); err != nil {
|
||||
utils.Debugf("Shutdown deactivate %s , error: %s\n", pool, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) MountDevice(hash, path string, readOnly bool) error {
|
||||
devices.Lock()
|
||||
defer devices.Unlock()
|
||||
|
||||
if err := devices.ensureInit(); err != nil {
|
||||
return fmt.Errorf("Error initializing devmapper: %s", err)
|
||||
}
|
||||
|
||||
if err := devices.activateDeviceIfNeeded(hash); err != nil {
|
||||
return fmt.Errorf("Error activating devmapper device for '%s': %s", hash, err)
|
||||
}
|
||||
|
||||
info := devices.Devices[hash]
|
||||
|
||||
var flags uintptr = syscall.MS_MGC_VAL
|
||||
|
||||
if readOnly {
|
||||
flags = flags | syscall.MS_RDONLY
|
||||
}
|
||||
|
||||
err := syscall.Mount(info.DevName(), path, "ext4", flags, "discard")
|
||||
if err != nil && err == syscall.EINVAL {
|
||||
err = syscall.Mount(info.DevName(), path, "ext4", flags, "")
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err)
|
||||
}
|
||||
|
||||
count := devices.activeMounts[path]
|
||||
devices.activeMounts[path] = count + 1
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) UnmountDevice(hash, path string, deactivate bool) error {
|
||||
utils.Debugf("[devmapper] UnmountDevice(hash=%s path=%s)", hash, path)
|
||||
defer utils.Debugf("[devmapper] UnmountDevice END")
|
||||
devices.Lock()
|
||||
defer devices.Unlock()
|
||||
|
||||
utils.Debugf("[devmapper] Unmount(%s)", path)
|
||||
if err := syscall.Unmount(path, 0); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
utils.Debugf("[devmapper] Unmount done")
|
||||
// Wait for the unmount to be effective,
|
||||
// by watching the value of Info.OpenCount for the device
|
||||
if err := devices.waitClose(hash); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count := devices.activeMounts[path]; count > 1 {
|
||||
devices.activeMounts[path] = count - 1
|
||||
} else {
|
||||
delete(devices.activeMounts, path)
|
||||
}
|
||||
|
||||
if deactivate {
|
||||
devices.deactivateDevice(hash)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) HasDevice(hash string) bool {
|
||||
devices.Lock()
|
||||
defer devices.Unlock()
|
||||
|
||||
if err := devices.ensureInit(); err != nil {
|
||||
return false
|
||||
}
|
||||
return devices.Devices[hash] != nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) HasInitializedDevice(hash string) bool {
|
||||
devices.Lock()
|
||||
defer devices.Unlock()
|
||||
|
||||
if err := devices.ensureInit(); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
info := devices.Devices[hash]
|
||||
return info != nil && info.Initialized
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) HasActivatedDevice(hash string) bool {
|
||||
devices.Lock()
|
||||
defer devices.Unlock()
|
||||
|
||||
if err := devices.ensureInit(); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
info := devices.Devices[hash]
|
||||
if info == nil {
|
||||
return false
|
||||
}
|
||||
devinfo, _ := getInfo(info.Name())
|
||||
return devinfo != nil && devinfo.Exists != 0
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) SetInitialized(hash string) error {
|
||||
devices.Lock()
|
||||
defer devices.Unlock()
|
||||
|
||||
if err := devices.ensureInit(); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
info := devices.Devices[hash]
|
||||
if info == nil {
|
||||
return fmt.Errorf("Unknown device %s", hash)
|
||||
}
|
||||
|
||||
info.Initialized = true
|
||||
if err := devices.saveMetadata(); err != nil {
|
||||
info.Initialized = false
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) Status() *Status {
|
||||
devices.Lock()
|
||||
defer devices.Unlock()
|
||||
|
||||
status := &Status{}
|
||||
|
||||
if err := devices.ensureInit(); err != nil {
|
||||
return status
|
||||
}
|
||||
|
||||
status.PoolName = devices.getPoolName()
|
||||
status.DataLoopback = path.Join(devices.loopbackDir(), "data")
|
||||
status.MetadataLoopback = path.Join(devices.loopbackDir(), "metadata")
|
||||
|
||||
_, totalSizeInSectors, _, params, err := getStatus(devices.getPoolName())
|
||||
if err == nil {
|
||||
var transactionId, dataUsed, dataTotal, metadataUsed, metadataTotal uint64
|
||||
if _, err := fmt.Sscanf(params, "%d %d/%d %d/%d", &transactionId, &metadataUsed, &metadataTotal, &dataUsed, &dataTotal); err == nil {
|
||||
// Convert from blocks to bytes
|
||||
blockSizeInSectors := totalSizeInSectors / dataTotal
|
||||
|
||||
status.Data.Used = dataUsed * blockSizeInSectors * 512
|
||||
status.Data.Total = dataTotal * blockSizeInSectors * 512
|
||||
|
||||
// metadata blocks are always 4k
|
||||
status.Metadata.Used = metadataUsed * 4096
|
||||
status.Metadata.Total = metadataTotal * 4096
|
||||
}
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) ensureInit() error {
|
||||
if !devices.initialized {
|
||||
devices.initialized = true
|
||||
if err := devices.initDevmapper(); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewDeviceSet(root string) *DeviceSet {
|
||||
SetDevDir("/dev")
|
||||
|
||||
return &DeviceSet{
|
||||
initialized: false,
|
||||
root: root,
|
||||
MetaData: MetaData{Devices: make(map[string]*DevInfo)},
|
||||
activeMounts: make(map[string]int),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,709 @@
|
||||
package devmapper
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -L. -ldevmapper
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <libdevmapper.h>
|
||||
#include <linux/loop.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/fs.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifndef LOOP_CTL_GET_FREE
|
||||
#define LOOP_CTL_GET_FREE 0x4C82
|
||||
#endif
|
||||
|
||||
// FIXME: this could easily be rewritten in go
|
||||
char* attach_loop_device(const char *filename, int *loop_fd_out)
|
||||
{
|
||||
struct loop_info64 loopinfo = {0};
|
||||
struct stat st;
|
||||
char buf[64];
|
||||
int i, loop_fd, fd, start_index;
|
||||
char* loopname;
|
||||
|
||||
|
||||
*loop_fd_out = -1;
|
||||
|
||||
start_index = 0;
|
||||
fd = open("/dev/loop-control", O_RDONLY);
|
||||
if (fd >= 0) {
|
||||
start_index = ioctl(fd, LOOP_CTL_GET_FREE);
|
||||
close(fd);
|
||||
|
||||
if (start_index < 0)
|
||||
start_index = 0;
|
||||
}
|
||||
|
||||
fd = open(filename, O_RDWR);
|
||||
if (fd < 0) {
|
||||
perror("open");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
loop_fd = -1;
|
||||
for (i = start_index ; loop_fd < 0 ; i++ ) {
|
||||
if (sprintf(buf, "/dev/loop%d", i) < 0) {
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (stat(buf, &st)) {
|
||||
if (!S_ISBLK(st.st_mode)) {
|
||||
fprintf(stderr, "[error] Loopback device %s is not a block device.\n", buf);
|
||||
} else if (errno == ENOENT) {
|
||||
fprintf(stderr, "[error] There are no more loopback device available.\n");
|
||||
} else {
|
||||
fprintf(stderr, "[error] Unkown error trying to stat the loopback device %s (errno: %d).\n", buf, errno);
|
||||
}
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
loop_fd = open(buf, O_RDWR);
|
||||
if (loop_fd < 0 && errno == ENOENT) {
|
||||
fprintf(stderr, "[error] The loopback device %s does not exists.\n", buf);
|
||||
close(fd);
|
||||
return NULL;
|
||||
} else if (loop_fd < 0) {
|
||||
fprintf(stderr, "[error] Unkown error openning the loopback device %s. (errno: %d)\n", buf, errno);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ioctl(loop_fd, LOOP_SET_FD, (void *)(size_t)fd) < 0) {
|
||||
int errsv = errno;
|
||||
close(loop_fd);
|
||||
loop_fd = -1;
|
||||
if (errsv != EBUSY) {
|
||||
close(fd);
|
||||
fprintf(stderr, "cannot set up loopback device %s: %s", buf, strerror(errsv));
|
||||
return NULL;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
|
||||
strncpy((char*)loopinfo.lo_file_name, buf, LO_NAME_SIZE);
|
||||
loopinfo.lo_offset = 0;
|
||||
loopinfo.lo_flags = LO_FLAGS_AUTOCLEAR;
|
||||
|
||||
if (ioctl(loop_fd, LOOP_SET_STATUS64, &loopinfo) < 0) {
|
||||
perror("ioctl LOOP_SET_STATUS64");
|
||||
if (ioctl(loop_fd, LOOP_CLR_FD, 0) < 0) {
|
||||
perror("ioctl LOOP_CLR_FD");
|
||||
}
|
||||
close(loop_fd);
|
||||
fprintf (stderr, "cannot set up loopback device info");
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
loopname = strdup(buf);
|
||||
if (loopname == NULL) {
|
||||
close(loop_fd);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
*loop_fd_out = loop_fd;
|
||||
return (loopname);
|
||||
}
|
||||
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
static int64_t get_block_size(int fd)
|
||||
{
|
||||
uint64_t size;
|
||||
|
||||
if (ioctl(fd, BLKGETSIZE64, &size) == -1)
|
||||
return -1;
|
||||
return ((int64_t)size);
|
||||
}
|
||||
|
||||
extern void DevmapperLogCallback(int level, char *file, int line, int dm_errno_or_class, char *str);
|
||||
|
||||
static void
|
||||
log_cb(int level, const char *file, int line,
|
||||
int dm_errno_or_class, const char *f, ...)
|
||||
{
|
||||
char buffer[256];
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, f);
|
||||
vsnprintf(buffer, 256, f, ap);
|
||||
va_end(ap);
|
||||
|
||||
DevmapperLogCallback(level, (char *)file, line, dm_errno_or_class, buffer);
|
||||
}
|
||||
|
||||
static void
|
||||
log_with_errno_init ()
|
||||
{
|
||||
dm_log_with_errno_init(log_cb);
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type DevmapperLogger interface {
|
||||
log(level int, file string, line int, dmError int, message string)
|
||||
}
|
||||
|
||||
const (
|
||||
DeviceCreate TaskType = iota
|
||||
DeviceReload
|
||||
DeviceRemove
|
||||
DeviceRemoveAll
|
||||
DeviceSuspend
|
||||
DeviceResume
|
||||
DeviceInfo
|
||||
DeviceDeps
|
||||
DeviceRename
|
||||
DeviceVersion
|
||||
DeviceStatus
|
||||
DeviceTable
|
||||
DeviceWaitevent
|
||||
DeviceList
|
||||
DeviceClear
|
||||
DeviceMknodes
|
||||
DeviceListVersions
|
||||
DeviceTargetMsg
|
||||
DeviceSetGeometry
|
||||
)
|
||||
|
||||
const (
|
||||
AddNodeOnResume AddNodeType = iota
|
||||
AddNodeOnCreate
|
||||
)
|
||||
|
||||
var (
|
||||
ErrTaskRun = errors.New("dm_task_run failed")
|
||||
ErrTaskSetName = errors.New("dm_task_set_name failed")
|
||||
ErrTaskSetMessage = errors.New("dm_task_set_message failed")
|
||||
ErrTaskSetAddNode = errors.New("dm_task_set_add_node failed")
|
||||
ErrTaskSetRO = errors.New("dm_task_set_ro failed")
|
||||
ErrTaskAddTarget = errors.New("dm_task_add_target failed")
|
||||
ErrGetDriverVersion = errors.New("dm_task_get_driver_version failed")
|
||||
ErrAttachLoopbackDevice = errors.New("loopback mounting failed")
|
||||
ErrGetBlockSize = errors.New("Can't get block size")
|
||||
ErrUdevWait = errors.New("wait on udev cookie failed")
|
||||
ErrSetDevDir = errors.New("dm_set_dev_dir failed")
|
||||
ErrGetLibraryVersion = errors.New("dm_get_library_version failed")
|
||||
ErrCreateRemoveTask = errors.New("Can't create task of type DeviceRemove")
|
||||
ErrRunRemoveDevice = errors.New("running removeDevice failed")
|
||||
)
|
||||
|
||||
type (
|
||||
Task struct {
|
||||
unmanaged *C.struct_dm_task
|
||||
}
|
||||
Info struct {
|
||||
Exists int
|
||||
Suspended int
|
||||
LiveTable int
|
||||
InactiveTable int
|
||||
OpenCount int32
|
||||
EventNr uint32
|
||||
Major uint32
|
||||
Minor uint32
|
||||
ReadOnly int
|
||||
TargetCount int32
|
||||
}
|
||||
TaskType int
|
||||
AddNodeType int
|
||||
)
|
||||
|
||||
func (t *Task) destroy() {
|
||||
if t != nil {
|
||||
C.dm_task_destroy(t.unmanaged)
|
||||
runtime.SetFinalizer(t, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TaskCreate(tasktype TaskType) *Task {
|
||||
c_task := C.dm_task_create(C.int(tasktype))
|
||||
if c_task == nil {
|
||||
return nil
|
||||
}
|
||||
task := &Task{unmanaged: c_task}
|
||||
runtime.SetFinalizer(task, (*Task).destroy)
|
||||
return task
|
||||
}
|
||||
|
||||
func (t *Task) Run() error {
|
||||
if res := C.dm_task_run(t.unmanaged); res != 1 {
|
||||
return ErrTaskRun
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Task) SetName(name string) error {
|
||||
c_name := C.CString(name)
|
||||
defer free(c_name)
|
||||
|
||||
if res := C.dm_task_set_name(t.unmanaged, c_name); res != 1 {
|
||||
return ErrTaskSetName
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Task) SetMessage(message string) error {
|
||||
c_message := C.CString(message)
|
||||
defer free(c_message)
|
||||
|
||||
if res := C.dm_task_set_message(t.unmanaged, c_message); res != 1 {
|
||||
return ErrTaskSetMessage
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Task) SetSector(sector uint64) error {
|
||||
if res := C.dm_task_set_sector(t.unmanaged, C.uint64_t(sector)); res != 1 {
|
||||
return ErrTaskSetAddNode
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Task) SetCookie(cookie *uint32, flags uint16) error {
|
||||
c_cookie := C.uint32_t(*cookie)
|
||||
if res := C.dm_task_set_cookie(t.unmanaged, &c_cookie, C.uint16_t(flags)); res != 1 {
|
||||
return ErrTaskSetAddNode
|
||||
}
|
||||
*cookie = uint32(c_cookie)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Task) SetAddNode(add_node AddNodeType) error {
|
||||
if res := C.dm_task_set_add_node(t.unmanaged, C.dm_add_node_t(add_node)); res != 1 {
|
||||
return ErrTaskSetAddNode
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Task) SetRo() error {
|
||||
if res := C.dm_task_set_ro(t.unmanaged); res != 1 {
|
||||
return ErrTaskSetRO
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Task) AddTarget(start uint64, size uint64, ttype string, params string) error {
|
||||
c_ttype := C.CString(ttype)
|
||||
defer free(c_ttype)
|
||||
|
||||
c_params := C.CString(params)
|
||||
defer free(c_params)
|
||||
|
||||
if res := C.dm_task_add_target(t.unmanaged, C.uint64_t(start), C.uint64_t(size), c_ttype, c_params); res != 1 {
|
||||
return ErrTaskAddTarget
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Task) GetDriverVersion() (string, error) {
|
||||
buffer := C.CString(string(make([]byte, 128)))
|
||||
defer free(buffer)
|
||||
|
||||
if res := C.dm_task_get_driver_version(t.unmanaged, buffer, 128); res != 1 {
|
||||
return "", ErrGetDriverVersion
|
||||
}
|
||||
return C.GoString(buffer), nil
|
||||
}
|
||||
|
||||
func (t *Task) GetInfo() (*Info, error) {
|
||||
c_info := C.struct_dm_info{}
|
||||
if res := C.dm_task_get_info(t.unmanaged, &c_info); res != 1 {
|
||||
return nil, ErrGetDriverVersion
|
||||
}
|
||||
return &Info{
|
||||
Exists: int(c_info.exists),
|
||||
Suspended: int(c_info.suspended),
|
||||
LiveTable: int(c_info.live_table),
|
||||
InactiveTable: int(c_info.inactive_table),
|
||||
OpenCount: int32(c_info.open_count),
|
||||
EventNr: uint32(c_info.event_nr),
|
||||
Major: uint32(c_info.major),
|
||||
Minor: uint32(c_info.minor),
|
||||
ReadOnly: int(c_info.read_only),
|
||||
TargetCount: int32(c_info.target_count),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *Task) GetNextTarget(next uintptr) (uintptr, uint64, uint64, string, string) {
|
||||
var (
|
||||
c_start, c_length C.uint64_t
|
||||
c_target_type, c_params *C.char
|
||||
)
|
||||
|
||||
nextp := C.dm_get_next_target(t.unmanaged, unsafe.Pointer(next), &c_start, &c_length, &c_target_type, &c_params)
|
||||
return uintptr(nextp), uint64(c_start), uint64(c_length), C.GoString(c_target_type), C.GoString(c_params)
|
||||
}
|
||||
|
||||
func AttachLoopDevice(filename string) (*os.File, error) {
|
||||
c_filename := C.CString(filename)
|
||||
defer free(c_filename)
|
||||
|
||||
var fd C.int
|
||||
res := C.attach_loop_device(c_filename, &fd)
|
||||
if res == nil {
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
C.perror(C.CString(fmt.Sprintf("[debug] Error attach_loop_device(%s, %d)", filename, int(fd))))
|
||||
}
|
||||
return nil, ErrAttachLoopbackDevice
|
||||
}
|
||||
defer free(res)
|
||||
|
||||
return os.NewFile(uintptr(fd), C.GoString(res)), nil
|
||||
}
|
||||
|
||||
func getBlockSize(fd uintptr) int {
|
||||
var size uint64
|
||||
|
||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))); err != 0 {
|
||||
utils.Debugf("Error ioctl: %s", err)
|
||||
return -1
|
||||
}
|
||||
return int(size)
|
||||
}
|
||||
|
||||
func GetBlockDeviceSize(file *os.File) (uint64, error) {
|
||||
if size := C.get_block_size(C.int(file.Fd())); size == -1 {
|
||||
return 0, ErrGetBlockSize
|
||||
} else {
|
||||
return uint64(size), nil
|
||||
}
|
||||
}
|
||||
|
||||
func UdevWait(cookie uint32) error {
|
||||
if res := C.dm_udev_wait(C.uint32_t(cookie)); res != 1 {
|
||||
utils.Debugf("Failed to wait on udev cookie %d", cookie)
|
||||
return ErrUdevWait
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func LogInitVerbose(level int) {
|
||||
C.dm_log_init_verbose(C.int(level))
|
||||
}
|
||||
|
||||
var dmLogger DevmapperLogger = nil
|
||||
|
||||
func logInit(logger DevmapperLogger) {
|
||||
dmLogger = logger
|
||||
C.log_with_errno_init()
|
||||
}
|
||||
|
||||
func SetDevDir(dir string) error {
|
||||
c_dir := C.CString(dir)
|
||||
defer free(c_dir)
|
||||
|
||||
if res := C.dm_set_dev_dir(c_dir); res != 1 {
|
||||
utils.Debugf("Error dm_set_dev_dir")
|
||||
return ErrSetDevDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetLibraryVersion() (string, error) {
|
||||
buffer := C.CString(string(make([]byte, 128)))
|
||||
defer free(buffer)
|
||||
|
||||
if res := C.dm_get_library_version(buffer, 128); res != 1 {
|
||||
return "", ErrGetLibraryVersion
|
||||
}
|
||||
return C.GoString(buffer), nil
|
||||
}
|
||||
|
||||
// Useful helper for cleanup
|
||||
func RemoveDevice(name string) error {
|
||||
task := TaskCreate(DeviceRemove)
|
||||
if task == nil {
|
||||
return ErrCreateRemoveTask
|
||||
}
|
||||
if err := task.SetName(name); err != nil {
|
||||
utils.Debugf("Can't set task name %s", name)
|
||||
return err
|
||||
}
|
||||
if err := task.Run(); err != nil {
|
||||
return ErrRunRemoveDevice
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func free(p *C.char) {
|
||||
C.free(unsafe.Pointer(p))
|
||||
}
|
||||
|
||||
// This is the programmatic example of "dmsetup create"
|
||||
func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error {
|
||||
task, err := createTask(DeviceCreate, poolName)
|
||||
if task == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
size, err := GetBlockDeviceSize(dataFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't get data size")
|
||||
}
|
||||
|
||||
params := metadataFile.Name() + " " + dataFile.Name() + " 128 32768"
|
||||
if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil {
|
||||
return fmt.Errorf("Can't add target")
|
||||
}
|
||||
|
||||
var cookie uint32 = 0
|
||||
if err := task.SetCookie(&cookie, 0); err != nil {
|
||||
return fmt.Errorf("Can't set cookie")
|
||||
}
|
||||
|
||||
if err := task.Run(); err != nil {
|
||||
return fmt.Errorf("Error running DeviceCreate")
|
||||
}
|
||||
|
||||
UdevWait(cookie)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createTask(t TaskType, name string) (*Task, error) {
|
||||
task := TaskCreate(t)
|
||||
if task == nil {
|
||||
return nil, fmt.Errorf("Can't create task of type %d", int(t))
|
||||
}
|
||||
if err := task.SetName(name); err != nil {
|
||||
return nil, fmt.Errorf("Can't set task name %s", name)
|
||||
}
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func getInfo(name string) (*Info, error) {
|
||||
task, err := createTask(DeviceInfo, name)
|
||||
if task == nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := task.Run(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return task.GetInfo()
|
||||
}
|
||||
|
||||
func getStatus(name string) (uint64, uint64, string, string, error) {
|
||||
task, err := createTask(DeviceStatus, name)
|
||||
if task == nil {
|
||||
utils.Debugf("getStatus: Error createTask: %s", err)
|
||||
return 0, 0, "", "", err
|
||||
}
|
||||
if err := task.Run(); err != nil {
|
||||
utils.Debugf("getStatus: Error Run: %s", err)
|
||||
return 0, 0, "", "", err
|
||||
}
|
||||
|
||||
devinfo, err := task.GetInfo()
|
||||
if err != nil {
|
||||
utils.Debugf("getStatus: Error GetInfo: %s", err)
|
||||
return 0, 0, "", "", err
|
||||
}
|
||||
if devinfo.Exists == 0 {
|
||||
utils.Debugf("getStatus: Non existing device %s", name)
|
||||
return 0, 0, "", "", fmt.Errorf("Non existing device %s", name)
|
||||
}
|
||||
|
||||
_, start, length, target_type, params := task.GetNextTarget(0)
|
||||
return start, length, target_type, params, nil
|
||||
}
|
||||
|
||||
func setTransactionId(poolName string, oldId uint64, newId uint64) error {
|
||||
task, err := createTask(DeviceTargetMsg, poolName)
|
||||
if task == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := task.SetSector(0); err != nil {
|
||||
return fmt.Errorf("Can't set sector")
|
||||
}
|
||||
|
||||
if err := task.SetMessage(fmt.Sprintf("set_transaction_id %d %d", oldId, newId)); err != nil {
|
||||
return fmt.Errorf("Can't set message")
|
||||
}
|
||||
|
||||
if err := task.Run(); err != nil {
|
||||
return fmt.Errorf("Error running setTransactionId")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func suspendDevice(name string) error {
|
||||
task, err := createTask(DeviceSuspend, name)
|
||||
if task == nil {
|
||||
return err
|
||||
}
|
||||
if err := task.Run(); err != nil {
|
||||
return fmt.Errorf("Error running DeviceSuspend")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resumeDevice(name string) error {
|
||||
task, err := createTask(DeviceResume, name)
|
||||
if task == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var cookie uint32 = 0
|
||||
if err := task.SetCookie(&cookie, 0); err != nil {
|
||||
return fmt.Errorf("Can't set cookie")
|
||||
}
|
||||
|
||||
if err := task.Run(); err != nil {
|
||||
return fmt.Errorf("Error running DeviceSuspend")
|
||||
}
|
||||
|
||||
UdevWait(cookie)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createDevice(poolName string, deviceId int) error {
|
||||
utils.Debugf("[devmapper] createDevice(poolName=%v, deviceId=%v)", poolName, deviceId)
|
||||
task, err := createTask(DeviceTargetMsg, poolName)
|
||||
if task == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := task.SetSector(0); err != nil {
|
||||
return fmt.Errorf("Can't set sector")
|
||||
}
|
||||
|
||||
if err := task.SetMessage(fmt.Sprintf("create_thin %d", deviceId)); err != nil {
|
||||
return fmt.Errorf("Can't set message")
|
||||
}
|
||||
|
||||
if err := task.Run(); err != nil {
|
||||
return fmt.Errorf("Error running createDevice")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteDevice(poolName string, deviceId int) error {
|
||||
task, err := createTask(DeviceTargetMsg, poolName)
|
||||
if task == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := task.SetSector(0); err != nil {
|
||||
return fmt.Errorf("Can't set sector")
|
||||
}
|
||||
|
||||
if err := task.SetMessage(fmt.Sprintf("delete %d", deviceId)); err != nil {
|
||||
return fmt.Errorf("Can't set message")
|
||||
}
|
||||
|
||||
if err := task.Run(); err != nil {
|
||||
return fmt.Errorf("Error running deleteDevice")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeDevice(name string) error {
|
||||
utils.Debugf("[devmapper] removeDevice START")
|
||||
defer utils.Debugf("[devmapper] removeDevice END")
|
||||
task, err := createTask(DeviceRemove, name)
|
||||
if task == nil {
|
||||
return err
|
||||
}
|
||||
if err = task.Run(); err != nil {
|
||||
return fmt.Errorf("Error running removeDevice")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func activateDevice(poolName string, name string, deviceId int, size uint64) error {
|
||||
task, err := createTask(DeviceCreate, name)
|
||||
if task == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
params := fmt.Sprintf("%s %d", poolName, deviceId)
|
||||
if err := task.AddTarget(0, size/512, "thin", params); err != nil {
|
||||
return fmt.Errorf("Can't add target")
|
||||
}
|
||||
if err := task.SetAddNode(AddNodeOnCreate); err != nil {
|
||||
return fmt.Errorf("Can't add node")
|
||||
}
|
||||
|
||||
var cookie uint32 = 0
|
||||
if err := task.SetCookie(&cookie, 0); err != nil {
|
||||
return fmt.Errorf("Can't set cookie")
|
||||
}
|
||||
|
||||
if err := task.Run(); err != nil {
|
||||
return fmt.Errorf("Error running DeviceCreate")
|
||||
}
|
||||
|
||||
UdevWait(cookie)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) createSnapDevice(poolName string, deviceId int, baseName string, baseDeviceId int) error {
|
||||
devinfo, _ := getInfo(baseName)
|
||||
doSuspend := devinfo != nil && devinfo.Exists != 0
|
||||
|
||||
if doSuspend {
|
||||
if err := suspendDevice(baseName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
task, err := createTask(DeviceTargetMsg, poolName)
|
||||
if task == nil {
|
||||
if doSuspend {
|
||||
resumeDevice(baseName)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err := task.SetSector(0); err != nil {
|
||||
if doSuspend {
|
||||
resumeDevice(baseName)
|
||||
}
|
||||
return fmt.Errorf("Can't set sector")
|
||||
}
|
||||
|
||||
if err := task.SetMessage(fmt.Sprintf("create_snap %d %d", deviceId, baseDeviceId)); err != nil {
|
||||
if doSuspend {
|
||||
resumeDevice(baseName)
|
||||
}
|
||||
return fmt.Errorf("Can't set message")
|
||||
}
|
||||
|
||||
if err := task.Run(); err != nil {
|
||||
if doSuspend {
|
||||
resumeDevice(baseName)
|
||||
}
|
||||
return fmt.Errorf("Error running DeviceCreate")
|
||||
}
|
||||
|
||||
if doSuspend {
|
||||
if err := resumeDevice(baseName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package devmapper
|
||||
|
||||
import "C"
|
||||
|
||||
// Due to the way cgo works this has to be in a separate file, as devmapper.go has
|
||||
// definitions in the cgo block, which is incompatible with using "//export"
|
||||
|
||||
//export DevmapperLogCallback
|
||||
func DevmapperLogCallback(level C.int, file *C.char, line C.int, dm_errno_or_class C.int, message *C.char) {
|
||||
if dmLogger != nil {
|
||||
dmLogger.log(int(level), C.GoString(file), int(line), int(dm_errno_or_class), C.GoString(message))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/devmapper"
|
||||
"os"
|
||||
)
|
||||
|
||||
func usage() {
|
||||
fmt.Printf("Usage: %s [snap new-id base-id] | [remove id] | [mount id mountpoint]\n", os.Args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func main() {
|
||||
devices := devmapper.NewDeviceSet("/var/lib/docker")
|
||||
|
||||
if len(os.Args) < 2 {
|
||||
usage()
|
||||
}
|
||||
|
||||
cmd := os.Args[1]
|
||||
if cmd == "snap" {
|
||||
if len(os.Args) < 4 {
|
||||
usage()
|
||||
}
|
||||
|
||||
err := devices.AddDevice(os.Args[2], os.Args[3])
|
||||
if err != nil {
|
||||
fmt.Println("Can't create snap device: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else if cmd == "remove" {
|
||||
if len(os.Args) < 3 {
|
||||
usage()
|
||||
}
|
||||
|
||||
err := devices.RemoveDevice(os.Args[2])
|
||||
if err != nil {
|
||||
fmt.Println("Can't remove device: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else if cmd == "mount" {
|
||||
if len(os.Args) < 4 {
|
||||
usage()
|
||||
}
|
||||
|
||||
err := devices.MountDevice(os.Args[2], os.Args[3])
|
||||
if err != nil {
|
||||
fmt.Println("Can't create snap device: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Unknown command %s\n", cmd)
|
||||
if len(os.Args) < 4 {
|
||||
usage()
|
||||
}
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
+104
-29
@@ -4,12 +4,16 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker"
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"github.com/dotcloud/docker/sysinit"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -30,14 +34,14 @@ func main() {
|
||||
flAutoRestart := flag.Bool("r", true, "Restart previously running containers")
|
||||
bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge. Use 'none' to disable container networking")
|
||||
pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
|
||||
flRoot := flag.String("g", "/var/lib/docker", "Path to use as the root of the docker runtime.")
|
||||
flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.")
|
||||
flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
|
||||
flDns := flag.String("dns", "", "Set custom dns servers")
|
||||
flHosts := utils.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)}
|
||||
flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use")
|
||||
flEnableIptables := flag.Bool("iptables", true, "Disable iptables within docker")
|
||||
flDefaultIp := flag.String("ip", "0.0.0.0", "Default ip address to use when binding a containers ports")
|
||||
flInterContainerComm := flag.Bool("icc", true, "Enable inter-container communication")
|
||||
flInterContainerComm := flag.Bool("enable-container-comm", false, "Enable inter-container communication")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
@@ -49,14 +53,13 @@ func main() {
|
||||
flHosts = flHosts[1:] //trick to display a nice default value in the usage
|
||||
}
|
||||
for i, flHost := range flHosts {
|
||||
host, err := utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
|
||||
if err == nil {
|
||||
flHosts[i] = host
|
||||
} else {
|
||||
log.Fatal(err)
|
||||
}
|
||||
flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
|
||||
}
|
||||
|
||||
bridge := docker.DefaultNetworkBridge
|
||||
if *bridgeName != "" {
|
||||
bridge = *bridgeName
|
||||
}
|
||||
if *flDebug {
|
||||
os.Setenv("DEBUG", "1")
|
||||
}
|
||||
@@ -67,33 +70,33 @@ func main() {
|
||||
flag.Usage()
|
||||
return
|
||||
}
|
||||
eng, err := engine.New(*flRoot)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
var dns []string
|
||||
if *flDns != "" {
|
||||
dns = []string{*flDns}
|
||||
}
|
||||
// Load plugin: httpapi
|
||||
job := eng.Job("initapi")
|
||||
job.Setenv("Pidfile", *pidfile)
|
||||
job.Setenv("Root", *flRoot)
|
||||
job.SetenvBool("AutoRestart", *flAutoRestart)
|
||||
job.SetenvBool("EnableCors", *flEnableCors)
|
||||
job.Setenv("Dns", *flDns)
|
||||
job.SetenvBool("EnableIptables", *flEnableIptables)
|
||||
job.Setenv("BridgeIface", *bridgeName)
|
||||
job.Setenv("DefaultIp", *flDefaultIp)
|
||||
job.SetenvBool("InterContainerCommunication", *flInterContainerComm)
|
||||
if err := job.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
|
||||
ip := net.ParseIP(*flDefaultIp)
|
||||
|
||||
config := &docker.DaemonConfig{
|
||||
Pidfile: *pidfile,
|
||||
GraphPath: *flGraphPath,
|
||||
AutoRestart: *flAutoRestart,
|
||||
EnableCors: *flEnableCors,
|
||||
Dns: dns,
|
||||
EnableIptables: *flEnableIptables,
|
||||
BridgeIface: bridge,
|
||||
ProtoAddresses: flHosts,
|
||||
DefaultIp: ip,
|
||||
InterContainerCommunication: *flInterContainerComm,
|
||||
}
|
||||
// Serve api
|
||||
job = eng.Job("serveapi", flHosts...)
|
||||
job.SetenvBool("Logging", true)
|
||||
if err := job.Run(); err != nil {
|
||||
if err := daemon(config); err != nil {
|
||||
log.Fatal(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
} else {
|
||||
if len(flHosts) > 1 {
|
||||
log.Fatal("Please specify only one -H")
|
||||
return
|
||||
}
|
||||
protoAddrParts := strings.SplitN(flHosts[0], "://", 2)
|
||||
if err := docker.ParseCommands(protoAddrParts[0], protoAddrParts[1], flag.Args()...); err != nil {
|
||||
@@ -101,6 +104,7 @@ func main() {
|
||||
os.Exit(sterr.Status)
|
||||
}
|
||||
log.Fatal(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,3 +112,74 @@ func main() {
|
||||
func showVersion() {
|
||||
fmt.Printf("Docker version %s, build %s\n", VERSION, GITCOMMIT)
|
||||
}
|
||||
|
||||
func createPidFile(pidfile string) error {
|
||||
if pidString, err := ioutil.ReadFile(pidfile); err == nil {
|
||||
pid, err := strconv.Atoi(string(pidString))
|
||||
if err == nil {
|
||||
if _, err := os.Stat(fmt.Sprintf("/proc/%d/", pid)); err == nil {
|
||||
return fmt.Errorf("pid file found, ensure docker is not running or delete %s", pidfile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file, err := os.Create(pidfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
_, err = fmt.Fprintf(file, "%d", os.Getpid())
|
||||
return err
|
||||
}
|
||||
|
||||
func removePidFile(pidfile string) {
|
||||
if err := os.Remove(pidfile); err != nil {
|
||||
log.Printf("Error removing %s: %s", pidfile, err)
|
||||
}
|
||||
}
|
||||
|
||||
func daemon(config *docker.DaemonConfig) error {
|
||||
if err := createPidFile(config.Pidfile); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer removePidFile(config.Pidfile)
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGTERM)
|
||||
go func() {
|
||||
sig := <-c
|
||||
log.Printf("Received signal '%v', exiting\n", sig)
|
||||
removePidFile(config.Pidfile)
|
||||
os.Exit(0)
|
||||
}()
|
||||
server, err := docker.NewServer(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chErrors := make(chan error, len(config.ProtoAddresses))
|
||||
for _, protoAddr := range config.ProtoAddresses {
|
||||
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
|
||||
if protoAddrParts[0] == "unix" {
|
||||
syscall.Unlink(protoAddrParts[1])
|
||||
} else if protoAddrParts[0] == "tcp" {
|
||||
if !strings.HasPrefix(protoAddrParts[1], "127.0.0.1") {
|
||||
log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
|
||||
}
|
||||
} else {
|
||||
log.Fatal("Invalid protocol format.")
|
||||
os.Exit(-1)
|
||||
}
|
||||
go func() {
|
||||
chErrors <- docker.ListenAndServe(protoAddrParts[0], protoAddrParts[1], server, true)
|
||||
}()
|
||||
}
|
||||
for i := 0; i < len(config.ProtoAddresses); i += 1 {
|
||||
err := <-chErrors
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+1
-6
@@ -1,16 +1,11 @@
|
||||
from ubuntu:12.04
|
||||
maintainer Nick Stinemates
|
||||
#
|
||||
# docker build -t docker:docs . && docker run -p 8000:8000 docker:docs
|
||||
#
|
||||
|
||||
run apt-get update
|
||||
run apt-get install -y python-setuptools make
|
||||
run easy_install pip
|
||||
#from docs/requirements.txt, but here to increase cacheability
|
||||
run pip install Sphinx==1.1.3
|
||||
run pip install sphinxcontrib-httpdomain==1.1.8
|
||||
add . /docs
|
||||
run pip install -r /docs/requirements.txt
|
||||
run cd /docs; make docs
|
||||
|
||||
expose 8000
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ help:
|
||||
# @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
# @echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
# @echo " text to make text files"
|
||||
@echo " man to make a manual page"
|
||||
# @echo " man to make manual pages"
|
||||
# @echo " texinfo to make Texinfo files"
|
||||
# @echo " info to make Texinfo files and run them through makeinfo"
|
||||
# @echo " gettext to make PO message catalogs"
|
||||
|
||||
+4
-5
@@ -39,7 +39,8 @@ Getting Started
|
||||
To edit and test the docs, you'll need to install the Sphinx tool and
|
||||
its dependencies. There are two main ways to install this tool:
|
||||
|
||||
###Native Installation
|
||||
Native Installation
|
||||
...................
|
||||
|
||||
* Install sphinx: `pip install sphinx`
|
||||
* Mac OS X: `[sudo] pip-2.7 install sphinx`
|
||||
@@ -47,7 +48,8 @@ its dependencies. There are two main ways to install this tool:
|
||||
* Mac OS X: `[sudo] pip-2.7 install sphinxcontrib-httpdomain`
|
||||
* If pip is not available you can probably install it using your favorite package manager as **python-pip**
|
||||
|
||||
###Alternative Installation: Docker Container
|
||||
Alternative Installation: Docker Container
|
||||
..........................................
|
||||
|
||||
If you're running ``docker`` on your development machine then you may
|
||||
find it easier and cleaner to use the Dockerfile. This installs Sphinx
|
||||
@@ -57,9 +59,6 @@ docs inside the container, even starting a simple HTTP server on port
|
||||
build .`` and run the resulting image. This is the equivalent to
|
||||
``make clean server`` since each container starts clean.
|
||||
|
||||
In the ``docs/`` directory, run:
|
||||
```docker build -t docker:docs . && docker run -p 8000:8000 docker:docs```
|
||||
|
||||
Usage
|
||||
-----
|
||||
* Follow the contribution guidelines (``../CONTRIBUTING.md``)
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
Sphinx==1.1.3
|
||||
sphinxcontrib-httpdomain==1.1.9
|
||||
sphinxcontrib-httpdomain==1.1.8
|
||||
@@ -26,118 +26,14 @@ Docker Remote API
|
||||
2. Versions
|
||||
===========
|
||||
|
||||
The current version of the API is 1.7
|
||||
The current version of the API is 1.6
|
||||
|
||||
Calling /images/<name>/insert is the same as calling
|
||||
/v1.7/images/<name>/insert
|
||||
/v1.6/images/<name>/insert
|
||||
|
||||
You can still call an old version of the api using
|
||||
/v1.0/images/<name>/insert
|
||||
|
||||
v1.7
|
||||
****
|
||||
|
||||
Full Documentation
|
||||
------------------
|
||||
|
||||
:doc:`docker_remote_api_v1.7`
|
||||
|
||||
What's new
|
||||
----------
|
||||
|
||||
.. http:get:: /images/json
|
||||
|
||||
The format of the json returned from this uri changed. Instead of an entry
|
||||
for each repo/tag on an image, each image is only represented once, with a
|
||||
nested attribute indicating the repo/tags that apply to that image.
|
||||
|
||||
Instead of:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
[
|
||||
{
|
||||
"VirtualSize": 131506275,
|
||||
"Size": 131506275,
|
||||
"Created": 1365714795,
|
||||
"Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
|
||||
"Tag": "12.04",
|
||||
"Repository": "ubuntu"
|
||||
},
|
||||
{
|
||||
"VirtualSize": 131506275,
|
||||
"Size": 131506275,
|
||||
"Created": 1365714795,
|
||||
"Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
|
||||
"Tag": "latest",
|
||||
"Repository": "ubuntu"
|
||||
},
|
||||
{
|
||||
"VirtualSize": 131506275,
|
||||
"Size": 131506275,
|
||||
"Created": 1365714795,
|
||||
"Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
|
||||
"Tag": "precise",
|
||||
"Repository": "ubuntu"
|
||||
},
|
||||
{
|
||||
"VirtualSize": 180116135,
|
||||
"Size": 24653,
|
||||
"Created": 1364102658,
|
||||
"Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
|
||||
"Tag": "12.10",
|
||||
"Repository": "ubuntu"
|
||||
},
|
||||
{
|
||||
"VirtualSize": 180116135,
|
||||
"Size": 24653,
|
||||
"Created": 1364102658,
|
||||
"Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
|
||||
"Tag": "quantal",
|
||||
"Repository": "ubuntu"
|
||||
}
|
||||
]
|
||||
|
||||
The returned json looks like this:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
[
|
||||
{
|
||||
"RepoTag": [
|
||||
"ubuntu:12.04",
|
||||
"ubuntu:precise",
|
||||
"ubuntu:latest"
|
||||
],
|
||||
"Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
|
||||
"Created": 1365714795,
|
||||
"Size": 131506275,
|
||||
"VirtualSize": 131506275
|
||||
},
|
||||
{
|
||||
"RepoTag": [
|
||||
"ubuntu:12.10",
|
||||
"ubuntu:quantal"
|
||||
],
|
||||
"ParentId": "27cf784147099545",
|
||||
"Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
|
||||
"Created": 1364102658,
|
||||
"Size": 24653,
|
||||
"VirtualSize": 180116135
|
||||
}
|
||||
]
|
||||
|
||||
.. http:get:: /images/viz
|
||||
|
||||
This URI no longer exists. The ``images -viz`` output is now generated in
|
||||
the client, using the ``/images/json`` data.
|
||||
|
||||
v1.6
|
||||
****
|
||||
|
||||
|
||||
@@ -970,33 +970,28 @@ Create a new image from a container's changes
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Cmd": ["cat", "/world"],
|
||||
"PortSpecs":["22"]
|
||||
}
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
|
||||
**Example response**:
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 201 OK
|
||||
Content-Type: application/vnd.docker.raw-stream
|
||||
HTTP/1.1 201 OK
|
||||
Content-Type: application/vnd.docker.raw-stream
|
||||
|
||||
{"Id":"596069db4bf5"}
|
||||
{"Id":"596069db4bf5"}
|
||||
|
||||
:query container: source container
|
||||
:query repo: repository
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
:query container: source container
|
||||
:query repo: repository
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
3. Going further
|
||||
|
||||
@@ -977,37 +977,32 @@ Create a new image from a container's changes
|
||||
|
||||
.. http:post:: /commit
|
||||
|
||||
Create a new image from a container's changes
|
||||
Create a new image from a container's changes
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Cmd": ["cat", "/world"],
|
||||
"PortSpecs":["22"]
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 201 OK
|
||||
Content-Type: application/vnd.docker.raw-stream
|
||||
**Example request**:
|
||||
|
||||
{"Id":"596069db4bf5"}
|
||||
.. sourcecode:: http
|
||||
|
||||
:query container: source container
|
||||
:query repo: repository
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 201 OK
|
||||
Content-Type: application/vnd.docker.raw-stream
|
||||
|
||||
{"Id":"596069db4bf5"}
|
||||
|
||||
:query container: source container
|
||||
:query repo: repository
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
3. Going further
|
||||
|
||||
@@ -985,37 +985,32 @@ Create a new image from a container's changes
|
||||
|
||||
.. http:post:: /commit
|
||||
|
||||
Create a new image from a container's changes
|
||||
Create a new image from a container's changes
|
||||
|
||||
**Example request**:
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Cmd": ["cat", "/world"],
|
||||
"PortSpecs":["22"]
|
||||
}
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
|
||||
**Example response**:
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 201 OK
|
||||
HTTP/1.1 201 OK
|
||||
Content-Type: application/vnd.docker.raw-stream
|
||||
|
||||
{"Id":"596069db4bf5"}
|
||||
{"Id":"596069db4bf5"}
|
||||
|
||||
:query container: source container
|
||||
:query repo: repository
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
:query container: source container
|
||||
:query repo: repository
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
3. Going further
|
||||
|
||||
@@ -1034,37 +1034,32 @@ Create a new image from a container's changes
|
||||
|
||||
.. http:post:: /commit
|
||||
|
||||
Create a new image from a container's changes
|
||||
Create a new image from a container's changes
|
||||
|
||||
**Example request**:
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Cmd": ["cat", "/world"],
|
||||
"PortSpecs":["22"]
|
||||
}
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
|
||||
**Example response**:
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 201 OK
|
||||
HTTP/1.1 201 OK
|
||||
Content-Type: application/vnd.docker.raw-stream
|
||||
|
||||
{"Id":"596069db4bf5"}
|
||||
{"Id":"596069db4bf5"}
|
||||
|
||||
:query container: source container
|
||||
:query repo: repository
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
:query container: source container
|
||||
:query repo: repository
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
Monitor Docker's events
|
||||
|
||||
@@ -943,36 +943,36 @@ Build an image from Dockerfile via stdin
|
||||
|
||||
.. http:post:: /build
|
||||
|
||||
Build an image from Dockerfile via stdin
|
||||
Build an image from Dockerfile via stdin
|
||||
|
||||
**Example request**:
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /build HTTP/1.1
|
||||
POST /build HTTP/1.1
|
||||
|
||||
{{ STREAM }}
|
||||
{{ STREAM }}
|
||||
|
||||
**Example response**:
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
{{ STREAM }}
|
||||
{{ STREAM }}
|
||||
|
||||
|
||||
The stream must be a tar archive compressed with one of the following algorithms:
|
||||
identity (no compression), gzip, bzip2, xz. The archive must include a file called
|
||||
`Dockerfile` at its root. It may include any number of other files, which will be
|
||||
accessible in the build context (See the ADD build command).
|
||||
The stream must be a tar archive compressed with one of the following algorithms:
|
||||
identity (no compression), gzip, bzip2, xz. The archive must include a file called
|
||||
`Dockerfile` at its root. It may include any number of other files, which will be
|
||||
accessible in the build context (See the ADD build command).
|
||||
|
||||
The Content-type header should be set to "application/tar".
|
||||
The Content-type header should be set to "application/tar".
|
||||
|
||||
:query t: repository name (and optionally a tag) to be applied to the resulting image in case of success
|
||||
:query q: suppress verbose build output
|
||||
:query t: repository name (and optionally a tag) to be applied to the resulting image in case of success
|
||||
:query q: suppress verbose build output
|
||||
:query nocache: do not use the cache when building the image
|
||||
:statuscode 200: no error
|
||||
:statuscode 200: no error
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
@@ -1069,8 +1069,8 @@ Show the docker version information
|
||||
"GoVersion":"go1.0.3"
|
||||
}
|
||||
|
||||
:statuscode 200: no error
|
||||
:statuscode 500: server error
|
||||
:statuscode 200: no error
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
Create a new image from a container's changes
|
||||
@@ -1084,28 +1084,23 @@ Create a new image from a container's changes
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Cmd": ["cat", "/world"],
|
||||
"PortSpecs":["22"]
|
||||
}
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 201 OK
|
||||
Content-Type: application/vnd.docker.raw-stream
|
||||
HTTP/1.1 201 OK
|
||||
Content-Type: application/vnd.docker.raw-stream
|
||||
|
||||
{"Id":"596069db4bf5"}
|
||||
{"Id":"596069db4bf5"}
|
||||
|
||||
:query container: source container
|
||||
:query repo: repository
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
|
||||
@@ -1050,37 +1050,32 @@ Create a new image from a container's changes
|
||||
|
||||
.. http:post:: /commit
|
||||
|
||||
Create a new image from a container's changes
|
||||
Create a new image from a container's changes
|
||||
|
||||
**Example request**:
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Cmd": ["cat", "/world"],
|
||||
"PortSpecs":["22"]
|
||||
}
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
|
||||
**Example response**:
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 201 OK
|
||||
Content-Type: application/vnd.docker.raw-stream
|
||||
HTTP/1.1 201 OK
|
||||
Content-Type: application/vnd.docker.raw-stream
|
||||
|
||||
{"Id":"596069db4bf5"}
|
||||
{"Id":"596069db4bf5"}
|
||||
|
||||
:query container: source container
|
||||
:query repo: repository
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
:query container: source container
|
||||
:query repo: repository
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
|
||||
Monitor Docker's events
|
||||
***********************
|
||||
|
||||
@@ -13,12 +13,9 @@ Docker Remote API v1.6
|
||||
1. Brief introduction
|
||||
=====================
|
||||
|
||||
- The Remote API has replaced rcli
|
||||
- The daemon listens on ``unix:///var/run/docker.sock``, but you can
|
||||
:ref:`bind_docker`.
|
||||
- The API tends to be REST, but for some complex commands, like
|
||||
``attach`` or ``pull``, the HTTP connection is hijacked to transport
|
||||
``stdout, stdin`` and ``stderr``
|
||||
- The Remote API is replacing rcli
|
||||
- Default port in the docker daemon is 4243
|
||||
- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
|
||||
|
||||
2. Endpoints
|
||||
============
|
||||
@@ -121,7 +118,8 @@ Create a container
|
||||
"AttachStdin":false,
|
||||
"AttachStdout":true,
|
||||
"AttachStderr":true,
|
||||
"ExposedPorts":{},
|
||||
"PortSpecs":null,
|
||||
"Privileged": false,
|
||||
"Tty":false,
|
||||
"OpenStdin":false,
|
||||
"StdinOnce":false,
|
||||
@@ -134,6 +132,7 @@ Create a container
|
||||
"Volumes":{},
|
||||
"VolumesFrom":"",
|
||||
"WorkingDir":""
|
||||
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
@@ -149,63 +148,11 @@ Create a container
|
||||
}
|
||||
|
||||
:jsonparam config: the container's configuration
|
||||
:query name: container name to use
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 406: impossible to attach (container not running)
|
||||
:statuscode 500: server error
|
||||
|
||||
**More Complex Example request, in 2 steps.**
|
||||
**First, use create to expose a Private Port, which can be bound back to a Public Port at startup**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /containers/create HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Cmd":[
|
||||
"/usr/sbin/sshd","-D"
|
||||
],
|
||||
"Image":"image-with-sshd",
|
||||
"ExposedPorts":{"22/tcp":{}}
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 201 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Id":"e90e34656806"
|
||||
"Warnings":[]
|
||||
}
|
||||
|
||||
**Second, start (using the ID returned above) the image we just created, mapping the ssh port 22 to something on the host**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /containers/e90e34656806/start HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"PortBindings": { "22/tcp": [{ "HostPort": "11022" }]}
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 204 No Content
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
Content-Length: 0
|
||||
|
||||
**Now you can ssh into your new container on port 11022.**
|
||||
|
||||
|
||||
|
||||
|
||||
Inspect a container
|
||||
*******************
|
||||
@@ -240,7 +187,7 @@ Inspect a container
|
||||
"AttachStdin": false,
|
||||
"AttachStdout": true,
|
||||
"AttachStderr": true,
|
||||
"ExposedPorts": {},
|
||||
"PortSpecs": null,
|
||||
"Tty": false,
|
||||
"OpenStdin": false,
|
||||
"StdinOnce": false,
|
||||
@@ -411,12 +358,7 @@ Start a container
|
||||
|
||||
{
|
||||
"Binds":["/tmp:/tmp"],
|
||||
"LxcConf":{"lxc.utsname":"docker"},
|
||||
"ContainerIDFile": "",
|
||||
"Privileged": false,
|
||||
"PortBindings": {"22/tcp": [{HostIp:"", HostPort:""}]},
|
||||
"Links": [],
|
||||
"PublishAllPorts": false
|
||||
"LxcConf":{"lxc.utsname":"docker"}
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
@@ -500,8 +442,7 @@ Kill a container
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 204 OK
|
||||
|
||||
:query signal: Signal to send to the container (integer). When not set, SIGKILL is assumed and the call will waits for the container to exit.
|
||||
|
||||
:statuscode 204: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
@@ -849,7 +790,7 @@ Inspect an image
|
||||
"AttachStdin":false,
|
||||
"AttachStdout":false,
|
||||
"AttachStderr":false,
|
||||
"ExposedPorts":{},
|
||||
"PortSpecs":null,
|
||||
"Tty":true,
|
||||
"OpenStdin":true,
|
||||
"StdinOnce":false,
|
||||
@@ -1049,36 +990,36 @@ Build an image from Dockerfile via stdin
|
||||
|
||||
.. http:post:: /build
|
||||
|
||||
Build an image from Dockerfile via stdin
|
||||
Build an image from Dockerfile via stdin
|
||||
|
||||
**Example request**:
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /build HTTP/1.1
|
||||
POST /build HTTP/1.1
|
||||
|
||||
{{ STREAM }}
|
||||
{{ STREAM }}
|
||||
|
||||
**Example response**:
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
{{ STREAM }}
|
||||
{{ STREAM }}
|
||||
|
||||
|
||||
The stream must be a tar archive compressed with one of the following algorithms:
|
||||
identity (no compression), gzip, bzip2, xz. The archive must include a file called
|
||||
`Dockerfile` at its root. It may include any number of other files, which will be
|
||||
accessible in the build context (See the ADD build command).
|
||||
|
||||
The Content-type header should be set to "application/tar".
|
||||
The stream must be a tar archive compressed with one of the following algorithms:
|
||||
identity (no compression), gzip, bzip2, xz. The archive must include a file called
|
||||
`Dockerfile` at its root. It may include any number of other files, which will be
|
||||
accessible in the build context (See the ADD build command).
|
||||
|
||||
:query t: repository name (and optionally a tag) to be applied to the resulting image in case of success
|
||||
:query q: suppress verbose build output
|
||||
The Content-type header should be set to "application/tar".
|
||||
|
||||
:query t: repository name (and optionally a tag) to be applied to the resulting image in case of success
|
||||
:query q: suppress verbose build output
|
||||
:query nocache: do not use the cache when building the image
|
||||
:statuscode 200: no error
|
||||
:statuscode 200: no error
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
@@ -1190,13 +1131,7 @@ Create a new image from a container's changes
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Cmd": ["cat", "/world"],
|
||||
"ExposedPorts":{"22/tcp":{}}
|
||||
}
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
|
||||
**Example response**:
|
||||
|
||||
@@ -1212,6 +1147,7 @@ Create a new image from a container's changes
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
:title: Remote API Client Libraries
|
||||
:title: Registry API
|
||||
:description: Various client libraries available to use with the Docker remote API
|
||||
:keywords: API, Docker, index, registry, REST, documentation, clients, Python, Ruby, Javascript, Erlang, Go
|
||||
|
||||
@@ -12,28 +12,26 @@ compatibility. Please file issues with the library owners. If you
|
||||
find more library implementations, please list them in Docker doc bugs
|
||||
and we will add the libraries here.
|
||||
|
||||
+----------------------+----------------+--------------------------------------------+----------+
|
||||
| Language/Framework | Name | Repository | Status |
|
||||
+======================+================+============================================+==========+
|
||||
| Python | docker-py | https://github.com/dotcloud/docker-py | Active |
|
||||
+----------------------+----------------+--------------------------------------------+----------+
|
||||
| Ruby | docker-client | https://github.com/geku/docker-client | Outdated |
|
||||
+----------------------+----------------+--------------------------------------------+----------+
|
||||
| Ruby | docker-api | https://github.com/swipely/docker-api | Active |
|
||||
+----------------------+----------------+--------------------------------------------+----------+
|
||||
| Javascript (NodeJS) | docker.io | https://github.com/appersonlabs/docker.io | Active |
|
||||
| | | Install via NPM: `npm install docker.io` | |
|
||||
+----------------------+----------------+--------------------------------------------+----------+
|
||||
| Javascript | docker-js | https://github.com/dgoujard/docker-js | Active |
|
||||
+----------------------+----------------+--------------------------------------------+----------+
|
||||
| Javascript (Angular) | dockerui | https://github.com/crosbymichael/dockerui | Active |
|
||||
| **WebUI** | | | |
|
||||
+----------------------+----------------+--------------------------------------------+----------+
|
||||
| Java | docker-java | https://github.com/kpelykh/docker-java | Active |
|
||||
+----------------------+----------------+--------------------------------------------+----------+
|
||||
| Erlang | erldocker | https://github.com/proger/erldocker | Active |
|
||||
+----------------------+----------------+--------------------------------------------+----------+
|
||||
| Go | go-dockerclient| https://github.com/fsouza/go-dockerclient | Active |
|
||||
+----------------------+----------------+--------------------------------------------+----------+
|
||||
| PHP | Alvine | http://pear.alvine.io/ (alpha) | Active |
|
||||
+----------------------+----------------+--------------------------------------------+----------+
|
||||
+----------------------+----------------+--------------------------------------------+
|
||||
| Language/Framework | Name | Repository |
|
||||
+======================+================+============================================+
|
||||
| Python | docker-py | https://github.com/dotcloud/docker-py |
|
||||
+----------------------+----------------+--------------------------------------------+
|
||||
| Ruby | docker-client | https://github.com/geku/docker-client |
|
||||
+----------------------+----------------+--------------------------------------------+
|
||||
| Ruby | docker-api | https://github.com/swipely/docker-api |
|
||||
+----------------------+----------------+--------------------------------------------+
|
||||
| Javascript (NodeJS) | docker.io | https://github.com/appersonlabs/docker.io |
|
||||
| | | Install via NPM: `npm install docker.io` |
|
||||
+----------------------+----------------+--------------------------------------------+
|
||||
| Javascript | docker-js | https://github.com/dgoujard/docker-js |
|
||||
+----------------------+----------------+--------------------------------------------+
|
||||
| Javascript (Angular) | dockerui | https://github.com/crosbymichael/dockerui |
|
||||
| **WebUI** | | |
|
||||
+----------------------+----------------+--------------------------------------------+
|
||||
| Java | docker-java | https://github.com/kpelykh/docker-java |
|
||||
+----------------------+----------------+--------------------------------------------+
|
||||
| Erlang | erldocker | https://github.com/proger/erldocker |
|
||||
+----------------------+----------------+--------------------------------------------+
|
||||
| Go | go-dockerclient| https://github.com/fsouza/go-dockerclient |
|
||||
+----------------------+----------------+--------------------------------------------+
|
||||
|
||||
@@ -29,9 +29,6 @@ To list available commands, either run ``docker`` with no parameters or execute
|
||||
|
||||
Attach to a running container.
|
||||
|
||||
-nostdin=false: Do not attach stdin
|
||||
-sig-proxy=true: Proxify all received signal to the process (even in non-tty mode)
|
||||
|
||||
You can detach from the container again (and leave it running) with
|
||||
``CTRL-c`` (for a quiet exit) or ``CTRL-\`` to get a stacktrace of
|
||||
the Docker client when it quits.
|
||||
@@ -41,7 +38,7 @@ To stop a container, use ``docker stop``
|
||||
To kill the container, use ``docker kill``
|
||||
|
||||
.. _cli_attach_examples:
|
||||
|
||||
|
||||
Examples:
|
||||
~~~~~~~~~
|
||||
|
||||
@@ -55,8 +52,8 @@ Examples:
|
||||
Mem: 373572k total, 355560k used, 18012k free, 27872k buffers
|
||||
Swap: 786428k total, 0k used, 786428k free, 221740k cached
|
||||
|
||||
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
|
||||
1 root 20 0 17200 1116 912 R 0 0.3 0:00.03 top
|
||||
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
|
||||
1 root 20 0 17200 1116 912 R 0 0.3 0:00.03 top
|
||||
|
||||
top - 02:05:55 up 3:05, 0 users, load average: 0.01, 0.02, 0.05
|
||||
Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie
|
||||
@@ -64,8 +61,8 @@ Examples:
|
||||
Mem: 373572k total, 355244k used, 18328k free, 27872k buffers
|
||||
Swap: 786428k total, 0k used, 786428k free, 221776k cached
|
||||
|
||||
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
|
||||
1 root 20 0 17208 1144 932 R 0 0.3 0:00.03 top
|
||||
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
|
||||
1 root 20 0 17208 1144 932 R 0 0.3 0:00.03 top
|
||||
|
||||
|
||||
top - 02:05:58 up 3:06, 0 users, load average: 0.01, 0.02, 0.05
|
||||
@@ -74,9 +71,9 @@ Examples:
|
||||
Mem: 373572k total, 355780k used, 17792k free, 27880k buffers
|
||||
Swap: 786428k total, 0k used, 786428k free, 221776k cached
|
||||
|
||||
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
|
||||
1 root 20 0 17208 1144 932 R 0 0.3 0:00.03 top
|
||||
^C$
|
||||
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
|
||||
1 root 20 0 17208 1144 932 R 0 0.3 0:00.03 top
|
||||
^C$
|
||||
$ sudo docker stop $ID
|
||||
|
||||
.. _cli_build:
|
||||
@@ -88,65 +85,31 @@ Examples:
|
||||
|
||||
Usage: docker build [OPTIONS] PATH | URL | -
|
||||
Build a new container image from the source code at PATH
|
||||
-t="": Repository name (and optionally a tag) to be applied
|
||||
to the resulting image in case of success.
|
||||
-t="": Repository name (and optionally a tag) to be applied to the resulting image in case of success.
|
||||
-q=false: Suppress verbose build output.
|
||||
-no-cache: Do not use the cache when building the image.
|
||||
-rm: Remove intermediate containers after a successful build
|
||||
|
||||
The files at PATH or URL are called the "context" of the build. The
|
||||
build process may refer to any of the files in the context, for
|
||||
example when using an :ref:`ADD <dockerfile_add>` instruction. When a
|
||||
single ``Dockerfile`` is given as URL, then no context is set. When a
|
||||
git repository is set as URL, then the repository is used as the
|
||||
context
|
||||
When a single Dockerfile is given as URL, then no context is set. When a git repository is set as URL, the repository is used as context
|
||||
|
||||
.. _cli_build_examples:
|
||||
|
||||
.. seealso:: :ref:`dockerbuilder`.
|
||||
|
||||
Examples:
|
||||
~~~~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker build .
|
||||
Uploading context 10240 bytes
|
||||
Step 1 : FROM busybox
|
||||
Pulling repository busybox
|
||||
---> e9aa60c60128MB/2.284 MB (100%) endpoint: https://cdn-registry-1.docker.io/v1/
|
||||
Step 2 : RUN ls -lh /
|
||||
---> Running in 9c9e81692ae9
|
||||
total 24
|
||||
drwxr-xr-x 2 root root 4.0K Mar 12 2013 bin
|
||||
drwxr-xr-x 5 root root 4.0K Oct 19 00:19 dev
|
||||
drwxr-xr-x 2 root root 4.0K Oct 19 00:19 etc
|
||||
drwxr-xr-x 2 root root 4.0K Nov 15 23:34 lib
|
||||
lrwxrwxrwx 1 root root 3 Mar 12 2013 lib64 -> lib
|
||||
dr-xr-xr-x 116 root root 0 Nov 15 23:34 proc
|
||||
lrwxrwxrwx 1 root root 3 Mar 12 2013 sbin -> bin
|
||||
dr-xr-xr-x 13 root root 0 Nov 15 23:34 sys
|
||||
drwxr-xr-x 2 root root 4.0K Mar 12 2013 tmp
|
||||
drwxr-xr-x 2 root root 4.0K Nov 15 23:34 usr
|
||||
---> b35f4035db3f
|
||||
Step 3 : CMD echo Hello World
|
||||
---> Running in 02071fceb21b
|
||||
---> f52f38b7823e
|
||||
Successfully built f52f38b7823e
|
||||
|
||||
This example specifies that the PATH is ``.``, and so all the files in
|
||||
the local directory get tar'd and sent to the Docker daemon. The PATH
|
||||
specifies where to find the files for the "context" of the build on
|
||||
the Docker daemon. Remember that the daemon could be running on a
|
||||
remote machine and that no parsing of the Dockerfile happens at the
|
||||
client side (where you're running ``docker build``). That means that
|
||||
*all* the files at PATH get sent, not just the ones listed to
|
||||
:ref:`ADD <dockerfile_add>` in the ``Dockerfile``.
|
||||
|
||||
The transfer of context from the local machine to the Docker daemon is
|
||||
what the ``docker`` client means when you see the "Uploading context"
|
||||
message.
|
||||
This will read the ``Dockerfile`` from the current directory. It will
|
||||
also send any other files and directories found in the current
|
||||
directory to the ``docker`` daemon.
|
||||
|
||||
The contents of this directory would be used by ``ADD`` commands found
|
||||
within the ``Dockerfile``. This will send a lot of data to the
|
||||
``docker`` daemon if the current directory contains a lot of data. If
|
||||
the absolute path is provided instead of ``.`` then only the files and
|
||||
directories required by the ADD commands from the ``Dockerfile`` will be
|
||||
added to the context and transferred to the ``docker`` daemon.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -163,15 +126,17 @@ tag will be ``2.0``
|
||||
|
||||
This will read a ``Dockerfile`` from *stdin* without context. Due to
|
||||
the lack of a context, no contents of any local directory will be sent
|
||||
to the ``docker`` daemon. Since there is no context, a Dockerfile
|
||||
``ADD`` only works if it refers to a remote URL.
|
||||
to the ``docker`` daemon. ``ADD`` doesn't work when running in this
|
||||
mode because the absence of the context provides no source files to
|
||||
copy to the container.
|
||||
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker build github.com/creack/docker-firefox
|
||||
|
||||
This will clone the Github repository and use the cloned repository as
|
||||
context. The ``Dockerfile`` at the root of the repository is used as
|
||||
This will clone the Github repository and use it as context. The
|
||||
``Dockerfile`` at the root of the repository is used as
|
||||
``Dockerfile``. Note that you can specify an arbitrary git repository
|
||||
by using the ``git://`` schema.
|
||||
|
||||
@@ -183,35 +148,16 @@ by using the ``git://`` schema.
|
||||
|
||||
::
|
||||
|
||||
Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
|
||||
Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY [TAG]]
|
||||
|
||||
Create a new image from a container's changes
|
||||
|
||||
-m="": Commit message
|
||||
-author="": Author (eg. "John Hannibal Smith <hannibal@a-team.com>"
|
||||
-run="": Configuration to be applied when the image is launched with `docker run`.
|
||||
(ex: -run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')
|
||||
-run="": Configuration to be applied when the image is launched with `docker run`.
|
||||
(ex: '{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')
|
||||
|
||||
Simple commit of an existing container
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ docker ps
|
||||
ID IMAGE COMMAND CREATED STATUS PORTS
|
||||
c3f279d17e0a ubuntu:12.04 /bin/bash 7 days ago Up 25 hours
|
||||
197387f1b436 ubuntu:12.04 /bin/bash 7 days ago Up 25 hours
|
||||
$ docker commit c3f279d17e0a SvenDowideit/testimage:version3
|
||||
f5283438590d
|
||||
$ docker images | head
|
||||
REPOSITORY TAG ID CREATED SIZE
|
||||
SvenDowideit/testimage version3 f5283438590d 16 seconds ago 204.2 MB (virtual 335.7 MB)
|
||||
|
||||
|
||||
Full -run example
|
||||
.................
|
||||
|
||||
(multiline is ok within a single quote ``'``)
|
||||
Full -run example (multiline is ok within a single quote ``'``)
|
||||
|
||||
::
|
||||
|
||||
@@ -252,15 +198,10 @@ Full -run example
|
||||
|
||||
::
|
||||
|
||||
Usage: docker cp CONTAINER:PATH HOSTPATH
|
||||
Usage: docker cp CONTAINER:RESOURCE HOSTPATH
|
||||
|
||||
Copy files/folders from the containers filesystem to the host
|
||||
path. Paths are relative to the root of the filesystem.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo docker cp 7bb0e258aefe:/etc/debian_version .
|
||||
$ sudo docker cp blue_frog:/etc/hosts .
|
||||
|
||||
.. _cli_diff:
|
||||
|
||||
@@ -269,33 +210,9 @@ Full -run example
|
||||
|
||||
::
|
||||
|
||||
Usage: docker diff CONTAINER
|
||||
|
||||
List the changed files and directories in a container's filesystem
|
||||
Usage: docker diff CONTAINER [OPTIONS]
|
||||
|
||||
There are 3 events that are listed in the 'diff':
|
||||
|
||||
1. ```A``` - Add
|
||||
2. ```D``` - Delete
|
||||
3. ```C``` - Change
|
||||
|
||||
for example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo docker diff 7bb0e258aefe
|
||||
|
||||
C /dev
|
||||
A /dev/kmsg
|
||||
C /etc
|
||||
A /etc/mtab
|
||||
A /go
|
||||
A /go/src
|
||||
A /go/src/github.com
|
||||
A /go/src/github.com/dotcloud
|
||||
A /go/src/github.com/dotcloud/docker
|
||||
A /go/src/github.com/dotcloud/docker/.git
|
||||
....
|
||||
Inspect changes on a container's filesystem
|
||||
|
||||
.. _cli_events:
|
||||
|
||||
@@ -307,9 +224,6 @@ for example:
|
||||
Usage: docker events
|
||||
|
||||
Get real time events from the server
|
||||
|
||||
-since="": Show previously created events and then stream.
|
||||
(either seconds since epoch, or date string as below)
|
||||
|
||||
.. _cli_events_example:
|
||||
|
||||
@@ -322,7 +236,7 @@ Shell 1: Listening for events
|
||||
.............................
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
||||
$ sudo docker events
|
||||
|
||||
Shell 2: Start and Stop a Container
|
||||
@@ -342,23 +256,6 @@ Shell 1: (Again .. now showing events)
|
||||
[2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die
|
||||
[2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop
|
||||
|
||||
Show events in the past from a specified time
|
||||
.............................................
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo docker events -since 1378216169
|
||||
[2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die
|
||||
[2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop
|
||||
|
||||
$ sudo docker events -since '2013-09-03'
|
||||
[2013-09-03 15:49:26 +0200 CEST] 4386fb97867d: (from 12de384bfb10) start
|
||||
[2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die
|
||||
[2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop
|
||||
|
||||
$ sudo docker events -since '2013-09-03 15:49:29 +0200 CEST'
|
||||
[2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die
|
||||
[2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop
|
||||
|
||||
.. _cli_export:
|
||||
|
||||
@@ -382,43 +279,6 @@ Show events in the past from a specified time
|
||||
|
||||
Show the history of an image
|
||||
|
||||
-notrunc=false: Don't truncate output
|
||||
-q=false: only show numeric IDs
|
||||
|
||||
To see how the docker:latest image was built:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ docker history docker
|
||||
ID CREATED CREATED BY
|
||||
docker:latest 19 hours ago /bin/sh -c #(nop) ADD . in /go/src/github.com/dotcloud/docker
|
||||
cf5f2467662d 2 weeks ago /bin/sh -c #(nop) ENTRYPOINT ["hack/dind"]
|
||||
3538fbe372bf 2 weeks ago /bin/sh -c #(nop) WORKDIR /go/src/github.com/dotcloud/docker
|
||||
7450f65072e5 2 weeks ago /bin/sh -c #(nop) VOLUME /var/lib/docker
|
||||
b79d62b97328 2 weeks ago /bin/sh -c apt-get install -y -q lxc
|
||||
36714852a550 2 weeks ago /bin/sh -c apt-get install -y -q iptables
|
||||
8c4c706df1d6 2 weeks ago /bin/sh -c /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEYn' > /.s3cfg
|
||||
b89989433c48 2 weeks ago /bin/sh -c pip install python-magic
|
||||
a23e640d85b5 2 weeks ago /bin/sh -c pip install s3cmd
|
||||
41f54fec7e79 2 weeks ago /bin/sh -c apt-get install -y -q python-pip
|
||||
d9bc04add907 2 weeks ago /bin/sh -c apt-get install -y -q reprepro dpkg-sig
|
||||
e74f4760fa70 2 weeks ago /bin/sh -c gem install --no-rdoc --no-ri fpm
|
||||
1e43224726eb 2 weeks ago /bin/sh -c apt-get install -y -q ruby1.9.3 rubygems libffi-dev
|
||||
460953ae9d7f 2 weeks ago /bin/sh -c #(nop) ENV GOPATH=/go:/go/src/github.com/dotcloud/docker/vendor
|
||||
8b63eb1d666b 2 weeks ago /bin/sh -c #(nop) ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/goroot/bin
|
||||
3087f3bcedf2 2 weeks ago /bin/sh -c #(nop) ENV GOROOT=/goroot
|
||||
635840d198e5 2 weeks ago /bin/sh -c cd /goroot/src && ./make.bash
|
||||
439f4a0592ba 2 weeks ago /bin/sh -c curl -s https://go.googlecode.com/files/go1.1.2.src.tar.gz | tar -v -C / -xz && mv /go /goroot
|
||||
13967ed36e93 2 weeks ago /bin/sh -c #(nop) ENV CGO_ENABLED=0
|
||||
bf7424458437 2 weeks ago /bin/sh -c apt-get install -y -q build-essential
|
||||
a89ec997c3bf 2 weeks ago /bin/sh -c apt-get install -y -q mercurial
|
||||
b9f165c6e749 2 weeks ago /bin/sh -c apt-get install -y -q git
|
||||
17a64374afa7 2 weeks ago /bin/sh -c apt-get install -y -q curl
|
||||
d5e85dc5b1d8 2 weeks ago /bin/sh -c apt-get update
|
||||
13e642467c11 2 weeks ago /bin/sh -c echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > /etc/apt/sources.list
|
||||
ae6dde92a94e 2 weeks ago /bin/sh -c #(nop) MAINTAINER Solomon Hykes <solomon@dotcloud.com>
|
||||
ubuntu:12.04 6 months ago
|
||||
|
||||
.. _cli_images:
|
||||
|
||||
``images``
|
||||
@@ -430,86 +290,20 @@ To see how the docker:latest image was built:
|
||||
|
||||
List images
|
||||
|
||||
-a=false: show all images (by default filter out the intermediate images used to build)
|
||||
-notrunc=false: Don't truncate output
|
||||
-a=false: show all images
|
||||
-q=false: only show numeric IDs
|
||||
-tree=false: output graph in tree format
|
||||
-viz=false: output graph in graphviz format
|
||||
|
||||
Listing the most recently created images
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo docker images | head
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
<none> <none> 77af4d6b9913 19 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
committest latest b6fa739cedf5 19 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
<none> <none> 78a85c484f71 19 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
docker latest 30557a29d5ab 20 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
<none> <none> 0124422dd9f9 20 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
<none> <none> 18ad6fad3402 22 hours ago 23.68 MB (virtual 1.082 GB)
|
||||
<none> <none> f9f1e26352f0 23 hours ago 30.46 MB (virtual 1.089 GB)
|
||||
tryout latest 2629d1fa0b81 23 hours ago 16.4 kB (virtual 131.5 MB)
|
||||
<none> <none> 5ed6274db6ce 24 hours ago 30.44 MB (virtual 1.089 GB)
|
||||
|
||||
Listing the full length image IDs
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo docker images -notrunc | head
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
<none> <none> 77af4d6b9913e693e8d0b4b294fa62ade6054e6b2f1ffb617ac955dd63fb0182 19 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
committest latest b6fa739cedf5ea12a620a439402b6004d057da800f91c7524b5086a5e4749c9f 19 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
<none> <none> 78a85c484f71509adeaace20e72e941f6bdd2b25b4c75da8693efd9f61a37921 19 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
docker latest 30557a29d5abc51e5f1d5b472e79b7e296f595abcf19fe6b9199dbbc809c6ff4 20 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
<none> <none> 0124422dd9f9cf7ef15c0617cda3931ee68346455441d66ab8bdc5b05e9fdce5 20 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
<none> <none> 18ad6fad340262ac2a636efd98a6d1f0ea775ae3d45240d3418466495a19a81b 22 hours ago 23.68 MB (virtual 1.082 GB)
|
||||
<none> <none> f9f1e26352f0a3ba6a0ff68167559f64f3e21ff7ada60366e2d44a04befd1d3a 23 hours ago 30.46 MB (virtual 1.089 GB)
|
||||
tryout latest 2629d1fa0b81b222fca63371ca16cbf6a0772d07759ff80e8d1369b926940074 23 hours ago 16.4 kB (virtual 131.5 MB)
|
||||
<none> <none> 5ed6274db6ceb2397844896966ea239290555e74ef307030ebb01ff91b1914df 24 hours ago 30.44 MB (virtual 1.089 GB)
|
||||
-viz=false: output in graphviz format
|
||||
|
||||
Displaying images visually
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
::
|
||||
|
||||
$ sudo docker images -viz | dot -Tpng -o docker.png
|
||||
sudo docker images -viz | dot -Tpng -o docker.png
|
||||
|
||||
.. image:: docker_images.gif
|
||||
:alt: Example inheritance graph of Docker images.
|
||||
|
||||
|
||||
Displaying image hierarchy
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo docker images -tree
|
||||
|
||||
|─8dbd9e392a96 Size: 131.5 MB (virtual 131.5 MB) Tags: ubuntu:12.04,ubuntu:latest,ubuntu:precise
|
||||
└─27cf78414709 Size: 180.1 MB (virtual 180.1 MB)
|
||||
└─b750fe79269d Size: 24.65 kB (virtual 180.1 MB) Tags: ubuntu:12.10,ubuntu:quantal
|
||||
|─f98de3b610d5 Size: 12.29 kB (virtual 180.1 MB)
|
||||
| └─7da80deb7dbf Size: 16.38 kB (virtual 180.1 MB)
|
||||
| └─65ed2fee0a34 Size: 20.66 kB (virtual 180.2 MB)
|
||||
| └─a2b9ea53dddc Size: 819.7 MB (virtual 999.8 MB)
|
||||
| └─a29b932eaba8 Size: 28.67 kB (virtual 999.9 MB)
|
||||
| └─e270a44f124d Size: 12.29 kB (virtual 999.9 MB) Tags: progrium/buildstep:latest
|
||||
└─17e74ac162d8 Size: 53.93 kB (virtual 180.2 MB)
|
||||
└─339a3f56b760 Size: 24.65 kB (virtual 180.2 MB)
|
||||
└─904fcc40e34d Size: 96.7 MB (virtual 276.9 MB)
|
||||
└─b1b0235328dd Size: 363.3 MB (virtual 640.2 MB)
|
||||
└─7cb05d1acb3b Size: 20.48 kB (virtual 640.2 MB)
|
||||
└─47bf6f34832d Size: 20.48 kB (virtual 640.2 MB)
|
||||
└─f165104e82ed Size: 12.29 kB (virtual 640.2 MB)
|
||||
└─d9cf85a47b7e Size: 1.911 MB (virtual 642.2 MB)
|
||||
└─3ee562df86ca Size: 17.07 kB (virtual 642.2 MB)
|
||||
└─b05fc2d00e4a Size: 24.96 kB (virtual 642.2 MB)
|
||||
└─c96a99614930 Size: 12.29 kB (virtual 642.2 MB)
|
||||
└─a6a357a48c49 Size: 12.29 kB (virtual 642.2 MB) Tags: ndj/mongodb:latest
|
||||
|
||||
.. _cli_import:
|
||||
|
||||
``import``
|
||||
@@ -517,7 +311,7 @@ Displaying image hierarchy
|
||||
|
||||
::
|
||||
|
||||
Usage: docker import URL|- [REPOSITORY[:TAG]]
|
||||
Usage: docker import URL|- [REPOSITORY [TAG]]
|
||||
|
||||
Create a new filesystem image from the contents of a tarball
|
||||
|
||||
@@ -533,16 +327,14 @@ Examples
|
||||
Import from a remote location
|
||||
.............................
|
||||
|
||||
This will create a new untagged image.
|
||||
|
||||
``$ sudo docker import http://example.com/exampleimage.tgz``
|
||||
``$ sudo docker import http://example.com/exampleimage.tgz exampleimagerepo``
|
||||
|
||||
Import from a local file
|
||||
........................
|
||||
|
||||
Import to docker via pipe and standard in
|
||||
|
||||
``$ cat exampleimage.tgz | sudo docker import - exampleimagelocal:new``
|
||||
``$ cat exampleimage.tgz | sudo docker import - exampleimagelocal``
|
||||
|
||||
Import from a local directory
|
||||
.............................
|
||||
@@ -565,21 +357,6 @@ might not get preserved.
|
||||
|
||||
Display system-wide information.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo docker info
|
||||
Containers: 292
|
||||
Images: 194
|
||||
Debug mode (server): false
|
||||
Debug mode (client): false
|
||||
Fds: 22
|
||||
Goroutines: 67
|
||||
LXC Version: 0.9.0
|
||||
EventsListeners: 115
|
||||
Kernel Version: 3.8.0-33-generic
|
||||
WARNING: No swap limit support
|
||||
|
||||
|
||||
.. _cli_insert:
|
||||
|
||||
``insert``
|
||||
@@ -619,29 +396,36 @@ Insert file from github
|
||||
|
||||
::
|
||||
|
||||
Usage: docker kill CONTAINER [CONTAINER...]
|
||||
Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
|
||||
|
||||
Kill a running container (Send SIGKILL)
|
||||
Kill a running container
|
||||
|
||||
The main process inside the container will be sent SIGKILL.
|
||||
.. _cli_link:
|
||||
|
||||
Known Issues (kill)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* :issue:`197` indicates that ``docker kill`` may leave directories
|
||||
behind and make it difficult to remove the container.
|
||||
|
||||
.. _cli_load:
|
||||
|
||||
``load``
|
||||
``link``
|
||||
--------
|
||||
|
||||
::
|
||||
|
||||
Usage: docker load < repository.tar
|
||||
Usage: docker link CURRENT_NAME NEW_NAME
|
||||
|
||||
Loads a tarred repository from the standard input stream.
|
||||
Restores both images and tags.
|
||||
Link a container to a new name.
|
||||
|
||||
|
||||
Examples:
|
||||
~~~~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ docker link /59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc /redis
|
||||
$ docker ls
|
||||
NAME ID IMAGE
|
||||
/redis 59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc crosbymichael/redis:latest
|
||||
/59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc 59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc crosbymichael/redis:latest
|
||||
|
||||
|
||||
This will create a new link for the existing name ``/59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc``
|
||||
with the new name ``/redis`` so that we can new reference the same container under the new name ``/redis``.
|
||||
|
||||
.. _cli_login:
|
||||
|
||||
@@ -750,13 +534,7 @@ Known Issues (kill)
|
||||
|
||||
Remove one or more containers
|
||||
-link="": Remove the link instead of the actual container
|
||||
|
||||
Known Issues (rm)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* :issue:`197` indicates that ``docker kill`` may leave directories
|
||||
behind and make it difficult to remove the container.
|
||||
|
||||
|
||||
|
||||
Examples:
|
||||
~~~~~~~~~
|
||||
@@ -779,15 +557,6 @@ This will remove the container referenced under the link ``/redis``.
|
||||
This will remove the underlying link between ``/webapp`` and the ``/redis`` containers removing all
|
||||
network communication.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ docker rm `docker ps -a -q`
|
||||
|
||||
|
||||
This command will delete all stopped containers. The command ``docker ps -a -q`` will return all
|
||||
existing container IDs and pass them to the ``rm`` command which will delete them. Any running
|
||||
containers will not be deleted.
|
||||
|
||||
.. _cli_rmi:
|
||||
|
||||
``rmi``
|
||||
@@ -818,7 +587,7 @@ containers will not be deleted.
|
||||
-h="": Container host name
|
||||
-i=false: Keep stdin open even if not attached
|
||||
-privileged=false: Give extended privileges to this container
|
||||
-m="": Memory limit (format: <number><optional unit>, where unit = b, k, m or g)
|
||||
-m=0: Memory limit (in bytes)
|
||||
-n=true: Enable networking for this container
|
||||
-p=[]: Map a network port to the container
|
||||
-rm=false: Automatically remove the container when it exits (incompatible with -d)
|
||||
@@ -826,15 +595,12 @@ containers will not be deleted.
|
||||
-u="": Username or UID
|
||||
-dns=[]: Set custom dns servers for the container
|
||||
-v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "container-dir" is missing, then docker creates a new volume.
|
||||
-volumes-from="": Mount all volumes from the given container(s)
|
||||
-volumes-from="": Mount all volumes from the given container
|
||||
-entrypoint="": Overwrite the default entrypoint set by the image
|
||||
-w="": Working directory inside the container
|
||||
-lxc-conf=[]: Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
|
||||
-sig-proxy=true: Proxify all received signal to the process (even in non-tty mode)
|
||||
-expose=[]: Expose a port from the container without publishing it to your host
|
||||
-link="": Add link to another container (name:alias)
|
||||
-name="": Assign the specified name to the container. If no name is specific docker will generate a random name
|
||||
-P=false: Publish all exposed ports to the host interfaces
|
||||
-link="": Add link to another container (containerid:alias)
|
||||
|
||||
Examples
|
||||
--------
|
||||
@@ -871,81 +637,51 @@ use-cases, like running Docker within Docker.
|
||||
|
||||
docker run -w /path/to/dir/ -i -t ubuntu pwd
|
||||
|
||||
The ``-w`` lets the command being executed inside directory given,
|
||||
here /path/to/dir/. If the path does not exists it is created inside the
|
||||
The ``-w`` lets the command being executed inside directory given,
|
||||
here /path/to/dir/. If the path does not exists it is created inside the
|
||||
container.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -v `pwd`:`pwd` -w `pwd` -i -t ubuntu pwd
|
||||
|
||||
The ``-v`` flag mounts the current working directory into the container.
|
||||
The ``-w`` lets the command being executed inside the current
|
||||
The ``-v`` flag mounts the current working directory into the container.
|
||||
The ``-w`` lets the command being executed inside the current
|
||||
working directory, by changing into the directory to the value
|
||||
returned by ``pwd``. So this combination executes the command
|
||||
using the container, but inside the current working directory.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -p 127.0.0.1:80:8080 ubuntu bash
|
||||
docker run -p 127.0.0.0::80 ubuntu bash
|
||||
|
||||
This binds port ``8080`` of the container to port ``80`` on 127.0.0.1 of the
|
||||
host machine. :ref:`port_redirection` explains in detail how to manipulate ports
|
||||
in Docker.
|
||||
This the ``-p`` flag now allows you to bind a port to a specific
|
||||
interface of the host machine. In this example port ``80`` of the
|
||||
container will have a dynamically allocated port bound to 127.0.0.1
|
||||
of the host.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -p 127.0.0.1:80:80 ubuntu bash
|
||||
|
||||
This will bind port ``80`` of the container to port ``80`` on 127.0.0.1 of your
|
||||
host machine.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -expose 80 ubuntu bash
|
||||
|
||||
This exposes port ``80`` of the container for use within a link without
|
||||
publishing the port to the host system's interfaces. :ref:`port_redirection`
|
||||
explains in detail how to manipulate ports in Docker.
|
||||
This will expose port ``80`` of the container for use within a link
|
||||
without publishing the port to the host system's interfaces.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -name console -t -i ubuntu bash
|
||||
docker run -link /redis:redis ubuntu bash
|
||||
|
||||
This will create and run a new container with the container name
|
||||
being ``console``.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -link /redis:redis -name console ubuntu bash
|
||||
|
||||
The ``-link`` flag will link the container named ``/redis`` into the
|
||||
The ``-link`` flag will link the container named ``/redis`` into the
|
||||
newly created container with the alias ``redis``. The new container
|
||||
can access the network and environment of the redis container via
|
||||
environment variables. The ``-name`` flag will assign the name ``console``
|
||||
to the newly created container.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -volumes-from 777f7dc92da7,ba8c0c54f0f2:ro -i -t ubuntu pwd
|
||||
|
||||
The ``-volumes-from`` flag mounts all the defined volumes from the
|
||||
refrence containers. Containers can be specified by a comma seperated
|
||||
list or by repetitions of the ``-volumes-from`` argument. The container
|
||||
id may be optionally suffixed with ``:ro`` or ``:rw`` to mount the volumes in
|
||||
read-only or read-write mode, respectively. By default, the volumes are mounted
|
||||
in the same mode (rw or ro) as the reference container.
|
||||
|
||||
Known Issues (run -volumes-from)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* :issue:`2702`: "lxc-start: Permission denied - failed to mount"
|
||||
could indicate a permissions problem with AppArmor. Please see the
|
||||
issue for a workaround.
|
||||
|
||||
.. _cli_save:
|
||||
|
||||
``save``
|
||||
|
||||
::
|
||||
|
||||
Usage: docker save image > repository.tar
|
||||
|
||||
Streams a tarred repository to the standard output stream.
|
||||
Contains all parent layers, and all tags + versions.
|
||||
environment variables.
|
||||
|
||||
.. _cli_search:
|
||||
|
||||
@@ -956,11 +692,8 @@ Known Issues (run -volumes-from)
|
||||
|
||||
Usage: docker search TERM
|
||||
|
||||
Search the docker index for images
|
||||
|
||||
-notrunc=false: Don't truncate output
|
||||
-stars=0: Only displays with at least xxx stars
|
||||
-trusted=false: Only show trusted builds
|
||||
Searches for the TERM parameter on the Docker index and prints out
|
||||
a list of repositories that match.
|
||||
|
||||
.. _cli_start:
|
||||
|
||||
@@ -973,9 +706,6 @@ Known Issues (run -volumes-from)
|
||||
|
||||
Start a stopped container
|
||||
|
||||
-a=false: Attach container's stdout/stderr and forward all signals to the process
|
||||
-i=false: Attach container's stdin
|
||||
|
||||
.. _cli_stop:
|
||||
|
||||
``stop``
|
||||
@@ -985,12 +715,10 @@ Known Issues (run -volumes-from)
|
||||
|
||||
Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
|
||||
|
||||
Stop a running container (Send SIGTERM, and then SIGKILL after grace period)
|
||||
Stop a running container
|
||||
|
||||
-t=10: Number of seconds to wait for the container to stop before killing it.
|
||||
|
||||
The main process inside the container will receive SIGTERM, and after a grace period, SIGKILL
|
||||
|
||||
.. _cli_tag:
|
||||
|
||||
``tag``
|
||||
@@ -998,7 +726,7 @@ The main process inside the container will receive SIGTERM, and after a grace pe
|
||||
|
||||
::
|
||||
|
||||
Usage: docker tag [OPTIONS] IMAGE REPOSITORY[:TAG]
|
||||
Usage: docker tag [OPTIONS] IMAGE REPOSITORY [TAG]
|
||||
|
||||
Tag an image into a repository
|
||||
|
||||
@@ -1011,7 +739,7 @@ The main process inside the container will receive SIGTERM, and after a grace pe
|
||||
|
||||
::
|
||||
|
||||
Usage: docker top CONTAINER [ps OPTIONS]
|
||||
Usage: docker top CONTAINER
|
||||
|
||||
Lookup the running processes of a container
|
||||
|
||||
@@ -1033,3 +761,6 @@ Show the version of the docker client, daemon, and latest released version.
|
||||
Usage: docker wait [OPTIONS] NAME
|
||||
|
||||
Block until a container stops, then print its exit code.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -40,11 +40,7 @@ html_additional_pages = {
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinxcontrib.httpdomain', 'sphinx.ext.extlinks']
|
||||
|
||||
# Configure extlinks
|
||||
extlinks = { 'issue': ('https://github.com/dotcloud/docker/issues/%s',
|
||||
'Issue ') }
|
||||
extensions = ['sphinxcontrib.httpdomain']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
@@ -235,7 +231,7 @@ latex_documents = [
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('commandline/cli', 'docker', u'Docker Documentation',
|
||||
('toctree', 'docker', u'Docker Documentation',
|
||||
[u'Team Docker'], 1)
|
||||
]
|
||||
|
||||
|
||||
@@ -5,21 +5,5 @@
|
||||
Contributing to Docker
|
||||
======================
|
||||
|
||||
Want to hack on Docker? Awesome!
|
||||
Want to hack on Docker? Awesome! The repository includes `all the instructions you need to get started <https://github.com/dotcloud/docker/blob/master/CONTRIBUTING.md>`_.
|
||||
|
||||
The repository includes `all the instructions you need to get
|
||||
started <https://github.com/dotcloud/docker/blob/master/CONTRIBUTING.md>`_.
|
||||
|
||||
The `developer environment Dockerfile
|
||||
<https://github.com/dotcloud/docker/blob/master/Dockerfile>`_
|
||||
specifies the tools and versions used to test and build Docker.
|
||||
|
||||
If you're making changes to the documentation, see the
|
||||
`README.md <https://github.com/dotcloud/docker/blob/master/docs/README.md>`_.
|
||||
|
||||
The `documentation environment Dockerfile
|
||||
<https://github.com/dotcloud/docker/blob/master/docs/Dockerfile>`_
|
||||
specifies the tools and versions used to build the Documentation.
|
||||
|
||||
Further interesting details can be found in the `Packaging hints
|
||||
<https://github.com/dotcloud/docker/blob/master/hack/PACKAGERS.md>`_.
|
||||
|
||||
@@ -56,7 +56,7 @@ To create the Docker binary, run this command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker run -privileged -v `pwd`:/go/src/github.com/dotcloud/docker docker hack/make.sh binary
|
||||
sudo docker run -lxc-conf=lxc.aa_profile=unconfined -privileged -v `pwd`:/go/src/github.com/dotcloud/docker docker hack/make.sh binary
|
||||
|
||||
This will create the Docker binary in ``./bundles/<version>-dev/binary/``
|
||||
|
||||
@@ -64,15 +64,20 @@ This will create the Docker binary in ``./bundles/<version>-dev/binary/``
|
||||
Step 5: Run the Tests
|
||||
---------------------
|
||||
|
||||
To run the Docker test cases you first need to disable `AppArmor <https://wiki.ubuntu.com/AppArmor>`_ using the following commands
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo /etc/init.d/apparmor stop
|
||||
sudo /etc/init.d/apparmor teardown
|
||||
|
||||
To execute the test cases, run this command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker run -privileged -v `pwd`:/go/src/github.com/dotcloud/docker docker hack/make.sh test
|
||||
sudo docker run -lxc-conf=lxc.aa_profile=unconfined -privileged -v `pwd`:/go/src/github.com/dotcloud/docker docker hack/make.sh test
|
||||
|
||||
|
||||
Note: if you're running the tests in vagrant, you need to specify a dns entry in the command: `-dns 8.8.8.8`
|
||||
|
||||
If the test are successful then the tail of the output should look something like this
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -10,7 +10,7 @@ CouchDB Service
|
||||
.. include:: example_header.inc
|
||||
|
||||
Here's an example of using data volumes to share the same data between
|
||||
two CouchDB containers. This could be used for hot upgrades, testing
|
||||
2 CouchDB containers. This could be used for hot upgrades, testing
|
||||
different versions of CouchDB on the same data, etc.
|
||||
|
||||
Create first database
|
||||
@@ -20,28 +20,28 @@ Note that we're marking ``/var/lib/couchdb`` as a data volume.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
COUCH1=$(sudo docker run -d -p 5984 -v /var/lib/couchdb shykes/couchdb:2013-05-03)
|
||||
COUCH1=$(sudo docker run -d -v /var/lib/couchdb shykes/couchdb:2013-05-03)
|
||||
|
||||
Add data to the first database
|
||||
------------------------------
|
||||
|
||||
We're assuming your Docker host is reachable at ``localhost``. If not,
|
||||
replace ``localhost`` with the public IP of your Docker host.
|
||||
We're assuming your docker host is reachable at `localhost`. If not,
|
||||
replace `localhost` with the public IP of your docker host.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
HOST=localhost
|
||||
URL="http://$HOST:$(sudo docker port $COUCH1 5984 | grep -Po '\d+$')/_utils/"
|
||||
URL="http://$HOST:$(sudo docker port $COUCH1 5984)/_utils/"
|
||||
echo "Navigate to $URL in your browser, and use the couch interface to add data"
|
||||
|
||||
Create second database
|
||||
----------------------
|
||||
|
||||
This time, we're requesting shared access to ``$COUCH1``'s volumes.
|
||||
This time, we're requesting shared access to $COUCH1's volumes.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
COUCH2=$(sudo docker run -d -p 5984 -volumes-from $COUCH1 shykes/couchdb:2013-05-03)
|
||||
COUCH2=$(sudo docker run -d -volumes-from $COUCH1 shykes/couchdb:2013-05-03)
|
||||
|
||||
Browse data on the second database
|
||||
----------------------------------
|
||||
@@ -49,8 +49,8 @@ Browse data on the second database
|
||||
.. code-block:: bash
|
||||
|
||||
HOST=localhost
|
||||
URL="http://$HOST:$(sudo docker port $COUCH2 5984 | grep -Po '\d+$')/_utils/"
|
||||
URL="http://$HOST:$(sudo docker port $COUCH2 5984)/_utils/"
|
||||
echo "Navigate to $URL in your browser. You should see the same data as in the first database"'!'
|
||||
|
||||
Congratulations, you are now running two Couchdb containers, completely
|
||||
Congratulations, you are running 2 Couchdb containers, completely
|
||||
isolated from each other *except* for their data.
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
|
||||
.. note::
|
||||
|
||||
* This example assumes you have Docker running in daemon mode. For
|
||||
more information please see :ref:`running_examples`.
|
||||
* **If you don't like sudo** then see :ref:`dockergroup`
|
||||
|
||||
This example assumes you have Docker running in daemon mode. For more information please see :ref:`running_examples`
|
||||
|
||||
@@ -12,16 +12,16 @@ Hello World
|
||||
Running the Examples
|
||||
====================
|
||||
|
||||
All the examples assume your machine is running the ``docker`` daemon. To
|
||||
run the ``docker`` daemon in the background, simply type:
|
||||
All the examples assume your machine is running the docker daemon. To
|
||||
run the docker daemon in the background, simply type:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker -d &
|
||||
|
||||
Now you can run Docker in client mode: by default all commands will be
|
||||
Now you can run docker in client mode: by default all commands will be
|
||||
forwarded to the ``docker`` daemon via a protected Unix socket, so you
|
||||
must run as the ``root`` or via the ``sudo`` command.
|
||||
must run as root.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -38,24 +38,23 @@ Hello World
|
||||
|
||||
This is the most basic example available for using Docker.
|
||||
|
||||
Download the base image which is named ``ubuntu``:
|
||||
Download the base image (named "ubuntu"):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Download an ubuntu image
|
||||
sudo docker pull ubuntu
|
||||
|
||||
Alternatively to the ``ubuntu`` image, you can select ``busybox``, a bare
|
||||
Alternatively to the *ubuntu* image, you can select *busybox*, a bare
|
||||
minimal Linux system. The images are retrieved from the Docker
|
||||
repository.
|
||||
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
#run a simple echo command, that will echo hello world back to the console over standard out.
|
||||
sudo docker run ubuntu /bin/echo hello world
|
||||
|
||||
This command will run a simple ``echo`` command, that will echo ``hello world`` back to the console over standard out.
|
||||
|
||||
**Explanation:**
|
||||
|
||||
- **"sudo"** execute the following commands as user *root*
|
||||
@@ -101,9 +100,9 @@ we stop it.
|
||||
CONTAINER_ID=$(sudo docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done")
|
||||
|
||||
We are going to run a simple hello world daemon in a new container
|
||||
made from the ``ubuntu`` image.
|
||||
made from the *ubuntu* image.
|
||||
|
||||
- **"sudo docker run -d "** run a command in a new container. We pass "-d"
|
||||
- **"docker run -d "** run a command in a new container. We pass "-d"
|
||||
so it runs as a daemon.
|
||||
- **"ubuntu"** is the image we want to run the command inside of.
|
||||
- **"/bin/sh -c"** is the command we want to run in the container
|
||||
@@ -127,12 +126,10 @@ Check the logs make sure it is working correctly.
|
||||
|
||||
sudo docker attach $CONTAINER_ID
|
||||
|
||||
Attach to the container to see the results in real-time.
|
||||
Attach to the container to see the results in realtime.
|
||||
|
||||
- **"docker attach**" This will allow us to attach to a background
|
||||
process to see what is going on.
|
||||
- **"-sig-proxy=true"** Proxify all received signal to the process
|
||||
(even in non-tty mode)
|
||||
- **$CONTAINER_ID** The Id of the container we want to attach too.
|
||||
|
||||
Exit from the container attachment by pressing Control-C.
|
||||
|
||||
@@ -10,7 +10,7 @@ Examples
|
||||
|
||||
Here are some examples of how to use Docker to create running
|
||||
processes, starting from a very simple *Hello World* and progressing
|
||||
to more substantial services like those which you might find in production.
|
||||
to more substantial services like you might find in production.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
@@ -24,3 +24,4 @@ to more substantial services like those which you might find in production.
|
||||
postgresql_service
|
||||
mongodb
|
||||
running_riak_service
|
||||
linking_into_redis
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
:title: Linking to an Redis container
|
||||
:description: Running redis linked into your web app
|
||||
:keywords: docker, example, networking, redis, link
|
||||
|
||||
.. _linking_redis:
|
||||
|
||||
Linking Redis
|
||||
=============
|
||||
|
||||
.. include:: example_header.inc
|
||||
|
||||
Building a redis container to link as a child of our web application.
|
||||
|
||||
Building the redis container
|
||||
----------------------------
|
||||
|
||||
We will use a pre-build version of redis from the index under
|
||||
the name ``crosbymichael/redis``. If you are interested in the
|
||||
Dockerfile that was used to build this container here it is.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Build redis from source
|
||||
# Make sure you have the redis source code checked out in
|
||||
# the same directory as this Dockerfile
|
||||
FROM ubuntu
|
||||
|
||||
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
|
||||
RUN apt-get update
|
||||
RUN apt-get upgrade -y
|
||||
|
||||
RUN apt-get install -y gcc make g++ build-essential libc6-dev tcl
|
||||
|
||||
ADD . /redis
|
||||
|
||||
RUN (cd /redis && make)
|
||||
RUN (cd /redis && make test)
|
||||
|
||||
RUN mkdir -p /redis-data
|
||||
VOLUME ["/redis-data"]
|
||||
EXPOSE 6379
|
||||
|
||||
ENTRYPOINT ["/redis/src/redis-server"]
|
||||
CMD ["--dir", "/redis-data"]
|
||||
|
||||
|
||||
We need to ``EXPOSE`` the default port of 6379 so that our link knows what ports
|
||||
to connect to our redis container on. If you do not expose any ports for the
|
||||
image then docker will not be able to establish the link between containers.
|
||||
|
||||
|
||||
Run the redis container
|
||||
-----------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -d -e PASSWORD=docker crosbymichael/redis --requirepass docker
|
||||
|
||||
This will run our redis container using the default port of 6379 and using
|
||||
as password to secure our service. Next we will link the redis container to
|
||||
a new name using ``docker link``.
|
||||
|
||||
|
||||
Linking an existing container
|
||||
-----------------------------
|
||||
|
||||
Docker will automatically create an initial link with the container's id but
|
||||
because the is long and not very user friendly we can link the container with
|
||||
a new name.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker link /39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 /redis
|
||||
|
||||
Now we can reference our running redis service using the friendly name ``/redis``.
|
||||
We can issue all the commands that you would expect; start, stop, attach, using the new name.
|
||||
|
||||
Linking redis as a child
|
||||
------------------------
|
||||
|
||||
Next we can start a new web application that has a dependency on redis and apply a link
|
||||
to connect both containers. If you noticed when running our redis service we did not use
|
||||
the ``-p`` option to publish the redis port to the host system. Redis exposed port 6379
|
||||
but we did not publish the port. This allows docker to prevent all network traffic to
|
||||
the redis container except when explicitly specified within a link. This is a big win
|
||||
for security.
|
||||
|
||||
|
||||
Now lets start our web application with a link into redis.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -t -i -link /redis:db ubuntu bash
|
||||
|
||||
root@4c01db0b339c:/# env
|
||||
|
||||
HOSTNAME=4c01db0b339c
|
||||
DB_NAME=/4c01db0b339cf19958731255a796ee072040a652f51652a4ade190ab8c27006f/db
|
||||
TERM=xterm
|
||||
DB_PORT=tcp://172.17.0.8:6379
|
||||
DB_PORT_6379_TCP=tcp://172.17.0.8:6379
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
PWD=/
|
||||
DB_ENV_PASSWORD=dockerpass
|
||||
SHLVL=1
|
||||
HOME=/
|
||||
container=lxc
|
||||
_=/usr/bin/env
|
||||
root@4c01db0b339c:/#
|
||||
|
||||
|
||||
When we inspect the environment of the linked container we can see a few extra environment
|
||||
variables have been added. When you specified ``-link /redis:db`` you are telling docker
|
||||
to link the container named ``/redis`` into this new container with the alias ``db``.
|
||||
Environment variables are prefixed with the alias so that the parent container can access
|
||||
network and environment information from the child.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# The name of the child container
|
||||
DB_NAME=/4c01db0b339cf19958731255a796ee072040a652f51652a4ade190ab8c27006f/db
|
||||
# The default protocol, ip, and port of the service running in the container
|
||||
DB_PORT=tcp://172.17.0.8:6379
|
||||
# A specific protocol, ip, and port of various services
|
||||
DB_PORT_6379_TCP=tcp://172.17.0.8:6379
|
||||
# Get environment variables of the container
|
||||
DB_ENV_PASSWORD=dockerpass
|
||||
|
||||
|
||||
Accessing the network information along with the environment of the child container allows
|
||||
us to easily connect to the redis service on the specific ip and port and use the password
|
||||
specified in the environment.
|
||||
@@ -10,8 +10,8 @@ Building an Image with MongoDB
|
||||
.. include:: example_header.inc
|
||||
|
||||
The goal of this example is to show how you can build your own
|
||||
Docker images with MongoDB pre-installed. We will do that by
|
||||
constructing a ``Dockerfile`` that downloads a base image, adds an
|
||||
docker images with MongoDB preinstalled. We will do that by
|
||||
constructing a Dockerfile that downloads a base image, adds an
|
||||
apt source and installs the database software on Ubuntu.
|
||||
|
||||
Creating a ``Dockerfile``
|
||||
@@ -41,7 +41,7 @@ Since we want to be running the latest version of MongoDB we'll need to add the
|
||||
RUN echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | tee /etc/apt/sources.list.d/10gen.list
|
||||
|
||||
Then, we don't want Ubuntu to complain about init not being available so we'll
|
||||
divert ``/sbin/initctl`` to ``/bin/true`` so it thinks everything is working.
|
||||
divert /sbin/initctl to /bin/true so it thinks everything is working.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -65,8 +65,8 @@ run without needing to provide a special configuration file)
|
||||
# Create the MongoDB data directory
|
||||
RUN mkdir -p /data/db
|
||||
|
||||
Finally, we'll expose the standard port that MongoDB runs on, 27107, as well as
|
||||
define an ``ENTRYPOINT`` instruction for the container.
|
||||
Finally, we'll expose the standard port that MongoDB runs on (27107) as well as
|
||||
define an ENTRYPOINT for the container.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -78,7 +78,7 @@ run all of the commands.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker build -t <yourname>/mongodb .
|
||||
docker build -t <yourname>/mongodb .
|
||||
|
||||
Now you should be able to run ``mongod`` as a daemon and be able to connect on
|
||||
the local port!
|
||||
@@ -86,13 +86,13 @@ the local port!
|
||||
.. code-block:: bash
|
||||
|
||||
# Regular style
|
||||
MONGO_ID=$(sudo docker run -d <yourname>/mongodb)
|
||||
MONGO_ID=$(docker run -d <yourname>/mongodb)
|
||||
|
||||
# Lean and mean
|
||||
MONGO_ID=$(sudo docker run -d <yourname>/mongodb --noprealloc --smallfiles)
|
||||
MONGO_ID=$(docker run -d <yourname>/mongodb --noprealloc --smallfiles)
|
||||
|
||||
# Check the logs out
|
||||
sudo docker logs $MONGO_ID
|
||||
docker logs $MONGO_ID
|
||||
|
||||
# Connect and play around
|
||||
mongo --port <port you get from `docker ps`>
|
||||
|
||||
@@ -10,7 +10,7 @@ Node.js Web App
|
||||
.. include:: example_header.inc
|
||||
|
||||
The goal of this example is to show you how you can build your own
|
||||
Docker images from a parent image using a ``Dockerfile`` . We will do
|
||||
docker images from a parent image using a ``Dockerfile`` . We will do
|
||||
that by making a simple Node.js hello world web application running on
|
||||
CentOS. You can get the full source code at
|
||||
https://github.com/gasi/docker-node-hello.
|
||||
@@ -55,7 +55,7 @@ Then, create an ``index.js`` file that defines a web app using the
|
||||
|
||||
|
||||
In the next steps, we’ll look at how you can run this app inside a CentOS
|
||||
container using Docker. First, you’ll need to build a Docker image of your app.
|
||||
container using docker. First, you’ll need to build a docker image of your app.
|
||||
|
||||
Creating a ``Dockerfile``
|
||||
+++++++++++++++++++++++++
|
||||
@@ -67,8 +67,8 @@ Create an empty file called ``Dockerfile``:
|
||||
touch Dockerfile
|
||||
|
||||
Open the ``Dockerfile`` in your favorite text editor and add the following line
|
||||
that defines the version of Docker the image requires to build
|
||||
(this example uses Docker 0.3.4):
|
||||
that defines the version of docker the image requires to build
|
||||
(this example uses docker 0.3.4):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -76,7 +76,7 @@ that defines the version of Docker the image requires to build
|
||||
|
||||
Next, define the parent image you want to use to build your own image on top of.
|
||||
Here, we’ll use `CentOS <https://index.docker.io/_/centos/>`_ (tag: ``6.4``)
|
||||
available on the `Docker index`_:
|
||||
available on the `docker index`_:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -95,23 +95,23 @@ To install the right package for CentOS, we’ll use the instructions from the
|
||||
# Install Node.js and npm
|
||||
RUN yum install -y npm
|
||||
|
||||
To bundle your app’s source code inside the Docker image, use the ``ADD``
|
||||
instruction:
|
||||
To bundle your app’s source code inside the docker image, use the ``ADD``
|
||||
command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Bundle app source
|
||||
ADD . /src
|
||||
|
||||
Install your app dependencies using the ``npm`` binary:
|
||||
Install your app dependencies using npm:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Install app dependencies
|
||||
RUN cd /src; npm install
|
||||
|
||||
Your app binds to port ``8080`` so you’ll use the ``EXPOSE`` instruction
|
||||
to have it mapped by the ``docker`` daemon:
|
||||
Your app binds to port ``8080`` so you’ll use the ``EXPOSE`` command
|
||||
to have it mapped by the docker daemon:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -152,7 +152,7 @@ Building your image
|
||||
+++++++++++++++++++
|
||||
|
||||
Go to the directory that has your ``Dockerfile`` and run the following
|
||||
command to build a Docker image. The ``-t`` flag let’s you tag your
|
||||
command to build a docker image. The ``-t`` flag let’s you tag your
|
||||
image so it’s easier to find later using the ``docker images``
|
||||
command:
|
||||
|
||||
@@ -160,7 +160,7 @@ command:
|
||||
|
||||
sudo docker build -t <your username>/centos-node-hello .
|
||||
|
||||
Your image will now be listed by Docker:
|
||||
Your image will now be listed by docker:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -176,11 +176,11 @@ Run the image
|
||||
+++++++++++++
|
||||
|
||||
Running your image with ``-d`` runs the container in detached mode, leaving the
|
||||
container running in the background. The ``-p`` flag redirects a public port to a private port in the container. Run the image you previously built:
|
||||
container running in the background. Run the image you previously built:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker run -p 49160:8080 -d <your username>/centos-node-hello
|
||||
sudo docker run -d <your username>/centos-node-hello
|
||||
|
||||
Print the output of your app:
|
||||
|
||||
@@ -199,17 +199,17 @@ Print the output of your app:
|
||||
Test
|
||||
++++
|
||||
|
||||
To test your app, get the the port of your app that Docker mapped:
|
||||
To test your app, get the the port of your app that docker mapped:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker ps
|
||||
docker ps
|
||||
|
||||
> # Example
|
||||
> ID IMAGE COMMAND ... PORTS
|
||||
> ecce33b30ebf gasi/centos-node-hello:latest node /src/index.js 49160->8080
|
||||
|
||||
In the example above, Docker mapped the ``8080`` port of the container to
|
||||
In the example above, docker mapped the ``8080`` port of the container to
|
||||
``49160``.
|
||||
|
||||
Now you can call your app using ``curl`` (install if needed via:
|
||||
@@ -229,7 +229,7 @@ Now you can call your app using ``curl`` (install if needed via:
|
||||
> Hello World
|
||||
|
||||
We hope this tutorial helped you get up and running with Node.js and
|
||||
CentOS on Docker. You can get the full source code at
|
||||
CentOS on docker. You can get the full source code at
|
||||
https://github.com/gasi/docker-node-hello.
|
||||
|
||||
Continue to :ref:`running_redis_service`.
|
||||
|
||||
@@ -13,7 +13,7 @@ PostgreSQL Service
|
||||
|
||||
.. note::
|
||||
|
||||
As of version 0.5.2, Docker requires root privileges to run.
|
||||
As of version 0.5.2, docker requires root privileges to run.
|
||||
You have to either manually adjust your system configuration (permissions on
|
||||
/var/run/docker.sock or sudo config), or prefix `docker` with `sudo`. Check
|
||||
`this thread`_ for details.
|
||||
@@ -24,7 +24,8 @@ PostgreSQL Service
|
||||
Installing PostgreSQL on Docker
|
||||
-------------------------------
|
||||
|
||||
For clarity I won't be showing command output.
|
||||
For clarity I won't be showing commands output.
|
||||
|
||||
|
||||
Run an interactive shell in Docker container.
|
||||
|
||||
@@ -61,7 +62,7 @@ Finally, install PostgreSQL 9.2
|
||||
|
||||
Now, create a PostgreSQL superuser role that can create databases and
|
||||
other roles. Following Vagrant's convention the role will be named
|
||||
``docker`` with ``docker`` password assigned to it.
|
||||
`docker` with `docker` password assigned to it.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -107,7 +108,7 @@ Bash prompt; you can also locate it using ``docker ps -a``.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker commit <container_id> <your username>/postgresql
|
||||
docker commit <container_id> <your username>/postgresql
|
||||
|
||||
Finally, run PostgreSQL server via ``docker``.
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ Python Web App
|
||||
.. include:: example_header.inc
|
||||
|
||||
The goal of this example is to show you how you can author your own
|
||||
Docker images using a parent image, making changes to it, and then
|
||||
docker images using a parent image, making changes to it, and then
|
||||
saving the results as a new image. We will do that by making a simple
|
||||
hello Flask web application image.
|
||||
hello flask web application image.
|
||||
|
||||
**Steps:**
|
||||
|
||||
@@ -20,37 +20,36 @@ hello Flask web application image.
|
||||
|
||||
sudo docker pull shykes/pybuilder
|
||||
|
||||
We are downloading the ``shykes/pybuilder`` Docker image
|
||||
We are downloading the "shykes/pybuilder" docker image
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
URL=http://github.com/shykes/helloflask/archive/master.tar.gz
|
||||
|
||||
We set a ``URL`` variable that points to a tarball of a simple helloflask web app
|
||||
We set a URL variable that points to a tarball of a simple helloflask web app
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
BUILD_JOB=$(sudo docker run -d -t shykes/pybuilder:latest /usr/local/bin/buildapp $URL)
|
||||
|
||||
Inside of the ``shykes/pybuilder`` image there is a command called
|
||||
``buildapp``, we are running that command and passing the ``$URL`` variable
|
||||
Inside of the "shykes/pybuilder" image there is a command called
|
||||
buildapp, we are running that command and passing the $URL variable
|
||||
from step 2 to it, and running the whole thing inside of a new
|
||||
container. The ``BUILD_JOB`` environment variable will be set with the new container ID.
|
||||
container. BUILD_JOB will be set with the new container_id.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker attach -sig-proxy=false $BUILD_JOB
|
||||
sudo docker attach $BUILD_JOB
|
||||
[...]
|
||||
|
||||
While this container is running, we can attach to the new container to
|
||||
see what is going on. The flag ``-sig-proxy`` set as ``false`` allows you to connect and
|
||||
disconnect (Ctrl-C) to it without stopping the container.
|
||||
see what is going on. Ctrl-C to disconnect.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker ps -a
|
||||
|
||||
List all Docker containers. If this container has already finished
|
||||
|
||||
List all docker containers. If this container has already finished
|
||||
running, it will still be listed here.
|
||||
|
||||
.. code-block:: bash
|
||||
@@ -58,8 +57,8 @@ running, it will still be listed here.
|
||||
BUILD_IMG=$(sudo docker commit $BUILD_JOB _/builds/github.com/shykes/helloflask/master)
|
||||
|
||||
Save the changes we just made in the container to a new image called
|
||||
``_/builds/github.com/hykes/helloflask/master`` and save the image ID in
|
||||
the ``BUILD_IMG`` variable name.
|
||||
``_/builds/github.com/hykes/helloflask/master`` and save the image id in
|
||||
the BUILD_IMG variable name.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -73,24 +72,24 @@ the ``BUILD_IMG`` variable name.
|
||||
- **/usr/local/bin/runapp** is the command which starts the web app.
|
||||
|
||||
Use the new image we just created and create a new container with
|
||||
network port 5000, and return the container ID and store in the
|
||||
``WEB_WORKER`` variable.
|
||||
network port 5000, and return the container id and store in the
|
||||
WEB_WORKER variable.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker logs $WEB_WORKER
|
||||
* Running on http://0.0.0.0:5000/
|
||||
|
||||
View the logs for the new container using the ``WEB_WORKER`` variable, and
|
||||
if everything worked as planned you should see the line ``Running on
|
||||
http://0.0.0.0:5000/`` in the log output.
|
||||
View the logs for the new container using the WEB_WORKER variable, and
|
||||
if everything worked as planned you should see the line "Running on
|
||||
http://0.0.0.0:5000/" in the log output.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
WEB_PORT=$(sudo docker port $WEB_WORKER 5000 | awk -F: '{ print $2 }')
|
||||
WEB_PORT=$(sudo docker port $WEB_WORKER 5000)
|
||||
|
||||
Look up the public-facing port which is NAT-ed. Find the private port
|
||||
used by the container and store it inside of the ``WEB_PORT`` variable.
|
||||
used by the container and store it inside of the WEB_PORT variable.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -98,8 +97,8 @@ used by the container and store it inside of the ``WEB_PORT`` variable.
|
||||
curl http://127.0.0.1:$WEB_PORT
|
||||
Hello world!
|
||||
|
||||
Access the web app using the ``curl`` binary. If everything worked as planned you
|
||||
should see the line ``Hello world!`` inside of your console.
|
||||
Access the web app using curl. If everything worked as planned you
|
||||
should see the line "Hello world!" inside of your console.
|
||||
|
||||
**Video:**
|
||||
|
||||
|
||||
@@ -9,93 +9,74 @@ Redis Service
|
||||
|
||||
.. include:: example_header.inc
|
||||
|
||||
Very simple, no frills, Redis service attached to a web application using a link.
|
||||
Very simple, no frills, redis service.
|
||||
|
||||
Create a docker container for Redis
|
||||
-----------------------------------
|
||||
|
||||
Firstly, we create a ``Dockerfile`` for our new Redis image.
|
||||
Open a docker container
|
||||
-----------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
FROM ubuntu:12.10
|
||||
RUN apt-get update
|
||||
RUN apt-get -y install redis-server
|
||||
EXPOSE 6379
|
||||
ENTRYPOINT ["/usr/bin/redis-server"]
|
||||
sudo docker run -i -t ubuntu /bin/bash
|
||||
|
||||
Next we build an image from our ``Dockerfile``. Replace ``<your username>``
|
||||
with your own user name.
|
||||
Building your image
|
||||
-------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker build -t <your username>/redis .
|
||||
|
||||
Run the service
|
||||
---------------
|
||||
|
||||
Use the image we've just created and name your container ``redis``.
|
||||
|
||||
Running the service with ``-d`` runs the container in detached mode, leaving the
|
||||
container running in the background.
|
||||
|
||||
Importantly, we're not exposing any ports on our container. Instead we're going to
|
||||
use a container link to provide access to our Redis database.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker run -name redis -d <your username>/redis
|
||||
|
||||
Create your web application container
|
||||
-------------------------------------
|
||||
|
||||
Next we can create a container for our application. We're going to use the ``-link``
|
||||
flag to create a link to the ``redis`` container we've just created with an alias of
|
||||
``db``. This will create a secure tunnel to the ``redis`` container and expose the
|
||||
Redis instance running inside that container to only this container.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker run -link redis:db -i -t ubuntu:12.10 /bin/bash
|
||||
|
||||
Once inside our freshly created container we need to install Redis to get the
|
||||
``redis-cli`` binary to test our connection.
|
||||
Update your Docker container, install the Redis server. Once
|
||||
installed, exit out of the Docker container.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
apt-get update
|
||||
apt-get -y install redis-server
|
||||
service redis-server stop
|
||||
apt-get install redis-server
|
||||
exit
|
||||
|
||||
Now we can test the connection. Firstly, let's look at the available environmental
|
||||
variables in our web application container. We can use these to get the IP and port
|
||||
of our ``redis`` container.
|
||||
Snapshot the installation
|
||||
-------------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
env
|
||||
. . .
|
||||
DB_NAME=/violet_wolf/db
|
||||
DB_PORT_6379_TCP_PORT=6379
|
||||
DB_PORT=tcp://172.17.0.33:6379
|
||||
DB_PORT_6379_TCP=tcp://172.17.0.33:6379
|
||||
DB_PORT_6379_TCP_ADDR=172.17.0.33
|
||||
DB_PORT_6379_TCP_PROTO=tcp
|
||||
docker ps -a # grab the container id (this will be the first one in the list)
|
||||
docker commit <container_id> <your username>/redis
|
||||
|
||||
We can see that we've got a small list of environment variables prefixed with ``DB``.
|
||||
The ``DB`` comes from the link alias specified when we launched the container. Let's use
|
||||
the ``DB_PORT_6379_TCP_ADDR`` variable to connect to our Redis container.
|
||||
Run the service
|
||||
---------------
|
||||
|
||||
Running the service with `-d` runs the container in detached mode, leaving the
|
||||
container running in the background. Use your snapshot.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
redis-cli -h $DB_PORT_6379_TCP_ADDR
|
||||
redis 172.17.0.33:6379>
|
||||
redis 172.17.0.33:6379> set docker awesome
|
||||
sudo docker run -d -p 6379 <your username>/redis /usr/bin/redis-server
|
||||
|
||||
Test 1
|
||||
++++++
|
||||
|
||||
Connect to the container with the redis-cli.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker ps # grab the new container id
|
||||
sudo docker inspect <container_id> # grab the ipaddress of the container
|
||||
redis-cli -h <ipaddress> -p 6379
|
||||
redis 10.0.3.32:6379> set docker awesome
|
||||
OK
|
||||
redis 172.17.0.33:6379> get docker
|
||||
redis 10.0.3.32:6379> get docker
|
||||
"awesome"
|
||||
redis 172.17.0.33:6379> exit
|
||||
redis 10.0.3.32:6379> exit
|
||||
|
||||
We could easily use this or other environment variables in our web application to make a
|
||||
connection to our ``redis`` container.
|
||||
Test 2
|
||||
++++++
|
||||
|
||||
Connect to the host os with the redis-cli.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker ps # grab the new container id
|
||||
sudo docker port <container_id> 6379 # grab the external port
|
||||
ip addr show # grab the host ip address
|
||||
redis-cli -h <host ipaddress> -p <external port>
|
||||
redis 192.168.0.1:49153> set docker awesome
|
||||
OK
|
||||
redis 192.168.0.1:49153> get docker
|
||||
"awesome"
|
||||
redis 192.168.0.1:49153> exit
|
||||
|
||||
@@ -107,7 +107,7 @@ Create a ``supervisord`` configuration file
|
||||
+++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
Create an empty file called ``supervisord.conf``. Make sure it's at the same
|
||||
directory level as your ``Dockerfile``:
|
||||
level as your ``Dockerfile``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
||||
@@ -12,14 +12,14 @@ SSH Daemon Service
|
||||
|
||||
**Video:**
|
||||
|
||||
I've created a little screencast to show how to create an SSHd service
|
||||
I've create a little screencast to show how to create a sshd service
|
||||
and connect to it. It is something like 11 minutes and not entirely
|
||||
smooth, but it gives you a good idea.
|
||||
smooth, but gives you a good idea.
|
||||
|
||||
.. note::
|
||||
This screencast was created before Docker version 0.5.2, so the
|
||||
This screencast was created before ``docker`` version 0.5.2, so the
|
||||
daemon is unprotected and available via a TCP port. When you run
|
||||
through the same steps in a newer version of Docker, you will
|
||||
through the same steps in a newer version of ``docker``, you will
|
||||
need to add ``sudo`` in front of each ``docker`` command in order
|
||||
to reach the daemon over its protected Unix socket.
|
||||
|
||||
@@ -29,14 +29,13 @@ smooth, but it gives you a good idea.
|
||||
<iframe width="800" height="400" src="http://ascii.io/a/2637/raw" frameborder="0"></iframe>
|
||||
</div>
|
||||
|
||||
You can also get this sshd container by using:
|
||||
|
||||
.. code-block:: bash
|
||||
You can also get this sshd container by using
|
||||
::
|
||||
|
||||
sudo docker pull dhrp/sshd
|
||||
|
||||
|
||||
The password is ``screencast``.
|
||||
The password is 'screencast'
|
||||
|
||||
**Video's Transcription:**
|
||||
|
||||
|
||||
@@ -22,10 +22,12 @@ Amazon QuickStart
|
||||
|
||||
1. **Choose an image:**
|
||||
|
||||
* Launch the `Create Instance Wizard` <https://console.aws.amazon.com/ec2/v2/home?#LaunchInstanceWizard:> menu on your AWS Console
|
||||
* Select "Community AMIs" option and serch for ``amd64 precise`` (click enter to search)
|
||||
* If you choose a EBS enabled AMI you will be able to launch a `t1.micro` instance (more info on `pricing` <http://aws.amazon.com/en/ec2/pricing/> )
|
||||
* When you click select you'll be taken to the instance setup, and you're one click away from having your Ubuntu VM up and running.
|
||||
* Open http://cloud-images.ubuntu.com/locator/ec2/
|
||||
* Enter ``amd64 precise`` in the search field (it will search as you
|
||||
type)
|
||||
* Pick an image by clicking on the image name. *An EBS-enabled
|
||||
image will let you use a t1.micro instance.* Clicking on the image
|
||||
name will take you to your AWS Console.
|
||||
|
||||
2. **Tell CloudInit to install Docker:**
|
||||
|
||||
@@ -100,45 +102,26 @@ Docker that way too. Vagrant 1.1 or higher is required.
|
||||
we need to set them there first. Make sure you have everything on
|
||||
amazon aws setup so you can (manually) deploy a new image to EC2.
|
||||
|
||||
Note that where possible these variables are the same as those honored by
|
||||
the ec2 api tools.
|
||||
::
|
||||
|
||||
export AWS_ACCESS_KEY=xxx
|
||||
export AWS_SECRET_KEY=xxx
|
||||
export AWS_ACCESS_KEY_ID=xxx
|
||||
export AWS_SECRET_ACCESS_KEY=xxx
|
||||
export AWS_KEYPAIR_NAME=xxx
|
||||
export SSH_PRIVKEY_PATH=xxx
|
||||
export AWS_SSH_PRIVKEY=xxx
|
||||
|
||||
export BOX_NAME=xxx
|
||||
export AWS_REGION=xxx
|
||||
export AWS_AMI=xxx
|
||||
export AWS_INSTANCE_TYPE=xxx
|
||||
The environment variables are:
|
||||
|
||||
The required environment variables are:
|
||||
|
||||
* ``AWS_ACCESS_KEY`` - The API key used to make requests to AWS
|
||||
* ``AWS_SECRET_KEY`` - The secret key to make AWS API requests
|
||||
* ``AWS_ACCESS_KEY_ID`` - The API key used to make requests to AWS
|
||||
* ``AWS_SECRET_ACCESS_KEY`` - The secret key to make AWS API requests
|
||||
* ``AWS_KEYPAIR_NAME`` - The name of the keypair used for this EC2 instance
|
||||
* ``SSH_PRIVKEY_PATH`` - The path to the private key for the named
|
||||
* ``AWS_SSH_PRIVKEY`` - The path to the private key for the named
|
||||
keypair, for example ``~/.ssh/docker.pem``
|
||||
|
||||
There are a number of optional environment variables:
|
||||
|
||||
* ``BOX_NAME`` - The name of the vagrant box to use. Defaults to
|
||||
``ubuntu``.
|
||||
* ``AWS_REGION`` - The aws region to spawn the vm in. Defaults to
|
||||
``us-east-1``.
|
||||
* ``AWS_AMI`` - The aws AMI to start with as a base. This must be
|
||||
be an ubuntu 12.04 precise image. You must change this value if
|
||||
``AWS_REGION`` is set to a value other than ``us-east-1``.
|
||||
This is because AMIs are region specific. Defaults to ``ami-69f5a900``.
|
||||
* ``AWS_INSTANCE_TYPE`` - The aws instance type. Defaults to ``t1.micro``.
|
||||
|
||||
You can check if they are set correctly by doing something like
|
||||
|
||||
::
|
||||
|
||||
echo $AWS_ACCESS_KEY
|
||||
echo $AWS_ACCESS_KEY_ID
|
||||
|
||||
6. Do the magic!
|
||||
|
||||
|
||||
@@ -19,12 +19,11 @@ Contents:
|
||||
|
||||
ubuntulinux
|
||||
binaries
|
||||
security
|
||||
upgrading
|
||||
kernel
|
||||
vagrant
|
||||
windows
|
||||
amazon
|
||||
rackspace
|
||||
archlinux
|
||||
gentoolinux
|
||||
upgrading
|
||||
kernel
|
||||
|
||||
@@ -25,7 +25,6 @@ If you cannot or do not want to use the "official" kernels,
|
||||
here is some technical background about the features (both optional and
|
||||
mandatory) that docker needs to run successfully.
|
||||
|
||||
|
||||
Linux version 3.8 or above
|
||||
--------------------------
|
||||
|
||||
@@ -40,15 +39,6 @@ The symptoms include:
|
||||
- kernel crash causing the machine to freeze for a few minutes, or even
|
||||
completely.
|
||||
|
||||
Additionally, kernels prior 3.4 did not implement ``reboot_pid_ns``,
|
||||
which means that the ``reboot()`` syscall could reboot the host machine,
|
||||
instead of terminating the container. To work around that problem,
|
||||
LXC userland tools (since version 0.8) automatically drop the ``SYS_BOOT``
|
||||
capability when necessary. Still, if you run a pre-3.4 kernel with pre-0.8
|
||||
LXC tools, be aware that containers can reboot the whole host! This is
|
||||
not something that Docker wants to address in the short term, since you
|
||||
shouldn't use kernels prior 3.8 with Docker anyway.
|
||||
|
||||
While it is still possible to use older kernels for development, it is
|
||||
really not advised to do so.
|
||||
|
||||
@@ -79,7 +69,7 @@ to run LXC containers. Note that 2.6.32 has some documented issues regarding
|
||||
network namespace setup and teardown; those issues are not a risk if you
|
||||
run containers in a private environment, but can lead to denial-of-service
|
||||
attacks if you want to run untrusted code in your containers. For more details,
|
||||
see `LP#720095 <https://bugs.launchpad.net/ubuntu/+source/linux/+bug/720095>`_.
|
||||
see `[LP#720095 <https://bugs.launchpad.net/ubuntu/+source/linux/+bug/720095>`_.
|
||||
|
||||
Kernels 2.6.38, and every version since 3.2, have been deployed successfully
|
||||
to run containerized production workloads. Feature-wise, there is no huge
|
||||
|
||||
@@ -1,267 +0,0 @@
|
||||
:title: Docker Security
|
||||
:description: Review of the Docker Daemon attack surface
|
||||
:keywords: Docker, Docker documentation, security
|
||||
|
||||
.. _dockersecurity:
|
||||
|
||||
Docker Security
|
||||
===============
|
||||
|
||||
*Adapted from* `Containers & Docker: How Secure are They? <blogsecurity>`_
|
||||
|
||||
There are three major areas to consider when reviewing Docker security:
|
||||
|
||||
* the intrinsic security of containers, as implemented by kernel
|
||||
namespaces and cgroups;
|
||||
* the attack surface of the Docker daemon itself;
|
||||
* the "hardening" security features of the kernel and how they
|
||||
interact with containers.
|
||||
|
||||
Kernel Namespaces
|
||||
-----------------
|
||||
|
||||
Docker containers are essentially LXC containers, and they come with
|
||||
the same security features. When you start a container with ``docker
|
||||
run``, behind the scenes Docker uses ``lxc-start`` to execute the
|
||||
Docker container. This creates a set of namespaces and control groups
|
||||
for the container. Those namespaces and control groups are not created
|
||||
by Docker itself, but by ``lxc-start``. This means that as the LXC
|
||||
userland tools evolve (and provide additional namespaces and isolation
|
||||
features), Docker will automatically make use of them.
|
||||
|
||||
**Namespaces provide the first and most straightforward form of
|
||||
isolation**: processes running within a container cannot see, and even
|
||||
less affect, processes running in another container, or in the host
|
||||
system.
|
||||
|
||||
**Each container also gets its own network stack**, meaning that a
|
||||
container doesn’t get a privileged access to the sockets or interfaces
|
||||
of another container. Of course, if the host system is setup
|
||||
accordingly, containers can interact with each other through their
|
||||
respective network interfaces — just like they can interact with
|
||||
external hosts. When you specify public ports for your containers or
|
||||
use :ref:`links <working_with_links_names>` then IP traffic is allowed
|
||||
between containers. They can ping each other, send/receive UDP
|
||||
packets, and establish TCP connections, but that can be restricted if
|
||||
necessary. From a network architecture point of view, all containers
|
||||
on a given Docker host are sitting on bridge interfaces. This means
|
||||
that they are just like physical machines connected through a common
|
||||
Ethernet switch; no more, no less.
|
||||
|
||||
How mature is the code providing kernel namespaces and private
|
||||
networking? Kernel namespaces were introduced `between kernel version
|
||||
2.6.15 and 2.6.26
|
||||
<http://lxc.sourceforge.net/index.php/about/kernel-namespaces/>`_. This
|
||||
means that since July 2008 (date of the 2.6.26 release, now 5 years
|
||||
ago), namespace code has been exercised and scrutinized on a large
|
||||
number of production systems. And there is more: the design and
|
||||
inspiration for the namespaces code are even older. Namespaces are
|
||||
actually an effort to reimplement the features of `OpenVZ
|
||||
<http://en.wikipedia.org/wiki/OpenVZ>`_ in such a way that they could
|
||||
be merged within the mainstream kernel. And OpenVZ was initially
|
||||
released in 2005, so both the design and the implementation are
|
||||
pretty mature.
|
||||
|
||||
Control Groups
|
||||
--------------
|
||||
|
||||
Control Groups are the other key component of Linux Containers. They
|
||||
implement resource accounting and limiting. They provide a lot of very
|
||||
useful metrics, but they also help to ensure that each container gets
|
||||
its fair share of memory, CPU, disk I/O; and, more importantly, that a
|
||||
single container cannot bring the system down by exhausting one of
|
||||
those resources.
|
||||
|
||||
So while they do not play a role in preventing one container from
|
||||
accessing or affecting the data and processes of another container,
|
||||
they are essential to fend off some denial-of-service attacks. They
|
||||
are particularly important on multi-tenant platforms, like public and
|
||||
private PaaS, to guarantee a consistent uptime (and performance) even
|
||||
when some applications start to misbehave.
|
||||
|
||||
Control Groups have been around for a while as well: the code was
|
||||
started in 2006, and initially merged in kernel 2.6.24.
|
||||
|
||||
Docker Daemon Attack Surface
|
||||
----------------------------
|
||||
|
||||
Running containers (and applications) with Docker implies running the
|
||||
Docker daemon. This daemon currently requires root privileges, and you
|
||||
should therefore be aware of some important details.
|
||||
|
||||
First of all, **only trusted users should be allowed to control your
|
||||
Docker daemon**. This is a direct consequence of some powerful Docker
|
||||
features. Specifically, Docker allows you to share a directory between
|
||||
the Docker host and a guest container; and it allows you to do so
|
||||
without limiting the access rights of the container. This means that
|
||||
you can start a container where the ``/host`` directory will be the
|
||||
``/`` directory on your host; and the container will be able to alter
|
||||
your host filesystem without any restriction. This sounds crazy? Well,
|
||||
you have to know that **all virtualization systems allowing filesystem
|
||||
resource sharing behave the same way**. Nothing prevents you from
|
||||
sharing your root filesystem (or even your root block device) with a
|
||||
virtual machine.
|
||||
|
||||
This has a strong security implication: if you instrument Docker from
|
||||
e.g. a web server to provision containers through an API, you should
|
||||
be even more careful than usual with parameter checking, to make sure
|
||||
that a malicious user cannot pass crafted parameters causing Docker to
|
||||
create arbitrary containers.
|
||||
|
||||
For this reason, the REST API endpoint (used by the Docker CLI to
|
||||
communicate with the Docker daemon) changed in Docker 0.5.2, and now
|
||||
uses a UNIX socket instead of a TCP socket bound on 127.0.0.1 (the
|
||||
latter being prone to cross-site-scripting attacks if you happen to
|
||||
run Docker directly on your local machine, outside of a VM). You can
|
||||
then use traditional UNIX permission checks to limit access to the
|
||||
control socket.
|
||||
|
||||
You can also expose the REST API over HTTP if you explicitly decide
|
||||
so. However, if you do that, being aware of the abovementioned
|
||||
security implication, you should ensure that it will be reachable
|
||||
only from a trusted network or VPN; or protected with e.g. ``stunnel``
|
||||
and client SSL certificates.
|
||||
|
||||
Recent improvements in Linux namespaces will soon allow to run
|
||||
full-featured containers without root privileges, thanks to the new
|
||||
user namespace. This is covered in detail `here
|
||||
<http://s3hh.wordpress.com/2013/07/19/creating-and-using-containers-without-privilege/>`_. Moreover,
|
||||
this will solve the problem caused by sharing filesystems between host
|
||||
and guest, since the user namespace allows users within containers
|
||||
(including the root user) to be mapped to other users in the host
|
||||
system.
|
||||
|
||||
The end goal for Docker is therefore to implement two additional
|
||||
security improvements:
|
||||
|
||||
* map the root user of a container to a non-root user of the Docker
|
||||
host, to mitigate the effects of a container-to-host privilege
|
||||
escalation;
|
||||
* allow the Docker daemon to run without root privileges, and delegate
|
||||
operations requiring those privileges to well-audited sub-processes,
|
||||
each with its own (very limited) scope: virtual network setup,
|
||||
filesystem management, etc.
|
||||
|
||||
Finally, if you run Docker on a server, it is recommended to run
|
||||
exclusively Docker in the server, and move all other services within
|
||||
containers controlled by Docker. Of course, it is fine to keep your
|
||||
favorite admin tools (probably at least an SSH server), as well as
|
||||
existing monitoring/supervision processes (e.g. NRPE, collectd, etc).
|
||||
|
||||
Linux Kernel Capabilities
|
||||
-------------------------
|
||||
|
||||
By default, Docker starts containers with a very restricted set of
|
||||
capabilities. What does that mean?
|
||||
|
||||
Capabilities turn the binary "root/non-root" dichotomy into a
|
||||
fine-grained access control system. Processes (like web servers) that
|
||||
just need to bind on a port below 1024 do not have to run as root:
|
||||
they can just be granted the ``net_bind_service`` capability
|
||||
instead. And there are many other capabilities, for almost all the
|
||||
specific areas where root privileges are usually needed.
|
||||
|
||||
This means a lot for container security; let’s see why!
|
||||
|
||||
Your average server (bare metal or virtual machine) needs to run a
|
||||
bunch of processes as root. Those typically include SSH, cron,
|
||||
syslogd; hardware management tools (to e.g. load modules), network
|
||||
configuration tools (to handle e.g. DHCP, WPA, or VPNs), and much
|
||||
more. A container is very different, because almost all of those tasks
|
||||
are handled by the infrastructure around the container:
|
||||
|
||||
* SSH access will typically be managed by a single server running in
|
||||
the Docker host;
|
||||
* ``cron``, when necessary, should run as a user process, dedicated
|
||||
and tailored for the app that needs its scheduling service, rather
|
||||
than as a platform-wide facility;
|
||||
* log management will also typically be handed to Docker, or by
|
||||
third-party services like Loggly or Splunk;
|
||||
* hardware management is irrelevant, meaning that you never need to
|
||||
run ``udevd`` or equivalent daemons within containers;
|
||||
* network management happens outside of the containers, enforcing
|
||||
separation of concerns as much as possible, meaning that a container
|
||||
should never need to perform ``ifconfig``, ``route``, or ip commands
|
||||
(except when a container is specifically engineered to behave like a
|
||||
router or firewall, of course).
|
||||
|
||||
This means that in most cases, containers will not need "real" root
|
||||
privileges *at all*. And therefore, containers can run with a reduced
|
||||
capability set; meaning that "root" within a container has much less
|
||||
privileges than the real "root". For instance, it is possible to:
|
||||
|
||||
* deny all "mount" operations;
|
||||
* deny access to raw sockets (to prevent packet spoofing);
|
||||
* deny access to some filesystem operations, like creating new device
|
||||
nodes, changing the owner of files, or altering attributes
|
||||
(including the immutable flag);
|
||||
* deny module loading;
|
||||
* and many others.
|
||||
|
||||
This means that even if an intruder manages to escalate to root within
|
||||
a container, it will be much harder to do serious damage, or to
|
||||
escalate to the host.
|
||||
|
||||
This won't affect regular web apps; but malicious users will find that
|
||||
the arsenal at their disposal has shrunk considerably! You can see
|
||||
`the list of dropped capabilities in the Docker code
|
||||
<https://github.com/dotcloud/docker/blob/v0.5.0/lxc_template.go#L97>`_,
|
||||
and a full list of available capabilities in `Linux manpages
|
||||
<http://man7.org/linux/man-pages/man7/capabilities.7.html>`_.
|
||||
|
||||
Of course, you can always enable extra capabilities if you really need
|
||||
them (for instance, if you want to use a FUSE-based filesystem), but
|
||||
by default, Docker containers will be locked down to ensure maximum
|
||||
safety.
|
||||
|
||||
Other Kernel Security Features
|
||||
------------------------------
|
||||
|
||||
Capabilities are just one of the many security features provided by
|
||||
modern Linux kernels. It is also possible to leverage existing,
|
||||
well-known systems like TOMOYO, AppArmor, SELinux, GRSEC, etc. with
|
||||
Docker.
|
||||
|
||||
While Docker currently only enables capabilities, it doesn't interfere
|
||||
with the other systems. This means that there are many different ways
|
||||
to harden a Docker host. Here are a few examples.
|
||||
|
||||
* You can run a kernel with GRSEC and PAX. This will add many safety
|
||||
checks, both at compile-time and run-time; it will also defeat many
|
||||
exploits, thanks to techniques like address randomization. It
|
||||
doesn’t require Docker-specific configuration, since those security
|
||||
features apply system-wide, independently of containers.
|
||||
* If your distribution comes with security model templates for LXC
|
||||
containers, you can use them out of the box. For instance, Ubuntu
|
||||
comes with AppArmor templates for LXC, and those templates provide
|
||||
an extra safety net (even though it overlaps greatly with
|
||||
capabilities).
|
||||
* You can define your own policies using your favorite access control
|
||||
mechanism. Since Docker containers are standard LXC containers,
|
||||
there is nothing “magic” or specific to Docker.
|
||||
|
||||
Just like there are many third-party tools to augment Docker
|
||||
containers with e.g. special network topologies or shared filesystems,
|
||||
you can expect to see tools to harden existing Docker containers
|
||||
without affecting Docker’s core.
|
||||
|
||||
Conclusions
|
||||
-----------
|
||||
|
||||
Docker containers are, by default, quite secure; especially if you
|
||||
take care of running your processes inside the containers as
|
||||
non-privileged users (i.e. non root).
|
||||
|
||||
You can add an extra layer of safety by enabling Apparmor, SELinux,
|
||||
GRSEC, or your favorite hardening solution.
|
||||
|
||||
Last but not least, if you see interesting security features in other
|
||||
containerization systems, you will be able to implement them as well
|
||||
with Docker, since everything is provided by the kernel anyway.
|
||||
|
||||
For more context and especially for comparisons with VMs and other
|
||||
container systems, please also see the `original blog post
|
||||
<blogsecurity>`_.
|
||||
|
||||
.. _blogsecurity: http://blog.docker.io/2013/08/containers-docker-how-secure-are-they/
|
||||
|
||||
@@ -78,8 +78,7 @@ Docker is available as a Debian package, which makes installation easy.
|
||||
sudo sh -c "wget -qO- https://get.docker.io/gpg | apt-key add -"
|
||||
|
||||
# Add the Docker repository to your apt sources list.
|
||||
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\
|
||||
> /etc/apt/sources.list.d/docker.list"
|
||||
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list"
|
||||
|
||||
# Update your sources
|
||||
sudo apt-get update
|
||||
@@ -133,8 +132,7 @@ to follow them again.*
|
||||
sudo sh -c "wget -qO- https://get.docker.io/gpg | apt-key add -"
|
||||
|
||||
# Add the Docker repository to your apt sources list.
|
||||
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\
|
||||
> /etc/apt/sources.list.d/docker.list"
|
||||
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list"
|
||||
|
||||
# update
|
||||
sudo apt-get update
|
||||
@@ -162,8 +160,8 @@ Verify it worked
|
||||
Docker and UFW
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
Docker uses a bridge to manage container networking. By default, UFW
|
||||
drops all `forwarding`, thus a first step is to enable UFW forwarding:
|
||||
Docker uses a bridge to manage containers networking, by default UFW
|
||||
drop all `forwarding`, a first step is to enable forwarding:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ Using Vagrant (Mac, Linux)
|
||||
|
||||
This guide will setup a new virtualbox virtual machine with docker
|
||||
installed on your computer. This works on most operating systems,
|
||||
including MacOSX, Windows, Linux, FreeBSD and others. If you can
|
||||
including MacOX, Windows, Linux, FreeBSD and others. If you can
|
||||
install these and have at least 400MB RAM to spare you should be good.
|
||||
|
||||
Install Vagrant and Virtualbox
|
||||
|
||||
@@ -208,10 +208,6 @@ configuration / Device configuration)
|
||||
|
||||
.. image:: images/win/hp_bios_vm.JPG
|
||||
|
||||
On some machines the BIOS menu can only be accessed before startup.
|
||||
To access BIOS in this scenario you should restart your computer and
|
||||
press ESC/Enter when prompted to access the boot and BIOS controls. Typically
|
||||
the option to allow virtualization is contained within the BIOS/Security menu.
|
||||
|
||||
Docker is not installed
|
||||
```````````````````````
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user