mirror of
https://github.com/clearlinux/docker.git
synced 2026-08-19 12:16:30 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bf369bd1b8 |
+2
-13
@@ -1,17 +1,6 @@
|
||||
.vagrant*
|
||||
bin
|
||||
.vagrant
|
||||
docker/docker
|
||||
dockerd/dockerd
|
||||
.*.swp
|
||||
a.out
|
||||
*.orig
|
||||
build_src
|
||||
command-line-arguments.test
|
||||
.flymake*
|
||||
docker.test
|
||||
auth/auth.test
|
||||
.idea
|
||||
.DS_Store
|
||||
docs/_build
|
||||
docs/_static
|
||||
docs/_templates
|
||||
.gopath/
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
# Generate AUTHORS: git log --all --format='%aN <%aE>' | sort -uf | grep -v vagrant-ubuntu-12
|
||||
<charles.hooper@dotcloud.com> <chooper@plumata.com>
|
||||
<daniel.mizyrycki@dotcloud.com> <daniel@dotcloud.com>
|
||||
<daniel.mizyrycki@dotcloud.com> <mzdaniel@glidelink.net>
|
||||
Guillaume J. Charmes <guillaume.charmes@dotcloud.com> <charmes.guillaume@gmail.com>
|
||||
<guillaume.charmes@dotcloud.com> <guillaume@dotcloud.com>
|
||||
<kencochrane@gmail.com> <KenCochrane@gmail.com>
|
||||
<sridharr@activestate.com> <github@srid.name>
|
||||
Thatcher Peskens <thatcher@dotcloud.com> dhrp <thatcher@dotcloud.com>
|
||||
Thatcher Peskens <thatcher@dotcloud.com> dhrp <thatcher@gmx.net>
|
||||
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com> jpetazzo <jerome.petazzoni@dotcloud.com>
|
||||
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com> <jp@enix.org>
|
||||
Joffrey F <joffrey@dotcloud.com>
|
||||
<joffrey@dotcloud.com> <f.joffrey@gmail.com>
|
||||
Tim Terhorst <mynamewastaken+git@gmail.com>
|
||||
Andy Smith <github@anarkystic.com>
|
||||
<kalessin@kalessin.fr> <louis@dotcloud.com>
|
||||
<victor.vieux@dotcloud.com> <victor@dotcloud.com>
|
||||
<victor.vieux@dotcloud.com> <dev@vvieux.com>
|
||||
<dominik@honnef.co> <dominikh@fork-bomb.org>
|
||||
Thatcher Peskens <thatcher@dotcloud.com>
|
||||
@@ -1,68 +0,0 @@
|
||||
# This file lists all individuals having contributed content to the repository.
|
||||
# If you're submitting a patch, please add your name here in alphabetical order as part of the patch.
|
||||
#
|
||||
# For a list of active project maintainers, see the MAINTAINERS file.
|
||||
#
|
||||
Al Tobey <al@ooyala.com>
|
||||
Alexey Shamrin <shamrin@gmail.com>
|
||||
Andrea Luzzardi <aluzzardi@gmail.com>
|
||||
Andy Rothfusz <github@metaliveblog.com>
|
||||
Andy Smith <github@anarkystic.com>
|
||||
Antony Messerli <amesserl@rackspace.com>
|
||||
Barry Allard <barry.allard@gmail.com>
|
||||
Brandon Liu <bdon@bdon.org>
|
||||
Brian McCallister <brianm@skife.org>
|
||||
Bruno Bigras <bigras.bruno@gmail.com>
|
||||
Caleb Spare <cespare@gmail.com>
|
||||
Charles Hooper <charles.hooper@dotcloud.com>
|
||||
Daniel Gasienica <daniel@gasienica.ch>
|
||||
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
|
||||
Daniel Robinson <gottagetmac@gmail.com>
|
||||
Daniel Von Fange <daniel@leancoder.com>
|
||||
Dominik Honnef <dominik@honnef.co>
|
||||
Don Spaulding <donspauldingii@gmail.com>
|
||||
Dr Nic Williams <drnicwilliams@gmail.com>
|
||||
Evan Wies <evan@neomantra.net>
|
||||
ezbercih <cem.ezberci@gmail.com>
|
||||
Flavio Castelli <fcastelli@suse.com>
|
||||
Francisco Souza <f@souza.cc>
|
||||
Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
|
||||
Guillaume J. Charmes <guillaume.charmes@dotcloud.com>
|
||||
Harley Laue <losinggeneration@gmail.com>
|
||||
Hunter Blanks <hunter@twilio.com>
|
||||
Jeff Lindsay <progrium@gmail.com>
|
||||
Jeremy Grosser <jeremy@synack.me>
|
||||
Joffrey F <joffrey@dotcloud.com>
|
||||
John Costa <john.costa@gmail.com>
|
||||
Jonas Pfenniger <jonas@pfenniger.name>
|
||||
Jonathan Rudenberg <jonathan@titanous.com>
|
||||
Julien Barbier <write0@gmail.com>
|
||||
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
|
||||
Ken Cochrane <kencochrane@gmail.com>
|
||||
Kevin J. Lynagh <kevin@keminglabs.com>
|
||||
Louis Opter <kalessin@kalessin.fr>
|
||||
Marcus Farkas <toothlessgear@finitebox.com>
|
||||
Maxim Treskin <zerthurd@gmail.com>
|
||||
Michael Crosby <crosby.michael@gmail.com>
|
||||
Mikhail Sobolev <mss@mawhrin.net>
|
||||
Nate Jones <nate@endot.org>
|
||||
Nelson Chen <crazysim@gmail.com>
|
||||
Niall O'Higgins <niallo@unworkable.org>
|
||||
odk- <github@odkurzacz.org>
|
||||
Paul Bowsher <pbowsher@globalpersonals.co.uk>
|
||||
Paul Hammond <paul@paulhammond.org>
|
||||
Piotr Bogdan <ppbogdan@gmail.com>
|
||||
Robert Obryk <robryk@gmail.com>
|
||||
Sam Alba <sam.alba@gmail.com>
|
||||
Shawn Siefkas <shawn.siefkas@meredith.com>
|
||||
Silas Sewell <silas@sewell.org>
|
||||
Solomon Hykes <solomon@dotcloud.com>
|
||||
Sridhar Ratnakumar <sridharr@activestate.com>
|
||||
Thatcher Peskens <thatcher@dotcloud.com>
|
||||
Thomas Bikeev <thomas.bikeev@mac.com>
|
||||
Tianon Gravi <admwiggin@gmail.com>
|
||||
Tim Terhorst <mynamewastaken+git@gmail.com>
|
||||
Troy Howard <thoward37@gmail.com>
|
||||
unclejack <unclejacksons@gmail.com>
|
||||
Victor Vieux <victor.vieux@dotcloud.com>
|
||||
Vivek Agarwal <me@vivek.im>
|
||||
-204
@@ -1,204 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
## 0.4.4 (2013-06-19)
|
||||
- Builder: fix a regression introduced in 0.4.3 which caused builds to fail on new clients.
|
||||
|
||||
## 0.4.3 (2013-06-19)
|
||||
+ Builder: ADD of a local file will detect tar archives and unpack them
|
||||
* Runtime: Remove bsdtar dependency
|
||||
* Runtime: Add unix socket and multiple -H support
|
||||
* Runtime: Prevent rm of running containers
|
||||
* Runtime: Use go1.1 cookiejar
|
||||
* Builder: ADD improvements: use tar for copy + automatically unpack local archives
|
||||
* Builder: ADD uses tar/untar for copies instead of calling 'cp -ar'
|
||||
* Builder: nicer output for 'docker build'
|
||||
* Builder: fixed the behavior of ADD to be (mostly) reverse-compatible, predictable and well-documented.
|
||||
* Client: HumanReadable ProgressBar sizes in pull
|
||||
* Client: Fix docker version's git commit output
|
||||
* API: Send all tags on History API call
|
||||
* API: Add tag lookup to history command. Fixes #882
|
||||
- Runtime: Fix issue detaching from running TTY container
|
||||
- Runtime: Forbid parralel push/pull for a single image/repo. Fixes #311
|
||||
- Runtime: Fix race condition within Run command when attaching.
|
||||
- Builder: fix a bug which caused builds to fail if ADD was the first command
|
||||
- Documentation: fix missing command in irc bouncer example
|
||||
|
||||
## 0.4.2 (2013-06-17)
|
||||
- Packaging: Bumped version to work around an Ubuntu bug
|
||||
|
||||
## 0.4.1 (2013-06-17)
|
||||
+ Remote Api: Add flag to enable cross domain requests
|
||||
+ Remote Api/Client: Add images and containers sizes in docker ps and docker images
|
||||
+ Runtime: Configure dns configuration host-wide with 'docker -d -dns'
|
||||
+ Runtime: Detect faulty DNS configuration and replace it with a public default
|
||||
+ Runtime: allow docker run <name>:<id>
|
||||
+ Runtime: you can now specify public port (ex: -p 80:4500)
|
||||
* Client: allow multiple params in inspect
|
||||
* Client: Print the container id before the hijack in `docker run`
|
||||
* Registry: add regexp check on repo's name
|
||||
* Registry: Move auth to the client
|
||||
* Runtime: improved image removal to garbage-collect unreferenced parents
|
||||
* Vagrantfile: Add the rest api port to vagrantfile's port_forward
|
||||
* Upgrade to Go 1.1
|
||||
- Builder: don't ignore last line in Dockerfile when it doesn't end with \n
|
||||
- Registry: Remove login check on pull
|
||||
|
||||
## 0.4.0 (2013-06-03)
|
||||
+ Introducing Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
|
||||
+ Introducing Remote API: control Docker programmatically using a simple HTTP/json API
|
||||
* Runtime: various reliability and usability improvements
|
||||
|
||||
## 0.3.4 (2013-05-30)
|
||||
+ Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
|
||||
+ Builder: 'docker build -t FOO' applies the tag FOO to the newly built container.
|
||||
+ Runtime: interactive TTYs correctly handle window resize
|
||||
* Runtime: fix how configuration is merged between layers
|
||||
+ Remote API: split stdout and stderr on 'docker run'
|
||||
+ Remote API: optionally listen on a different IP and port (use at your own risk)
|
||||
* Documentation: improved install instructions.
|
||||
|
||||
## 0.3.3 (2013-05-23)
|
||||
- Registry: Fix push regression
|
||||
- Various bugfixes
|
||||
|
||||
## 0.3.2 (2013-05-09)
|
||||
* Runtime: Store the actual archive on commit
|
||||
* Registry: Improve the checksum process
|
||||
* Registry: Use the size to have a good progress bar while pushing
|
||||
* Registry: Use the actual archive if it exists in order to speed up the push
|
||||
- Registry: Fix error 400 on push
|
||||
|
||||
## 0.3.1 (2013-05-08)
|
||||
+ Builder: Implement the autorun capability within docker builder
|
||||
+ Builder: Add caching to docker builder
|
||||
+ Builder: Add support for docker builder with native API as top level command
|
||||
+ Runtime: Add go version to debug infos
|
||||
+ Builder: Implement ENV within docker builder
|
||||
+ Registry: Add docker search top level command in order to search a repository
|
||||
+ Images: output graph of images to dot (graphviz)
|
||||
+ Documentation: new introduction and high-level overview
|
||||
+ Documentation: Add the documentation for docker builder
|
||||
+ Website: new high-level overview
|
||||
- Makefile: Swap "go get" for "go get -d", especially to compile on go1.1rc
|
||||
- Images: fix ByParent function
|
||||
- Builder: Check the command existance prior create and add Unit tests for the case
|
||||
- Registry: Fix pull for official images with specific tag
|
||||
- Registry: Fix issue when login in with a different user and trying to push
|
||||
- Documentation: CSS fix for docker documentation to make REST API docs look better.
|
||||
- Documentation: Fixed CouchDB example page header mistake
|
||||
- Documentation: fixed README formatting
|
||||
* Registry: Improve checksum - async calculation
|
||||
* Runtime: kernel version - don't show the dash if flavor is empty
|
||||
* Documentation: updated www.docker.io website.
|
||||
* Builder: use any whitespaces instead of tabs
|
||||
* Packaging: packaging ubuntu; issue #510: Use goland-stable PPA package to build docker
|
||||
|
||||
## 0.3.0 (2013-05-06)
|
||||
+ Registry: Implement the new registry
|
||||
+ Documentation: new example: sharing data between 2 couchdb databases
|
||||
- Runtime: Fix the command existance check
|
||||
- Runtime: strings.Split may return an empty string on no match
|
||||
- Runtime: Fix an index out of range crash if cgroup memory is not
|
||||
* Documentation: Various improvments
|
||||
* Vagrant: Use only one deb line in /etc/apt
|
||||
|
||||
## 0.2.2 (2013-05-03)
|
||||
+ Support for data volumes ('docker run -v=PATH')
|
||||
+ Share data volumes between containers ('docker run -volumes-from')
|
||||
+ Improved documentation
|
||||
* Upgrade to Go 1.0.3
|
||||
* Various upgrades to the dev environment for contributors
|
||||
|
||||
## 0.2.1 (2013-05-01)
|
||||
+ 'docker commit -run' bundles a layer with default runtime options: command, ports etc.
|
||||
* Improve install process on Vagrant
|
||||
+ New Dockerfile operation: "maintainer"
|
||||
+ New Dockerfile operation: "expose"
|
||||
+ New Dockerfile operation: "cmd"
|
||||
+ Contrib script to build a Debian base layer
|
||||
+ 'docker -d -r': restart crashed containers at daemon startup
|
||||
* Runtime: improve test coverage
|
||||
|
||||
## 0.2.0 (2013-04-23)
|
||||
- Runtime: ghost containers can be killed and waited for
|
||||
* Documentation: update install intructions
|
||||
- Packaging: fix Vagrantfile
|
||||
- Development: automate releasing binaries and ubuntu packages
|
||||
+ Add a changelog
|
||||
- Various bugfixes
|
||||
|
||||
## 0.1.8 (2013-04-22)
|
||||
- Dynamically detect cgroup capabilities
|
||||
- Issue stability warning on kernels <3.8
|
||||
- 'docker push' buffers on disk instead of memory
|
||||
- Fix 'docker diff' for removed files
|
||||
- Fix 'docker stop' for ghost containers
|
||||
- Fix handling of pidfile
|
||||
- Various bugfixes and stability improvements
|
||||
|
||||
## 0.1.7 (2013-04-18)
|
||||
- Container ports are available on localhost
|
||||
- 'docker ps' shows allocated TCP ports
|
||||
- Contributors can run 'make hack' to start a continuous integration VM
|
||||
- Streamline ubuntu packaging & uploading
|
||||
- Various bugfixes and stability improvements
|
||||
|
||||
## 0.1.6 (2013-04-17)
|
||||
- Record the author an image with 'docker commit -author'
|
||||
|
||||
## 0.1.5 (2013-04-17)
|
||||
- Disable standalone mode
|
||||
- Use a custom DNS resolver with 'docker -d -dns'
|
||||
- Detect ghost containers
|
||||
- Improve diagnosis of missing system capabilities
|
||||
- Allow disabling memory limits at compile time
|
||||
- Add debian packaging
|
||||
- Documentation: installing on Arch Linux
|
||||
- Documentation: running Redis on docker
|
||||
- Fixed lxc 0.9 compatibility
|
||||
- Automatically load aufs module
|
||||
- Various bugfixes and stability improvements
|
||||
|
||||
## 0.1.4 (2013-04-09)
|
||||
- Full support for TTY emulation
|
||||
- Detach from a TTY session with the escape sequence `C-p C-q`
|
||||
- Various bugfixes and stability improvements
|
||||
- Minor UI improvements
|
||||
- Automatically create our own bridge interface 'docker0'
|
||||
|
||||
## 0.1.3 (2013-04-04)
|
||||
- Choose TCP frontend port with '-p :PORT'
|
||||
- Layer format is versioned
|
||||
- Major reliability improvements to the process manager
|
||||
- Various bugfixes and stability improvements
|
||||
|
||||
## 0.1.2 (2013-04-03)
|
||||
- Set container hostname with 'docker run -h'
|
||||
- Selective attach at run with 'docker run -a [stdin[,stdout[,stderr]]]'
|
||||
- Various bugfixes and stability improvements
|
||||
- UI polish
|
||||
- Progress bar on push/pull
|
||||
- Use XZ compression by default
|
||||
- Make IP allocator lazy
|
||||
|
||||
## 0.1.1 (2013-03-31)
|
||||
- Display shorthand IDs for convenience
|
||||
- Stabilize process management
|
||||
- Layers can include a commit message
|
||||
- Simplified 'docker attach'
|
||||
- Fixed support for re-attaching
|
||||
- Various bugfixes and stability improvements
|
||||
- Auto-download at run
|
||||
- Auto-login on push
|
||||
- Beefed up documentation
|
||||
|
||||
## 0.1.0 (2013-03-23)
|
||||
- First release
|
||||
- Implement registry in order to push/pull images
|
||||
- TCP port allocation
|
||||
- Fix termcaps on Linux
|
||||
- Add documentation
|
||||
- Add Vagrant support with Vagrantfile
|
||||
- Add unit tests
|
||||
- Add repository/tags to ease image management
|
||||
- Improve the layer implementation
|
||||
-160
@@ -1,160 +0,0 @@
|
||||
# Contributing to Docker
|
||||
|
||||
Want to hack on Docker? Awesome! Here are instructions to get you started. They are probably not perfect, please let us know if anything feels
|
||||
wrong or incomplete.
|
||||
|
||||
## Contribution guidelines
|
||||
|
||||
### Pull requests are always welcome
|
||||
|
||||
We are always thrilled to receive pull requests, and do our best to
|
||||
process them as fast as possible. Not sure if that typo is worth a pull
|
||||
request? Do it! We will appreciate it.
|
||||
|
||||
If your pull request is not accepted on the first try, don't be
|
||||
discouraged! If there's a problem with the implementation, hopefully you
|
||||
received feedback on what to improve.
|
||||
|
||||
We're trying very hard to keep Docker lean and focused. We don't want it
|
||||
to do everything for everybody. This means that we might decide against
|
||||
incorporating a new feature. However, there might be a way to implement
|
||||
that feature *on top of* docker.
|
||||
|
||||
### Discuss your design on the mailing list
|
||||
|
||||
We recommend discussing your plans [on the mailing
|
||||
list](https://groups.google.com/forum/?fromgroups#!forum/docker-club)
|
||||
before starting to code - especially for more ambitious contributions.
|
||||
This gives other contributors a chance to point you in the right
|
||||
direction, give feedback on your design, and maybe point out if someone
|
||||
else is working on the same thing.
|
||||
|
||||
### Create issues...
|
||||
|
||||
Any significant improvement should be documented as [a github
|
||||
issue](https://github.com/dotcloud/docker/issues) before anybody
|
||||
starts working on it.
|
||||
|
||||
### ...but check for existing issues first!
|
||||
|
||||
Please take a moment to check that an issue doesn't already exist
|
||||
documenting your bug report or improvement proposal. If it does, it
|
||||
never hurts to add a quick "+1" or "I have this problem too". This will
|
||||
help prioritize the most common problems and requests.
|
||||
|
||||
### Conventions
|
||||
|
||||
Fork the repo and make changes on your fork in a feature branch:
|
||||
|
||||
- If it's a bugfix branch, name it XXX-something where XXX is the number of the
|
||||
issue
|
||||
- If it's a feature branch, create an enhancement issue to announce your
|
||||
intentions, and name it XXX-something where XXX is the number of the issue.
|
||||
|
||||
Submit unit tests for your changes. Go has a great test framework built in; use
|
||||
it! Take a look at existing tests for inspiration. Run the full test suite on
|
||||
your branch before submitting a pull request.
|
||||
|
||||
Make sure you include relevant updates or additions to documentation when
|
||||
creating or modifying features.
|
||||
|
||||
Write clean code. Universally formatted code promotes ease of writing, reading,
|
||||
and maintenance. Always run `go fmt` before committing your changes. Most
|
||||
editors have plugins that do this automatically, and there's also a git
|
||||
pre-commit hook:
|
||||
|
||||
```
|
||||
curl -o .git/hooks/pre-commit https://raw.github.com/edsrzf/gofmt-git-hook/master/fmt-check && chmod +x .git/hooks/pre-commit
|
||||
```
|
||||
|
||||
Pull requests descriptions should be as clear as possible and include a
|
||||
reference to all the issues that they address.
|
||||
|
||||
Code review comments may be added to your pull request. Discuss, then make the
|
||||
suggested modifications and push additional commits to your feature branch. Be
|
||||
sure to post a comment after pushing. The new commits will show up in the pull
|
||||
request automatically, but the reviewers will not be notified unless you
|
||||
comment.
|
||||
|
||||
Before the pull request is merged, make sure that you squash your commits into
|
||||
logical units of work using `git rebase -i` and `git push -f`. After every
|
||||
commit the test suite should be passing. Include documentation changes in the
|
||||
same commit so that a revert would remove all traces of the feature or fix.
|
||||
|
||||
Commits that fix or close an issue should include a reference like `Closes #XXX`
|
||||
or `Fixes #XXX`, which will automatically close the issue when merged.
|
||||
|
||||
Add your name to the AUTHORS file, but make sure the list is sorted and your
|
||||
name and email address match your git configuration. The AUTHORS file is
|
||||
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
|
||||
* Step 2: make yourself useful by contributing code, bugfixes, support etc.
|
||||
* Step 3: volunteer on the irc channel (#docker@freenode)
|
||||
|
||||
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 :)
|
||||
@@ -1,35 +0,0 @@
|
||||
|
||||
## FIXME
|
||||
|
||||
This file is a loose collection of things to improve in the codebase, for the internal
|
||||
use of the maintainers.
|
||||
|
||||
They are not big enough to be in the roadmap, not user-facing enough to be github issues,
|
||||
and not important enough to be discussed in the mailing list.
|
||||
|
||||
They are just like FIXME comments in the source code, except we're not sure where in the source
|
||||
to put them - so we put them here :)
|
||||
|
||||
|
||||
* Merge Runtime, Server and Builder into Runtime
|
||||
* 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
|
||||
* 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 clean up containers for disk space
|
||||
* Caching after an ADD
|
||||
* entry point config
|
||||
* bring back git revision info, looks like it was lost
|
||||
@@ -1,5 +0,0 @@
|
||||
Solomon Hykes <solomon@dotcloud.com>
|
||||
Guillaume Charmes <guillaume@dotcloud.com>
|
||||
Victor Vieux <victor@dotcloud.com>
|
||||
api.go: Victor Vieux <victor@dotcloud.com>
|
||||
Vagrantfile: Daniel Mizyrycki <daniel@dotcloud.com>
|
||||
@@ -1,87 +1,23 @@
|
||||
DOCKER_PACKAGE := github.com/dotcloud/docker
|
||||
RELEASE_VERSION := $(shell git tag | grep -E "v[0-9\.]+$$" | sort -nr | head -n 1)
|
||||
SRCRELEASE := docker-$(RELEASE_VERSION)
|
||||
BINRELEASE := docker-$(RELEASE_VERSION).tgz
|
||||
GOPATH := $(PWD)/env
|
||||
BUILDPATH := $(PWD)/build
|
||||
|
||||
GIT_ROOT := $(shell git rev-parse --show-toplevel)
|
||||
BUILD_DIR := $(CURDIR)/.gopath
|
||||
all: docker.o dockerd.o
|
||||
|
||||
GOPATH ?= $(BUILD_DIR)
|
||||
export GOPATH
|
||||
env:
|
||||
mkdir -p ${BUILDPATH} ${GOPATH}/src/github.com/dotcloud/
|
||||
ln -s $(PWD) ${GOPATH}/src/github.com/dotcloud/docker
|
||||
|
||||
GO_OPTIONS ?=
|
||||
ifeq ($(VERBOSE), 1)
|
||||
GO_OPTIONS += -v
|
||||
endif
|
||||
|
||||
GIT_COMMIT = $(shell git rev-parse --short HEAD)
|
||||
GIT_STATUS = $(shell test -n "`git status --porcelain`" && echo "+CHANGES")
|
||||
|
||||
BUILD_OPTIONS = -ldflags "-X main.GITCOMMIT $(GIT_COMMIT)$(GIT_STATUS)"
|
||||
|
||||
SRC_DIR := $(GOPATH)/src
|
||||
|
||||
DOCKER_DIR := $(SRC_DIR)/$(DOCKER_PACKAGE)
|
||||
DOCKER_MAIN := $(DOCKER_DIR)/docker
|
||||
|
||||
DOCKER_BIN_RELATIVE := bin/docker
|
||||
DOCKER_BIN := $(CURDIR)/$(DOCKER_BIN_RELATIVE)
|
||||
|
||||
.PHONY: all clean test hack release srcrelease $(BINRELEASE) $(SRCRELEASE) $(DOCKER_BIN) $(DOCKER_DIR)
|
||||
|
||||
all: $(DOCKER_BIN)
|
||||
|
||||
$(DOCKER_BIN): $(DOCKER_DIR)
|
||||
@mkdir -p $(dir $@)
|
||||
@(cd $(DOCKER_MAIN); go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o $@)
|
||||
@echo $(DOCKER_BIN_RELATIVE) is created.
|
||||
|
||||
$(DOCKER_DIR):
|
||||
@mkdir -p $(dir $@)
|
||||
@if [ -h $@ ]; then rm -f $@; fi; ln -sf $(CURDIR)/ $@
|
||||
@(cd $(DOCKER_MAIN); go get -d $(GO_OPTIONS))
|
||||
|
||||
whichrelease:
|
||||
echo $(RELEASE_VERSION)
|
||||
|
||||
release: $(BINRELEASE)
|
||||
s3cmd -P put $(BINRELEASE) s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-$(RELEASE_VERSION).tgz
|
||||
s3cmd -P put docker-latest.tgz s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-latest.tgz
|
||||
|
||||
srcrelease: $(SRCRELEASE)
|
||||
deps: $(DOCKER_DIR)
|
||||
|
||||
# A clean checkout of $RELEASE_VERSION, with vendored dependencies
|
||||
$(SRCRELEASE):
|
||||
rm -fr $(SRCRELEASE)
|
||||
git clone $(GIT_ROOT) $(SRCRELEASE)
|
||||
cd $(SRCRELEASE); git checkout -q $(RELEASE_VERSION)
|
||||
|
||||
# A binary release ready to be uploaded to a mirror
|
||||
$(BINRELEASE): $(SRCRELEASE)
|
||||
rm -f $(BINRELEASE)
|
||||
cd $(SRCRELEASE); make; cp -R bin docker-$(RELEASE_VERSION); tar -f ../$(BINRELEASE) -zv -c docker-$(RELEASE_VERSION)
|
||||
cd $(SRCRELEASE); cp -R bin docker-latest; tar -f ../docker-latest.tgz -zv -c docker-latest
|
||||
deps:
|
||||
GOPATH=${GOPATH} go get github.com/kr/pty
|
||||
GOPATH=${GOPATH} go get github.com/mattn/go-sqlite3
|
||||
GOPATH=${GOPATH} go get github.com/shykes/gorp
|
||||
|
||||
clean:
|
||||
@rm -rf $(dir $(DOCKER_BIN))
|
||||
ifeq ($(GOPATH), $(BUILD_DIR))
|
||||
@rm -rf $(BUILD_DIR)
|
||||
else ifneq ($(DOCKER_DIR), $(realpath $(DOCKER_DIR)))
|
||||
@rm -f $(DOCKER_DIR)
|
||||
endif
|
||||
go clean
|
||||
rm -rf env build
|
||||
|
||||
test: all
|
||||
@(cd $(DOCKER_DIR); sudo -E go test $(GO_OPTIONS))
|
||||
docker.o: env deps
|
||||
GOPATH=${GOPATH} go build -o ${BUILDPATH}/docker docker/docker.go
|
||||
|
||||
testall: all
|
||||
@(cd $(DOCKER_DIR); sudo -E go test ./... $(GO_OPTIONS))
|
||||
|
||||
fmt:
|
||||
@gofmt -s -l -w .
|
||||
|
||||
hack:
|
||||
cd $(CURDIR)/hack && vagrant up
|
||||
|
||||
ssh-dev:
|
||||
cd $(CURDIR)/hack && vagrant ssh
|
||||
dockerd.o: env deps
|
||||
GOPATH=${GOPATH} go build -o ${BUILDPATH}/dockerd dockerd/dockerd.go
|
||||
|
||||
@@ -3,9 +3,4 @@ Copyright 2012-2013 dotCloud, inc.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
This product contains software (https://github.com/kr/pty) developed by Keith Rarick, licensed under the MIT License.
|
||||
@@ -1,228 +1,39 @@
|
||||
Docker: the Linux container engine
|
||||
==================================
|
||||
Docker is a process manager with superpowers
|
||||
============================================
|
||||
|
||||
Docker is an open-source engine which automates the deployment of applications as highly portable, self-sufficient containers.
|
||||
It encapsulates heterogeneous payloads in Standard Containers, and runs them on any server with strong guarantees of isolation and repeatability.
|
||||
|
||||
Docker containers are both *hardware-agnostic* and *platform-agnostic*. This means that they can run anywhere, from your
|
||||
laptop to the largest EC2 compute instance and everything in between - and they don't require that you use a particular
|
||||
language, framework or packaging system. That makes them great building blocks for deploying and scaling web apps, databases
|
||||
and backend services without depending on a particular stack or provider.
|
||||
Is is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc.
|
||||
|
||||
Docker is an open-source implementation of the deployment engine which powers [dotCloud](http://dotcloud.com), a popular 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.
|
||||
<img src="http://bricks.argz.com/bricksfiles/lego/07000/7823/012.jpg"/>
|
||||
|
||||

|
||||
* *Heterogeneous payloads*: any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all.
|
||||
|
||||
## Better than VMs
|
||||
* *Any server*: docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments.
|
||||
|
||||
A common method for distributing applications and sandbox their execution is to use virtual machines, or VMs. Typical VM formats
|
||||
are VMWare's vmdk, Oracle Virtualbox's vdi, and Amazon EC2's ami. In theory these formats should allow every developer to
|
||||
automatically package their application into a "machine" for easy distribution and deployment. In practice, that almost never
|
||||
happens, for a few reasons:
|
||||
* *Isolation*: docker isolates processes from each other and from the underlying host, using lightweight containers.
|
||||
|
||||
* *Size*: VMs are very large which makes them impractical to store and transfer.
|
||||
* *Performance*: running VMs consumes significant CPU and memory, which makes them impractical in many scenarios, for example local development of multi-tier applications, and
|
||||
large-scale deployment of cpu and memory-intensive applications on large numbers of machines.
|
||||
* *Portability*: competing VM environments don't play well with each other. Although conversion tools do exist, they are limited and add even more overhead.
|
||||
* *Hardware-centric*: VMs were designed with machine operators in mind, not software developers. As a result, they offer very limited tooling for what developers need most:
|
||||
building, testing and running their software. For example, VMs offer no facilities for application versioning, monitoring, configuration, logging or service discovery.
|
||||
* *Repeatability*: because containers are isolated in their own filesystem, they behave the same regardless of where, when, and alongside what they run.
|
||||
|
||||
By contrast, Docker relies on a different sandboxing method known as *containerization*. Unlike traditional virtualization,
|
||||
containerization takes place at the kernel level. Most modern operating system kernels now support the primitives necessary
|
||||
for containerization, including Linux with [openvz](http://openvz.org), [vserver](http://linux-vserver.org) and more recently [lxc](http://lxc.sourceforge.net),
|
||||
Solaris with [zones](http://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc) and FreeBSD with [Jails](http://www.freebsd.org/doc/handbook/jails.html).
|
||||
|
||||
Docker builds on top of these low-level primitives to offer developers a portable format and runtime environment that solves
|
||||
all 4 problems. Docker containers are small (and their transfer can be optimized with layers), they have basically zero memory and cpu overhead,
|
||||
they are completely portable and are designed from the ground up with an application-centric design.
|
||||
Notable features
|
||||
-----------------
|
||||
|
||||
The best part: because docker operates at the OS level, it can still be run inside a VM!
|
||||
* Filesystem isolation: each process container runs in a completely separate root filesystem.
|
||||
|
||||
## Plays well with others
|
||||
* Resource isolation: system resources like cpu and memory can be allocated differently to each process container, using cgroups.
|
||||
|
||||
Docker does not require that you buy into a particular programming language, framework, packaging system or configuration language.
|
||||
* Network isolation: each process container runs in its own network namespace, with a virtual interface and IP address of its own.
|
||||
|
||||
Is your application a unix process? Does it use files, tcp connections, environment variables, standard unix streams and command-line
|
||||
arguments as inputs and outputs? Then docker can run it.
|
||||
* Copy-on-write: root filesystems are created using copy-on-write, which makes deployment extremeley fast, memory-cheap and disk-cheap.
|
||||
|
||||
Can your application's build be expressed as a sequence of such commands? Then docker can build it.
|
||||
* Logging: the standard streams (stdout/stderr/stdin) of each process container is collected and logged for real-time or batch retrieval.
|
||||
|
||||
* Change management: changes to a container's filesystem can be committed into a new image and re-used to create more containers. No templating or manual configuration required.
|
||||
|
||||
## Escape dependency hell
|
||||
* Interactive shell: docker can allocate a pseudo-tty and attach to the standard input of any container, for example to run a throaway interactive shell.
|
||||
|
||||
A common problem for developers is the difficulty of managing all their application's dependencies in a simple and automated way.
|
||||
|
||||
This is usually difficult for several reasons:
|
||||
|
||||
* *Cross-platform dependencies*. Modern applications often depend on a combination of system libraries and binaries, language-specific packages, framework-specific modules,
|
||||
internal components developed for another project, etc. These dependencies live in different "worlds" and require different tools - these tools typically don't work
|
||||
well with each other, requiring awkward custom integrations.
|
||||
|
||||
* Conflicting dependencies. Different applications may depend on different versions of the same dependency. Packaging tools handle these situations with various degrees of ease -
|
||||
but they all handle them in different and incompatible ways, which again forces the developer to do extra work.
|
||||
|
||||
* Custom dependencies. A developer may need to prepare a custom version of his application's dependency. Some packaging systems can handle custom versions of a dependency,
|
||||
others can't - and all of them handle it differently.
|
||||
|
||||
|
||||
Docker solves dependency hell by giving the developer a simple way to express *all* his application's dependencies in one place,
|
||||
and streamline the process of assembling them. If this makes you think of [XKCD 927](http://xkcd.com/927/), don't worry. Docker doesn't
|
||||
*replace* your favorite packaging systems. It simply orchestrates their use in a simple and repeatable way. How does it do that? With layers.
|
||||
|
||||
Docker defines a build as running a sequence of unix commands, one after the other, in the same container. Build commands modify the contents of the container
|
||||
(usually by installing new files on the filesystem), the next command modifies it some more, etc. Since each build command inherits the result of the previous
|
||||
commands, the *order* in which the commands are executed expresses *dependencies*.
|
||||
|
||||
Here's a typical docker build process:
|
||||
|
||||
```bash
|
||||
from ubuntu:12.10
|
||||
run apt-get update
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -q -y python
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -q -y python-pip
|
||||
run pip install django
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -q -y curl
|
||||
run curl -L https://github.com/shykes/helloflask/archive/master.tar.gz | tar -xzv
|
||||
run cd helloflask-master && pip install -r requirements.txt
|
||||
```
|
||||
|
||||
Note that Docker doesn't care *how* dependencies are built - as long as they can be built by running a unix command in a container.
|
||||
|
||||
|
||||
Install instructions
|
||||
==================
|
||||
|
||||
Quick install on Ubuntu 12.04 and 12.10
|
||||
---------------------------------------
|
||||
|
||||
```bash
|
||||
curl get.docker.io | sh -x
|
||||
```
|
||||
|
||||
Binary installs
|
||||
----------------
|
||||
|
||||
Docker supports the following binary installation methods.
|
||||
Note that some methods are community contributions and not yet officially supported.
|
||||
|
||||
* [Ubuntu 12.04 and 12.10 (officially supported)](http://docs.docker.io/en/latest/installation/ubuntulinux/)
|
||||
* [Arch Linux](http://docs.docker.io/en/latest/installation/archlinux/)
|
||||
* [Mac OS X (with Vagrant)](http://docs.docker.io/en/latest/installation/vagrant/)
|
||||
* [Windows (with Vagrant)](http://docs.docker.io/en/latest/installation/windows/)
|
||||
* [Amazon EC2 (with Vagrant)](http://docs.docker.io/en/latest/installation/amazon/)
|
||||
|
||||
Installing from source
|
||||
----------------------
|
||||
|
||||
1. Make sure you have a [Go language](http://golang.org/doc/install) compiler and [git](http://git-scm.com) installed.
|
||||
|
||||
2. Checkout the source code
|
||||
|
||||
```bash
|
||||
git clone http://github.com/dotcloud/docker
|
||||
```
|
||||
|
||||
3. Build the docker binary
|
||||
|
||||
```bash
|
||||
cd docker
|
||||
make VERBOSE=1
|
||||
sudo cp ./bin/docker /usr/local/bin/docker
|
||||
```
|
||||
|
||||
Usage examples
|
||||
==============
|
||||
|
||||
First run the docker daemon
|
||||
---------------------------
|
||||
|
||||
All the examples assume your machine is running the docker daemon. To run the docker daemon in the background, simply type:
|
||||
|
||||
```bash
|
||||
# On a production system you want this running in an init script
|
||||
sudo docker -d &
|
||||
```
|
||||
|
||||
Now you can run docker in client mode: all commands will be forwarded to the docker daemon, so the client can run from any account.
|
||||
|
||||
```bash
|
||||
# Now you can run docker commands from any account.
|
||||
docker help
|
||||
```
|
||||
|
||||
|
||||
Throwaway shell in a base ubuntu image
|
||||
--------------------------------------
|
||||
|
||||
```bash
|
||||
docker pull ubuntu:12.10
|
||||
|
||||
# Run an interactive shell, allocate a tty, attach stdin and stdout
|
||||
# To detach the tty without exiting the shell, use the escape sequence Ctrl-p + Ctrl-q
|
||||
docker run -i -t ubuntu:12.10 /bin/bash
|
||||
```
|
||||
|
||||
Starting a long-running worker process
|
||||
--------------------------------------
|
||||
|
||||
```bash
|
||||
# Start a very useful long-running process
|
||||
JOB=$(docker run -d ubuntu /bin/sh -c "while true; do echo Hello world; sleep 1; done")
|
||||
|
||||
# Collect the output of the job so far
|
||||
docker logs $JOB
|
||||
|
||||
# Kill the job
|
||||
docker kill $JOB
|
||||
```
|
||||
|
||||
Running an irc bouncer
|
||||
----------------------
|
||||
|
||||
```bash
|
||||
BOUNCER_ID=$(docker run -d -p 6667 -u irc shykes/znc zncrun $USER $PASSWORD)
|
||||
echo "Configure your irc client to connect to port $(docker port $BOUNCER_ID 6667) of this machine"
|
||||
```
|
||||
|
||||
Running Redis
|
||||
-------------
|
||||
|
||||
```bash
|
||||
REDIS_ID=$(docker run -d -p 6379 shykes/redis redis-server)
|
||||
echo "Configure your redis client to connect to port $(docker port $REDIS_ID 6379) of this machine"
|
||||
```
|
||||
|
||||
Share your own image!
|
||||
---------------------
|
||||
|
||||
```bash
|
||||
CONTAINER=$(docker run -d ubuntu:12.10 apt-get install -y curl)
|
||||
docker commit -m "Installed curl" $CONTAINER $USER/betterbase
|
||||
docker push $USER/betterbase
|
||||
```
|
||||
|
||||
A list of publicly available images is [available here](https://github.com/dotcloud/docker/wiki/Public-docker-images).
|
||||
|
||||
Expose a service on a TCP port
|
||||
------------------------------
|
||||
|
||||
```bash
|
||||
# Expose port 4444 of this container, and tell netcat to listen on it
|
||||
JOB=$(docker run -d -p 4444 base /bin/nc -l -p 4444)
|
||||
|
||||
# Which public port is NATed to my container?
|
||||
PORT=$(docker port $JOB 4444)
|
||||
|
||||
# Connect to the public port via the host's public address
|
||||
# Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
|
||||
# Replace *eth0* according to your local interface name.
|
||||
IP=$(ip -o -4 addr list eth0 | perl -n -e 'if (m{inet\s([\d\.]+)\/\d+\s}xms) { print $1 }')
|
||||
echo hello world | nc $IP $PORT
|
||||
|
||||
# Verify that the network connection worked
|
||||
echo "Daemon received: $(docker logs $JOB)"
|
||||
```
|
||||
|
||||
Under the hood
|
||||
--------------
|
||||
@@ -239,62 +50,49 @@ Under the hood, Docker is built on the following components:
|
||||
* [lxc](http://lxc.sourceforge.net/), a set of convenience scripts to simplify the creation of linux containers.
|
||||
|
||||
|
||||
Setup instructions
|
||||
==================
|
||||
|
||||
Contributing to Docker
|
||||
======================
|
||||
Requirements
|
||||
------------
|
||||
|
||||
Want to hack on Docker? Awesome! There are instructions to get you started on the website: http://docs.docker.io/en/latest/contributing/contributing/
|
||||
Right now, the officially supported distributions are:
|
||||
|
||||
They are probably not perfect, please let us know if anything feels wrong or incomplete.
|
||||
* Ubuntu 12.04 (precise LTS)
|
||||
* Ubuntu 12.10 (quantal)
|
||||
|
||||
Docker probably works on other distributions featuring a recent kernel, the AUFS patch, and up-to-date lxc. However this has not been tested.
|
||||
|
||||
|
||||
Note
|
||||
----
|
||||
Installation
|
||||
---------------
|
||||
|
||||
We also keep the documentation in this repository. The website documentation is generated using sphinx using these sources.
|
||||
Please find it under docs/sources/ and read more about it https://github.com/dotcloud/docker/tree/master/docs/README.md
|
||||
1. Set up your host of choice on a physical / virtual machine
|
||||
2. Assume root identity on your newly installed environment (`sudo -s`)
|
||||
3. Type the following commands:
|
||||
|
||||
Please feel free to fix / update the documentation and send us pull requests. More tutorials are also welcome.
|
||||
apt-get update
|
||||
apt-get install lxc wget bsdtar curl
|
||||
|
||||
4. Download the latest docker binaries: `wget http://docker.io.s3.amazonaws.com/builds/$(uname -s)/$(uname -m)/docker-master.tgz` ([Or get the Linux/x86_64 binaries here](http://docker.io.s3.amazonaws.com/builds/Linux/x86_64/docker-master.tgz) )
|
||||
5. Extract the contents of the tar file `tar -xf docker-master.tar.gz`
|
||||
6. Launch the docker daemon in the background `./dockerd &`
|
||||
7. Download a base image `./docker pull base`
|
||||
8. Run your first container! `./docker run -i -a -t base /bin/bash`
|
||||
9. Start exploring `./docker --help`
|
||||
|
||||
Setting up a dev environment
|
||||
----------------------------
|
||||
|
||||
Instructions that have been verified to work on Ubuntu 12.10,
|
||||
|
||||
```bash
|
||||
sudo apt-get -y install lxc curl xz-utils golang git
|
||||
|
||||
export GOPATH=~/go/
|
||||
export PATH=$GOPATH/bin:$PATH
|
||||
|
||||
mkdir -p $GOPATH/src/github.com/dotcloud
|
||||
cd $GOPATH/src/github.com/dotcloud
|
||||
git clone https://github.com/dotcloud/docker.git
|
||||
cd docker
|
||||
|
||||
go get -v github.com/dotcloud/docker/...
|
||||
go install -v github.com/dotcloud/docker/...
|
||||
```
|
||||
|
||||
Then run the docker daemon,
|
||||
|
||||
```bash
|
||||
sudo $GOPATH/bin/docker -d
|
||||
```
|
||||
|
||||
Run the `go install` command (above) to recompile docker.
|
||||
Consider adding docker and dockerd to your `PATH` for simplicity.
|
||||
|
||||
|
||||
What is a Standard Container?
|
||||
=============================
|
||||
-----------------------------
|
||||
|
||||
Docker defines a unit of software delivery called a Standard Container. The goal of a Standard Container is to encapsulate a software component and all its dependencies in
|
||||
a format that is self-describing and portable, so that any compliant runtime can run it without extra dependencies, regardless of the underlying machine and the contents of the container.
|
||||
a format that is self-describing and portable, so that any compliant runtime can run it without extra dependency, regardless of the underlying machine and the contents of the container.
|
||||
|
||||
The spec for Standard Containers is currently a work in progress, but it is very straightforward. It mostly defines 1) an image format, 2) a set of standard operations, and 3) an execution environment.
|
||||
The spec for Standard Containers is currently work in progress, but it is very straightforward. It mostly defines 1) an image format, 2) a set of standard operations, and 3) an execution environment.
|
||||
|
||||
A great analogy for this is the shipping container. Just like how Standard Containers are a fundamental unit of software delivery, shipping containers (http://bricks.argz.com/ins/7823-1/12) are a fundamental unit of physical delivery.
|
||||
A great analogy for this is the shipping container. Just like Standard Containers are a fundamental unit of software delivery, shipping containers (http://bricks.argz.com/ins/7823-1/12) are a fundamental unit of physical delivery.
|
||||
|
||||
### 1. STANDARD OPERATIONS
|
||||
|
||||
@@ -303,7 +101,7 @@ Just like shipping containers, Standard Containers define a set of STANDARD OPER
|
||||
|
||||
### 2. CONTENT-AGNOSTIC
|
||||
|
||||
Just like shipping containers, Standard Containers are CONTENT-AGNOSTIC: all standard operations have the same effect regardless of the contents. A shipping container will be stacked in exactly the same way whether it contains Vietnamese powder coffee or spare Maserati parts. Similarly, Standard Containers are started or uploaded in the same way whether they contain a postgres database, a php application with its dependencies and application server, or Java build artifacts.
|
||||
Just like shipping containers, Standard Containers are CONTENT-AGNOSTIC: all standard operations have the same effect regardless of the contents. A shipping container will be stacked in exactly the same way whether it contains Vietnamese powder coffe or spare Maserati parts. Similarly, Standard Containers are started or uploaded in the same way whether they contain a postgres database, a php application with its dependencies and application server, or Java build artifacts.
|
||||
|
||||
|
||||
### 3. INFRASTRUCTURE-AGNOSTIC
|
||||
@@ -322,7 +120,7 @@ Similarly, before Standard Containers, by the time a software component ran in p
|
||||
|
||||
### 5. INDUSTRIAL-GRADE DELIVERY
|
||||
|
||||
There are 17 million shipping containers in existence, packed with every physical good imaginable. Every single one of them can be loaded onto the same boats, by the same cranes, in the same facilities, and sent anywhere in the World with incredible efficiency. It is embarrassing to think that a 30 ton shipment of coffee can safely travel half-way across the World in *less time* than it takes a software team to deliver its code from one datacenter to another sitting 10 miles away.
|
||||
There are 17 million shipping containers in existence, packed with every physical good imaginable. Every single one of them can be loaded on the same boats, by the same cranes, in the same facilities, and sent anywhere in the World with incredible efficiency. It is embarrassing to think that a 30 ton shipment of coffee can safely travel half-way across the World in *less time* than it takes a software team to deliver its code from one datacenter to another sitting 10 miles away.
|
||||
|
||||
With Standard Containers we can put an end to that embarrassment, by making INDUSTRIAL-GRADE DELIVERY of software a reality.
|
||||
|
||||
@@ -372,10 +170,4 @@ Standard Container Specification
|
||||
|
||||
#### Security
|
||||
|
||||
### Legal
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Vendored
+93
-89
@@ -1,96 +1,100 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
BOX_NAME = ENV['BOX_NAME'] || "ubuntu"
|
||||
BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64.box"
|
||||
VF_BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64_vmware_fusion.box"
|
||||
AWS_REGION = ENV['AWS_REGION'] || "us-east-1"
|
||||
AWS_AMI = ENV['AWS_AMI'] || "ami-d0f89fb9"
|
||||
FORWARD_DOCKER_PORTS = ENV['FORWARD_DOCKER_PORTS']
|
||||
|
||||
Vagrant::Config.run do |config|
|
||||
# Setup virtual machine box. This VM configuration code is always executed.
|
||||
config.vm.box = BOX_NAME
|
||||
config.vm.box_url = BOX_URI
|
||||
config.vm.forward_port 4243, 4243
|
||||
# All Vagrant configuration is done here. The most common configuration
|
||||
# options are documented and commented below. For a complete reference,
|
||||
# please see the online documentation at vagrantup.com.
|
||||
|
||||
# Provision docker and new kernel if deployment was not done
|
||||
if Dir.glob("#{File.dirname(__FILE__)}/.vagrant/machines/default/*/id").empty?
|
||||
# Add lxc-docker package
|
||||
pkg_cmd = "apt-get update -qq; apt-get install -q -y python-software-properties; " \
|
||||
"add-apt-repository -y ppa:dotcloud/lxc-docker; apt-get update -qq; " \
|
||||
"apt-get install -q -y lxc-docker; "
|
||||
# Add X.org Ubuntu backported 3.8 kernel
|
||||
pkg_cmd << "add-apt-repository -y ppa:ubuntu-x-swat/r-lts-backport; " \
|
||||
"apt-get update -qq; apt-get install -q -y linux-image-3.8.0-19-generic; "
|
||||
# Add guest additions if local vbox VM
|
||||
is_vbox = true
|
||||
ARGV.each do |arg| is_vbox &&= !arg.downcase.start_with?("--provider") end
|
||||
if is_vbox
|
||||
pkg_cmd << "apt-get install -q -y linux-headers-3.8.0-19-generic dkms; " \
|
||||
"echo 'Downloading VBox Guest Additions...'; " \
|
||||
"wget -q http://dlc.sun.com.edgesuite.net/virtualbox/4.2.12/VBoxGuestAdditions_4.2.12.iso; "
|
||||
# Prepare the VM to add guest additions after reboot
|
||||
pkg_cmd << "echo -e 'mount -o loop,ro /home/vagrant/VBoxGuestAdditions_4.2.12.iso /mnt\n" \
|
||||
"echo yes | /mnt/VBoxLinuxAdditions.run\numount /mnt\n" \
|
||||
"rm /root/guest_additions.sh; ' > /root/guest_additions.sh; " \
|
||||
"chmod 700 /root/guest_additions.sh; " \
|
||||
"sed -i -E 's#^exit 0#[ -x /root/guest_additions.sh ] \\&\\& /root/guest_additions.sh#' /etc/rc.local; " \
|
||||
"echo 'Installation of VBox Guest Additions is proceeding in the background.'; " \
|
||||
"echo '\"vagrant reload\" can be used in about 2 minutes to activate the new guest additions.'; "
|
||||
end
|
||||
# Activate new kernel
|
||||
pkg_cmd << "shutdown -r +1; "
|
||||
config.vm.provision :shell, :inline => pkg_cmd
|
||||
# Every Vagrant virtual environment requires a box to build off of.
|
||||
config.vm.box = "quantal64_3.5.0-25"
|
||||
|
||||
# The url from where the 'config.vm.box' box will be fetched if it
|
||||
# doesn't already exist on the user's system.
|
||||
config.vm.box_url = "http://get.docker.io/vbox/ubuntu/12.10/quantal64_3.5.0-25.box"
|
||||
|
||||
# Boot with a GUI so you can see the screen. (Default is headless)
|
||||
# config.vm.boot_mode = :gui
|
||||
|
||||
# Assign this VM to a host-only network IP, allowing you to access it
|
||||
# via the IP. Host-only networks can talk to the host machine as well as
|
||||
# any other machines on the same network, but cannot be accessed (through this
|
||||
# network interface) by any external networks.
|
||||
# config.vm.network :hostonly, "192.168.33.10"
|
||||
|
||||
# Assign this VM to a bridged network, allowing you to connect directly to a
|
||||
# network using the host's network device. This makes the VM appear as another
|
||||
# physical device on your network.
|
||||
# config.vm.network :bridged
|
||||
|
||||
# Forward a port from the guest to the host, which allows for outside
|
||||
# computers to access the VM, whereas host only networking does not.
|
||||
# config.vm.forward_port 80, 8080
|
||||
|
||||
# Share an additional folder to the guest VM. The first argument is
|
||||
# an identifier, the second is the path on the guest to mount the
|
||||
# folder, and the third is the path on the host to the actual folder.
|
||||
# config.vm.share_folder "v-data", "/vagrant_data", "../data"
|
||||
|
||||
# Enable provisioning with Puppet stand alone. Puppet manifests
|
||||
# are contained in a directory path relative to this Vagrantfile.
|
||||
# You will need to create the manifests directory and a manifest in
|
||||
# the file quantal64.pp in the manifests_path directory.
|
||||
#
|
||||
# An example Puppet manifest to provision the message of the day:
|
||||
#
|
||||
# # group { "puppet":
|
||||
# # ensure => "present",
|
||||
# # }
|
||||
# #
|
||||
# # File { owner => 0, group => 0, mode => 0644 }
|
||||
# #
|
||||
# # file { '/etc/motd':
|
||||
# # content => "Welcome to your Vagrant-built virtual machine!
|
||||
# # Managed by Puppet.\n"
|
||||
# # }
|
||||
#
|
||||
config.vm.provision :puppet do |puppet|
|
||||
puppet.manifests_path = "puppet/manifests"
|
||||
puppet.manifest_file = "quantal64.pp"
|
||||
puppet.module_path = "puppet/modules"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Providers were added on Vagrant >= 1.1.0
|
||||
Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
|
||||
config.vm.provider :aws do |aws, override|
|
||||
aws.access_key_id = ENV["AWS_ACCESS_KEY_ID"]
|
||||
aws.secret_access_key = ENV["AWS_SECRET_ACCESS_KEY"]
|
||||
aws.keypair_name = ENV["AWS_KEYPAIR_NAME"]
|
||||
override.ssh.private_key_path = ENV["AWS_SSH_PRIVKEY"]
|
||||
override.ssh.username = "ubuntu"
|
||||
aws.region = AWS_REGION
|
||||
aws.ami = AWS_AMI
|
||||
aws.instance_type = "t1.micro"
|
||||
end
|
||||
|
||||
config.vm.provider :rackspace do |rs|
|
||||
config.ssh.private_key_path = ENV["RS_PRIVATE_KEY"]
|
||||
rs.username = ENV["RS_USERNAME"]
|
||||
rs.api_key = ENV["RS_API_KEY"]
|
||||
rs.public_key_path = ENV["RS_PUBLIC_KEY"]
|
||||
rs.flavor = /512MB/
|
||||
rs.image = /Ubuntu/
|
||||
end
|
||||
|
||||
config.vm.provider :vmware_fusion do |f, override|
|
||||
override.vm.box = BOX_NAME
|
||||
override.vm.box_url = VF_BOX_URI
|
||||
override.vm.synced_folder ".", "/vagrant", disabled: true
|
||||
f.vmx["displayName"] = "docker"
|
||||
end
|
||||
|
||||
config.vm.provider :virtualbox do |vb|
|
||||
config.vm.box = BOX_NAME
|
||||
config.vm.box_url = BOX_URI
|
||||
end
|
||||
end
|
||||
|
||||
if !FORWARD_DOCKER_PORTS.nil?
|
||||
Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config|
|
||||
(49000..49900).each do |port|
|
||||
config.vm.forward_port port, port
|
||||
end
|
||||
end
|
||||
|
||||
Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
|
||||
(49000..49900).each do |port|
|
||||
config.vm.network :forwarded_port, :host => port, :guest => port
|
||||
end
|
||||
end
|
||||
|
||||
# Enable provisioning with chef solo, specifying a cookbooks path, roles
|
||||
# path, and data_bags path (all relative to this Vagrantfile), and adding
|
||||
# some recipes and/or roles.
|
||||
#
|
||||
# config.vm.provision :chef_solo do |chef|
|
||||
# chef.cookbooks_path = "../my-recipes/cookbooks"
|
||||
# chef.roles_path = "../my-recipes/roles"
|
||||
# chef.data_bags_path = "../my-recipes/data_bags"
|
||||
# chef.add_recipe "mysql"
|
||||
# chef.add_role "web"
|
||||
#
|
||||
# # You may also specify custom JSON attributes:
|
||||
# chef.json = { :mysql_password => "foo" }
|
||||
# end
|
||||
|
||||
# Enable provisioning with chef server, specifying the chef server URL,
|
||||
# and the path to the validation key (relative to this Vagrantfile).
|
||||
#
|
||||
# The Opscode Platform uses HTTPS. Substitute your organization for
|
||||
# ORGNAME in the URL and validation key.
|
||||
#
|
||||
# If you have your own Chef Server, use the appropriate URL, which may be
|
||||
# HTTP instead of HTTPS depending on your configuration. Also change the
|
||||
# validation key to validation.pem.
|
||||
#
|
||||
# config.vm.provision :chef_client do |chef|
|
||||
# chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME"
|
||||
# chef.validation_key_path = "ORGNAME-validator.pem"
|
||||
# end
|
||||
#
|
||||
# If you're using the Opscode platform, your validator client is
|
||||
# ORGNAME-validator, replacing ORGNAME with your organization name.
|
||||
#
|
||||
# IF you have your own Chef Server, the default validation client name is
|
||||
# chef-validator, unless you changed the configuration.
|
||||
#
|
||||
# chef.validation_client_name = "ORGNAME-validator"
|
||||
end
|
||||
|
||||
@@ -1,872 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/auth"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"github.com/gorilla/mux"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const APIVERSION = 1.2
|
||||
const DEFAULTHTTPHOST string = "127.0.0.1"
|
||||
const DEFAULTHTTPPORT int = 4243
|
||||
|
||||
func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
|
||||
conn, _, err := w.(http.Hijacker).Hijack()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// Flush the options to make sure the client sets the raw mode
|
||||
conn.Write([]byte{})
|
||||
return conn, conn, nil
|
||||
}
|
||||
|
||||
//If we don't do this, POST method without Content-type (even with empty body) will fail
|
||||
func parseForm(r *http.Request) error {
|
||||
if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseMultipartForm(r *http.Request) error {
|
||||
if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func httpError(w http.ResponseWriter, err error) {
|
||||
if strings.HasPrefix(err.Error(), "No such") {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
} else if strings.HasPrefix(err.Error(), "Bad parameter") {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
} else if strings.HasPrefix(err.Error(), "Conflict") {
|
||||
http.Error(w, err.Error(), http.StatusConflict)
|
||||
} else if strings.HasPrefix(err.Error(), "Impossible") {
|
||||
http.Error(w, err.Error(), http.StatusNotAcceptable)
|
||||
} else {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, b []byte) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
// FIXME: Use stvconv.ParseBool() instead?
|
||||
func getBoolParam(value string) (bool, error) {
|
||||
if value == "1" || strings.ToLower(value) == "true" {
|
||||
return true, nil
|
||||
}
|
||||
if value == "" || value == "0" || strings.ToLower(value) == "false" {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("Bad parameter")
|
||||
}
|
||||
|
||||
func getAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if version > 1.1 {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return nil
|
||||
}
|
||||
authConfig, err := auth.LoadConfig(srv.runtime.root)
|
||||
if err != nil {
|
||||
if err != auth.ErrConfigFileMissing {
|
||||
return err
|
||||
}
|
||||
authConfig = &auth.AuthConfig{}
|
||||
}
|
||||
b, err := json.Marshal(&auth.AuthConfig{Username: authConfig.Username, Email: authConfig.Email})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
authConfig := &auth.AuthConfig{}
|
||||
err := json.NewDecoder(r.Body).Decode(authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
status := ""
|
||||
if version > 1.1 {
|
||||
status, err = auth.Login(authConfig, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
localAuthConfig, err := auth.LoadConfig(srv.runtime.root)
|
||||
if err != nil {
|
||||
if err != auth.ErrConfigFileMissing {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if authConfig.Username == localAuthConfig.Username {
|
||||
authConfig.Password = localAuthConfig.Password
|
||||
}
|
||||
|
||||
newAuthConfig := auth.NewAuthConfig(authConfig.Username, authConfig.Password, authConfig.Email, srv.runtime.root)
|
||||
status, err = auth.Login(newAuthConfig, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if status != "" {
|
||||
b, err := json.Marshal(&APIAuth{Status: status})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getVersion(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
m := srv.DockerVersion()
|
||||
b, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postContainersKill(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
if err := srv.ContainerKill(name); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getContainersExport(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
|
||||
if err := srv.ContainerExport(name, w); err != nil {
|
||||
utils.Debugf("%s", err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
all, err := getBoolParam(r.Form.Get("all"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filter := r.Form.Get("filter")
|
||||
|
||||
outs, err := srv.Images(all, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(outs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getImagesViz(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := srv.ImagesViz(w); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
out := srv.DockerInfo()
|
||||
b, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getImagesHistory(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
outs, err := srv.ImageHistory(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(outs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getContainersChanges(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
changesStr, err := srv.ContainerChanges(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(changesStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getContainersJSON(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
all, err := getBoolParam(r.Form.Get("all"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
since := r.Form.Get("since")
|
||||
before := r.Form.Get("before")
|
||||
n, err := strconv.Atoi(r.Form.Get("limit"))
|
||||
if err != nil {
|
||||
n = -1
|
||||
}
|
||||
|
||||
outs := srv.Containers(all, n, since, before)
|
||||
b, err := json.Marshal(outs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postImagesTag(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
repo := r.Form.Get("repo")
|
||||
tag := r.Form.Get("tag")
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
force, err := getBoolParam(r.Form.Get("force"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := srv.ContainerTag(name, repo, tag, force); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postCommit(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
config := &Config{}
|
||||
if err := json.NewDecoder(r.Body).Decode(config); err != nil {
|
||||
utils.Debugf("%s", err.Error())
|
||||
}
|
||||
repo := r.Form.Get("repo")
|
||||
tag := r.Form.Get("tag")
|
||||
container := r.Form.Get("container")
|
||||
author := r.Form.Get("author")
|
||||
comment := r.Form.Get("comment")
|
||||
id, err := srv.ContainerCommit(container, repo, tag, author, comment, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(&APIID{id})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Creates an image from Pull or from Import
|
||||
func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
src := r.Form.Get("fromSrc")
|
||||
image := r.Form.Get("fromImage")
|
||||
tag := r.Form.Get("tag")
|
||||
repo := r.Form.Get("repo")
|
||||
|
||||
if version > 1.0 {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
}
|
||||
sf := utils.NewStreamFormatter(version > 1.0)
|
||||
if image != "" { //pull
|
||||
registry := r.Form.Get("registry")
|
||||
if err := srv.ImagePull(image, tag, registry, w, sf, &auth.AuthConfig{}); err != nil {
|
||||
if sf.Used() {
|
||||
w.Write(sf.FormatError(err))
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
} else { //import
|
||||
if err := srv.ImageImport(src, repo, tag, r.Body, w, sf); err != nil {
|
||||
if sf.Used() {
|
||||
w.Write(sf.FormatError(err))
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getImagesSearch(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
term := r.Form.Get("term")
|
||||
outs, err := srv.ImagesSearch(term)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(outs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
url := r.Form.Get("url")
|
||||
path := r.Form.Get("path")
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
if version > 1.0 {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
}
|
||||
sf := utils.NewStreamFormatter(version > 1.0)
|
||||
imgID, err := srv.ImageInsert(name, url, path, w, sf)
|
||||
if err != nil {
|
||||
if sf.Used() {
|
||||
w.Write(sf.FormatError(err))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
b, err := json.Marshal(&APIID{ID: imgID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
authConfig := &auth.AuthConfig{}
|
||||
if version > 1.1 {
|
||||
if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
localAuthConfig, err := auth.LoadConfig(srv.runtime.root)
|
||||
if err != nil && err != auth.ErrConfigFileMissing {
|
||||
return err
|
||||
}
|
||||
authConfig = localAuthConfig
|
||||
}
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
registry := r.Form.Get("registry")
|
||||
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
if version > 1.0 {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
}
|
||||
sf := utils.NewStreamFormatter(version > 1.0)
|
||||
if err := srv.ImagePush(name, registry, w, sf, authConfig); err != nil {
|
||||
if sf.Used() {
|
||||
w.Write(sf.FormatError(err))
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
config := &Config{}
|
||||
out := &APIRun{}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(config.Dns) == 0 && len(srv.runtime.Dns) == 0 && utils.CheckLocalDns() {
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out.ID = id
|
||||
|
||||
if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
|
||||
log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.")
|
||||
out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.")
|
||||
}
|
||||
if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit {
|
||||
log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.")
|
||||
out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.")
|
||||
}
|
||||
|
||||
b, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postContainersRestart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
t, err := strconv.Atoi(r.Form.Get("t"))
|
||||
if err != nil || t < 0 {
|
||||
t = 10
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
if err := srv.ContainerRestart(name, t); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteContainers(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
removeVolume, err := getBoolParam(r.Form.Get("v"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := srv.ContainerDestroy(name, removeVolume); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
imgs, err := srv.ImageDelete(name, version > 1.1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if imgs != nil {
|
||||
if len(*imgs) != 0 {
|
||||
b, err := json.Marshal(imgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
} else {
|
||||
return fmt.Errorf("Conflict, %s wasn't deleted", name)
|
||||
}
|
||||
} else {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
if err := srv.ContainerStart(name); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postContainersStop(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
t, err := strconv.Atoi(r.Form.Get("t"))
|
||||
if err != nil || t < 0 {
|
||||
t = 10
|
||||
}
|
||||
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
|
||||
if err := srv.ContainerStop(name, t); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postContainersWait(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
status, err := srv.ContainerWait(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(&APIWait{StatusCode: status})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postContainersResize(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
height, err := strconv.Atoi(r.Form.Get("h"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
width, err := strconv.Atoi(r.Form.Get("w"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
if err := srv.ContainerResize(name, height, width); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
logs, err := getBoolParam(r.Form.Get("logs"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stream, err := getBoolParam(r.Form.Get("stream"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stdin, err := getBoolParam(r.Form.Get("stdin"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stdout, err := getBoolParam(r.Form.Get("stdout"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stderr, err := getBoolParam(r.Form.Get("stderr"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
|
||||
if _, err := srv.ContainerInspect(name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
in, out, err := hijackServer(w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.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)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getContainersByName(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
|
||||
container, err := srv.ContainerInspect(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(container)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getImagesByName(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
|
||||
image, err := srv.ImageInspect(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postImagesGetCache(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
apiConfig := &APIImageConfig{}
|
||||
if err := json.NewDecoder(r.Body).Decode(apiConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
image, err := srv.ImageGetCached(apiConfig.ID, apiConfig.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if image == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return nil
|
||||
}
|
||||
apiID := &APIID{ID: image.ID}
|
||||
b, err := json.Marshal(apiID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := r.ParseMultipartForm(4096); err != nil {
|
||||
return err
|
||||
}
|
||||
remote := r.FormValue("t")
|
||||
tag := ""
|
||||
if strings.Contains(remote, ":") {
|
||||
remoteParts := strings.Split(remote, ":")
|
||||
tag = remoteParts[1]
|
||||
remote = remoteParts[0]
|
||||
}
|
||||
|
||||
dockerfile, _, err := r.FormFile("Dockerfile")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
context, _, err := r.FormFile("Context")
|
||||
if err != nil {
|
||||
if err != http.ErrMissingFile {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
b := NewBuildFile(srv, utils.NewWriteFlusher(w))
|
||||
if id, err := b.Build(dockerfile, context); err != nil {
|
||||
fmt.Fprintf(w, "Error build: %s\n", err)
|
||||
} else if remote != "" {
|
||||
srv.runtime.repositories.Set(remote, tag, id, false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func optionsHandler(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return nil
|
||||
}
|
||||
func writeCorsHeaders(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
|
||||
w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS")
|
||||
}
|
||||
|
||||
func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
||||
r := mux.NewRouter()
|
||||
|
||||
m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{
|
||||
"GET": {
|
||||
"/auth": getAuth,
|
||||
"/version": getVersion,
|
||||
"/info": getInfo,
|
||||
"/images/json": getImagesJSON,
|
||||
"/images/viz": getImagesViz,
|
||||
"/images/search": getImagesSearch,
|
||||
"/images/{name:.*}/history": getImagesHistory,
|
||||
"/images/{name:.*}/json": getImagesByName,
|
||||
"/containers/ps": getContainersJSON,
|
||||
"/containers/json": getContainersJSON,
|
||||
"/containers/{name:.*}/export": getContainersExport,
|
||||
"/containers/{name:.*}/changes": getContainersChanges,
|
||||
"/containers/{name:.*}/json": getContainersByName,
|
||||
},
|
||||
"POST": {
|
||||
"/auth": postAuth,
|
||||
"/commit": postCommit,
|
||||
"/build": postBuild,
|
||||
"/images/create": postImagesCreate,
|
||||
"/images/{name:.*}/insert": postImagesInsert,
|
||||
"/images/{name:.*}/push": postImagesPush,
|
||||
"/images/{name:.*}/tag": postImagesTag,
|
||||
"/images/getCache": postImagesGetCache,
|
||||
"/containers/create": postContainersCreate,
|
||||
"/containers/{name:.*}/kill": postContainersKill,
|
||||
"/containers/{name:.*}/restart": postContainersRestart,
|
||||
"/containers/{name:.*}/start": postContainersStart,
|
||||
"/containers/{name:.*}/stop": postContainersStop,
|
||||
"/containers/{name:.*}/wait": postContainersWait,
|
||||
"/containers/{name:.*}/resize": postContainersResize,
|
||||
"/containers/{name:.*}/attach": postContainersAttach,
|
||||
},
|
||||
"DELETE": {
|
||||
"/containers/{name:.*}": deleteContainers,
|
||||
"/images/{name:.*}": deleteImages,
|
||||
},
|
||||
"OPTIONS": {
|
||||
"": optionsHandler,
|
||||
},
|
||||
}
|
||||
|
||||
for method, routes := range m {
|
||||
for route, fct := range routes {
|
||||
utils.Debugf("Registering %s, %s", method, route)
|
||||
// NOTE: scope issue, make sure the variables are local and won't be changed
|
||||
localRoute := route
|
||||
localMethod := method
|
||||
localFct := fct
|
||||
f := func(w http.ResponseWriter, r *http.Request) {
|
||||
utils.Debugf("Calling %s %s", localMethod, localRoute)
|
||||
if logging {
|
||||
log.Println(r.Method, r.RequestURI)
|
||||
}
|
||||
if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
|
||||
userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
|
||||
if len(userAgent) == 2 && userAgent[1] != VERSION {
|
||||
utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], VERSION)
|
||||
}
|
||||
}
|
||||
version, err := strconv.ParseFloat(mux.Vars(r)["version"], 64)
|
||||
if err != nil {
|
||||
version = APIVERSION
|
||||
}
|
||||
if srv.enableCors {
|
||||
writeCorsHeaders(w, r)
|
||||
}
|
||||
if version == 0 || version > APIVERSION {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if err := localFct(srv, version, w, r, mux.Vars(r)); err != nil {
|
||||
httpError(w, err)
|
||||
}
|
||||
}
|
||||
|
||||
if localRoute == "" {
|
||||
r.Methods(localMethod).HandlerFunc(f)
|
||||
} else {
|
||||
r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f)
|
||||
r.Path(localRoute).Methods(localMethod).HandlerFunc(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func ListenAndServe(proto, addr string, srv *Server, logging bool) error {
|
||||
log.Printf("Listening for HTTP on %s (%s)\n", addr, proto)
|
||||
|
||||
r, err := createRouter(srv, logging)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l, e := net.Listen(proto, addr)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
//as the daemon is launched as root, change to permission of the socket to allow non-root to connect
|
||||
if proto == "unix" {
|
||||
os.Chmod(addr, 0777)
|
||||
}
|
||||
httpSrv := http.Server{Addr: addr, Handler: r}
|
||||
return httpSrv.Serve(l)
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package docker
|
||||
|
||||
type APIHistory struct {
|
||||
ID string `json:"Id"`
|
||||
Tags []string `json:",omitempty"`
|
||||
Created int64
|
||||
CreatedBy string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APIImages struct {
|
||||
Repository string `json:",omitempty"`
|
||||
Tag string `json:",omitempty"`
|
||||
ID string `json:"Id"`
|
||||
Created int64
|
||||
Size int64
|
||||
VirtualSize int64
|
||||
}
|
||||
|
||||
type APIInfo struct {
|
||||
Debug bool
|
||||
Containers int
|
||||
Images int
|
||||
NFd int `json:",omitempty"`
|
||||
NGoroutines int `json:",omitempty"`
|
||||
MemoryLimit bool `json:",omitempty"`
|
||||
SwapLimit bool `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APIRmi struct {
|
||||
Deleted string `json:",omitempty"`
|
||||
Untagged string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APIContainers struct {
|
||||
ID string `json:"Id"`
|
||||
Image string
|
||||
Command string
|
||||
Created int64
|
||||
Status string
|
||||
Ports string
|
||||
SizeRw int64
|
||||
SizeRootFs int64
|
||||
}
|
||||
|
||||
type APISearch struct {
|
||||
Name string
|
||||
Description string
|
||||
}
|
||||
|
||||
type APIID struct {
|
||||
ID string `json:"Id"`
|
||||
}
|
||||
|
||||
type APIRun struct {
|
||||
ID string `json:"Id"`
|
||||
Warnings []string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APIPort struct {
|
||||
Port string
|
||||
}
|
||||
|
||||
type APIVersion struct {
|
||||
Version string
|
||||
GitCommit string `json:",omitempty"`
|
||||
GoVersion string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APIWait struct {
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
type APIAuth struct {
|
||||
Status string
|
||||
}
|
||||
|
||||
type APIImageConfig struct {
|
||||
ID string `json:"Id"`
|
||||
*Config
|
||||
}
|
||||
-1353
File diff suppressed because it is too large
Load Diff
-285
@@ -1,285 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
)
|
||||
|
||||
type Archive io.Reader
|
||||
|
||||
type Compression uint32
|
||||
|
||||
const (
|
||||
Uncompressed Compression = iota
|
||||
Bzip2
|
||||
Gzip
|
||||
Xz
|
||||
)
|
||||
|
||||
func DetectCompression(source []byte) Compression {
|
||||
for _, c := range source[:10] {
|
||||
utils.Debugf("%x", c)
|
||||
}
|
||||
|
||||
sourceLen := len(source)
|
||||
for compression, m := range map[Compression][]byte{
|
||||
Bzip2: {0x42, 0x5A, 0x68},
|
||||
Gzip: {0x1F, 0x8B, 0x08},
|
||||
Xz: {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00},
|
||||
} {
|
||||
fail := false
|
||||
if len(m) > sourceLen {
|
||||
utils.Debugf("Len too short")
|
||||
continue
|
||||
}
|
||||
i := 0
|
||||
for _, b := range m {
|
||||
if b != source[i] {
|
||||
fail = true
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
if !fail {
|
||||
return compression
|
||||
}
|
||||
}
|
||||
return Uncompressed
|
||||
}
|
||||
|
||||
func (compression *Compression) Flag() string {
|
||||
switch *compression {
|
||||
case Bzip2:
|
||||
return "j"
|
||||
case Gzip:
|
||||
return "z"
|
||||
case Xz:
|
||||
return "J"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (compression *Compression) Extension() string {
|
||||
switch *compression {
|
||||
case Uncompressed:
|
||||
return "tar"
|
||||
case Bzip2:
|
||||
return "tar.bz2"
|
||||
case Gzip:
|
||||
return "tar.gz"
|
||||
case Xz:
|
||||
return "tar.xz"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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", "-f", "-", "-C", path}
|
||||
if filter == nil {
|
||||
filter = []string{"."}
|
||||
}
|
||||
for _, f := range filter {
|
||||
args = append(args, "-c"+compression.Flag(), f)
|
||||
}
|
||||
return CmdStream(exec.Command(args[0], args[1:]...))
|
||||
}
|
||||
|
||||
// Untar reads a stream of bytes from `archive`, parses it as a tar archive,
|
||||
// and unpacks it into the directory at `path`.
|
||||
// The archive may be compressed with one of the following algorithgms:
|
||||
// identity (uncompressed), gzip, bzip2, xz.
|
||||
// FIXME: specify behavior when target path exists vs. doesn't exist.
|
||||
func Untar(archive io.Reader, path string) error {
|
||||
|
||||
bufferedArchive := bufio.NewReaderSize(archive, 10)
|
||||
buf, err := bufferedArchive.Peek(10)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
compression := DetectCompression(buf)
|
||||
|
||||
utils.Debugf("Archive compression detected: %s", compression.Extension())
|
||||
|
||||
cmd := exec.Command("tar", "-f", "-", "-C", path, "-x"+compression.Flag())
|
||||
cmd.Stdin = bufferedArchive
|
||||
// Hardcode locale environment for predictable outcome regardless of host configuration.
|
||||
// (see https://github.com/dotcloud/docker/issues/355)
|
||||
cmd.Env = []string{"LANG=en_US.utf-8", "LC_ALL=en_US.utf-8"}
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %s", err, output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TarUntar is a convenience function which calls Tar and Untar, with
|
||||
// the output of one piped into the other. If either Tar or Untar fails,
|
||||
// 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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return Untar(archive, dst)
|
||||
}
|
||||
|
||||
// UntarPath is a convenience function which looks for an archive
|
||||
// at filesystem path `src`, and unpacks it at `dst`.
|
||||
func UntarPath(src, dst string) error {
|
||||
if archive, err := os.Open(src); err != nil {
|
||||
return err
|
||||
} else if err := Untar(archive, dst); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyWithTar creates a tar archive of filesystem path `src`, and
|
||||
// unpacks it at filesystem path `dst`.
|
||||
// The archive is streamed directly with fixed buffering and no
|
||||
// intermediary disk IO.
|
||||
//
|
||||
func CopyWithTar(src, dst string) error {
|
||||
srcSt, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var dstExists bool
|
||||
dstSt, err := os.Stat(dst)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
dstExists = true
|
||||
}
|
||||
// Things that can go wrong if the source is a directory
|
||||
if srcSt.IsDir() {
|
||||
// The destination exists and is a regular file
|
||||
if dstExists && !dstSt.IsDir() {
|
||||
return fmt.Errorf("Can't copy a directory over a regular file")
|
||||
}
|
||||
// Things that can go wrong if the source is a regular file
|
||||
} else {
|
||||
utils.Debugf("The destination exists, it's a directory, and doesn't end in /")
|
||||
// The destination exists, it's a directory, and doesn't end in /
|
||||
if dstExists && dstSt.IsDir() && dst[len(dst)-1] != '/' {
|
||||
return fmt.Errorf("Can't copy a regular file over a directory %s |%s|", dst, dst[len(dst)-1])
|
||||
}
|
||||
}
|
||||
// Create the destination
|
||||
var dstDir string
|
||||
if srcSt.IsDir() || dst[len(dst)-1] == '/' {
|
||||
// The destination ends in /, or the source is a directory
|
||||
// --> dst is the holding directory and needs to be created for -C
|
||||
dstDir = dst
|
||||
} else {
|
||||
// The destination doesn't end in /
|
||||
// --> dst is the file
|
||||
dstDir = path.Dir(dst)
|
||||
}
|
||||
if !dstExists {
|
||||
// Create the holding directory if necessary
|
||||
utils.Debugf("Creating the holding directory %s", dstDir)
|
||||
if err := os.MkdirAll(dstDir, 0700); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !srcSt.IsDir() {
|
||||
return TarUntar(path.Dir(src), []string{path.Base(src)}, dstDir)
|
||||
}
|
||||
return TarUntar(src, nil, dstDir)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pipeR, pipeW := io.Pipe()
|
||||
errChan := make(chan []byte)
|
||||
// Collect stderr, we will use it in case of an error
|
||||
go func() {
|
||||
errText, e := ioutil.ReadAll(stderr)
|
||||
if e != nil {
|
||||
errText = []byte("(...couldn't fetch stderr: " + e.Error() + ")")
|
||||
}
|
||||
errChan <- errText
|
||||
}()
|
||||
// Copy stdout to the returned pipe
|
||||
go func() {
|
||||
_, err := io.Copy(pipeW, stdout)
|
||||
if err != nil {
|
||||
pipeW.CloseWithError(err)
|
||||
}
|
||||
errText := <-errChan
|
||||
if err := cmd.Wait(); err != nil {
|
||||
pipeW.CloseWithError(errors.New(err.Error() + ": " + string(errText)))
|
||||
} else {
|
||||
pipeW.Close()
|
||||
}
|
||||
}()
|
||||
// Run the command and return the pipe
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pipeR, nil
|
||||
}
|
||||
|
||||
// NewTempArchive reads the content of src into a temporary file, and returns the contents
|
||||
// of that file as an archive. The archive can only be read once - as soon as reading completes,
|
||||
// the file will be deleted.
|
||||
func NewTempArchive(src Archive, dir string) (*TempArchive, error) {
|
||||
f, err := ioutil.TempFile(dir, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := io.Copy(f, src); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := f.Seek(0, 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
st, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
size := st.Size()
|
||||
return &TempArchive{f, size}, nil
|
||||
}
|
||||
|
||||
type TempArchive struct {
|
||||
*os.File
|
||||
Size int64 // Pre-computed from Stat().Size() as a convenience
|
||||
}
|
||||
|
||||
func (archive *TempArchive) Read(data []byte) (int, error) {
|
||||
n, err := archive.File.Read(data)
|
||||
if err != nil {
|
||||
os.Remove(archive.File.Name())
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
-118
@@ -1,118 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start command: " + err.Error())
|
||||
}
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
_, err := io.Copy(ioutil.Discard, out)
|
||||
errCh <- err
|
||||
}()
|
||||
select {
|
||||
case err := <-errCh:
|
||||
if err != nil {
|
||||
t.Fatalf("Command should not have failed (err=%s...)", err.Error()[:100])
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatalf("Command did not complete in 5 seconds; probable deadlock")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start command: " + err.Error())
|
||||
}
|
||||
if output, err := ioutil.ReadAll(out); err == nil {
|
||||
t.Fatalf("Command should have failed")
|
||||
} else if err.Error() != "exit status 1: error couldn't reverse the phase pulser\n" {
|
||||
t.Fatalf("Wrong error value (%s)", err.Error())
|
||||
} else if s := string(output); s != "hello\n" {
|
||||
t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCmdStreamGood(t *testing.T) {
|
||||
cmd := exec.Command("/bin/sh", "-c", "echo hello; exit 0")
|
||||
out, err := CmdStream(cmd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if output, err := ioutil.ReadAll(out); err != nil {
|
||||
t.Fatalf("Command should not have failed (err=%s)", err)
|
||||
} else if s := string(output); s != "hello\n" {
|
||||
t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
|
||||
}
|
||||
}
|
||||
|
||||
func tarUntar(t *testing.T, origin string, compression Compression) error {
|
||||
archive, err := Tar(origin, compression)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
buf := make([]byte, 10)
|
||||
if _, err := archive.Read(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
archive = io.MultiReader(bytes.NewReader(buf), archive)
|
||||
|
||||
detectedCompression := DetectCompression(buf)
|
||||
if detectedCompression.Extension() != compression.Extension() {
|
||||
return fmt.Errorf("Wrong compression detected. Actual compression: %s, found %s", compression.Extension(), detectedCompression.Extension())
|
||||
}
|
||||
|
||||
tmp, err := ioutil.TempDir("", "docker-test-untar")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
if err := Untar(archive, tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := os.Stat(tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestTarUntar(t *testing.T) {
|
||||
origin, err := ioutil.TempDir("", "docker-test-untar-origin")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(origin)
|
||||
if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(origin, "2"), []byte("welcome!"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, c := range []Compression{
|
||||
Uncompressed,
|
||||
Gzip,
|
||||
Bzip2,
|
||||
Xz,
|
||||
} {
|
||||
if err := tarUntar(t, origin, c); err != nil {
|
||||
t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
../registry/MAINTAINERS
|
||||
-193
@@ -1,193 +0,0 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Where we store the config file
|
||||
const CONFIGFILE = ".dockercfg"
|
||||
|
||||
// the registry server we want to login against
|
||||
const INDEXSERVER = "https://index.docker.io/v1"
|
||||
|
||||
//const INDEXSERVER = "http://indexstaging-docker.dotcloud.com/"
|
||||
|
||||
var (
|
||||
ErrConfigFileMissing = errors.New("The Auth config file is missing")
|
||||
)
|
||||
|
||||
type AuthConfig struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Email string `json:"email"`
|
||||
rootPath string
|
||||
}
|
||||
|
||||
func NewAuthConfig(username, password, email, rootPath string) *AuthConfig {
|
||||
return &AuthConfig{
|
||||
Username: username,
|
||||
Password: password,
|
||||
Email: email,
|
||||
rootPath: rootPath,
|
||||
}
|
||||
}
|
||||
|
||||
func IndexServerAddress() string {
|
||||
if os.Getenv("DOCKER_INDEX_URL") != "" {
|
||||
return os.Getenv("DOCKER_INDEX_URL") + "/v1"
|
||||
}
|
||||
return INDEXSERVER
|
||||
}
|
||||
|
||||
// create a base64 encoded auth string to store in config
|
||||
func encodeAuth(authConfig *AuthConfig) string {
|
||||
authStr := authConfig.Username + ":" + authConfig.Password
|
||||
msg := []byte(authStr)
|
||||
encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg)))
|
||||
base64.StdEncoding.Encode(encoded, msg)
|
||||
return string(encoded)
|
||||
}
|
||||
|
||||
// decode the auth string
|
||||
func decodeAuth(authStr string) (*AuthConfig, error) {
|
||||
decLen := base64.StdEncoding.DecodedLen(len(authStr))
|
||||
decoded := make([]byte, decLen)
|
||||
authByte := []byte(authStr)
|
||||
n, err := base64.StdEncoding.Decode(decoded, authByte)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n > decLen {
|
||||
return nil, fmt.Errorf("Something went wrong decoding auth config")
|
||||
}
|
||||
arr := strings.Split(string(decoded), ":")
|
||||
if len(arr) != 2 {
|
||||
return nil, fmt.Errorf("Invalid auth configuration file")
|
||||
}
|
||||
password := strings.Trim(arr[1], "\x00")
|
||||
return &AuthConfig{Username: arr[0], Password: password}, nil
|
||||
|
||||
}
|
||||
|
||||
// load up the auth config information and return values
|
||||
// FIXME: use the internal golang config parser
|
||||
func LoadConfig(rootPath string) (*AuthConfig, error) {
|
||||
confFile := path.Join(rootPath, CONFIGFILE)
|
||||
if _, err := os.Stat(confFile); err != nil {
|
||||
return &AuthConfig{rootPath: rootPath}, ErrConfigFileMissing
|
||||
}
|
||||
b, err := ioutil.ReadFile(confFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arr := strings.Split(string(b), "\n")
|
||||
if len(arr) < 2 {
|
||||
return nil, fmt.Errorf("The Auth config file is empty")
|
||||
}
|
||||
origAuth := strings.Split(arr[0], " = ")
|
||||
origEmail := strings.Split(arr[1], " = ")
|
||||
authConfig, err := decodeAuth(origAuth[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authConfig.Email = origEmail[1]
|
||||
authConfig.rootPath = rootPath
|
||||
return authConfig, nil
|
||||
}
|
||||
|
||||
// save the auth config
|
||||
func SaveConfig(authConfig *AuthConfig) error {
|
||||
confFile := path.Join(authConfig.rootPath, CONFIGFILE)
|
||||
if len(authConfig.Email) == 0 {
|
||||
os.Remove(confFile)
|
||||
return nil
|
||||
}
|
||||
lines := "auth = " + encodeAuth(authConfig) + "\n" + "email = " + authConfig.Email + "\n"
|
||||
b := []byte(lines)
|
||||
err := ioutil.WriteFile(confFile, b, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// try to register/login to the registry server
|
||||
func Login(authConfig *AuthConfig, store bool) (string, error) {
|
||||
storeConfig := false
|
||||
client := &http.Client{}
|
||||
reqStatusCode := 0
|
||||
var status string
|
||||
var reqBody []byte
|
||||
jsonBody, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Config Error: %s", err)
|
||||
}
|
||||
|
||||
// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
|
||||
b := strings.NewReader(string(jsonBody))
|
||||
req1, err := http.Post(IndexServerAddress()+"/users/", "application/json; charset=utf-8", b)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Server Error: %s", err)
|
||||
}
|
||||
reqStatusCode = req1.StatusCode
|
||||
defer req1.Body.Close()
|
||||
reqBody, err = ioutil.ReadAll(req1.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Server Error: [%#v] %s", reqStatusCode, err)
|
||||
}
|
||||
|
||||
if reqStatusCode == 201 {
|
||||
status = "Account created. Please use the confirmation link we sent" +
|
||||
" to your e-mail to activate it.\n"
|
||||
storeConfig = true
|
||||
} else if reqStatusCode == 403 {
|
||||
return "", fmt.Errorf("Login: Your account hasn't been activated. " +
|
||||
"Please check your e-mail for a confirmation link.")
|
||||
} else if reqStatusCode == 400 {
|
||||
if string(reqBody) == "\"Username or email already exists\"" {
|
||||
req, err := http.NewRequest("GET", IndexServerAddress()+"/users/", nil)
|
||||
req.SetBasicAuth(authConfig.Username, authConfig.Password)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if resp.StatusCode == 200 {
|
||||
status = "Login Succeeded\n"
|
||||
storeConfig = true
|
||||
} else if resp.StatusCode == 401 {
|
||||
if store {
|
||||
if err := SaveConfig(authConfig); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("Wrong login/password, please try again")
|
||||
} else {
|
||||
return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
|
||||
resp.StatusCode, resp.Header)
|
||||
}
|
||||
} else {
|
||||
return "", fmt.Errorf("Registration: %s", reqBody)
|
||||
}
|
||||
} else {
|
||||
return "", fmt.Errorf("Unexpected status code [%d] : %s", reqStatusCode, reqBody)
|
||||
}
|
||||
if storeConfig && store {
|
||||
if err := SaveConfig(authConfig); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEncodeAuth(t *testing.T) {
|
||||
newAuthConfig := &AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"}
|
||||
authStr := encodeAuth(newAuthConfig)
|
||||
decAuthConfig, err := decodeAuth(authStr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if newAuthConfig.Username != decAuthConfig.Username {
|
||||
t.Fatal("Encode Username doesn't match decoded Username")
|
||||
}
|
||||
if newAuthConfig.Password != decAuthConfig.Password {
|
||||
t.Fatal("Encode Password doesn't match decoded Password")
|
||||
}
|
||||
if authStr != "a2VuOnRlc3Q=" {
|
||||
t.Fatal("AuthString encoding isn't correct.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogin(t *testing.T) {
|
||||
os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
|
||||
defer os.Setenv("DOCKER_INDEX_URL", "")
|
||||
authConfig := NewAuthConfig("unittester", "surlautrerivejetattendrai", "noise+unittester@dotcloud.com", "/tmp")
|
||||
status, err := Login(authConfig, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if status != "Login Succeeded\n" {
|
||||
t.Fatalf("Expected status \"Login Succeeded\", found \"%s\" instead", status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateAccount(t *testing.T) {
|
||||
os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
|
||||
defer os.Setenv("DOCKER_INDEX_URL", "")
|
||||
tokenBuffer := make([]byte, 16)
|
||||
_, err := rand.Read(tokenBuffer)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
token := hex.EncodeToString(tokenBuffer)[:12]
|
||||
username := "ut" + token
|
||||
authConfig := NewAuthConfig(username, "test42", "docker-ut+"+token+"@example.com", "/tmp")
|
||||
status, err := Login(authConfig, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expectedStatus := "Account created. Please use the confirmation link we sent" +
|
||||
" to your e-mail to activate it.\n"
|
||||
if status != expectedStatus {
|
||||
t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status)
|
||||
}
|
||||
|
||||
status, err = Login(authConfig, false)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error but found nil instead")
|
||||
}
|
||||
|
||||
expectedError := "Login: Account is not Active"
|
||||
|
||||
if !strings.Contains(err.Error(), expectedError) {
|
||||
t.Fatalf("Expected message \"%s\" but found \"%s\" instead", expectedError, err.Error())
|
||||
}
|
||||
}
|
||||
-132
@@ -1,132 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
var defaultDns = []string{"8.8.8.8", "8.8.4.4"}
|
||||
|
||||
type Builder struct {
|
||||
runtime *Runtime
|
||||
repositories *TagStore
|
||||
graph *Graph
|
||||
|
||||
config *Config
|
||||
image *Image
|
||||
}
|
||||
|
||||
func NewBuilder(runtime *Runtime) *Builder {
|
||||
return &Builder{
|
||||
runtime: runtime,
|
||||
graph: runtime.graph,
|
||||
repositories: runtime.repositories,
|
||||
}
|
||||
}
|
||||
|
||||
func (builder *Builder) Create(config *Config) (*Container, error) {
|
||||
// Lookup image
|
||||
img, err := builder.repositories.LookupImage(config.Image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if img.Config != nil {
|
||||
MergeConfig(config, img.Config)
|
||||
}
|
||||
|
||||
if config.Cmd == nil || len(config.Cmd) == 0 {
|
||||
return nil, fmt.Errorf("No command specified")
|
||||
}
|
||||
|
||||
// Generate id
|
||||
id := GenerateID()
|
||||
// Generate default hostname
|
||||
// FIXME: the lxc template no longer needs to set a default hostname
|
||||
if config.Hostname == "" {
|
||||
config.Hostname = id[:12]
|
||||
}
|
||||
|
||||
container := &Container{
|
||||
// FIXME: we should generate the ID here instead of receiving it as an argument
|
||||
ID: id,
|
||||
Created: time.Now(),
|
||||
Path: config.Cmd[0],
|
||||
Args: config.Cmd[1:], //FIXME: de-duplicate from config
|
||||
Config: config,
|
||||
Image: img.ID, // Always use the resolved image id
|
||||
NetworkSettings: &NetworkSettings{},
|
||||
// FIXME: do we need to store this in the container?
|
||||
SysInitPath: sysInitPath,
|
||||
}
|
||||
container.root = builder.runtime.containerRoot(container.ID)
|
||||
// Step 1: create the container directory.
|
||||
// This doubles as a barrier to avoid race conditions.
|
||||
if err := os.Mkdir(container.root, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(config.Dns) == 0 && len(builder.runtime.Dns) == 0 && utils.CheckLocalDns() {
|
||||
//"WARNING: Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns
|
||||
builder.runtime.Dns = defaultDns
|
||||
}
|
||||
|
||||
// If custom dns exists, then create a resolv.conf for the container
|
||||
if len(config.Dns) > 0 || len(builder.runtime.Dns) > 0 {
|
||||
var dns []string
|
||||
if len(config.Dns) > 0 {
|
||||
dns = config.Dns
|
||||
} else {
|
||||
dns = builder.runtime.Dns
|
||||
}
|
||||
container.ResolvConfPath = path.Join(container.root, "resolv.conf")
|
||||
f, err := os.Create(container.ResolvConfPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
for _, dns := range dns {
|
||||
if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
container.ResolvConfPath = "/etc/resolv.conf"
|
||||
}
|
||||
|
||||
// Step 2: save the container json
|
||||
if err := container.ToDisk(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Step 3: register the container
|
||||
if err := builder.runtime.Register(container); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return container, nil
|
||||
}
|
||||
|
||||
// Commit creates a new filesystem image from the current state of a container.
|
||||
// The image can optionally be tagged into a repository
|
||||
func (builder *Builder) Commit(container *Container, repository, tag, comment, author string, config *Config) (*Image, error) {
|
||||
// FIXME: freeze the container before copying it to avoid data corruption?
|
||||
// FIXME: this shouldn't be in commands.
|
||||
rwTar, err := container.ExportRw()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Create a new image from the container's base layers + a new layer from container changes
|
||||
img, err := builder.graph.Create(rwTar, container, comment, author, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Register the image if needed
|
||||
if repository != "" {
|
||||
if err := builder.repositories.Set(repository, tag, img.ID, true); err != nil {
|
||||
return img, err
|
||||
}
|
||||
}
|
||||
return img, nil
|
||||
}
|
||||
@@ -1,314 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type builderClient struct {
|
||||
cli *DockerCli
|
||||
|
||||
image string
|
||||
maintainer string
|
||||
config *Config
|
||||
|
||||
tmpContainers map[string]struct{}
|
||||
tmpImages map[string]struct{}
|
||||
|
||||
needCommit bool
|
||||
}
|
||||
|
||||
func (b *builderClient) clearTmp(containers, images map[string]struct{}) {
|
||||
for i := range images {
|
||||
if _, _, err := b.cli.call("DELETE", "/images/"+i, nil); err != nil {
|
||||
utils.Debugf("%s", err)
|
||||
}
|
||||
utils.Debugf("Removing image %s", i)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builderClient) CmdFrom(name string) error {
|
||||
obj, statusCode, err := b.cli.call("GET", "/images/"+name+"/json", nil)
|
||||
if statusCode == 404 {
|
||||
|
||||
remote := name
|
||||
var tag string
|
||||
if strings.Contains(remote, ":") {
|
||||
remoteParts := strings.Split(remote, ":")
|
||||
tag = remoteParts[1]
|
||||
remote = remoteParts[0]
|
||||
}
|
||||
var out io.Writer
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
out = os.Stdout
|
||||
} else {
|
||||
out = &utils.NopWriter{}
|
||||
}
|
||||
if err := b.cli.stream("POST", "/images/create?fromImage="+remote+"&tag="+tag, nil, out); err != nil {
|
||||
return err
|
||||
}
|
||||
obj, _, err = b.cli.call("GET", "/images/"+name+"/json", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
img := &APIID{}
|
||||
if err := json.Unmarshal(obj, img); err != nil {
|
||||
return err
|
||||
}
|
||||
b.image = img.ID
|
||||
utils.Debugf("Using image %s", b.image)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *builderClient) CmdMaintainer(name string) error {
|
||||
b.needCommit = true
|
||||
b.maintainer = name
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *builderClient) CmdRun(args string) error {
|
||||
if b.image == "" {
|
||||
return fmt.Errorf("Please provide a source image with `from` prior to run")
|
||||
}
|
||||
config, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd, env := b.config.Cmd, b.config.Env
|
||||
b.config.Cmd = nil
|
||||
MergeConfig(b.config, config)
|
||||
|
||||
body, statusCode, err := b.cli.call("POST", "/images/getCache", &APIImageConfig{ID: b.image, Config: b.config})
|
||||
if err != nil {
|
||||
if statusCode != 404 {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if statusCode != 404 {
|
||||
apiID := &APIID{}
|
||||
if err := json.Unmarshal(body, apiID); err != nil {
|
||||
return err
|
||||
}
|
||||
utils.Debugf("Use cached version")
|
||||
b.image = apiID.ID
|
||||
return nil
|
||||
}
|
||||
cid, err := b.run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.config.Cmd, b.config.Env = cmd, env
|
||||
return b.commit(cid)
|
||||
}
|
||||
|
||||
func (b *builderClient) CmdEnv(args string) error {
|
||||
b.needCommit = true
|
||||
tmp := strings.SplitN(args, " ", 2)
|
||||
if len(tmp) != 2 {
|
||||
return fmt.Errorf("Invalid ENV format")
|
||||
}
|
||||
key := strings.Trim(tmp[0], " ")
|
||||
value := strings.Trim(tmp[1], " ")
|
||||
|
||||
for i, elem := range b.config.Env {
|
||||
if strings.HasPrefix(elem, key+"=") {
|
||||
b.config.Env[i] = key + "=" + value
|
||||
return nil
|
||||
}
|
||||
}
|
||||
b.config.Env = append(b.config.Env, key+"="+value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *builderClient) CmdCmd(args string) error {
|
||||
b.needCommit = true
|
||||
var cmd []string
|
||||
if err := json.Unmarshal([]byte(args), &cmd); err != nil {
|
||||
utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err)
|
||||
b.config.Cmd = []string{"/bin/sh", "-c", args}
|
||||
} else {
|
||||
b.config.Cmd = cmd
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *builderClient) CmdExpose(args string) error {
|
||||
ports := strings.Split(args, " ")
|
||||
b.config.PortSpecs = append(ports, b.config.PortSpecs...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *builderClient) CmdInsert(args string) error {
|
||||
// tmp := strings.SplitN(args, "\t ", 2)
|
||||
// sourceUrl, destPath := tmp[0], tmp[1]
|
||||
|
||||
// v := url.Values{}
|
||||
// v.Set("url", sourceUrl)
|
||||
// v.Set("path", destPath)
|
||||
// body, _, err := b.cli.call("POST", "/images/insert?"+v.Encode(), nil)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// apiId := &APIId{}
|
||||
// if err := json.Unmarshal(body, apiId); err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// FIXME: Reimplement this, we need to retrieve the resulting Id
|
||||
return fmt.Errorf("INSERT not implemented")
|
||||
}
|
||||
|
||||
func (b *builderClient) run() (string, error) {
|
||||
if b.image == "" {
|
||||
return "", fmt.Errorf("Please provide a source image with `from` prior to run")
|
||||
}
|
||||
b.config.Image = b.image
|
||||
body, _, err := b.cli.call("POST", "/containers/create", b.config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
apiRun := &APIRun{}
|
||||
if err := json.Unmarshal(body, apiRun); err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, warning := range apiRun.Warnings {
|
||||
fmt.Fprintln(os.Stderr, "WARNING: ", warning)
|
||||
}
|
||||
|
||||
//start the container
|
||||
_, _, err = b.cli.call("POST", "/containers/"+apiRun.ID+"/start", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b.tmpContainers[apiRun.ID] = struct{}{}
|
||||
|
||||
// Wait for it to finish
|
||||
body, _, err = b.cli.call("POST", "/containers/"+apiRun.ID+"/wait", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
apiWait := &APIWait{}
|
||||
if err := json.Unmarshal(body, apiWait); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if apiWait.StatusCode != 0 {
|
||||
return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, apiWait.StatusCode)
|
||||
}
|
||||
|
||||
return apiRun.ID, nil
|
||||
}
|
||||
|
||||
func (b *builderClient) commit(id string) error {
|
||||
if b.image == "" {
|
||||
return fmt.Errorf("Please provide a source image with `from` prior to run")
|
||||
}
|
||||
b.config.Image = b.image
|
||||
|
||||
if id == "" {
|
||||
cmd := b.config.Cmd
|
||||
b.config.Cmd = []string{"true"}
|
||||
cid, err := b.run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
id = cid
|
||||
b.config.Cmd = cmd
|
||||
}
|
||||
|
||||
// Commit the container
|
||||
v := url.Values{}
|
||||
v.Set("container", id)
|
||||
v.Set("author", b.maintainer)
|
||||
|
||||
body, _, err := b.cli.call("POST", "/commit?"+v.Encode(), b.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apiID := &APIID{}
|
||||
if err := json.Unmarshal(body, apiID); err != nil {
|
||||
return err
|
||||
}
|
||||
b.tmpImages[apiID.ID] = struct{}{}
|
||||
b.image = apiID.ID
|
||||
b.needCommit = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *builderClient) Build(dockerfile, context io.Reader) (string, error) {
|
||||
defer b.clearTmp(b.tmpContainers, b.tmpImages)
|
||||
file := bufio.NewReader(dockerfile)
|
||||
for {
|
||||
line, err := file.ReadString('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
line = strings.Replace(strings.TrimSpace(line), " ", " ", 1)
|
||||
// Skip comments and empty line
|
||||
if len(line) == 0 || line[0] == '#' {
|
||||
continue
|
||||
}
|
||||
tmp := strings.SplitN(line, " ", 2)
|
||||
if len(tmp) != 2 {
|
||||
return "", fmt.Errorf("Invalid Dockerfile format")
|
||||
}
|
||||
instruction := strings.ToLower(strings.Trim(tmp[0], " "))
|
||||
arguments := strings.Trim(tmp[1], " ")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "%s %s (%s)\n", strings.ToUpper(instruction), arguments, b.image)
|
||||
|
||||
method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
|
||||
if !exists {
|
||||
fmt.Fprintf(os.Stderr, "Skipping unknown instruction %s\n", strings.ToUpper(instruction))
|
||||
}
|
||||
ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
|
||||
if ret != nil {
|
||||
return "", ret.(error)
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "===> %v\n", b.image)
|
||||
}
|
||||
if b.needCommit {
|
||||
if err := b.commit(""); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if b.image != "" {
|
||||
// The build is successful, keep the temporary containers and images
|
||||
for i := range b.tmpImages {
|
||||
delete(b.tmpImages, i)
|
||||
}
|
||||
for i := range b.tmpContainers {
|
||||
delete(b.tmpContainers, i)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "Build finished. image id: %s\n", b.image)
|
||||
return b.image, nil
|
||||
}
|
||||
return "", fmt.Errorf("An error occured during the build\n")
|
||||
}
|
||||
|
||||
func NewBuilderClient(proto, addr string) BuildFile {
|
||||
return &builderClient{
|
||||
cli: NewDockerCli(proto, addr),
|
||||
config: &Config{},
|
||||
tmpContainers: make(map[string]struct{}),
|
||||
tmpImages: make(map[string]struct{}),
|
||||
}
|
||||
}
|
||||
-376
@@ -1,376 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type BuildFile interface {
|
||||
Build(io.Reader, io.Reader) (string, error)
|
||||
CmdFrom(string) error
|
||||
CmdRun(string) error
|
||||
}
|
||||
|
||||
type buildFile struct {
|
||||
runtime *Runtime
|
||||
builder *Builder
|
||||
srv *Server
|
||||
|
||||
image string
|
||||
maintainer string
|
||||
config *Config
|
||||
context string
|
||||
|
||||
tmpContainers map[string]struct{}
|
||||
tmpImages map[string]struct{}
|
||||
|
||||
out io.Writer
|
||||
}
|
||||
|
||||
func (b *buildFile) clearTmp(containers, images map[string]struct{}) {
|
||||
for c := range containers {
|
||||
tmp := b.runtime.Get(c)
|
||||
b.runtime.Destroy(tmp)
|
||||
utils.Debugf("Removing container %s", c)
|
||||
}
|
||||
for i := range images {
|
||||
b.runtime.graph.Delete(i)
|
||||
utils.Debugf("Removing image %s", i)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdFrom(name string) error {
|
||||
image, err := b.runtime.repositories.LookupImage(name)
|
||||
if err != nil {
|
||||
if b.runtime.graph.IsNotExist(err) {
|
||||
|
||||
var tag, remote string
|
||||
if strings.Contains(name, ":") {
|
||||
remoteParts := strings.Split(name, ":")
|
||||
tag = remoteParts[1]
|
||||
remote = remoteParts[0]
|
||||
} else {
|
||||
remote = name
|
||||
}
|
||||
|
||||
if err := b.srv.ImagePull(remote, tag, "", b.out, utils.NewStreamFormatter(false), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
image, err = b.runtime.repositories.LookupImage(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
b.image = image.ID
|
||||
b.config = &Config{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdMaintainer(name string) error {
|
||||
b.maintainer = name
|
||||
return b.commit("", b.config.Cmd, fmt.Sprintf("MAINTAINER %s", name))
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdRun(args string) error {
|
||||
if b.image == "" {
|
||||
return fmt.Errorf("Please provide a source image with `from` prior to run")
|
||||
}
|
||||
config, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := b.config.Cmd
|
||||
b.config.Cmd = nil
|
||||
MergeConfig(b.config, config)
|
||||
|
||||
utils.Debugf("Command to be executed: %v", b.config.Cmd)
|
||||
|
||||
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
|
||||
return err
|
||||
} else if cache != nil {
|
||||
fmt.Fprintf(b.out, " ---> Using cache\n")
|
||||
utils.Debugf("[BUILDER] Use cached version")
|
||||
b.image = cache.ID
|
||||
return nil
|
||||
} else {
|
||||
utils.Debugf("[BUILDER] Cache miss")
|
||||
}
|
||||
|
||||
cid, err := b.run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := b.commit(cid, cmd, "run"); err != nil {
|
||||
return err
|
||||
}
|
||||
b.config.Cmd = cmd
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdEnv(args string) error {
|
||||
tmp := strings.SplitN(args, " ", 2)
|
||||
if len(tmp) != 2 {
|
||||
return fmt.Errorf("Invalid ENV format")
|
||||
}
|
||||
key := strings.Trim(tmp[0], " ")
|
||||
value := strings.Trim(tmp[1], " ")
|
||||
|
||||
for i, elem := range b.config.Env {
|
||||
if strings.HasPrefix(elem, key+"=") {
|
||||
b.config.Env[i] = key + "=" + value
|
||||
return nil
|
||||
}
|
||||
}
|
||||
b.config.Env = append(b.config.Env, key+"="+value)
|
||||
return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s=%s", key, value))
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdCmd(args string) error {
|
||||
var cmd []string
|
||||
if err := json.Unmarshal([]byte(args), &cmd); err != nil {
|
||||
utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err)
|
||||
cmd = []string{"/bin/sh", "-c", args}
|
||||
}
|
||||
if err := b.commit("", cmd, fmt.Sprintf("CMD %v", cmd)); err != nil {
|
||||
return err
|
||||
}
|
||||
b.config.Cmd = cmd
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdExpose(args string) error {
|
||||
ports := strings.Split(args, " ")
|
||||
b.config.PortSpecs = append(ports, b.config.PortSpecs...)
|
||||
return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports))
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdInsert(args string) error {
|
||||
return fmt.Errorf("INSERT has been deprecated. Please use ADD instead")
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdCopy(args string) error {
|
||||
return fmt.Errorf("COPY has been deprecated. Please use ADD instead")
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdAdd(args string) error {
|
||||
if b.context == "" {
|
||||
return fmt.Errorf("No context given. Impossible to use ADD")
|
||||
}
|
||||
tmp := strings.SplitN(args, " ", 2)
|
||||
if len(tmp) != 2 {
|
||||
return fmt.Errorf("Invalid ADD format")
|
||||
}
|
||||
orig := strings.Trim(tmp[0], " ")
|
||||
dest := strings.Trim(tmp[1], " ")
|
||||
|
||||
cmd := b.config.Cmd
|
||||
|
||||
// Create the container and start it
|
||||
b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
|
||||
b.config.Image = b.image
|
||||
container, err := b.builder.Create(b.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.tmpContainers[container.ID] = struct{}{}
|
||||
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID))
|
||||
|
||||
if err := container.EnsureMounted(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer container.Unmount()
|
||||
|
||||
origPath := path.Join(b.context, orig)
|
||||
destPath := path.Join(container.RootfsPath(), dest)
|
||||
// Preserve the trailing '/'
|
||||
if dest[len(dest)-1] == '/' {
|
||||
destPath = destPath + "/"
|
||||
}
|
||||
fi, err := os.Stat(origPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fi.IsDir() {
|
||||
if err := CopyWithTar(origPath, destPath); err != nil {
|
||||
return err
|
||||
}
|
||||
// First try to unpack the source as an archive
|
||||
} else if err := UntarPath(origPath, destPath); err != nil {
|
||||
utils.Debugf("Couldn't untar %s to %s: %s", origPath, destPath, err)
|
||||
// If that fails, just copy it as a regular file
|
||||
if err := os.MkdirAll(path.Dir(destPath), 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := CopyWithTar(origPath, destPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := b.commit(container.ID, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil {
|
||||
return err
|
||||
}
|
||||
b.config.Cmd = cmd
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buildFile) run() (string, error) {
|
||||
if b.image == "" {
|
||||
return "", fmt.Errorf("Please provide a source image with `from` prior to run")
|
||||
}
|
||||
b.config.Image = b.image
|
||||
|
||||
// Create the container and start it
|
||||
c, err := b.builder.Create(b.config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b.tmpContainers[c.ID] = struct{}{}
|
||||
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(c.ID))
|
||||
|
||||
//start the container
|
||||
if err := c.Start(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Wait for it to finish
|
||||
if ret := c.Wait(); ret != 0 {
|
||||
return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, ret)
|
||||
}
|
||||
|
||||
return c.ID, nil
|
||||
}
|
||||
|
||||
// Commit the container <id> with the autorun command <autoCmd>
|
||||
func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
|
||||
if b.image == "" {
|
||||
return fmt.Errorf("Please provide a source image with `from` prior to commit")
|
||||
}
|
||||
b.config.Image = b.image
|
||||
if id == "" {
|
||||
b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
|
||||
|
||||
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
|
||||
return err
|
||||
} else if cache != nil {
|
||||
fmt.Fprintf(b.out, " ---> Using cache\n")
|
||||
utils.Debugf("[BUILDER] Use cached version")
|
||||
b.image = cache.ID
|
||||
return nil
|
||||
} else {
|
||||
utils.Debugf("[BUILDER] Cache miss")
|
||||
}
|
||||
|
||||
// Create the container and start it
|
||||
container, err := b.builder.Create(b.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.tmpContainers[container.ID] = struct{}{}
|
||||
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID))
|
||||
|
||||
if err := container.EnsureMounted(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer container.Unmount()
|
||||
|
||||
id = container.ID
|
||||
}
|
||||
|
||||
container := b.runtime.Get(id)
|
||||
if container == nil {
|
||||
return fmt.Errorf("An error occured while creating the container")
|
||||
}
|
||||
|
||||
// Note: Actually copy the struct
|
||||
autoConfig := *b.config
|
||||
autoConfig.Cmd = autoCmd
|
||||
// Commit the container
|
||||
image, err := b.builder.Commit(container, "", "", "", b.maintainer, &autoConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.tmpImages[image.ID] = struct{}{}
|
||||
b.image = image.ID
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
|
||||
if context != nil {
|
||||
name, err := ioutil.TempDir("/tmp", "docker-build")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := Untar(context, name); err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer os.RemoveAll(name)
|
||||
b.context = name
|
||||
}
|
||||
file := bufio.NewReader(dockerfile)
|
||||
stepN := 0
|
||||
for {
|
||||
line, err := file.ReadString('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF && line == "" {
|
||||
break
|
||||
} else if err != io.EOF {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
line = strings.Replace(strings.TrimSpace(line), " ", " ", 1)
|
||||
// Skip comments and empty line
|
||||
if len(line) == 0 || line[0] == '#' {
|
||||
continue
|
||||
}
|
||||
tmp := strings.SplitN(line, " ", 2)
|
||||
if len(tmp) != 2 {
|
||||
return "", fmt.Errorf("Invalid Dockerfile format")
|
||||
}
|
||||
instruction := strings.ToLower(strings.Trim(tmp[0], " "))
|
||||
arguments := strings.Trim(tmp[1], " ")
|
||||
stepN += 1
|
||||
// FIXME: only count known instructions as build steps
|
||||
fmt.Fprintf(b.out, "Step %d : %s %s\n", stepN, strings.ToUpper(instruction), arguments)
|
||||
|
||||
method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
|
||||
if !exists {
|
||||
fmt.Fprintf(b.out, "# Skipping unknown instruction %s\n", strings.ToUpper(instruction))
|
||||
continue
|
||||
}
|
||||
ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
|
||||
if ret != nil {
|
||||
return "", ret.(error)
|
||||
}
|
||||
|
||||
fmt.Fprintf(b.out, " ---> %v\n", utils.TruncateID(b.image))
|
||||
}
|
||||
if b.image != "" {
|
||||
fmt.Fprintf(b.out, "Successfully built %s\n", utils.TruncateID(b.image))
|
||||
return b.image, nil
|
||||
}
|
||||
return "", fmt.Errorf("An error occured during the build\n")
|
||||
}
|
||||
|
||||
func NewBuildFile(srv *Server, out io.Writer) BuildFile {
|
||||
return &buildFile{
|
||||
builder: NewBuilder(srv.runtime),
|
||||
runtime: srv.runtime,
|
||||
srv: srv,
|
||||
config: &Config{},
|
||||
out: out,
|
||||
tmpContainers: make(map[string]struct{}),
|
||||
tmpImages: make(map[string]struct{}),
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const Dockerfile = `
|
||||
# VERSION 0.1
|
||||
# DOCKER-VERSION 0.2
|
||||
|
||||
from ` + unitTestImageName + `
|
||||
run sh -c 'echo root:testpass > /tmp/passwd'
|
||||
run mkdir -p /var/run/sshd
|
||||
`
|
||||
|
||||
const DockerfileNoNewLine = `
|
||||
# VERSION 0.1
|
||||
# DOCKER-VERSION 0.2
|
||||
|
||||
from ` + unitTestImageName + `
|
||||
run sh -c 'echo root:testpass > /tmp/passwd'
|
||||
run mkdir -p /var/run/sshd`
|
||||
|
||||
// FIXME: test building with a context
|
||||
|
||||
// FIXME: test building with a local ADD as first command
|
||||
|
||||
// FIXME: test building with 2 successive overlapping ADD commands
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
dockerfiles := []string{Dockerfile, DockerfileNoNewLine}
|
||||
for _, Dockerfile := range dockerfiles {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
buildfile := NewBuildFile(srv, &utils.NopWriter{})
|
||||
|
||||
imgID, err := buildfile.Build(strings.NewReader(Dockerfile), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
builder := NewBuilder(runtime)
|
||||
container, err := builder.Create(
|
||||
&Config{
|
||||
Image: imgID,
|
||||
Cmd: []string{"cat", "/tmp/passwd"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
output, err := container.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(output) != "root:testpass\n" {
|
||||
t.Fatalf("Unexpected output. Read '%s', expected '%s'", output, "root:testpass\n")
|
||||
}
|
||||
|
||||
container2, err := builder.Create(
|
||||
&Config{
|
||||
Image: imgID,
|
||||
Cmd: []string{"ls", "-d", "/var/run/sshd"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container2)
|
||||
|
||||
output, err = container2.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(output) != "/var/run/sshd\n" {
|
||||
t.Fatal("/var/run/sshd has not been created")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"../future"
|
||||
"../rcli"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Run docker in "simple mode": run a single command and return.
|
||||
func SimpleMode(args []string) error {
|
||||
var oldState *State
|
||||
var err error
|
||||
if IsTerminal(0) && os.Getenv("NORAW") == "" {
|
||||
oldState, err = MakeRaw(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer Restore(0, oldState)
|
||||
}
|
||||
// FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose
|
||||
// CloseWrite(), which we need to cleanly signal that stdin is closed without
|
||||
// closing the connection.
|
||||
// See http://code.google.com/p/go/issues/detail?id=3345
|
||||
conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
receive_stdout := future.Go(func() error {
|
||||
_, err := io.Copy(os.Stdout, conn)
|
||||
return err
|
||||
})
|
||||
send_stdin := future.Go(func() error {
|
||||
_, err := io.Copy(conn, os.Stdin)
|
||||
if err := conn.CloseWrite(); err != nil {
|
||||
log.Printf("Couldn't send EOF: " + err.Error())
|
||||
}
|
||||
return err
|
||||
})
|
||||
if err := <-receive_stdout; err != nil {
|
||||
return err
|
||||
}
|
||||
if oldState != nil {
|
||||
Restore(0, oldState)
|
||||
}
|
||||
if !IsTerminal(0) {
|
||||
if err := <-send_stdin; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run docker in "interactive mode": run a bash-compatible shell capable of running docker commands.
|
||||
func InteractiveMode(scripts ...string) error {
|
||||
// Determine path of current docker binary
|
||||
dockerPath, err := exec.LookPath(os.Args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dockerPath, err = filepath.Abs(dockerPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create a temp directory
|
||||
tmp, err := ioutil.TempDir("", "docker-shell")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
// For each command, create an alias in temp directory
|
||||
// FIXME: generate this list dynamically with introspection of some sort
|
||||
// It might make sense to merge docker and dockerd to keep that introspection
|
||||
// within a single binary.
|
||||
for _, cmd := range []string{
|
||||
"help",
|
||||
"run",
|
||||
"ps",
|
||||
"pull",
|
||||
"put",
|
||||
"rm",
|
||||
"kill",
|
||||
"wait",
|
||||
"stop",
|
||||
"start",
|
||||
"restart",
|
||||
"logs",
|
||||
"diff",
|
||||
"commit",
|
||||
"attach",
|
||||
"info",
|
||||
"tar",
|
||||
"web",
|
||||
"images",
|
||||
"docker",
|
||||
} {
|
||||
if err := os.Symlink(dockerPath, path.Join(tmp, cmd)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Run $SHELL with PATH set to temp directory
|
||||
rcfile, err := ioutil.TempFile("", "docker-shell-rc")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(rcfile.Name())
|
||||
io.WriteString(rcfile, "enable -n help\n")
|
||||
os.Setenv("PATH", tmp+":"+os.Getenv("PATH"))
|
||||
os.Setenv("PS1", "\\h docker> ")
|
||||
shell := exec.Command("/bin/bash", append([]string{"--rcfile", rcfile.Name()}, scripts...)...)
|
||||
shell.Stdin = os.Stdin
|
||||
shell.Stdout = os.Stdout
|
||||
shell.Stderr = os.Stderr
|
||||
if err := shell.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type Termios struct {
|
||||
Iflag uintptr
|
||||
Oflag uintptr
|
||||
Cflag uintptr
|
||||
Lflag uintptr
|
||||
Cc [20]byte
|
||||
Ispeed uintptr
|
||||
Ospeed uintptr
|
||||
}
|
||||
|
||||
const (
|
||||
// Input flags
|
||||
inpck = 0x010
|
||||
istrip = 0x020
|
||||
icrnl = 0x100
|
||||
ixon = 0x200
|
||||
|
||||
// Output flags
|
||||
opost = 0x1
|
||||
|
||||
// Control flags
|
||||
cs8 = 0x300
|
||||
|
||||
// Local flags
|
||||
icanon = 0x100
|
||||
iexten = 0x400
|
||||
)
|
||||
|
||||
const (
|
||||
HUPCL = 0x4000
|
||||
ICANON = 0x100
|
||||
ICRNL = 0x100
|
||||
IEXTEN = 0x400
|
||||
BRKINT = 0x2
|
||||
CFLUSH = 0xf
|
||||
CLOCAL = 0x8000
|
||||
CREAD = 0x800
|
||||
CS5 = 0x0
|
||||
CS6 = 0x100
|
||||
CS7 = 0x200
|
||||
CS8 = 0x300
|
||||
CSIZE = 0x300
|
||||
CSTART = 0x11
|
||||
CSTATUS = 0x14
|
||||
CSTOP = 0x13
|
||||
CSTOPB = 0x400
|
||||
CSUSP = 0x1a
|
||||
IGNBRK = 0x1
|
||||
IGNCR = 0x80
|
||||
IGNPAR = 0x4
|
||||
IMAXBEL = 0x2000
|
||||
INLCR = 0x40
|
||||
INPCK = 0x10
|
||||
ISIG = 0x80
|
||||
ISTRIP = 0x20
|
||||
IUTF8 = 0x4000
|
||||
IXANY = 0x800
|
||||
IXOFF = 0x400
|
||||
IXON = 0x200
|
||||
NOFLSH = 0x80000000
|
||||
OCRNL = 0x10
|
||||
OFDEL = 0x20000
|
||||
OFILL = 0x80
|
||||
ONLCR = 0x2
|
||||
ONLRET = 0x40
|
||||
ONOCR = 0x20
|
||||
ONOEOT = 0x8
|
||||
OPOST = 0x1
|
||||
RENB = 0x1000
|
||||
PARMRK = 0x8
|
||||
PARODD = 0x2000
|
||||
|
||||
TOSTOP = 0x400000
|
||||
VDISCARD = 0xf
|
||||
VDSUSP = 0xb
|
||||
VEOF = 0x0
|
||||
VEOL = 0x1
|
||||
VEOL2 = 0x2
|
||||
VERASE = 0x3
|
||||
VINTR = 0x8
|
||||
VKILL = 0x5
|
||||
VLNEXT = 0xe
|
||||
VMIN = 0x10
|
||||
VQUIT = 0x9
|
||||
VREPRINT = 0x6
|
||||
VSTART = 0xc
|
||||
VSTATUS = 0x12
|
||||
VSTOP = 0xd
|
||||
VSUSP = 0xa
|
||||
VT0 = 0x0
|
||||
VT1 = 0x10000
|
||||
VTDLY = 0x10000
|
||||
VTIME = 0x11
|
||||
ECHO = 0x00000008
|
||||
|
||||
PENDIN = 0x20000000
|
||||
)
|
||||
|
||||
type State struct {
|
||||
termios Termios
|
||||
}
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal(fd int) bool {
|
||||
var termios Termios
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
||||
return err == 0
|
||||
}
|
||||
|
||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||
// mode and returns the previous state of the terminal so that it can be
|
||||
// restored.
|
||||
func MakeRaw(fd int) (*State, error) {
|
||||
var oldState State
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newState := oldState.termios
|
||||
newState.Iflag &^= ISTRIP | INLCR | IGNCR | IXON | IXOFF
|
||||
newState.Iflag |= ICRNL
|
||||
newState.Oflag |= ONLCR
|
||||
newState.Lflag &^= ECHO | ICANON | ISIG
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &oldState, nil
|
||||
}
|
||||
|
||||
// Restore restores the terminal connected to the given file descriptor to a
|
||||
// previous state.
|
||||
func Restore(fd int, state *State) error {
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package client
|
||||
|
||||
import "syscall"
|
||||
|
||||
const (
|
||||
getTermios = syscall.TIOCGETA
|
||||
setTermios = syscall.TIOCSETA
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
package client
|
||||
|
||||
import "syscall"
|
||||
|
||||
const (
|
||||
getTermios = syscall.TCGETS
|
||||
setTermios = syscall.TCSETS
|
||||
)
|
||||
-1507
File diff suppressed because it is too large
Load Diff
@@ -1,471 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
_ "io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func closeWrap(args ...io.Closer) error {
|
||||
e := false
|
||||
ret := fmt.Errorf("Error closing elements")
|
||||
for _, c := range args {
|
||||
if err := c.Close(); err != nil {
|
||||
e = true
|
||||
ret = fmt.Errorf("%s\n%s", ret, err)
|
||||
}
|
||||
}
|
||||
if e {
|
||||
return ret
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setTimeout(t *testing.T, msg string, d time.Duration, f func()) {
|
||||
c := make(chan bool)
|
||||
|
||||
// Make sure we are not too long
|
||||
go func() {
|
||||
time.Sleep(d)
|
||||
c <- true
|
||||
}()
|
||||
go func() {
|
||||
f()
|
||||
c <- false
|
||||
}()
|
||||
if <-c {
|
||||
t.Fatal(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error {
|
||||
for i := 0; i < count; i++ {
|
||||
if _, err := w.Write([]byte(input)); err != nil {
|
||||
return err
|
||||
}
|
||||
o, err := bufio.NewReader(r).ReadString('\n')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.Trim(o, " \r\n") != output {
|
||||
return fmt.Errorf("Unexpected output. Expected [%s], received [%s]", output, o)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*TODO
|
||||
func cmdWait(srv *Server, container *Container) error {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
go func() {
|
||||
srv.CmdWait(nil, stdoutPipe, container.Id)
|
||||
}()
|
||||
|
||||
if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
// Cleanup pipes
|
||||
return closeWrap(stdout, stdoutPipe)
|
||||
}
|
||||
|
||||
func cmdImages(srv *Server, args ...string) (string, error) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
go func() {
|
||||
if err := srv.CmdImages(nil, stdoutPipe, args...); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// force the pipe closed, so that the code below gets an EOF
|
||||
stdoutPipe.Close()
|
||||
}()
|
||||
|
||||
output, err := ioutil.ReadAll(stdout)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Cleanup pipes
|
||||
return string(output), closeWrap(stdout, stdoutPipe)
|
||||
}
|
||||
|
||||
// TestImages checks that 'docker images' displays information correctly
|
||||
func TestImages(t *testing.T) {
|
||||
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
output, err := cmdImages(srv)
|
||||
|
||||
if !strings.Contains(output, "REPOSITORY") {
|
||||
t.Fatal("'images' should have a header")
|
||||
}
|
||||
if !strings.Contains(output, "docker-ut") {
|
||||
t.Fatal("'images' should show the docker-ut image")
|
||||
}
|
||||
if !strings.Contains(output, "e9aa60c60128") {
|
||||
t.Fatal("'images' should show the docker-ut image id")
|
||||
}
|
||||
|
||||
output, err = cmdImages(srv, "-q")
|
||||
|
||||
if strings.Contains(output, "REPOSITORY") {
|
||||
t.Fatal("'images -q' should not have a header")
|
||||
}
|
||||
if strings.Contains(output, "docker-ut") {
|
||||
t.Fatal("'images' should not show the docker-ut image name")
|
||||
}
|
||||
if !strings.Contains(output, "e9aa60c60128") {
|
||||
t.Fatal("'images' should show the docker-ut image id")
|
||||
}
|
||||
|
||||
output, err = cmdImages(srv, "-viz")
|
||||
|
||||
if !strings.HasPrefix(output, "digraph docker {") {
|
||||
t.Fatal("'images -v' should start with the dot header")
|
||||
}
|
||||
if !strings.HasSuffix(output, "}\n") {
|
||||
t.Fatal("'images -v' should end with a '}'")
|
||||
}
|
||||
if !strings.Contains(output, "base -> \"e9aa60c60128\" [style=invis]") {
|
||||
t.Fatal("'images -v' should have the docker-ut image id node")
|
||||
}
|
||||
|
||||
// todo: add checks for -a
|
||||
}
|
||||
|
||||
// TestRunHostname checks that 'docker run -h' correctly sets a custom hostname
|
||||
func TestRunHostname(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
stdin, _ := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
if err := srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-h", "foobar", GetTestImage(runtime).Id, "hostname"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
close(c)
|
||||
}()
|
||||
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cmdOutput != "foobar\n" {
|
||||
t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", cmdOutput)
|
||||
}
|
||||
|
||||
setTimeout(t, "CmdRun timed out", 2*time.Second, func() {
|
||||
<-c
|
||||
cmdWait(srv, srv.runtime.List()[0])
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestRunExit(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", GetTestImage(runtime).Id, "/bin/cat")
|
||||
close(c1)
|
||||
}()
|
||||
|
||||
setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
container := runtime.List()[0]
|
||||
|
||||
// Closing /bin/cat stdin, expect it to exit
|
||||
p, err := container.StdinPipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := p.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// as the process exited, CmdRun must finish and unblock. Wait for it
|
||||
setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
|
||||
<-c1
|
||||
cmdWait(srv, container)
|
||||
})
|
||||
|
||||
// Make sure that the client has been disconnected
|
||||
setTimeout(t, "The client should have been disconnected once the remote process exited.", 2*time.Second, func() {
|
||||
// Expecting pipe i/o error, just check that read does not block
|
||||
stdin.Read([]byte{})
|
||||
})
|
||||
|
||||
// Cleanup pipes
|
||||
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Expected behaviour: the process dies when the client disconnects
|
||||
func TestRunDisconnect(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
// We're simulating a disconnect so the return value doesn't matter. What matters is the
|
||||
// fact that CmdRun returns.
|
||||
srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", GetTestImage(runtime).Id, "/bin/cat")
|
||||
close(c1)
|
||||
}()
|
||||
|
||||
setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
// Close pipes (simulate disconnect)
|
||||
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// as the pipes are close, we expect the process to die,
|
||||
// therefore CmdRun to unblock. Wait for CmdRun
|
||||
setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
|
||||
<-c1
|
||||
})
|
||||
|
||||
// Client disconnect after run -i should cause stdin to be closed, which should
|
||||
// cause /bin/cat to exit.
|
||||
setTimeout(t, "Waiting for /bin/cat to exit timed out", 2*time.Second, func() {
|
||||
container := runtime.List()[0]
|
||||
container.Wait()
|
||||
if container.State.Running {
|
||||
t.Fatalf("/bin/cat is still running after closing stdin")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Expected behaviour: the process dies when the client disconnects
|
||||
func TestRunDisconnectTty(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
// We're simulating a disconnect so the return value doesn't matter. What matters is the
|
||||
// fact that CmdRun returns.
|
||||
srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", "-t", GetTestImage(runtime).Id, "/bin/cat")
|
||||
close(c1)
|
||||
}()
|
||||
|
||||
setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
|
||||
for {
|
||||
// Client disconnect after run -i should keep stdin out in TTY mode
|
||||
l := runtime.List()
|
||||
if len(l) == 1 && l[0].State.Running {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
})
|
||||
|
||||
// Client disconnect after run -i should keep stdin out in TTY mode
|
||||
container := runtime.List()[0]
|
||||
|
||||
setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
// Close pipes (simulate disconnect)
|
||||
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// In tty mode, we expect the process to stay alive even after client's stdin closes.
|
||||
// Do not wait for run to finish
|
||||
|
||||
// Give some time to monitor to do his thing
|
||||
container.WaitTimeout(500 * time.Millisecond)
|
||||
if !container.State.Running {
|
||||
t.Fatalf("/bin/cat should still be running after closing stdin (tty mode)")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAttachStdin checks attaching to stdin without stdout and stderr.
|
||||
// 'docker run -i -a stdin' should sends the client's stdin to the command,
|
||||
// then detach from it and print the container id.
|
||||
func TestRunAttachStdin(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", "-a", "stdin", GetTestImage(runtime).Id, "sh", "-c", "echo hello; cat")
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
// Send input to the command, close stdin
|
||||
setTimeout(t, "Write timed out", 2*time.Second, func() {
|
||||
if _, err := stdinPipe.Write([]byte("hi there\n")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := stdinPipe.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
container := runtime.List()[0]
|
||||
|
||||
// Check output
|
||||
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cmdOutput != container.ShortId()+"\n" {
|
||||
t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortId()+"\n", cmdOutput)
|
||||
}
|
||||
|
||||
// wait for CmdRun to return
|
||||
setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
|
||||
<-ch
|
||||
})
|
||||
|
||||
setTimeout(t, "Waiting for command to exit timed out", 2*time.Second, func() {
|
||||
container.Wait()
|
||||
})
|
||||
|
||||
// Check logs
|
||||
if cmdLogs, err := container.ReadLog("stdout"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
if output, err := ioutil.ReadAll(cmdLogs); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
expectedLog := "hello\nhi there\n"
|
||||
if string(output) != expectedLog {
|
||||
t.Fatalf("Unexpected logs: should be '%s', not '%s'\n", expectedLog, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Expected behaviour, the process stays alive when the client disconnects
|
||||
func TestAttachDisconnect(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := NewBuilder(runtime).Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
CpuShares: 1000,
|
||||
Memory: 33554432,
|
||||
Cmd: []string{"/bin/cat"},
|
||||
OpenStdin: true,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
// Start the process
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
// Attach to it
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
// We're simulating a disconnect so the return value doesn't matter. What matters is the
|
||||
// fact that CmdAttach returns.
|
||||
srv.CmdAttach(stdin, rcli.NewDockerLocalConn(stdoutPipe), container.Id)
|
||||
close(c1)
|
||||
}()
|
||||
|
||||
setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "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", 2*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 timeoout in destroy. Best effort, don't check error
|
||||
cStdin, _ := container.StdinPipe()
|
||||
cStdin.Close()
|
||||
container.Wait()
|
||||
}
|
||||
*/
|
||||
+256
-661
File diff suppressed because it is too large
Load Diff
+268
-712
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
# Maintainer wanted! Enroll on #docker@freenode
|
||||
@@ -1,4 +0,0 @@
|
||||
The `contrib` directory contains scripts, images, and other helpful things
|
||||
which are not part of the core docker distribution. Please note that they
|
||||
could be out of date, since they do not receive the same attention as the
|
||||
rest of the repository.
|
||||
@@ -1,126 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
var DOCKERPATH = path.Join(os.Getenv("DOCKERPATH"), "docker")
|
||||
|
||||
// WARNING: this crashTest will 1) crash your host, 2) remove all containers
|
||||
func runDaemon() (*exec.Cmd, error) {
|
||||
os.Remove("/var/run/docker.pid")
|
||||
exec.Command("rm", "-rf", "/var/lib/docker/containers").Run()
|
||||
cmd := exec.Command(DOCKERPATH, "-d")
|
||||
outPipe, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
errPipe, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go func() {
|
||||
io.Copy(os.Stdout, outPipe)
|
||||
}()
|
||||
go func() {
|
||||
io.Copy(os.Stderr, errPipe)
|
||||
}()
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func crashTest() error {
|
||||
if err := exec.Command("/bin/bash", "-c", "while true; do true; done").Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var endpoint string
|
||||
if ep := os.Getenv("TEST_ENDPOINT"); ep == "" {
|
||||
endpoint = "192.168.56.1:7979"
|
||||
} else {
|
||||
endpoint = ep
|
||||
}
|
||||
|
||||
c := make(chan bool)
|
||||
var conn io.Writer
|
||||
|
||||
go func() {
|
||||
conn, _ = net.Dial("tcp", endpoint)
|
||||
c <- false
|
||||
}()
|
||||
go func() {
|
||||
time.Sleep(2 * time.Second)
|
||||
c <- true
|
||||
}()
|
||||
<-c
|
||||
|
||||
restartCount := 0
|
||||
totalTestCount := 1
|
||||
for {
|
||||
daemon, err := runDaemon()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
restartCount++
|
||||
// time.Sleep(5000 * time.Millisecond)
|
||||
var stop bool
|
||||
go func() error {
|
||||
stop = false
|
||||
for i := 0; i < 100 && !stop; {
|
||||
func() error {
|
||||
cmd := exec.Command(DOCKERPATH, "run", "base", "echo", fmt.Sprintf("%d", totalTestCount))
|
||||
i++
|
||||
totalTestCount++
|
||||
outPipe, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
inPipe, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
if conn != nil {
|
||||
go io.Copy(conn, outPipe)
|
||||
}
|
||||
|
||||
// Expecting error, do not check
|
||||
inPipe.Write([]byte("hello world!!!!!\n"))
|
||||
go inPipe.Write([]byte("hello world!!!!!\n"))
|
||||
go inPipe.Write([]byte("hello world!!!!!\n"))
|
||||
inPipe.Close()
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
outPipe.Close()
|
||||
return nil
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
time.Sleep(20 * time.Second)
|
||||
stop = true
|
||||
if err := daemon.Process.Kill(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := crashTest(); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Generate a very minimal filesystem based on busybox-static,
|
||||
# and load it into the local docker under the name "busybox".
|
||||
|
||||
BUSYBOX=$(which busybox)
|
||||
[ "$BUSYBOX" ] || {
|
||||
echo "Sorry, I could not locate busybox."
|
||||
echo "Try 'apt-get install busybox-static'?"
|
||||
exit 1
|
||||
}
|
||||
|
||||
set -e
|
||||
ROOTFS=/tmp/rootfs-busybox-$$-$RANDOM
|
||||
mkdir $ROOTFS
|
||||
cd $ROOTFS
|
||||
|
||||
mkdir bin etc dev dev/pts lib proc sys tmp
|
||||
touch etc/resolv.conf
|
||||
cp /etc/nsswitch.conf etc/nsswitch.conf
|
||||
echo root:x:0:0:root:/:/bin/sh > etc/passwd
|
||||
echo root:x:0: > etc/group
|
||||
ln -s lib lib64
|
||||
ln -s bin sbin
|
||||
cp $BUSYBOX bin
|
||||
for X in $(busybox --list)
|
||||
do
|
||||
ln -s busybox bin/$X
|
||||
done
|
||||
rm bin/init
|
||||
ln bin/busybox bin/init
|
||||
cp /lib/x86_64-linux-gnu/lib{pthread,c,dl,nsl,nss_*}.so.* lib
|
||||
cp /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 lib
|
||||
for X in console null ptmx random stdin stdout stderr tty urandom zero
|
||||
do
|
||||
cp -a /dev/$X dev
|
||||
done
|
||||
|
||||
tar -cf- . | docker import - busybox
|
||||
docker run -i -u root busybox /bin/echo Success.
|
||||
@@ -1,55 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# these should match the names found at http://www.debian.org/releases/
|
||||
stableSuite='wheezy'
|
||||
testingSuite='jessie'
|
||||
unstableSuite='sid'
|
||||
|
||||
variant='minbase'
|
||||
include='iproute,iputils-ping'
|
||||
|
||||
repo="$1"
|
||||
suite="${2:-$stableSuite}"
|
||||
mirror="${3:-}" # stick to the default debootstrap mirror if one is not provided
|
||||
|
||||
if [ ! "$repo" ]; then
|
||||
echo >&2 "usage: $0 repo [suite [mirror]]"
|
||||
echo >&2 " ie: $0 tianon/debian squeeze"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
target="/tmp/docker-rootfs-debian-$suite-$$-$RANDOM"
|
||||
|
||||
cd "$(dirname "$(readlink -f "$BASH_SOURCE")")"
|
||||
returnTo="$(pwd -P)"
|
||||
|
||||
set -x
|
||||
|
||||
# bootstrap
|
||||
mkdir -p "$target"
|
||||
sudo debootstrap --verbose --variant="$variant" --include="$include" "$suite" "$target" "$mirror"
|
||||
|
||||
cd "$target"
|
||||
|
||||
# create the image
|
||||
img=$(sudo tar -c . | docker import -)
|
||||
|
||||
# tag suite
|
||||
docker tag $img $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
|
||||
|
||||
# tag the specific debian release version
|
||||
ver=$(docker run $repo:$suite cat /etc/debian_version)
|
||||
docker tag $img $repo $ver
|
||||
fi
|
||||
|
||||
# cleanup
|
||||
cd "$returnTo"
|
||||
sudo rm -rf "$target"
|
||||
@@ -1,3 +0,0 @@
|
||||
# Vagrant-docker
|
||||
|
||||
This is a placeholder for the official vagrant-docker, a plugin for Vagrant (http://vagrantup.com) which exposes Docker as a provider.
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
dockerd (0.1.1-1) unstable; urgency=low
|
||||
|
||||
* fs branch was merged upstream
|
||||
|
||||
-- Jeremy Grosser <jeremy@synack.me> Wed, 13 Mar 2013 00:23:46 +0000
|
||||
|
||||
dockerd (0.1-2) unstable; urgency=low
|
||||
|
||||
* Fix upstart script
|
||||
|
||||
-- Jeremy Grosser <jeremy@synack.me> Tue, 12 Mar 2013 23:50:16 +0000
|
||||
|
||||
dockerd (0.1-1) unstable; urgency=low
|
||||
|
||||
* Initial release
|
||||
|
||||
-- Jeremy Grosser <jeremy@synack.me> Tue, 12 Mar 2013 03:24:45 +0000
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
Source: dockerd
|
||||
Section: misc
|
||||
Priority: extra
|
||||
Maintainer: Jeremy Grosser <jeremy@synack.me>
|
||||
Build-Depends: debhelper (>= 8.0.0), golang, git, libsqlite3-dev, pkg-config
|
||||
Standards-Version: 3.9.3
|
||||
Homepage: https://github.com/dotcloud/docker/
|
||||
|
||||
Package: dockerd
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}, bsdtar, lxc, curl
|
||||
Description: Process manager with superpowers
|
||||
Encapsulates heterogeneous payloads in Standard Containers, and runs them on
|
||||
any server with strong guarantees of isolation and repeatability.
|
||||
+4
-10
@@ -1,22 +1,16 @@
|
||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Format: http://dep.debian.net/deps/dep5
|
||||
Upstream-Name: docker
|
||||
Upstream-Contact: dotCloud Inc <opensource@dotcloud.com>
|
||||
Source: http://github.com/dotcloud/docker
|
||||
Source: https://github.com/dotcloud/docker
|
||||
|
||||
Files: *
|
||||
Copyright: 2012, dotCloud Inc <opensource@dotcloud.com>
|
||||
License: Apache-2.0
|
||||
Copyright: 2012-2013 dotCloud, inc.
|
||||
License: Apache 2.0
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
.
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
.
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
.
|
||||
On Debian systems, the complete text of the Apache version 2.0 license
|
||||
can be found in "/usr/share/common-licenses/Apache-2.0".
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
build/docker usr/bin
|
||||
build/dockerd usr/sbin
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
description "Run dockerd"
|
||||
|
||||
start on runlevel [2345]
|
||||
stop on starting rc RUNLEVEL=[016]
|
||||
respawn
|
||||
|
||||
script
|
||||
test -f /etc/default/locale && . /etc/default/locale || true
|
||||
LANG=$LANG LC_ALL=$LANG /usr/sbin/dockerd
|
||||
end script
|
||||
+1
-1
@@ -10,4 +10,4 @@
|
||||
#export DH_VERBOSE=1
|
||||
|
||||
%:
|
||||
dh $@
|
||||
dh $@
|
||||
@@ -0,0 +1,160 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"./fs"
|
||||
"container/list"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type Docker struct {
|
||||
root string
|
||||
repository string
|
||||
containers *list.List
|
||||
networkManager *NetworkManager
|
||||
Store *fs.Store
|
||||
}
|
||||
|
||||
func (docker *Docker) List() []*Container {
|
||||
containers := new(History)
|
||||
for e := docker.containers.Front(); e != nil; e = e.Next() {
|
||||
containers.Add(e.Value.(*Container))
|
||||
}
|
||||
return *containers
|
||||
}
|
||||
|
||||
func (docker *Docker) getContainerElement(id string) *list.Element {
|
||||
for e := docker.containers.Front(); e != nil; e = e.Next() {
|
||||
container := e.Value.(*Container)
|
||||
if container.Id == id {
|
||||
return e
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (docker *Docker) Get(id string) *Container {
|
||||
e := docker.getContainerElement(id)
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
return e.Value.(*Container)
|
||||
}
|
||||
|
||||
func (docker *Docker) Exists(id string) bool {
|
||||
return docker.Get(id) != nil
|
||||
}
|
||||
|
||||
func (docker *Docker) Create(id string, command string, args []string, image *fs.Image, config *Config) (*Container, error) {
|
||||
if docker.Exists(id) {
|
||||
return nil, fmt.Errorf("Container %v already exists", id)
|
||||
}
|
||||
root := path.Join(docker.repository, id)
|
||||
|
||||
container, err := createContainer(id, root, command, args, image, config, docker.networkManager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
docker.containers.PushBack(container)
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func (docker *Docker) Destroy(container *Container) error {
|
||||
element := docker.getContainerElement(container.Id)
|
||||
if element == nil {
|
||||
return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.Id)
|
||||
}
|
||||
|
||||
if err := container.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
if container.Mountpoint.Mounted() {
|
||||
if err := container.Mountpoint.Umount(); err != nil {
|
||||
log.Printf("Unable to umount container %v: %v", container.Id, err)
|
||||
}
|
||||
|
||||
if err := container.Mountpoint.Deregister(); err != nil {
|
||||
log.Printf("Unable to deregiser mountpoint %v: %v", container.Mountpoint.Root, err)
|
||||
}
|
||||
}
|
||||
if err := os.RemoveAll(container.Root); err != nil {
|
||||
log.Printf("Unable to remove filesystem for %v: %v", container.Id, err)
|
||||
}
|
||||
docker.containers.Remove(element)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (docker *Docker) restore() error {
|
||||
dir, err := ioutil.ReadDir(docker.repository)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range dir {
|
||||
container, err := loadContainer(docker.Store, path.Join(docker.repository, v.Name()), docker.networkManager)
|
||||
if err != nil {
|
||||
log.Printf("Failed to load container %v: %v", v.Name(), err)
|
||||
continue
|
||||
}
|
||||
docker.containers.PushBack(container)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func New() (*Docker, error) {
|
||||
return NewFromDirectory("/var/lib/docker")
|
||||
}
|
||||
|
||||
func NewFromDirectory(root string) (*Docker, error) {
|
||||
store, err := fs.New(path.Join(root, "images"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
netManager, err := newNetworkManager(networkBridgeIface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
docker := &Docker{
|
||||
root: root,
|
||||
repository: path.Join(root, "containers"),
|
||||
containers: list.New(),
|
||||
Store: store,
|
||||
networkManager: netManager,
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(docker.repository, 0700); err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := docker.restore(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return docker, nil
|
||||
}
|
||||
|
||||
type History []*Container
|
||||
|
||||
func (history *History) Len() int {
|
||||
return len(*history)
|
||||
}
|
||||
|
||||
func (history *History) Less(i, j int) bool {
|
||||
containers := *history
|
||||
return containers[j].When().Before(containers[i].When())
|
||||
}
|
||||
|
||||
func (history *History) Swap(i, j int) {
|
||||
containers := *history
|
||||
tmp := containers[i]
|
||||
containers[i] = containers[j]
|
||||
containers[j] = tmp
|
||||
}
|
||||
|
||||
func (history *History) Add(container *Container) {
|
||||
*history = append(*history, container)
|
||||
sort.Sort(history)
|
||||
}
|
||||
+14
-135
@@ -1,150 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"../client"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
GITCOMMIT string
|
||||
"path"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if utils.SelfPath() == "/sbin/init" {
|
||||
// Running in init mode
|
||||
docker.SysInit()
|
||||
return
|
||||
}
|
||||
// FIXME: Switch d and D ? (to be more sshd like)
|
||||
flDaemon := flag.Bool("d", false, "Daemon mode")
|
||||
flDebug := flag.Bool("D", false, "Debug mode")
|
||||
flAutoRestart := flag.Bool("r", false, "Restart previously running containers")
|
||||
bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge")
|
||||
pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
|
||||
flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
|
||||
flDns := flag.String("dns", "", "Set custom dns servers")
|
||||
flHosts := docker.ListOpts{fmt.Sprintf("tcp://%s:%d", docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT)}
|
||||
flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use")
|
||||
flag.Parse()
|
||||
if len(flHosts) > 1 {
|
||||
flHosts = flHosts[1:len(flHosts)] //trick to display a nice defaul value in the usage
|
||||
}
|
||||
for i, flHost := range flHosts {
|
||||
flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
|
||||
}
|
||||
|
||||
if *bridgeName != "" {
|
||||
docker.NetworkBridgeIface = *bridgeName
|
||||
} else {
|
||||
docker.NetworkBridgeIface = docker.DefaultNetworkBridge
|
||||
}
|
||||
if *flDebug {
|
||||
os.Setenv("DEBUG", "1")
|
||||
}
|
||||
docker.GITCOMMIT = GITCOMMIT
|
||||
if *flDaemon {
|
||||
if flag.NArg() != 0 {
|
||||
flag.Usage()
|
||||
return
|
||||
}
|
||||
if err := daemon(*pidfile, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil {
|
||||
log.Fatal(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
} else {
|
||||
if len(flHosts) > 1 {
|
||||
log.Fatal("Please specify only one -H")
|
||||
return
|
||||
}
|
||||
protoAddrParts := strings.SplitN(flHosts[0], "://", 2)
|
||||
if err := docker.ParseCommands(protoAddrParts[0], protoAddrParts[1], flag.Args()...); err != nil {
|
||||
log.Fatal(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createPidFile(pidfile string) error {
|
||||
if pidString, err := ioutil.ReadFile(pidfile); err == nil {
|
||||
pid, err := strconv.Atoi(string(pidString))
|
||||
if err == nil {
|
||||
if _, err := os.Stat(fmt.Sprintf("/proc/%d/", pid)); err == nil {
|
||||
return fmt.Errorf("pid file found, ensure docker is not running or delete %s", pidfile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file, err := os.Create(pidfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
_, err = fmt.Fprintf(file, "%d", os.Getpid())
|
||||
return err
|
||||
}
|
||||
|
||||
func removePidFile(pidfile string) {
|
||||
if err := os.Remove(pidfile); err != nil {
|
||||
log.Printf("Error removing %s: %s", pidfile, err)
|
||||
}
|
||||
}
|
||||
|
||||
func daemon(pidfile string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error {
|
||||
if err := createPidFile(pidfile); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer removePidFile(pidfile)
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM))
|
||||
go func() {
|
||||
sig := <-c
|
||||
log.Printf("Received signal '%v', exiting\n", sig)
|
||||
removePidFile(pidfile)
|
||||
os.Exit(0)
|
||||
}()
|
||||
var dns []string
|
||||
if flDns != "" {
|
||||
dns = []string{flDns}
|
||||
}
|
||||
server, err := docker.NewServer(autoRestart, enableCors, dns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chErrors := make(chan error, len(protoAddrs))
|
||||
for _, protoAddr := range protoAddrs {
|
||||
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
|
||||
if protoAddrParts[0] == "unix" {
|
||||
syscall.Unlink(protoAddrParts[1]);
|
||||
} else if protoAddrParts[0] == "tcp" {
|
||||
if !strings.HasPrefix(protoAddrParts[1], "127.0.0.1") {
|
||||
log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
|
||||
if cmd := path.Base(os.Args[0]); cmd == "docker" {
|
||||
fl_shell := flag.Bool("i", false, "Interactive mode")
|
||||
flag.Parse()
|
||||
if *fl_shell {
|
||||
if err := client.InteractiveMode(flag.Args()...); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
log.Fatal("Invalid protocol format.")
|
||||
os.Exit(-1)
|
||||
if err := client.SimpleMode(os.Args[1:]); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
go func() {
|
||||
chErrors <- docker.ListenAndServe(protoAddrParts[0], protoAddrParts[1], server, true)
|
||||
}()
|
||||
}
|
||||
for i :=0 ; i < len(protoAddrs); i+=1 {
|
||||
err := <-chErrors
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
if err := client.SimpleMode(append([]string{cmd}, os.Args[1:]...)); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"./fs"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const testLayerPath string = "/var/lib/docker/docker-ut.tar"
|
||||
|
||||
func nuke(docker *Docker) error {
|
||||
return os.RemoveAll(docker.root)
|
||||
}
|
||||
|
||||
func layerArchive(tarfile string) (io.Reader, error) {
|
||||
// FIXME: need to close f somewhere
|
||||
f, err := os.Open(tarfile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Hack to run sys init during unit testing
|
||||
if SelfPath() == "/sbin/init" {
|
||||
SysInit()
|
||||
}
|
||||
|
||||
// Make sure the unit test image is there
|
||||
if _, err := os.Stat(testLayerPath); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
panic(err)
|
||||
}
|
||||
log.Fatalf("Unit test base image not found. Please fix the problem by running \"debootstrap --arch=amd64 quantal %v\"", testLayerPath)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func newTestDocker() (*Docker, error) {
|
||||
root, err := ioutil.TempDir("", "docker-test")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
docker, err := NewFromDirectory(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if layer, err := layerArchive(testLayerPath); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
_, err = docker.Store.Create(layer, nil, "docker-ut", "unit tests")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return docker, nil
|
||||
}
|
||||
|
||||
func GetTestImage(docker *Docker) *fs.Image {
|
||||
imgs, err := docker.Store.Images()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
} else if len(imgs) < 1 {
|
||||
panic("GASP")
|
||||
}
|
||||
return imgs[0]
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
docker, err := newTestDocker()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(docker)
|
||||
|
||||
// Make sure we start we 0 containers
|
||||
if len(docker.List()) != 0 {
|
||||
t.Errorf("Expected 0 containers, %v found", len(docker.List()))
|
||||
}
|
||||
container, err := docker.Create(
|
||||
"test_create",
|
||||
"ls",
|
||||
[]string{"-al"},
|
||||
GetTestImage(docker),
|
||||
&Config{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := docker.Destroy(container); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Make sure we can find the newly created container with List()
|
||||
if len(docker.List()) != 1 {
|
||||
t.Errorf("Expected 1 container, %v found", len(docker.List()))
|
||||
}
|
||||
|
||||
// Make sure the container List() returns is the right one
|
||||
if docker.List()[0].Id != "test_create" {
|
||||
t.Errorf("Unexpected container %v returned by List", docker.List()[0])
|
||||
}
|
||||
|
||||
// Make sure we can get the container with Get()
|
||||
if docker.Get("test_create") == nil {
|
||||
t.Errorf("Unable to get newly created container")
|
||||
}
|
||||
|
||||
// Make sure it is the right container
|
||||
if docker.Get("test_create") != container {
|
||||
t.Errorf("Get() returned the wrong container")
|
||||
}
|
||||
|
||||
// Make sure Exists returns it as existing
|
||||
if !docker.Exists("test_create") {
|
||||
t.Errorf("Exists() returned false for a newly created container")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDestroy(t *testing.T) {
|
||||
docker, err := newTestDocker()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(docker)
|
||||
container, err := docker.Create(
|
||||
"test_destroy",
|
||||
"ls",
|
||||
[]string{"-al"},
|
||||
GetTestImage(docker),
|
||||
&Config{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Destroy
|
||||
if err := docker.Destroy(container); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// Make sure docker.Exists() behaves correctly
|
||||
if docker.Exists("test_destroy") {
|
||||
t.Errorf("Exists() returned true")
|
||||
}
|
||||
|
||||
// Make sure docker.List() doesn't list the destroyed container
|
||||
if len(docker.List()) != 0 {
|
||||
t.Errorf("Expected 0 container, %v found", len(docker.List()))
|
||||
}
|
||||
|
||||
// Make sure docker.Get() refuses to return the unexisting container
|
||||
if docker.Get("test_destroy") != nil {
|
||||
t.Errorf("Unable to get newly created container")
|
||||
}
|
||||
|
||||
// Make sure the container root directory does not exist anymore
|
||||
_, err = os.Stat(container.Root)
|
||||
if err == nil || !os.IsNotExist(err) {
|
||||
t.Errorf("Container root directory still exists after destroy")
|
||||
}
|
||||
|
||||
// Test double destroy
|
||||
if err := docker.Destroy(container); err == nil {
|
||||
// It should have failed
|
||||
t.Errorf("Double destroy did not fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
docker, err := newTestDocker()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(docker)
|
||||
container1, err := docker.Create(
|
||||
"test1",
|
||||
"ls",
|
||||
[]string{"-al"},
|
||||
GetTestImage(docker),
|
||||
&Config{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(container1)
|
||||
|
||||
container2, err := docker.Create(
|
||||
"test2",
|
||||
"ls",
|
||||
[]string{"-al"},
|
||||
GetTestImage(docker),
|
||||
&Config{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(container2)
|
||||
|
||||
container3, err := docker.Create(
|
||||
"test3",
|
||||
"ls",
|
||||
[]string{"-al"},
|
||||
GetTestImage(docker),
|
||||
&Config{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(container3)
|
||||
|
||||
if docker.Get("test1") != container1 {
|
||||
t.Errorf("Get(test1) returned %v while expecting %v", docker.Get("test1"), container1)
|
||||
}
|
||||
|
||||
if docker.Get("test2") != container2 {
|
||||
t.Errorf("Get(test2) returned %v while expecting %v", docker.Get("test2"), container2)
|
||||
}
|
||||
|
||||
if docker.Get("test3") != container3 {
|
||||
t.Errorf("Get(test3) returned %v while expecting %v", docker.Get("test3"), container3)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRestore(t *testing.T) {
|
||||
root, err := ioutil.TempDir("", "docker-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
docker1, err := NewFromDirectory(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(docker1)
|
||||
|
||||
if layer, err := layerArchive(testLayerPath); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
_, err = docker1.Store.Create(layer, nil, "docker-ut", "unit tests")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a container with one instance of docker
|
||||
container1, err := docker1.Create(
|
||||
"restore_test",
|
||||
"ls",
|
||||
[]string{"-al"},
|
||||
GetTestImage(docker1),
|
||||
&Config{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker1.Destroy(container1)
|
||||
if len(docker1.List()) != 1 {
|
||||
t.Errorf("Expected 1 container, %v found", len(docker1.List()))
|
||||
}
|
||||
if err := container1.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Here are are simulating a docker restart - that is, reloading all containers
|
||||
// from scratch
|
||||
docker2, err := NewFromDirectory(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(docker2)
|
||||
if len(docker2.List()) != 1 {
|
||||
t.Errorf("Expected 1 container, %v found", len(docker2.List()))
|
||||
}
|
||||
container2 := docker2.Get("restore_test")
|
||||
if container2 == nil {
|
||||
t.Fatal("Unable to Get container")
|
||||
}
|
||||
if err := container2.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
".."
|
||||
"../server"
|
||||
"flag"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if docker.SelfPath() == "/sbin/init" {
|
||||
// Running in init mode
|
||||
docker.SysInit()
|
||||
return
|
||||
}
|
||||
flag.Parse()
|
||||
d, err := server.New()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := d.ListenAndServe(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
Executable
+221
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
||||
Andy Rothfusz <andy@dotcloud.com>
|
||||
Ken Cochrane <ken@dotcloud.com>
|
||||
-185
@@ -1,185 +0,0 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
PYTHON = python
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) sources
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
# @echo " html to make standalone HTML files"
|
||||
# @echo " dirhtml to make HTML files named index.html in directories"
|
||||
# @echo " singlehtml to make a single large HTML file"
|
||||
# @echo " pickle to make pickle files"
|
||||
# @echo " json to make JSON files"
|
||||
# @echo " htmlhelp to make HTML files and a HTML help project"
|
||||
# @echo " qthelp to make HTML files and a qthelp project"
|
||||
# @echo " devhelp to make HTML files and a Devhelp project"
|
||||
# @echo " epub to make an epub"
|
||||
# @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
# @echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
# @echo " text to make text files"
|
||||
# @echo " man to make manual pages"
|
||||
# @echo " texinfo to make Texinfo files"
|
||||
# @echo " info to make Texinfo files and run them through makeinfo"
|
||||
# @echo " gettext to make PO message catalogs"
|
||||
# @echo " changes to make an overview of all changed/added/deprecated items"
|
||||
# @echo " linkcheck to check all external links for integrity"
|
||||
# @echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
@echo " docs to build the docs and copy the static files to the outputdir"
|
||||
@echo " server to serve the docs in your browser under \`http://localhost:8000\`"
|
||||
@echo " publish to publish the app to dotcloud"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
docs:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The documentation pages are now in $(BUILDDIR)/html."
|
||||
|
||||
server: docs
|
||||
@cd $(BUILDDIR)/html; $(PYTHON) -m SimpleHTTPServer 8000
|
||||
|
||||
site:
|
||||
cp -r website $(BUILDDIR)/
|
||||
cp -r theme/docker/static/ $(BUILDDIR)/website/
|
||||
@echo
|
||||
@echo "The Website pages are in $(BUILDDIR)/site."
|
||||
|
||||
connect:
|
||||
@echo connecting dotcloud to www.docker.io website, make sure to use user 1
|
||||
@echo or create your own "dockerwebsite" app
|
||||
@cd $(BUILDDIR)/website/ ; \
|
||||
dotcloud connect dockerwebsite ; \
|
||||
dotcloud list
|
||||
|
||||
push:
|
||||
@cd $(BUILDDIR)/website/ ; \
|
||||
dotcloud push
|
||||
|
||||
$(VERSIONS):
|
||||
@echo "Hello world"
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Docker.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Docker.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/Docker"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Docker"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
@@ -1,77 +0,0 @@
|
||||
Docker documentation and website
|
||||
================================
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
This is your definite place to contribute to the docker documentation. The documentation is generated from the
|
||||
.rst files under sources.
|
||||
|
||||
The folder also contains the other files to create the http://docker.io website, but you can generally ignore
|
||||
most of those.
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
* Work in your own fork of the code, we accept pull requests.
|
||||
* Install sphinx: `pip install sphinx`
|
||||
* Mac OS X: `[sudo] pip-2.7 install sphinx`)
|
||||
* Install sphinx httpdomain contrib package: `pip install sphinxcontrib-httpdomain`
|
||||
* Mac OS X: `[sudo] pip-2.7 install sphinxcontrib-httpdomain`
|
||||
* If pip is not available you can probably install it using your favorite package manager as **python-pip**
|
||||
|
||||
Usage
|
||||
-----
|
||||
* Change the `.rst` files with your favorite editor to your liking.
|
||||
* Run `make docs` to clean up old files and generate new ones.
|
||||
* Your static website can now be found in the `_build` directory.
|
||||
* To preview what you have generated run `make server` and open <http://localhost:8000/> in your favorite browser.
|
||||
|
||||
Working using GitHub's file editor
|
||||
----------------------------------
|
||||
Alternatively, for small changes and typo's you might want to use GitHub's built in file editor. It allows
|
||||
you to preview your changes right online. Just be carefull not to create many commits.
|
||||
|
||||
Images
|
||||
------
|
||||
When you need to add images, try to make them as small as possible (e.g. as gif).
|
||||
|
||||
|
||||
Notes
|
||||
-----
|
||||
* The index.html and gettingstarted.html files are copied from the source dir to the output dir without modification.
|
||||
So changes to those pages should be made directly in html
|
||||
* For the template the css is compiled from less. When changes are needed they can be compiled using
|
||||
lessc ``lessc main.less`` or watched using watch-lessc ``watch-lessc -i main.less -o main.css``
|
||||
|
||||
|
||||
Guides on using sphinx
|
||||
----------------------
|
||||
* To make links to certain pages create a link target like so:
|
||||
|
||||
```
|
||||
.. _hello_world:
|
||||
|
||||
Hello world
|
||||
===========
|
||||
|
||||
This is.. (etc.)
|
||||
```
|
||||
|
||||
The ``_hello_world:`` will make it possible to link to this position (page and marker) from all other pages.
|
||||
|
||||
* Notes, warnings and alarms
|
||||
|
||||
```
|
||||
# a note (use when something is important)
|
||||
.. note::
|
||||
|
||||
# a warning (orange)
|
||||
.. warning::
|
||||
|
||||
# danger (red, use sparsely)
|
||||
.. danger::
|
||||
|
||||
* Code examples
|
||||
|
||||
Start without $, so it's easy to copy and paste.
|
||||
@@ -1,2 +0,0 @@
|
||||
Sphinx==1.1.3
|
||||
sphinxcontrib-httpdomain==1.1.8
|
||||
@@ -1 +0,0 @@
|
||||
Solomon Hykes <solomon@dotcloud.com>
|
||||
@@ -1,5 +0,0 @@
|
||||
This directory holds the authoritative specifications of APIs defined and implemented by Docker. Currently this includes:
|
||||
|
||||
* The remote API by which a docker node can be queried over HTTP
|
||||
* The registry API by which a docker node can download and upload container images for storage and sharing
|
||||
* The index search API by which a docker node can search the public index for images to download
|
||||
@@ -1,100 +0,0 @@
|
||||
:title: Remote API
|
||||
:description: API Documentation for Docker
|
||||
:keywords: API, Docker, rcli, REST, documentation
|
||||
|
||||
=================
|
||||
Docker Remote API
|
||||
=================
|
||||
|
||||
.. contents:: Table of Contents
|
||||
|
||||
1. Brief introduction
|
||||
=====================
|
||||
|
||||
- The Remote API is replacing rcli
|
||||
- Default port in the docker deamon is 4243
|
||||
- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
|
||||
- Since API version 1.2, the auth configuration is now handled client side, so the client has to send the authConfig as POST in /images/(name)/push
|
||||
|
||||
2. Versions
|
||||
===========
|
||||
|
||||
The current verson of the API is 1.2
|
||||
Calling /images/<name>/insert is the same as calling /v1.2/images/<name>/insert
|
||||
You can still call an old version of the api using /v1.0/images/<name>/insert
|
||||
|
||||
:doc:`docker_remote_api_v1.2`
|
||||
*****************************
|
||||
|
||||
What's new
|
||||
----------
|
||||
|
||||
The auth configuration is now handled by the client.
|
||||
The client should send it's authConfig as POST on each call of /images/(name)/push
|
||||
|
||||
.. http:get:: /auth is now deprecated
|
||||
.. http:post:: /auth only checks the configuration but doesn't store it on the server
|
||||
|
||||
Deleting an image is now improved, will only untag the image if it has chidrens and remove all the untagged parents if has any.
|
||||
.. http:post:: /images/<name>/delete now returns a JSON with the list of images deleted/untagged
|
||||
|
||||
|
||||
:doc:`docker_remote_api_v1.1`
|
||||
*****************************
|
||||
|
||||
docker v0.4.0 a8ae398_
|
||||
|
||||
What's new
|
||||
----------
|
||||
|
||||
.. http:post:: /images/create
|
||||
.. http:post:: /images/(name)/insert
|
||||
.. http:post:: /images/(name)/push
|
||||
|
||||
Uses json stream instead of HTML hijack, it looks like this:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{"status":"Pushing..."}
|
||||
{"status":"Pushing", "progress":"1/? (n/a)"}
|
||||
{"error":"Invalid..."}
|
||||
...
|
||||
|
||||
|
||||
docker v0.3.4 8d73740_
|
||||
|
||||
What's new
|
||||
----------
|
||||
|
||||
Initial version
|
||||
|
||||
|
||||
.. _a8ae398: https://github.com/dotcloud/docker/commit/a8ae398bf52e97148ee7bd0d5868de2e15bd297f
|
||||
.. _8d73740: https://github.com/dotcloud/docker/commit/8d73740343778651c09160cde9661f5f387b36f4
|
||||
|
||||
==================================
|
||||
Docker Remote API Client Libraries
|
||||
==================================
|
||||
|
||||
These libraries have been not 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-ruby | https://github.com/ActiveState/docker-ruby |
|
||||
+----------------------+----------------+--------------------------------------------+
|
||||
| Ruby | docker-client | https://github.com/geku/docker-client |
|
||||
+----------------------+----------------+--------------------------------------------+
|
||||
| Javascript | docker-js | https://github.com/dgoujard/docker-js |
|
||||
+----------------------+----------------+--------------------------------------------+
|
||||
| Javascript (Angular) | dockerui | https://github.com/crosbymichael/dockerui |
|
||||
| **WebUI** | | |
|
||||
+----------------------+----------------+--------------------------------------------+
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,18 +0,0 @@
|
||||
:title: API Documentation
|
||||
:description: docker documentation
|
||||
:keywords: docker, ipa, documentation
|
||||
|
||||
APIs
|
||||
====
|
||||
|
||||
Your programs and scripts can access Docker's functionality via these interfaces:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
|
||||
registry_index_spec
|
||||
registry_api
|
||||
index_api
|
||||
docker_remote_api
|
||||
|
||||
|
||||
@@ -1,553 +0,0 @@
|
||||
:title: Index API
|
||||
:description: API Documentation for Docker Index
|
||||
:keywords: API, Docker, index, REST, documentation
|
||||
|
||||
=================
|
||||
Docker Index API
|
||||
=================
|
||||
|
||||
.. contents:: Table of Contents
|
||||
|
||||
1. Brief introduction
|
||||
=====================
|
||||
|
||||
- This is the REST API for the Docker index
|
||||
- Authorization is done with basic auth over SSL
|
||||
- Not all commands require authentication, only those noted as such.
|
||||
|
||||
2. Endpoints
|
||||
============
|
||||
|
||||
2.1 Repository
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
Repositories
|
||||
*************
|
||||
|
||||
User Repo
|
||||
~~~~~~~~~
|
||||
|
||||
.. http:put:: /v1/repositories/(namespace)/(repo_name)/
|
||||
|
||||
Create a user repository with the given ``namespace`` and ``repo_name``.
|
||||
|
||||
**Example Request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PUT /v1/repositories/foo/bar/ HTTP/1.1
|
||||
Host: index.docker.io
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Basic akmklmasadalkm==
|
||||
X-Docker-Token: true
|
||||
|
||||
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”}]
|
||||
|
||||
:parameter namespace: the namespace for the repo
|
||||
:parameter repo_name: the name for the repo
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
WWW-Authenticate: Token signature=123abc,repository=”foo/bar”,access=write
|
||||
X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io]
|
||||
|
||||
""
|
||||
|
||||
:statuscode 200: Created
|
||||
:statuscode 400: Errors (invalid json, missing or invalid fields, etc)
|
||||
:statuscode 401: Unauthorized
|
||||
:statuscode 403: Account is not Active
|
||||
|
||||
|
||||
.. http:delete:: /v1/repositories/(namespace)/(repo_name)/
|
||||
|
||||
Delete a user repository with the given ``namespace`` and ``repo_name``.
|
||||
|
||||
**Example Request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
DELETE /v1/repositories/foo/bar/ HTTP/1.1
|
||||
Host: index.docker.io
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Basic akmklmasadalkm==
|
||||
X-Docker-Token: true
|
||||
|
||||
""
|
||||
|
||||
:parameter namespace: the namespace for the repo
|
||||
:parameter repo_name: the name for the repo
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 202
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
WWW-Authenticate: Token signature=123abc,repository=”foo/bar”,access=delete
|
||||
X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io]
|
||||
|
||||
""
|
||||
|
||||
:statuscode 200: Deleted
|
||||
:statuscode 202: Accepted
|
||||
:statuscode 400: Errors (invalid json, missing or invalid fields, etc)
|
||||
:statuscode 401: Unauthorized
|
||||
:statuscode 403: Account is not Active
|
||||
|
||||
Library Repo
|
||||
~~~~~~~~~~~~
|
||||
|
||||
.. http:put:: /v1/repositories/(repo_name)/
|
||||
|
||||
Create a library repository with the given ``repo_name``.
|
||||
This is a restricted feature only available to docker admins.
|
||||
|
||||
When namespace is missing, it is assumed to be ``library``
|
||||
|
||||
**Example Request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PUT /v1/repositories/foobar/ HTTP/1.1
|
||||
Host: index.docker.io
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Basic akmklmasadalkm==
|
||||
X-Docker-Token: true
|
||||
|
||||
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”}]
|
||||
|
||||
:parameter repo_name: the library name for the repo
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
WWW-Authenticate: Token signature=123abc,repository=”library/foobar”,access=write
|
||||
X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io]
|
||||
|
||||
""
|
||||
|
||||
:statuscode 200: Created
|
||||
:statuscode 400: Errors (invalid json, missing or invalid fields, etc)
|
||||
:statuscode 401: Unauthorized
|
||||
:statuscode 403: Account is not Active
|
||||
|
||||
.. http:delete:: /v1/repositories/(repo_name)/
|
||||
|
||||
Delete a library repository with the given ``repo_name``.
|
||||
This is a restricted feature only available to docker admins.
|
||||
|
||||
When namespace is missing, it is assumed to be ``library``
|
||||
|
||||
**Example Request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
DELETE /v1/repositories/foobar/ HTTP/1.1
|
||||
Host: index.docker.io
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Basic akmklmasadalkm==
|
||||
X-Docker-Token: true
|
||||
|
||||
""
|
||||
|
||||
:parameter repo_name: the library name for the repo
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 202
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
WWW-Authenticate: Token signature=123abc,repository=”library/foobar”,access=delete
|
||||
X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io]
|
||||
|
||||
""
|
||||
|
||||
:statuscode 200: Deleted
|
||||
:statuscode 202: Accepted
|
||||
:statuscode 400: Errors (invalid json, missing or invalid fields, etc)
|
||||
:statuscode 401: Unauthorized
|
||||
:statuscode 403: Account is not Active
|
||||
|
||||
Repository Images
|
||||
*****************
|
||||
|
||||
User Repo Images
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
.. http:put:: /v1/repositories/(namespace)/(repo_name)/images
|
||||
|
||||
Update the images for a user repo.
|
||||
|
||||
**Example Request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PUT /v1/repositories/foo/bar/images HTTP/1.1
|
||||
Host: index.docker.io
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Basic akmklmasadalkm==
|
||||
|
||||
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”,
|
||||
“checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}]
|
||||
|
||||
:parameter namespace: the namespace for the repo
|
||||
:parameter repo_name: the name for the repo
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 204
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
""
|
||||
|
||||
:statuscode 204: Created
|
||||
:statuscode 400: Errors (invalid json, missing or invalid fields, etc)
|
||||
:statuscode 401: Unauthorized
|
||||
:statuscode 403: Account is not Active or permission denied
|
||||
|
||||
|
||||
.. http:get:: /v1/repositories/(namespace)/(repo_name)/images
|
||||
|
||||
get the images for a user repo.
|
||||
|
||||
**Example Request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /v1/repositories/foo/bar/images HTTP/1.1
|
||||
Host: index.docker.io
|
||||
Accept: application/json
|
||||
|
||||
:parameter namespace: the namespace for the repo
|
||||
:parameter repo_name: the name for the repo
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”,
|
||||
“checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”},
|
||||
{“id”: “ertwetewtwe38722009fe6857087b486531f9a779a0c1dfddgfgsdgdsgds”,
|
||||
“checksum”: “34t23f23fc17e3ed29dae8f12c4f9e89cc6f0bsdfgfsdgdsgdsgerwgew”}]
|
||||
|
||||
:statuscode 200: OK
|
||||
:statuscode 404: Not found
|
||||
|
||||
Library Repo Images
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. http:put:: /v1/repositories/(repo_name)/images
|
||||
|
||||
Update the images for a library repo.
|
||||
|
||||
**Example Request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PUT /v1/repositories/foobar/images HTTP/1.1
|
||||
Host: index.docker.io
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Basic akmklmasadalkm==
|
||||
|
||||
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”,
|
||||
“checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}]
|
||||
|
||||
:parameter repo_name: the library name for the repo
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 204
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
""
|
||||
|
||||
:statuscode 204: Created
|
||||
:statuscode 400: Errors (invalid json, missing or invalid fields, etc)
|
||||
:statuscode 401: Unauthorized
|
||||
:statuscode 403: Account is not Active or permission denied
|
||||
|
||||
|
||||
.. http:get:: /v1/repositories/(repo_name)/images
|
||||
|
||||
get the images for a library repo.
|
||||
|
||||
**Example Request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /v1/repositories/foobar/images HTTP/1.1
|
||||
Host: index.docker.io
|
||||
Accept: application/json
|
||||
|
||||
:parameter repo_name: the library name for the repo
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”,
|
||||
“checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”},
|
||||
{“id”: “ertwetewtwe38722009fe6857087b486531f9a779a0c1dfddgfgsdgdsgds”,
|
||||
“checksum”: “34t23f23fc17e3ed29dae8f12c4f9e89cc6f0bsdfgfsdgdsgdsgerwgew”}]
|
||||
|
||||
:statuscode 200: OK
|
||||
:statuscode 404: Not found
|
||||
|
||||
|
||||
Repository Authorization
|
||||
************************
|
||||
|
||||
Library Repo
|
||||
~~~~~~~~~~~~
|
||||
|
||||
.. http:put:: /v1/repositories/(repo_name)/auth
|
||||
|
||||
authorize a token for a library repo
|
||||
|
||||
**Example Request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PUT /v1/repositories/foobar/auth HTTP/1.1
|
||||
Host: index.docker.io
|
||||
Accept: application/json
|
||||
Authorization: Token signature=123abc,repository="library/foobar",access=write
|
||||
|
||||
:parameter repo_name: the library name for the repo
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
"OK"
|
||||
|
||||
:statuscode 200: OK
|
||||
:statuscode 403: Permission denied
|
||||
:statuscode 404: Not found
|
||||
|
||||
|
||||
User Repo
|
||||
~~~~~~~~~
|
||||
|
||||
.. http:put:: /v1/repositories/(namespace)/(repo_name)/auth
|
||||
|
||||
authorize a token for a user repo
|
||||
|
||||
**Example Request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PUT /v1/repositories/foo/bar/auth HTTP/1.1
|
||||
Host: index.docker.io
|
||||
Accept: application/json
|
||||
Authorization: Token signature=123abc,repository="foo/bar",access=write
|
||||
|
||||
:parameter namespace: the namespace for the repo
|
||||
:parameter repo_name: the name for the repo
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
"OK"
|
||||
|
||||
:statuscode 200: OK
|
||||
:statuscode 403: Permission denied
|
||||
:statuscode 404: Not found
|
||||
|
||||
|
||||
2.2 Users
|
||||
^^^^^^^^^
|
||||
|
||||
User Login
|
||||
**********
|
||||
|
||||
.. http:get:: /v1/users
|
||||
|
||||
If you want to check your login, you can try this endpoint
|
||||
|
||||
**Example Request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /v1/users HTTP/1.1
|
||||
Host: index.docker.io
|
||||
Accept: application/json
|
||||
Authorization: Basic akmklmasadalkm==
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
OK
|
||||
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Unauthorized
|
||||
:statuscode 403: Account is not Active
|
||||
|
||||
|
||||
User Register
|
||||
*************
|
||||
|
||||
.. http:post:: /v1/users
|
||||
|
||||
Registering a new account.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /v1/users HTTP/1.1
|
||||
Host: index.docker.io
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
|
||||
{"email": "sam@dotcloud.com",
|
||||
"password": "toto42",
|
||||
"username": "foobar"'}
|
||||
|
||||
:jsonparameter email: valid email address, that needs to be confirmed
|
||||
:jsonparameter username: min 4 character, max 30 characters, must match the regular expression [a-z0-9_].
|
||||
:jsonparameter password: min 5 characters
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 201 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
"User Created"
|
||||
|
||||
:statuscode 201: User Created
|
||||
:statuscode 400: Errors (invalid json, missing or invalid fields, etc)
|
||||
|
||||
Update User
|
||||
***********
|
||||
|
||||
.. http:put:: /v1/users/(username)/
|
||||
|
||||
Change a password or email address for given user. If you pass in an email,
|
||||
it will add it to your account, it will not remove the old one. Passwords will
|
||||
be updated.
|
||||
|
||||
It is up to the client to verify that that password that is sent is the one that
|
||||
they want. Common approach is to have them type it twice.
|
||||
|
||||
**Example Request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PUT /v1/users/fakeuser/ HTTP/1.1
|
||||
Host: index.docker.io
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Basic akmklmasadalkm==
|
||||
|
||||
{"email": "sam@dotcloud.com",
|
||||
"password": "toto42"}
|
||||
|
||||
:parameter username: username for the person you want to update
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 204
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
""
|
||||
|
||||
:statuscode 204: User Updated
|
||||
:statuscode 400: Errors (invalid json, missing or invalid fields, etc)
|
||||
:statuscode 401: Unauthorized
|
||||
:statuscode 403: Account is not Active
|
||||
:statuscode 404: User not found
|
||||
|
||||
|
||||
2.3 Search
|
||||
^^^^^^^^^^
|
||||
If you need to search the index, this is the endpoint you would use.
|
||||
|
||||
Search
|
||||
******
|
||||
|
||||
.. http:get:: /v1/search
|
||||
|
||||
Search the Index given a search term. It accepts :http:method:`get` only.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /v1/search?q=search_term HTTP/1.1
|
||||
Host: example.com
|
||||
Accept: application/json
|
||||
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{"query":"search_term",
|
||||
"num_results": 2,
|
||||
"results" : [
|
||||
{"name": "dotcloud/base", "description": "A base ubuntu64 image..."},
|
||||
{"name": "base2", "description": "A base ubuntu64 image..."},
|
||||
]
|
||||
}
|
||||
|
||||
:query q: what you want to search for
|
||||
:statuscode 200: no error
|
||||
:statuscode 500: server error
|
||||
@@ -1,463 +0,0 @@
|
||||
:title: Registry API
|
||||
:description: API Documentation for Docker Registry
|
||||
:keywords: API, Docker, index, registry, REST, documentation
|
||||
|
||||
===================
|
||||
Docker Registry API
|
||||
===================
|
||||
|
||||
.. contents:: Table of Contents
|
||||
|
||||
1. Brief introduction
|
||||
=====================
|
||||
|
||||
- This is the REST API for the Docker Registry
|
||||
- It stores the images and the graph for a set of repositories
|
||||
- It does not have user accounts data
|
||||
- It has no notion of user accounts or authorization
|
||||
- It delegates authentication and authorization to the Index Auth service using tokens
|
||||
- It supports different storage backends (S3, cloud files, local FS)
|
||||
- It doesn’t have a local database
|
||||
- It will be open-sourced at some point
|
||||
|
||||
We expect that there will be multiple registries out there. To help to grasp the context, here are some examples of registries:
|
||||
|
||||
- **sponsor registry**: such a registry is provided by a third-party hosting infrastructure as a convenience for their customers and the docker community as a whole. Its costs are supported by the third party, but the management and operation of the registry are supported by dotCloud. It features read/write access, and delegates authentication and authorization to the Index.
|
||||
- **mirror registry**: such a registry is provided by a third-party hosting infrastructure but is targeted at their customers only. Some mechanism (unspecified to date) ensures that public images are pulled from a sponsor registry to the mirror registry, to make sure that the customers of the third-party provider can “docker pull” those images locally.
|
||||
- **vendor registry**: such a registry is provided by a software vendor, who wants to distribute docker images. It would be operated and managed by the vendor. Only users authorized by the vendor would be able to get write access. Some images would be public (accessible for anyone), others private (accessible only for authorized users). Authentication and authorization would be delegated to the Index. The goal of vendor registries is to let someone do “docker pull basho/riak1.3” and automatically push from the vendor registry (instead of a sponsor registry); i.e. get all the convenience of a sponsor registry, while retaining control on the asset distribution.
|
||||
- **private registry**: such a registry is located behind a firewall, or protected by an additional security layer (HTTP authorization, SSL client-side certificates, IP address authorization...). The registry is operated by a private entity, outside of dotCloud’s control. It can optionally delegate additional authorization to the Index, but it is not mandatory.
|
||||
|
||||
.. note::
|
||||
|
||||
Mirror registries and private registries which do not use the Index don’t even need to run the registry code. They can be implemented by any kind of transport implementing HTTP GET and PUT. Read-only registries can be powered by a simple static HTTP server.
|
||||
|
||||
.. note::
|
||||
|
||||
The latter implies that while HTTP is the protocol of choice for a registry, multiple schemes are possible (and in some cases, trivial):
|
||||
- HTTP with GET (and PUT for read-write registries);
|
||||
- local mount point;
|
||||
- remote docker addressed through SSH.
|
||||
|
||||
The latter would only require two new commands in docker, e.g. “registryget” and “registryput”, wrapping access to the local filesystem (and optionally doing consistency checks). Authentication and authorization are then delegated to SSH (e.g. with public keys).
|
||||
|
||||
2. Endpoints
|
||||
============
|
||||
|
||||
2.1 Images
|
||||
----------
|
||||
|
||||
Layer
|
||||
*****
|
||||
|
||||
.. http:get:: /v1/images/(image_id)/layer
|
||||
|
||||
get image layer for a given ``image_id``
|
||||
|
||||
**Example Request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /v1/images/088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c/layer HTTP/1.1
|
||||
Host: registry-1.docker.io
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Token akmklmasadalkmsdfgsdgdge33
|
||||
|
||||
:parameter image_id: the id for the layer you want to get
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
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"
|
||||
}
|
||||
|
||||
:statuscode 200: OK
|
||||
:statuscode 401: Requires authorization
|
||||
:statuscode 404: Image not found
|
||||
|
||||
|
||||
.. http:put:: /v1/images/(image_id)/layer
|
||||
|
||||
put image layer for a given ``image_id``
|
||||
|
||||
**Example Request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PUT /v1/images/088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c/layer HTTP/1.1
|
||||
Host: registry-1.docker.io
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Token akmklmasadalkmsdfgsdgdge33
|
||||
|
||||
{
|
||||
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
|
||||
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
""
|
||||
|
||||
:statuscode 200: OK
|
||||
:statuscode 401: Requires authorization
|
||||
:statuscode 404: Image not found
|
||||
|
||||
|
||||
Image
|
||||
*****
|
||||
|
||||
.. http:put:: /v1/images/(image_id)/json
|
||||
|
||||
put image for a given ``image_id``
|
||||
|
||||
**Example Request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PUT /v1/images/088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c/json HTTP/1.1
|
||||
Host: registry-1.docker.io
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Cookie: (Cookie provided by the Registry)
|
||||
|
||||
{
|
||||
“id”: “088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c”,
|
||||
“checksum”: “sha256:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”
|
||||
}
|
||||
|
||||
:parameter image_id: the id for the layer you want to get
|
||||
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
""
|
||||
|
||||
:statuscode 200: OK
|
||||
:statuscode 401: Requires authorization
|
||||
|
||||
.. http:get:: /v1/images/(image_id)/json
|
||||
|
||||
get image for a given ``image_id``
|
||||
|
||||
**Example Request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /v1/images/088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c/json HTTP/1.1
|
||||
Host: registry-1.docker.io
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Cookie: (Cookie provided by the Registry)
|
||||
|
||||
:parameter image_id: the id for the layer you want to get
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
“id”: “088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c”,
|
||||
“checksum”: “sha256:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”
|
||||
}
|
||||
|
||||
:statuscode 200: OK
|
||||
:statuscode 401: Requires authorization
|
||||
:statuscode 404: Image not found
|
||||
|
||||
|
||||
Ancestry
|
||||
********
|
||||
|
||||
.. http:get:: /v1/images/(image_id)/ancestry
|
||||
|
||||
get ancestry for an image given an ``image_id``
|
||||
|
||||
**Example Request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /v1/images/088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c/ancestry HTTP/1.1
|
||||
Host: registry-1.docker.io
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Cookie: (Cookie provided by the Registry)
|
||||
|
||||
:parameter image_id: the id for the layer you want to get
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
["088b4502f51920fbd9b7c503e87c7a2c05aa3adc3d35e79c031fa126b403200f",
|
||||
"aeee63968d87c7da4a5cf5d2be6bee4e21bc226fd62273d180a49c96c62e4543",
|
||||
"bfa4c5326bc764280b0863b46a4b20d940bc1897ef9c1dfec060604bdc383280",
|
||||
"6ab5893c6927c15a15665191f2c6cf751f5056d8b95ceee32e43c5e8a3648544"]
|
||||
|
||||
:statuscode 200: OK
|
||||
:statuscode 401: Requires authorization
|
||||
:statuscode 404: Image not found
|
||||
|
||||
|
||||
2.2 Tags
|
||||
--------
|
||||
|
||||
.. http:get:: /v1/repositories/(namespace)/(repository)/tags
|
||||
|
||||
get all of the tags for the given repo.
|
||||
|
||||
**Example Request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /v1/repositories/foo/bar/tags HTTP/1.1
|
||||
Host: registry-1.docker.io
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Cookie: (Cookie provided by the Registry)
|
||||
|
||||
:parameter namespace: namespace for the repo
|
||||
:parameter repository: name for the repo
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"latest": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f",
|
||||
“0.1.1”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”
|
||||
}
|
||||
|
||||
:statuscode 200: OK
|
||||
:statuscode 401: Requires authorization
|
||||
:statuscode 404: Repository not found
|
||||
|
||||
|
||||
.. http:get:: /v1/repositories/(namespace)/(repository)/tags/(tag)
|
||||
|
||||
get a tag for the given repo.
|
||||
|
||||
**Example Request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /v1/repositories/foo/bar/tags/latest HTTP/1.1
|
||||
Host: registry-1.docker.io
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Cookie: (Cookie provided by the Registry)
|
||||
|
||||
:parameter namespace: namespace for the repo
|
||||
:parameter repository: name for the repo
|
||||
:parameter tag: name of tag you want to get
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
"9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f"
|
||||
|
||||
:statuscode 200: OK
|
||||
:statuscode 401: Requires authorization
|
||||
:statuscode 404: Tag not found
|
||||
|
||||
.. http:delete:: /v1/repositories/(namespace)/(repository)/tags/(tag)
|
||||
|
||||
delete the tag for the repo
|
||||
|
||||
**Example Request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
DELETE /v1/repositories/foo/bar/tags/latest HTTP/1.1
|
||||
Host: registry-1.docker.io
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Cookie: (Cookie provided by the Registry)
|
||||
|
||||
:parameter namespace: namespace for the repo
|
||||
:parameter repository: name for the repo
|
||||
:parameter tag: name of tag you want to delete
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
""
|
||||
|
||||
:statuscode 200: OK
|
||||
:statuscode 401: Requires authorization
|
||||
:statuscode 404: Tag not found
|
||||
|
||||
|
||||
.. http:put:: /v1/repositories/(namespace)/(repository)/tags/(tag)
|
||||
|
||||
put a tag for the given repo.
|
||||
|
||||
**Example Request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PUT /v1/repositories/foo/bar/tags/latest HTTP/1.1
|
||||
Host: registry-1.docker.io
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Cookie: (Cookie provided by the Registry)
|
||||
|
||||
“9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”
|
||||
|
||||
:parameter namespace: namespace for the repo
|
||||
:parameter repository: name for the repo
|
||||
:parameter tag: name of tag you want to add
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
""
|
||||
|
||||
:statuscode 200: OK
|
||||
:statuscode 400: Invalid data
|
||||
:statuscode 401: Requires authorization
|
||||
:statuscode 404: Image not found
|
||||
|
||||
2.3 Repositories
|
||||
----------------
|
||||
|
||||
.. http:delete:: /v1/repositories/(namespace)/(repository)/
|
||||
|
||||
delete a repository
|
||||
|
||||
**Example Request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
DELETE /v1/repositories/foo/bar/ HTTP/1.1
|
||||
Host: registry-1.docker.io
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Cookie: (Cookie provided by the Registry)
|
||||
|
||||
""
|
||||
|
||||
:parameter namespace: namespace for the repo
|
||||
:parameter repository: name for the repo
|
||||
|
||||
**Example Response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
""
|
||||
|
||||
:statuscode 200: OK
|
||||
:statuscode 401: Requires authorization
|
||||
:statuscode 404: Repository not found
|
||||
|
||||
3.0 Authorization
|
||||
=================
|
||||
This is where we describe the authorization process, including the tokens and cookies.
|
||||
|
||||
TODO: add more info.
|
||||
@@ -1,569 +0,0 @@
|
||||
:title: Registry Documentation
|
||||
:description: Documentation for docker Registry and Registry API
|
||||
:keywords: docker, registry, api, index
|
||||
|
||||
|
||||
=====================
|
||||
Registry & index Spec
|
||||
=====================
|
||||
|
||||
.. contents:: Table of Contents
|
||||
|
||||
1. The 3 roles
|
||||
===============
|
||||
|
||||
1.1 Index
|
||||
---------
|
||||
|
||||
The Index is responsible for centralizing information about:
|
||||
- User accounts
|
||||
- Checksums of the images
|
||||
- Public namespaces
|
||||
|
||||
The Index has different components:
|
||||
- Web UI
|
||||
- Meta-data store (comments, stars, list public repositories)
|
||||
- Authentication service
|
||||
- Tokenization
|
||||
|
||||
The index is authoritative for those information.
|
||||
|
||||
We expect that there will be only one instance of the index, run and managed by dotCloud.
|
||||
|
||||
1.2 Registry
|
||||
------------
|
||||
- It stores the images and the graph for a set of repositories
|
||||
- It does not have user accounts data
|
||||
- It has no notion of user accounts or authorization
|
||||
- It delegates authentication and authorization to the Index Auth service using tokens
|
||||
- It supports different storage backends (S3, cloud files, local FS)
|
||||
- It doesn’t have a local database
|
||||
- It will be open-sourced at some point
|
||||
|
||||
We expect that there will be multiple registries out there. To help to grasp the context, here are some examples of registries:
|
||||
|
||||
- **sponsor registry**: such a registry is provided by a third-party hosting infrastructure as a convenience for their customers and the docker community as a whole. Its costs are supported by the third party, but the management and operation of the registry are supported by dotCloud. It features read/write access, and delegates authentication and authorization to the Index.
|
||||
- **mirror registry**: such a registry is provided by a third-party hosting infrastructure but is targeted at their customers only. Some mechanism (unspecified to date) ensures that public images are pulled from a sponsor registry to the mirror registry, to make sure that the customers of the third-party provider can “docker pull” those images locally.
|
||||
- **vendor registry**: such a registry is provided by a software vendor, who wants to distribute docker images. It would be operated and managed by the vendor. Only users authorized by the vendor would be able to get write access. Some images would be public (accessible for anyone), others private (accessible only for authorized users). Authentication and authorization would be delegated to the Index. The goal of vendor registries is to let someone do “docker pull basho/riak1.3” and automatically push from the vendor registry (instead of a sponsor registry); i.e. get all the convenience of a sponsor registry, while retaining control on the asset distribution.
|
||||
- **private registry**: such a registry is located behind a firewall, or protected by an additional security layer (HTTP authorization, SSL client-side certificates, IP address authorization...). The registry is operated by a private entity, outside of dotCloud’s control. It can optionally delegate additional authorization to the Index, but it is not mandatory.
|
||||
|
||||
.. note::
|
||||
|
||||
Mirror registries and private registries which do not use the Index don’t even need to run the registry code. They can be implemented by any kind of transport implementing HTTP GET and PUT. Read-only registries can be powered by a simple static HTTP server.
|
||||
|
||||
.. note::
|
||||
|
||||
The latter implies that while HTTP is the protocol of choice for a registry, multiple schemes are possible (and in some cases, trivial):
|
||||
- HTTP with GET (and PUT for read-write registries);
|
||||
- local mount point;
|
||||
- remote docker addressed through SSH.
|
||||
|
||||
The latter would only require two new commands in docker, e.g. “registryget” and “registryput”, wrapping access to the local filesystem (and optionally doing consistency checks). Authentication and authorization are then delegated to SSH (e.g. with public keys).
|
||||
|
||||
1.3 Docker
|
||||
----------
|
||||
|
||||
On top of being a runtime for LXC, Docker is the Registry client. It supports:
|
||||
- Push / Pull on the registry
|
||||
- Client authentication on the Index
|
||||
|
||||
2. Workflow
|
||||
===========
|
||||
|
||||
2.1 Pull
|
||||
--------
|
||||
|
||||
.. image:: /static_files/docker_pull_chart.png
|
||||
|
||||
1. Contact the Index to know where I should download “samalba/busybox”
|
||||
2. Index replies:
|
||||
a. “samalba/busybox” is on Registry A
|
||||
b. here are the checksums for “samalba/busybox” (for all layers)
|
||||
c. token
|
||||
3. Contact Registry A to receive the layers for “samalba/busybox” (all of them to the base image). Registry A is authoritative for “samalba/busybox” but keeps a copy of all inherited layers and serve them all from the same location.
|
||||
4. registry contacts index to verify if token/user is allowed to download images
|
||||
5. Index returns true/false lettings registry know if it should proceed or error out
|
||||
6. Get the payload for all layers
|
||||
|
||||
It’s possible to run docker pull \https://<registry>/repositories/samalba/busybox. In this case, docker bypasses the Index. However the security is not guaranteed (in case Registry A is corrupted) because there won’t be any checksum checks.
|
||||
|
||||
Currently registry redirects to s3 urls for downloads, going forward all downloads need to be streamed through the registry. The Registry will then abstract the calls to S3 by a top-level class which implements sub-classes for S3 and local storage.
|
||||
|
||||
Token is only returned when the 'X-Docker-Token' header is sent with request.
|
||||
|
||||
Basic Auth is required to pull private repos. Basic auth isn't required for pulling public repos, but if one is provided, it needs to be valid and for an active account.
|
||||
|
||||
API (pulling repository foo/bar):
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
1. (Docker -> Index) GET /v1/repositories/foo/bar/images
|
||||
**Headers**:
|
||||
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
|
||||
X-Docker-Token: true
|
||||
**Action**:
|
||||
(looking up the foo/bar in db and gets images and checksums for that repo (all if no tag is specified, if tag, only checksums for those tags) see part 4.4.1)
|
||||
|
||||
2. (Index -> Docker) HTTP 200 OK
|
||||
|
||||
**Headers**:
|
||||
- Authorization: Token signature=123abc,repository=”foo/bar”,access=write
|
||||
- X-Docker-Endpoints: registry.docker.io [, registry2.docker.io]
|
||||
**Body**:
|
||||
Jsonified checksums (see part 4.4.1)
|
||||
|
||||
3. (Docker -> Registry) GET /v1/repositories/foo/bar/tags/latest
|
||||
**Headers**:
|
||||
Authorization: Token signature=123abc,repository=”foo/bar”,access=write
|
||||
|
||||
4. (Registry -> Index) GET /v1/repositories/foo/bar/images
|
||||
|
||||
**Headers**:
|
||||
Authorization: Token signature=123abc,repository=”foo/bar”,access=read
|
||||
|
||||
**Body**:
|
||||
<ids and checksums in payload>
|
||||
|
||||
**Action**:
|
||||
( Lookup token see if they have access to pull.)
|
||||
|
||||
If good:
|
||||
HTTP 200 OK
|
||||
Index will invalidate the token
|
||||
If bad:
|
||||
HTTP 401 Unauthorized
|
||||
|
||||
5. (Docker -> Registry) GET /v1/images/928374982374/ancestry
|
||||
**Action**:
|
||||
(for each image id returned in the registry, fetch /json + /layer)
|
||||
|
||||
.. note::
|
||||
|
||||
If someone makes a second request, then we will always give a new token, never reuse tokens.
|
||||
|
||||
2.2 Push
|
||||
--------
|
||||
|
||||
.. image:: /static_files/docker_push_chart.png
|
||||
|
||||
1. Contact the index to allocate the repository name “samalba/busybox” (authentication required with user credentials)
|
||||
2. If authentication works and namespace available, “samalba/busybox” is allocated and a temporary token is returned (namespace is marked as initialized in index)
|
||||
3. Push the image on the registry (along with the token)
|
||||
4. Registry A contacts the Index to verify the token (token must corresponds to the repository name)
|
||||
5. Index validates the token. Registry A starts reading the stream pushed by docker and store the repository (with its images)
|
||||
6. docker contacts the index to give checksums for upload images
|
||||
|
||||
.. note::
|
||||
|
||||
**It’s possible not to use the Index at all!** In this case, a deployed version of the Registry is deployed to store and serve images. Those images are not authentified and the security is not guaranteed.
|
||||
|
||||
.. note::
|
||||
|
||||
**Index can be replaced!** For a private Registry deployed, a custom Index can be used to serve and validate token according to different policies.
|
||||
|
||||
Docker computes the checksums and submit them to the Index at the end of the push. When a repository name does not have checksums on the Index, it means that the push is in progress (since checksums are submitted at the end).
|
||||
|
||||
API (pushing repos foo/bar):
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
1. (Docker -> Index) PUT /v1/repositories/foo/bar/
|
||||
**Headers**:
|
||||
Authorization: Basic sdkjfskdjfhsdkjfh==
|
||||
X-Docker-Token: true
|
||||
|
||||
**Action**::
|
||||
- in index, we allocated a new repository, and set to initialized
|
||||
|
||||
**Body**::
|
||||
(The body contains the list of images that are going to be pushed, with empty checksums. The checksums will be set at the end of the push)::
|
||||
|
||||
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”}]
|
||||
|
||||
2. (Index -> Docker) 200 Created
|
||||
**Headers**:
|
||||
- WWW-Authenticate: Token signature=123abc,repository=”foo/bar”,access=write
|
||||
- X-Docker-Endpoints: registry.docker.io [, registry2.docker.io]
|
||||
|
||||
3. (Docker -> Registry) PUT /v1/images/98765432_parent/json
|
||||
**Headers**:
|
||||
Authorization: Token signature=123abc,repository=”foo/bar”,access=write
|
||||
|
||||
4. (Registry->Index) GET /v1/repositories/foo/bar/images
|
||||
**Headers**:
|
||||
Authorization: Token signature=123abc,repository=”foo/bar”,access=write
|
||||
**Action**::
|
||||
- Index:
|
||||
will invalidate the token.
|
||||
- Registry:
|
||||
grants a session (if token is approved) and fetches the images id
|
||||
|
||||
5. (Docker -> Registry) PUT /v1/images/98765432_parent/json
|
||||
**Headers**::
|
||||
- Authorization: Token signature=123abc,repository=”foo/bar”,access=write
|
||||
- Cookie: (Cookie provided by the Registry)
|
||||
|
||||
6. (Docker -> Registry) PUT /v1/images/98765432/json
|
||||
**Headers**:
|
||||
Cookie: (Cookie provided by the Registry)
|
||||
|
||||
7. (Docker -> Registry) PUT /v1/images/98765432_parent/layer
|
||||
**Headers**:
|
||||
Cookie: (Cookie provided by the Registry)
|
||||
|
||||
8. (Docker -> Registry) PUT /v1/images/98765432/layer
|
||||
**Headers**:
|
||||
X-Docker-Checksum: sha256:436745873465fdjkhdfjkgh
|
||||
|
||||
9. (Docker -> Registry) PUT /v1/repositories/foo/bar/tags/latest
|
||||
**Headers**:
|
||||
Cookie: (Cookie provided by the Registry)
|
||||
**Body**:
|
||||
“98765432”
|
||||
|
||||
10. (Docker -> Index) PUT /v1/repositories/foo/bar/images
|
||||
|
||||
**Headers**:
|
||||
Authorization: Basic 123oislifjsldfj==
|
||||
X-Docker-Endpoints: registry1.docker.io (no validation on this right now)
|
||||
|
||||
**Body**:
|
||||
(The image, id’s, tags and checksums)
|
||||
|
||||
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”,
|
||||
“checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}]
|
||||
|
||||
**Return** HTTP 204
|
||||
|
||||
.. note::
|
||||
|
||||
If push fails and they need to start again, what happens in the index, there will already be a record for the namespace/name, but it will be initialized. Should we allow it, or mark as name already used? One edge case could be if someone pushes the same thing at the same time with two different shells.
|
||||
|
||||
If it's a retry on the Registry, Docker has a cookie (provided by the registry after token validation). So the Index won’t have to provide a new token.
|
||||
|
||||
2.3 Delete
|
||||
----------
|
||||
|
||||
If you need to delete something from the index or registry, we need a nice clean way to do that. Here is the workflow.
|
||||
|
||||
1. Docker contacts the index to request a delete of a repository “samalba/busybox” (authentication required with user credentials)
|
||||
2. If authentication works and repository is valid, “samalba/busybox” is marked as deleted and a temporary token is returned
|
||||
3. Send a delete request to the registry for the repository (along with the token)
|
||||
4. Registry A contacts the Index to verify the token (token must corresponds to the repository name)
|
||||
5. Index validates the token. Registry A deletes the repository and everything associated to it.
|
||||
6. docker contacts the index to let it know it was removed from the registry, the index removes all records from the database.
|
||||
|
||||
.. note::
|
||||
|
||||
The Docker client should present an "Are you sure?" prompt to confirm the deletion before starting the process. Once it starts it can't be undone.
|
||||
|
||||
API (deleting repository foo/bar):
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
1. (Docker -> Index) DELETE /v1/repositories/foo/bar/
|
||||
**Headers**:
|
||||
Authorization: Basic sdkjfskdjfhsdkjfh==
|
||||
X-Docker-Token: true
|
||||
|
||||
**Action**::
|
||||
- in index, we make sure it is a valid repository, and set to deleted (logically)
|
||||
|
||||
**Body**::
|
||||
Empty
|
||||
|
||||
2. (Index -> Docker) 202 Accepted
|
||||
**Headers**:
|
||||
- WWW-Authenticate: Token signature=123abc,repository=”foo/bar”,access=delete
|
||||
- X-Docker-Endpoints: registry.docker.io [, registry2.docker.io] # list of endpoints where this repo lives.
|
||||
|
||||
3. (Docker -> Registry) DELETE /v1/repositories/foo/bar/
|
||||
**Headers**:
|
||||
Authorization: Token signature=123abc,repository=”foo/bar”,access=delete
|
||||
|
||||
4. (Registry->Index) PUT /v1/repositories/foo/bar/auth
|
||||
**Headers**:
|
||||
Authorization: Token signature=123abc,repository=”foo/bar”,access=delete
|
||||
**Action**::
|
||||
- Index:
|
||||
will invalidate the token.
|
||||
- Registry:
|
||||
deletes the repository (if token is approved)
|
||||
|
||||
5. (Registry -> Docker) 200 OK
|
||||
200 If success
|
||||
403 if forbidden
|
||||
400 if bad request
|
||||
404 if repository isn't found
|
||||
|
||||
6. (Docker -> Index) DELETE /v1/repositories/foo/bar/
|
||||
|
||||
**Headers**:
|
||||
Authorization: Basic 123oislifjsldfj==
|
||||
X-Docker-Endpoints: registry-1.docker.io (no validation on this right now)
|
||||
|
||||
**Body**:
|
||||
Empty
|
||||
|
||||
**Return** HTTP 200
|
||||
|
||||
|
||||
3. How to use the Registry in standalone mode
|
||||
=============================================
|
||||
|
||||
The Index has two main purposes (along with its fancy social features):
|
||||
|
||||
- Resolve short names (to avoid passing absolute URLs all the time)
|
||||
- username/projectname -> \https://registry.docker.io/users/<username>/repositories/<projectname>/
|
||||
- team/projectname -> \https://registry.docker.io/team/<team>/repositories/<projectname>/
|
||||
- Authenticate a user as a repos owner (for a central referenced repository)
|
||||
|
||||
3.1 Without an Index
|
||||
--------------------
|
||||
Using the Registry without the Index can be useful to store the images on a private network without having to rely on an external entity controlled by dotCloud.
|
||||
|
||||
In this case, the registry will be launched in a special mode (--standalone? --no-index?). In this mode, the only thing which changes is that Registry will never contact the Index to verify a token. It will be the Registry owner responsibility to authenticate the user who pushes (or even pulls) an image using any mechanism (HTTP auth, IP based, etc...).
|
||||
|
||||
In this scenario, the Registry is responsible for the security in case of data corruption since the checksums are not delivered by a trusted entity.
|
||||
|
||||
As hinted previously, a standalone registry can also be implemented by any HTTP server handling GET/PUT requests (or even only GET requests if no write access is necessary).
|
||||
|
||||
3.2 With an Index
|
||||
-----------------
|
||||
|
||||
The Index data needed by the Registry are simple:
|
||||
- Serve the checksums
|
||||
- Provide and authorize a Token
|
||||
|
||||
In the scenario of a Registry running on a private network with the need of centralizing and authorizing, it’s easy to use a custom Index.
|
||||
|
||||
The only challenge will be to tell Docker to contact (and trust) this custom Index. Docker will be configurable at some point to use a specific Index, it’ll be the private entity responsibility (basically the organization who uses Docker in a private environment) to maintain the Index and the Docker’s configuration among its consumers.
|
||||
|
||||
4. The API
|
||||
==========
|
||||
|
||||
The first version of the api is available here: https://github.com/jpetazzo/docker/blob/acd51ecea8f5d3c02b00a08176171c59442df8b3/docs/images-repositories-push-pull.md
|
||||
|
||||
4.1 Images
|
||||
----------
|
||||
|
||||
The format returned in the images is not defined here (for layer and json), basically because Registry stores exactly the same kind of information as Docker uses to manage them.
|
||||
|
||||
The format of ancestry is a line-separated list of image ids, in age order. I.e. the image’s parent is on the last line, the parent of the parent on the next-to-last line, etc.; if the image has no parent, the file is empty.
|
||||
|
||||
GET /v1/images/<image_id>/layer
|
||||
PUT /v1/images/<image_id>/layer
|
||||
GET /v1/images/<image_id>/json
|
||||
PUT /v1/images/<image_id>/json
|
||||
GET /v1/images/<image_id>/ancestry
|
||||
PUT /v1/images/<image_id>/ancestry
|
||||
|
||||
4.2 Users
|
||||
---------
|
||||
|
||||
4.2.1 Create a user (Index)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
POST /v1/users
|
||||
|
||||
**Body**:
|
||||
{"email": "sam@dotcloud.com", "password": "toto42", "username": "foobar"'}
|
||||
|
||||
**Validation**:
|
||||
- **username** : min 4 character, max 30 characters, must match the regular expression [a-z0-9_].
|
||||
- **password**: min 5 characters
|
||||
|
||||
**Valid**: return HTTP 200
|
||||
|
||||
Errors: HTTP 400 (we should create error codes for possible errors)
|
||||
- invalid json
|
||||
- missing field
|
||||
- wrong format (username, password, email, etc)
|
||||
- forbidden name
|
||||
- name already exists
|
||||
|
||||
.. note::
|
||||
|
||||
A user account will be valid only if the email has been validated (a validation link is sent to the email address).
|
||||
|
||||
4.2.2 Update a user (Index)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
PUT /v1/users/<username>
|
||||
|
||||
**Body**:
|
||||
{"password": "toto"}
|
||||
|
||||
.. note::
|
||||
|
||||
We can also update email address, if they do, they will need to reverify their new email address.
|
||||
|
||||
4.2.3 Login (Index)
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
Does nothing else but asking for a user authentication. Can be used to validate credentials. HTTP Basic Auth for now, maybe change in future.
|
||||
|
||||
GET /v1/users
|
||||
|
||||
**Return**:
|
||||
- Valid: HTTP 200
|
||||
- Invalid login: HTTP 401
|
||||
- Account inactive: HTTP 403 Account is not Active
|
||||
|
||||
4.3 Tags (Registry)
|
||||
-------------------
|
||||
|
||||
The Registry does not know anything about users. Even though repositories are under usernames, it’s just a namespace for the registry. Allowing us to implement organizations or different namespaces per user later, without modifying the Registry’s API.
|
||||
|
||||
The following naming restrictions apply:
|
||||
|
||||
- Namespaces must match the same regular expression as usernames (See 4.2.1.)
|
||||
- Repository names must match the regular expression [a-zA-Z0-9-_.]
|
||||
|
||||
4.3.1 Get all tags
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
GET /v1/repositories/<namespace>/<repository_name>/tags
|
||||
|
||||
**Return**: HTTP 200
|
||||
{
|
||||
"latest": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f",
|
||||
“0.1.1”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”
|
||||
}
|
||||
|
||||
4.3.2 Read the content of a tag (resolve the image id)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
GET /v1/repositories/<namespace>/<repo_name>/tags/<tag>
|
||||
|
||||
**Return**:
|
||||
"9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f"
|
||||
|
||||
4.3.3 Delete a tag (registry)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
DELETE /v1/repositories/<namespace>/<repo_name>/tags/<tag>
|
||||
|
||||
4.4 Images (Index)
|
||||
------------------
|
||||
|
||||
For the Index to “resolve” the repository name to a Registry location, it uses the X-Docker-Endpoints header. In other terms, this requests always add a “X-Docker-Endpoints” to indicate the location of the registry which hosts this repository.
|
||||
|
||||
4.4.1 Get the images
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
GET /v1/repositories/<namespace>/<repo_name>/images
|
||||
|
||||
**Return**: HTTP 200
|
||||
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, “checksum”: “md5:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}]
|
||||
|
||||
|
||||
4.4.2 Add/update the images
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You always add images, you never remove them.
|
||||
|
||||
PUT /v1/repositories/<namespace>/<repo_name>/images
|
||||
|
||||
**Body**:
|
||||
[ {“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, “checksum”: “sha256:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”} ]
|
||||
|
||||
**Return** 204
|
||||
|
||||
4.5 Repositories
|
||||
----------------
|
||||
|
||||
4.5.1 Remove a Repository (Registry)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
DELETE /v1/repositories/<namespace>/<repo_name>
|
||||
|
||||
Return 200 OK
|
||||
|
||||
4.5.2 Remove a Repository (Index)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
This starts the delete process. see 2.3 for more details.
|
||||
|
||||
DELETE /v1/repositories/<namespace>/<repo_name>
|
||||
|
||||
Return 202 OK
|
||||
|
||||
5. Chaining Registries
|
||||
======================
|
||||
|
||||
It’s possible to chain Registries server for several reasons:
|
||||
- Load balancing
|
||||
- Delegate the next request to another server
|
||||
|
||||
When a Registry is a reference for a repository, it should host the entire images chain in order to avoid breaking the chain during the download.
|
||||
|
||||
The Index and Registry use this mechanism to redirect on one or the other.
|
||||
|
||||
Example with an image download:
|
||||
On every request, a special header can be returned:
|
||||
|
||||
X-Docker-Endpoints: server1,server2
|
||||
|
||||
On the next request, the client will always pick a server from this list.
|
||||
|
||||
6. Authentication & Authorization
|
||||
=================================
|
||||
|
||||
6.1 On the Index
|
||||
-----------------
|
||||
|
||||
The Index supports both “Basic” and “Token” challenges. Usually when there is a “401 Unauthorized”, the Index replies this::
|
||||
|
||||
401 Unauthorized
|
||||
WWW-Authenticate: Basic realm="auth required",Token
|
||||
|
||||
You have 3 options:
|
||||
|
||||
1. Provide user credentials and ask for a token
|
||||
|
||||
**Header**:
|
||||
- Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
|
||||
- X-Docker-Token: true
|
||||
|
||||
In this case, along with the 200 response, you’ll get a new token (if user auth is ok):
|
||||
If authorization isn't correct you get a 401 response.
|
||||
If account isn't active you will get a 403 response.
|
||||
|
||||
**Response**:
|
||||
- 200 OK
|
||||
- X-Docker-Token: Token signature=123abc,repository=”foo/bar”,access=read
|
||||
|
||||
2. Provide user credentials only
|
||||
|
||||
**Header**:
|
||||
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
|
||||
|
||||
3. Provide Token
|
||||
|
||||
**Header**:
|
||||
Authorization: Token signature=123abc,repository=”foo/bar”,access=read
|
||||
|
||||
6.2 On the Registry
|
||||
-------------------
|
||||
|
||||
The Registry only supports the Token challenge::
|
||||
|
||||
401 Unauthorized
|
||||
WWW-Authenticate: Token
|
||||
|
||||
The only way is to provide a token on “401 Unauthorized” responses::
|
||||
|
||||
Authorization: Token signature=123abc,repository=”foo/bar”,access=read
|
||||
|
||||
Usually, the Registry provides a Cookie when a Token verification succeeded. Every time the Registry passes a Cookie, you have to pass it back the same cookie.::
|
||||
|
||||
200 OK
|
||||
Set-Cookie: session="wD/J7LqL5ctqw8haL10vgfhrb2Q=?foo=UydiYXInCnAxCi4=×tamp=RjEzNjYzMTQ5NDcuNDc0NjQzCi4="; Path=/; HttpOnly
|
||||
|
||||
Next request::
|
||||
|
||||
GET /(...)
|
||||
Cookie: session="wD/J7LqL5ctqw8haL10vgfhrb2Q=?foo=UydiYXInCnAxCi4=×tamp=RjEzNjYzMTQ5NDcuNDc0NjQzCi4="
|
||||
|
||||
|
||||
7.0 Document Version
|
||||
---------------------
|
||||
|
||||
- 1.0 : May 6th 2013 : initial release
|
||||
- 1.1 : June 1st 2013 : Added Delete Repository and way to handle new source namespace.
|
||||
@@ -1,56 +0,0 @@
|
||||
:title: Command Line Interface
|
||||
:description: Docker's CLI command description and usage
|
||||
:keywords: Docker, Docker documentation, CLI, command line
|
||||
|
||||
.. _cli:
|
||||
|
||||
Overview
|
||||
======================
|
||||
|
||||
Docker Usage
|
||||
~~~~~~~~~~~~
|
||||
|
||||
To list available commands, either run ``docker`` with no parameters or execute
|
||||
``docker help``::
|
||||
|
||||
$ docker
|
||||
Usage: docker [OPTIONS] COMMAND [arg...]
|
||||
-H=[tcp://127.0.0.1:4243]: tcp://host:port to bind/connect to or unix://path/to/socket to use
|
||||
|
||||
A self-sufficient runtime for linux containers.
|
||||
|
||||
...
|
||||
|
||||
Available Commands
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
command/attach
|
||||
command/build
|
||||
command/commit
|
||||
command/diff
|
||||
command/export
|
||||
command/history
|
||||
command/images
|
||||
command/import
|
||||
command/info
|
||||
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/version
|
||||
command/wait
|
||||
@@ -1,13 +0,0 @@
|
||||
:title: Attach Command
|
||||
:description: Attach to a running container
|
||||
:keywords: attach, container, docker, documentation
|
||||
|
||||
===========================================
|
||||
``attach`` -- Attach to a running container
|
||||
===========================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker attach CONTAINER
|
||||
|
||||
Attach to a running container
|
||||
@@ -1,33 +0,0 @@
|
||||
:title: Build Command
|
||||
:description: Build a new image from the Dockerfile passed via stdin
|
||||
:keywords: build, docker, container, documentation
|
||||
|
||||
================================================
|
||||
``build`` -- Build a container from a Dockerfile
|
||||
================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker build [OPTIONS] PATH | -
|
||||
Build a new container image from the source code at PATH
|
||||
-t="": Tag to be applied to the resulting image in case of success.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker build .
|
||||
|
||||
| This will read the Dockerfile from the current directory. It will also send any other files and directories found in the current directory to the docker daemon.
|
||||
| The contents of this directory would be used by ADD commands found within the Dockerfile.
|
||||
| This will send a lot of data to the docker daemon if the current directory contains a lot of data.
|
||||
| If the absolute path is provided instead of '.', only the files and directories required by the ADD commands from the Dockerfile will be added to the context and transferred to the docker daemon.
|
||||
|
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker build -
|
||||
|
||||
| This will read a Dockerfile from Stdin without context. Due to the lack of a context, no contents of any local directory will be sent to the docker daemon.
|
||||
| ADD doesn't work when running in this mode due to the absence of the context, thus having no source files to copy to the container.
|
||||
@@ -1,32 +0,0 @@
|
||||
:title: Commit Command
|
||||
:description: Create a new image from a container's changes
|
||||
:keywords: commit, docker, container, documentation
|
||||
|
||||
===========================================================
|
||||
``commit`` -- Create a new image from a container's changes
|
||||
===========================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY [TAG]]
|
||||
|
||||
Create a new image from a container's changes
|
||||
|
||||
-m="": Commit message
|
||||
-author="": Author (eg. "John Hannibal Smith <hannibal@a-team.com>"
|
||||
-run="": Config automatically applied when the image is run. "+`(ex: {"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')
|
||||
|
||||
Full -run example::
|
||||
|
||||
{"Hostname": "",
|
||||
"User": "",
|
||||
"CpuShares": 0,
|
||||
"Memory": 0,
|
||||
"MemorySwap": 0,
|
||||
"PortSpecs": ["22", "80", "443"],
|
||||
"Tty": true,
|
||||
"OpenStdin": true,
|
||||
"StdinOnce": true,
|
||||
"Env": ["FOO=BAR", "FOO2=BAR2"],
|
||||
"Cmd": ["cat", "-e", "/etc/resolv.conf"],
|
||||
"Dns": ["8.8.8.8", "8.8.4.4"]}
|
||||
@@ -1,13 +0,0 @@
|
||||
:title: Diff Command
|
||||
:description: Inspect changes on a container's filesystem
|
||||
:keywords: diff, docker, container, documentation
|
||||
|
||||
=======================================================
|
||||
``diff`` -- Inspect changes on a container's filesystem
|
||||
=======================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker diff CONTAINER [OPTIONS]
|
||||
|
||||
Inspect changes on a container's filesystem
|
||||
@@ -1,13 +0,0 @@
|
||||
:title: Export Command
|
||||
:description: Export the contents of a filesystem as a tar archive
|
||||
:keywords: export, docker, container, documentation
|
||||
|
||||
=================================================================
|
||||
``export`` -- Stream the contents of a container as a tar archive
|
||||
=================================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker export CONTAINER
|
||||
|
||||
Export the contents of a filesystem as a tar archive
|
||||
@@ -1,13 +0,0 @@
|
||||
:title: History Command
|
||||
:description: Show the history of an image
|
||||
:keywords: history, docker, container, documentation
|
||||
|
||||
===========================================
|
||||
``history`` -- Show the history of an image
|
||||
===========================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker history [OPTIONS] IMAGE
|
||||
|
||||
Show the history of an image
|
||||
@@ -1,26 +0,0 @@
|
||||
:title: Images Command
|
||||
:description: List images
|
||||
:keywords: images, docker, container, documentation
|
||||
|
||||
=========================
|
||||
``images`` -- List images
|
||||
=========================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker images [OPTIONS] [NAME]
|
||||
|
||||
List images
|
||||
|
||||
-a=false: show all images
|
||||
-q=false: only show numeric IDs
|
||||
-viz=false: output in graphviz format
|
||||
|
||||
Displaying images visually
|
||||
--------------------------
|
||||
|
||||
::
|
||||
|
||||
docker images -viz | dot -Tpng -o docker.png
|
||||
|
||||
.. image:: images/docker_images.gif
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 35 KiB |
@@ -1,40 +0,0 @@
|
||||
:title: Import Command
|
||||
:description: Create a new filesystem image from the contents of a tarball
|
||||
:keywords: import, tarball, docker, url, documentation
|
||||
|
||||
==========================================================================
|
||||
``import`` -- Create a new filesystem image from the contents of a tarball
|
||||
==========================================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker import URL|- [REPOSITORY [TAG]]
|
||||
|
||||
Create a new filesystem image from the contents of a tarball
|
||||
|
||||
At this time, the URL must start with ``http`` and point to a single file archive (.tar, .tar.gz, .bzip)
|
||||
containing a root filesystem. If you would like to import from a local directory or archive,
|
||||
you can use the ``-`` parameter to take the data from standard in.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Import from a remote location
|
||||
.............................
|
||||
|
||||
``$ docker import http://example.com/exampleimage.tgz exampleimagerepo``
|
||||
|
||||
Import from a local file
|
||||
........................
|
||||
|
||||
Import to docker via pipe and standard in
|
||||
|
||||
``$ cat exampleimage.tgz | docker import - exampleimagelocal``
|
||||
|
||||
Import from a local directory
|
||||
.............................
|
||||
|
||||
``$ sudo tar -c . | docker import - exampleimagedir``
|
||||
|
||||
Note the ``sudo`` in this example -- you must preserve the ownership of the files (especially root ownership)
|
||||
during the archiving with tar. If you are not root (or sudo) when you tar, then the ownerships might not get preserved.
|
||||
@@ -1,13 +0,0 @@
|
||||
:title: Info Command
|
||||
:description: Display system-wide information.
|
||||
:keywords: info, docker, information, documentation
|
||||
|
||||
===========================================
|
||||
``info`` -- Display system-wide information
|
||||
===========================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker info
|
||||
|
||||
Display system-wide information.
|
||||
@@ -1,13 +0,0 @@
|
||||
:title: Inspect Command
|
||||
:description: Return low-level information on a container
|
||||
:keywords: inspect, container, docker, documentation
|
||||
|
||||
==========================================================
|
||||
``inspect`` -- Return low-level information on a container
|
||||
==========================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker inspect [OPTIONS] CONTAINER
|
||||
|
||||
Return low-level information on a container
|
||||
@@ -1,13 +0,0 @@
|
||||
:title: Kill Command
|
||||
:description: Kill a running container
|
||||
:keywords: kill, container, docker, documentation
|
||||
|
||||
====================================
|
||||
``kill`` -- Kill a running container
|
||||
====================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
|
||||
|
||||
Kill a running container
|
||||
@@ -1,13 +0,0 @@
|
||||
:title: Login Command
|
||||
:description: Register or Login to the docker registry server
|
||||
:keywords: login, docker, documentation
|
||||
|
||||
============================================================
|
||||
``login`` -- Register or Login to the docker registry server
|
||||
============================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker login
|
||||
|
||||
Register or Login to the docker registry server
|
||||
@@ -1,13 +0,0 @@
|
||||
:title: Logs Command
|
||||
:description: Fetch the logs of a container
|
||||
:keywords: logs, container, docker, documentation
|
||||
|
||||
=========================================
|
||||
``logs`` -- Fetch the logs of a container
|
||||
=========================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker logs [OPTIONS] CONTAINER
|
||||
|
||||
Fetch the logs of a container
|
||||
@@ -1,13 +0,0 @@
|
||||
:title: Port Command
|
||||
:description: Lookup the public-facing port which is NAT-ed to PRIVATE_PORT
|
||||
:keywords: port, docker, container, documentation
|
||||
|
||||
=========================================================================
|
||||
``port`` -- Lookup the public-facing port which is NAT-ed to PRIVATE_PORT
|
||||
=========================================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker port [OPTIONS] CONTAINER PRIVATE_PORT
|
||||
|
||||
Lookup the public-facing port which is NAT-ed to PRIVATE_PORT
|
||||
@@ -1,17 +0,0 @@
|
||||
:title: Ps Command
|
||||
:description: List containers
|
||||
:keywords: ps, docker, documentation, container
|
||||
|
||||
=========================
|
||||
``ps`` -- List containers
|
||||
=========================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker ps [OPTIONS]
|
||||
|
||||
List containers
|
||||
|
||||
-a=false: Show all containers. Only running containers are shown by default.
|
||||
-notrunc=false: Don't truncate output
|
||||
-q=false: Only display numeric IDs
|
||||
@@ -1,13 +0,0 @@
|
||||
:title: Pull Command
|
||||
:description: Pull an image or a repository from the registry
|
||||
:keywords: pull, image, repo, repository, documentation, docker
|
||||
|
||||
=========================================================================
|
||||
``pull`` -- Pull an image or a repository from the docker registry server
|
||||
=========================================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker pull NAME
|
||||
|
||||
Pull an image or a repository from the registry
|
||||
@@ -1,13 +0,0 @@
|
||||
:title: Push Command
|
||||
:description: Push an image or a repository to the registry
|
||||
:keywords: push, docker, image, repository, documentation, repo
|
||||
|
||||
=======================================================================
|
||||
``push`` -- Push an image or a repository to the docker registry server
|
||||
=======================================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker push NAME
|
||||
|
||||
Push an image or a repository to the registry
|
||||
@@ -1,13 +0,0 @@
|
||||
:title: Restart Command
|
||||
:description: Restart a running container
|
||||
:keywords: restart, container, docker, documentation
|
||||
|
||||
==========================================
|
||||
``restart`` -- Restart a running container
|
||||
==========================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker restart [OPTIONS] NAME
|
||||
|
||||
Restart a running container
|
||||
@@ -1,13 +0,0 @@
|
||||
:title: Rm Command
|
||||
:description: Remove a container
|
||||
:keywords: remove, container, docker, documentation, rm
|
||||
|
||||
============================
|
||||
``rm`` -- Remove a container
|
||||
============================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker rm [OPTIONS] CONTAINER
|
||||
|
||||
Remove a container
|
||||
@@ -1,13 +0,0 @@
|
||||
:title: Rmi Command
|
||||
:description: Remove an image
|
||||
:keywords: rmi, remove, image, docker, documentation
|
||||
|
||||
==========================
|
||||
``rmi`` -- Remove an image
|
||||
==========================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker rmimage [OPTIONS] IMAGE
|
||||
|
||||
Remove an image
|
||||
@@ -1,27 +0,0 @@
|
||||
:title: Run Command
|
||||
:description: Run a command in a new container
|
||||
:keywords: run, container, docker, documentation
|
||||
|
||||
===========================================
|
||||
``run`` -- Run a command in a new container
|
||||
===========================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker run [OPTIONS] IMAGE COMMAND [ARG...]
|
||||
|
||||
Run a command in a new container
|
||||
|
||||
-a=map[]: Attach to stdin, stdout or stderr.
|
||||
-c=0: CPU shares (relative weight)
|
||||
-d=false: Detached mode: leave the container running in the background
|
||||
-e=[]: Set environment variables
|
||||
-h="": Container host name
|
||||
-i=false: Keep stdin open even if not attached
|
||||
-m=0: Memory limit (in bytes)
|
||||
-p=[]: Map a network port to the container
|
||||
-t=false: Allocate a pseudo-tty
|
||||
-u="": Username or UID
|
||||
-d=[]: Set custom dns servers for the container
|
||||
-v=[]: Creates a new volume and mounts it at the specified path.
|
||||
-volumes-from="": Mount all volumes from the given container.
|
||||
@@ -1,14 +0,0 @@
|
||||
:title: Search Command
|
||||
:description: Searches for the TERM parameter on the Docker index and prints out a list of repositories that match.
|
||||
:keywords: search, docker, image, documentation
|
||||
|
||||
===================================================================
|
||||
``search`` -- Search for an image in the docker index
|
||||
===================================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker search TERM
|
||||
|
||||
Searches for the TERM parameter on the Docker index and prints out a list of repositories
|
||||
that match.
|
||||
@@ -1,13 +0,0 @@
|
||||
:title: Start Command
|
||||
:description: Start a stopped container
|
||||
:keywords: start, docker, container, documentation
|
||||
|
||||
======================================
|
||||
``start`` -- Start a stopped container
|
||||
======================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker start [OPTIONS] NAME
|
||||
|
||||
Start a stopped container
|
||||
@@ -1,13 +0,0 @@
|
||||
:title: Stop Command
|
||||
:description: Stop a running container
|
||||
:keywords: stop, container, docker, documentation
|
||||
|
||||
====================================
|
||||
``stop`` -- Stop a running container
|
||||
====================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker stop [OPTIONS] NAME
|
||||
|
||||
Stop a running container
|
||||
@@ -1,15 +0,0 @@
|
||||
:title: Tag Command
|
||||
:description: Tag an image into a repository
|
||||
:keywords: tag, docker, image, repository, documentation, repo
|
||||
|
||||
=========================================
|
||||
``tag`` -- Tag an image into a repository
|
||||
=========================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker tag [OPTIONS] IMAGE REPOSITORY [TAG]
|
||||
|
||||
Tag an image into a repository
|
||||
|
||||
-f=false: Force
|
||||
@@ -1,7 +0,0 @@
|
||||
:title: Version Command
|
||||
:description:
|
||||
:keywords: version, docker, documentation
|
||||
|
||||
==================================================
|
||||
``version`` -- Show the docker version information
|
||||
==================================================
|
||||
@@ -1,13 +0,0 @@
|
||||
:title: Wait Command
|
||||
:description: Block until a container stops, then print its exit code.
|
||||
:keywords: wait, docker, container, documentation
|
||||
|
||||
===================================================================
|
||||
``wait`` -- Block until a container stops, then print its exit code
|
||||
===================================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker wait [OPTIONS] NAME
|
||||
|
||||
Block until a container stops, then print its exit code.
|
||||
@@ -1,41 +0,0 @@
|
||||
:title: Commands
|
||||
:description: -- todo: change me
|
||||
:keywords: todo, commands, command line, help, docker, documentation
|
||||
|
||||
|
||||
Commands
|
||||
========
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
cli
|
||||
attach <command/attach>
|
||||
build <command/build>
|
||||
commit <command/commit>
|
||||
diff <command/diff>
|
||||
export <command/export>
|
||||
history <command/history>
|
||||
images <command/images>
|
||||
import <command/import>
|
||||
info <command/info>
|
||||
inspect <command/inspect>
|
||||
kill <command/kill>
|
||||
login <command/login>
|
||||
logs <command/logs>
|
||||
port <command/port>
|
||||
ps <command/ps>
|
||||
pull <command/pull>
|
||||
push <command/push>
|
||||
restart <command/restart>
|
||||
rm <command/rm>
|
||||
rmi <command/rmi>
|
||||
run <command/run>
|
||||
search <command/search>
|
||||
start <command/start>
|
||||
stop <command/stop>
|
||||
tag <command/tag>
|
||||
version <command/version>
|
||||
wait <command/wait>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 194 KiB |
@@ -1,16 +0,0 @@
|
||||
:title: Concepts
|
||||
:description: -- todo: change me
|
||||
:keywords: concepts, documentation, docker, containers
|
||||
|
||||
|
||||
|
||||
Concepts
|
||||
========
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
../index
|
||||
|
||||
@@ -1,261 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Docker documentation build configuration file, created by
|
||||
# sphinx-quickstart on Tue Mar 19 12:34:07 2013.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
# the 'redirect_home.html' page redirects using a http meta refresh which, according
|
||||
# to official sources is more or less equivalent of a 301.
|
||||
|
||||
html_additional_pages = {
|
||||
'concepts/containers': 'redirect_home.html',
|
||||
'concepts/introduction': 'redirect_home.html',
|
||||
}
|
||||
|
||||
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinxcontrib.httpdomain']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
#disable the parmalinks on headers, I find them really annoying
|
||||
html_add_permalinks = None
|
||||
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'toctree'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Docker'
|
||||
copyright = u'2013, Team Docker'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.1'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'docker'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
html_theme_path = ['../theme']
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
|
||||
# We use a png favicon. This is not compatible with internet explorer, but looks
|
||||
# much better on all other browsers. However, sphynx doesn't like it (it likes
|
||||
# .ico better) so we have just put it in the template rather than used this setting
|
||||
# html_favicon = 'favicon.png'
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['static_files']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
html_show_sourcelink = False
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Dockerdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'Docker.tex', u'Docker Documentation',
|
||||
u'Team Docker', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'docker', u'Docker Documentation',
|
||||
[u'Team Docker'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output ------------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'Docker', u'Docker Documentation',
|
||||
u'Team Docker', 'Docker', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
@@ -1,9 +0,0 @@
|
||||
:title: Contribution Guidelines
|
||||
:description: Contribution guidelines: create issues, convetions, pull requests
|
||||
:keywords: contributing, docker, documentation, help, guideline
|
||||
|
||||
Contributing to Docker
|
||||
======================
|
||||
|
||||
Want to hack on Docker? Awesome! The repository includes `all the instructions you need to get started <https://github.com/dotcloud/docker/blob/master/CONTRIBUTING.md>`_.
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
:title: Setting Up a Dev Environment
|
||||
:description: Guides on how to contribute to docker
|
||||
:keywords: Docker, documentation, developers, contributing, dev environment
|
||||
|
||||
Setting Up a Dev Environment
|
||||
============================
|
||||
|
||||
Instructions that have been verified to work on Ubuntu Precise 12.04 (LTS) (64-bit),
|
||||
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
**Linux kernel 3.8**
|
||||
|
||||
Due to a bug in LXC docker works best on the 3.8 kernel. Precise comes with a 3.2 kernel, so we need to upgrade it. The kernel we install comes with AUFS built in.
|
||||
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# install the backported kernel
|
||||
sudo apt-get update && sudo apt-get install linux-image-generic-lts-raring
|
||||
|
||||
# reboot
|
||||
sudo reboot
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo apt-get install python-software-properties
|
||||
sudo add-apt-repository ppa:gophers/go
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install lxc xz-utils curl golang-stable git aufs-tools
|
||||
|
||||
export GOPATH=~/go/
|
||||
export PATH=$GOPATH/bin:$PATH
|
||||
|
||||
mkdir -p $GOPATH/src/github.com/dotcloud
|
||||
cd $GOPATH/src/github.com/dotcloud
|
||||
git clone git://github.com/dotcloud/docker.git
|
||||
cd docker
|
||||
|
||||
go get -v github.com/dotcloud/docker/...
|
||||
go install -v github.com/dotcloud/docker/...
|
||||
|
||||
|
||||
Then run the docker daemon,
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo $GOPATH/bin/docker -d
|
||||
|
||||
|
||||
Run the ``go install`` command (above) to recompile docker.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user