mirror of
https://github.com/moby/moby.git
synced 2026-01-12 03:01:38 +00:00
Compare commits
358 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e289308dff | ||
|
|
8fb8a08ff2 | ||
|
|
30f009150f | ||
|
|
0410397704 | ||
|
|
3cbf5670c5 | ||
|
|
55cf05835b | ||
|
|
c7a7983fcb | ||
|
|
44b33b44aa | ||
|
|
463658dc8f | ||
|
|
80f4b0df75 | ||
|
|
aaaf3f0726 | ||
|
|
eed64e6777 | ||
|
|
0f4469c2b1 | ||
|
|
06767fb99d | ||
|
|
5098c4fc00 | ||
|
|
c255976909 | ||
|
|
0e23b4e10e | ||
|
|
d6c24092eb | ||
|
|
6cafed45af | ||
|
|
3484781a6f | ||
|
|
c4ad6b077d | ||
|
|
45b5d3027e | ||
|
|
070b1cd541 | ||
|
|
8ff1765674 | ||
|
|
c4ebf870c8 | ||
|
|
7b1ec9ff30 | ||
|
|
244e6022ec | ||
|
|
f8dd04d567 | ||
|
|
42b1ea4889 | ||
|
|
d2eb2455a1 | ||
|
|
a2b5196061 | ||
|
|
f46ab22b7a | ||
|
|
074310063d | ||
|
|
01575e1f67 | ||
|
|
82513815f1 | ||
|
|
4df26b9ee7 | ||
|
|
fc2df7e634 | ||
|
|
c718eb282b | ||
|
|
0a13ce9bef | ||
|
|
0a197f9b4f | ||
|
|
3d25e09c3b | ||
|
|
d56c5406ac | ||
|
|
23c5c13014 | ||
|
|
6ac33eb649 | ||
|
|
a02ad8c896 | ||
|
|
4c7c177e4e | ||
|
|
e45aef0c82 | ||
|
|
8472a27e80 | ||
|
|
0d929d13d3 | ||
|
|
a0d80ed3e6 | ||
|
|
e8853ec3a4 | ||
|
|
8ea9811089 | ||
|
|
f3f2cba386 | ||
|
|
d581f0808c | ||
|
|
ce4e87196f | ||
|
|
7757be1f45 | ||
|
|
49b05eb24a | ||
|
|
dae2828957 | ||
|
|
3439cd9cea | ||
|
|
979db00d9a | ||
|
|
db4417b601 | ||
|
|
a64ebe5feb | ||
|
|
602786cd60 | ||
|
|
35c59f4e05 | ||
|
|
756df27e45 | ||
|
|
a46fc3a59e | ||
|
|
9959e2cd63 | ||
|
|
f911ccc27b | ||
|
|
96069de4e0 | ||
|
|
d92166cc79 | ||
|
|
ebb59c1125 | ||
|
|
e2880950c5 | ||
|
|
62a1850c16 | ||
|
|
2bc4ad9402 | ||
|
|
ae1e655fb1 | ||
|
|
92e98c66af | ||
|
|
6d6a03dfba | ||
|
|
924b61328c | ||
|
|
4ebec08add | ||
|
|
15ea5a479a | ||
|
|
6c168a8986 | ||
|
|
4386edff0b | ||
|
|
6bfb652f5b | ||
|
|
bbb634a980 | ||
|
|
034c7a7a5e | ||
|
|
4390a3182f | ||
|
|
e337949cb0 | ||
|
|
dade95844f | ||
|
|
74b9e851f6 | ||
|
|
ff95f2b0ec | ||
|
|
f7c5e92a2e | ||
|
|
6f2125386a | ||
|
|
0aebb25410 | ||
|
|
9db4972a70 | ||
|
|
9751483112 | ||
|
|
7bccdc0d33 | ||
|
|
97215ca384 | ||
|
|
b8f66c0d14 | ||
|
|
27319da0d2 | ||
|
|
f20b5e1323 | ||
|
|
15b85d9d76 | ||
|
|
b38fc9fcdc | ||
|
|
8646f7f11c | ||
|
|
87cc8b6058 | ||
|
|
0fabd390a9 | ||
|
|
2ac7298e4e | ||
|
|
840bde4393 | ||
|
|
bbad653b1a | ||
|
|
4f202cd07f | ||
|
|
da01dd3d56 | ||
|
|
09f1cbabb9 | ||
|
|
c9994ed0fb | ||
|
|
b0e076f374 | ||
|
|
00266df8ac | ||
|
|
3febeb93f5 | ||
|
|
bcdf03037b | ||
|
|
a372f982c1 | ||
|
|
d985050aeb | ||
|
|
0c5e76958b | ||
|
|
0f68042053 | ||
|
|
18796d55a6 | ||
|
|
594827d416 | ||
|
|
5690562fc8 | ||
|
|
be791a223b | ||
|
|
19045b530e | ||
|
|
b5873806d0 | ||
|
|
f10b0f75e0 | ||
|
|
6e2ddf6f60 | ||
|
|
e81a53eea9 | ||
|
|
1c76f91fc4 | ||
|
|
be75608906 | ||
|
|
3c85e9390e | ||
|
|
ea3374bcb0 | ||
|
|
6e936c8fd3 | ||
|
|
4cd9e4722c | ||
|
|
630d358384 | ||
|
|
84be35dce1 | ||
|
|
23953e7d67 | ||
|
|
6644a3c78a | ||
|
|
e179c66400 | ||
|
|
048fd671ef | ||
|
|
7c1a27e2ad | ||
|
|
1cf8a2c26c | ||
|
|
e639309a7a | ||
|
|
2f082510a7 | ||
|
|
2421838b0a | ||
|
|
58ca46af39 | ||
|
|
09d4b9452d | ||
|
|
d78b2d4ade | ||
|
|
424cc678eb | ||
|
|
1561232261 | ||
|
|
e392b7ee9b | ||
|
|
c3a5dd76cf | ||
|
|
359ecf88de | ||
|
|
3dba4022ad | ||
|
|
f4de9d919d | ||
|
|
04f41ebdbc | ||
|
|
c34989f1c4 | ||
|
|
a7c0e9a355 | ||
|
|
19df5a7965 | ||
|
|
589d7c68db | ||
|
|
8ef72cbc94 | ||
|
|
b9ec03c21b | ||
|
|
6cbe27b7a5 | ||
|
|
a82b60b30d | ||
|
|
c08d245539 | ||
|
|
64d7bc442d | ||
|
|
4bc8ef42d4 | ||
|
|
8378498951 | ||
|
|
1617a18258 | ||
|
|
6c1bb39c09 | ||
|
|
f6b5cd77eb | ||
|
|
5ffd63070f | ||
|
|
701132259d | ||
|
|
18b8eeb484 | ||
|
|
b6a5e604ab | ||
|
|
b682a8ea9e | ||
|
|
6e486b638b | ||
|
|
74cd7e822d | ||
|
|
21b9dcd518 | ||
|
|
897cc573f0 | ||
|
|
b0459adc27 | ||
|
|
3edd14b8c2 | ||
|
|
4099a31304 | ||
|
|
6fb495bf6f | ||
|
|
faf8daa7c6 | ||
|
|
8d9aaee60b | ||
|
|
35d704c8a0 | ||
|
|
1df5f4094b | ||
|
|
528da23d6a | ||
|
|
ff5e238de9 | ||
|
|
c63dce393e | ||
|
|
d6a63132ef | ||
|
|
e7271cdaae | ||
|
|
6ca3b151b1 | ||
|
|
0d9475346f | ||
|
|
71199f595d | ||
|
|
58b95878f1 | ||
|
|
e431dc26f1 | ||
|
|
09b1cd58c0 | ||
|
|
d42639e5c5 | ||
|
|
d0c2e31fb9 | ||
|
|
509a01bbe4 | ||
|
|
e7fb7f13d5 | ||
|
|
d172da58ce | ||
|
|
ad86dde10c | ||
|
|
c20e46587d | ||
|
|
eeb03164cf | ||
|
|
bb61678b57 | ||
|
|
a75a1b3859 | ||
|
|
08812096f5 | ||
|
|
5c30faf6f7 | ||
|
|
f7aaa06606 | ||
|
|
7ff65d40d5 | ||
|
|
904c2a0fc3 | ||
|
|
a3ce90b78b | ||
|
|
03b83b3210 | ||
|
|
40ccf1d300 | ||
|
|
038ca5ee39 | ||
|
|
957c500ac9 | ||
|
|
62a595da5c | ||
|
|
d97661aa71 | ||
|
|
c6119da339 | ||
|
|
5051c20833 | ||
|
|
cdc2657ee9 | ||
|
|
76a1a7cf5b | ||
|
|
20c2a4f80f | ||
|
|
ebe157ebb5 | ||
|
|
cb431f223f | ||
|
|
ab34115b42 | ||
|
|
4b3354af3f | ||
|
|
9042535f5a | ||
|
|
8f81e175af | ||
|
|
636c7835d3 | ||
|
|
6d1dd8b41a | ||
|
|
ae97477284 | ||
|
|
86ad98e72a | ||
|
|
03d82922aa | ||
|
|
30d327d37e | ||
|
|
724e2d6b0a | ||
|
|
51d6228261 | ||
|
|
4db680fda4 | ||
|
|
9c7293508d | ||
|
|
9d8743a7ae | ||
|
|
50144aeb42 | ||
|
|
ee298d1420 | ||
|
|
03855b0027 | ||
|
|
2726e3649a | ||
|
|
90668a8a99 | ||
|
|
c7fd84b8a0 | ||
|
|
874a40ed3a | ||
|
|
370fafacbf | ||
|
|
a0478f726d | ||
|
|
e5bc5a2e31 | ||
|
|
25fc3a7e76 | ||
|
|
b3ab0b561e | ||
|
|
8b8c8bf7cb | ||
|
|
a8651a23b2 | ||
|
|
f744cfd5a7 | ||
|
|
e03b241fb1 | ||
|
|
1ddca1948b | ||
|
|
2485bb2cd2 | ||
|
|
6ebb249131 | ||
|
|
c45beabcd5 | ||
|
|
a22c78523f | ||
|
|
5a02c9ba0a | ||
|
|
7577f48dc4 | ||
|
|
0512cf9c83 | ||
|
|
73da7a12e7 | ||
|
|
50f5723f1d | ||
|
|
cbc4eccd50 | ||
|
|
cff26b3a6c | ||
|
|
329c3e0ffd | ||
|
|
4f6cc5c733 | ||
|
|
e413340723 | ||
|
|
95e066d24f | ||
|
|
82b8f7a565 | ||
|
|
97badbd29e | ||
|
|
5a5e417d46 | ||
|
|
4031a01af1 | ||
|
|
0b0d958b88 | ||
|
|
03e4704ae5 | ||
|
|
7a8ac76299 | ||
|
|
c05c91ca3b | ||
|
|
b76d63cb0c | ||
|
|
f926ed182f | ||
|
|
d440782e17 | ||
|
|
82848d4158 | ||
|
|
97535e5a64 | ||
|
|
f079fbe3fa | ||
|
|
90d144b612 | ||
|
|
d3db94696d | ||
|
|
ffe16e3224 | ||
|
|
0b60829df7 | ||
|
|
690e118670 | ||
|
|
038e1d174b | ||
|
|
6c8dcd5cbb | ||
|
|
16aeb77d51 | ||
|
|
4ac3b803b9 | ||
|
|
3514e47edc | ||
|
|
acb546cd1b | ||
|
|
2ced94b414 | ||
|
|
71b5806614 | ||
|
|
1f65c6bf4c | ||
|
|
965e8a02d2 | ||
|
|
baacae8345 | ||
|
|
52cedb8a05 | ||
|
|
15c7e72e2a | ||
|
|
76b40ad6c9 | ||
|
|
6909f3911f | ||
|
|
3b6c540fe8 | ||
|
|
d49a273071 | ||
|
|
1201c418cd | ||
|
|
4a9c3a92e1 | ||
|
|
28831a412f | ||
|
|
70cf467fdf | ||
|
|
c40f01319f | ||
|
|
0731d1a582 | ||
|
|
8ecde8f9a5 | ||
|
|
911925b54a | ||
|
|
7f1a32b9ff | ||
|
|
930e9a7e43 | ||
|
|
61259ab4b4 | ||
|
|
931ca464a7 | ||
|
|
cc5a044a8c | ||
|
|
0eb425426f | ||
|
|
3bfb70db24 | ||
|
|
e49af5b6de | ||
|
|
d8416539b3 | ||
|
|
a76c3a9c95 | ||
|
|
e81ddb2dc7 | ||
|
|
4d728821e3 | ||
|
|
e92c4b1f39 | ||
|
|
152a9f77b4 | ||
|
|
bfb84b564c | ||
|
|
e8a67f632e | ||
|
|
3d2fd8a650 | ||
|
|
79a78d37e7 | ||
|
|
3ae5c45d9a | ||
|
|
f3e89fae28 | ||
|
|
c42a4179fc | ||
|
|
2d32ac8cff | ||
|
|
f68d107a13 | ||
|
|
640efc2ed2 | ||
|
|
003622c8b6 | ||
|
|
6de5ca1e64 | ||
|
|
deb55e416e | ||
|
|
7eda9c64b8 | ||
|
|
84c13a3dcf | ||
|
|
90602ab62a | ||
|
|
8e6ba343bf | ||
|
|
523cd8e29c | ||
|
|
fd39af7f85 | ||
|
|
1615bb08c7 | ||
|
|
d2c1850fb5 | ||
|
|
cb68662b9b | ||
|
|
b7cda3288e | ||
|
|
bbaa975ec8 |
143
CHANGELOG.md
Normal file
143
CHANGELOG.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# Changelog
|
||||
|
||||
## 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
|
||||
42
Makefile
42
Makefile
@@ -1,5 +1,9 @@
|
||||
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
|
||||
|
||||
GIT_ROOT := $(shell git rev-parse --show-toplevel)
|
||||
BUILD_DIR := $(CURDIR)/.gopath
|
||||
|
||||
GOPATH ?= $(BUILD_DIR)
|
||||
@@ -13,10 +17,7 @@ endif
|
||||
GIT_COMMIT = $(shell git rev-parse --short HEAD)
|
||||
GIT_STATUS = $(shell test -n "`git status --porcelain`" && echo "+CHANGES")
|
||||
|
||||
NO_MEMORY_LIMIT ?= 0
|
||||
export NO_MEMORY_LIMIT
|
||||
|
||||
BUILD_OPTIONS = -ldflags "-X main.GIT_COMMIT $(GIT_COMMIT)$(GIT_STATUS) -X main.NO_MEMORY_LIMIT $(NO_MEMORY_LIMIT)"
|
||||
BUILD_OPTIONS = -ldflags "-X main.GIT_COMMIT $(GIT_COMMIT)$(GIT_STATUS)"
|
||||
|
||||
SRC_DIR := $(GOPATH)/src
|
||||
|
||||
@@ -26,18 +27,39 @@ DOCKER_MAIN := $(DOCKER_DIR)/docker
|
||||
DOCKER_BIN_RELATIVE := bin/docker
|
||||
DOCKER_BIN := $(CURDIR)/$(DOCKER_BIN_RELATIVE)
|
||||
|
||||
.PHONY: all clean test
|
||||
.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 get $(GO_OPTIONS); go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o $@)
|
||||
@(cd $(DOCKER_MAIN); go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o $@)
|
||||
@echo $(DOCKER_BIN_RELATIVE) is created.
|
||||
|
||||
$(DOCKER_DIR):
|
||||
@mkdir -p $(dir $@)
|
||||
@ln -sf $(CURDIR)/ $@
|
||||
@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
|
||||
|
||||
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)
|
||||
|
||||
clean:
|
||||
@rm -rf $(dir $(DOCKER_BIN))
|
||||
@@ -52,3 +74,9 @@ test: all
|
||||
|
||||
fmt:
|
||||
@gofmt -s -l -w .
|
||||
|
||||
hack:
|
||||
cd $(CURDIR)/hack && vagrant up
|
||||
|
||||
ssh-dev:
|
||||
cd $(CURDIR)/hack && vagrant ssh
|
||||
|
||||
264
README.md
264
README.md
@@ -1,155 +1,174 @@
|
||||
Docker: the Linux container runtime
|
||||
===================================
|
||||
Docker: the Linux container engine
|
||||
==================================
|
||||
|
||||
Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers.
|
||||
Docker is an open-source engine which automates the deployment of applications as highly portable, self-sufficient containers.
|
||||
|
||||
Docker 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 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.
|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
* *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,
|
||||
the are completely portable and are designed from the ground up with an application-centric design.
|
||||
|
||||
The best part: because docker operates at the OS level, it can still be run inside a VM!
|
||||
|
||||
## Plays well with others
|
||||
|
||||
Docker does not require that you buy into a particular programming language, framework, packaging system or configuration language.
|
||||
|
||||
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.
|
||||
|
||||
Can your application's build be expressed a sequence of such commands? Then docker can build it.
|
||||
|
||||
|
||||
Notable features
|
||||
-----------------
|
||||
## Escape dependency hell
|
||||
|
||||
* Filesystem isolation: each process container runs in a completely separate root filesystem.
|
||||
A common problem for developers is the difficulty of managing all their application's dependencies in a simple and automated way.
|
||||
|
||||
* Resource isolation: system resources like cpu and memory can be allocated differently to each process container, using cgroups.
|
||||
This is usually difficult for several reasons:
|
||||
|
||||
* Network isolation: each process container runs in its own network namespace, with a virtual interface and IP address of its own.
|
||||
* *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.
|
||||
|
||||
* Copy-on-write: root filesystems are created using copy-on-write, which makes deployment extremely fast, memory-cheap and disk-cheap.
|
||||
|
||||
* Logging: the standard streams (stdout/stderr/stdin) of each process container are 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.
|
||||
|
||||
* Interactive shell: docker can allocate a pseudo-tty and attach to the standard input of any container, for example to run a throwaway interactive shell.
|
||||
* 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.
|
||||
|
||||
Under the hood
|
||||
--------------
|
||||
Docker defines a build as running a sequence 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*.
|
||||
|
||||
Under the hood, Docker is built on the following components:
|
||||
Here's a typical docker build process:
|
||||
|
||||
```bash
|
||||
from ubuntu:12.10
|
||||
run apt-get update
|
||||
run apt-get install python
|
||||
run apt-get install python-pip
|
||||
run pip install django
|
||||
run apt-get install curl
|
||||
run curl http://github.com/shykes/helloflask/helloflask/master.tar.gz | tar -zxv
|
||||
run cd master && pip install -r requirements.txt
|
||||
```
|
||||
|
||||
* The [cgroup](http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c) and [namespacing](http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part) capabilities of the Linux kernel;
|
||||
|
||||
* [AUFS](http://aufs.sourceforge.net/aufs.html), a powerful union filesystem with copy-on-write capabilities;
|
||||
|
||||
* The [Go](http://golang.org) programming language;
|
||||
|
||||
* [lxc](http://lxc.sourceforge.net/), a set of convenience scripts to simplify the creation of linux containers.
|
||||
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
|
||||
==================
|
||||
|
||||
Building from source
|
||||
--------------------
|
||||
|
||||
1. Make sure you have a [Go language](http://golang.org) compiler.
|
||||
|
||||
On a Debian/wheezy or Ubuntu 12.10 install the package:
|
||||
|
||||
```bash
|
||||
|
||||
$ sudo apt-get install golang-go
|
||||
```
|
||||
|
||||
2. Execute ``make``
|
||||
|
||||
This command will install all necessary dependencies and build the
|
||||
executable that you can find in ``bin/docker``
|
||||
|
||||
3. Should you like to see what's happening, run ``make`` with ``VERBOSE=1`` parameter:
|
||||
|
||||
```bash
|
||||
|
||||
$ make VERBOSE=1
|
||||
```
|
||||
|
||||
Installing on Ubuntu 12.04 and 12.10
|
||||
------------------------------------
|
||||
|
||||
1. Install dependencies:
|
||||
|
||||
```bash
|
||||
sudo apt-get install lxc bsdtar
|
||||
sudo apt-get install linux-image-extra-`uname -r`
|
||||
```
|
||||
|
||||
The `linux-image-extra` package is needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module.
|
||||
|
||||
2. Install the latest docker binary:
|
||||
|
||||
```bash
|
||||
wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-master.tgz
|
||||
tar -xf docker-master.tgz
|
||||
```
|
||||
|
||||
3. Run your first container!
|
||||
|
||||
```bash
|
||||
cd docker-master
|
||||
sudo ./docker pull base
|
||||
sudo ./docker run -i -t base /bin/bash
|
||||
```
|
||||
|
||||
Consider adding docker to your `PATH` for simplicity.
|
||||
|
||||
Installing on other Linux distributions
|
||||
Quick install on Ubuntu 12.04 and 12.10
|
||||
---------------------------------------
|
||||
|
||||
Right now, the officially supported distributions are:
|
||||
```bash
|
||||
curl get.docker.io | sh -x
|
||||
```
|
||||
|
||||
* Ubuntu 12.04 (precise LTS)
|
||||
* Ubuntu 12.10 (quantal)
|
||||
Binary installs
|
||||
----------------
|
||||
|
||||
Docker probably works on other distributions featuring a recent kernel, the AUFS patch, and up-to-date lxc. However this has not been tested.
|
||||
Docker supports the following binary installation methods.
|
||||
Note that some methods are community contributions and not yet officially supported.
|
||||
|
||||
Some streamlined (but possibly outdated) installation paths' are available from the website: http://docker.io/documentation/
|
||||
* [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/)
|
||||
* [MacOS X (with Vagrant)](http://docs.docker.io/en/latest/installation/macos/)
|
||||
* [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
|
||||
==============
|
||||
|
||||
Running an interactive shell
|
||||
----------------------------
|
||||
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
|
||||
# Download a base image
|
||||
docker pull base
|
||||
|
||||
# Run an interactive shell in the base image,
|
||||
# allocate a tty, attach stdin and stdout
|
||||
docker run -i -t base /bin/bash
|
||||
# On a production system you want this running in an init script
|
||||
sudo docker -d &
|
||||
```
|
||||
|
||||
Detaching from the interactive shell
|
||||
------------------------------------
|
||||
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
|
||||
```
|
||||
# In order to detach without killing the shell, you can use the escape sequence Ctrl-p + Ctrl-q
|
||||
# Note: this works only in tty mode (run with -t option).
|
||||
|
||||
|
||||
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
|
||||
# Run docker in daemon mode
|
||||
(docker -d || echo "Docker daemon already running") &
|
||||
|
||||
# Start a very useful long-running process
|
||||
JOB=$(docker run -d base /bin/sh -c "while true; do echo Hello world; sleep 1; done")
|
||||
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
|
||||
@@ -158,25 +177,32 @@ docker logs $JOB
|
||||
docker kill $JOB
|
||||
```
|
||||
|
||||
|
||||
Listing all running containers
|
||||
------------------------------
|
||||
Running an irc bouncer
|
||||
----------------------
|
||||
|
||||
```bash
|
||||
docker ps
|
||||
BOUNCER_ID=$(docker run -d -p 6667 -u irc shykes/znc $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
|
||||
docker pull base
|
||||
CONTAINER=$(docker run -d base apt-get install -y curl)
|
||||
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
|
||||
------------------------------
|
||||
@@ -197,6 +223,22 @@ echo hello world | nc $IP $PORT
|
||||
echo "Daemon received: $(docker logs $JOB)"
|
||||
```
|
||||
|
||||
Under the hood
|
||||
--------------
|
||||
|
||||
Under the hood, Docker is built on the following components:
|
||||
|
||||
|
||||
* The [cgroup](http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c) and [namespacing](http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part) capabilities of the Linux kernel;
|
||||
|
||||
* [AUFS](http://aufs.sourceforge.net/aufs.html), a powerful union filesystem with copy-on-write capabilities;
|
||||
|
||||
* The [Go](http://golang.org) programming language;
|
||||
|
||||
* [lxc](http://lxc.sourceforge.net/), a set of convenience scripts to simplify the creation of linux containers.
|
||||
|
||||
|
||||
|
||||
Contributing to Docker
|
||||
======================
|
||||
|
||||
|
||||
48
Vagrantfile
vendored
48
Vagrantfile
vendored
@@ -1,41 +1,37 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
def v10(config)
|
||||
config.vm.box = "quantal64_3.5.0-25"
|
||||
config.vm.box_url = "http://get.docker.io/vbox/ubuntu/12.10/quantal64_3.5.0-25.box"
|
||||
BOX_NAME = ENV['BOX_NAME'] || "ubuntu"
|
||||
BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64.box"
|
||||
PPA_KEY = "E61D797F63561DC6"
|
||||
|
||||
config.vm.share_folder "v-data", "/opt/go/src/github.com/dotcloud/docker", File.dirname(__FILE__)
|
||||
|
||||
# Ensure puppet is installed on the instance
|
||||
config.vm.provision :shell, :inline => "apt-get -qq update; apt-get install -y puppet"
|
||||
|
||||
config.vm.provision :puppet do |puppet|
|
||||
puppet.manifests_path = "puppet/manifests"
|
||||
puppet.manifest_file = "quantal64.pp"
|
||||
puppet.module_path = "puppet/modules"
|
||||
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
|
||||
# Add docker PPA key to the local repository and install docker
|
||||
pkg_cmd = "apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys #{PPA_KEY}; "
|
||||
pkg_cmd << "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >/etc/apt/sources.list.d/lxc-docker.list; "
|
||||
pkg_cmd << "apt-get update -qq; apt-get install -q -y lxc-docker"
|
||||
if ARGV.include?("--provider=aws".downcase)
|
||||
# Add AUFS dependency to amazon's VM
|
||||
pkg_cmd << "; apt-get install linux-image-extra-3.2.0-40-virtual"
|
||||
end
|
||||
config.vm.provision :shell, :inline => pkg_cmd
|
||||
end
|
||||
|
||||
Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config|
|
||||
v10(config)
|
||||
end
|
||||
|
||||
Vagrant::VERSION >= "1.1.0" and Vagrant.configure("1") do |config|
|
||||
v10(config)
|
||||
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|
|
||||
config.vm.provider :aws do |aws, override|
|
||||
config.vm.box = "dummy"
|
||||
config.vm.box_url = "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box"
|
||||
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"]
|
||||
aws.ssh_private_key_path = ENV["AWS_SSH_PRIVKEY"]
|
||||
override.ssh.private_key_path = ENV["AWS_SSH_PRIVKEY"]
|
||||
override.ssh.username = "ubuntu"
|
||||
aws.region = "us-east-1"
|
||||
aws.ami = "ami-ae9806c7"
|
||||
aws.ssh_username = "ubuntu"
|
||||
aws.ami = "ami-d0f89fb9"
|
||||
aws.instance_type = "t1.micro"
|
||||
end
|
||||
|
||||
@@ -51,7 +47,7 @@ Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
|
||||
end
|
||||
|
||||
config.vm.provider :virtualbox do |vb|
|
||||
config.vm.box = "quantal64_3.5.0-25"
|
||||
config.vm.box_url = "http://get.docker.io/vbox/ubuntu/12.10/quantal64_3.5.0-25.box"
|
||||
config.vm.box = BOX_NAME
|
||||
config.vm.box_url = BOX_URI
|
||||
end
|
||||
end
|
||||
|
||||
36
archive.go
36
archive.go
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
@@ -86,3 +87,38 @@ func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
52
auth/auth.go
52
auth/auth.go
@@ -3,7 +3,6 @@ package auth
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
@@ -16,7 +15,7 @@ import (
|
||||
const CONFIGFILE = ".dockercfg"
|
||||
|
||||
// the registry server we want to login against
|
||||
const REGISTRY_SERVER = "https://registry.docker.io"
|
||||
const INDEX_SERVER = "https://index.docker.io"
|
||||
|
||||
type AuthConfig struct {
|
||||
Username string `json:"username"`
|
||||
@@ -76,6 +75,9 @@ func LoadConfig(rootPath string) (*AuthConfig, error) {
|
||||
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])
|
||||
@@ -89,9 +91,14 @@ func LoadConfig(rootPath string) (*AuthConfig, error) {
|
||||
|
||||
// save the auth config
|
||||
func saveConfig(rootPath, authStr string, email string) error {
|
||||
confFile := path.Join(rootPath, CONFIGFILE)
|
||||
if len(email) == 0 {
|
||||
os.Remove(confFile)
|
||||
return nil
|
||||
}
|
||||
lines := "auth = " + authStr + "\n" + "email = " + email + "\n"
|
||||
b := []byte(lines)
|
||||
err := ioutil.WriteFile(path.Join(rootPath, CONFIGFILE), b, 0600)
|
||||
err := ioutil.WriteFile(confFile, b, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -101,40 +108,38 @@ func saveConfig(rootPath, authStr string, email string) error {
|
||||
// try to register/login to the registry server
|
||||
func Login(authConfig *AuthConfig) (string, error) {
|
||||
storeConfig := false
|
||||
client := &http.Client{}
|
||||
reqStatusCode := 0
|
||||
var status string
|
||||
var errMsg string
|
||||
var reqBody []byte
|
||||
jsonBody, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
errMsg = fmt.Sprintf("Config Error: %s", err)
|
||||
return "", errors.New(errMsg)
|
||||
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(REGISTRY_SERVER+"/v1/users", "application/json; charset=utf-8", b)
|
||||
req1, err := http.Post(INDEX_SERVER+"/v1/users/", "application/json; charset=utf-8", b)
|
||||
if err != nil {
|
||||
errMsg = fmt.Sprintf("Server Error: %s", err)
|
||||
return "", errors.New(errMsg)
|
||||
return "", fmt.Errorf("Server Error: %s", err)
|
||||
}
|
||||
|
||||
reqStatusCode = req1.StatusCode
|
||||
defer req1.Body.Close()
|
||||
reqBody, err = ioutil.ReadAll(req1.Body)
|
||||
if err != nil {
|
||||
errMsg = fmt.Sprintf("Server Error: [%#v] %s", reqStatusCode, err)
|
||||
return "", errors.New(errMsg)
|
||||
return "", fmt.Errorf("Server Error: [%#v] %s", reqStatusCode, err)
|
||||
}
|
||||
|
||||
if reqStatusCode == 201 {
|
||||
status = "Account Created\n"
|
||||
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 {
|
||||
// FIXME: This should be 'exists', not 'exist'. Need to change on the server first.
|
||||
if string(reqBody) == "Username or email already exist" {
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", REGISTRY_SERVER+"/v1/users", nil)
|
||||
if string(reqBody) == "\"Username or email already exists\"" {
|
||||
req, err := http.NewRequest("GET", INDEX_SERVER+"/v1/users/", nil)
|
||||
req.SetBasicAuth(authConfig.Username, authConfig.Password)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
@@ -148,17 +153,18 @@ func Login(authConfig *AuthConfig) (string, error) {
|
||||
if resp.StatusCode == 200 {
|
||||
status = "Login Succeeded\n"
|
||||
storeConfig = true
|
||||
} else if resp.StatusCode == 401 {
|
||||
saveConfig(authConfig.rootPath, "", "")
|
||||
return "", fmt.Errorf("Wrong login/password, please try again")
|
||||
} else {
|
||||
status = fmt.Sprintf("Login: %s", body)
|
||||
return "", errors.New(status)
|
||||
return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
|
||||
resp.StatusCode, resp.Header)
|
||||
}
|
||||
} else {
|
||||
status = fmt.Sprintf("Registration: %s", reqBody)
|
||||
return "", errors.New(status)
|
||||
return "", fmt.Errorf("Registration: %s", reqBody)
|
||||
}
|
||||
} else {
|
||||
status = fmt.Sprintf("[%s] : %s", reqStatusCode, reqBody)
|
||||
return "", errors.New(status)
|
||||
return "", fmt.Errorf("Unexpected status code [%d] : %s", reqStatusCode, reqBody)
|
||||
}
|
||||
if storeConfig {
|
||||
authStr := EncodeAuth(authConfig)
|
||||
|
||||
20
buildbot/README.rst
Normal file
20
buildbot/README.rst
Normal file
@@ -0,0 +1,20 @@
|
||||
Buildbot
|
||||
========
|
||||
|
||||
Buildbot is a continuous integration system designed to automate the
|
||||
build/test cycle. By automatically rebuilding and testing the tree each time
|
||||
something has changed, build problems are pinpointed quickly, before other
|
||||
developers are inconvenienced by the failure.
|
||||
|
||||
When running 'make hack' at the docker root directory, it spawns a virtual
|
||||
machine in the background running a buildbot instance and adds a git
|
||||
post-commit hook that automatically run docker tests for you.
|
||||
|
||||
You can check your buildbot instance at http://192.168.33.21:8010/waterfall
|
||||
|
||||
|
||||
Buildbot dependencies
|
||||
---------------------
|
||||
|
||||
vagrant, virtualbox packages and python package requests
|
||||
|
||||
28
buildbot/Vagrantfile
vendored
Normal file
28
buildbot/Vagrantfile
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
$BUILDBOT_IP = '192.168.33.21'
|
||||
|
||||
def v10(config)
|
||||
config.vm.box = "quantal64_3.5.0-25"
|
||||
config.vm.box_url = "http://get.docker.io/vbox/ubuntu/12.10/quantal64_3.5.0-25.box"
|
||||
config.vm.share_folder 'v-data', '/data/docker', File.dirname(__FILE__) + '/..'
|
||||
config.vm.network :hostonly, $BUILDBOT_IP
|
||||
|
||||
# Ensure puppet is installed on the instance
|
||||
config.vm.provision :shell, :inline => 'apt-get -qq update; apt-get install -y puppet'
|
||||
|
||||
config.vm.provision :puppet do |puppet|
|
||||
puppet.manifests_path = '.'
|
||||
puppet.manifest_file = 'buildbot.pp'
|
||||
puppet.options = ['--templatedir','.']
|
||||
end
|
||||
end
|
||||
|
||||
Vagrant::VERSION < '1.1.0' and Vagrant::Config.run do |config|
|
||||
v10(config)
|
||||
end
|
||||
|
||||
Vagrant::VERSION >= '1.1.0' and Vagrant.configure('1') do |config|
|
||||
v10(config)
|
||||
end
|
||||
43
buildbot/buildbot-cfg/buildbot-cfg.sh
Executable file
43
buildbot/buildbot-cfg/buildbot-cfg.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Auto setup of buildbot configuration. Package installation is being done
|
||||
# on buildbot.pp
|
||||
# Dependencies: buildbot, buildbot-slave, supervisor
|
||||
|
||||
SLAVE_NAME='buildworker'
|
||||
SLAVE_SOCKET='localhost:9989'
|
||||
BUILDBOT_PWD='pass-docker'
|
||||
USER='vagrant'
|
||||
ROOT_PATH='/data/buildbot'
|
||||
DOCKER_PATH='/data/docker'
|
||||
BUILDBOT_CFG="$DOCKER_PATH/buildbot/buildbot-cfg"
|
||||
IP=$(grep BUILDBOT_IP /data/docker/buildbot/Vagrantfile | awk -F "'" '{ print $2; }')
|
||||
|
||||
function run { su $USER -c "$1"; }
|
||||
|
||||
export PATH=/bin:sbin:/usr/bin:/usr/sbin:/usr/local/bin
|
||||
|
||||
# Exit if buildbot has already been installed
|
||||
[ -d "$ROOT_PATH" ] && exit 0
|
||||
|
||||
# Setup buildbot
|
||||
run "mkdir -p ${ROOT_PATH}"
|
||||
cd ${ROOT_PATH}
|
||||
run "buildbot create-master master"
|
||||
run "cp $BUILDBOT_CFG/master.cfg master"
|
||||
run "sed -i 's/localhost/$IP/' master/master.cfg"
|
||||
run "buildslave create-slave slave $SLAVE_SOCKET $SLAVE_NAME $BUILDBOT_PWD"
|
||||
|
||||
# Allow buildbot subprocesses (docker tests) to properly run in containers,
|
||||
# in particular with docker -u
|
||||
run "sed -i 's/^umask = None/umask = 000/' ${ROOT_PATH}/slave/buildbot.tac"
|
||||
|
||||
# Setup supervisor
|
||||
cp $BUILDBOT_CFG/buildbot.conf /etc/supervisor/conf.d/buildbot.conf
|
||||
sed -i "s/^chmod=0700.*0700./chmod=0770\nchown=root:$USER/" /etc/supervisor/supervisord.conf
|
||||
kill -HUP `pgrep -f "/usr/bin/python /usr/bin/supervisord"`
|
||||
|
||||
# Add git hook
|
||||
cp $BUILDBOT_CFG/post-commit $DOCKER_PATH/.git/hooks
|
||||
sed -i "s/localhost/$IP/" $DOCKER_PATH/.git/hooks/post-commit
|
||||
|
||||
32
buildbot/buildbot.pp
Normal file
32
buildbot/buildbot.pp
Normal file
@@ -0,0 +1,32 @@
|
||||
node default {
|
||||
$USER = 'vagrant'
|
||||
$ROOT_PATH = '/data/buildbot'
|
||||
$DOCKER_PATH = '/data/docker'
|
||||
|
||||
exec {'apt_update': command => '/usr/bin/apt-get update' }
|
||||
Package { require => Exec['apt_update'] }
|
||||
group {'puppet': ensure => 'present'}
|
||||
|
||||
# Install dependencies
|
||||
Package { ensure => 'installed' }
|
||||
package { ['python-dev','python-pip','supervisor','lxc','bsdtar','git','golang']: }
|
||||
|
||||
file{[ '/data' ]:
|
||||
owner => $USER, group => $USER, ensure => 'directory' }
|
||||
|
||||
file {'/var/tmp/requirements.txt':
|
||||
content => template('requirements.txt') }
|
||||
|
||||
exec {'requirements':
|
||||
require => [ Package['python-dev'], Package['python-pip'],
|
||||
File['/var/tmp/requirements.txt'] ],
|
||||
cwd => '/var/tmp',
|
||||
command => "/bin/sh -c '(/usr/bin/pip install -r requirements.txt;
|
||||
rm /var/tmp/requirements.txt)'" }
|
||||
|
||||
exec {'buildbot-cfg-sh':
|
||||
require => [ Package['supervisor'], Exec['requirements']],
|
||||
path => '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin',
|
||||
cwd => '/data',
|
||||
command => "$DOCKER_PATH/buildbot/buildbot-cfg/buildbot-cfg.sh" }
|
||||
}
|
||||
6
buildbot/requirements.txt
Normal file
6
buildbot/requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
sqlalchemy<=0.7.9
|
||||
sqlalchemy-migrate>=0.7.2
|
||||
buildbot==0.8.7p1
|
||||
buildbot_slave==0.8.7p1
|
||||
nose==1.2.1
|
||||
requests==1.1.0
|
||||
463
builder.go
Normal file
463
builder.go
Normal file
@@ -0,0 +1,463 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Builder struct {
|
||||
runtime *Runtime
|
||||
repositories *TagStore
|
||||
graph *Graph
|
||||
}
|
||||
|
||||
func NewBuilder(runtime *Runtime) *Builder {
|
||||
return &Builder{
|
||||
runtime: runtime,
|
||||
graph: runtime.graph,
|
||||
repositories: runtime.repositories,
|
||||
}
|
||||
}
|
||||
|
||||
func (builder *Builder) mergeConfig(userConf, imageConf *Config) {
|
||||
if userConf.Hostname != "" {
|
||||
userConf.Hostname = imageConf.Hostname
|
||||
}
|
||||
if userConf.User != "" {
|
||||
userConf.User = imageConf.User
|
||||
}
|
||||
if userConf.Memory == 0 {
|
||||
userConf.Memory = imageConf.Memory
|
||||
}
|
||||
if userConf.MemorySwap == 0 {
|
||||
userConf.MemorySwap = imageConf.MemorySwap
|
||||
}
|
||||
if userConf.PortSpecs == nil || len(userConf.PortSpecs) == 0 {
|
||||
userConf.PortSpecs = imageConf.PortSpecs
|
||||
}
|
||||
if !userConf.Tty {
|
||||
userConf.Tty = userConf.Tty
|
||||
}
|
||||
if !userConf.OpenStdin {
|
||||
userConf.OpenStdin = imageConf.OpenStdin
|
||||
}
|
||||
if !userConf.StdinOnce {
|
||||
userConf.StdinOnce = imageConf.StdinOnce
|
||||
}
|
||||
if userConf.Env == nil || len(userConf.Env) == 0 {
|
||||
userConf.Env = imageConf.Env
|
||||
}
|
||||
if userConf.Cmd == nil || len(userConf.Cmd) == 0 {
|
||||
userConf.Cmd = imageConf.Cmd
|
||||
}
|
||||
if userConf.Dns == nil || len(userConf.Dns) == 0 {
|
||||
userConf.Dns = imageConf.Dns
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
builder.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 custom dns exists, then create a resolv.conf for the container
|
||||
if len(config.Dns) > 0 {
|
||||
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 config.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
|
||||
}
|
||||
|
||||
func (builder *Builder) clearTmp(containers, images map[string]struct{}) {
|
||||
for c := range containers {
|
||||
tmp := builder.runtime.Get(c)
|
||||
builder.runtime.Destroy(tmp)
|
||||
Debugf("Removing container %s", c)
|
||||
}
|
||||
for i := range images {
|
||||
builder.runtime.graph.Delete(i)
|
||||
Debugf("Removing image %s", i)
|
||||
}
|
||||
}
|
||||
|
||||
func (builder *Builder) getCachedImage(image *Image, config *Config) (*Image, error) {
|
||||
// Retrieve all images
|
||||
images, err := builder.graph.All()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Store the tree in a map of map (map[parentId][childId])
|
||||
imageMap := make(map[string]map[string]struct{})
|
||||
for _, img := range images {
|
||||
if _, exists := imageMap[img.Parent]; !exists {
|
||||
imageMap[img.Parent] = make(map[string]struct{})
|
||||
}
|
||||
imageMap[img.Parent][img.Id] = struct{}{}
|
||||
}
|
||||
|
||||
// Loop on the children of the given image and check the config
|
||||
for elem := range imageMap[image.Id] {
|
||||
img, err := builder.graph.Get(elem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if CompareConfig(&img.ContainerConfig, config) {
|
||||
return img, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) (*Image, error) {
|
||||
var (
|
||||
image, base *Image
|
||||
config *Config
|
||||
maintainer string
|
||||
env map[string]string = make(map[string]string)
|
||||
tmpContainers map[string]struct{} = make(map[string]struct{})
|
||||
tmpImages map[string]struct{} = make(map[string]struct{})
|
||||
)
|
||||
defer builder.clearTmp(tmpContainers, tmpImages)
|
||||
|
||||
file := bufio.NewReader(dockerfile)
|
||||
for {
|
||||
line, err := file.ReadString('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, 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 nil, fmt.Errorf("Invalid Dockerfile format")
|
||||
}
|
||||
instruction := strings.Trim(tmp[0], " ")
|
||||
arguments := strings.Trim(tmp[1], " ")
|
||||
switch strings.ToLower(instruction) {
|
||||
case "from":
|
||||
fmt.Fprintf(stdout, "FROM %s\n", arguments)
|
||||
image, err = builder.runtime.repositories.LookupImage(arguments)
|
||||
if err != nil {
|
||||
if builder.runtime.graph.IsNotExist(err) {
|
||||
|
||||
var tag, remote string
|
||||
if strings.Contains(arguments, ":") {
|
||||
remoteParts := strings.Split(arguments, ":")
|
||||
tag = remoteParts[1]
|
||||
remote = remoteParts[0]
|
||||
} else {
|
||||
remote = arguments
|
||||
}
|
||||
|
||||
if err := builder.runtime.graph.PullRepository(stdout, remote, tag, builder.runtime.repositories, builder.runtime.authConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
image, err = builder.runtime.repositories.LookupImage(arguments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
config = &Config{}
|
||||
|
||||
break
|
||||
case "maintainer":
|
||||
fmt.Fprintf(stdout, "MAINTAINER %s\n", arguments)
|
||||
maintainer = arguments
|
||||
break
|
||||
case "run":
|
||||
fmt.Fprintf(stdout, "RUN %s\n", arguments)
|
||||
if image == nil {
|
||||
return nil, fmt.Errorf("Please provide a source image with `from` prior to run")
|
||||
}
|
||||
config, err := ParseRun([]string{image.Id, "/bin/sh", "-c", arguments}, nil, builder.runtime.capabilities)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for key, value := range env {
|
||||
config.Env = append(config.Env, fmt.Sprintf("%s=%s", key, value))
|
||||
}
|
||||
|
||||
if cache, err := builder.getCachedImage(image, config); err != nil {
|
||||
return nil, err
|
||||
} else if cache != nil {
|
||||
image = cache
|
||||
fmt.Fprintf(stdout, "===> %s\n", image.ShortId())
|
||||
break
|
||||
}
|
||||
|
||||
Debugf("Env -----> %v ------ %v\n", config.Env, env)
|
||||
|
||||
// Create the container and start it
|
||||
c, err := builder.Create(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
out, _ := c.StdoutPipe()
|
||||
err2, _ := c.StderrPipe()
|
||||
go io.Copy(os.Stdout, out)
|
||||
go io.Copy(os.Stdout, err2)
|
||||
}
|
||||
|
||||
if err := c.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tmpContainers[c.Id] = struct{}{}
|
||||
|
||||
// Wait for it to finish
|
||||
if result := c.Wait(); result != 0 {
|
||||
return nil, fmt.Errorf("!!! '%s' return non-zero exit code '%d'. Aborting.", arguments, result)
|
||||
}
|
||||
|
||||
// Commit the container
|
||||
base, err = builder.Commit(c, "", "", "", maintainer, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tmpImages[base.Id] = struct{}{}
|
||||
|
||||
fmt.Fprintf(stdout, "===> %s\n", base.ShortId())
|
||||
|
||||
// use the base as the new image
|
||||
image = base
|
||||
|
||||
break
|
||||
case "env":
|
||||
tmp := strings.SplitN(arguments, " ", 2)
|
||||
if len(tmp) != 2 {
|
||||
return nil, fmt.Errorf("Invalid ENV format")
|
||||
}
|
||||
key := strings.Trim(tmp[0], " ")
|
||||
value := strings.Trim(tmp[1], " ")
|
||||
fmt.Fprintf(stdout, "ENV %s %s\n", key, value)
|
||||
env[key] = value
|
||||
if image != nil {
|
||||
fmt.Fprintf(stdout, "===> %s\n", image.ShortId())
|
||||
} else {
|
||||
fmt.Fprintf(stdout, "===> <nil>\n")
|
||||
}
|
||||
break
|
||||
case "cmd":
|
||||
fmt.Fprintf(stdout, "CMD %s\n", arguments)
|
||||
|
||||
// Create the container and start it
|
||||
c, err := builder.Create(&Config{Image: image.Id, Cmd: []string{"", ""}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tmpContainers[c.Id] = struct{}{}
|
||||
|
||||
cmd := []string{}
|
||||
if err := json.Unmarshal([]byte(arguments), &cmd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Cmd = cmd
|
||||
|
||||
// Commit the container
|
||||
base, err = builder.Commit(c, "", "", "", maintainer, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tmpImages[base.Id] = struct{}{}
|
||||
|
||||
fmt.Fprintf(stdout, "===> %s\n", base.ShortId())
|
||||
image = base
|
||||
break
|
||||
case "expose":
|
||||
ports := strings.Split(arguments, " ")
|
||||
|
||||
fmt.Fprintf(stdout, "EXPOSE %v\n", ports)
|
||||
if image == nil {
|
||||
return nil, fmt.Errorf("Please provide a source image with `from` prior to copy")
|
||||
}
|
||||
|
||||
// Create the container and start it
|
||||
c, err := builder.Create(&Config{Image: image.Id, Cmd: []string{"", ""}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tmpContainers[c.Id] = struct{}{}
|
||||
|
||||
config.PortSpecs = append(ports, config.PortSpecs...)
|
||||
|
||||
// Commit the container
|
||||
base, err = builder.Commit(c, "", "", "", maintainer, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tmpImages[base.Id] = struct{}{}
|
||||
|
||||
fmt.Fprintf(stdout, "===> %s\n", base.ShortId())
|
||||
image = base
|
||||
break
|
||||
case "insert":
|
||||
if image == nil {
|
||||
return nil, fmt.Errorf("Please provide a source image with `from` prior to copy")
|
||||
}
|
||||
tmp = strings.SplitN(arguments, " ", 2)
|
||||
if len(tmp) != 2 {
|
||||
return nil, fmt.Errorf("Invalid INSERT format")
|
||||
}
|
||||
sourceUrl := strings.Trim(tmp[0], " ")
|
||||
destPath := strings.Trim(tmp[1], " ")
|
||||
fmt.Fprintf(stdout, "COPY %s to %s in %s\n", sourceUrl, destPath, base.ShortId())
|
||||
|
||||
file, err := Download(sourceUrl, stdout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Body.Close()
|
||||
|
||||
config, err := ParseRun([]string{base.Id, "echo", "insert", sourceUrl, destPath}, nil, builder.runtime.capabilities)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, err := builder.Create(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Wait for echo to finish
|
||||
if result := c.Wait(); result != 0 {
|
||||
return nil, fmt.Errorf("!!! '%s' return non-zero exit code '%d'. Aborting.", arguments, result)
|
||||
}
|
||||
|
||||
if err := c.Inject(file.Body, destPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
base, err = builder.Commit(c, "", "", "", maintainer, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fmt.Fprintf(stdout, "===> %s\n", base.ShortId())
|
||||
|
||||
image = base
|
||||
|
||||
break
|
||||
default:
|
||||
fmt.Fprintf(stdout, "Skipping unknown instruction %s\n", strings.ToUpper(instruction))
|
||||
}
|
||||
}
|
||||
if image != nil {
|
||||
// The build is successful, keep the temporary containers and images
|
||||
for i := range tmpImages {
|
||||
delete(tmpImages, i)
|
||||
}
|
||||
for i := range tmpContainers {
|
||||
delete(tmpContainers, i)
|
||||
}
|
||||
fmt.Fprintf(stdout, "Build finished. image id: %s\n", image.ShortId())
|
||||
return image, nil
|
||||
}
|
||||
return nil, fmt.Errorf("An error occured during the build\n")
|
||||
}
|
||||
88
builder_test.go
Normal file
88
builder_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"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
|
||||
insert https://raw.github.com/dotcloud/docker/master/CHANGELOG.md /tmp/CHANGELOG.md
|
||||
`
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
builder := NewBuilder(runtime)
|
||||
|
||||
img, err := builder.Build(strings.NewReader(Dockerfile), &nopWriter{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
container, err := builder.Create(
|
||||
&Config{
|
||||
Image: img.Id,
|
||||
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: img.Id,
|
||||
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")
|
||||
}
|
||||
|
||||
container3, err := builder.Create(
|
||||
&Config{
|
||||
Image: img.Id,
|
||||
Cmd: []string{"cat", "/tmp/CHANGELOG.md"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container3)
|
||||
|
||||
output, err = container3.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(output) == 0 {
|
||||
t.Fatal("/tmp/CHANGELOG.md has not been copied")
|
||||
}
|
||||
}
|
||||
418
commands.go
418
commands.go
@@ -6,10 +6,13 @@ import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/auth"
|
||||
"github.com/dotcloud/docker/rcli"
|
||||
"github.com/shin-/cookiejar"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -18,11 +21,10 @@ import (
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const VERSION = "0.1.6"
|
||||
const VERSION = "0.3.2"
|
||||
|
||||
var (
|
||||
GIT_COMMIT string
|
||||
NO_MEMORY_LIMIT bool
|
||||
GIT_COMMIT string
|
||||
)
|
||||
|
||||
func (srv *Server) Name() string {
|
||||
@@ -34,6 +36,7 @@ func (srv *Server) Help() string {
|
||||
help := "Usage: docker COMMAND [arg...]\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n"
|
||||
for _, cmd := range [][]string{
|
||||
{"attach", "Attach to a running container"},
|
||||
{"build", "Build a container from Dockerfile via stdin"},
|
||||
{"commit", "Create a new image from a container's changes"},
|
||||
{"diff", "Inspect changes on a container's filesystem"},
|
||||
{"export", "Stream the contents of a container as a tar archive"},
|
||||
@@ -41,6 +44,7 @@ func (srv *Server) Help() string {
|
||||
{"images", "List images"},
|
||||
{"import", "Create a new filesystem image from the contents of a tarball"},
|
||||
{"info", "Display system-wide information"},
|
||||
{"insert", "Insert a file in an image"},
|
||||
{"inspect", "Return low-level information on a container"},
|
||||
{"kill", "Kill a running container"},
|
||||
{"login", "Register or Login to the docker registry server"},
|
||||
@@ -53,6 +57,7 @@ func (srv *Server) Help() string {
|
||||
{"rm", "Remove a container"},
|
||||
{"rmi", "Remove an image"},
|
||||
{"run", "Run a command in a new container"},
|
||||
{"search", "Search for an image in the docker index"},
|
||||
{"start", "Start a stopped container"},
|
||||
{"stop", "Stop a running container"},
|
||||
{"tag", "Tag an image into a repository"},
|
||||
@@ -64,6 +69,67 @@ func (srv *Server) Help() string {
|
||||
return help
|
||||
}
|
||||
|
||||
func (srv *Server) CmdInsert(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
stdout.Flush()
|
||||
cmd := rcli.Subcmd(stdout, "insert", "IMAGE URL PATH", "Insert a file from URL in the IMAGE at PATH")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
if cmd.NArg() != 3 {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
imageId := cmd.Arg(0)
|
||||
url := cmd.Arg(1)
|
||||
path := cmd.Arg(2)
|
||||
|
||||
img, err := srv.runtime.repositories.LookupImage(imageId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := Download(url, stdout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Body.Close()
|
||||
|
||||
config, err := ParseRun([]string{img.Id, "echo", "insert", url, path}, nil, srv.runtime.capabilities)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b := NewBuilder(srv.runtime)
|
||||
c, err := b.Create(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.Inject(ProgressReader(file.Body, int(file.ContentLength), stdout, "Downloading %v/%v (%v)"), path); err != nil {
|
||||
return err
|
||||
}
|
||||
// FIXME: Handle custom repo, tag comment, author
|
||||
img, err = b.Commit(c, "", "", img.Comment, img.Author, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(stdout, "%s\n", img.Id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) CmdBuild(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
stdout.Flush()
|
||||
cmd := rcli.Subcmd(stdout, "build", "-", "Build a container from Dockerfile via stdin")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
img, err := NewBuilder(srv.runtime).Build(stdin, stdout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(stdout, "%s\n", img.ShortId())
|
||||
return nil
|
||||
}
|
||||
|
||||
// 'docker login': login / register a user to registry service.
|
||||
func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
// Read a line on raw terminal with support for simple backspace
|
||||
@@ -154,6 +220,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args ..
|
||||
if err != nil {
|
||||
fmt.Fprintf(stdout, "Error: %s\r\n", err)
|
||||
} else {
|
||||
srv.runtime.graph.getHttpClient().Jar = cookiejar.NewCookieJar()
|
||||
srv.runtime.authConfig = newAuthConfig
|
||||
}
|
||||
if status != "" {
|
||||
@@ -184,10 +251,14 @@ func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string
|
||||
|
||||
// 'docker version': show version information
|
||||
func (srv *Server) CmdVersion(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
fmt.Fprintf(stdout, "Version:%s\n", VERSION)
|
||||
fmt.Fprintf(stdout, "Git Commit:%s\n", GIT_COMMIT)
|
||||
if NO_MEMORY_LIMIT {
|
||||
fmt.Fprintf(stdout, "Memory limit disabled\n")
|
||||
fmt.Fprintf(stdout, "Version: %s\n", VERSION)
|
||||
fmt.Fprintf(stdout, "Git Commit: %s\n", GIT_COMMIT)
|
||||
fmt.Fprintf(stdout, "Kernel: %s\n", srv.runtime.kernelVersion)
|
||||
if !srv.runtime.capabilities.MemoryLimit {
|
||||
fmt.Fprintf(stdout, "WARNING: No memory limit support\n")
|
||||
}
|
||||
if !srv.runtime.capabilities.SwapLimit {
|
||||
fmt.Fprintf(stdout, "WARNING: No swap limit support\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -213,17 +284,20 @@ func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string
|
||||
len(srv.runtime.List()),
|
||||
VERSION,
|
||||
imgcount)
|
||||
fmt.Fprintf(stdout, "Go version: %s\n", runtime.Version())
|
||||
|
||||
if !rcli.DEBUG_FLAG {
|
||||
if os.Getenv("DEBUG") == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Fprintln(stdout, "debug mode enabled")
|
||||
fmt.Fprintf(stdout, "fds: %d\ngoroutines: %d\n", getTotalUsedFds(), runtime.NumGoroutine())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "stop", "CONTAINER [CONTAINER...]", "Stop a running container")
|
||||
cmd := rcli.Subcmd(stdout, "stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container")
|
||||
nSeconds := cmd.Int("t", 10, "wait t seconds before killing the container")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -233,7 +307,7 @@ func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string
|
||||
}
|
||||
for _, name := range cmd.Args() {
|
||||
if container := srv.runtime.Get(name); container != nil {
|
||||
if err := container.Stop(); err != nil {
|
||||
if err := container.Stop(*nSeconds); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(stdout, container.ShortId())
|
||||
@@ -246,6 +320,7 @@ func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string
|
||||
|
||||
func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "restart", "CONTAINER [CONTAINER...]", "Restart a running container")
|
||||
nSeconds := cmd.Int("t", 10, "wait t seconds before killing the container")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -255,7 +330,7 @@ func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...str
|
||||
}
|
||||
for _, name := range cmd.Args() {
|
||||
if container := srv.runtime.Get(name); container != nil {
|
||||
if err := container.Restart(); err != nil {
|
||||
if err := container.Restart(*nSeconds); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(stdout, container.ShortId())
|
||||
@@ -395,7 +470,8 @@ func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...str
|
||||
}
|
||||
|
||||
func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "rm", "CONTAINER [CONTAINER...]", "Remove a container")
|
||||
cmd := rcli.Subcmd(stdout, "rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove a container")
|
||||
v := cmd.Bool("v", false, "Remove the volumes associated to the container")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -403,15 +479,40 @@ func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
volumes := make(map[string]struct{})
|
||||
for _, name := range cmd.Args() {
|
||||
container := srv.runtime.Get(name)
|
||||
if container == nil {
|
||||
return fmt.Errorf("No such container: %s", name)
|
||||
}
|
||||
// Store all the deleted containers volumes
|
||||
for _, volumeId := range container.Volumes {
|
||||
volumes[volumeId] = struct{}{}
|
||||
}
|
||||
if err := srv.runtime.Destroy(container); err != nil {
|
||||
fmt.Fprintln(stdout, "Error destroying container "+name+": "+err.Error())
|
||||
}
|
||||
}
|
||||
if *v {
|
||||
// Retrieve all volumes from all remaining containers
|
||||
usedVolumes := make(map[string]*Container)
|
||||
for _, container := range srv.runtime.List() {
|
||||
for _, containerVolumeId := range container.Volumes {
|
||||
usedVolumes[containerVolumeId] = container
|
||||
}
|
||||
}
|
||||
|
||||
for volumeId := range volumes {
|
||||
// If the requested volu
|
||||
if c, exists := usedVolumes[volumeId]; exists {
|
||||
fmt.Fprintf(stdout, "The volume %s is used by the container %s. Impossible to remove it. Skipping.\n", volumeId, c.Id)
|
||||
continue
|
||||
}
|
||||
if err := srv.runtime.volumes.Delete(volumeId); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -470,9 +571,9 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args .
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
archive = ProgressReader(resp.Body, int(resp.ContentLength), stdout)
|
||||
archive = ProgressReader(resp.Body, int(resp.ContentLength), stdout, "Importing %v/%v (%v)")
|
||||
}
|
||||
img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "")
|
||||
img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -489,6 +590,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args .
|
||||
|
||||
func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "push", "NAME", "Push an image or a repository to the registry")
|
||||
registry := cmd.String("registry", "", "Registry host to push the image to")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -499,8 +601,8 @@ func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the login failed, abort
|
||||
if srv.runtime.authConfig == nil || srv.runtime.authConfig.Username == "" {
|
||||
// If the login failed AND we're using the index, abort
|
||||
if *registry == "" && (srv.runtime.authConfig == nil || srv.runtime.authConfig.Username == "") {
|
||||
if err := srv.CmdLogin(stdin, stdout, args...); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -523,9 +625,6 @@ func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...
|
||||
Debugf("Pushing [%s] to [%s]\n", local, remote)
|
||||
|
||||
// Try to get the image
|
||||
// FIXME: Handle lookup
|
||||
// FIXME: Also push the tags in case of ./docker push myrepo:mytag
|
||||
// img, err := srv.runtime.LookupImage(cmd.Arg(0))
|
||||
img, err := srv.runtime.graph.Get(local)
|
||||
if err != nil {
|
||||
Debugf("The push refers to a repository [%s] (len: %d)\n", local, len(srv.runtime.repositories.Repositories[local]))
|
||||
@@ -539,7 +638,7 @@ func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...
|
||||
|
||||
return err
|
||||
}
|
||||
err = srv.runtime.graph.PushImage(stdout, img, srv.runtime.authConfig)
|
||||
err = srv.runtime.graph.PushImage(stdout, img, *registry, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -548,6 +647,8 @@ func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...
|
||||
|
||||
func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "pull", "NAME", "Pull an image or a repository from the registry")
|
||||
tag := cmd.String("t", "", "Download tagged image in repository")
|
||||
registry := cmd.String("registry", "", "Registry to download from. Necessary if image is pulled by ID")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -557,15 +658,20 @@ func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.Contains(remote, ":") {
|
||||
remoteParts := strings.Split(remote, ":")
|
||||
tag = &remoteParts[1]
|
||||
remote = remoteParts[0]
|
||||
}
|
||||
|
||||
// FIXME: CmdPull should be a wrapper around Runtime.Pull()
|
||||
if srv.runtime.graph.LookupRemoteImage(remote, srv.runtime.authConfig) {
|
||||
if err := srv.runtime.graph.PullImage(stdout, remote, srv.runtime.authConfig); err != nil {
|
||||
if *registry != "" {
|
||||
if err := srv.runtime.graph.PullImage(stdout, remote, *registry, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// FIXME: Allow pull repo:tag
|
||||
if err := srv.runtime.graph.PullRepository(stdout, remote, "", srv.runtime.repositories, srv.runtime.authConfig); err != nil {
|
||||
if err := srv.runtime.graph.PullRepository(stdout, remote, *tag, srv.runtime.repositories, srv.runtime.authConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -576,85 +682,126 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||
//limit := cmd.Int("l", 0, "Only show the N most recent versions of each image")
|
||||
quiet := cmd.Bool("q", false, "only show numeric IDs")
|
||||
flAll := cmd.Bool("a", false, "show all images")
|
||||
flViz := cmd.Bool("viz", false, "output graph in graphviz format")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
if cmd.NArg() > 1 {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
var nameFilter string
|
||||
if cmd.NArg() == 1 {
|
||||
nameFilter = cmd.Arg(0)
|
||||
}
|
||||
w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)
|
||||
if !*quiet {
|
||||
fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED")
|
||||
}
|
||||
var allImages map[string]*Image
|
||||
var err error
|
||||
if *flAll {
|
||||
allImages, err = srv.runtime.graph.Map()
|
||||
} else {
|
||||
allImages, err = srv.runtime.graph.Heads()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for name, repository := range srv.runtime.repositories.Repositories {
|
||||
if nameFilter != "" && name != nameFilter {
|
||||
continue
|
||||
|
||||
if *flViz {
|
||||
images, _ := srv.runtime.graph.All()
|
||||
if images == nil {
|
||||
return nil
|
||||
}
|
||||
for tag, id := range repository {
|
||||
image, err := srv.runtime.graph.Get(id)
|
||||
|
||||
fmt.Fprintf(stdout, "digraph docker {\n")
|
||||
|
||||
var parentImage *Image
|
||||
var err error
|
||||
for _, image := range images {
|
||||
parentImage, err = image.GetParent()
|
||||
if err != nil {
|
||||
log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err)
|
||||
fmt.Errorf("Error while getting parent image: %v", err)
|
||||
return nil
|
||||
}
|
||||
if parentImage != nil {
|
||||
fmt.Fprintf(stdout, " \"%s\" -> \"%s\"\n", parentImage.ShortId(), image.ShortId())
|
||||
} else {
|
||||
fmt.Fprintf(stdout, " base -> \"%s\" [style=invis]\n", image.ShortId())
|
||||
}
|
||||
}
|
||||
|
||||
reporefs := make(map[string][]string)
|
||||
|
||||
for name, repository := range srv.runtime.repositories.Repositories {
|
||||
for tag, id := range repository {
|
||||
reporefs[TruncateId(id)] = append(reporefs[TruncateId(id)], fmt.Sprintf("%s:%s", name, tag))
|
||||
}
|
||||
}
|
||||
|
||||
for id, repos := range reporefs {
|
||||
fmt.Fprintf(stdout, " \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", id, id, strings.Join(repos, "\\n"))
|
||||
}
|
||||
|
||||
fmt.Fprintf(stdout, " base [style=invisible]\n")
|
||||
fmt.Fprintf(stdout, "}\n")
|
||||
} else {
|
||||
if cmd.NArg() > 1 {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
var nameFilter string
|
||||
if cmd.NArg() == 1 {
|
||||
nameFilter = cmd.Arg(0)
|
||||
}
|
||||
w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)
|
||||
if !*quiet {
|
||||
fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED")
|
||||
}
|
||||
var allImages map[string]*Image
|
||||
var err error
|
||||
if *flAll {
|
||||
allImages, err = srv.runtime.graph.Map()
|
||||
} else {
|
||||
allImages, err = srv.runtime.graph.Heads()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for name, repository := range srv.runtime.repositories.Repositories {
|
||||
if nameFilter != "" && name != nameFilter {
|
||||
continue
|
||||
}
|
||||
delete(allImages, id)
|
||||
if !*quiet {
|
||||
for idx, field := range []string{
|
||||
/* REPOSITORY */ name,
|
||||
/* TAG */ tag,
|
||||
/* ID */ TruncateId(id),
|
||||
/* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago",
|
||||
} {
|
||||
if idx == 0 {
|
||||
w.Write([]byte(field))
|
||||
} else {
|
||||
w.Write([]byte("\t" + field))
|
||||
}
|
||||
for tag, id := range repository {
|
||||
image, err := srv.runtime.graph.Get(id)
|
||||
if err != nil {
|
||||
log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err)
|
||||
continue
|
||||
}
|
||||
delete(allImages, id)
|
||||
if !*quiet {
|
||||
for idx, field := range []string{
|
||||
/* REPOSITORY */ name,
|
||||
/* TAG */ tag,
|
||||
/* ID */ TruncateId(id),
|
||||
/* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago",
|
||||
} {
|
||||
if idx == 0 {
|
||||
w.Write([]byte(field))
|
||||
} else {
|
||||
w.Write([]byte("\t" + field))
|
||||
}
|
||||
}
|
||||
w.Write([]byte{'\n'})
|
||||
} else {
|
||||
stdout.Write([]byte(image.ShortId() + "\n"))
|
||||
}
|
||||
w.Write([]byte{'\n'})
|
||||
} else {
|
||||
stdout.Write([]byte(image.ShortId() + "\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
// Display images which aren't part of a
|
||||
if nameFilter == "" {
|
||||
for id, image := range allImages {
|
||||
if !*quiet {
|
||||
for idx, field := range []string{
|
||||
/* REPOSITORY */ "<none>",
|
||||
/* TAG */ "<none>",
|
||||
/* ID */ TruncateId(id),
|
||||
/* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago",
|
||||
} {
|
||||
if idx == 0 {
|
||||
w.Write([]byte(field))
|
||||
} else {
|
||||
w.Write([]byte("\t" + field))
|
||||
// Display images which aren't part of a
|
||||
if nameFilter == "" {
|
||||
for id, image := range allImages {
|
||||
if !*quiet {
|
||||
for idx, field := range []string{
|
||||
/* REPOSITORY */ "<none>",
|
||||
/* TAG */ "<none>",
|
||||
/* ID */ TruncateId(id),
|
||||
/* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago",
|
||||
} {
|
||||
if idx == 0 {
|
||||
w.Write([]byte(field))
|
||||
} else {
|
||||
w.Write([]byte("\t" + field))
|
||||
}
|
||||
}
|
||||
w.Write([]byte{'\n'})
|
||||
} else {
|
||||
stdout.Write([]byte(image.ShortId() + "\n"))
|
||||
}
|
||||
w.Write([]byte{'\n'})
|
||||
} else {
|
||||
stdout.Write([]byte(image.ShortId() + "\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
if !*quiet {
|
||||
w.Flush()
|
||||
if !*quiet {
|
||||
w.Flush()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -675,7 +822,7 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||
}
|
||||
w := tabwriter.NewWriter(stdout, 12, 1, 3, ' ', 0)
|
||||
if !*quiet {
|
||||
fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tCOMMENT")
|
||||
fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tCOMMENT\tPORTS")
|
||||
}
|
||||
for i, container := range srv.runtime.List() {
|
||||
if !container.State.Running && !*flAll && *nLast == -1 {
|
||||
@@ -696,6 +843,7 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||
/* CREATED */ HumanDuration(time.Now().Sub(container.Created)) + " ago",
|
||||
/* STATUS */ container.State.String(),
|
||||
/* COMMENT */ "",
|
||||
/* PORTS */ container.NetworkSettings.PortMappingHuman(),
|
||||
} {
|
||||
if idx == 0 {
|
||||
w.Write([]byte(field))
|
||||
@@ -720,6 +868,7 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||
"Create a new image from a container's changes")
|
||||
flComment := cmd.String("m", "", "Commit message")
|
||||
flAuthor := cmd.String("author", "", "Author (eg. \"John Hannibal Smith <hannibal@a-team.com>\"")
|
||||
flConfig := cmd.String("run", "", "Config automatically applied when the image is run. "+`(ex: {"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`)
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -728,7 +877,21 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
img, err := srv.runtime.Commit(containerName, repository, tag, *flComment, *flAuthor)
|
||||
|
||||
var config *Config
|
||||
if *flConfig != "" {
|
||||
config = &Config{}
|
||||
if err := json.Unmarshal([]byte(*flConfig), config); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
container := srv.runtime.Get(containerName)
|
||||
if container == nil {
|
||||
return fmt.Errorf("No such container: %s", containerName)
|
||||
}
|
||||
|
||||
img, err := NewBuilder(srv.runtime).Commit(container, repository, tag, *flComment, *flAuthor, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -830,6 +993,10 @@ func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout rcli.DockerConn, args .
|
||||
return fmt.Errorf("No such container: %s", name)
|
||||
}
|
||||
|
||||
if container.State.Ghost {
|
||||
return fmt.Errorf("Impossible to attach to a ghost container")
|
||||
}
|
||||
|
||||
if container.Config.Tty {
|
||||
stdout.SetOptionRawTerminal()
|
||||
}
|
||||
@@ -838,6 +1005,34 @@ func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout rcli.DockerConn, args .
|
||||
return <-container.Attach(stdin, nil, stdout, stdout)
|
||||
}
|
||||
|
||||
func (srv *Server) CmdSearch(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "search", "NAME", "Search the docker index for images")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
if cmd.NArg() != 1 {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
term := cmd.Arg(0)
|
||||
results, err := srv.runtime.graph.SearchRepositories(stdout, term)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(stdout, "Found %d results matching your query (\"%s\")\n", results.NumResults, results.Query)
|
||||
w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)
|
||||
fmt.Fprintf(w, "NAME\tDESCRIPTION\n")
|
||||
for _, repo := range results.Results {
|
||||
description := repo["description"]
|
||||
if len(description) > 45 {
|
||||
description = Trunc(description, 42) + "..."
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\n", repo["name"], description)
|
||||
}
|
||||
w.Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ports type - Used to parse multiple -p flags
|
||||
type ports []int
|
||||
|
||||
@@ -893,6 +1088,25 @@ func (opts AttachOpts) Get(val string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// PathOpts stores a unique set of absolute paths
|
||||
type PathOpts map[string]struct{}
|
||||
|
||||
func NewPathOpts() PathOpts {
|
||||
return make(PathOpts)
|
||||
}
|
||||
|
||||
func (opts PathOpts) String() string {
|
||||
return fmt.Sprintf("%v", map[string]struct{}(opts))
|
||||
}
|
||||
|
||||
func (opts PathOpts) Set(val string) error {
|
||||
if !filepath.IsAbs(val) {
|
||||
return fmt.Errorf("%s is not an absolute path", val)
|
||||
}
|
||||
opts[filepath.Clean(val)] = struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "tag", "[OPTIONS] IMAGE REPOSITORY [TAG]", "Tag an image into a repository")
|
||||
force := cmd.Bool("f", false, "Force")
|
||||
@@ -907,7 +1121,7 @@ func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||
}
|
||||
|
||||
func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
config, err := ParseRun(args, stdout)
|
||||
config, err := ParseRun(args, stdout, srv.runtime.capabilities)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -915,10 +1129,6 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...s
|
||||
fmt.Fprintln(stdout, "Error: Image not specified")
|
||||
return fmt.Errorf("Image not specified")
|
||||
}
|
||||
if len(config.Cmd) == 0 {
|
||||
fmt.Fprintln(stdout, "Error: Command not specified")
|
||||
return fmt.Errorf("Command not specified")
|
||||
}
|
||||
|
||||
if config.Tty {
|
||||
stdout.SetOptionRawTerminal()
|
||||
@@ -927,8 +1137,10 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...s
|
||||
// or tell the client there is no options
|
||||
stdout.Flush()
|
||||
|
||||
b := NewBuilder(srv.runtime)
|
||||
|
||||
// Create new container
|
||||
container, err := srv.runtime.Create(config)
|
||||
container, err := b.Create(config)
|
||||
if err != nil {
|
||||
// If container not found, try to pull it
|
||||
if srv.runtime.graph.IsNotExist(err) {
|
||||
@@ -936,7 +1148,7 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...s
|
||||
if err = srv.CmdPull(stdin, stdout, config.Image); err != nil {
|
||||
return err
|
||||
}
|
||||
if container, err = srv.runtime.Create(config); err != nil {
|
||||
if container, err = b.Create(config); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
@@ -974,14 +1186,20 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...s
|
||||
Debugf("Waiting for attach to return\n")
|
||||
<-attachErr
|
||||
// Expecting I/O pipe error, discarding
|
||||
|
||||
// If we are in stdinonce mode, wait for the process to end
|
||||
// otherwise, simply return
|
||||
if config.StdinOnce && !config.Tty {
|
||||
container.Wait()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewServer() (*Server, error) {
|
||||
func NewServer(autoRestart bool) (*Server, error) {
|
||||
if runtime.GOARCH != "amd64" {
|
||||
log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH)
|
||||
}
|
||||
runtime, err := NewRuntime()
|
||||
runtime, err := NewRuntime(autoRestart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -73,6 +73,77 @@ func cmdWait(srv *Server, container *Container) error {
|
||||
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()
|
||||
@@ -228,6 +299,21 @@ func TestRunDisconnectTty(t *testing.T) {
|
||||
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)
|
||||
@@ -239,14 +325,9 @@ func TestRunDisconnectTty(t *testing.T) {
|
||||
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
|
||||
})
|
||||
// In tty mode, we expect the process to stay alive even after client's stdin closes.
|
||||
// Do not wait for run to finish
|
||||
|
||||
// Client disconnect after run -i should keep stdin out in TTY mode
|
||||
container := runtime.List()[0]
|
||||
// Give some time to monitor to do his thing
|
||||
container.WaitTimeout(500 * time.Millisecond)
|
||||
if !container.State.Running {
|
||||
@@ -329,7 +410,7 @@ func TestAttachDisconnect(t *testing.T) {
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := runtime.Create(
|
||||
container, err := NewBuilder(runtime).Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Memory: 33554432,
|
||||
@@ -384,4 +465,5 @@ func TestAttachDisconnect(t *testing.T) {
|
||||
// Try to avoid the timeoout in destroy. Best effort, don't check error
|
||||
cStdin, _ := container.StdinPipe()
|
||||
cStdin.Close()
|
||||
container.Wait()
|
||||
}
|
||||
|
||||
170
container.go
170
container.go
@@ -11,7 +11,9 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
@@ -46,6 +48,7 @@ type Container struct {
|
||||
runtime *Runtime
|
||||
|
||||
waitLock chan struct{}
|
||||
Volumes map[string]string
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@@ -64,9 +67,11 @@ type Config struct {
|
||||
Cmd []string
|
||||
Dns []string
|
||||
Image string // Name of the image as it was passed by the operator (eg. could be symbolic)
|
||||
Volumes map[string]struct{}
|
||||
VolumesFrom string
|
||||
}
|
||||
|
||||
func ParseRun(args []string, stdout io.Writer) (*Config, error) {
|
||||
func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Config, error) {
|
||||
cmd := rcli.Subcmd(stdout, "run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container")
|
||||
if len(args) > 0 && args[0] != "--help" {
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
@@ -81,8 +86,8 @@ func ParseRun(args []string, stdout io.Writer) (*Config, error) {
|
||||
flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
|
||||
flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)")
|
||||
|
||||
if *flMemory > 0 && NO_MEMORY_LIMIT {
|
||||
fmt.Fprintf(stdout, "WARNING: This version of docker has been compiled without memory limit support. Discarding -m.")
|
||||
if *flMemory > 0 && !capabilities.MemoryLimit {
|
||||
fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
|
||||
*flMemory = 0
|
||||
}
|
||||
|
||||
@@ -95,6 +100,11 @@ func ParseRun(args []string, stdout io.Writer) (*Config, error) {
|
||||
var flDns ListOpts
|
||||
cmd.Var(&flDns, "dns", "Set custom dns servers")
|
||||
|
||||
flVolumes := NewPathOpts()
|
||||
cmd.Var(flVolumes, "v", "Attach a data volume")
|
||||
|
||||
flVolumesFrom := cmd.String("volumes-from", "", "Mount volumes from the specified container")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -134,7 +144,15 @@ func ParseRun(args []string, stdout io.Writer) (*Config, error) {
|
||||
Cmd: runCmd,
|
||||
Dns: flDns,
|
||||
Image: image,
|
||||
Volumes: flVolumes,
|
||||
VolumesFrom: *flVolumesFrom,
|
||||
}
|
||||
|
||||
if *flMemory > 0 && !capabilities.SwapLimit {
|
||||
fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
|
||||
config.MemorySwap = -1
|
||||
}
|
||||
|
||||
// When allocating stdin in attached mode, close stdin at client disconnect
|
||||
if config.OpenStdin && config.AttachStdin {
|
||||
config.StdinOnce = true
|
||||
@@ -150,6 +168,33 @@ type NetworkSettings struct {
|
||||
PortMapping map[string]string
|
||||
}
|
||||
|
||||
// String returns a human-readable description of the port mapping defined in the settings
|
||||
func (settings *NetworkSettings) PortMappingHuman() string {
|
||||
var mapping []string
|
||||
for private, public := range settings.PortMapping {
|
||||
mapping = append(mapping, fmt.Sprintf("%s->%s", public, private))
|
||||
}
|
||||
sort.Strings(mapping)
|
||||
return strings.Join(mapping, ", ")
|
||||
}
|
||||
|
||||
// Inject the io.Reader at the given path. Note: do not close the reader
|
||||
func (container *Container) Inject(file io.Reader, pth string) error {
|
||||
// Make sure the directory exists
|
||||
if err := os.MkdirAll(path.Join(container.rwPath(), path.Dir(pth)), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
// FIXME: Handle permissions/already existing dest
|
||||
dest, err := os.Create(path.Join(container.rwPath(), pth))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(dest, file); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) Cmd() *exec.Cmd {
|
||||
return container.cmd
|
||||
}
|
||||
@@ -367,14 +412,49 @@ func (container *Container) Start() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if container.Config.Memory > 0 && NO_MEMORY_LIMIT {
|
||||
log.Printf("WARNING: This version of docker has been compiled without memory limit support. Discarding the limit.")
|
||||
// Make sure the config is compatible with the current kernel
|
||||
if container.Config.Memory > 0 && !container.runtime.capabilities.MemoryLimit {
|
||||
log.Printf("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
|
||||
container.Config.Memory = 0
|
||||
}
|
||||
if container.Config.Memory > 0 && !container.runtime.capabilities.SwapLimit {
|
||||
log.Printf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
|
||||
container.Config.MemorySwap = -1
|
||||
}
|
||||
container.Volumes = make(map[string]string)
|
||||
|
||||
// Create the requested volumes volumes
|
||||
for volPath := range container.Config.Volumes {
|
||||
if c, err := container.runtime.volumes.Create(nil, container, "", "", nil); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
|
||||
return nil
|
||||
}
|
||||
container.Volumes[volPath] = c.Id
|
||||
}
|
||||
}
|
||||
|
||||
if container.Config.VolumesFrom != "" {
|
||||
c := container.runtime.Get(container.Config.VolumesFrom)
|
||||
if c == nil {
|
||||
return fmt.Errorf("Container %s not found. Impossible to mount its volumes", container.Id)
|
||||
}
|
||||
for volPath, id := range c.Volumes {
|
||||
if _, exists := container.Volumes[volPath]; exists {
|
||||
return fmt.Errorf("The requested volume %s overlap one of the volume of the container %s", volPath, c.Id)
|
||||
}
|
||||
if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
|
||||
return nil
|
||||
}
|
||||
container.Volumes[volPath] = id
|
||||
}
|
||||
}
|
||||
|
||||
if err := container.generateLXCConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
params := []string{
|
||||
"-n", container.Id,
|
||||
"-f", container.lxcConfigPath(),
|
||||
@@ -433,6 +513,7 @@ func (container *Container) Start() error {
|
||||
|
||||
// Init the lock
|
||||
container.waitLock = make(chan struct{})
|
||||
|
||||
container.ToDisk()
|
||||
go container.monitor()
|
||||
return nil
|
||||
@@ -507,16 +588,42 @@ func (container *Container) releaseNetwork() {
|
||||
container.NetworkSettings = &NetworkSettings{}
|
||||
}
|
||||
|
||||
// FIXME: replace this with a control socket within docker-init
|
||||
func (container *Container) waitLxc() error {
|
||||
for {
|
||||
if output, err := exec.Command("lxc-info", "-n", container.Id).CombinedOutput(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if !strings.Contains(string(output), "RUNNING") {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) monitor() {
|
||||
// Wait for the program to exit
|
||||
Debugf("Waiting for process")
|
||||
if err := container.cmd.Wait(); err != nil {
|
||||
// Discard the error as any signals or non 0 returns will generate an error
|
||||
Debugf("%s: Process: %s", container.Id, err)
|
||||
|
||||
// If the command does not exists, try to wait via lxc
|
||||
if container.cmd == nil {
|
||||
if err := container.waitLxc(); err != nil {
|
||||
Debugf("%s: Process: %s", container.Id, err)
|
||||
}
|
||||
} else {
|
||||
if err := container.cmd.Wait(); err != nil {
|
||||
// Discard the error as any signals or non 0 returns will generate an error
|
||||
Debugf("%s: Process: %s", container.Id, err)
|
||||
}
|
||||
}
|
||||
Debugf("Process finished")
|
||||
|
||||
exitCode := container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
|
||||
var exitCode int = -1
|
||||
if container.cmd != nil {
|
||||
exitCode = container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
container.releaseNetwork()
|
||||
@@ -565,7 +672,7 @@ func (container *Container) monitor() {
|
||||
}
|
||||
|
||||
func (container *Container) kill() error {
|
||||
if !container.State.Running || container.cmd == nil {
|
||||
if !container.State.Running {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -577,6 +684,9 @@ func (container *Container) kill() error {
|
||||
|
||||
// 2. Wait for the process to die, in last resort, try to kill the process directly
|
||||
if err := container.WaitTimeout(10 * time.Second); err != nil {
|
||||
if container.cmd == nil {
|
||||
return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", container.Id)
|
||||
}
|
||||
log.Printf("Container %s failed to exit within 10 seconds of lxc SIGKILL - trying direct SIGKILL", container.Id)
|
||||
if err := container.cmd.Process.Kill(); err != nil {
|
||||
return err
|
||||
@@ -594,21 +704,15 @@ func (container *Container) Kill() error {
|
||||
if !container.State.Running {
|
||||
return nil
|
||||
}
|
||||
if container.State.Ghost {
|
||||
return fmt.Errorf("Can't kill ghost container")
|
||||
}
|
||||
return container.kill()
|
||||
}
|
||||
|
||||
func (container *Container) Stop() error {
|
||||
func (container *Container) Stop(seconds int) error {
|
||||
container.State.lock()
|
||||
defer container.State.unlock()
|
||||
if !container.State.Running {
|
||||
return nil
|
||||
}
|
||||
if container.State.Ghost {
|
||||
return fmt.Errorf("Can't stop ghot container")
|
||||
}
|
||||
|
||||
// 1. Send a SIGTERM
|
||||
if output, err := exec.Command("lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil {
|
||||
@@ -620,8 +724,8 @@ func (container *Container) Stop() error {
|
||||
}
|
||||
|
||||
// 2. Wait for the process to exit on its own
|
||||
if err := container.WaitTimeout(10 * time.Second); err != nil {
|
||||
log.Printf("Container %v failed to exit within 10 seconds of SIGTERM - using the force", container.Id)
|
||||
if err := container.WaitTimeout(time.Duration(seconds) * time.Second); err != nil {
|
||||
log.Printf("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.Id, seconds)
|
||||
if err := container.kill(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -629,8 +733,8 @@ func (container *Container) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) Restart() error {
|
||||
if err := container.Stop(); err != nil {
|
||||
func (container *Container) Restart(seconds int) error {
|
||||
if err := container.Stop(seconds); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.Start(); err != nil {
|
||||
@@ -649,6 +753,14 @@ func (container *Container) ExportRw() (Archive, error) {
|
||||
return Tar(container.rwPath(), Uncompressed)
|
||||
}
|
||||
|
||||
func (container *Container) RwChecksum() (string, error) {
|
||||
rwData, err := Tar(container.rwPath(), Xz)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return HashData(rwData)
|
||||
}
|
||||
|
||||
func (container *Container) Export() (Archive, error) {
|
||||
if err := container.EnsureMounted(); err != nil {
|
||||
return nil, err
|
||||
@@ -741,6 +853,22 @@ func (container *Container) RootfsPath() string {
|
||||
return path.Join(container.root, "rootfs")
|
||||
}
|
||||
|
||||
func (container *Container) GetVolumes() (map[string]string, error) {
|
||||
ret := make(map[string]string)
|
||||
for volPath, id := range container.Volumes {
|
||||
volume, err := container.runtime.volumes.Get(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
root, err := volume.root()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret[volPath] = path.Join(root, "layer")
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (container *Container) rwPath() string {
|
||||
return path.Join(container.root, "rw")
|
||||
}
|
||||
|
||||
@@ -20,11 +20,10 @@ func TestIdFormat(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
container1, err := runtime.Create(
|
||||
container1, err := NewBuilder(runtime).Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"/bin/sh", "-c", "echo hello world"},
|
||||
Memory: 33554432,
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"/bin/sh", "-c", "echo hello world"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -45,12 +44,11 @@ func TestMultipleAttachRestart(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(
|
||||
container, err := NewBuilder(runtime).Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"/bin/sh", "-c",
|
||||
"i=1; while [ $i -le 5 ]; do i=`expr $i + 1`; echo hello; done"},
|
||||
Memory: 33554432,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -97,7 +95,7 @@ func TestMultipleAttachRestart(t *testing.T) {
|
||||
t.Fatalf("Unexpected output. Expected [%s], received [%s]", "hello", l3)
|
||||
}
|
||||
|
||||
if err := container.Stop(); err != nil {
|
||||
if err := container.Stop(10); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -116,8 +114,8 @@ func TestMultipleAttachRestart(t *testing.T) {
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
timeout := make(chan bool)
|
||||
go func() {
|
||||
|
||||
setTimeout(t, "Timeout reading from the process", 3*time.Second, func() {
|
||||
l1, err = bufio.NewReader(stdout1).ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -139,28 +137,100 @@ func TestMultipleAttachRestart(t *testing.T) {
|
||||
if strings.Trim(l3, " \r\n") != "hello" {
|
||||
t.Fatalf("Unexpected output. Expected [%s], received [%s]", "hello", l3)
|
||||
}
|
||||
timeout <- false
|
||||
}()
|
||||
go func() {
|
||||
time.Sleep(3 * time.Second)
|
||||
timeout <- true
|
||||
}()
|
||||
if <-timeout {
|
||||
t.Fatalf("Timeout reading from the process")
|
||||
}
|
||||
})
|
||||
container.Wait()
|
||||
}
|
||||
|
||||
func TestCommitRun(t *testing.T) {
|
||||
func TestDiff(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
container1, err := runtime.Create(
|
||||
|
||||
builder := NewBuilder(runtime)
|
||||
|
||||
// Create a container and remove a file
|
||||
container1, err := builder.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"/bin/sh", "-c", "echo hello > /world"},
|
||||
Memory: 33554432,
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"/bin/rm", "/etc/passwd"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container1)
|
||||
|
||||
if err := container1.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check the changelog
|
||||
c, err := container1.Changes()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
success := false
|
||||
for _, elem := range c {
|
||||
if elem.Path == "/etc/passwd" && elem.Kind == 2 {
|
||||
success = true
|
||||
}
|
||||
}
|
||||
if !success {
|
||||
t.Fatalf("/etc/passwd as been removed but is not present in the diff")
|
||||
}
|
||||
|
||||
// Commit the container
|
||||
rwTar, err := container1.ExportRw()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
img, err := runtime.graph.Create(rwTar, container1, "unit test commited image - diff", "", nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// Create a new container from the commited image
|
||||
container2, err := builder.Create(
|
||||
&Config{
|
||||
Image: img.Id,
|
||||
Cmd: []string{"cat", "/etc/passwd"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container2)
|
||||
|
||||
if err := container2.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check the changelog
|
||||
c, err = container2.Changes()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, elem := range c {
|
||||
if elem.Path == "/etc/passwd" {
|
||||
t.Fatalf("/etc/passwd should not be present in the diff after commit.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitAutoRun(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
builder := NewBuilder(runtime)
|
||||
container1, err := builder.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"/bin/sh", "-c", "echo hello > /world"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -182,18 +252,97 @@ func TestCommitRun(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
img, err := runtime.graph.Create(rwTar, container1, "unit test commited image", "")
|
||||
img, err := runtime.graph.Create(rwTar, container1, "unit test commited image", "", &Config{Cmd: []string{"cat", "/world"}})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world
|
||||
container2, err := builder.Create(
|
||||
&Config{
|
||||
Image: img.Id,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container2)
|
||||
stdout, err := container2.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stderr, err := container2.StderrPipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := container2.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container2.Wait()
|
||||
output, err := ioutil.ReadAll(stdout)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
output2, err := ioutil.ReadAll(stderr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := stdout.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := stderr.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(output) != "hello\n" {
|
||||
t.Fatalf("Unexpected output. Expected %s, received: %s (err: %s)", "hello\n", output, output2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitRun(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
builder := NewBuilder(runtime)
|
||||
|
||||
container1, err := builder.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"/bin/sh", "-c", "echo hello > /world"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container1)
|
||||
|
||||
if container1.State.Running {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
if err := container1.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container1.State.Running {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
|
||||
rwTar, err := container1.ExportRw()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
img, err := runtime.graph.Create(rwTar, container1, "unit test commited image", "", nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world
|
||||
|
||||
container2, err := runtime.Create(
|
||||
container2, err := builder.Create(
|
||||
&Config{
|
||||
Image: img.Id,
|
||||
Memory: 33554432,
|
||||
Cmd: []string{"cat", "/world"},
|
||||
Image: img.Id,
|
||||
Cmd: []string{"cat", "/world"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -237,7 +386,7 @@ func TestStart(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(
|
||||
container, err := NewBuilder(runtime).Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Memory: 33554432,
|
||||
@@ -276,11 +425,10 @@ func TestRun(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(
|
||||
container, err := NewBuilder(runtime).Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Memory: 33554432,
|
||||
Cmd: []string{"ls", "-al"},
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"ls", "-al"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -305,7 +453,7 @@ func TestOutput(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(
|
||||
container, err := NewBuilder(runtime).Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"echo", "-n", "foobar"},
|
||||
@@ -330,7 +478,7 @@ func TestKillDifferentUser(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, err := NewBuilder(runtime).Create(&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"tail", "-f", "/etc/resolv.conf"},
|
||||
User: "daemon",
|
||||
@@ -378,7 +526,7 @@ func TestKill(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, err := NewBuilder(runtime).Create(&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"cat", "/dev/zero"},
|
||||
},
|
||||
@@ -424,7 +572,9 @@ func TestExitCode(t *testing.T) {
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
trueContainer, err := runtime.Create(&Config{
|
||||
builder := NewBuilder(runtime)
|
||||
|
||||
trueContainer, err := builder.Create(&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"/bin/true", ""},
|
||||
})
|
||||
@@ -439,7 +589,7 @@ func TestExitCode(t *testing.T) {
|
||||
t.Errorf("Unexpected exit code %d (expected 0)", trueContainer.State.ExitCode)
|
||||
}
|
||||
|
||||
falseContainer, err := runtime.Create(&Config{
|
||||
falseContainer, err := builder.Create(&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"/bin/false", ""},
|
||||
})
|
||||
@@ -461,7 +611,7 @@ func TestRestart(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, err := NewBuilder(runtime).Create(&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"echo", "-n", "foobar"},
|
||||
},
|
||||
@@ -494,7 +644,7 @@ func TestRestartStdin(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, err := NewBuilder(runtime).Create(&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"cat"},
|
||||
|
||||
@@ -573,8 +723,10 @@ func TestUser(t *testing.T) {
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
builder := NewBuilder(runtime)
|
||||
|
||||
// Default user must be root
|
||||
container, err := runtime.Create(&Config{
|
||||
container, err := builder.Create(&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"id"},
|
||||
},
|
||||
@@ -592,7 +744,7 @@ func TestUser(t *testing.T) {
|
||||
}
|
||||
|
||||
// Set a username
|
||||
container, err = runtime.Create(&Config{
|
||||
container, err = builder.Create(&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"id"},
|
||||
|
||||
@@ -612,7 +764,7 @@ func TestUser(t *testing.T) {
|
||||
}
|
||||
|
||||
// Set a UID
|
||||
container, err = runtime.Create(&Config{
|
||||
container, err = builder.Create(&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"id"},
|
||||
|
||||
@@ -632,7 +784,7 @@ func TestUser(t *testing.T) {
|
||||
}
|
||||
|
||||
// Set a different user by uid
|
||||
container, err = runtime.Create(&Config{
|
||||
container, err = builder.Create(&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"id"},
|
||||
|
||||
@@ -654,7 +806,7 @@ func TestUser(t *testing.T) {
|
||||
}
|
||||
|
||||
// Set a different user by username
|
||||
container, err = runtime.Create(&Config{
|
||||
container, err = builder.Create(&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"id"},
|
||||
|
||||
@@ -681,7 +833,9 @@ func TestMultipleContainers(t *testing.T) {
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
container1, err := runtime.Create(&Config{
|
||||
builder := NewBuilder(runtime)
|
||||
|
||||
container1, err := builder.Create(&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"cat", "/dev/zero"},
|
||||
},
|
||||
@@ -691,7 +845,7 @@ func TestMultipleContainers(t *testing.T) {
|
||||
}
|
||||
defer runtime.Destroy(container1)
|
||||
|
||||
container2, err := runtime.Create(&Config{
|
||||
container2, err := builder.Create(&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"cat", "/dev/zero"},
|
||||
},
|
||||
@@ -737,7 +891,7 @@ func TestStdin(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, err := NewBuilder(runtime).Create(&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"cat"},
|
||||
|
||||
@@ -784,7 +938,7 @@ func TestTty(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, err := NewBuilder(runtime).Create(&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"cat"},
|
||||
|
||||
@@ -831,7 +985,7 @@ func TestEnv(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, err := NewBuilder(runtime).Create(&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"/usr/bin/env"},
|
||||
},
|
||||
@@ -905,7 +1059,7 @@ func TestLXCConfig(t *testing.T) {
|
||||
memMin := 33554432
|
||||
memMax := 536870912
|
||||
mem := memMin + rand.Intn(memMax-memMin)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, err := NewBuilder(runtime).Create(&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"/bin/true"},
|
||||
|
||||
@@ -932,7 +1086,7 @@ func BenchmarkRunSequencial(b *testing.B) {
|
||||
}
|
||||
defer nuke(runtime)
|
||||
for i := 0; i < b.N; i++ {
|
||||
container, err := runtime.Create(&Config{
|
||||
container, err := NewBuilder(runtime).Create(&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"echo", "-n", "foo"},
|
||||
},
|
||||
@@ -967,7 +1121,7 @@ func BenchmarkRunParallel(b *testing.B) {
|
||||
complete := make(chan error)
|
||||
tasks = append(tasks, complete)
|
||||
go func(i int, complete chan error) {
|
||||
container, err := runtime.Create(&Config{
|
||||
container, err := NewBuilder(runtime).Create(&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Cmd: []string{"echo", "-n", "foo"},
|
||||
},
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
const DOCKER_PATH = "/home/creack/dotcloud/docker/docker/docker"
|
||||
var DOCKER_PATH string = 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(DOCKER_PATH, "-d")
|
||||
outPipe, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
@@ -38,19 +43,43 @@ func crashTest() error {
|
||||
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; i++ {
|
||||
for i := 0; i < 100 && !stop; {
|
||||
func() error {
|
||||
cmd := exec.Command(DOCKER_PATH, "run", "base", "echo", "hello", "world")
|
||||
log.Printf("%d", i)
|
||||
cmd := exec.Command(DOCKER_PATH, "run", "base", "echo", fmt.Sprintf("%d", totalTestCount))
|
||||
i++
|
||||
totalTestCount++
|
||||
outPipe, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -62,9 +91,10 @@ func crashTest() error {
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
io.Copy(os.Stdout, outPipe)
|
||||
}()
|
||||
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"))
|
||||
|
||||
@@ -49,26 +49,39 @@ def docker(args, stdin=None):
|
||||
def image_exists(img):
|
||||
return docker(["inspect", img]).read().strip() != ""
|
||||
|
||||
def run_and_commit(img_in, cmd, stdin=None):
|
||||
def image_config(img):
|
||||
return json.loads(docker(["inspect", img]).read()).get("config", {})
|
||||
|
||||
def run_and_commit(img_in, cmd, stdin=None, author=None, run=None):
|
||||
run_id = docker(["run"] + (["-i", "-a", "stdin"] if stdin else ["-d"]) + [img_in, "/bin/sh", "-c", cmd], stdin=stdin).read().rstrip()
|
||||
print "---> Waiting for " + run_id
|
||||
result=int(docker(["wait", run_id]).read().rstrip())
|
||||
if result != 0:
|
||||
print "!!! '{}' return non-zero exit code '{}'. Aborting.".format(cmd, result)
|
||||
sys.exit(1)
|
||||
return docker(["commit", run_id]).read().rstrip()
|
||||
return docker(["commit"] + (["-author", author] if author else []) + (["-run", json.dumps(run)] if run is not None else []) + [run_id]).read().rstrip()
|
||||
|
||||
def insert(base, src, dst):
|
||||
def insert(base, src, dst, author=None):
|
||||
print "COPY {} to {} in {}".format(src, dst, base)
|
||||
if dst == "":
|
||||
raise Exception("Missing destination path")
|
||||
stdin = file(src)
|
||||
stdin.seek(0)
|
||||
return run_and_commit(base, "cat > {0}; chmod +x {0}".format(dst), stdin=stdin)
|
||||
|
||||
return run_and_commit(base, "cat > {0}; chmod +x {0}".format(dst), stdin=stdin, author=author)
|
||||
|
||||
def add(base, src, dst, author=None):
|
||||
print "PUSH to {} in {}".format(dst, base)
|
||||
if src == ".":
|
||||
tar = subprocess.Popen(["tar", "-c", "."], stdout=subprocess.PIPE).stdout
|
||||
else:
|
||||
tar = subprocess.Popen(["curl", src], stdout=subprocess.PIPE).stdout
|
||||
if dst == "":
|
||||
raise Exception("Missing argument to push")
|
||||
return run_and_commit(base, "mkdir -p '{0}' && tar -C '{0}' -x".format(dst), stdin=tar, author=author)
|
||||
|
||||
def main():
|
||||
base=""
|
||||
maintainer=""
|
||||
steps = []
|
||||
try:
|
||||
for line in sys.stdin.readlines():
|
||||
@@ -76,27 +89,52 @@ def main():
|
||||
# Skip comments and empty lines
|
||||
if line == "" or line[0] == "#":
|
||||
continue
|
||||
op, param = line.split(" ", 1)
|
||||
op, param = line.split(None, 1)
|
||||
print op.upper() + " " + param
|
||||
if op == "from":
|
||||
print "FROM " + param
|
||||
base = param
|
||||
steps.append(base)
|
||||
elif op == "maintainer":
|
||||
maintainer = param
|
||||
elif op == "run":
|
||||
print "RUN " + param
|
||||
result = run_and_commit(base, param)
|
||||
result = run_and_commit(base, param, author=maintainer)
|
||||
steps.append(result)
|
||||
base = result
|
||||
print "===> " + base
|
||||
elif op == "copy":
|
||||
src, dst = param.split(" ", 1)
|
||||
result = insert(base, src, dst)
|
||||
result = insert(base, src, dst, author=maintainer)
|
||||
steps.append(result)
|
||||
base = result
|
||||
print "===> " + base
|
||||
elif op == "add":
|
||||
src, dst = param.split(" ", 1)
|
||||
result = add(base, src, dst, author=maintainer)
|
||||
steps.append(result)
|
||||
base=result
|
||||
print "===> " + base
|
||||
elif op == "expose":
|
||||
config = image_config(base)
|
||||
if config.get("PortSpecs") is None:
|
||||
config["PortSpecs"] = []
|
||||
portspec = param.strip()
|
||||
config["PortSpecs"].append(portspec)
|
||||
result = run_and_commit(base, "# (nop) expose port {}".format(portspec), author=maintainer, run=config)
|
||||
steps.append(result)
|
||||
base=result
|
||||
print "===> " + base
|
||||
elif op == "cmd":
|
||||
config = image_config(base)
|
||||
cmd = list(json.loads(param))
|
||||
config["Cmd"] = cmd
|
||||
result = run_and_commit(base, "# (nop) set default command to '{}'".format(" ".join(cmd)), author=maintainer, run=config)
|
||||
steps.append(result)
|
||||
base=result
|
||||
print "===> " + base
|
||||
else:
|
||||
print "Skipping uknown op " + op
|
||||
except:
|
||||
docker(["rmi"] + steps)
|
||||
docker(["rmi"] + steps[1:])
|
||||
raise
|
||||
print base
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# Start build from a know base image
|
||||
maintainer Solomon Hykes <solomon@dotcloud.com>
|
||||
from base:ubuntu-12.10
|
||||
# Update ubuntu sources
|
||||
run echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list
|
||||
run apt-get update
|
||||
# Install system packages
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang
|
||||
# Insert files from the host (./myscript must be present in the current directory)
|
||||
copy myscript /usr/local/bin/myscript
|
||||
copy myscript /usr/local/bin/myscript
|
||||
push /src
|
||||
|
||||
3
contrib/docker-build/myscript
Normal file
3
contrib/docker-build/myscript
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo hello, world!
|
||||
@@ -36,9 +36,9 @@ else
|
||||
fi
|
||||
|
||||
echo "Downloading docker binary and uncompressing into /usr/local/bin..."
|
||||
curl -s http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-master.tgz |
|
||||
curl -s http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-latest.tgz |
|
||||
tar -C /usr/local/bin --strip-components=1 -zxf- \
|
||||
docker-master/docker
|
||||
docker-latest/docker
|
||||
|
||||
if [ -f /etc/init/dockerd.conf ]
|
||||
then
|
||||
|
||||
61
contrib/mkimage-debian.sh
Executable file
61
contrib/mkimage-debian.sh
Executable file
@@ -0,0 +1,61 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# these should match the names found at http://www.debian.org/releases/
|
||||
stableSuite='squeeze'
|
||||
testingSuite='wheezy'
|
||||
unstableSuite='sid'
|
||||
|
||||
# if suite is equal to this, it gets the "latest" tag
|
||||
latestSuite="$testingSuite"
|
||||
|
||||
variant='minbase'
|
||||
include='iproute,iputils-ping'
|
||||
|
||||
repo="$1"
|
||||
suite="${2:-$latestSuite}"
|
||||
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
|
||||
|
||||
if [ "$suite" = "$latestSuite" ]; then
|
||||
# tag latest
|
||||
docker tag $img $repo latest
|
||||
fi
|
||||
|
||||
# test the image
|
||||
docker run -i -t $repo:$suite echo success
|
||||
|
||||
# unstable's version numbers match testing (since it's mostly just a sandbox for testing), so it doesn't get a version number tag
|
||||
if [ "$suite" != "$unstableSuite" -a "$suite" != 'unstable' ]; then
|
||||
# tag the specific version
|
||||
ver=$(docker run $repo:$suite cat /etc/debian_version)
|
||||
docker tag $img $repo $ver
|
||||
fi
|
||||
|
||||
# cleanup
|
||||
cd "$returnTo"
|
||||
sudo rm -rf "$target"
|
||||
3
contrib/vagrant-docker/README.md
Normal file
3
contrib/vagrant-docker/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Vagrant-docker
|
||||
|
||||
This is a placeholder for the official vagrant-docker, a plugin for Vagrant (http://vagrantup.com) which exposes Docker as a provider.
|
||||
@@ -7,15 +7,16 @@ import (
|
||||
"github.com/dotcloud/docker/rcli"
|
||||
"github.com/dotcloud/docker/term"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
GIT_COMMIT string
|
||||
NO_MEMORY_LIMIT string
|
||||
GIT_COMMIT string
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -27,6 +28,7 @@ func main() {
|
||||
// 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")
|
||||
flag.Parse()
|
||||
@@ -39,16 +41,12 @@ func main() {
|
||||
os.Setenv("DEBUG", "1")
|
||||
}
|
||||
docker.GIT_COMMIT = GIT_COMMIT
|
||||
docker.NO_MEMORY_LIMIT = NO_MEMORY_LIMIT == "1"
|
||||
if *flDaemon {
|
||||
if flag.NArg() != 0 {
|
||||
flag.Usage()
|
||||
return
|
||||
}
|
||||
if NO_MEMORY_LIMIT == "1" {
|
||||
log.Printf("WARNING: This version of docker has been compiled without memory limit support.")
|
||||
}
|
||||
if err := daemon(*pidfile); err != nil {
|
||||
if err := daemon(*pidfile, *flAutoRestart); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
@@ -59,8 +57,13 @@ func main() {
|
||||
}
|
||||
|
||||
func createPidFile(pidfile string) error {
|
||||
if _, err := os.Stat(pidfile); err == nil {
|
||||
return fmt.Errorf("pid file found, ensure docker is not running or delete %s", pidfile)
|
||||
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)
|
||||
@@ -80,7 +83,7 @@ func removePidFile(pidfile string) {
|
||||
}
|
||||
}
|
||||
|
||||
func daemon(pidfile string) error {
|
||||
func daemon(pidfile string, autoRestart bool) error {
|
||||
if err := createPidFile(pidfile); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -95,7 +98,7 @@ func daemon(pidfile string) error {
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
service, err := docker.NewServer()
|
||||
service, err := docker.NewServer(autoRestart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -46,22 +46,24 @@ clean:
|
||||
docs:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
cp sources/index.html $(BUILDDIR)/html/
|
||||
cp -r sources/gettingstarted $(BUILDDIR)/html/
|
||||
cp sources/dotcloud.yml $(BUILDDIR)/html/
|
||||
cp sources/CNAME $(BUILDDIR)/html/
|
||||
cp sources/.nojekyll $(BUILDDIR)/html/
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
@echo "Build finished. The documentation pages are now in $(BUILDDIR)/html."
|
||||
|
||||
|
||||
site:
|
||||
cp -r website $(BUILDDIR)/
|
||||
cp -r theme/docker/static/ $(BUILDDIR)/website/
|
||||
@echo
|
||||
@echo "The Website pages are in $(BUILDDIR)/site."
|
||||
|
||||
connect:
|
||||
@echo pushing changes to staging site
|
||||
@cd _build/html/ ; \
|
||||
@dotcloud list ; \
|
||||
@echo connecting dotcloud to www.docker.io website, make sure to use user 1
|
||||
@cd _build/website/ ; \
|
||||
dotcloud list ; \
|
||||
dotcloud connect dockerwebsite
|
||||
|
||||
push:
|
||||
@cd _build/html/ ; \
|
||||
@cd _build/website/ ; \
|
||||
dotcloud push
|
||||
|
||||
github-deploy: docs
|
||||
|
||||
@@ -15,6 +15,7 @@ Installation
|
||||
|
||||
* Work in your own fork of the code, we accept pull requests.
|
||||
* Install sphinx: ``pip install sphinx``
|
||||
* Install sphinx httpdomain contrib package ``sphinxcontrib-httpdomain``
|
||||
* If pip is not available you can probably install it using your favorite package manager as **python-pip**
|
||||
|
||||
Usage
|
||||
|
||||
2
docs/requirements.txt
Normal file
2
docs/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Sphinx==1.1.3
|
||||
sphinxcontrib-httpdomain==1.1.8
|
||||
@@ -1 +0,0 @@
|
||||
docker.io
|
||||
130
docs/sources/builder/basics.rst
Normal file
130
docs/sources/builder/basics.rst
Normal file
@@ -0,0 +1,130 @@
|
||||
==============
|
||||
Docker Builder
|
||||
==============
|
||||
|
||||
.. contents:: Table of Contents
|
||||
|
||||
1. Format
|
||||
=========
|
||||
|
||||
The Docker builder format is quite simple:
|
||||
|
||||
``instruction arguments``
|
||||
|
||||
The first instruction must be `FROM`
|
||||
|
||||
All instruction are to be placed in a file named `Dockerfile`
|
||||
|
||||
In order to place comments within a Dockerfile, simply prefix the line with "`#`"
|
||||
|
||||
2. Instructions
|
||||
===============
|
||||
|
||||
Docker builder comes with a set of instructions:
|
||||
|
||||
1. FROM: Set from what image to build
|
||||
2. RUN: Execute a command
|
||||
3. INSERT: Insert a remote file (http) into the image
|
||||
|
||||
2.1 FROM
|
||||
--------
|
||||
``FROM <image>``
|
||||
|
||||
The `FROM` instruction must be the first one in order for Builder to know from where to run commands.
|
||||
|
||||
`FROM` can also be used in order to build multiple images within a single Dockerfile
|
||||
|
||||
2.2 MAINTAINER
|
||||
--------------
|
||||
``MAINTAINER <name>``
|
||||
|
||||
The `MAINTAINER` instruction allow you to set the Author field of the generated images.
|
||||
This instruction is never automatically reset.
|
||||
|
||||
2.3 RUN
|
||||
-------
|
||||
``RUN <command>``
|
||||
|
||||
The `RUN` instruction is the main one, it allows you to execute any commands on the `FROM` image and to save the results.
|
||||
You can use as many `RUN` as you want within a Dockerfile, the commands will be executed on the result of the previous command.
|
||||
|
||||
|
||||
2.4 CMD
|
||||
-------
|
||||
``CMD <command>``
|
||||
|
||||
The `CMD` instruction sets the command to be executed when running the image.
|
||||
It is equivalent to do `docker commit -run '{"Cmd": <command>}'` outside the builder.
|
||||
|
||||
.. note::
|
||||
Do not confuse `RUN` with `CMD`. `RUN` actually run a command and save the result, `CMD` does not execute anything.
|
||||
|
||||
2.5 EXPOSE
|
||||
----------
|
||||
``EXPOSE <port> [<port>...]``
|
||||
|
||||
The `EXPOSE` instruction sets ports to be publicly exposed when running the image.
|
||||
This is equivalent to do `docker commit -run '{"PortSpecs": ["<port>", "<port2>"]}'` outside the builder.
|
||||
|
||||
2.6 ENV
|
||||
-------
|
||||
``ENV <key> <value>``
|
||||
|
||||
The `ENV` instruction set as environment variable `<key>` with the value `<value>`. This value will be passed to all future ``RUN`` instructions.
|
||||
|
||||
.. note::
|
||||
The environment variables are local to the Dockerfile, they will not be set as autorun.
|
||||
|
||||
2.7 INSERT
|
||||
----------
|
||||
|
||||
``INSERT <file url> <path>``
|
||||
|
||||
The `INSERT` instruction will download the file at the given url and place it within the image at the given path.
|
||||
|
||||
.. note::
|
||||
The path must include the file name.
|
||||
|
||||
|
||||
3. Dockerfile Examples
|
||||
======================
|
||||
|
||||
::
|
||||
|
||||
# Nginx
|
||||
#
|
||||
# VERSION 0.0.1
|
||||
# DOCKER-VERSION 0.2
|
||||
|
||||
from ubuntu
|
||||
maintainer Guillaume J. Charmes "guillaume@dotcloud.com"
|
||||
|
||||
# make sure the package repository is up to date
|
||||
run echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
|
||||
run apt-get update
|
||||
|
||||
run apt-get install -y inotify-tools nginx apache2 openssh-server
|
||||
insert https://raw.github.com/creack/docker-vps/master/nginx-wrapper.sh /usr/sbin/nginx-wrapper
|
||||
|
||||
::
|
||||
|
||||
# Firefox over VNC
|
||||
#
|
||||
# VERSION 0.3
|
||||
# DOCKER-VERSION 0.2
|
||||
|
||||
from ubuntu
|
||||
# make sure the package repository is up to date
|
||||
run echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
|
||||
run apt-get update
|
||||
|
||||
# Install vnc, xvfb in order to create a 'fake' display and firefox
|
||||
run apt-get install -y x11vnc xvfb firefox
|
||||
run mkdir /.vnc
|
||||
# Setup a password
|
||||
run x11vnc -storepasswd 1234 ~/.vnc/passwd
|
||||
# Autostart firefox (might not be the best way to do it, but it does the trick)
|
||||
run bash -c 'echo "firefox" >> /.bashrc'
|
||||
|
||||
expose 5900
|
||||
cmd ["x11vnc", "-forever", "-usepw", "-create"]
|
||||
14
docs/sources/builder/index.rst
Normal file
14
docs/sources/builder/index.rst
Normal file
@@ -0,0 +1,14 @@
|
||||
:title: docker documentation
|
||||
:description: Documentation for docker builder
|
||||
:keywords: docker, builder, dockerfile
|
||||
|
||||
|
||||
Builder
|
||||
=======
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
basics
|
||||
@@ -27,6 +27,7 @@ Available Commands
|
||||
:maxdepth: 1
|
||||
|
||||
command/attach
|
||||
command/build
|
||||
command/commit
|
||||
command/diff
|
||||
command/export
|
||||
@@ -46,6 +47,7 @@ Available Commands
|
||||
command/rm
|
||||
command/rmi
|
||||
command/run
|
||||
command/search
|
||||
command/start
|
||||
command/stop
|
||||
command/tag
|
||||
|
||||
9
docs/sources/commandline/command/build.rst
Normal file
9
docs/sources/commandline/command/build.rst
Normal file
@@ -0,0 +1,9 @@
|
||||
========================================================
|
||||
``build`` -- Build a container from Dockerfile via stdin
|
||||
========================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker build -
|
||||
Example: cat Dockerfile | docker build -
|
||||
Build a new image from the Dockerfile passed via stdin
|
||||
@@ -9,3 +9,19 @@
|
||||
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": "",
|
||||
"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"]}
|
||||
|
||||
@@ -10,3 +10,13 @@
|
||||
|
||||
-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
|
||||
|
||||
BIN
docs/sources/commandline/command/images/docker_images.gif
Normal file
BIN
docs/sources/commandline/command/images/docker_images.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
@@ -17,3 +17,6 @@
|
||||
-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 volumes and mount it at the specified path.
|
||||
-volumes-from="": Mount all volumes from the given container.
|
||||
|
||||
10
docs/sources/commandline/command/search.rst
Normal file
10
docs/sources/commandline/command/search.rst
Normal file
@@ -0,0 +1,10 @@
|
||||
===================================================================
|
||||
``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.
|
||||
@@ -9,7 +9,7 @@ Commands
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:maxdepth: 3
|
||||
|
||||
basics
|
||||
workingwithrepository
|
||||
|
||||
@@ -10,9 +10,9 @@ Building blocks
|
||||
|
||||
Images
|
||||
------
|
||||
An original container image. These are stored on disk and are comparable with what you normally expect from a stoppped virtual machine image. Images are stored (and retrieved from) repository
|
||||
An original container image. These are stored on disk and are comparable with what you normally expect from a stopped virtual machine image. Images are stored (and retrieved from) repository
|
||||
|
||||
Images are stored on your local file system under /var/lib/docker/images
|
||||
Images are stored on your local file system under /var/lib/docker/graph
|
||||
|
||||
|
||||
.. _containers:
|
||||
|
||||
@@ -5,124 +5,4 @@
|
||||
|
||||
:note: This version of the introduction is temporary, just to make sure we don't break the links from the website when the documentation is updated
|
||||
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
Docker - The Linux container runtime
|
||||
------------------------------------
|
||||
|
||||
Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers.
|
||||
|
||||
Docker is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc.
|
||||
|
||||
|
||||
- **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.
|
||||
- **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.
|
||||
- **Isolation** docker isolates processes from each other and from the underlying host, using lightweight containers.
|
||||
- **Repeatability** Because containers are isolated in their own filesystem, they behave the same regardless of where, when, and alongside what they run.
|
||||
|
||||
|
||||
|
||||
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 dependency, regardless of the underlying machine and the contents of the container.
|
||||
|
||||
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 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.
|
||||
|
||||
Standard operations
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Just like shipping containers, Standard Containers define a set of STANDARD OPERATIONS. Shipping containers can be lifted, stacked, locked, loaded, unloaded and labelled. Similarly, standard containers can be started, stopped, copied, snapshotted, downloaded, uploaded and tagged.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Infrastructure-agnostic
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Both types of containers are INFRASTRUCTURE-AGNOSTIC: they can be transported to thousands of facilities around the world, and manipulated by a wide variety of equipment. A shipping container can be packed in a factory in Ukraine, transported by truck to the nearest routing center, stacked onto a train, loaded into a German boat by an Australian-built crane, stored in a warehouse at a US facility, etc. Similarly, a standard container can be bundled on my laptop, uploaded to S3, downloaded, run and snapshotted by a build server at Equinix in Virginia, uploaded to 10 staging servers in a home-made Openstack cluster, then sent to 30 production instances across 3 EC2 regions.
|
||||
|
||||
|
||||
Designed for automation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Because they offer the same standard operations regardless of content and infrastructure, Standard Containers, just like their physical counterpart, are extremely well-suited for automation. In fact, you could say automation is their secret weapon.
|
||||
|
||||
Many things that once required time-consuming and error-prone human effort can now be programmed. Before shipping containers, a bag of powder coffee was hauled, dragged, dropped, rolled and stacked by 10 different people in 10 different locations by the time it reached its destination. 1 out of 50 disappeared. 1 out of 20 was damaged. The process was slow, inefficient and cost a fortune - and was entirely different depending on the facility and the type of goods.
|
||||
|
||||
Similarly, before Standard Containers, by the time a software component ran in production, it had been individually built, configured, bundled, documented, patched, vendored, templated, tweaked and instrumented by 10 different people on 10 different computers. Builds failed, libraries conflicted, mirrors crashed, post-it notes were lost, logs were misplaced, cluster updates were half-broken. The process was slow, inefficient and cost a fortune - and was entirely different depending on the language and infrastructure provider.
|
||||
|
||||
|
||||
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 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.
|
||||
|
||||
|
||||
Standard Container Specification
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
(TODO)
|
||||
|
||||
Image format
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Standard operations
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Copy
|
||||
- Run
|
||||
- Stop
|
||||
- Wait
|
||||
- Commit
|
||||
- Attach standard streams
|
||||
- List filesystem changes
|
||||
- ...
|
||||
|
||||
Execution environment
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Root filesystem
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Environment variables
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Process arguments
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
Networking
|
||||
^^^^^^^^^^
|
||||
|
||||
Process namespacing
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Resource limits
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Process monitoring
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Logging
|
||||
^^^^^^^
|
||||
|
||||
Signals
|
||||
^^^^^^^
|
||||
|
||||
Pseudo-terminal allocation
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Security
|
||||
^^^^^^^^
|
||||
|
||||
This document has been moved to :ref:`introduction`, please update your bookmarks.
|
||||
@@ -2,7 +2,7 @@
|
||||
:description: An introduction to docker and standard containers?
|
||||
:keywords: containers, lxc, concepts, explanation
|
||||
|
||||
|
||||
.. _introduction:
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
@@ -25,7 +25,7 @@ import sys, os
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = []
|
||||
extensions = ['sphinxcontrib.httpdomain']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
53
docs/sources/examples/couchdb_data_volumes.rst
Normal file
53
docs/sources/examples/couchdb_data_volumes.rst
Normal file
@@ -0,0 +1,53 @@
|
||||
:title: Sharing data between 2 couchdb databases
|
||||
:description: Sharing data between 2 couchdb databases
|
||||
:keywords: docker, example, package installation, networking, couchdb, data volumes
|
||||
|
||||
.. _running_couchdb_service:
|
||||
|
||||
Create a CouchDB service
|
||||
======================
|
||||
|
||||
.. include:: example_header.inc
|
||||
|
||||
Here's an example of using data volumes to share the same data between 2 couchdb containers.
|
||||
This could be used for hot upgrades, testing different versions of couchdb on the same data, etc.
|
||||
|
||||
Create first database
|
||||
---------------------
|
||||
|
||||
Note that we're marking /var/lib/couchdb as a data volume.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
COUCH1=$(docker run -d -v /var/lib/couchdb shykes/couchdb:2013-05-03)
|
||||
|
||||
Add data to the first database
|
||||
------------------------------
|
||||
|
||||
We're assuming your docker host is reachable at `localhost`. If not, replace `localhost` with the public IP of your docker host.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
HOST=localhost
|
||||
URL="http://$HOST:$(docker port $COUCH1 5984)/_utils/"
|
||||
echo "Navigate to $URL in your browser, and use the couch interface to add data"
|
||||
|
||||
Create second database
|
||||
----------------------
|
||||
|
||||
This time, we're requesting shared access to $COUCH1's volumes.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
COUCH2=$(docker run -d -volumes-from $COUCH1) shykes/couchdb:2013-05-03)
|
||||
|
||||
Browse data on the second database
|
||||
----------------------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
HOST=localhost
|
||||
URL="http://$HOST:$(docker port $COUCH2 5984)/_utils/"
|
||||
echo "Navigate to $URL in your browser. You should see the same data as in the first database!"
|
||||
|
||||
Congratulations, you are running 2 Couchdb containers, completely isolated from each other *except* for their data.
|
||||
@@ -18,3 +18,4 @@ Contents:
|
||||
python_web_app
|
||||
running_redis_service
|
||||
running_ssh_service
|
||||
couchdb_data_volumes
|
||||
|
||||
@@ -49,7 +49,7 @@ Save the changed we just made in the container to a new image called "_/builds/g
|
||||
WEB_WORKER=$(docker run -d -p 5000 $BUILD_IMG /usr/local/bin/runapp)
|
||||
|
||||
- **"docker run -d "** run a command in a new container. We pass "-d" so it runs as a daemon.
|
||||
**"-p 5000"* the web app is going to listen on this port, so it must be mapped from the container to the host system.
|
||||
- **"-p 5000"** the web app is going to listen on this port, so it must be mapped from the container to the host system.
|
||||
- **"$BUILD_IMG"** is the image we want to run the command inside of.
|
||||
- **/usr/local/bin/runapp** is the command which starts the web app.
|
||||
|
||||
|
||||
@@ -7,27 +7,16 @@
|
||||
Running The Examples
|
||||
--------------------
|
||||
|
||||
There are two ways to run docker, daemon mode and standalone mode.
|
||||
|
||||
When you run the docker command it will first check if there is a docker daemon running in the background it can connect to.
|
||||
|
||||
* If it exists it will use that daemon to run all of the commands.
|
||||
* If it does not exist docker will run in standalone mode (docker will exit after each command).
|
||||
|
||||
Docker needs to be run from a privileged account (root).
|
||||
|
||||
1. The most common (and recommended) way is to run a docker daemon as root in the background, and then connect to it from the docker client from any account.
|
||||
All the examples assume your machine is running the docker daemon. To run the docker daemon in the background, simply type:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# starting docker daemon in the background
|
||||
sudo docker -d &
|
||||
|
||||
# now you can run docker commands from any account.
|
||||
docker <command>
|
||||
|
||||
2. Standalone: You need to run every command as root, or using sudo
|
||||
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.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker <command>
|
||||
# now you can run docker commands from any account.
|
||||
docker help
|
||||
|
||||
@@ -15,7 +15,10 @@ This documentation has the following resources:
|
||||
examples/index
|
||||
contributing/index
|
||||
commandline/index
|
||||
registry/index
|
||||
index/index
|
||||
builder/index
|
||||
faq
|
||||
|
||||
|
||||
.. image:: http://www.docker.io/_static/lego_docker.jpg
|
||||
.. image:: http://www.docker.io/_static/lego_docker.jpg
|
||||
|
||||
15
docs/sources/index/index.rst
Normal file
15
docs/sources/index/index.rst
Normal file
@@ -0,0 +1,15 @@
|
||||
:title: Docker Index documentation
|
||||
:description: Documentation for docker Index
|
||||
:keywords: docker, index, api
|
||||
|
||||
|
||||
|
||||
Index
|
||||
=====
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
search
|
||||
38
docs/sources/index/search.rst
Normal file
38
docs/sources/index/search.rst
Normal file
@@ -0,0 +1,38 @@
|
||||
=======================
|
||||
Docker Index Search API
|
||||
=======================
|
||||
|
||||
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,8 +1,9 @@
|
||||
Amazon EC2
|
||||
==========
|
||||
|
||||
Please note this is a community contributed installation path. The only 'official' installation is using the :ref:`ubuntu_linux` installation path. This version
|
||||
may be out of date because it depends on some binaries to be updated and published
|
||||
Please note this is a community contributed installation path. The only 'official' installation is using the
|
||||
:ref:`ubuntu_linux` installation path. This version may sometimes be out of date.
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
@@ -17,7 +18,7 @@ Docker can now be installed on Amazon EC2 with a single vagrant command. Vagrant
|
||||
vagrant plugin install vagrant-aws
|
||||
|
||||
|
||||
3. Get the docker sources, this will give you the latest Vagrantfile and puppet manifests.
|
||||
3. Get the docker sources, this will give you the latest Vagrantfile.
|
||||
|
||||
::
|
||||
|
||||
|
||||
@@ -3,19 +3,23 @@
|
||||
Arch Linux
|
||||
==========
|
||||
|
||||
Please note this is a community contributed installation path. The only 'official' installation is using the
|
||||
:ref:`ubuntu_linux` installation path. This version may sometimes be out of date.
|
||||
|
||||
|
||||
Installing on Arch Linux is not officially supported but can be handled via
|
||||
either of the following AUR packages:
|
||||
|
||||
* `dotcloud-docker <https://aur.archlinux.org/packages/dotcloud-docker/>`_
|
||||
* `dotcloud-docker-git <https://aur.archlinux.org/packages/dotcloud-docker-git/>`_
|
||||
* `lxc-docker <https://aur.archlinux.org/packages/lxc-docker/>`_
|
||||
* `lxc-docker-git <https://aur.archlinux.org/packages/lxc-docker-git/>`_
|
||||
|
||||
The dotcloud-docker package will install the latest tagged version of docker.
|
||||
The dotcloud-docker-git package will build from the current master branch.
|
||||
The lxc-docker package will install the latest tagged version of docker.
|
||||
The lxc-docker-git package will build from the current master branch.
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
Docker depends on several packages which will be installed automatically with
|
||||
Docker depends on several packages which are specified as dependencies in
|
||||
either AUR package.
|
||||
|
||||
* aufs3
|
||||
@@ -23,6 +27,7 @@ either AUR package.
|
||||
* go
|
||||
* iproute2
|
||||
* linux-aufs_friendly
|
||||
* lxc
|
||||
|
||||
Installation
|
||||
------------
|
||||
@@ -37,7 +42,24 @@ new kernel will be compiled and this can take quite a while.
|
||||
|
||||
::
|
||||
|
||||
yaourt -S dotcloud-docker-git
|
||||
yaourt -S lxc-docker-git
|
||||
|
||||
|
||||
Starting Docker
|
||||
---------------
|
||||
|
||||
Prior to starting docker modify your bootloader to use the
|
||||
**linux-aufs_friendly** kernel and reboot your system.
|
||||
|
||||
There is a systemd service unit created for docker. To start the docker service:
|
||||
|
||||
::
|
||||
|
||||
sudo systemctl start docker
|
||||
|
||||
|
||||
To start on system boot:
|
||||
|
||||
::
|
||||
|
||||
sudo systemctl enable docker
|
||||
|
||||
53
docs/sources/installation/binaries.rst
Normal file
53
docs/sources/installation/binaries.rst
Normal file
@@ -0,0 +1,53 @@
|
||||
.. _binaries:
|
||||
|
||||
Binaries
|
||||
========
|
||||
|
||||
**Please note this project is currently under heavy development. It should not be used in production.**
|
||||
|
||||
|
||||
Right now, the officially supported distributions are:
|
||||
|
||||
- Ubuntu 12.04 (precise LTS) (64-bit)
|
||||
- Ubuntu 12.10 (quantal) (64-bit)
|
||||
|
||||
|
||||
Install dependencies:
|
||||
---------------------
|
||||
|
||||
::
|
||||
|
||||
sudo apt-get install lxc bsdtar
|
||||
sudo apt-get install linux-image-extra-`uname -r`
|
||||
|
||||
The linux-image-extra package is needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module.
|
||||
|
||||
Install the docker binary:
|
||||
|
||||
::
|
||||
|
||||
wget http://get.docker.io/builds/Linux/x86_64/docker-latest.tgz
|
||||
tar -xf docker-latest.tgz
|
||||
sudo cp ./docker-latest/docker /usr/local/bin
|
||||
|
||||
Note: docker currently only supports 64-bit Linux hosts.
|
||||
|
||||
|
||||
Run the docker daemon
|
||||
---------------------
|
||||
|
||||
::
|
||||
|
||||
sudo docker -d &
|
||||
|
||||
|
||||
Run your first container!
|
||||
-------------------------
|
||||
|
||||
::
|
||||
|
||||
docker run -i -t ubuntu /bin/bash
|
||||
|
||||
|
||||
|
||||
Continue with the :ref:`hello_world` example.
|
||||
@@ -13,8 +13,9 @@ Contents:
|
||||
:maxdepth: 1
|
||||
|
||||
ubuntulinux
|
||||
binaries
|
||||
archlinux
|
||||
macos
|
||||
vagrant
|
||||
windows
|
||||
amazon
|
||||
upgrading
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
|
||||
Mac OS X and other linux
|
||||
========================
|
||||
|
||||
Please note this is a community contributed installation path. The only 'official' installation is using the :ref:`ubuntu_linux` installation path. This version
|
||||
may be out of date because it depends on some binaries to be updated and published
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
We currently rely on some Ubuntu-linux specific packages, this will change in the future, but for now we provide a
|
||||
streamlined path to install Virtualbox with a Ubuntu 12.10 image using Vagrant.
|
||||
|
||||
1. Install virtualbox from https://www.virtualbox.org/ (or use your package manager)
|
||||
2. Install vagrant from http://www.vagrantup.com/ (or use your package manager)
|
||||
3. Install git if you had not installed it before, check if it is installed by running
|
||||
``git`` in a terminal window
|
||||
|
||||
We recommend having at least about 2Gb of free disk space and 2Gb RAM (or more).
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
1. Fetch the docker sources
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git clone https://github.com/dotcloud/docker.git
|
||||
|
||||
2. Run vagrant from the sources directory
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
vagrant up
|
||||
|
||||
Vagrant will:
|
||||
|
||||
* Download the Quantal64 base ubuntu virtual machine image from get.docker.io/
|
||||
* Boot this image in virtualbox
|
||||
|
||||
Then it will use Puppet to perform an initial setup in this machine:
|
||||
|
||||
* Download & untar the most recent docker binary tarball to vagrant homedir.
|
||||
* Debootstrap to /var/lib/docker/images/ubuntu.
|
||||
* Install & run dockerd as service.
|
||||
* Put docker in /usr/local/bin.
|
||||
* Put latest Go toolchain in /usr/local/go.
|
||||
|
||||
You now have a Ubuntu Virtual Machine running with docker pre-installed.
|
||||
|
||||
To access the VM and use Docker, Run ``vagrant ssh`` from the same directory as where you ran
|
||||
``vagrant up``. Vagrant will make sure to connect you to the correct VM.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
vagrant ssh
|
||||
|
||||
Now you are in the VM, run docker
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker
|
||||
|
||||
|
||||
Continue with the :ref:`hello_world` example.
|
||||
@@ -6,51 +6,56 @@ Ubuntu Linux
|
||||
**Please note this project is currently under heavy development. It should not be used in production.**
|
||||
|
||||
|
||||
|
||||
Installing on Ubuntu 12.04 and 12.10
|
||||
|
||||
Right now, the officially supported distributions are:
|
||||
|
||||
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.
|
||||
- Ubuntu 12.04 (precise LTS) (64-bit)
|
||||
- Ubuntu 12.10 (quantal) (64-bit)
|
||||
|
||||
Install dependencies:
|
||||
---------------------
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
::
|
||||
The linux-image-extra package is only needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module.
|
||||
|
||||
sudo apt-get install lxc wget bsdtar curl
|
||||
sudo apt-get install linux-image-extra-`uname -r`
|
||||
.. code-block:: bash
|
||||
|
||||
The linux-image-extra package is needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module.
|
||||
|
||||
Install the latest docker binary:
|
||||
|
||||
::
|
||||
|
||||
wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-master.tgz
|
||||
tar -xf docker-master.tgz
|
||||
|
||||
Run your first container!
|
||||
|
||||
::
|
||||
|
||||
cd docker-master
|
||||
|
||||
::
|
||||
|
||||
sudo ./docker run -i -t base /bin/bash
|
||||
sudo apt-get install linux-image-extra-`uname -r` lxc bsdtar
|
||||
|
||||
|
||||
To run docker as a daemon, in the background, and allow non-root users to run ``docker`` start
|
||||
docker -d
|
||||
Installation
|
||||
------------
|
||||
|
||||
::
|
||||
|
||||
sudo ./docker -d &
|
||||
Docker is available as a Ubuntu PPA (Personal Package Archive),
|
||||
`hosted on launchpad <https://launchpad.net/~dotcloud/+archive/lxc-docker>`_
|
||||
which makes installing Docker on Ubuntu very easy.
|
||||
|
||||
|
||||
Consider adding docker to your PATH for simplicity.
|
||||
|
||||
Continue with the :ref:`hello_world` example.
|
||||
Add the custom package sources to your apt sources list. Copy and paste the following lines at once.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo sh -c "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >> /etc/apt/sources.list"
|
||||
|
||||
|
||||
Update your sources. You will see a warning that GPG signatures cannot be verified.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo apt-get update
|
||||
|
||||
|
||||
Now install it, you will see another warning that the package cannot be authenticated. Confirm install.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl get.docker.io | sudo sh -x
|
||||
|
||||
|
||||
Verify it worked
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker
|
||||
|
||||
|
||||
**Done!**, now continue with the :ref:`hello_world` example.
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
Upgrading
|
||||
============
|
||||
|
||||
We assume you are upgrading from within the operating system which runs your docker daemon.
|
||||
These instructions are for upgrading your Docker binary for when you had a custom (non package manager) installation.
|
||||
If you istalled docker using apt-get, use that to upgrade.
|
||||
|
||||
|
||||
Get the latest docker binary:
|
||||
|
||||
::
|
||||
|
||||
wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-master.tgz
|
||||
wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-latest.tgz
|
||||
|
||||
|
||||
|
||||
@@ -18,7 +19,7 @@ Unpack it to your current dir
|
||||
|
||||
::
|
||||
|
||||
tar -xf docker-master.tgz
|
||||
tar -xf docker-latest.tgz
|
||||
|
||||
|
||||
Stop your current daemon. How you stop your daemon depends on how you started it.
|
||||
@@ -37,4 +38,4 @@ Now start the daemon
|
||||
sudo ./docker -d &
|
||||
|
||||
|
||||
Alternatively you can replace the docker binary in ``/usr/local/bin``
|
||||
Alternatively you can replace the docker binary in ``/usr/local/bin``
|
||||
|
||||
70
docs/sources/installation/vagrant.rst
Normal file
70
docs/sources/installation/vagrant.rst
Normal file
@@ -0,0 +1,70 @@
|
||||
|
||||
.. _install_using_vagrant:
|
||||
|
||||
Using Vagrant
|
||||
=============
|
||||
|
||||
Please note this is a community contributed installation path. The only 'official' installation is using the
|
||||
:ref:`ubuntu_linux` installation path. This version may sometimes be out of date.
|
||||
|
||||
**Requirements:**
|
||||
This guide will setup a new virtual machine with docker installed on your computer. This works on most operating
|
||||
systems, including MacOX, Windows, Linux, FreeBSD and others. If you can install these and have at least 400Mb RAM
|
||||
to spare you should be good.
|
||||
|
||||
|
||||
Install Vagrant and Virtualbox
|
||||
------------------------------
|
||||
|
||||
1. Install virtualbox from https://www.virtualbox.org/ (or use your package manager)
|
||||
2. Install vagrant from http://www.vagrantup.com/ (or use your package manager)
|
||||
3. Install git if you had not installed it before, check if it is installed by running
|
||||
``git`` in a terminal window
|
||||
|
||||
|
||||
Spin it up
|
||||
----------
|
||||
|
||||
1. Fetch the docker sources (this includes the Vagrantfile for machine setup).
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git clone https://github.com/dotcloud/docker.git
|
||||
|
||||
2. Run vagrant from the sources directory
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
vagrant up
|
||||
|
||||
Vagrant will:
|
||||
|
||||
* Download the 'official' Precise64 base ubuntu virtual machine image from vagrantup.com
|
||||
* Boot this image in virtualbox
|
||||
* Add the `Docker PPA sources <https://launchpad.net/~dotcloud/+archive/lxc-docker>`_ to /etc/apt/sources.lst
|
||||
* Update your sources
|
||||
* Install lxc-docker
|
||||
|
||||
You now have a Ubuntu Virtual Machine running with docker pre-installed.
|
||||
|
||||
Connect
|
||||
-------
|
||||
|
||||
To access the VM and use Docker, Run ``vagrant ssh`` from the same directory as where you ran
|
||||
``vagrant up``. Vagrant will connect you to the correct VM.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
vagrant ssh
|
||||
|
||||
Run
|
||||
-----
|
||||
|
||||
Now you are in the VM, run docker
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker
|
||||
|
||||
|
||||
Continue with the :ref:`hello_world` example.
|
||||
@@ -3,8 +3,8 @@
|
||||
:keywords: Docker, Docker documentation, Windows, requirements, virtualbox, vagrant, git, ssh, putty, cygwin
|
||||
|
||||
|
||||
Windows
|
||||
=========
|
||||
Windows (with Vagrant)
|
||||
======================
|
||||
|
||||
Please note this is a community contributed installation path. The only 'official' installation is using the :ref:`ubuntu_linux` installation path. This version
|
||||
may be out of date because it depends on some binaries to be updated and published
|
||||
|
||||
468
docs/sources/registry/api.rst
Normal file
468
docs/sources/registry/api.rst
Normal file
@@ -0,0 +1,468 @@
|
||||
===================
|
||||
Docker Registry API
|
||||
===================
|
||||
|
||||
.. 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.
|
||||
|
||||
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, all lowercase no special characters.
|
||||
- **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.
|
||||
|
||||
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
|
||||
|
||||
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="
|
||||
15
docs/sources/registry/index.rst
Normal file
15
docs/sources/registry/index.rst
Normal file
@@ -0,0 +1,15 @@
|
||||
:title: docker Registry documentation
|
||||
:description: Documentation for docker Registry and Registry API
|
||||
:keywords: docker, registry, api, index
|
||||
|
||||
|
||||
|
||||
Registry
|
||||
========
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
api
|
||||
BIN
docs/sources/static_files/docker_pull_chart.png
Normal file
BIN
docs/sources/static_files/docker_pull_chart.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
BIN
docs/sources/static_files/docker_push_chart.png
Normal file
BIN
docs/sources/static_files/docker_push_chart.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
3
docs/theme/docker/layout.html
vendored
3
docs/theme/docker/layout.html
vendored
@@ -6,6 +6,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="google-site-verification" content="UxV66EKuPe87dgnH1sbrldrx6VsoWMrx5NjwkgUFxXI" />
|
||||
|
||||
<title>Docker - {{ meta['title'] if meta and meta['title'] else title }}</title>
|
||||
|
||||
@@ -74,7 +75,7 @@
|
||||
</div>
|
||||
|
||||
<div style="margin-left: -12px; float: left;">
|
||||
<a href="{{ pathto('./', 1) }}"><img style="margin-top: 12px; height: 38px" src="{{ pathto('_static/img/docker-letters-logo.gif', 1) }}"></a>
|
||||
<a href="http://www.docker.io"><img style="margin-top: 12px; height: 38px" src="{{ pathto('_static/img/docker-letters-logo.gif', 1) }}"></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
6
docs/theme/docker/static/css/main.css
vendored
6
docs/theme/docker/static/css/main.css
vendored
@@ -82,7 +82,7 @@ h4 {
|
||||
.btn-custom {
|
||||
background-color: #292929 !important;
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#515151", endColorstr="#282828");
|
||||
filter: progid:dximagetransform.microsoft.gradient(startColorstr="#515151", endColorstr="#282828");
|
||||
background-image: -khtml-gradient(linear, left top, left bottom, from(#515151), to(#282828));
|
||||
background-image: -moz-linear-gradient(top, #515151, #282828);
|
||||
background-image: -ms-linear-gradient(top, #515151, #282828);
|
||||
@@ -330,3 +330,7 @@ section.header {
|
||||
@media (max-width: 480px) {
|
||||
|
||||
}
|
||||
/* Misc fixes */
|
||||
table th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
5
docs/theme/docker/static/css/main.less
vendored
5
docs/theme/docker/static/css/main.less
vendored
@@ -449,4 +449,9 @@ section.header {
|
||||
@media (max-width: 480px) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
/* Misc fixes */
|
||||
table th {
|
||||
text-align: left;
|
||||
}
|
||||
BIN
docs/theme/docker/static/img/docker_letters_500px.png
vendored
Normal file
BIN
docs/theme/docker/static/img/docker_letters_500px.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
@@ -13,15 +13,15 @@
|
||||
<meta name="viewport" content="width=device-width">
|
||||
|
||||
<!-- twitter bootstrap -->
|
||||
<link rel="stylesheet" href="../_static/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="../_static/css/bootstrap-responsive.min.css">
|
||||
<link rel="stylesheet" href="../static/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="../static/css/bootstrap-responsive.min.css">
|
||||
|
||||
<!-- main style file -->
|
||||
<link rel="stylesheet" href="../_static/css/main.css">
|
||||
<link rel="stylesheet" href="../static/css/main.css">
|
||||
|
||||
<!-- vendor scripts -->
|
||||
<script src="../_static/js/vendor/jquery-1.9.1.min.js" type="text/javascript" ></script>
|
||||
<script src="../_static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js" type="text/javascript" ></script>
|
||||
<script src="../static/js/vendor/jquery-1.9.1.min.js" type="text/javascript" ></script>
|
||||
<script src="../static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js" type="text/javascript" ></script>
|
||||
|
||||
</head>
|
||||
|
||||
@@ -35,8 +35,8 @@
|
||||
<div style="float: right" class="pull-right">
|
||||
<ul class="nav">
|
||||
<li><a href="../">Introduction</a></li>
|
||||
<li class="active"><a href="./">Getting started</a></li>
|
||||
<li class=""><a href="http://docs.docker.io/en/latest/concepts/containers/">Documentation</a></li>
|
||||
<li class="active"><a href="">Getting started</a></li>
|
||||
<li class=""><a href="http://docs.docker.io/en/latest/concepts/introduction/">Documentation</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="social links" style="float: right; margin-top: 14px; margin-left: 12px">
|
||||
@@ -46,7 +46,7 @@
|
||||
</div>
|
||||
|
||||
<div style="margin-left: -12px; float: left;">
|
||||
<a href="../index.html"><img style="margin-top: 12px; height: 38px" src="../_static/img/docker-letters-logo.gif"></a>
|
||||
<a href="../index.html"><img style="margin-top: 12px; height: 38px" src="../static/img/docker-letters-logo.gif"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,34 +71,40 @@
|
||||
<h2>
|
||||
<a name="installing-on-ubuntu-1204-and-1210" class="anchor" href="#installing-on-ubuntu-1204-and-1210"><span class="mini-icon mini-icon-link"></span>
|
||||
</a>Installing on Ubuntu</h2>
|
||||
|
||||
<p><strong>Requirements</strong></p>
|
||||
<ul>
|
||||
<li>Ubuntu 12.04 (LTS) (64-bit)</li>
|
||||
<li> or Ubuntu 12.10 (quantal) (64-bit)</li>
|
||||
</ul>
|
||||
<ol>
|
||||
<li>
|
||||
<p>Install dependencies:</p>
|
||||
<p><strong>Install dependencies</strong></p>
|
||||
The linux-image-extra package is only needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module.
|
||||
<pre>sudo apt-get install linux-image-extra-`uname -r`</pre>
|
||||
|
||||
<div class="highlight">
|
||||
<pre>sudo apt-get install lxc wget bsdtar curl</pre>
|
||||
<pre>sudo apt-get install linux-image-extra-<span class="sb">`</span>uname -r<span class="sb">`</span></pre></div>
|
||||
|
||||
<p>The <code>linux-image-extra</code> package is needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Install the latest docker binary:</p>
|
||||
<p><strong>Install Docker</strong></p>
|
||||
<p>Add the Ubuntu PPA (Personal Package Archive) sources to your apt sources list, update and install.</p>
|
||||
<p>You may see some warnings that the GPG keys cannot be verified.</p>
|
||||
<div class="highlight">
|
||||
<pre>sudo sh -c "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >> /etc/apt/sources.list"</pre>
|
||||
<pre>sudo apt-get update</pre>
|
||||
<pre>sudo apt-get install lxc-docker</pre>
|
||||
</div>
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p><strong>Run!</strong></p>
|
||||
|
||||
<div class="highlight">
|
||||
<pre>wget http://get.docker.io/builds/<span class="k">$(</span>uname -s<span class="k">)</span>/<span class="k">$(</span>uname -m<span class="k">)</span>/docker-master.tgz</pre>
|
||||
<pre>tar -xf docker-master.tgz</pre>
|
||||
<pre>docker run -i -t ubuntu /bin/bash</pre>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<p>Run your first container!</p>
|
||||
|
||||
<div class="highlight"><pre><span class="nb">cd </span>docker-master</pre>
|
||||
<pre>sudo ./docker run -i -t base /bin/bash</pre>
|
||||
</div>
|
||||
<p>Done!</p>
|
||||
<p>Consider adding docker to your <code>PATH</code> for simplicity.</p>
|
||||
</li>
|
||||
|
||||
Continue with the <a href="http://docs.docker.io/en/latest/examples/hello_world/">Hello world</a> example.
|
||||
</ol>
|
||||
</section>
|
||||
@@ -117,7 +123,7 @@
|
||||
vagrant and an Ubuntu virtual machine.</strong></p>
|
||||
|
||||
<ul>
|
||||
<li><a href="http://docs.docker.io/en/latest/installation/macos/">Mac OS X and other linuxes</a></li>
|
||||
<li><a href="http://docs.docker.io/en/latest/installation/vagrant/">Mac OS X and other linuxes</a></li>
|
||||
<li><a href="http://docs.docker.io/en/latest/installation/windows/">Windows</a></li>
|
||||
</ul>
|
||||
|
||||
@@ -180,7 +186,7 @@
|
||||
|
||||
|
||||
<!-- bootstrap javascipts -->
|
||||
<script src="../_static/js/vendor/bootstrap.min.js" type="text/javascript"></script>
|
||||
<script src="../static/js/vendor/bootstrap.min.js" type="text/javascript"></script>
|
||||
|
||||
<!-- Google analytics -->
|
||||
<script type="text/javascript">
|
||||
@@ -7,21 +7,45 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<title>Docker - the Linux container runtime</title>
|
||||
<meta name="google-site-verification" content="UxV66EKuPe87dgnH1sbrldrx6VsoWMrx5NjwkgUFxXI" />
|
||||
<title>Docker - the Linux container engine</title>
|
||||
|
||||
<meta name="description" content="Docker encapsulates heterogeneous payloads in standard containers">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
|
||||
<!-- twitter bootstrap -->
|
||||
<link rel="stylesheet" href="_static/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="_static/css/bootstrap-responsive.min.css">
|
||||
<link rel="stylesheet" href="static/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="static/css/bootstrap-responsive.min.css">
|
||||
|
||||
<!-- main style file -->
|
||||
<link rel="stylesheet" href="_static/css/main.css">
|
||||
<link rel="stylesheet" href="static/css/main.css">
|
||||
|
||||
<!-- vendor scripts -->
|
||||
<script src="_static/js/vendor/jquery-1.9.1.min.js" type="text/javascript" ></script>
|
||||
<script src="_static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js" type="text/javascript" ></script>
|
||||
<script src="static/js/vendor/jquery-1.9.1.min.js" type="text/javascript" ></script>
|
||||
<script src="static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js" type="text/javascript" ></script>
|
||||
|
||||
<style>
|
||||
.indexlabel {
|
||||
float: left;
|
||||
width: 150px;
|
||||
display: block;
|
||||
padding: 10px 20px 10px;
|
||||
font-size: 20px;
|
||||
font-weight: 200;
|
||||
background-color: #a30000;
|
||||
color: white;
|
||||
height: 22px;
|
||||
}
|
||||
.searchbutton {
|
||||
font-size: 20px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.debug {
|
||||
border: 1px red dotted;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
@@ -34,9 +58,9 @@
|
||||
|
||||
<div class="pull-right" >
|
||||
<ul class="nav">
|
||||
<li class="active"><a href="./">Introduction</a></li>
|
||||
<li ><a href="gettingstarted/">Getting started</a></li>
|
||||
<li class=""><a href="http://docs.docker.io/en/latest/concepts/containers/">Documentation</a></li>
|
||||
<li class="active"><a href="../sources">Introduction</a></li>
|
||||
<li ><a href="gettingstarted">Getting started</a></li>
|
||||
<li class=""><a href="http://docs.docker.io/en/latest/concepts/introduction/">Documentation</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="social links" style="float: right; margin-top: 14px; margin-left: 12px">
|
||||
@@ -49,49 +73,40 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="text-center">
|
||||
<img src="_static/img/docker-letters-logo.gif">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="container" style="margin-top: 30px;">
|
||||
<div class="row">
|
||||
|
||||
<div class="span12">
|
||||
<section class="contentblock header">
|
||||
|
||||
<div class="span6" style="margin:10px 0px 0px 30px; float: right; ">
|
||||
<div class="span5" style="margin-bottom: 15px;">
|
||||
<div style="text-align: center;" >
|
||||
<img src="static/img/docker_letters_500px.png">
|
||||
|
||||
<h2>The Linux container engine</h2>
|
||||
</div>
|
||||
|
||||
<div style="display: block; text-align: center; margin-top: 20px;">
|
||||
|
||||
<h5>
|
||||
Docker is an open-source engine which automates the deployment of applications as highly portable, self-sufficient containers which are independent of hardware, language, framework, packaging system and hosting provider.
|
||||
</h5>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div style="display: block; text-align: center; margin-top: 30px;">
|
||||
<a class="btn btn-custom btn-large" href="gettingstarted/">Let's get started</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="span6" >
|
||||
<div class="js-video" >
|
||||
<iframe width="640" height="360" src="http://www.youtube.com/embed/wW9CAH9nSLs?feature=player_detailpage&rel=0&modestbranding=1&start=11" frameborder="0" allowfullscreen></iframe>
|
||||
<iframe width="600" height="360" src="http://www.youtube.com/embed/wW9CAH9nSLs?feature=player_detailpage&rel=0&modestbranding=1&start=11" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; padding: 50px 30px 50px 30px;">
|
||||
<h1>Docker</h1>
|
||||
<h2>The Linux container runtime</h2>
|
||||
</div>
|
||||
|
||||
<div style="display: block; text-align: center; padding: 10px 30px 50px 30px;">
|
||||
<p>
|
||||
Docker complements LXC with a high-level API which operates at the process level.
|
||||
It runs unix processes with strong guarantees of isolation and repeatability across servers.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Docker is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="display: block; text-align: center;">
|
||||
<a class="btn btn-custom btn-large" href="gettingstarted/">Let's get started</a>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<br style="clear: both"/>
|
||||
</section>
|
||||
</div>
|
||||
@@ -101,31 +116,56 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
|
||||
<div class="span3">
|
||||
<div class="span6">
|
||||
<section class="contentblock">
|
||||
<h4>Heterogeneous payloads</h4>
|
||||
<p>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.</p>
|
||||
</section>
|
||||
</div>
|
||||
<div class="span3">
|
||||
<section class="contentblock">
|
||||
<h4>Any server</h4>
|
||||
<p>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.</p>
|
||||
</section>
|
||||
</div>
|
||||
<div class="span3">
|
||||
<section class="contentblock">
|
||||
<h4>Isolation</h4>
|
||||
<p>docker isolates processes from each other and from the underlying host, using lightweight containers.</p>
|
||||
<p>Docker isolates processes from each other and from the underlying host, using lightweight containers.</p>
|
||||
<h4>Repeatability</h4>
|
||||
<p>Because each container is isolated in its own filesystem, they behave the same regardless of where, when, and alongside what they run.</p>
|
||||
</section>
|
||||
</div>
|
||||
<div class="span3">
|
||||
<div class="span6">
|
||||
<section class="contentblock">
|
||||
<h4>Repeatability</h4>
|
||||
<p>Because containers are isolated in their own filesystem, they behave the same regardless of where, when, and alongside what they run.</p>
|
||||
<h1>New! Docker Index</h1>
|
||||
On the Docker Index you can find and explore pre-made container images. It allows you to share your images and download them.
|
||||
|
||||
<br><br>
|
||||
<a href="https://index.docker.io" target="_blank">
|
||||
<div class="indexlabel">
|
||||
DOCKER index
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<input type="button" class="searchbutton" type="submit" value="Search images"
|
||||
onClick="window.open('https://index.docker.io')" />
|
||||
|
||||
</section>
|
||||
<section class="contentblock">
|
||||
<div id="wufoo-z7x3p3">
|
||||
Fill out my <a href="http://dotclouddocker.wufoo.com/forms/z7x3p3">online form</a>.
|
||||
</div>
|
||||
<script type="text/javascript">var z7x3p3;(function(d, t) {
|
||||
var s = d.createElement(t), options = {
|
||||
'userName':'dotclouddocker',
|
||||
'formHash':'z7x3p3',
|
||||
'autoResize':true,
|
||||
'height':'577',
|
||||
'async':true,
|
||||
'header':'show'};
|
||||
s.src = ('https:' == d.location.protocol ? 'https://' : 'http://') + 'wufoo.com/scripts/embed/form.js';
|
||||
s.onload = s.onreadystatechange = function() {
|
||||
var rs = this.readyState; if (rs) if (rs != 'complete') if (rs != 'loaded') return;
|
||||
try { z7x3p3 = new WufooForm();z7x3p3.initialize(options);z7x3p3.display(); } catch (e) {}};
|
||||
var scr = d.getElementsByTagName(t)[0], par = scr.parentNode; par.insertBefore(s, scr);
|
||||
})(document, 'script');</script>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@@ -173,18 +213,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <p>Docker encapsulates heterogeneous payloads in <a href="#container">Standard Containers</a>, and runs them on any server with strong guarantees of isolation and repeatability.</p>
|
||||
|
||||
<p>It is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc.</p>
|
||||
|
||||
-->
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="span6">
|
||||
|
||||
<section class="contentblock">
|
||||
|
||||
<!-- <img src="_static/lego_docker.jpg" width="600px" style="float:right; margin-left: 10px"> -->
|
||||
<h2>Notable features</h2>
|
||||
|
||||
<ul>
|
||||
@@ -208,35 +242,26 @@
|
||||
<li><a href="http://lxc.sourceforge.net/">lxc</a>, a set of convenience scripts to simplify the creation of linux containers.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Who started it</h2>
|
||||
<p>
|
||||
Docker is an open-source implementation of the deployment engine which powers <a href="http://dotcloud.com">dotCloud</a>, a popular Platform-as-a-Service.</p>
|
||||
|
||||
<p>It benefits directly from the experience accumulated over several years of large-scale operation and support of hundreds of thousands
|
||||
of applications and databases.
|
||||
</p>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="span6">
|
||||
<section class="contentblock">
|
||||
<div id="wufoo-z7x3p3">
|
||||
Fill out my <a href="http://dotclouddocker.wufoo.com/forms/z7x3p3">online form</a>.
|
||||
</div>
|
||||
<script type="text/javascript">var z7x3p3;(function(d, t) {
|
||||
var s = d.createElement(t), options = {
|
||||
'userName':'dotclouddocker',
|
||||
'formHash':'z7x3p3',
|
||||
'autoResize':true,
|
||||
'height':'577',
|
||||
'async':true,
|
||||
'header':'show'};
|
||||
s.src = ('https:' == d.location.protocol ? 'https://' : 'http://') + 'wufoo.com/scripts/embed/form.js';
|
||||
s.onload = s.onreadystatechange = function() {
|
||||
var rs = this.readyState; if (rs) if (rs != 'complete') if (rs != 'loaded') return;
|
||||
try { z7x3p3 = new WufooForm();z7x3p3.initialize(options);z7x3p3.display(); } catch (e) {}};
|
||||
var scr = d.getElementsByTagName(t)[0], par = scr.parentNode; par.insertBefore(s, scr);
|
||||
})(document, 'script');</script>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="contentblock">
|
||||
<h3 id="twitter">Twitter</h3>
|
||||
<a class="twitter-timeline" href="https://twitter.com/getdocker" data-widget-id="312730839718957056">Tweets by @getdocker</a>
|
||||
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -265,7 +290,7 @@
|
||||
|
||||
|
||||
<!-- bootstrap javascipts -->
|
||||
<script src="_static/js/vendor/bootstrap.min.js" type="text/javascript"></script>
|
||||
<script src="static/js/vendor/bootstrap.min.js" type="text/javascript"></script>
|
||||
|
||||
<!-- Google analytics -->
|
||||
<script type="text/javascript">
|
||||
6
docs/website/nginx.conf
Normal file
6
docs/website/nginx.conf
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
# rule to redirect original links created when hosted on github pages
|
||||
rewrite ^/documentation/(.*).html http://docs.docker.io/en/latest/$1/ permanent;
|
||||
|
||||
# rewrite the stuff which was on the current page
|
||||
rewrite ^/gettingstarted.html$ /gettingstarted/ permanent;
|
||||
1
docs/website/static
Symbolic link
1
docs/website/static
Symbolic link
@@ -0,0 +1 @@
|
||||
../theme/docker/static
|
||||
9
getKernelVersion_darwin.go
Normal file
9
getKernelVersion_darwin.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func getKernelVersion() (*KernelVersionInfo, error) {
|
||||
return nil, fmt.Errorf("Kernel version detection is not available on darwin")
|
||||
}
|
||||
69
getKernelVersion_linux.go
Normal file
69
getKernelVersion_linux.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func getKernelVersion() (*KernelVersionInfo, error) {
|
||||
var (
|
||||
uts syscall.Utsname
|
||||
flavor string
|
||||
kernel, major, minor int
|
||||
err error
|
||||
)
|
||||
|
||||
if err := syscall.Uname(&uts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
release := make([]byte, len(uts.Release))
|
||||
|
||||
i := 0
|
||||
for _, c := range uts.Release {
|
||||
release[i] = byte(c)
|
||||
i++
|
||||
}
|
||||
|
||||
// Remove the \x00 from the release for Atoi to parse correctly
|
||||
release = release[:bytes.IndexByte(release, 0)]
|
||||
|
||||
tmp := strings.SplitN(string(release), "-", 2)
|
||||
tmp2 := strings.SplitN(tmp[0], ".", 3)
|
||||
|
||||
if len(tmp2) > 0 {
|
||||
kernel, err = strconv.Atoi(tmp2[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(tmp2) > 1 {
|
||||
major, err = strconv.Atoi(tmp2[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(tmp2) > 2 {
|
||||
minor, err = strconv.Atoi(tmp2[2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(tmp) == 2 {
|
||||
flavor = tmp[1]
|
||||
} else {
|
||||
flavor = ""
|
||||
}
|
||||
|
||||
return &KernelVersionInfo{
|
||||
Kernel: kernel,
|
||||
Major: major,
|
||||
Minor: minor,
|
||||
Flavor: flavor,
|
||||
}, nil
|
||||
}
|
||||
94
graph.go
94
graph.go
@@ -1,19 +1,27 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Graph is a store for versioned filesystem images and the relationship between them.
|
||||
type Graph struct {
|
||||
Root string
|
||||
idIndex *TruncIndex
|
||||
Root string
|
||||
idIndex *TruncIndex
|
||||
httpClient *http.Client
|
||||
checksumLock map[string]*sync.Mutex
|
||||
lockSumFile *sync.Mutex
|
||||
lockSumMap *sync.Mutex
|
||||
}
|
||||
|
||||
// NewGraph instantiates a new graph at the given root path in the filesystem.
|
||||
@@ -24,12 +32,15 @@ func NewGraph(root string) (*Graph, error) {
|
||||
return nil, err
|
||||
}
|
||||
// Create the root directory if it doesn't exists
|
||||
if err := os.Mkdir(root, 0700); err != nil && !os.IsExist(err) {
|
||||
if err := os.MkdirAll(root, 0700); err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
graph := &Graph{
|
||||
Root: abspath,
|
||||
idIndex: NewTruncIndex(),
|
||||
Root: abspath,
|
||||
idIndex: NewTruncIndex(),
|
||||
checksumLock: make(map[string]*sync.Mutex),
|
||||
lockSumFile: &sync.Mutex{},
|
||||
lockSumMap: &sync.Mutex{},
|
||||
}
|
||||
if err := graph.restore(); err != nil {
|
||||
return nil, err
|
||||
@@ -79,32 +90,39 @@ func (graph *Graph) Get(name string) (*Image, error) {
|
||||
return nil, fmt.Errorf("Image stored at '%s' has wrong id '%s'", id, img.Id)
|
||||
}
|
||||
img.graph = graph
|
||||
graph.lockSumMap.Lock()
|
||||
defer graph.lockSumMap.Unlock()
|
||||
if _, exists := graph.checksumLock[img.Id]; !exists {
|
||||
graph.checksumLock[img.Id] = &sync.Mutex{}
|
||||
}
|
||||
return img, nil
|
||||
}
|
||||
|
||||
// Create creates a new image and registers it in the graph.
|
||||
func (graph *Graph) Create(layerData Archive, container *Container, comment, author string) (*Image, error) {
|
||||
func (graph *Graph) Create(layerData Archive, container *Container, comment, author string, config *Config) (*Image, error) {
|
||||
img := &Image{
|
||||
Id: GenerateId(),
|
||||
Comment: comment,
|
||||
Created: time.Now(),
|
||||
DockerVersion: VERSION,
|
||||
Author: author,
|
||||
Config: config,
|
||||
}
|
||||
if container != nil {
|
||||
img.Parent = container.Image
|
||||
img.Container = container.Id
|
||||
img.ContainerConfig = *container.Config
|
||||
}
|
||||
if err := graph.Register(layerData, img); err != nil {
|
||||
if err := graph.Register(layerData, true, img); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go img.Checksum()
|
||||
return img, nil
|
||||
}
|
||||
|
||||
// Register imports a pre-existing image into the graph.
|
||||
// FIXME: pass img as first argument
|
||||
func (graph *Graph) Register(layerData Archive, img *Image) error {
|
||||
func (graph *Graph) Register(layerData Archive, store bool, img *Image) error {
|
||||
if err := ValidateId(img.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -117,7 +135,7 @@ func (graph *Graph) Register(layerData Archive, img *Image) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("Mktemp failed: %s", err)
|
||||
}
|
||||
if err := StoreImage(img, layerData, tmp); err != nil {
|
||||
if err := StoreImage(img, layerData, tmp, store); err != nil {
|
||||
return err
|
||||
}
|
||||
// Commit
|
||||
@@ -126,15 +144,36 @@ func (graph *Graph) Register(layerData Archive, img *Image) error {
|
||||
}
|
||||
img.graph = graph
|
||||
graph.idIndex.Add(img.Id)
|
||||
graph.checksumLock[img.Id] = &sync.Mutex{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TempLayerArchive creates a temporary archive of the given image's filesystem layer.
|
||||
// The archive is stored on disk and will be automatically deleted as soon as has been read.
|
||||
// If output is not nil, a human-readable progress bar will be written to it.
|
||||
// FIXME: does this belong in Graph? How about MktempFile, let the caller use it for archives?
|
||||
func (graph *Graph) TempLayerArchive(id string, compression Compression, output io.Writer) (*TempArchive, error) {
|
||||
image, err := graph.Get(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tmp, err := graph.tmp()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
archive, err := image.TarLayer(compression)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewTempArchive(ProgressReader(ioutil.NopCloser(archive), 0, output, "Buffering to disk %v/%v (%v)"), tmp.Root)
|
||||
}
|
||||
|
||||
// Mktemp creates a temporary sub-directory inside the graph's filesystem.
|
||||
func (graph *Graph) Mktemp(id string) (string, error) {
|
||||
if id == "" {
|
||||
id = GenerateId()
|
||||
}
|
||||
tmp, err := NewGraph(path.Join(graph.Root, ":tmp:"))
|
||||
tmp, err := graph.tmp()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Couldn't create temp: %s", err)
|
||||
}
|
||||
@@ -144,6 +183,10 @@ func (graph *Graph) Mktemp(id string) (string, error) {
|
||||
return tmp.imageRoot(id), nil
|
||||
}
|
||||
|
||||
func (graph *Graph) tmp() (*Graph, error) {
|
||||
return NewGraph(path.Join(graph.Root, ":tmp:"))
|
||||
}
|
||||
|
||||
// Check if given error is "not empty".
|
||||
// Note: this is the way golang does it internally with os.IsNotExists.
|
||||
func isNotEmpty(err error) bool {
|
||||
@@ -224,14 +267,14 @@ func (graph *Graph) WalkAll(handler func(*Image)) error {
|
||||
func (graph *Graph) ByParent() (map[string][]*Image, error) {
|
||||
byParent := make(map[string][]*Image)
|
||||
err := graph.WalkAll(func(image *Image) {
|
||||
image, err := graph.Get(image.Parent)
|
||||
parent, err := graph.Get(image.Parent)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if children, exists := byParent[image.Parent]; exists {
|
||||
byParent[image.Parent] = []*Image{image}
|
||||
if children, exists := byParent[parent.Id]; exists {
|
||||
byParent[parent.Id] = []*Image{image}
|
||||
} else {
|
||||
byParent[image.Parent] = append(children, image)
|
||||
byParent[parent.Id] = append(children, image)
|
||||
}
|
||||
})
|
||||
return byParent, err
|
||||
@@ -258,3 +301,26 @@ func (graph *Graph) Heads() (map[string]*Image, error) {
|
||||
func (graph *Graph) imageRoot(id string) string {
|
||||
return path.Join(graph.Root, id)
|
||||
}
|
||||
|
||||
func (graph *Graph) getStoredChecksums() (map[string]string, error) {
|
||||
checksums := make(map[string]string)
|
||||
// FIXME: Store the checksum in memory
|
||||
|
||||
if checksumDict, err := ioutil.ReadFile(path.Join(graph.Root, "checksums")); err == nil {
|
||||
if err := json.Unmarshal(checksumDict, &checksums); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return checksums, nil
|
||||
}
|
||||
|
||||
func (graph *Graph) storeChecksums(checksums map[string]string) error {
|
||||
checksumJson, err := json.Marshal(checksums)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(graph.Root, "checksums"), checksumJson, 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ func TestGraphCreate(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image, err := graph.Create(archive, nil, "Testing", "")
|
||||
image, err := graph.Create(archive, nil, "Testing", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -122,7 +122,7 @@ func TestMount(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image, err := graph.Create(archive, nil, "Testing", "")
|
||||
image, err := graph.Create(archive, nil, "Testing", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -166,7 +166,7 @@ func createTestImage(graph *Graph, t *testing.T) *Image {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
img, err := graph.Create(archive, nil, "Test image", "")
|
||||
img, err := graph.Create(archive, nil, "Test image", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -181,7 +181,7 @@ func TestDelete(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 0)
|
||||
img, err := graph.Create(archive, nil, "Bla bla", "")
|
||||
img, err := graph.Create(archive, nil, "Bla bla", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -192,11 +192,11 @@ func TestDelete(t *testing.T) {
|
||||
assertNImages(graph, t, 0)
|
||||
|
||||
// Test 2 create (same name) / 1 delete
|
||||
img1, err := graph.Create(archive, nil, "Testing", "")
|
||||
img1, err := graph.Create(archive, nil, "Testing", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = graph.Create(archive, nil, "Testing", ""); err != nil {
|
||||
if _, err = graph.Create(archive, nil, "Testing", "", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 2)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
This directory contains material helpful for hacking on docker.
|
||||
27
hack/README.rst
Normal file
27
hack/README.rst
Normal file
@@ -0,0 +1,27 @@
|
||||
This directory contains material helpful for hacking on docker.
|
||||
|
||||
make hack
|
||||
=========
|
||||
|
||||
Set up an Ubuntu 13.04 virtual machine for developers including kernel 3.8
|
||||
and buildbot. The environment is setup in a way that can be used through
|
||||
the usual go workflow and/or the root Makefile. You can either edit on
|
||||
your host, or inside the VM (using make ssh-dev) and run and test docker
|
||||
inside the VM.
|
||||
|
||||
dependencies: vagrant, virtualbox packages and python package requests
|
||||
|
||||
|
||||
Buildbot
|
||||
~~~~~~~~
|
||||
|
||||
Buildbot is a continuous integration system designed to automate the
|
||||
build/test cycle. By automatically rebuilding and testing the tree each time
|
||||
something has changed, build problems are pinpointed quickly, before other
|
||||
developers are inconvenienced by the failure.
|
||||
|
||||
When running 'make hack' at the docker root directory, it spawns a virtual
|
||||
machine in the background running a buildbot instance and adds a git
|
||||
post-commit hook that automatically run docker tests for you.
|
||||
|
||||
You can check your buildbot instance at http://192.168.33.21:8010/waterfall
|
||||
35
hack/Vagrantfile
vendored
Normal file
35
hack/Vagrantfile
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
BOX_NAME = "ubuntu-dev"
|
||||
BOX_URI = "http://cloud-images.ubuntu.com/raring/current/raring-server-cloudimg-vagrant-amd64-disk1.box"
|
||||
VM_IP = "192.168.33.21"
|
||||
USER = "vagrant"
|
||||
GOPATH = "/data/docker"
|
||||
DOCKER_PATH = "#{GOPATH}/src/github.com/dotcloud/docker"
|
||||
CFG_PATH = "#{DOCKER_PATH}/hack/environment"
|
||||
BUILDBOT_PATH = "/data/buildbot"
|
||||
|
||||
Vagrant::Config.run do |config|
|
||||
# Setup virtual machine box
|
||||
config.vm.box = BOX_NAME
|
||||
config.vm.box_url = BOX_URI
|
||||
config.vm.share_folder "v-data", DOCKER_PATH, "#{File.dirname(__FILE__)}/.."
|
||||
config.vm.network :hostonly, VM_IP
|
||||
# Stop if deployment has been done
|
||||
config.vm.provision :shell, :inline => "[ ! -f /usr/bin/git ]"
|
||||
# Touch for makefile
|
||||
pkg_cmd = "touch #{DOCKER_PATH}; "
|
||||
# Install docker dependencies
|
||||
pkg_cmd << "export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; " \
|
||||
"apt-get install -q -y lxc bsdtar git golang make linux-image-extra-3.8.0-19-generic; " \
|
||||
"chown -R #{USER}.#{USER} #{GOPATH}; " \
|
||||
"install -m 0664 #{CFG_PATH}/bash_profile /home/#{USER}/.bash_profile"
|
||||
config.vm.provision :shell, :inline => pkg_cmd
|
||||
# Deploy buildbot CI
|
||||
pkg_cmd = "apt-get install -q -y python-dev python-pip supervisor; " \
|
||||
"pip install -r #{CFG_PATH}/requirements.txt; " \
|
||||
"chown #{USER}.#{USER} /data; cd /data; " \
|
||||
"#{CFG_PATH}/setup.sh #{USER} #{GOPATH} #{DOCKER_PATH} #{CFG_PATH} #{BUILDBOT_PATH}"
|
||||
config.vm.provision :shell, :inline => pkg_cmd
|
||||
end
|
||||
23
hack/dockerbuilder/Dockerfile
Normal file
23
hack/dockerbuilder/Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
||||
# This will build a container capable of producing an official binary build of docker and
|
||||
# uploading it to S3
|
||||
maintainer Solomon Hykes <solomon@dotcloud.com>
|
||||
from ubuntu:12.10
|
||||
run apt-get update
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
|
||||
# Packages required to checkout and build docker
|
||||
run curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.0.3.linux-amd64.tar.gz
|
||||
run tar -C /usr/local -xzf /go.tar.gz
|
||||
run echo "export PATH=$PATH:/usr/local/go/bin" > /.bashrc
|
||||
run echo "export PATH=$PATH:/usr/local/go/bin" > /.bash_profile
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q build-essential
|
||||
# Packages required to build an ubuntu package
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q debhelper
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q autotools-dev
|
||||
copy fake_initctl /usr/local/bin/initctl
|
||||
run apt-get install -y -q devscripts
|
||||
add . /src
|
||||
run cp /src/dockerbuilder /usr/local/bin/ && chmod +x /usr/local/bin/dockerbuilder
|
||||
run cp /src/s3cfg /.s3cfg
|
||||
cmd ["dockerbuilder"]
|
||||
40
hack/dockerbuilder/dockerbuilder
Normal file
40
hack/dockerbuilder/dockerbuilder
Normal file
@@ -0,0 +1,40 @@
|
||||
#!/bin/sh
|
||||
set -x
|
||||
set -e
|
||||
|
||||
export PATH=$PATH:/usr/local/go/bin
|
||||
|
||||
PACKAGE=github.com/dotcloud/docker
|
||||
|
||||
if [ $# -gt 1 ]; then
|
||||
echo "Usage: $0 [REVISION]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export REVISION=$1
|
||||
|
||||
if [ -z "$AWS_ID" ]; then
|
||||
echo "Warning: environment variable AWS_ID is not set. Won't upload to S3."
|
||||
fi
|
||||
|
||||
if [ -z "$AWS_KEY" ]; then
|
||||
echo "Warning: environment variable AWS_KEY is not set. Won't upload to S3."
|
||||
fi
|
||||
|
||||
if [ -z "$GPG_KEY" ]; then
|
||||
echo "Warning: environment variable GPG_KEY is not set. Ubuntu package upload will not succeed."
|
||||
NO_UBUNTU=1
|
||||
fi
|
||||
|
||||
rm -fr docker-release
|
||||
git clone https://github.com/dotcloud/docker docker-release
|
||||
cd docker-release
|
||||
if [ -z "$REVISION" ]; then
|
||||
make release
|
||||
else
|
||||
make release RELEASE_VERSION=$REVISION
|
||||
fi
|
||||
|
||||
if [ -z "$NO_UBUNTU" ]; then
|
||||
(cd packaging/ubuntu && make ubuntu)
|
||||
fi
|
||||
3
hack/dockerbuilder/fake_initctl
Executable file
3
hack/dockerbuilder/fake_initctl
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo Whatever you say, man
|
||||
3
hack/dockerbuilder/s3cfg
Normal file
3
hack/dockerbuilder/s3cfg
Normal file
@@ -0,0 +1,3 @@
|
||||
[default]
|
||||
access_key = $AWS_ID
|
||||
secret_key = $AWS_KEY
|
||||
1
hack/environment/README.rst
Normal file
1
hack/environment/README.rst
Normal file
@@ -0,0 +1 @@
|
||||
Files used to setup the developer virtual machine
|
||||
19
hack/environment/bash_profile
Normal file
19
hack/environment/bash_profile
Normal file
@@ -0,0 +1,19 @@
|
||||
# ~/.bash_profile : executed by the command interpreter for login shells.
|
||||
|
||||
# if running bash
|
||||
if [ -n "$BASH_VERSION" ]; then
|
||||
# include .bashrc if it exists
|
||||
if [ -f "$HOME/.bashrc" ]; then
|
||||
. "$HOME/.bashrc"
|
||||
fi
|
||||
fi
|
||||
|
||||
# set PATH so it includes user's private bin if it exists
|
||||
[ -d "$HOME/bin" ] && PATH="$HOME/bin:$PATH"
|
||||
|
||||
docker=/data/docker/src/github.com/dotcloud/docker
|
||||
[ -d $docker ] && cd $docker
|
||||
|
||||
export GOPATH=/data/docker
|
||||
export PATH=$PATH:$GOPATH/bin
|
||||
|
||||
18
hack/environment/buildbot.conf
Normal file
18
hack/environment/buildbot.conf
Normal file
@@ -0,0 +1,18 @@
|
||||
[program:buildmaster]
|
||||
command=su vagrant -c "buildbot start master"
|
||||
directory=/data/buildbot
|
||||
chown= root:root
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/log/supervisor/buildbot-master.log
|
||||
stderr_logfile=/var/log/supervisor/buildbot-master.log
|
||||
|
||||
[program:buildworker]
|
||||
command=buildslave start slave
|
||||
directory=/data/buildbot
|
||||
chown= root:root
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/log/supervisor/buildbot-slave.log
|
||||
stderr_logfile=/var/log/supervisor/buildbot-slave.log
|
||||
|
||||
[group:buildbot]
|
||||
programs=buildmaster,buildworker
|
||||
43
hack/environment/master.cfg
Normal file
43
hack/environment/master.cfg
Normal file
@@ -0,0 +1,43 @@
|
||||
import os
|
||||
from buildbot.buildslave import BuildSlave
|
||||
from buildbot.schedulers.forcesched import ForceScheduler
|
||||
from buildbot.config import BuilderConfig
|
||||
from buildbot.process.factory import BuildFactory
|
||||
from buildbot.steps.shell import ShellCommand
|
||||
from buildbot.status import html
|
||||
from buildbot.status.web import authz, auth
|
||||
|
||||
PORT_WEB = 8010 # Buildbot webserver port
|
||||
PORT_MASTER = 9989 # Port where buildbot master listen buildworkers
|
||||
TEST_USER = 'buildbot' # Credential to authenticate build triggers
|
||||
TEST_PWD = 'docker' # Credential to authenticate build triggers
|
||||
BUILDER_NAME = 'docker'
|
||||
BUILDPASSWORD = 'pass-docker' # Credential to authenticate buildworkers
|
||||
GOPATH = '/data/docker'
|
||||
DOCKER_PATH = '{0}/src/github.com/dotcloud/docker'.format(GOPATH)
|
||||
|
||||
c = BuildmasterConfig = {}
|
||||
|
||||
c['title'] = "Docker"
|
||||
c['titleURL'] = "waterfall"
|
||||
c['buildbotURL'] = "http://localhost:{0}/".format(PORT_WEB)
|
||||
c['db'] = {'db_url':"sqlite:///state.sqlite"}
|
||||
c['slaves'] = [BuildSlave('buildworker', BUILDPASSWORD)]
|
||||
c['slavePortnum'] = PORT_MASTER
|
||||
|
||||
c['schedulers'] = [ForceScheduler(name='trigger',builderNames=[BUILDER_NAME])]
|
||||
|
||||
# Docker test command
|
||||
test_cmd = "GOPATH={0} make -C {1} test".format(GOPATH,DOCKER_PATH)
|
||||
|
||||
# Builder
|
||||
factory = BuildFactory()
|
||||
factory.addStep(ShellCommand(description='Docker',logEnviron=False,
|
||||
usePTY=True,command=test_cmd))
|
||||
c['builders'] = [BuilderConfig(name=BUILDER_NAME,slavenames=['buildworker'],
|
||||
factory=factory)]
|
||||
|
||||
# Status
|
||||
authz_cfg=authz.Authz(auth=auth.BasicAuth([(TEST_USER,TEST_PWD)]),
|
||||
forceBuild='auth')
|
||||
c['status'] = [html.WebStatus(http_port=PORT_WEB, authz=authz_cfg)]
|
||||
21
hack/environment/post-commit
Executable file
21
hack/environment/post-commit
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''Trigger buildbot docker test build
|
||||
|
||||
post-commit git hook designed to automatically trigger buildbot on
|
||||
the provided vagrant docker VM.'''
|
||||
|
||||
import requests
|
||||
|
||||
USERNAME = 'buildbot'
|
||||
PASSWORD = 'docker'
|
||||
BASE_URL = 'http://localhost:8010'
|
||||
path = lambda s: BASE_URL + '/' + s
|
||||
|
||||
try:
|
||||
session = requests.session()
|
||||
session.post(path('login'),data={'username':USERNAME,'passwd':PASSWORD})
|
||||
session.post(path('builders/docker/force'),
|
||||
data={'forcescheduler':'trigger','reason':'Test commit'})
|
||||
except:
|
||||
pass
|
||||
6
hack/environment/requirements.txt
Normal file
6
hack/environment/requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
sqlalchemy<=0.7.9
|
||||
sqlalchemy-migrate>=0.7.2
|
||||
buildbot==0.8.7p1
|
||||
buildbot_slave==0.8.7p1
|
||||
nose==1.2.1
|
||||
requests==1.1.0
|
||||
45
hack/environment/setup.sh
Executable file
45
hack/environment/setup.sh
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Setup of buildbot configuration. Package installation is being done by
|
||||
# Vagrantfile
|
||||
# Dependencies: buildbot, buildbot-slave, supervisor
|
||||
|
||||
USER=$1
|
||||
GOPATH=$2
|
||||
DOCKER_PATH=$3
|
||||
CFG_PATH=$4
|
||||
BUILDBOT_PATH=$5
|
||||
SLAVE_NAME="buildworker"
|
||||
SLAVE_SOCKET="localhost:9989"
|
||||
BUILDBOT_PWD="pass-docker"
|
||||
IP=$(sed -nE 's/VM_IP = "(.+)"/\1/p' ${DOCKER_PATH}/hack/Vagrantfile)
|
||||
export PATH="/bin:sbin:/usr/bin:/usr/sbin:/usr/local/bin"
|
||||
|
||||
function run { su $USER -c "$1"; }
|
||||
|
||||
# Exit if buildbot has already been installed
|
||||
[ -d "$BUILDBOT_PATH" ] && exit 0
|
||||
|
||||
# Setup buildbot
|
||||
run "mkdir -p $BUILDBOT_PATH"
|
||||
cd $BUILDBOT_PATH
|
||||
run "buildbot create-master master"
|
||||
run "cp $CFG_PATH/master.cfg master"
|
||||
run "sed -i 's/localhost/$IP/' master/master.cfg"
|
||||
run "sed -i -E 's#(GOPATH = ).+#\1\"$GOPATH\"#' master/master.cfg"
|
||||
run "sed -i -E 's#(DOCKER_PATH = ).+#\1\"$DOCKER_PATH\"#' master/master.cfg"
|
||||
run "buildslave create-slave slave $SLAVE_SOCKET $SLAVE_NAME $BUILDBOT_PWD"
|
||||
|
||||
# Allow buildbot subprocesses (docker tests) to properly run in containers,
|
||||
# in particular with docker -u
|
||||
run "sed -i 's/^umask = None/umask = 000/' slave/buildbot.tac"
|
||||
|
||||
# Setup supervisor
|
||||
cp $CFG_PATH/buildbot.conf /etc/supervisor/conf.d/buildbot.conf
|
||||
sed -i -E "s/^chmod=0700.+/chmod=0770\nchown=root:$USER/" /etc/supervisor/supervisord.conf
|
||||
kill -HUP $(pgrep -f "/usr/bin/python /usr/bin/supervisord")
|
||||
|
||||
# Add git hook
|
||||
cp $CFG_PATH/post-commit $DOCKER_PATH/.git/hooks
|
||||
sed -i "s/localhost/$IP/" $DOCKER_PATH/.git/hooks/post-commit
|
||||
|
||||
147
image.go
147
image.go
@@ -2,6 +2,7 @@ package docker
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -24,6 +25,7 @@ type Image struct {
|
||||
ContainerConfig Config `json:"container_config,omitempty"`
|
||||
DockerVersion string `json:"docker_version,omitempty"`
|
||||
Author string `json:"author,omitempty"`
|
||||
Config *Config `json:"config,omitempty"`
|
||||
graph *Graph
|
||||
}
|
||||
|
||||
@@ -33,8 +35,9 @@ func LoadImage(root string) (*Image, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var img Image
|
||||
if err := json.Unmarshal(jsonData, &img); err != nil {
|
||||
img := &Image{}
|
||||
|
||||
if err := json.Unmarshal(jsonData, img); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := ValidateId(img.Id); err != nil {
|
||||
@@ -50,10 +53,10 @@ func LoadImage(root string) (*Image, error) {
|
||||
} else if !stat.IsDir() {
|
||||
return nil, fmt.Errorf("Couldn't load image %s: %s is not a directory", img.Id, layerPath(root))
|
||||
}
|
||||
return &img, nil
|
||||
return img, nil
|
||||
}
|
||||
|
||||
func StoreImage(img *Image, layerData Archive, root string) error {
|
||||
func StoreImage(img *Image, layerData Archive, root string, store bool) error {
|
||||
// Check that root doesn't already exist
|
||||
if _, err := os.Stat(root); err == nil {
|
||||
return fmt.Errorf("Image %s already exists", img.Id)
|
||||
@@ -65,6 +68,28 @@ func StoreImage(img *Image, layerData Archive, root string) error {
|
||||
if err := os.MkdirAll(layer, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if store {
|
||||
layerArchive := layerArchivePath(root)
|
||||
file, err := os.OpenFile(layerArchive, os.O_WRONLY|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// FIXME: Retrieve the image layer size from here?
|
||||
if _, err := io.Copy(file, layerData); err != nil {
|
||||
return err
|
||||
}
|
||||
// FIXME: Don't close/open, read/write instead of Copy
|
||||
file.Close()
|
||||
|
||||
file, err = os.Open(layerArchive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
layerData = file
|
||||
}
|
||||
|
||||
if err := Untar(layerData, layer); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -83,6 +108,10 @@ func layerPath(root string) string {
|
||||
return path.Join(root, "layer")
|
||||
}
|
||||
|
||||
func layerArchivePath(root string) string {
|
||||
return path.Join(root, "layer.tar.xz")
|
||||
}
|
||||
|
||||
func jsonPath(root string) string {
|
||||
return path.Join(root, "json")
|
||||
}
|
||||
@@ -92,7 +121,7 @@ func MountAUFS(ro []string, rw string, target string) error {
|
||||
rwBranch := fmt.Sprintf("%v=rw", rw)
|
||||
roBranches := ""
|
||||
for _, layer := range ro {
|
||||
roBranches += fmt.Sprintf("%v=ro:", layer)
|
||||
roBranches += fmt.Sprintf("%v=ro+wh:", layer)
|
||||
}
|
||||
branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches)
|
||||
|
||||
@@ -110,6 +139,15 @@ func MountAUFS(ro []string, rw string, target string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TarLayer returns a tar archive of the image's filesystem layer.
|
||||
func (image *Image) TarLayer(compression Compression) (Archive, error) {
|
||||
layerPath, err := image.layer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return Tar(layerPath, compression)
|
||||
}
|
||||
|
||||
func (image *Image) Mount(root, rw string) error {
|
||||
if mounted, err := Mounted(root); err != nil {
|
||||
return err
|
||||
@@ -127,34 +165,9 @@ func (image *Image) Mount(root, rw string) error {
|
||||
if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
// FIXME: @creack shouldn't we do this after going over changes?
|
||||
if err := MountAUFS(layers, rw, root); err != nil {
|
||||
return err
|
||||
}
|
||||
// FIXME: Create tests for deletion
|
||||
// FIXME: move this part to change.go
|
||||
// Retrieve the changeset from the parent and apply it to the container
|
||||
// - Retrieve the changes
|
||||
changes, err := Changes(layers, layers[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Iterate on changes
|
||||
for _, c := range changes {
|
||||
// If there is a delete
|
||||
if c.Kind == ChangeDelete {
|
||||
// Make sure the directory exists
|
||||
file_path, file_name := path.Dir(c.Path), path.Base(c.Path)
|
||||
if err := os.MkdirAll(path.Join(rw, file_path), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
// And create the whiteout (we just need to create empty file, discard the return)
|
||||
if _, err := os.Create(path.Join(path.Join(rw, file_path),
|
||||
".wh."+path.Base(file_name))); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -272,3 +285,77 @@ func (img *Image) layer() (string, error) {
|
||||
}
|
||||
return layerPath(root), nil
|
||||
}
|
||||
|
||||
func (img *Image) Checksum() (string, error) {
|
||||
img.graph.checksumLock[img.Id].Lock()
|
||||
defer img.graph.checksumLock[img.Id].Unlock()
|
||||
|
||||
root, err := img.root()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
checksums, err := img.graph.getStoredChecksums()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if checksum, ok := checksums[img.Id]; ok {
|
||||
return checksum, nil
|
||||
}
|
||||
|
||||
layer, err := img.layer()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
jsonData, err := ioutil.ReadFile(jsonPath(root))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var layerData io.Reader
|
||||
|
||||
if file, err := os.Open(layerArchivePath(root)); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
layerData, err = Tar(layer, Xz)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
defer file.Close()
|
||||
layerData = file
|
||||
}
|
||||
|
||||
h := sha256.New()
|
||||
if _, err := h.Write(jsonData); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := h.Write([]byte("\n")); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(h, layerData); err != nil {
|
||||
return "", err
|
||||
}
|
||||
hash := "sha256:" + hex.EncodeToString(h.Sum(nil))
|
||||
|
||||
// Reload the json file to make sure not to overwrite faster sums
|
||||
img.graph.lockSumFile.Lock()
|
||||
defer img.graph.lockSumFile.Unlock()
|
||||
|
||||
checksums, err = img.graph.getStoredChecksums()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
checksums[img.Id] = hash
|
||||
|
||||
// Dump the checksums to disc
|
||||
if err := img.graph.storeChecksums(checksums); err != nil {
|
||||
return hash, err
|
||||
}
|
||||
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
@@ -79,7 +79,11 @@ lxc.mount.entry = {{.SysInitPath}} {{$ROOTFS}}/sbin/init none bind,ro 0 0
|
||||
|
||||
# In order to get a working DNS environment, mount bind (ro) the host's /etc/resolv.conf into the container
|
||||
lxc.mount.entry = {{.ResolvConfPath}} {{$ROOTFS}}/etc/resolv.conf none bind,ro 0 0
|
||||
|
||||
{{if .Volumes}}
|
||||
{{range $virtualPath, $realPath := .GetVolumes}}
|
||||
lxc.mount.entry = {{$realPath}} {{$ROOTFS}}/{{$virtualPath}} none bind,rw 0 0
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
# drop linux capabilities (apply mainly to the user root in the container)
|
||||
lxc.cap.drop = audit_control audit_write mac_admin mac_override mknod setfcap setpcap sys_admin sys_boot sys_module sys_nice sys_pacct sys_rawio sys_resource sys_time sys_tty_config
|
||||
|
||||
57
network.go
57
network.go
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os/exec"
|
||||
@@ -183,18 +184,21 @@ func getIfaceAddr(name string) (net.Addr, error) {
|
||||
// It keeps track of all mappings and is able to unmap at will
|
||||
type PortMapper struct {
|
||||
mapping map[int]net.TCPAddr
|
||||
proxies map[int]net.Listener
|
||||
}
|
||||
|
||||
func (mapper *PortMapper) cleanup() error {
|
||||
// Ignore errors - This could mean the chains were never set up
|
||||
iptables("-t", "nat", "-D", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER")
|
||||
iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER")
|
||||
iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", "DOCKER")
|
||||
iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER") // Created in versions <= 0.1.6
|
||||
// Also cleanup rules created by older versions, or -X might fail.
|
||||
iptables("-t", "nat", "-D", "PREROUTING", "-j", "DOCKER")
|
||||
iptables("-t", "nat", "-D", "OUTPUT", "-j", "DOCKER")
|
||||
iptables("-t", "nat", "-F", "DOCKER")
|
||||
iptables("-t", "nat", "-X", "DOCKER")
|
||||
mapper.mapping = make(map[int]net.TCPAddr)
|
||||
mapper.proxies = make(map[int]net.Listener)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -205,7 +209,7 @@ func (mapper *PortMapper) setup() error {
|
||||
if err := iptables("-t", "nat", "-A", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER"); err != nil {
|
||||
return fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
|
||||
}
|
||||
if err := iptables("-t", "nat", "-A", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER"); err != nil {
|
||||
if err := iptables("-t", "nat", "-A", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", "DOCKER"); err != nil {
|
||||
return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
|
||||
}
|
||||
return nil
|
||||
@@ -220,15 +224,64 @@ func (mapper *PortMapper) Map(port int, dest net.TCPAddr) error {
|
||||
if err := mapper.iptablesForward("-A", port, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mapper.mapping[port] = dest
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
|
||||
if err != nil {
|
||||
mapper.Unmap(port)
|
||||
return err
|
||||
}
|
||||
mapper.proxies[port] = listener
|
||||
go proxy(listener, "tcp", dest.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
// proxy listens for socket connections on `listener`, and forwards them unmodified
|
||||
// to `proto:address`
|
||||
func proxy(listener net.Listener, proto, address string) error {
|
||||
Debugf("proxying to %s:%s", proto, address)
|
||||
defer Debugf("Done proxying to %s:%s", proto, address)
|
||||
for {
|
||||
Debugf("Listening on %s", listener)
|
||||
src, err := listener.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
Debugf("Connecting to %s:%s", proto, address)
|
||||
dst, err := net.Dial(proto, address)
|
||||
if err != nil {
|
||||
log.Printf("Error connecting to %s:%s: %s", proto, address, err)
|
||||
src.Close()
|
||||
continue
|
||||
}
|
||||
Debugf("Connected to backend, splicing")
|
||||
splice(src, dst)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func halfSplice(dst, src net.Conn) error {
|
||||
_, err := io.Copy(dst, src)
|
||||
// FIXME: on EOF from a tcp connection, pass WriteClose()
|
||||
dst.Close()
|
||||
src.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
func splice(a, b net.Conn) {
|
||||
go halfSplice(a, b)
|
||||
go halfSplice(b, a)
|
||||
}
|
||||
|
||||
func (mapper *PortMapper) Unmap(port int) error {
|
||||
dest, ok := mapper.mapping[port]
|
||||
if !ok {
|
||||
return errors.New("Port is not mapped")
|
||||
}
|
||||
if proxy, exists := mapper.proxies[port]; exists {
|
||||
proxy.Close()
|
||||
delete(mapper.proxies, port)
|
||||
}
|
||||
if err := mapper.iptablesForward("-D", port, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
25
packaging/archlinux/README.archlinux
Normal file
25
packaging/archlinux/README.archlinux
Normal file
@@ -0,0 +1,25 @@
|
||||
Docker on Arch
|
||||
==============
|
||||
|
||||
The AUR lxc-docker and lxc-docker-git packages handle building docker on Arch
|
||||
linux. The PKGBUILD specifies all dependencies, build, and packaging steps.
|
||||
|
||||
Dependencies
|
||||
============
|
||||
|
||||
The only buildtime dependencies are git and go which are available via pacman.
|
||||
The -s flag can be used on makepkg commands below to automatically install
|
||||
these dependencies.
|
||||
|
||||
Building Package
|
||||
================
|
||||
|
||||
Download the tarball for either AUR packaged to a local directory. In that
|
||||
directory makepkg can be run to build the package.
|
||||
|
||||
# Build the binary package
|
||||
makepkg
|
||||
|
||||
# Build an updated source tarball
|
||||
makepkg --source
|
||||
|
||||
@@ -1,73 +1,62 @@
|
||||
# Ubuntu package Makefile
|
||||
#
|
||||
# Dependencies: debhelper autotools-dev devscripts golang
|
||||
# Notes:
|
||||
# Use 'make ubuntu' to create the ubuntu package
|
||||
# GPG_KEY environment variable needs to contain a GPG private key for package to be signed
|
||||
# and uploaded to docker PPA.
|
||||
# If GPG_KEY is not defined, make ubuntu will create docker package and exit with
|
||||
# status code 2
|
||||
|
||||
PKG_NAME=lxc-docker
|
||||
PKG_ARCH=amd64
|
||||
PKG_VERSION=1
|
||||
ROOT_PATH:=$(PWD)
|
||||
BUILD_PATH=build # Do not change, decided by dpkg-buildpackage
|
||||
BUILD_SRC=build_src
|
||||
GITHUB_PATH=src/github.com/dotcloud/docker
|
||||
INSDIR=usr/bin
|
||||
SOURCE_PACKAGE=$(PKG_NAME)_$(PKG_VERSION).orig.tar.gz
|
||||
DEB_PACKAGE=$(PKG_NAME)_$(PKG_VERSION)_$(PKG_ARCH).deb
|
||||
EXTRA_GO_PKG=./auth
|
||||
VERSION=$(shell head -1 changelog | sed 's/^.\+(\(.\+\)..).\+$$/\1/')
|
||||
GITHUB_PATH=github.com/dotcloud/docker
|
||||
DOCKER_VERSION=${PKG_NAME}_${VERSION}
|
||||
DOCKER_FVERSION=${PKG_NAME}_$(shell head -1 changelog | sed 's/^.\+(\(.\+\)).\+$$/\1/')
|
||||
BUILD_SRC=${CURDIR}/../../build_src
|
||||
VERSION_TAG=v$(shell head -1 changelog | sed 's/^.\+(\(.\+\)-[0-9]\+).\+$$/\1/')
|
||||
|
||||
TMPDIR=$(shell mktemp -d -t XXXXXX)
|
||||
all:
|
||||
# Compile docker. Used by dpkg-buildpackage.
|
||||
cd src/${GITHUB_PATH}/docker; GOPATH=${CURDIR} go build
|
||||
|
||||
|
||||
# Build a debian source package
|
||||
all: clean build_in_deb
|
||||
|
||||
build_in_deb:
|
||||
echo "GOPATH = " $(ROOT_PATH)
|
||||
mkdir bin
|
||||
cd $(GITHUB_PATH)/docker; GOPATH=$(ROOT_PATH) go build -o $(ROOT_PATH)/bin/docker
|
||||
|
||||
# DESTDIR provided by Debian packaging
|
||||
install:
|
||||
# Call this from a go environment (as packaged for deb source package)
|
||||
mkdir -p $(DESTDIR)/$(INSDIR)
|
||||
mkdir -p $(DESTDIR)/etc/init
|
||||
install -m 0755 bin/docker $(DESTDIR)/$(INSDIR)
|
||||
install -o root -m 0755 etc/docker.upstart $(DESTDIR)/etc/init/docker.conf
|
||||
# Used by dpkg-buildpackage
|
||||
mkdir -p ${DESTDIR}/usr/bin
|
||||
mkdir -p ${DESTDIR}/etc/init
|
||||
mkdir -p ${DESTDIR}/DEBIAN
|
||||
install -m 0755 src/${GITHUB_PATH}/docker/docker ${DESTDIR}/usr/bin
|
||||
install -o root -m 0755 debian/docker.upstart ${DESTDIR}/etc/init/docker.conf
|
||||
install debian/lxc-docker.prerm ${DESTDIR}/DEBIAN/prerm
|
||||
install debian/lxc-docker.postinst ${DESTDIR}/DEBIAN/postinst
|
||||
|
||||
$(BUILD_SRC): clean
|
||||
# Copy ourselves into $BUILD_SRC to comply with unusual golang constraints
|
||||
tar --exclude=*.tar.gz --exclude=checkout.tgz -f checkout.tgz -cz *
|
||||
mkdir -p $(BUILD_SRC)/$(GITHUB_PATH)
|
||||
tar -f checkout.tgz -C $(BUILD_SRC)/$(GITHUB_PATH) -xz
|
||||
cd $(BUILD_SRC)/$(GITHUB_PATH)/docker; GOPATH=$(ROOT_PATH)/$(BUILD_SRC) go get -d
|
||||
for d in `find $(BUILD_SRC) -name '.git*'`; do rm -rf $$d; done
|
||||
# Populate source build with debian stuff
|
||||
cp -R -L ./deb/* $(BUILD_SRC)
|
||||
|
||||
$(SOURCE_PACKAGE): $(BUILD_SRC)
|
||||
rm -f $(SOURCE_PACKAGE)
|
||||
# Create the debian source package
|
||||
tar -f $(SOURCE_PACKAGE) -C ${ROOT_PATH}/${BUILD_SRC} -cz .
|
||||
|
||||
# Build deb package fetching go dependencies and cleaning up git repositories
|
||||
deb: $(DEB_PACKAGE)
|
||||
|
||||
$(DEB_PACKAGE): $(SOURCE_PACKAGE)
|
||||
# dpkg-buildpackage looks for source package tarball in ../
|
||||
cd $(BUILD_SRC); dpkg-buildpackage
|
||||
rm -rf $(BUILD_PATH) debian/$(PKG_NAME)* debian/files
|
||||
|
||||
debsrc: $(SOURCE_PACKAGE)
|
||||
|
||||
# Build local sources
|
||||
#$(PKG_NAME): build_local
|
||||
|
||||
build_local:
|
||||
-@mkdir -p bin
|
||||
cd docker && go build -o ../bin/docker
|
||||
|
||||
gotest:
|
||||
@echo "\033[36m[Testing]\033[00m docker..."
|
||||
@sudo -E GOPATH=$(ROOT_PATH)/$(BUILD_SRC) go test -v . $(EXTRA_GO_PKG) && \
|
||||
echo -n "\033[32m[OK]\033[00m" || \
|
||||
echo -n "\033[31m[FAIL]\033[00m"; \
|
||||
echo " docker"
|
||||
@sudo rm -rf /tmp/docker-*
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_PATH) debian/$(PKG_NAME)* debian/files $(BUILD_SRC) checkout.tgz bin
|
||||
ubuntu:
|
||||
# This Makefile will compile the github master branch of dotcloud/docker
|
||||
# Retrieve docker project and its go structure from internet
|
||||
rm -rf ${BUILD_SRC}
|
||||
git clone $(shell git rev-parse --show-toplevel) ${BUILD_SRC}/${GITHUB_PATH}
|
||||
cd ${BUILD_SRC}/${GITHUB_PATH}; git checkout ${VERSION_TAG} && GOPATH=${BUILD_SRC} go get -d
|
||||
# Add debianization
|
||||
mkdir ${BUILD_SRC}/debian
|
||||
cp Makefile ${BUILD_SRC}
|
||||
cp -r * ${BUILD_SRC}/debian
|
||||
cp ../../README.md ${BUILD_SRC}
|
||||
# Cleanup
|
||||
for d in `find ${BUILD_SRC} -name '.git*'`; do rm -rf $$d; done
|
||||
rm -rf ${BUILD_SRC}/../${DOCKER_VERSION}.orig.tar.gz
|
||||
rm -rf ${BUILD_SRC}/pkg
|
||||
# Create docker debian files
|
||||
cd ${BUILD_SRC}; tar czf ../${DOCKER_VERSION}.orig.tar.gz .
|
||||
cd ${BUILD_SRC}; dpkg-buildpackage -us -uc
|
||||
rm -rf ${BUILD_SRC}
|
||||
# Sign package and upload it to PPA if GPG_KEY environment variable
|
||||
# holds a private GPG KEY
|
||||
if /usr/bin/test "$${GPG_KEY}" == ""; then exit 2; fi
|
||||
mkdir ${BUILD_SRC}
|
||||
# Import gpg signing key
|
||||
echo "$${GPG_KEY}" | gpg --allow-secret-key-import --import
|
||||
# Sign the package
|
||||
cd ${BUILD_SRC}; dpkg-source -x ${BUILD_SRC}/../${DOCKER_FVERSION}.dsc
|
||||
cd ${BUILD_SRC}/${PKG_NAME}-${VERSION}; debuild -S -sa
|
||||
cd ${BUILD_SRC};dput ppa:dotcloud/lxc-docker ${DOCKER_FVERSION}_source.changes
|
||||
rm -rf ${BUILD_SRC}
|
||||
|
||||
37
packaging/ubuntu/README.ubuntu
Normal file
37
packaging/ubuntu/README.ubuntu
Normal file
@@ -0,0 +1,37 @@
|
||||
Docker on Ubuntu
|
||||
================
|
||||
|
||||
The easiest way to get docker up and running natively on Ubuntu is installing
|
||||
it from its official PPA::
|
||||
|
||||
sudo sh -c "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >>/etc/apt/sources.list"
|
||||
sudo apt-get update
|
||||
sudo apt-get install lxc-docker
|
||||
|
||||
|
||||
Building docker package
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The building process is shared by both, developers and maintainers. If you are
|
||||
a developer, the Makefile will stop with exit status 2 right before signing
|
||||
the built packages.
|
||||
|
||||
Assuming you are working on an Ubuntu 12.04 TLS system ::
|
||||
|
||||
# Download a fresh copy of the docker project
|
||||
git clone https://github.com/dotcloud/docker.git
|
||||
cd docker
|
||||
|
||||
# Get building dependencies
|
||||
sudo apt-get update; sudo apt-get install -y debhelper autotools-dev devscripts golang
|
||||
|
||||
# Make the ubuntu package
|
||||
(cd packaging/ubuntu; make ubuntu)
|
||||
|
||||
|
||||
Install docker built package
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
sudo dpkg -i lxc-docker_*_amd64.deb; sudo apt-get install -f -y
|
||||
15
packaging/ubuntu/Vagrantfile
vendored
Normal file
15
packaging/ubuntu/Vagrantfile
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
GOPHERS_KEY = "308C15A29AD198E9"
|
||||
|
||||
Vagrant::Config.run do |config|
|
||||
config.vm.box = 'precise64'
|
||||
config.vm.box_url = 'http://files.vagrantup.com/precise64.box'
|
||||
config.vm.share_folder 'v-data', '/data/docker', "#{File.dirname(__FILE__)}/../.."
|
||||
|
||||
# Add docker PPA key to the local repository and install docker
|
||||
pkg_cmd = "apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys #{GOPHERS_KEY}; " \
|
||||
"echo 'deb http://ppa.launchpad.net/gophers/go/ubuntu precise main' >/etc/apt/sources.list.d/gophers-go.list; " \
|
||||
# Install ubuntu packaging dependencies and create ubuntu packages
|
||||
pkg_cmd << "export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; apt-get install -qq -y git debhelper autotools-dev devscripts golang-stable; " \
|
||||
"export GPG_KEY='#{ENV['GPG_KEY']}'; cd /data/docker/packaging/ubuntu; make ubuntu"
|
||||
config.vm.provision :shell, :inline => pkg_cmd
|
||||
end
|
||||
196
packaging/ubuntu/changelog
Normal file
196
packaging/ubuntu/changelog
Normal file
@@ -0,0 +1,196 @@
|
||||
lxc-docker (0.3.2-1) precise; urgency=low
|
||||
- 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
|
||||
|
||||
-- dotCloud <ops@dotcloud.com> Fri, 9 May 2013 00:00:00 -0700
|
||||
|
||||
|
||||
lxc-docker (0.3.1-1) precise; urgency=low
|
||||
- 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
|
||||
|
||||
-- dotCloud <ops@dotcloud.com> Fri, 8 May 2013 00:00:00 -0700
|
||||
|
||||
|
||||
lxc-docker (0.3.0-1) precise; urgency=low
|
||||
- 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
|
||||
|
||||
-- dotCloud <ops@dotcloud.com> Fri, 5 May 2013 00:00:00 -0700
|
||||
|
||||
|
||||
lxc-docker (0.2.2-1) precise; urgency=low
|
||||
- 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
|
||||
|
||||
-- dotCloud <ops@dotcloud.com> Fri, 3 May 2013 00:00:00 -0700
|
||||
|
||||
|
||||
lxc-docker (0.2.1-1) precise; urgency=low
|
||||
|
||||
- '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
|
||||
|
||||
-- dotCloud <ops@dotcloud.com> Wed, 1 May 2013 00:00:00 -0700
|
||||
|
||||
|
||||
lxc-docker (0.2.0-1) precise; urgency=low
|
||||
|
||||
- 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
|
||||
|
||||
-- dotCloud <ops@dotcloud.com> Mon, 23 Apr 2013 00:00:00 -0700
|
||||
|
||||
|
||||
lxc-docker (0.1.8-1) precise; urgency=low
|
||||
|
||||
- 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
|
||||
|
||||
-- dotCloud <ops@dotcloud.com> Mon, 22 Apr 2013 00:00:00 -0700
|
||||
|
||||
|
||||
lxc-docker (0.1.7-1) precise; urgency=low
|
||||
|
||||
- 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
|
||||
|
||||
-- dotCloud <ops@dotcloud.com> Thu, 18 Apr 2013 00:00:00 -0700
|
||||
|
||||
|
||||
lxc-docker (0.1.6-1) precise; urgency=low
|
||||
|
||||
- Record the author an image with 'docker commit -author'
|
||||
|
||||
-- dotCloud <ops@dotcloud.com> Wed, 17 Apr 2013 00:00:00 -0700
|
||||
|
||||
|
||||
lxc-docker (0.1.5-1) precise; urgency=low
|
||||
|
||||
- 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
|
||||
|
||||
-- dotCloud <ops@dotcloud.com> Wed, 17 Apr 2013 00:00:00 -0700
|
||||
|
||||
|
||||
lxc-docker (0.1.4-1) precise; urgency=low
|
||||
|
||||
- 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'
|
||||
|
||||
-- dotCloud <ops@dotcloud.com> Tue, 9 Apr 2013 00:00:00 -0700
|
||||
|
||||
|
||||
lxc-docker (0.1.3-1) precise; urgency=low
|
||||
|
||||
- Choose TCP frontend port with '-p :PORT'
|
||||
- Layer format is versioned
|
||||
- Major reliability improvements to the process manager
|
||||
- Various bugfixes and stability improvements
|
||||
|
||||
-- dotCloud <ops@dotcloud.com> Thu, 4 Apr 2013 00:00:00 -0700
|
||||
|
||||
|
||||
lxc-docker (0.1.2-1) precise; urgency=low
|
||||
|
||||
- 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
|
||||
|
||||
-- dotCloud <ops@dotcloud.com> Wed, 3 Apr 2013 00:00:00 -0700
|
||||
|
||||
|
||||
lxc-docker (0.1.1-1) precise; urgency=low
|
||||
|
||||
- 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
|
||||
|
||||
-- dotCloud <ops@dotcloud.com> Sun, 31 Mar 2013 00:00:00 -0700
|
||||
|
||||
|
||||
lxc-docker (0.1.0-1) precise; urgency=low
|
||||
|
||||
- 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
|
||||
|
||||
-- dotCloud <ops@dotcloud.com> Sat, 23 Mar 2013 00:00:00 -0700
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user