Compare commits
297 Commits
v0.6.3
..
v0.7.0-rc3
| Author | SHA1 | Date | |
|---|---|---|---|
| 1781b18deb | |||
| d67f8d3414 | |||
| b414d9d4e2 | |||
| b5a5a56271 | |||
| c1bb33ea4d | |||
| 6545e53803 | |||
| d30806590c | |||
| d5f9160441 | |||
| 875b3001f8 | |||
| bb62c281f5 | |||
| 3565bf8d00 | |||
| 9a4b0b9823 | |||
| 4fda3314d8 | |||
| e906485b07 | |||
| a85667f80a | |||
| 6d67ac2a1b | |||
| b67c94a963 | |||
| 2d425af1b1 | |||
| ed19693f50 | |||
| 194c1dbbd8 | |||
| a9eac7eab4 | |||
| 7c28683f08 | |||
| c18e849451 | |||
| a665517151 | |||
| ea5b19cc01 | |||
| 86c96da8d0 | |||
| 6c091462ed | |||
| 1cbe361b08 | |||
| 04f092792f | |||
| aeb4b196bd | |||
| f2696470e2 | |||
| 623314936e | |||
| 9c00d25b04 | |||
| bd4b65e882 | |||
| 1b43908dbb | |||
| 784e7647e1 | |||
| 99f5fc3116 | |||
| 9d887e5f25 | |||
| f80d778077 | |||
| 7a3d4b1d3f | |||
| 27fc64dbc6 | |||
| a07d919ea1 | |||
| e1952ae844 | |||
| b184ca1383 | |||
| 29de4792da | |||
| adecaeb40b | |||
| 9d150f02f9 | |||
| 30e4a8e524 | |||
| 48f1f6fa0d | |||
| 232ec55c1e | |||
| 3e4c9d723c | |||
| 675f1a8069 | |||
| 6c1ec9c28f | |||
| 46c82d0391 | |||
| 96d3008465 | |||
| 729b94d448 | |||
| 193767edd7 | |||
| 2e25dff7b5 | |||
| 863eebe7bd | |||
| f6913592a1 | |||
| aaf1f73bcc | |||
| 9b65c7cf49 | |||
| f7e374fb3a | |||
| 3c5350ba92 | |||
| c52c95f97f | |||
| 85a5139665 | |||
| f29c500d8d | |||
| b843998718 | |||
| ad4b09cdb8 | |||
| e594c788e4 | |||
| 826aaa0e9b | |||
| 1a1be5a87c | |||
| 7b58e15b08 | |||
| 8b2f4aab23 | |||
| 06d0843a61 | |||
| deb05a36e8 | |||
| 55189307d0 | |||
| ff4f32d58f | |||
| 462ac1efcf | |||
| 1332f400f7 | |||
| a292c04ddb | |||
| 37c4513cf9 | |||
| b584c21ca4 | |||
| c92507341d | |||
| 9f0feaa2e1 | |||
| 0f830aa431 | |||
| 5f05cb4817 | |||
| 4778039dd2 | |||
| b180770b1d | |||
| 4f5c2cbccc | |||
| c3acd9bece | |||
| cb21a40490 | |||
| 5025b4d094 | |||
| 509e25cf04 | |||
| 135c1fce90 | |||
| d517fd3d77 | |||
| b4e1b8d2e2 | |||
| 45bd52d472 | |||
| 534a991cff | |||
| 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 | |||
| 23015fa14d | |||
| fb46d911c2 | |||
| 85b776995c | |||
| fa44555fb4 | |||
| 3e7c50e8a1 | |||
| ccefe47897 | |||
| ff85031980 | |||
| 017ecefd66 | |||
| 88f710f0e7 | |||
| 6496059154 | |||
| bcce3cbdd1 | |||
| bbf644ed62 | |||
| d89ce09a76 | |||
| e3e041b6bf | |||
| 0a35b1fb36 | |||
| 74d54b9b2e | |||
| 31fd11860b | |||
| b081a740b3 | |||
| db869ecce5 | |||
| 537149829a | |||
| ba10e28927 | |||
| f4855a7cf0 | |||
| 22e7e107ad | |||
| edd90d46c7 | |||
| 514886c73d | |||
| f435970695 | |||
| cb18a6e1b9 | |||
| 75e958bf48 | |||
| ce05083d4b | |||
| 723de87681 | |||
| c22e377a6d | |||
| 3f0d0075f8 | |||
| f8c9f11f06 | |||
| 80319add55 | |||
| 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 | |||
| 818ec10da3 | |||
| 34e28ccc88 | |||
| 792403f2dc | |||
| 8f5c33dc1f | |||
| 06c1f000e8 | |||
| 00b3acb8ab | |||
| 420ba9c85a | |||
| 0f829bf5cf | |||
| 1363de0934 | |||
| d06116d2e8 | |||
| 595210a370 | |||
| c9b916b293 | |||
| 1e6370fd4b | |||
| a59a66528b | |||
| b048e9dffc | |||
| c000e6a7fc | |||
| aa3de0b849 | |||
| 648d759517 | |||
| 5c190fa926 | |||
| 03fe5632d0 | |||
| edde4f55e0 | |||
| 7447867edd | |||
| c1c74cb0b1 | |||
| 17b7194d91 | |||
| 15ad2915b9 | |||
| d6887b769c | |||
| 10362870db | |||
| a7db125480 | |||
| f6c64827c8 | |||
| 30b759ffa9 | |||
| 35ca35ffb6 | |||
| a4067b1b44 | |||
| 6097644e4b | |||
| fa3837abf1 | |||
| baff72bc82 | |||
| 99377de7d2 | |||
| 0ef5bcb17d | |||
| fd1c43f0e0 | |||
| e37bb50628 | |||
| 2819cfb3f3 | |||
| a479f7659d | |||
| b02a591f57 | |||
| c654075654 | |||
| 5a3dcbb3f6 | |||
| cce6f00c0c | |||
| 082d142024 | |||
| e854b7b2e6 | |||
| 289350e633 | |||
| 8ae5348a51 |
@@ -26,7 +26,7 @@
|
||||
* Registry: Implement login with private registry
|
||||
* Remote API: Bump to v1.5
|
||||
* Packaging: Break down hack/make.sh into small scripts, one per 'bundle': test, binary, ubuntu etc.
|
||||
* Documentation: General improvments
|
||||
* Documentation: General improvements
|
||||
- Runtime: UID and GID are now also applied to volumes
|
||||
- Runtime: `docker start` set error code upon error
|
||||
- Runtime: `docker run` set the same error code as the process started
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
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/).
|
||||
|
||||
## Contribution guidelines
|
||||
|
||||
### Pull requests are always welcome
|
||||
@@ -90,53 +94,6 @@ regenerated occasionally from the git commit history, so a mismatch may result
|
||||
in your changes being overwritten.
|
||||
|
||||
|
||||
## Decision process
|
||||
|
||||
### How are decisions made?
|
||||
|
||||
Short answer: with pull requests to the docker repository.
|
||||
|
||||
Docker is an open-source project with an open design philosophy. This means that the repository is the source of truth for EVERY aspect of the project,
|
||||
including its philosophy, design, roadmap and APIs. *If it's part of the project, it's in the repo. It's in the repo, it's part of the project.*
|
||||
|
||||
As a result, all decisions can be expressed as changes to the repository. An implementation change is a change to the source code. An API change is a change to
|
||||
the API specification. A philosophy change is a change to the philosophy manifesto. And so on.
|
||||
|
||||
All decisions affecting docker, big and small, follow the same 3 steps:
|
||||
|
||||
* Step 1: Open a pull request. Anyone can do this.
|
||||
|
||||
* Step 2: Discuss the pull request. Anyone can do this.
|
||||
|
||||
* Step 3: Accept or refuse a pull request. The relevant maintainer does this (see below "Who decides what?")
|
||||
|
||||
|
||||
### Who decides what?
|
||||
|
||||
So all decisions are pull requests, and the relevant maintainer makes the decision by accepting or refusing the pull request.
|
||||
But how do we identify the relevant maintainer for a given pull request?
|
||||
|
||||
Docker follows the timeless, highly efficient and totally unfair system known as [Benevolent dictator for life](http://en.wikipedia.org/wiki/Benevolent_Dictator_for_Life),
|
||||
with yours truly, Solomon Hykes, in the role of BDFL.
|
||||
This means that all decisions are made by default by me. Since making every decision myself would be highly unscalable, in practice decisions are spread across multiple maintainers.
|
||||
|
||||
The relevant maintainer for a pull request is assigned in 3 steps:
|
||||
|
||||
* Step 1: Determine the subdirectory affected by the pull request. This might be src/registry, docs/source/api, or any other part of the repo.
|
||||
|
||||
* Step 2: Find the MAINTAINERS file which affects this directory. If the directory itself does not have a MAINTAINERS file, work your way up the the repo hierarchy until you find one.
|
||||
|
||||
* Step 3: The first maintainer listed is the primary maintainer. The pull request is assigned to him. He may assign it to other listed maintainers, at his discretion.
|
||||
|
||||
|
||||
### I'm a maintainer, should I make pull requests too?
|
||||
|
||||
Primary maintainers are not required to create pull requests when changing their own subdirectory, but secondary maintainers are.
|
||||
|
||||
### Who assigns maintainers?
|
||||
|
||||
Solomon.
|
||||
|
||||
### How can I become a maintainer?
|
||||
|
||||
* Step 1: learn the component inside out
|
||||
@@ -146,15 +103,3 @@ Solomon.
|
||||
Don't forget: being a maintainer is a time investment. Make sure you will have time to make yourself available.
|
||||
You don't have to be a maintainer to make a difference on the project!
|
||||
|
||||
### What are a maintainer's responsibility?
|
||||
|
||||
It is every maintainer's responsibility to:
|
||||
|
||||
* 1) Expose a clear roadmap for improving their component.
|
||||
* 2) Deliver prompt feedback and decisions on pull requests.
|
||||
* 3) Be available to anyone with questions, bug reports, criticism etc. on their component. This includes irc, github requests and the mailing list.
|
||||
* 4) Make sure their component respects the philosophy, design and roadmap of the project.
|
||||
|
||||
### How is this process changed?
|
||||
|
||||
Just like everything else: by making a pull request :)
|
||||
|
||||
@@ -16,43 +16,66 @@
|
||||
#
|
||||
# # Publish a release:
|
||||
# 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 \
|
||||
# -lxc-conf=lxc.aa_profile=unconfined -privileged docker hack/release.sh
|
||||
#
|
||||
# -e AWS_S3_BUCKET=baz \
|
||||
# -e AWS_ACCESS_KEY=foo \
|
||||
# -e AWS_SECRET_KEY=bar \
|
||||
# -e GPG_PASSPHRASE=gloubiboulga \
|
||||
# docker hack/release.sh
|
||||
#
|
||||
|
||||
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
|
||||
# Install Go
|
||||
run curl -s https://go.googlecode.com/files/go1.1.2.linux-amd64.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
|
||||
env CGO_ENABLED 0
|
||||
run cd /tmp && echo 'package main' > t.go && go test -a -i -v
|
||||
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 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 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
|
||||
|
||||
# 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
|
||||
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
|
||||
volume /var/lib/docker
|
||||
workdir /go/src/github.com/dotcloud/docker
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
@@ -15,23 +15,16 @@ to put them - so we put them here :)
|
||||
* Run linter on codebase
|
||||
* Unify build commands and regular commands
|
||||
* Move source code into src/ subdir for clarity
|
||||
* Clean up the Makefile, it's a mess
|
||||
* docker build: on non-existent local path for ADD, don't show full absolute path on the host
|
||||
* mount into /dockerinit rather than /sbin/init
|
||||
* docker tag foo REPO:TAG
|
||||
* use size header for progress bar in pull
|
||||
* Clean up context upload in build!!!
|
||||
* Parallel pull
|
||||
* Ensure /proc/sys/net/ipv4/ip_forward is 1
|
||||
* Force DNS to public!
|
||||
* Always generate a resolv.conf per container, to avoid changing resolv.conf under thne container's feet
|
||||
* Save metadata with import/export
|
||||
* Save metadata with import/export (#1974)
|
||||
* Upgrade dockerd without stopping containers
|
||||
* bring back git revision info, looks like it was lost
|
||||
* Simple command to remove all untagged images
|
||||
* Simple command to remove all untagged images (`docker rmi $(docker images | awk '/^<none>/ { print $3 }')`)
|
||||
* Simple command to clean up containers for disk space
|
||||
* Caching after an ADD
|
||||
* entry point config
|
||||
* bring back git revision info, looks like it was lost
|
||||
* Caching after an ADD (#880)
|
||||
* Clean up the ProgressReader api, it's a PITA to use
|
||||
* Use netlink instead of iproute2/iptables (#925)
|
||||
|
||||
@@ -1,11 +1,43 @@
|
||||
Docker
|
||||
Copyright 2012-2013 dotCloud, inc.
|
||||
|
||||
This product includes software developed at dotCloud, inc. (http://www.dotcloud.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.
|
||||
This product contains software (https://github.com/kr/pty) developed
|
||||
by Keith Rarick, licensed under the MIT License.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
What does that mean?
|
||||
Here is a further explanation from our legal counsel:
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -18,7 +18,7 @@ Platform-as-a-Service. It benefits directly from the experience
|
||||
accumulated over several years of large-scale operation and support of
|
||||
hundreds of thousands of applications and databases.
|
||||
|
||||

|
||||

|
||||
|
||||
## Better than VMs
|
||||
|
||||
@@ -190,6 +190,9 @@ wrong or incomplete.
|
||||
|
||||
### Legal
|
||||
|
||||
*Brought to you courtesy of our legal counsel. For more context,
|
||||
please see the Notice document.*
|
||||
|
||||
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,
|
||||
|
||||
@@ -13,6 +13,8 @@ Vagrant::Config.run do |config|
|
||||
config.vm.box = BOX_NAME
|
||||
config.vm.box_url = BOX_URI
|
||||
|
||||
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?
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/auth"
|
||||
"github.com/dotcloud/docker/gograph"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"github.com/gorilla/mux"
|
||||
"io"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"mime"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
@@ -21,10 +23,12 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const APIVERSION = 1.5
|
||||
const DEFAULTHTTPHOST = "127.0.0.1"
|
||||
const DEFAULTHTTPPORT = 4243
|
||||
const DEFAULTUNIXSOCKET = "/var/run/docker.sock"
|
||||
const (
|
||||
APIVERSION = 1.6
|
||||
DEFAULTHTTPHOST = "127.0.0.1"
|
||||
DEFAULTHTTPPORT = 4243
|
||||
DEFAULTUNIXSOCKET = "/var/run/docker.sock"
|
||||
)
|
||||
|
||||
type HttpApiFunc func(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error
|
||||
|
||||
@@ -131,6 +135,7 @@ func postContainersKill(srv *Server, version float64, w http.ResponseWriter, r *
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
name = decodeName(name)
|
||||
if err := srv.ContainerKill(name); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -143,6 +148,7 @@ func getContainersExport(srv *Server, version float64, w http.ResponseWriter, r
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
name = decodeName(name)
|
||||
|
||||
if err := srv.ContainerExport(name, w); err != nil {
|
||||
utils.Debugf("%s", err)
|
||||
@@ -510,16 +516,19 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r
|
||||
return err
|
||||
}
|
||||
|
||||
if !config.NetworkDisabled && len(config.Dns) == 0 && len(srv.runtime.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))
|
||||
config.Dns = defaultDns
|
||||
}
|
||||
|
||||
id, err := srv.ContainerCreate(config)
|
||||
id, warnings, err := srv.ContainerCreate(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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.")
|
||||
@@ -550,6 +559,7 @@ func postContainersRestart(srv *Server, version float64, w http.ResponseWriter,
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
name = decodeName(name)
|
||||
if err := srv.ContainerRestart(name, t); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -565,12 +575,18 @@ func deleteContainers(srv *Server, version float64, w http.ResponseWriter, r *ht
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
name = decodeName(name)
|
||||
|
||||
removeVolume, err := getBoolParam(r.Form.Get("v"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
removeLink, err := getBoolParam(r.Form.Get("link"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := srv.ContainerDestroy(name, removeVolume); err != nil {
|
||||
if err := srv.ContainerDestroy(name, removeVolume, removeLink); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
@@ -616,8 +632,14 @@ func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
var err error
|
||||
name := vars["name"]
|
||||
name = decodeName(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := srv.ContainerStart(name, hostConfig); err != nil {
|
||||
utils.Debugf("error ContainerStart: %s", err)
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
@@ -637,6 +659,7 @@ func postContainersStop(srv *Server, version float64, w http.ResponseWriter, r *
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
name = decodeName(name)
|
||||
|
||||
if err := srv.ContainerStop(name, t); err != nil {
|
||||
return err
|
||||
@@ -650,6 +673,8 @@ func postContainersWait(srv *Server, version float64, w http.ResponseWriter, r *
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
name = decodeName(name)
|
||||
|
||||
status, err := srv.ContainerWait(name)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -709,33 +734,45 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
name = decodeName(name)
|
||||
|
||||
if _, err := srv.ContainerInspect(name); err != nil {
|
||||
c, err := srv.ContainerInspect(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
in, out, err := hijackServer(w)
|
||||
inStream, outStream, err := hijackServer(w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if tcpc, ok := in.(*net.TCPConn); ok {
|
||||
if tcpc, ok := inStream.(*net.TCPConn); ok {
|
||||
tcpc.CloseWrite()
|
||||
} else {
|
||||
in.Close()
|
||||
inStream.Close()
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
if tcpc, ok := out.(*net.TCPConn); ok {
|
||||
if tcpc, ok := outStream.(*net.TCPConn); ok {
|
||||
tcpc.CloseWrite()
|
||||
} else if closer, ok := out.(io.Closer); ok {
|
||||
} else if closer, ok := outStream.(io.Closer); ok {
|
||||
closer.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
|
||||
if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, in, out); err != nil {
|
||||
fmt.Fprintf(out, "Error: %s\n", err)
|
||||
var errStream io.Writer
|
||||
|
||||
fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
|
||||
|
||||
if !c.Config.Tty && version >= 1.6 {
|
||||
errStream = utils.NewStdWriter(outStream, utils.Stderr)
|
||||
outStream = utils.NewStdWriter(outStream, utils.Stdout)
|
||||
} else {
|
||||
errStream = outStream
|
||||
}
|
||||
|
||||
if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, inStream, outStream, errStream); err != nil {
|
||||
fmt.Fprintf(outStream, "Error: %s\n", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -770,6 +807,7 @@ func wsContainersAttach(srv *Server, version float64, w http.ResponseWriter, r *
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
name = decodeName(name)
|
||||
|
||||
if _, err := srv.ContainerInspect(name); err != nil {
|
||||
return err
|
||||
@@ -778,7 +816,7 @@ func wsContainersAttach(srv *Server, version float64, w http.ResponseWriter, r *
|
||||
h := websocket.Handler(func(ws *websocket.Conn) {
|
||||
defer ws.Close()
|
||||
|
||||
if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, ws, ws); err != nil {
|
||||
if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, ws, ws, ws); err != nil {
|
||||
utils.Debugf("Error: %s", err)
|
||||
}
|
||||
})
|
||||
@@ -792,6 +830,7 @@ func getContainersByName(srv *Server, version float64, w http.ResponseWriter, r
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
name = decodeName(name)
|
||||
|
||||
container, err := srv.ContainerInspect(name)
|
||||
if err != nil {
|
||||
@@ -960,7 +999,7 @@ func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute s
|
||||
if err != nil {
|
||||
version = APIVERSION
|
||||
}
|
||||
if srv.enableCors {
|
||||
if srv.runtime.config.EnableCors {
|
||||
writeCorsHeaders(w, r)
|
||||
}
|
||||
|
||||
@@ -976,6 +1015,75 @@ 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 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeName(name string) string {
|
||||
s, _ := url.QueryUnescape(name)
|
||||
return s
|
||||
}
|
||||
|
||||
func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
||||
r := mux.NewRouter()
|
||||
|
||||
@@ -996,6 +1104,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,
|
||||
@@ -1014,6 +1123,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,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package docker
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type APIHistory struct {
|
||||
ID string `json:"Id"`
|
||||
Tags []string `json:",omitempty"`
|
||||
@@ -52,17 +50,18 @@ type APIContainers struct {
|
||||
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,
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -96,14 +95,7 @@ type APIPort struct {
|
||||
PrivatePort int64
|
||||
PublicPort int64
|
||||
Type string
|
||||
}
|
||||
|
||||
func (port *APIPort) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]interface{}{
|
||||
"PrivatePort": port.PrivatePort,
|
||||
"PublicPort": port.PublicPort,
|
||||
"Type": port.Type,
|
||||
})
|
||||
IP string
|
||||
}
|
||||
|
||||
type APIVersion struct {
|
||||
@@ -129,3 +121,9 @@ type APICopy struct {
|
||||
Resource string
|
||||
HostPath string
|
||||
}
|
||||
|
||||
type APILink struct {
|
||||
Path string
|
||||
ContainerID string
|
||||
Image string
|
||||
}
|
||||
|
||||
@@ -321,7 +321,7 @@ func TestGetContainersJSON(t *testing.T) {
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"echo", "test"},
|
||||
})
|
||||
@@ -336,9 +336,11 @@ func TestGetContainersJSON(t *testing.T) {
|
||||
}
|
||||
|
||||
r := httptest.NewRecorder()
|
||||
if err := getContainersJSON(srv, APIVERSION, r, req, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
setTimeout(t, "getContainerJSON timed out", 5*time.Second, func() {
|
||||
if err := getContainersJSON(srv, APIVERSION, r, req, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
containers := []APIContainers{}
|
||||
if err := json.Unmarshal(r.Body.Bytes(), &containers); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -358,7 +360,7 @@ func TestGetContainersExport(t *testing.T) {
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
// Create a container and remove a file
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"touch", "/test"},
|
||||
@@ -374,7 +376,7 @@ func TestGetContainersExport(t *testing.T) {
|
||||
}
|
||||
|
||||
r := httptest.NewRecorder()
|
||||
if err = getContainersExport(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
|
||||
if err := getContainersExport(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -408,7 +410,7 @@ func TestGetContainersChanges(t *testing.T) {
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
// Create a container and remove a file
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/rm", "/etc/passwd"},
|
||||
@@ -454,7 +456,7 @@ func TestGetContainersTop(t *testing.T) {
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/sh", "-c", "cat"},
|
||||
@@ -536,7 +538,7 @@ func TestGetContainersByName(t *testing.T) {
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
// Create a container and remove a file
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"echo", "test"},
|
||||
@@ -566,9 +568,8 @@ func TestPostCommit(t *testing.T) {
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
|
||||
// Create a container and remove a file
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"touch", "/test"},
|
||||
@@ -647,13 +648,21 @@ func TestPostContainersCreate(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(path.Join(container.rwPath(), "test")); err != nil {
|
||||
if err := container.EnsureMounted(); err != nil {
|
||||
t.Fatalf("Unable to mount container: %s", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(path.Join(container.RootfsPath(), "test")); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
utils.Debugf("Err: %s", err)
|
||||
t.Fatalf("The test file has not been created")
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := container.Unmount(); err != nil {
|
||||
t.Fatalf("Unable to unmount container: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostContainersKill(t *testing.T) {
|
||||
@@ -662,7 +671,7 @@ func TestPostContainersKill(t *testing.T) {
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/cat"},
|
||||
@@ -704,7 +713,7 @@ func TestPostContainersRestart(t *testing.T) {
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/cat"},
|
||||
@@ -758,7 +767,7 @@ func TestPostContainersStart(t *testing.T) {
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/cat"},
|
||||
@@ -808,7 +817,7 @@ func TestPostContainersStop(t *testing.T) {
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/cat"},
|
||||
@@ -855,7 +864,7 @@ func TestPostContainersWait(t *testing.T) {
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/sleep", "1"},
|
||||
@@ -897,7 +906,7 @@ func TestPostContainersAttach(t *testing.T) {
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/cat"},
|
||||
@@ -952,7 +961,96 @@ func TestPostContainersAttach(t *testing.T) {
|
||||
})
|
||||
|
||||
setTimeout(t, "read/write assertion timed out", 2*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
if err := assertPipe("hello\n", string([]byte{1, 0, 0, 0, 0, 0, 0, 6})+"hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
// Close pipes (client disconnects)
|
||||
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Wait for attach to finish, the client disconnected, therefore, Attach finished his job
|
||||
setTimeout(t, "Waiting for CmdAttach timed out", 10*time.Second, func() {
|
||||
<-c1
|
||||
})
|
||||
|
||||
// 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.Running {
|
||||
t.Fatalf("/bin/cat is not running after closing stdin")
|
||||
}
|
||||
|
||||
// Try to avoid the timeout in destroy. Best effort, don't check error
|
||||
cStdin, _ := container.StdinPipe()
|
||||
cStdin.Close()
|
||||
container.Wait()
|
||||
}
|
||||
|
||||
func TestPostContainersAttachStderr(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/sh", "-c", "/bin/cat >&2"},
|
||||
OpenStdin: true,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
// Start the process
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
// Try to avoid the timeout in destroy. Best effort, don't check error
|
||||
defer func() {
|
||||
closeWrap(stdin, stdinPipe, stdout, stdoutPipe)
|
||||
container.Kill()
|
||||
}()
|
||||
|
||||
// Attach to it
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
defer close(c1)
|
||||
|
||||
r := &hijackTester{
|
||||
ResponseRecorder: httptest.NewRecorder(),
|
||||
in: stdin,
|
||||
out: stdoutPipe,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", "/containers/"+container.ID+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := postContainersAttach(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Acknowledge hijack
|
||||
setTimeout(t, "hijack acknowledge timed out", 2*time.Second, func() {
|
||||
stdout.Read([]byte{})
|
||||
stdout.Read(make([]byte, 4096))
|
||||
})
|
||||
|
||||
setTimeout(t, "read/write assertion timed out", 2*time.Second, func() {
|
||||
if err := assertPipe("hello\n", string([]byte{2, 0, 0, 0, 0, 0, 0, 6})+"hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
@@ -989,7 +1087,7 @@ func TestDeleteContainers(t *testing.T) {
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"touch", "/test"},
|
||||
})
|
||||
@@ -1027,7 +1125,8 @@ func TestOptionsRoute(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime, enableCors: true}
|
||||
runtime.config.EnableCors = true
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
r := httptest.NewRecorder()
|
||||
router, err := createRouter(srv, false)
|
||||
@@ -1050,7 +1149,8 @@ func TestGetEnabledCors(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime, enableCors: true}
|
||||
runtime.config.EnableCors = true
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
r := httptest.NewRecorder()
|
||||
|
||||
@@ -1177,7 +1277,7 @@ func TestPostContainersCopy(t *testing.T) {
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
// Create a container and remove a file
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"touch", "/test.txt"},
|
||||
|
||||
@@ -80,20 +80,73 @@ 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 {
|
||||
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,
|
||||
@@ -140,7 +193,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
|
||||
}
|
||||
@@ -227,7 +280,18 @@ 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 {
|
||||
return nil, err
|
||||
}
|
||||
// Write stdin if any
|
||||
go func() {
|
||||
_, _ = stdin.Write([]byte(*input))
|
||||
stdin.Close()
|
||||
}()
|
||||
}
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -258,6 +322,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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -91,11 +91,17 @@ func LoadConfig(rootPath string) (*ConfigFile, error) {
|
||||
}
|
||||
authConfig := AuthConfig{}
|
||||
origAuth := strings.Split(arr[0], " = ")
|
||||
if len(origAuth) != 2 {
|
||||
return &configFile, fmt.Errorf("Invalid Auth config file")
|
||||
}
|
||||
authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1])
|
||||
if err != nil {
|
||||
return &configFile, err
|
||||
}
|
||||
origEmail := strings.Split(arr[1], " = ")
|
||||
if len(origEmail) != 2 {
|
||||
return &configFile, fmt.Errorf("Invalid Auth config file")
|
||||
}
|
||||
authConfig.Email = origEmail[1]
|
||||
authConfig.ServerAddress = IndexServerAddress()
|
||||
configFile.Configs[IndexServerAddress()] = authConfig
|
||||
@@ -239,7 +245,7 @@ func (config *ConfigFile) ResolveAuthConfig(registry string) AuthConfig {
|
||||
// as there is only one auth entry which is fully qualified we need to start
|
||||
// parsing and matching
|
||||
|
||||
swapProtocoll := func(url string) string {
|
||||
swapProtocol := func(url string) string {
|
||||
if strings.HasPrefix(url, "http:") {
|
||||
return strings.Replace(url, "http:", "https:", 1)
|
||||
}
|
||||
@@ -253,9 +259,9 @@ func (config *ConfigFile) ResolveAuthConfig(registry string) AuthConfig {
|
||||
if c, found := config.Configs[url]; found {
|
||||
return c
|
||||
}
|
||||
registrySwappedProtocoll := swapProtocoll(url)
|
||||
registrySwappedProtocol := swapProtocol(url)
|
||||
// now try to match with the different protocol
|
||||
if c, found := config.Configs[registrySwappedProtocoll]; found {
|
||||
if c, found := config.Configs[registrySwappedProtocol]; found {
|
||||
return c
|
||||
}
|
||||
return AuthConfig{}
|
||||
|
||||
@@ -75,20 +75,31 @@ func TestCreateAccount(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSameAuthDataPostSave(t *testing.T) {
|
||||
func setupTempConfigFile() (*ConfigFile, error) {
|
||||
root, err := ioutil.TempDir("", "docker-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return nil, err
|
||||
}
|
||||
configFile := &ConfigFile{
|
||||
rootPath: root,
|
||||
Configs: make(map[string]AuthConfig, 1),
|
||||
Configs: make(map[string]AuthConfig),
|
||||
}
|
||||
|
||||
configFile.Configs["testIndex"] = AuthConfig{
|
||||
Username: "docker-user",
|
||||
Password: "docker-pass",
|
||||
Email: "docker@docker.io",
|
||||
for _, registry := range []string{"testIndex", IndexServerAddress()} {
|
||||
configFile.Configs[registry] = AuthConfig{
|
||||
Username: "docker-user",
|
||||
Password: "docker-pass",
|
||||
Email: "docker@docker.io",
|
||||
}
|
||||
}
|
||||
|
||||
return configFile, nil
|
||||
}
|
||||
|
||||
func TestSameAuthDataPostSave(t *testing.T) {
|
||||
configFile, err := setupTempConfigFile()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = SaveConfig(configFile)
|
||||
@@ -110,3 +121,68 @@ func TestSameAuthDataPostSave(t *testing.T) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveAuthConfigIndexServer(t *testing.T) {
|
||||
configFile, err := setupTempConfigFile()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, registry := range []string{"", IndexServerAddress()} {
|
||||
resolved := configFile.ResolveAuthConfig(registry)
|
||||
if resolved != configFile.Configs[IndexServerAddress()] {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveAuthConfigFullURL(t *testing.T) {
|
||||
configFile, err := setupTempConfigFile()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
registryAuth := AuthConfig{
|
||||
Username: "foo-user",
|
||||
Password: "foo-pass",
|
||||
Email: "foo@example.com",
|
||||
}
|
||||
localAuth := AuthConfig{
|
||||
Username: "bar-user",
|
||||
Password: "bar-pass",
|
||||
Email: "bar@example.com",
|
||||
}
|
||||
configFile.Configs["https://registry.example.com/v1/"] = registryAuth
|
||||
configFile.Configs["http://localhost:8000/v1/"] = localAuth
|
||||
|
||||
validRegistries := map[string][]string{
|
||||
"https://registry.example.com/v1/": {
|
||||
"https://registry.example.com/v1/",
|
||||
"http://registry.example.com/v1/",
|
||||
"registry.example.com",
|
||||
"registry.example.com/v1/",
|
||||
},
|
||||
"http://localhost:8000/v1/": {
|
||||
"https://localhost:8000/v1/",
|
||||
"http://localhost:8000/v1/",
|
||||
"localhost:8000",
|
||||
"localhost:8000/v1/",
|
||||
},
|
||||
}
|
||||
|
||||
for configKey, registries := range validRegistries {
|
||||
for _, registry := range registries {
|
||||
var (
|
||||
configured AuthConfig
|
||||
ok bool
|
||||
)
|
||||
resolved := configFile.ResolveAuthConfig(registry)
|
||||
if configured, ok = configFile.Configs[configKey]; !ok {
|
||||
t.Fail()
|
||||
}
|
||||
if resolved.Email != configured.Email {
|
||||
t.Errorf("%s -> %q != %q\n", registry, resolved.Email, configured.Email)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +187,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))
|
||||
@@ -332,7 +335,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
|
||||
}
|
||||
@@ -367,7 +370,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
|
||||
}
|
||||
@@ -423,7 +426,7 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
|
||||
}
|
||||
}
|
||||
|
||||
container, err := b.runtime.Create(b.config)
|
||||
container, _, err := b.runtime.Create(b.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type ChangeType int
|
||||
@@ -33,74 +34,284 @@ 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Ignore changes in .docker-id
|
||||
_ = newRoot.Remove("/.docker-id")
|
||||
_ = oldRoot.Remove("/.docker-id")
|
||||
|
||||
return newRoot.Changes(oldRoot), nil
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
|
||||
{"kill", "Kill a running container"},
|
||||
{"login", "Register or Login to the docker registry server"},
|
||||
{"logs", "Fetch the logs of a container"},
|
||||
{"ls", "List links for containers"},
|
||||
{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
|
||||
{"top", "Lookup the running processes of a container"},
|
||||
{"ps", "List containers"},
|
||||
@@ -510,7 +511,8 @@ func (cli *DockerCli) CmdStop(args ...string) error {
|
||||
v.Set("t", strconv.Itoa(*nSeconds))
|
||||
|
||||
for _, name := range cmd.Args() {
|
||||
_, _, err := cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil)
|
||||
encName := cleanName(name)
|
||||
_, _, err := cli.call("POST", "/containers/"+encName+"/stop?"+v.Encode(), nil)
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
} else {
|
||||
@@ -535,7 +537,8 @@ func (cli *DockerCli) CmdRestart(args ...string) error {
|
||||
v.Set("t", strconv.Itoa(*nSeconds))
|
||||
|
||||
for _, name := range cmd.Args() {
|
||||
_, _, err := cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil)
|
||||
encName := cleanName(name)
|
||||
_, _, err := cli.call("POST", "/containers/"+encName+"/restart?"+v.Encode(), nil)
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
} else {
|
||||
@@ -557,7 +560,8 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
||||
|
||||
var encounteredError error
|
||||
for _, name := range args {
|
||||
_, _, err := cli.call("POST", "/containers/"+name+"/start", nil)
|
||||
encName := cleanName(name)
|
||||
_, _, err := cli.call("POST", "/containers/"+encName+"/start", nil)
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
encounteredError = fmt.Errorf("Error: failed to start one or more containers")
|
||||
@@ -579,10 +583,11 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
||||
}
|
||||
fmt.Fprintf(cli.out, "[")
|
||||
for i, name := range args {
|
||||
encName := cleanName(name)
|
||||
if i > 0 {
|
||||
fmt.Fprintf(cli.out, ",")
|
||||
}
|
||||
obj, _, err := cli.call("GET", "/containers/"+name+"/json", nil)
|
||||
obj, _, err := cli.call("GET", "/containers/"+encName+"/json", nil)
|
||||
if err != nil {
|
||||
obj, _, err = cli.call("GET", "/images/"+name+"/json", nil)
|
||||
if err != nil {
|
||||
@@ -740,6 +745,8 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
|
||||
func (cli *DockerCli) CmdRm(args ...string) error {
|
||||
cmd := Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers")
|
||||
v := cmd.Bool("v", false, "Remove the volumes associated to the container")
|
||||
link := cmd.Bool("link", false, "Remove the specified link and not the underlying container")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -751,8 +758,12 @@ func (cli *DockerCli) CmdRm(args ...string) error {
|
||||
if *v {
|
||||
val.Set("v", "1")
|
||||
}
|
||||
if *link {
|
||||
val.Set("link", "1")
|
||||
}
|
||||
for _, name := range cmd.Args() {
|
||||
_, _, err := cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil)
|
||||
encName := cleanName(name)
|
||||
_, _, err := cli.call("DELETE", "/containers/"+encName+"?"+val.Encode(), nil)
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
} else {
|
||||
@@ -774,7 +785,8 @@ func (cli *DockerCli) CmdKill(args ...string) error {
|
||||
}
|
||||
|
||||
for _, name := range args {
|
||||
_, _, err := cli.call("POST", "/containers/"+name+"/kill", nil)
|
||||
encName := cleanName(name)
|
||||
_, _, err := cli.call("POST", "/containers/"+encName+"/kill", nil)
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
} else {
|
||||
@@ -1017,10 +1029,10 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
||||
func displayablePorts(ports []APIPort) string {
|
||||
result := []string{}
|
||||
for _, port := range ports {
|
||||
if port.Type == "tcp" {
|
||||
result = append(result, fmt.Sprintf("%d->%d", port.PublicPort, port.PrivatePort))
|
||||
if port.IP == "" {
|
||||
result = append(result, fmt.Sprintf("%d/%s", port.PublicPort, port.Type))
|
||||
} else {
|
||||
result = append(result, fmt.Sprintf("%d->%d/%s", port.PublicPort, port.PrivatePort, port.Type))
|
||||
result = append(result, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type))
|
||||
}
|
||||
}
|
||||
sort.Strings(result)
|
||||
@@ -1073,7 +1085,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
||||
}
|
||||
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
||||
if !*quiet {
|
||||
fmt.Fprint(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS")
|
||||
fmt.Fprint(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES")
|
||||
if *size {
|
||||
fmt.Fprintln(w, "\tSIZE")
|
||||
} else {
|
||||
@@ -1082,11 +1094,16 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
||||
}
|
||||
|
||||
for _, out := range outs {
|
||||
for i := 0; i < len(out.Names); i++ {
|
||||
out.Names[i] = utils.Trunc(out.Names[i], 10)
|
||||
}
|
||||
|
||||
names := strings.Join(out.Names, ",")
|
||||
if !*quiet {
|
||||
if *noTrunc {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports))
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), names)
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports))
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\t%s\t", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), names)
|
||||
}
|
||||
if *size {
|
||||
if out.SizeRootFs > 0 {
|
||||
@@ -1112,6 +1129,64 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdLs(args ...string) error {
|
||||
cmd := Subcmd("ls", "", "List links for containers")
|
||||
flAll := cmd.Bool("a", false, "Show all links")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
v := url.Values{}
|
||||
if *flAll {
|
||||
v.Set("all", "1")
|
||||
}
|
||||
|
||||
body, _, err := cli.call("GET", "/containers/links?"+v.Encode(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var links []APILink
|
||||
if err := json.Unmarshal(body, &links); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
||||
fmt.Fprintf(w, "NAME\tID\tIMAGE")
|
||||
fmt.Fprintf(w, "\n")
|
||||
|
||||
sortLinks(links, func(i, j APILink) bool {
|
||||
return len(i.Path) < len(j.Path)
|
||||
})
|
||||
for _, link := range links {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s", link.Path, utils.TruncateID(link.ContainerID), link.Image)
|
||||
fmt.Fprintf(w, "\n")
|
||||
}
|
||||
w.Flush()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdLink(args ...string) error {
|
||||
cmd := Subcmd("link", "CURRENT_NAME NEW_NAME", "Link the container with a new name")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
if cmd.NArg() != 2 {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
body := map[string]string{
|
||||
"currentName": cmd.Arg(0),
|
||||
"newName": cmd.Arg(1),
|
||||
}
|
||||
|
||||
_, _, err := cli.call("POST", "/containers/link", body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdCommit(args ...string) error {
|
||||
cmd := Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]", "Create a new image from a container's changes")
|
||||
flComment := cmd.String("m", "", "Commit message")
|
||||
@@ -1229,8 +1304,9 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
name := cleanName(cmd.Arg(0))
|
||||
|
||||
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out); err != nil {
|
||||
if err := cli.hijack("POST", "/containers/"+name+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out, cli.err); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -1245,8 +1321,9 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
|
||||
body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
|
||||
name := cmd.Arg(0)
|
||||
name = cleanName(name)
|
||||
body, _, err := cli.call("GET", "/containers/"+name+"/json", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1273,7 +1350,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
||||
v.Set("stdout", "1")
|
||||
v.Set("stderr", "1")
|
||||
|
||||
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, cli.in, cli.out); err != nil {
|
||||
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, cli.in, cli.out, cli.err); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -1433,6 +1510,9 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
flRm := cmd.Lookup("rm")
|
||||
autoRemove, _ := strconv.ParseBool(flRm.Value.String())
|
||||
|
||||
var containerIDFile *os.File
|
||||
if len(hostConfig.ContainerIDFile) > 0 {
|
||||
if _, err := ioutil.ReadFile(hostConfig.ContainerIDFile); err == nil {
|
||||
@@ -1537,7 +1617,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
v := url.Values{}
|
||||
v.Set("logs", "1")
|
||||
v.Set("stream", "1")
|
||||
var out io.Writer
|
||||
var out, stderr io.Writer
|
||||
|
||||
if config.AttachStdin {
|
||||
v.Set("stdin", "1")
|
||||
@@ -1548,7 +1628,11 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
}
|
||||
if config.AttachStderr {
|
||||
v.Set("stderr", "1")
|
||||
out = cli.out
|
||||
if config.Tty {
|
||||
stderr = cli.out
|
||||
} else {
|
||||
stderr = cli.err
|
||||
}
|
||||
}
|
||||
|
||||
signals := make(chan os.Signal, 1)
|
||||
@@ -1562,7 +1646,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
}
|
||||
}()
|
||||
|
||||
if err := cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, cli.in, out); err != nil {
|
||||
if err := cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, cli.in, out, stderr); err != nil {
|
||||
utils.Debugf("Error hijack: %s", err)
|
||||
return err
|
||||
}
|
||||
@@ -1576,6 +1660,12 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if autoRemove {
|
||||
_, _, err = cli.call("DELETE", "/containers/"+runResult.ID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if status != 0 {
|
||||
return &utils.StatusError{Status: status}
|
||||
}
|
||||
@@ -1729,7 +1819,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, out io.Writer) error {
|
||||
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer) error {
|
||||
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil)
|
||||
if err != nil {
|
||||
@@ -1755,10 +1845,16 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
||||
rwc, br := clientconn.Hijack()
|
||||
defer rwc.Close()
|
||||
|
||||
var receiveStdout (chan error)
|
||||
if out != nil {
|
||||
receiveStdout = utils.Go(func() error {
|
||||
_, err := io.Copy(out, br)
|
||||
var receiveStdout chan error
|
||||
|
||||
if stdout != nil {
|
||||
receiveStdout = utils.Go(func() (err error) {
|
||||
// When TTY is ON, use regular copy
|
||||
if setRawTerminal {
|
||||
_, err = io.Copy(stdout, br)
|
||||
} else {
|
||||
_, err = utils.StdCopy(stdout, stderr, br)
|
||||
}
|
||||
utils.Debugf("[hijack] End of stdout")
|
||||
return err
|
||||
})
|
||||
@@ -1790,7 +1886,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
||||
return nil
|
||||
})
|
||||
|
||||
if out != nil {
|
||||
if stdout != nil {
|
||||
if err := <-receiveStdout; err != nil {
|
||||
utils.Debugf("Error receiveStdout: %s", err)
|
||||
return err
|
||||
@@ -1901,6 +1997,10 @@ func getExitCode(cli *DockerCli, containerId string) (int, error) {
|
||||
return c.State.ExitCode, nil
|
||||
}
|
||||
|
||||
func cleanName(name string) string {
|
||||
return strings.Replace(name, "/", "%252F", -1)
|
||||
}
|
||||
|
||||
func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli {
|
||||
var (
|
||||
isTerminal = false
|
||||
|
||||
@@ -400,7 +400,7 @@ func TestRunDetach(t *testing.T) {
|
||||
})
|
||||
|
||||
// wait for CmdRun to return
|
||||
setTimeout(t, "Waiting for CmdRun timed out", 5*time.Second, func() {
|
||||
setTimeout(t, "Waiting for CmdRun timed out", 15*time.Second, func() {
|
||||
<-ch
|
||||
})
|
||||
|
||||
@@ -458,7 +458,7 @@ func TestAttachDetach(t *testing.T) {
|
||||
})
|
||||
|
||||
// wait for CmdRun to return
|
||||
setTimeout(t, "Waiting for CmdAttach timed out", 5*time.Second, func() {
|
||||
setTimeout(t, "Waiting for CmdAttach timed out", 15*time.Second, func() {
|
||||
<-ch
|
||||
})
|
||||
|
||||
@@ -542,3 +542,40 @@ func TestAttachDisconnect(t *testing.T) {
|
||||
cStdin.Close()
|
||||
container.Wait()
|
||||
}
|
||||
|
||||
// Expected behaviour: container gets deleted automatically after exit
|
||||
func TestRunAutoRemove(t *testing.T) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
defer close(c)
|
||||
if err := cli.CmdRun("-rm", unitTestImageID, "hostname"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
var temporaryContainerID string
|
||||
setTimeout(t, "Reading command output time out", 2*time.Second, func() {
|
||||
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
temporaryContainerID = cmdOutput
|
||||
if err := closeWrap(stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
|
||||
<-c
|
||||
})
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
if len(globalRuntime.List()) > 0 {
|
||||
t.Fatalf("failed to remove container automatically: container %s still exists", temporaryContainerID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
type DaemonConfig struct {
|
||||
Pidfile string
|
||||
GraphPath string
|
||||
ProtoAddresses []string
|
||||
AutoRestart bool
|
||||
EnableCors bool
|
||||
Dns []string
|
||||
EnableIptables bool
|
||||
BridgeIface string
|
||||
DefaultIp net.IP
|
||||
DeviceSet DeviceSet
|
||||
}
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
@@ -58,6 +57,8 @@ type Container struct {
|
||||
// Store rw/ro in a separate structure to preserve reverse-compatibility on-disk.
|
||||
// Easier than migrating older container configs :)
|
||||
VolumesRW map[string]bool
|
||||
|
||||
activeLinks map[string]*Link
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@@ -70,7 +71,8 @@ type Config struct {
|
||||
AttachStdin bool
|
||||
AttachStdout bool
|
||||
AttachStderr bool
|
||||
PortSpecs []string
|
||||
PortSpecs []string // Deprecated - Can be in the format of 8080/tcp
|
||||
ExposedPorts map[Port]struct{}
|
||||
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
|
||||
OpenStdin bool // Open stdin
|
||||
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
|
||||
@@ -90,6 +92,8 @@ type HostConfig struct {
|
||||
Binds []string
|
||||
ContainerIDFile string
|
||||
LxcConf []KeyValuePair
|
||||
PortBindings map[Port][]PortBinding
|
||||
Links []string
|
||||
}
|
||||
|
||||
type BindMap struct {
|
||||
@@ -107,6 +111,34 @@ type KeyValuePair struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
type PortBinding struct {
|
||||
HostIp string
|
||||
HostPort string
|
||||
}
|
||||
|
||||
// 80/tcp
|
||||
type Port string
|
||||
|
||||
func (p Port) Proto() string {
|
||||
return strings.Split(string(p), "/")[1]
|
||||
}
|
||||
|
||||
func (p Port) Port() string {
|
||||
return strings.Split(string(p), "/")[0]
|
||||
}
|
||||
|
||||
func (p Port) Int() int {
|
||||
i, err := parsePort(p.Port())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func NewPort(proto, port string) Port {
|
||||
return Port(fmt.Sprintf("%s/%s", port, proto))
|
||||
}
|
||||
|
||||
func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
|
||||
cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
|
||||
if os.Getenv("TEST") != "" {
|
||||
@@ -126,6 +158,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
||||
flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file")
|
||||
flNetwork := cmd.Bool("n", true, "Enable networking for this container")
|
||||
flPrivileged := cmd.Bool("privileged", false, "Give extended privileges to this container")
|
||||
flAutoRemove := cmd.Bool("rm", false, "Automatically remove the container when it exits (incompatible with -d)")
|
||||
|
||||
if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit {
|
||||
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
|
||||
@@ -134,8 +167,11 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
||||
|
||||
flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)")
|
||||
|
||||
var flPorts ListOpts
|
||||
cmd.Var(&flPorts, "p", "Expose a container's port to the host (use 'docker port' to see the actual mapping)")
|
||||
var flPublish ListOpts
|
||||
cmd.Var(&flPublish, "p", "Publish a container's port to the host (use 'docker port' to see the actual mapping)")
|
||||
|
||||
var flExpose ListOpts
|
||||
cmd.Var(&flExpose, "expose", "Expose a port from the container without publishing it to your host")
|
||||
|
||||
var flEnv ListOpts
|
||||
cmd.Var(&flEnv, "e", "Set environment variables")
|
||||
@@ -154,6 +190,9 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
||||
var flLxcOpts ListOpts
|
||||
cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
|
||||
|
||||
var flLinks ListOpts
|
||||
cmd.Var(&flLinks, "link", "Add link to another container (containerid:alias)")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
@@ -174,6 +213,10 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
||||
}
|
||||
}
|
||||
|
||||
if *flDetach && *flAutoRemove {
|
||||
return nil, nil, cmd, fmt.Errorf("Conflicting options: -rm and -d")
|
||||
}
|
||||
|
||||
var binds []string
|
||||
|
||||
// add any bind targets to the list of container volumes
|
||||
@@ -215,10 +258,28 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
||||
hostname = parts[0]
|
||||
domainname = parts[1]
|
||||
}
|
||||
|
||||
ports, portBindings, err := parsePortSpecs(flPublish)
|
||||
if err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
|
||||
// Merge in exposed ports to the map of published ports
|
||||
for _, e := range flExpose {
|
||||
if strings.Contains(e, ":") {
|
||||
return nil, nil, cmd, fmt.Errorf("Invalid port format for -expose: %s", e)
|
||||
}
|
||||
p := NewPort(splitProtoPort(e))
|
||||
if _, exists := ports[p]; !exists {
|
||||
ports[p] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
config := &Config{
|
||||
Hostname: hostname,
|
||||
Hostname: *flHostname,
|
||||
Domainname: domainname,
|
||||
PortSpecs: flPorts,
|
||||
PortSpecs: nil, // Deprecated
|
||||
ExposedPorts: ports,
|
||||
User: *flUser,
|
||||
Tty: *flTty,
|
||||
NetworkDisabled: !*flNetwork,
|
||||
@@ -238,10 +299,13 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
||||
Privileged: *flPrivileged,
|
||||
WorkingDir: *flWorkingDir,
|
||||
}
|
||||
|
||||
hostConfig := &HostConfig{
|
||||
Binds: binds,
|
||||
ContainerIDFile: *flContainerIDFile,
|
||||
LxcConf: lxcConf,
|
||||
PortBindings: portBindings,
|
||||
Links: flLinks,
|
||||
}
|
||||
|
||||
if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
|
||||
@@ -256,48 +320,55 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
||||
return config, hostConfig, cmd, nil
|
||||
}
|
||||
|
||||
type PortMapping map[string]string
|
||||
type PortMapping map[string]string // Deprecated
|
||||
|
||||
type NetworkSettings struct {
|
||||
IPAddress string
|
||||
IPPrefixLen int
|
||||
Gateway string
|
||||
Bridge string
|
||||
PortMapping map[string]PortMapping
|
||||
PortMapping map[string]PortMapping // Deprecated
|
||||
Ports map[Port][]PortBinding
|
||||
}
|
||||
|
||||
// returns a more easy to process description of the port mapping defined in the settings
|
||||
func (settings *NetworkSettings) PortMappingAPI() []APIPort {
|
||||
var mapping []APIPort
|
||||
for private, public := range settings.PortMapping["Tcp"] {
|
||||
pubint, _ := strconv.ParseInt(public, 0, 0)
|
||||
privint, _ := strconv.ParseInt(private, 0, 0)
|
||||
mapping = append(mapping, APIPort{
|
||||
PrivatePort: privint,
|
||||
PublicPort: pubint,
|
||||
Type: "tcp",
|
||||
})
|
||||
}
|
||||
for private, public := range settings.PortMapping["Udp"] {
|
||||
pubint, _ := strconv.ParseInt(public, 0, 0)
|
||||
privint, _ := strconv.ParseInt(private, 0, 0)
|
||||
mapping = append(mapping, APIPort{
|
||||
PrivatePort: privint,
|
||||
PublicPort: pubint,
|
||||
Type: "udp",
|
||||
})
|
||||
for port, bindings := range settings.Ports {
|
||||
p, _ := parsePort(port.Port())
|
||||
if len(bindings) == 0 {
|
||||
mapping = append(mapping, APIPort{
|
||||
PublicPort: int64(p),
|
||||
Type: port.Proto(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
for _, binding := range bindings {
|
||||
p, _ := parsePort(port.Port())
|
||||
h, _ := parsePort(binding.HostPort)
|
||||
mapping = append(mapping, APIPort{
|
||||
PrivatePort: int64(p),
|
||||
PublicPort: int64(h),
|
||||
Type: port.Proto(),
|
||||
IP: binding.HostIp,
|
||||
})
|
||||
}
|
||||
}
|
||||
return mapping
|
||||
}
|
||||
|
||||
// Inject the io.Reader at the given path. Note: do not close the reader
|
||||
func (container *Container) Inject(file io.Reader, pth string) error {
|
||||
// Make sure the directory exists
|
||||
if err := os.MkdirAll(path.Join(container.rwPath(), path.Dir(pth)), 0755); err != nil {
|
||||
if err := container.EnsureMounted(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make sure the directory exists
|
||||
if err := os.MkdirAll(path.Join(container.RootfsPath(), path.Dir(pth)), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// FIXME: Handle permissions/already existing dest
|
||||
dest, err := os.Create(path.Join(container.rwPath(), pth))
|
||||
dest, err := os.Create(path.Join(container.RootfsPath(), pth))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -393,7 +464,7 @@ func (container *Container) startPty() error {
|
||||
// stdin
|
||||
if container.Config.OpenStdin {
|
||||
container.cmd.Stdin = ptySlave
|
||||
container.cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
|
||||
container.cmd.SysProcAttr.Setctty = true
|
||||
go func() {
|
||||
defer container.stdin.Close()
|
||||
utils.Debugf("[startPty] Begin of stdin pipe")
|
||||
@@ -473,13 +544,11 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
|
||||
utils.Debugf("[start] attach stdout\n")
|
||||
defer utils.Debugf("[end] attach stdout\n")
|
||||
// If we are in StdinOnce mode, then close stdin
|
||||
if container.Config.StdinOnce {
|
||||
if stdin != nil {
|
||||
defer stdin.Close()
|
||||
}
|
||||
if stdinCloser != nil {
|
||||
defer stdinCloser.Close()
|
||||
}
|
||||
if container.Config.StdinOnce && stdin != nil {
|
||||
defer stdin.Close()
|
||||
}
|
||||
if stdinCloser != nil {
|
||||
defer stdinCloser.Close()
|
||||
}
|
||||
_, err := io.Copy(stdout, cStdout)
|
||||
if err != nil {
|
||||
@@ -511,13 +580,11 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
|
||||
utils.Debugf("[start] attach stderr\n")
|
||||
defer utils.Debugf("[end] attach stderr\n")
|
||||
// If we are in StdinOnce mode, then close stdin
|
||||
if container.Config.StdinOnce {
|
||||
if stdin != nil {
|
||||
defer stdin.Close()
|
||||
}
|
||||
if stdinCloser != nil {
|
||||
defer stdinCloser.Close()
|
||||
}
|
||||
if container.Config.StdinOnce && stdin != nil {
|
||||
defer stdin.Close()
|
||||
}
|
||||
if stdinCloser != nil {
|
||||
defer stdinCloser.Close()
|
||||
}
|
||||
_, err := io.Copy(stderr, cStderr)
|
||||
if err != nil {
|
||||
@@ -579,7 +646,7 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
||||
if container.runtime.networkManager.disabled {
|
||||
container.Config.NetworkDisabled = true
|
||||
} else {
|
||||
if err := container.allocateNetwork(); err != nil {
|
||||
if err := container.allocateNetwork(hostConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -742,6 +809,7 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
||||
}
|
||||
|
||||
params := []string{
|
||||
"lxc-start",
|
||||
"-n", container.ID,
|
||||
"-f", container.lxcConfigPath(),
|
||||
"--",
|
||||
@@ -769,6 +837,46 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
||||
"-e", "container=lxc",
|
||||
"-e", "HOSTNAME="+container.Config.Hostname,
|
||||
)
|
||||
|
||||
// Init any links between the parent and children
|
||||
runtime := container.runtime
|
||||
|
||||
children, err := runtime.Children(fmt.Sprintf("/%s", container.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(children) > 0 {
|
||||
container.activeLinks = make(map[string]*Link, len(children))
|
||||
|
||||
// If we encounter an error make sure that we rollback any network
|
||||
// config and ip table changes
|
||||
rollback := func() {
|
||||
for _, link := range container.activeLinks {
|
||||
link.Disable()
|
||||
}
|
||||
container.activeLinks = nil
|
||||
}
|
||||
|
||||
for p, child := range children {
|
||||
link, err := NewLink(container, child, p, runtime.networkManager.bridgeIface)
|
||||
if err != nil {
|
||||
rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
container.activeLinks[link.Alias()] = link
|
||||
if err := link.Enable(); err != nil {
|
||||
rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
for _, envVar := range link.ToEnv() {
|
||||
params = append(params, "-e", envVar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if container.Config.WorkingDir != "" {
|
||||
workingDir := path.Clean(container.Config.WorkingDir)
|
||||
utils.Debugf("[working dir] working dir is %s", workingDir)
|
||||
@@ -790,7 +898,21 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
||||
params = append(params, "--", container.Path)
|
||||
params = append(params, container.Args...)
|
||||
|
||||
container.cmd = exec.Command("lxc-start", params...)
|
||||
if RootIsShared() {
|
||||
// lxc-start really needs / to be private, or all kinds of stuff break
|
||||
// What we really want is to clone into a new namespace and then
|
||||
// mount / MS_REC|MS_PRIVATE, but since we can't really clone or fork
|
||||
// without exec in go we have to do this horrible shell hack...
|
||||
shellString :=
|
||||
"mount --make-rprivate /; exec " +
|
||||
utils.ShellQuoteArguments(params)
|
||||
|
||||
params = []string{
|
||||
"unshare", "-m", "--", "/bin/sh", "-c", shellString,
|
||||
}
|
||||
}
|
||||
|
||||
container.cmd = exec.Command(params[0], params[1:]...)
|
||||
|
||||
// Setup logging of stdout and stderr to disk
|
||||
if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil {
|
||||
@@ -800,7 +922,8 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
container.cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
||||
|
||||
if container.Config.Tty {
|
||||
err = container.startPty()
|
||||
} else {
|
||||
@@ -818,7 +941,7 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
||||
|
||||
container.ToDisk()
|
||||
container.SaveHostConfig(hostConfig)
|
||||
go container.monitor()
|
||||
go container.monitor(hostConfig)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -865,7 +988,7 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) {
|
||||
return utils.NewBufReader(reader), nil
|
||||
}
|
||||
|
||||
func (container *Container) allocateNetwork() error {
|
||||
func (container *Container) allocateNetwork(hostConfig *HostConfig) error {
|
||||
if container.Config.NetworkDisabled {
|
||||
return nil
|
||||
}
|
||||
@@ -892,36 +1015,59 @@ func (container *Container) allocateNetwork() error {
|
||||
}
|
||||
}
|
||||
|
||||
var portSpecs []string
|
||||
if !container.State.Ghost {
|
||||
portSpecs = container.Config.PortSpecs
|
||||
} else {
|
||||
for backend, frontend := range container.NetworkSettings.PortMapping["Tcp"] {
|
||||
portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/tcp", frontend, backend))
|
||||
if container.Config.PortSpecs != nil {
|
||||
utils.Debugf("Migrating port mappings for container: %s", strings.Join(container.Config.PortSpecs, ", "))
|
||||
if err := migratePortMappings(container.Config); err != nil {
|
||||
return err
|
||||
}
|
||||
for backend, frontend := range container.NetworkSettings.PortMapping["Udp"] {
|
||||
portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/udp", frontend, backend))
|
||||
container.Config.PortSpecs = nil
|
||||
}
|
||||
|
||||
portSpecs := make(map[Port]struct{})
|
||||
bindings := make(map[Port][]PortBinding)
|
||||
|
||||
if !container.State.Ghost {
|
||||
if container.Config.ExposedPorts != nil {
|
||||
portSpecs = container.Config.ExposedPorts
|
||||
}
|
||||
if hostConfig.PortBindings != nil {
|
||||
bindings = hostConfig.PortBindings
|
||||
}
|
||||
} else {
|
||||
if container.NetworkSettings.Ports != nil {
|
||||
for port, binding := range container.NetworkSettings.Ports {
|
||||
portSpecs[port] = struct{}{}
|
||||
bindings[port] = binding
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
container.NetworkSettings.PortMapping = make(map[string]PortMapping)
|
||||
container.NetworkSettings.PortMapping["Tcp"] = make(PortMapping)
|
||||
container.NetworkSettings.PortMapping["Udp"] = make(PortMapping)
|
||||
for _, spec := range portSpecs {
|
||||
nat, err := iface.AllocatePort(spec)
|
||||
if err != nil {
|
||||
iface.Release()
|
||||
return err
|
||||
container.NetworkSettings.PortMapping = nil
|
||||
|
||||
for port := range portSpecs {
|
||||
binding := bindings[port]
|
||||
for i := 0; i < len(binding); i++ {
|
||||
b := binding[i]
|
||||
nat, err := iface.AllocatePort(port, b)
|
||||
if err != nil {
|
||||
iface.Release()
|
||||
return err
|
||||
}
|
||||
utils.Debugf("Allocate port: %s:%s->%s", nat.Binding.HostIp, port, nat.Binding.HostPort)
|
||||
binding[i] = nat.Binding
|
||||
}
|
||||
proto := strings.Title(nat.Proto)
|
||||
backend, frontend := strconv.Itoa(nat.Backend), strconv.Itoa(nat.Frontend)
|
||||
container.NetworkSettings.PortMapping[proto][backend] = frontend
|
||||
bindings[port] = binding
|
||||
}
|
||||
container.SaveHostConfig(hostConfig)
|
||||
|
||||
container.NetworkSettings.Ports = bindings
|
||||
container.network = iface
|
||||
|
||||
container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface
|
||||
container.NetworkSettings.IPAddress = iface.IPNet.IP.String()
|
||||
container.NetworkSettings.IPPrefixLen, _ = iface.IPNet.Mask.Size()
|
||||
container.NetworkSettings.Gateway = iface.Gateway.String()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -948,7 +1094,7 @@ func (container *Container) waitLxc() error {
|
||||
}
|
||||
}
|
||||
|
||||
func (container *Container) monitor() {
|
||||
func (container *Container) monitor(hostConfig *HostConfig) {
|
||||
// Wait for the program to exit
|
||||
utils.Debugf("Waiting for process")
|
||||
|
||||
@@ -979,6 +1125,14 @@ func (container *Container) monitor() {
|
||||
|
||||
// Cleanup
|
||||
container.releaseNetwork()
|
||||
|
||||
// Disable all active links
|
||||
if container.activeLinks != nil {
|
||||
for _, link := range container.activeLinks {
|
||||
link.Disable()
|
||||
}
|
||||
}
|
||||
|
||||
if container.Config.OpenStdin {
|
||||
if err := container.stdin.Close(); err != nil {
|
||||
utils.Debugf("%s: Error close stdin: %s", container.ID, err)
|
||||
@@ -1108,7 +1262,15 @@ func (container *Container) Resize(h, w int) error {
|
||||
}
|
||||
|
||||
func (container *Container) ExportRw() (Archive, error) {
|
||||
return Tar(container.rwPath(), Uncompressed)
|
||||
if err := container.EnsureMounted(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
image, err := container.GetImage()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return image.ExportChanges(container.runtime, container.RootfsPath(), container.rwPath(), container.ID)
|
||||
}
|
||||
|
||||
func (container *Container) RwChecksum() (string, error) {
|
||||
@@ -1150,20 +1312,33 @@ func (container *Container) EnsureMounted() error {
|
||||
return container.Mount()
|
||||
}
|
||||
|
||||
func (container *Container) EnsureUnmounted() error {
|
||||
if mounted, err := container.Mounted(); err != nil {
|
||||
return err
|
||||
} else if !mounted {
|
||||
return nil
|
||||
}
|
||||
return container.Unmount()
|
||||
}
|
||||
|
||||
func (container *Container) Mount() error {
|
||||
image, err := container.GetImage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return image.Mount(container.RootfsPath(), container.rwPath())
|
||||
return image.Mount(container.runtime, container.RootfsPath(), container.rwPath(), container.ID)
|
||||
}
|
||||
|
||||
func (container *Container) Changes() ([]Change, error) {
|
||||
if err := container.EnsureMounted(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
image, err := container.GetImage()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return image.Changes(container.rwPath())
|
||||
return image.Changes(container.runtime, container.RootfsPath(), container.rwPath(), container.ID)
|
||||
}
|
||||
|
||||
func (container *Container) GetImage() (*Image, error) {
|
||||
@@ -1174,11 +1349,20 @@ func (container *Container) GetImage() (*Image, error) {
|
||||
}
|
||||
|
||||
func (container *Container) Mounted() (bool, error) {
|
||||
return Mounted(container.RootfsPath())
|
||||
image, err := container.GetImage()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return image.Mounted(container.runtime, container.RootfsPath(), container.rwPath())
|
||||
}
|
||||
|
||||
func (container *Container) Unmount() error {
|
||||
return Unmount(container.RootfsPath())
|
||||
image, err := container.GetImage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = image.Unmount(container.runtime, container.RootfsPath(), container.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
// ShortID returns a shorthand version of the container's id for convenience.
|
||||
@@ -1266,5 +1450,11 @@ func (container *Container) Copy(resource string) (Archive, error) {
|
||||
filter = []string{path.Base(basePath)}
|
||||
basePath = path.Dir(basePath)
|
||||
}
|
||||
return TarFilter(basePath, Uncompressed, filter)
|
||||
return TarFilter(basePath, Uncompressed, filter, true, nil)
|
||||
}
|
||||
|
||||
// Returns true if the container exposes a certain port
|
||||
func (container *Container) Exposes(p Port) bool {
|
||||
_, exists := container.Config.ExposedPorts[p]
|
||||
return exists
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
func TestIDFormat(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container1, err := runtime.Create(
|
||||
container1, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/sh", "-c", "echo hello world"},
|
||||
@@ -388,7 +388,7 @@ func TestRun(t *testing.T) {
|
||||
func TestOutput(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"echo", "-n", "foobar"},
|
||||
@@ -411,7 +411,7 @@ func TestKillDifferentUser(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"cat"},
|
||||
OpenStdin: true,
|
||||
@@ -471,7 +471,7 @@ func TestCreateVolume(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c, err := runtime.Create(config)
|
||||
c, _, err := runtime.Create(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -486,7 +486,7 @@ func TestCreateVolume(t *testing.T) {
|
||||
func TestKill(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"sleep", "2"},
|
||||
},
|
||||
@@ -530,7 +530,7 @@ func TestExitCode(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
trueContainer, err := runtime.Create(&Config{
|
||||
trueContainer, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/true", ""},
|
||||
})
|
||||
@@ -545,7 +545,7 @@ func TestExitCode(t *testing.T) {
|
||||
t.Errorf("Unexpected exit code %d (expected 0)", trueContainer.State.ExitCode)
|
||||
}
|
||||
|
||||
falseContainer, err := runtime.Create(&Config{
|
||||
falseContainer, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/false", ""},
|
||||
})
|
||||
@@ -564,7 +564,7 @@ func TestExitCode(t *testing.T) {
|
||||
func TestRestart(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"echo", "-n", "foobar"},
|
||||
},
|
||||
@@ -594,7 +594,7 @@ func TestRestart(t *testing.T) {
|
||||
func TestRestartStdin(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"cat"},
|
||||
|
||||
@@ -672,7 +672,7 @@ func TestUser(t *testing.T) {
|
||||
defer nuke(runtime)
|
||||
|
||||
// Default user must be root
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"id"},
|
||||
},
|
||||
@@ -690,7 +690,7 @@ func TestUser(t *testing.T) {
|
||||
}
|
||||
|
||||
// Set a username
|
||||
container, err = runtime.Create(&Config{
|
||||
container, _, err = runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"id"},
|
||||
|
||||
@@ -710,7 +710,7 @@ func TestUser(t *testing.T) {
|
||||
}
|
||||
|
||||
// Set a UID
|
||||
container, err = runtime.Create(&Config{
|
||||
container, _, err = runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"id"},
|
||||
|
||||
@@ -730,7 +730,7 @@ func TestUser(t *testing.T) {
|
||||
}
|
||||
|
||||
// Set a different user by uid
|
||||
container, err = runtime.Create(&Config{
|
||||
container, _, err = runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"id"},
|
||||
|
||||
@@ -752,7 +752,7 @@ func TestUser(t *testing.T) {
|
||||
}
|
||||
|
||||
// Set a different user by username
|
||||
container, err = runtime.Create(&Config{
|
||||
container, _, err = runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"id"},
|
||||
|
||||
@@ -772,7 +772,7 @@ func TestUser(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test an wrong username
|
||||
container, err = runtime.Create(&Config{
|
||||
container, _, err = runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"id"},
|
||||
|
||||
@@ -793,7 +793,7 @@ func TestMultipleContainers(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
container1, err := runtime.Create(&Config{
|
||||
container1, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"sleep", "2"},
|
||||
},
|
||||
@@ -803,7 +803,7 @@ func TestMultipleContainers(t *testing.T) {
|
||||
}
|
||||
defer runtime.Destroy(container1)
|
||||
|
||||
container2, err := runtime.Create(&Config{
|
||||
container2, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"sleep", "2"},
|
||||
},
|
||||
@@ -847,7 +847,7 @@ func TestMultipleContainers(t *testing.T) {
|
||||
func TestStdin(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"cat"},
|
||||
|
||||
@@ -892,7 +892,7 @@ func TestStdin(t *testing.T) {
|
||||
func TestTty(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"cat"},
|
||||
|
||||
@@ -937,7 +937,7 @@ func TestTty(t *testing.T) {
|
||||
func TestEnv(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"env"},
|
||||
},
|
||||
@@ -986,7 +986,7 @@ func TestEnv(t *testing.T) {
|
||||
func TestEntrypoint(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Entrypoint: []string{"/bin/echo"},
|
||||
@@ -1009,7 +1009,7 @@ func TestEntrypoint(t *testing.T) {
|
||||
func TestEntrypointNoCmd(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Entrypoint: []string{"/bin/echo", "foobar"},
|
||||
@@ -1060,7 +1060,7 @@ func TestLXCConfig(t *testing.T) {
|
||||
cpuMin := 100
|
||||
cpuMax := 10000
|
||||
cpu := cpuMin + rand.Intn(cpuMax-cpuMin)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/true"},
|
||||
|
||||
@@ -1084,7 +1084,7 @@ func TestLXCConfig(t *testing.T) {
|
||||
func TestCustomLxcConfig(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/true"},
|
||||
|
||||
@@ -1115,7 +1115,7 @@ func BenchmarkRunSequencial(b *testing.B) {
|
||||
runtime := mkRuntime(b)
|
||||
defer nuke(runtime)
|
||||
for i := 0; i < b.N; i++ {
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"echo", "-n", "foo"},
|
||||
},
|
||||
@@ -1147,7 +1147,7 @@ func BenchmarkRunParallel(b *testing.B) {
|
||||
complete := make(chan error)
|
||||
tasks = append(tasks, complete)
|
||||
go func(i int, complete chan error) {
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"echo", "-n", "foo"},
|
||||
},
|
||||
@@ -1297,7 +1297,7 @@ func TestBindMounts(t *testing.T) {
|
||||
func TestVolumesFromReadonlyMount(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/echo", "-n", "foobar"},
|
||||
@@ -1316,7 +1316,7 @@ func TestVolumesFromReadonlyMount(t *testing.T) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
container2, err := runtime.Create(
|
||||
container2, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/echo", "-n", "foobar"},
|
||||
@@ -1352,7 +1352,7 @@ func TestRestartWithVolumes(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"echo", "-n", "foobar"},
|
||||
Volumes: map[string]struct{}{"/test": {}},
|
||||
@@ -1395,7 +1395,7 @@ func TestVolumesFromWithVolumes(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"},
|
||||
Volumes: map[string]struct{}{"/test": {}},
|
||||
@@ -1422,7 +1422,7 @@ func TestVolumesFromWithVolumes(t *testing.T) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
container2, err := runtime.Create(
|
||||
container2, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"cat", "/test/foo"},
|
||||
@@ -1463,7 +1463,7 @@ func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c, err := runtime.Create(config)
|
||||
c, _, err := runtime.Create(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1529,7 +1529,7 @@ func TestMultipleVolumesFrom(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"},
|
||||
Volumes: map[string]struct{}{"/test": {}},
|
||||
@@ -1556,7 +1556,7 @@ func TestMultipleVolumesFrom(t *testing.T) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
container2, err := runtime.Create(
|
||||
container2, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"sh", "-c", "echo -n bar > /other/foo"},
|
||||
@@ -1577,7 +1577,7 @@ func TestMultipleVolumesFrom(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
container3, err := runtime.Create(
|
||||
container3, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/echo", "-n", "foobar"},
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# [PackageDev] target_format: plist, ext: tmLanguage
|
||||
---
|
||||
name: Dockerfile
|
||||
scopeName: source.dockerfile
|
||||
uuid: a39d8795-59d2-49af-aa00-fe74ee29576e
|
||||
|
||||
patterns:
|
||||
# Keywords
|
||||
- name: keyword.control.dockerfile
|
||||
match: ^\s*(FROM|MAINTAINER|RUN|CMD|EXPOSE|ENV|ADD)\s
|
||||
- name: keyword.operator.dockerfile
|
||||
match: ^\s*(ENTRYPOINT|VOLUME|USER|WORKDIR)\s
|
||||
# String
|
||||
- name: string.quoted.double.dockerfile
|
||||
begin: "\""
|
||||
end: "\""
|
||||
patterns:
|
||||
- name: constant.character.escaped.dockerfile
|
||||
match: \\.
|
||||
# Comment
|
||||
- name: comment.block.dockerfile
|
||||
match: ^\s*#.*$
|
||||
...
|
||||
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Dockerfile</string>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>^\s*(FROM|MAINTAINER|RUN|CMD|EXPOSE|ENV|ADD)\s</string>
|
||||
<key>name</key>
|
||||
<string>keyword.control.dockerfile</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>^\s*(ENTRYPOINT|VOLUME|USER|WORKDIR)\s</string>
|
||||
<key>name</key>
|
||||
<string>keyword.operator.dockerfile</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>begin</key>
|
||||
<string>"</string>
|
||||
<key>end</key>
|
||||
<string>"</string>
|
||||
<key>name</key>
|
||||
<string>string.quoted.double.dockerfile</string>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\\.</string>
|
||||
<key>name</key>
|
||||
<string>constant.character.escaped.dockerfile</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>^\s*#.*$</string>
|
||||
<key>name</key>
|
||||
<string>comment.block.dockerfile</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>scopeName</key>
|
||||
<string>source.dockerfile</string>
|
||||
<key>uuid</key>
|
||||
<string>a39d8795-59d2-49af-aa00-fe74ee29576e</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1 @@
|
||||
Asbjorn Enge <asbjorn@hanafjedle.net> (@asbjornenge)
|
||||
@@ -0,0 +1,9 @@
|
||||
# Dockerfile.tmLanguage
|
||||
|
||||
Pretty basic Dockerfile.tmLanguage for Sublime Text syntax highlighting.
|
||||
|
||||
PR's with syntax updates, suggestions etc. are all very much appreciated!
|
||||
|
||||
I'll get to making this installable via Package Control soon!
|
||||
|
||||
enjoy.
|
||||
@@ -1,2 +1,2 @@
|
||||
Kawsar Saiyeed <kawsar.saiyeed@projiris.com> (@KSid)
|
||||
Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
Kawsar Saiyeed <kawsar.saiyeed@projiris.com> (@KSid)
|
||||
|
||||
@@ -341,7 +341,7 @@ _docker_pull()
|
||||
|
||||
_docker_push()
|
||||
{
|
||||
return
|
||||
__docker_image_repos
|
||||
}
|
||||
|
||||
_docker_restart()
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
# Create a CentOS base image for Docker
|
||||
# From unclejack https://github.com/dotcloud/docker/issues/290
|
||||
set -e
|
||||
|
||||
MIRROR_URL="http://centos.netnitco.net/6.4/os/x86_64/"
|
||||
MIRROR_URL_UPDATES="http://centos.netnitco.net/6.4/updates/x86_64/"
|
||||
|
||||
yum install -y febootstrap xz
|
||||
|
||||
febootstrap -i bash -i coreutils -i tar -i bzip2 -i gzip -i vim-minimal -i wget -i patch -i diffutils -i iproute -i yum centos centos64 $MIRROR_URL -u $MIRROR_URL_UPDATES
|
||||
touch centos64/etc/resolv.conf
|
||||
touch centos64/sbin/init
|
||||
|
||||
tar --numeric-owner -Jcpf centos-64.tar.xz -C centos64 .
|
||||
@@ -32,22 +32,33 @@ sudo debootstrap --verbose --variant="$variant" --include="$include" "$suite" "$
|
||||
|
||||
cd "$target"
|
||||
|
||||
# create the image
|
||||
img=$(sudo tar -c . | docker import -)
|
||||
# prevent init scripts from running during install/update
|
||||
echo $'#!/bin/sh\nexit 101' | sudo tee usr/sbin/policy-rc.d > /dev/null
|
||||
sudo chmod +x usr/sbin/policy-rc.d
|
||||
# see https://github.com/dotcloud/docker/issues/446#issuecomment-16953173
|
||||
|
||||
# tag suite
|
||||
docker tag $img $repo $suite
|
||||
# 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
|
||||
echo 'force-unsafe-io' | sudo tee etc/dpkg/dpkg.cfg.d/02apt-speedup > /dev/null
|
||||
# we don't need an apt cache in a container
|
||||
echo 'Acquire::http {No-Cache=True;};' | sudo tee etc/apt/apt.conf.d/no-cache > /dev/null
|
||||
|
||||
# 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 $img $repo latest
|
||||
docker tag $repo:$suite $repo latest
|
||||
|
||||
# tag the specific debian release version
|
||||
ver=$(docker run $repo:$suite cat /etc/debian_version)
|
||||
docker tag $img $repo $ver
|
||||
docker tag $repo:$suite $repo $ver
|
||||
fi
|
||||
|
||||
# cleanup
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package docker
|
||||
|
||||
type DeviceSet interface {
|
||||
AddDevice(hash, baseHash string) error
|
||||
SetInitialized(hash string) error
|
||||
DeactivateDevice(hash string) error
|
||||
RemoveDevice(hash string) error
|
||||
MountDevice(hash, path string) error
|
||||
UnmountDevice(hash, path string, deactivate bool) error
|
||||
HasDevice(hash string) bool
|
||||
HasInitializedDevice(hash string) bool
|
||||
HasActivatedDevice(hash string) bool
|
||||
Shutdown() error
|
||||
}
|
||||
@@ -0,0 +1,700 @@
|
||||
package devmapper
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
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 *DeviceSetDM `json:"-"`
|
||||
}
|
||||
|
||||
type MetaData struct {
|
||||
Devices map[string]*DevInfo `json:devices`
|
||||
}
|
||||
|
||||
type DeviceSetDM struct {
|
||||
MetaData
|
||||
sync.Mutex
|
||||
initialized bool
|
||||
root string
|
||||
devicePrefix string
|
||||
TransactionId uint64
|
||||
NewTransactionId uint64
|
||||
nextFreeDevice int
|
||||
activeMounts map[string]int
|
||||
}
|
||||
|
||||
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 *DeviceSetDM) loopbackDir() string {
|
||||
return path.Join(devices.root, "loopback")
|
||||
}
|
||||
|
||||
func (devices *DeviceSetDM) jsonFile() string {
|
||||
return path.Join(devices.loopbackDir(), "json")
|
||||
}
|
||||
|
||||
func (devices *DeviceSetDM) getPoolName() string {
|
||||
return devices.devicePrefix + "-pool"
|
||||
}
|
||||
|
||||
func (devices *DeviceSetDM) getPoolDevName() string {
|
||||
return getDevName(devices.getPoolName())
|
||||
}
|
||||
|
||||
func (devices *DeviceSetDM) hasImage(name string) bool {
|
||||
dirname := devices.loopbackDir()
|
||||
filename := path.Join(dirname, name)
|
||||
|
||||
_, err := os.Stat(filename)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSetDM) 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 *DeviceSetDM) allocateDeviceId() int {
|
||||
// TODO: Add smarter reuse of deleted devices
|
||||
id := devices.nextFreeDevice
|
||||
devices.nextFreeDevice = devices.nextFreeDevice + 1
|
||||
return id
|
||||
}
|
||||
|
||||
func (devices *DeviceSetDM) allocateTransactionId() uint64 {
|
||||
devices.NewTransactionId = devices.NewTransactionId + 1
|
||||
return devices.NewTransactionId
|
||||
}
|
||||
|
||||
func (devices *DeviceSetDM) saveMetadata() error {
|
||||
jsonData, err := json.Marshal(devices.MetaData)
|
||||
if err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
tmpFile, err := ioutil.TempFile(filepath.Dir(devices.jsonFile()), ".json")
|
||||
if err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
n, err := tmpFile.Write(jsonData)
|
||||
if err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
if n < len(jsonData) {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
if err := tmpFile.Sync(); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
if err := tmpFile.Close(); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
if err := os.Rename(tmpFile.Name(), devices.jsonFile()); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if devices.NewTransactionId != devices.TransactionId {
|
||||
if err = setTransactionId(devices.getPoolDevName(), devices.TransactionId, devices.NewTransactionId); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
devices.TransactionId = devices.NewTransactionId
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSetDM) registerDevice(id int, hash string, size uint64) (*DevInfo, error) {
|
||||
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 *DeviceSetDM) activateDeviceIfNeeded(hash string) error {
|
||||
utils.Debugf("activateDeviceIfNeeded()")
|
||||
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 *DeviceSetDM) 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 *DeviceSetDM) loadMetaData() error {
|
||||
_, _, _, 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 *DeviceSetDM) 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
|
||||
}
|
||||
|
||||
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 *DeviceSetDM) initDevmapper() error {
|
||||
info, err := getInfo(devices.getPoolName())
|
||||
if info == nil {
|
||||
utils.Debugf("Error device getInfo: %s", err)
|
||||
return err
|
||||
}
|
||||
utils.Debugf("initDevmapper(). Pool exists: %v", info.Exists)
|
||||
|
||||
// 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 info.Exists != 0 {
|
||||
/* Pool exists, assume everything is up */
|
||||
if err := devices.loadMetaData(); err != nil {
|
||||
utils.Debugf("Error device loadMetaData: %s\n", err)
|
||||
return err
|
||||
}
|
||||
if err := devices.setupBaseImage(); err != nil {
|
||||
utils.Debugf("Error device setupBaseImage: %s\n", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/* If we create the loopback mounts we also need to initialize the base fs */
|
||||
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
|
||||
}
|
||||
|
||||
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 !createdLoopback {
|
||||
if err = devices.loadMetaData(); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := devices.setupBaseImage(); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSetDM) 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 {
|
||||
utils.Debugf("Base Hash not found")
|
||||
return fmt.Errorf("Unknown base hash %s", 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 *DeviceSetDM) 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 *DeviceSetDM) 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 *DeviceSetDM) deactivateDevice(hash string) error {
|
||||
info := devices.Devices[hash]
|
||||
if info == nil {
|
||||
return fmt.Errorf("hash %s doesn't exists", hash)
|
||||
}
|
||||
|
||||
devinfo, err := getInfo(info.Name())
|
||||
if err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
if devinfo.Exists != 0 {
|
||||
if err := removeDevice(info.Name()); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSetDM) DeactivateDevice(hash string) error {
|
||||
devices.Lock()
|
||||
defer devices.Unlock()
|
||||
|
||||
if err := devices.ensureInit(); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
utils.Debugf("DeactivateDevice %s", hash)
|
||||
return devices.deactivateDevice(hash);
|
||||
}
|
||||
|
||||
|
||||
func (devices *DeviceSetDM) Shutdown() error {
|
||||
devices.Lock()
|
||||
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.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 := removeDevice(pool); err != nil {
|
||||
utils.Debugf("Shutdown deactivate %s , error: %s\n", pool, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSetDM) MountDevice(hash, path string) error {
|
||||
devices.Lock()
|
||||
defer devices.Unlock()
|
||||
|
||||
if err := devices.ensureInit(); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := devices.activateDeviceIfNeeded(hash); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
info := devices.Devices[hash]
|
||||
|
||||
err := syscall.Mount(info.DevName(), path, "ext4", syscall.MS_MGC_VAL, "discard")
|
||||
if err != nil && err == syscall.EINVAL {
|
||||
err = syscall.Mount(info.DevName(), path, "ext4", syscall.MS_MGC_VAL, "")
|
||||
}
|
||||
if err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
count := devices.activeMounts[path]
|
||||
devices.activeMounts[path] = count + 1
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSetDM) UnmountDevice(hash, path string, deactivate bool) error {
|
||||
devices.Lock()
|
||||
defer devices.Unlock()
|
||||
|
||||
if err := syscall.Unmount(path, 0); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
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 *DeviceSetDM) HasDevice(hash string) bool {
|
||||
devices.Lock()
|
||||
defer devices.Unlock()
|
||||
|
||||
if err := devices.ensureInit(); err != nil {
|
||||
return false
|
||||
}
|
||||
return devices.Devices[hash] != nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSetDM) 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 *DeviceSetDM) 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 *DeviceSetDM) 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 *DeviceSetDM) 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 NewDeviceSetDM(root string) *DeviceSetDM {
|
||||
SetDevDir("/dev")
|
||||
|
||||
base := filepath.Base(root)
|
||||
if !strings.HasPrefix(base, "docker") {
|
||||
base = "docker-" + base
|
||||
}
|
||||
|
||||
return &DeviceSetDM{
|
||||
initialized: false,
|
||||
root: root,
|
||||
devicePrefix: base,
|
||||
MetaData: MetaData{Devices: make(map[string]*DevInfo)},
|
||||
activeMounts: make(map[string]int),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,645 @@
|
||||
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
|
||||
|
||||
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);
|
||||
perror("sprintf");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (stat(buf, &st) || !S_ISBLK(st.st_mode)) {
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
loop_fd = open(buf, O_RDWR);
|
||||
if (loop_fd < 0 && errno == ENOENT) {
|
||||
close(fd);
|
||||
fprintf (stderr, "no available loopback device!");
|
||||
return NULL;
|
||||
} else if (loop_fd < 0)
|
||||
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("ioctl1");
|
||||
if (ioctl(loop_fd, LOOP_CLR_FD, 0) < 0) {
|
||||
perror("ioctl2");
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
DeviceCreate TaskType = iota
|
||||
DeviceReload
|
||||
DeviceRemove
|
||||
DeviceRemoveAll
|
||||
DeviceSuspend
|
||||
DeviceResume
|
||||
DeviceInfo
|
||||
DeviceDeps
|
||||
DeviceRename
|
||||
DeviceVersion
|
||||
DeviceStatus
|
||||
DeviceTable
|
||||
DeviceWaitevent
|
||||
DeviceList
|
||||
DeviceClear
|
||||
DeviceMknodes
|
||||
DeviceListVersions
|
||||
DeviceTargetMsg
|
||||
DeviceSetGeometry
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
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 {
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
C.perror(C.CString(fmt.Sprintf("[debug] Error dm_task_set_name(%s, %#v)", name, t.unmanaged)))
|
||||
}
|
||||
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) 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 {
|
||||
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))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
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() + " 512 8192"
|
||||
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 {
|
||||
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 {
|
||||
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")
|
||||
}
|
||||
|
||||
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 *DeviceSetDM) 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,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.NewDeviceSetDM("/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
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker"
|
||||
)
|
||||
|
||||
var (
|
||||
GITCOMMIT string
|
||||
VERSION string
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Running in init mode
|
||||
docker.SysInit()
|
||||
return
|
||||
}
|
||||
@@ -4,9 +4,11 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker"
|
||||
"github.com/dotcloud/docker/devmapper"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
@@ -29,7 +31,7 @@ func main() {
|
||||
flVersion := flag.Bool("v", false, "Print version information and quit")
|
||||
flDaemon := flag.Bool("d", false, "Daemon mode")
|
||||
flDebug := flag.Bool("D", false, "Debug mode")
|
||||
flAutoRestart := flag.Bool("r", false, "Restart previously running containers")
|
||||
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")
|
||||
flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.")
|
||||
@@ -37,7 +39,11 @@ func main() {
|
||||
flDns := flag.String("dns", "", "Set custom dns servers")
|
||||
flHosts := docker.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")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *flVersion {
|
||||
showVersion()
|
||||
return
|
||||
@@ -49,10 +55,9 @@ func main() {
|
||||
flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
|
||||
}
|
||||
|
||||
bridge := docker.DefaultNetworkBridge
|
||||
if *bridgeName != "" {
|
||||
docker.NetworkBridgeIface = *bridgeName
|
||||
} else {
|
||||
docker.NetworkBridgeIface = docker.DefaultNetworkBridge
|
||||
bridge = *bridgeName
|
||||
}
|
||||
if *flDebug {
|
||||
os.Setenv("DEBUG", "1")
|
||||
@@ -64,7 +69,26 @@ func main() {
|
||||
flag.Usage()
|
||||
return
|
||||
}
|
||||
if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil {
|
||||
var dns []string
|
||||
if *flDns != "" {
|
||||
dns = []string{*flDns}
|
||||
}
|
||||
|
||||
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,
|
||||
DeviceSet: devmapper.NewDeviceSetDM(*flGraphPath),
|
||||
}
|
||||
if err := daemon(config); err != nil {
|
||||
log.Fatal(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
@@ -115,30 +139,26 @@ func removePidFile(pidfile string) {
|
||||
}
|
||||
}
|
||||
|
||||
func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error {
|
||||
if err := createPidFile(pidfile); err != nil {
|
||||
func daemon(config *docker.DaemonConfig) error {
|
||||
if err := createPidFile(config.Pidfile); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer removePidFile(pidfile)
|
||||
defer removePidFile(config.Pidfile)
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM))
|
||||
signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGTERM)
|
||||
go func() {
|
||||
sig := <-c
|
||||
log.Printf("Received signal '%v', exiting\n", sig)
|
||||
removePidFile(pidfile)
|
||||
removePidFile(config.Pidfile)
|
||||
os.Exit(0)
|
||||
}()
|
||||
var dns []string
|
||||
if flDns != "" {
|
||||
dns = []string{flDns}
|
||||
}
|
||||
server, err := docker.NewServer(flGraphPath, autoRestart, enableCors, dns)
|
||||
server, err := docker.NewServer(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chErrors := make(chan error, len(protoAddrs))
|
||||
for _, protoAddr := range protoAddrs {
|
||||
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])
|
||||
@@ -154,7 +174,7 @@ func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart
|
||||
chErrors <- docker.ListenAndServe(protoAddrParts[0], protoAddrParts[1], server, true)
|
||||
}()
|
||||
}
|
||||
for i := 0; i < len(protoAddrs); i += 1 {
|
||||
for i := 0; i < len(config.ProtoAddresses); i += 1 {
|
||||
err := <-chErrors
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
Docker Remote API
|
||||
=================
|
||||
|
||||
.. contents:: Table of Contents
|
||||
|
||||
1. Brief introduction
|
||||
=====================
|
||||
@@ -27,16 +26,41 @@ Docker Remote API
|
||||
2. Versions
|
||||
===========
|
||||
|
||||
The current version of the API is 1.5
|
||||
The current version of the API is 1.6
|
||||
|
||||
Calling /images/<name>/insert is the same as calling
|
||||
/v1.5/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.6
|
||||
****
|
||||
|
||||
Full Documentation
|
||||
------------------
|
||||
|
||||
:doc:`docker_remote_api_v1.6`
|
||||
|
||||
What's new
|
||||
----------
|
||||
|
||||
.. http:post:: /containers/(id)/attach
|
||||
|
||||
**New!** You can now split stderr from stdout. This is done by prefixing
|
||||
a header to each transmition. See :http:post:`/containers/(id)/attach`.
|
||||
The WebSocket attach is unchanged.
|
||||
Note that attach calls on the previous API version didn't change. Stdout and
|
||||
stderr are merged.
|
||||
|
||||
|
||||
v1.5
|
||||
****
|
||||
|
||||
Full Documentation
|
||||
------------------
|
||||
|
||||
:doc:`docker_remote_api_v1.5`
|
||||
*****************************
|
||||
|
||||
What's new
|
||||
----------
|
||||
@@ -57,8 +81,13 @@ What's new
|
||||
dicts each containing `PublicPort`, `PrivatePort` and `Type` describing a
|
||||
port mapping.
|
||||
|
||||
v1.4
|
||||
****
|
||||
|
||||
Full Documentation
|
||||
------------------
|
||||
|
||||
:doc:`docker_remote_api_v1.4`
|
||||
*****************************
|
||||
|
||||
What's new
|
||||
----------
|
||||
@@ -75,11 +104,16 @@ What's new
|
||||
|
||||
**New!** Image's name added in the events
|
||||
|
||||
:doc:`docker_remote_api_v1.3`
|
||||
*****************************
|
||||
v1.3
|
||||
****
|
||||
|
||||
docker v0.5.0 51f6c4a_
|
||||
|
||||
Full Documentation
|
||||
------------------
|
||||
|
||||
:doc:`docker_remote_api_v1.3`
|
||||
|
||||
What's new
|
||||
----------
|
||||
|
||||
@@ -112,11 +146,16 @@ Start containers (/containers/<id>/start):
|
||||
- You can now pass host-specific configuration (e.g. bind mounts) in
|
||||
the POST body for start calls
|
||||
|
||||
:doc:`docker_remote_api_v1.2`
|
||||
*****************************
|
||||
v1.2
|
||||
****
|
||||
|
||||
docker v0.4.2 2e7649b_
|
||||
|
||||
Full Documentation
|
||||
------------------
|
||||
|
||||
:doc:`docker_remote_api_v1.2`
|
||||
|
||||
What's new
|
||||
----------
|
||||
|
||||
@@ -142,11 +181,16 @@ The client should send it's authConfig as POST on each call of
|
||||
deleted/untagged.
|
||||
|
||||
|
||||
:doc:`docker_remote_api_v1.1`
|
||||
*****************************
|
||||
v1.1
|
||||
****
|
||||
|
||||
docker v0.4.0 a8ae398_
|
||||
|
||||
Full Documentation
|
||||
------------------
|
||||
|
||||
:doc:`docker_remote_api_v1.1`
|
||||
|
||||
What's new
|
||||
----------
|
||||
|
||||
@@ -166,12 +210,16 @@ What's new
|
||||
{"error":"Invalid..."}
|
||||
...
|
||||
|
||||
|
||||
:doc:`docker_remote_api_v1.0`
|
||||
*****************************
|
||||
v1.0
|
||||
****
|
||||
|
||||
docker v0.3.4 8d73740_
|
||||
|
||||
Full Documentation
|
||||
------------------
|
||||
|
||||
:doc:`docker_remote_api_v1.0`
|
||||
|
||||
What's new
|
||||
----------
|
||||
|
||||
@@ -182,36 +230,3 @@ Initial version
|
||||
.. _8d73740: https://github.com/dotcloud/docker/commit/8d73740343778651c09160cde9661f5f387b36f4
|
||||
.. _2e7649b: https://github.com/dotcloud/docker/commit/2e7649beda7c820793bd46766cbc2cfeace7b168
|
||||
.. _51f6c4a: https://github.com/dotcloud/docker/commit/51f6c4a7372450d164c61e0054daf0223ddbd909
|
||||
|
||||
==================================
|
||||
Docker Remote API Client Libraries
|
||||
==================================
|
||||
|
||||
These libraries have not been tested by the Docker Maintainers for
|
||||
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 |
|
||||
+======================+================+============================================+
|
||||
| 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 |
|
||||
+----------------------+----------------+--------------------------------------------+
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
Docker Index API
|
||||
=================
|
||||
|
||||
.. contents:: Table of Contents
|
||||
|
||||
1. Brief introduction
|
||||
=====================
|
||||
|
||||
@@ -42,7 +40,7 @@ User Repo
|
||||
Authorization: Basic akmklmasadalkm==
|
||||
X-Docker-Token: true
|
||||
|
||||
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”}]
|
||||
[{"id": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f"}]
|
||||
|
||||
:parameter namespace: the namespace for the repo
|
||||
:parameter repo_name: the name for the repo
|
||||
@@ -54,7 +52,8 @@ User Repo
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
WWW-Authenticate: Token signature=123abc,repository=”foo/bar”,access=write
|
||||
WWW-Authenticate: Token signature=123abc,repository="foo/bar",access=write
|
||||
X-Docker-Token: signature=123abc,repository="foo/bar",access=write
|
||||
X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io]
|
||||
|
||||
""
|
||||
@@ -92,7 +91,8 @@ User Repo
|
||||
HTTP/1.1 202
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
WWW-Authenticate: Token signature=123abc,repository=”foo/bar”,access=delete
|
||||
WWW-Authenticate: Token signature=123abc,repository="foo/bar",access=delete
|
||||
X-Docker-Token: signature=123abc,repository="foo/bar",access=delete
|
||||
X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io]
|
||||
|
||||
""
|
||||
@@ -124,7 +124,7 @@ Library Repo
|
||||
Authorization: Basic akmklmasadalkm==
|
||||
X-Docker-Token: true
|
||||
|
||||
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”}]
|
||||
[{"id": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f"}]
|
||||
|
||||
:parameter repo_name: the library name for the repo
|
||||
|
||||
@@ -135,7 +135,8 @@ Library Repo
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
WWW-Authenticate: Token signature=123abc,repository=”library/foobar”,access=write
|
||||
WWW-Authenticate: Token signature=123abc,repository="library/foobar",access=write
|
||||
X-Docker-Token: signature=123abc,repository="foo/bar",access=write
|
||||
X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io]
|
||||
|
||||
""
|
||||
@@ -174,7 +175,8 @@ Library Repo
|
||||
HTTP/1.1 202
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
WWW-Authenticate: Token signature=123abc,repository=”library/foobar”,access=delete
|
||||
WWW-Authenticate: Token signature=123abc,repository="library/foobar",access=delete
|
||||
X-Docker-Token: signature=123abc,repository="foo/bar",access=delete
|
||||
X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io]
|
||||
|
||||
""
|
||||
@@ -205,8 +207,8 @@ User Repo Images
|
||||
Content-Type: application/json
|
||||
Authorization: Basic akmklmasadalkm==
|
||||
|
||||
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”,
|
||||
“checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}]
|
||||
[{"id": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f",
|
||||
"checksum": "b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087"}]
|
||||
|
||||
:parameter namespace: the namespace for the repo
|
||||
:parameter repo_name: the name for the repo
|
||||
@@ -250,10 +252,10 @@ User Repo Images
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”,
|
||||
“checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”},
|
||||
{“id”: “ertwetewtwe38722009fe6857087b486531f9a779a0c1dfddgfgsdgdsgds”,
|
||||
“checksum”: “34t23f23fc17e3ed29dae8f12c4f9e89cc6f0bsdfgfsdgdsgdsgerwgew”}]
|
||||
[{"id": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f",
|
||||
"checksum": "b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087"},
|
||||
{"id": "ertwetewtwe38722009fe6857087b486531f9a779a0c1dfddgfgsdgdsgds",
|
||||
"checksum": "34t23f23fc17e3ed29dae8f12c4f9e89cc6f0bsdfgfsdgdsgdsgerwgew"}]
|
||||
|
||||
:statuscode 200: OK
|
||||
:statuscode 404: Not found
|
||||
@@ -275,8 +277,8 @@ Library Repo Images
|
||||
Content-Type: application/json
|
||||
Authorization: Basic akmklmasadalkm==
|
||||
|
||||
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”,
|
||||
“checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}]
|
||||
[{"id": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f",
|
||||
"checksum": "b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087"}]
|
||||
|
||||
:parameter repo_name: the library name for the repo
|
||||
|
||||
@@ -318,10 +320,10 @@ Library Repo Images
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”,
|
||||
“checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”},
|
||||
{“id”: “ertwetewtwe38722009fe6857087b486531f9a779a0c1dfddgfgsdgdsgds”,
|
||||
“checksum”: “34t23f23fc17e3ed29dae8f12c4f9e89cc6f0bsdfgfsdgdsgdsgerwgew”}]
|
||||
[{"id": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f",
|
||||
"checksum": "b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087"},
|
||||
{"id": "ertwetewtwe38722009fe6857087b486531f9a779a0c1dfddgfgsdgdsgds",
|
||||
"checksum": "34t23f23fc17e3ed29dae8f12c4f9e89cc6f0bsdfgfsdgdsgdsgerwgew"}]
|
||||
|
||||
:statuscode 200: OK
|
||||
:statuscode 404: Not found
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
Docker Registry API
|
||||
===================
|
||||
|
||||
.. contents:: Table of Contents
|
||||
|
||||
1. Brief introduction
|
||||
=====================
|
||||
@@ -61,7 +60,7 @@ Layer
|
||||
Host: registry-1.docker.io
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Token akmklmasadalkmsdfgsdgdge33
|
||||
Authorization: Token signature=123abc,repository="foo/bar",access=read
|
||||
|
||||
:parameter image_id: the id for the layer you want to get
|
||||
|
||||
@@ -71,39 +70,10 @@ Layer
|
||||
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
X-Docker-Registry-Version: 0.6.0
|
||||
Cookie: (Cookie provided by the Registry)
|
||||
|
||||
{
|
||||
id: "088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c",
|
||||
parent: "aeee6396d62273d180a49c96c62e45438d87c7da4a5cf5d2be6bee4e21bc226f",
|
||||
created: "2013-04-30T17:46:10.843673+03:00",
|
||||
container: "8305672a76cc5e3d168f97221106ced35a76ec7ddbb03209b0f0d96bf74f6ef7",
|
||||
container_config: {
|
||||
Hostname: "host-test",
|
||||
User: "",
|
||||
Memory: 0,
|
||||
MemorySwap: 0,
|
||||
AttachStdin: false,
|
||||
AttachStdout: false,
|
||||
AttachStderr: false,
|
||||
PortSpecs: null,
|
||||
Tty: false,
|
||||
OpenStdin: false,
|
||||
StdinOnce: false,
|
||||
Env: null,
|
||||
Cmd: [
|
||||
"/bin/bash",
|
||||
"-c",
|
||||
"apt-get -q -yy -f install libevent-dev"
|
||||
],
|
||||
Dns: null,
|
||||
Image: "imagename/blah",
|
||||
Volumes: { },
|
||||
VolumesFrom: ""
|
||||
},
|
||||
docker_version: "0.1.7"
|
||||
}
|
||||
{layer binary data stream}
|
||||
|
||||
:statuscode 200: OK
|
||||
:statuscode 401: Requires authorization
|
||||
@@ -120,40 +90,10 @@ Layer
|
||||
|
||||
PUT /v1/images/088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c/layer HTTP/1.1
|
||||
Host: registry-1.docker.io
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Token akmklmasadalkmsdfgsdgdge33
|
||||
Transfer-Encoding: chunked
|
||||
Authorization: Token signature=123abc,repository="foo/bar",access=write
|
||||
|
||||
{
|
||||
id: "088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c",
|
||||
parent: "aeee6396d62273d180a49c96c62e45438d87c7da4a5cf5d2be6bee4e21bc226f",
|
||||
created: "2013-04-30T17:46:10.843673+03:00",
|
||||
container: "8305672a76cc5e3d168f97221106ced35a76ec7ddbb03209b0f0d96bf74f6ef7",
|
||||
container_config: {
|
||||
Hostname: "host-test",
|
||||
User: "",
|
||||
Memory: 0,
|
||||
MemorySwap: 0,
|
||||
AttachStdin: false,
|
||||
AttachStdout: false,
|
||||
AttachStderr: false,
|
||||
PortSpecs: null,
|
||||
Tty: false,
|
||||
OpenStdin: false,
|
||||
StdinOnce: false,
|
||||
Env: null,
|
||||
Cmd: [
|
||||
"/bin/bash",
|
||||
"-c",
|
||||
"apt-get -q -yy -f install libevent-dev"
|
||||
],
|
||||
Dns: null,
|
||||
Image: "imagename/blah",
|
||||
Volumes: { },
|
||||
VolumesFrom: ""
|
||||
},
|
||||
docker_version: "0.1.7"
|
||||
}
|
||||
{layer binary data stream}
|
||||
|
||||
:parameter image_id: the id for the layer you want to get
|
||||
|
||||
@@ -165,6 +105,7 @@ Layer
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
X-Docker-Registry-Version: 0.6.0
|
||||
|
||||
""
|
||||
|
||||
@@ -191,13 +132,38 @@ Image
|
||||
Cookie: (Cookie provided by the Registry)
|
||||
|
||||
{
|
||||
“id”: “088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c”,
|
||||
“checksum”: “sha256:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”
|
||||
}
|
||||
id: "088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c",
|
||||
parent: "aeee6396d62273d180a49c96c62e45438d87c7da4a5cf5d2be6bee4e21bc226f",
|
||||
created: "2013-04-30T17:46:10.843673+03:00",
|
||||
container: "8305672a76cc5e3d168f97221106ced35a76ec7ddbb03209b0f0d96bf74f6ef7",
|
||||
container_config: {
|
||||
Hostname: "host-test",
|
||||
User: "",
|
||||
Memory: 0,
|
||||
MemorySwap: 0,
|
||||
AttachStdin: false,
|
||||
AttachStdout: false,
|
||||
AttachStderr: false,
|
||||
PortSpecs: null,
|
||||
Tty: false,
|
||||
OpenStdin: false,
|
||||
StdinOnce: false,
|
||||
Env: null,
|
||||
Cmd: [
|
||||
"/bin/bash",
|
||||
"-c",
|
||||
"apt-get -q -yy -f install libevent-dev"
|
||||
],
|
||||
Dns: null,
|
||||
Image: "imagename/blah",
|
||||
Volumes: { },
|
||||
VolumesFrom: ""
|
||||
},
|
||||
docker_version: "0.1.7"
|
||||
}
|
||||
|
||||
:parameter image_id: the id for the layer you want to get
|
||||
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
@@ -205,6 +171,7 @@ Image
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
X-Docker-Registry-Version: 0.6.0
|
||||
|
||||
""
|
||||
|
||||
@@ -234,11 +201,40 @@ Image
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
X-Docker-Registry-Version: 0.6.0
|
||||
X-Docker-Size: 456789
|
||||
X-Docker-Checksum: b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087
|
||||
|
||||
{
|
||||
“id”: “088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c”,
|
||||
“checksum”: “sha256:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”
|
||||
}
|
||||
id: "088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c",
|
||||
parent: "aeee6396d62273d180a49c96c62e45438d87c7da4a5cf5d2be6bee4e21bc226f",
|
||||
created: "2013-04-30T17:46:10.843673+03:00",
|
||||
container: "8305672a76cc5e3d168f97221106ced35a76ec7ddbb03209b0f0d96bf74f6ef7",
|
||||
container_config: {
|
||||
Hostname: "host-test",
|
||||
User: "",
|
||||
Memory: 0,
|
||||
MemorySwap: 0,
|
||||
AttachStdin: false,
|
||||
AttachStdout: false,
|
||||
AttachStderr: false,
|
||||
PortSpecs: null,
|
||||
Tty: false,
|
||||
OpenStdin: false,
|
||||
StdinOnce: false,
|
||||
Env: null,
|
||||
Cmd: [
|
||||
"/bin/bash",
|
||||
"-c",
|
||||
"apt-get -q -yy -f install libevent-dev"
|
||||
],
|
||||
Dns: null,
|
||||
Image: "imagename/blah",
|
||||
Volumes: { },
|
||||
VolumesFrom: ""
|
||||
},
|
||||
docker_version: "0.1.7"
|
||||
}
|
||||
|
||||
:statuscode 200: OK
|
||||
:statuscode 401: Requires authorization
|
||||
@@ -271,6 +267,7 @@ Ancestry
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
X-Docker-Registry-Version: 0.6.0
|
||||
|
||||
["088b4502f51920fbd9b7c503e87c7a2c05aa3adc3d35e79c031fa126b403200f",
|
||||
"aeee63968d87c7da4a5cf5d2be6bee4e21bc226fd62273d180a49c96c62e4543",
|
||||
@@ -297,6 +294,7 @@ Ancestry
|
||||
Host: registry-1.docker.io
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
X-Docker-Registry-Version: 0.6.0
|
||||
Cookie: (Cookie provided by the Registry)
|
||||
|
||||
:parameter namespace: namespace for the repo
|
||||
@@ -309,10 +307,11 @@ Ancestry
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
X-Docker-Registry-Version: 0.6.0
|
||||
|
||||
{
|
||||
"latest": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f",
|
||||
“0.1.1”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”
|
||||
"0.1.1": "b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087"
|
||||
}
|
||||
|
||||
:statuscode 200: OK
|
||||
@@ -332,6 +331,7 @@ Ancestry
|
||||
Host: registry-1.docker.io
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
X-Docker-Registry-Version: 0.6.0
|
||||
Cookie: (Cookie provided by the Registry)
|
||||
|
||||
:parameter namespace: namespace for the repo
|
||||
@@ -345,6 +345,7 @@ Ancestry
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
X-Docker-Registry-Version: 0.6.0
|
||||
|
||||
"9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f"
|
||||
|
||||
@@ -377,6 +378,7 @@ Ancestry
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
X-Docker-Registry-Version: 0.6.0
|
||||
|
||||
""
|
||||
|
||||
@@ -399,7 +401,7 @@ Ancestry
|
||||
Content-Type: application/json
|
||||
Cookie: (Cookie provided by the Registry)
|
||||
|
||||
“9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”
|
||||
"9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f"
|
||||
|
||||
:parameter namespace: namespace for the repo
|
||||
:parameter repository: name for the repo
|
||||
@@ -412,6 +414,7 @@ Ancestry
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
X-Docker-Registry-Version: 0.6.0
|
||||
|
||||
""
|
||||
|
||||
@@ -449,6 +452,7 @@ Ancestry
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
X-Docker-Registry-Version: 0.6.0
|
||||
|
||||
""
|
||||
|
||||
@@ -456,8 +460,41 @@ Ancestry
|
||||
:statuscode 401: Requires authorization
|
||||
:statuscode 404: Repository not found
|
||||
|
||||
3.0 Authorization
|
||||
=================
|
||||
2.4 Status
|
||||
----------
|
||||
|
||||
.. http:get:: /v1/_ping
|
||||
|
||||
Check status of the registry. This endpoint is also used to determine if
|
||||
the registry supports SSL.
|
||||
|
||||
**Example Request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /v1/_ping HTTP/1.1
|
||||
Host: registry-1.docker.io
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
|
||||
""
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
X-Docker-Registry-Version: 0.6.0
|
||||
|
||||
""
|
||||
|
||||
:statuscode 200: OK
|
||||
|
||||
|
||||
3 Authorization
|
||||
===============
|
||||
This is where we describe the authorization process, including the tokens and cookies.
|
||||
|
||||
TODO: add more info.
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
Registry & Index Spec
|
||||
=====================
|
||||
|
||||
.. contents:: Table of Contents
|
||||
|
||||
1. The 3 roles
|
||||
===============
|
||||
|
||||
@@ -564,8 +562,8 @@ Next request::
|
||||
Cookie: session="wD/J7LqL5ctqw8haL10vgfhrb2Q=?foo=UydiYXInCnAxCi4=×tamp=RjEzNjYzMTQ5NDcuNDc0NjQzCi4="
|
||||
|
||||
|
||||
7.0 Document Version
|
||||
---------------------
|
||||
7 Document Version
|
||||
====================
|
||||
|
||||
- 1.0 : May 6th 2013 : initial release
|
||||
- 1.1 : June 1st 2013 : Added Delete Repository and way to handle new source namespace.
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
: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
|
||||
|
||||
|
||||
==================================
|
||||
Docker Remote API Client Libraries
|
||||
==================================
|
||||
|
||||
These libraries have not been tested by the Docker Maintainers for
|
||||
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 |
|
||||
+======================+================+============================================+
|
||||
| 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 |
|
||||
+----------------------+----------------+--------------------------------------------+
|
||||
@@ -8,7 +8,7 @@ Overview
|
||||
======================
|
||||
|
||||
Docker Usage
|
||||
~~~~~~~~~~~~
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To list available commands, either run ``docker`` with no parameters or execute
|
||||
``docker help``::
|
||||
@@ -21,40 +21,75 @@ To list available commands, either run ``docker`` with no parameters or execute
|
||||
|
||||
...
|
||||
|
||||
|
||||
|
||||
Available Commands
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
.. include:: command/attach.rst
|
||||
|
||||
.. include:: command/build.rst
|
||||
|
||||
.. include:: command/commit.rst
|
||||
|
||||
.. include:: command/cp.rst
|
||||
|
||||
.. include:: command/diff.rst
|
||||
|
||||
.. include:: command/events.rst
|
||||
|
||||
.. include:: command/export.rst
|
||||
|
||||
.. include:: command/history.rst
|
||||
|
||||
.. include:: command/images.rst
|
||||
|
||||
.. include:: command/import.rst
|
||||
|
||||
.. include:: command/info.rst
|
||||
|
||||
.. include:: command/insert.rst
|
||||
|
||||
.. include:: command/inspect.rst
|
||||
|
||||
.. include:: command/kill.rst
|
||||
|
||||
.. include:: command/link.rst
|
||||
|
||||
.. include:: command/login.rst
|
||||
|
||||
.. include:: command/logs.rst
|
||||
|
||||
.. include:: command/ls.rst
|
||||
|
||||
.. include:: command/port.rst
|
||||
|
||||
.. include:: command/ps.rst
|
||||
|
||||
.. include:: command/pull.rst
|
||||
|
||||
.. include:: command/push.rst
|
||||
|
||||
.. include:: command/restart.rst
|
||||
|
||||
.. include:: command/rm.rst
|
||||
|
||||
.. include:: command/rmi.rst
|
||||
|
||||
.. include:: command/run.rst
|
||||
|
||||
.. include:: command/search.rst
|
||||
|
||||
.. include:: command/start.rst
|
||||
|
||||
.. include:: command/stop.rst
|
||||
|
||||
.. include:: command/tag.rst
|
||||
|
||||
.. include:: command/top.rst
|
||||
|
||||
.. include:: command/version.rst
|
||||
|
||||
.. include:: command/wait.rst
|
||||
|
||||
|
||||
command/attach
|
||||
command/build
|
||||
command/commit
|
||||
command/cp
|
||||
command/diff
|
||||
command/events
|
||||
command/export
|
||||
command/history
|
||||
command/images
|
||||
command/import
|
||||
command/info
|
||||
command/insert
|
||||
command/inspect
|
||||
command/kill
|
||||
command/login
|
||||
command/logs
|
||||
command/port
|
||||
command/ps
|
||||
command/pull
|
||||
command/push
|
||||
command/restart
|
||||
command/rm
|
||||
command/rmi
|
||||
command/run
|
||||
command/search
|
||||
command/start
|
||||
command/stop
|
||||
command/tag
|
||||
command/top
|
||||
command/version
|
||||
command/wait
|
||||
|
||||
@@ -14,12 +14,15 @@
|
||||
|
||||
-m="": Commit message
|
||||
-author="": Author (eg. "John Hannibal Smith <hannibal@a-team.com>"
|
||||
-run="": Config automatically applied when the image is
|
||||
run. "+`(ex: {"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')
|
||||
-run="": Configuration to be applied when the image is launched with `docker run`.
|
||||
(ex: '{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')
|
||||
|
||||
Full -run example::
|
||||
Full -run example (multiline is ok within a single quote ``'``)
|
||||
|
||||
{
|
||||
::
|
||||
|
||||
$ sudo docker commit -run='
|
||||
{
|
||||
"Entrypoint" : null,
|
||||
"Privileged" : false,
|
||||
"User" : "",
|
||||
@@ -46,4 +49,4 @@ Full -run example::
|
||||
"NetworkDisabled" : false,
|
||||
"Memory" : 0,
|
||||
"AttachStdout" : false
|
||||
}
|
||||
}' $CONTAINER_ID
|
||||
|
||||
@@ -23,4 +23,4 @@ Displaying images visually
|
||||
|
||||
sudo docker images -viz | dot -Tpng -o docker.png
|
||||
|
||||
.. image:: images/docker_images.gif
|
||||
.. image:: https://docs.docker.io/en/latest/_static/docker_images.gif
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
:title: Link Command
|
||||
:description: Add a link or rename the link for a container
|
||||
:keywords: link, docker, container, documentation, link, links
|
||||
|
||||
============================================================================
|
||||
``link`` -- Add a link or rename the link for a container
|
||||
============================================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker link CURRENT_NAME NEW_NAME
|
||||
|
||||
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``.
|
||||
@@ -0,0 +1,29 @@
|
||||
:title: Ls Command
|
||||
:description: Ls all links for containers
|
||||
:keywords: ls, docker, container, documentation, link, links
|
||||
|
||||
============================================================================
|
||||
``ls`` -- List all links for containers
|
||||
============================================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker ls
|
||||
|
||||
List all links for containers and display the relationship between parent
|
||||
and child containers.
|
||||
|
||||
|
||||
Examples:
|
||||
--------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ docker ls
|
||||
NAME ID IMAGE
|
||||
/redis 39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 crosbymichael/redis:latest
|
||||
/webapp cffb86ffa80b11cd8777d300759ee53c4e61729431c30ec9552dd9e6d3abc87d demo:latest
|
||||
/webapp/redis 39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 crosbymichael/redis:latest
|
||||
|
||||
This will display all links and the names that you can use the reference the link. Parent child
|
||||
relationships are also displayed with ls.
|
||||
@@ -11,3 +11,26 @@
|
||||
Usage: docker rm [OPTIONS] CONTAINER
|
||||
|
||||
Remove one or more containers
|
||||
-link="": Remove the link instead of the actual container
|
||||
|
||||
|
||||
Examples:
|
||||
--------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ docker rm /redis
|
||||
/redis
|
||||
|
||||
|
||||
This will remove the container referenced under the link ``/redis``.
|
||||
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ docker rm -link /webapp/redis
|
||||
/webapp/redis
|
||||
|
||||
|
||||
This will remove the underlying link between ``/webapp`` and the ``/redis`` containers removing all
|
||||
network communication.
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
Run a command in a new container
|
||||
|
||||
-a=map[]: Attach to stdin, stdout or stderr.
|
||||
-a=map[]: Attach to stdin, stdout or stderr
|
||||
-c=0: CPU shares (relative weight)
|
||||
-cidfile="": Write the container ID to the file
|
||||
-d=false: Detached mode: Run container in the background, print new container id
|
||||
@@ -23,14 +23,17 @@
|
||||
-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)
|
||||
-t=false: Allocate a pseudo-tty
|
||||
-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 "host-dir" is missing, then docker creates a new volume.
|
||||
-volumes-from="": Mount all volumes from the given container.
|
||||
-entrypoint="": Overwrite the default entrypoint set by the image.
|
||||
-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
|
||||
-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"
|
||||
-expose=[]: Expose a port from the container without publishing it to your host
|
||||
-link="": Add link to another container (containerid:alias)
|
||||
|
||||
Examples
|
||||
--------
|
||||
@@ -81,4 +84,34 @@ 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.0::80 ubuntu bash
|
||||
|
||||
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 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 -link /redis:redis ubuntu bash
|
||||
|
||||
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.
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
:description: A simple hello world example with Docker
|
||||
:keywords: docker, example, hello world
|
||||
|
||||
.. _examples:
|
||||
|
||||
Hello World
|
||||
-----------
|
||||
|
||||
.. _running_examples:
|
||||
|
||||
Running the Examples
|
||||
@@ -166,13 +171,11 @@ See the example in action
|
||||
The next example in the series is a :ref:`python_web_app` example, or
|
||||
you could skip to any of the other examples:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
python_web_app
|
||||
nodejs_web_app
|
||||
running_redis_service
|
||||
running_ssh_service
|
||||
couchdb_data_volumes
|
||||
postgresql_service
|
||||
mongodb
|
||||
* :ref:`python_web_app`
|
||||
* :ref:`nodejs_web_app`
|
||||
* :ref:`running_redis_service`
|
||||
* :ref:`running_ssh_service`
|
||||
* :ref:`running_couchdb_service`
|
||||
* :ref:`postgresql_service`
|
||||
* :ref:`mongodb`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
:title: Docker Examples
|
||||
:description: Examples on how to use Docker
|
||||
:keywords: docker, hello world, node, nodejs, python, couch, couchdb, redis, ssh, sshd, examples, postgresql
|
||||
:keywords: docker, hello world, node, nodejs, python, couch, couchdb, redis, ssh, sshd, examples, postgresql, link
|
||||
|
||||
|
||||
.. _example_list:
|
||||
@@ -24,3 +24,4 @@ to more substantial services like you might find in production.
|
||||
postgresql_service
|
||||
mongodb
|
||||
running_riak_service
|
||||
linking_into_redis
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
: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`` and ``docker ls``.
|
||||
|
||||
|
||||
Linking an existing container
|
||||
-----------------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker ls
|
||||
|
||||
NAME ID IMAGE
|
||||
/39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 crosbymichael/redis:latest
|
||||
|
||||
|
||||
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
|
||||
|
||||
docker ls
|
||||
|
||||
NAME ID IMAGE
|
||||
/redis 39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 crosbymichael/redis:latest
|
||||
/39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 crosbymichael/redis:latest
|
||||
|
||||
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.
|
||||
@@ -66,14 +66,14 @@ other roles. Following Vagrant's convention the role will be named
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo -u postgres createuser -P -d -r -s docker
|
||||
su postgres -c "createuser -P -d -r -s docker"
|
||||
|
||||
Create a test database also named ``docker`` owned by previously created ``docker``
|
||||
role.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo -u postgres createdb -O docker docker
|
||||
su postgres -c "createdb -O docker docker"
|
||||
|
||||
Adjust PostgreSQL configuration so that remote connections to the
|
||||
database are possible. Make sure that inside
|
||||
@@ -149,10 +149,10 @@ container starts.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker commit <container_id> <your username>/postgresql -run='{"Cmd": \
|
||||
sudo docker commit <container_id> <your username> postgresql -run='{"Cmd": \
|
||||
["/bin/su", "postgres", "-c", "/usr/lib/postgresql/9.2/bin/postgres -D \
|
||||
/var/lib/postgresql/9.2/main -c \
|
||||
config_file=/etc/postgresql/9.2/main/postgresql.conf"], PortSpecs": ["5432"]}
|
||||
config_file=/etc/postgresql/9.2/main/postgresql.conf"], "PortSpecs": ["5432"]}'
|
||||
|
||||
From now on, just type ``docker run <your username>/postgresql`` and
|
||||
PostgreSQL should automatically start.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
:description: An overview of the Docker Documentation
|
||||
:keywords: containers, lxc, concepts, explanation
|
||||
|
||||
.. image:: static_files/dockerlogo-h.png
|
||||
.. image:: https://www.docker.io/static/img/linked/dockerlogo-horizontal.png
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
@@ -80,7 +80,7 @@ To enable the forwarding, run as root on the host system:
|
||||
|
||||
sysctl net.ipv4.ip_forward=1
|
||||
|
||||
And, to make it persistent across reboots, enable it on the host's **/etc/sysctl.conf**:
|
||||
And, to make it persistent across reboots, enable it on the host's **/etc/sysctl.d/docker.conf**:
|
||||
|
||||
::
|
||||
|
||||
|
||||
@@ -11,59 +11,45 @@ Gentoo Linux
|
||||
|
||||
.. include:: install_unofficial.inc
|
||||
|
||||
Installing Docker on Gentoo Linux can be accomplished by using the overlay
|
||||
provided at https://github.com/tianon/docker-overlay. The most up-to-date
|
||||
documentation for properly installing the overlay can be found in the overlay
|
||||
README. The information here is provided for reference, and may be out of date.
|
||||
Installing Docker on Gentoo Linux can be accomplished using one of two methods.
|
||||
The first and best way if you're looking for a stable experience is to use the
|
||||
official `app-emulation/docker` package directly in the portage tree.
|
||||
|
||||
If you're looking for a ``-bin`` ebuild, a live ebuild, or bleeding edge
|
||||
ebuild changes/fixes, the second installation method is to use the overlay
|
||||
provided at https://github.com/tianon/docker-overlay which can be added using
|
||||
``app-portage/layman``. The most accurate and up-to-date documentation for
|
||||
properly installing and using the overlay can be found in `the overlay README
|
||||
<https://github.com/tianon/docker-overlay/blob/master/README.md#using-this-overlay>`_.
|
||||
|
||||
Installation
|
||||
^^^^^^^^^^^^
|
||||
|
||||
Ensure that layman is installed:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo emerge -av app-portage/layman
|
||||
|
||||
Add the "docker" overlay using layman:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo layman -a docker
|
||||
|
||||
Once that completes, the ``app-emulation/docker`` package will be available
|
||||
for emerge:
|
||||
The package should properly pull in all the necessary dependencies and prompt
|
||||
for all necessary kernel options. For the most straightforward installation
|
||||
experience, use ``sys-kernel/aufs-sources`` as your kernel sources. If you
|
||||
prefer not to use ``sys-kernel/aufs-sources``, the portage tree also contains
|
||||
``sys-fs/aufs3``, which includes the patches necessary for adding AUFS support
|
||||
to other kernel source packages such as ``sys-kernel/gentoo-sources`` (and a
|
||||
``kernel-patch`` USE flag to perform the patching to ``/usr/src/linux``
|
||||
automatically).
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo emerge -av app-emulation/docker
|
||||
|
||||
If you prefer to use the official binaries, or just do not wish to compile
|
||||
docker, emerge ``app-emulation/docker-bin`` instead. It is important to
|
||||
remember that Gentoo is still an unofficial platform, even when using the
|
||||
official binaries.
|
||||
|
||||
The package should already include all the necessary dependencies. For the
|
||||
simplest installation experience, use ``sys-kernel/aufs-sources`` directly as
|
||||
your kernel sources. If you prefer not to use ``sys-kernel/aufs-sources``, the
|
||||
portage tree also contains ``sys-fs/aufs3``, which contains the patches
|
||||
necessary for adding AUFS support to other kernel source packages (and a
|
||||
``kernel-patch`` use flag to perform the patching automatically).
|
||||
|
||||
Between ``app-emulation/lxc`` and ``app-emulation/docker``, all the
|
||||
necessary kernel configuration flags should be checked for and warned about in
|
||||
the standard manner.
|
||||
|
||||
If any issues arise from this ebuild or the resulting binary, including and
|
||||
especially missing kernel configuration flags and/or dependencies, `open an
|
||||
issue <https://github.com/tianon/docker-overlay/issues>`_ on the docker-overlay
|
||||
repository or ping tianon in the #docker IRC channel.
|
||||
issue on the docker-overlay repository
|
||||
<https://github.com/tianon/docker-overlay/issues>`_ or ping tianon directly in
|
||||
the #docker IRC channel on the freenode network.
|
||||
|
||||
Starting Docker
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Ensure that you are running a kernel that includes the necessary AUFS support
|
||||
and includes all the necessary modules and/or configuration for LXC.
|
||||
Ensure that you are running a kernel that includes the necessary AUFS
|
||||
patches/support and includes all the necessary modules and/or configuration for
|
||||
LXC.
|
||||
|
||||
OpenRC
|
||||
------
|
||||
@@ -110,16 +96,3 @@ Or, to enable it more permanently:
|
||||
.. code-block:: bash
|
||||
|
||||
echo net.ipv4.ip_forward = 1 | sudo tee /etc/sysctl.d/docker.conf
|
||||
|
||||
fork/exec /usr/sbin/lxc-start: operation not permitted
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Unfortunately, Gentoo suffers from `issue #1422
|
||||
<https://github.com/dotcloud/docker/issues/1422>`_, meaning that after every
|
||||
fresh start of docker, the first docker run fails due to some tricky terminal
|
||||
issues, so be sure to run something trivial (such as ``docker run -i -t busybox
|
||||
echo hi``) before attempting to run anything important.
|
||||
|
||||
There is a tentative (and very hacky) workaround for this in the OpenRC init
|
||||
script, and it can be enabled by modifying the appropriate value in
|
||||
``/etc/conf.d/docker`` after successful installation.
|
||||
|
||||
@@ -75,7 +75,7 @@ Docker is available as a Debian package, which makes installation easy.
|
||||
|
||||
# Add the Docker repository key to your local keychain
|
||||
# using apt-key finger you can check the fingerprint matches 36A1 D786 9245 C895 0F96 6E92 D857 6A8B A88D 21E9
|
||||
sudo sh -c "curl https://get.docker.io/gpg | apt-key add -"
|
||||
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"
|
||||
@@ -129,7 +129,7 @@ to follow them again.*
|
||||
|
||||
# Add the Docker repository key to your local keychain
|
||||
# using apt-key finger you can check the fingerprint matches 36A1 D786 9245 C895 0F96 6E92 D857 6A8B A88D 21E9
|
||||
sudo sh -c "curl https://get.docker.io/gpg | apt-key add -"
|
||||
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"
|
||||
|
||||
@@ -53,13 +53,10 @@ If you installed the Docker :ref:`binaries` then follow these steps:
|
||||
.. code-block:: bash
|
||||
|
||||
# get the latest binary
|
||||
wget http://get.docker.io/builds/Linux/x86_64/docker-latest.tgz
|
||||
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Unpack it to your current dir
|
||||
tar -xf docker-latest.tgz
|
||||
wget http://get.docker.io/builds/Linux/x86_64/docker-latest -O docker
|
||||
|
||||
# make it executable
|
||||
chmod +x docker
|
||||
|
||||
|
||||
Start docker in daemon mode (``-d``) and disconnect, running the
|
||||
@@ -73,4 +70,4 @@ which might reside in your path.
|
||||
sudo ./docker -d &
|
||||
|
||||
|
||||
Alternatively you can replace the docker binary in ``/usr/local/bin``
|
||||
Alternatively you can replace the docker binary in ``/usr/local/bin``.
|
||||
|
||||
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
@@ -8,7 +8,7 @@ Documentation
|
||||
This documentation has the following resources:
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:maxdepth: 1
|
||||
|
||||
Introduction <index>
|
||||
installation/index
|
||||
|
||||
@@ -27,7 +27,7 @@ It can be as simple as this to create an Ubuntu base image::
|
||||
$ sudo debootstrap raring raring > /dev/null
|
||||
$ sudo tar -C raring -c . | sudo docker import - raring
|
||||
a29c15f1bf7a
|
||||
$ sudo docker run raring cat /etc/lsb-release
|
||||
$ sudo docker run raring cat /etc/lsb-release
|
||||
DISTRIB_ID=Ubuntu
|
||||
DISTRIB_RELEASE=13.04
|
||||
DISTRIB_CODENAME=raring
|
||||
@@ -37,5 +37,7 @@ There are more example scripts for creating base images in the
|
||||
Docker Github Repo:
|
||||
|
||||
* `BusyBox <https://github.com/dotcloud/docker/blob/master/contrib/mkimage-busybox.sh>`_
|
||||
* `Debian
|
||||
* `CentOS
|
||||
<https://github.com/dotcloud/docker/blob/master/contrib/mkimage-centos.sh>`_
|
||||
* `Debian/Ubuntu
|
||||
<https://github.com/dotcloud/docker/blob/master/contrib/mkimage-debian.sh>`_
|
||||
|
||||
@@ -321,7 +321,7 @@ the command given by ``CMD`` is executed.
|
||||
# VERSION 0.0.1
|
||||
|
||||
FROM ubuntu
|
||||
MAINTAINER Guillaume J. Charmes "guillaume@dotcloud.com"
|
||||
MAINTAINER Guillaume J. Charmes <guillaume@dotcloud.com>
|
||||
|
||||
# make sure the package repository is up to date
|
||||
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
|
||||
|
||||
@@ -176,3 +176,30 @@ you can push and pull it like any other repository, but it will
|
||||
**not** be searchable (or indexed at all) in the Central Index, and
|
||||
there will be no user name checking performed. Your registry will
|
||||
function completely independently from the Central Index.
|
||||
|
||||
Authentication file
|
||||
-------------------
|
||||
|
||||
The authentication is stored in a json file, ``.dockercfg`` located in your
|
||||
home directory. It supports multiple registry urls.
|
||||
|
||||
``docker login`` will create the "https://index.docker.io/v1/" key.
|
||||
|
||||
``docker login https://my-registry.com`` will create the "https://my-registry.com" key.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"https://index.docker.io/v1/": {
|
||||
"auth": "xXxXxXxXxXx=",
|
||||
"email": "email@example.com"
|
||||
},
|
||||
"https://my-registry.com": {
|
||||
"auth": "XxXxXxXxXxX=",
|
||||
"email": "email@my-registry.com"
|
||||
}
|
||||
}
|
||||
|
||||
The ``auth`` field represents ``base64(<username>:<password>)``
|
||||
|
||||
@@ -1,51 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
|
||||
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
|
||||
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
|
||||
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="google-site-verification" content="UxV66EKuPe87dgnH1sbrldrx6VsoWMrx5NjwkgUFxXI" />
|
||||
|
||||
<title>{{ meta['title'] if meta and meta['title'] else title }} - Docker Documentation</title>
|
||||
|
||||
<meta name="description" content="{{ meta['description'] if meta }}" />
|
||||
<meta name="keywords" content="{{ meta['keywords'] if meta }}" />
|
||||
<!-- Swiftype tags: https://swiftype.com/documentation/meta_tags -->
|
||||
<meta property='st:popularity' content='4' />
|
||||
<meta property='st:type' content='docker_doc' />
|
||||
|
||||
{%- set url_root = pathto('', 1) %}
|
||||
{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
|
||||
|
||||
<script type="text/javascript">
|
||||
// This is probably used by the search engine
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: '{{ url_root }}',
|
||||
VERSION: '{{ release|e }}',
|
||||
COLLAPSE_INDEX: false,
|
||||
FILE_SUFFIX: '{{ '' if no_search_suffix else file_suffix }}',
|
||||
HAS_SOURCE: {{ has_source|lower }}
|
||||
};
|
||||
// This is included here for Javascript that doesn't have access to the templates.
|
||||
var doc_version = "{{ current_version }}";
|
||||
var doc_slug = "{{ slug }}";
|
||||
</script>
|
||||
|
||||
{%- set css_files = css_files + ['_static/css/bootstrap.css'] %}
|
||||
{%- set css_files = css_files + ['_static/css/bootstrap-responsive.css'] %}
|
||||
{%- set css_files = css_files + ['_static/pygments.css'] %}
|
||||
{%- set css_files = css_files + ['_static/css/main.css'] %}
|
||||
|
||||
{%- set script_files =
|
||||
['https://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.js']
|
||||
+ ['https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.12/jquery-ui.min.js']
|
||||
+ script_files
|
||||
['//code.jquery.com/jquery-1.10.1.min.js']
|
||||
+ ['//fonts.googleapis.com/css?family=Cabin:400,700,400italic']
|
||||
%}
|
||||
|
||||
{%- set script_files = script_files + ['_static/js/docs.js'] %}
|
||||
{#
|
||||
This part is hopefully complex because things like |cut '/index/' are not available in spinx jinja
|
||||
and will make it crash. (and we need index/ out.
|
||||
#}
|
||||
<link rel="canonical" href="http://docs.docker.io/en/latest/
|
||||
{%- for word in pagename.split('/') -%}
|
||||
{%- if word != 'index' -%}
|
||||
{%- if word != '' -%}
|
||||
{{ word }}/
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
">
|
||||
|
||||
{%- if pagename == 'index' %}
|
||||
<link rel="canonical" href="http://docs.docker.io/en/latest/">
|
||||
{% else %}
|
||||
<link rel="canonical" href="http://docs.docker.io/en/latest/{{ pagename }}/">
|
||||
{% endif %}
|
||||
{%- for cssfile in css_files %}
|
||||
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
|
||||
{%- endfor %}
|
||||
@@ -53,19 +49,17 @@
|
||||
{%- for scriptfile in script_files if scriptfile != '_static/jquery.js' %}
|
||||
<script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
|
||||
{%- endfor %}
|
||||
|
||||
<link rel="shortcut icon" href="{{ pathto('_static/favicon.png', 1) }}"/>
|
||||
|
||||
|
||||
{%- block extrahead %}{% endblock %}
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="navbar navbar-fixed-top">
|
||||
<div id="wrap">
|
||||
<div class="navbar navbar-static-top navbar-inner navbar-fixed-top ">
|
||||
<div class="navbar-dotcloud">
|
||||
<div class="container" style="text-align: center;">
|
||||
<div class="container">
|
||||
|
||||
<div style="float: right" class="pull-right">
|
||||
<ul class="nav">
|
||||
@@ -75,49 +69,37 @@
|
||||
<li id="nav-community"><a href="http://www.docker.io/community/" title="Community">Community</a></li>
|
||||
<li id="nav-documentation" class="active"><a href="http://docs.docker.io/en/latest/">Documentation</a></li>
|
||||
<li id="nav-blog"><a href="http://blog.docker.io/" title="Docker Blog">Blog</a></li>
|
||||
<li id="nav-index"><a href="http://index.docker.io/" title="Docker Image Index, find images here">INDEX <img class="inline-icon" src="{{ pathto('_static/img/external-link-icon.png', 1) }}" title="external link"> </a></li>
|
||||
<li id="nav-index"><a href="http://index.docker.io/" title="Docker Image Index, find images here">INDEX <img class="inline-icon" alt="link to external site" src="{{ pathto('_static/img/external-link-icon.png', 1) }}" title="external link"> </a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div style="margin-left: -12px; float: left;">
|
||||
<a href="http://www.docker.io" title="Docker Homepage"><img style="margin-top: 0px; height: 60px; width: 160px; margin-left: 10px;" src="{{ pathto('_static/img/docker-top-logo.png', 1) }}"></a>
|
||||
<div class="brand-logo">
|
||||
<a href="http://www.docker.io" title="Docker Homepage"><img src="{{ pathto('_static/img/docker-top-logo.png', 1) }}" alt="Docker logo"></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="span12 titlebar">
|
||||
<!--<span class="pull-right" style="margin-left: 20px; font-size: 20px">{{version}}</span>-->
|
||||
<div class="pull-right" id="fork-us" style="margin-top: 16px; margin-right: 16px;">
|
||||
<a href="https://github.com/dotcloud/docker/blob/master/docs/sources/{{ pagename }}.rst"><img src="{{ pathto('_static/img/fork-us.png', 1) }}"> Edit this page on Github</a>
|
||||
</div>
|
||||
<h1 class="pageheader"><a href="http://docs.docker.io/en/latest/" title="Documentation" style="color: white;">DOCUMENTATION</a></h1>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- Docs nav
|
||||
================================================== -->
|
||||
<div class="row" style="position: relative">
|
||||
<div class="row main-row">
|
||||
|
||||
<div class="span3 sidebar bs-docs-sidebar">
|
||||
<div class="page-title" >
|
||||
<h4>DOCUMENTATION</h4>
|
||||
</div>
|
||||
|
||||
{{ toctree(collapse=False, maxdepth=3) }}
|
||||
<form>
|
||||
<input type="text" id="st-search-input" class="st-search-input span3" style="width:160px;" />
|
||||
<div id="st-results-container"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- body block -->
|
||||
<div class="span9">
|
||||
<div class="span9 main-content">
|
||||
|
||||
<!-- Main section
|
||||
================================================== -->
|
||||
@@ -125,167 +107,100 @@
|
||||
{% block body %}{% endblock %}
|
||||
</section>
|
||||
|
||||
<!-- Swiftype search -->
|
||||
<div id="st-results-container"></div>
|
||||
<script type="text/javascript">
|
||||
var Swiftype = window.Swiftype || {};
|
||||
(function() {
|
||||
Swiftype.key = 'pWPnnyvwcfpcrw1o51Sz';
|
||||
Swiftype.inputElement = '#st-search-input';
|
||||
Swiftype.resultContainingElement = '#st-results-container';
|
||||
Swiftype.attachElement = '#st-search-input';
|
||||
Swiftype.renderStyle = "overlay";
|
||||
|
||||
var script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.async = true;
|
||||
script.src = "//swiftype.com/embed.js";
|
||||
var entry = document.getElementsByTagName('script')[0];
|
||||
entry.parentNode.insertBefore(script, entry);
|
||||
}());
|
||||
</script>
|
||||
|
||||
<div class="pull-right"><a href="https://github.com/dotcloud/docker/blob/{{ current_version }}/docs/sources/{{ pagename }}.rst" title="edit this article">Edit this article on GitHub</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer" class="container" >
|
||||
<div class="row">
|
||||
<div id="push-the-footer"></div>
|
||||
</div> <!-- end wrap for pushing footer -->
|
||||
|
||||
<div class="span12 footer">
|
||||
<div class="tbox textright forceleftmargin social links pull-right">
|
||||
<a class="twitter" href="http://twitter.com/docker">Twitter</a>
|
||||
<a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
|
||||
<div id="footer">
|
||||
<div class="footer-landscape">
|
||||
<div class="footer-landscape-image">
|
||||
<!-- footer -->
|
||||
<div class="container">
|
||||
<div class="row footer">
|
||||
<div class="span12 tbox">
|
||||
<div class="tbox">
|
||||
<p>Docker is an open source project, sponsored by <a href="https://dotcloud.com">dotCloud</a>, under the <a href="https://github.com/dotcloud/docker/blob/master/LICENSE" title="Docker licence, hosted in the Github repository">apache 2.0 licence</a></p>
|
||||
</div>
|
||||
|
||||
<div class="social links">
|
||||
<a class="twitter" href="http://twitter.com/docker">Twitter</a>
|
||||
<a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
|
||||
</div>
|
||||
|
||||
<div class="tbox version-flyer ">
|
||||
<div class="content">
|
||||
<small>Current version:</small>
|
||||
<ul class="inline">
|
||||
{% for slug, url in versions %}
|
||||
<li class="alternative"><a href="{{ url }}{%- for word in pagename.split('/') -%}
|
||||
{%- if word != 'index' -%}
|
||||
{%- if word != '' -%}
|
||||
{{ word }}/
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}"
|
||||
title="Switch to {{ slug }}">{{ slug }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Docker is a project by <a href="http://www.dotcloud.com">dotCloud</a>
|
||||
|
||||
|
||||
{# {%- if show_source and has_source and sourcename %}#}
|
||||
{# ·#}
|
||||
{# <a href="{{ pathto('_sources/' + sourcename, true)|e }}"#}
|
||||
{# rel="nofollow">View the RST source of this page</a>#}
|
||||
{# {%- endif %}#}
|
||||
{# {%- if pagename != "search" %}#}
|
||||
|
||||
{#TODO: Make a proper location for the search #}
|
||||
{# Search:#}
|
||||
{# <form#}
|
||||
{# style="display: inline;"#}
|
||||
{# class="search" action="{{ pathto('search') }}" method="get">#}
|
||||
{# <input type="text" name="q" size="18" />#}
|
||||
{# <input type="hidden" name="check_keywords" value="yes" />#}
|
||||
{# <input type="hidden" name="area" value="default" />#}
|
||||
{# </form>#}
|
||||
{# {%- endif %}#}
|
||||
|
||||
|
||||
</div>
|
||||
<!-- end of footer -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- script which should be loaded after everything else -->
|
||||
<script type="text/javascript" src="{{ pathto('_static/js/docs.js', 1) }}"></script>
|
||||
|
||||
<!-- Swiftype search -->
|
||||
|
||||
<script type="text/javascript">
|
||||
var Swiftype = window.Swiftype || {};
|
||||
(function() {
|
||||
Swiftype.key = 'pWPnnyvwcfpcrw1o51Sz';
|
||||
Swiftype.inputElement = '#st-search-input';
|
||||
Swiftype.resultContainingElement = '#st-results-container';
|
||||
Swiftype.attachElement = '#st-search-input';
|
||||
Swiftype.renderStyle = "overlay";
|
||||
// from https://swiftype.com/questions/how-can-i-make-more-popular-content-rank-higher
|
||||
// Use "page" for now -- they don't subgroup by document type yet.
|
||||
Swiftype.searchFunctionalBoosts = {"page": {"popularity": "linear"}};
|
||||
|
||||
// Function to make the sticky header possible
|
||||
var shiftWindow = function() {
|
||||
scrollBy(0, -70);
|
||||
console.log("window shifted")
|
||||
};
|
||||
window.addEventListener("hashchange", shiftWindow);
|
||||
|
||||
function loadShift() {
|
||||
if (window.location.hash) {
|
||||
console.log("window has hash");
|
||||
shiftWindow();
|
||||
}
|
||||
}
|
||||
|
||||
$(window).load(function() {
|
||||
loadShift();
|
||||
console.log("late loadshift");
|
||||
});
|
||||
|
||||
$(function(){
|
||||
|
||||
// sidebar accordian-ing
|
||||
// don't apply on last object (it should be the FAQ)
|
||||
|
||||
// define an array to which all opened items should be added
|
||||
var openmenus = [];
|
||||
|
||||
var elements = $('.toctree-l2');
|
||||
for (var i = 0; i < elements.length; i += 1) { var current = $(elements[i]); current.children('ul').hide();}
|
||||
|
||||
|
||||
// set initial collapsed state
|
||||
var elements = $('.toctree-l1');
|
||||
for (var i = 0; i < elements.length; i += 1) {
|
||||
var current = $(elements[i]);
|
||||
if (current.hasClass('current')) {
|
||||
|
||||
currentlink = current.children('a')[0].href;
|
||||
openmenus.push(currentlink);
|
||||
|
||||
// do nothing
|
||||
} else {
|
||||
// collapse children
|
||||
current.children('ul').hide();
|
||||
}
|
||||
}
|
||||
|
||||
// attached handler on click
|
||||
// Do not attach to first element or last (intro, faq) so that
|
||||
// first and last link directly instead of accordian
|
||||
$('.sidebar > ul > li > a').not(':last').not(':first').click(function(){
|
||||
|
||||
var index = $.inArray(this.href, openmenus)
|
||||
|
||||
if (index > -1) {
|
||||
console.log(index);
|
||||
openmenus.splice(index, 1);
|
||||
|
||||
|
||||
$(this).parent().children('ul').slideUp(200, function() {
|
||||
// $(this).parent().removeClass('current'); // toggle after effect
|
||||
});
|
||||
}
|
||||
else {
|
||||
openmenus.push(this.href);
|
||||
console.log(this);
|
||||
|
||||
var current = $(this);
|
||||
|
||||
setTimeout(function() {
|
||||
$('.sidebar > ul > li').removeClass('current');
|
||||
current.parent().addClass('current'); // toggle before effect
|
||||
current.parent().children('ul').hide();
|
||||
current.parent().children('ul').slideDown(200);
|
||||
}, 100);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
});
|
||||
var script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.async = true;
|
||||
script.src = "//swiftype.com/embed.js";
|
||||
var entry = document.getElementsByTagName('script')[0];
|
||||
entry.parentNode.insertBefore(script, entry);
|
||||
}());
|
||||
</script>
|
||||
|
||||
<!-- Google analytics -->
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-6096819-11']);
|
||||
_gaq.push(['_setDomainName', 'docker.io']);
|
||||
_gaq.push(['_setAllowLinker', true]);
|
||||
_gaq.push(['_trackPageview']);
|
||||
<!-- Google analytics -->
|
||||
<script type="text/javascript">
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-6096819-11']);
|
||||
_gaq.push(['_setDomainName', 'docker.io']);
|
||||
_gaq.push(['_setAllowLinker', true]);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
</script>
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,280 +1,205 @@
|
||||
/* ==========================================================================
|
||||
Author's custom styles
|
||||
========================================================================== */
|
||||
.red {
|
||||
background-color: red;
|
||||
}
|
||||
.blue {
|
||||
background-color: blue;
|
||||
}
|
||||
.orange {
|
||||
background-color: orange;
|
||||
}
|
||||
.gray {
|
||||
background-color: grey;
|
||||
.debug {
|
||||
border: 2px dotted red !important;
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
padding-top: 58px;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
min-width: 940px;
|
||||
font-family: "Cabin", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-weight: 900;
|
||||
p a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
/* ===================
|
||||
Top navigation
|
||||
===================== */
|
||||
.navbar {
|
||||
z-index: 999;
|
||||
background-color: white;
|
||||
p a.btn {
|
||||
text-decoration: none;
|
||||
}
|
||||
.brand.logo a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.navbar .navbar-inner {
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
.navbar .nav li a {
|
||||
padding: 22px 15px 22px;
|
||||
padding: 24.2857142855px 17px 24.2857142855px;
|
||||
color: #777777;
|
||||
text-decoration: none;
|
||||
text-shadow: 0 1px 0 #f2f2f2;
|
||||
}
|
||||
.navbar-dotcloud .container {
|
||||
border-bottom: 2px #000000 solid;
|
||||
.navbar .nav > li {
|
||||
float: left;
|
||||
}
|
||||
.nav-underline {
|
||||
height: 6px;
|
||||
background-color: #71afc0;
|
||||
}
|
||||
.nav-login li a {
|
||||
color: white;
|
||||
padding: 10px 15px 10px;
|
||||
}
|
||||
.navbar .brand {
|
||||
margin-left: 0px;
|
||||
float: left;
|
||||
display: block;
|
||||
}
|
||||
.navbar-inner {
|
||||
min-height: 70px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
background-color: #ededed;
|
||||
background-image: -moz-linear-gradient(top, #f2f2f2, #e5e5e5);
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5));
|
||||
background-image: -webkit-linear-gradient(top, #f2f2f2, #e5e5e5);
|
||||
background-image: -o-linear-gradient(top, #f2f2f2, #e5e5e5);
|
||||
background-image: linear-gradient(to bottom, #f2f2f2, #e5e5e5);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffe5e5e5', GradientType=0);
|
||||
border: 1px solid #c7c7c7;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
|
||||
-moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
|
||||
}
|
||||
.brand.logo a {
|
||||
color: white;
|
||||
}
|
||||
.inline-icon {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
/*
|
||||
* Responsive YouTube, Vimeo, Embed, and HTML5 Videos with CSS
|
||||
* http://www.jonsuh.com
|
||||
*
|
||||
* Copyright (c) 2012 Jonathan Suh
|
||||
* Free to use under the MIT license.
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
.js-video {
|
||||
height: 0;
|
||||
padding-top: 25px;
|
||||
padding-bottom: 67.5%;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.js-video.vimeo {
|
||||
padding-top: 0;
|
||||
}
|
||||
.js-video.widescreen {
|
||||
padding-bottom: 57.25%;
|
||||
}
|
||||
.js-video embed,
|
||||
.js-video iframe,
|
||||
.js-video object,
|
||||
.js-video video {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
/* Responsive */
|
||||
@media (max-width: 767px) {
|
||||
.js-video {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
/* button style from http://charliepark.org/bootstrap_buttons/ */
|
||||
.btn-custom {
|
||||
background-color: #292929 !important;
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#515151", endColorstr="#282828");
|
||||
background-image: -khtml-gradient(linear, left top, left bottom, from(#515151), to(#282828));
|
||||
background-image: -moz-linear-gradient(top, #515151, #282828);
|
||||
background-image: -ms-linear-gradient(top, #515151, #282828);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #515151), color-stop(100%, #282828));
|
||||
background-image: -webkit-linear-gradient(top, #515151, #282828);
|
||||
background-image: -o-linear-gradient(top, #515151, #282828);
|
||||
background-image: linear-gradient(#515151, #282828);
|
||||
border-color: #282828 #282828 #1f1f1f;
|
||||
color: #fff !important;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.26);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
/* ===================
|
||||
Page title bar
|
||||
===================== */
|
||||
h1.pageheader {
|
||||
color: #ffffff;
|
||||
font-size: 20px;
|
||||
font-family: "Arial Black", Tahoma, sans-serif;
|
||||
margin: 8px;
|
||||
margin-left: 22px;
|
||||
}
|
||||
/* ===================
|
||||
Hero unit
|
||||
===================== */
|
||||
section.header {
|
||||
margin-top: 0;
|
||||
}
|
||||
.hero-unit {
|
||||
background-color: #292e33;
|
||||
}
|
||||
.hero-unit h5 {
|
||||
color: #ffffff;
|
||||
}
|
||||
/* ===================
|
||||
Main content layout
|
||||
===================== */
|
||||
.contentblock {
|
||||
margin-top: 20px;
|
||||
border-width: 3px;
|
||||
background-color: #eeeeee;
|
||||
box-sizing: content-box;
|
||||
padding: 20px;
|
||||
}
|
||||
.section img {
|
||||
margin: 15px 15px 15px 0;
|
||||
border: 2px solid gray;
|
||||
}
|
||||
.admonition {
|
||||
padding: 10px;
|
||||
border: 1px solid grey;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.admonition .admonition-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
.admonition.note {
|
||||
background-color: #f1ebba;
|
||||
}
|
||||
.admonition.warning {
|
||||
background-color: #eed9af;
|
||||
}
|
||||
.admonition.danger {
|
||||
background-color: #e9bcab;
|
||||
}
|
||||
/* ===================
|
||||
left navigation
|
||||
===================== */
|
||||
.dotcloudsidebar {
|
||||
float: left;
|
||||
height: 100%;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
margin-top: 78px;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
.sidebar {
|
||||
font-weight: normal;
|
||||
float: left;
|
||||
/* min-height: 475px;*/
|
||||
|
||||
background: #ececec;
|
||||
/* border-left: 1px solid #bbbbbb;*/
|
||||
|
||||
/* border-right: 1px solid #cccccc;*/
|
||||
|
||||
position: relative;
|
||||
}
|
||||
.sidebar ul {
|
||||
padding: 0px;
|
||||
}
|
||||
.sidebar ul li {
|
||||
font-size: 14px;
|
||||
list-style-type: none;
|
||||
list-style-position: outside;
|
||||
list-style-image: none;
|
||||
margin-left: -25px;
|
||||
padding: 0px;
|
||||
}
|
||||
.sidebar ul li a {
|
||||
display: block;
|
||||
color: #443331;
|
||||
outline: 1px solid #dddddd;
|
||||
padding: 12px 12px 10px 12px;
|
||||
margin-top: 1px;
|
||||
background-color: #d2d2d2;
|
||||
}
|
||||
.sidebar ul li .toctree-l1 {
|
||||
font-size: larger;
|
||||
}
|
||||
.sidebar ul li .toctree-l1 a {
|
||||
background-color: #dfdfdf;
|
||||
}
|
||||
.sidebar ul li .toctree-l1 .current {
|
||||
font-weight: bold;
|
||||
}
|
||||
.sidebar ul li .toctree-l2 a {
|
||||
padding-left: 18px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.sidebar ul li .toctree-l2 .current {
|
||||
font-weight: bold;
|
||||
}
|
||||
.sidebar ul li .toctree-l3 {
|
||||
font-size: smaller;
|
||||
}
|
||||
.sidebar ul li .toctree-l3 a {
|
||||
padding-left: 36px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.sidebar ul li .toctree-l3 .current {
|
||||
font-weight: bold;
|
||||
}
|
||||
.brand img {
|
||||
height: 38px;
|
||||
margin-left: -6px;
|
||||
}
|
||||
.border-box {
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
background-color: #111188;
|
||||
color: white;
|
||||
}
|
||||
.titlebar {
|
||||
background-color: #000000;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 20px;
|
||||
min-height: 40px;
|
||||
color: white;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
.footer {
|
||||
border-top: 2px solid black;
|
||||
.row {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 20px;
|
||||
min-height: 40px;
|
||||
padding-left: 8px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
/* This is the default */
|
||||
.span6.with-padding {
|
||||
background-color: #111188;
|
||||
height: 200px;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
div[class*='span'] {
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#global {
|
||||
min-height: 500px;
|
||||
.box {
|
||||
padding: 30px;
|
||||
background-color: white;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.paper {
|
||||
background-color: white;
|
||||
padding-top: 30px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
.copy-headline {
|
||||
margin-top: 0px;
|
||||
}
|
||||
.box h1,
|
||||
.box h2,
|
||||
.box h3,
|
||||
.box h4 {
|
||||
margin-top: -5px;
|
||||
}
|
||||
.nested {
|
||||
padding: 30px;
|
||||
}
|
||||
.box.div {
|
||||
padding: 30px;
|
||||
}
|
||||
span.read-more {
|
||||
margin-left: 15px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.forcetopalign {
|
||||
margin-top: 15px !important;
|
||||
}
|
||||
.forcetopmargin {
|
||||
margin-top: 23px !important;
|
||||
}
|
||||
.forceleftalign {
|
||||
margin-left: 15px !important;
|
||||
}
|
||||
.forceleftmargin {
|
||||
margin-left: 21px !important;
|
||||
}
|
||||
.textcenter {
|
||||
text-align: center;
|
||||
}
|
||||
.textright {
|
||||
text-align: right;
|
||||
}
|
||||
.textsmaller {
|
||||
font-size: 12px;
|
||||
}
|
||||
.modal-backdrop {
|
||||
opacity: 0.4;
|
||||
}
|
||||
/* generic page copy styles */
|
||||
.copy-headline h1 {
|
||||
font-size: 21px;
|
||||
}
|
||||
/* =======================
|
||||
Row size
|
||||
Sticky footer
|
||||
======================= */
|
||||
.row1 {
|
||||
background-color: #999999;
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
/* The html and body elements cannot have any padding or margin. */
|
||||
|
||||
}
|
||||
/* Wrapper for page content to push down footer */
|
||||
#wrap {
|
||||
min-height: 100%;
|
||||
height: auto !important;
|
||||
height: 100%;
|
||||
/* Negative indent footer by it's height */
|
||||
|
||||
margin: 0 auto -280px;
|
||||
}
|
||||
/* Set the fixed height of the footer here */
|
||||
#push-the-footer,
|
||||
#footer {
|
||||
height: 280px;
|
||||
}
|
||||
.main-row {
|
||||
padding-top: 50px;
|
||||
}
|
||||
#footer .footer {
|
||||
margin-top: 160px;
|
||||
}
|
||||
#footer .footer .ligaturesymbols {
|
||||
font-size: 30px;
|
||||
color: black;
|
||||
}
|
||||
#footer .footer .ligaturesymbols a {
|
||||
color: black;
|
||||
}
|
||||
#footer .footer .footerlist h3,
|
||||
#footer .footer .footerlist h4 {
|
||||
/* correct the top alignment */
|
||||
|
||||
margin-top: 0px;
|
||||
}
|
||||
.footer-landscape-image {
|
||||
position: absolute:
|
||||
bottom: 0;
|
||||
margin-bottom: 0;
|
||||
background-image: url('https://www.docker.io/static/img/website-footer_clean.svg');
|
||||
background-repeat: repeat-x;
|
||||
height: 280px;
|
||||
}
|
||||
.main-row {
|
||||
margin-top: 40px;
|
||||
}
|
||||
.main-content {
|
||||
padding: 16px 18px inherit;
|
||||
}
|
||||
/* =======================
|
||||
Social footer
|
||||
======================= */
|
||||
.social {
|
||||
margin-left: 0px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
.social .twitter,
|
||||
.social .github,
|
||||
.social .googleplus {
|
||||
background: url("../img/footer-links.png") no-repeat transparent;
|
||||
background: url("https://www.docker.io/static/img/footer-links.png") no-repeat transparent;
|
||||
display: inline-block;
|
||||
height: 35px;
|
||||
overflow: hidden;
|
||||
@@ -288,93 +213,160 @@ section.header {
|
||||
.social .github {
|
||||
background-position: -59px 2px;
|
||||
}
|
||||
#fork-us {
|
||||
/*font-family: 'Maven Pro';*/
|
||||
|
||||
/*font-weight: bold;*/
|
||||
|
||||
form table th {
|
||||
vertical-align: top;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
form .labeldd label {
|
||||
font-weight: bold;
|
||||
}
|
||||
form .helptext {
|
||||
font-size: 12px;
|
||||
/*text-transform: uppercase;*/
|
||||
|
||||
display: block;
|
||||
padding: 0px 1em;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
background-color: #43484c;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFF6E56', endColorstr='#FFED4F35');
|
||||
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #747474), color-stop(100%, #43484c));
|
||||
background-image: -webkit-linear-gradient(top, #747474 0%, #43484c 100%);
|
||||
background-image: -moz-linear-gradient(top, #747474 0%, #43484c 100%);
|
||||
background-image: -o-linear-gradient(top, #747474 0%, #43484c 100%);
|
||||
background-image: linear-gradient(top, #747474 0%, #43484c 100%);
|
||||
border: 1px solid #43484c;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
-ms-border-radius: 4px;
|
||||
-o-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: inset rgba(255, 255, 255, 0.17) 0 1px 1px;
|
||||
-moz-box-shadow: inset rgba(255, 255, 255, 0.17) 0 1px 1px;
|
||||
box-shadow: inset rgba(255, 255, 255, 0.17) 0 1px 1px;
|
||||
margin: 8px;
|
||||
margin-top: -4px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
#fork-us a {
|
||||
color: #faf2ee;
|
||||
text-shadow: rgba(0, 0, 0, 0.3) 0px 1px 0px;
|
||||
form .fielddd input {
|
||||
width: 250px;
|
||||
}
|
||||
form .error {
|
||||
color: #a30000;
|
||||
}
|
||||
div.alert.alert-block {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
/* ======================= =======================
|
||||
Documentation
|
||||
========================= ========================= */
|
||||
/* =======================
|
||||
Media size overrides
|
||||
======================= */
|
||||
/* Large desktop */
|
||||
@media (min-width: 1200px) {
|
||||
.span6.with-padding {
|
||||
background-color: #dc143c;
|
||||
width: 540px;
|
||||
padding: 15px;
|
||||
}
|
||||
Styles for the sidebar
|
||||
========================= */
|
||||
.page-title {
|
||||
background-color: white;
|
||||
border: 1px solid transparent;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
/* Normal desktop */
|
||||
@media (min-width: 980px) and (max-width: 1199px) {
|
||||
.span6.with-padding {
|
||||
background-color: #ee1111;
|
||||
width: 440px;
|
||||
padding: 10px;
|
||||
}
|
||||
.page-title h4 {
|
||||
font-size: 20px;
|
||||
}
|
||||
/* Portrait tablet to landscape and desktop */
|
||||
@media (min-width: 768px) and (max-width: 979px) {
|
||||
body {
|
||||
padding-top: 0px;
|
||||
}
|
||||
.span6.with-padding {
|
||||
background-color: #292e33;
|
||||
width: 332px;
|
||||
padding: 10px;
|
||||
}
|
||||
.bs-docs-sidebar {
|
||||
padding-left: 5px;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
margin-top: 18px;
|
||||
}
|
||||
/* Landscape phone to portrait tablet */
|
||||
@media (max-width: 767px) {
|
||||
body {
|
||||
padding-top: 0px;
|
||||
}
|
||||
#global {
|
||||
/* TODO: Fix this to be relative to the navigation size */
|
||||
|
||||
}
|
||||
#fork-us {
|
||||
display: none;
|
||||
}
|
||||
.bs-docs-sidebar ul {
|
||||
list-style: none;
|
||||
margin-left: 0px;
|
||||
}
|
||||
/* Landscape phones and down */
|
||||
@media (max-width: 480px) {
|
||||
#nav-gettingstarted {
|
||||
display: none;
|
||||
}
|
||||
.bs-docs-sidebar .toctree-l2 > ul {
|
||||
width: 100%;
|
||||
}
|
||||
/* Misc fixes */
|
||||
table th {
|
||||
text-align: left;
|
||||
.bs-docs-sidebar ul > li.toctree-l1.has-children {
|
||||
background-image: url('../img/menu_arrow_right.gif');
|
||||
background-repeat: no-repeat;
|
||||
background-position: 13px 13px;
|
||||
list-style-type: none;
|
||||
padding: 0px 0px 0px 0px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.bs-docs-sidebar ul > li.toctree-l1.has-children.open {
|
||||
background-image: url('../img/menu_arrow_down.gif');
|
||||
}
|
||||
.bs-docs-sidebar ul > li > a {
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 35px;
|
||||
padding-right: 20px;
|
||||
font-size: 14px;
|
||||
border-bottom: 1.5px solid #595959;
|
||||
line-height: 20px;
|
||||
}
|
||||
.bs-docs-sidebar ul > li:first-child.active > a {
|
||||
border-top: 1.5px solid #595959;
|
||||
}
|
||||
.bs-docs-sidebar ul > li:last-child > a {
|
||||
border-bottom: none;
|
||||
}
|
||||
.bs-docs-sidebar ul > li:last-child.active > a {
|
||||
border-bottom: 1.5px solid #595959;
|
||||
}
|
||||
.bs-docs-sidebar ul > li.active > a {
|
||||
border-right: 1.5px solid #595959;
|
||||
border-left: 1.5px solid #595959;
|
||||
color: #394d54;
|
||||
}
|
||||
.bs-docs-sidebar ul > li:hover {
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
.bs-docs-sidebar.toctree-l3 ul {
|
||||
display: inherit;
|
||||
margin-left: 15px;
|
||||
font-size: smaller;
|
||||
}
|
||||
.bs-docs-sidebar .toctree-l3 a {
|
||||
border: none;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
}
|
||||
.bs-docs-sidebar ul > li > ul {
|
||||
display: none;
|
||||
}
|
||||
.bs-docs-sidebar ul > li.current > ul {
|
||||
display: inline-block;
|
||||
padding-left: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
.toctree-l2.current > a {
|
||||
font-weight: bold;
|
||||
}
|
||||
.toctree-l2.current {
|
||||
border: 1.5px solid #595959;
|
||||
color: #394d54;
|
||||
}
|
||||
/* =====================================
|
||||
Styles for the floating version widget
|
||||
====================================== */
|
||||
.version-flyer {
|
||||
position: fixed;
|
||||
float: right;
|
||||
right: 0;
|
||||
bottom: 40px;
|
||||
background-color: #E0E0E0;
|
||||
border: 1px solid #88BABC;
|
||||
padding: 5px;
|
||||
font-size: larger;
|
||||
}
|
||||
.version-flyer .content {
|
||||
padding-right: 45px;
|
||||
margin-top: 7px;
|
||||
margin-left: 7px;
|
||||
background-image: url('../img/container3.png');
|
||||
background-position: right center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.version-flyer .alternative {
|
||||
visibility: hidden;
|
||||
display: none;
|
||||
}
|
||||
.version-flyer .active-slug {
|
||||
visibility: visible;
|
||||
display: inline-block;
|
||||
}
|
||||
.version-flyer:hover .alternative {
|
||||
animation-duration: 1s;
|
||||
display: inline-block;
|
||||
visibility: visible;
|
||||
}
|
||||
/* =====================================
|
||||
Styles for
|
||||
====================================== */
|
||||
h1:hover > a.headerlink,
|
||||
h2:hover > a.headerlink,
|
||||
h3:hover > a.headerlink,
|
||||
@@ -391,21 +383,30 @@ dt:hover > a.headerlink {
|
||||
float: right;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* Swiftype style */
|
||||
|
||||
#st-search-input {
|
||||
margin-right: 14px;
|
||||
margin-left: 9px;
|
||||
height: 19px;
|
||||
width: 120px;
|
||||
|
||||
/* =====================================
|
||||
Miscellaneous information
|
||||
====================================== */
|
||||
.admonition.warning,
|
||||
.admonition.note,
|
||||
.admonition.seealso,
|
||||
.admonition.todo {
|
||||
border: 3px solid black;
|
||||
padding: 10px;
|
||||
margin: 5px auto 10px;
|
||||
}
|
||||
.admonition .admonition-title {
|
||||
font-size: larger;
|
||||
}
|
||||
.admonition.warning,
|
||||
.admonition.danger {
|
||||
border-color: #ac0004;
|
||||
}
|
||||
.admonition.note {
|
||||
border-color: #cbc200;
|
||||
}
|
||||
.admonition.todo {
|
||||
border-color: orange;
|
||||
}
|
||||
.admonition.seealso {
|
||||
border-color: #23cb1f;
|
||||
}
|
||||
#swiftype-img {
|
||||
border: none;
|
||||
width: 145px;
|
||||
height: auto;
|
||||
margin: 0px auto;
|
||||
margin-left: 13px;
|
||||
margin-top: -30px;
|
||||
}
|
||||
@@ -1,376 +1,337 @@
|
||||
// Main CSS configuration file
|
||||
// by Thatcher Peskens, thatcher@dotcloud.com
|
||||
//
|
||||
// Please note variables.less is customized to include custom font, background-color, and link colors.
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
Author's custom styles
|
||||
========================================================================== */
|
||||
|
||||
@import "variables.less";
|
||||
|
||||
@red: crimson;
|
||||
@lightblue: #118;
|
||||
@lightred: #e11;
|
||||
@darkblue: #292E33;
|
||||
// Variables for main.less
|
||||
// -----------------------
|
||||
|
||||
@borderGray: #888;
|
||||
@box-top-margin: 8px;
|
||||
@box-padding-size: 30px;
|
||||
@docker-background-color: #71AFC0;
|
||||
@very-dark-sea-green: #394D54;
|
||||
|
||||
// Custom colors for Docker
|
||||
// --------------------------
|
||||
@gray-super-light: #F2F2F2;
|
||||
@deep-red: #A30000;
|
||||
@deep-blue: #1B2033;
|
||||
@deep-green: #007035;
|
||||
@link-blue: #213B8F;
|
||||
|
||||
|
||||
.red {
|
||||
background-color: red;
|
||||
}
|
||||
.blue {
|
||||
background-color: blue;
|
||||
}
|
||||
.orange {
|
||||
background-color: orange;
|
||||
}
|
||||
.gray {
|
||||
background-color: grey;
|
||||
.debug {
|
||||
border: 2px dotted red !important;
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
// Other custom colors for Docker
|
||||
// --------------------------
|
||||
|
||||
// ** are defined in sources/less/variables **
|
||||
//@import "bootstrap/variables.less";
|
||||
|
||||
|
||||
// Styles generic for each and every page
|
||||
// ----------------------------------- // -----------------------------------
|
||||
|
||||
|
||||
// moving body down to make place for fixed navigation
|
||||
body {
|
||||
padding-top: 58px;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
min-width: 940px;
|
||||
font-family: @font-family-base;
|
||||
|
||||
}
|
||||
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
// font-weight: bold;
|
||||
font-weight: 900;
|
||||
}
|
||||
p a {
|
||||
text-decoration: underline;
|
||||
|
||||
/* ===================
|
||||
Top navigation
|
||||
===================== */
|
||||
|
||||
.navbar {
|
||||
z-index: 999;
|
||||
.nav {
|
||||
// float: right;
|
||||
|
||||
li a{
|
||||
padding: 22px 15px 22px;
|
||||
&.btn {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
background-color: white;
|
||||
|
||||
}
|
||||
|
||||
.navbar-dotcloud .container {
|
||||
border-bottom: 2px @black solid;
|
||||
.brand.logo a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
// Styles for top navigation
|
||||
// ----------------------------------
|
||||
.navbar .navbar-inner {
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.navbar .nav {
|
||||
li a {
|
||||
padding: ((@navbar-height - @line-height-base) / 2) 17px ((@navbar-height - @line-height-base) / 2);
|
||||
color: #777777;
|
||||
text-decoration: none;
|
||||
text-shadow: 0 1px 0 #f2f2f2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.navbar .nav > li {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.nav-underline {
|
||||
height: 6px;
|
||||
background-color: @docker-background-color;
|
||||
}
|
||||
|
||||
.nav-login {
|
||||
li {
|
||||
a {
|
||||
color: white;
|
||||
padding: 10px 15px 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.navbar .brand {
|
||||
margin-left: 0px;
|
||||
float: left;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.navbar-inner {
|
||||
min-height: 70px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
background-color: #ededed;
|
||||
background-image: -moz-linear-gradient(top, #f2f2f2, #e5e5e5);
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5));
|
||||
background-image: -webkit-linear-gradient(top, #f2f2f2, #e5e5e5);
|
||||
background-image: -o-linear-gradient(top, #f2f2f2, #e5e5e5);
|
||||
background-image: linear-gradient(to bottom, #f2f2f2, #e5e5e5);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffe5e5e5', GradientType=0);
|
||||
border: 1px solid #c7c7c7;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
|
||||
-moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
|
||||
}
|
||||
|
||||
.brand.logo a {
|
||||
color: white;
|
||||
|
||||
}
|
||||
|
||||
.logo {
|
||||
// background-color: #A30000;
|
||||
// color: white;
|
||||
}
|
||||
|
||||
.inline-icon {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Responsive YouTube, Vimeo, Embed, and HTML5 Videos with CSS
|
||||
* http://www.jonsuh.com
|
||||
*
|
||||
* Copyright (c) 2012 Jonathan Suh
|
||||
* Free to use under the MIT license.
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
// Bootstrap elements
|
||||
// ----------------------------------
|
||||
|
||||
.js-video {
|
||||
height: 0;
|
||||
padding-top: 25px;
|
||||
padding-bottom: 67.5%;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.js-video.vimeo {
|
||||
padding-top: 0;
|
||||
}
|
||||
.js-video.widescreen {
|
||||
padding-bottom: 57.25%;
|
||||
}
|
||||
.js-video embed, .js-video iframe, .js-video object, .js-video video {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 767px) {
|
||||
.js-video {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* button style from http://charliepark.org/bootstrap_buttons/ */
|
||||
.btn-custom {
|
||||
background-color: hsl(0, 0%, 16%) !important;
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#515151", endColorstr="#282828");
|
||||
background-image: -khtml-gradient(linear, left top, left bottom, from(#515151), to(#282828));
|
||||
background-image: -moz-linear-gradient(top, #515151, #282828);
|
||||
background-image: -ms-linear-gradient(top, #515151, #282828);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #515151), color-stop(100%, #282828));
|
||||
background-image: -webkit-linear-gradient(top, #515151, #282828);
|
||||
background-image: -o-linear-gradient(top, #515151, #282828);
|
||||
background-image: linear-gradient(#515151, #282828);
|
||||
border-color: #282828 #282828 hsl(0, 0%, 12%);
|
||||
color: #fff !important;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.26);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ===================
|
||||
Page title bar
|
||||
===================== */
|
||||
|
||||
h1.pageheader {
|
||||
color: @white;
|
||||
font-size: 20px;
|
||||
font-family: "Arial Black", Tahoma, sans-serif;
|
||||
margin: 8px;
|
||||
margin-left: 22px;
|
||||
}
|
||||
|
||||
/* ===================
|
||||
Hero unit
|
||||
===================== */
|
||||
|
||||
section.header {
|
||||
margin-top:0;
|
||||
}
|
||||
|
||||
.hero-unit {
|
||||
background-color: @darkblue;
|
||||
|
||||
h5 {
|
||||
color: @white;
|
||||
}
|
||||
.subtitle {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* ===================
|
||||
Main content layout
|
||||
===================== */
|
||||
|
||||
.contentblock {
|
||||
margin-top: 20px;
|
||||
border-width: 3px;
|
||||
// border-color: #E00;
|
||||
// border-style:solid;
|
||||
// border-color: @borderGray;
|
||||
// box-sizing: border-box;
|
||||
background-color: @grayLighter;
|
||||
box-sizing: content-box;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.section img {
|
||||
margin: 15px 15px 15px 0;
|
||||
border: 2px solid gray;
|
||||
}
|
||||
|
||||
.admonition {
|
||||
padding: 10px;
|
||||
border: 1px solid grey;
|
||||
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.admonition .admonition-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.admonition.note {
|
||||
background-color: rgb(241, 235, 186);
|
||||
}
|
||||
|
||||
.admonition.warning {
|
||||
background-color: rgb(238, 217, 175);
|
||||
}
|
||||
|
||||
.admonition.danger {
|
||||
background-color: rgb(233, 188, 171);
|
||||
}
|
||||
|
||||
/* ===================
|
||||
left navigation
|
||||
===================== */
|
||||
|
||||
.dotcloudsidebar {
|
||||
// background-color: #ee3;
|
||||
// border: 1px red dotted;
|
||||
float: left;
|
||||
height: 100%;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
position: relative;
|
||||
// margin: 0px;
|
||||
min-height: 100%;
|
||||
margin-top: 78px;
|
||||
margin-bottom: 22px;
|
||||
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
// font-family: "Maven Pro";
|
||||
font-weight: normal;
|
||||
// margin-top: 38px;
|
||||
float: left;
|
||||
// width: 220px;
|
||||
/* min-height: 475px;*/
|
||||
// margin-bottom: 28px;
|
||||
// padding-bottom: 120px;
|
||||
background: #ececec;
|
||||
/* border-left: 1px solid #bbbbbb;*/
|
||||
/* border-right: 1px solid #cccccc;*/
|
||||
position: relative;
|
||||
|
||||
|
||||
|
||||
ul {
|
||||
padding: 0px;
|
||||
li {
|
||||
font-size: 14px;
|
||||
// list-style: none;
|
||||
list-style-type: none;
|
||||
list-style-position: outside;
|
||||
list-style-image: none;
|
||||
margin-left: -25px;
|
||||
padding: 0px;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
color: #443331;
|
||||
outline: 1px solid #dddddd;
|
||||
padding: 12px 12px 10px 12px;
|
||||
margin-top: 1px;
|
||||
background-color: #d2d2d2;
|
||||
}
|
||||
|
||||
.toctree-l1, .toctree-l2 {
|
||||
|
||||
}
|
||||
|
||||
.toctree-l1 {
|
||||
font-size: larger;
|
||||
a {
|
||||
background-color: rgb(223, 223, 223);
|
||||
}
|
||||
.current {
|
||||
font-weight: bold;
|
||||
}
|
||||
// margin-left: -25px;
|
||||
}
|
||||
.toctree-l2 {
|
||||
a {
|
||||
padding-left: 18px;
|
||||
background-color: rgb(255, 255, 255);
|
||||
}
|
||||
.current {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
}
|
||||
.toctree-l3 {
|
||||
font-size: smaller;
|
||||
a {
|
||||
padding-left: 36px;
|
||||
background-color: rgb(255, 255, 255);
|
||||
}
|
||||
.current {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.brand img {
|
||||
height: 38px;
|
||||
margin-left: -6px;
|
||||
}
|
||||
|
||||
.border-box {
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
background-color: @lightblue;
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
.titlebar {
|
||||
background-color: @black;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 20px;
|
||||
min-height: 40px;
|
||||
color: white;
|
||||
// box-sizing: border-box;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
|
||||
.footer {
|
||||
border-top: 2px solid black;
|
||||
|
||||
// background-color: #d2d2d2;
|
||||
.row {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
min-height: 40px;
|
||||
.container {
|
||||
// background-color: green;
|
||||
}
|
||||
|
||||
padding-left: 8px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
// Styles on blocks of content
|
||||
// ----------------------------------
|
||||
|
||||
// everything which is a block should have box-sizing: border-box;
|
||||
|
||||
div[class*='span']
|
||||
{
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* This is the default */
|
||||
.span6.with-padding {
|
||||
background-color: @lightblue;
|
||||
height: 200px;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
// Box for making white with a border, and some nice spacings
|
||||
.box {
|
||||
padding: @box-padding-size;
|
||||
background-color: white;
|
||||
margin-top: @box-top-margin;
|
||||
}
|
||||
|
||||
#global {
|
||||
min-height: 500px;
|
||||
.paper {
|
||||
background-color: white;
|
||||
padding-top: 30px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
.copy-headline {
|
||||
margin-top: 0px;
|
||||
// border-bottom: 1.2px solid @veryDarkSeaGreen;
|
||||
}
|
||||
|
||||
.box {
|
||||
h1, h2, h3, h4 {
|
||||
margin-top: -5px;
|
||||
}
|
||||
}
|
||||
|
||||
.nested {
|
||||
padding: @box-padding-size;
|
||||
}
|
||||
|
||||
.box.div {
|
||||
padding: @box-padding-size;
|
||||
}
|
||||
|
||||
span.read-more {
|
||||
margin-left: 15px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
// set a top margin of @box-top-margin + 8 px to make it show a margin
|
||||
//instead of the div being flush against the side. Typically only
|
||||
// required for a stacked div in a column, w.o. using row.
|
||||
.forcetopalign {
|
||||
margin-top: 15px !important;
|
||||
}
|
||||
.forcetopmargin {
|
||||
margin-top: 23px !important;
|
||||
}
|
||||
.forceleftalign {
|
||||
margin-left: 15px !important;
|
||||
}
|
||||
.forceleftmargin {
|
||||
margin-left: 21px !important;
|
||||
}
|
||||
|
||||
|
||||
// simple text aligns
|
||||
.textcenter {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.textright {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.textsmaller {
|
||||
font-size: @font-size-small;
|
||||
}
|
||||
|
||||
.modal-backdrop {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
|
||||
/* generic page copy styles */
|
||||
|
||||
.copy-headline h1 {
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
|
||||
/* =======================
|
||||
Row size
|
||||
Sticky footer
|
||||
======================= */
|
||||
|
||||
.row1 {
|
||||
background-color: @grayLight;
|
||||
@sticky-footer-height: 280px;
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
/* The html and body elements cannot have any padding or margin. */
|
||||
}
|
||||
|
||||
/* Wrapper for page content to push down footer */
|
||||
#wrap {
|
||||
min-height: 100%;
|
||||
height: auto !important;
|
||||
height: 100%;
|
||||
/* Negative indent footer by it's height */
|
||||
margin: 0 auto -@sticky-footer-height;
|
||||
}
|
||||
|
||||
/* Set the fixed height of the footer here */
|
||||
#push-the-footer,
|
||||
#footer {
|
||||
height: @sticky-footer-height;
|
||||
}
|
||||
|
||||
#footer {
|
||||
// margin-bottom: -60px;
|
||||
// margin-top: 160px;
|
||||
}
|
||||
|
||||
.main-row {
|
||||
padding-top: @navbar-height;
|
||||
}
|
||||
|
||||
|
||||
// Styles on the footer
|
||||
// ----------------------------------
|
||||
|
||||
//
|
||||
#footer .footer {
|
||||
margin-top: 160px;
|
||||
.ligaturesymbols {
|
||||
font-size: 30px;
|
||||
color: black;
|
||||
a {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
|
||||
.footerlist {
|
||||
h3, h4 {
|
||||
/* correct the top alignment */
|
||||
margin-top: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.footer-landscape-image {
|
||||
position: absolute:
|
||||
bottom: 0;
|
||||
margin-bottom: 0;
|
||||
background-image: url('https://www.docker.io/static/img/website-footer_clean.svg');
|
||||
background-repeat: repeat-x;
|
||||
height: @sticky-footer-height;
|
||||
}
|
||||
|
||||
.main-row {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 16px 18px inherit;
|
||||
}
|
||||
|
||||
/* =======================
|
||||
Social footer
|
||||
======================= */
|
||||
|
||||
.social {
|
||||
margin-left: 0px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.social .twitter, .social .github, .social .googleplus {
|
||||
background: url("../img/footer-links.png") no-repeat transparent;
|
||||
background: url("https://www.docker.io/static/img/footer-links.png") no-repeat transparent;
|
||||
display: inline-block;
|
||||
height: 35px;
|
||||
overflow: hidden;
|
||||
@@ -387,107 +348,219 @@ section.header {
|
||||
background-position: -59px 2px;
|
||||
}
|
||||
|
||||
// Styles on the forms
|
||||
// ----------------------------------
|
||||
|
||||
#fork-us {
|
||||
/*font-family: 'Maven Pro';*/
|
||||
/*font-weight: bold;*/
|
||||
font-size: 12px;
|
||||
/*text-transform: uppercase;*/
|
||||
display: block;
|
||||
padding: 0px 1em;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
background-color: #43484c;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFF6E56', endColorstr='#FFED4F35');
|
||||
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #747474), color-stop(100%, #43484c));
|
||||
background-image: -webkit-linear-gradient(top, #747474 0%, #43484c 100%);
|
||||
background-image: -moz-linear-gradient(top, #747474 0%, #43484c 100%);
|
||||
background-image: -o-linear-gradient(top, #747474 0%, #43484c 100%);
|
||||
background-image: linear-gradient(top, #747474 0%, #43484c 100%);
|
||||
border: 1px solid #43484c;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
-ms-border-radius: 4px;
|
||||
-o-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: inset rgba(255, 255, 255, 0.17) 0 1px 1px;
|
||||
-moz-box-shadow: inset rgba(255, 255, 255, 0.17) 0 1px 1px;
|
||||
box-shadow: inset rgba(255, 255, 255, 0.17) 0 1px 1px;
|
||||
margin: 8px;
|
||||
|
||||
a {
|
||||
color: #faf2ee;
|
||||
text-shadow: rgba(0, 0, 0, 0.3) 0px 1px 0px;
|
||||
form table {
|
||||
th {
|
||||
vertical-align: top;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
.labeldd label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.helptext {
|
||||
font-size: @font-size-small;
|
||||
margin-top: -4px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.fielddd input {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: @deep-red;
|
||||
}
|
||||
|
||||
[type=submit] {
|
||||
// margin-top: -8px;
|
||||
}
|
||||
}
|
||||
|
||||
div.alert.alert-block {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
/* ======================= =======================
|
||||
Documentation
|
||||
========================= ========================= */
|
||||
|
||||
|
||||
/* =======================
|
||||
Media size overrides
|
||||
======================= */
|
||||
Styles for the sidebar
|
||||
========================= */
|
||||
|
||||
/* Large desktop */
|
||||
@media (min-width: 1200px) {
|
||||
.span6.with-padding {
|
||||
background-color: @red;
|
||||
|
||||
width: (@gridColumnWidth1200 * 6) + (@gridGutterWidth1200 * 5) - @gridGutterWidth1200;
|
||||
padding: @gridGutterWidth1200/2;
|
||||
@sidebar-navigation-border: 1.5px solid #595959;
|
||||
@sidebar-navigation-width: 225px;
|
||||
|
||||
|
||||
.page-title {
|
||||
// border-bottom: 1px solid #bbbbbb;
|
||||
background-color: white;
|
||||
border: 1px solid transparent;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
h4 {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Normal desktop */
|
||||
@media (min-width: 980px) and (max-width: 1199px) {
|
||||
.span6.with-padding {
|
||||
background-color: @lightred;
|
||||
.bs-docs-sidebar {
|
||||
padding-left: 5px;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
margin-top: 18px;
|
||||
|
||||
width: (@gridColumnWidth * 6) + (@gridGutterWidth * 5) - @gridGutterWidth;
|
||||
padding: @gridGutterWidth/2;
|
||||
ul {
|
||||
list-style: none;
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Portrait tablet to landscape and desktop */
|
||||
@media (min-width: 768px) and (max-width: 979px) {
|
||||
body {
|
||||
padding-top: 0px;
|
||||
.toctree-l2 > ul {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
ul > li {
|
||||
&.toctree-l1.has-children {
|
||||
background-image: url('../img/menu_arrow_right.gif');
|
||||
background-repeat: no-repeat;
|
||||
background-position: 13px 13px;
|
||||
list-style-type: none;
|
||||
// margin-left: px;
|
||||
padding: 0px 0px 0px 0px;
|
||||
vertical-align: middle;
|
||||
|
||||
.span6.with-padding {
|
||||
background-color: @darkblue;
|
||||
&.open {
|
||||
background-image: url('../img/menu_arrow_down.gif');
|
||||
}
|
||||
}
|
||||
|
||||
width: (@gridColumnWidth768 * 6) + (@gridGutterWidth768 * 5) - @gridGutterWidth768;
|
||||
padding: @gridGutterWidth768/2;
|
||||
& > a {
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
width: 100%;
|
||||
display:inline-block;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 35px;
|
||||
padding-right: 20px;
|
||||
font-size: @font-size-base;
|
||||
border-bottom: @sidebar-navigation-border;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
&:first-child.active > a {
|
||||
border-top: @sidebar-navigation-border;
|
||||
}
|
||||
|
||||
&:last-child > a {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:last-child.active > a {
|
||||
border-bottom: @sidebar-navigation-border;
|
||||
}
|
||||
|
||||
&.active > a {
|
||||
border-right: @sidebar-navigation-border;
|
||||
border-left: @sidebar-navigation-border;
|
||||
color: @very-dark-sea-green;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Landscape phone to portrait tablet */
|
||||
@media (max-width: 767px) {
|
||||
body {
|
||||
padding-top: 0px;
|
||||
&.toctree-l3 ul {
|
||||
display: inherit;
|
||||
|
||||
margin-left: 15px;
|
||||
font-size: smaller;
|
||||
}
|
||||
#global {
|
||||
/* TODO: Fix this to be relative to the navigation size */
|
||||
// padding-top: 600px;
|
||||
|
||||
.toctree-l3 a {
|
||||
border: none;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
}
|
||||
#fork-us {
|
||||
|
||||
ul > li > ul {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Landscape phones and down */
|
||||
@media (max-width: 480px) {
|
||||
#nav-gettingstarted {
|
||||
display: none;
|
||||
ul > li.current > ul {
|
||||
display: inline-block;
|
||||
padding-left: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Misc fixes */
|
||||
table th {
|
||||
text-align: left;
|
||||
.toctree-l2 {
|
||||
&.current > a {
|
||||
font-weight: bold;
|
||||
}
|
||||
&.current {
|
||||
border: 1.5px solid #595959;
|
||||
color: #394d54;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* =====================================
|
||||
Styles for the floating version widget
|
||||
====================================== */
|
||||
|
||||
.version-flyer {
|
||||
position: fixed;
|
||||
float: right;
|
||||
right: 0;
|
||||
bottom: 40px;
|
||||
background-color: #E0E0E0;
|
||||
border: 1px solid #88BABC;
|
||||
padding: 5px;
|
||||
font-size: larger;
|
||||
|
||||
.content {
|
||||
padding-right: 45px;
|
||||
margin-top: 7px;
|
||||
margin-left: 7px;
|
||||
// display: inline-block;
|
||||
background-image: url('../img/container3.png');
|
||||
background-position: right center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.alternative {
|
||||
visibility: hidden;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.active-slug {
|
||||
visibility: visible;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&:hover .alternative {
|
||||
animation-duration: 1s;
|
||||
display: inline-block;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* =====================================
|
||||
Styles for
|
||||
====================================== */
|
||||
|
||||
h1:hover > a.headerlink,
|
||||
h2:hover > a.headerlink,
|
||||
h3:hover > a.headerlink,
|
||||
@@ -504,4 +577,38 @@ dt:hover > a.headerlink {
|
||||
font-weight: bold;
|
||||
float: right;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/* =====================================
|
||||
Miscellaneous information
|
||||
====================================== */
|
||||
|
||||
.admonition {
|
||||
&.warning, &.note, &.seealso, &.todo {
|
||||
border: 3px solid black;
|
||||
padding: 10px;
|
||||
margin: 5px auto 10px;
|
||||
}
|
||||
|
||||
.admonition-title {
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
&.warning, &.danger {
|
||||
border-color: #ac0004;
|
||||
}
|
||||
|
||||
&.note {
|
||||
border-color: #cbc200;
|
||||
}
|
||||
|
||||
&.todo {
|
||||
border-color: orange;
|
||||
}
|
||||
|
||||
&.seealso {
|
||||
border-color: #23cb1f;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -9,128 +9,175 @@
|
||||
|
||||
// Grays
|
||||
// -------------------------
|
||||
@black: #000;
|
||||
@grayDarker: #222;
|
||||
@grayDark: #333;
|
||||
@gray: #555;
|
||||
@grayLight: #999;
|
||||
@grayLighter: #eee;
|
||||
@white: #fff;
|
||||
|
||||
@gray-darker: lighten(#000, 13.5%); // #222
|
||||
@gray-dark: lighten(#000, 20%); // #333
|
||||
@gray: lighten(#000, 33.5%); // #555
|
||||
@gray-light: lighten(#000, 60%); // #999
|
||||
@gray-lighter: lighten(#000, 93.5%); // #eee
|
||||
|
||||
// Accent colors
|
||||
// Brand colors
|
||||
// -------------------------
|
||||
@blue: #049cdb;
|
||||
@blueDark: #0064cd;
|
||||
@green: #46a546;
|
||||
@red: #9d261d;
|
||||
@yellow: #ffc40d;
|
||||
@orange: #f89406;
|
||||
@pink: #c3325f;
|
||||
@purple: #7a43b6;
|
||||
|
||||
@brand-primary: #428bca;
|
||||
@brand-success: #5cb85c;
|
||||
@brand-warning: #f0ad4e;
|
||||
@brand-danger: #d9534f;
|
||||
@brand-info: #5bc0de;
|
||||
|
||||
// Scaffolding
|
||||
// -------------------------
|
||||
@bodyBackground: @white;
|
||||
@textColor: @grayDark;
|
||||
|
||||
@body-bg: #fff;
|
||||
@text-color: @gray-dark;
|
||||
|
||||
// Links
|
||||
// -------------------------
|
||||
@linkColor: #08c;
|
||||
@linkColorHover: darken(@linkColor, 15%);
|
||||
|
||||
@link-color: @brand-primary;
|
||||
@link-hover-color: darken(@link-color, 15%);
|
||||
|
||||
// Typography
|
||||
// -------------------------
|
||||
@sansFontFamily: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
@serifFontFamily: Georgia, "Times New Roman", Times, serif;
|
||||
@monoFontFamily: Monaco, Menlo, Consolas, "Courier New", monospace;
|
||||
|
||||
@baseFontSize: 14px;
|
||||
@baseFontFamily: @sansFontFamily;
|
||||
@baseLineHeight: 20px;
|
||||
@altFontFamily: @serifFontFamily;
|
||||
@font-family-sans-serif: "Cabin", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
@font-family-serif: Georgia, "Times New Roman", Times, serif;
|
||||
@font-family-monospace: Monaco, Menlo, Consolas, "Courier New", monospace;
|
||||
@font-family-base: @font-family-sans-serif;
|
||||
|
||||
@headingsFontFamily: inherit; // empty to use BS default, @baseFontFamily
|
||||
@headingsFontWeight: bold; // instead of browser default, bold
|
||||
@headingsColor: inherit; // empty to use BS default, @textColor
|
||||
@font-size-base: 14px;
|
||||
@font-size-large: ceil(@font-size-base * 1.25); // ~18px
|
||||
@font-size-small: ceil(@font-size-base * 0.85); // ~12px
|
||||
|
||||
@line-height-base: 1.428571429; // 20/14
|
||||
@line-height-computed: floor(@font-size-base * @line-height-base); // ~20px
|
||||
|
||||
// Component sizing
|
||||
@headings-font-family: @font-family-base;
|
||||
@headings-font-weight: 500;
|
||||
@headings-line-height: 1.1;
|
||||
|
||||
// Iconography
|
||||
// -------------------------
|
||||
// Based on 14px font-size and 20px line-height
|
||||
|
||||
@fontSizeLarge: @baseFontSize * 1.25; // ~18px
|
||||
@fontSizeSmall: @baseFontSize * 0.85; // ~12px
|
||||
@fontSizeMini: @baseFontSize * 0.75; // ~11px
|
||||
@icon-font-path: "../fonts/";
|
||||
@icon-font-name: "glyphicons-halflings-regular";
|
||||
|
||||
@paddingLarge: 11px 19px; // 44px
|
||||
@paddingSmall: 2px 10px; // 26px
|
||||
@paddingMini: 0 6px; // 22px
|
||||
|
||||
@baseBorderRadius: 4px;
|
||||
@borderRadiusLarge: 6px;
|
||||
@borderRadiusSmall: 3px;
|
||||
// Components
|
||||
// -------------------------
|
||||
// Based on 14px font-size and 1.428 line-height (~20px to start)
|
||||
|
||||
@padding-base-vertical: 6px;
|
||||
@padding-base-horizontal: 12px;
|
||||
|
||||
@padding-large-vertical: 10px;
|
||||
@padding-large-horizontal: 16px;
|
||||
|
||||
@padding-small-vertical: 5px;
|
||||
@padding-small-horizontal: 10px;
|
||||
|
||||
@line-height-large: 1.33;
|
||||
@line-height-small: 1.5;
|
||||
|
||||
@border-radius-base: 4px;
|
||||
@border-radius-large: 6px;
|
||||
@border-radius-small: 3px;
|
||||
|
||||
@component-active-bg: @brand-primary;
|
||||
|
||||
@caret-width-base: 4px;
|
||||
@caret-width-large: 5px;
|
||||
|
||||
// Tables
|
||||
// -------------------------
|
||||
@tableBackground: transparent; // overall background-color
|
||||
@tableBackgroundAccent: #f9f9f9; // for striping
|
||||
@tableBackgroundHover: #f5f5f5; // for hover
|
||||
@tableBorder: #ddd; // table and cell border
|
||||
|
||||
@table-cell-padding: 8px;
|
||||
@table-condensed-cell-padding: 5px;
|
||||
|
||||
@table-bg: transparent; // overall background-color
|
||||
@table-bg-accent: #f9f9f9; // for striping
|
||||
@table-bg-hover: #f5f5f5;
|
||||
@table-bg-active: @table-bg-hover;
|
||||
|
||||
@table-border-color: #ddd; // table and cell border
|
||||
|
||||
|
||||
// Buttons
|
||||
// -------------------------
|
||||
@btnBackground: @white;
|
||||
@btnBackgroundHighlight: darken(@white, 10%);
|
||||
@btnBorder: #ccc;
|
||||
|
||||
@btnPrimaryBackground: @linkColor;
|
||||
@btnPrimaryBackgroundHighlight: spin(@btnPrimaryBackground, 20%);
|
||||
@btn-font-weight: normal;
|
||||
|
||||
@btnInfoBackground: #5bc0de;
|
||||
@btnInfoBackgroundHighlight: #2f96b4;
|
||||
@btn-default-color: #333;
|
||||
@btn-default-bg: #fff;
|
||||
@btn-default-border: #ccc;
|
||||
|
||||
@btnSuccessBackground: #62c462;
|
||||
@btnSuccessBackgroundHighlight: #51a351;
|
||||
@btn-primary-color: #fff;
|
||||
@btn-primary-bg: @brand-primary;
|
||||
@btn-primary-border: darken(@btn-primary-bg, 5%);
|
||||
|
||||
@btnWarningBackground: lighten(@orange, 15%);
|
||||
@btnWarningBackgroundHighlight: @orange;
|
||||
@btn-success-color: #fff;
|
||||
@btn-success-bg: @brand-success;
|
||||
@btn-success-border: darken(@btn-success-bg, 5%);
|
||||
|
||||
@btnDangerBackground: #ee5f5b;
|
||||
@btnDangerBackgroundHighlight: #bd362f;
|
||||
@btn-warning-color: #fff;
|
||||
@btn-warning-bg: @brand-warning;
|
||||
@btn-warning-border: darken(@btn-warning-bg, 5%);
|
||||
|
||||
@btnInverseBackground: #444;
|
||||
@btnInverseBackgroundHighlight: @grayDarker;
|
||||
@btn-danger-color: #fff;
|
||||
@btn-danger-bg: @brand-danger;
|
||||
@btn-danger-border: darken(@btn-danger-bg, 5%);
|
||||
|
||||
@btn-info-color: #fff;
|
||||
@btn-info-bg: @brand-info;
|
||||
@btn-info-border: darken(@btn-info-bg, 5%);
|
||||
|
||||
@btn-link-disabled-color: @gray-light;
|
||||
|
||||
|
||||
// Forms
|
||||
// -------------------------
|
||||
@inputBackground: @white;
|
||||
@inputBorder: #ccc;
|
||||
@inputBorderRadius: @baseBorderRadius;
|
||||
@inputDisabledBackground: @grayLighter;
|
||||
@formActionsBackground: #f5f5f5;
|
||||
@inputHeight: @baseLineHeight + 10px; // base line-height + 8px vertical padding + 2px top/bottom border
|
||||
|
||||
@input-bg: #fff;
|
||||
@input-bg-disabled: @gray-lighter;
|
||||
|
||||
@input-color: @gray;
|
||||
@input-border: #ccc;
|
||||
@input-border-radius: @border-radius-base;
|
||||
@input-border-focus: #66afe9;
|
||||
|
||||
@input-color-placeholder: @gray-light;
|
||||
|
||||
@input-height-base: (@line-height-computed + (@padding-base-vertical * 2) + 2);
|
||||
@input-height-large: (floor(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2);
|
||||
@input-height-small: (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2);
|
||||
|
||||
@legend-color: @gray-dark;
|
||||
@legend-border-color: #e5e5e5;
|
||||
|
||||
@input-group-addon-bg: @gray-lighter;
|
||||
@input-group-addon-border-color: @input-border;
|
||||
|
||||
|
||||
// Dropdowns
|
||||
// -------------------------
|
||||
@dropdownBackground: @white;
|
||||
@dropdownBorder: rgba(0,0,0,.2);
|
||||
@dropdownDividerTop: #e5e5e5;
|
||||
@dropdownDividerBottom: @white;
|
||||
|
||||
@dropdownLinkColor: @grayDark;
|
||||
@dropdownLinkColorHover: @white;
|
||||
@dropdownLinkColorActive: @white;
|
||||
@dropdown-bg: #fff;
|
||||
@dropdown-border: rgba(0,0,0,.15);
|
||||
@dropdown-fallback-border: #ccc;
|
||||
@dropdown-divider-bg: #e5e5e5;
|
||||
|
||||
@dropdownLinkBackgroundActive: @linkColor;
|
||||
@dropdownLinkBackgroundHover: @dropdownLinkBackgroundActive;
|
||||
@dropdown-link-active-color: #fff;
|
||||
@dropdown-link-active-bg: @component-active-bg;
|
||||
|
||||
@dropdown-link-color: @gray-dark;
|
||||
@dropdown-link-hover-color: #fff;
|
||||
@dropdown-link-hover-bg: @dropdown-link-active-bg;
|
||||
|
||||
@dropdown-link-disabled-color: @gray-light;
|
||||
|
||||
@dropdown-header-color: @gray-light;
|
||||
|
||||
@dropdown-caret-color: #000;
|
||||
|
||||
|
||||
// COMPONENT VARIABLES
|
||||
@@ -141,161 +188,435 @@
|
||||
// -------------------------
|
||||
// Used for a bird's eye view of components dependent on the z-axis
|
||||
// Try to avoid customizing these :)
|
||||
@zindexDropdown: 1000;
|
||||
@zindexPopover: 1010;
|
||||
@zindexTooltip: 1030;
|
||||
@zindexFixedNavbar: 1030;
|
||||
@zindexModalBackdrop: 1040;
|
||||
@zindexModal: 1050;
|
||||
|
||||
@zindex-navbar: 1000;
|
||||
@zindex-dropdown: 1000;
|
||||
@zindex-popover: 1010;
|
||||
@zindex-tooltip: 1030;
|
||||
@zindex-navbar-fixed: 1030;
|
||||
@zindex-modal-background: 1040;
|
||||
@zindex-modal: 1050;
|
||||
|
||||
// Media queries breakpoints
|
||||
// --------------------------------------------------
|
||||
|
||||
// Extra small screen / phone
|
||||
@screen-xs: 480px;
|
||||
@screen-phone: @screen-xs;
|
||||
|
||||
// Small screen / tablet
|
||||
@screen-sm: 768px;
|
||||
@screen-tablet: @screen-sm;
|
||||
|
||||
// Medium screen / desktop
|
||||
@screen-md: 992px;
|
||||
@screen-desktop: @screen-md;
|
||||
|
||||
// Large screen / wide desktop
|
||||
@screen-lg: 1600px;
|
||||
@screen-lg-desktop: @screen-lg;
|
||||
|
||||
// So media queries don't overlap when required, provide a maximum
|
||||
@screen-xs-max: (@screen-sm - 1);
|
||||
@screen-sm-max: (@screen-md - 1);
|
||||
@screen-md-max: (@screen-lg - 1);
|
||||
|
||||
|
||||
// Sprite icons path
|
||||
// -------------------------
|
||||
@iconSpritePath: "../img/glyphicons-halflings.png";
|
||||
@iconWhiteSpritePath: "../img/glyphicons-halflings-white.png";
|
||||
// Grid system
|
||||
// --------------------------------------------------
|
||||
|
||||
|
||||
// Input placeholder text color
|
||||
// -------------------------
|
||||
@placeholderText: @grayLight;
|
||||
|
||||
|
||||
// Hr border color
|
||||
// -------------------------
|
||||
@hrBorder: @grayLighter;
|
||||
|
||||
|
||||
// Horizontal forms & lists
|
||||
// -------------------------
|
||||
@horizontalComponentOffset: 180px;
|
||||
|
||||
|
||||
// Wells
|
||||
// -------------------------
|
||||
@wellBackground: #f5f5f5;
|
||||
// Number of columns in the grid system
|
||||
@grid-columns: 12;
|
||||
// Padding, to be divided by two and applied to the left and right of all columns
|
||||
@grid-gutter-width: 30px;
|
||||
// Point at which the navbar stops collapsing
|
||||
@grid-float-breakpoint: @screen-desktop;
|
||||
|
||||
|
||||
// Navbar
|
||||
// -------------------------
|
||||
@navbarCollapseWidth: 979px;
|
||||
@navbarCollapseDesktopWidth: @navbarCollapseWidth + 1;
|
||||
|
||||
@navbarHeight: 40px;
|
||||
@navbarBackgroundHighlight: #ffffff;
|
||||
@navbarBackground: darken(@navbarBackgroundHighlight, 5%);
|
||||
@navbarBorder: darken(@navbarBackground, 12%);
|
||||
|
||||
@navbarText: #777;
|
||||
@navbarLinkColor: #777;
|
||||
@navbarLinkColorHover: @grayDark;
|
||||
@navbarLinkColorActive: @gray;
|
||||
@navbarLinkBackgroundHover: transparent;
|
||||
@navbarLinkBackgroundActive: darken(@navbarBackground, 5%);
|
||||
// Basics of a navbar
|
||||
@navbar-height: 50px;
|
||||
@navbar-margin-bottom: @line-height-computed;
|
||||
@navbar-default-color: #777;
|
||||
@navbar-default-bg: #f8f8f8;
|
||||
@navbar-default-border: darken(@navbar-default-bg, 6.5%);
|
||||
@navbar-border-radius: @border-radius-base;
|
||||
@navbar-padding-horizontal: floor(@grid-gutter-width / 2);
|
||||
@navbar-padding-vertical: ((@navbar-height - @line-height-computed) / 2);
|
||||
|
||||
// Navbar links
|
||||
@navbar-default-link-color: #777;
|
||||
@navbar-default-link-hover-color: #333;
|
||||
@navbar-default-link-hover-bg: transparent;
|
||||
@navbar-default-link-active-color: #555;
|
||||
@navbar-default-link-active-bg: darken(@navbar-default-bg, 6.5%);
|
||||
@navbar-default-link-disabled-color: #ccc;
|
||||
@navbar-default-link-disabled-bg: transparent;
|
||||
|
||||
// Navbar brand label
|
||||
@navbar-default-brand-color: @navbar-default-link-color;
|
||||
@navbar-default-brand-hover-color: darken(@navbar-default-link-color, 10%);
|
||||
@navbar-default-brand-hover-bg: transparent;
|
||||
|
||||
// Navbar toggle
|
||||
@navbar-default-toggle-hover-bg: #ddd;
|
||||
@navbar-default-toggle-icon-bar-bg: #ccc;
|
||||
@navbar-default-toggle-border-color: #ddd;
|
||||
|
||||
@navbarBrandColor: @navbarLinkColor;
|
||||
|
||||
// Inverted navbar
|
||||
@navbarInverseBackground: #111111;
|
||||
@navbarInverseBackgroundHighlight: #222222;
|
||||
@navbarInverseBorder: #252525;
|
||||
//
|
||||
// Reset inverted navbar basics
|
||||
@navbar-inverse-color: @gray-light;
|
||||
@navbar-inverse-bg: #222;
|
||||
@navbar-inverse-border: darken(@navbar-inverse-bg, 10%);
|
||||
|
||||
@navbarInverseText: @grayLight;
|
||||
@navbarInverseLinkColor: @grayLight;
|
||||
@navbarInverseLinkColorHover: @white;
|
||||
@navbarInverseLinkColorActive: @navbarInverseLinkColorHover;
|
||||
@navbarInverseLinkBackgroundHover: transparent;
|
||||
@navbarInverseLinkBackgroundActive: @navbarInverseBackground;
|
||||
// Inverted navbar links
|
||||
@navbar-inverse-link-color: @gray-light;
|
||||
@navbar-inverse-link-hover-color: #fff;
|
||||
@navbar-inverse-link-hover-bg: transparent;
|
||||
@navbar-inverse-link-active-color: @navbar-inverse-link-hover-color;
|
||||
@navbar-inverse-link-active-bg: darken(@navbar-inverse-bg, 10%);
|
||||
@navbar-inverse-link-disabled-color: #444;
|
||||
@navbar-inverse-link-disabled-bg: transparent;
|
||||
|
||||
@navbarInverseSearchBackground: lighten(@navbarInverseBackground, 25%);
|
||||
@navbarInverseSearchBackgroundFocus: @white;
|
||||
@navbarInverseSearchBorder: @navbarInverseBackground;
|
||||
@navbarInverseSearchPlaceholderColor: #ccc;
|
||||
// Inverted navbar brand label
|
||||
@navbar-inverse-brand-color: @navbar-inverse-link-color;
|
||||
@navbar-inverse-brand-hover-color: #fff;
|
||||
@navbar-inverse-brand-hover-bg: transparent;
|
||||
|
||||
@navbarInverseBrandColor: @navbarInverseLinkColor;
|
||||
// Inverted navbar search
|
||||
// Normal navbar needs no special styles or vars
|
||||
@navbar-inverse-search-bg: lighten(@navbar-inverse-bg, 25%);
|
||||
@navbar-inverse-search-bg-focus: #fff;
|
||||
@navbar-inverse-search-border: @navbar-inverse-bg;
|
||||
@navbar-inverse-search-placeholder-color: #ccc;
|
||||
|
||||
// Inverted navbar toggle
|
||||
@navbar-inverse-toggle-hover-bg: #333;
|
||||
@navbar-inverse-toggle-icon-bar-bg: #fff;
|
||||
@navbar-inverse-toggle-border-color: #333;
|
||||
|
||||
|
||||
// Navs
|
||||
// -------------------------
|
||||
|
||||
@nav-link-padding: 10px 15px;
|
||||
@nav-link-hover-bg: @gray-lighter;
|
||||
|
||||
@nav-disabled-link-color: @gray-light;
|
||||
@nav-disabled-link-hover-color: @gray-light;
|
||||
|
||||
@nav-open-link-hover-color: #fff;
|
||||
@nav-open-caret-border-color: #fff;
|
||||
|
||||
// Tabs
|
||||
@nav-tabs-border-color: #ddd;
|
||||
|
||||
@nav-tabs-link-hover-border-color: @gray-lighter;
|
||||
|
||||
@nav-tabs-active-link-hover-bg: @body-bg;
|
||||
@nav-tabs-active-link-hover-color: @gray;
|
||||
@nav-tabs-active-link-hover-border-color: #ddd;
|
||||
|
||||
@nav-tabs-justified-link-border-color: #ddd;
|
||||
@nav-tabs-justified-active-link-border-color: @body-bg;
|
||||
|
||||
// Pills
|
||||
@nav-pills-active-link-hover-bg: @component-active-bg;
|
||||
@nav-pills-active-link-hover-color: #fff;
|
||||
|
||||
|
||||
// Pagination
|
||||
// -------------------------
|
||||
@paginationBackground: #fff;
|
||||
@paginationBorder: #ddd;
|
||||
@paginationActiveBackground: #f5f5f5;
|
||||
|
||||
@pagination-bg: #fff;
|
||||
@pagination-border: #ddd;
|
||||
|
||||
@pagination-hover-bg: @gray-lighter;
|
||||
|
||||
@pagination-active-bg: @brand-primary;
|
||||
@pagination-active-color: #fff;
|
||||
|
||||
@pagination-disabled-color: @gray-light;
|
||||
|
||||
|
||||
// Hero unit
|
||||
// Pager
|
||||
// -------------------------
|
||||
@heroUnitBackground: @grayLighter;
|
||||
@heroUnitHeadingColor: inherit;
|
||||
@heroUnitLeadColor: inherit;
|
||||
|
||||
@pager-border-radius: 15px;
|
||||
@pager-disabled-color: @gray-light;
|
||||
|
||||
|
||||
// Jumbotron
|
||||
// -------------------------
|
||||
|
||||
@jumbotron-padding: 30px;
|
||||
@jumbotron-color: inherit;
|
||||
@jumbotron-bg: @gray-lighter;
|
||||
|
||||
@jumbotron-heading-color: inherit;
|
||||
|
||||
|
||||
// Form states and alerts
|
||||
// -------------------------
|
||||
@warningText: #c09853;
|
||||
@warningBackground: #fcf8e3;
|
||||
@warningBorder: darken(spin(@warningBackground, -10), 3%);
|
||||
|
||||
@errorText: #b94a48;
|
||||
@errorBackground: #f2dede;
|
||||
@errorBorder: darken(spin(@errorBackground, -10), 3%);
|
||||
@state-warning-text: #c09853;
|
||||
@state-warning-bg: #fcf8e3;
|
||||
@state-warning-border: darken(spin(@state-warning-bg, -10), 3%);
|
||||
|
||||
@successText: #468847;
|
||||
@successBackground: #dff0d8;
|
||||
@successBorder: darken(spin(@successBackground, -10), 5%);
|
||||
@state-danger-text: #b94a48;
|
||||
@state-danger-bg: #f2dede;
|
||||
@state-danger-border: darken(spin(@state-danger-bg, -10), 3%);
|
||||
|
||||
@infoText: #3a87ad;
|
||||
@infoBackground: #d9edf7;
|
||||
@infoBorder: darken(spin(@infoBackground, -10), 7%);
|
||||
@state-success-text: #468847;
|
||||
@state-success-bg: #dff0d8;
|
||||
@state-success-border: darken(spin(@state-success-bg, -10), 5%);
|
||||
|
||||
@state-info-text: #3a87ad;
|
||||
@state-info-bg: #d9edf7;
|
||||
@state-info-border: darken(spin(@state-info-bg, -10), 7%);
|
||||
|
||||
|
||||
// Tooltips and popovers
|
||||
// Tooltips
|
||||
// -------------------------
|
||||
@tooltipColor: #fff;
|
||||
@tooltipBackground: #000;
|
||||
@tooltipArrowWidth: 5px;
|
||||
@tooltipArrowColor: @tooltipBackground;
|
||||
@tooltip-max-width: 200px;
|
||||
@tooltip-color: #fff;
|
||||
@tooltip-bg: #000;
|
||||
|
||||
@popoverBackground: #fff;
|
||||
@popoverArrowWidth: 10px;
|
||||
@popoverArrowColor: #fff;
|
||||
@popoverTitleBackground: darken(@popoverBackground, 3%);
|
||||
|
||||
// Special enhancement for popovers
|
||||
@popoverArrowOuterWidth: @popoverArrowWidth + 1;
|
||||
@popoverArrowOuterColor: rgba(0,0,0,.25);
|
||||
@tooltip-arrow-width: 5px;
|
||||
@tooltip-arrow-color: @tooltip-bg;
|
||||
|
||||
|
||||
// Popovers
|
||||
// -------------------------
|
||||
@popover-bg: #fff;
|
||||
@popover-max-width: 276px;
|
||||
@popover-border-color: rgba(0,0,0,.2);
|
||||
@popover-fallback-border-color: #ccc;
|
||||
|
||||
// GRID
|
||||
@popover-title-bg: darken(@popover-bg, 3%);
|
||||
|
||||
@popover-arrow-width: 10px;
|
||||
@popover-arrow-color: #fff;
|
||||
|
||||
@popover-arrow-outer-width: (@popover-arrow-width + 1);
|
||||
@popover-arrow-outer-color: rgba(0,0,0,.25);
|
||||
@popover-arrow-outer-fallback-color: #999;
|
||||
|
||||
|
||||
// Labels
|
||||
// -------------------------
|
||||
|
||||
@label-default-bg: @gray-light;
|
||||
@label-primary-bg: @brand-primary;
|
||||
@label-success-bg: @brand-success;
|
||||
@label-info-bg: @brand-info;
|
||||
@label-warning-bg: @brand-warning;
|
||||
@label-danger-bg: @brand-danger;
|
||||
|
||||
@label-color: #fff;
|
||||
@label-link-hover-color: #fff;
|
||||
|
||||
|
||||
// Modals
|
||||
// -------------------------
|
||||
@modal-inner-padding: 20px;
|
||||
|
||||
@modal-title-padding: 15px;
|
||||
@modal-title-line-height: @line-height-base;
|
||||
|
||||
@modal-content-bg: #fff;
|
||||
@modal-content-border-color: rgba(0,0,0,.2);
|
||||
@modal-content-fallback-border-color: #999;
|
||||
|
||||
@modal-backdrop-bg: #000;
|
||||
@modal-header-border-color: #e5e5e5;
|
||||
@modal-footer-border-color: @modal-header-border-color;
|
||||
|
||||
|
||||
// Alerts
|
||||
// -------------------------
|
||||
@alert-padding: 15px;
|
||||
@alert-border-radius: @border-radius-base;
|
||||
@alert-link-font-weight: bold;
|
||||
|
||||
@alert-success-bg: @state-success-bg;
|
||||
@alert-success-text: @state-success-text;
|
||||
@alert-success-border: @state-success-border;
|
||||
|
||||
@alert-info-bg: @state-info-bg;
|
||||
@alert-info-text: @state-info-text;
|
||||
@alert-info-border: @state-info-border;
|
||||
|
||||
@alert-warning-bg: @state-warning-bg;
|
||||
@alert-warning-text: @state-warning-text;
|
||||
@alert-warning-border: @state-warning-border;
|
||||
|
||||
@alert-danger-bg: @state-danger-bg;
|
||||
@alert-danger-text: @state-danger-text;
|
||||
@alert-danger-border: @state-danger-border;
|
||||
|
||||
|
||||
// Progress bars
|
||||
// -------------------------
|
||||
@progress-bg: #f5f5f5;
|
||||
@progress-bar-color: #fff;
|
||||
|
||||
@progress-bar-bg: @brand-primary;
|
||||
@progress-bar-success-bg: @brand-success;
|
||||
@progress-bar-warning-bg: @brand-warning;
|
||||
@progress-bar-danger-bg: @brand-danger;
|
||||
@progress-bar-info-bg: @brand-info;
|
||||
|
||||
|
||||
// List group
|
||||
// -------------------------
|
||||
@list-group-bg: #fff;
|
||||
@list-group-border: #ddd;
|
||||
@list-group-border-radius: @border-radius-base;
|
||||
|
||||
@list-group-hover-bg: #f5f5f5;
|
||||
@list-group-active-color: #fff;
|
||||
@list-group-active-bg: @component-active-bg;
|
||||
@list-group-active-border: @list-group-active-bg;
|
||||
|
||||
@list-group-link-color: #555;
|
||||
@list-group-link-heading-color: #333;
|
||||
|
||||
|
||||
// Panels
|
||||
// -------------------------
|
||||
@panel-bg: #fff;
|
||||
@panel-inner-border: #ddd;
|
||||
@panel-border-radius: @border-radius-base;
|
||||
@panel-footer-bg: #f5f5f5;
|
||||
|
||||
@panel-default-text: @gray-dark;
|
||||
@panel-default-border: #ddd;
|
||||
@panel-default-heading-bg: #f5f5f5;
|
||||
|
||||
@panel-primary-text: #fff;
|
||||
@panel-primary-border: @brand-primary;
|
||||
@panel-primary-heading-bg: @brand-primary;
|
||||
|
||||
@panel-success-text: @state-success-text;
|
||||
@panel-success-border: @state-success-border;
|
||||
@panel-success-heading-bg: @state-success-bg;
|
||||
|
||||
@panel-warning-text: @state-warning-text;
|
||||
@panel-warning-border: @state-warning-border;
|
||||
@panel-warning-heading-bg: @state-warning-bg;
|
||||
|
||||
@panel-danger-text: @state-danger-text;
|
||||
@panel-danger-border: @state-danger-border;
|
||||
@panel-danger-heading-bg: @state-danger-bg;
|
||||
|
||||
@panel-info-text: @state-info-text;
|
||||
@panel-info-border: @state-info-border;
|
||||
@panel-info-heading-bg: @state-info-bg;
|
||||
|
||||
|
||||
// Thumbnails
|
||||
// -------------------------
|
||||
@thumbnail-padding: 4px;
|
||||
@thumbnail-bg: @body-bg;
|
||||
@thumbnail-border: #ddd;
|
||||
@thumbnail-border-radius: @border-radius-base;
|
||||
|
||||
@thumbnail-caption-color: @text-color;
|
||||
@thumbnail-caption-padding: 9px;
|
||||
|
||||
|
||||
// Wells
|
||||
// -------------------------
|
||||
@well-bg: #f5f5f5;
|
||||
|
||||
|
||||
// Badges
|
||||
// -------------------------
|
||||
@badge-color: #fff;
|
||||
@badge-link-hover-color: #fff;
|
||||
@badge-bg: @gray-light;
|
||||
|
||||
@badge-active-color: @link-color;
|
||||
@badge-active-bg: #fff;
|
||||
|
||||
@badge-font-weight: bold;
|
||||
@badge-line-height: 1;
|
||||
@badge-border-radius: 10px;
|
||||
|
||||
|
||||
// Breadcrumbs
|
||||
// -------------------------
|
||||
@breadcrumb-bg: #f5f5f5;
|
||||
@breadcrumb-color: #ccc;
|
||||
@breadcrumb-active-color: @gray-light;
|
||||
|
||||
|
||||
// Carousel
|
||||
// ------------------------
|
||||
|
||||
@carousel-text-shadow: 0 1px 2px rgba(0,0,0,.6);
|
||||
|
||||
@carousel-control-color: #fff;
|
||||
@carousel-control-width: 15%;
|
||||
@carousel-control-opacity: .5;
|
||||
@carousel-control-font-size: 20px;
|
||||
|
||||
@carousel-indicator-active-bg: #fff;
|
||||
@carousel-indicator-border-color: #fff;
|
||||
|
||||
@carousel-caption-color: #fff;
|
||||
|
||||
|
||||
// Close
|
||||
// ------------------------
|
||||
@close-color: #000;
|
||||
@close-font-weight: bold;
|
||||
@close-text-shadow: 0 1px 0 #fff;
|
||||
|
||||
|
||||
// Code
|
||||
// ------------------------
|
||||
@code-color: #c7254e;
|
||||
@code-bg: #f9f2f4;
|
||||
|
||||
@pre-bg: #f5f5f5;
|
||||
@pre-color: @gray-dark;
|
||||
@pre-border-color: #ccc;
|
||||
@pre-scrollable-max-height: 340px;
|
||||
|
||||
// Type
|
||||
// ------------------------
|
||||
@text-muted: @gray-light;
|
||||
@abbr-border-color: @gray-light;
|
||||
@headings-small-color: @gray-light;
|
||||
@blockquote-small-color: @gray-light;
|
||||
@blockquote-border-color: @gray-lighter;
|
||||
@page-header-border-color: @gray-lighter;
|
||||
|
||||
// Miscellaneous
|
||||
// -------------------------
|
||||
|
||||
// Hr border color
|
||||
@hr-border: @gray-lighter;
|
||||
|
||||
// Horizontal forms & lists
|
||||
@component-offset-horizontal: 180px;
|
||||
|
||||
|
||||
// Container sizes
|
||||
// --------------------------------------------------
|
||||
|
||||
// Small screen / tablet
|
||||
@container-tablet: ((720px + @grid-gutter-width));
|
||||
|
||||
// Default 940px grid
|
||||
// -------------------------
|
||||
@gridColumns: 12;
|
||||
@gridColumnWidth: 60px;
|
||||
@gridGutterWidth: 20px;
|
||||
@gridRowWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1));
|
||||
// Medium screen / desktop
|
||||
@container-desktop: ((940px + @grid-gutter-width));
|
||||
|
||||
// 1200px min
|
||||
@gridColumnWidth1200: 70px;
|
||||
@gridGutterWidth1200: 30px;
|
||||
@gridRowWidth1200: (@gridColumns * @gridColumnWidth1200) + (@gridGutterWidth1200 * (@gridColumns - 1));
|
||||
|
||||
// 768px-979px
|
||||
@gridColumnWidth768: 42px;
|
||||
@gridGutterWidth768: 20px;
|
||||
@gridRowWidth768: (@gridColumns * @gridColumnWidth768) + (@gridGutterWidth768 * (@gridColumns - 1));
|
||||
|
||||
|
||||
// Fluid grid
|
||||
// -------------------------
|
||||
@fluidGridColumnWidth: percentage(@gridColumnWidth/@gridRowWidth);
|
||||
@fluidGridGutterWidth: percentage(@gridGutterWidth/@gridRowWidth);
|
||||
|
||||
// 1200px min
|
||||
@fluidGridColumnWidth1200: percentage(@gridColumnWidth1200/@gridRowWidth1200);
|
||||
@fluidGridGutterWidth1200: percentage(@gridGutterWidth1200/@gridRowWidth1200);
|
||||
|
||||
// 768px-979px
|
||||
@fluidGridColumnWidth768: percentage(@gridColumnWidth768/@gridRowWidth768);
|
||||
@fluidGridGutterWidth768: percentage(@gridGutterWidth768/@gridRowWidth768);
|
||||
// Large screen / wide desktop
|
||||
@container-lg-desktop: ((1140px + @grid-gutter-width));
|
||||
|
||||
|
After Width: | Height: | Size: 263 B |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
@@ -1,37 +1,98 @@
|
||||
|
||||
// This script should be included at the END of the document.
|
||||
// For the fastest loading it does not inlude $(document).ready()
|
||||
|
||||
// This Document contains a few helper functions for the documentation to display the current version,
|
||||
// collapse and expand the menu etc.
|
||||
|
||||
|
||||
// Function to make the sticky header possible
|
||||
function shiftWindow() {
|
||||
scrollBy(0, -70);
|
||||
console.log("window shifted")
|
||||
}
|
||||
|
||||
window.addEventListener("hashchange", shiftWindow);
|
||||
|
||||
function loadShift() {
|
||||
if (window.location.hash) {
|
||||
console.log("window has hash");
|
||||
shiftWindow();
|
||||
}
|
||||
}
|
||||
|
||||
$(window).load(function() {
|
||||
loadShift();
|
||||
});
|
||||
|
||||
$(function(){
|
||||
|
||||
// init multi-vers stuff
|
||||
$('.tabswitcher').each(function(i, multi_vers){
|
||||
var tabs = $('<ul></ul>');
|
||||
$(multi_vers).prepend(tabs);
|
||||
$(multi_vers).children('.tab').each(function(j, vers_content){
|
||||
vers = $(vers_content).children(':first').text();
|
||||
var id = 'multi_vers_' + '_' + i + '_' + j;
|
||||
$(vers_content).attr('id', id);
|
||||
$(tabs).append('<li><a href="#' + id + '">' + vers + '</a></li>');
|
||||
});
|
||||
// sidebar accordian-ing
|
||||
// don't apply on last object (it should be the FAQ) or the first (it should be introduction)
|
||||
|
||||
// define an array to which all opened items should be added
|
||||
var openmenus = [];
|
||||
|
||||
var elements = $('.toctree-l2');
|
||||
// for (var i = 0; i < elements.length; i += 1) { var current = $(elements[i]); current.children('ul').hide();}
|
||||
|
||||
|
||||
// set initial collapsed state
|
||||
var elements = $('.toctree-l1');
|
||||
for (var i = 0; i < elements.length; i += 1) {
|
||||
var current = $(elements[i]);
|
||||
if (current.hasClass('current')) {
|
||||
current.addClass('open');
|
||||
currentlink = current.children('a')[0].href;
|
||||
openmenus.push(currentlink);
|
||||
|
||||
// do nothing
|
||||
} else {
|
||||
// collapse children
|
||||
current.children('ul').hide();
|
||||
}
|
||||
}
|
||||
|
||||
if (doc_version == "") {
|
||||
$('.version-flyer ul').html('<li class="alternative active-slug"><a href="" title="Switch to local">Local</a></li>');
|
||||
}
|
||||
|
||||
// mark the active documentation in the version widget
|
||||
$(".version-flyer a:contains('" + doc_version + "')").parent().addClass('active-slug');
|
||||
|
||||
|
||||
// attached handler on click
|
||||
// Do not attach to first element or last (intro, faq) so that
|
||||
// first and last link directly instead of accordian
|
||||
$('.sidebar > ul > li > a').not(':last').not(':first').click(function(){
|
||||
|
||||
var index = $.inArray(this.href, openmenus)
|
||||
|
||||
if (index > -1) {
|
||||
console.log(index);
|
||||
openmenus.splice(index, 1);
|
||||
|
||||
|
||||
$(this).parent().children('ul').slideUp(200, function() {
|
||||
$(this).parent().removeClass('open'); // toggle after effect
|
||||
});
|
||||
}
|
||||
else {
|
||||
openmenus.push(this.href);
|
||||
|
||||
var current = $(this);
|
||||
|
||||
setTimeout(function() {
|
||||
// $('.sidebar > ul > li').removeClass('current');
|
||||
current.parent().addClass('current').addClass('open'); // toggle before effect
|
||||
current.parent().children('ul').hide();
|
||||
current.parent().children('ul').slideDown(200);
|
||||
}, 100);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
$( ".tabswitcher" ).tabs();
|
||||
|
||||
// sidebar acordian-ing
|
||||
// don't apply on last object (it should be the FAQ)
|
||||
$('nav > ul > li > a').not(':last').click(function(){
|
||||
if ($(this).parent().hasClass('current')) {
|
||||
$(this).parent().children('ul').slideUp(200, function() {
|
||||
$(this).parent().removeClass('current'); // toggle after effect
|
||||
});
|
||||
} else {
|
||||
$('nav > ul > li > ul').slideUp(100);
|
||||
var current = $(this);
|
||||
setTimeout(function() {
|
||||
$('nav > ul > li').removeClass('current');
|
||||
current.parent().addClass('current'); // toggle before effect
|
||||
current.parent().children('ul').hide();
|
||||
current.parent().children('ul').slideDown(200);
|
||||
}, 100);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
// add class to all those which have children
|
||||
$('.sidebar > ul > li').not(':last').not(':first').addClass('has-children');
|
||||
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
* jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010
|
||||
* http://benalman.com/projects/jquery-bbq-plugin/
|
||||
*
|
||||
* Copyright (c) 2010 "Cowboy" Ben Alman
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
* http://benalman.com/about/license/
|
||||
*/
|
||||
(function($,p){var i,m=Array.prototype.slice,r=decodeURIComponent,a=$.param,c,l,v,b=$.bbq=$.bbq||{},q,u,j,e=$.event.special,d="hashchange",A="querystring",D="fragment",y="elemUrlAttr",g="location",k="href",t="src",x=/^.*\?|#.*$/g,w=/^.*\#/,h,C={};function E(F){return typeof F==="string"}function B(G){var F=m.call(arguments,1);return function(){return G.apply(this,F.concat(m.call(arguments)))}}function n(F){return F.replace(/^[^#]*#?(.*)$/,"$1")}function o(F){return F.replace(/(?:^[^?#]*\?([^#]*).*$)?.*/,"$1")}function f(H,M,F,I,G){var O,L,K,N,J;if(I!==i){K=F.match(H?/^([^#]*)\#?(.*)$/:/^([^#?]*)\??([^#]*)(#?.*)/);J=K[3]||"";if(G===2&&E(I)){L=I.replace(H?w:x,"")}else{N=l(K[2]);I=E(I)?l[H?D:A](I):I;L=G===2?I:G===1?$.extend({},I,N):$.extend({},N,I);L=a(L);if(H){L=L.replace(h,r)}}O=K[1]+(H?"#":L||!K[1]?"?":"")+L+J}else{O=M(F!==i?F:p[g][k])}return O}a[A]=B(f,0,o);a[D]=c=B(f,1,n);c.noEscape=function(G){G=G||"";var F=$.map(G.split(""),encodeURIComponent);h=new RegExp(F.join("|"),"g")};c.noEscape(",/");$.deparam=l=function(I,F){var H={},G={"true":!0,"false":!1,"null":null};$.each(I.replace(/\+/g," ").split("&"),function(L,Q){var K=Q.split("="),P=r(K[0]),J,O=H,M=0,R=P.split("]["),N=R.length-1;if(/\[/.test(R[0])&&/\]$/.test(R[N])){R[N]=R[N].replace(/\]$/,"");R=R.shift().split("[").concat(R);N=R.length-1}else{N=0}if(K.length===2){J=r(K[1]);if(F){J=J&&!isNaN(J)?+J:J==="undefined"?i:G[J]!==i?G[J]:J}if(N){for(;M<=N;M++){P=R[M]===""?O.length:R[M];O=O[P]=M<N?O[P]||(R[M+1]&&isNaN(R[M+1])?{}:[]):J}}else{if($.isArray(H[P])){H[P].push(J)}else{if(H[P]!==i){H[P]=[H[P],J]}else{H[P]=J}}}}else{if(P){H[P]=F?i:""}}});return H};function z(H,F,G){if(F===i||typeof F==="boolean"){G=F;F=a[H?D:A]()}else{F=E(F)?F.replace(H?w:x,""):F}return l(F,G)}l[A]=B(z,0);l[D]=v=B(z,1);$[y]||($[y]=function(F){return $.extend(C,F)})({a:k,base:k,iframe:t,img:t,input:t,form:"action",link:k,script:t});j=$[y];function s(I,G,H,F){if(!E(H)&&typeof H!=="object"){F=H;H=G;G=i}return this.each(function(){var L=$(this),J=G||j()[(this.nodeName||"").toLowerCase()]||"",K=J&&L.attr(J)||"";L.attr(J,a[I](K,H,F))})}$.fn[A]=B(s,A);$.fn[D]=B(s,D);b.pushState=q=function(I,F){if(E(I)&&/^#/.test(I)&&F===i){F=2}var H=I!==i,G=c(p[g][k],H?I:{},H?F:2);p[g][k]=G+(/#/.test(G)?"":"#")};b.getState=u=function(F,G){return F===i||typeof F==="boolean"?v(F):v(G)[F]};b.removeState=function(F){var G={};if(F!==i){G=u();$.each($.isArray(F)?F:arguments,function(I,H){delete G[H]})}q(G,2)};e[d]=$.extend(e[d],{add:function(F){var H;function G(J){var I=J[D]=c();J.getState=function(K,L){return K===i||typeof K==="boolean"?l(I,K):l(I,L)[K]};H.apply(this,arguments)}if($.isFunction(F)){H=F;return G}else{H=F.handler;F.handler=G}}})})(jQuery,this);
|
||||
/*
|
||||
* jQuery hashchange event - v1.2 - 2/11/2010
|
||||
* http://benalman.com/projects/jquery-hashchange-plugin/
|
||||
*
|
||||
* Copyright (c) 2010 "Cowboy" Ben Alman
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
* http://benalman.com/about/license/
|
||||
*/
|
||||
(function($,i,b){var j,k=$.event.special,c="location",d="hashchange",l="href",f=$.browser,g=document.documentMode,h=f.msie&&(g===b||g<8),e="on"+d in i&&!h;function a(m){m=m||i[c][l];return m.replace(/^[^#]*#?(.*)$/,"$1")}$[d+"Delay"]=100;k[d]=$.extend(k[d],{setup:function(){if(e){return false}$(j.start)},teardown:function(){if(e){return false}$(j.stop)}});j=(function(){var m={},r,n,o,q;function p(){o=q=function(s){return s};if(h){n=$('<iframe src="javascript:0"/>').hide().insertAfter("body")[0].contentWindow;q=function(){return a(n.document[c][l])};o=function(u,s){if(u!==s){var t=n.document;t.open().close();t[c].hash="#"+u}};o(a())}}m.start=function(){if(r){return}var t=a();o||p();(function s(){var v=a(),u=q(t);if(v!==t){o(t=v,u);$(i).trigger(d)}else{if(u!==t){i[c][l]=i[c][l].replace(/#.*/,"")+"#"+u}}r=setTimeout(s,$[d+"Delay"])})()};m.stop=function(){if(!n){r&&clearTimeout(r);r=0}};return m})()})(jQuery,this);
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
* urlInternal - v1.0 - 10/7/2009
|
||||
* http://benalman.com/projects/jquery-urlinternal-plugin/
|
||||
*
|
||||
* Copyright (c) 2009 "Cowboy" Ben Alman
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
* http://benalman.com/about/license/
|
||||
*/
|
||||
(function($){var g,i=!0,r=!1,m=window.location,h=Array.prototype.slice,b=m.href.match(/^((https?:\/\/.*?\/)?[^#]*)#?.*$/),u=b[1]+"#",t=b[2],e,l,f,q,c,j,x="elemUrlAttr",k="href",y="src",p="urlInternal",d="urlExternal",n="urlFragment",a,s={};function w(A){var z=h.call(arguments,1);return function(){return A.apply(this,z.concat(h.call(arguments)))}}$.isUrlInternal=q=function(z){if(!z||j(z)){return g}if(a.test(z)){return i}if(/^(?:https?:)?\/\//i.test(z)){return r}if(/^[a-z\d.-]+:/i.test(z)){return g}return i};$.isUrlExternal=c=function(z){var A=q(z);return typeof A==="boolean"?!A:A};$.isUrlFragment=j=function(z){var A=(z||"").match(/^([^#]?)([^#]*#).*$/);return !!A&&(A[2]==="#"||z.indexOf(u)===0||(A[1]==="/"?t+A[2]===u:!/^https?:\/\//i.test(z)&&$('<a href="'+z+'"/>')[0].href.indexOf(u)===0))};function v(A,z){return this.filter(":"+A+(z?"("+z+")":""))}$.fn[p]=w(v,p);$.fn[d]=w(v,d);$.fn[n]=w(v,n);function o(D,C,B,A){var z=A[3]||e()[(C.nodeName||"").toLowerCase()]||"";return z?!!D(C.getAttribute(z)):r}$.expr[":"][p]=w(o,q);$.expr[":"][d]=w(o,c);$.expr[":"][n]=w(o,j);$[x]||($[x]=function(z){return $.extend(s,z)})({a:k,base:k,iframe:y,img:y,input:y,form:"action",link:k,script:y});e=$[x];$.urlInternalHost=l=function(B){B=B?"(?:(?:"+Array.prototype.join.call(arguments,"|")+")\\.)?":"";var A=new RegExp("^"+B+"(.*)","i"),z="^(?:"+m.protocol+")?//"+m.hostname.replace(A,B+"$1").replace(/\\?\./g,"\\.")+(m.port?":"+m.port:"")+"/";return f(z)};$.urlInternalRegExp=f=function(z){if(z){a=typeof z==="string"?new RegExp(z,"i"):z}return a};l("www")})(jQuery);
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
|
||||
@@ -0,0 +1,455 @@
|
||||
package gograph
|
||||
|
||||
import (
|
||||
_ "code.google.com/p/gosqlite/sqlite3"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
const (
|
||||
createEntityTable = `
|
||||
CREATE TABLE IF NOT EXISTS entity (
|
||||
id text NOT NULL PRIMARY KEY
|
||||
);`
|
||||
|
||||
createEdgeTable = `
|
||||
CREATE TABLE IF NOT EXISTS edge (
|
||||
"entity_id" text NOT NULL,
|
||||
"parent_id" text NULL,
|
||||
"name" text NOT NULL,
|
||||
CONSTRAINT "parent_fk" FOREIGN KEY ("parent_id") REFERENCES "entity" ("id"),
|
||||
CONSTRAINT "entity_fk" FOREIGN KEY ("entity_id") REFERENCES "entity" ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "name_parent_ix" ON "edge" (parent_id, name);
|
||||
`
|
||||
)
|
||||
|
||||
// Entity with a unique id
|
||||
type Entity struct {
|
||||
id string
|
||||
}
|
||||
|
||||
// An Edge connects two entities together
|
||||
type Edge struct {
|
||||
EntityID string
|
||||
Name string
|
||||
ParentID string
|
||||
}
|
||||
|
||||
type Entities map[string]*Entity
|
||||
type Edges []*Edge
|
||||
|
||||
type WalkFunc func(fullPath string, entity *Entity) error
|
||||
|
||||
// Graph database for storing entities and their relationships
|
||||
type Database struct {
|
||||
dbPath string
|
||||
}
|
||||
|
||||
// Create a new graph database initialized with a root entity
|
||||
func NewDatabase(dbPath string) (*Database, error) {
|
||||
db := &Database{dbPath}
|
||||
if _, err := os.Stat(dbPath); err == nil {
|
||||
return db, nil
|
||||
}
|
||||
conn, err := db.openConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
if _, err := conn.Exec(createEntityTable); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := conn.Exec(createEdgeTable); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rollback := func() {
|
||||
conn.Exec("ROLLBACK")
|
||||
}
|
||||
|
||||
// Create root entities
|
||||
if _, err := conn.Exec("BEGIN"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := conn.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil {
|
||||
rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := conn.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil {
|
||||
rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := conn.Exec("COMMIT"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// Set the entity id for a given path
|
||||
func (db *Database) Set(fullPath, id string) (*Entity, error) {
|
||||
conn, err := db.openConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
rollback := func() {
|
||||
conn.Exec("ROLLBACK")
|
||||
}
|
||||
if _, err := conn.Exec("BEGIN"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var entityId string
|
||||
if err := conn.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityId); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
if _, err := conn.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil {
|
||||
rollback()
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
rollback()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
e := &Entity{id}
|
||||
|
||||
parentPath, name := splitPath(fullPath)
|
||||
if err := db.setEdge(conn, parentPath, name, e); err != nil {
|
||||
rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := conn.Exec("COMMIT"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (db *Database) setEdge(conn *sql.DB, parentPath, name string, e *Entity) error {
|
||||
parent, err := db.get(conn, parentPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if parent.id == e.id {
|
||||
return fmt.Errorf("Cannot set self as child")
|
||||
}
|
||||
|
||||
if _, err := conn.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return the root "/" entity for the database
|
||||
func (db *Database) RootEntity() *Entity {
|
||||
return &Entity{
|
||||
id: "0",
|
||||
}
|
||||
}
|
||||
|
||||
// Return the entity for a given path
|
||||
func (db *Database) Get(name string) *Entity {
|
||||
conn, err := db.openConn()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
e, err := db.get(conn, name)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func (db *Database) get(conn *sql.DB, name string) (*Entity, error) {
|
||||
e := db.RootEntity()
|
||||
// We always know the root name so return it if
|
||||
// it is requested
|
||||
if name == "/" {
|
||||
return e, nil
|
||||
}
|
||||
|
||||
parts := split(name)
|
||||
for i := 1; i < len(parts); i++ {
|
||||
p := parts[i]
|
||||
|
||||
next := db.child(conn, e, p)
|
||||
if next == nil {
|
||||
return nil, fmt.Errorf("Cannot find child")
|
||||
}
|
||||
e = next
|
||||
}
|
||||
return e, nil
|
||||
|
||||
}
|
||||
|
||||
// List all entities by from the name
|
||||
// The key will be the full path of the entity
|
||||
func (db *Database) List(name string, depth int) Entities {
|
||||
out := Entities{}
|
||||
conn, err := db.openConn()
|
||||
if err != nil {
|
||||
return out
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
for c := range db.children(conn, name, depth) {
|
||||
out[c.FullPath] = c.Entity
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
|
||||
conn, err := db.openConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
for c := range db.children(conn, name, depth) {
|
||||
if err := walkFunc(c.FullPath, c.Entity); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return the refrence count for a specified id
|
||||
func (db *Database) Refs(id string) int {
|
||||
conn, err := db.openConn()
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
var count int
|
||||
if err := conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil {
|
||||
return 0
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// Return all the id's path references
|
||||
func (db *Database) RefPaths(id string) Edges {
|
||||
refs := Edges{}
|
||||
conn, err := db.openConn()
|
||||
if err != nil {
|
||||
return refs
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
rows, err := conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id)
|
||||
if err != nil {
|
||||
return refs
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var name string
|
||||
var parentId string
|
||||
if err := rows.Scan(&name, &parentId); err != nil {
|
||||
return refs
|
||||
}
|
||||
refs = append(refs, &Edge{
|
||||
EntityID: id,
|
||||
Name: name,
|
||||
ParentID: parentId,
|
||||
})
|
||||
}
|
||||
return refs
|
||||
}
|
||||
|
||||
// Delete the reference to an entity at a given path
|
||||
func (db *Database) Delete(name string) error {
|
||||
if name == "/" {
|
||||
return fmt.Errorf("Cannot delete root entity")
|
||||
}
|
||||
conn, err := db.openConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
parentPath, n := splitPath(name)
|
||||
parent, err := db.get(conn, parentPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name = ?;", parent.id, n); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove the entity with the specified id
|
||||
// Walk the graph to make sure all references to the entity
|
||||
// are removed and return the number of references removed
|
||||
func (db *Database) Purge(id string) (int, error) {
|
||||
conn, err := db.openConn()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
rollback := func() {
|
||||
conn.Exec("ROLLBACK")
|
||||
}
|
||||
|
||||
if _, err := conn.Exec("BEGIN"); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// Delete all edges
|
||||
rows, err := conn.Exec("DELETE FROM edge WHERE entity_id = ?;", id)
|
||||
if err != nil {
|
||||
rollback()
|
||||
return -1, err
|
||||
}
|
||||
|
||||
changes, err := rows.RowsAffected()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// Delete entity
|
||||
if _, err := conn.Exec("DELETE FROM entity where id = ?;", id); err != nil {
|
||||
rollback()
|
||||
return -1, err
|
||||
}
|
||||
|
||||
if _, err := conn.Exec("COMMIT"); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return int(changes), nil
|
||||
}
|
||||
|
||||
// Rename an edge for a given path
|
||||
func (db *Database) Rename(currentName, newName string) error {
|
||||
parentPath, name := splitPath(currentName)
|
||||
newParentPath, newEdgeName := splitPath(newName)
|
||||
|
||||
if parentPath != newParentPath {
|
||||
return fmt.Errorf("Cannot rename when root paths do not match %s != %s", parentPath, newParentPath)
|
||||
}
|
||||
|
||||
conn, err := db.openConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
parent, err := db.get(conn, parentPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rows, err := conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name = ?;", newEdgeName, parent.id, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i, err := rows.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if i == 0 {
|
||||
return fmt.Errorf("Cannot locate edge for %s %s", parent.id, name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type WalkMeta struct {
|
||||
Parent *Entity
|
||||
Entity *Entity
|
||||
FullPath string
|
||||
Edge *Edge
|
||||
}
|
||||
|
||||
func (db *Database) children(conn *sql.DB, name string, depth int) <-chan WalkMeta {
|
||||
out := make(chan WalkMeta)
|
||||
e, err := db.get(conn, name)
|
||||
if err != nil {
|
||||
close(out)
|
||||
return out
|
||||
}
|
||||
|
||||
go func() {
|
||||
rows, err := conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
|
||||
if err != nil {
|
||||
close(out)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var entityId, entityName string
|
||||
if err := rows.Scan(&entityId, &entityName); err != nil {
|
||||
// Log error
|
||||
continue
|
||||
}
|
||||
child := &Entity{entityId}
|
||||
edge := &Edge{
|
||||
ParentID: e.id,
|
||||
Name: entityName,
|
||||
EntityID: child.id,
|
||||
}
|
||||
|
||||
meta := WalkMeta{
|
||||
Parent: e,
|
||||
Entity: child,
|
||||
FullPath: path.Join(name, edge.Name),
|
||||
Edge: edge,
|
||||
}
|
||||
|
||||
out <- meta
|
||||
if depth == 0 {
|
||||
continue
|
||||
}
|
||||
nDepth := depth
|
||||
if depth != -1 {
|
||||
nDepth -= 1
|
||||
}
|
||||
sc := db.children(conn, meta.FullPath, nDepth)
|
||||
for c := range sc {
|
||||
out <- c
|
||||
}
|
||||
}
|
||||
close(out)
|
||||
}()
|
||||
return out
|
||||
}
|
||||
|
||||
// Return the entity based on the parent path and name
|
||||
func (db *Database) child(conn *sql.DB, parent *Entity, name string) *Entity {
|
||||
var id string
|
||||
if err := conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name = ?;", parent.id, name).Scan(&id); err != nil {
|
||||
return nil
|
||||
}
|
||||
return &Entity{id}
|
||||
}
|
||||
|
||||
func (db *Database) openConn() (*sql.DB, error) {
|
||||
return sql.Open("sqlite3", db.dbPath)
|
||||
}
|
||||
|
||||
// Return the id used to reference this entity
|
||||
func (e *Entity) ID() string {
|
||||
return e.id
|
||||
}
|
||||
|
||||
// Return the paths sorted by depth
|
||||
func (e Entities) Paths() []string {
|
||||
out := make([]string, len(e))
|
||||
var i int
|
||||
for k := range e {
|
||||
out[i] = k
|
||||
i++
|
||||
}
|
||||
sortByDepth(out)
|
||||
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
package gograph
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func newTestDb(t *testing.T) *Database {
|
||||
db, err := NewDatabase(path.Join(os.TempDir(), "sqlite.db"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func destroyTestDb(db *Database) {
|
||||
os.Remove(db.dbPath)
|
||||
}
|
||||
|
||||
func TestNewDatabase(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
if db == nil {
|
||||
t.Fatal("Datbase should not be nil")
|
||||
}
|
||||
defer destroyTestDb(db)
|
||||
}
|
||||
|
||||
func TestCreateRootEnity(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
root := db.RootEntity()
|
||||
if root == nil {
|
||||
t.Fatal("Root entity should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRootEntity(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
e := db.Get("/")
|
||||
if e == nil {
|
||||
t.Fatal("Entity should not be nil")
|
||||
}
|
||||
if e.ID() != "0" {
|
||||
t.Fatalf("Enity id should be 0, got %s", e.ID())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetEntityWithDifferentName(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
db.Set("/test", "1")
|
||||
if _, err := db.Set("/other", "1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateChild(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
child, err := db.Set("/db", "1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if child == nil {
|
||||
t.Fatal("Child should not be nil")
|
||||
}
|
||||
if child.ID() != "1" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestListAllRootChildren(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
for i := 1; i < 6; i++ {
|
||||
a := strconv.Itoa(i)
|
||||
if _, err := db.Set("/"+a, a); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
entries := db.List("/", -1)
|
||||
if len(entries) != 5 {
|
||||
t.Fatalf("Expect 5 entries for / got %d", len(entries))
|
||||
}
|
||||
}
|
||||
|
||||
func TestListAllSubChildren(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
_, err := db.Set("/webapp", "1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
child2, err := db.Set("/db", "2")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
child4, err := db.Set("/logs", "4")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/db/logs", child4.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
child3, err := db.Set("/sentry", "3")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
entries := db.List("/webapp", 1)
|
||||
if len(entries) != 3 {
|
||||
t.Fatalf("Expect 3 entries for / got %d", len(entries))
|
||||
}
|
||||
|
||||
entries = db.List("/webapp", 0)
|
||||
if len(entries) != 2 {
|
||||
t.Fatalf("Expect 2 entries for / got %d", len(entries))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddSelfAsChild(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
child, err := db.Set("/test", "1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/test/other", child.ID()); err == nil {
|
||||
t.Fatal("Error should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddChildToNonExistantRoot(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
if _, err := db.Set("/myapp", "1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := db.Set("/myapp/proxy/db", "2"); err == nil {
|
||||
t.Fatal("Error should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWalkAll(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
_, err := db.Set("/webapp", "1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
child2, err := db.Set("/db", "2")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
child4, err := db.Set("/db/logs", "4")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/logs", child4.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
child3, err := db.Set("/sentry", "3")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
child5, err := db.Set("/gograph", "5")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := db.Walk("/", func(p string, e *Entity) error {
|
||||
t.Logf("Path: %s Entity: %s", p, e.ID())
|
||||
return nil
|
||||
}, -1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEntityByPath(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
_, err := db.Set("/webapp", "1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
child2, err := db.Set("/db", "2")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
child4, err := db.Set("/logs", "4")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/db/logs", child4.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
child3, err := db.Set("/sentry", "3")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
child5, err := db.Set("/gograph", "5")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
entity := db.Get("/webapp/db/logs")
|
||||
if entity == nil {
|
||||
t.Fatal("Entity should not be nil")
|
||||
}
|
||||
if entity.ID() != "4" {
|
||||
t.Fatalf("Expected to get entity with id 4, got %s", entity.ID())
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnitiesPaths(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
_, err := db.Set("/webapp", "1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
child2, err := db.Set("/db", "2")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
child4, err := db.Set("/logs", "4")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/db/logs", child4.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
child3, err := db.Set("/sentry", "3")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
child5, err := db.Set("/gograph", "5")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
out := db.List("/", -1)
|
||||
for _, p := range out.Paths() {
|
||||
t.Log(p)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteRootEntity(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
if err := db.Delete("/"); err == nil {
|
||||
t.Fatal("Error should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteEntity(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
_, err := db.Set("/webapp", "1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
child2, err := db.Set("/db", "2")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
child4, err := db.Set("/logs", "4")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/db/logs", child4.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
child3, err := db.Set("/sentry", "3")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
child5, err := db.Set("/gograph", "5")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := db.Delete("/webapp/sentry"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
entity := db.Get("/webapp/sentry")
|
||||
if entity != nil {
|
||||
t.Fatal("Entity /webapp/sentry should be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCountRefs(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
db.Set("/webapp", "1")
|
||||
|
||||
if db.Refs("1") != 1 {
|
||||
t.Fatal("Expect reference count to be 1")
|
||||
}
|
||||
|
||||
db.Set("/db", "2")
|
||||
db.Set("/webapp/db", "2")
|
||||
if db.Refs("2") != 2 {
|
||||
t.Fatal("Expect reference count to be 2")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPurgeId(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
db.Set("/webapp", "1")
|
||||
|
||||
if db.Refs("1") != 1 {
|
||||
t.Fatal("Expect reference count to be 1")
|
||||
}
|
||||
|
||||
db.Set("/db", "2")
|
||||
db.Set("/webapp/db", "2")
|
||||
|
||||
count, err := db.Purge("2")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if count != 2 {
|
||||
t.Fatal("Expected 2 references to be removed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRename(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
db.Set("/webapp", "1")
|
||||
|
||||
if db.Refs("1") != 1 {
|
||||
t.Fatal("Expect reference count to be 1")
|
||||
}
|
||||
|
||||
db.Set("/db", "2")
|
||||
db.Set("/webapp/db", "2")
|
||||
|
||||
if db.Get("/webapp/db") == nil {
|
||||
t.Fatal("Cannot find entity at path /webapp/db")
|
||||
}
|
||||
|
||||
if err := db.Rename("/webapp/db", "/webapp/newdb"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if db.Get("/webapp/db") != nil {
|
||||
t.Fatal("Entity should not exist at /webapp/db")
|
||||
}
|
||||
if db.Get("/webapp/newdb") == nil {
|
||||
t.Fatal("Cannot find entity at path /webapp/newdb")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCreateMultipleNames(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
db.Set("/db", "1")
|
||||
if _, err := db.Set("/myapp", "1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
db.Walk("/", func(p string, e *Entity) error {
|
||||
t.Logf("%s\n", p)
|
||||
return nil
|
||||
}, -1)
|
||||
}
|
||||
|
||||
func TestRefPaths(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
db.Set("/webapp", "1")
|
||||
|
||||
db.Set("/db", "2")
|
||||
db.Set("/webapp/db", "2")
|
||||
|
||||
refs := db.RefPaths("2")
|
||||
if len(refs) != 2 {
|
||||
t.Fatalf("Expected reference count to be 2, got %d", len(refs))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package gograph
|
||||
|
||||
import "sort"
|
||||
|
||||
type pathSorter struct {
|
||||
paths []string
|
||||
by func(i, j string) bool
|
||||
}
|
||||
|
||||
func sortByDepth(paths []string) {
|
||||
s := &pathSorter{paths, func(i, j string) bool {
|
||||
return pathDepth(i) > pathDepth(j)
|
||||
}}
|
||||
sort.Sort(s)
|
||||
}
|
||||
|
||||
func (s *pathSorter) Len() int {
|
||||
return len(s.paths)
|
||||
}
|
||||
|
||||
func (s *pathSorter) Swap(i, j int) {
|
||||
s.paths[i], s.paths[j] = s.paths[j], s.paths[i]
|
||||
}
|
||||
|
||||
func (s *pathSorter) Less(i, j int) bool {
|
||||
return s.by(s.paths[i], s.paths[j])
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package gograph
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSort(t *testing.T) {
|
||||
paths := []string{
|
||||
"/",
|
||||
"/myreallylongname",
|
||||
"/app/db",
|
||||
}
|
||||
|
||||
sortByDepth(paths)
|
||||
|
||||
if len(paths) != 3 {
|
||||
t.Fatalf("Expected 3 parts got %d", len(paths))
|
||||
}
|
||||
|
||||
if paths[0] != "/app/db" {
|
||||
t.Fatalf("Expected /app/db got %s", paths[0])
|
||||
}
|
||||
if paths[1] != "/myreallylongname" {
|
||||
t.Fatalf("Expected /myreallylongname got %s", paths[1])
|
||||
}
|
||||
if paths[2] != "/" {
|
||||
t.Fatalf("Expected / got %s", paths[2])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package gograph
|
||||
|
||||
import (
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Split p on /
|
||||
func split(p string) []string {
|
||||
return strings.Split(p, "/")
|
||||
}
|
||||
|
||||
// Returns the depth or number of / in a given path
|
||||
func pathDepth(p string) int {
|
||||
parts := split(p)
|
||||
if len(parts) == 2 && parts[1] == "" {
|
||||
return 1
|
||||
}
|
||||
return len(parts)
|
||||
}
|
||||
|
||||
func splitPath(p string) (parent, name string) {
|
||||
if p[0] != '/' {
|
||||
p = "/" + p
|
||||
}
|
||||
parent, name = path.Split(p)
|
||||
l := len(parent)
|
||||
if parent[l-1] == '/' {
|
||||
parent = parent[:l-1]
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -121,6 +121,9 @@ func TestRegister(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMount(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
graph := tempGraph(t)
|
||||
defer os.RemoveAll(graph.Root)
|
||||
archive, err := fakeTar()
|
||||
@@ -144,12 +147,12 @@ func TestMount(t *testing.T) {
|
||||
if err := os.MkdirAll(rw, 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := image.Mount(rootfs, rw); err != nil {
|
||||
if err := image.Mount(runtime, rootfs, rw, "testing"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// FIXME: test for mount contents
|
||||
defer func() {
|
||||
if err := Unmount(rootfs); err != nil {
|
||||
if err := image.Unmount(runtime, rootfs, "testing"); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
../CONTRIBUTING.md
|
||||
@@ -1 +1,2 @@
|
||||
Solomon Hykes <solomon@dotcloud.com> (@shykes)
|
||||
Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
# The Docker maintainer manual
|
||||
|
||||
## Introduction
|
||||
|
||||
Dear maintainer. Thank you for investing the time and energy to help make Docker as
|
||||
useful as possible. Maintaining a project is difficult, sometimes unrewarding work.
|
||||
Sure, you will get to contribute cool features to the project. But most of your time
|
||||
will be spent reviewing, cleaning up, documenting, andswering questions, justifying
|
||||
design decisions - while everyone has all the fun! But remember - the quality of the
|
||||
maintainers work is what distinguishes the good projects from the great.
|
||||
So please be proud of your work, even the unglamourous parts, and encourage a culture
|
||||
of appreciation and respect for *every* aspect of improving the project - not just the
|
||||
hot new features.
|
||||
|
||||
This document is a manual for maintainers old and new. It explains what is expected of
|
||||
maintainers, how they should work, and what tools are available to them.
|
||||
|
||||
This is a living document - if you see something out of date or missing, speak up!
|
||||
|
||||
|
||||
## What are a maintainer's responsibility?
|
||||
|
||||
It is every maintainer's responsibility to:
|
||||
|
||||
* 1) Expose a clear roadmap for improving their component.
|
||||
* 2) Deliver prompt feedback and decisions on pull requests.
|
||||
* 3) Be available to anyone with questions, bug reports, criticism etc. on their component. This includes irc, github requests and the mailing list.
|
||||
* 4) Make sure their component respects the philosophy, design and roadmap of the project.
|
||||
|
||||
|
||||
## How are decisions made?
|
||||
|
||||
Short answer: with pull requests to the docker repository.
|
||||
|
||||
Docker is an open-source project with an open design philosophy. This means that the repository is the source of truth for EVERY aspect of the project,
|
||||
including its philosophy, design, roadmap and APIs. *If it's part of the project, it's in the repo. It's in the repo, it's part of the project.*
|
||||
|
||||
As a result, all decisions can be expressed as changes to the repository. An implementation change is a change to the source code. An API change is a change to
|
||||
the API specification. A philosophy change is a change to the philosophy manifesto. And so on.
|
||||
|
||||
All decisions affecting docker, big and small, follow the same 3 steps:
|
||||
|
||||
* Step 1: Open a pull request. Anyone can do this.
|
||||
|
||||
* Step 2: Discuss the pull request. Anyone can do this.
|
||||
|
||||
* Step 3: Accept or refuse a pull request. The relevant maintainer does this (see below "Who decides what?")
|
||||
|
||||
|
||||
## Who decides what?
|
||||
|
||||
So all decisions are pull requests, and the relevant maintainer makes the decision by accepting or refusing the pull request.
|
||||
But how do we identify the relevant maintainer for a given pull request?
|
||||
|
||||
Docker follows the timeless, highly efficient and totally unfair system known as [Benevolent dictator for life](http://en.wikipedia.org/wiki/Benevolent_Dictator_for_Life),
|
||||
with yours truly, Solomon Hykes, in the role of BDFL.
|
||||
This means that all decisions are made by default by me. Since making every decision myself would be highly unscalable, in practice decisions are spread across multiple maintainers.
|
||||
|
||||
The relevant maintainer for a pull request is assigned in 3 steps:
|
||||
|
||||
* Step 1: Determine the subdirectory affected by the pull request. This might be src/registry, docs/source/api, or any other part of the repo.
|
||||
|
||||
* Step 2: Find the MAINTAINERS file which affects this directory. If the directory itself does not have a MAINTAINERS file, work your way up the the repo hierarchy until you find one.
|
||||
|
||||
* Step 3: The first maintainer listed is the primary maintainer. The pull request is assigned to him. He may assign it to other listed maintainers, at his discretion.
|
||||
|
||||
|
||||
### I'm a maintainer, should I make pull requests too?
|
||||
|
||||
Yes. Nobody should ever push to master directly. All changes should be made through a pull request.
|
||||
|
||||
### Who assigns maintainers?
|
||||
|
||||
Solomon.
|
||||
|
||||
### How is this process changed?
|
||||
|
||||
Just like everything else: by making a pull request :)
|
||||
@@ -32,14 +32,14 @@ the process.
|
||||
|
||||
## System build dependencies
|
||||
|
||||
To build docker, you will need the following system dependencies
|
||||
To build docker, you will need the following system dependencies:
|
||||
|
||||
* An amd64 machine
|
||||
* A recent version of git and mercurial
|
||||
* Go version 1.1.2
|
||||
* Go version 1.2rc1
|
||||
* A copy of libdevmapper.a (statically compiled), and associated headers
|
||||
* A clean checkout of the source must be added to a valid Go [workspace](http://golang.org/doc/code.html#Workspaces)
|
||||
under the path *src/github.com/dotcloud/docker*. See
|
||||
|
||||
under the path *src/github.com/dotcloud/docker*.
|
||||
|
||||
## Go dependencies
|
||||
|
||||
@@ -55,15 +55,13 @@ NOTE: if you''re not able to package the exact version (to the exact commit) of
|
||||
please get in touch so we can remediate! Who knows what discrepancies can be caused by even the
|
||||
slightest deviation. We promise to do our best to make everybody happy.
|
||||
|
||||
## Disabling CGO for the net package
|
||||
|
||||
## Disabling CGO
|
||||
|
||||
Make sure to disable CGO on your system, and then recompile the standard library on the build
|
||||
machine:
|
||||
Make sure to disable CGO on your system for the net package using `-tags netgo`,
|
||||
and then recompile the standard library on the build machine:
|
||||
|
||||
```bash
|
||||
export CGO_ENABLED=0
|
||||
cd /tmp && echo 'package main' > t.go && go test -a -i -v
|
||||
go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std
|
||||
```
|
||||
|
||||
## Building Docker
|
||||
@@ -71,7 +69,7 @@ cd /tmp && echo 'package main' > t.go && go test -a -i -v
|
||||
To build the docker binary, run the following command with the source checkout as the
|
||||
working directory:
|
||||
|
||||
```
|
||||
```bash
|
||||
./hack/make.sh binary
|
||||
```
|
||||
|
||||
@@ -80,9 +78,9 @@ This will create a static binary under *./bundles/$VERSION/binary/docker-$VERSIO
|
||||
|
||||
You are encouraged to use ./hack/make.sh without modification. If you must absolutely write
|
||||
your own script (are you really, really sure you need to? make.sh is really not that complicated),
|
||||
then please take care the respect the following:
|
||||
then please take care to respect the following:
|
||||
|
||||
* In *./hack/make.sh*: $LDFLAGS, $VERSION and $GITCOMMIT
|
||||
* In *./hack/make.sh*: $LDFLAGS, $BUILDFLAGS, $VERSION and $GITCOMMIT
|
||||
* In *./hack/make/binary*: the exact build command to run
|
||||
|
||||
You may be tempted to tweak these settings. In particular, being a rigorous maintainer, you may want
|
||||
@@ -106,7 +104,6 @@ dependencies to be installed (see below).
|
||||
|
||||
The test suite will also download a small test container, so you will need internet connectivity.
|
||||
|
||||
|
||||
## Runtime dependencies
|
||||
|
||||
To run properly, docker needs the following software to be installed at runtime:
|
||||
@@ -116,6 +113,7 @@ To run properly, docker needs the following software to be installed at runtime:
|
||||
* iptables version 1.4 or later
|
||||
* The lxc utility scripts (http://lxc.sourceforge.net) version 0.8 or later.
|
||||
* Git version 1.7 or later
|
||||
* XZ Utils 4.9 or later
|
||||
|
||||
## Kernel dependencies
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Hacking on Docker
|
||||
|
||||
The hack/ directory holds information and tools for everyone involved in the process of creating and
|
||||
distributing Docker, specifically:
|
||||
|
||||
## Guides
|
||||
|
||||
If you're a *contributor* or aspiring contributor, you should read CONTRIBUTORS.md.
|
||||
|
||||
If you're a *maintainer* or aspiring maintainer, you should read MAINTAINERS.md.
|
||||
|
||||
If you're a *packager* or aspiring packager, you should read PACKAGERS.md.
|
||||
|
||||
If you're a maintainer in charge of a *release*, you should read RELEASE-CHECKLIST.md.
|
||||
|
||||
## Roadmap
|
||||
|
||||
A high-level roadmap is available at ROADMAP.md.
|
||||
|
||||
|
||||
## Build tools
|
||||
|
||||
make.sh is the primary build tool for docker. It is used for compiling the official binary,
|
||||
running the test suite, and pushing releases.
|
||||
@@ -1,28 +0,0 @@
|
||||
This directory contains material helpful for hacking on docker.
|
||||
|
||||
make hack
|
||||
=========
|
||||
|
||||
Set up an Ubuntu 12.04 virtual machine for developers including kernel 3.8
|
||||
go1.1 and buildbot. The environment is setup in a way that can be used through
|
||||
the usual go workflow and/or the root Makefile. You can either edit on
|
||||
your host, or inside the VM (using make ssh-dev) and run and test docker
|
||||
inside the VM.
|
||||
|
||||
dependencies: vagrant, virtualbox packages and python package requests
|
||||
|
||||
|
||||
Buildbot
|
||||
~~~~~~~~
|
||||
|
||||
Buildbot is a continuous integration system designed to automate the
|
||||
build/test cycle. By automatically rebuilding and testing the tree each time
|
||||
something has changed, build problems are pinpointed quickly, before other
|
||||
developers are inconvenienced by the failure.
|
||||
|
||||
When running 'make hack' at the docker root directory, it spawns a virtual
|
||||
machine in the background running a buildbot instance and adds a git
|
||||
post-commit hook that automatically run docker tests for you each time you
|
||||
commit in your local docker repository.
|
||||
|
||||
You can check your buildbot instance at http://192.168.33.21:8010/waterfall
|
||||
@@ -55,11 +55,9 @@ EXAMPLES:
|
||||
|
||||
### 4. Run all tests
|
||||
|
||||
```bash
|
||||
go test
|
||||
```
|
||||
FIXME
|
||||
|
||||
### 5. Commit and create a pull request
|
||||
### 5. Commit and create a pull request to the "release" branch
|
||||
|
||||
```bash
|
||||
git add CHANGELOG.md
|
||||
@@ -72,7 +70,7 @@ git push origin bump_$VERSION
|
||||
### 7. Merge the pull request and apply tags
|
||||
|
||||
```bash
|
||||
git checkout master
|
||||
git checkout release
|
||||
git merge bump_$VERSION
|
||||
git tag -a v$VERSION # Don't forget the v!
|
||||
git tag -f -a latest
|
||||
@@ -87,13 +85,14 @@ Get them from [the infrastructure maintainers](
|
||||
https://github.com/dotcloud/docker/blob/master/hack/infrastructure/MAINTAINERS).
|
||||
|
||||
```bash
|
||||
docker build -t releasedocker .
|
||||
docker build -t docker .
|
||||
docker run \
|
||||
-e AWS_S3_BUCKET=get-nightly.docker.io \
|
||||
-e AWS_ACCESS_KEY=$(cat ~/.aws/access_key) \
|
||||
-e AWS_SECRET_KEY=$(cat ~/.aws/secret_key) \
|
||||
-e GPG_PASSPHRASE=supersecretsesame \
|
||||
releasedocker
|
||||
docker
|
||||
hack/release.sh
|
||||
```
|
||||
|
||||
It will build and upload the binaries on the specified bucket (you should
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
BOX_NAME = "ubuntu-dev"
|
||||
BOX_URI = "http://files.vagrantup.com/precise64.box"
|
||||
VM_IP = "192.168.33.21"
|
||||
USER = "vagrant"
|
||||
GOPATH = "/data/docker"
|
||||
DOCKER_PATH = "#{GOPATH}/src/github.com/dotcloud/docker"
|
||||
CFG_PATH = "#{DOCKER_PATH}/hack/environment"
|
||||
BUILDBOT_PATH = "/data/buildbot"
|
||||
|
||||
Vagrant::Config.run do |config|
|
||||
# Setup virtual machine box
|
||||
config.vm.box = BOX_NAME
|
||||
config.vm.box_url = BOX_URI
|
||||
config.vm.share_folder "v-data", DOCKER_PATH, "#{File.dirname(__FILE__)}/.."
|
||||
config.vm.network :hostonly, VM_IP
|
||||
# Stop if deployment has been done
|
||||
config.vm.provision :shell, :inline => "[ ! -f /usr/bin/git ]"
|
||||
# Touch for makefile
|
||||
pkg_cmd = "touch #{DOCKER_PATH}; "
|
||||
# Install docker dependencies
|
||||
pkg_cmd << "apt-get update -qq; apt-get install -y python-software-properties; " \
|
||||
"add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu; apt-get update -qq; " \
|
||||
"apt-get install -y linux-image-generic-lts-raring lxc git aufs-tools golang-stable make; " \
|
||||
"chown -R #{USER}.#{USER} #{GOPATH}; " \
|
||||
"install -m 0664 #{CFG_PATH}/bash_profile /home/#{USER}/.bash_profile"
|
||||
config.vm.provision :shell, :inline => pkg_cmd
|
||||
# Deploy buildbot CI
|
||||
pkg_cmd = "apt-get install -q -y python-dev python-pip supervisor; " \
|
||||
"pip install -q -r #{CFG_PATH}/requirements.txt; " \
|
||||
"chown #{USER}.#{USER} /data; cd /data; " \
|
||||
"#{CFG_PATH}/setup.sh #{USER} #{GOPATH} #{DOCKER_PATH} #{CFG_PATH} #{BUILDBOT_PATH}"
|
||||
config.vm.provision :shell, :inline => pkg_cmd
|
||||
end
|
||||
@@ -1 +0,0 @@
|
||||
Files used to setup the developer virtual machine
|
||||
@@ -1,19 +0,0 @@
|
||||
# ~/.bash_profile : executed by the command interpreter for login shells.
|
||||
|
||||
# if running bash
|
||||
if [ -n "$BASH_VERSION" ]; then
|
||||
# include .bashrc if it exists
|
||||
if [ -f "$HOME/.bashrc" ]; then
|
||||
. "$HOME/.bashrc"
|
||||
fi
|
||||
fi
|
||||
|
||||
# set PATH so it includes user's private bin if it exists
|
||||
[ -d "$HOME/bin" ] && PATH="$HOME/bin:$PATH"
|
||||
|
||||
docker=/data/docker/src/github.com/dotcloud/docker
|
||||
[ -d $docker ] && cd $docker
|
||||
|
||||
export GOPATH=/data/docker
|
||||
export PATH=$PATH:$GOPATH/bin
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
[program:buildmaster]
|
||||
command=su vagrant -c "buildbot start master"
|
||||
directory=/data/buildbot
|
||||
chown= root:root
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/log/supervisor/buildbot-master.log
|
||||
stderr_logfile=/var/log/supervisor/buildbot-master.log
|
||||
|
||||
[program:buildworker]
|
||||
command=buildslave start slave
|
||||
directory=/data/buildbot
|
||||
chown= root:root
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/log/supervisor/buildbot-slave.log
|
||||
stderr_logfile=/var/log/supervisor/buildbot-slave.log
|
||||
|
||||
[group:buildbot]
|
||||
programs=buildmaster,buildworker
|
||||