Compare commits

...

91 Commits

Author SHA1 Message Date
Solomon Hykes fe0378e9b3 Rephrase changelog 2013-07-01 17:05:49 -07:00
Guillaume J. Charmes 96a1d7c645 Bump version to 0.4.8 2013-07-01 16:58:25 -07:00
Guillaume J. Charmes 79ee8b46f4 Merge pull request #1046 from dotcloud/1043-output_id_non_attach-fix
- Runtime: Make sure the ID is displayed usgin run -d
2013-07-01 16:49:43 -07:00
Victor Vieux 55a7a8b8c9 Merge pull request #1092 from lopter/master
Fix TestGetInfo when there is more than one image in the test repository
2013-07-01 16:41:01 -07:00
Victor Vieux d1d66b9c5f Merge pull request #1078 from kstaken/fix_json_error
* Remote API: Small fix in /start if empty host config
2013-07-01 16:36:58 -07:00
Louis Opter 6dacbb451f Fix TestGetInfo when there is more than one image in the test repository
See also #1089, #1072.
2013-07-01 15:06:08 -07:00
Guillaume J. Charmes ead9cefadb Merge pull request #1089 from dotcloud/multiple_test_images-fix
- Tests: Fix unit tests when there is more than one tag within the test image
2013-07-01 13:58:04 -07:00
Guillaume J. Charmes 185a2fc55e Merge pull request #1086 from crosbymichael/1008-image-entrypoint
+ Builder: Add Entrypoint to builder and container config
2013-07-01 13:33:12 -07:00
Ken Cochrane fb8fac6c60 Merge pull request #1088 from kpelykh/master
* Documentation: Update Docker Remote API client list to include Java library
2013-07-01 12:50:31 -07:00
Guillaume J. Charmes b6f288a1ce Fix unit tests when there is more than one tag within the test image 2013-07-01 11:45:45 -07:00
zettaset-kpelykh aa9bec96b1 Issue #1087 Docker Java API client -- added java to Docker Remote API Client document 2013-07-01 11:28:40 -07:00
Michael Crosby b16ff9f859 Add Entrypoint to builder and container config
By setting an entrypoint in the Dockerfile this
allows one to run an image and only pass arguments.
2013-07-01 05:34:27 -09:00
Victor Vieux 348c5c4838 Merge pull request #1085 from dotcloud/1076-doc_delete-fix
fix status code in doc
2013-07-01 06:30:21 -07:00
Victor Vieux 8dcc6a0280 fix status code in doc 2013-07-01 13:28:58 +00:00
Victor Vieux 5e029f7600 Merge pull request #1061 from proppy/fix-slices-ref
api,server: slice are already refs, no need to return ptr
2013-07-01 04:51:56 -07:00
Kimbro Staken d8d33e8b8b Adding check for content-type header 2013-06-30 10:46:09 -07:00
Solomon Hykes b37f7d49d8 Documented release process for maintainers in hack/RELEASE.md 2013-06-29 22:08:25 -07:00
Solomon Hykes d67d5dd963 Merge pull request #1065 from dotcloud/bump_0.4.7
Bump version to 0.4.7
2013-06-29 21:23:59 -07:00
Solomon Hykes 273e0d42b7 * Hack: change builder tests to always use the current unit test image, instead of hardcoding 'docker-ut' 2013-06-29 21:22:15 -07:00
Solomon Hykes ca497a82ab Bump version to 0.4.7 2013-06-29 21:12:29 -07:00
Solomon Hykes b7226316c7 * Hack: move unit tests to a different source image, to work around issues when docker-ut has more than 1 tag. 2013-06-28 19:43:55 -07:00
Guillaume J. Charmes 84f41954ae Merge pull request #1052 from lopter/master
Fix a couple critical bugs on the test suite
2013-06-28 17:00:51 -07:00
Johan Euphrosine 54da339b2c api,server: slice are already refs, no need to return ptr 2013-06-28 12:41:09 -07:00
Sam Alba ac37fcf6f3 Fixed conflicts 2013-06-28 12:36:59 -07:00
Sam Alba 893c974b08 Resolve conflict 2013-06-28 12:32:41 -07:00
Joffrey F 30342efa37 Merge pull request #700 from dotcloud/615-pushbyid
Allow to push/pull on independent registries (by repository or image ID)
2013-06-28 10:29:10 -07:00
Daniel Mizyrycki 6165c246d4 Merge pull request #1057 from dotcloud/973-testing-stabilization
testing|stabilization, issue 973: Use docker-golang PPA and lts-raring kernel
2013-06-28 09:54:06 -07:00
shin- 72befeef24 Fixed issue in registry.GetRemoteTags 2013-06-28 18:42:37 +02:00
Daniel Mizyrycki af2a92f22b testing|stabilization, issue 973: Use docker-golang PPA and lts-raring kernel 2013-06-28 09:23:25 -07:00
shin- ad2f826a82 go fmt pass 2013-06-28 18:19:58 +02:00
shin- e095a1572f Allow push by ID when using a custom registry 2013-06-28 18:19:58 +02:00
shin- c3dd6e1926 Several fixes and updates to make this work with latest changes in master 2013-06-28 18:19:58 +02:00
Guillaume J. Charmes 67ecd2cb82 Reenable writeflusher for pull 2013-06-28 18:19:58 +02:00
Guillaume J. Charmes 57d751c377 Remove https prefix from registry 2013-06-28 18:19:58 +02:00
shin- 50075106b6 Rolled back of previous commit (skip cert verification) 2013-06-28 18:19:58 +02:00
shin- 2a1f8f6fda Ignore 'registry not found' when pushing on independent registries 2013-06-28 18:19:58 +02:00
shin- 1c817913ee Skip certificate check (don't error out on self-signed certs) 2013-06-28 18:19:58 +02:00
shin- de0a48bd6f Tentative support for independent registries 2013-06-28 18:19:58 +02:00
Victor Vieux a11fc9f067 Merge pull request #1032 from andrewsmedina/govet
following the 'go vet' suggestions for the docker package.
2013-06-28 05:27:53 -07:00
Thatcher e12a204bcc Merge pull request #1028 from dhrp/bugfixes-on-docs
Bugfixes on documentation code
2013-06-27 19:01:13 -07:00
Louis Opter fe014a8e6c Always return the correct test image.
And not a *random* one from its history.
2013-06-27 18:01:20 -07:00
Louis Opter aa8ea84d11 Fix a nil dereference in buildfile_test.go
The test runtime object wasn't properly initialized.
2013-06-27 18:01:10 -07:00
Sam Alba 3175e56ad0 URL schemes of both Registry and Index are now consistent 2013-06-27 17:55:17 -07:00
Guillaume J. Charmes 1a201d2433 Merge pull request #1035 from dotcloud/fix_panic_cast
- Runtime: fix panic with unix socket
2013-06-27 15:22:18 -07:00
Guillaume J. Charmes 750c94efbb Merge pull request #1041 from unclejack/fix_minor_kernel_version_for_git_kernels
remove + from minor kernel version for kernels built from git
2013-06-27 15:21:34 -07:00
Guillaume J. Charmes bd144a64f6 Make sure the ID is displayed usgin run -d 2013-06-27 12:48:25 -07:00
unclejack 5ed4386bbf remove + from minor kernel version 2013-06-27 17:51:17 +03:00
Victor Vieux 9d3ec7b39f fix panic with unix socket 2013-06-27 12:57:19 +00:00
Victor Vieux e68a23bdc1 Merge pull request #1019 from dotcloud/1002-change_update_progress_bar_rate-feature
*Remote API: update progressbar every MIN(1%, 512kB)
2013-06-27 04:19:42 -07:00
Andrews Medina 6cf493bea7 following 'go vet' in utils pkg. 2013-06-27 01:40:13 -03:00
Andrews Medina 3d5633a0a0 following the 'go vet' suggestions. 2013-06-27 01:33:55 -03:00
Solomon Hykes c4a44f6f0b Merge pull request #1029 from lopter/master
* Hack: add a script to create the docker-ut image (busybox + socat)
2013-06-26 16:28:58 -07:00
Solomon Hykes 3e29695c1f Merge pull request #602 from gabrtv/111-bind-mounts
+ Runtime: mount volumes from a host directory with 'docker run -b'
2013-06-26 15:59:35 -07:00
Solomon Hykes 46a9f29bae - Runtime: small bugfixes in external mount-bind integration 2013-06-26 15:26:47 -07:00
Gabriel Monroy 67239957c9 - Fix a few bugs in external mount-bind integration 2013-06-26 15:10:38 -07:00
Solomon Hykes d4e62101ab * Runtime: better integration of external bind-mounts (run -b) into the volume subsystem (run -v) 2013-06-26 15:08:07 -07:00
Gabriel Monroy 4fdf11b2e6 + Runtime: mount volumes from a host directory with 'docker run -b' 2013-06-26 15:07:31 -07:00
Guillaume J. Charmes cd0f22ef72 Merge pull request #1005 from dotcloud/1004-stdin_piping-fix
- Runtime: Fix issue when attaching stdin alone
2013-06-26 12:56:14 -07:00
Louis Opter e5c0b31107 Add a script to create the docker-ut image
It's a fork of the mkimage-busybox.sh script and it adds socat to the
image. (socat being needed to add udp support, see #33).

This script, like mkimage-busybox.sh, probably only works on
Debian/Ubuntu.
2013-06-26 12:35:14 -07:00
Guillaume J. Charmes 5cdbd2ed7a Merge pull request #1021 from errnoh/fix-test-filled-tmpfs
TestKill and TestMultipleContainers: run sleep instead of cat /bin/zero....
2013-06-26 11:59:06 -07:00
Guillaume J. Charmes b44e2e71aa Merge pull request #1010 from dotcloud/1009-testing-hack
Testing|hack, issue #1009: Update make hack environment
2013-06-25 17:07:18 -07:00
Thatcher Peskens 73afc6311d Bugfixes on docs
* fixed canonical link from index
* added http redirect from builder/basics
* fixed url in redirect_home
2013-06-25 15:31:22 -07:00
Guillaume J. Charmes 6127d757a7 Add missing fprintf instead of printf 2013-06-25 10:39:11 -07:00
Erno Hopearuoho fb86dcfb17 TestKill and TestMultipleContainers: run sleep instead of cat /bin/zero. fixes #737 2013-06-25 17:52:10 +03:00
Victor Vieux bccf06c748 update progressbar every MIN(1%, 512kB) 2013-06-25 14:03:15 +00:00
Victor Vieux 862e223cec Merge branch 'add-daemon-storage-path-param' of https://github.com/heavenlyhash/docker into heavenlyhash-add-daemon-storage-path-param 2013-06-25 13:33:45 +00:00
Ken Cochrane e1e2ff52fe Merge pull request #1018 from nahiluhmot/add-swipely-docker-gem
* Documentation: Added Swipely's `docker-api` gem to the table of Remote API Client Libraries.
2013-06-25 05:49:52 -07:00
Tom Hulihan d03edf12e4 Added Swipely's docker-api gem to the table of Remote API Client
Libraries.
2013-06-25 08:26:41 -04:00
Guillaume J. Charmes ec1dfc521c Merge pull request #992 from unclejack/use_numeric_owner_for_tar
* Runtime: use --numeric-owner for Tar and Untar
2013-06-24 18:40:43 -07:00
Guillaume J. Charmes 5190f7f33a Implement regression test for stdin attach 2013-06-24 18:36:04 -07:00
Guillaume J. Charmes 873a5aa8e7 Make NewDockerCli handle terminal 2013-06-24 18:29:08 -07:00
Guillaume J. Charmes 672d3a6c6c Make term function consistent with each other 2013-06-24 18:27:57 -07:00
Guillaume J. Charmes a749fb2130 Make DockerCli use its own stdin/out/err instead of the os.Std* 2013-06-24 18:27:57 -07:00
Guillaume J. Charmes 25d1bc2c09 Fix issue when attaching stdin alone 2013-06-24 18:27:57 -07:00
Daniel Mizyrycki cc63c1b584 Testing|hack, issue #1009: Update make kack environment 2013-06-24 15:01:51 -07:00
Solomon Hykes 145c622aba Merge pull request #990 from dotcloud/fix-tests-cgo
* Hack: remove dependency of unit tests on 'os/user', which cannot be used with CGO_ENABLED=0
2013-06-24 12:31:54 -07:00
Ken Cochrane e2516c01b4 Merge pull request #932 from metalivedev/docs20130614
+ Documentation: Add terminology section
2013-06-24 10:39:25 -07:00
Victor Vieux a3cb18d0f0 Merge pull request #1003 from dotcloud/fix_utils_tests
fix regression in utils tests introduced by #980
2013-06-24 09:14:13 -07:00
Victor Vieux eca9f9c1a1 fix regrettion in utils tests introduced by #980 2013-06-24 16:12:39 +00:00
Daniel Mizyrycki aee845682f Merge pull request #998 from dotcloud/861-hack-vagrant
Fixing hack/Vagrantfile to use uname for aufs linux extras
2013-06-22 20:10:00 -07:00
Anthony Bishopric e3dbe2f2ba Fixing hack/Vagrantfile to use uname for aufs linux extras
Conflicts: hack/Vagrantfile
Resolved by: Daniel Mizyrycki <daniel@dotcloud.com>
2013-06-22 19:58:33 -07:00
Solomon Hykes 193888a2b4 Merge pull request #997 from dotcloud/bump_0.4.6
Bump version to 0.4.6
2013-06-22 13:41:48 -07:00
Solomon Hykes 9fe8bfb2bc Bump version to 0.4.6 2013-06-22 13:36:45 -07:00
Solomon Hykes fc25973371 Merge pull request #996 from dotcloud/995-volumes-crash
- Runtime: fix a bug which caused creation of empty images (and volumes) to crash.
2013-06-22 13:35:26 -07:00
Solomon Hykes f9acd605dc - Runtime: add regression test for issue #995 2013-06-22 13:33:43 -07:00
Solomon Hykes 290b1973a9 Fix a bug which caused creation of empty images (and volumes) to crash. FIxes #995. 2013-06-22 12:29:42 -07:00
unclejack d7d42ff4fe use --numeric-owner for tar and untar 2013-06-22 18:53:10 +03:00
Solomon Hykes ce9e50f4ee Remove dependency on 'os/user', which cannot be used with CGO_ENABLED=0. This allows running the tests without CGO. 2013-06-21 19:40:42 -07:00
Eric Myhre e44f62a95c Add argument to allow setting base directory for docker daemon's storage to values other than "/var/lib/docker". 2013-06-20 16:29:54 -05:00
Andy Rothfusz 5183399f50 Added multilayer example image. 2013-06-18 19:31:35 -07:00
Andy Rothfusz a780b7c6b5 New Terminology section and illustrations. 2013-06-18 19:31:35 -07:00
52 changed files with 2669 additions and 456 deletions
+1
View File
@@ -29,6 +29,7 @@ Dr Nic Williams <drnicwilliams@gmail.com>
Elias Probst <mail@eliasprobst.eu>
Eric Hanchrow <ehanchrow@ine.com>
Evan Wies <evan@neomantra.net>
Eric Myhre <hash@exultant.us>
ezbercih <cem.ezberci@gmail.com>
Flavio Castelli <fcastelli@suse.com>
Francisco Souza <f@souza.cc>
+20
View File
@@ -1,5 +1,25 @@
# Changelog
## 0.4.8 (2013-07-01)
+ Builder: New build operation ENTRYPOINT adds an executable entry point to the container.
- Runtime: Fix a bug which caused 'docker run -d' to no longer print the container ID.
- Tests: Fix issues in the test suite
## 0.4.7 (2013-06-28)
* Registry: easier push/pull to a custom registry
* Remote API: the progress bar updates faster when downloading and uploading large files
- Remote API: fix a bug in the optional unix socket transport
* Runtime: improve detection of kernel version
+ Runtime: host directories can be mounted as volumes with 'docker run -b'
- Runtime: fix an issue when only attaching to stdin
* Runtime: use 'tar --numeric-owner' to avoid uid mismatch across multiple hosts
* Hack: improve test suite and dev environment
* Hack: remove dependency on unit tests on 'os/user'
+ Documentation: add terminology section
## 0.4.6 (2013-06-22)
- Runtime: fix a bug which caused creation of empty images (and volumes) to crash.
## 0.4.5 (2013-06-21)
+ Builder: 'docker build git://URL' fetches and builds a remote git repository
* Runtime: 'docker ps -s' optionally prints container size
+9 -2
View File
@@ -2,6 +2,8 @@ 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
BUILD_SRC := build_src
BUILD_PATH := ${BUILD_SRC}/src/${DOCKER_PACKAGE}
GIT_ROOT := $(shell git rev-parse --show-toplevel)
BUILD_DIR := $(CURDIR)/.gopath
@@ -71,8 +73,13 @@ else ifneq ($(DOCKER_DIR), $(realpath $(DOCKER_DIR)))
@rm -f $(DOCKER_DIR)
endif
test: all
@(cd $(DOCKER_DIR); sudo -E go test $(GO_OPTIONS))
test:
# Copy docker source and dependencies for testing
rm -rf ${BUILD_SRC}; mkdir -p ${BUILD_PATH}
tar --exclude=${BUILD_SRC} -cz . | tar -xz -C ${BUILD_PATH}
GOPATH=${CURDIR}/${BUILD_SRC} go get -d
# Do the test
sudo -E GOPATH=${CURDIR}/${BUILD_SRC} go test ${GO_OPTIONS}
testall: all
@(cd $(DOCKER_DIR); sudo -E go test ./... $(GO_OPTIONS))
+27 -3
View File
@@ -535,7 +535,7 @@ func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.R
return err
}
if imgs != nil {
if len(*imgs) != 0 {
if len(imgs) != 0 {
b, err := json.Marshal(imgs)
if err != nil {
return err
@@ -551,11 +551,22 @@ func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.R
}
func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
hostConfig := &HostConfig{}
// allow a nil body for backwards compatibility
if r.Body != nil {
if r.Header.Get("Content-Type") == "application/json" {
if err := json.NewDecoder(r.Body).Decode(hostConfig); err != nil {
return err
}
}
}
if vars == nil {
return fmt.Errorf("Missing parameter")
}
name := vars["name"]
if err := srv.ContainerStart(name); err != nil {
if err := srv.ContainerStart(name, hostConfig); err != nil {
return err
}
w.WriteHeader(http.StatusNoContent)
@@ -660,7 +671,20 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r
if err != nil {
return err
}
defer in.Close()
defer func() {
if tcpc, ok := in.(*net.TCPConn); ok {
tcpc.CloseWrite()
} else {
in.Close()
}
}()
defer func() {
if tcpc, ok := out.(*net.TCPConn); ok {
tcpc.CloseWrite()
} else if closer, ok := out.(io.Closer); ok {
closer.Close()
}
}()
fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, in, out); err != nil {
+71 -30
View File
@@ -19,25 +19,25 @@ import (
func TestGetBoolParam(t *testing.T) {
if ret, err := getBoolParam("true"); err != nil || !ret {
t.Fatalf("true -> true, nil | got %b %s", ret, err)
t.Fatalf("true -> true, nil | got %t %s", ret, err)
}
if ret, err := getBoolParam("True"); err != nil || !ret {
t.Fatalf("True -> true, nil | got %b %s", ret, err)
t.Fatalf("True -> true, nil | got %t %s", ret, err)
}
if ret, err := getBoolParam("1"); err != nil || !ret {
t.Fatalf("1 -> true, nil | got %b %s", ret, err)
t.Fatalf("1 -> true, nil | got %t %s", ret, err)
}
if ret, err := getBoolParam(""); err != nil || ret {
t.Fatalf("\"\" -> false, nil | got %b %s", ret, err)
t.Fatalf("\"\" -> false, nil | got %t %s", ret, err)
}
if ret, err := getBoolParam("false"); err != nil || ret {
t.Fatalf("false -> false, nil | got %b %s", ret, err)
t.Fatalf("false -> false, nil | got %t %s", ret, err)
}
if ret, err := getBoolParam("0"); err != nil || ret {
t.Fatalf("0 -> false, nil | got %b %s", ret, err)
t.Fatalf("0 -> false, nil | got %t %s", ret, err)
}
if ret, err := getBoolParam("faux"); err == nil || ret {
t.Fatalf("faux -> false, err | got %b %s", ret, err)
t.Fatalf("faux -> false, err | got %t %s", ret, err)
}
}
@@ -112,6 +112,11 @@ func TestGetInfo(t *testing.T) {
srv := &Server{runtime: runtime}
initialImages, err := srv.runtime.graph.All()
if err != nil {
t.Fatal(err)
}
r := httptest.NewRecorder()
if err := getInfo(srv, APIVERSION, r, nil, nil); err != nil {
@@ -123,8 +128,8 @@ func TestGetInfo(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if infos.Images != 1 {
t.Errorf("Excepted images: %d, %d found", 1, infos.Images)
if infos.Images != len(initialImages) {
t.Errorf("Excepted images: %d, %d found", len(initialImages), infos.Images)
}
}
@@ -137,6 +142,11 @@ func TestGetImagesJSON(t *testing.T) {
srv := &Server{runtime: runtime}
initialImages, err := srv.Images(true, "")
if err != nil {
t.Fatal(err)
}
// all=0
req, err := http.NewRequest("GET", "/images/json?all=0", nil)
if err != nil {
@@ -154,12 +164,19 @@ func TestGetImagesJSON(t *testing.T) {
t.Fatal(err)
}
if len(images) != 1 {
t.Errorf("Excepted 1 image, %d found", len(images))
if len(images) != len(initialImages) {
t.Errorf("Excepted %d image, %d found", len(initialImages), len(images))
}
if images[0].Repository != unitTestImageName {
t.Errorf("Excepted image %s, %s found", unitTestImageName, images[0].Repository)
found := false
for _, img := range images {
if img.Repository == unitTestImageName {
found = true
break
}
}
if !found {
t.Errorf("Excepted image %s, %+v found", unitTestImageName, images)
}
r2 := httptest.NewRecorder()
@@ -179,18 +196,25 @@ func TestGetImagesJSON(t *testing.T) {
t.Fatal(err)
}
if len(images2) != 1 {
t.Errorf("Excepted 1 image, %d found", len(images2))
if len(images2) != len(initialImages) {
t.Errorf("Excepted %d image, %d found", len(initialImages), len(images2))
}
if images2[0].ID != GetTestImage(runtime).ID {
t.Errorf("Retrieved image Id differs, expected %s, received %s", GetTestImage(runtime).ID, images2[0].ID)
found = false
for _, img := range images2 {
if img.ID == GetTestImage(runtime).ID {
found = true
break
}
}
if !found {
t.Errorf("Retrieved image Id differs, expected %s, received %+v", GetTestImage(runtime).ID, images2)
}
r3 := httptest.NewRecorder()
// filter=a
req3, err := http.NewRequest("GET", "/images/json?filter=a", nil)
req3, err := http.NewRequest("GET", "/images/json?filter=aaaaaaaaaa", nil)
if err != nil {
t.Fatal(err)
}
@@ -205,7 +229,7 @@ func TestGetImagesJSON(t *testing.T) {
}
if len(images3) != 0 {
t.Errorf("Excepted 1 image, %d found", len(images3))
t.Errorf("Excepted 0 image, %d found", len(images3))
}
r4 := httptest.NewRecorder()
@@ -873,7 +897,8 @@ func TestPostContainersKill(t *testing.T) {
}
defer runtime.Destroy(container)
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@@ -917,7 +942,8 @@ func TestPostContainersRestart(t *testing.T) {
}
defer runtime.Destroy(container)
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@@ -973,8 +999,15 @@ func TestPostContainersStart(t *testing.T) {
}
defer runtime.Destroy(container)
hostConfigJSON, err := json.Marshal(&HostConfig{})
req, err := http.NewRequest("POST", "/containers/"+container.ID+"/start", bytes.NewReader(hostConfigJSON))
if err != nil {
t.Fatal(err)
}
r := httptest.NewRecorder()
if err := postContainersStart(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
if err := postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
if r.Code != http.StatusNoContent {
@@ -989,7 +1022,7 @@ func TestPostContainersStart(t *testing.T) {
}
r = httptest.NewRecorder()
if err = postContainersStart(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err == nil {
if err = postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err == nil {
t.Fatalf("A running containter should be able to be started")
}
@@ -1019,7 +1052,8 @@ func TestPostContainersStop(t *testing.T) {
}
defer runtime.Destroy(container)
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@@ -1068,7 +1102,8 @@ func TestPostContainersWait(t *testing.T) {
}
defer runtime.Destroy(container)
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@@ -1113,7 +1148,8 @@ func TestPostContainersAttach(t *testing.T) {
defer runtime.Destroy(container)
// Start the process
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@@ -1298,6 +1334,11 @@ func TestDeleteImages(t *testing.T) {
srv := &Server{runtime: runtime}
initialImages, err := srv.Images(false, "")
if err != nil {
t.Fatal(err)
}
if err := srv.runtime.repositories.Set("test", "test", unitTestImageName, true); err != nil {
t.Fatal(err)
}
@@ -1307,8 +1348,8 @@ func TestDeleteImages(t *testing.T) {
t.Fatal(err)
}
if len(images) != 2 {
t.Errorf("Excepted 2 images, %d found", len(images))
if len(images) != len(initialImages)+1 {
t.Errorf("Excepted %d images, %d found", len(initialImages)+1, len(images))
}
req, err := http.NewRequest("DELETE", "/images/test:test", nil)
@@ -1336,8 +1377,8 @@ func TestDeleteImages(t *testing.T) {
t.Fatal(err)
}
if len(images) != 1 {
t.Errorf("Excepted 1 image, %d found", len(images))
if len(images) != len(initialImages) {
t.Errorf("Excepted %d image, %d found", len(initialImages), len(images))
}
/* if c := runtime.Get(container.Id); c != nil {
+5 -3
View File
@@ -92,7 +92,7 @@ func Tar(path string, compression Compression) (io.Reader, error) {
// Tar creates an archive from the directory at `path`, only including files whose relative
// paths are included in `filter`. If `filter` is nil, then all files are included.
func TarFilter(path string, compression Compression, filter []string) (io.Reader, error) {
args := []string{"tar", "-f", "-", "-C", path}
args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path}
if filter == nil {
filter = []string{"."}
}
@@ -108,7 +108,9 @@ func TarFilter(path string, compression Compression, filter []string) (io.Reader
// identity (uncompressed), gzip, bzip2, xz.
// FIXME: specify behavior when target path exists vs. doesn't exist.
func Untar(archive io.Reader, path string) error {
if archive == nil {
return fmt.Errorf("Empty archive")
}
bufferedArchive := bufio.NewReaderSize(archive, 10)
buf, err := bufferedArchive.Peek(10)
if err != nil {
@@ -118,7 +120,7 @@ func Untar(archive io.Reader, path string) error {
utils.Debugf("Archive compression detected: %s", compression.Extension())
cmd := exec.Command("tar", "-f", "-", "-C", path, "-x"+compression.Flag())
cmd := exec.Command("tar", "--numeric-owner", "-f", "-", "-C", path, "-x"+compression.Flag())
cmd.Stdin = bufferedArchive
// Hardcode locale environment for predictable outcome regardless of host configuration.
// (see https://github.com/dotcloud/docker/issues/355)
-1
View File
@@ -74,7 +74,6 @@ func decodeAuth(authStr string) (*AuthConfig, error) {
}
password := strings.Trim(arr[1], "\x00")
return &AuthConfig{Username: arr[0], Password: password}, nil
}
// load up the auth config information and return values
+13 -2
View File
@@ -50,12 +50,23 @@ func (builder *Builder) Create(config *Config) (*Container, error) {
config.Hostname = id[:12]
}
var args []string
var entrypoint string
if len(config.Entrypoint) != 0 {
entrypoint = config.Entrypoint[0]
args = append(config.Entrypoint[1:], config.Cmd...)
} else {
entrypoint = config.Cmd[0]
args = config.Cmd[1:]
}
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
Path: entrypoint,
Args: args, //FIXME: de-duplicate from config
Config: config,
Image: img.ID, // Always use the resolved image id
NetworkSettings: &NetworkSettings{},
+21 -3
View File
@@ -87,7 +87,7 @@ func (b *buildFile) CmdRun(args string) error {
if b.image == "" {
return fmt.Errorf("Please provide a source image with `from` prior to run")
}
config, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
config, _, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
if err != nil {
return err
}
@@ -141,7 +141,7 @@ func (b *buildFile) CmdEnv(args string) error {
func (b *buildFile) CmdCmd(args string) error {
var cmd []string
if err := json.Unmarshal([]byte(args), &cmd); err != nil {
utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err)
utils.Debugf("Error unmarshalling: %s, setting cmd to /bin/sh -c", err)
cmd = []string{"/bin/sh", "-c", args}
}
if err := b.commit("", cmd, fmt.Sprintf("CMD %v", cmd)); err != nil {
@@ -165,6 +165,23 @@ func (b *buildFile) CmdCopy(args string) error {
return fmt.Errorf("COPY has been deprecated. Please use ADD instead")
}
func (b *buildFile) CmdEntrypoint(args string) error {
if args == "" {
return fmt.Errorf("Entrypoint cannot be empty")
}
var entrypoint []string
if err := json.Unmarshal([]byte(args), &entrypoint); err != nil {
b.config.Entrypoint = []string{"/bin/sh", "-c", args}
} else {
b.config.Entrypoint = entrypoint
}
if err := b.commit("", b.config.Cmd, fmt.Sprintf("ENTRYPOINT %s", args)); err != nil {
return err
}
return nil
}
func (b *buildFile) addRemote(container *Container, orig, dest string) error {
file, err := utils.Download(orig, ioutil.Discard)
if err != nil {
@@ -263,7 +280,8 @@ func (b *buildFile) run() (string, error) {
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(c.ID))
//start the container
if err := c.Start(); err != nil {
hostConfig := &HostConfig{}
if err := c.Start(hostConfig); err != nil {
return "", err
}
+22 -6
View File
@@ -2,13 +2,15 @@ package docker
import (
"io/ioutil"
"sync"
"testing"
"fmt"
)
// mkTestContext generates a build context from the contents of the provided dockerfile.
// This context is suitable for use as an argument to BuildFile.Build()
func mkTestContext(dockerfile string, files [][2]string, t *testing.T) Archive {
context, err := mkBuildContext(dockerfile, files)
context, err := mkBuildContext(fmt.Sprintf(dockerfile, unitTestImageId), files)
if err != nil {
t.Fatal(err)
}
@@ -28,7 +30,7 @@ type testContextTemplate struct {
var testContexts []testContextTemplate = []testContextTemplate{
{
`
from docker-ut
from %s
run sh -c 'echo root:testpass > /tmp/passwd'
run mkdir -p /var/run/sshd
run [ "$(cat /tmp/passwd)" = "root:testpass" ]
@@ -39,7 +41,7 @@ run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
{
`
from docker-ut
from %s
add foo /usr/lib/bla/bar
run [ "$(cat /usr/lib/bla/bar)" = 'hello world!' ]
`,
@@ -48,7 +50,7 @@ run [ "$(cat /usr/lib/bla/bar)" = 'hello world!' ]
{
`
from docker-ut
from %s
add f /
run [ "$(cat /f)" = "hello" ]
add f /abc
@@ -74,9 +76,18 @@ run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ]
{
`
from docker-ut
from %s
env FOO BAR
run [ "$FOO" = "BAR" ]
`,
nil,
},
{
`
from docker-ut
ENTRYPOINT /bin/echo
CMD Hello world
`,
nil,
},
@@ -92,7 +103,12 @@ func TestBuild(t *testing.T) {
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
srv := &Server{
runtime: runtime,
lock: &sync.Mutex{},
pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}),
}
buildfile := NewBuildFile(srv, ioutil.Discard)
if _, err := buildfile.Build(mkTestContext(ctx.dockerfile, ctx.files, t)); err != nil {
+182 -109
View File
@@ -28,7 +28,7 @@ import (
"unicode"
)
const VERSION = "0.4.5"
const VERSION = "0.4.8"
var (
GITCOMMIT string
@@ -40,7 +40,7 @@ func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) {
}
func ParseCommands(proto, addr string, args ...string) error {
cli := NewDockerCli(proto, addr)
cli := NewDockerCli(os.Stdin, os.Stdout, os.Stderr, proto, addr)
if len(args) > 0 {
method, exists := cli.getMethod(args[0])
@@ -64,7 +64,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
if len(args) > 0 {
method, exists := cli.getMethod(args[0])
if !exists {
fmt.Println("Error: Command not found:", args[0])
fmt.Fprintf(cli.err, "Error: Command not found: %s\n", args[0])
} else {
method.Func.CallSlice([]reflect.Value{
reflect.ValueOf(cli),
@@ -74,7 +74,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
}
}
help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=[tcp://%s:%d]: tcp://host:port to bind/connect to or unix://path/to/socker to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", DEFAULTHTTPHOST, DEFAULTHTTPPORT)
for _, command := range [][2]string{
for _, command := range [][]string{
{"attach", "Attach to a running container"},
{"build", "Build a container from a Dockerfile"},
{"commit", "Create a new image from a container's changes"},
@@ -106,7 +106,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
} {
help += fmt.Sprintf(" %-10.10s%s\n", command[0], command[1])
}
fmt.Println(help)
fmt.Fprintf(cli.err, "%s\n", help)
return nil
}
@@ -124,7 +124,7 @@ func (cli *DockerCli) CmdInsert(args ...string) error {
v.Set("url", cmd.Arg(1))
v.Set("path", cmd.Arg(2))
if err := cli.stream("POST", "/images/"+cmd.Arg(0)+"/insert?"+v.Encode(), nil, os.Stdout); err != nil {
if err := cli.stream("POST", "/images/"+cmd.Arg(0)+"/insert?"+v.Encode(), nil, cli.out); err != nil {
return err
}
return nil
@@ -175,7 +175,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
if cmd.Arg(0) == "-" {
// As a special case, 'docker build -' will build from an empty context with the
// contents of stdin as a Dockerfile
dockerfile, err := ioutil.ReadAll(os.Stdin)
dockerfile, err := ioutil.ReadAll(cli.in)
if err != nil {
return err
}
@@ -190,7 +190,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
// FIXME: ProgressReader shouldn't be this annoyning to use
if context != nil {
sf := utils.NewStreamFormatter(false)
body = utils.ProgressReader(ioutil.NopCloser(context), 0, os.Stderr, sf.FormatProgress("Uploading context", "%v bytes%0.0s%0.0s"), sf)
body = utils.ProgressReader(ioutil.NopCloser(context), 0, cli.err, sf.FormatProgress("Uploading context", "%v bytes%0.0s%0.0s"), sf)
}
// Upload the build context
v := &url.Values{}
@@ -229,7 +229,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
}
// Output the result
if _, err := io.Copy(os.Stdout, resp.Body); err != nil {
if _, err := io.Copy(cli.out, resp.Body); err != nil {
return err
}
@@ -280,36 +280,38 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
return readStringOnRawTerminal(stdin, stdout, false)
}
oldState, err := term.SetRawTerminal()
oldState, err := term.SetRawTerminal(cli.terminalFd)
if err != nil {
return err
}
defer term.RestoreTerminal(oldState)
defer term.RestoreTerminal(cli.terminalFd, oldState)
cmd := Subcmd("login", "", "Register or Login to the docker registry server")
if err := cmd.Parse(args); err != nil {
return nil
}
var username string
var password string
var email string
var (
username string
password string
email string
)
fmt.Print("Username (", cli.authConfig.Username, "): ")
username = readAndEchoString(os.Stdin, os.Stdout)
fmt.Fprintf(cli.out, "Username (%s):", cli.authConfig.Username)
username = readAndEchoString(cli.in, cli.out)
if username == "" {
username = cli.authConfig.Username
}
if username != cli.authConfig.Username {
fmt.Print("Password: ")
password = readString(os.Stdin, os.Stdout)
fmt.Fprintf(cli.out, "Password: ")
password = readString(cli.in, cli.out)
if password == "" {
return fmt.Errorf("Error : Password Required")
}
fmt.Print("Email (", cli.authConfig.Email, "): ")
email = readAndEchoString(os.Stdin, os.Stdout)
fmt.Fprintf(cli.out, "Email (%s): ", cli.authConfig.Email)
email = readAndEchoString(cli.in, cli.out)
if email == "" {
email = cli.authConfig.Email
}
@@ -317,7 +319,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
password = cli.authConfig.Password
email = cli.authConfig.Email
}
term.RestoreTerminal(oldState)
term.RestoreTerminal(cli.terminalFd, oldState)
cli.authConfig.Username = username
cli.authConfig.Password = password
@@ -343,7 +345,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
}
auth.SaveConfig(cli.authConfig)
if out2.Status != "" {
fmt.Println(out2.Status)
fmt.Fprintf(cli.out, "%s\n", out2.Status)
}
return nil
}
@@ -361,14 +363,14 @@ func (cli *DockerCli) CmdWait(args ...string) error {
for _, name := range cmd.Args() {
body, _, err := cli.call("POST", "/containers/"+name+"/wait", nil)
if err != nil {
fmt.Printf("%s", err)
fmt.Fprintf(cli.err, "%s", err)
} else {
var out APIWait
err = json.Unmarshal(body, &out)
if err != nil {
return err
}
fmt.Println(out.StatusCode)
fmt.Fprintf(cli.out, "%d\n", out.StatusCode)
}
}
return nil
@@ -397,13 +399,13 @@ func (cli *DockerCli) CmdVersion(args ...string) error {
utils.Debugf("Error unmarshal: body: %s, err: %s\n", body, err)
return err
}
fmt.Println("Client version:", VERSION)
fmt.Println("Server version:", out.Version)
fmt.Fprintf(cli.out, "Client version: %s\n", VERSION)
fmt.Fprintf(cli.out, "Server version: %s\n", out.Version)
if out.GitCommit != "" {
fmt.Println("Git commit:", out.GitCommit)
fmt.Fprintf(cli.out, "Git commit: %s\n", out.GitCommit)
}
if out.GoVersion != "" {
fmt.Println("Go version:", out.GoVersion)
fmt.Fprintf(cli.out, "Go version: %s\n", out.GoVersion)
}
return nil
}
@@ -429,19 +431,19 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
return err
}
fmt.Printf("Containers: %d\n", out.Containers)
fmt.Printf("Images: %d\n", out.Images)
fmt.Fprintf(cli.out, "Containers: %d\n", out.Containers)
fmt.Fprintf(cli.out, "Images: %d\n", out.Images)
if out.Debug || os.Getenv("DEBUG") != "" {
fmt.Printf("Debug mode (server): %v\n", out.Debug)
fmt.Printf("Debug mode (client): %v\n", os.Getenv("DEBUG") != "")
fmt.Printf("Fds: %d\n", out.NFd)
fmt.Printf("Goroutines: %d\n", out.NGoroutines)
fmt.Fprintf(cli.out, "Debug mode (server): %v\n", out.Debug)
fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "")
fmt.Fprintf(cli.out, "Fds: %d\n", out.NFd)
fmt.Fprintf(cli.out, "Goroutines: %d\n", out.NGoroutines)
}
if !out.MemoryLimit {
fmt.Println("WARNING: No memory limit support")
fmt.Fprintf(cli.err, "WARNING: No memory limit support\n")
}
if !out.SwapLimit {
fmt.Println("WARNING: No swap limit support")
fmt.Fprintf(cli.err, "WARNING: No swap limit support\n")
}
return nil
}
@@ -463,9 +465,9 @@ func (cli *DockerCli) CmdStop(args ...string) error {
for _, name := range cmd.Args() {
_, _, err := cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil)
if err != nil {
fmt.Fprintf(os.Stderr, "%s", err)
fmt.Fprintf(cli.err, "%s\n", err)
} else {
fmt.Println(name)
fmt.Fprintf(cli.out, "%s\n", name)
}
}
return nil
@@ -488,9 +490,9 @@ func (cli *DockerCli) CmdRestart(args ...string) error {
for _, name := range cmd.Args() {
_, _, err := cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil)
if err != nil {
fmt.Fprintf(os.Stderr, "%s", err)
fmt.Fprintf(cli.err, "%s\n", err)
} else {
fmt.Println(name)
fmt.Fprintf(cli.out, "%s\n", name)
}
}
return nil
@@ -509,9 +511,9 @@ func (cli *DockerCli) CmdStart(args ...string) error {
for _, name := range args {
_, _, err := cli.call("POST", "/containers/"+name+"/start", nil)
if err != nil {
fmt.Fprintf(os.Stderr, "%s", err)
fmt.Fprintf(cli.err, "%s\n", err)
} else {
fmt.Println(name)
fmt.Fprintf(cli.out, "%s\n", name)
}
}
return nil
@@ -526,30 +528,30 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
cmd.Usage()
return nil
}
fmt.Printf("[")
fmt.Fprintf(cli.out, "[")
for i, name := range args {
if i > 0 {
fmt.Printf(",")
fmt.Fprintf(cli.out, ",")
}
obj, _, err := cli.call("GET", "/containers/"+name+"/json", nil)
if err != nil {
obj, _, err = cli.call("GET", "/images/"+name+"/json", nil)
if err != nil {
fmt.Fprintf(os.Stderr, "%s", err)
fmt.Fprintf(cli.err, "%s\n", err)
continue
}
}
indented := new(bytes.Buffer)
if err = json.Indent(indented, obj, "", " "); err != nil {
fmt.Fprintf(os.Stderr, "%s", err)
fmt.Fprintf(cli.err, "%s\n", err)
continue
}
if _, err := io.Copy(os.Stdout, indented); err != nil {
fmt.Fprintf(os.Stderr, "%s", err)
if _, err := io.Copy(cli.out, indented); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
}
}
fmt.Printf("]")
fmt.Fprintf(cli.out, "]")
return nil
}
@@ -574,7 +576,7 @@ func (cli *DockerCli) CmdPort(args ...string) error {
}
if frontend, exists := out.NetworkSettings.PortMapping[cmd.Arg(1)]; exists {
fmt.Println(frontend)
fmt.Fprintf(cli.out, "%s\n", frontend)
} else {
return fmt.Errorf("Error: No private port '%s' allocated on %s", cmd.Arg(1), cmd.Arg(0))
}
@@ -595,7 +597,7 @@ func (cli *DockerCli) CmdRmi(args ...string) error {
for _, name := range cmd.Args() {
body, _, err := cli.call("DELETE", "/images/"+name, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "%s", err)
fmt.Fprintf(cli.err, "%s", err)
} else {
var outs []APIRmi
err = json.Unmarshal(body, &outs)
@@ -604,9 +606,9 @@ func (cli *DockerCli) CmdRmi(args ...string) error {
}
for _, out := range outs {
if out.Deleted != "" {
fmt.Println("Deleted:", out.Deleted)
fmt.Fprintf(cli.out, "Deleted: %s\n", out.Deleted)
} else {
fmt.Println("Untagged:", out.Untagged)
fmt.Fprintf(cli.out, "Untagged: %s\n", out.Untagged)
}
}
}
@@ -634,7 +636,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintln(w, "ID\tCREATED\tCREATED BY")
for _, out := range outs {
@@ -664,9 +666,9 @@ func (cli *DockerCli) CmdRm(args ...string) error {
for _, name := range cmd.Args() {
_, _, err := cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil)
if err != nil {
fmt.Printf("%s", err)
fmt.Fprintf(cli.err, "%s\n", err)
} else {
fmt.Println(name)
fmt.Fprintf(cli.out, "%s\n", name)
}
}
return nil
@@ -686,9 +688,9 @@ func (cli *DockerCli) CmdKill(args ...string) error {
for _, name := range args {
_, _, err := cli.call("POST", "/containers/"+name+"/kill", nil)
if err != nil {
fmt.Printf("%s", err)
fmt.Fprintf(cli.err, "%s\n", err)
} else {
fmt.Println(name)
fmt.Fprintf(cli.out, "%s\n", name)
}
}
return nil
@@ -710,7 +712,7 @@ func (cli *DockerCli) CmdImport(args ...string) error {
v.Set("tag", tag)
v.Set("fromSrc", src)
err := cli.stream("POST", "/images/create?"+v.Encode(), os.Stdin, os.Stdout)
err := cli.stream("POST", "/images/create?"+v.Encode(), cli.in, cli.out)
if err != nil {
return err
}
@@ -734,27 +736,34 @@ func (cli *DockerCli) CmdPush(args ...string) error {
return err
}
if len(strings.SplitN(name, "/", 2)) == 1 {
return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", cli.authConfig.Username, name)
if *registry == "" {
// If we're not using a custom registry, we know the restrictions
// applied to repository names and can warn the user in advance.
// Custom repositories can have different rules, and we must also
// allow pushing by image ID.
if len(strings.SplitN(name, "/", 2)) == 1 {
return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", cli.authConfig.Username, name)
}
nameParts := strings.SplitN(name, "/", 2)
validNamespace := regexp.MustCompile(`^([a-z0-9_]{4,30})$`)
if !validNamespace.MatchString(nameParts[0]) {
return fmt.Errorf("Invalid namespace name (%s), only [a-z0-9_] are allowed, size between 4 and 30", nameParts[0])
}
validRepo := regexp.MustCompile(`^([a-zA-Z0-9-_.]+)$`)
if !validRepo.MatchString(nameParts[1]) {
return fmt.Errorf("Invalid repository name (%s), only [a-zA-Z0-9-_.] are allowed", nameParts[1])
}
}
buf, err := json.Marshal(cli.authConfig)
if err != nil {
return err
}
nameParts := strings.SplitN(name, "/", 2)
validNamespace := regexp.MustCompile(`^([a-z0-9_]{4,30})$`)
if !validNamespace.MatchString(nameParts[0]) {
return fmt.Errorf("Invalid namespace name (%s), only [a-z0-9_] are allowed, size between 4 and 30", nameParts[0])
}
validRepo := regexp.MustCompile(`^([a-zA-Z0-9-_.]+)$`)
if !validRepo.MatchString(nameParts[1]) {
return fmt.Errorf("Invalid repository name (%s), only [a-zA-Z0-9-_.] are allowed", nameParts[1])
}
v := url.Values{}
v.Set("registry", *registry)
if err := cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), bytes.NewBuffer(buf), os.Stdout); err != nil {
if err := cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), bytes.NewBuffer(buf), cli.out); err != nil {
return err
}
return nil
@@ -785,7 +794,7 @@ func (cli *DockerCli) CmdPull(args ...string) error {
v.Set("tag", *tag)
v.Set("registry", *registry)
if err := cli.stream("POST", "/images/create?"+v.Encode(), nil, os.Stdout); err != nil {
if err := cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out); err != nil {
return err
}
@@ -812,7 +821,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
if err != nil {
return err
}
fmt.Printf("%s", body)
fmt.Fprintf(cli.out, "%s", body)
} else {
v := url.Values{}
if cmd.NArg() == 1 {
@@ -833,7 +842,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
return err
}
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
if !*quiet {
fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED\tSIZE")
}
@@ -919,7 +928,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
if !*quiet {
fmt.Fprint(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS")
if *size {
@@ -998,7 +1007,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
return err
}
fmt.Println(apiID.ID)
fmt.Fprintf(cli.out, "%s\n", apiID.ID)
return nil
}
@@ -1013,7 +1022,7 @@ func (cli *DockerCli) CmdExport(args ...string) error {
return nil
}
if err := cli.stream("GET", "/containers/"+cmd.Arg(0)+"/export", nil, os.Stdout); err != nil {
if err := cli.stream("GET", "/containers/"+cmd.Arg(0)+"/export", nil, cli.out); err != nil {
return err
}
return nil
@@ -1040,7 +1049,7 @@ func (cli *DockerCli) CmdDiff(args ...string) error {
return err
}
for _, change := range changes {
fmt.Println(change.String())
fmt.Fprintf(cli.out, "%s\n", change.String())
}
return nil
}
@@ -1055,10 +1064,10 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
return nil
}
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", false, nil, os.Stdout); err != nil {
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", false, nil, cli.out); err != nil {
return err
}
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", false, nil, os.Stderr); err != nil {
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", false, nil, cli.err); err != nil {
return err
}
return nil
@@ -1090,7 +1099,9 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
}
if container.Config.Tty {
cli.monitorTtySize(cmd.Arg(0))
if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
return err
}
}
v := url.Values{}
@@ -1099,7 +1110,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
v.Set("stdout", "1")
v.Set("stderr", "1")
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, os.Stdin, os.Stdout); err != nil {
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, cli.in, cli.out); err != nil {
return err
}
return nil
@@ -1127,8 +1138,8 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
if err != nil {
return err
}
fmt.Printf("Found %d results matching your query (\"%s\")\n", len(outs), cmd.Arg(0))
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
fmt.Fprintf(cli.out, "Found %d results matching your query (\"%s\")\n", len(outs), cmd.Arg(0))
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "NAME\tDESCRIPTION\n")
for _, out := range outs {
desc := strings.Replace(out.Description, "\n", " ", -1)
@@ -1231,7 +1242,7 @@ func (cli *DockerCli) CmdTag(args ...string) error {
}
func (cli *DockerCli) CmdRun(args ...string) error {
config, cmd, err := ParseRun(args, nil)
config, hostConfig, cmd, err := ParseRun(args, nil)
if err != nil {
return err
}
@@ -1246,7 +1257,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
if statusCode == 404 {
v := url.Values{}
v.Set("fromImage", config.Image)
err = cli.stream("POST", "/images/create?"+v.Encode(), nil, os.Stderr)
err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err)
if err != nil {
return err
}
@@ -1259,27 +1270,37 @@ func (cli *DockerCli) CmdRun(args ...string) error {
return err
}
out := &APIRun{}
err = json.Unmarshal(body, out)
runResult := &APIRun{}
err = json.Unmarshal(body, runResult)
if err != nil {
return err
}
for _, warning := range out.Warnings {
fmt.Fprintln(os.Stderr, "WARNING: ", warning)
for _, warning := range runResult.Warnings {
fmt.Fprintln(cli.err, "WARNING: ", warning)
}
//start the container
_, _, err = cli.call("POST", "/containers/"+out.ID+"/start", nil)
if err != nil {
if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig); err != nil {
return err
}
var wait chan struct{}
if !config.AttachStdout && !config.AttachStderr {
fmt.Println(out.ID)
} else {
// Make this asynchrone in order to let the client write to stdin before having to read the ID
wait = make(chan struct{})
go func() {
defer close(wait)
fmt.Fprintf(cli.out, "%s\n", runResult.ID)
}()
}
if config.AttachStdin || config.AttachStdout || config.AttachStderr {
if config.Tty {
cli.monitorTtySize(out.ID)
if err := cli.monitorTtySize(runResult.ID); err != nil {
return err
}
}
v := url.Values{}
@@ -1295,11 +1316,16 @@ func (cli *DockerCli) CmdRun(args ...string) error {
if config.AttachStderr {
v.Set("stderr", "1")
}
if err := cli.hijack("POST", "/containers/"+out.ID+"/attach?"+v.Encode(), config.Tty, os.Stdin, os.Stdout); err != nil {
if err := cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, cli.in, cli.out); err != nil {
utils.Debugf("Error hijack: %s", err)
return err
}
}
if !config.AttachStdout && !config.AttachStderr {
<-wait
}
return nil
}
@@ -1426,7 +1452,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
return nil
}
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.File, out io.Writer) error {
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, out io.Writer) error {
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil)
if err != nil {
@@ -1453,17 +1479,26 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.Fi
return err
})
if in != nil && setRawTerminal && term.IsTerminal(in.Fd()) && os.Getenv("NORAW") == "" {
oldState, err := term.SetRawTerminal()
if in != nil && setRawTerminal && cli.isTerminal && os.Getenv("NORAW") == "" {
oldState, err := term.SetRawTerminal(cli.terminalFd)
if err != nil {
return err
}
defer term.RestoreTerminal(oldState)
defer term.RestoreTerminal(cli.terminalFd, oldState)
}
sendStdin := utils.Go(func() error {
io.Copy(rwc, in)
if err := rwc.(*net.TCPConn).CloseWrite(); err != nil {
utils.Debugf("Couldn't send EOF: %s\n", err)
if in != nil {
io.Copy(rwc, in)
}
if tcpc, ok := rwc.(*net.TCPConn); ok {
if err := tcpc.CloseWrite(); err != nil {
utils.Debugf("Couldn't send EOF: %s\n", err)
}
} else if unixc, ok := rwc.(*net.UnixConn); ok {
if err := unixc.CloseWrite(); err != nil {
utils.Debugf("Couldn't send EOF: %s\n", err)
}
}
// Discard errors due to pipe interruption
return nil
@@ -1474,7 +1509,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.Fi
return err
}
if !term.IsTerminal(in.Fd()) {
if !cli.isTerminal {
if err := <-sendStdin; err != nil {
utils.Debugf("Error sendStdin: %s", err)
return err
@@ -1485,7 +1520,10 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.Fi
}
func (cli *DockerCli) resizeTty(id string) {
ws, err := term.GetWinsize(os.Stdin.Fd())
if !cli.isTerminal {
return
}
ws, err := term.GetWinsize(cli.terminalFd)
if err != nil {
utils.Debugf("Error getting size: %s", err)
}
@@ -1497,7 +1535,10 @@ func (cli *DockerCli) resizeTty(id string) {
}
}
func (cli *DockerCli) monitorTtySize(id string) {
func (cli *DockerCli) monitorTtySize(id string) error {
if !cli.isTerminal {
return fmt.Errorf("Impossible to monitor size on non-tty")
}
cli.resizeTty(id)
c := make(chan os.Signal, 1)
@@ -1509,24 +1550,56 @@ func (cli *DockerCli) monitorTtySize(id string) {
}
}
}()
return nil
}
func Subcmd(name, signature, description string) *flag.FlagSet {
flags := flag.NewFlagSet(name, flag.ContinueOnError)
flags.Usage = func() {
fmt.Printf("\nUsage: docker %s %s\n\n%s\n\n", name, signature, description)
// FIXME: use custom stdout or return error
fmt.Fprintf(os.Stdout, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description)
flags.PrintDefaults()
}
return flags
}
func NewDockerCli(proto, addr string) *DockerCli {
func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli {
var (
isTerminal bool = false
terminalFd uintptr
)
if in != nil {
if file, ok := in.(*os.File); ok {
terminalFd = file.Fd()
isTerminal = term.IsTerminal(terminalFd)
}
}
if err == nil {
err = out
}
authConfig, _ := auth.LoadConfig(os.Getenv("HOME"))
return &DockerCli{proto, addr, authConfig}
return &DockerCli{
proto: proto,
addr: addr,
authConfig: authConfig,
in: in,
out: out,
err: err,
isTerminal: isTerminal,
terminalFd: terminalFd,
}
}
type DockerCli struct {
proto string
addr string
authConfig *auth.AuthConfig
in io.ReadCloser
out io.Writer
err io.Writer
isTerminal bool
terminalFd uintptr
}
+42 -54
View File
@@ -3,8 +3,9 @@ package docker
import (
"bufio"
"fmt"
"github.com/dotcloud/docker/utils"
"io"
_ "io/ioutil"
"io/ioutil"
"strings"
"testing"
"time"
@@ -59,20 +60,6 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error
}
/*TODO
func cmdWait(srv *Server, container *Container) error {
stdout, stdoutPipe := io.Pipe()
go func() {
srv.CmdWait(nil, stdoutPipe, container.Id)
}()
if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
return err
}
// Cleanup pipes
return closeWrap(stdout, stdoutPipe)
}
func cmdImages(srv *Server, args ...string) (string, error) {
stdout, stdoutPipe := io.Pipe()
@@ -144,41 +131,39 @@ func TestImages(t *testing.T) {
// todo: add checks for -a
}
*/
// TestRunHostname checks that 'docker run -h' correctly sets a custom hostname
func TestRunHostname(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
stdin, _ := io.Pipe()
stdout, stdoutPipe := io.Pipe()
cli := NewDockerCli(nil, stdoutPipe, nil, testDaemonProto, testDaemonAddr)
defer cleanup(globalRuntime)
c := make(chan struct{})
go func() {
if err := srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-h", "foobar", GetTestImage(runtime).Id, "hostname"); err != nil {
defer close(c)
if err := cli.CmdRun("-h", "foobar", unitTestImageId, "hostname"); err != nil {
t.Fatal(err)
}
close(c)
}()
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
if err != nil {
t.Fatal(err)
}
if cmdOutput != "foobar\n" {
t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", cmdOutput)
}
utils.Debugf("--")
setTimeout(t, "Reading command output time out", 2*time.Second, func() {
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
if err != nil {
t.Fatal(err)
}
if cmdOutput != "foobar\n" {
t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", cmdOutput)
}
})
setTimeout(t, "CmdRun timed out", 2*time.Second, func() {
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
<-c
cmdWait(srv, srv.runtime.List()[0])
})
}
/*
func TestRunExit(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
@@ -334,29 +319,27 @@ func TestRunDisconnectTty(t *testing.T) {
t.Fatalf("/bin/cat should still be running after closing stdin (tty mode)")
}
}
*/
// TestAttachStdin checks attaching to stdin without stdout and stderr.
// 'docker run -i -a stdin' should sends the client's stdin to the command,
// then detach from it and print the container id.
func TestRunAttachStdin(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
stdin, stdinPipe := io.Pipe()
stdout, stdoutPipe := io.Pipe()
cli := NewDockerCli(stdin, stdoutPipe, nil, testDaemonProto, testDaemonAddr)
defer cleanup(globalRuntime)
ch := make(chan struct{})
go func() {
srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", "-a", "stdin", GetTestImage(runtime).Id, "sh", "-c", "echo hello; cat")
close(ch)
defer close(ch)
cli.CmdRun("-i", "-a", "stdin", unitTestImageId, "sh", "-c", "echo hello && cat")
}()
// Send input to the command, close stdin
setTimeout(t, "Write timed out", 2*time.Second, func() {
setTimeout(t, "Write timed out", 10*time.Second, func() {
if _, err := stdinPipe.Write([]byte("hi there\n")); err != nil {
t.Fatal(err)
}
@@ -365,23 +348,27 @@ func TestRunAttachStdin(t *testing.T) {
}
})
container := runtime.List()[0]
container := globalRuntime.List()[0]
// Check output
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
if err != nil {
t.Fatal(err)
}
if cmdOutput != container.ShortId()+"\n" {
t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortId()+"\n", cmdOutput)
}
setTimeout(t, "Reading command output time out", 10*time.Second, func() {
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
if err != nil {
t.Fatal(err)
}
if cmdOutput != container.ShortID()+"\n" {
t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortID()+"\n", cmdOutput)
}
})
// wait for CmdRun to return
setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
setTimeout(t, "Waiting for CmdRun timed out", 5*time.Second, func() {
// Unblock hijack end
stdout.Read([]byte{})
<-ch
})
setTimeout(t, "Waiting for command to exit timed out", 2*time.Second, func() {
setTimeout(t, "Waiting for command to exit timed out", 5*time.Second, func() {
container.Wait()
})
@@ -400,6 +387,7 @@ func TestRunAttachStdin(t *testing.T) {
}
}
/*
// Expected behaviour, the process stays alive when the client disconnects
func TestAttachDisconnect(t *testing.T) {
runtime, err := newTestRuntime()
+104 -28
View File
@@ -52,6 +52,9 @@ type Container struct {
waitLock chan struct{}
Volumes map[string]string
// Store rw/ro in a separate structure to preserve reserve-compatibility on-disk.
// Easier than migrating older container configs :)
VolumesRW map[string]bool
}
type Config struct {
@@ -73,9 +76,20 @@ type Config struct {
Image string // Name of the image as it was passed by the operator (eg. could be symbolic)
Volumes map[string]struct{}
VolumesFrom string
Entrypoint []string
}
func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet, error) {
type HostConfig struct {
Binds []string
}
type BindMap struct {
SrcPath string
DstPath string
Mode string
}
func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
if len(args) > 0 && args[0] != "--help" {
cmd.SetOutput(ioutil.Discard)
@@ -110,12 +124,16 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet
cmd.Var(flVolumes, "v", "Attach a data volume")
flVolumesFrom := cmd.String("volumes-from", "", "Mount volumes from the specified container")
flEntrypoint := cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image")
var flBinds ListOpts
cmd.Var(&flBinds, "b", "Bind mount a volume from the host (e.g. -b /host:/container)")
if err := cmd.Parse(args); err != nil {
return nil, cmd, err
return nil, nil, cmd, err
}
if *flDetach && len(flAttach) > 0 {
return nil, cmd, fmt.Errorf("Conflicting options: -a and -d")
return nil, nil, cmd, fmt.Errorf("Conflicting options: -a and -d")
}
// If neither -d or -a are set, attach to everything by default
if len(flAttach) == 0 && !*flDetach {
@@ -127,8 +145,17 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet
}
}
}
// add any bind targets to the list of container volumes
for _, bind := range flBinds {
arr := strings.Split(bind, ":")
dstDir := arr[1]
flVolumes[dstDir] = struct{}{}
}
parsedArgs := cmd.Args()
runCmd := []string{}
entrypoint := []string{}
image := ""
if len(parsedArgs) >= 1 {
image = cmd.Arg(0)
@@ -136,6 +163,10 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet
if len(parsedArgs) > 1 {
runCmd = parsedArgs[1:]
}
if *flEntrypoint != "" {
entrypoint = []string{*flEntrypoint}
}
config := &Config{
Hostname: *flHostname,
PortSpecs: flPorts,
@@ -153,6 +184,10 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet
Image: image,
Volumes: flVolumes,
VolumesFrom: *flVolumesFrom,
Entrypoint: entrypoint,
}
hostConfig := &HostConfig{
Binds: flBinds,
}
if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
@@ -164,7 +199,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet
if config.OpenStdin && config.AttachStdin {
config.StdinOnce = true
}
return config, cmd, nil
return config, hostConfig, cmd, nil
}
type NetworkSettings struct {
@@ -430,7 +465,7 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
})
}
func (container *Container) Start() error {
func (container *Container) Start(hostConfig *HostConfig) error {
container.State.lock()
defer container.State.unlock()
@@ -454,17 +489,71 @@ func (container *Container) Start() error {
container.Config.MemorySwap = -1
}
container.Volumes = make(map[string]string)
container.VolumesRW = make(map[string]bool)
// Create the requested bind mounts
binds := make(map[string]BindMap)
// Define illegal container destinations
illegal_dsts := []string{"/", "."}
for _, bind := range hostConfig.Binds {
// FIXME: factorize bind parsing in parseBind
var src, dst, mode string
arr := strings.Split(bind, ":")
if len(arr) == 2 {
src = arr[0]
dst = arr[1]
mode = "rw"
} else if len(arr) == 3 {
src = arr[0]
dst = arr[1]
mode = arr[2]
} else {
return fmt.Errorf("Invalid bind specification: %s", bind)
}
// Bail if trying to mount to an illegal destination
for _, illegal := range illegal_dsts {
if dst == illegal {
return fmt.Errorf("Illegal bind destination: %s", dst)
}
}
bindMap := BindMap{
SrcPath: src,
DstPath: dst,
Mode: mode,
}
binds[path.Clean(dst)] = bindMap
}
// FIXME: evaluate volumes-from before individual volumes, so that the latter can override the former.
// Create the requested volumes volumes
for volPath := range container.Config.Volumes {
c, err := container.runtime.volumes.Create(nil, container, "", "", nil)
if err != nil {
return err
volPath = path.Clean(volPath)
// If an external bind is defined for this volume, use that as a source
if bindMap, exists := binds[volPath]; exists {
container.Volumes[volPath] = bindMap.SrcPath
if strings.ToLower(bindMap.Mode) == "rw" {
container.VolumesRW[volPath] = true
}
// Otherwise create an directory in $ROOT/volumes/ and use that
} else {
c, err := container.runtime.volumes.Create(nil, container, "", "", nil)
if err != nil {
return err
}
srcPath, err := c.layer()
if err != nil {
return err
}
container.Volumes[volPath] = srcPath
container.VolumesRW[volPath] = true // RW by default
}
// Create the mountpoint
if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
return nil
}
container.Volumes[volPath] = c.ID
}
if container.Config.VolumesFrom != "" {
@@ -552,7 +641,8 @@ func (container *Container) Start() error {
}
func (container *Container) Run() error {
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
return err
}
container.Wait()
@@ -565,7 +655,8 @@ func (container *Container) Output() (output []byte, err error) {
return nil, err
}
defer pipe.Close()
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
return nil, err
}
output, err = ioutil.ReadAll(pipe)
@@ -768,7 +859,8 @@ func (container *Container) Restart(seconds int) error {
if err := container.Stop(seconds); err != nil {
return err
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
return err
}
return nil
@@ -891,22 +983,6 @@ 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")
}
+128 -93
View File
@@ -7,6 +7,7 @@ import (
"io/ioutil"
"math/rand"
"os"
"path"
"regexp"
"sort"
"strings"
@@ -15,10 +16,7 @@ import (
)
func TestIDFormat(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
container1, err := NewBuilder(runtime).Create(
&Config{
@@ -39,10 +37,7 @@ func TestIDFormat(t *testing.T) {
}
func TestMultipleAttachRestart(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(
&Config{
@@ -70,7 +65,8 @@ func TestMultipleAttachRestart(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
l1, err := bufio.NewReader(stdout1).ReadString('\n')
@@ -111,7 +107,7 @@ func TestMultipleAttachRestart(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@@ -142,10 +138,7 @@ func TestMultipleAttachRestart(t *testing.T) {
}
func TestDiff(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
builder := NewBuilder(runtime)
@@ -251,10 +244,7 @@ func TestDiff(t *testing.T) {
}
func TestCommitAutoRun(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
builder := NewBuilder(runtime)
@@ -306,7 +296,8 @@ func TestCommitAutoRun(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container2.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container2.Start(hostConfig); err != nil {
t.Fatal(err)
}
container2.Wait()
@@ -330,10 +321,7 @@ func TestCommitAutoRun(t *testing.T) {
}
func TestCommitRun(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
builder := NewBuilder(runtime)
@@ -388,7 +376,8 @@ func TestCommitRun(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container2.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container2.Start(hostConfig); err != nil {
t.Fatal(err)
}
container2.Wait()
@@ -412,10 +401,7 @@ func TestCommitRun(t *testing.T) {
}
func TestStart(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(
&Config{
@@ -436,7 +422,8 @@ func TestStart(t *testing.T) {
t.Fatal(err)
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@@ -446,7 +433,7 @@ func TestStart(t *testing.T) {
if !container.State.Running {
t.Errorf("Container should be running")
}
if err := container.Start(); err == nil {
if err := container.Start(hostConfig); err == nil {
t.Fatalf("A running containter should be able to be started")
}
@@ -456,10 +443,7 @@ func TestStart(t *testing.T) {
}
func TestRun(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(
&Config{
@@ -484,10 +468,7 @@ func TestRun(t *testing.T) {
}
func TestOutput(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(
&Config{
@@ -509,10 +490,7 @@ func TestOutput(t *testing.T) {
}
func TestKillDifferentUser(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).ID,
@@ -528,7 +506,8 @@ func TestKillDifferentUser(t *testing.T) {
if container.State.Running {
t.Errorf("Container shouldn't be running")
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@@ -556,15 +535,33 @@ func TestKillDifferentUser(t *testing.T) {
}
}
func TestKill(t *testing.T) {
runtime, err := newTestRuntime()
// Test that creating a container with a volume doesn't crash. Regression test for #995.
func TestCreateVolume(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
config, hc, _, err := ParseRun([]string{"-v", "/var/lib/data", GetTestImage(runtime).ID, "echo", "hello", "world"}, nil)
if err != nil {
t.Fatal(err)
}
c, err := NewBuilder(runtime).Create(config)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(c)
if err := c.Start(hc); err != nil {
t.Fatal(err)
}
c.WaitTimeout(500 * time.Millisecond)
c.Wait()
}
func TestKill(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat", "/dev/zero"},
Cmd: []string{"sleep", "2"},
},
)
if err != nil {
@@ -575,7 +572,8 @@ func TestKill(t *testing.T) {
if container.State.Running {
t.Errorf("Container shouldn't be running")
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@@ -602,10 +600,7 @@ func TestKill(t *testing.T) {
}
func TestExitCode(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
builder := NewBuilder(runtime)
@@ -642,10 +637,7 @@ func TestExitCode(t *testing.T) {
}
func TestRestart(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).ID,
@@ -675,10 +667,7 @@ func TestRestart(t *testing.T) {
}
func TestRestartStdin(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).ID,
@@ -700,7 +689,8 @@ func TestRestartStdin(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
if _, err := io.WriteString(stdin, "hello world"); err != nil {
@@ -730,7 +720,7 @@ func TestRestartStdin(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
if _, err := io.WriteString(stdin, "hello world #2"); err != nil {
@@ -753,10 +743,7 @@ func TestRestartStdin(t *testing.T) {
}
func TestUser(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
builder := NewBuilder(runtime)
@@ -863,17 +850,14 @@ func TestUser(t *testing.T) {
}
func TestMultipleContainers(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
builder := NewBuilder(runtime)
container1, err := builder.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat", "/dev/zero"},
Cmd: []string{"sleep", "2"},
},
)
if err != nil {
@@ -883,7 +867,7 @@ func TestMultipleContainers(t *testing.T) {
container2, err := builder.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat", "/dev/zero"},
Cmd: []string{"sleep", "2"},
},
)
if err != nil {
@@ -892,10 +876,11 @@ func TestMultipleContainers(t *testing.T) {
defer runtime.Destroy(container2)
// Start both containers
if err := container1.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container1.Start(hostConfig); err != nil {
t.Fatal(err)
}
if err := container2.Start(); err != nil {
if err := container2.Start(hostConfig); err != nil {
t.Fatal(err)
}
@@ -922,10 +907,7 @@ func TestMultipleContainers(t *testing.T) {
}
func TestStdin(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).ID,
@@ -947,7 +929,8 @@ func TestStdin(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
defer stdin.Close()
@@ -969,10 +952,7 @@ func TestStdin(t *testing.T) {
}
func TestTty(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).ID,
@@ -994,7 +974,8 @@ func TestTty(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
defer stdin.Close()
@@ -1016,10 +997,7 @@ func TestTty(t *testing.T) {
}
func TestEnv(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).ID,
@@ -1036,7 +1014,8 @@ func TestEnv(t *testing.T) {
t.Fatal(err)
}
defer stdout.Close()
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
container.Wait()
@@ -1064,6 +1043,32 @@ func TestEnv(t *testing.T) {
}
}
func TestEntrypoint(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).ID,
Entrypoint: []string{"/bin/echo"},
Cmd: []string{"-n", "foobar"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
output, err := container.Output()
if err != nil {
t.Fatal(err)
}
if string(output) != "foobar" {
t.Error(string(output))
}
}
func grepFile(t *testing.T, path string, pattern string) {
f, err := os.Open(path)
if err != nil {
@@ -1085,10 +1090,7 @@ func grepFile(t *testing.T, path string, pattern string) {
}
func TestLXCConfig(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
// Memory is allocated randomly for testing
rand.Seed(time.Now().UTC().UnixNano())
@@ -1172,7 +1174,8 @@ func BenchmarkRunParallel(b *testing.B) {
return
}
defer runtime.Destroy(container)
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
complete <- err
return
}
@@ -1201,3 +1204,35 @@ func BenchmarkRunParallel(b *testing.B) {
b.Fatal(errors)
}
}
func tempDir(t *testing.T) string {
tmpDir, err := ioutil.TempDir("", "docker-test")
if err != nil {
t.Fatal(err)
}
return tmpDir
}
func TestBindMounts(t *testing.T) {
r := mkRuntime(t)
defer nuke(r)
tmpDir := tempDir(t)
defer os.RemoveAll(tmpDir)
writeFile(path.Join(tmpDir, "touch-me"), "", t)
// Test reading from a read-only bind mount
stdout, _ := runContainer(r, []string{"-b", fmt.Sprintf("%s:/tmp:ro", tmpDir), "_", "ls", "/tmp"}, t)
if !strings.Contains(stdout, "touch-me") {
t.Fatal("Container failed to read from bind mount")
}
// test writing to bind mount
runContainer(r, []string{"-b", fmt.Sprintf("%s:/tmp:rw", tmpDir), "_", "touch", "/tmp/holla"}, t)
readFile(path.Join(tmpDir, "holla"), t) // Will fail if the file doesn't exist
// test mounting to an illegal destination directory
if _, err := runContainer(r, []string{"-b", fmt.Sprintf("%s:.", tmpDir), "ls", "."}, nil); err == nil {
t.Fatal("Container bind mounted illegal directory")
}
}
+46
View File
@@ -0,0 +1,46 @@
#!/bin/bash
# Generate a very minimal filesystem based on busybox-static,
# and load it into the local docker under the name "docker-ut".
missing_pkg() {
echo "Sorry, I could not locate $1"
echo "Try 'apt-get install ${2:-$1}'?"
exit 1
}
BUSYBOX=$(which busybox)
[ "$BUSYBOX" ] || missing_pkg busybox busybox-static
SOCAT=$(which socat)
[ "$SOCAT" ] || missing_pkg socat
shopt -s extglob
set -ex
ROOTFS=`mktemp -d /tmp/rootfs-busybox.XXXXXXXXXX`
trap "rm -rf $ROOTFS" INT QUIT TERM
cd $ROOTFS
mkdir bin etc dev dev/pts lib proc sys tmp
touch etc/resolv.conf
cp /etc/nsswitch.conf etc/nsswitch.conf
echo root:x:0:0:root:/:/bin/sh > etc/passwd
echo root:x:0: > etc/group
ln -s lib lib64
ln -s bin sbin
cp $BUSYBOX $SOCAT bin
for X in $(busybox --list)
do
ln -s busybox bin/$X
done
rm bin/init
ln bin/busybox bin/init
cp -P /lib/x86_64-linux-gnu/lib{pthread*,c*(-*),dl*(-*),nsl*(-*),nss_*,util*(-*),wrap,z}.so* lib
cp /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 lib
cp -P /usr/lib/x86_64-linux-gnu/lib{crypto,ssl}.so* lib
for X in console null ptmx random stdin stdout stderr tty urandom zero
do
cp -a /dev/$X dev
done
tar -cf- . | docker import - docker-ut
docker run -i -u root docker-ut /bin/echo Success.
rm -rf $ROOTFS
+4 -3
View File
@@ -30,6 +30,7 @@ func main() {
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")
flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.")
flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
flDns := flag.String("dns", "", "Set custom dns servers")
flHosts := docker.ListOpts{fmt.Sprintf("tcp://%s:%d", docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT)}
@@ -56,7 +57,7 @@ func main() {
flag.Usage()
return
}
if err := daemon(*pidfile, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil {
if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil {
log.Fatal(err)
os.Exit(-1)
}
@@ -100,7 +101,7 @@ func removePidFile(pidfile string) {
}
}
func daemon(pidfile string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error {
func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error {
if err := createPidFile(pidfile); err != nil {
log.Fatal(err)
}
@@ -118,7 +119,7 @@ func daemon(pidfile string, protoAddrs []string, autoRestart, enableCors bool, f
if flDns != "" {
dns = []string{flDns}
}
server, err := docker.NewServer(autoRestart, enableCors, dns)
server, err := docker.NewServer(flGraphPath, autoRestart, enableCors, dns)
if err != nil {
return err
}
+8
View File
@@ -42,6 +42,9 @@ List containers (/containers/json):
- You can use size=1 to get the size of the containers
Start containers (/containers/<id>/start):
- You can now pass host-specific configuration (e.g. bind mounts) in the POST body for start calls
:doc:`docker_remote_api_v1.2`
*****************************
@@ -120,8 +123,13 @@ and we will add the libraries here.
+----------------------+----------------+--------------------------------------------+
| Ruby | docker-client | https://github.com/geku/docker-client |
+----------------------+----------------+--------------------------------------------+
| Ruby | docker-api | https://github.com/swipely/docker-api |
+----------------------+----------------+--------------------------------------------+
| Javascript | docker-js | https://github.com/dgoujard/docker-js |
+----------------------+----------------+--------------------------------------------+
| Javascript (Angular) | dockerui | https://github.com/crosbymichael/dockerui |
| **WebUI** | | |
+----------------------+----------------+--------------------------------------------+
| Java | docker-java | https://github.com/kpelykh/docker-java |
+----------------------+----------------+--------------------------------------------+
+20 -13
View File
@@ -294,23 +294,30 @@ Start a container
.. http:post:: /containers/(id)/start
Start the container ``id``
Start the container ``id``
**Example request**:
**Example request**:
.. sourcecode:: http
.. sourcecode:: http
POST /containers/e90e34656806/start HTTP/1.1
**Example response**:
POST /containers/(id)/start HTTP/1.1
Content-Type: application/json
.. sourcecode:: http
{
"Binds":["/tmp:/tmp"]
}
HTTP/1.1 200 OK
:statuscode 200: no error
:statuscode 404: no such container
:statuscode 500: server error
**Example response**:
.. sourcecode:: http
HTTP/1.1 204 No Content
Content-Type: text/plain
:jsonparam hostConfig: the container's host configuration (optional)
:statuscode 200: no error
:statuscode 404: no such container
:statuscode 500: server error
Stop a contaier
@@ -793,7 +800,7 @@ Remove an image
{"Deleted":"53b4f83ac9"}
]
:statuscode 204: no error
:statuscode 200: no error
:statuscode 404: no such image
:statuscode 409: conflict
:statuscode 500: server error
+2
View File
@@ -25,3 +25,5 @@
-d=[]: Set custom dns servers for the container
-v=[]: Creates a new volume and mounts it at the specified path.
-volumes-from="": Mount all volumes from the given container.
-b=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]
-entrypoint="": Overwrite the default entrypoint set by the image.
+1
View File
@@ -30,6 +30,7 @@ import sys, os
html_additional_pages = {
'concepts/containers': 'redirect_home.html',
'concepts/introduction': 'redirect_home.html',
'builder/basics': 'redirect_build.html',
}
+97
View File
@@ -0,0 +1,97 @@
:title: Image & Container
:description: Definitions of an image and container
:keywords: containers, lxc, concepts, explanation, image, container
File Systems
============
.. image:: images/docker-filesystems-generic.png
In order for a Linux system to run, it typically needs two `file
systems <http://en.wikipedia.org/wiki/Filesystem>`_:
1. boot file system (bootfs)
2. root file system (rootfs)
The **boot file system** contains the bootloader and the kernel. The
user never makes any changes to the boot file system. In fact, soon
after the boot process is complete, the entire kernel is in memory,
and the boot file system is unmounted to free up the RAM associated
with the initrd disk image.
The **root file system** includes the typical directory structure we
associate with Unix-like operating systems: ``/dev, /proc, /bin, /etc,
/lib, /usr,`` and ``/tmp`` plus all the configuration files, binaries
and libraries required to run user applications (like bash, ls, and so
forth).
While there can be important kernal differences between different
Linux distributions, the contents and organization of the root file
system are usually what make your software packages dependent on one
distribution versus another. Docker can help solve this problem by
running multiple distributions at the same time.
.. image:: images/docker-filesystems-multiroot.png
Layers and Union Mounts
=======================
In a traditional Linux boot, the kernel first mounts the root file
system as read-only, checks its integrity, and then switches the whole
rootfs volume to read-write mode. Docker does something similar,
*except* that instead of changing the file system to read-write mode,
it takes advantage of a `union mount
<http://en.wikipedia.org/wiki/Union_mount>`_ to add a read-write file
system *over* the read-only file system. In fact there may be multiple
read-only file systems stacked on top of each other.
.. image:: images/docker-filesystems-multilayer.png
At first, the top layer has nothing in it, but any time a process
creates a file, this happens in the top layer. And if something needs
to update an existing file in a lower layer, then the file gets copied
to the upper layer and changes go into the copy. The version of the
file on the lower layer cannot be seen by the applications anymore,
but it is there, unchanged.
We call the union of the read-write layer and all the read-only layers
a **union file system**.
Image
=====
In Docker terminology, a read-only layer is called an **image**. An
image never changes. Because Docker uses a union file system, the
applications think the whole file system is mounted read-write,
because any file can be changed. But all the changes go to the
top-most layer, and underneath, the image is unchanged. Since they
don't change, images do not have state.
Each image may depend on one more image which forms the layer beneath
it. We sometimes say that the lower image is the **parent** of the
upper image.
Base Image
==========
An image that has no parent is a **base image**.
Container
=========
Once you start a process in Docker from an image, Docker fetches the
image and its parent, and repeats the process until it reaches the
base image. Then the union file system adds a read-write layer on
top. That read-write layer, plus the information about its parent and
some additional information like its unique id, is called a
**container**.
Containers can change, and so they have state. A container may be
running or exited. In either case, the state of the file system and
its exit value is preserved. You can start, stop, and restart a
container. The processes restart from scratch (their memory state is
**not** preserved in a container), but the file system is just as it
was when the container was stopped.
You can promote a container to an image with ``docker commit``. Once a
container is an image, you can use it as a parent for new containers.
Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 243 KiB

+18
View File
@@ -0,0 +1,18 @@
:title: Terms
:description: Definitions of terms used in Docker documentation
:keywords: concepts, documentation, docker, containers
Terms
=====
Definitions of terms used in Docker documentation.
Contents:
.. toctree::
:maxdepth: 1
fundamentals
+1
View File
@@ -18,5 +18,6 @@ This documentation has the following resources:
contributing/index
api/index
faq
terms/index
.. image:: concepts/images/lego_docker.jpg
+7
View File
@@ -153,6 +153,13 @@ of `<src>` will be written at `<dst>`.
If `<dest>` doesn't exist, it is created along with all missing directories in its path. All new
files and directories are created with mode 0700, uid and gid 0.
2.8 ENTRYPOINT
-------------
``ENTRYPOINT /bin/echo``
The `ENTRYPOINT` instruction adds an entry command that will not be overwritten when arguments are passed to docker run, unlike the behavior of `CMD`. This allows arguments to be passed to the entrypoint. i.e. `docker run <image> -d` will pass the "-d" argument to the entrypoint.
3. Dockerfile Examples
======================
+4 -1
View File
@@ -40,8 +40,11 @@
{%- set script_files = script_files + ['_static/js/docs.js'] %}
{%- if pagename == 'index' %}
<link rel="canonical" href="http://docs.docker.io/en/latest/">
{% else %}
<link rel="canonical" href="http://docs.docker.io/en/latest/{{ pagename }}/">
{% endif %}
{%- for cssfile in css_files %}
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
{%- endfor %}
+12
View File
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Moved</title>
<meta http-equiv="refresh" content="0; url=http://docs.docker.io/en/latest/use/builder/">
</head>
<body>
This page has moved. Perhaps you should visit the <a href="http://docs.docker.io/en/latest/use/builder/" title="builder page">Builder page</a>
</body>
</html>
+1 -1
View File
@@ -2,7 +2,7 @@
<html>
<head>
<title>Page Moved</title>
<meta http-equiv="refresh" content="0; url=http://docks.docker.io/en/latest/">
<meta http-equiv="refresh" content="0; url=http://docs.docker.io/en/latest/">
</head>
<body>
+1 -1
View File
@@ -189,7 +189,7 @@ func (graph *Graph) Mktemp(id string) (string, error) {
return "", fmt.Errorf("Couldn't create temp: %s", err)
}
if tmp.Exists(id) {
return "", fmt.Errorf("Image %d already exists", id)
return "", fmt.Errorf("Image %s already exists", id)
}
return tmp.imageRoot(id), nil
}
+4 -3
View File
@@ -3,8 +3,8 @@ 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
Set up an Ubuntu 12.04 virtual machine for developers including kernel 3.8
go1.1 and buildbot. The environment is setup in a way that can be used through
the usual go workflow and/or the root Makefile. You can either edit on
your host, or inside the VM (using make ssh-dev) and run and test docker
inside the VM.
@@ -22,6 +22,7 @@ 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.
post-commit hook that automatically run docker tests for you each time you
commit in your local docker repository.
You can check your buildbot instance at http://192.168.33.21:8010/waterfall
+119
View File
@@ -0,0 +1,119 @@
## A maintainer's guide to releasing Docker
So you're in charge of a docker release? Cool. Here's what to do.
If your experience deviates from this document, please document the changes to keep it
up-to-date.
### 1. Pull from master and create a release branch
```bash
$ git checkout master
$ git pull
$ git checkout -b bump_$VERSION
```
### 2. Update CHANGELOG.md
You can run this command for reference:
```bash
LAST_VERSION=$(git tag | grep -E "v[0-9\.]+$" | sort -nr | head -n 1)
git log $LAST_VERSION..HEAD
```
Each change should be formatted as ```BULLET CATEGORY: DESCRIPTION```
* BULLET is either ```-```, ```+``` or ```*```, to indicate a bugfix,
new feature or upgrade, respectively.
* CATEGORY should describe which part of the project is affected.
Valid categories are:
* Runtime
* Remote API
* Builder
* Documentation
* Hack
* DESCRIPTION: a concise description of the change that is relevant to the end-user,
using the present tense.
Changes should be described in terms of how they affect the user, for example "new feature
X which allows Y", "fixed bug which caused X", "increased performance of Y".
EXAMPLES:
```
+ Builder: 'docker build -t FOO' applies the tag FOO to the newly built container.
* Runtime: improve detection of kernel version
- Remote API: fix a bug in the optional unix socket transport
```
### 3. Change VERSION in commands.go
### 4. Run all tests
### 5. Commit and create a pull request
```bash
$ git add commands.go CHANGELOG.md
$ git commit -m "Bump version to $VERSION"
$ git push origin bump_$VERSION
```
### 6. Get 2 other maintainers to validate the pull request
### 7. Merge the pull request and apply tags
```bash
$ git checkout master
$ git merge bump_$VERSION
$ git tag -a v$VERSION # Don't forget the v!
$ git tag -f -a latest
$ git push
$ git push --tags
```
### 8. Publish binaries
To run this you will need access to the release credentials.
Get them from [the infrastructure maintainers](https://github.com/dotcloud/docker/blob/master/hack/infrastructure/MAINTAINERS).
```bash
$ RELEASE_IMAGE=image_provided_by_infrastructure_maintainers
$ BUILD=$(docker run -d -e RELEASE_PPA=0 $RELEASE_IMAGE)
```
This will do 2 things:
* It will build and upload the binaries on http://get.docker.io
* It will *test* the release on our Ubuntu PPA (a PPA is a community repository for ubuntu packages)
Wait for the build to complete.
```bash
$ docker wait $BUILD # This should print 0. If it doesn't, your build failed.
```
Check that the output looks OK. Here's an example of a correct output:
```bash
$ docker logs 2>&1 b4e7c8299d73 | grep -e 'Public URL' -e 'Successfully uploaded'
Public URL of the object is: http://get.docker.io.s3.amazonaws.com/builds/Linux/x86_64/docker-v0.4.7.tgz
Public URL of the object is: http://get.docker.io.s3.amazonaws.com/builds/Linux/x86_64/docker-latest.tgz
Successfully uploaded packages.
```
If you don't see 3 lines similar to this, something might be wrong. Check the full logs and try again.
### 9. Publish Ubuntu packages
If everything went well in the previous step, you can finalize the release by submitting the Ubuntu packages.
```bash
$ RELEASE_IMAGE=image_provided_by_infrastructure_maintainers
$ docker run -e RELEASE_PPA=1 $RELEASE_IMAGE
```
If that goes well, congratulations! You're done.
+5 -4
View File
@@ -2,7 +2,7 @@
# vi: set ft=ruby :
BOX_NAME = "ubuntu-dev"
BOX_URI = "http://cloud-images.ubuntu.com/raring/current/raring-server-cloudimg-vagrant-amd64-disk1.box"
BOX_URI = "http://files.vagrantup.com/precise64.box"
VM_IP = "192.168.33.21"
USER = "vagrant"
GOPATH = "/data/docker"
@@ -21,14 +21,15 @@ Vagrant::Config.run do |config|
# 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 git aufs-tools golang make linux-image-extra-3.8.0-19-generic; " \
pkg_cmd << "apt-get update -qq; apt-get install -y python-software-properties; " \
"add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu; apt-get update -qq; " \
"apt-get install -y linux-image-generic-lts-raring lxc git aufs-tools golang-stable make; " \
"chown -R #{USER}.#{USER} #{GOPATH}; " \
"install -m 0664 #{CFG_PATH}/bash_profile /home/#{USER}/.bash_profile"
config.vm.provision :shell, :inline => pkg_cmd
# Deploy buildbot CI
pkg_cmd = "apt-get install -q -y python-dev python-pip supervisor; " \
"pip install -r #{CFG_PATH}/requirements.txt; " \
"pip install -q -r #{CFG_PATH}/requirements.txt; " \
"chown #{USER}.#{USER} /data; cd /data; " \
"#{CFG_PATH}/setup.sh #{USER} #{GOPATH} #{DOCKER_PATH} #{CFG_PATH} #{BUILDBOT_PATH}"
config.vm.provision :shell, :inline => pkg_cmd
+5 -3
View File
@@ -92,9 +92,11 @@ func StoreImage(img *Image, layerData Archive, root string, store bool) error {
defer file.Close()
layerData = file
}
if err := Untar(layerData, layer); err != nil {
return err
// If layerData is not nil, unpack it into the new layer
if layerData != nil {
if err := Untar(layerData, layer); err != nil {
return err
}
}
return StoreSize(img, root)
+3 -2
View File
@@ -84,8 +84,9 @@ 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
{{ $rw := .VolumesRW }}
{{range $virtualPath, $realPath := .Volumes}}
lxc.mount.entry = {{$realPath}} {{$ROOTFS}}/{{$virtualPath}} none bind,{{ if index $rw $virtualPath }}rw{{else}}ro{{end}} 0 0
{{end}}
{{end}}
+18 -7
View File
@@ -18,6 +18,14 @@ import (
var ErrAlreadyExists = errors.New("Image already exists")
func UrlScheme() string {
u, err := url.Parse(auth.IndexServerAddress())
if err != nil {
return "https"
}
return u.Scheme
}
func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) {
for _, cookie := range c.Jar.Cookies(req.URL) {
req.AddCookie(cookie)
@@ -56,20 +64,19 @@ func (r *Registry) GetRemoteHistory(imgId, registry string, token []string) ([]s
}
// Check if an image exists in the Registry
func (r *Registry) LookupRemoteImage(imgId, registry string, authConfig *auth.AuthConfig) bool {
func (r *Registry) LookupRemoteImage(imgId, registry string, token []string) bool {
rt := &http.Transport{Proxy: http.ProxyFromEnvironment}
req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil)
if err != nil {
return false
}
req.SetBasicAuth(authConfig.Username, authConfig.Password)
res, err := rt.RoundTrip(req)
if err != nil {
return false
}
res.Body.Close()
return res.StatusCode == 307
return res.StatusCode == 200
}
func (r *Registry) getImagesInRepository(repository string, authConfig *auth.AuthConfig) ([]map[string]string, error) {
@@ -155,7 +162,10 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [
repository = "library/" + repository
}
for _, host := range registries {
endpoint := fmt.Sprintf("https://%s/v1/repositories/%s/tags", host, repository)
endpoint := fmt.Sprintf("%s/v1/repositories/%s/tags", host, repository)
if !(strings.HasPrefix(endpoint, "http://") || strings.HasPrefix(endpoint, "https://")) {
endpoint = fmt.Sprintf("%s://%s", UrlScheme(), endpoint)
}
req, err := r.opaqueRequest("GET", endpoint, nil)
if err != nil {
return nil, err
@@ -165,6 +175,7 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [
if err != nil {
return nil, err
}
utils.Debugf("Got status code %d from %s", res.StatusCode, endpoint)
defer res.Body.Close()
@@ -249,7 +260,7 @@ func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) {
// Push a local image to the registry
func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error {
registry = "https://" + registry + "/v1"
registry = registry + "/v1"
// FIXME: try json with UTF8
req, err := http.NewRequest("PUT", registry+"/images/"+imgData.ID+"/json", strings.NewReader(string(jsonRaw)))
if err != nil {
@@ -285,7 +296,7 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis
}
func (r *Registry) PushImageLayerRegistry(imgId string, layer io.Reader, registry string, token []string) error {
registry = "https://" + registry + "/v1"
registry = registry + "/v1"
req, err := http.NewRequest("PUT", registry+"/images/"+imgId+"/layer", layer)
if err != nil {
return err
@@ -323,7 +334,7 @@ func (r *Registry) opaqueRequest(method, urlStr string, body io.Reader) (*http.R
func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token []string) error {
// "jsonify" the string
revision = "\"" + revision + "\""
registry = "https://" + registry + "/v1"
registry = registry + "/v1"
req, err := r.opaqueRequest("PUT", registry+"/repositories/"+remote+"/tags/"+tag, strings.NewReader(revision))
if err != nil {
+5 -3
View File
@@ -144,7 +144,9 @@ func (runtime *Runtime) Register(container *Container) error {
utils.Debugf("Restarting")
container.State.Ghost = false
container.State.setStopped(0)
if err := container.Start(); err != nil {
// assume empty host config
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
return err
}
nomonitor = true
@@ -246,8 +248,8 @@ func (runtime *Runtime) UpdateCapabilities(quiet bool) {
}
// FIXME: harmonize with NewGraph()
func NewRuntime(autoRestart bool, dns []string) (*Runtime, error) {
runtime, err := NewRuntimeFromDirectory("/var/lib/docker", autoRestart)
func NewRuntime(flGraphPath string, autoRestart bool, dns []string) (*Runtime, error) {
runtime, err := NewRuntimeFromDirectory(flGraphPath, autoRestart)
if err != nil {
return nil, err
}
+49 -13
View File
@@ -8,17 +8,23 @@ import (
"log"
"net"
"os"
"os/user"
"strconv"
"strings"
"sync"
"syscall"
"testing"
"time"
)
const unitTestImageName string = "docker-ut"
const unitTestImageId string = "e9aa60c60128cad1"
const unitTestStoreBase string = "/var/lib/docker/unit-tests"
const (
unitTestImageName = "docker-unit-tests"
unitTestImageId = "e9aa60c60128cad1"
unitTestStoreBase = "/var/lib/docker/unit-tests"
testDaemonAddr = "127.0.0.1:4270"
testDaemonProto = "tcp"
)
var globalRuntime *Runtime
func nuke(runtime *Runtime) error {
var wg sync.WaitGroup
@@ -33,6 +39,23 @@ func nuke(runtime *Runtime) error {
return os.RemoveAll(runtime.root)
}
func cleanup(runtime *Runtime) error {
for _, container := range runtime.List() {
container.Kill()
runtime.Destroy(container)
}
images, err := runtime.graph.All()
if err != nil {
return err
}
for _, image := range images {
if image.ID != unitTestImageId {
runtime.graph.Delete(image.ID)
}
}
return nil
}
func layerArchive(tarfile string) (io.Reader, error) {
// FIXME: need to close f somewhere
f, err := os.Open(tarfile)
@@ -49,10 +72,8 @@ func init() {
return
}
if usr, err := user.Current(); err != nil {
panic(err)
} else if usr.Uid != "0" {
panic("docker tests needs to be run as root")
if uid := syscall.Geteuid(); uid != 0 {
log.Fatal("docker tests needs to be run as root")
}
NetworkBridgeIface = "testdockbr0"
@@ -62,6 +83,7 @@ func init() {
if err != nil {
panic(err)
}
globalRuntime = runtime
// Create the "Server"
srv := &Server{
@@ -75,6 +97,15 @@ func init() {
if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout, utils.NewStreamFormatter(false), nil); err != nil {
panic(err)
}
// Spawn a Daemon
go func() {
if err := ListenAndServe(testDaemonProto, testDaemonAddr, srv, os.Getenv("DEBUG") != ""); err != nil {
panic(err)
}
}()
// Give some time to ListenAndServer to actually start
time.Sleep(time.Second)
}
// FIXME: test that ImagePull(json=true) send correct json output
@@ -103,10 +134,13 @@ func GetTestImage(runtime *Runtime) *Image {
imgs, err := runtime.graph.All()
if err != nil {
panic(err)
} else if len(imgs) < 1 {
panic("GASP")
}
return imgs[0]
for i := range imgs {
if imgs[i].ID == unitTestImageId {
return imgs[i]
}
}
panic(fmt.Errorf("Test image %v not found", unitTestImageId))
}
func TestRuntimeCreate(t *testing.T) {
@@ -295,7 +329,8 @@ func findAvailalblePort(runtime *Runtime, port int) (*Container, error) {
if err != nil {
return nil, err
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
if strings.Contains(err.Error(), "address already in use") {
return nil, nil
}
@@ -405,7 +440,8 @@ func TestRestore(t *testing.T) {
defer runtime1.Destroy(container2)
// Start the container non blocking
if err := container2.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container2.Start(hostConfig); err != nil {
t.Fatal(err)
}
+93 -39
View File
@@ -87,7 +87,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils.
}
defer file.Body.Close()
config, _, err := ParseRun([]string{img.ID, "echo", "insert", url, path}, srv.runtime.capabilities)
config, _, _, err := ParseRun([]string{img.ID, "echo", "insert", url, path}, srv.runtime.capabilities)
if err != nil {
return "", err
}
@@ -351,26 +351,49 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
return nil
}
func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, remote, askedTag string, sf *utils.StreamFormatter) error {
func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, remote, askedTag, registryEp string, sf *utils.StreamFormatter) error {
out.Write(sf.FormatStatus("Pulling repository %s from %s", local, auth.IndexServerAddress()))
repoData, err := r.GetRepositoryData(remote)
if err != nil {
return err
}
utils.Debugf("Updating checksums")
// Reload the json file to make sure not to overwrite faster sums
if err := srv.runtime.graph.UpdateChecksums(repoData.ImgList); err != nil {
return err
var repoData *registry.RepositoryData
var err error
if registryEp == "" {
repoData, err = r.GetRepositoryData(remote)
if err != nil {
return err
}
utils.Debugf("Updating checksums")
// Reload the json file to make sure not to overwrite faster sums
if err := srv.runtime.graph.UpdateChecksums(repoData.ImgList); err != nil {
return err
}
} else {
repoData = &registry.RepositoryData{
Tokens: []string{},
ImgList: make(map[string]*registry.ImgData),
Endpoints: []string{registryEp},
}
}
utils.Debugf("Retrieving the tag list")
tagsList, err := r.GetRemoteTags(repoData.Endpoints, remote, repoData.Tokens)
if err != nil {
utils.Debugf("%v", err)
return err
}
if registryEp != "" {
for tag, id := range tagsList {
repoData.ImgList[id] = &registry.ImgData{
ID: id,
Tag: tag,
Checksum: "",
}
}
}
utils.Debugf("Registering tags")
// If not specific tag have been asked, take all
// If no tag has been specified, pull them all
if askedTag == "" {
for tag, id := range tagsList {
repoData.ImgList[id].Tag = tag
@@ -392,7 +415,10 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, re
out.Write(sf.FormatStatus("Pulling image %s (%s) from %s", img.ID, img.Tag, remote))
success := false
for _, ep := range repoData.Endpoints {
if err := srv.pullImage(r, out, img.ID, "https://"+ep+"/v1", repoData.Tokens, sf); err != nil {
if !(strings.HasPrefix(ep, "http://") || strings.HasPrefix(ep, "https://")) {
ep = fmt.Sprintf("%s://%s", registry.UrlScheme(), ep)
}
if err := srv.pullImage(r, out, img.ID, ep+"/v1", repoData.Tokens, sf); err != nil {
out.Write(sf.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint", askedTag, err))
continue
}
@@ -452,7 +478,6 @@ func (srv *Server) poolRemove(kind, key string) error {
}
return nil
}
func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
r, err := registry.NewRegistry(srv.runtime.root, authConfig)
if err != nil {
@@ -463,21 +488,20 @@ func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *util
}
defer srv.poolRemove("pull", name+":"+tag)
out = utils.NewWriteFlusher(out)
if endpoint != "" {
if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil {
return err
}
return nil
}
remote := name
parts := strings.Split(name, "/")
if len(parts) > 2 {
remote = fmt.Sprintf("src/%s", url.QueryEscape(strings.Join(parts, "/")))
}
if err := srv.pullRepository(r, out, name, remote, tag, sf); err != nil {
return err
out = utils.NewWriteFlusher(out)
err = srv.pullRepository(r, out, name, remote, tag, endpoint, sf)
if err != nil && endpoint != "" {
if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil {
return err
}
return nil
}
return nil
}
@@ -547,7 +571,7 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat
return imgList, nil
}
func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name string, localRepo map[string]string, sf *utils.StreamFormatter) error {
func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name, registryEp string, localRepo map[string]string, sf *utils.StreamFormatter) error {
out = utils.NewWriteFlusher(out)
out.Write(sf.FormatStatus("Processing checksums"))
imgList, err := srv.getImageList(localRepo)
@@ -555,25 +579,51 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name stri
return err
}
out.Write(sf.FormatStatus("Sending image list"))
srvName := name
parts := strings.Split(name, "/")
if len(parts) > 2 {
srvName = fmt.Sprintf("src/%s", url.QueryEscape(strings.Join(parts, "/")))
}
repoData, err := r.PushImageJSONIndex(srvName, imgList, false, nil)
if err != nil {
return err
var repoData *registry.RepositoryData
if registryEp == "" {
repoData, err = r.PushImageJSONIndex(name, imgList, false, nil)
if err != nil {
return err
}
} else {
repoData = &registry.RepositoryData{
ImgList: make(map[string]*registry.ImgData),
Tokens: []string{},
Endpoints: []string{registryEp},
}
tagsList, err := r.GetRemoteTags(repoData.Endpoints, name, repoData.Tokens)
if err != nil && err.Error() != "Repository not found" {
return err
} else if err == nil {
for tag, id := range tagsList {
repoData.ImgList[id] = &registry.ImgData{
ID: id,
Tag: tag,
Checksum: "",
}
}
}
}
for _, ep := range repoData.Endpoints {
if !(strings.HasPrefix(ep, "http://") || strings.HasPrefix(ep, "https://")) {
ep = fmt.Sprintf("%s://%s", registry.UrlScheme(), ep)
}
out.Write(sf.FormatStatus("Pushing repository %s to %s (%d tags)", name, ep, len(localRepo)))
// For each image within the repo, push them
for _, elem := range imgList {
if _, exists := repoData.ImgList[elem.ID]; exists {
out.Write(sf.FormatStatus("Image %s already on registry, skipping", name))
continue
} else if registryEp != "" && r.LookupRemoteImage(elem.ID, registryEp, repoData.Tokens) {
fmt.Fprintf(out, "Image %s already on registry, skipping\n", name)
continue
}
if err := srv.pushImage(r, out, name, elem.ID, ep, repoData.Tokens, sf); err != nil {
// FIXME: Continue on error?
@@ -586,9 +636,12 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name stri
}
}
if _, err := r.PushImageJSONIndex(srvName, imgList, true, repoData.Endpoints); err != nil {
return err
if registryEp == "" {
if _, err := r.PushImageJSONIndex(name, imgList, true, repoData.Endpoints); err != nil {
return err
}
}
return nil
}
@@ -665,11 +718,12 @@ func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.Str
if err2 != nil {
return err2
}
if err != nil {
out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name])))
// If it fails, try to get the repository
if localRepo, exists := srv.runtime.repositories.Repositories[name]; exists {
if err := srv.pushRepository(r, out, name, localRepo, sf); err != nil {
if err := srv.pushRepository(r, out, name, endpoint, localRepo, sf); err != nil {
return err
}
return nil
@@ -858,7 +912,7 @@ func (srv *Server) deleteImageParents(img *Image, imgs *[]APIRmi) error {
return nil
}
func (srv *Server) deleteImage(img *Image, repoName, tag string) (*[]APIRmi, error) {
func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, error) {
//Untag the current image
var imgs []APIRmi
tagDeleted, err := srv.runtime.repositories.Delete(repoName, tag)
@@ -871,18 +925,18 @@ func (srv *Server) deleteImage(img *Image, repoName, tag string) (*[]APIRmi, err
if len(srv.runtime.repositories.ByID()[img.ID]) == 0 {
if err := srv.deleteImageAndChildren(img.ID, &imgs); err != nil {
if err != ErrImageReferenced {
return &imgs, err
return imgs, err
}
} else if err := srv.deleteImageParents(img, &imgs); err != nil {
if err != ErrImageReferenced {
return &imgs, err
return imgs, err
}
}
}
return &imgs, nil
return imgs, nil
}
func (srv *Server) ImageDelete(name string, autoPrune bool) (*[]APIRmi, error) {
func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) {
img, err := srv.runtime.repositories.LookupImage(name)
if err != nil {
return nil, fmt.Errorf("No such image: %s", name)
@@ -934,9 +988,9 @@ func (srv *Server) ImageGetCached(imgId string, config *Config) (*Image, error)
return nil, nil
}
func (srv *Server) ContainerStart(name string) error {
func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error {
if container := srv.runtime.Get(name); container != nil {
if err := container.Start(); err != nil {
if err := container.Start(hostConfig); err != nil {
return fmt.Errorf("Error starting container %s: %s", name, err.Error())
}
} else {
@@ -1049,11 +1103,11 @@ func (srv *Server) ImageInspect(name string) (*Image, error) {
return nil, fmt.Errorf("No such image: %s", name)
}
func NewServer(autoRestart, enableCors bool, dns ListOpts) (*Server, error) {
func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) (*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(autoRestart, dns)
runtime, err := NewRuntime(flGraphPath, autoRestart, dns)
if err != nil {
return nil, err
}
+15 -10
View File
@@ -13,6 +13,11 @@ func TestContainerTagImageDelete(t *testing.T) {
srv := &Server{runtime: runtime}
initialImages, err := srv.Images(false, "")
if err != nil {
t.Fatal(err)
}
if err := srv.runtime.repositories.Set("utest", "tag1", unitTestImageName, false); err != nil {
t.Fatal(err)
}
@@ -25,8 +30,8 @@ func TestContainerTagImageDelete(t *testing.T) {
t.Fatal(err)
}
if len(images) != 3 {
t.Errorf("Excepted 3 images, %d found", len(images))
if len(images) != len(initialImages)+2 {
t.Errorf("Excepted %d images, %d found", len(initialImages)+2, len(images))
}
if _, err := srv.ImageDelete("utest/docker:tag2", true); err != nil {
@@ -38,8 +43,8 @@ func TestContainerTagImageDelete(t *testing.T) {
t.Fatal(err)
}
if len(images) != 2 {
t.Errorf("Excepted 2 images, %d found", len(images))
if len(images) != len(initialImages)+1 {
t.Errorf("Excepted %d images, %d found", len(initialImages)+1, len(images))
}
if _, err := srv.ImageDelete("utest:tag1", true); err != nil {
@@ -51,8 +56,8 @@ func TestContainerTagImageDelete(t *testing.T) {
t.Fatal(err)
}
if len(images) != 1 {
t.Errorf("Excepted 1 image, %d found", len(images))
if len(images) != len(initialImages) {
t.Errorf("Excepted %d image, %d found", len(initialImages), len(images))
}
}
@@ -65,7 +70,7 @@ func TestCreateRm(t *testing.T) {
srv := &Server{runtime: runtime}
config, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
if err != nil {
t.Fatal(err)
}
@@ -98,7 +103,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
srv := &Server{runtime: runtime}
config, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil)
config, hostConfig, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil)
if err != nil {
t.Fatal(err)
}
@@ -112,7 +117,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
t.Errorf("Expected 1 container, %v found", len(runtime.List()))
}
err = srv.ContainerStart(id)
err = srv.ContainerStart(id, hostConfig)
if err != nil {
t.Fatal(err)
}
@@ -127,7 +132,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
t.Fatal(err)
}
err = srv.ContainerStart(id)
err = srv.ContainerStart(id, hostConfig)
if err != nil {
t.Fatal(err)
}
+4 -8
View File
@@ -38,13 +38,13 @@ func IsTerminal(fd uintptr) bool {
// Restore restores the terminal connected to the given file descriptor to a
// previous state.
func Restore(fd uintptr, state *State) error {
func RestoreTerminal(fd uintptr, state *State) error {
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)))
return err
}
func SetRawTerminal() (*State, error) {
oldState, err := MakeRaw(os.Stdin.Fd())
func SetRawTerminal(fd uintptr) (*State, error) {
oldState, err := MakeRaw(fd)
if err != nil {
return nil, err
}
@@ -52,12 +52,8 @@ func SetRawTerminal() (*State, error) {
signal.Notify(c, os.Interrupt)
go func() {
_ = <-c
Restore(os.Stdin.Fd(), oldState)
RestoreTerminal(fd, oldState)
os.Exit(0)
}()
return oldState, err
}
func RestoreTerminal(state *State) {
Restore(os.Stdin.Fd(), state)
}
+4 -2
View File
@@ -19,9 +19,10 @@ Vagrant::Config.run do |config|
config.vm.share_folder "v-data", DOCKER_PATH, "#{File.dirname(__FILE__)}/.."
config.vm.network :hostonly, BUILDBOT_IP
# Deploy buildbot and its dependencies if it was not done
if Dir.glob("#{File.dirname(__FILE__)}/.vagrant/machines/default/*/id").empty?
pkg_cmd = "apt-get update -qq; apt-get install -q -y linux-image-3.8.0-19-generic; "
pkg_cmd = "apt-get update -qq; apt-get install -q -y linux-image-generic-lts-raring; "
# Deploy buildbot CI
pkg_cmd << "apt-get install -q -y python-dev python-pip supervisor; " \
"pip install -r #{CFG_PATH}/requirements.txt; " \
@@ -29,7 +30,7 @@ Vagrant::Config.run do |config|
"#{CFG_PATH}/setup.sh #{USER} #{CFG_PATH}; "
# Install docker dependencies
pkg_cmd << "apt-get install -q -y python-software-properties; " \
"add-apt-repository -y ppa:gophers/go/ubuntu; apt-get update -qq; " \
"add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu; apt-get update -qq; " \
"DEBIAN_FRONTEND=noninteractive apt-get install -q -y lxc git golang-stable aufs-tools make; "
# Activate new kernel
pkg_cmd << "shutdown -r +1; "
@@ -40,6 +41,7 @@ end
# Providers were added on Vagrant >= 1.1.0
Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
config.vm.provider :aws do |aws, override|
aws.tags = { 'Name' => 'docker-ci' }
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"]
+8 -1
View File
@@ -44,7 +44,11 @@ func CompareConfig(a, b *Config) bool {
return false
}
}
for i := 0; i < len(a.Entrypoint); i++ {
if a.Entrypoint[i] != b.Entrypoint[i] {
return false
}
}
return true
}
@@ -85,4 +89,7 @@ func MergeConfig(userConf, imageConf *Config) {
if userConf.Dns == nil || len(userConf.Dns) == 0 {
userConf.Dns = imageConf.Dns
}
if userConf.Entrypoint == nil || len(userConf.Entrypoint) == 0 {
userConf.Entrypoint = imageConf.Entrypoint
}
}
+8 -6
View File
@@ -78,16 +78,16 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
read, err := io.ReadCloser(r.reader).Read(p)
r.readProgress += read
updateEvery := 4096
updateEvery := 1024 * 512 //512kB
if r.readTotal > 0 {
// Only update progress for every 1% read
if increment := int(0.01 * float64(r.readTotal)); increment > updateEvery {
// Update progress for every 1% read if 1% < 512kB
if increment := int(0.01 * float64(r.readTotal)); increment < updateEvery {
updateEvery = increment
}
}
if r.readProgress-r.lastUpdate > updateEvery || err != nil {
if r.readTotal > 0 {
fmt.Fprintf(r.output, r.template, HumanSize(int64(r.readProgress)), HumanSize(int64(r.readTotal)), fmt.Sprintf("%2.0f%%",float64(r.readProgress)/float64(r.readTotal)*100))
fmt.Fprintf(r.output, r.template, HumanSize(int64(r.readProgress)), HumanSize(int64(r.readTotal)), fmt.Sprintf("%2.0f%%", float64(r.readProgress)/float64(r.readTotal)*100))
} else {
fmt.Fprintf(r.output, r.template, r.readProgress, "?", "n/a")
}
@@ -133,7 +133,7 @@ func HumanDuration(d time.Duration) string {
} else if hours < 24*365*2 {
return fmt.Sprintf("%d months", hours/24/30)
}
return fmt.Sprintf("%d years", d.Hours()/24/365)
return fmt.Sprintf("%f years", d.Hours()/24/365)
}
// HumanSize returns a human-readable approximation of a size
@@ -528,7 +528,9 @@ func GetKernelVersion() (*KernelVersionInfo, error) {
}
if len(tmp2) > 2 {
minor, err = strconv.Atoi(tmp2[2])
// Removes "+" because git kernels might set it
minorUnparsed := strings.Trim(tmp2[2], "+")
minor, err = strconv.Atoi(minorUnparsed)
if err != nil {
return nil, err
}
+2 -2
View File
@@ -265,8 +265,8 @@ func TestCompareKernelVersion(t *testing.T) {
func TestHumanSize(t *testing.T) {
size1000 := HumanSize(1000)
if size1000 != "1 kB" {
t.Errorf("1000 -> expected 1 kB, got %s", size1000)
if size1000 != " 1 kB" {
t.Errorf("1000 -> expected 1 kB, got %s", size1000)
}
size1024 := HumanSize(1024)
+103
View File
@@ -0,0 +1,103 @@
package docker
import (
"io"
"io/ioutil"
"os"
"path"
"strings"
"testing"
)
// This file contains utility functions for docker's unit test suite.
// It has to be named XXX_test.go, apparently, in other to access private functions
// from other XXX_test.go functions.
// Create a temporary runtime suitable for unit testing.
// Call t.Fatal() at the first error.
func mkRuntime(t *testing.T) *Runtime {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
return runtime
}
// Write `content` to the file at path `dst`, creating it if necessary,
// as well as any missing directories.
// The file is truncated if it already exists.
// Call t.Fatal() at the first error.
func writeFile(dst, content string, t *testing.T) {
// Create subdirectories if necessary
if err := os.MkdirAll(path.Dir(dst), 0700); err != nil && !os.IsExist(err) {
t.Fatal(err)
}
f, err := os.OpenFile(dst, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0700)
if err != nil {
t.Fatal(err)
}
// Write content (truncate if it exists)
if _, err := io.Copy(f, strings.NewReader(content)); err != nil {
t.Fatal(err)
}
}
// Return the contents of file at path `src`.
// Call t.Fatal() at the first error (including if the file doesn't exist)
func readFile(src string, t *testing.T) (content string) {
f, err := os.Open(src)
if err != nil {
t.Fatal(err)
}
data, err := ioutil.ReadAll(f)
if err != nil {
t.Fatal(err)
}
return string(data)
}
// Create a test container from the given runtime `r` and run arguments `args`.
// The image name (eg. the XXX in []string{"-i", "-t", "XXX", "bash"}, is dynamically replaced by the current test image.
// The caller is responsible for destroying the container.
// Call t.Fatal() at the first error.
func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, *HostConfig) {
config, hostConfig, _, err := ParseRun(args, nil)
if err != nil {
t.Fatal(err)
}
config.Image = GetTestImage(r).ID
c, err := NewBuilder(r).Create(config)
if err != nil {
t.Fatal(err)
}
return c, hostConfig
}
// Create a test container, start it, wait for it to complete, destroy it,
// and return its standard output as a string.
// The image name (eg. the XXX in []string{"-i", "-t", "XXX", "bash"}, is dynamically replaced by the current test image.
// If t is not nil, call t.Fatal() at the first error. Otherwise return errors normally.
func runContainer(r *Runtime, args []string, t *testing.T) (output string, err error) {
defer func() {
if err != nil && t != nil {
t.Fatal(err)
}
}()
container, hostConfig := mkContainer(r, args, t)
defer r.Destroy(container)
stdout, err := container.StdoutPipe()
if err != nil {
return "", err
}
defer stdout.Close()
if err := container.Start(hostConfig); err != nil {
return "", err
}
container.Wait()
data, err := ioutil.ReadAll(stdout)
if err != nil {
return "", err
}
output = string(data)
return
}
+12
View File
@@ -0,0 +1,12 @@
package docker
import (
"github.com/dotcloud/docker/utils"
"runtime"
"testing"
)
func TestFinal(t *testing.T) {
cleanup(globalRuntime)
t.Logf("Fds: %d, Goroutines: %d", utils.GetTotalUsedFds(), runtime.NumGoroutine())
}