mirror of
https://github.com/clearlinux/docker.git
synced 2026-08-19 12:16:30 +00:00
Compare commits
313 Commits
v0.7.0-rc5
...
v0.7.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d078b6581 | |||
| 14d9f04e89 | |||
| db28e839e0 | |||
| de30ffb2c3 | |||
| 5c5ee194cb | |||
| b6dd67c707 | |||
| 740958dda7 | |||
| c38386d876 | |||
| 4267fb66ef | |||
| a74b512540 | |||
| 60809a4f72 | |||
| 65fcc81b42 | |||
| 06cf8fee1b | |||
| c92dab0eb4 | |||
| 6ad5b2bcf4 | |||
| 77f1362c64 | |||
| 4049359bee | |||
| 7daefc9d3f | |||
| d4c32b9015 | |||
| 8bd6127ab3 | |||
| 2302293244 | |||
| fd7ff6411d | |||
| 59f76bf1c7 | |||
| 02cb7f45fa | |||
| a937313747 | |||
| fb3d60f27a | |||
| 5ff74e268d | |||
| 09b7b55e2c | |||
| 110c4f2043 | |||
| 0d1b5d7676 | |||
| 5242a49f3f | |||
| 2586c042ae | |||
| 688e86c625 | |||
| 750d2d8d07 | |||
| 19df6c32c0 | |||
| 1d903da6fd | |||
| aaefb8c07c | |||
| b3959e69b5 | |||
| 43c7df946d | |||
| 6acdf68ee1 | |||
| 487b3d8a8c | |||
| 33f70f8978 | |||
| 809239c0af | |||
| 937f52aef9 | |||
| aa48acc5ec | |||
| ac70e296db | |||
| e2c3860ec3 | |||
| 2d715bf3c0 | |||
| d9e54e28e7 | |||
| 78d2e2dc37 | |||
| abfdaca3f8 | |||
| 3a2fbcfdec | |||
| ba2b36e192 | |||
| d47d49a2f9 | |||
| 8b0b10b6f9 | |||
| 399c71de83 | |||
| d8f4b733f2 | |||
| b4eeb6be61 | |||
| 41704d8933 | |||
| 64dd4afed6 | |||
| 5da1ed3291 | |||
| ad23745456 | |||
| cee0a292d0 | |||
| 07180f3aa7 | |||
| a606474825 | |||
| 5d6ef3177b | |||
| 0a89db04fe | |||
| 1cce9f25b2 | |||
| f1b3e278b9 | |||
| e288e7763e | |||
| 9696ec509a | |||
| ba6dd1d8d6 | |||
| c67f9b671d | |||
| 1c8ae47770 | |||
| d55998be81 | |||
| e69bbd239e | |||
| a26f9183bd | |||
| 944a48ec5a | |||
| 79e2b33ede | |||
| 076c0eab70 | |||
| 1f9223a7c2 | |||
| 476559458d | |||
| d4c8fb9ee2 | |||
| ae8c589d35 | |||
| 6130f2531e | |||
| ef14aaf627 | |||
| 1e7c04fcfe | |||
| 8b0cd60019 | |||
| b3f5973f41 | |||
| 3314e005f3 | |||
| a93e40a158 | |||
| cb48ecc9dc | |||
| 53f1bf0f99 | |||
| 9dc59797e0 | |||
| 0cecc2a78c | |||
| 437bdeee59 | |||
| 806abe90ba | |||
| 25e443a3c7 | |||
| 33e70864a2 | |||
| bc82940a57 | |||
| d6e6214d37 | |||
| 70f1bd3104 | |||
| f7c2a00557 | |||
| 8498b44eac | |||
| e2dcfc2cf7 | |||
| 9b4c151142 | |||
| 50239e0573 | |||
| 42c23b0f04 | |||
| eec91e7941 | |||
| 3f17844b6e | |||
| efd0e13ca7 | |||
| bcdeb37bb6 | |||
| 362e9d6b3c | |||
| c4ab498920 | |||
| cb70eedfda | |||
| 75a7f4d90c | |||
| da824b4a5a | |||
| 1ab6b8bf49 | |||
| eaeb969138 | |||
| 253214f07d | |||
| a2c9d2da93 | |||
| d7e2fc8982 | |||
| f20c738963 | |||
| df258f5861 | |||
| 60f728b170 | |||
| 2b7c63b1b5 | |||
| fd7ab143bf | |||
| 82cdd21a34 | |||
| a9230af52e | |||
| 2f0d18ac4a | |||
| 6469422465 | |||
| 5306053e21 | |||
| e2390318bb | |||
| 4e0c76b321 | |||
| da514223d1 | |||
| 023ff36704 | |||
| 8fdbf46afb | |||
| d233894c25 | |||
| 8a756f417e | |||
| a39bd65662 | |||
| 5690139785 | |||
| 92f94f06ae | |||
| 2bc35287a0 | |||
| 2382a0f920 | |||
| 579a5c843b | |||
| 6ebb236aa1 | |||
| b4f7078a02 | |||
| 9e68913397 | |||
| 1b28cdc7f9 | |||
| 304a80fcd5 | |||
| 04f1d4dcdb | |||
| 171d681724 | |||
| 60cb5f1a34 | |||
| cfdc284abe | |||
| 7192be47c5 | |||
| cd4c1ac356 | |||
| b8af68a92b | |||
| 9de4590498 | |||
| 0ef6fed5c7 | |||
| 383f95bba1 | |||
| 1211065c8d | |||
| 844c13bce6 | |||
| 6014db4a7e | |||
| bf504f2afa | |||
| 61a8020e51 | |||
| 7eaa59f626 | |||
| 2cccbbdadd | |||
| 24586d7af5 | |||
| b700ee006a | |||
| 880a0e9c36 | |||
| 0169cf15dd | |||
| cd022376b8 | |||
| 234f5ac39f | |||
| d4ef551d65 | |||
| 76f54f3a28 | |||
| 43b709ab36 | |||
| 43899a77bf | |||
| d575918038 | |||
| f6629bbbd5 | |||
| 91330243b5 | |||
| 3e0a5ac48b | |||
| 0d603df6dc | |||
| 12e993549d | |||
| 28d4cbbc59 | |||
| 5a4113140e | |||
| 85b5062502 | |||
| 66beafa9f3 | |||
| 3cbec95177 | |||
| 8e5ab5bfca | |||
| 9d867a389b | |||
| 4f9f83d6c6 | |||
| 145c2008ae | |||
| 4eaba8de91 | |||
| 434c1613a4 | |||
| 18da26c62a | |||
| 4c2624a277 | |||
| f4b3b7c055 | |||
| 9af1b07086 | |||
| aeb304b37c | |||
| 1da8c50ded | |||
| 1cd9529ad3 | |||
| f78d45863c | |||
| 3656db66d2 | |||
| 0ed762f2d2 | |||
| 242fd4b3ef | |||
| 78c843c8ef | |||
| ac821f2446 | |||
| 51a972f38d | |||
| aea6001baf | |||
| 6dbeed89c0 | |||
| 5d76681c3d | |||
| 9415c2b1f0 | |||
| a0224e61b4 | |||
| af753cbad8 | |||
| 80aecc7014 | |||
| 3edb4af663 | |||
| 00401a30b7 | |||
| 5ee8e41e43 | |||
| 29f07f8544 | |||
| 2fe4467d73 | |||
| 194d4b9376 | |||
| 1de02a70de | |||
| 5e941f1ca0 | |||
| f88b760809 | |||
| 511a62a099 | |||
| 256d46fe68 | |||
| 76ee860ece | |||
| 94e854823f | |||
| e42b574579 | |||
| 2ae1f29dfe | |||
| 4b80ec9aae | |||
| fef41ef7bf | |||
| f946a486ea | |||
| e008c7c068 | |||
| 669e4bac30 | |||
| f6362fbb0e | |||
| c1cd824570 | |||
| 4546832507 | |||
| b5934711d0 | |||
| ed8f75d25c | |||
| cfbd9ea16d | |||
| 75dd1663e0 | |||
| 3c67a28493 | |||
| 440422a963 | |||
| fc2f5758cf | |||
| fe302fbfd2 | |||
| 72d02ecdde | |||
| baa687bed2 | |||
| 30ea0bebce | |||
| 8ed4307f50 | |||
| 9e1da3ec9c | |||
| b76574f855 | |||
| 457375ea37 | |||
| 7e580aacec | |||
| 0e64b987e8 | |||
| bc74f65068 | |||
| 5c741ef11d | |||
| 152459b727 | |||
| f04142c1f7 | |||
| af05ab399e | |||
| 27159ce6ba | |||
| 51576069ad | |||
| d33ffe44a9 | |||
| afe230718e | |||
| 427649eee1 | |||
| a518b84751 | |||
| 9261690593 | |||
| cbd1281ec9 | |||
| 035c144242 | |||
| fb245f7903 | |||
| dc5b7b32c3 | |||
| e12bba66ea | |||
| 92c3a319fb | |||
| 323e86032d | |||
| c06d903edd | |||
| 9a9d3239e1 | |||
| 33110ddc3f | |||
| 6341768393 | |||
| ad96d5ff04 | |||
| 7cad77b1e2 | |||
| 2b23da1d2f | |||
| bcf96d95bc | |||
| 492ce4c665 | |||
| 76ab8fa00f | |||
| 007964793d | |||
| 10f23a94f6 | |||
| 243843c078 | |||
| 062810caed | |||
| f7238f94e8 | |||
| d69a6a20f0 | |||
| 4908d7f81d | |||
| 29fa1b6666 | |||
| bed87ea103 | |||
| 823674816e | |||
| 8d3e35cd8d | |||
| 596810d8db | |||
| 661a8a0e0c | |||
| 006e2a600c | |||
| e9624bf715 | |||
| 2a209afe5f | |||
| c001a5af67 | |||
| 359a6f49b9 | |||
| 37abc07852 | |||
| 059950b095 | |||
| 02884ee429 | |||
| 8145e57cee | |||
| 0013aa7d9f | |||
| 1d7f22c0d4 | |||
| 6a693176d6 | |||
| eba3c36b38 | |||
| 304d39cc50 | |||
| 6d420407ca | |||
| b64ce8e33c |
@@ -44,6 +44,7 @@ Daniel Nordberg <dnordberg@gmail.com>
|
||||
Daniel Robinson <gottagetmac@gmail.com>
|
||||
Daniel Von Fange <daniel@leancoder.com>
|
||||
Daniel YC Lin <dlin.tw@gmail.com>
|
||||
Darren Coxall <darren@darrencoxall.com>
|
||||
David Calavera <david.calavera@gmail.com>
|
||||
David Sissitka <me@dsissitka.com>
|
||||
Deni Bertovic <deni@kset.org>
|
||||
@@ -120,6 +121,7 @@ Marko Mikulicic <mmikulicic@gmail.com>
|
||||
Markus Fix <lispmeister@gmail.com>
|
||||
Martin Redmond <martin@tinychat.com>
|
||||
Matt Apperson <me@mattapperson.com>
|
||||
Mathieu Le Marec - Pasquet <kiorky@cryptelium.net>
|
||||
Matt Bachmann <bachmann.matt@gmail.com>
|
||||
Matthew Mueller <mattmuelle@gmail.com>
|
||||
Maxim Treskin <zerthurd@gmail.com>
|
||||
|
||||
@@ -1,5 +1,55 @@
|
||||
# Changelog
|
||||
|
||||
## 0.7.0 (2013-11-25)
|
||||
|
||||
#### Notable features since 0.6.0
|
||||
|
||||
* Storage drivers: choose from aufs, device mapper, vfs or btrfs.
|
||||
* Standard Linux support: docker now runs on unmodified linux kernels and all major distributions.
|
||||
* Links: compose complex software stacks by connecting containers to each other.
|
||||
* Container naming: organize your containers by giving them memorable names.
|
||||
* Advanced port redirects: specify port redirects per interface, or keep sensitive ports private.
|
||||
* Offline transfer: push and pull images to the filesystem without losing information.
|
||||
* Quality: numerous bugfixes and small usability improvements. Significant increase in test coverage.
|
||||
|
||||
## 0.6.7 (2013-11-21)
|
||||
|
||||
#### Runtime
|
||||
|
||||
* Improved stability, fixes some race conditons
|
||||
* Skip the volumes mounted when deleting the volumes of container.
|
||||
* Fix layer size computation: handle hard links correctly
|
||||
* Use the work Path for docker cp CONTAINER:PATH
|
||||
* Fix tmp dir never cleanup
|
||||
* Speedup docker ps
|
||||
* More informative error message on name collisions
|
||||
* Fix nameserver regex
|
||||
* Always return long id's
|
||||
* Fix container restart race condition
|
||||
* Keep published ports on docker stop;docker start
|
||||
* Fix container networking on Fedora
|
||||
* Correctly express "any address" to iptables
|
||||
* Fix network setup when reconnecting to ghost container
|
||||
* Prevent deletion if image is used by a running container
|
||||
* Lock around read operations in graph
|
||||
|
||||
#### RemoteAPI
|
||||
|
||||
* Return full ID on docker rmi
|
||||
|
||||
#### Client
|
||||
|
||||
+ Add -tree option to images
|
||||
+ Offline image transfer
|
||||
* Exit with status 2 on usage error and display usage on stderr
|
||||
* Do not forward SIGCHLD to container
|
||||
* Use string timestamp for docker events -since
|
||||
|
||||
#### Other
|
||||
|
||||
* Update to go 1.2rc5
|
||||
+ Add /etc/default/docker support to upstart
|
||||
|
||||
## 0.6.6 (2013-11-06)
|
||||
|
||||
#### Runtime
|
||||
@@ -17,6 +67,7 @@
|
||||
+ Prevent DNS server conflicts in CreateBridgeIface
|
||||
+ Validate bind mounts on the server side
|
||||
+ Use parent image config in docker build
|
||||
* Fix regression in /etc/hosts
|
||||
|
||||
#### Client
|
||||
|
||||
|
||||
+4
-5
@@ -36,7 +36,7 @@ run apt-get install -y -q mercurial
|
||||
run apt-get install -y -q build-essential libsqlite3-dev
|
||||
|
||||
# Install Go
|
||||
run curl -s https://go.googlecode.com/files/go1.2rc4.src.tar.gz | tar -v -C /usr/local -xz
|
||||
run curl -s https://go.googlecode.com/files/go1.2rc5.src.tar.gz | tar -v -C /usr/local -xz
|
||||
env PATH /usr/local/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
|
||||
env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor
|
||||
run cd /usr/local/go/src && ./make.bash && go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std
|
||||
@@ -46,10 +46,9 @@ run apt-get install -y -q ruby1.9.3 rubygems libffi-dev
|
||||
run gem install --no-rdoc --no-ri fpm
|
||||
run apt-get install -y -q reprepro dpkg-sig
|
||||
|
||||
# Install s3cmd 1.0.1 (earlier versions don't support env variables in the config)
|
||||
run apt-get install -y -q python-pip
|
||||
run pip install s3cmd
|
||||
run pip install python-magic
|
||||
run pip install s3cmd==1.1.0-beta3
|
||||
run pip install python-magic==0.4.6
|
||||
run /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY\n' > /.s3cfg
|
||||
|
||||
# Runtime dependencies
|
||||
@@ -58,7 +57,7 @@ run apt-get install -y -q lxc
|
||||
run apt-get install -y -q aufs-tools
|
||||
|
||||
# Get lvm2 source for compiling statically
|
||||
run git clone git://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2 && cd /usr/local/lvm2 && git checkout v2_02_103
|
||||
run git clone https://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2 && cd /usr/local/lvm2 && git checkout v2_02_103
|
||||
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
|
||||
# note: we can't use "git clone -b" above because it requires at least git 1.7.10 to be able to use that on a tag instead of a branch and we only have 1.7.9.5
|
||||
|
||||
|
||||
Vendored
+3
-3
@@ -70,7 +70,7 @@ SCRIPT
|
||||
# trigger dkms to build the virtualbox guest module install.
|
||||
$vbox_script = <<VBOX_SCRIPT + $script
|
||||
# Install the VirtualBox guest additions if they aren't already installed.
|
||||
if [ ! -d /opt/VBoxGuestAdditions-4.2.12/ ]; then
|
||||
if [ ! -d /opt/VBoxGuestAdditions-4.3.2/ ]; then
|
||||
# Update remote package metadata. 'apt-get update' is idempotent.
|
||||
apt-get update -q
|
||||
|
||||
@@ -79,9 +79,9 @@ if [ ! -d /opt/VBoxGuestAdditions-4.2.12/ ]; then
|
||||
apt-get install -q -y linux-headers-generic-lts-raring dkms
|
||||
|
||||
echo 'Downloading VBox Guest Additions...'
|
||||
wget -cq http://dlc.sun.com.edgesuite.net/virtualbox/4.2.12/VBoxGuestAdditions_4.2.12.iso
|
||||
wget -cq http://dlc.sun.com.edgesuite.net/virtualbox/4.3.2/VBoxGuestAdditions_4.3.2.iso
|
||||
|
||||
mount -o loop,ro /home/vagrant/VBoxGuestAdditions_4.2.12.iso /mnt
|
||||
mount -o loop,ro /home/vagrant/VBoxGuestAdditions_4.3.2.iso /mnt
|
||||
/mnt/VBoxLinuxAdditions.run --nox11
|
||||
umount /mnt
|
||||
fi
|
||||
|
||||
@@ -149,13 +149,12 @@ func postContainersKill(srv *Server, version float64, w http.ResponseWriter, r *
|
||||
|
||||
signal := 0
|
||||
if r != nil {
|
||||
s := r.Form.Get("signal")
|
||||
if s != "" {
|
||||
if s, err := strconv.Atoi(s); err != nil {
|
||||
if s := r.Form.Get("signal"); s != "" {
|
||||
s, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
signal = s
|
||||
}
|
||||
signal = s
|
||||
}
|
||||
}
|
||||
if err := srv.ContainerKill(name, signal); err != nil {
|
||||
@@ -201,9 +200,8 @@ func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http.
|
||||
}
|
||||
|
||||
return writeJSON(w, http.StatusOK, outs2)
|
||||
} else {
|
||||
return writeJSON(w, http.StatusOK, outs)
|
||||
}
|
||||
return writeJSON(w, http.StatusOK, outs)
|
||||
}
|
||||
|
||||
func getImagesViz(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
@@ -256,7 +254,7 @@ func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
|
||||
wf.Flush()
|
||||
if since != 0 {
|
||||
// If since, send previous events that happened after the timestamp
|
||||
for _, event := range srv.events {
|
||||
for _, event := range srv.GetEvents() {
|
||||
if event.Time >= since {
|
||||
err := sendEvent(wf, &event)
|
||||
if err != nil && err.Error() == "JSON error" {
|
||||
@@ -316,13 +314,10 @@ func getContainersTop(srv *Server, version float64, w http.ResponseWriter, r *ht
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
name := vars["name"]
|
||||
ps_args := r.Form.Get("ps_args")
|
||||
procsStr, err := srv.ContainerTop(name, ps_args)
|
||||
procsStr, err := srv.ContainerTop(vars["name"], r.Form.Get("ps_args"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return writeJSON(w, http.StatusOK, procsStr)
|
||||
}
|
||||
|
||||
@@ -350,13 +345,12 @@ func getContainersJSON(srv *Server, version float64, w http.ResponseWriter, r *h
|
||||
if version < 1.5 {
|
||||
outs2 := []APIContainersOld{}
|
||||
for _, ctnr := range outs {
|
||||
outs2 = append(outs2, ctnr.ToLegacy())
|
||||
outs2 = append(outs2, *ctnr.ToLegacy())
|
||||
}
|
||||
|
||||
return writeJSON(w, http.StatusOK, outs2)
|
||||
} else {
|
||||
return writeJSON(w, http.StatusOK, outs)
|
||||
}
|
||||
return writeJSON(w, http.StatusOK, outs)
|
||||
}
|
||||
|
||||
func postImagesTag(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
@@ -540,6 +534,18 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
|
||||
return nil
|
||||
}
|
||||
|
||||
func getImagesGet(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
name := vars["name"]
|
||||
if version > 1.0 {
|
||||
w.Header().Set("Content-Type", "application/x-tar")
|
||||
}
|
||||
return srv.ImageExport(name, w)
|
||||
}
|
||||
|
||||
func postImagesLoad(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
return srv.ImageLoad(r.Body)
|
||||
}
|
||||
|
||||
func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return nil
|
||||
@@ -640,12 +646,10 @@ func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.R
|
||||
if imgs != nil {
|
||||
if len(imgs) != 0 {
|
||||
return writeJSON(w, http.StatusOK, imgs)
|
||||
} else {
|
||||
return fmt.Errorf("Conflict, %s wasn't deleted", name)
|
||||
}
|
||||
} else {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return fmt.Errorf("Conflict, %s wasn't deleted", name)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -655,9 +659,6 @@ func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r
|
||||
}
|
||||
name := vars["name"]
|
||||
job := srv.Eng.Job("start", name)
|
||||
if err := job.ImportEnv(HostConfig{}); err != nil {
|
||||
return fmt.Errorf("Couldn't initialize host configuration")
|
||||
}
|
||||
// allow a nil body for backwards compatibility
|
||||
if r.Body != nil {
|
||||
if matchesContentType(r.Header.Get("Content-Type"), "application/json") {
|
||||
@@ -930,7 +931,7 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c, err := mkBuildContext(string(dockerFile), nil)
|
||||
c, err := MkBuildContext(string(dockerFile), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -978,7 +979,7 @@ func postContainersCopy(srv *Server, version float64, w http.ResponseWriter, r *
|
||||
}
|
||||
|
||||
if copyData.Resource == "" {
|
||||
return fmt.Errorf("Resource cannot be empty")
|
||||
return fmt.Errorf("Path cannot be empty")
|
||||
}
|
||||
if copyData.Resource[0] == '/' {
|
||||
copyData.Resource = copyData.Resource[1:]
|
||||
@@ -1047,6 +1048,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
||||
"/images/json": getImagesJSON,
|
||||
"/images/viz": getImagesViz,
|
||||
"/images/search": getImagesSearch,
|
||||
"/images/{name:.*}/get": getImagesGet,
|
||||
"/images/{name:.*}/history": getImagesHistory,
|
||||
"/images/{name:.*}/json": getImagesByName,
|
||||
"/containers/ps": getContainersJSON,
|
||||
@@ -1063,6 +1065,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
||||
"/build": postBuild,
|
||||
"/images/create": postImagesCreate,
|
||||
"/images/{name:.*}/insert": postImagesInsert,
|
||||
"/images/load": postImagesLoad,
|
||||
"/images/{name:.*}/push": postImagesPush,
|
||||
"/images/{name:.*}/tag": postImagesTag,
|
||||
"/containers/create": postContainersCreate,
|
||||
@@ -1108,6 +1111,20 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// ServeRequest processes a single http request to the docker remote api.
|
||||
// FIXME: refactor this to be part of Server and not require re-creating a new
|
||||
// router each time. This requires first moving ListenAndServe into Server.
|
||||
func ServeRequest(srv *Server, apiversion float64, w http.ResponseWriter, req *http.Request) error {
|
||||
router, err := createRouter(srv, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Insert APIVERSION into the request as a convenience
|
||||
req.URL.Path = fmt.Sprintf("/v%g%s", apiversion, req.URL.Path)
|
||||
router.ServeHTTP(w, req)
|
||||
return nil
|
||||
}
|
||||
|
||||
func ListenAndServe(proto, addr string, srv *Server, logging bool) error {
|
||||
log.Printf("Listening for HTTP on %s (%s)\n", addr, proto)
|
||||
|
||||
|
||||
+139
-139
@@ -2,149 +2,149 @@ package docker
|
||||
|
||||
import "strings"
|
||||
|
||||
type APIHistory struct {
|
||||
ID string `json:"Id"`
|
||||
Tags []string `json:",omitempty"`
|
||||
Created int64
|
||||
CreatedBy string `json:",omitempty"`
|
||||
Size int64
|
||||
}
|
||||
|
||||
type APIImages struct {
|
||||
ID string `json:"Id"`
|
||||
RepoTags []string `json:",omitempty"`
|
||||
Created int64
|
||||
Size int64
|
||||
VirtualSize int64
|
||||
ParentId string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APIImagesOld struct {
|
||||
Repository string `json:",omitempty"`
|
||||
Tag string `json:",omitempty"`
|
||||
ID string `json:"Id"`
|
||||
Created int64
|
||||
Size int64
|
||||
VirtualSize int64
|
||||
}
|
||||
|
||||
func (self *APIImages) ToLegacy() []APIImagesOld {
|
||||
|
||||
outs := []APIImagesOld{}
|
||||
for _, repotag := range self.RepoTags {
|
||||
|
||||
components := strings.SplitN(repotag, ":", 2)
|
||||
|
||||
outs = append(outs, APIImagesOld{
|
||||
ID: self.ID,
|
||||
Repository: components[0],
|
||||
Tag: components[1],
|
||||
Created: self.Created,
|
||||
Size: self.Size,
|
||||
VirtualSize: self.VirtualSize,
|
||||
})
|
||||
type (
|
||||
APIHistory struct {
|
||||
ID string `json:"Id"`
|
||||
Tags []string `json:",omitempty"`
|
||||
Created int64
|
||||
CreatedBy string `json:",omitempty"`
|
||||
Size int64
|
||||
}
|
||||
|
||||
APIImages struct {
|
||||
ID string `json:"Id"`
|
||||
RepoTags []string `json:",omitempty"`
|
||||
Created int64
|
||||
Size int64
|
||||
VirtualSize int64
|
||||
ParentId string `json:",omitempty"`
|
||||
}
|
||||
|
||||
APIImagesOld struct {
|
||||
Repository string `json:",omitempty"`
|
||||
Tag string `json:",omitempty"`
|
||||
ID string `json:"Id"`
|
||||
Created int64
|
||||
Size int64
|
||||
VirtualSize int64
|
||||
}
|
||||
|
||||
APIInfo struct {
|
||||
Debug bool
|
||||
Containers int
|
||||
Images int
|
||||
Driver string `json:",omitempty"`
|
||||
DriverStatus [][2]string `json:",omitempty"`
|
||||
NFd int `json:",omitempty"`
|
||||
NGoroutines int `json:",omitempty"`
|
||||
MemoryLimit bool `json:",omitempty"`
|
||||
SwapLimit bool `json:",omitempty"`
|
||||
IPv4Forwarding bool `json:",omitempty"`
|
||||
LXCVersion string `json:",omitempty"`
|
||||
NEventsListener int `json:",omitempty"`
|
||||
KernelVersion string `json:",omitempty"`
|
||||
IndexServerAddress string `json:",omitempty"`
|
||||
}
|
||||
|
||||
APITop struct {
|
||||
Titles []string
|
||||
Processes [][]string
|
||||
}
|
||||
|
||||
APIRmi struct {
|
||||
Deleted string `json:",omitempty"`
|
||||
Untagged string `json:",omitempty"`
|
||||
}
|
||||
|
||||
APIContainers struct {
|
||||
ID string `json:"Id"`
|
||||
Image string
|
||||
Command string
|
||||
Created int64
|
||||
Status string
|
||||
Ports []APIPort
|
||||
SizeRw int64
|
||||
SizeRootFs int64
|
||||
Names []string
|
||||
}
|
||||
|
||||
APIContainersOld struct {
|
||||
ID string `json:"Id"`
|
||||
Image string
|
||||
Command string
|
||||
Created int64
|
||||
Status string
|
||||
Ports string
|
||||
SizeRw int64
|
||||
SizeRootFs int64
|
||||
}
|
||||
|
||||
APIID struct {
|
||||
ID string `json:"Id"`
|
||||
}
|
||||
|
||||
APIRun struct {
|
||||
ID string `json:"Id"`
|
||||
Warnings []string `json:",omitempty"`
|
||||
}
|
||||
|
||||
APIPort struct {
|
||||
PrivatePort int64
|
||||
PublicPort int64
|
||||
Type string
|
||||
IP string
|
||||
}
|
||||
|
||||
APIVersion struct {
|
||||
Version string
|
||||
GitCommit string `json:",omitempty"`
|
||||
GoVersion string `json:",omitempty"`
|
||||
}
|
||||
|
||||
APIWait struct {
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
APIAuth struct {
|
||||
Status string
|
||||
}
|
||||
|
||||
APIImageConfig struct {
|
||||
ID string `json:"Id"`
|
||||
*Config
|
||||
}
|
||||
|
||||
APICopy struct {
|
||||
Resource string
|
||||
HostPath string
|
||||
}
|
||||
)
|
||||
|
||||
func (api APIImages) ToLegacy() []APIImagesOld {
|
||||
outs := []APIImagesOld{}
|
||||
for _, repotag := range api.RepoTags {
|
||||
components := strings.SplitN(repotag, ":", 2)
|
||||
outs = append(outs, APIImagesOld{
|
||||
ID: api.ID,
|
||||
Repository: components[0],
|
||||
Tag: components[1],
|
||||
Created: api.Created,
|
||||
Size: api.Size,
|
||||
VirtualSize: api.VirtualSize,
|
||||
})
|
||||
}
|
||||
return outs
|
||||
}
|
||||
|
||||
type APIInfo struct {
|
||||
Debug bool
|
||||
Containers int
|
||||
Images int
|
||||
NFd int `json:",omitempty"`
|
||||
NGoroutines int `json:",omitempty"`
|
||||
MemoryLimit bool `json:",omitempty"`
|
||||
SwapLimit bool `json:",omitempty"`
|
||||
IPv4Forwarding bool `json:",omitempty"`
|
||||
LXCVersion string `json:",omitempty"`
|
||||
NEventsListener int `json:",omitempty"`
|
||||
KernelVersion string `json:",omitempty"`
|
||||
IndexServerAddress string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APITop struct {
|
||||
Titles []string
|
||||
Processes [][]string
|
||||
}
|
||||
|
||||
type APIRmi struct {
|
||||
Deleted string `json:",omitempty"`
|
||||
Untagged string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APIContainers struct {
|
||||
ID string `json:"Id"`
|
||||
Image string
|
||||
Command string
|
||||
Created int64
|
||||
Status string
|
||||
Ports []APIPort
|
||||
SizeRw int64
|
||||
SizeRootFs int64
|
||||
Names []string
|
||||
}
|
||||
|
||||
func (self *APIContainers) ToLegacy() APIContainersOld {
|
||||
return APIContainersOld{
|
||||
ID: self.ID,
|
||||
Image: self.Image,
|
||||
Command: self.Command,
|
||||
Created: self.Created,
|
||||
Status: self.Status,
|
||||
Ports: displayablePorts(self.Ports),
|
||||
SizeRw: self.SizeRw,
|
||||
SizeRootFs: self.SizeRootFs,
|
||||
func (api APIContainers) ToLegacy() *APIContainersOld {
|
||||
return &APIContainersOld{
|
||||
ID: api.ID,
|
||||
Image: api.Image,
|
||||
Command: api.Command,
|
||||
Created: api.Created,
|
||||
Status: api.Status,
|
||||
Ports: displayablePorts(api.Ports),
|
||||
SizeRw: api.SizeRw,
|
||||
SizeRootFs: api.SizeRootFs,
|
||||
}
|
||||
}
|
||||
|
||||
type APIContainersOld struct {
|
||||
ID string `json:"Id"`
|
||||
Image string
|
||||
Command string
|
||||
Created int64
|
||||
Status string
|
||||
Ports string
|
||||
SizeRw int64
|
||||
SizeRootFs int64
|
||||
}
|
||||
|
||||
type APIID struct {
|
||||
ID string `json:"Id"`
|
||||
}
|
||||
|
||||
type APIRun struct {
|
||||
ID string `json:"Id"`
|
||||
Warnings []string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APIPort struct {
|
||||
PrivatePort int64
|
||||
PublicPort int64
|
||||
Type string
|
||||
IP string
|
||||
}
|
||||
|
||||
type APIVersion struct {
|
||||
Version string
|
||||
GitCommit string `json:",omitempty"`
|
||||
GoVersion string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APIWait struct {
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
type APIAuth struct {
|
||||
Status string
|
||||
}
|
||||
|
||||
type APIImageConfig struct {
|
||||
ID string `json:"Id"`
|
||||
*Config
|
||||
}
|
||||
|
||||
type APICopy struct {
|
||||
Resource string
|
||||
HostPath string
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestJsonContentType(t *testing.T) {
|
||||
if !matchesContentType("application/json", "application/json") {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if !matchesContentType("application/json; charset=utf-8", "application/json") {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if matchesContentType("dockerapplication/json", "application/json") {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -175,16 +175,17 @@ func Untar(archive io.Reader, path string, options *TarOptions) error {
|
||||
buf := make([]byte, 10)
|
||||
totalN := 0
|
||||
for totalN < 10 {
|
||||
if n, err := archive.Read(buf[totalN:]); err != nil {
|
||||
n, err := archive.Read(buf[totalN:])
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return fmt.Errorf("Tarball too short")
|
||||
}
|
||||
return err
|
||||
} else {
|
||||
totalN += n
|
||||
utils.Debugf("[tar autodetect] n: %d", n)
|
||||
}
|
||||
totalN += n
|
||||
utils.Debugf("[tar autodetect] n: %d", n)
|
||||
}
|
||||
|
||||
compression := DetectCompression(buf)
|
||||
|
||||
utils.Debugf("Archive compression detected: %s", compression.Extension())
|
||||
|
||||
+21
-5
@@ -280,11 +280,21 @@ func ChangesDirs(newDir, oldDir string) ([]Change, error) {
|
||||
return newRoot.Changes(oldRoot), nil
|
||||
}
|
||||
|
||||
func ExportChanges(root, rw string) (Archive, error) {
|
||||
changes, err := ChangesDirs(root, rw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func ChangesSize(newDir string, changes []Change) int64 {
|
||||
var size int64
|
||||
for _, change := range changes {
|
||||
if change.Kind == ChangeModify || change.Kind == ChangeAdd {
|
||||
file := filepath.Join(newDir, change.Path)
|
||||
fileInfo, _ := os.Lstat(file)
|
||||
if fileInfo != nil && !fileInfo.IsDir() {
|
||||
size += fileInfo.Size()
|
||||
}
|
||||
}
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
func ExportChanges(dir string, changes []Change) (Archive, error) {
|
||||
files := make([]string, 0)
|
||||
deletions := make([]string, 0)
|
||||
for _, change := range changes {
|
||||
@@ -297,5 +307,11 @@ func ExportChanges(root, rw string) (Archive, error) {
|
||||
deletions = append(deletions, filepath.Join(dir, ".wh."+base))
|
||||
}
|
||||
}
|
||||
return TarFilter(root, &TarOptions{Compression: Uncompressed, Recursive: false, Includes: files, CreateFiles: deletions})
|
||||
// FIXME: Why do we create whiteout files inside Tar code ?
|
||||
return TarFilter(dir, &TarOptions{
|
||||
Compression: Uncompressed,
|
||||
Includes: files,
|
||||
Recursive: false,
|
||||
CreateFiles: deletions,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func max(x, y int) int {
|
||||
if x >= y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
func copyDir(src, dst string) error {
|
||||
cmd := exec.Command("cp", "-a", src, dst)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper to sort []Change by path
|
||||
type byPath struct{ changes []Change }
|
||||
|
||||
func (b byPath) Less(i, j int) bool { return b.changes[i].Path < b.changes[j].Path }
|
||||
func (b byPath) Len() int { return len(b.changes) }
|
||||
func (b byPath) Swap(i, j int) { b.changes[i], b.changes[j] = b.changes[j], b.changes[i] }
|
||||
|
||||
type FileType uint32
|
||||
|
||||
const (
|
||||
Regular FileType = iota
|
||||
Dir
|
||||
Symlink
|
||||
)
|
||||
|
||||
type FileData struct {
|
||||
filetype FileType
|
||||
path string
|
||||
contents string
|
||||
permissions os.FileMode
|
||||
}
|
||||
|
||||
func createSampleDir(t *testing.T, root string) {
|
||||
files := []FileData{
|
||||
{Regular, "file1", "file1\n", 0600},
|
||||
{Regular, "file2", "file2\n", 0666},
|
||||
{Regular, "file3", "file3\n", 0404},
|
||||
{Regular, "file4", "file4\n", 0600},
|
||||
{Regular, "file5", "file5\n", 0600},
|
||||
{Regular, "file6", "file6\n", 0600},
|
||||
{Regular, "file7", "file7\n", 0600},
|
||||
{Dir, "dir1", "", 0740},
|
||||
{Regular, "dir1/file1-1", "file1-1\n", 01444},
|
||||
{Regular, "dir1/file1-2", "file1-2\n", 0666},
|
||||
{Dir, "dir2", "", 0700},
|
||||
{Regular, "dir2/file2-1", "file2-1\n", 0666},
|
||||
{Regular, "dir2/file2-2", "file2-2\n", 0666},
|
||||
{Dir, "dir3", "", 0700},
|
||||
{Regular, "dir3/file3-1", "file3-1\n", 0666},
|
||||
{Regular, "dir3/file3-2", "file3-2\n", 0666},
|
||||
{Dir, "dir4", "", 0700},
|
||||
{Regular, "dir4/file3-1", "file4-1\n", 0666},
|
||||
{Regular, "dir4/file3-2", "file4-2\n", 0666},
|
||||
{Symlink, "symlink1", "target1", 0666},
|
||||
{Symlink, "symlink2", "target2", 0666},
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
for _, info := range files {
|
||||
p := path.Join(root, info.path)
|
||||
if info.filetype == Dir {
|
||||
if err := os.MkdirAll(p, info.permissions); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else if info.filetype == Regular {
|
||||
if err := ioutil.WriteFile(p, []byte(info.contents), info.permissions); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else if info.filetype == Symlink {
|
||||
if err := os.Symlink(info.contents, p); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if info.filetype != Symlink {
|
||||
// Set a consistent ctime, atime for all files and dirs
|
||||
if err := os.Chtimes(p, now, now); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create an directory, copy it, make sure we report no changes between the two
|
||||
func TestChangesDirsEmpty(t *testing.T) {
|
||||
src, err := ioutil.TempDir("", "docker-changes-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
createSampleDir(t, src)
|
||||
dst := src + "-copy"
|
||||
if err := copyDir(src, dst); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
changes, err := ChangesDirs(dst, src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(changes) != 0 {
|
||||
t.Fatalf("Reported changes for identical dirs: %v", changes)
|
||||
}
|
||||
os.RemoveAll(src)
|
||||
os.RemoveAll(dst)
|
||||
}
|
||||
|
||||
func mutateSampleDir(t *testing.T, root string) {
|
||||
// Remove a regular file
|
||||
if err := os.RemoveAll(path.Join(root, "file1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Remove a directory
|
||||
if err := os.RemoveAll(path.Join(root, "dir1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Remove a symlink
|
||||
if err := os.RemoveAll(path.Join(root, "symlink1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Rewrite a file
|
||||
if err := ioutil.WriteFile(path.Join(root, "file2"), []byte("fileN\n"), 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Replace a file
|
||||
if err := os.RemoveAll(path.Join(root, "file3")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(root, "file3"), []byte("fileM\n"), 0404); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Touch file
|
||||
if err := os.Chtimes(path.Join(root, "file4"), time.Now(), time.Now()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Replace file with dir
|
||||
if err := os.RemoveAll(path.Join(root, "file5")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.MkdirAll(path.Join(root, "file5"), 0666); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create new file
|
||||
if err := ioutil.WriteFile(path.Join(root, "filenew"), []byte("filenew\n"), 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create new dir
|
||||
if err := os.MkdirAll(path.Join(root, "dirnew"), 0766); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create a new symlink
|
||||
if err := os.Symlink("targetnew", path.Join(root, "symlinknew")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Change a symlink
|
||||
if err := os.RemoveAll(path.Join(root, "symlink2")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Symlink("target2change", path.Join(root, "symlink2")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Replace dir with file
|
||||
if err := os.RemoveAll(path.Join(root, "dir2")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(root, "dir2"), []byte("dir2\n"), 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Touch dir
|
||||
if err := os.Chtimes(path.Join(root, "dir3"), time.Now(), time.Now()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangesDirsMutated(t *testing.T) {
|
||||
src, err := ioutil.TempDir("", "docker-changes-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
createSampleDir(t, src)
|
||||
dst := src + "-copy"
|
||||
if err := copyDir(src, dst); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(src)
|
||||
defer os.RemoveAll(dst)
|
||||
|
||||
mutateSampleDir(t, dst)
|
||||
|
||||
changes, err := ChangesDirs(dst, src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sort.Sort(byPath{changes})
|
||||
|
||||
expectedChanges := []Change{
|
||||
{"/dir1", ChangeDelete},
|
||||
{"/dir2", ChangeModify},
|
||||
{"/dir3", ChangeModify},
|
||||
{"/dirnew", ChangeAdd},
|
||||
{"/file1", ChangeDelete},
|
||||
{"/file2", ChangeModify},
|
||||
{"/file3", ChangeModify},
|
||||
{"/file4", ChangeModify},
|
||||
{"/file5", ChangeModify},
|
||||
{"/filenew", ChangeAdd},
|
||||
{"/symlink1", ChangeDelete},
|
||||
{"/symlink2", ChangeModify},
|
||||
{"/symlinknew", ChangeAdd},
|
||||
}
|
||||
|
||||
for i := 0; i < max(len(changes), len(expectedChanges)); i++ {
|
||||
if i >= len(expectedChanges) {
|
||||
t.Fatalf("unexpected change %s\n", changes[i].String())
|
||||
}
|
||||
if i >= len(changes) {
|
||||
t.Fatalf("no change for expected change %s\n", expectedChanges[i].String())
|
||||
}
|
||||
if changes[i].Path == expectedChanges[i].Path {
|
||||
if changes[i] != expectedChanges[i] {
|
||||
t.Fatalf("Wrong change for %s, expected %s, got %d\n", changes[i].Path, changes[i].String(), expectedChanges[i].String())
|
||||
}
|
||||
} else if changes[i].Path < expectedChanges[i].Path {
|
||||
t.Fatalf("unexpected change %s\n", changes[i].String())
|
||||
} else {
|
||||
t.Fatalf("no change for expected change %s != %s\n", expectedChanges[i].String(), changes[i].String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyLayer(t *testing.T) {
|
||||
t.Skip("Skipping TestApplyLayer due to known failures") // Disable this for now as it is broken
|
||||
return
|
||||
|
||||
src, err := ioutil.TempDir("", "docker-changes-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
createSampleDir(t, src)
|
||||
dst := src + "-copy"
|
||||
if err := copyDir(src, dst); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mutateSampleDir(t, dst)
|
||||
|
||||
changes, err := ChangesDirs(dst, src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layer, err := ExportChanges(dst, changes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
layerCopy, err := NewTempArchive(layer, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := ApplyLayer(src, layerCopy); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
changes2, err := ChangesDirs(src, dst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(changes2) != 0 {
|
||||
t.Fatalf("Unexpected differences after re applying mutation: %v", changes)
|
||||
}
|
||||
|
||||
os.RemoveAll(src)
|
||||
os.RemoveAll(dst)
|
||||
}
|
||||
+39
-7
@@ -1,10 +1,11 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ApplyLayer parses a diff in the standard layer format from `layer`, and
|
||||
@@ -17,6 +18,20 @@ func ApplyLayer(dest string, layer Archive) error {
|
||||
return err
|
||||
}
|
||||
|
||||
modifiedDirs := make(map[string]*syscall.Stat_t)
|
||||
addDir := func(file string) {
|
||||
d := filepath.Dir(file)
|
||||
if _, exists := modifiedDirs[d]; !exists {
|
||||
if s, err := os.Lstat(d); err == nil {
|
||||
if sys := s.Sys(); sys != nil {
|
||||
if stat, ok := sys.(*syscall.Stat_t); ok {
|
||||
modifiedDirs[d] = stat
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: walk for whiteouts and apply them, removing them in the process
|
||||
err := filepath.Walk(dest, func(fullPath string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
@@ -39,25 +54,42 @@ func ApplyLayer(dest string, layer Archive) error {
|
||||
if matched, err := filepath.Match("/.wh..wh.*", path); err != nil {
|
||||
return err
|
||||
} else if matched {
|
||||
log.Printf("Removing aufs metadata %s", fullPath)
|
||||
_ = os.RemoveAll(fullPath)
|
||||
addDir(fullPath)
|
||||
if err := os.RemoveAll(fullPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
filename := filepath.Base(path)
|
||||
if strings.HasPrefix(filename, ".wh.") {
|
||||
rmTargetName := filename[len(".wh."):]
|
||||
rmTargetPath := filepath.Join(filepath.Dir(fullPath), rmTargetName)
|
||||
|
||||
// Remove the file targeted by the whiteout
|
||||
log.Printf("Removing whiteout target %s", rmTargetPath)
|
||||
_ = os.Remove(rmTargetPath)
|
||||
addDir(rmTargetPath)
|
||||
if err := os.RemoveAll(rmTargetPath); err != nil {
|
||||
return err
|
||||
}
|
||||
// Remove the whiteout itself
|
||||
log.Printf("Removing whiteout %s", fullPath)
|
||||
_ = os.RemoveAll(fullPath)
|
||||
addDir(fullPath)
|
||||
if err := os.RemoveAll(fullPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for k, v := range modifiedDirs {
|
||||
aTime := time.Unix(v.Atim.Unix())
|
||||
mTime := time.Unix(v.Mtim.Unix())
|
||||
|
||||
if err := os.Chtimes(k, aTime, mTime); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
+2
-3
@@ -196,10 +196,9 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e
|
||||
if loginAgainstOfficialIndex {
|
||||
return "", fmt.Errorf("Login: Your account hasn't been activated. " +
|
||||
"Please check your e-mail for a confirmation link.")
|
||||
} else {
|
||||
return "", fmt.Errorf("Login: Your account hasn't been activated. " +
|
||||
"Please see the documentation of the registry " + serverAddress + " for instructions how to activate it.")
|
||||
}
|
||||
return "", fmt.Errorf("Login: Your account hasn't been activated. " +
|
||||
"Please see the documentation of the registry " + serverAddress + " for instructions how to activate it.")
|
||||
} else if reqStatusCode == 400 {
|
||||
if string(reqBody) == "\"Username or email already exists\"" {
|
||||
req, err := factory.NewRequest("GET", serverAddress+"users/", nil)
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -29,52 +26,6 @@ func TestEncodeAuth(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogin(t *testing.T) {
|
||||
os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
|
||||
defer os.Setenv("DOCKER_INDEX_URL", "")
|
||||
authConfig := &AuthConfig{Username: "unittester", Password: "surlautrerivejetattendrai", Email: "noise+unittester@dotcloud.com"}
|
||||
status, err := Login(authConfig, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if status != "Login Succeeded" {
|
||||
t.Fatalf("Expected status \"Login Succeeded\", found \"%s\" instead", status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateAccount(t *testing.T) {
|
||||
os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
|
||||
defer os.Setenv("DOCKER_INDEX_URL", "")
|
||||
tokenBuffer := make([]byte, 16)
|
||||
_, err := rand.Read(tokenBuffer)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
token := hex.EncodeToString(tokenBuffer)[:12]
|
||||
username := "ut" + token
|
||||
authConfig := &AuthConfig{Username: username, Password: "test42", Email: "docker-ut+" + token + "@example.com"}
|
||||
status, err := Login(authConfig, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expectedStatus := "Account created. Please use the confirmation link we sent" +
|
||||
" to your e-mail to activate it."
|
||||
if status != expectedStatus {
|
||||
t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status)
|
||||
}
|
||||
|
||||
status, err = Login(authConfig, nil)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error but found nil instead")
|
||||
}
|
||||
|
||||
expectedError := "Login: Account is not Active"
|
||||
|
||||
if !strings.Contains(err.Error(), expectedError) {
|
||||
t.Fatalf("Expected message \"%s\" but found \"%s\" instead", expectedError, err)
|
||||
}
|
||||
}
|
||||
|
||||
func setupTempConfigFile() (*ConfigFile, error) {
|
||||
root, err := ioutil.TempDir("", "docker-test-auth")
|
||||
if err != nil {
|
||||
|
||||
+1
-1
@@ -241,7 +241,7 @@ func (b *buildFile) CmdVolume(args string) error {
|
||||
volume = []string{args}
|
||||
}
|
||||
if b.config.Volumes == nil {
|
||||
b.config.Volumes = NewPathOpts()
|
||||
b.config.Volumes = PathOpts{}
|
||||
}
|
||||
for _, v := range volume {
|
||||
b.config.Volumes[v] = struct{}{}
|
||||
|
||||
+407
-134
@@ -22,6 +22,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
@@ -92,6 +93,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
|
||||
{"insert", "Insert a file in an image"},
|
||||
{"inspect", "Return low-level information on a container"},
|
||||
{"kill", "Kill a running container"},
|
||||
{"load", "Load an image from a tar archive"},
|
||||
{"login", "Register or Login to the docker registry server"},
|
||||
{"logs", "Fetch the logs of a container"},
|
||||
{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
|
||||
@@ -102,6 +104,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
|
||||
{"rm", "Remove one or more containers"},
|
||||
{"rmi", "Remove one or more images"},
|
||||
{"run", "Run a command in a new container"},
|
||||
{"save", "Save an image to a tar archive"},
|
||||
{"search", "Search for an image in the docker index"},
|
||||
{"start", "Start a stopped container"},
|
||||
{"stop", "Stop a running container"},
|
||||
@@ -117,7 +120,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdInsert(args ...string) error {
|
||||
cmd := Subcmd("insert", "IMAGE URL PATH", "Insert a file from URL in the IMAGE at PATH")
|
||||
cmd := cli.Subcmd("insert", "IMAGE URL PATH", "Insert a file from URL in the IMAGE at PATH")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -135,7 +138,7 @@ func (cli *DockerCli) CmdInsert(args ...string) error {
|
||||
|
||||
// mkBuildContext returns an archive of an empty context with the contents
|
||||
// of `dockerfile` at the path ./Dockerfile
|
||||
func mkBuildContext(dockerfile string, files [][2]string) (archive.Archive, error) {
|
||||
func MkBuildContext(dockerfile string, files [][2]string) (archive.Archive, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
tw := tar.NewWriter(buf)
|
||||
files = append(files, [2]string{"Dockerfile", dockerfile})
|
||||
@@ -159,7 +162,7 @@ func mkBuildContext(dockerfile string, files [][2]string) (archive.Archive, erro
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||
cmd := Subcmd("build", "[OPTIONS] PATH | URL | -", "Build a new container image from the source code at PATH")
|
||||
cmd := cli.Subcmd("build", "[OPTIONS] PATH | URL | -", "Build a new container image from the source code at PATH")
|
||||
tag := cmd.String("t", "", "Repository name (and optionally a tag) to be applied to the resulting image in case of success")
|
||||
suppressOutput := cmd.Bool("q", false, "Suppress verbose build output")
|
||||
noCache := cmd.Bool("no-cache", false, "Do not use cache when building the image")
|
||||
@@ -185,7 +188,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
context, err = mkBuildContext(string(dockerfile), nil)
|
||||
context, err = MkBuildContext(string(dockerfile), nil)
|
||||
} else if utils.IsURL(cmd.Arg(0)) || utils.IsGIT(cmd.Arg(0)) {
|
||||
isRemote = true
|
||||
} else {
|
||||
@@ -257,7 +260,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||
|
||||
// 'docker login': login / register a user to registry service.
|
||||
func (cli *DockerCli) CmdLogin(args ...string) error {
|
||||
cmd := Subcmd("login", "[OPTIONS] [SERVER]", "Register or Login to a docker registry server, if no server is specified \""+auth.IndexServerAddress()+"\" is the default.")
|
||||
cmd := cli.Subcmd("login", "[OPTIONS] [SERVER]", "Register or Login to a docker registry server, if no server is specified \""+auth.IndexServerAddress()+"\" is the default.")
|
||||
|
||||
var username, password, email string
|
||||
|
||||
@@ -365,7 +368,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
||||
|
||||
// 'docker wait': block until a container stops
|
||||
func (cli *DockerCli) CmdWait(args ...string) error {
|
||||
cmd := Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.")
|
||||
cmd := cli.Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -388,7 +391,7 @@ func (cli *DockerCli) CmdWait(args ...string) error {
|
||||
|
||||
// 'docker version': show version information
|
||||
func (cli *DockerCli) CmdVersion(args ...string) error {
|
||||
cmd := Subcmd("version", "", "Show the docker version information.")
|
||||
cmd := cli.Subcmd("version", "", "Show the docker version information.")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -439,7 +442,7 @@ func (cli *DockerCli) CmdVersion(args ...string) error {
|
||||
|
||||
// 'docker info': display system-wide information.
|
||||
func (cli *DockerCli) CmdInfo(args ...string) error {
|
||||
cmd := Subcmd("info", "", "Display system-wide information")
|
||||
cmd := cli.Subcmd("info", "", "Display system-wide information")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -460,6 +463,10 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
||||
|
||||
fmt.Fprintf(cli.out, "Containers: %d\n", out.Containers)
|
||||
fmt.Fprintf(cli.out, "Images: %d\n", out.Images)
|
||||
fmt.Fprintf(cli.out, "Driver: %s\n", out.Driver)
|
||||
for _, pair := range out.DriverStatus {
|
||||
fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1])
|
||||
}
|
||||
if out.Debug || os.Getenv("DEBUG") != "" {
|
||||
fmt.Fprintf(cli.out, "Debug mode (server): %v\n", out.Debug)
|
||||
fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "")
|
||||
@@ -491,7 +498,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdStop(args ...string) error {
|
||||
cmd := Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container (Send SIGTERM, and then SIGKILL after grace period)")
|
||||
cmd := cli.Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container (Send SIGTERM, and then SIGKILL after grace period)")
|
||||
nSeconds := cmd.Int("t", 10, "Number of seconds to wait for the container to stop before killing it.")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
@@ -518,7 +525,7 @@ func (cli *DockerCli) CmdStop(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdRestart(args ...string) error {
|
||||
cmd := Subcmd("restart", "[OPTIONS] CONTAINER [CONTAINER...]", "Restart a running container")
|
||||
cmd := cli.Subcmd("restart", "[OPTIONS] CONTAINER [CONTAINER...]", "Restart a running container")
|
||||
nSeconds := cmd.Int("t", 10, "Number of seconds to try to stop for before killing the container. Once killed it will then be restarted. Default=10")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
@@ -549,6 +556,9 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
|
||||
utils.CatchAll(sigc)
|
||||
go func() {
|
||||
for s := range sigc {
|
||||
if s == syscall.SIGCHLD {
|
||||
continue
|
||||
}
|
||||
if _, _, err := cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%d", cid, s), nil); err != nil {
|
||||
utils.Debugf("Error sending signal: %s", err)
|
||||
}
|
||||
@@ -558,7 +568,7 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdStart(args ...string) error {
|
||||
cmd := Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container")
|
||||
cmd := cli.Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container")
|
||||
attach := cmd.Bool("a", false, "Attach container's stdout/stderr and forward all signals to the process")
|
||||
openStdin := cmd.Bool("i", false, "Attach container's stdin")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
@@ -570,6 +580,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
||||
}
|
||||
|
||||
var cErr chan error
|
||||
var tty bool
|
||||
if *attach || *openStdin {
|
||||
if cmd.NArg() > 1 {
|
||||
return fmt.Errorf("Impossible to start and attach multiple containers at once.")
|
||||
@@ -586,17 +597,13 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
tty = container.Config.Tty
|
||||
|
||||
if !container.Config.Tty {
|
||||
sigc := cli.forwardAllSignals(cmd.Arg(0))
|
||||
defer utils.StopCatch(sigc)
|
||||
}
|
||||
|
||||
if container.Config.Tty && cli.isTerminal {
|
||||
if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var in io.ReadCloser
|
||||
|
||||
v := url.Values{}
|
||||
@@ -634,14 +641,20 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
||||
}
|
||||
return encounteredError
|
||||
}
|
||||
|
||||
if *openStdin || *attach {
|
||||
if tty && cli.isTerminal {
|
||||
if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
|
||||
utils.Errorf("Error monitoring TTY size: %s\n", err)
|
||||
}
|
||||
}
|
||||
return <-cErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdInspect(args ...string) error {
|
||||
cmd := Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container/image")
|
||||
cmd := cli.Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container/image")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -692,7 +705,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdTop(args ...string) error {
|
||||
cmd := Subcmd("top", "CONTAINER [ps OPTIONS]", "Lookup the running processes of a container")
|
||||
cmd := cli.Subcmd("top", "CONTAINER [ps OPTIONS]", "Lookup the running processes of a container")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -724,7 +737,7 @@ func (cli *DockerCli) CmdTop(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdPort(args ...string) error {
|
||||
cmd := Subcmd("port", "CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT")
|
||||
cmd := cli.Subcmd("port", "CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -766,7 +779,7 @@ func (cli *DockerCli) CmdPort(args ...string) error {
|
||||
|
||||
// 'docker rmi IMAGE' removes all images with the name IMAGE
|
||||
func (cli *DockerCli) CmdRmi(args ...string) error {
|
||||
cmd := Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images")
|
||||
cmd := cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -802,7 +815,7 @@ func (cli *DockerCli) CmdRmi(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdHistory(args ...string) error {
|
||||
cmd := Subcmd("history", "[OPTIONS] IMAGE", "Show the history of an image")
|
||||
cmd := cli.Subcmd("history", "[OPTIONS] IMAGE", "Show the history of an image")
|
||||
quiet := cmd.Bool("q", false, "only show numeric IDs")
|
||||
noTrunc := cmd.Bool("notrunc", false, "Don't truncate output")
|
||||
|
||||
@@ -838,7 +851,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
|
||||
fmt.Fprintf(w, "%s\t", utils.TruncateID(out.ID))
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s ago\t", utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
|
||||
fmt.Fprintf(w, "%s ago\t", utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.Created, 0))))
|
||||
|
||||
if *noTrunc {
|
||||
fmt.Fprintf(w, "%s\t", out.CreatedBy)
|
||||
@@ -859,7 +872,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdRm(args ...string) error {
|
||||
cmd := Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers")
|
||||
cmd := cli.Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers")
|
||||
v := cmd.Bool("v", false, "Remove the volumes associated to the container")
|
||||
link := cmd.Bool("link", false, "Remove the specified link and not the underlying container")
|
||||
|
||||
@@ -893,7 +906,7 @@ func (cli *DockerCli) CmdRm(args ...string) error {
|
||||
|
||||
// 'docker kill NAME' kills a running container
|
||||
func (cli *DockerCli) CmdKill(args ...string) error {
|
||||
cmd := Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container (send SIGKILL)")
|
||||
cmd := cli.Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container (send SIGKILL)")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -915,7 +928,7 @@ func (cli *DockerCli) CmdKill(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdImport(args ...string) error {
|
||||
cmd := Subcmd("import", "URL|- [REPOSITORY[:TAG]]", "Create a new filesystem image from the contents of a tarball(.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz).")
|
||||
cmd := cli.Subcmd("import", "URL|- [REPOSITORY[:TAG]]", "Create a new filesystem image from the contents of a tarball(.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz).")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
@@ -949,7 +962,7 @@ func (cli *DockerCli) CmdImport(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdPush(args ...string) error {
|
||||
cmd := Subcmd("push", "NAME", "Push an image or a repository to the registry")
|
||||
cmd := cli.Subcmd("push", "NAME", "Push an image or a repository to the registry")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -1011,7 +1024,7 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdPull(args ...string) error {
|
||||
cmd := Subcmd("pull", "NAME", "Pull an image or a repository from the registry")
|
||||
cmd := cli.Subcmd("pull", "NAME", "Pull an image or a repository from the registry")
|
||||
tag := cmd.String("t", "", "Download tagged image in repository")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
@@ -1071,9 +1084,9 @@ func (cli *DockerCli) CmdPull(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdImages(args ...string) error {
|
||||
cmd := Subcmd("images", "[OPTIONS] [NAME]", "List images")
|
||||
cmd := cli.Subcmd("images", "[OPTIONS] [NAME]", "List images")
|
||||
quiet := cmd.Bool("q", false, "only show numeric IDs")
|
||||
all := cmd.Bool("a", false, "show all images")
|
||||
all := cmd.Bool("a", false, "show all images (by default filter out the intermediate images used to build)")
|
||||
noTrunc := cmd.Bool("notrunc", false, "Don't truncate output")
|
||||
flViz := cmd.Bool("viz", false, "output graph in graphviz format")
|
||||
flTree := cmd.Bool("tree", false, "output graph in tree format")
|
||||
@@ -1195,7 +1208,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t", repo, tag, out.ID, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t", repo, tag, out.ID, utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.Created, 0))))
|
||||
if out.VirtualSize > 0 {
|
||||
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.Size), utils.HumanSize(out.VirtualSize))
|
||||
} else {
|
||||
@@ -1270,7 +1283,7 @@ func displayablePorts(ports []APIPort) string {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdPs(args ...string) error {
|
||||
cmd := Subcmd("ps", "[OPTIONS]", "List containers")
|
||||
cmd := cli.Subcmd("ps", "[OPTIONS]", "List containers")
|
||||
quiet := cmd.Bool("q", false, "Only display numeric IDs")
|
||||
size := cmd.Bool("s", false, "Display sizes")
|
||||
all := cmd.Bool("a", false, "Show all containers. Only running containers are shown by default.")
|
||||
@@ -1337,7 +1350,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
||||
if !*noTrunc {
|
||||
out.Command = utils.Trunc(out.Command, 20)
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), strings.Join(out.Names, ","))
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), strings.Join(out.Names, ","))
|
||||
if *size {
|
||||
if out.SizeRootFs > 0 {
|
||||
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.SizeRw), utils.HumanSize(out.SizeRootFs))
|
||||
@@ -1359,10 +1372,10 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdCommit(args ...string) error {
|
||||
cmd := Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes")
|
||||
cmd := cli.Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes")
|
||||
flComment := cmd.String("m", "", "Commit message")
|
||||
flAuthor := cmd.String("author", "", "Author (eg. \"John Hannibal Smith <hannibal@a-team.com>\"")
|
||||
flConfig := cmd.String("run", "", "Config automatically applied when the image is run. "+`(ex: {"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`)
|
||||
flConfig := cmd.String("run", "", "Config automatically applied when the image is run. "+`(ex: -run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`)
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -1411,7 +1424,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdEvents(args ...string) error {
|
||||
cmd := Subcmd("events", "[OPTIONS]", "Get real time events from the server")
|
||||
cmd := cli.Subcmd("events", "[OPTIONS]", "Get real time events from the server")
|
||||
since := cmd.String("since", "", "Show previously created events and then stream.")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
@@ -1444,7 +1457,7 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdExport(args ...string) error {
|
||||
cmd := Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive")
|
||||
cmd := cli.Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive to STDOUT")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -1461,7 +1474,7 @@ func (cli *DockerCli) CmdExport(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdDiff(args ...string) error {
|
||||
cmd := Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem")
|
||||
cmd := cli.Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -1487,7 +1500,7 @@ func (cli *DockerCli) CmdDiff(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdLogs(args ...string) error {
|
||||
cmd := Subcmd("logs", "CONTAINER", "Fetch the logs of a container")
|
||||
cmd := cli.Subcmd("logs", "CONTAINER", "Fetch the logs of a container")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -1514,7 +1527,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdAttach(args ...string) error {
|
||||
cmd := Subcmd("attach", "[OPTIONS] CONTAINER", "Attach to a running container")
|
||||
cmd := cli.Subcmd("attach", "[OPTIONS] CONTAINER", "Attach to a running container")
|
||||
noStdin := cmd.Bool("nostdin", false, "Do not attach stdin")
|
||||
proxy := cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
@@ -1536,7 +1549,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if !container.State.Running {
|
||||
if !container.State.IsRunning() {
|
||||
return fmt.Errorf("Impossible to attach to a stopped container, start it first")
|
||||
}
|
||||
|
||||
@@ -1569,7 +1582,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdSearch(args ...string) error {
|
||||
cmd := Subcmd("search", "TERM", "Search the docker index for images")
|
||||
cmd := cli.Subcmd("search", "TERM", "Search the docker index for images")
|
||||
noTrunc := cmd.Bool("notrunc", false, "Don't truncate output")
|
||||
trusted := cmd.Bool("trusted", false, "Only show trusted builds")
|
||||
stars := cmd.Int("stars", 0, "Only displays with at least xxx stars")
|
||||
@@ -1625,15 +1638,7 @@ type ports []int
|
||||
// AttachOpts stores arguments to 'docker run -a', eg. which streams to attach to
|
||||
type AttachOpts map[string]bool
|
||||
|
||||
func NewAttachOpts() AttachOpts {
|
||||
return make(AttachOpts)
|
||||
}
|
||||
|
||||
func (opts AttachOpts) String() string {
|
||||
// Cast to underlying map type to avoid infinite recursion
|
||||
return fmt.Sprintf("%v", map[string]bool(opts))
|
||||
}
|
||||
|
||||
func (opts AttachOpts) String() string { return fmt.Sprintf("%v", map[string]bool(opts)) }
|
||||
func (opts AttachOpts) Set(val string) error {
|
||||
if val != "stdin" && val != "stdout" && val != "stderr" {
|
||||
return fmt.Errorf("Unsupported stream name: %s", val)
|
||||
@@ -1642,24 +1647,22 @@ func (opts AttachOpts) Set(val string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (opts AttachOpts) Get(val string) bool {
|
||||
if res, exists := opts[val]; exists {
|
||||
return res
|
||||
// LinkOpts stores arguments to `docker run -link`
|
||||
type LinkOpts []string
|
||||
|
||||
func (link *LinkOpts) String() string { return fmt.Sprintf("%v", []string(*link)) }
|
||||
func (link *LinkOpts) Set(val string) error {
|
||||
if _, err := parseLink(val); err != nil {
|
||||
return err
|
||||
}
|
||||
return false
|
||||
*link = append(*link, val)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PathOpts stores a unique set of absolute paths
|
||||
type PathOpts map[string]struct{}
|
||||
|
||||
func NewPathOpts() PathOpts {
|
||||
return make(PathOpts)
|
||||
}
|
||||
|
||||
func (opts PathOpts) String() string {
|
||||
return fmt.Sprintf("%v", map[string]struct{}(opts))
|
||||
}
|
||||
|
||||
func (opts PathOpts) String() string { return fmt.Sprintf("%v", map[string]struct{}(opts)) }
|
||||
func (opts PathOpts) Set(val string) error {
|
||||
var containerPath string
|
||||
|
||||
@@ -1681,7 +1684,7 @@ func (opts PathOpts) Set(val string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdTag(args ...string) error {
|
||||
cmd := Subcmd("tag", "[OPTIONS] IMAGE REPOSITORY[:TAG]", "Tag an image into a repository")
|
||||
cmd := cli.Subcmd("tag", "[OPTIONS] IMAGE REPOSITORY[:TAG]", "Tag an image into a repository")
|
||||
force := cmd.Bool("f", false, "Force")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
@@ -1714,8 +1717,220 @@ func (cli *DockerCli) CmdTag(args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
//FIXME Only used in tests
|
||||
func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
|
||||
cmd := flag.NewFlagSet("run", flag.ContinueOnError)
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
cmd.Usage = nil
|
||||
return parseRun(cmd, args, capabilities)
|
||||
}
|
||||
|
||||
func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
|
||||
var (
|
||||
// FIXME: use utils.ListOpts for attach and volumes?
|
||||
flAttach = AttachOpts{}
|
||||
flVolumes = PathOpts{}
|
||||
flLinks = LinkOpts{}
|
||||
|
||||
flPublish utils.ListOpts
|
||||
flExpose utils.ListOpts
|
||||
flEnv utils.ListOpts
|
||||
flDns utils.ListOpts
|
||||
flVolumesFrom utils.ListOpts
|
||||
flLxcOpts utils.ListOpts
|
||||
|
||||
flAutoRemove = cmd.Bool("rm", false, "Automatically remove the container when it exits (incompatible with -d)")
|
||||
flDetach = cmd.Bool("d", false, "Detached mode: Run container in the background, print new container id")
|
||||
flNetwork = cmd.Bool("n", true, "Enable networking for this container")
|
||||
flPrivileged = cmd.Bool("privileged", false, "Give extended privileges to this container")
|
||||
flPublishAll = cmd.Bool("P", false, "Publish all exposed ports to the host interfaces")
|
||||
flStdin = cmd.Bool("i", false, "Keep stdin open even if not attached")
|
||||
flTty = cmd.Bool("t", false, "Allocate a pseudo-tty")
|
||||
flContainerIDFile = cmd.String("cidfile", "", "Write the container ID to the file")
|
||||
flEntrypoint = cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image")
|
||||
flHostname = cmd.String("h", "", "Container host name")
|
||||
flMemoryString = cmd.String("m", "", "Memory limit (format: <number><optional unit>, where unit = b, k, m or g)")
|
||||
flUser = cmd.String("u", "", "Username or UID")
|
||||
flWorkingDir = cmd.String("w", "", "Working directory inside the container")
|
||||
flCpuShares = cmd.Int64("c", 0, "CPU shares (relative weight)")
|
||||
|
||||
// For documentation purpose
|
||||
_ = cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)")
|
||||
_ = cmd.String("name", "", "Assign a name to the container")
|
||||
)
|
||||
|
||||
cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.")
|
||||
cmd.Var(flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)")
|
||||
cmd.Var(&flLinks, "link", "Add link to another container (name:alias)")
|
||||
|
||||
cmd.Var(&flPublish, "p", fmt.Sprintf("Publish a container's port to the host (format: %s) (use 'docker port' to see the actual mapping)", PortSpecTemplateFormat))
|
||||
cmd.Var(&flExpose, "expose", "Expose a port from the container without publishing it to your host")
|
||||
cmd.Var(&flEnv, "e", "Set environment variables")
|
||||
cmd.Var(&flDns, "dns", "Set custom dns servers")
|
||||
cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
|
||||
cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
|
||||
// Check if the kernel supports memory limit cgroup.
|
||||
if capabilities != nil && *flMemoryString != "" && !capabilities.MemoryLimit {
|
||||
*flMemoryString = ""
|
||||
}
|
||||
|
||||
// Validate input params
|
||||
if *flDetach && len(flAttach) > 0 {
|
||||
return nil, nil, cmd, ErrConflictAttachDetach
|
||||
}
|
||||
if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
|
||||
return nil, nil, cmd, ErrInvalidWorikingDirectory
|
||||
}
|
||||
if *flDetach && *flAutoRemove {
|
||||
return nil, nil, cmd, ErrConflictDetachAutoRemove
|
||||
}
|
||||
|
||||
// If neither -d or -a are set, attach to everything by default
|
||||
if len(flAttach) == 0 && !*flDetach {
|
||||
if !*flDetach {
|
||||
flAttach.Set("stdout")
|
||||
flAttach.Set("stderr")
|
||||
if *flStdin {
|
||||
flAttach.Set("stdin")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var envs []string
|
||||
for _, env := range flEnv {
|
||||
arr := strings.Split(env, "=")
|
||||
if len(arr) > 1 {
|
||||
envs = append(envs, env)
|
||||
} else {
|
||||
v := os.Getenv(env)
|
||||
envs = append(envs, env+"="+v)
|
||||
}
|
||||
}
|
||||
|
||||
var flMemory int64
|
||||
if *flMemoryString != "" {
|
||||
parsedMemory, err := utils.RAMInBytes(*flMemoryString)
|
||||
if err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
flMemory = parsedMemory
|
||||
}
|
||||
|
||||
var binds []string
|
||||
// add any bind targets to the list of container volumes
|
||||
for bind := range flVolumes {
|
||||
arr := strings.Split(bind, ":")
|
||||
if len(arr) > 1 {
|
||||
if arr[0] == "/" {
|
||||
return nil, nil, cmd, fmt.Errorf("Invalid bind mount: source can't be '/'")
|
||||
}
|
||||
dstDir := arr[1]
|
||||
flVolumes[dstDir] = struct{}{}
|
||||
binds = append(binds, bind)
|
||||
delete(flVolumes, bind)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
parsedArgs = cmd.Args()
|
||||
runCmd []string
|
||||
entrypoint []string
|
||||
image string
|
||||
)
|
||||
if len(parsedArgs) >= 1 {
|
||||
image = cmd.Arg(0)
|
||||
}
|
||||
if len(parsedArgs) > 1 {
|
||||
runCmd = parsedArgs[1:]
|
||||
}
|
||||
if *flEntrypoint != "" {
|
||||
entrypoint = []string{*flEntrypoint}
|
||||
}
|
||||
|
||||
lxcConf, err := parseLxcConfOpts(flLxcOpts)
|
||||
if err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
|
||||
var (
|
||||
domainname string
|
||||
hostname = *flHostname
|
||||
parts = strings.SplitN(hostname, ".", 2)
|
||||
)
|
||||
if len(parts) > 1 {
|
||||
hostname = parts[0]
|
||||
domainname = parts[1]
|
||||
}
|
||||
|
||||
ports, portBindings, err := parsePortSpecs(flPublish)
|
||||
if err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
|
||||
// Merge in exposed ports to the map of published ports
|
||||
for _, e := range flExpose {
|
||||
if strings.Contains(e, ":") {
|
||||
return nil, nil, cmd, fmt.Errorf("Invalid port format for -expose: %s", e)
|
||||
}
|
||||
p := NewPort(splitProtoPort(e))
|
||||
if _, exists := ports[p]; !exists {
|
||||
ports[p] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
config := &Config{
|
||||
Hostname: hostname,
|
||||
Domainname: domainname,
|
||||
PortSpecs: nil, // Deprecated
|
||||
ExposedPorts: ports,
|
||||
User: *flUser,
|
||||
Tty: *flTty,
|
||||
NetworkDisabled: !*flNetwork,
|
||||
OpenStdin: *flStdin,
|
||||
Memory: flMemory,
|
||||
CpuShares: *flCpuShares,
|
||||
AttachStdin: flAttach["stdin"],
|
||||
AttachStdout: flAttach["stdout"],
|
||||
AttachStderr: flAttach["stderr"],
|
||||
Env: envs,
|
||||
Cmd: runCmd,
|
||||
Dns: flDns,
|
||||
Image: image,
|
||||
Volumes: flVolumes,
|
||||
VolumesFrom: strings.Join(flVolumesFrom, ","),
|
||||
Entrypoint: entrypoint,
|
||||
WorkingDir: *flWorkingDir,
|
||||
}
|
||||
|
||||
hostConfig := &HostConfig{
|
||||
Binds: binds,
|
||||
ContainerIDFile: *flContainerIDFile,
|
||||
LxcConf: lxcConf,
|
||||
Privileged: *flPrivileged,
|
||||
PortBindings: portBindings,
|
||||
Links: flLinks,
|
||||
PublishAllPorts: *flPublishAll,
|
||||
}
|
||||
|
||||
if capabilities != nil && flMemory > 0 && !capabilities.SwapLimit {
|
||||
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
|
||||
config.MemorySwap = -1
|
||||
}
|
||||
|
||||
// When allocating stdin in attached mode, close stdin at client disconnect
|
||||
if config.OpenStdin && config.AttachStdin {
|
||||
config.StdinOnce = true
|
||||
}
|
||||
return config, hostConfig, cmd, nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
config, hostConfig, cmd, err := ParseRun(args, nil)
|
||||
config, hostConfig, cmd, err := parseRun(cli.Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container"), args, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1724,30 +1939,33 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
flRm := cmd.Lookup("rm")
|
||||
autoRemove, _ := strconv.ParseBool(flRm.Value.String())
|
||||
// Retrieve relevant client-side config
|
||||
var (
|
||||
flName = cmd.Lookup("name")
|
||||
flRm = cmd.Lookup("rm")
|
||||
flSigProxy = cmd.Lookup("sig-proxy")
|
||||
autoRemove, _ = strconv.ParseBool(flRm.Value.String())
|
||||
sigProxy, _ = strconv.ParseBool(flSigProxy.Value.String())
|
||||
)
|
||||
|
||||
flSigProxy := cmd.Lookup("sig-proxy")
|
||||
sigProxy, _ := strconv.ParseBool(flSigProxy.Value.String())
|
||||
flName := cmd.Lookup("name")
|
||||
// Disable sigProxy in case on TTY
|
||||
if config.Tty {
|
||||
sigProxy = false
|
||||
}
|
||||
|
||||
var containerIDFile *os.File
|
||||
var containerIDFile io.WriteCloser
|
||||
if len(hostConfig.ContainerIDFile) > 0 {
|
||||
if _, err := ioutil.ReadFile(hostConfig.ContainerIDFile); err == nil {
|
||||
if _, err := os.Stat(hostConfig.ContainerIDFile); err == nil {
|
||||
return fmt.Errorf("cid file found, make sure the other container isn't running or delete %s", hostConfig.ContainerIDFile)
|
||||
}
|
||||
containerIDFile, err = os.Create(hostConfig.ContainerIDFile)
|
||||
if err != nil {
|
||||
if containerIDFile, err = os.Create(hostConfig.ContainerIDFile); err != nil {
|
||||
return fmt.Errorf("failed to create the container ID file: %s", err)
|
||||
}
|
||||
defer containerIDFile.Close()
|
||||
}
|
||||
|
||||
containerValues := url.Values{}
|
||||
name := flName.Value.String()
|
||||
if name != "" {
|
||||
if name := flName.Value.String(); name != "" {
|
||||
containerValues.Set("name", name)
|
||||
}
|
||||
|
||||
@@ -1768,8 +1986,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
v.Set("tag", tag)
|
||||
|
||||
// Resolve the Repository name from fqn to endpoint + name
|
||||
var endpoint string
|
||||
endpoint, _, err = registry.ResolveRepositoryName(repos)
|
||||
endpoint, _, err := registry.ResolveRepositoryName(repos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1787,32 +2004,27 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
registryAuthHeader := []string{
|
||||
base64.URLEncoding.EncodeToString(buf),
|
||||
}
|
||||
err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err, map[string][]string{
|
||||
"X-Registry-Auth": registryAuthHeader,
|
||||
})
|
||||
if err != nil {
|
||||
if err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err, map[string][]string{"X-Registry-Auth": registryAuthHeader}); err != nil {
|
||||
return err
|
||||
}
|
||||
body, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config)
|
||||
if err != nil {
|
||||
if body, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
runResult := &APIRun{}
|
||||
err = json.Unmarshal(body, runResult)
|
||||
if err != nil {
|
||||
var runResult APIRun
|
||||
if err := json.Unmarshal(body, &runResult); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, warning := range runResult.Warnings {
|
||||
fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
|
||||
}
|
||||
|
||||
if len(hostConfig.ContainerIDFile) > 0 {
|
||||
if _, err = containerIDFile.WriteString(runResult.ID); err != nil {
|
||||
if _, err = containerIDFile.Write([]byte(runResult.ID)); err != nil {
|
||||
return fmt.Errorf("failed to write the container ID to the file: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -1823,27 +2035,38 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
}
|
||||
|
||||
var (
|
||||
wait chan struct{}
|
||||
errCh chan error
|
||||
waitDisplayId chan struct{}
|
||||
errCh chan error
|
||||
)
|
||||
|
||||
if !config.AttachStdout && !config.AttachStderr {
|
||||
// Make this asynchrone in order to let the client write to stdin before having to read the ID
|
||||
wait = make(chan struct{})
|
||||
waitDisplayId = make(chan struct{})
|
||||
go func() {
|
||||
defer close(wait)
|
||||
defer close(waitDisplayId)
|
||||
fmt.Fprintf(cli.out, "%s\n", runResult.ID)
|
||||
}()
|
||||
}
|
||||
|
||||
hijacked := make(chan bool)
|
||||
// We need to instanciate the chan because the select needs it. It can
|
||||
// be closed but can't be uninitialized.
|
||||
hijacked := make(chan io.Closer)
|
||||
|
||||
// Block the return until the chan gets closed
|
||||
defer func() {
|
||||
utils.Debugf("End of CmdRun(), Waiting for hijack to finish.")
|
||||
if _, ok := <-hijacked; ok {
|
||||
utils.Errorf("Hijack did not finish (chan still open)")
|
||||
}
|
||||
}()
|
||||
|
||||
if config.AttachStdin || config.AttachStdout || config.AttachStderr {
|
||||
|
||||
v := url.Values{}
|
||||
var (
|
||||
out, stderr io.Writer
|
||||
in io.ReadCloser
|
||||
v = url.Values{}
|
||||
)
|
||||
v.Set("stream", "1")
|
||||
var out, stderr io.Writer
|
||||
var in io.ReadCloser
|
||||
|
||||
if config.AttachStdin {
|
||||
v.Set("stdin", "1")
|
||||
@@ -1871,7 +2094,12 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
|
||||
// Acknowledge the hijack before starting
|
||||
select {
|
||||
case <-hijacked:
|
||||
case closer := <-hijacked:
|
||||
// Make sure that hijack gets closed when returning. (result
|
||||
// in closing hijack chan and freeing server's goroutines.
|
||||
if closer != nil {
|
||||
defer closer.Close()
|
||||
}
|
||||
case err := <-errCh:
|
||||
if err != nil {
|
||||
utils.Debugf("Error hijack: %s", err)
|
||||
@@ -1897,36 +2125,42 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Detached mode: wait for the id to be displayed and return.
|
||||
if !config.AttachStdout && !config.AttachStderr {
|
||||
// Detached mode
|
||||
<-wait
|
||||
} else {
|
||||
running, status, err := getExitCode(cli, runResult.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if autoRemove {
|
||||
if running {
|
||||
return fmt.Errorf("Impossible to auto-remove a detached container")
|
||||
}
|
||||
// Wait for the process to
|
||||
if _, _, err := cli.call("POST", "/containers/"+runResult.ID+"/wait", nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, _, err := cli.call("DELETE", "/containers/"+runResult.ID, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if status != 0 {
|
||||
return &utils.StatusError{Status: status}
|
||||
}
|
||||
<-waitDisplayId
|
||||
return nil
|
||||
}
|
||||
|
||||
var status int
|
||||
|
||||
// Attached mode
|
||||
if autoRemove {
|
||||
// Autoremove: wait for the container to finish, retrieve
|
||||
// the exit code and remove the container
|
||||
if _, _, err := cli.call("POST", "/containers/"+runResult.ID+"/wait", nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, status, err = getExitCode(cli, runResult.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, _, err := cli.call("DELETE", "/containers/"+runResult.ID, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// No Autoremove: Simply retrieve the exit code
|
||||
if _, status, err = getExitCode(cli, runResult.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if status != 0 {
|
||||
return &utils.StatusError{Status: status}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdCp(args ...string) error {
|
||||
cmd := Subcmd("cp", "CONTAINER:RESOURCE HOSTPATH", "Copy files/folders from the RESOURCE to the HOSTPATH")
|
||||
cmd := cli.Subcmd("cp", "CONTAINER:PATH HOSTPATH", "Copy files/folders from the PATH to the HOSTPATH")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -1940,7 +2174,7 @@ func (cli *DockerCli) CmdCp(args ...string) error {
|
||||
info := strings.Split(cmd.Arg(0), ":")
|
||||
|
||||
if len(info) != 2 {
|
||||
return fmt.Errorf("Error: Resource not specified")
|
||||
return fmt.Errorf("Error: Path not specified")
|
||||
}
|
||||
|
||||
copyData.Resource = info[1]
|
||||
@@ -1960,6 +2194,41 @@ func (cli *DockerCli) CmdCp(args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdSave(args ...string) error {
|
||||
cmd := cli.Subcmd("save", "IMAGE DESTINATION", "Save an image to a tar archive")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cmd.NArg() != 1 {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
|
||||
image := cmd.Arg(0)
|
||||
if err := cli.stream("GET", "/images/"+image+"/get", nil, cli.out, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdLoad(args ...string) error {
|
||||
cmd := cli.Subcmd("load", "SOURCE", "Load an image from a tar archive")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cmd.NArg() != 0 {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := cli.stream("POST", "/images/load", cli.in, cli.out, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, error) {
|
||||
var params io.Reader
|
||||
if data != nil {
|
||||
@@ -2071,16 +2340,20 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h
|
||||
}
|
||||
|
||||
if matchesContentType(resp.Header.Get("Content-Type"), "application/json") {
|
||||
return utils.DisplayJSONMessagesStream(resp.Body, out)
|
||||
} else {
|
||||
if _, err := io.Copy(out, resp.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
return utils.DisplayJSONMessagesStream(resp.Body, out, cli.isTerminal)
|
||||
}
|
||||
if _, err := io.Copy(out, resp.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan bool) error {
|
||||
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer) error {
|
||||
defer func() {
|
||||
if started != nil {
|
||||
close(started)
|
||||
}
|
||||
}()
|
||||
// fixme: refactor client to support redirect
|
||||
re := regexp.MustCompile("/+")
|
||||
path = re.ReplaceAllString(path, "/")
|
||||
@@ -2110,7 +2383,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
||||
defer rwc.Close()
|
||||
|
||||
if started != nil {
|
||||
started <- true
|
||||
started <- rwc
|
||||
}
|
||||
|
||||
var receiveStdout chan error
|
||||
@@ -2211,12 +2484,12 @@ func (cli *DockerCli) monitorTtySize(id string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func Subcmd(name, signature, description string) *flag.FlagSet {
|
||||
func (cli *DockerCli) Subcmd(name, signature, description string) *flag.FlagSet {
|
||||
flags := flag.NewFlagSet(name, flag.ContinueOnError)
|
||||
flags.Usage = func() {
|
||||
// FIXME: use custom stdout or return error
|
||||
fmt.Fprintf(os.Stdout, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description)
|
||||
fmt.Fprintf(cli.err, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description)
|
||||
flags.PrintDefaults()
|
||||
os.Exit(2)
|
||||
}
|
||||
return flags
|
||||
}
|
||||
@@ -2261,7 +2534,7 @@ func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
|
||||
if err := json.Unmarshal(body, c); err != nil {
|
||||
return false, -1, err
|
||||
}
|
||||
return c.State.Running, c.State.ExitCode, nil
|
||||
return c.State.IsRunning(), c.State.GetExitCode(), nil
|
||||
}
|
||||
|
||||
func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli {
|
||||
|
||||
@@ -16,6 +16,7 @@ type DaemonConfig struct {
|
||||
BridgeIface string
|
||||
DefaultIp net.IP
|
||||
InterContainerCommunication bool
|
||||
GraphDriver string
|
||||
}
|
||||
|
||||
// ConfigFromJob creates and returns a new DaemonConfig object
|
||||
@@ -37,5 +38,6 @@ func ConfigFromJob(job *engine.Job) *DaemonConfig {
|
||||
}
|
||||
config.DefaultIp = net.ParseIP(job.Getenv("DefaultIp"))
|
||||
config.InterContainerCommunication = job.GetenvBool("InterContainerCommunication")
|
||||
config.GraphDriver = job.Getenv("GraphDriver")
|
||||
return &config
|
||||
}
|
||||
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCompareConfig(t *testing.T) {
|
||||
volumes1 := make(map[string]struct{})
|
||||
volumes1["/test1"] = struct{}{}
|
||||
config1 := Config{
|
||||
Dns: []string{"1.1.1.1", "2.2.2.2"},
|
||||
PortSpecs: []string{"1111:1111", "2222:2222"},
|
||||
Env: []string{"VAR1=1", "VAR2=2"},
|
||||
VolumesFrom: "11111111",
|
||||
Volumes: volumes1,
|
||||
}
|
||||
config2 := Config{
|
||||
Dns: []string{"0.0.0.0", "2.2.2.2"},
|
||||
PortSpecs: []string{"1111:1111", "2222:2222"},
|
||||
Env: []string{"VAR1=1", "VAR2=2"},
|
||||
VolumesFrom: "11111111",
|
||||
Volumes: volumes1,
|
||||
}
|
||||
config3 := Config{
|
||||
Dns: []string{"1.1.1.1", "2.2.2.2"},
|
||||
PortSpecs: []string{"0000:0000", "2222:2222"},
|
||||
Env: []string{"VAR1=1", "VAR2=2"},
|
||||
VolumesFrom: "11111111",
|
||||
Volumes: volumes1,
|
||||
}
|
||||
config4 := Config{
|
||||
Dns: []string{"1.1.1.1", "2.2.2.2"},
|
||||
PortSpecs: []string{"0000:0000", "2222:2222"},
|
||||
Env: []string{"VAR1=1", "VAR2=2"},
|
||||
VolumesFrom: "22222222",
|
||||
Volumes: volumes1,
|
||||
}
|
||||
volumes2 := make(map[string]struct{})
|
||||
volumes2["/test2"] = struct{}{}
|
||||
config5 := Config{
|
||||
Dns: []string{"1.1.1.1", "2.2.2.2"},
|
||||
PortSpecs: []string{"0000:0000", "2222:2222"},
|
||||
Env: []string{"VAR1=1", "VAR2=2"},
|
||||
VolumesFrom: "11111111",
|
||||
Volumes: volumes2,
|
||||
}
|
||||
if CompareConfig(&config1, &config2) {
|
||||
t.Fatalf("CompareConfig should return false, Dns are different")
|
||||
}
|
||||
if CompareConfig(&config1, &config3) {
|
||||
t.Fatalf("CompareConfig should return false, PortSpecs are different")
|
||||
}
|
||||
if CompareConfig(&config1, &config4) {
|
||||
t.Fatalf("CompareConfig should return false, VolumesFrom are different")
|
||||
}
|
||||
if CompareConfig(&config1, &config5) {
|
||||
t.Fatalf("CompareConfig should return false, Volumes are different")
|
||||
}
|
||||
if !CompareConfig(&config1, &config1) {
|
||||
t.Fatalf("CompareConfig should return true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeConfig(t *testing.T) {
|
||||
volumesImage := make(map[string]struct{})
|
||||
volumesImage["/test1"] = struct{}{}
|
||||
volumesImage["/test2"] = struct{}{}
|
||||
configImage := &Config{
|
||||
Dns: []string{"1.1.1.1", "2.2.2.2"},
|
||||
PortSpecs: []string{"1111:1111", "2222:2222"},
|
||||
Env: []string{"VAR1=1", "VAR2=2"},
|
||||
VolumesFrom: "1111",
|
||||
Volumes: volumesImage,
|
||||
}
|
||||
|
||||
volumesUser := make(map[string]struct{})
|
||||
volumesUser["/test3"] = struct{}{}
|
||||
configUser := &Config{
|
||||
Dns: []string{"3.3.3.3"},
|
||||
PortSpecs: []string{"3333:2222", "3333:3333"},
|
||||
Env: []string{"VAR2=3", "VAR3=3"},
|
||||
Volumes: volumesUser,
|
||||
}
|
||||
|
||||
if err := MergeConfig(configUser, configImage); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if len(configUser.Dns) != 3 {
|
||||
t.Fatalf("Expected 3 dns, 1.1.1.1, 2.2.2.2 and 3.3.3.3, found %d", len(configUser.Dns))
|
||||
}
|
||||
for _, dns := range configUser.Dns {
|
||||
if dns != "1.1.1.1" && dns != "2.2.2.2" && dns != "3.3.3.3" {
|
||||
t.Fatalf("Expected 1.1.1.1 or 2.2.2.2 or 3.3.3.3, found %s", dns)
|
||||
}
|
||||
}
|
||||
|
||||
if len(configUser.ExposedPorts) != 3 {
|
||||
t.Fatalf("Expected 3 ExposedPorts, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts))
|
||||
}
|
||||
for portSpecs := range configUser.ExposedPorts {
|
||||
if portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" {
|
||||
t.Fatalf("Expected 1111 or 2222 or 3333, found %s", portSpecs)
|
||||
}
|
||||
}
|
||||
if len(configUser.Env) != 3 {
|
||||
t.Fatalf("Expected 3 env var, VAR1=1, VAR2=3 and VAR3=3, found %d", len(configUser.Env))
|
||||
}
|
||||
for _, env := range configUser.Env {
|
||||
if env != "VAR1=1" && env != "VAR2=3" && env != "VAR3=3" {
|
||||
t.Fatalf("Expected VAR1=1 or VAR2=3 or VAR3=3, found %s", env)
|
||||
}
|
||||
}
|
||||
|
||||
if len(configUser.Volumes) != 3 {
|
||||
t.Fatalf("Expected 3 volumes, /test1, /test2 and /test3, found %d", len(configUser.Volumes))
|
||||
}
|
||||
for v := range configUser.Volumes {
|
||||
if v != "/test1" && v != "/test2" && v != "/test3" {
|
||||
t.Fatalf("Expected /test1 or /test2 or /test3, found %s", v)
|
||||
}
|
||||
}
|
||||
|
||||
if configUser.VolumesFrom != "1111" {
|
||||
t.Fatalf("Expected VolumesFrom to be 1111, found %s", configUser.VolumesFrom)
|
||||
}
|
||||
|
||||
ports, _, err := parsePortSpecs([]string{"0000"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
configImage2 := &Config{
|
||||
ExposedPorts: ports,
|
||||
}
|
||||
|
||||
if err := MergeConfig(configUser, configImage2); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if len(configUser.ExposedPorts) != 4 {
|
||||
t.Fatalf("Expected 4 ExposedPorts, 0000, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts))
|
||||
}
|
||||
for portSpecs := range configUser.ExposedPorts {
|
||||
if portSpecs.Port() != "0000" && portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" {
|
||||
t.Fatalf("Expected 0000 or 1111 or 2222 or 3333, found %s", portSpecs)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+55
-255
@@ -4,9 +4,9 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/archive"
|
||||
"github.com/dotcloud/docker/graphdriver"
|
||||
"github.com/dotcloud/docker/term"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"github.com/kr/pty"
|
||||
@@ -17,14 +17,15 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Container struct {
|
||||
sync.Mutex
|
||||
root string // Path to the "home" of the container, including metadata.
|
||||
rootfs string // Path to the root filesystem of the container.
|
||||
|
||||
@@ -47,6 +48,7 @@ type Container struct {
|
||||
HostnamePath string
|
||||
HostsPath string
|
||||
Name string
|
||||
Driver string
|
||||
|
||||
cmd *exec.Cmd
|
||||
stdout *utils.WriteBroadcaster
|
||||
@@ -157,218 +159,6 @@ func NewPort(proto, port string) Port {
|
||||
return Port(fmt.Sprintf("%s/%s", port, proto))
|
||||
}
|
||||
|
||||
func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
|
||||
cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
|
||||
if os.Getenv("TEST") != "" {
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
cmd.Usage = nil
|
||||
}
|
||||
|
||||
flHostname := cmd.String("h", "", "Container host name")
|
||||
flWorkingDir := cmd.String("w", "", "Working directory inside the container")
|
||||
flUser := cmd.String("u", "", "Username or UID")
|
||||
flDetach := cmd.Bool("d", false, "Detached mode: Run container in the background, print new container id")
|
||||
flAttach := NewAttachOpts()
|
||||
cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.")
|
||||
flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
|
||||
flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
|
||||
flMemoryString := cmd.String("m", "", "Memory limit (format: <number><optional unit>, where unit = b, k, m or g)")
|
||||
flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file")
|
||||
flNetwork := cmd.Bool("n", true, "Enable networking for this container")
|
||||
flPrivileged := cmd.Bool("privileged", false, "Give extended privileges to this container")
|
||||
flAutoRemove := cmd.Bool("rm", false, "Automatically remove the container when it exits (incompatible with -d)")
|
||||
cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)")
|
||||
cmd.String("name", "", "Assign a name to the container")
|
||||
flPublishAll := cmd.Bool("P", false, "Publish all exposed ports to the host interfaces")
|
||||
|
||||
if capabilities != nil && *flMemoryString != "" && !capabilities.MemoryLimit {
|
||||
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
|
||||
*flMemoryString = ""
|
||||
}
|
||||
|
||||
flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)")
|
||||
|
||||
var flPublish utils.ListOpts
|
||||
cmd.Var(&flPublish, "p", "Publish a container's port to the host (use 'docker port' to see the actual mapping)")
|
||||
|
||||
var flExpose utils.ListOpts
|
||||
cmd.Var(&flExpose, "expose", "Expose a port from the container without publishing it to your host")
|
||||
|
||||
var flEnv utils.ListOpts
|
||||
cmd.Var(&flEnv, "e", "Set environment variables")
|
||||
|
||||
var flDns utils.ListOpts
|
||||
cmd.Var(&flDns, "dns", "Set custom dns servers")
|
||||
|
||||
flVolumes := NewPathOpts()
|
||||
cmd.Var(flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)")
|
||||
|
||||
var flVolumesFrom utils.ListOpts
|
||||
cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
|
||||
|
||||
flEntrypoint := cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image")
|
||||
|
||||
var flLxcOpts utils.ListOpts
|
||||
cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
|
||||
|
||||
var flLinks utils.ListOpts
|
||||
cmd.Var(&flLinks, "link", "Add link to another container (name:alias)")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
if *flDetach && len(flAttach) > 0 {
|
||||
return nil, nil, cmd, ErrConflictAttachDetach
|
||||
}
|
||||
if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
|
||||
return nil, nil, cmd, ErrInvalidWorikingDirectory
|
||||
}
|
||||
if *flDetach && *flAutoRemove {
|
||||
return nil, nil, cmd, ErrConflictDetachAutoRemove
|
||||
}
|
||||
|
||||
// If neither -d or -a are set, attach to everything by default
|
||||
if len(flAttach) == 0 && !*flDetach {
|
||||
if !*flDetach {
|
||||
flAttach.Set("stdout")
|
||||
flAttach.Set("stderr")
|
||||
if *flStdin {
|
||||
flAttach.Set("stdin")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
envs := []string{}
|
||||
|
||||
for _, env := range flEnv {
|
||||
arr := strings.Split(env, "=")
|
||||
if len(arr) > 1 {
|
||||
envs = append(envs, env)
|
||||
} else {
|
||||
v := os.Getenv(env)
|
||||
envs = append(envs, env+"="+v)
|
||||
}
|
||||
}
|
||||
|
||||
var flMemory int64
|
||||
|
||||
if *flMemoryString != "" {
|
||||
parsedMemory, err := utils.RAMInBytes(*flMemoryString)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
|
||||
flMemory = parsedMemory
|
||||
}
|
||||
|
||||
var binds []string
|
||||
|
||||
// add any bind targets to the list of container volumes
|
||||
for bind := range flVolumes {
|
||||
arr := strings.Split(bind, ":")
|
||||
if len(arr) > 1 {
|
||||
if arr[0] == "/" {
|
||||
return nil, nil, cmd, fmt.Errorf("Invalid bind mount: source can't be '/'")
|
||||
}
|
||||
dstDir := arr[1]
|
||||
flVolumes[dstDir] = struct{}{}
|
||||
binds = append(binds, bind)
|
||||
delete(flVolumes, bind)
|
||||
}
|
||||
}
|
||||
|
||||
parsedArgs := cmd.Args()
|
||||
runCmd := []string{}
|
||||
entrypoint := []string{}
|
||||
image := ""
|
||||
if len(parsedArgs) >= 1 {
|
||||
image = cmd.Arg(0)
|
||||
}
|
||||
if len(parsedArgs) > 1 {
|
||||
runCmd = parsedArgs[1:]
|
||||
}
|
||||
if *flEntrypoint != "" {
|
||||
entrypoint = []string{*flEntrypoint}
|
||||
}
|
||||
|
||||
var lxcConf []KeyValuePair
|
||||
lxcConf, err := parseLxcConfOpts(flLxcOpts)
|
||||
if err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
|
||||
hostname := *flHostname
|
||||
domainname := ""
|
||||
|
||||
parts := strings.SplitN(hostname, ".", 2)
|
||||
if len(parts) > 1 {
|
||||
hostname = parts[0]
|
||||
domainname = parts[1]
|
||||
}
|
||||
|
||||
ports, portBindings, err := parsePortSpecs(flPublish)
|
||||
if err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
|
||||
// Merge in exposed ports to the map of published ports
|
||||
for _, e := range flExpose {
|
||||
if strings.Contains(e, ":") {
|
||||
return nil, nil, cmd, fmt.Errorf("Invalid port format for -expose: %s", e)
|
||||
}
|
||||
p := NewPort(splitProtoPort(e))
|
||||
if _, exists := ports[p]; !exists {
|
||||
ports[p] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
config := &Config{
|
||||
Hostname: hostname,
|
||||
Domainname: domainname,
|
||||
PortSpecs: nil, // Deprecated
|
||||
ExposedPorts: ports,
|
||||
User: *flUser,
|
||||
Tty: *flTty,
|
||||
NetworkDisabled: !*flNetwork,
|
||||
OpenStdin: *flStdin,
|
||||
Memory: flMemory,
|
||||
CpuShares: *flCpuShares,
|
||||
AttachStdin: flAttach.Get("stdin"),
|
||||
AttachStdout: flAttach.Get("stdout"),
|
||||
AttachStderr: flAttach.Get("stderr"),
|
||||
Env: envs,
|
||||
Cmd: runCmd,
|
||||
Dns: flDns,
|
||||
Image: image,
|
||||
Volumes: flVolumes,
|
||||
VolumesFrom: strings.Join(flVolumesFrom, ","),
|
||||
Entrypoint: entrypoint,
|
||||
WorkingDir: *flWorkingDir,
|
||||
}
|
||||
|
||||
hostConfig := &HostConfig{
|
||||
Binds: binds,
|
||||
ContainerIDFile: *flContainerIDFile,
|
||||
LxcConf: lxcConf,
|
||||
Privileged: *flPrivileged,
|
||||
PortBindings: portBindings,
|
||||
Links: flLinks,
|
||||
PublishAllPorts: *flPublishAll,
|
||||
}
|
||||
|
||||
if capabilities != nil && flMemory > 0 && !capabilities.SwapLimit {
|
||||
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
|
||||
config.MemorySwap = -1
|
||||
}
|
||||
|
||||
// When allocating stdin in attached mode, close stdin at client disconnect
|
||||
if config.OpenStdin && config.AttachStdin {
|
||||
config.StdinOnce = true
|
||||
}
|
||||
return config, hostConfig, cmd, nil
|
||||
}
|
||||
|
||||
type PortMapping map[string]string // Deprecated
|
||||
|
||||
type NetworkSettings struct {
|
||||
@@ -708,9 +498,10 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
|
||||
}
|
||||
|
||||
func (container *Container) Start() (err error) {
|
||||
container.State.Lock()
|
||||
defer container.State.Unlock()
|
||||
if container.State.Running {
|
||||
container.Lock()
|
||||
defer container.Unlock()
|
||||
|
||||
if container.State.IsRunning() {
|
||||
return fmt.Errorf("The container %s is already running.", container.ID)
|
||||
}
|
||||
defer func() {
|
||||
@@ -1044,7 +835,7 @@ func (container *Container) Start() (err error) {
|
||||
}
|
||||
// FIXME: save state on disk *first*, then converge
|
||||
// this way disk state is used as a journal, eg. we can restore after crash etc.
|
||||
container.State.setRunning(container.cmd.Process.Pid)
|
||||
container.State.SetRunning(container.cmd.Process.Pid)
|
||||
|
||||
// Init the lock
|
||||
container.waitLock = make(chan struct{})
|
||||
@@ -1052,14 +843,14 @@ func (container *Container) Start() (err error) {
|
||||
container.ToDisk()
|
||||
go container.monitor()
|
||||
|
||||
defer utils.Debugf("Container running: %v", container.State.Running)
|
||||
defer utils.Debugf("Container running: %v", container.State.IsRunning())
|
||||
// We wait for the container to be fully running.
|
||||
// Timeout after 5 seconds. In case of broken pipe, just retry.
|
||||
// Note: The container can run and finish correctly before
|
||||
// the end of this loop
|
||||
for now := time.Now(); time.Since(now) < 5*time.Second; {
|
||||
// If the container dies while waiting for it, just return
|
||||
if !container.State.Running {
|
||||
if !container.State.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
output, err := exec.Command("lxc-info", "-s", "-n", container.ID).CombinedOutput()
|
||||
@@ -1076,11 +867,11 @@ func (container *Container) Start() (err error) {
|
||||
if strings.Contains(string(output), "RUNNING") {
|
||||
return nil
|
||||
}
|
||||
utils.Debugf("Waiting for the container to start (running: %v): %s", container.State.Running, bytes.TrimSpace(output))
|
||||
utils.Debugf("Waiting for the container to start (running: %v): %s", container.State.IsRunning(), bytes.TrimSpace(output))
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
return ErrContainerStartTimeout
|
||||
}
|
||||
return ErrContainerStart
|
||||
@@ -1161,11 +952,12 @@ func (container *Container) allocateNetwork() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var iface *NetworkInterface
|
||||
var err error
|
||||
if container.State.Ghost {
|
||||
manager := container.runtime.networkManager
|
||||
if manager.disabled {
|
||||
var (
|
||||
iface *NetworkInterface
|
||||
err error
|
||||
)
|
||||
if container.State.IsGhost() {
|
||||
if manager := container.runtime.networkManager; manager.disabled {
|
||||
iface = &NetworkInterface{disabled: true}
|
||||
} else {
|
||||
iface = &NetworkInterface{
|
||||
@@ -1201,10 +993,12 @@ func (container *Container) allocateNetwork() error {
|
||||
}
|
||||
}
|
||||
|
||||
portSpecs := make(map[Port]struct{})
|
||||
bindings := make(map[Port][]PortBinding)
|
||||
var (
|
||||
portSpecs = make(map[Port]struct{})
|
||||
bindings = make(map[Port][]PortBinding)
|
||||
)
|
||||
|
||||
if !container.State.Ghost {
|
||||
if !container.State.IsGhost() {
|
||||
if container.Config.ExposedPorts != nil {
|
||||
portSpecs = container.Config.ExposedPorts
|
||||
}
|
||||
@@ -1300,9 +1094,6 @@ func (container *Container) monitor() {
|
||||
exitCode = container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
|
||||
}
|
||||
|
||||
// Report status back
|
||||
container.State.setStopped(exitCode)
|
||||
|
||||
if container.runtime != nil && container.runtime.srv != nil {
|
||||
container.runtime.srv.LogEvent("die", container.ID, container.runtime.repositories.ImageName(container.Image))
|
||||
}
|
||||
@@ -1315,6 +1106,9 @@ func (container *Container) monitor() {
|
||||
container.stdin, container.stdinPipe = io.Pipe()
|
||||
}
|
||||
|
||||
// Report status back
|
||||
container.State.SetStopped(exitCode)
|
||||
|
||||
// Release the lock
|
||||
close(container.waitLock)
|
||||
|
||||
@@ -1363,10 +1157,10 @@ func (container *Container) cleanup() {
|
||||
}
|
||||
|
||||
func (container *Container) kill(sig int) error {
|
||||
container.State.Lock()
|
||||
defer container.State.Unlock()
|
||||
container.Lock()
|
||||
defer container.Unlock()
|
||||
|
||||
if !container.State.Running {
|
||||
if !container.State.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1379,7 +1173,7 @@ func (container *Container) kill(sig int) error {
|
||||
}
|
||||
|
||||
func (container *Container) Kill() error {
|
||||
if !container.State.Running {
|
||||
if !container.State.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1404,7 +1198,7 @@ func (container *Container) Kill() error {
|
||||
}
|
||||
|
||||
func (container *Container) Stop(seconds int) error {
|
||||
if !container.State.Running {
|
||||
if !container.State.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1438,7 +1232,7 @@ func (container *Container) Restart(seconds int) error {
|
||||
// Wait blocks until the container stops running, then returns its exit code.
|
||||
func (container *Container) Wait() int {
|
||||
<-container.waitLock
|
||||
return container.State.ExitCode
|
||||
return container.State.GetExitCode()
|
||||
}
|
||||
|
||||
func (container *Container) Resize(h, w int) error {
|
||||
@@ -1551,26 +1345,32 @@ func (container *Container) GetSize() (int64, int64) {
|
||||
driver = container.runtime.driver
|
||||
)
|
||||
|
||||
sizeRw, err = driver.Size(container.ID)
|
||||
if err != nil {
|
||||
utils.Errorf("Warning: driver %s couldn't return diff size of container %s: %s", driver, container.ID, err)
|
||||
// FIXME: GetSize should return an error. Not changing it now in case
|
||||
// there is a side-effect.
|
||||
sizeRw = -1
|
||||
}
|
||||
|
||||
if err := container.EnsureMounted(); err != nil {
|
||||
utils.Errorf("Warning: failed to compute size of container rootfs %s: %s", container.ID, err)
|
||||
return sizeRw, sizeRootfs
|
||||
}
|
||||
_, err = os.Stat(container.RootfsPath())
|
||||
if err == nil {
|
||||
filepath.Walk(container.RootfsPath(), func(path string, fileInfo os.FileInfo, err error) error {
|
||||
if fileInfo != nil {
|
||||
sizeRootfs += fileInfo.Size()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if differ, ok := container.runtime.driver.(graphdriver.Differ); ok {
|
||||
sizeRw, err = differ.DiffSize(container.ID)
|
||||
if err != nil {
|
||||
utils.Errorf("Warning: driver %s couldn't return diff size of container %s: %s", driver, container.ID, err)
|
||||
// FIXME: GetSize should return an error. Not changing it now in case
|
||||
// there is a side-effect.
|
||||
sizeRw = -1
|
||||
}
|
||||
} else {
|
||||
changes, _ := container.Changes()
|
||||
if changes != nil {
|
||||
sizeRw = archive.ChangesSize(container.RootfsPath(), changes)
|
||||
} else {
|
||||
sizeRw = -1
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = os.Stat(container.RootfsPath()); err != nil {
|
||||
if sizeRootfs, err = utils.TreeSize(container.RootfsPath()); err != nil {
|
||||
sizeRootfs = -1
|
||||
}
|
||||
}
|
||||
return sizeRw, sizeRootfs
|
||||
}
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseLxcConfOpt(t *testing.T) {
|
||||
opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "}
|
||||
|
||||
for _, o := range opts {
|
||||
k, v, err := parseLxcOpt(o)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
if k != "lxc.utsname" {
|
||||
t.Fail()
|
||||
}
|
||||
if v != "docker" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNetworkOptsPrivateOnly(t *testing.T) {
|
||||
ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::80"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(ports) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(ports))
|
||||
t.FailNow()
|
||||
}
|
||||
if len(bindings) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(bindings))
|
||||
t.FailNow()
|
||||
}
|
||||
for k := range ports {
|
||||
if k.Proto() != "tcp" {
|
||||
t.Logf("Expected tcp got %s", k.Proto())
|
||||
t.Fail()
|
||||
}
|
||||
if k.Port() != "80" {
|
||||
t.Logf("Expected 80 got %s", k.Port())
|
||||
t.Fail()
|
||||
}
|
||||
b, exists := bindings[k]
|
||||
if !exists {
|
||||
t.Log("Binding does not exist")
|
||||
t.FailNow()
|
||||
}
|
||||
if len(b) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(b))
|
||||
t.FailNow()
|
||||
}
|
||||
s := b[0]
|
||||
if s.HostPort != "" {
|
||||
t.Logf("Expected \"\" got %s", s.HostPort)
|
||||
t.Fail()
|
||||
}
|
||||
if s.HostIp != "192.168.1.100" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNetworkOptsPublic(t *testing.T) {
|
||||
ports, bindings, err := parsePortSpecs([]string{"192.168.1.100:8080:80"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(ports) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(ports))
|
||||
t.FailNow()
|
||||
}
|
||||
if len(bindings) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(bindings))
|
||||
t.FailNow()
|
||||
}
|
||||
for k := range ports {
|
||||
if k.Proto() != "tcp" {
|
||||
t.Logf("Expected tcp got %s", k.Proto())
|
||||
t.Fail()
|
||||
}
|
||||
if k.Port() != "80" {
|
||||
t.Logf("Expected 80 got %s", k.Port())
|
||||
t.Fail()
|
||||
}
|
||||
b, exists := bindings[k]
|
||||
if !exists {
|
||||
t.Log("Binding does not exist")
|
||||
t.FailNow()
|
||||
}
|
||||
if len(b) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(b))
|
||||
t.FailNow()
|
||||
}
|
||||
s := b[0]
|
||||
if s.HostPort != "8080" {
|
||||
t.Logf("Expected 8080 got %s", s.HostPort)
|
||||
t.Fail()
|
||||
}
|
||||
if s.HostIp != "192.168.1.100" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNetworkOptsUdp(t *testing.T) {
|
||||
ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::6000/udp"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(ports) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(ports))
|
||||
t.FailNow()
|
||||
}
|
||||
if len(bindings) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(bindings))
|
||||
t.FailNow()
|
||||
}
|
||||
for k := range ports {
|
||||
if k.Proto() != "udp" {
|
||||
t.Logf("Expected udp got %s", k.Proto())
|
||||
t.Fail()
|
||||
}
|
||||
if k.Port() != "6000" {
|
||||
t.Logf("Expected 6000 got %s", k.Port())
|
||||
t.Fail()
|
||||
}
|
||||
b, exists := bindings[k]
|
||||
if !exists {
|
||||
t.Log("Binding does not exist")
|
||||
t.FailNow()
|
||||
}
|
||||
if len(b) != 1 {
|
||||
t.Logf("Expected 1 got %d", len(b))
|
||||
t.FailNow()
|
||||
}
|
||||
s := b[0]
|
||||
if s.HostPort != "" {
|
||||
t.Logf("Expected \"\" got %s", s.HostPort)
|
||||
t.Fail()
|
||||
}
|
||||
if s.HostIp != "192.168.1.100" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFullName(t *testing.T) {
|
||||
name, err := getFullName("testing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if name != "/testing" {
|
||||
t.Fatalf("Expected /testing got %s", name)
|
||||
}
|
||||
if _, err := getFullName(""); err == nil {
|
||||
t.Fatal("Error should not be nil")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/graphdriver/devmapper"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s <flags> [status] | [list] | [device id] | [resize new-pool-size] | [snap new-id base-id] | [remove id] | [mount id mountpoint]\n", os.Args[0])
|
||||
flag.PrintDefaults()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func byteSizeFromString(arg string) (int64, error) {
|
||||
digits := ""
|
||||
rest := ""
|
||||
last := strings.LastIndexAny(arg, "0123456789")
|
||||
if last >= 0 {
|
||||
digits = arg[:last+1]
|
||||
rest = arg[last+1:]
|
||||
}
|
||||
|
||||
val, err := strconv.ParseInt(digits, 10, 64)
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
|
||||
rest = strings.ToLower(strings.TrimSpace(rest))
|
||||
|
||||
var multiplier int64 = 1
|
||||
switch rest {
|
||||
case "":
|
||||
multiplier = 1
|
||||
case "k", "kb":
|
||||
multiplier = 1024
|
||||
case "m", "mb":
|
||||
multiplier = 1024 * 1024
|
||||
case "g", "gb":
|
||||
multiplier = 1024 * 1024 * 1024
|
||||
case "t", "tb":
|
||||
multiplier = 1024 * 1024 * 1024 * 1024
|
||||
default:
|
||||
return 0, fmt.Errorf("Unknown size unit: %s", rest)
|
||||
}
|
||||
|
||||
return val * multiplier, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
root := flag.String("r", "/var/lib/docker", "Docker root dir")
|
||||
flDebug := flag.Bool("D", false, "Debug mode")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *flDebug {
|
||||
os.Setenv("DEBUG", "1")
|
||||
}
|
||||
|
||||
if flag.NArg() < 1 {
|
||||
usage()
|
||||
}
|
||||
|
||||
args := flag.Args()
|
||||
|
||||
home := path.Join(*root, "devicemapper")
|
||||
devices, err := devmapper.NewDeviceSet(home, false)
|
||||
if err != nil {
|
||||
fmt.Println("Can't initialize device mapper: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
switch args[0] {
|
||||
case "status":
|
||||
status := devices.Status()
|
||||
fmt.Printf("Pool name: %s\n", status.PoolName)
|
||||
fmt.Printf("Data Loopback file: %s\n", status.DataLoopback)
|
||||
fmt.Printf("Metadata Loopback file: %s\n", status.MetadataLoopback)
|
||||
fmt.Printf("Sector size: %d\n", status.SectorSize)
|
||||
fmt.Printf("Data use: %d of %d (%.1f %%)\n", status.Data.Used, status.Data.Total, 100.0*float64(status.Data.Used)/float64(status.Data.Total))
|
||||
fmt.Printf("Metadata use: %d of %d (%.1f %%)\n", status.Metadata.Used, status.Metadata.Total, 100.0*float64(status.Metadata.Used)/float64(status.Metadata.Total))
|
||||
break
|
||||
case "list":
|
||||
ids := devices.List()
|
||||
sort.Strings(ids)
|
||||
for _, id := range ids {
|
||||
fmt.Println(id)
|
||||
}
|
||||
break
|
||||
case "device":
|
||||
if flag.NArg() < 2 {
|
||||
usage()
|
||||
}
|
||||
status, err := devices.GetDeviceStatus(args[1])
|
||||
if err != nil {
|
||||
fmt.Println("Can't get device info: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Id: %d\n", status.DeviceId)
|
||||
fmt.Printf("Size: %d\n", status.Size)
|
||||
fmt.Printf("Transaction Id: %d\n", status.TransactionId)
|
||||
fmt.Printf("Size in Sectors: %d\n", status.SizeInSectors)
|
||||
fmt.Printf("Mapped Sectors: %d\n", status.MappedSectors)
|
||||
fmt.Printf("Highest Mapped Sector: %d\n", status.HighestMappedSector)
|
||||
break
|
||||
case "resize":
|
||||
if flag.NArg() < 2 {
|
||||
usage()
|
||||
}
|
||||
|
||||
size, err := byteSizeFromString(args[1])
|
||||
if err != nil {
|
||||
fmt.Println("Invalid size: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = devices.ResizePool(size)
|
||||
if err != nil {
|
||||
fmt.Println("Error resizeing pool: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
break
|
||||
case "snap":
|
||||
if flag.NArg() < 3 {
|
||||
usage()
|
||||
}
|
||||
|
||||
err := devices.AddDevice(args[1], args[2])
|
||||
if err != nil {
|
||||
fmt.Println("Can't create snap device: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
break
|
||||
case "remove":
|
||||
if flag.NArg() < 2 {
|
||||
usage()
|
||||
}
|
||||
|
||||
err := devices.RemoveDevice(args[1])
|
||||
if err != nil {
|
||||
fmt.Println("Can't remove device: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
break
|
||||
case "mount":
|
||||
if flag.NArg() < 3 {
|
||||
usage()
|
||||
}
|
||||
|
||||
err := devices.MountDevice(args[1], args[2], false)
|
||||
if err != nil {
|
||||
fmt.Println("Can't create snap device: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
break
|
||||
default:
|
||||
fmt.Printf("Unknown command %s\n", args[0])
|
||||
usage()
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -192,7 +192,7 @@ if [ "$justTar" ]; then
|
||||
sudo tar --numeric-owner -caf "$repo" .
|
||||
else
|
||||
# create the image (and tag $repo:$suite)
|
||||
sudo tar --numeric-owner -c . | $docker import - $repo $suite
|
||||
sudo tar --numeric-owner -c . | $docker import - $repo:$suite
|
||||
|
||||
# test the image
|
||||
$docker run -i -t $repo:$suite echo success
|
||||
@@ -202,25 +202,25 @@ else
|
||||
Debian)
|
||||
if [ "$suite" = "$debianStable" -o "$suite" = 'stable' ] && [ -r etc/debian_version ]; then
|
||||
# tag latest
|
||||
$docker tag $repo:$suite $repo latest
|
||||
$docker tag $repo:$suite $repo:latest
|
||||
|
||||
if [ -r etc/debian_version ]; then
|
||||
# tag the specific debian release version (which is only reasonable to tag on debian stable)
|
||||
ver=$(cat etc/debian_version)
|
||||
$docker tag $repo:$suite $repo $ver
|
||||
$docker tag $repo:$suite $repo:$ver
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
Ubuntu)
|
||||
if [ "$suite" = "$ubuntuLatestLTS" ]; then
|
||||
# tag latest
|
||||
$docker tag $repo:$suite $repo latest
|
||||
$docker tag $repo:$suite $repo:latest
|
||||
fi
|
||||
if [ -r etc/lsb-release ]; then
|
||||
lsbRelease="$(. etc/lsb-release && echo "$DISTRIB_RELEASE")"
|
||||
if [ "$lsbRelease" ]; then
|
||||
# tag specific Ubuntu version number, if available (12.04, etc.)
|
||||
$docker tag $repo:$suite $repo $lsbRelease
|
||||
$docker tag $repo:$suite $repo:$lsbRelease
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/devmapper"
|
||||
"os"
|
||||
)
|
||||
|
||||
func usage() {
|
||||
fmt.Printf("Usage: %s [snap new-id base-id] | [remove id] | [mount id mountpoint]\n", os.Args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func main() {
|
||||
devices := devmapper.NewDeviceSet("/var/lib/docker")
|
||||
|
||||
if len(os.Args) < 2 {
|
||||
usage()
|
||||
}
|
||||
|
||||
cmd := os.Args[1]
|
||||
if cmd == "snap" {
|
||||
if len(os.Args) < 4 {
|
||||
usage()
|
||||
}
|
||||
|
||||
err := devices.AddDevice(os.Args[2], os.Args[3])
|
||||
if err != nil {
|
||||
fmt.Println("Can't create snap device: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else if cmd == "remove" {
|
||||
if len(os.Args) < 3 {
|
||||
usage()
|
||||
}
|
||||
|
||||
err := devices.RemoveDevice(os.Args[2])
|
||||
if err != nil {
|
||||
fmt.Println("Can't remove device: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else if cmd == "mount" {
|
||||
if len(os.Args) < 4 {
|
||||
usage()
|
||||
}
|
||||
|
||||
err := devices.MountDevice(os.Args[2], os.Args[3])
|
||||
if err != nil {
|
||||
fmt.Println("Can't create snap device: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Unknown command %s\n", cmd)
|
||||
if len(os.Args) < 4 {
|
||||
usage()
|
||||
}
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package devmapper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/graphdriver"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
func init() {
|
||||
graphdriver.Register("devicemapper", Init)
|
||||
}
|
||||
|
||||
// Placeholder interfaces, to be replaced
|
||||
// at integration.
|
||||
|
||||
// End of placeholder interfaces.
|
||||
|
||||
type Driver struct {
|
||||
*DeviceSet
|
||||
home string
|
||||
}
|
||||
|
||||
func Init(home string) (graphdriver.Driver, error) {
|
||||
deviceSet, err := NewDeviceSet(home)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d := &Driver{
|
||||
DeviceSet: deviceSet,
|
||||
home: home,
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (d *Driver) String() string {
|
||||
return "devicemapper"
|
||||
}
|
||||
|
||||
func (d *Driver) Cleanup() error {
|
||||
return d.DeviceSet.Shutdown()
|
||||
}
|
||||
|
||||
func (d *Driver) Create(id string, parent string) error {
|
||||
return d.DeviceSet.AddDevice(id, parent)
|
||||
}
|
||||
|
||||
func (d *Driver) Remove(id string) error {
|
||||
return d.DeviceSet.RemoveDevice(id)
|
||||
}
|
||||
|
||||
func (d *Driver) Get(id string) (string, error) {
|
||||
mp := path.Join(d.home, "mnt", id)
|
||||
if err := d.mount(id, mp); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return mp, nil
|
||||
}
|
||||
|
||||
func (d *Driver) Size(id string) (int64, error) {
|
||||
return -1, fmt.Errorf("Not implemented")
|
||||
}
|
||||
|
||||
func (d *Driver) mount(id, mountPoint string) error {
|
||||
// Create the target directories if they don't exist
|
||||
if err := os.MkdirAll(mountPoint, 0755); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
// If mountpoint is already mounted, do nothing
|
||||
if mounted, err := Mounted(mountPoint); err != nil {
|
||||
return fmt.Errorf("Error checking mountpoint: %s", err)
|
||||
} else if mounted {
|
||||
return nil
|
||||
}
|
||||
// Mount the device
|
||||
return d.DeviceSet.MountDevice(id, mountPoint, false)
|
||||
}
|
||||
@@ -1,301 +0,0 @@
|
||||
package devmapper
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Reduce the size the the base fs and loopback for the tests
|
||||
DefaultDataLoopbackSize = 300 * 1024 * 1024
|
||||
DefaultMetaDataLoopbackSize = 200 * 1024 * 1024
|
||||
DefaultBaseFsSize = 300 * 1024 * 1024
|
||||
|
||||
}
|
||||
|
||||
func mkTestDirectory(t *testing.T) string {
|
||||
dir, err := ioutil.TempDir("", "docker-test-devmapper-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
func newDriver(t *testing.T) *Driver {
|
||||
home := mkTestDirectory(t)
|
||||
d, err := Init(home)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return d.(*Driver)
|
||||
}
|
||||
|
||||
func cleanup(d *Driver) {
|
||||
d.Cleanup()
|
||||
os.RemoveAll(d.home)
|
||||
}
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
home := mkTestDirectory(t)
|
||||
defer os.RemoveAll(home)
|
||||
driver, err := Init(home)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := driver.Cleanup(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
id := "foo"
|
||||
if err := driver.Create(id, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dir, err := driver.Get(id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if st, err := os.Stat(dir); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !st.IsDir() {
|
||||
t.Fatalf("Get(%V) did not return a directory", id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDriverName(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer cleanup(d)
|
||||
|
||||
if d.String() != "devicemapper" {
|
||||
t.Fatalf("Expected driver name to be devicemapper got %s", d.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDriverCreate(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer cleanup(d)
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDriverRemove(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer cleanup(d)
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := d.Remove("1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanup(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(d.home)
|
||||
|
||||
mountPoints := make([]string, 2)
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Mount the id
|
||||
p, err := d.Get("1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mountPoints[0] = p
|
||||
|
||||
if err := d.Create("2", "1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p, err = d.Get("2")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mountPoints[1] = p
|
||||
|
||||
// Ensure that all the mount points are currently mounted
|
||||
for _, p := range mountPoints {
|
||||
if mounted, err := Mounted(p); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !mounted {
|
||||
t.Fatalf("Expected %s to be mounted", p)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that devices are active
|
||||
for _, p := range []string{"1", "2"} {
|
||||
if !d.HasActivatedDevice(p) {
|
||||
t.Fatalf("Expected %s to have an active device", p)
|
||||
}
|
||||
}
|
||||
|
||||
if err := d.Cleanup(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Ensure that all the mount points are no longer mounted
|
||||
for _, p := range mountPoints {
|
||||
if mounted, err := Mounted(p); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if mounted {
|
||||
t.Fatalf("Expected %s to not be mounted", p)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that devices are no longer activated
|
||||
for _, p := range []string{"1", "2"} {
|
||||
if d.HasActivatedDevice(p) {
|
||||
t.Fatalf("Expected %s not be an active device", p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotMounted(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer cleanup(d)
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mounted, err := Mounted(path.Join(d.home, "mnt", "1"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if mounted {
|
||||
t.Fatal("Id 1 should not be mounted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMounted(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer cleanup(d)
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := d.Get("1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mounted, err := Mounted(path.Join(d.home, "mnt", "1"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !mounted {
|
||||
t.Fatal("Id 1 should be mounted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitCleanedDriver(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := d.Get("1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := d.Cleanup(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
driver, err := Init(d.home)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
d = driver.(*Driver)
|
||||
defer cleanup(d)
|
||||
|
||||
if _, err := d.Get("1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountMountedDriver(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer cleanup(d)
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Perform get on same id to ensure that it will
|
||||
// not be mounted twice
|
||||
if _, err := d.Get("1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := d.Get("1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetReturnsValidDevice(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer cleanup(d)
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !d.HasDevice("1") {
|
||||
t.Fatalf("Expected id 1 to be in device set")
|
||||
}
|
||||
|
||||
if _, err := d.Get("1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !d.HasActivatedDevice("1") {
|
||||
t.Fatalf("Expected id 1 to be activated")
|
||||
}
|
||||
|
||||
if !d.HasInitializedDevice("1") {
|
||||
t.Fatalf("Expected id 1 to be initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDriverGetSize(t *testing.T) {
|
||||
t.Skipf("Size is currently not implemented")
|
||||
|
||||
d := newDriver(t)
|
||||
defer cleanup(d)
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mountPoint, err := d.Get("1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
size := int64(1024)
|
||||
|
||||
f, err := os.Create(path.Join(mountPoint, "test_file"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := f.Truncate(size); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
diffSize, err := d.Size("1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diffSize != size {
|
||||
t.Fatalf("Expected size %d got %d", size, diffSize)
|
||||
}
|
||||
}
|
||||
+12
-10
@@ -25,19 +25,20 @@ func main() {
|
||||
}
|
||||
// FIXME: Switch d and D ? (to be more sshd like)
|
||||
flVersion := flag.Bool("v", false, "Print version information and quit")
|
||||
flDaemon := flag.Bool("d", false, "Daemon mode")
|
||||
flDebug := flag.Bool("D", false, "Debug mode")
|
||||
flDaemon := flag.Bool("d", false, "Enable daemon mode")
|
||||
flDebug := flag.Bool("D", false, "Enable debug mode")
|
||||
flAutoRestart := flag.Bool("r", true, "Restart previously running containers")
|
||||
bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge. Use 'none' to disable container networking")
|
||||
pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
|
||||
flRoot := flag.String("g", "/var/lib/docker", "Path to use as the root of the docker runtime.")
|
||||
flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
|
||||
flDns := flag.String("dns", "", "Set custom dns servers")
|
||||
bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge; use 'none' to disable container networking")
|
||||
pidfile := flag.String("p", "/var/run/docker.pid", "Path to use for daemon PID file")
|
||||
flRoot := flag.String("g", "/var/lib/docker", "Path to use as the root of the docker runtime")
|
||||
flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS headers in the remote API")
|
||||
flDns := flag.String("dns", "", "Force docker to use specific DNS servers")
|
||||
flHosts := utils.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)}
|
||||
flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use")
|
||||
flEnableIptables := flag.Bool("iptables", true, "Disable iptables within docker")
|
||||
flDefaultIp := flag.String("ip", "0.0.0.0", "Default ip address to use when binding a containers ports")
|
||||
flag.Var(&flHosts, "H", "Multiple tcp://host:port or unix://path/to/socket to bind in daemon mode, single connection otherwise")
|
||||
flEnableIptables := flag.Bool("iptables", true, "Disable docker's addition of iptables rules")
|
||||
flDefaultIp := flag.String("ip", "0.0.0.0", "Default IP address to use when binding container ports")
|
||||
flInterContainerComm := flag.Bool("icc", true, "Enable inter-container communication")
|
||||
flGraphDriver := flag.String("s", "", "Force the docker runtime to use a specific storage driver")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
@@ -82,6 +83,7 @@ func main() {
|
||||
job.Setenv("BridgeIface", *bridgeName)
|
||||
job.Setenv("DefaultIp", *flDefaultIp)
|
||||
job.SetenvBool("InterContainerCommunication", *flInterContainerComm)
|
||||
job.Setenv("GraphDriver", *flGraphDriver)
|
||||
if err := job.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ run apt-get install -y python-setuptools make
|
||||
run easy_install pip
|
||||
#from docs/requirements.txt, but here to increase cacheability
|
||||
run pip install Sphinx==1.1.3
|
||||
run pip install sphinxcontrib-httpdomain==1.1.8
|
||||
run pip install sphinxcontrib-httpdomain==1.1.9
|
||||
add . /docs
|
||||
run cd /docs; make docs
|
||||
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ help:
|
||||
# @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
# @echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
# @echo " text to make text files"
|
||||
# @echo " man to make manual pages"
|
||||
@echo " man to make a manual page"
|
||||
# @echo " texinfo to make Texinfo files"
|
||||
# @echo " info to make Texinfo files and run them through makeinfo"
|
||||
# @echo " gettext to make PO message catalogs"
|
||||
|
||||
+7
-6
@@ -41,11 +41,12 @@ its dependencies. There are two main ways to install this tool:
|
||||
|
||||
###Native Installation
|
||||
|
||||
* Install sphinx: `pip install sphinx`
|
||||
* Mac OS X: `[sudo] pip-2.7 install sphinx`
|
||||
* Install sphinx httpdomain contrib package: `pip install sphinxcontrib-httpdomain`
|
||||
* Mac OS X: `[sudo] pip-2.7 install sphinxcontrib-httpdomain`
|
||||
* If pip is not available you can probably install it using your favorite package manager as **python-pip**
|
||||
Install dependencies from `requirements.txt` file in your `docker/docs`
|
||||
directory:
|
||||
|
||||
* Linux: `pip install -r docs/requirements.txt`
|
||||
|
||||
* Mac OS X: `[sudo] pip-2.7 -r docs/requirements.txt`
|
||||
|
||||
###Alternative Installation: Docker Container
|
||||
|
||||
@@ -136,7 +137,7 @@ Manpages
|
||||
--------
|
||||
|
||||
* To make the manpages, run ``make man``. Please note there is a bug
|
||||
in spinx 1.1.3 which makes this fail. Upgrade to the latest version
|
||||
in Sphinx 1.1.3 which makes this fail. Upgrade to the latest version
|
||||
of Sphinx.
|
||||
* Then preview the manpage by running ``man _build/man/docker.1``,
|
||||
where ``_build/man/docker.1`` is the path to the generated manfile
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
Sphinx==1.1.3
|
||||
sphinxcontrib-httpdomain==1.1.8
|
||||
sphinxcontrib-httpdomain==1.1.9
|
||||
|
||||
@@ -13,9 +13,12 @@ Docker Remote API v1.7
|
||||
1. Brief introduction
|
||||
=====================
|
||||
|
||||
- The Remote API is replacing rcli
|
||||
- Default port in the docker daemon is 4243
|
||||
- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
|
||||
- The Remote API has replaced rcli
|
||||
- The daemon listens on ``unix:///var/run/docker.sock``, but you can
|
||||
:ref:`bind_docker`.
|
||||
- The API tends to be REST, but for some complex commands, like
|
||||
``attach`` or ``pull``, the HTTP connection is hijacked to transport
|
||||
``stdout, stdin`` and ``stderr``
|
||||
|
||||
2. Endpoints
|
||||
============
|
||||
@@ -690,10 +693,12 @@ Create an image
|
||||
:query repo: repository
|
||||
:query tag: tag
|
||||
:query registry: the registry to pull from
|
||||
:reqheader X-Registry-Auth: base64-encoded AuthConfig object
|
||||
:statuscode 200: no error
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
|
||||
Insert a file in an image
|
||||
*************************
|
||||
|
||||
@@ -835,18 +840,16 @@ Push an image on the registry
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{"status":"Pushing..."}
|
||||
{"status":"Pushing", "progress":"1/? (n/a)"}
|
||||
{"error":"Invalid..."}
|
||||
...
|
||||
|
||||
The ``X-Registry-Auth`` header can be used to include a
|
||||
base64-encoded AuthConfig object.
|
||||
{"status":"Pushing..."}
|
||||
{"status":"Pushing", "progress":"1/? (n/a)"}
|
||||
{"error":"Invalid..."}
|
||||
...
|
||||
|
||||
:query registry: the registry you wan to push, optional
|
||||
:reqheader X-Registry-Auth: include a base64-encoded AuthConfig object.
|
||||
:statuscode 200: no error
|
||||
:statuscode 404: no such image
|
||||
:statuscode 500: server error
|
||||
:statuscode 404: no such image
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
Tag an image into a repository
|
||||
@@ -959,9 +962,9 @@ Search images
|
||||
...
|
||||
]
|
||||
|
||||
:query term: term to search
|
||||
:statuscode 200: no error
|
||||
:statuscode 500: server error
|
||||
:query term: term to search
|
||||
:statuscode 200: no error
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
2.3 Misc
|
||||
@@ -991,18 +994,22 @@ Build an image from Dockerfile via stdin
|
||||
{{ STREAM }}
|
||||
|
||||
|
||||
The stream must be a tar archive compressed with one of the following algorithms:
|
||||
identity (no compression), gzip, bzip2, xz. The archive must include a file called
|
||||
`Dockerfile` at its root. It may include any number of other files, which will be
|
||||
accessible in the build context (See the ADD build command).
|
||||
The stream must be a tar archive compressed with one of the
|
||||
following algorithms: identity (no compression), gzip, bzip2,
|
||||
xz.
|
||||
|
||||
The Content-type header should be set to "application/tar".
|
||||
The archive must include a file called ``Dockerfile`` at its
|
||||
root. It may include any number of other files, which will be
|
||||
accessible in the build context (See the :ref:`ADD build command
|
||||
<dockerbuilder>`).
|
||||
|
||||
:query t: repository name (and optionally a tag) to be applied to the resulting image in case of success
|
||||
:query q: suppress verbose build output
|
||||
:query nocache: do not use the cache when building the image
|
||||
:reqheader Content-type: should be set to ``"application/tar"``.
|
||||
:statuscode 200: no error
|
||||
:statuscode 500: server error
|
||||
|
||||
:query t: repository name (and optionally a tag) to be applied to the resulting image in case of success
|
||||
:query q: suppress verbose build output
|
||||
:query nocache: do not use the cache when building the image
|
||||
:statuscode 200: no error
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
Check auth configuration
|
||||
@@ -1164,6 +1171,53 @@ Monitor Docker's events
|
||||
:statuscode 200: no error
|
||||
:statuscode 500: server error
|
||||
|
||||
Get a tarball containing all images and tags in a repository
|
||||
************************************************************
|
||||
|
||||
.. http:get:: /images/(name)/get
|
||||
|
||||
Get a tarball containing all images and metadata for the repository specified by ``name``.
|
||||
|
||||
**Example request**
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /images/ubuntu/get
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/x-tar
|
||||
|
||||
Binary data stream
|
||||
:statuscode 200: no error
|
||||
:statuscode 500: server error
|
||||
|
||||
Load a tarball with a set of images and tags into docker
|
||||
********************************************************
|
||||
|
||||
.. http:post:: /images/load
|
||||
|
||||
Load a set of images and tags into the docker repository.
|
||||
|
||||
**Example request**
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /images/load
|
||||
|
||||
Tarball in body
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
:statuscode 200: no error
|
||||
:statuscode 500: server error
|
||||
|
||||
3. Going further
|
||||
================
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
:title: Registry API
|
||||
:title: Remote API Client Libraries
|
||||
:description: Various client libraries available to use with the Docker remote API
|
||||
:keywords: API, Docker, index, registry, REST, documentation, clients, Python, Ruby, Javascript, Erlang, Go
|
||||
|
||||
|
||||
@@ -18,6 +18,38 @@ To list available commands, either run ``docker`` with no parameters or execute
|
||||
|
||||
...
|
||||
|
||||
.. _cli_daemon:
|
||||
|
||||
``daemon``
|
||||
----------
|
||||
|
||||
::
|
||||
|
||||
Usage of docker:
|
||||
-D=false: Enable debug mode
|
||||
-H=[unix:///var/run/docker.sock]: Multiple tcp://host:port or unix://path/to/socket to bind in daemon mode, single connection otherwise
|
||||
-api-enable-cors=false: Enable CORS headers in the remote API
|
||||
-b="": Attach containers to a pre-existing network bridge; use 'none' to disable container networking
|
||||
-d=false: Enable daemon mode
|
||||
-dns="": Force docker to use specific DNS servers
|
||||
-g="/var/lib/docker": Path to use as the root of the docker runtime
|
||||
-icc=true: Enable inter-container communication
|
||||
-ip="0.0.0.0": Default IP address to use when binding container ports
|
||||
-iptables=true: Disable docker's addition of iptables rules
|
||||
-p="/var/run/docker.pid": Path to use for daemon PID file
|
||||
-r=true: Restart previously running containers
|
||||
-s="": Force the docker runtime to use a specific storage driver
|
||||
-v=false: Print version information and quit
|
||||
|
||||
The docker daemon is the persistent process that manages containers. Docker uses the same binary for both the
|
||||
daemon and client. To run the daemon you provide the ``-d`` flag.
|
||||
|
||||
To force docker to use devicemapper as the storage driver, use ``docker -d -s devicemapper``
|
||||
|
||||
To set the dns server for all docker containers, use ``docker -d -dns 8.8.8.8``
|
||||
|
||||
To run the daemon with debug output, use ``docker -d -D``
|
||||
|
||||
.. _cli_attach:
|
||||
|
||||
``attach``
|
||||
@@ -88,31 +120,65 @@ Examples:
|
||||
|
||||
Usage: docker build [OPTIONS] PATH | URL | -
|
||||
Build a new container image from the source code at PATH
|
||||
-t="": Repository name (and optionally a tag) to be applied to the resulting image in case of success.
|
||||
-t="": Repository name (and optionally a tag) to be applied
|
||||
to the resulting image in case of success.
|
||||
-q=false: Suppress verbose build output.
|
||||
-no-cache: Do not use the cache when building the image.
|
||||
-rm: Remove intermediate containers after a successful build
|
||||
When a single Dockerfile is given as URL, then no context is set. When a git repository is set as URL, the repository is used as context
|
||||
|
||||
The files at PATH or URL are called the "context" of the build. The
|
||||
build process may refer to any of the files in the context, for
|
||||
example when using an :ref:`ADD <dockerfile_add>` instruction. When a
|
||||
single ``Dockerfile`` is given as URL, then no context is set. When a
|
||||
git repository is set as URL, then the repository is used as the
|
||||
context
|
||||
|
||||
.. _cli_build_examples:
|
||||
|
||||
.. seealso:: :ref:`dockerbuilder`.
|
||||
|
||||
Examples:
|
||||
~~~~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker build .
|
||||
Uploading context 10240 bytes
|
||||
Step 1 : FROM busybox
|
||||
Pulling repository busybox
|
||||
---> e9aa60c60128MB/2.284 MB (100%) endpoint: https://cdn-registry-1.docker.io/v1/
|
||||
Step 2 : RUN ls -lh /
|
||||
---> Running in 9c9e81692ae9
|
||||
total 24
|
||||
drwxr-xr-x 2 root root 4.0K Mar 12 2013 bin
|
||||
drwxr-xr-x 5 root root 4.0K Oct 19 00:19 dev
|
||||
drwxr-xr-x 2 root root 4.0K Oct 19 00:19 etc
|
||||
drwxr-xr-x 2 root root 4.0K Nov 15 23:34 lib
|
||||
lrwxrwxrwx 1 root root 3 Mar 12 2013 lib64 -> lib
|
||||
dr-xr-xr-x 116 root root 0 Nov 15 23:34 proc
|
||||
lrwxrwxrwx 1 root root 3 Mar 12 2013 sbin -> bin
|
||||
dr-xr-xr-x 13 root root 0 Nov 15 23:34 sys
|
||||
drwxr-xr-x 2 root root 4.0K Mar 12 2013 tmp
|
||||
drwxr-xr-x 2 root root 4.0K Nov 15 23:34 usr
|
||||
---> b35f4035db3f
|
||||
Step 3 : CMD echo Hello World
|
||||
---> Running in 02071fceb21b
|
||||
---> f52f38b7823e
|
||||
Successfully built f52f38b7823e
|
||||
|
||||
This will read the ``Dockerfile`` from the current directory. It will
|
||||
also send any other files and directories found in the current
|
||||
directory to the ``docker`` daemon.
|
||||
This example specifies that the PATH is ``.``, and so all the files in
|
||||
the local directory get tar'd and sent to the Docker daemon. The PATH
|
||||
specifies where to find the files for the "context" of the build on
|
||||
the Docker daemon. Remember that the daemon could be running on a
|
||||
remote machine and that no parsing of the Dockerfile happens at the
|
||||
client side (where you're running ``docker build``). That means that
|
||||
*all* the files at PATH get sent, not just the ones listed to
|
||||
:ref:`ADD <dockerfile_add>` in the ``Dockerfile``.
|
||||
|
||||
The transfer of context from the local machine to the Docker daemon is
|
||||
what the ``docker`` client means when you see the "Uploading context"
|
||||
message.
|
||||
|
||||
The contents of this directory would be used by ``ADD`` commands found
|
||||
within the ``Dockerfile``. This will send a lot of data to the
|
||||
``docker`` daemon if the current directory contains a lot of data. If
|
||||
the absolute path is provided instead of ``.`` then only the files and
|
||||
directories required by the ADD commands from the ``Dockerfile`` will be
|
||||
added to the context and transferred to the ``docker`` daemon.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -129,16 +195,15 @@ tag will be ``2.0``
|
||||
|
||||
This will read a ``Dockerfile`` from *stdin* without context. Due to
|
||||
the lack of a context, no contents of any local directory will be sent
|
||||
to the ``docker`` daemon. ``ADD`` doesn't work when running in this
|
||||
mode because the absence of the context provides no source files to
|
||||
copy to the container.
|
||||
to the ``docker`` daemon. Since there is no context, a Dockerfile
|
||||
``ADD`` only works if it refers to a remote URL.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker build github.com/creack/docker-firefox
|
||||
|
||||
This will clone the Github repository and use it as context. The
|
||||
``Dockerfile`` at the root of the repository is used as
|
||||
This will clone the Github repository and use the cloned repository as
|
||||
context. The ``Dockerfile`` at the root of the repository is used as
|
||||
``Dockerfile``. Note that you can specify an arbitrary git repository
|
||||
by using the ``git://`` schema.
|
||||
|
||||
@@ -157,7 +222,7 @@ by using the ``git://`` schema.
|
||||
-m="": Commit message
|
||||
-author="": Author (eg. "John Hannibal Smith <hannibal@a-team.com>"
|
||||
-run="": Configuration to be applied when the image is launched with `docker run`.
|
||||
(ex: '{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')
|
||||
(ex: -run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')
|
||||
|
||||
Simple commit of an existing container
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -173,7 +238,7 @@ Simple commit of an existing container
|
||||
$ docker images | head
|
||||
REPOSITORY TAG ID CREATED SIZE
|
||||
SvenDowideit/testimage version3 f5283438590d 16 seconds ago 204.2 MB (virtual 335.7 MB)
|
||||
S
|
||||
|
||||
|
||||
Full -run example
|
||||
.................
|
||||
@@ -219,10 +284,15 @@ Full -run example
|
||||
|
||||
::
|
||||
|
||||
Usage: docker cp CONTAINER:RESOURCE HOSTPATH
|
||||
Usage: docker cp CONTAINER:PATH HOSTPATH
|
||||
|
||||
Copy files/folders from the containers filesystem to the host
|
||||
path. Paths are relative to the root of the filesystem.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo docker cp 7bb0e258aefe:/etc/debian_version .
|
||||
$ sudo docker cp blue_frog:/etc/hosts .
|
||||
|
||||
.. _cli_diff:
|
||||
|
||||
@@ -231,9 +301,33 @@ Full -run example
|
||||
|
||||
::
|
||||
|
||||
Usage: docker diff CONTAINER [OPTIONS]
|
||||
Usage: docker diff CONTAINER
|
||||
|
||||
List the changed files and directories in a container's filesystem
|
||||
|
||||
Inspect changes on a container's filesystem
|
||||
There are 3 events that are listed in the 'diff':
|
||||
|
||||
1. ```A``` - Add
|
||||
2. ```D``` - Delete
|
||||
3. ```C``` - Change
|
||||
|
||||
for example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo docker diff 7bb0e258aefe
|
||||
|
||||
C /dev
|
||||
A /dev/kmsg
|
||||
C /etc
|
||||
A /etc/mtab
|
||||
A /go
|
||||
A /go/src
|
||||
A /go/src/github.com
|
||||
A /go/src/github.com/dotcloud
|
||||
A /go/src/github.com/dotcloud/docker
|
||||
A /go/src/github.com/dotcloud/docker/.git
|
||||
....
|
||||
|
||||
.. _cli_events:
|
||||
|
||||
@@ -307,7 +401,13 @@ Show events in the past from a specified time
|
||||
|
||||
Usage: docker export CONTAINER
|
||||
|
||||
Export the contents of a filesystem as a tar archive
|
||||
Export the contents of a filesystem as a tar archive to STDOUT
|
||||
|
||||
for example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo docker export red_panda > latest.tar
|
||||
|
||||
.. _cli_history:
|
||||
|
||||
@@ -323,6 +423,40 @@ Show events in the past from a specified time
|
||||
-notrunc=false: Don't truncate output
|
||||
-q=false: only show numeric IDs
|
||||
|
||||
To see how the docker:latest image was built:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ docker history docker
|
||||
ID CREATED CREATED BY
|
||||
docker:latest 19 hours ago /bin/sh -c #(nop) ADD . in /go/src/github.com/dotcloud/docker
|
||||
cf5f2467662d 2 weeks ago /bin/sh -c #(nop) ENTRYPOINT ["hack/dind"]
|
||||
3538fbe372bf 2 weeks ago /bin/sh -c #(nop) WORKDIR /go/src/github.com/dotcloud/docker
|
||||
7450f65072e5 2 weeks ago /bin/sh -c #(nop) VOLUME /var/lib/docker
|
||||
b79d62b97328 2 weeks ago /bin/sh -c apt-get install -y -q lxc
|
||||
36714852a550 2 weeks ago /bin/sh -c apt-get install -y -q iptables
|
||||
8c4c706df1d6 2 weeks ago /bin/sh -c /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEYn' > /.s3cfg
|
||||
b89989433c48 2 weeks ago /bin/sh -c pip install python-magic
|
||||
a23e640d85b5 2 weeks ago /bin/sh -c pip install s3cmd
|
||||
41f54fec7e79 2 weeks ago /bin/sh -c apt-get install -y -q python-pip
|
||||
d9bc04add907 2 weeks ago /bin/sh -c apt-get install -y -q reprepro dpkg-sig
|
||||
e74f4760fa70 2 weeks ago /bin/sh -c gem install --no-rdoc --no-ri fpm
|
||||
1e43224726eb 2 weeks ago /bin/sh -c apt-get install -y -q ruby1.9.3 rubygems libffi-dev
|
||||
460953ae9d7f 2 weeks ago /bin/sh -c #(nop) ENV GOPATH=/go:/go/src/github.com/dotcloud/docker/vendor
|
||||
8b63eb1d666b 2 weeks ago /bin/sh -c #(nop) ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/goroot/bin
|
||||
3087f3bcedf2 2 weeks ago /bin/sh -c #(nop) ENV GOROOT=/goroot
|
||||
635840d198e5 2 weeks ago /bin/sh -c cd /goroot/src && ./make.bash
|
||||
439f4a0592ba 2 weeks ago /bin/sh -c curl -s https://go.googlecode.com/files/go1.1.2.src.tar.gz | tar -v -C / -xz && mv /go /goroot
|
||||
13967ed36e93 2 weeks ago /bin/sh -c #(nop) ENV CGO_ENABLED=0
|
||||
bf7424458437 2 weeks ago /bin/sh -c apt-get install -y -q build-essential
|
||||
a89ec997c3bf 2 weeks ago /bin/sh -c apt-get install -y -q mercurial
|
||||
b9f165c6e749 2 weeks ago /bin/sh -c apt-get install -y -q git
|
||||
17a64374afa7 2 weeks ago /bin/sh -c apt-get install -y -q curl
|
||||
d5e85dc5b1d8 2 weeks ago /bin/sh -c apt-get update
|
||||
13e642467c11 2 weeks ago /bin/sh -c echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > /etc/apt/sources.list
|
||||
ae6dde92a94e 2 weeks ago /bin/sh -c #(nop) MAINTAINER Solomon Hykes <solomon@dotcloud.com>
|
||||
ubuntu:12.04 6 months ago
|
||||
|
||||
.. _cli_images:
|
||||
|
||||
``images``
|
||||
@@ -334,18 +468,52 @@ Show events in the past from a specified time
|
||||
|
||||
List images
|
||||
|
||||
-a=false: show all images
|
||||
-a=false: show all images (by default filter out the intermediate images used to build)
|
||||
-notrunc=false: Don't truncate output
|
||||
-q=false: only show numeric IDs
|
||||
-tree=false: output graph in tree format
|
||||
-viz=false: output graph in graphviz format
|
||||
|
||||
Listing the most recently created images
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo docker images | head
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
<none> <none> 77af4d6b9913 19 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
committest latest b6fa739cedf5 19 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
<none> <none> 78a85c484f71 19 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
docker latest 30557a29d5ab 20 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
<none> <none> 0124422dd9f9 20 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
<none> <none> 18ad6fad3402 22 hours ago 23.68 MB (virtual 1.082 GB)
|
||||
<none> <none> f9f1e26352f0 23 hours ago 30.46 MB (virtual 1.089 GB)
|
||||
tryout latest 2629d1fa0b81 23 hours ago 16.4 kB (virtual 131.5 MB)
|
||||
<none> <none> 5ed6274db6ce 24 hours ago 30.44 MB (virtual 1.089 GB)
|
||||
|
||||
Listing the full length image IDs
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo docker images -notrunc | head
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
<none> <none> 77af4d6b9913e693e8d0b4b294fa62ade6054e6b2f1ffb617ac955dd63fb0182 19 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
committest latest b6fa739cedf5ea12a620a439402b6004d057da800f91c7524b5086a5e4749c9f 19 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
<none> <none> 78a85c484f71509adeaace20e72e941f6bdd2b25b4c75da8693efd9f61a37921 19 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
docker latest 30557a29d5abc51e5f1d5b472e79b7e296f595abcf19fe6b9199dbbc809c6ff4 20 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
<none> <none> 0124422dd9f9cf7ef15c0617cda3931ee68346455441d66ab8bdc5b05e9fdce5 20 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
<none> <none> 18ad6fad340262ac2a636efd98a6d1f0ea775ae3d45240d3418466495a19a81b 22 hours ago 23.68 MB (virtual 1.082 GB)
|
||||
<none> <none> f9f1e26352f0a3ba6a0ff68167559f64f3e21ff7ada60366e2d44a04befd1d3a 23 hours ago 30.46 MB (virtual 1.089 GB)
|
||||
tryout latest 2629d1fa0b81b222fca63371ca16cbf6a0772d07759ff80e8d1369b926940074 23 hours ago 16.4 kB (virtual 131.5 MB)
|
||||
<none> <none> 5ed6274db6ceb2397844896966ea239290555e74ef307030ebb01ff91b1914df 24 hours ago 30.44 MB (virtual 1.089 GB)
|
||||
|
||||
Displaying images visually
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker images -viz | dot -Tpng -o docker.png
|
||||
$ sudo docker images -viz | dot -Tpng -o docker.png
|
||||
|
||||
.. image:: docker_images.gif
|
||||
:alt: Example inheritance graph of Docker images.
|
||||
@@ -354,9 +522,9 @@ Displaying images visually
|
||||
Displaying image hierarchy
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker images -tree
|
||||
$ sudo docker images -tree
|
||||
|
||||
|─8dbd9e392a96 Size: 131.5 MB (virtual 131.5 MB) Tags: ubuntu:12.04,ubuntu:latest,ubuntu:precise
|
||||
└─27cf78414709 Size: 180.1 MB (virtual 180.1 MB)
|
||||
@@ -435,6 +603,21 @@ might not get preserved.
|
||||
|
||||
Display system-wide information.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo docker info
|
||||
Containers: 292
|
||||
Images: 194
|
||||
Debug mode (server): false
|
||||
Debug mode (client): false
|
||||
Fds: 22
|
||||
Goroutines: 67
|
||||
LXC Version: 0.9.0
|
||||
EventsListeners: 115
|
||||
Kernel Version: 3.8.0-33-generic
|
||||
WARNING: No swap limit support
|
||||
|
||||
|
||||
.. _cli_insert:
|
||||
|
||||
``insert``
|
||||
@@ -446,6 +629,12 @@ might not get preserved.
|
||||
|
||||
Insert a file from URL in the IMAGE at PATH
|
||||
|
||||
Use the specified IMAGE as the parent for a new image which adds a
|
||||
:ref:`layer <layer_def>` containing the new file. ``insert`` does not modify
|
||||
the original image, and the new image has the contents of the parent image,
|
||||
plus the new file.
|
||||
|
||||
|
||||
Examples
|
||||
~~~~~~~~
|
||||
|
||||
@@ -455,6 +644,7 @@ Insert file from github
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo docker insert 8283e18b24bc https://raw.github.com/metalivedev/django/master/postinstall /tmp/postinstall.sh
|
||||
06fd35556d7b
|
||||
|
||||
.. _cli_inspect:
|
||||
|
||||
@@ -486,6 +676,18 @@ Known Issues (kill)
|
||||
* :issue:`197` indicates that ``docker kill`` may leave directories
|
||||
behind and make it difficult to remove the container.
|
||||
|
||||
.. _cli_load:
|
||||
|
||||
``load``
|
||||
--------
|
||||
|
||||
::
|
||||
|
||||
Usage: docker load < repository.tar
|
||||
|
||||
Loads a tarred repository from the standard input stream.
|
||||
Restores both images and tags.
|
||||
|
||||
.. _cli_login:
|
||||
|
||||
``login``
|
||||
@@ -772,6 +974,24 @@ id may be optionally suffixed with ``:ro`` or ``:rw`` to mount the volumes in
|
||||
read-only or read-write mode, respectively. By default, the volumes are mounted
|
||||
in the same mode (rw or ro) as the reference container.
|
||||
|
||||
Known Issues (run -volumes-from)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* :issue:`2702`: "lxc-start: Permission denied - failed to mount"
|
||||
could indicate a permissions problem with AppArmor. Please see the
|
||||
issue for a workaround.
|
||||
|
||||
.. _cli_save:
|
||||
|
||||
``save``
|
||||
|
||||
::
|
||||
|
||||
Usage: docker save image > repository.tar
|
||||
|
||||
Streams a tarred repository to the standard output stream.
|
||||
Contains all parent layers, and all tags + versions.
|
||||
|
||||
.. _cli_search:
|
||||
|
||||
``search``
|
||||
|
||||
@@ -235,7 +235,7 @@ latex_documents = [
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('toctree', 'docker', u'Docker Documentation',
|
||||
('commandline/cli', 'docker', u'Docker Documentation',
|
||||
[u'Team Docker'], 1)
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
:title: Process Management with CFEngine
|
||||
:description: Managing containerized processes with CFEngine
|
||||
:keywords: cfengine, process, management, usage, docker, documentation
|
||||
|
||||
Process Management with CFEngine
|
||||
================================
|
||||
|
||||
Create Docker containers with managed processes.
|
||||
|
||||
Docker monitors one process in each running container and the container lives or dies with that process.
|
||||
By introducing CFEngine inside Docker containers, we can alleviate a few of the issues that may arise:
|
||||
|
||||
* It is possible to easily start multiple processes within a container, all of which will be managed automatically, with the normal ``docker run`` command.
|
||||
* If a managed process dies or crashes, CFEngine will start it again within 1 minute.
|
||||
* The container itself will live as long as the CFEngine scheduling daemon (cf-execd) lives. With CFEngine, we are able to decouple the life of the container from the uptime of the service it provides.
|
||||
|
||||
|
||||
How it works
|
||||
------------
|
||||
|
||||
CFEngine, together with the cfe-docker integration policies, are installed as part of the Dockerfile. This builds CFEngine into our Docker image.
|
||||
|
||||
The Dockerfile's ``ENTRYPOINT`` takes an arbitrary amount of commands (with any desired arguments) as parameters.
|
||||
When we run the Docker container these parameters get written to CFEngine policies and CFEngine takes over to ensure that the desired processes are running in the container.
|
||||
|
||||
CFEngine scans the process table for the ``basename`` of the commands given to the ``ENTRYPOINT`` and runs the command to start the process if the ``basename`` is not found.
|
||||
For example, if we start the container with ``docker run "/path/to/my/application parameters"``, CFEngine will look for a process named ``application`` and run the command.
|
||||
If an entry for ``application`` is not found in the process table at any point in time, CFEngine will execute ``/path/to/my/application parameters`` to start the application once again.
|
||||
The check on the process table happens every minute.
|
||||
|
||||
Note that it is therefore important that the command to start your application leaves a process with the basename of the command.
|
||||
This can be made more flexible by making some minor adjustments to the CFEngine policies, if desired.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
This example assumes you have Docker installed and working.
|
||||
We will install and manage ``apache2`` and ``sshd`` in a single container.
|
||||
|
||||
There are three steps:
|
||||
|
||||
1. Install CFEngine into the container.
|
||||
2. Copy the CFEngine Docker process management policy into the containerized CFEngine installation.
|
||||
3. Start your application processes as part of the ``docker run`` command.
|
||||
|
||||
|
||||
Building the container image
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The first two steps can be done as part of a Dockerfile, as follows.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
FROM ubuntu
|
||||
MAINTAINER Eystein Måløy Stenberg <eytein.stenberg@gmail.com>
|
||||
|
||||
RUN apt-get -y install wget lsb-release unzip
|
||||
|
||||
# install latest CFEngine
|
||||
RUN wget -qO- http://cfengine.com/pub/gpg.key | apt-key add -
|
||||
RUN echo "deb http://cfengine.com/pub/apt $(lsb_release -cs) main" > /etc/apt/sources.list.d/cfengine-community.list
|
||||
RUN apt-get update
|
||||
RUN apt-get install cfengine-community
|
||||
|
||||
# install cfe-docker process management policy
|
||||
RUN wget --no-check-certificate https://github.com/estenberg/cfe-docker/archive/master.zip -P /tmp/ && unzip /tmp/master.zip -d /tmp/
|
||||
RUN cp /tmp/cfe-docker-master/cfengine/bin/* /var/cfengine/bin/
|
||||
RUN cp /tmp/cfe-docker-master/cfengine/inputs/* /var/cfengine/inputs/
|
||||
RUN rm -rf /tmp/cfe-docker-master /tmp/master.zip
|
||||
|
||||
# apache2 and openssh are just for testing purposes, install your own apps here
|
||||
RUN apt-get -y install openssh-server apache2
|
||||
RUN mkdir -p /var/run/sshd
|
||||
RUN echo "root:password" | chpasswd # need a password for ssh
|
||||
|
||||
ENTRYPOINT ["/var/cfengine/bin/docker_processes_run.sh"]
|
||||
|
||||
|
||||
By saving this file as ``Dockerfile`` to a working directory, you can then build your container with the docker build command,
|
||||
e.g. ``docker build -t managed_image``.
|
||||
|
||||
Testing the container
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Start the container with ``apache2`` and ``sshd`` running and managed, forwarding a port to our SSH instance:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -p 127.0.0.1:222:22 -d managed_image "/usr/sbin/sshd" "/etc/init.d/apache2 start"
|
||||
|
||||
We now clearly see one of the benefits of the cfe-docker integration: it allows to start several processes
|
||||
as part of a normal ``docker run`` command.
|
||||
|
||||
We can now log in to our new container and see that both ``apache2`` and ``sshd`` are running. We have set the root password to
|
||||
"password" in the Dockerfile above and can use that to log in with ssh:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
ssh -p222 root@127.0.0.1
|
||||
|
||||
ps -ef
|
||||
UID PID PPID C STIME TTY TIME CMD
|
||||
root 1 0 0 07:48 ? 00:00:00 /bin/bash /var/cfengine/bin/docker_processes_run.sh /usr/sbin/sshd /etc/init.d/apache2 start
|
||||
root 18 1 0 07:48 ? 00:00:00 /var/cfengine/bin/cf-execd -F
|
||||
root 20 1 0 07:48 ? 00:00:00 /usr/sbin/sshd
|
||||
root 32 1 0 07:48 ? 00:00:00 /usr/sbin/apache2 -k start
|
||||
www-data 34 32 0 07:48 ? 00:00:00 /usr/sbin/apache2 -k start
|
||||
www-data 35 32 0 07:48 ? 00:00:00 /usr/sbin/apache2 -k start
|
||||
www-data 36 32 0 07:48 ? 00:00:00 /usr/sbin/apache2 -k start
|
||||
root 93 20 0 07:48 ? 00:00:00 sshd: root@pts/0
|
||||
root 105 93 0 07:48 pts/0 00:00:00 -bash
|
||||
root 112 105 0 07:49 pts/0 00:00:00 ps -ef
|
||||
|
||||
|
||||
If we stop apache2, it will be started again within a minute by CFEngine.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
service apache2 status
|
||||
Apache2 is running (pid 32).
|
||||
service apache2 stop
|
||||
* Stopping web server apache2 ... waiting [ OK ]
|
||||
service apache2 status
|
||||
Apache2 is NOT running.
|
||||
# ... wait up to 1 minute...
|
||||
service apache2 status
|
||||
Apache2 is running (pid 173).
|
||||
|
||||
|
||||
Adapting to your applications
|
||||
-----------------------------
|
||||
|
||||
To make sure your applications get managed in the same manner, there are just two things you need to adjust from the above example:
|
||||
|
||||
* In the Dockerfile used above, install your applications instead of ``apache2`` and ``sshd``.
|
||||
* When you start the container with ``docker run``, specify the command line arguments to your applications rather than ``apache2`` and ``sshd``.
|
||||
@@ -20,7 +20,7 @@ Note that we're marking ``/var/lib/couchdb`` as a data volume.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
COUCH1=$(sudo docker run -d -v /var/lib/couchdb shykes/couchdb:2013-05-03)
|
||||
COUCH1=$(sudo docker run -d -p 5984 -v /var/lib/couchdb shykes/couchdb:2013-05-03)
|
||||
|
||||
Add data to the first database
|
||||
------------------------------
|
||||
@@ -31,7 +31,7 @@ replace ``localhost`` with the public IP of your Docker host.
|
||||
.. code-block:: bash
|
||||
|
||||
HOST=localhost
|
||||
URL="http://$HOST:$(sudo docker port $COUCH1 5984)/_utils/"
|
||||
URL="http://$HOST:$(sudo docker port $COUCH1 5984 | grep -Po '\d+$')/_utils/"
|
||||
echo "Navigate to $URL in your browser, and use the couch interface to add data"
|
||||
|
||||
Create second database
|
||||
@@ -41,7 +41,7 @@ This time, we're requesting shared access to ``$COUCH1``'s volumes.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
COUCH2=$(sudo docker run -d -volumes-from $COUCH1 shykes/couchdb:2013-05-03)
|
||||
COUCH2=$(sudo docker run -d -p 5984 -volumes-from $COUCH1 shykes/couchdb:2013-05-03)
|
||||
|
||||
Browse data on the second database
|
||||
----------------------------------
|
||||
@@ -49,7 +49,7 @@ Browse data on the second database
|
||||
.. code-block:: bash
|
||||
|
||||
HOST=localhost
|
||||
URL="http://$HOST:$(sudo docker port $COUCH2 5984)/_utils/"
|
||||
URL="http://$HOST:$(sudo docker port $COUCH2 5984 | grep -Po '\d+$')/_utils/"
|
||||
echo "Navigate to $URL in your browser. You should see the same data as in the first database"'!'
|
||||
|
||||
Congratulations, you are now running two Couchdb containers, completely
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
|
||||
.. note::
|
||||
|
||||
This example assumes you have Docker running in daemon mode. For more information please see :ref:`running_examples`
|
||||
* This example assumes you have Docker running in daemon mode. For
|
||||
more information please see :ref:`running_examples`.
|
||||
* **If you don't like sudo** then see :ref:`dockergroup`
|
||||
|
||||
|
||||
@@ -127,10 +127,12 @@ Check the logs make sure it is working correctly.
|
||||
|
||||
sudo docker attach $CONTAINER_ID
|
||||
|
||||
Attach to the container to see the results in realtime.
|
||||
Attach to the container to see the results in real-time.
|
||||
|
||||
- **"docker attach**" This will allow us to attach to a background
|
||||
process to see what is going on.
|
||||
- **"-sig-proxy=true"** Proxify all received signal to the process
|
||||
(even in non-tty mode)
|
||||
- **$CONTAINER_ID** The Id of the container we want to attach too.
|
||||
|
||||
Exit from the container attachment by pressing Control-C.
|
||||
|
||||
@@ -24,3 +24,5 @@ to more substantial services like those which you might find in production.
|
||||
postgresql_service
|
||||
mongodb
|
||||
running_riak_service
|
||||
using_supervisord
|
||||
cfengine_process_management
|
||||
|
||||
@@ -39,11 +39,12 @@ container. The ``BUILD_JOB`` environment variable will be set with the new conta
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker attach $BUILD_JOB
|
||||
sudo docker attach -sig-proxy=false $BUILD_JOB
|
||||
[...]
|
||||
|
||||
While this container is running, we can attach to the new container to
|
||||
see what is going on. You can use Ctrl-C to disconnect.
|
||||
see what is going on. The flag ``-sig-proxy`` set as ``false`` allows you to connect and
|
||||
disconnect (Ctrl-C) to it without stopping the container.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ with your own user name.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker build -t <your username>/redis
|
||||
sudo docker build -t <your username>/redis .
|
||||
|
||||
Run the service
|
||||
---------------
|
||||
@@ -82,7 +82,7 @@ of our ``redis`` container.
|
||||
DB_PORT_6379_TCP_ADDR=172.17.0.33
|
||||
DB_PORT_6379_TCP_PROTO=tcp
|
||||
|
||||
We can see that we've got a small list of environmental varaibles prefixed with ``DB``.
|
||||
We can see that we've got a small list of environment variables prefixed with ``DB``.
|
||||
The ``DB`` comes from the link alias specified when we launched the container. Let's use
|
||||
the ``DB_PORT_6379_TCP_ADDR`` variable to connect to our Redis container.
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ SSH Daemon Service
|
||||
|
||||
**Video:**
|
||||
|
||||
I've create a little screencast to show how to create a SSHd service
|
||||
I've created a little screencast to show how to create an SSHd service
|
||||
and connect to it. It is something like 11 minutes and not entirely
|
||||
smooth, but gives you a good idea.
|
||||
smooth, but it gives you a good idea.
|
||||
|
||||
.. note::
|
||||
This screencast was created before Docker version 0.5.2, so the
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
:title: Using Supervisor with Docker
|
||||
:description: How to use Supervisor process management with Docker
|
||||
:keywords: docker, supervisor, process management
|
||||
|
||||
.. _using_supervisord:
|
||||
|
||||
Using Supervisor with Docker
|
||||
============================
|
||||
|
||||
.. include:: example_header.inc
|
||||
|
||||
Traditionally a Docker container runs a single process when it is launched, for
|
||||
example an Apache daemon or a SSH server daemon. Often though you want to run
|
||||
more than one process in a container. There are a number of ways you can
|
||||
achieve this ranging from using a simple Bash script as the value of your
|
||||
container's ``CMD`` instruction to installing a process management tool.
|
||||
|
||||
In this example we're going to make use of the process management tool,
|
||||
`Supervisor <http://supervisord.org/>`_, to manage multiple processes in our
|
||||
container. Using Supervisor allows us to better control, manage, and restart the
|
||||
processes we want to run. To demonstrate this we're going to install and manage both an
|
||||
SSH daemon and an Apache daemon.
|
||||
|
||||
Creating a Dockerfile
|
||||
---------------------
|
||||
|
||||
Let's start by creating a basic ``Dockerfile`` for our new image.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
FROM ubuntu:latest
|
||||
MAINTAINER examples@docker.io
|
||||
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
|
||||
RUN apt-get update
|
||||
RUN apt-get upgrade -y
|
||||
|
||||
Installing Supervisor
|
||||
---------------------
|
||||
|
||||
We can now install our SSH and Apache daemons as well as Supervisor in our container.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
RUN apt-get install -y openssh-server apache2 supervisor
|
||||
RUN mkdir -p /var/run/sshd
|
||||
RUN mkdir -p /var/log/supervisor
|
||||
|
||||
Here we're installing the ``openssh-server``, ``apache2`` and ``supervisor``
|
||||
(which provides the Supervisor daemon) packages. We're also creating two new
|
||||
directories that are needed to run our SSH daemon and Supervisor.
|
||||
|
||||
Adding Supervisor's configuration file
|
||||
--------------------------------------
|
||||
|
||||
Now let's add a configuration file for Supervisor. The default file is called
|
||||
``supervisord.conf`` and is located in ``/etc/supervisor/conf.d/``.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
ADD supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||
|
||||
Let's see what is inside our ``supervisord.conf`` file.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
|
||||
[program:sshd]
|
||||
command=/usr/sbin/sshd -D
|
||||
|
||||
[program:apache2]
|
||||
command=/bin/bash -c "source /etc/apache2/envvars && /usr/sbin/apache2 -DFOREGROUND"
|
||||
|
||||
The ``supervisord.conf`` configuration file contains directives that configure
|
||||
Supervisor and the processes it manages. The first block ``[supervisord]``
|
||||
provides configuration for Supervisor itself. We're using one directive,
|
||||
``nodaemon`` which tells Supervisor to run interactively rather than daemonize.
|
||||
|
||||
The next two blocks manage the services we wish to control. Each block controls
|
||||
a separate process. The blocks contain a single directive, ``command``, which
|
||||
specifies what command to run to start each process.
|
||||
|
||||
Exposing ports and running Supervisor
|
||||
-------------------------------------
|
||||
|
||||
Now let's finish our ``Dockerfile`` by exposing some required ports and
|
||||
specifying the ``CMD`` instruction to start Supervisor when our container
|
||||
launches.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
EXPOSE 22 80
|
||||
CMD ["/usr/bin/supervisord"]
|
||||
|
||||
Here we've exposed ports 22 and 80 on the container and we're running the
|
||||
``/usr/bin/supervisord`` binary when the container launches.
|
||||
|
||||
Building our container
|
||||
----------------------
|
||||
|
||||
We can now build our new container.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker build -t <yourname>/supervisord .
|
||||
|
||||
Running our Supervisor container
|
||||
--------------------------------
|
||||
|
||||
Once we've got a built image we can launch a container from it.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker run -p 22 -p 80 -t -i <yourname>/supervisor
|
||||
2013-11-25 18:53:22,312 CRIT Supervisor running as root (no user in config file)
|
||||
2013-11-25 18:53:22,312 WARN Included extra file "/etc/supervisor/conf.d/supervisord.conf" during parsing
|
||||
2013-11-25 18:53:22,342 INFO supervisord started with pid 1
|
||||
2013-11-25 18:53:23,346 INFO spawned: 'sshd' with pid 6
|
||||
2013-11-25 18:53:23,349 INFO spawned: 'apache2' with pid 7
|
||||
. . .
|
||||
|
||||
We've launched a new container interactively using the ``docker run`` command.
|
||||
That container has run Supervisor and launched the SSH and Apache daemons with
|
||||
it. We've specified the ``-p`` flag to expose ports 22 and 80. From here we can
|
||||
now identify the exposed ports and connect to one or both of the SSH and Apache
|
||||
daemons.
|
||||
|
||||
@@ -22,12 +22,10 @@ Amazon QuickStart
|
||||
|
||||
1. **Choose an image:**
|
||||
|
||||
* Open http://cloud-images.ubuntu.com/locator/ec2/
|
||||
* Enter ``amd64 precise`` in the search field (it will search as you
|
||||
type)
|
||||
* Pick an image by clicking on the image name. *An EBS-enabled
|
||||
image will let you use a t1.micro instance.* Clicking on the image
|
||||
name will take you to your AWS Console.
|
||||
* Launch the `Create Instance Wizard` <https://console.aws.amazon.com/ec2/v2/home?#LaunchInstanceWizard:> menu on your AWS Console
|
||||
* Select "Community AMIs" option and serch for ``amd64 precise`` (click enter to search)
|
||||
* If you choose a EBS enabled AMI you will be able to launch a `t1.micro` instance (more info on `pricing` <http://aws.amazon.com/en/ec2/pricing/> )
|
||||
* When you click select you'll be taken to the instance setup, and you're one click away from having your Ubuntu VM up and running.
|
||||
|
||||
2. **Tell CloudInit to install Docker:**
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
:title: Installation on Arch Linux
|
||||
:description: Docker installation on Arch Linux.
|
||||
:description: Docker installation on Arch Linux.
|
||||
:keywords: arch linux, virtualization, docker, documentation, installation
|
||||
|
||||
.. _arch_linux:
|
||||
@@ -7,6 +7,10 @@
|
||||
Arch Linux
|
||||
==========
|
||||
|
||||
.. include:: install_header.inc
|
||||
|
||||
.. include:: install_unofficial.inc
|
||||
|
||||
Installing on Arch Linux is not officially supported but can be handled via
|
||||
either of the following AUR packages:
|
||||
|
||||
@@ -32,10 +36,6 @@ either AUR package.
|
||||
Installation
|
||||
------------
|
||||
|
||||
.. include:: install_header.inc
|
||||
|
||||
.. include:: install_unofficial.inc
|
||||
|
||||
The instructions here assume **yaourt** is installed. See
|
||||
`Arch User Repository <https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages>`_
|
||||
for information on building and installing packages from the AUR if you have not
|
||||
|
||||
@@ -12,17 +12,9 @@ Binaries
|
||||
**This instruction set is meant for hackers who want to try out Docker
|
||||
on a variety of environments.**
|
||||
|
||||
Right now, the officially supported distributions are:
|
||||
|
||||
- :ref:`ubuntu_precise`
|
||||
- :ref:`ubuntu_raring`
|
||||
|
||||
|
||||
But we know people have had success running it under
|
||||
|
||||
- Debian
|
||||
- Suse
|
||||
- :ref:`arch_linux`
|
||||
Before following these directions, you should really check if a packaged version
|
||||
of Docker is already available for your distribution. We have packages for many
|
||||
distributions, and more keep showing up all the time!
|
||||
|
||||
Check Your Kernel
|
||||
-----------------
|
||||
@@ -34,7 +26,7 @@ Get the docker binary:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
wget --output-document=docker https://get.docker.io/builds/Linux/x86_64/docker-latest
|
||||
wget https://get.docker.io/builds/Linux/x86_64/docker-latest -O docker
|
||||
chmod +x docker
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
:title: Requirements and Installation on Fedora
|
||||
:description: Please note this project is currently under heavy development. It should not be used in production.
|
||||
:keywords: Docker, Docker documentation, requirements, virtualbox, vagrant, git, ssh, putty, cygwin, linux
|
||||
|
||||
.. _fedora:
|
||||
|
||||
Fedora
|
||||
======
|
||||
|
||||
.. include:: install_header.inc
|
||||
|
||||
.. include:: install_unofficial.inc
|
||||
|
||||
.. warning::
|
||||
|
||||
This is a placeholder for the Fedora installation instructions. Currently there is not an available
|
||||
Docker package in the Fedora distribution. These packages are being built and should be available soon.
|
||||
These instructions will be updated when the package is available.
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
.. _gentoo_linux:
|
||||
|
||||
Gentoo Linux
|
||||
============
|
||||
Gentoo
|
||||
======
|
||||
|
||||
.. include:: install_header.inc
|
||||
|
||||
@@ -22,17 +22,19 @@ provided at https://github.com/tianon/docker-overlay which can be added using
|
||||
properly installing and using the overlay can be found in `the overlay README
|
||||
<https://github.com/tianon/docker-overlay/blob/master/README.md#using-this-overlay>`_.
|
||||
|
||||
Note that sometimes there is a disparity between the latest version and what's
|
||||
in the overlay, and between the latest version in the overlay and what's in the
|
||||
portage tree. Please be patient, and the latest version should propagate
|
||||
shortly.
|
||||
|
||||
Installation
|
||||
^^^^^^^^^^^^
|
||||
|
||||
The package should properly pull in all the necessary dependencies and prompt
|
||||
for all necessary kernel options. For the most straightforward installation
|
||||
experience, use ``sys-kernel/aufs-sources`` as your kernel sources. If you
|
||||
prefer not to use ``sys-kernel/aufs-sources``, the portage tree also contains
|
||||
``sys-fs/aufs3``, which includes the patches necessary for adding AUFS support
|
||||
to other kernel source packages such as ``sys-kernel/gentoo-sources`` (and a
|
||||
``kernel-patch`` USE flag to perform the patching to ``/usr/src/linux``
|
||||
automatically).
|
||||
for all necessary kernel options. The ebuilds for 0.7+ include use flags to
|
||||
pull in the proper dependencies of the major storage drivers, with the
|
||||
"device-mapper" use flag being enabled by default, since that is the simplest
|
||||
installation path.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -47,9 +49,9 @@ the #docker IRC channel on the freenode network.
|
||||
Starting Docker
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Ensure that you are running a kernel that includes the necessary AUFS
|
||||
patches/support and includes all the necessary modules and/or configuration for
|
||||
LXC.
|
||||
Ensure that you are running a kernel that includes all the necessary modules
|
||||
and/or configuration for LXC (and optionally for device-mapper and/or AUFS,
|
||||
depending on the storage driver you've decided to use).
|
||||
|
||||
OpenRC
|
||||
------
|
||||
|
||||
@@ -9,7 +9,7 @@ Installation
|
||||
|
||||
There are a number of ways to install Docker, depending on where you
|
||||
want to run the daemon. The :ref:`ubuntu_linux` installation is the
|
||||
officially-tested version, and the community adds more techniques for
|
||||
officially-tested version. The community adds more techniques for
|
||||
installing Docker all the time.
|
||||
|
||||
Contents:
|
||||
@@ -18,12 +18,14 @@ Contents:
|
||||
:maxdepth: 1
|
||||
|
||||
ubuntulinux
|
||||
binaries
|
||||
fedora
|
||||
archlinux
|
||||
gentoolinux
|
||||
vagrant
|
||||
windows
|
||||
amazon
|
||||
rackspace
|
||||
archlinux
|
||||
gentoolinux
|
||||
upgrading
|
||||
kernel
|
||||
binaries
|
||||
security
|
||||
upgrading
|
||||
|
||||
@@ -11,10 +11,10 @@ In short, Docker has the following kernel requirements:
|
||||
|
||||
- Linux version 3.8 or above.
|
||||
|
||||
- `AUFS support <http://aufs.sourceforge.net/>`_.
|
||||
|
||||
- Cgroups and namespaces must be enabled.
|
||||
|
||||
*Note: as of 0.7 docker no longer requires aufs. AUFS support is still available as an optional driver.*
|
||||
|
||||
The officially supported kernel is the one recommended by the
|
||||
:ref:`ubuntu_linux` installation path. It is the one that most developers
|
||||
will use, and the one that receives the most attention from the core
|
||||
@@ -25,6 +25,7 @@ If you cannot or do not want to use the "official" kernels,
|
||||
here is some technical background about the features (both optional and
|
||||
mandatory) that docker needs to run successfully.
|
||||
|
||||
|
||||
Linux version 3.8 or above
|
||||
--------------------------
|
||||
|
||||
@@ -39,6 +40,15 @@ The symptoms include:
|
||||
- kernel crash causing the machine to freeze for a few minutes, or even
|
||||
completely.
|
||||
|
||||
Additionally, kernels prior 3.4 did not implement ``reboot_pid_ns``,
|
||||
which means that the ``reboot()`` syscall could reboot the host machine,
|
||||
instead of terminating the container. To work around that problem,
|
||||
LXC userland tools (since version 0.8) automatically drop the ``SYS_BOOT``
|
||||
capability when necessary. Still, if you run a pre-3.4 kernel with pre-0.8
|
||||
LXC tools, be aware that containers can reboot the whole host! This is
|
||||
not something that Docker wants to address in the short term, since you
|
||||
shouldn't use kernels prior 3.8 with Docker anyway.
|
||||
|
||||
While it is still possible to use older kernels for development, it is
|
||||
really not advised to do so.
|
||||
|
||||
@@ -48,17 +58,6 @@ detects something older than 3.8.
|
||||
See issue `#407 <https://github.com/dotcloud/docker/issues/407>`_ for details.
|
||||
|
||||
|
||||
AUFS support
|
||||
------------
|
||||
|
||||
Docker currently relies on AUFS, an unioning filesystem.
|
||||
While AUFS is included in the kernels built by the Debian and Ubuntu
|
||||
distributions, is not part of the standard kernel. This means that if
|
||||
you decide to roll your own kernel, you will have to patch your
|
||||
kernel tree to add AUFS. The process is documented on
|
||||
`AUFS webpage <http://aufs.sourceforge.net/>`_.
|
||||
|
||||
|
||||
Cgroups and namespaces
|
||||
----------------------
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
:description: Installing Docker on Ubuntu proviced by Rackspace
|
||||
:keywords: Rackspace Cloud, installation, docker, linux, ubuntu
|
||||
|
||||
===============
|
||||
Rackspace Cloud
|
||||
===============
|
||||
|
||||
@@ -14,14 +13,14 @@ straightforward, and you should mostly be able to follow the
|
||||
|
||||
**However, there is one caveat:**
|
||||
|
||||
If you are using any linux not already shipping with the 3.8 kernel
|
||||
If you are using any Linux not already shipping with the 3.8 kernel
|
||||
you will need to install it. And this is a little more difficult on
|
||||
Rackspace.
|
||||
|
||||
Rackspace boots their servers using grub's ``menu.lst`` and does not
|
||||
like non 'virtual' packages (e.g. xen compatible) kernels there,
|
||||
although they do work. This makes ``update-grub`` to not have the
|
||||
expected result, and you need to set the kernel manually.
|
||||
like non 'virtual' packages (e.g. Xen compatible) kernels there,
|
||||
although they do work. This results in ``update-grub`` not having the
|
||||
expected result, and you will need to set the kernel manually.
|
||||
|
||||
**Do not attempt this on a production machine!**
|
||||
|
||||
@@ -34,7 +33,7 @@ expected result, and you need to set the kernel manually.
|
||||
apt-get install linux-generic-lts-raring
|
||||
|
||||
|
||||
Great, now you have kernel installed in ``/boot/``, next is to make it
|
||||
Great, now you have the kernel installed in ``/boot/``, next you need to make it
|
||||
boot next time.
|
||||
|
||||
.. code-block:: bash
|
||||
@@ -48,9 +47,9 @@ boot next time.
|
||||
Now you need to manually edit ``/boot/grub/menu.lst``, you will find a
|
||||
section at the bottom with the existing options. Copy the top one and
|
||||
substitute the new kernel into that. Make sure the new kernel is on
|
||||
top, and double check kernel and initrd point to the right files.
|
||||
top, and double check the kernel and initrd lines point to the right files.
|
||||
|
||||
Make special care to double check the kernel and initrd entries.
|
||||
Take special care to double check the kernel and initrd entries.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -79,7 +78,7 @@ It will probably look something like this:
|
||||
initrd /boot/initrd.img-3.2.0-38-virtual
|
||||
|
||||
|
||||
Reboot server (either via command line or console)
|
||||
Reboot the server (either via command line or console)
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
:title: Docker Security
|
||||
:description: Review of the Docker Daemon attack surface
|
||||
:keywords: Docker, Docker documentation, security
|
||||
|
||||
.. _dockersecurity:
|
||||
|
||||
Docker Security
|
||||
===============
|
||||
|
||||
*Adapted from* `Containers & Docker: How Secure are They? <blogsecurity>`_
|
||||
|
||||
There are three major areas to consider when reviewing Docker security:
|
||||
|
||||
* the intrinsic security of containers, as implemented by kernel
|
||||
namespaces and cgroups;
|
||||
* the attack surface of the Docker daemon itself;
|
||||
* the "hardening" security features of the kernel and how they
|
||||
interact with containers.
|
||||
|
||||
Kernel Namespaces
|
||||
-----------------
|
||||
|
||||
Docker containers are essentially LXC containers, and they come with
|
||||
the same security features. When you start a container with ``docker
|
||||
run``, behind the scenes Docker uses ``lxc-start`` to execute the
|
||||
Docker container. This creates a set of namespaces and control groups
|
||||
for the container. Those namespaces and control groups are not created
|
||||
by Docker itself, but by ``lxc-start``. This means that as the LXC
|
||||
userland tools evolve (and provide additional namespaces and isolation
|
||||
features), Docker will automatically make use of them.
|
||||
|
||||
**Namespaces provide the first and most straightforward form of
|
||||
isolation**: processes running within a container cannot see, and even
|
||||
less affect, processes running in another container, or in the host
|
||||
system.
|
||||
|
||||
**Each container also gets its own network stack**, meaning that a
|
||||
container doesn’t get a privileged access to the sockets or interfaces
|
||||
of another container. Of course, if the host system is setup
|
||||
accordingly, containers can interact with each other through their
|
||||
respective network interfaces — just like they can interact with
|
||||
external hosts. When you specify public ports for your containers or
|
||||
use :ref:`links <working_with_links_names>` then IP traffic is allowed
|
||||
between containers. They can ping each other, send/receive UDP
|
||||
packets, and establish TCP connections, but that can be restricted if
|
||||
necessary. From a network architecture point of view, all containers
|
||||
on a given Docker host are sitting on bridge interfaces. This means
|
||||
that they are just like physical machines connected through a common
|
||||
Ethernet switch; no more, no less.
|
||||
|
||||
How mature is the code providing kernel namespaces and private
|
||||
networking? Kernel namespaces were introduced `between kernel version
|
||||
2.6.15 and 2.6.26
|
||||
<http://lxc.sourceforge.net/index.php/about/kernel-namespaces/>`_. This
|
||||
means that since July 2008 (date of the 2.6.26 release, now 5 years
|
||||
ago), namespace code has been exercised and scrutinized on a large
|
||||
number of production systems. And there is more: the design and
|
||||
inspiration for the namespaces code are even older. Namespaces are
|
||||
actually an effort to reimplement the features of `OpenVZ
|
||||
<http://en.wikipedia.org/wiki/OpenVZ>`_ in such a way that they could
|
||||
be merged within the mainstream kernel. And OpenVZ was initially
|
||||
released in 2005, so both the design and the implementation are
|
||||
pretty mature.
|
||||
|
||||
Control Groups
|
||||
--------------
|
||||
|
||||
Control Groups are the other key component of Linux Containers. They
|
||||
implement resource accounting and limiting. They provide a lot of very
|
||||
useful metrics, but they also help to ensure that each container gets
|
||||
its fair share of memory, CPU, disk I/O; and, more importantly, that a
|
||||
single container cannot bring the system down by exhausting one of
|
||||
those resources.
|
||||
|
||||
So while they do not play a role in preventing one container from
|
||||
accessing or affecting the data and processes of another container,
|
||||
they are essential to fend off some denial-of-service attacks. They
|
||||
are particularly important on multi-tenant platforms, like public and
|
||||
private PaaS, to guarantee a consistent uptime (and performance) even
|
||||
when some applications start to misbehave.
|
||||
|
||||
Control Groups have been around for a while as well: the code was
|
||||
started in 2006, and initially merged in kernel 2.6.24.
|
||||
|
||||
Docker Daemon Attack Surface
|
||||
----------------------------
|
||||
|
||||
Running containers (and applications) with Docker implies running the
|
||||
Docker daemon. This daemon currently requires root privileges, and you
|
||||
should therefore be aware of some important details.
|
||||
|
||||
First of all, **only trusted users should be allowed to control your
|
||||
Docker daemon**. This is a direct consequence of some powerful Docker
|
||||
features. Specifically, Docker allows you to share a directory between
|
||||
the Docker host and a guest container; and it allows you to do so
|
||||
without limiting the access rights of the container. This means that
|
||||
you can start a container where the ``/host`` directory will be the
|
||||
``/`` directory on your host; and the container will be able to alter
|
||||
your host filesystem without any restriction. This sounds crazy? Well,
|
||||
you have to know that **all virtualization systems allowing filesystem
|
||||
resource sharing behave the same way**. Nothing prevents you from
|
||||
sharing your root filesystem (or even your root block device) with a
|
||||
virtual machine.
|
||||
|
||||
This has a strong security implication: if you instrument Docker from
|
||||
e.g. a web server to provision containers through an API, you should
|
||||
be even more careful than usual with parameter checking, to make sure
|
||||
that a malicious user cannot pass crafted parameters causing Docker to
|
||||
create arbitrary containers.
|
||||
|
||||
For this reason, the REST API endpoint (used by the Docker CLI to
|
||||
communicate with the Docker daemon) changed in Docker 0.5.2, and now
|
||||
uses a UNIX socket instead of a TCP socket bound on 127.0.0.1 (the
|
||||
latter being prone to cross-site-scripting attacks if you happen to
|
||||
run Docker directly on your local machine, outside of a VM). You can
|
||||
then use traditional UNIX permission checks to limit access to the
|
||||
control socket.
|
||||
|
||||
You can also expose the REST API over HTTP if you explicitly decide
|
||||
so. However, if you do that, being aware of the abovementioned
|
||||
security implication, you should ensure that it will be reachable
|
||||
only from a trusted network or VPN; or protected with e.g. ``stunnel``
|
||||
and client SSL certificates.
|
||||
|
||||
Recent improvements in Linux namespaces will soon allow to run
|
||||
full-featured containers without root privileges, thanks to the new
|
||||
user namespace. This is covered in detail `here
|
||||
<http://s3hh.wordpress.com/2013/07/19/creating-and-using-containers-without-privilege/>`_. Moreover,
|
||||
this will solve the problem caused by sharing filesystems between host
|
||||
and guest, since the user namespace allows users within containers
|
||||
(including the root user) to be mapped to other users in the host
|
||||
system.
|
||||
|
||||
The end goal for Docker is therefore to implement two additional
|
||||
security improvements:
|
||||
|
||||
* map the root user of a container to a non-root user of the Docker
|
||||
host, to mitigate the effects of a container-to-host privilege
|
||||
escalation;
|
||||
* allow the Docker daemon to run without root privileges, and delegate
|
||||
operations requiring those privileges to well-audited sub-processes,
|
||||
each with its own (very limited) scope: virtual network setup,
|
||||
filesystem management, etc.
|
||||
|
||||
Finally, if you run Docker on a server, it is recommended to run
|
||||
exclusively Docker in the server, and move all other services within
|
||||
containers controlled by Docker. Of course, it is fine to keep your
|
||||
favorite admin tools (probably at least an SSH server), as well as
|
||||
existing monitoring/supervision processes (e.g. NRPE, collectd, etc).
|
||||
|
||||
Linux Kernel Capabilities
|
||||
-------------------------
|
||||
|
||||
By default, Docker starts containers with a very restricted set of
|
||||
capabilities. What does that mean?
|
||||
|
||||
Capabilities turn the binary "root/non-root" dichotomy into a
|
||||
fine-grained access control system. Processes (like web servers) that
|
||||
just need to bind on a port below 1024 do not have to run as root:
|
||||
they can just be granted the ``net_bind_service`` capability
|
||||
instead. And there are many other capabilities, for almost all the
|
||||
specific areas where root privileges are usually needed.
|
||||
|
||||
This means a lot for container security; let’s see why!
|
||||
|
||||
Your average server (bare metal or virtual machine) needs to run a
|
||||
bunch of processes as root. Those typically include SSH, cron,
|
||||
syslogd; hardware management tools (to e.g. load modules), network
|
||||
configuration tools (to handle e.g. DHCP, WPA, or VPNs), and much
|
||||
more. A container is very different, because almost all of those tasks
|
||||
are handled by the infrastructure around the container:
|
||||
|
||||
* SSH access will typically be managed by a single server running in
|
||||
the Docker host;
|
||||
* ``cron``, when necessary, should run as a user process, dedicated
|
||||
and tailored for the app that needs its scheduling service, rather
|
||||
than as a platform-wide facility;
|
||||
* log management will also typically be handed to Docker, or by
|
||||
third-party services like Loggly or Splunk;
|
||||
* hardware management is irrelevant, meaning that you never need to
|
||||
run ``udevd`` or equivalent daemons within containers;
|
||||
* network management happens outside of the containers, enforcing
|
||||
separation of concerns as much as possible, meaning that a container
|
||||
should never need to perform ``ifconfig``, ``route``, or ip commands
|
||||
(except when a container is specifically engineered to behave like a
|
||||
router or firewall, of course).
|
||||
|
||||
This means that in most cases, containers will not need "real" root
|
||||
privileges *at all*. And therefore, containers can run with a reduced
|
||||
capability set; meaning that "root" within a container has much less
|
||||
privileges than the real "root". For instance, it is possible to:
|
||||
|
||||
* deny all "mount" operations;
|
||||
* deny access to raw sockets (to prevent packet spoofing);
|
||||
* deny access to some filesystem operations, like creating new device
|
||||
nodes, changing the owner of files, or altering attributes
|
||||
(including the immutable flag);
|
||||
* deny module loading;
|
||||
* and many others.
|
||||
|
||||
This means that even if an intruder manages to escalate to root within
|
||||
a container, it will be much harder to do serious damage, or to
|
||||
escalate to the host.
|
||||
|
||||
This won't affect regular web apps; but malicious users will find that
|
||||
the arsenal at their disposal has shrunk considerably! You can see
|
||||
`the list of dropped capabilities in the Docker code
|
||||
<https://github.com/dotcloud/docker/blob/v0.5.0/lxc_template.go#L97>`_,
|
||||
and a full list of available capabilities in `Linux manpages
|
||||
<http://man7.org/linux/man-pages/man7/capabilities.7.html>`_.
|
||||
|
||||
Of course, you can always enable extra capabilities if you really need
|
||||
them (for instance, if you want to use a FUSE-based filesystem), but
|
||||
by default, Docker containers will be locked down to ensure maximum
|
||||
safety.
|
||||
|
||||
Other Kernel Security Features
|
||||
------------------------------
|
||||
|
||||
Capabilities are just one of the many security features provided by
|
||||
modern Linux kernels. It is also possible to leverage existing,
|
||||
well-known systems like TOMOYO, AppArmor, SELinux, GRSEC, etc. with
|
||||
Docker.
|
||||
|
||||
While Docker currently only enables capabilities, it doesn't interfere
|
||||
with the other systems. This means that there are many different ways
|
||||
to harden a Docker host. Here are a few examples.
|
||||
|
||||
* You can run a kernel with GRSEC and PAX. This will add many safety
|
||||
checks, both at compile-time and run-time; it will also defeat many
|
||||
exploits, thanks to techniques like address randomization. It
|
||||
doesn’t require Docker-specific configuration, since those security
|
||||
features apply system-wide, independently of containers.
|
||||
* If your distribution comes with security model templates for LXC
|
||||
containers, you can use them out of the box. For instance, Ubuntu
|
||||
comes with AppArmor templates for LXC, and those templates provide
|
||||
an extra safety net (even though it overlaps greatly with
|
||||
capabilities).
|
||||
* You can define your own policies using your favorite access control
|
||||
mechanism. Since Docker containers are standard LXC containers,
|
||||
there is nothing “magic” or specific to Docker.
|
||||
|
||||
Just like there are many third-party tools to augment Docker
|
||||
containers with e.g. special network topologies or shared filesystems,
|
||||
you can expect to see tools to harden existing Docker containers
|
||||
without affecting Docker’s core.
|
||||
|
||||
Conclusions
|
||||
-----------
|
||||
|
||||
Docker containers are, by default, quite secure; especially if you
|
||||
take care of running your processes inside the containers as
|
||||
non-privileged users (i.e. non root).
|
||||
|
||||
You can add an extra layer of safety by enabling Apparmor, SELinux,
|
||||
GRSEC, or your favorite hardening solution.
|
||||
|
||||
Last but not least, if you see interesting security features in other
|
||||
containerization systems, you will be able to implement them as well
|
||||
with Docker, since everything is provided by the kernel anyway.
|
||||
|
||||
For more context and especially for comparisons with VMs and other
|
||||
container systems, please also see the `original blog post
|
||||
<blogsecurity>`_.
|
||||
|
||||
.. _blogsecurity: http://blog.docker.io/2013/08/containers-docker-how-secure-are-they/
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
.. _ubuntu_linux:
|
||||
|
||||
Ubuntu Linux
|
||||
============
|
||||
Ubuntu
|
||||
======
|
||||
|
||||
.. warning::
|
||||
|
||||
@@ -14,16 +14,11 @@ Ubuntu Linux
|
||||
|
||||
.. include:: install_header.inc
|
||||
|
||||
Right now, the officially supported distribution are:
|
||||
Docker is supported on the following versions of Ubuntu:
|
||||
|
||||
- :ref:`ubuntu_precise`
|
||||
- :ref:`ubuntu_raring`
|
||||
|
||||
Docker has the following dependencies
|
||||
|
||||
* Linux kernel 3.8 (read more about :ref:`kernel`)
|
||||
* AUFS file system support (we are working on BTRFS support as an alternative)
|
||||
|
||||
Please read :ref:`ufw`, if you plan to use `UFW (Uncomplicated
|
||||
Firewall) <https://help.ubuntu.com/community/UFW>`_
|
||||
|
||||
@@ -70,32 +65,35 @@ Installation
|
||||
|
||||
Docker is available as a Debian package, which makes installation easy.
|
||||
|
||||
First add the Docker repository key to your local keychain. You can use the
|
||||
``apt-key`` command to check the fingerprint matches: ``36A1 D786 9245 C895 0F96
|
||||
6E92 D857 6A8B A88D 21E9``
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Add the Docker repository key to your local keychain
|
||||
# using apt-key finger you can check the fingerprint matches 36A1 D786 9245 C895 0F96 6E92 D857 6A8B A88D 21E9
|
||||
sudo sh -c "wget -qO- https://get.docker.io/gpg | apt-key add -"
|
||||
|
||||
# Add the Docker repository to your apt sources list.
|
||||
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\
|
||||
> /etc/apt/sources.list.d/docker.list"
|
||||
Add the Docker repository to your apt sources list, update and install the
|
||||
``lxc-docker`` package.
|
||||
|
||||
# Update your sources
|
||||
sudo apt-get update
|
||||
|
||||
# Install, you will see another warning that the package cannot be authenticated. Confirm install.
|
||||
sudo apt-get install lxc-docker
|
||||
|
||||
Verify it worked
|
||||
*You may receive a warning that the package isn't trusted. Answer yes to
|
||||
continue installation.*
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\
|
||||
> /etc/apt/sources.list.d/docker.list"
|
||||
sudo apt-get update
|
||||
sudo apt-get install lxc-docker
|
||||
|
||||
Now verify that the installation has worked by downloading the ``ubuntu`` image
|
||||
and launching a container.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# download the base 'ubuntu' container and run bash inside it while setting up an interactive shell
|
||||
sudo docker run -i -t ubuntu /bin/bash
|
||||
|
||||
# type 'exit' to exit
|
||||
|
||||
Type ``exit`` to exit
|
||||
|
||||
**Done!**, now continue with the :ref:`hello_world` example.
|
||||
|
||||
@@ -107,10 +105,13 @@ Ubuntu Raring 13.04 (64 bit)
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
**AUFS filesystem support**
|
||||
**Optional AUFS filesystem support**
|
||||
|
||||
Ubuntu Raring already comes with the 3.8 kernel, so we don't need to install it. However, not all systems
|
||||
have AUFS filesystem support enabled, so we need to install it.
|
||||
have AUFS filesystem support enabled. AUFS support is optional as of version 0.7, but it's still available as
|
||||
a driver and we recommend using it if you can.
|
||||
|
||||
To make sure AUFS is installed, run the following commands:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -123,36 +124,37 @@ Installation
|
||||
|
||||
Docker is available as a Debian package, which makes installation easy.
|
||||
|
||||
*Please note that these instructions have changed for 0.6. If you are upgrading from an earlier version, you will need
|
||||
to follow them again.*
|
||||
.. warning::
|
||||
|
||||
Please note that these instructions have changed for 0.6. If you are upgrading from an earlier version, you will need
|
||||
to follow them again.
|
||||
|
||||
First add the Docker repository key to your local keychain. You can use the
|
||||
``apt-key`` command to check the fingerprint matches: ``36A1 D786 9245 C895 0F96
|
||||
6E92 D857 6A8B A88D 21E9``
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Add the Docker repository key to your local keychain
|
||||
# using apt-key finger you can check the fingerprint matches 36A1 D786 9245 C895 0F96 6E92 D857 6A8B A88D 21E9
|
||||
sudo sh -c "wget -qO- https://get.docker.io/gpg | apt-key add -"
|
||||
|
||||
# Add the Docker repository to your apt sources list.
|
||||
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\
|
||||
> /etc/apt/sources.list.d/docker.list"
|
||||
|
||||
# update
|
||||
sudo apt-get update
|
||||
|
||||
# install
|
||||
sudo apt-get install lxc-docker
|
||||
|
||||
|
||||
Verify it worked
|
||||
Add the Docker repository to your apt sources list, update and install the
|
||||
``lxc-docker`` package.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\
|
||||
> /etc/apt/sources.list.d/docker.list"
|
||||
sudo apt-get update
|
||||
sudo apt-get install lxc-docker
|
||||
|
||||
Now verify that the installation has worked by downloading the ``ubuntu`` image
|
||||
and launching a container.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# download the base 'ubuntu' container
|
||||
# and run bash inside it while setting up an interactive shell
|
||||
sudo docker run -i -t ubuntu /bin/bash
|
||||
|
||||
# type exit to exit
|
||||
|
||||
Type ``exit`` to exit
|
||||
|
||||
**Done!**, now continue with the :ref:`hello_world` example.
|
||||
|
||||
@@ -162,8 +164,8 @@ Verify it worked
|
||||
Docker and UFW
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
Docker uses a bridge to manage container networking. By default, UFW
|
||||
drops all `forwarding`, thus a first step is to enable UFW forwarding:
|
||||
Docker uses a bridge to manage container networking. By default, UFW drops all
|
||||
`forwarding` traffic. As a result will you need to enable UFW forwarding:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -181,9 +183,9 @@ Then reload UFW:
|
||||
sudo ufw reload
|
||||
|
||||
|
||||
UFW's default set of rules denied all `incoming`, so if you want to be
|
||||
able to reach your containers from another host, you should allow
|
||||
incoming connections on the docker port (default 4243):
|
||||
UFW's default set of rules denies all `incoming` traffic. If you want to be
|
||||
able to reach your containers from another host then you should allow
|
||||
incoming connections on the Docker port (default 4243):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
||||
+24
-21
@@ -34,13 +34,11 @@ This will find the ``ubuntu`` image by name in the :ref:`Central Index
|
||||
<searching_central_index>` and download it from the top-level Central
|
||||
Repository to a local image cache.
|
||||
|
||||
.. NOTE:: When the image has successfully downloaded, you will see a 12
|
||||
character hash ``539c0211cd76: Download complete`` which is the short
|
||||
form of the image ID. These short image IDs are the first 12 characters
|
||||
of the full image ID - which can be found using ``docker inspect`` or
|
||||
``docker images -notrunc=true``
|
||||
|
||||
.. _dockergroup:
|
||||
.. NOTE:: When the image has successfully downloaded, you will see a
|
||||
12 character hash ``539c0211cd76: Download complete`` which is the
|
||||
short form of the image ID. These short image IDs are the first 12
|
||||
characters of the full image ID - which can be found using ``docker
|
||||
inspect`` or ``docker images -notrunc=true``
|
||||
|
||||
Running an interactive shell
|
||||
----------------------------
|
||||
@@ -53,33 +51,38 @@ Running an interactive shell
|
||||
# use the escape sequence Ctrl-p + Ctrl-q
|
||||
sudo docker run -i -t ubuntu /bin/bash
|
||||
|
||||
.. _dockergroup:
|
||||
|
||||
Why ``sudo``?
|
||||
-------------
|
||||
sudo and the docker Group
|
||||
-------------------------
|
||||
|
||||
The ``docker`` daemon always runs as root, and since ``docker``
|
||||
version 0.5.2, ``docker`` binds to a Unix socket instead of a TCP
|
||||
port. By default that Unix socket is owned by the user *root*, and so,
|
||||
by default, you can access it with ``sudo``.
|
||||
|
||||
Starting in version 0.5.3, if you create a Unix group called *docker*
|
||||
and add users to it, then the ``docker`` daemon will make the
|
||||
ownership of the Unix socket read/writable by the *docker* group when
|
||||
the daemon starts. The ``docker`` daemon must always run as root, but
|
||||
if you run the ``docker`` client as a user in the *docker* group then
|
||||
you don't need to add ``sudo`` to all the client commands.
|
||||
Starting in version 0.5.3, if you (or your Docker installer) create a
|
||||
Unix group called *docker* and add users to it, then the ``docker``
|
||||
daemon will make the ownership of the Unix socket read/writable by the
|
||||
*docker* group when the daemon starts. The ``docker`` daemon must
|
||||
always run as root, but if you run the ``docker`` client as a user in
|
||||
the *docker* group then you don't need to add ``sudo`` to all the
|
||||
client commands.
|
||||
|
||||
**Example:**
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Add the docker group
|
||||
# Add the docker group if it doesn't already exist.
|
||||
sudo groupadd docker
|
||||
|
||||
# Add the ubuntu user to the docker group
|
||||
# Add the connected user "${USERNAME}" to the docker group.
|
||||
# Change the user name to match your preferred user.
|
||||
# You may have to logout and log back in again for
|
||||
# this to take effect
|
||||
sudo gpasswd -a ubuntu docker
|
||||
# this to take effect.
|
||||
sudo gpasswd -a ${USERNAME} docker
|
||||
|
||||
# Restart the docker daemon
|
||||
# Restart the docker daemon.
|
||||
sudo service docker restart
|
||||
|
||||
.. _bind_docker:
|
||||
@@ -87,7 +90,7 @@ you don't need to add ``sudo`` to all the client commands.
|
||||
Bind Docker to another host/port or a Unix socket
|
||||
-------------------------------------------------
|
||||
|
||||
.. DANGER:: Changing the default ``docker`` daemon binding to a TCP
|
||||
.. warning:: Changing the default ``docker`` daemon binding to a TCP
|
||||
port or Unix *docker* user group will increase your security risks
|
||||
by allowing non-root users to potentially gain *root* access on the
|
||||
host (`e.g. #1369
|
||||
|
||||
@@ -15,27 +15,39 @@ commit them along the way, giving you a final image.
|
||||
|
||||
.. contents:: Table of Contents
|
||||
|
||||
.. _dockerfile_usage:
|
||||
|
||||
1. Usage
|
||||
========
|
||||
|
||||
To build an image from a source repository, create a description file
|
||||
called ``Dockerfile`` at the root of your repository. This file will
|
||||
describe the steps to assemble the image.
|
||||
To :ref:`build <cli_build>` an image from a source repository, create
|
||||
a description file called ``Dockerfile`` at the root of your
|
||||
repository. This file will describe the steps to assemble the image.
|
||||
|
||||
Then call ``docker build`` with the path of your source repository as
|
||||
argument:
|
||||
argument (for example, ``.``):
|
||||
|
||||
``sudo docker build .``
|
||||
|
||||
The path to the source repository defines where to find the *context*
|
||||
of the build. The build is run by the Docker daemon, not by the CLI,
|
||||
so the whole context must be transferred to the daemon. The Docker CLI
|
||||
reports "Uploading context" when the context is sent to the daemon.
|
||||
|
||||
You can specify a repository and tag at which to save the new image if the
|
||||
build succeeds:
|
||||
|
||||
``sudo docker build -t shykes/myapp .``
|
||||
|
||||
Docker will run your steps one-by-one, committing the result if necessary,
|
||||
before finally outputting the ID of your new image.
|
||||
The Docker daemon will run your steps one-by-one, committing the
|
||||
result if necessary, before finally outputting the ID of your new
|
||||
image. The Docker daemon will automatically clean up the context you
|
||||
sent.
|
||||
|
||||
When you're done with your build, you're ready to look into :ref:`image_push`.
|
||||
When you're done with your build, you're ready to look into
|
||||
:ref:`image_push`.
|
||||
|
||||
.. _dockerfile_format:
|
||||
|
||||
2. Format
|
||||
=========
|
||||
@@ -63,12 +75,16 @@ allows statements like:
|
||||
# Comment
|
||||
RUN echo 'we are running some # of cool things'
|
||||
|
||||
.. _dockerfile_instructions:
|
||||
|
||||
3. Instructions
|
||||
===============
|
||||
|
||||
Here is the set of instructions you can use in a ``Dockerfile`` for
|
||||
building images.
|
||||
|
||||
.. _dockerfile_from:
|
||||
|
||||
3.1 FROM
|
||||
--------
|
||||
|
||||
@@ -94,6 +110,8 @@ output by the commit before each new ``FROM`` command.
|
||||
If no ``tag`` is given to the ``FROM`` instruction, ``latest`` is
|
||||
assumed. If the used tag does not exist, an error will be returned.
|
||||
|
||||
.. _dockerfile_maintainer:
|
||||
|
||||
3.2 MAINTAINER
|
||||
--------------
|
||||
|
||||
@@ -102,6 +120,8 @@ assumed. If the used tag does not exist, an error will be returned.
|
||||
The ``MAINTAINER`` instruction allows you to set the *Author* field of
|
||||
the generated images.
|
||||
|
||||
.. _dockerfile_run:
|
||||
|
||||
3.3 RUN
|
||||
-------
|
||||
|
||||
@@ -124,7 +144,7 @@ Known Issues (RUN)
|
||||
``rm`` a file, for example. The issue describes a workaround.
|
||||
* :issue:`2424` Locale will not be set automatically.
|
||||
|
||||
|
||||
.. _dockerfile_cmd:
|
||||
|
||||
3.4 CMD
|
||||
-------
|
||||
@@ -169,7 +189,7 @@ array:
|
||||
|
||||
If you would like your container to run the same executable every
|
||||
time, then you should consider using ``ENTRYPOINT`` in combination
|
||||
with ``CMD``. See :ref:`entrypoint_def`.
|
||||
with ``CMD``. See :ref:`dockerfile_entrypoint`.
|
||||
|
||||
If the user specifies arguments to ``docker run`` then they will
|
||||
override the default specified in CMD.
|
||||
@@ -179,6 +199,8 @@ override the default specified in CMD.
|
||||
command and commits the result; ``CMD`` does not execute anything at
|
||||
build time, but specifies the intended command for the image.
|
||||
|
||||
.. _dockerfile_expose:
|
||||
|
||||
3.5 EXPOSE
|
||||
----------
|
||||
|
||||
@@ -189,6 +211,8 @@ functionally equivalent to running ``docker commit -run '{"PortSpecs":
|
||||
["<port>", "<port2>"]}'`` outside the builder. Refer to
|
||||
:ref:`port_redirection` for detailed information.
|
||||
|
||||
.. _dockerfile_env:
|
||||
|
||||
3.6 ENV
|
||||
-------
|
||||
|
||||
@@ -203,6 +227,8 @@ with ``<key>=<value>``
|
||||
The environment variables will persist when a container is run
|
||||
from the resulting image.
|
||||
|
||||
.. _dockerfile_add:
|
||||
|
||||
3.7 ADD
|
||||
-------
|
||||
|
||||
@@ -263,7 +289,7 @@ The copy obeys the following rules:
|
||||
* If ``<dest>`` doesn't exist, it is created along with all missing
|
||||
directories in its path.
|
||||
|
||||
.. _entrypoint_def:
|
||||
.. _dockerfile_entrypoint:
|
||||
|
||||
3.8 ENTRYPOINT
|
||||
--------------
|
||||
@@ -312,14 +338,18 @@ this optional but default, you could use a CMD:
|
||||
CMD ["-l", "-"]
|
||||
ENTRYPOINT ["/usr/bin/wc"]
|
||||
|
||||
.. _dockerfile_volume:
|
||||
|
||||
3.9 VOLUME
|
||||
----------
|
||||
|
||||
``VOLUME ["/data"]``
|
||||
|
||||
The ``VOLUME`` instruction will add one or more new volumes to any
|
||||
container created from the image.
|
||||
The ``VOLUME`` instruction will create a mount point with the specified name and mark it
|
||||
as holding externally mounted volumes from native host or other containers. For more information/examples
|
||||
and mounting instructions via docker client, refer to :ref:`volume_def` documentation.
|
||||
|
||||
.. _dockerfile_user:
|
||||
|
||||
3.10 USER
|
||||
---------
|
||||
@@ -329,6 +359,8 @@ container created from the image.
|
||||
The ``USER`` instruction sets the username or UID to use when running
|
||||
the image.
|
||||
|
||||
.. _dockerfile_workdir:
|
||||
|
||||
3.11 WORKDIR
|
||||
------------
|
||||
|
||||
@@ -337,6 +369,7 @@ the image.
|
||||
The ``WORKDIR`` instruction sets the working directory in which
|
||||
the command given by ``CMD`` is executed.
|
||||
|
||||
.. _dockerfile_examples:
|
||||
|
||||
4. Dockerfile Examples
|
||||
======================
|
||||
|
||||
@@ -29,14 +29,32 @@ Here are a few sample scripts for systemd and upstart to integrate with docker.
|
||||
Sample Upstart Script
|
||||
---------------------
|
||||
|
||||
In this example we've already created a container to run Redis with an id of
|
||||
0a7e070b698b. To create an upstart script for our container, we create a file
|
||||
named ``/etc/init/redis.conf`` and place the following into it:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
description "Redis container"
|
||||
author "Me"
|
||||
start on filesystem and started lxc-net and started docker
|
||||
start on filesystem and started docker
|
||||
stop on runlevel [!2345]
|
||||
respawn
|
||||
exec docker start -a 0a7e070b698b
|
||||
script
|
||||
# Wait for docker to finish starting up first.
|
||||
FILE=/var/run/docker.sock
|
||||
while [ ! -e $FILE ] ; do
|
||||
inotifywait -t 2 -e create $(dirname $FILE)
|
||||
done
|
||||
/usr/bin/docker start -a 0a7e070b698b
|
||||
end script
|
||||
|
||||
Next, we have to configure docker so that it's run with the option ``-r=false``.
|
||||
Run the following command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo sh -c "echo 'DOCKER_OPTS=\"-r=false\"' > /etc/default/docker"
|
||||
|
||||
|
||||
Sample systemd Script
|
||||
|
||||
@@ -30,44 +30,60 @@ Each container can have zero or more data volumes.
|
||||
Getting Started
|
||||
...............
|
||||
|
||||
|
||||
|
||||
Using data volumes is as simple as adding a new flag: ``-v``. The parameter ``-v`` can be used more than once in order to create more volumes within the new container. The example below shows the instruction to create a container with two new volumes::
|
||||
Using data volumes is as simple as adding a new flag: ``-v``. The
|
||||
parameter ``-v`` can be used more than once in order to create more
|
||||
volumes within the new container. The example below shows the
|
||||
instruction to create a container with two new volumes::
|
||||
|
||||
docker run -v /var/volume1 -v /var/volume2 shykes/couchdb
|
||||
|
||||
For a Dockerfile, the VOLUME instruction will add one or more new volumes to any container created from the image::
|
||||
For a Dockerfile, the VOLUME instruction will add one or more new
|
||||
volumes to any container created from the image::
|
||||
|
||||
VOLUME ["/var/volume1", "/var/volume2"]
|
||||
|
||||
|
||||
Create a new container using existing volumes from an existing container:
|
||||
---------------------------------------------------------------------------
|
||||
Mount Volumes from an Existing Container:
|
||||
-----------------------------------------
|
||||
|
||||
|
||||
The command below creates a new container which is runnning as daemon ``-d`` and with one volume ``/var/lib/couchdb``::
|
||||
The command below creates a new container which is runnning as daemon
|
||||
``-d`` and with one volume ``/var/lib/couchdb``::
|
||||
|
||||
COUCH1=$(sudo docker run -d -v /var/lib/couchdb shykes/couchdb:2013-05-03)
|
||||
|
||||
From the container id of that previous container ``$COUCH1`` it's possible to create new container sharing the same volume using the parameter ``-volumes-from container_id``::
|
||||
From the container id of that previous container ``$COUCH1`` it's
|
||||
possible to create new container sharing the same volume using the
|
||||
parameter ``-volumes-from container_id``::
|
||||
|
||||
COUCH2=$(sudo docker run -d -volumes-from $COUCH1 shykes/couchdb:2013-05-03)
|
||||
|
||||
Now, the second container has the all the information from the first volume.
|
||||
|
||||
|
||||
Create a new container which mounts a host directory into it:
|
||||
-------------------------------------------------------------
|
||||
Mount a Host Directory as a Container Volume:
|
||||
---------------------------------------------
|
||||
|
||||
::
|
||||
|
||||
-v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro].
|
||||
If "host-dir" is missing, then docker creates a new volume.
|
||||
|
||||
This is not available for a Dockerfile due the portability and sharing purpose of it. The [host-dir] volumes is something 100% host dependent and will break on any other machine.
|
||||
This is not available for a Dockerfile due the portability and sharing
|
||||
purpose of it. The [host-dir] volumes is something 100% host dependent
|
||||
and will break on any other machine.
|
||||
|
||||
For example::
|
||||
|
||||
sudo docker run -v /var/logs:/var/host_logs:ro shykes/couchdb:2013-05-03
|
||||
|
||||
The command above mounts the host directory ``/var/logs`` into the container with read only permissions as ``/var/host_logs``.
|
||||
The command above mounts the host directory ``/var/logs`` into the
|
||||
container with read only permissions as ``/var/host_logs``.
|
||||
|
||||
.. versionadded:: v0.5.0
|
||||
|
||||
Known Issues
|
||||
............
|
||||
|
||||
* :issue:`2702`: "lxc-start: Permission denied - failed to mount"
|
||||
could indicate a permissions problem with AppArmor. Please see the
|
||||
issue for a workaround.
|
||||
|
||||
@@ -177,6 +177,15 @@ you can push and pull it like any other repository, but it will
|
||||
there will be no user name checking performed. Your registry will
|
||||
function completely independently from the Central Index.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="640" height="360"
|
||||
src="//www.youtube.com/embed/CAewZCBT4PI?rel=0" frameborder="0"
|
||||
allowfullscreen></iframe>
|
||||
|
||||
.. seealso:: `Docker Blog: How to use your own registry
|
||||
<http://blog.docker.io/2013/07/how-to-use-your-own-registry/>`_
|
||||
|
||||
Authentication file
|
||||
-------------------
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -35,7 +35,7 @@
|
||||
%}
|
||||
|
||||
{#
|
||||
This part is hopefully complex because things like |cut '/index/' are not available in spinx jinja
|
||||
This part is hopefully complex because things like |cut '/index/' are not available in Sphinx jinja
|
||||
and will make it crash. (and we need index/ out.
|
||||
#}
|
||||
<link rel="canonical" href="http://docs.docker.io/en/latest/
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
if err := Register("dummy1", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := Register("dummy1", nil); err == nil {
|
||||
t.Fatalf("Expecting error, got none")
|
||||
}
|
||||
|
||||
eng := newTestEngine(t)
|
||||
|
||||
//Should fail because globan handlers are copied
|
||||
//at the engine creation
|
||||
if err := eng.Register("dummy1", nil); err == nil {
|
||||
t.Fatalf("Expecting error, got none")
|
||||
}
|
||||
|
||||
if err := eng.Register("dummy2", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := eng.Register("dummy2", nil); err == nil {
|
||||
t.Fatalf("Expecting error, got none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJob(t *testing.T) {
|
||||
eng := newTestEngine(t)
|
||||
job1 := eng.Job("dummy1", "--level=awesome")
|
||||
|
||||
if job1.handler != nil {
|
||||
t.Fatalf("job1.handler should be empty")
|
||||
}
|
||||
|
||||
h := func(j *Job) string {
|
||||
return j.Name
|
||||
}
|
||||
|
||||
eng.Register("dummy2", h)
|
||||
job2 := eng.Job("dummy2", "--level=awesome")
|
||||
|
||||
if job2.handler == nil {
|
||||
t.Fatalf("job2.handler shouldn't be nil")
|
||||
}
|
||||
|
||||
if job2.handler(job2) != job2.Name {
|
||||
t.Fatalf("handler dummy2 was not found in job2")
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,101 @@ func TestSetenv(t *testing.T) {
|
||||
if val := job.Getenv("foo"); val != "bar" {
|
||||
t.Fatalf("Getenv returns incorrect value: %s", val)
|
||||
}
|
||||
|
||||
job.Setenv("bar", "")
|
||||
if val := job.Getenv("bar"); val != "" {
|
||||
t.Fatalf("Getenv returns incorrect value: %s", val)
|
||||
}
|
||||
if val := job.Getenv("nonexistent"); val != "" {
|
||||
t.Fatalf("Getenv returns incorrect value: %s", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetenvBool(t *testing.T) {
|
||||
job := mkJob(t, "dummy")
|
||||
job.SetenvBool("foo", true)
|
||||
if val := job.GetenvBool("foo"); !val {
|
||||
t.Fatalf("GetenvBool returns incorrect value: %b", val)
|
||||
}
|
||||
|
||||
job.SetenvBool("bar", false)
|
||||
if val := job.GetenvBool("bar"); val {
|
||||
t.Fatalf("GetenvBool returns incorrect value: %b", val)
|
||||
}
|
||||
|
||||
if val := job.GetenvBool("nonexistent"); val {
|
||||
t.Fatalf("GetenvBool returns incorrect value: %b", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetenvInt(t *testing.T) {
|
||||
job := mkJob(t, "dummy")
|
||||
|
||||
job.SetenvInt("foo", -42)
|
||||
if val := job.GetenvInt("foo"); val != -42 {
|
||||
t.Fatalf("GetenvInt returns incorrect value: %d", val)
|
||||
}
|
||||
|
||||
job.SetenvInt("bar", 42)
|
||||
if val := job.GetenvInt("bar"); val != 42 {
|
||||
t.Fatalf("GetenvInt returns incorrect value: %d", val)
|
||||
}
|
||||
if val := job.GetenvInt("nonexistent"); val != -1 {
|
||||
t.Fatalf("GetenvInt returns incorrect value: %d", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetenvList(t *testing.T) {
|
||||
job := mkJob(t, "dummy")
|
||||
|
||||
job.SetenvList("foo", []string{"bar"})
|
||||
if val := job.GetenvList("foo"); len(val) != 1 || val[0] != "bar" {
|
||||
t.Fatalf("GetenvList returns incorrect value: %v", val)
|
||||
}
|
||||
|
||||
job.SetenvList("bar", nil)
|
||||
if val := job.GetenvList("bar"); val != nil {
|
||||
t.Fatalf("GetenvList returns incorrect value: %v", val)
|
||||
}
|
||||
if val := job.GetenvList("nonexistent"); val != nil {
|
||||
t.Fatalf("GetenvList returns incorrect value: %v", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportEnv(t *testing.T) {
|
||||
type dummy struct {
|
||||
DummyInt int
|
||||
DummyStringArray []string
|
||||
}
|
||||
|
||||
job := mkJob(t, "dummy")
|
||||
if err := job.ImportEnv(&dummy{42, []string{"foo", "bar"}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dmy := dummy{}
|
||||
if err := job.ExportEnv(&dmy); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if dmy.DummyInt != 42 {
|
||||
t.Fatalf("Expected 42, got %d", dmy.DummyInt)
|
||||
}
|
||||
|
||||
if len(dmy.DummyStringArray) != 2 || dmy.DummyStringArray[0] != "foo" || dmy.DummyStringArray[1] != "bar" {
|
||||
t.Fatalf("Expected {foo, bar}, got %v", dmy.DummyStringArray)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestEnviron(t *testing.T) {
|
||||
job := mkJob(t, "dummy")
|
||||
job.Setenv("foo", "bar")
|
||||
val, exists := job.Environ()["foo"]
|
||||
if !exists {
|
||||
t.Fatalf("foo not found in the environ")
|
||||
}
|
||||
if val != "bar" {
|
||||
t.Fatalf("bar not found in the environ")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,6 @@ import (
|
||||
|
||||
var globalTestID string
|
||||
|
||||
func init() {
|
||||
Register("dummy", func(job *Job) string { return "" })
|
||||
}
|
||||
|
||||
func newTestEngine(t *testing.T) *Engine {
|
||||
// Use the caller function name as a prefix.
|
||||
// This helps trace temp directories back to their test.
|
||||
+10
-2
@@ -205,8 +205,12 @@ func (job *Job) SetenvInt(key string, value int64) {
|
||||
job.Setenv(key, fmt.Sprintf("%d", value))
|
||||
}
|
||||
|
||||
// Returns nil if key not found
|
||||
func (job *Job) GetenvList(key string) []string {
|
||||
sval := job.Getenv(key)
|
||||
if sval == "" {
|
||||
return nil
|
||||
}
|
||||
l := make([]string, 0, 1)
|
||||
if err := json.Unmarshal([]byte(sval), &l); err != nil {
|
||||
l = append(l, sval)
|
||||
@@ -214,7 +218,7 @@ func (job *Job) GetenvList(key string) []string {
|
||||
return l
|
||||
}
|
||||
|
||||
func (job *Job) SetenvList(key string, value []string) error {
|
||||
func (job *Job) SetenvJson(key string, value interface{}) error {
|
||||
sval, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -223,6 +227,10 @@ func (job *Job) SetenvList(key string, value []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (job *Job) SetenvList(key string, value []string) error {
|
||||
return job.SetenvJson(key, value)
|
||||
}
|
||||
|
||||
func (job *Job) Setenv(key, value string) {
|
||||
job.env = append(job.env, key+"="+value)
|
||||
}
|
||||
@@ -230,7 +238,7 @@ func (job *Job) Setenv(key, value string) {
|
||||
// DecodeEnv decodes `src` as a json dictionary, and adds
|
||||
// each decoded key-value pair to the environment.
|
||||
//
|
||||
// If `text` cannot be decoded as a json dictionary, an error
|
||||
// If `src` cannot be decoded as a json dictionary, an error
|
||||
// is returned.
|
||||
func (job *Job) DecodeEnv(src io.Reader) error {
|
||||
m := make(map[string]interface{})
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -51,7 +52,9 @@ func (graph *Graph) restore() error {
|
||||
}
|
||||
for _, v := range dir {
|
||||
id := v.Name()
|
||||
graph.idIndex.Add(id)
|
||||
if graph.driver.Exists(id) {
|
||||
graph.idIndex.Add(id)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -109,7 +112,7 @@ func (graph *Graph) Create(layerData archive.Archive, container *Container, comm
|
||||
img := &Image{
|
||||
ID: GenerateID(),
|
||||
Comment: comment,
|
||||
Created: time.Now(),
|
||||
Created: time.Now().UTC(),
|
||||
DockerVersion: VERSION,
|
||||
Author: author,
|
||||
Config: config,
|
||||
@@ -128,7 +131,15 @@ func (graph *Graph) Create(layerData archive.Archive, container *Container, comm
|
||||
|
||||
// Register imports a pre-existing image into the graph.
|
||||
// FIXME: pass img as first argument
|
||||
func (graph *Graph) Register(jsonData []byte, layerData archive.Archive, img *Image) error {
|
||||
func (graph *Graph) Register(jsonData []byte, layerData archive.Archive, img *Image) (err error) {
|
||||
defer func() {
|
||||
// If any error occurs, remove the new dir from the driver.
|
||||
// Don't check for errors since the dir might not have been created.
|
||||
// FIXME: this leaves a possible race condition.
|
||||
if err != nil {
|
||||
graph.driver.Remove(img.ID)
|
||||
}
|
||||
}()
|
||||
if err := ValidateID(img.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -136,6 +147,20 @@ func (graph *Graph) Register(jsonData []byte, layerData archive.Archive, img *Im
|
||||
if graph.Exists(img.ID) {
|
||||
return fmt.Errorf("Image %s already exists", img.ID)
|
||||
}
|
||||
|
||||
// Ensure that the image root does not exist on the filesystem
|
||||
// when it is not registered in the graph.
|
||||
// This is common when you switch from one graph driver to another
|
||||
if err := os.RemoveAll(graph.imageRoot(img.ID)); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the driver has this ID but the graph doesn't, remove it from the driver to start fresh.
|
||||
// (the graph is the source of truth).
|
||||
// Ignore errors, since we don't know if the driver correctly returns ErrNotExist.
|
||||
// (FIXME: make that mandatory for drivers).
|
||||
graph.driver.Remove(img.ID)
|
||||
|
||||
tmp, err := graph.Mktemp("")
|
||||
defer os.RemoveAll(tmp)
|
||||
if err != nil {
|
||||
@@ -176,7 +201,7 @@ func (graph *Graph) TempLayerArchive(id string, compression archive.Compression,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a, err := image.TarLayer(compression)
|
||||
a, err := image.TarLayer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -212,6 +237,13 @@ func setupInitLayer(initLayer string) error {
|
||||
// "var/run": "dir",
|
||||
// "var/lock": "dir",
|
||||
} {
|
||||
parts := strings.Split(pth, "/")
|
||||
prev := "/"
|
||||
for _, p := range parts[1:] {
|
||||
prev = path.Join(prev, p)
|
||||
syscall.Unlink(path.Join(initLayer, prev))
|
||||
}
|
||||
|
||||
if _, err := os.Stat(path.Join(initLayer, pth)); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
switch typ {
|
||||
@@ -223,12 +255,11 @@ func setupInitLayer(initLayer string) error {
|
||||
if err := os.MkdirAll(path.Join(initLayer, path.Dir(pth)), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f, err := os.OpenFile(path.Join(initLayer, pth), os.O_CREATE, 0755); err != nil {
|
||||
f, err := os.OpenFile(path.Join(initLayer, pth), os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
f.Close()
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
@@ -346,3 +377,7 @@ func (graph *Graph) Heads() (map[string]*Image, error) {
|
||||
func (graph *Graph) imageRoot(id string) string {
|
||||
return path.Join(graph.Root, id)
|
||||
}
|
||||
|
||||
func (graph *Graph) Driver() graphdriver.Driver {
|
||||
return graph.driver
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package gograph
|
||||
package graphdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
@@ -1,4 +1,4 @@
|
||||
package gograph
|
||||
package graphdb
|
||||
|
||||
import (
|
||||
_ "code.google.com/p/gosqlite/sqlite3"
|
||||
@@ -1,4 +1,4 @@
|
||||
package gograph
|
||||
package graphdb
|
||||
|
||||
import "sort"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package gograph
|
||||
package graphdb
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,4 +1,4 @@
|
||||
package gograph
|
||||
package graphdb
|
||||
|
||||
import (
|
||||
"path"
|
||||
@@ -37,7 +37,7 @@ func init() {
|
||||
graphdriver.Register("aufs", Init)
|
||||
}
|
||||
|
||||
type AufsDriver struct {
|
||||
type Driver struct {
|
||||
root string
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func Init(root string) (graphdriver.Driver, error) {
|
||||
// If not populate the dir structure
|
||||
if err := os.MkdirAll(root, 0755); err != nil {
|
||||
if os.IsExist(err) {
|
||||
return &AufsDriver{root}, nil
|
||||
return &Driver{root}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -69,7 +69,7 @@ func Init(root string) (graphdriver.Driver, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &AufsDriver{root}, nil
|
||||
return &Driver{root}, nil
|
||||
}
|
||||
|
||||
// Return a nil error if the kernel supports aufs
|
||||
@@ -95,17 +95,34 @@ func supportsAufs() error {
|
||||
return fmt.Errorf("AUFS was not found in /proc/filesystems")
|
||||
}
|
||||
|
||||
func (a *AufsDriver) rootPath() string {
|
||||
func (a Driver) rootPath() string {
|
||||
return a.root
|
||||
}
|
||||
|
||||
func (a *AufsDriver) String() string {
|
||||
func (Driver) String() string {
|
||||
return "aufs"
|
||||
}
|
||||
|
||||
func (a Driver) Status() [][2]string {
|
||||
ids, _ := loadIds(path.Join(a.rootPath(), "layers"))
|
||||
return [][2]string{
|
||||
{"Root Dir", a.rootPath()},
|
||||
{"Dirs", fmt.Sprintf("%d", len(ids))},
|
||||
}
|
||||
}
|
||||
|
||||
// Exists returns true if the given id is registered with
|
||||
// this driver
|
||||
func (a Driver) Exists(id string) bool {
|
||||
if _, err := os.Lstat(path.Join(a.rootPath(), "layers", id)); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Three folders are created for each id
|
||||
// mnt, layers, and diff
|
||||
func (a *AufsDriver) Create(id, parent string) error {
|
||||
func (a *Driver) Create(id, parent string) error {
|
||||
if err := a.createDirsFor(id); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -134,7 +151,7 @@ func (a *AufsDriver) Create(id, parent string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AufsDriver) createDirsFor(id string) error {
|
||||
func (a *Driver) createDirsFor(id string) error {
|
||||
paths := []string{
|
||||
"mnt",
|
||||
"diff",
|
||||
@@ -149,7 +166,7 @@ func (a *AufsDriver) createDirsFor(id string) error {
|
||||
}
|
||||
|
||||
// Unmount and remove the dir information
|
||||
func (a *AufsDriver) Remove(id string) error {
|
||||
func (a *Driver) Remove(id string) error {
|
||||
// Make sure the dir is umounted first
|
||||
if err := a.unmount(id); err != nil {
|
||||
return err
|
||||
@@ -169,19 +186,22 @@ func (a *AufsDriver) Remove(id string) error {
|
||||
return err
|
||||
}
|
||||
realPath := path.Join(a.rootPath(), p, id)
|
||||
if err := os.Rename(realPath, tmp); err != nil {
|
||||
if err := os.Rename(realPath, tmp); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
}
|
||||
|
||||
// Remove the layers file for the id
|
||||
return os.Remove(path.Join(a.rootPath(), "layers", id))
|
||||
if err := os.Remove(path.Join(a.rootPath(), "layers", id)); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return the rootfs path for the id
|
||||
// This will mount the dir at it's given path
|
||||
func (a *AufsDriver) Get(id string) (string, error) {
|
||||
func (a *Driver) Get(id string) (string, error) {
|
||||
ids, err := getParentIds(a.rootPath(), id)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
@@ -203,23 +223,23 @@ func (a *AufsDriver) Get(id string) (string, error) {
|
||||
}
|
||||
|
||||
// Returns an archive of the contents for the id
|
||||
func (a *AufsDriver) Diff(id string) (archive.Archive, error) {
|
||||
func (a *Driver) Diff(id string) (archive.Archive, error) {
|
||||
return archive.TarFilter(path.Join(a.rootPath(), "diff", id), &archive.TarOptions{
|
||||
Recursive: true,
|
||||
Compression: archive.Uncompressed,
|
||||
})
|
||||
}
|
||||
|
||||
func (a *AufsDriver) ApplyDiff(id string, diff archive.Archive) error {
|
||||
func (a *Driver) ApplyDiff(id string, diff archive.Archive) error {
|
||||
return archive.Untar(diff, path.Join(a.rootPath(), "diff", id), nil)
|
||||
}
|
||||
|
||||
// Returns the size of the contents for the id
|
||||
func (a *AufsDriver) Size(id string) (int64, error) {
|
||||
func (a *Driver) DiffSize(id string) (int64, error) {
|
||||
return utils.TreeSize(path.Join(a.rootPath(), "diff", id))
|
||||
}
|
||||
|
||||
func (a *AufsDriver) Changes(id string) ([]archive.Change, error) {
|
||||
func (a *Driver) Changes(id string) ([]archive.Change, error) {
|
||||
layers, err := a.getParentLayerPaths(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -227,7 +247,7 @@ func (a *AufsDriver) Changes(id string) ([]archive.Change, error) {
|
||||
return archive.Changes(layers, path.Join(a.rootPath(), "diff", id))
|
||||
}
|
||||
|
||||
func (a *AufsDriver) getParentLayerPaths(id string) ([]string, error) {
|
||||
func (a *Driver) getParentLayerPaths(id string) ([]string, error) {
|
||||
parentIds, err := getParentIds(a.rootPath(), id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -244,7 +264,7 @@ func (a *AufsDriver) getParentLayerPaths(id string) ([]string, error) {
|
||||
return layers, nil
|
||||
}
|
||||
|
||||
func (a *AufsDriver) mount(id string) error {
|
||||
func (a *Driver) mount(id string) error {
|
||||
// If the id is mounted or we get an error return
|
||||
if mounted, err := a.mounted(id); err != nil || mounted {
|
||||
return err
|
||||
@@ -266,7 +286,7 @@ func (a *AufsDriver) mount(id string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AufsDriver) unmount(id string) error {
|
||||
func (a *Driver) unmount(id string) error {
|
||||
if mounted, err := a.mounted(id); err != nil || !mounted {
|
||||
return err
|
||||
}
|
||||
@@ -274,26 +294,26 @@ func (a *AufsDriver) unmount(id string) error {
|
||||
return Unmount(target)
|
||||
}
|
||||
|
||||
func (a *AufsDriver) mounted(id string) (bool, error) {
|
||||
func (a *Driver) mounted(id string) (bool, error) {
|
||||
target := path.Join(a.rootPath(), "mnt", id)
|
||||
return Mounted(target)
|
||||
}
|
||||
|
||||
// During cleanup aufs needs to unmount all mountpoints
|
||||
func (a *AufsDriver) Cleanup() error {
|
||||
func (a *Driver) Cleanup() error {
|
||||
ids, err := loadIds(path.Join(a.rootPath(), "layers"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, id := range ids {
|
||||
if err := a.unmount(id); err != nil {
|
||||
return err
|
||||
utils.Errorf("Unmounting %s: %s", utils.TruncateID(id), err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AufsDriver) aufsMount(ro []string, rw, target string) error {
|
||||
func (a *Driver) aufsMount(ro []string, rw, target string) error {
|
||||
rwBranch := fmt.Sprintf("%v=rw", rw)
|
||||
roBranches := ""
|
||||
for _, layer := range ro {
|
||||
@@ -11,7 +11,7 @@ var (
|
||||
tmp = path.Join(os.TempDir(), "aufs-tests", "aufs")
|
||||
)
|
||||
|
||||
func newDriver(t *testing.T) *AufsDriver {
|
||||
func newDriver(t *testing.T) *Driver {
|
||||
if err := os.MkdirAll(tmp, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -20,10 +20,10 @@ func newDriver(t *testing.T) *AufsDriver {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return d.(*AufsDriver)
|
||||
return d.(*Driver)
|
||||
}
|
||||
|
||||
func TestNewAufsDriver(t *testing.T) {
|
||||
func TestNewDriver(t *testing.T) {
|
||||
if err := os.MkdirAll(tmp, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -446,7 +446,9 @@ func TestDiffSize(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Truncate(size)
|
||||
if err := f.Truncate(size); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s, err := f.Stat()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -456,7 +458,7 @@ func TestDiffSize(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
diffSize, err := d.Size("1")
|
||||
diffSize, err := d.DiffSize("1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -465,6 +467,108 @@ func TestDiffSize(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestChildDiffSize(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
defer d.Cleanup()
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
diffPath, err := d.Get("1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Add a file to the diff path with a fixed size
|
||||
size := int64(1024)
|
||||
|
||||
f, err := os.Create(path.Join(diffPath, "test_file"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := f.Truncate(size); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s, err := f.Stat()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
size = s.Size()
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
diffSize, err := d.DiffSize("1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diffSize != size {
|
||||
t.Fatalf("Expected size to be %d got %d", size, diffSize)
|
||||
}
|
||||
|
||||
if err := d.Create("2", "1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
diffSize, err = d.DiffSize("2")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// The diff size for the child should be zero
|
||||
if diffSize != 0 {
|
||||
t.Fatalf("Expected size to be %d got %d", 0, diffSize)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExists(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
defer d.Cleanup()
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if d.Exists("none") {
|
||||
t.Fatal("id name should not exist in the driver")
|
||||
}
|
||||
|
||||
if !d.Exists("1") {
|
||||
t.Fatal("id 1 should exist in the driver")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatus(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
defer d.Cleanup()
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
status := d.Status()
|
||||
if status == nil || len(status) == 0 {
|
||||
t.Fatal("Status should not be nil or empty")
|
||||
}
|
||||
rootDir := status[0]
|
||||
dirs := status[1]
|
||||
if rootDir[0] != "Root Dir" {
|
||||
t.Fatalf("Expected Root Dir got %s", rootDir[0])
|
||||
}
|
||||
if rootDir[1] != d.rootPath() {
|
||||
t.Fatalf("Expected %s got %s", d.rootPath(), rootDir[1])
|
||||
}
|
||||
if dirs[0] != "Dirs" {
|
||||
t.Fatalf("Expected Dirs got %s", dirs[0])
|
||||
}
|
||||
if dirs[1] != "1" {
|
||||
t.Fatalf("Expected 1 got %s", dirs[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyDiff(t *testing.T) {
|
||||
d := newDriver(t)
|
||||
defer os.RemoveAll(tmp)
|
||||
@@ -486,7 +590,9 @@ func TestApplyDiff(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Truncate(size)
|
||||
if err := f.Truncate(size); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
diff, err := d.Diff("1")
|
||||
@@ -0,0 +1,194 @@
|
||||
package aufs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
type metadata struct {
|
||||
ID string `json:"id"`
|
||||
ParentID string `json:"parent,omitempty"`
|
||||
Image string `json:"Image,omitempty"`
|
||||
|
||||
parent *metadata
|
||||
}
|
||||
|
||||
func pathExists(pth string) bool {
|
||||
if _, err := os.Stat(pth); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Migrate existing images and containers from docker < 0.7.x
|
||||
//
|
||||
// The format pre 0.7 is for docker to store the metadata and filesystem
|
||||
// content in the same directory. For the migration to work we need to move Image layer
|
||||
// data from /var/lib/docker/graph/<id>/layers to the diff of the registered id.
|
||||
//
|
||||
// Next we need to migrate the container's rw layer to diff of the driver. After the
|
||||
// contents are migrated we need to register the image and container ids with the
|
||||
// driver.
|
||||
//
|
||||
// For the migration we try to move the folder containing the layer files, if that
|
||||
// fails because the data is currently mounted we will fallback to creating a
|
||||
// symlink.
|
||||
func (a *Driver) Migrate(pth string, setupInit func(p string) error) error {
|
||||
if pathExists(path.Join(pth, "graph")) {
|
||||
if err := a.migrateRepositories(pth); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.migrateImages(path.Join(pth, "graph")); err != nil {
|
||||
return err
|
||||
}
|
||||
return a.migrateContainers(path.Join(pth, "containers"), setupInit)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Driver) migrateRepositories(pth string) error {
|
||||
name := path.Join(pth, "repositories")
|
||||
if err := os.Rename(name, name+"-aufs"); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Driver) migrateContainers(pth string, setupInit func(p string) error) error {
|
||||
fis, err := ioutil.ReadDir(pth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, fi := range fis {
|
||||
if id := fi.Name(); fi.IsDir() && pathExists(path.Join(pth, id, "rw")) {
|
||||
if err := tryRelocate(path.Join(pth, id, "rw"), path.Join(a.rootPath(), "diff", id)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !a.Exists(id) {
|
||||
|
||||
metadata, err := loadMetadata(path.Join(pth, id, "config.json"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
initID := fmt.Sprintf("%s-init", id)
|
||||
if err := a.Create(initID, metadata.Image); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
initPath, err := a.Get(initID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// setup init layer
|
||||
if err := setupInit(initPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := a.Create(id, initID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Driver) migrateImages(pth string) error {
|
||||
fis, err := ioutil.ReadDir(pth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
m = make(map[string]*metadata)
|
||||
current *metadata
|
||||
exists bool
|
||||
)
|
||||
|
||||
for _, fi := range fis {
|
||||
if id := fi.Name(); fi.IsDir() && pathExists(path.Join(pth, id, "layer")) {
|
||||
if current, exists = m[id]; !exists {
|
||||
current, err = loadMetadata(path.Join(pth, id, "json"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m[id] = current
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range m {
|
||||
v.parent = m[v.ParentID]
|
||||
}
|
||||
|
||||
migrated := make(map[string]bool)
|
||||
for _, v := range m {
|
||||
if err := a.migrateImage(v, pth, migrated); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Driver) migrateImage(m *metadata, pth string, migrated map[string]bool) error {
|
||||
if !migrated[m.ID] {
|
||||
if m.parent != nil {
|
||||
a.migrateImage(m.parent, pth, migrated)
|
||||
}
|
||||
if err := tryRelocate(path.Join(pth, m.ID, "layer"), path.Join(a.rootPath(), "diff", m.ID)); err != nil {
|
||||
return err
|
||||
}
|
||||
if !a.Exists(m.ID) {
|
||||
if err := a.Create(m.ID, m.ParentID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
migrated[m.ID] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// tryRelocate will try to rename the old path to the new pack and if
|
||||
// the operation fails, it will fallback to a symlink
|
||||
func tryRelocate(oldPath, newPath string) error {
|
||||
s, err := os.Lstat(newPath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
// If the destination is a symlink then we already tried to relocate once before
|
||||
// and it failed so we delete it and try to remove
|
||||
if s != nil && s.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
if err := os.RemoveAll(newPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := os.Rename(oldPath, newPath); err != nil {
|
||||
if sErr := os.Symlink(oldPath, newPath); sErr != nil {
|
||||
return fmt.Errorf("Unable to relocate %s to %s: Rename err %s Symlink err %s", oldPath, newPath, err, sErr)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadMetadata(pth string) (*metadata, error) {
|
||||
f, err := os.Open(pth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var (
|
||||
out = &metadata{}
|
||||
dec = json.NewDecoder(f)
|
||||
)
|
||||
|
||||
if err := dec.Decode(out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@@ -6,13 +6,10 @@ import (
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -57,6 +54,16 @@ type Status struct {
|
||||
MetadataLoopback string
|
||||
Data DiskUsage
|
||||
Metadata DiskUsage
|
||||
SectorSize uint64
|
||||
}
|
||||
|
||||
type DevStatus struct {
|
||||
DeviceId int
|
||||
Size uint64
|
||||
TransactionId uint64
|
||||
SizeInSectors uint64
|
||||
MappedSectors uint64
|
||||
HighestMappedSector uint64
|
||||
}
|
||||
|
||||
func getDevName(name string) string {
|
||||
@@ -95,7 +102,7 @@ func (devices *DeviceSet) hasImage(name string) bool {
|
||||
dirname := devices.loopbackDir()
|
||||
filename := path.Join(dirname, name)
|
||||
|
||||
_, err := os.Stat(filename)
|
||||
_, err := osStat(filename)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
@@ -107,16 +114,16 @@ func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) {
|
||||
dirname := devices.loopbackDir()
|
||||
filename := path.Join(dirname, name)
|
||||
|
||||
if err := os.MkdirAll(dirname, 0700); err != nil && !os.IsExist(err) {
|
||||
if err := osMkdirAll(dirname, 0700); err != nil && !osIsExist(err) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filename); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
if _, err := osStat(filename); err != nil {
|
||||
if !osIsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
utils.Debugf("Creating loopback file %s for device-manage use", filename)
|
||||
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600)
|
||||
file, err := osOpenFile(filename, osORdWr|osOCreate, 0600)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -164,7 +171,7 @@ func (devices *DeviceSet) saveMetadata() error {
|
||||
if err := tmpFile.Close(); err != nil {
|
||||
return fmt.Errorf("Error closing metadata file %s: %s", tmpFile.Name(), err)
|
||||
}
|
||||
if err := os.Rename(tmpFile.Name(), devices.jsonFile()); err != nil {
|
||||
if err := osRename(tmpFile.Name(), devices.jsonFile()); err != nil {
|
||||
return fmt.Errorf("Error committing metadata file", err)
|
||||
}
|
||||
|
||||
@@ -215,9 +222,9 @@ func (devices *DeviceSet) activateDeviceIfNeeded(hash string) error {
|
||||
func (devices *DeviceSet) createFilesystem(info *DevInfo) error {
|
||||
devname := info.DevName()
|
||||
|
||||
err := exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0,lazy_journal_init=0", devname).Run()
|
||||
err := execRun("mkfs.ext4", "-E", "discard,lazy_itable_init=0,lazy_journal_init=0", devname)
|
||||
if err != nil {
|
||||
err = exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0", devname).Run()
|
||||
err = execRun("mkfs.ext4", "-E", "discard,lazy_itable_init=0", devname)
|
||||
}
|
||||
if err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
@@ -242,7 +249,7 @@ func (devices *DeviceSet) loadMetaData() error {
|
||||
devices.NewTransactionId = devices.TransactionId
|
||||
|
||||
jsonData, err := ioutil.ReadFile(devices.jsonFile())
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
if err != nil && !osIsNotExist(err) {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
@@ -327,14 +334,13 @@ func (devices *DeviceSet) setupBaseImage() error {
|
||||
}
|
||||
|
||||
func setCloseOnExec(name string) {
|
||||
fileInfos, _ := ioutil.ReadDir("/proc/self/fd")
|
||||
if fileInfos != nil {
|
||||
if fileInfos, _ := ioutil.ReadDir("/proc/self/fd"); fileInfos != nil {
|
||||
for _, i := range fileInfos {
|
||||
link, _ := os.Readlink(filepath.Join("/proc/self/fd", i.Name()))
|
||||
link, _ := osReadlink(filepath.Join("/proc/self/fd", i.Name()))
|
||||
if link == name {
|
||||
fd, err := strconv.Atoi(i.Name())
|
||||
if err == nil {
|
||||
syscall.CloseOnExec(fd)
|
||||
sysCloseOnExec(fd)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -357,13 +363,90 @@ func minor(device uint64) uint64 {
|
||||
return (device & 0xff) | ((device >> 12) & 0xfff00)
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) initDevmapper() error {
|
||||
func (devices *DeviceSet) ResizePool(size int64) error {
|
||||
dirname := devices.loopbackDir()
|
||||
datafilename := path.Join(dirname, "data")
|
||||
metadatafilename := path.Join(dirname, "metadata")
|
||||
|
||||
datafile, err := osOpenFile(datafilename, osORdWr, 0)
|
||||
if datafile == nil {
|
||||
return err
|
||||
}
|
||||
defer datafile.Close()
|
||||
|
||||
fi, err := datafile.Stat()
|
||||
if fi == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fi.Size() > size {
|
||||
return fmt.Errorf("Can't shrink file")
|
||||
}
|
||||
|
||||
dataloopback := FindLoopDeviceFor(&osFile{File: datafile})
|
||||
if dataloopback == nil {
|
||||
return fmt.Errorf("Unable to find loopback mount for: %s", datafilename)
|
||||
}
|
||||
defer dataloopback.Close()
|
||||
|
||||
metadatafile, err := osOpenFile(metadatafilename, osORdWr, 0)
|
||||
if metadatafile == nil {
|
||||
return err
|
||||
}
|
||||
defer metadatafile.Close()
|
||||
|
||||
metadataloopback := FindLoopDeviceFor(&osFile{File: metadatafile})
|
||||
if metadataloopback == nil {
|
||||
return fmt.Errorf("Unable to find loopback mount for: %s", metadatafilename)
|
||||
}
|
||||
defer metadataloopback.Close()
|
||||
|
||||
// Grow loopback file
|
||||
if err := datafile.Truncate(size); err != nil {
|
||||
return fmt.Errorf("Unable to grow loopback file: %s", err)
|
||||
}
|
||||
|
||||
// Reload size for loopback device
|
||||
if err := LoopbackSetCapacity(dataloopback); err != nil {
|
||||
return fmt.Errorf("Unable to update loopback capacity: %s", err)
|
||||
}
|
||||
|
||||
// Suspend the pool
|
||||
if err := suspendDevice(devices.getPoolName()); err != nil {
|
||||
return fmt.Errorf("Unable to suspend pool: %s", err)
|
||||
}
|
||||
|
||||
// Reload with the new block sizes
|
||||
if err := reloadPool(devices.getPoolName(), dataloopback, metadataloopback); err != nil {
|
||||
return fmt.Errorf("Unable to reload pool: %s", err)
|
||||
}
|
||||
|
||||
// Resume the pool
|
||||
if err := resumeDevice(devices.getPoolName()); err != nil {
|
||||
return fmt.Errorf("Unable to resume pool: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) initDevmapper(doInit bool) error {
|
||||
logInit(devices)
|
||||
|
||||
// Make sure the sparse images exist in <root>/devicemapper/data and
|
||||
// <root>/devicemapper/metadata
|
||||
|
||||
createdLoopback := !devices.hasImage("data") || !devices.hasImage("metadata")
|
||||
hasData := devices.hasImage("data")
|
||||
hasMetadata := devices.hasImage("metadata")
|
||||
|
||||
if !doInit && !hasData {
|
||||
return fmt.Errorf("Looback data file not found %s")
|
||||
}
|
||||
|
||||
if !doInit && !hasMetadata {
|
||||
return fmt.Errorf("Looback metadata file not found %s")
|
||||
}
|
||||
|
||||
createdLoopback := !hasData || !hasMetadata
|
||||
data, err := devices.ensureImage("data", DefaultDataLoopbackSize)
|
||||
if err != nil {
|
||||
utils.Debugf("Error device ensureImage (data): %s\n", err)
|
||||
@@ -377,11 +460,11 @@ func (devices *DeviceSet) initDevmapper() error {
|
||||
|
||||
// Set the device prefix from the device id and inode of the docker root dir
|
||||
|
||||
st, err := os.Stat(devices.root)
|
||||
st, err := osStat(devices.root)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error looking up dir %s: %s", devices.root, err)
|
||||
}
|
||||
sysSt := st.Sys().(*syscall.Stat_t)
|
||||
sysSt := toSysStatT(st.Sys())
|
||||
// "reg-" stands for "regular file".
|
||||
// In the future we might use "dev-" for "device file", etc.
|
||||
// docker-maj,min[-inode] stands for:
|
||||
@@ -438,9 +521,11 @@ func (devices *DeviceSet) initDevmapper() error {
|
||||
}
|
||||
|
||||
// Setup the base image
|
||||
if err := devices.setupBaseImage(); err != nil {
|
||||
utils.Debugf("Error device setupBaseImage: %s\n", err)
|
||||
return err
|
||||
if doInit {
|
||||
if err := devices.setupBaseImage(); err != nil {
|
||||
utils.Debugf("Error device setupBaseImage: %s\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -565,10 +650,13 @@ func (devices *DeviceSet) waitRemove(hash string) error {
|
||||
// The error might actually be something else, but we can't differentiate.
|
||||
return nil
|
||||
}
|
||||
utils.Debugf("Waiting for removal of %s: exists=%d", devname, devinfo.Exists)
|
||||
if i%100 == 0 {
|
||||
utils.Debugf("Waiting for removal of %s: exists=%d", devname, devinfo.Exists)
|
||||
}
|
||||
if devinfo.Exists == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
if i == 1000 {
|
||||
@@ -591,7 +679,9 @@ func (devices *DeviceSet) waitClose(hash string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
utils.Debugf("Waiting for unmount of %s: opencount=%d", devname, devinfo.OpenCount)
|
||||
if i%100 == 0 {
|
||||
utils.Debugf("Waiting for unmount of %s: opencount=%d", devname, devinfo.OpenCount)
|
||||
}
|
||||
if devinfo.OpenCount == 0 {
|
||||
break
|
||||
}
|
||||
@@ -619,15 +709,16 @@ func (devices *DeviceSet) byHash(hash string) (devname string, err error) {
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) Shutdown() error {
|
||||
utils.Debugf("[deviceset %s] shutdown()", devices.devicePrefix)
|
||||
defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix)
|
||||
devices.Lock()
|
||||
utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root)
|
||||
defer devices.Unlock()
|
||||
|
||||
utils.Debugf("[deviceset %s] shutdown()", devices.devicePrefix)
|
||||
utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root)
|
||||
defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix)
|
||||
|
||||
for path, count := range devices.activeMounts {
|
||||
for i := count; i > 0; i-- {
|
||||
if err := syscall.Unmount(path, 0); err != nil {
|
||||
if err := sysUnmount(path, 0); err != nil {
|
||||
utils.Debugf("Shutdown unmounting %s, error: %s\n", path, err)
|
||||
}
|
||||
}
|
||||
@@ -663,15 +754,15 @@ func (devices *DeviceSet) MountDevice(hash, path string, readOnly bool) error {
|
||||
|
||||
info := devices.Devices[hash]
|
||||
|
||||
var flags uintptr = syscall.MS_MGC_VAL
|
||||
var flags uintptr = sysMsMgcVal
|
||||
|
||||
if readOnly {
|
||||
flags = flags | syscall.MS_RDONLY
|
||||
flags = flags | sysMsRdOnly
|
||||
}
|
||||
|
||||
err := syscall.Mount(info.DevName(), path, "ext4", flags, "discard")
|
||||
if err != nil && err == syscall.EINVAL {
|
||||
err = syscall.Mount(info.DevName(), path, "ext4", flags, "")
|
||||
err := sysMount(info.DevName(), path, "ext4", flags, "discard")
|
||||
if err != nil && err == sysEInval {
|
||||
err = sysMount(info.DevName(), path, "ext4", flags, "")
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err)
|
||||
@@ -690,7 +781,7 @@ func (devices *DeviceSet) UnmountDevice(hash, path string, deactivate bool) erro
|
||||
defer devices.Unlock()
|
||||
|
||||
utils.Debugf("[devmapper] Unmount(%s)", path)
|
||||
if err := syscall.Unmount(path, 0); err != nil {
|
||||
if err := sysUnmount(path, 0); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
@@ -757,6 +848,69 @@ func (devices *DeviceSet) setInitialized(hash string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) List() []string {
|
||||
devices.Lock()
|
||||
defer devices.Unlock()
|
||||
|
||||
ids := make([]string, len(devices.Devices))
|
||||
i := 0
|
||||
for k := range devices.Devices {
|
||||
ids[i] = k
|
||||
i++
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) deviceStatus(devName string) (sizeInSectors, mappedSectors, highestMappedSector uint64, err error) {
|
||||
var params string
|
||||
_, sizeInSectors, _, params, err = getStatus(devName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = fmt.Sscanf(params, "%d %d", &mappedSectors, &highestMappedSector); err == nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) GetDeviceStatus(hash string) (*DevStatus, error) {
|
||||
devices.Lock()
|
||||
defer devices.Unlock()
|
||||
|
||||
info := devices.Devices[hash]
|
||||
if info == nil {
|
||||
return nil, fmt.Errorf("No device %s", hash)
|
||||
}
|
||||
|
||||
status := &DevStatus{
|
||||
DeviceId: info.DeviceId,
|
||||
Size: info.Size,
|
||||
TransactionId: info.TransactionId,
|
||||
}
|
||||
|
||||
if err := devices.activateDeviceIfNeeded(hash); err != nil {
|
||||
return nil, fmt.Errorf("Error activating devmapper device for '%s': %s", hash, err)
|
||||
}
|
||||
|
||||
if sizeInSectors, mappedSectors, highestMappedSector, err := devices.deviceStatus(info.DevName()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
status.SizeInSectors = sizeInSectors
|
||||
status.MappedSectors = mappedSectors
|
||||
status.HighestMappedSector = highestMappedSector
|
||||
}
|
||||
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) poolStatus() (totalSizeInSectors, transactionId, dataUsed, dataTotal, metadataUsed, metadataTotal uint64, err error) {
|
||||
var params string
|
||||
if _, totalSizeInSectors, _, params, err = getStatus(devices.getPoolName()); err == nil {
|
||||
_, err = fmt.Sscanf(params, "%d %d/%d %d/%d", &transactionId, &metadataUsed, &metadataTotal, &dataUsed, &dataTotal)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) Status() *Status {
|
||||
devices.Lock()
|
||||
defer devices.Unlock()
|
||||
@@ -767,26 +921,25 @@ func (devices *DeviceSet) Status() *Status {
|
||||
status.DataLoopback = path.Join(devices.loopbackDir(), "data")
|
||||
status.MetadataLoopback = path.Join(devices.loopbackDir(), "metadata")
|
||||
|
||||
_, totalSizeInSectors, _, params, err := getStatus(devices.getPoolName())
|
||||
totalSizeInSectors, _, dataUsed, dataTotal, metadataUsed, metadataTotal, err := devices.poolStatus()
|
||||
if err == nil {
|
||||
var transactionId, dataUsed, dataTotal, metadataUsed, metadataTotal uint64
|
||||
if _, err := fmt.Sscanf(params, "%d %d/%d %d/%d", &transactionId, &metadataUsed, &metadataTotal, &dataUsed, &dataTotal); err == nil {
|
||||
// Convert from blocks to bytes
|
||||
blockSizeInSectors := totalSizeInSectors / dataTotal
|
||||
// Convert from blocks to bytes
|
||||
blockSizeInSectors := totalSizeInSectors / dataTotal
|
||||
|
||||
status.Data.Used = dataUsed * blockSizeInSectors * 512
|
||||
status.Data.Total = dataTotal * blockSizeInSectors * 512
|
||||
status.Data.Used = dataUsed * blockSizeInSectors * 512
|
||||
status.Data.Total = dataTotal * blockSizeInSectors * 512
|
||||
|
||||
// metadata blocks are always 4k
|
||||
status.Metadata.Used = metadataUsed * 4096
|
||||
status.Metadata.Total = metadataTotal * 4096
|
||||
}
|
||||
// metadata blocks are always 4k
|
||||
status.Metadata.Used = metadataUsed * 4096
|
||||
status.Metadata.Total = metadataTotal * 4096
|
||||
|
||||
status.SectorSize = blockSizeInSectors * 512
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
func NewDeviceSet(root string) (*DeviceSet, error) {
|
||||
func NewDeviceSet(root string, doInit bool) (*DeviceSet, error) {
|
||||
SetDevDir("/dev")
|
||||
|
||||
devices := &DeviceSet{
|
||||
@@ -795,7 +948,7 @@ func NewDeviceSet(root string) (*DeviceSet, error) {
|
||||
activeMounts: make(map[string]int),
|
||||
}
|
||||
|
||||
if err := devices.initDevmapper(); err != nil {
|
||||
if err := devices.initDevmapper(doInit); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
@@ -40,25 +39,26 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrTaskRun = errors.New("dm_task_run failed")
|
||||
ErrTaskSetName = errors.New("dm_task_set_name failed")
|
||||
ErrTaskSetMessage = errors.New("dm_task_set_message failed")
|
||||
ErrTaskSetAddNode = errors.New("dm_task_set_add_node failed")
|
||||
ErrTaskSetRo = errors.New("dm_task_set_ro failed")
|
||||
ErrTaskAddTarget = errors.New("dm_task_add_target failed")
|
||||
ErrTaskSetSector = errors.New("dm_task_set_sector failed")
|
||||
ErrTaskGetInfo = errors.New("dm_task_get_info failed")
|
||||
ErrTaskGetDriverVersion = errors.New("dm_task_get_driver_version failed")
|
||||
ErrTaskSetCookie = errors.New("dm_task_set_cookie failed")
|
||||
ErrNilCookie = errors.New("cookie ptr can't be nil")
|
||||
ErrAttachLoopbackDevice = errors.New("loopback mounting failed")
|
||||
ErrGetBlockSize = errors.New("Can't get block size")
|
||||
ErrUdevWait = errors.New("wait on udev cookie failed")
|
||||
ErrSetDevDir = errors.New("dm_set_dev_dir failed")
|
||||
ErrGetLibraryVersion = errors.New("dm_get_library_version failed")
|
||||
ErrCreateRemoveTask = errors.New("Can't create task of type DeviceRemove")
|
||||
ErrRunRemoveDevice = errors.New("running removeDevice failed")
|
||||
ErrInvalidAddNode = errors.New("Invalide AddNoce type")
|
||||
ErrTaskRun = errors.New("dm_task_run failed")
|
||||
ErrTaskSetName = errors.New("dm_task_set_name failed")
|
||||
ErrTaskSetMessage = errors.New("dm_task_set_message failed")
|
||||
ErrTaskSetAddNode = errors.New("dm_task_set_add_node failed")
|
||||
ErrTaskSetRo = errors.New("dm_task_set_ro failed")
|
||||
ErrTaskAddTarget = errors.New("dm_task_add_target failed")
|
||||
ErrTaskSetSector = errors.New("dm_task_set_sector failed")
|
||||
ErrTaskGetInfo = errors.New("dm_task_get_info failed")
|
||||
ErrTaskSetCookie = errors.New("dm_task_set_cookie failed")
|
||||
ErrNilCookie = errors.New("cookie ptr can't be nil")
|
||||
ErrAttachLoopbackDevice = errors.New("loopback mounting failed")
|
||||
ErrGetBlockSize = errors.New("Can't get block size")
|
||||
ErrUdevWait = errors.New("wait on udev cookie failed")
|
||||
ErrSetDevDir = errors.New("dm_set_dev_dir failed")
|
||||
ErrGetLibraryVersion = errors.New("dm_get_library_version failed")
|
||||
ErrCreateRemoveTask = errors.New("Can't create task of type DeviceRemove")
|
||||
ErrRunRemoveDevice = errors.New("running removeDevice failed")
|
||||
ErrInvalidAddNode = errors.New("Invalide AddNoce type")
|
||||
ErrGetLoopbackBackingFile = errors.New("Unable to get loopback backing file")
|
||||
ErrLoopbackSetCapacity = errors.New("Unable set loopback capacity")
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -83,7 +83,7 @@ type (
|
||||
|
||||
func (t *Task) destroy() {
|
||||
if t != nil {
|
||||
DmTaskDestory(t.unmanaged)
|
||||
DmTaskDestroy(t.unmanaged)
|
||||
runtime.SetFinalizer(t, nil)
|
||||
}
|
||||
}
|
||||
@@ -177,13 +177,61 @@ func (t *Task) GetNextTarget(next uintptr) (nextPtr uintptr, start uint64,
|
||||
start, length, targetType, params
|
||||
}
|
||||
|
||||
func AttachLoopDevice(filename string) (*os.File, error) {
|
||||
func AttachLoopDevice(filename string) (*osFile, error) {
|
||||
var fd int
|
||||
res := DmAttachLoopDevice(filename, &fd)
|
||||
if res == "" {
|
||||
return nil, ErrAttachLoopbackDevice
|
||||
}
|
||||
return os.NewFile(uintptr(fd), res), nil
|
||||
return &osFile{File: osNewFile(uintptr(fd), res)}, nil
|
||||
}
|
||||
|
||||
func getLoopbackBackingFile(file *osFile) (uint64, uint64, error) {
|
||||
dev, inode, err := DmGetLoopbackBackingFile(file.Fd())
|
||||
if err != 0 {
|
||||
return 0, 0, ErrGetLoopbackBackingFile
|
||||
}
|
||||
return dev, inode, nil
|
||||
}
|
||||
|
||||
func LoopbackSetCapacity(file *osFile) error {
|
||||
if err := DmLoopbackSetCapacity(file.Fd()); err != 0 {
|
||||
return ErrLoopbackSetCapacity
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func FindLoopDeviceFor(file *osFile) *osFile {
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
targetInode := stat.Sys().(*sysStatT).Ino
|
||||
targetDevice := stat.Sys().(*sysStatT).Dev
|
||||
|
||||
for i := 0; true; i++ {
|
||||
path := fmt.Sprintf("/dev/loop%d", i)
|
||||
|
||||
file, err := osOpenFile(path, osORdWr, 0)
|
||||
if err != nil {
|
||||
if osIsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ignore all errors until the first not-exist
|
||||
// we want to continue looking for the file
|
||||
continue
|
||||
}
|
||||
|
||||
dev, inode, err := getLoopbackBackingFile(&osFile{File: file})
|
||||
if err == nil && dev == targetDevice && inode == targetInode {
|
||||
return &osFile{File: file}
|
||||
}
|
||||
|
||||
file.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func UdevWait(cookie uint) error {
|
||||
@@ -237,7 +285,7 @@ func RemoveDevice(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetBlockDeviceSize(file *os.File) (uint64, error) {
|
||||
func GetBlockDeviceSize(file *osFile) (uint64, error) {
|
||||
size, errno := DmGetBlockSize(file.Fd())
|
||||
if size == -1 || errno != 0 {
|
||||
return 0, ErrGetBlockSize
|
||||
@@ -246,7 +294,7 @@ func GetBlockDeviceSize(file *os.File) (uint64, error) {
|
||||
}
|
||||
|
||||
// This is the programmatic example of "dmsetup create"
|
||||
func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error {
|
||||
func createPool(poolName string, dataFile, metadataFile *osFile) error {
|
||||
task, err := createTask(DeviceCreate, poolName)
|
||||
if task == nil {
|
||||
return err
|
||||
@@ -276,6 +324,29 @@ func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func reloadPool(poolName string, dataFile, metadataFile *osFile) error {
|
||||
task, err := createTask(DeviceReload, poolName)
|
||||
if task == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
size, err := GetBlockDeviceSize(dataFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't get data size")
|
||||
}
|
||||
|
||||
params := metadataFile.Name() + " " + dataFile.Name() + " 128 32768"
|
||||
if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil {
|
||||
return fmt.Errorf("Can't add target")
|
||||
}
|
||||
|
||||
if err := task.Run(); err != nil {
|
||||
return fmt.Errorf("Error running DeviceCreate")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createTask(t TaskType, name string) (*Task, error) {
|
||||
task := TaskCreate(t)
|
||||
if task == nil {
|
||||
@@ -319,8 +390,8 @@ func getStatus(name string) (uint64, uint64, string, string, error) {
|
||||
return 0, 0, "", "", fmt.Errorf("Non existing device %s", name)
|
||||
}
|
||||
|
||||
_, start, length, target_type, params := task.GetNextTarget(0)
|
||||
return start, length, target_type, params, nil
|
||||
_, start, length, targetType, params := task.GetNextTarget(0)
|
||||
return start, length, targetType, params, nil
|
||||
}
|
||||
|
||||
func setTransactionId(poolName string, oldId uint64, newId uint64) error {
|
||||
@@ -0,0 +1,106 @@
|
||||
package devmapper
|
||||
|
||||
// Definition of struct dm_task and sub structures (from lvm2)
|
||||
//
|
||||
// struct dm_ioctl {
|
||||
// /*
|
||||
// * The version number is made up of three parts:
|
||||
// * major - no backward or forward compatibility,
|
||||
// * minor - only backwards compatible,
|
||||
// * patch - both backwards and forwards compatible.
|
||||
// *
|
||||
// * All clients of the ioctl interface should fill in the
|
||||
// * version number of the interface that they were
|
||||
// * compiled with.
|
||||
// *
|
||||
// * All recognised ioctl commands (ie. those that don't
|
||||
// * return -ENOTTY) fill out this field, even if the
|
||||
// * command failed.
|
||||
// */
|
||||
// uint32_t version[3]; /* in/out */
|
||||
// uint32_t data_size; /* total size of data passed in
|
||||
// * including this struct */
|
||||
|
||||
// uint32_t data_start; /* offset to start of data
|
||||
// * relative to start of this struct */
|
||||
|
||||
// uint32_t target_count; /* in/out */
|
||||
// int32_t open_count; /* out */
|
||||
// uint32_t flags; /* in/out */
|
||||
|
||||
// /*
|
||||
// * event_nr holds either the event number (input and output) or the
|
||||
// * udev cookie value (input only).
|
||||
// * The DM_DEV_WAIT ioctl takes an event number as input.
|
||||
// * The DM_SUSPEND, DM_DEV_REMOVE and DM_DEV_RENAME ioctls
|
||||
// * use the field as a cookie to return in the DM_COOKIE
|
||||
// * variable with the uevents they issue.
|
||||
// * For output, the ioctls return the event number, not the cookie.
|
||||
// */
|
||||
// uint32_t event_nr; /* in/out */
|
||||
// uint32_t padding;
|
||||
|
||||
// uint64_t dev; /* in/out */
|
||||
|
||||
// char name[DM_NAME_LEN]; /* device name */
|
||||
// char uuid[DM_UUID_LEN]; /* unique identifier for
|
||||
// * the block device */
|
||||
// char data[7]; /* padding or data */
|
||||
// };
|
||||
|
||||
// struct target {
|
||||
// uint64_t start;
|
||||
// uint64_t length;
|
||||
// char *type;
|
||||
// char *params;
|
||||
|
||||
// struct target *next;
|
||||
// };
|
||||
|
||||
// typedef enum {
|
||||
// DM_ADD_NODE_ON_RESUME, /* add /dev/mapper node with dmsetup resume */
|
||||
// DM_ADD_NODE_ON_CREATE /* add /dev/mapper node with dmsetup create */
|
||||
// } dm_add_node_t;
|
||||
|
||||
// struct dm_task {
|
||||
// int type;
|
||||
// char *dev_name;
|
||||
// char *mangled_dev_name;
|
||||
|
||||
// struct target *head, *tail;
|
||||
|
||||
// int read_only;
|
||||
// uint32_t event_nr;
|
||||
// int major;
|
||||
// int minor;
|
||||
// int allow_default_major_fallback;
|
||||
// uid_t uid;
|
||||
// gid_t gid;
|
||||
// mode_t mode;
|
||||
// uint32_t read_ahead;
|
||||
// uint32_t read_ahead_flags;
|
||||
// union {
|
||||
// struct dm_ioctl *v4;
|
||||
// } dmi;
|
||||
// char *newname;
|
||||
// char *message;
|
||||
// char *geometry;
|
||||
// uint64_t sector;
|
||||
// int no_flush;
|
||||
// int no_open_count;
|
||||
// int skip_lockfs;
|
||||
// int query_inactive_table;
|
||||
// int suppress_identical_reload;
|
||||
// dm_add_node_t add_node;
|
||||
// uint64_t existing_table_size;
|
||||
// int cookie_set;
|
||||
// int new_uuid;
|
||||
// int secure_data;
|
||||
// int retry_remove;
|
||||
// int enable_checks;
|
||||
// int expected_errno;
|
||||
|
||||
// char *uuid;
|
||||
// char *mangled_uuid;
|
||||
// };
|
||||
//
|
||||
@@ -1,11 +1,11 @@
|
||||
package devmapper
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTaskCreate(t *testing.T) {
|
||||
t.Skip("FIXME: not a unit test")
|
||||
// Test success
|
||||
taskCreate(t, DeviceInfo)
|
||||
|
||||
@@ -18,6 +18,7 @@ func TestTaskCreate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTaskRun(t *testing.T) {
|
||||
t.Skip("FIXME: not a unit test")
|
||||
task := taskCreate(t, DeviceInfo)
|
||||
|
||||
// Test success
|
||||
@@ -46,6 +47,7 @@ func TestTaskRun(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTaskSetName(t *testing.T) {
|
||||
t.Skip("FIXME: not a unit test")
|
||||
task := taskCreate(t, DeviceInfo)
|
||||
|
||||
// Test success
|
||||
@@ -63,6 +65,7 @@ func TestTaskSetName(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTaskSetMessage(t *testing.T) {
|
||||
t.Skip("FIXME: not a unit test")
|
||||
task := taskCreate(t, DeviceInfo)
|
||||
|
||||
// Test success
|
||||
@@ -80,6 +83,7 @@ func TestTaskSetMessage(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTaskSetSector(t *testing.T) {
|
||||
t.Skip("FIXME: not a unit test")
|
||||
task := taskCreate(t, DeviceInfo)
|
||||
|
||||
// Test success
|
||||
@@ -97,6 +101,7 @@ func TestTaskSetSector(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTaskSetCookie(t *testing.T) {
|
||||
t.Skip("FIXME: not a unit test")
|
||||
var (
|
||||
cookie uint = 0
|
||||
task = taskCreate(t, DeviceInfo)
|
||||
@@ -121,6 +126,7 @@ func TestTaskSetCookie(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTaskSetAddNode(t *testing.T) {
|
||||
t.Skip("FIXME: not a unit test")
|
||||
task := taskCreate(t, DeviceInfo)
|
||||
|
||||
// Test success
|
||||
@@ -142,6 +148,7 @@ func TestTaskSetAddNode(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTaskSetRo(t *testing.T) {
|
||||
t.Skip("FIXME: not a unit test")
|
||||
task := taskCreate(t, DeviceInfo)
|
||||
|
||||
// Test success
|
||||
@@ -159,6 +166,7 @@ func TestTaskSetRo(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTaskAddTarget(t *testing.T) {
|
||||
t.Skip("FIXME: not a unit test")
|
||||
task := taskCreate(t, DeviceInfo)
|
||||
|
||||
// Test success
|
||||
@@ -247,10 +255,6 @@ func dmTaskAddTargetFail(task *CDmTask,
|
||||
return -1
|
||||
}
|
||||
|
||||
func dmTaskGetDriverVersionFail(task *CDmTask, version *string) int {
|
||||
return -1
|
||||
}
|
||||
|
||||
func dmTaskGetInfoFail(task *CDmTask, info *Info) int {
|
||||
return -1
|
||||
}
|
||||
@@ -264,14 +268,10 @@ func dmAttachLoopDeviceFail(filename string, fd *int) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func sysGetBlockSizeFail(fd uintptr, size *uint64) syscall.Errno {
|
||||
func sysGetBlockSizeFail(fd uintptr, size *uint64) sysErrno {
|
||||
return 1
|
||||
}
|
||||
|
||||
func dmGetBlockSizeFail(fd uintptr) int64 {
|
||||
return -1
|
||||
}
|
||||
|
||||
func dmUdevWaitFail(cookie uint) int {
|
||||
return -1
|
||||
}
|
||||
@@ -140,7 +140,6 @@ static void log_with_errno_init()
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
@@ -149,26 +148,28 @@ type (
|
||||
)
|
||||
|
||||
var (
|
||||
DmTaskDestory = dmTaskDestroyFct
|
||||
DmTaskCreate = dmTaskCreateFct
|
||||
DmTaskRun = dmTaskRunFct
|
||||
DmTaskSetName = dmTaskSetNameFct
|
||||
DmTaskSetMessage = dmTaskSetMessageFct
|
||||
DmTaskSetSector = dmTaskSetSectorFct
|
||||
DmTaskSetCookie = dmTaskSetCookieFct
|
||||
DmTaskSetAddNode = dmTaskSetAddNodeFct
|
||||
DmTaskSetRo = dmTaskSetRoFct
|
||||
DmTaskAddTarget = dmTaskAddTargetFct
|
||||
DmTaskGetInfo = dmTaskGetInfoFct
|
||||
DmGetNextTarget = dmGetNextTargetFct
|
||||
DmGetBlockSize = dmGetBlockSizeFct
|
||||
DmAttachLoopDevice = dmAttachLoopDeviceFct
|
||||
DmUdevWait = dmUdevWaitFct
|
||||
DmLogInitVerbose = dmLogInitVerboseFct
|
||||
DmSetDevDir = dmSetDevDirFct
|
||||
DmGetLibraryVersion = dmGetLibraryVersionFct
|
||||
LogWithErrnoInit = logWithErrnoInitFct
|
||||
GetBlockSize = getBlockSizeFct
|
||||
DmAttachLoopDevice = dmAttachLoopDeviceFct
|
||||
DmGetBlockSize = dmGetBlockSizeFct
|
||||
DmGetLibraryVersion = dmGetLibraryVersionFct
|
||||
DmGetNextTarget = dmGetNextTargetFct
|
||||
DmLogInitVerbose = dmLogInitVerboseFct
|
||||
DmSetDevDir = dmSetDevDirFct
|
||||
DmTaskAddTarget = dmTaskAddTargetFct
|
||||
DmTaskCreate = dmTaskCreateFct
|
||||
DmTaskDestroy = dmTaskDestroyFct
|
||||
DmTaskGetInfo = dmTaskGetInfoFct
|
||||
DmTaskRun = dmTaskRunFct
|
||||
DmTaskSetAddNode = dmTaskSetAddNodeFct
|
||||
DmTaskSetCookie = dmTaskSetCookieFct
|
||||
DmTaskSetMessage = dmTaskSetMessageFct
|
||||
DmTaskSetName = dmTaskSetNameFct
|
||||
DmTaskSetRo = dmTaskSetRoFct
|
||||
DmTaskSetSector = dmTaskSetSectorFct
|
||||
DmUdevWait = dmUdevWaitFct
|
||||
GetBlockSize = getBlockSizeFct
|
||||
LogWithErrnoInit = logWithErrnoInitFct
|
||||
DmGetLoopbackBackingFile = dmGetLoopbackBackingFileFct
|
||||
DmLoopbackSetCapacity = dmLoopbackSetCapacityFct
|
||||
)
|
||||
|
||||
func free(p *C.char) {
|
||||
@@ -239,11 +240,22 @@ func dmTaskAddTargetFct(task *CDmTask,
|
||||
C.uint64_t(start), C.uint64_t(size), Cttype, Cparams))
|
||||
}
|
||||
|
||||
func dmGetBlockSizeFct(fd uintptr) (int64, syscall.Errno) {
|
||||
func dmGetLoopbackBackingFileFct(fd uintptr) (uint64, uint64, sysErrno) {
|
||||
var lo64 C.struct_loop_info64
|
||||
_, _, err := sysSyscall(sysSysIoctl, fd, C.LOOP_GET_STATUS64,
|
||||
uintptr(unsafe.Pointer(&lo64)))
|
||||
return uint64(lo64.lo_device), uint64(lo64.lo_inode), sysErrno(err)
|
||||
}
|
||||
|
||||
func dmLoopbackSetCapacityFct(fd uintptr) sysErrno {
|
||||
_, _, err := sysSyscall(sysSysIoctl, fd, C.LOOP_SET_CAPACITY, 0)
|
||||
return sysErrno(err)
|
||||
}
|
||||
|
||||
func dmGetBlockSizeFct(fd uintptr) (int64, sysErrno) {
|
||||
var size int64
|
||||
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64,
|
||||
uintptr(unsafe.Pointer(&size)))
|
||||
return size, err
|
||||
_, _, err := sysSyscall(sysSysIoctl, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size)))
|
||||
return size, sysErrno(err)
|
||||
}
|
||||
|
||||
func dmTaskGetInfoFct(task *CDmTask, info *Info) int {
|
||||
@@ -263,9 +275,7 @@ func dmTaskGetInfoFct(task *CDmTask, info *Info) int {
|
||||
return int(C.dm_task_get_info((*C.struct_dm_task)(task), &Cinfo))
|
||||
}
|
||||
|
||||
func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64,
|
||||
target, params *string) uintptr {
|
||||
|
||||
func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr {
|
||||
var (
|
||||
Cstart, Clength C.uint64_t
|
||||
CtargetType, Cparams *C.char
|
||||
@@ -276,6 +286,7 @@ func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64,
|
||||
*target = C.GoString(CtargetType)
|
||||
*params = C.GoString(Cparams)
|
||||
}()
|
||||
|
||||
nextp := C.dm_get_next_target((*C.struct_dm_task)(task),
|
||||
unsafe.Pointer(next), &Cstart, &Clength, &CtargetType, &Cparams)
|
||||
return uintptr(nextp)
|
||||
@@ -295,10 +306,9 @@ func dmAttachLoopDeviceFct(filename string, fd *int) string {
|
||||
return C.GoString(ret)
|
||||
}
|
||||
|
||||
func getBlockSizeFct(fd uintptr, size *uint64) syscall.Errno {
|
||||
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64,
|
||||
uintptr(unsafe.Pointer(&size)))
|
||||
return err
|
||||
func getBlockSizeFct(fd uintptr, size *uint64) sysErrno {
|
||||
_, _, err := sysSyscall(sysSysIoctl, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size)))
|
||||
return sysErrno(err)
|
||||
}
|
||||
|
||||
func dmUdevWaitFct(cookie uint) int {
|
||||
@@ -0,0 +1,126 @@
|
||||
package devmapper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/graphdriver"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
)
|
||||
|
||||
func init() {
|
||||
graphdriver.Register("devicemapper", Init)
|
||||
}
|
||||
|
||||
// Placeholder interfaces, to be replaced
|
||||
// at integration.
|
||||
|
||||
// End of placeholder interfaces.
|
||||
|
||||
type Driver struct {
|
||||
*DeviceSet
|
||||
home string
|
||||
}
|
||||
|
||||
var Init = func(home string) (graphdriver.Driver, error) {
|
||||
deviceSet, err := NewDeviceSet(home, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d := &Driver{
|
||||
DeviceSet: deviceSet,
|
||||
home: home,
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (d *Driver) String() string {
|
||||
return "devicemapper"
|
||||
}
|
||||
|
||||
func (d *Driver) Status() [][2]string {
|
||||
s := d.DeviceSet.Status()
|
||||
|
||||
status := [][2]string{
|
||||
{"Pool Name", s.PoolName},
|
||||
{"Data file", s.DataLoopback},
|
||||
{"Metadata file", s.MetadataLoopback},
|
||||
{"Data Space Used", fmt.Sprintf("%.1f Mb", float64(s.Data.Used)/(1024*1024))},
|
||||
{"Data Space Total", fmt.Sprintf("%.1f Mb", float64(s.Data.Total)/(1024*1024))},
|
||||
{"Metadata Space Used", fmt.Sprintf("%.1f Mb", float64(s.Metadata.Used)/(1024*1024))},
|
||||
{"Metadata Space Total", fmt.Sprintf("%.1f Mb", float64(s.Metadata.Total)/(1024*1024))},
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
func (d *Driver) Cleanup() error {
|
||||
return d.DeviceSet.Shutdown()
|
||||
}
|
||||
|
||||
func (d *Driver) Create(id, parent string) error {
|
||||
if err := d.DeviceSet.AddDevice(id, parent); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mp := path.Join(d.home, "mnt", id)
|
||||
if err := d.mount(id, mp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := osMkdirAll(path.Join(mp, "rootfs"), 0755); err != nil && !osIsExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create an "id" file with the container/image id in it to help reconscruct this in case
|
||||
// of later problems
|
||||
if err := ioutil.WriteFile(path.Join(mp, "id"), []byte(id), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) Remove(id string) error {
|
||||
mp := path.Join(d.home, "mnt", id)
|
||||
if err := d.unmount(id, mp); err != nil {
|
||||
return err
|
||||
}
|
||||
return d.DeviceSet.RemoveDevice(id)
|
||||
}
|
||||
|
||||
func (d *Driver) Get(id string) (string, error) {
|
||||
mp := path.Join(d.home, "mnt", id)
|
||||
if err := d.mount(id, mp); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return path.Join(mp, "rootfs"), nil
|
||||
}
|
||||
|
||||
func (d *Driver) mount(id, mountPoint string) error {
|
||||
// Create the target directories if they don't exist
|
||||
if err := osMkdirAll(mountPoint, 0755); err != nil && !osIsExist(err) {
|
||||
return err
|
||||
}
|
||||
// If mountpoint is already mounted, do nothing
|
||||
if mounted, err := Mounted(mountPoint); err != nil {
|
||||
return fmt.Errorf("Error checking mountpoint: %s", err)
|
||||
} else if mounted {
|
||||
return nil
|
||||
}
|
||||
// Mount the device
|
||||
return d.DeviceSet.MountDevice(id, mountPoint, false)
|
||||
}
|
||||
|
||||
func (d *Driver) unmount(id, mountPoint string) error {
|
||||
// If mountpoint is not mounted, do nothing
|
||||
if mounted, err := Mounted(mountPoint); err != nil {
|
||||
return fmt.Errorf("Error checking mountpoint: %s", err)
|
||||
} else if !mounted {
|
||||
return nil
|
||||
}
|
||||
// Unmount the device
|
||||
return d.DeviceSet.UnmountDevice(id, mountPoint, true)
|
||||
}
|
||||
|
||||
func (d *Driver) Exists(id string) bool {
|
||||
return d.Devices[id] != nil
|
||||
}
|
||||
@@ -0,0 +1,872 @@
|
||||
package devmapper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/graphdriver"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Reduce the size the the base fs and loopback for the tests
|
||||
DefaultDataLoopbackSize = 300 * 1024 * 1024
|
||||
DefaultMetaDataLoopbackSize = 200 * 1024 * 1024
|
||||
DefaultBaseFsSize = 300 * 1024 * 1024
|
||||
}
|
||||
|
||||
// denyAllDevmapper mocks all calls to libdevmapper in the unit tests, and denies them by default
|
||||
func denyAllDevmapper() {
|
||||
// Hijack all calls to libdevmapper with default panics.
|
||||
// Authorized calls are selectively hijacked in each tests.
|
||||
DmTaskCreate = func(t int) *CDmTask {
|
||||
panic("DmTaskCreate: this method should not be called here")
|
||||
}
|
||||
DmTaskRun = func(task *CDmTask) int {
|
||||
panic("DmTaskRun: this method should not be called here")
|
||||
}
|
||||
DmTaskSetName = func(task *CDmTask, name string) int {
|
||||
panic("DmTaskSetName: this method should not be called here")
|
||||
}
|
||||
DmTaskSetMessage = func(task *CDmTask, message string) int {
|
||||
panic("DmTaskSetMessage: this method should not be called here")
|
||||
}
|
||||
DmTaskSetSector = func(task *CDmTask, sector uint64) int {
|
||||
panic("DmTaskSetSector: this method should not be called here")
|
||||
}
|
||||
DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int {
|
||||
panic("DmTaskSetCookie: this method should not be called here")
|
||||
}
|
||||
DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int {
|
||||
panic("DmTaskSetAddNode: this method should not be called here")
|
||||
}
|
||||
DmTaskSetRo = func(task *CDmTask) int {
|
||||
panic("DmTaskSetRo: this method should not be called here")
|
||||
}
|
||||
DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int {
|
||||
panic("DmTaskAddTarget: this method should not be called here")
|
||||
}
|
||||
DmTaskGetInfo = func(task *CDmTask, info *Info) int {
|
||||
panic("DmTaskGetInfo: this method should not be called here")
|
||||
}
|
||||
DmGetNextTarget = func(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr {
|
||||
panic("DmGetNextTarget: this method should not be called here")
|
||||
}
|
||||
DmAttachLoopDevice = func(filename string, fd *int) string {
|
||||
panic("DmAttachLoopDevice: this method should not be called here")
|
||||
}
|
||||
DmGetBlockSize = func(fd uintptr) (int64, sysErrno) {
|
||||
panic("DmGetBlockSize: this method should not be called here")
|
||||
}
|
||||
DmUdevWait = func(cookie uint) int {
|
||||
panic("DmUdevWait: this method should not be called here")
|
||||
}
|
||||
DmSetDevDir = func(dir string) int {
|
||||
panic("DmSetDevDir: this method should not be called here")
|
||||
}
|
||||
DmGetLibraryVersion = func(version *string) int {
|
||||
panic("DmGetLibraryVersion: this method should not be called here")
|
||||
}
|
||||
DmLogInitVerbose = func(level int) {
|
||||
panic("DmLogInitVerbose: this method should not be called here")
|
||||
}
|
||||
DmTaskDestroy = func(task *CDmTask) {
|
||||
panic("DmTaskDestroy: this method should not be called here")
|
||||
}
|
||||
GetBlockSize = func(fd uintptr, size *uint64) sysErrno {
|
||||
panic("GetBlockSize: this method should not be called here")
|
||||
}
|
||||
LogWithErrnoInit = func() {
|
||||
panic("LogWithErrnoInit: this method should not be called here")
|
||||
}
|
||||
}
|
||||
|
||||
func denyAllSyscall() {
|
||||
sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) {
|
||||
panic("sysMount: this method should not be called here")
|
||||
}
|
||||
sysUnmount = func(target string, flags int) (err error) {
|
||||
panic("sysUnmount: this method should not be called here")
|
||||
}
|
||||
sysCloseOnExec = func(fd int) {
|
||||
panic("sysCloseOnExec: this method should not be called here")
|
||||
}
|
||||
sysSyscall = func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
|
||||
panic("sysSyscall: this method should not be called here")
|
||||
}
|
||||
// Not a syscall, but forbidding it here anyway
|
||||
Mounted = func(mnt string) (bool, error) {
|
||||
panic("devmapper.Mounted: this method should not be called here")
|
||||
}
|
||||
// osOpenFile = os.OpenFile
|
||||
// osNewFile = os.NewFile
|
||||
// osCreate = os.Create
|
||||
// osStat = os.Stat
|
||||
// osIsNotExist = os.IsNotExist
|
||||
// osIsExist = os.IsExist
|
||||
// osMkdirAll = os.MkdirAll
|
||||
// osRemoveAll = os.RemoveAll
|
||||
// osRename = os.Rename
|
||||
// osReadlink = os.Readlink
|
||||
|
||||
// execRun = func(name string, args ...string) error {
|
||||
// return exec.Command(name, args...).Run()
|
||||
// }
|
||||
}
|
||||
|
||||
func mkTestDirectory(t *testing.T) string {
|
||||
dir, err := ioutil.TempDir("", "docker-test-devmapper-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
func newDriver(t *testing.T) *Driver {
|
||||
home := mkTestDirectory(t)
|
||||
d, err := Init(home)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return d.(*Driver)
|
||||
}
|
||||
|
||||
func cleanup(d *Driver) {
|
||||
d.Cleanup()
|
||||
osRemoveAll(d.home)
|
||||
}
|
||||
|
||||
type Set map[string]bool
|
||||
|
||||
func (r Set) Assert(t *testing.T, names ...string) {
|
||||
for _, key := range names {
|
||||
if _, exists := r[key]; !exists {
|
||||
t.Fatalf("Key not set: %s", key)
|
||||
}
|
||||
delete(r, key)
|
||||
}
|
||||
if len(r) != 0 {
|
||||
t.Fatalf("Unexpected keys: %v", r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
var (
|
||||
calls = make(Set)
|
||||
devicesAttached = make(Set)
|
||||
taskMessages = make(Set)
|
||||
taskTypes = make(Set)
|
||||
home = mkTestDirectory(t)
|
||||
)
|
||||
defer osRemoveAll(home)
|
||||
|
||||
func() {
|
||||
denyAllDevmapper()
|
||||
DmSetDevDir = func(dir string) int {
|
||||
calls["DmSetDevDir"] = true
|
||||
expectedDir := "/dev"
|
||||
if dir != expectedDir {
|
||||
t.Fatalf("Wrong libdevmapper call\nExpected: DmSetDevDir(%v)\nReceived: DmSetDevDir(%v)\n", expectedDir, dir)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
LogWithErrnoInit = func() {
|
||||
calls["DmLogWithErrnoInit"] = true
|
||||
}
|
||||
var task1 CDmTask
|
||||
DmTaskCreate = func(taskType int) *CDmTask {
|
||||
calls["DmTaskCreate"] = true
|
||||
taskTypes[fmt.Sprintf("%d", taskType)] = true
|
||||
return &task1
|
||||
}
|
||||
DmTaskSetName = func(task *CDmTask, name string) int {
|
||||
calls["DmTaskSetName"] = true
|
||||
expectedTask := &task1
|
||||
if task != expectedTask {
|
||||
t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetName(%v)\nReceived: DmTaskSetName(%v)\n", expectedTask, task)
|
||||
}
|
||||
// FIXME: use Set.AssertRegexp()
|
||||
if !strings.HasPrefix(name, "docker-") && !strings.HasPrefix(name, "/dev/mapper/docker-") ||
|
||||
!strings.HasSuffix(name, "-pool") && !strings.HasSuffix(name, "-base") {
|
||||
t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetName(%v)\nReceived: DmTaskSetName(%v)\n", "docker-...-pool", name)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
DmTaskRun = func(task *CDmTask) int {
|
||||
calls["DmTaskRun"] = true
|
||||
expectedTask := &task1
|
||||
if task != expectedTask {
|
||||
t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskRun(%v)\nReceived: DmTaskRun(%v)\n", expectedTask, task)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
DmTaskGetInfo = func(task *CDmTask, info *Info) int {
|
||||
calls["DmTaskGetInfo"] = true
|
||||
expectedTask := &task1
|
||||
if task != expectedTask {
|
||||
t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskGetInfo(%v)\nReceived: DmTaskGetInfo(%v)\n", expectedTask, task)
|
||||
}
|
||||
// This will crash if info is not dereferenceable
|
||||
info.Exists = 0
|
||||
return 1
|
||||
}
|
||||
DmTaskSetSector = func(task *CDmTask, sector uint64) int {
|
||||
calls["DmTaskSetSector"] = true
|
||||
expectedTask := &task1
|
||||
if task != expectedTask {
|
||||
t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetSector(%v)\nReceived: DmTaskSetSector(%v)\n", expectedTask, task)
|
||||
}
|
||||
if expectedSector := uint64(0); sector != expectedSector {
|
||||
t.Fatalf("Wrong libdevmapper call to DmTaskSetSector\nExpected: %v\nReceived: %v\n", expectedSector, sector)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
DmTaskSetMessage = func(task *CDmTask, message string) int {
|
||||
calls["DmTaskSetMessage"] = true
|
||||
expectedTask := &task1
|
||||
if task != expectedTask {
|
||||
t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetSector(%v)\nReceived: DmTaskSetSector(%v)\n", expectedTask, task)
|
||||
}
|
||||
taskMessages[message] = true
|
||||
return 1
|
||||
}
|
||||
var (
|
||||
fakeDataLoop = "/dev/loop42"
|
||||
fakeMetadataLoop = "/dev/loop43"
|
||||
fakeDataLoopFd = 42
|
||||
fakeMetadataLoopFd = 43
|
||||
)
|
||||
var attachCount int
|
||||
DmAttachLoopDevice = func(filename string, fd *int) string {
|
||||
calls["DmAttachLoopDevice"] = true
|
||||
if _, exists := devicesAttached[filename]; exists {
|
||||
t.Fatalf("Already attached %s", filename)
|
||||
}
|
||||
devicesAttached[filename] = true
|
||||
// This will crash if fd is not dereferenceable
|
||||
if attachCount == 0 {
|
||||
attachCount++
|
||||
*fd = fakeDataLoopFd
|
||||
return fakeDataLoop
|
||||
} else {
|
||||
*fd = fakeMetadataLoopFd
|
||||
return fakeMetadataLoop
|
||||
}
|
||||
}
|
||||
DmTaskDestroy = func(task *CDmTask) {
|
||||
calls["DmTaskDestroy"] = true
|
||||
expectedTask := &task1
|
||||
if task != expectedTask {
|
||||
t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task)
|
||||
}
|
||||
}
|
||||
fakeBlockSize := int64(4242 * 512)
|
||||
DmGetBlockSize = func(fd uintptr) (int64, sysErrno) {
|
||||
calls["DmGetBlockSize"] = true
|
||||
if expectedFd := uintptr(42); fd != expectedFd {
|
||||
t.Fatalf("Wrong libdevmapper call\nExpected: DmGetBlockSize(%v)\nReceived: DmGetBlockSize(%v)\n", expectedFd, fd)
|
||||
}
|
||||
return fakeBlockSize, 0
|
||||
}
|
||||
DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int {
|
||||
calls["DmTaskSetTarget"] = true
|
||||
expectedTask := &task1
|
||||
if task != expectedTask {
|
||||
t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task)
|
||||
}
|
||||
if start != 0 {
|
||||
t.Fatalf("Wrong start: %d != %d", start, 0)
|
||||
}
|
||||
if ttype != "thin" && ttype != "thin-pool" {
|
||||
t.Fatalf("Wrong ttype: %s", ttype)
|
||||
}
|
||||
// Quick smoke test
|
||||
if params == "" {
|
||||
t.Fatalf("Params should not be empty")
|
||||
}
|
||||
return 1
|
||||
}
|
||||
fakeCookie := uint(4321)
|
||||
DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int {
|
||||
calls["DmTaskSetCookie"] = true
|
||||
expectedTask := &task1
|
||||
if task != expectedTask {
|
||||
t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task)
|
||||
}
|
||||
if flags != 0 {
|
||||
t.Fatalf("Cookie flags should be 0 (not %x)", flags)
|
||||
}
|
||||
*cookie = fakeCookie
|
||||
return 1
|
||||
}
|
||||
DmUdevWait = func(cookie uint) int {
|
||||
calls["DmUdevWait"] = true
|
||||
if cookie != fakeCookie {
|
||||
t.Fatalf("Wrong cookie: %d != %d", cookie, fakeCookie)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int {
|
||||
if addNode != AddNodeOnCreate {
|
||||
t.Fatalf("Wrong AddNoteType: %v (expected %v)", addNode, AddNodeOnCreate)
|
||||
}
|
||||
calls["DmTaskSetAddNode"] = true
|
||||
return 1
|
||||
}
|
||||
execRun = func(name string, args ...string) error {
|
||||
calls["execRun"] = true
|
||||
if name != "mkfs.ext4" {
|
||||
t.Fatalf("Expected %s to be executed, not %s", "mkfs.ext4", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
driver, err := Init(home)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := driver.Cleanup(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
}()
|
||||
// Put all tests in a funciton to make sure the garbage collection will
|
||||
// occur.
|
||||
|
||||
// Call GC to cleanup runtime.Finalizers
|
||||
runtime.GC()
|
||||
|
||||
calls.Assert(t,
|
||||
"DmSetDevDir",
|
||||
"DmLogWithErrnoInit",
|
||||
"DmTaskSetName",
|
||||
"DmTaskRun",
|
||||
"DmTaskGetInfo",
|
||||
"DmAttachLoopDevice",
|
||||
"DmTaskDestroy",
|
||||
"execRun",
|
||||
"DmTaskCreate",
|
||||
"DmGetBlockSize",
|
||||
"DmTaskSetTarget",
|
||||
"DmTaskSetCookie",
|
||||
"DmUdevWait",
|
||||
"DmTaskSetSector",
|
||||
"DmTaskSetMessage",
|
||||
"DmTaskSetAddNode",
|
||||
)
|
||||
devicesAttached.Assert(t, path.Join(home, "devicemapper", "data"), path.Join(home, "devicemapper", "metadata"))
|
||||
taskTypes.Assert(t, "0", "6", "17")
|
||||
taskMessages.Assert(t, "create_thin 0", "set_transaction_id 0 1")
|
||||
}
|
||||
|
||||
func fakeInit() func(home string) (graphdriver.Driver, error) {
|
||||
oldInit := Init
|
||||
Init = func(home string) (graphdriver.Driver, error) {
|
||||
return &Driver{
|
||||
home: home,
|
||||
}, nil
|
||||
}
|
||||
return oldInit
|
||||
}
|
||||
|
||||
func restoreInit(init func(home string) (graphdriver.Driver, error)) {
|
||||
Init = init
|
||||
}
|
||||
|
||||
func mockAllDevmapper(calls Set) {
|
||||
DmSetDevDir = func(dir string) int {
|
||||
calls["DmSetDevDir"] = true
|
||||
return 0
|
||||
}
|
||||
LogWithErrnoInit = func() {
|
||||
calls["DmLogWithErrnoInit"] = true
|
||||
}
|
||||
DmTaskCreate = func(taskType int) *CDmTask {
|
||||
calls["DmTaskCreate"] = true
|
||||
return &CDmTask{}
|
||||
}
|
||||
DmTaskSetName = func(task *CDmTask, name string) int {
|
||||
calls["DmTaskSetName"] = true
|
||||
return 1
|
||||
}
|
||||
DmTaskRun = func(task *CDmTask) int {
|
||||
calls["DmTaskRun"] = true
|
||||
return 1
|
||||
}
|
||||
DmTaskGetInfo = func(task *CDmTask, info *Info) int {
|
||||
calls["DmTaskGetInfo"] = true
|
||||
return 1
|
||||
}
|
||||
DmTaskSetSector = func(task *CDmTask, sector uint64) int {
|
||||
calls["DmTaskSetSector"] = true
|
||||
return 1
|
||||
}
|
||||
DmTaskSetMessage = func(task *CDmTask, message string) int {
|
||||
calls["DmTaskSetMessage"] = true
|
||||
return 1
|
||||
}
|
||||
DmAttachLoopDevice = func(filename string, fd *int) string {
|
||||
calls["DmAttachLoopDevice"] = true
|
||||
return "/dev/loop42"
|
||||
}
|
||||
DmTaskDestroy = func(task *CDmTask) {
|
||||
calls["DmTaskDestroy"] = true
|
||||
}
|
||||
DmGetBlockSize = func(fd uintptr) (int64, sysErrno) {
|
||||
calls["DmGetBlockSize"] = true
|
||||
return int64(4242 * 512), 0
|
||||
}
|
||||
DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int {
|
||||
calls["DmTaskSetTarget"] = true
|
||||
return 1
|
||||
}
|
||||
DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int {
|
||||
calls["DmTaskSetCookie"] = true
|
||||
return 1
|
||||
}
|
||||
DmUdevWait = func(cookie uint) int {
|
||||
calls["DmUdevWait"] = true
|
||||
return 1
|
||||
}
|
||||
DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int {
|
||||
calls["DmTaskSetAddNode"] = true
|
||||
return 1
|
||||
}
|
||||
execRun = func(name string, args ...string) error {
|
||||
calls["execRun"] = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func TestDriverName(t *testing.T) {
|
||||
denyAllDevmapper()
|
||||
defer denyAllDevmapper()
|
||||
|
||||
oldInit := fakeInit()
|
||||
defer restoreInit(oldInit)
|
||||
|
||||
d := newDriver(t)
|
||||
if d.String() != "devicemapper" {
|
||||
t.Fatalf("Expected driver name to be devicemapper got %s", d.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDriverCreate(t *testing.T) {
|
||||
denyAllDevmapper()
|
||||
denyAllSyscall()
|
||||
defer denyAllSyscall()
|
||||
defer denyAllDevmapper()
|
||||
|
||||
calls := make(Set)
|
||||
mockAllDevmapper(calls)
|
||||
|
||||
sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) {
|
||||
calls["sysMount"] = true
|
||||
// FIXME: compare the exact source and target strings (inodes + devname)
|
||||
if expectedSource := "/dev/mapper/docker-"; !strings.HasPrefix(source, expectedSource) {
|
||||
t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedSource, source)
|
||||
}
|
||||
if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) {
|
||||
t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target)
|
||||
}
|
||||
if expectedFstype := "ext4"; fstype != expectedFstype {
|
||||
t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFstype, fstype)
|
||||
}
|
||||
if expectedFlags := uintptr(3236757504); flags != expectedFlags {
|
||||
t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Mounted = func(mnt string) (bool, error) {
|
||||
calls["Mounted"] = true
|
||||
if !strings.HasPrefix(mnt, "/tmp/docker-test-devmapper-") || !strings.HasSuffix(mnt, "/mnt/1") {
|
||||
t.Fatalf("Wrong mounted call\nExpected: Mounted(%v)\nReceived: Mounted(%v)\n", "/tmp/docker-test-devmapper-.../mnt/1", mnt)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func() {
|
||||
d := newDriver(t)
|
||||
|
||||
calls.Assert(t,
|
||||
"DmSetDevDir",
|
||||
"DmLogWithErrnoInit",
|
||||
"DmTaskSetName",
|
||||
"DmTaskRun",
|
||||
"DmTaskGetInfo",
|
||||
"DmAttachLoopDevice",
|
||||
"execRun",
|
||||
"DmTaskCreate",
|
||||
"DmGetBlockSize",
|
||||
"DmTaskSetTarget",
|
||||
"DmTaskSetCookie",
|
||||
"DmUdevWait",
|
||||
"DmTaskSetSector",
|
||||
"DmTaskSetMessage",
|
||||
"DmTaskSetAddNode",
|
||||
)
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
calls.Assert(t,
|
||||
"DmTaskCreate",
|
||||
"DmTaskGetInfo",
|
||||
"sysMount",
|
||||
"Mounted",
|
||||
"DmTaskRun",
|
||||
"DmTaskSetTarget",
|
||||
"DmTaskSetSector",
|
||||
"DmTaskSetCookie",
|
||||
"DmUdevWait",
|
||||
"DmTaskSetName",
|
||||
"DmTaskSetMessage",
|
||||
"DmTaskSetAddNode",
|
||||
)
|
||||
|
||||
}()
|
||||
|
||||
runtime.GC()
|
||||
|
||||
calls.Assert(t,
|
||||
"DmTaskDestroy",
|
||||
)
|
||||
}
|
||||
|
||||
func TestDriverRemove(t *testing.T) {
|
||||
denyAllDevmapper()
|
||||
denyAllSyscall()
|
||||
defer denyAllSyscall()
|
||||
defer denyAllDevmapper()
|
||||
|
||||
calls := make(Set)
|
||||
mockAllDevmapper(calls)
|
||||
|
||||
sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) {
|
||||
calls["sysMount"] = true
|
||||
// FIXME: compare the exact source and target strings (inodes + devname)
|
||||
if expectedSource := "/dev/mapper/docker-"; !strings.HasPrefix(source, expectedSource) {
|
||||
t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedSource, source)
|
||||
}
|
||||
if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) {
|
||||
t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target)
|
||||
}
|
||||
if expectedFstype := "ext4"; fstype != expectedFstype {
|
||||
t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFstype, fstype)
|
||||
}
|
||||
if expectedFlags := uintptr(3236757504); flags != expectedFlags {
|
||||
t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
sysUnmount = func(target string, flags int) (err error) {
|
||||
calls["sysUnmount"] = true
|
||||
// FIXME: compare the exact source and target strings (inodes + devname)
|
||||
if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) {
|
||||
t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target)
|
||||
}
|
||||
if expectedFlags := 0; flags != expectedFlags {
|
||||
t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Mounted = func(mnt string) (bool, error) {
|
||||
calls["Mounted"] = true
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func() {
|
||||
d := newDriver(t)
|
||||
|
||||
calls.Assert(t,
|
||||
"DmSetDevDir",
|
||||
"DmLogWithErrnoInit",
|
||||
"DmTaskSetName",
|
||||
"DmTaskRun",
|
||||
"DmTaskGetInfo",
|
||||
"DmAttachLoopDevice",
|
||||
"execRun",
|
||||
"DmTaskCreate",
|
||||
"DmGetBlockSize",
|
||||
"DmTaskSetTarget",
|
||||
"DmTaskSetCookie",
|
||||
"DmUdevWait",
|
||||
"DmTaskSetSector",
|
||||
"DmTaskSetMessage",
|
||||
"DmTaskSetAddNode",
|
||||
)
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
calls.Assert(t,
|
||||
"DmTaskCreate",
|
||||
"DmTaskGetInfo",
|
||||
"sysMount",
|
||||
"Mounted",
|
||||
"DmTaskRun",
|
||||
"DmTaskSetTarget",
|
||||
"DmTaskSetSector",
|
||||
"DmTaskSetCookie",
|
||||
"DmUdevWait",
|
||||
"DmTaskSetName",
|
||||
"DmTaskSetMessage",
|
||||
"DmTaskSetAddNode",
|
||||
)
|
||||
|
||||
Mounted = func(mnt string) (bool, error) {
|
||||
calls["Mounted"] = true
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if err := d.Remove("1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
calls.Assert(t,
|
||||
"DmTaskRun",
|
||||
"DmTaskSetSector",
|
||||
"DmTaskSetName",
|
||||
"DmTaskSetMessage",
|
||||
"DmTaskCreate",
|
||||
"DmTaskGetInfo",
|
||||
"Mounted",
|
||||
"sysUnmount",
|
||||
)
|
||||
}()
|
||||
runtime.GC()
|
||||
|
||||
calls.Assert(t,
|
||||
"DmTaskDestroy",
|
||||
)
|
||||
}
|
||||
|
||||
func TestCleanup(t *testing.T) {
|
||||
t.Skip("FIXME: not a unit test")
|
||||
t.Skip("Unimplemented")
|
||||
d := newDriver(t)
|
||||
defer osRemoveAll(d.home)
|
||||
|
||||
mountPoints := make([]string, 2)
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Mount the id
|
||||
p, err := d.Get("1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mountPoints[0] = p
|
||||
|
||||
if err := d.Create("2", "1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
p, err = d.Get("2")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mountPoints[1] = p
|
||||
|
||||
// Ensure that all the mount points are currently mounted
|
||||
for _, p := range mountPoints {
|
||||
if mounted, err := Mounted(p); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !mounted {
|
||||
t.Fatalf("Expected %s to be mounted", p)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that devices are active
|
||||
for _, p := range []string{"1", "2"} {
|
||||
if !d.HasActivatedDevice(p) {
|
||||
t.Fatalf("Expected %s to have an active device", p)
|
||||
}
|
||||
}
|
||||
|
||||
if err := d.Cleanup(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Ensure that all the mount points are no longer mounted
|
||||
for _, p := range mountPoints {
|
||||
if mounted, err := Mounted(p); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if mounted {
|
||||
t.Fatalf("Expected %s to not be mounted", p)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that devices are no longer activated
|
||||
for _, p := range []string{"1", "2"} {
|
||||
if d.HasActivatedDevice(p) {
|
||||
t.Fatalf("Expected %s not be an active device", p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotMounted(t *testing.T) {
|
||||
t.Skip("FIXME: not a unit test")
|
||||
t.Skip("Not implemented")
|
||||
d := newDriver(t)
|
||||
defer cleanup(d)
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mounted, err := Mounted(path.Join(d.home, "mnt", "1"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if mounted {
|
||||
t.Fatal("Id 1 should not be mounted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMounted(t *testing.T) {
|
||||
t.Skip("FIXME: not a unit test")
|
||||
d := newDriver(t)
|
||||
defer cleanup(d)
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := d.Get("1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mounted, err := Mounted(path.Join(d.home, "mnt", "1"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !mounted {
|
||||
t.Fatal("Id 1 should be mounted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitCleanedDriver(t *testing.T) {
|
||||
t.Skip("FIXME: not a unit test")
|
||||
d := newDriver(t)
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := d.Get("1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := d.Cleanup(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
driver, err := Init(d.home)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
d = driver.(*Driver)
|
||||
defer cleanup(d)
|
||||
|
||||
if _, err := d.Get("1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountMountedDriver(t *testing.T) {
|
||||
t.Skip("FIXME: not a unit test")
|
||||
d := newDriver(t)
|
||||
defer cleanup(d)
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Perform get on same id to ensure that it will
|
||||
// not be mounted twice
|
||||
if _, err := d.Get("1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := d.Get("1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetReturnsValidDevice(t *testing.T) {
|
||||
t.Skip("FIXME: not a unit test")
|
||||
d := newDriver(t)
|
||||
defer cleanup(d)
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !d.HasDevice("1") {
|
||||
t.Fatalf("Expected id 1 to be in device set")
|
||||
}
|
||||
|
||||
if _, err := d.Get("1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !d.HasActivatedDevice("1") {
|
||||
t.Fatalf("Expected id 1 to be activated")
|
||||
}
|
||||
|
||||
if !d.HasInitializedDevice("1") {
|
||||
t.Fatalf("Expected id 1 to be initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDriverGetSize(t *testing.T) {
|
||||
t.Skip("FIXME: not a unit test")
|
||||
t.Skipf("Size is currently not implemented")
|
||||
|
||||
d := newDriver(t)
|
||||
defer cleanup(d)
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mountPoint, err := d.Get("1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
size := int64(1024)
|
||||
|
||||
f, err := osCreate(path.Join(mountPoint, "test_file"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := f.Truncate(size); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
// diffSize, err := d.DiffSize("1")
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// if diffSize != size {
|
||||
// t.Fatalf("Expected size %d got %d", size, diffSize)
|
||||
// }
|
||||
}
|
||||
|
||||
func assertMap(t *testing.T, m map[string]bool, keys ...string) {
|
||||
for _, key := range keys {
|
||||
if _, exists := m[key]; !exists {
|
||||
t.Fatalf("Key not set: %s", key)
|
||||
}
|
||||
delete(m, key)
|
||||
}
|
||||
if len(m) != 0 {
|
||||
t.Fatalf("Unexpected keys: %v", m)
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,25 @@
|
||||
package devmapper
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// FIXME: this is copy-pasted from the aufs driver.
|
||||
// It should be moved into the core.
|
||||
|
||||
func Mounted(mountpoint string) (bool, error) {
|
||||
mntpoint, err := os.Stat(mountpoint)
|
||||
var Mounted = func(mountpoint string) (bool, error) {
|
||||
mntpoint, err := osStat(mountpoint)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if osIsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
parent, err := os.Stat(filepath.Join(mountpoint, ".."))
|
||||
parent, err := osStat(filepath.Join(mountpoint, ".."))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
mntpointSt := mntpoint.Sys().(*syscall.Stat_t)
|
||||
parentSt := parent.Sys().(*syscall.Stat_t)
|
||||
mntpointSt := toSysStatT(mntpoint.Sys())
|
||||
parentSt := toSysStatT(parent.Sys())
|
||||
return mntpointSt.Dev != parentSt.Dev, nil
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package devmapper
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type (
|
||||
sysStatT syscall.Stat_t
|
||||
sysErrno syscall.Errno
|
||||
|
||||
osFile struct{ *os.File }
|
||||
)
|
||||
|
||||
var (
|
||||
sysMount = syscall.Mount
|
||||
sysUnmount = syscall.Unmount
|
||||
sysCloseOnExec = syscall.CloseOnExec
|
||||
sysSyscall = syscall.Syscall
|
||||
|
||||
osOpenFile = os.OpenFile
|
||||
osNewFile = os.NewFile
|
||||
osCreate = os.Create
|
||||
osStat = os.Stat
|
||||
osIsNotExist = os.IsNotExist
|
||||
osIsExist = os.IsExist
|
||||
osMkdirAll = os.MkdirAll
|
||||
osRemoveAll = os.RemoveAll
|
||||
osRename = os.Rename
|
||||
osReadlink = os.Readlink
|
||||
|
||||
execRun = func(name string, args ...string) error {
|
||||
return exec.Command(name, args...).Run()
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
sysMsMgcVal = syscall.MS_MGC_VAL
|
||||
sysMsRdOnly = syscall.MS_RDONLY
|
||||
sysEInval = syscall.EINVAL
|
||||
sysSysIoctl = syscall.SYS_IOCTL
|
||||
|
||||
osORdWr = os.O_RDWR
|
||||
osOCreate = os.O_CREATE
|
||||
)
|
||||
|
||||
func toSysStatT(i interface{}) *sysStatT {
|
||||
return (*sysStatT)(i.(*syscall.Stat_t))
|
||||
}
|
||||
+19
-15
@@ -11,11 +11,15 @@ import (
|
||||
type InitFunc func(root string) (Driver, error)
|
||||
|
||||
type Driver interface {
|
||||
String() string
|
||||
|
||||
Create(id, parent string) error
|
||||
Remove(id string) error
|
||||
|
||||
Get(id string) (dir string, err error)
|
||||
Size(id string) (bytes int64, err error)
|
||||
Exists(id string) bool
|
||||
|
||||
Status() [][2]string
|
||||
|
||||
Cleanup() error
|
||||
}
|
||||
@@ -24,16 +28,18 @@ type Differ interface {
|
||||
Diff(id string) (archive.Archive, error)
|
||||
Changes(id string) ([]archive.Change, error)
|
||||
ApplyDiff(id string, diff archive.Archive) error
|
||||
DiffSize(id string) (bytes int64, err error)
|
||||
}
|
||||
|
||||
var (
|
||||
DefaultDriver string
|
||||
// All registred drivers
|
||||
drivers map[string]InitFunc
|
||||
// Slice of drivers that should be used in an order
|
||||
priority = []string{
|
||||
"aufs",
|
||||
"devicemapper",
|
||||
"dummy",
|
||||
"vfs",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -50,25 +56,24 @@ func Register(name string, initFunc InitFunc) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDriver(name, home string) (Driver, error) {
|
||||
func GetDriver(name, home string) (Driver, error) {
|
||||
if initFunc, exists := drivers[name]; exists {
|
||||
return initFunc(path.Join(home, name))
|
||||
}
|
||||
return nil, fmt.Errorf("No such driver: %s", name)
|
||||
}
|
||||
|
||||
func New(root string) (Driver, error) {
|
||||
var driver Driver
|
||||
var lastError error
|
||||
// Use environment variable DOCKER_DRIVER to force a choice of driver
|
||||
if name := os.Getenv("DOCKER_DRIVER"); name != "" {
|
||||
return getDriver(name, root)
|
||||
func New(root string) (driver Driver, err error) {
|
||||
for _, name := range []string{os.Getenv("DOCKER_DRIVER"), DefaultDriver} {
|
||||
if name != "" {
|
||||
return GetDriver(name, root)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for priority drivers first
|
||||
for _, name := range priority {
|
||||
driver, lastError = getDriver(name, root)
|
||||
if lastError != nil {
|
||||
utils.Debugf("Error loading driver %s: %s", name, lastError)
|
||||
if driver, err = GetDriver(name, root); err != nil {
|
||||
utils.Debugf("Error loading driver %s: %s", name, err)
|
||||
continue
|
||||
}
|
||||
return driver, nil
|
||||
@@ -76,11 +81,10 @@ func New(root string) (Driver, error) {
|
||||
|
||||
// Check all registered drivers if no priority driver is found
|
||||
for _, initFunc := range drivers {
|
||||
driver, lastError = initFunc(root)
|
||||
if lastError != nil {
|
||||
if driver, err = initFunc(root); err != nil {
|
||||
continue
|
||||
}
|
||||
return driver, nil
|
||||
}
|
||||
return nil, lastError
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package dummy
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/archive"
|
||||
"github.com/dotcloud/docker/graphdriver"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
)
|
||||
|
||||
func init() {
|
||||
graphdriver.Register("dummy", Init)
|
||||
graphdriver.Register("vfs", Init)
|
||||
}
|
||||
|
||||
func Init(home string) (graphdriver.Driver, error) {
|
||||
@@ -24,13 +24,25 @@ type Driver struct {
|
||||
}
|
||||
|
||||
func (d *Driver) String() string {
|
||||
return "dummy"
|
||||
return "vfs"
|
||||
}
|
||||
|
||||
func (d *Driver) Status() [][2]string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) Cleanup() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyDir(src, dst string) error {
|
||||
cmd := exec.Command("cp", "-aT", "--reflink=auto", src, dst)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) Create(id string, parent string) error {
|
||||
dir := d.dir(id)
|
||||
if err := os.MkdirAll(path.Dir(dir), 0700); err != nil {
|
||||
@@ -46,7 +58,7 @@ func (d *Driver) Create(id string, parent string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %s", parent, err)
|
||||
}
|
||||
if err := archive.CopyWithTar(parentDir, dir); err != nil {
|
||||
if err := copyDir(parentDir, dir); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -73,6 +85,7 @@ func (d *Driver) Get(id string) (string, error) {
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
func (d *Driver) Size(id string) (int64, error) {
|
||||
return -1, fmt.Errorf("Not implemented")
|
||||
func (d *Driver) Exists(id string) bool {
|
||||
_, err := os.Stat(d.dir(id))
|
||||
return err == nil
|
||||
}
|
||||
+14
-3
@@ -37,13 +37,24 @@ DEFAULT_BUNDLES=(
|
||||
test
|
||||
dynbinary
|
||||
dyntest
|
||||
tgz
|
||||
ubuntu
|
||||
)
|
||||
|
||||
VERSION=$(cat ./VERSION)
|
||||
GITCOMMIT=$(git rev-parse --short HEAD)
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
GITCOMMIT="$GITCOMMIT-dirty"
|
||||
if [ -d .git ] && command -v git &> /dev/null; then
|
||||
GITCOMMIT=$(git rev-parse --short HEAD)
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
GITCOMMIT="$GITCOMMIT-dirty"
|
||||
fi
|
||||
elif [ "$DOCKER_GITCOMMIT" ]; then
|
||||
GITCOMMIT="$DOCKER_GITCOMMIT"
|
||||
else
|
||||
echo >&2 'error: .git directory missing and DOCKER_GITCOMMIT not specified'
|
||||
echo >&2 ' Please either build with the .git directory accessible, or specify the'
|
||||
echo >&2 ' exact (--short) commit hash you are building using DOCKER_GITCOMMIT for'
|
||||
echo >&2 ' future accountability in diagnosing build issues. Thanks!'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Use these flags when compiling the tests and final binary
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
|
||||
DEST="$1"
|
||||
BINARY="$DEST/../binary/docker-$VERSION"
|
||||
TGZ="$DEST/docker-$VERSION.tgz"
|
||||
|
||||
set -e
|
||||
|
||||
if [ ! -x "$BINARY" ]; then
|
||||
echo >&2 'error: binary must be run before tgz'
|
||||
false
|
||||
fi
|
||||
|
||||
mkdir -p "$DEST/build"
|
||||
|
||||
mkdir -p "$DEST/build/usr/local/bin"
|
||||
cp -L "$BINARY" "$DEST/build/usr/local/bin/docker"
|
||||
|
||||
tar --numeric-owner --owner 0 -C "$DEST/build" -czf "$TGZ" usr
|
||||
|
||||
rm -rf "$DEST/build"
|
||||
|
||||
echo "Created tgz: $TGZ"
|
||||
@@ -47,6 +47,7 @@ cd /go/src/github.com/dotcloud/docker
|
||||
|
||||
RELEASE_BUNDLES=(
|
||||
binary
|
||||
tgz
|
||||
ubuntu
|
||||
)
|
||||
|
||||
@@ -188,6 +189,22 @@ EOF
|
||||
echo "APT repository uploaded. Instructions available at $(s3_url)/ubuntu"
|
||||
}
|
||||
|
||||
# Upload a tgz to S3
|
||||
release_tgz() {
|
||||
[ -e bundles/$VERSION/tgz/docker-$VERSION.tgz ] || {
|
||||
echo >&2 './hack/make.sh must be run before release_binary'
|
||||
exit 1
|
||||
}
|
||||
|
||||
S3DIR=s3://$BUCKET/builds/Linux/x86_64
|
||||
s3cmd --acl-public put bundles/$VERSION/tgz/docker-$VERSION.tgz $S3DIR/docker-$VERSION.tgz
|
||||
|
||||
if [ -z "$NOLATEST" ]; then
|
||||
echo "Copying docker-$VERSION.tgz to docker-latest.tgz"
|
||||
s3cmd --acl-public cp $S3DIR/docker-$VERSION.tgz $S3DIR/docker-latest.tgz
|
||||
fi
|
||||
}
|
||||
|
||||
# Upload a static binary to S3
|
||||
release_binary() {
|
||||
[ -e bundles/$VERSION/binary/docker-$VERSION ] || {
|
||||
@@ -230,6 +247,7 @@ release_test() {
|
||||
main() {
|
||||
setup_s3
|
||||
release_binary
|
||||
release_tgz
|
||||
release_ubuntu
|
||||
release_index
|
||||
release_test
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetBoolParam(t *testing.T) {
|
||||
if ret, err := getBoolParam("true"); err != nil || !ret {
|
||||
t.Fatalf("true -> true, nil | got %t %s", ret, err)
|
||||
}
|
||||
if ret, err := getBoolParam("True"); err != nil || !ret {
|
||||
t.Fatalf("True -> true, nil | got %t %s", ret, err)
|
||||
}
|
||||
if ret, err := getBoolParam("1"); err != nil || !ret {
|
||||
t.Fatalf("1 -> true, nil | got %t %s", ret, err)
|
||||
}
|
||||
if ret, err := getBoolParam(""); err != nil || ret {
|
||||
t.Fatalf("\"\" -> false, nil | got %t %s", ret, err)
|
||||
}
|
||||
if ret, err := getBoolParam("false"); err != nil || ret {
|
||||
t.Fatalf("false -> false, nil | got %t %s", ret, err)
|
||||
}
|
||||
if ret, err := getBoolParam("0"); err != nil || ret {
|
||||
t.Fatalf("0 -> false, nil | got %t %s", ret, err)
|
||||
}
|
||||
if ret, err := getBoolParam("faux"); err == nil || ret {
|
||||
t.Fatalf("faux -> false, err | got %t %s", ret, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TesthttpError(t *testing.T) {
|
||||
r := httptest.NewRecorder()
|
||||
|
||||
httpError(r, fmt.Errorf("No such method"))
|
||||
if r.Code != http.StatusNotFound {
|
||||
t.Fatalf("Expected %d, got %d", http.StatusNotFound, r.Code)
|
||||
}
|
||||
|
||||
httpError(r, fmt.Errorf("This accound hasn't been activated"))
|
||||
if r.Code != http.StatusForbidden {
|
||||
t.Fatalf("Expected %d, got %d", http.StatusForbidden, r.Code)
|
||||
}
|
||||
|
||||
httpError(r, fmt.Errorf("Some error"))
|
||||
if r.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("Expected %d, got %d", http.StatusInternalServerError, r.Code)
|
||||
}
|
||||
}
|
||||
@@ -52,56 +52,77 @@ func LoadImage(root string) (*Image, error) {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if size, err := strconv.Atoi(string(buf)); err != nil {
|
||||
size, err := strconv.Atoi(string(buf))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
img.Size = int64(size)
|
||||
}
|
||||
img.Size = int64(size)
|
||||
}
|
||||
|
||||
return img, nil
|
||||
}
|
||||
|
||||
func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root, rootfs string) error {
|
||||
func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root, layer string) error {
|
||||
// Store the layer
|
||||
layer := rootfs
|
||||
var (
|
||||
size int64
|
||||
err error
|
||||
driver = img.graph.driver
|
||||
)
|
||||
if err := os.MkdirAll(layer, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If layerData is not nil, unpack it into the new layer
|
||||
if layerData != nil {
|
||||
if differ, ok := img.graph.driver.(graphdriver.Differ); ok {
|
||||
if differ, ok := driver.(graphdriver.Differ); ok {
|
||||
if err := differ.ApplyDiff(img.ID, layerData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if size, err = differ.DiffSize(img.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
start := time.Now()
|
||||
start := time.Now().UTC()
|
||||
utils.Debugf("Start untar layer")
|
||||
if err := archive.ApplyLayer(layer, layerData); err != nil {
|
||||
return err
|
||||
}
|
||||
utils.Debugf("Untar time: %vs", time.Now().Sub(start).Seconds())
|
||||
utils.Debugf("Untar time: %vs", time.Now().UTC().Sub(start).Seconds())
|
||||
|
||||
if img.Parent == "" {
|
||||
if size, err = utils.TreeSize(layer); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
parent, err := driver.Get(img.Parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
changes, err := archive.ChangesDirs(layer, parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if size = archive.ChangesSize(layer, changes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If raw json is provided, then use it
|
||||
if jsonData != nil {
|
||||
return ioutil.WriteFile(jsonPath(root), jsonData, 0600)
|
||||
} else { // Otherwise, unmarshal the image
|
||||
jsonData, err := json.Marshal(img)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Compute and save the size of the rootfs
|
||||
size, err := utils.TreeSize(rootfs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error computing size of rootfs %s: %s", img.ID, err)
|
||||
// Otherwise, unmarshal the image
|
||||
if jsonData, err = json.Marshal(img); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
img.Size = size
|
||||
if err := img.SaveSize(root); err != nil {
|
||||
return err
|
||||
@@ -123,15 +144,32 @@ func jsonPath(root string) string {
|
||||
}
|
||||
|
||||
// TarLayer returns a tar archive of the image's filesystem layer.
|
||||
func (image *Image) TarLayer(compression archive.Compression) (archive.Archive, error) {
|
||||
if image.graph == nil {
|
||||
return nil, fmt.Errorf("Can't load storage driver for unregistered image %s", image.ID)
|
||||
func (img *Image) TarLayer() (archive.Archive, error) {
|
||||
if img.graph == nil {
|
||||
return nil, fmt.Errorf("Can't load storage driver for unregistered image %s", img.ID)
|
||||
}
|
||||
layerPath, err := image.graph.driver.Get(image.ID)
|
||||
driver := img.graph.driver
|
||||
if differ, ok := driver.(graphdriver.Differ); ok {
|
||||
return differ.Diff(img.ID)
|
||||
}
|
||||
|
||||
imgFs, err := driver.Get(img.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return archive.Tar(layerPath, compression)
|
||||
if img.Parent == "" {
|
||||
return archive.Tar(imgFs, archive.Uncompressed)
|
||||
} else {
|
||||
parentFs, err := driver.Get(img.Parent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
changes, err := archive.ChangesDirs(imgFs, parentFs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return archive.ExportChanges(imgFs, changes)
|
||||
}
|
||||
}
|
||||
|
||||
func ValidateID(id string) error {
|
||||
@@ -211,6 +249,25 @@ func (img *Image) getParentsSize(size int64) int64 {
|
||||
return parentImage.getParentsSize(size)
|
||||
}
|
||||
|
||||
// Depth returns the number of parents for a
|
||||
// current image
|
||||
func (img *Image) Depth() (int, error) {
|
||||
var (
|
||||
count = 0
|
||||
parent = img
|
||||
err error
|
||||
)
|
||||
|
||||
for parent != nil {
|
||||
count++
|
||||
parent, err = parent.GetParent()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// Build an Image object from raw json data
|
||||
func NewImgJSON(src []byte) (*Image, error) {
|
||||
ret := &Image{}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"github.com/dotcloud/docker/auth"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// FIXME: these tests have an external dependency on a staging index hosted
|
||||
// on the docker.io infrastructure. That dependency should be removed.
|
||||
// - Unit tests should have no side-effect dependencies.
|
||||
// - Integration tests should have side-effects limited to the host environment being tested.
|
||||
|
||||
func TestLogin(t *testing.T) {
|
||||
os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
|
||||
defer os.Setenv("DOCKER_INDEX_URL", "")
|
||||
authConfig := &auth.AuthConfig{Username: "unittester", Password: "surlautrerivejetattendrai", Email: "noise+unittester@dotcloud.com"}
|
||||
status, err := auth.Login(authConfig, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if status != "Login Succeeded" {
|
||||
t.Fatalf("Expected status \"Login Succeeded\", found \"%s\" instead", status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateAccount(t *testing.T) {
|
||||
os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
|
||||
defer os.Setenv("DOCKER_INDEX_URL", "")
|
||||
tokenBuffer := make([]byte, 16)
|
||||
_, err := rand.Read(tokenBuffer)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
token := hex.EncodeToString(tokenBuffer)[:12]
|
||||
username := "ut" + token
|
||||
authConfig := &auth.AuthConfig{Username: username, Password: "test42", Email: "docker-ut+" + token + "@example.com"}
|
||||
status, err := auth.Login(authConfig, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expectedStatus := "Account created. Please use the confirmation link we sent" +
|
||||
" to your e-mail to activate it."
|
||||
if status != expectedStatus {
|
||||
t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status)
|
||||
}
|
||||
|
||||
status, err = auth.Login(authConfig, nil)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error but found nil instead")
|
||||
}
|
||||
|
||||
expectedError := "Login: Account is not Active"
|
||||
|
||||
if !strings.Contains(err.Error(), expectedError) {
|
||||
t.Fatalf("Expected message \"%s\" but found \"%s\" instead", expectedError, err)
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@ package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker"
|
||||
"github.com/dotcloud/docker/archive"
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -14,7 +16,7 @@ import (
|
||||
// mkTestContext generates a build context from the contents of the provided dockerfile.
|
||||
// This context is suitable for use as an argument to BuildFile.Build()
|
||||
func mkTestContext(dockerfile string, files [][2]string, t *testing.T) archive.Archive {
|
||||
context, err := mkBuildContext(dockerfile, files)
|
||||
context, err := docker.MkBuildContext(dockerfile, files)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -228,17 +230,15 @@ func TestBuild(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func buildImage(context testContextTemplate, t *testing.T, srv *Server, useCache bool) *Image {
|
||||
if srv == nil {
|
||||
runtime := mkRuntime(t)
|
||||
func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, useCache bool) *docker.Image {
|
||||
if eng == nil {
|
||||
eng = NewTestEngine(t)
|
||||
runtime := mkRuntimeFromEngine(eng, t)
|
||||
// FIXME: we might not need runtime, why not simply nuke
|
||||
// the engine?
|
||||
defer nuke(runtime)
|
||||
|
||||
srv = &Server{
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
}
|
||||
}
|
||||
srv := mkServerFromEngine(eng, t)
|
||||
|
||||
httpServer, err := mkTestingFileServer(context.remoteFiles)
|
||||
if err != nil {
|
||||
@@ -252,10 +252,17 @@ func buildImage(context testContextTemplate, t *testing.T, srv *Server, useCache
|
||||
}
|
||||
port := httpServer.URL[idx+1:]
|
||||
|
||||
ip := srv.runtime.networkManager.bridgeNetwork.IP
|
||||
iIP := eng.Hack_GetGlobalVar("httpapi.bridgeIP")
|
||||
if iIP == nil {
|
||||
t.Fatal("Legacy bridgeIP field not set in engine")
|
||||
}
|
||||
ip, ok := iIP.(net.IP)
|
||||
if !ok {
|
||||
panic("Legacy bridgeIP field in engine does not cast to net.IP")
|
||||
}
|
||||
dockerfile := constructDockerfile(context.dockerfile, ip, port)
|
||||
|
||||
buildfile := NewBuildFile(srv, ioutil.Discard, false, useCache, false)
|
||||
buildfile := docker.NewBuildFile(srv, ioutil.Discard, false, useCache, false)
|
||||
id, err := buildfile.Build(mkTestContext(dockerfile, context.files, t))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -368,20 +375,14 @@ func TestBuildEntrypoint(t *testing.T) {
|
||||
// testing #1405 - config.Cmd does not get cleaned up if
|
||||
// utilizing cache
|
||||
func TestBuildEntrypointRunCleanup(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
}
|
||||
eng := NewTestEngine(t)
|
||||
defer nuke(mkRuntimeFromEngine(eng, t))
|
||||
|
||||
img := buildImage(testContextTemplate{`
|
||||
from {IMAGE}
|
||||
run echo "hello"
|
||||
`,
|
||||
nil, nil}, t, srv, true)
|
||||
nil, nil}, t, eng, true)
|
||||
|
||||
img = buildImage(testContextTemplate{`
|
||||
from {IMAGE}
|
||||
@@ -389,7 +390,7 @@ func TestBuildEntrypointRunCleanup(t *testing.T) {
|
||||
add foo /foo
|
||||
entrypoint ["/bin/echo"]
|
||||
`,
|
||||
[][2]string{{"foo", "HEYO"}}, nil}, t, srv, true)
|
||||
[][2]string{{"foo", "HEYO"}}, nil}, t, eng, true)
|
||||
|
||||
if len(img.Config.Cmd) != 0 {
|
||||
t.Fail()
|
||||
@@ -397,14 +398,8 @@ func TestBuildEntrypointRunCleanup(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBuildImageWithCache(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
}
|
||||
eng := NewTestEngine(t)
|
||||
defer nuke(mkRuntimeFromEngine(eng, t))
|
||||
|
||||
template := testContextTemplate{`
|
||||
from {IMAGE}
|
||||
@@ -412,11 +407,11 @@ func TestBuildImageWithCache(t *testing.T) {
|
||||
`,
|
||||
nil, nil}
|
||||
|
||||
img := buildImage(template, t, srv, true)
|
||||
img := buildImage(template, t, eng, true)
|
||||
imageId := img.ID
|
||||
|
||||
img = nil
|
||||
img = buildImage(template, t, srv, true)
|
||||
img = buildImage(template, t, eng, true)
|
||||
|
||||
if imageId != img.ID {
|
||||
t.Logf("Image ids should match: %s != %s", imageId, img.ID)
|
||||
@@ -425,14 +420,8 @@ func TestBuildImageWithCache(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBuildImageWithoutCache(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
}
|
||||
eng := NewTestEngine(t)
|
||||
defer nuke(mkRuntimeFromEngine(eng, t))
|
||||
|
||||
template := testContextTemplate{`
|
||||
from {IMAGE}
|
||||
@@ -440,11 +429,11 @@ func TestBuildImageWithoutCache(t *testing.T) {
|
||||
`,
|
||||
nil, nil}
|
||||
|
||||
img := buildImage(template, t, srv, true)
|
||||
img := buildImage(template, t, eng, true)
|
||||
imageId := img.ID
|
||||
|
||||
img = nil
|
||||
img = buildImage(template, t, srv, false)
|
||||
img = buildImage(template, t, eng, false)
|
||||
|
||||
if imageId == img.ID {
|
||||
t.Logf("Image ids should not match: %s == %s", imageId, img.ID)
|
||||
@@ -453,14 +442,9 @@ func TestBuildImageWithoutCache(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestForbiddenContextPath(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
}
|
||||
eng := NewTestEngine(t)
|
||||
defer nuke(mkRuntimeFromEngine(eng, t))
|
||||
srv := mkServerFromEngine(eng, t)
|
||||
|
||||
context := testContextTemplate{`
|
||||
from {IMAGE}
|
||||
@@ -481,10 +465,17 @@ func TestForbiddenContextPath(t *testing.T) {
|
||||
}
|
||||
port := httpServer.URL[idx+1:]
|
||||
|
||||
ip := srv.runtime.networkManager.bridgeNetwork.IP
|
||||
iIP := eng.Hack_GetGlobalVar("httpapi.bridgeIP")
|
||||
if iIP == nil {
|
||||
t.Fatal("Legacy bridgeIP field not set in engine")
|
||||
}
|
||||
ip, ok := iIP.(net.IP)
|
||||
if !ok {
|
||||
panic("Legacy bridgeIP field in engine does not cast to net.IP")
|
||||
}
|
||||
dockerfile := constructDockerfile(context.dockerfile, ip, port)
|
||||
|
||||
buildfile := NewBuildFile(srv, ioutil.Discard, false, true, false)
|
||||
buildfile := docker.NewBuildFile(srv, ioutil.Discard, false, true, false)
|
||||
_, err = buildfile.Build(mkTestContext(dockerfile, context.files, t))
|
||||
|
||||
if err == nil {
|
||||
@@ -499,14 +490,8 @@ func TestForbiddenContextPath(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBuildADDFileNotFound(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
}
|
||||
eng := NewTestEngine(t)
|
||||
defer nuke(mkRuntimeFromEngine(eng, t))
|
||||
|
||||
context := testContextTemplate{`
|
||||
from {IMAGE}
|
||||
@@ -526,10 +511,17 @@ func TestBuildADDFileNotFound(t *testing.T) {
|
||||
}
|
||||
port := httpServer.URL[idx+1:]
|
||||
|
||||
ip := srv.runtime.networkManager.bridgeNetwork.IP
|
||||
iIP := eng.Hack_GetGlobalVar("httpapi.bridgeIP")
|
||||
if iIP == nil {
|
||||
t.Fatal("Legacy bridgeIP field not set in engine")
|
||||
}
|
||||
ip, ok := iIP.(net.IP)
|
||||
if !ok {
|
||||
panic("Legacy bridgeIP field in engine does not cast to net.IP")
|
||||
}
|
||||
dockerfile := constructDockerfile(context.dockerfile, ip, port)
|
||||
|
||||
buildfile := NewBuildFile(srv, ioutil.Discard, false, true, false)
|
||||
buildfile := docker.NewBuildFile(mkServerFromEngine(eng, t), ioutil.Discard, false, true, false)
|
||||
_, err = buildfile.Build(mkTestContext(dockerfile, context.files, t))
|
||||
|
||||
if err == nil {
|
||||
@@ -544,26 +536,20 @@ func TestBuildADDFileNotFound(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBuildInheritance(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
}
|
||||
eng := NewTestEngine(t)
|
||||
defer nuke(mkRuntimeFromEngine(eng, t))
|
||||
|
||||
img := buildImage(testContextTemplate{`
|
||||
from {IMAGE}
|
||||
expose 4243
|
||||
`,
|
||||
nil, nil}, t, srv, true)
|
||||
nil, nil}, t, eng, true)
|
||||
|
||||
img2 := buildImage(testContextTemplate{fmt.Sprintf(`
|
||||
from %s
|
||||
entrypoint ["/bin/echo"]
|
||||
`, img.ID),
|
||||
nil, nil}, t, srv, true)
|
||||
nil, nil}, t, eng, true)
|
||||
|
||||
// from child
|
||||
if img2.Config.Entrypoint[0] != "/bin/echo" {
|
||||
@@ -3,6 +3,8 @@ package docker
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker"
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -66,8 +68,8 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error
|
||||
func TestRunHostname(t *testing.T) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
cli := docker.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
@@ -111,8 +113,8 @@ func TestRunHostname(t *testing.T) {
|
||||
func TestRunWorkdir(t *testing.T) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
cli := docker.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
@@ -156,8 +158,8 @@ func TestRunWorkdir(t *testing.T) {
|
||||
func TestRunWorkdirExists(t *testing.T) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
cli := docker.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
@@ -201,8 +203,8 @@ func TestRunExit(t *testing.T) {
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
cli := docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
@@ -254,8 +256,8 @@ func TestRunDisconnect(t *testing.T) {
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
cli := docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
@@ -287,7 +289,7 @@ func TestRunDisconnect(t *testing.T) {
|
||||
setTimeout(t, "Waiting for /bin/cat to exit timed out", 2*time.Second, func() {
|
||||
container := globalRuntime.List()[0]
|
||||
container.Wait()
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
t.Fatalf("/bin/cat is still running after closing stdin")
|
||||
}
|
||||
})
|
||||
@@ -299,8 +301,8 @@ func TestRunDisconnectTty(t *testing.T) {
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
cli := docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
@@ -317,7 +319,7 @@ func TestRunDisconnectTty(t *testing.T) {
|
||||
for {
|
||||
// Client disconnect after run -i should keep stdin out in TTY mode
|
||||
l := globalRuntime.List()
|
||||
if len(l) == 1 && l[0].State.Running {
|
||||
if len(l) == 1 && l[0].State.IsRunning() {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
@@ -327,7 +329,7 @@ func TestRunDisconnectTty(t *testing.T) {
|
||||
// Client disconnect after run -i should keep stdin out in TTY mode
|
||||
container := globalRuntime.List()[0]
|
||||
|
||||
setTimeout(t, "Read/Write assertion timed out", 2000*time.Second, func() {
|
||||
setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -343,7 +345,7 @@ func TestRunDisconnectTty(t *testing.T) {
|
||||
|
||||
// Give some time to monitor to do his thing
|
||||
container.WaitTimeout(500 * time.Millisecond)
|
||||
if !container.State.Running {
|
||||
if !container.State.IsRunning() {
|
||||
t.Fatalf("/bin/cat should still be running after closing stdin (tty mode)")
|
||||
}
|
||||
}
|
||||
@@ -356,8 +358,8 @@ func TestRunAttachStdin(t *testing.T) {
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
cli := docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
@@ -420,8 +422,8 @@ func TestRunDetach(t *testing.T) {
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
cli := docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
@@ -452,7 +454,7 @@ func TestRunDetach(t *testing.T) {
|
||||
})
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
if !container.State.Running {
|
||||
if !container.State.IsRunning() {
|
||||
t.Fatal("The detached container should be still running")
|
||||
}
|
||||
|
||||
@@ -466,8 +468,8 @@ func TestAttachDetach(t *testing.T) {
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
cli := docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
@@ -477,7 +479,7 @@ func TestAttachDetach(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
var container *Container
|
||||
var container *docker.Container
|
||||
|
||||
setTimeout(t, "Reading container's id timed out", 10*time.Second, func() {
|
||||
buf := make([]byte, 1024)
|
||||
@@ -498,7 +500,7 @@ func TestAttachDetach(t *testing.T) {
|
||||
|
||||
stdin, stdinPipe = io.Pipe()
|
||||
stdout, stdoutPipe = io.Pipe()
|
||||
cli = NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
cli = docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
|
||||
ch = make(chan struct{})
|
||||
go func() {
|
||||
@@ -532,7 +534,7 @@ func TestAttachDetach(t *testing.T) {
|
||||
})
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
if !container.State.Running {
|
||||
if !container.State.IsRunning() {
|
||||
t.Fatal("The detached container should be still running")
|
||||
}
|
||||
|
||||
@@ -546,8 +548,8 @@ func TestAttachDetachTruncatedID(t *testing.T) {
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
cli := docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
go stdout.Read(make([]byte, 1024))
|
||||
setTimeout(t, "Starting container timed out", 2*time.Second, func() {
|
||||
@@ -560,7 +562,7 @@ func TestAttachDetachTruncatedID(t *testing.T) {
|
||||
|
||||
stdin, stdinPipe = io.Pipe()
|
||||
stdout, stdoutPipe = io.Pipe()
|
||||
cli = NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
cli = docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
@@ -594,7 +596,7 @@ func TestAttachDetachTruncatedID(t *testing.T) {
|
||||
})
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
if !container.State.Running {
|
||||
if !container.State.IsRunning() {
|
||||
t.Fatal("The detached container should be still running")
|
||||
}
|
||||
|
||||
@@ -608,8 +610,8 @@ func TestAttachDisconnect(t *testing.T) {
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
cli := docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
go func() {
|
||||
// Start a process in daemon mode
|
||||
@@ -627,7 +629,7 @@ func TestAttachDisconnect(t *testing.T) {
|
||||
setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() {
|
||||
for {
|
||||
l := globalRuntime.List()
|
||||
if len(l) == 1 && l[0].State.Running {
|
||||
if len(l) == 1 && l[0].State.IsRunning() {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
@@ -663,7 +665,7 @@ func TestAttachDisconnect(t *testing.T) {
|
||||
// We closed stdin, expect /bin/cat to still be running
|
||||
// Wait a little bit to make sure container.monitor() did his thing
|
||||
err := container.WaitTimeout(500 * time.Millisecond)
|
||||
if err == nil || !container.State.Running {
|
||||
if err == nil || !container.State.IsRunning() {
|
||||
t.Fatalf("/bin/cat is not running after closing stdin")
|
||||
}
|
||||
|
||||
@@ -677,8 +679,8 @@ func TestAttachDisconnect(t *testing.T) {
|
||||
func TestRunAutoRemove(t *testing.T) {
|
||||
t.Skip("Fixme. Skipping test for now, race condition")
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
cli := docker.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
@@ -712,8 +714,8 @@ func TestRunAutoRemove(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCmdLogs(t *testing.T) {
|
||||
cli := NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
cli := docker.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
if err := cli.CmdRun(unitTestImageID, "sh", "-c", "ls -l"); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -730,8 +732,8 @@ func TestCmdLogs(t *testing.T) {
|
||||
// Expected behaviour: using / as a bind mount source should throw an error
|
||||
func TestRunErrorBindMountRootSource(t *testing.T) {
|
||||
|
||||
cli := NewDockerCli(nil, nil, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
cli := docker.NewDockerCli(nil, nil, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
@@ -749,8 +751,8 @@ func TestRunErrorBindMountRootSource(t *testing.T) {
|
||||
// Expected behaviour: error out when attempting to bind mount non-existing source paths
|
||||
func TestRunErrorBindNonExistingSource(t *testing.T) {
|
||||
|
||||
cli := NewDockerCli(nil, nil, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
cli := docker.NewDockerCli(nil, nil, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
@@ -768,11 +770,10 @@ func TestRunErrorBindNonExistingSource(t *testing.T) {
|
||||
func TestImagesViz(t *testing.T) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
cli := docker.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
srv := &Server{runtime: globalRuntime}
|
||||
image := buildTestImages(t, srv)
|
||||
image := buildTestImages(t, globalEngine)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
@@ -819,11 +820,10 @@ func TestImagesViz(t *testing.T) {
|
||||
func TestImagesTree(t *testing.T) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
cli := docker.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
srv := &Server{runtime: globalRuntime}
|
||||
image := buildTestImages(t, srv)
|
||||
image := buildTestImages(t, globalEngine)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
@@ -866,7 +866,7 @@ func TestImagesTree(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func buildTestImages(t *testing.T, srv *Server) *Image {
|
||||
func buildTestImages(t *testing.T, eng *engine.Engine) *docker.Image {
|
||||
|
||||
var testBuilder = testContextTemplate{
|
||||
`
|
||||
@@ -879,9 +879,9 @@ run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
|
||||
nil,
|
||||
nil,
|
||||
}
|
||||
image := buildImage(testBuilder, t, srv, true)
|
||||
image := buildImage(testBuilder, t, eng, true)
|
||||
|
||||
err := srv.ContainerTag(image.ID, "test", "latest", false)
|
||||
err := mkServerFromEngine(eng, t).ContainerTag(image.ID, "test", "latest", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -901,8 +901,8 @@ func TestRunCidFile(t *testing.T) {
|
||||
}
|
||||
tmpCidFile := path.Join(tmpDir, "cid")
|
||||
|
||||
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
cli := docker.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalEngine, t)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,8 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"errors"
|
||||
"github.com/dotcloud/docker"
|
||||
"github.com/dotcloud/docker/archive"
|
||||
"github.com/dotcloud/docker/graphdriver"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
@@ -15,9 +14,41 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
graph := tempGraph(t)
|
||||
func TestMount(t *testing.T) {
|
||||
graph, driver := tempGraph(t)
|
||||
defer os.RemoveAll(graph.Root)
|
||||
defer driver.Cleanup()
|
||||
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image, err := graph.Create(archive, nil, "Testing", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tmp, err := ioutil.TempDir("", "docker-test-graph-mount-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
rootfs := path.Join(tmp, "rootfs")
|
||||
if err := os.MkdirAll(rootfs, 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rw := path.Join(tmp, "rw")
|
||||
if err := os.MkdirAll(rw, 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := driver.Get(image.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
graph, _ := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
// Root should exist
|
||||
if _, err := os.Stat(graph.Root); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -32,17 +63,16 @@ func TestInit(t *testing.T) {
|
||||
|
||||
// Test that Register can be interrupted cleanly without side effects
|
||||
func TestInterruptedRegister(t *testing.T) {
|
||||
graph := tempGraph(t)
|
||||
defer os.RemoveAll(graph.Root)
|
||||
graph, _ := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
badArchive, w := io.Pipe() // Use a pipe reader as a fake archive which never yields data
|
||||
image := &Image{
|
||||
ID: GenerateID(),
|
||||
image := &docker.Image{
|
||||
ID: docker.GenerateID(),
|
||||
Comment: "testing",
|
||||
Created: time.Now(),
|
||||
}
|
||||
go graph.Register(nil, badArchive, image)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
w.CloseWithError(errors.New("But I'm not a tarball!")) // (Nobody's perfect, darling)
|
||||
graph.Register(nil, badArchive, image)
|
||||
if _, err := graph.Get(image.ID); err == nil {
|
||||
t.Fatal("Image should not exist after Register is interrupted")
|
||||
}
|
||||
@@ -59,8 +89,8 @@ func TestInterruptedRegister(t *testing.T) {
|
||||
// FIXME: Do more extensive tests (ex: create multiple, delete, recreate;
|
||||
// create multiple, check the amount of images and paths, etc..)
|
||||
func TestGraphCreate(t *testing.T) {
|
||||
graph := tempGraph(t)
|
||||
defer os.RemoveAll(graph.Root)
|
||||
graph, _ := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -69,14 +99,14 @@ func TestGraphCreate(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ValidateID(image.ID); err != nil {
|
||||
if err := docker.ValidateID(image.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if image.Comment != "Testing" {
|
||||
t.Fatalf("Wrong comment: should be '%s', not '%s'", "Testing", image.Comment)
|
||||
}
|
||||
if image.DockerVersion != VERSION {
|
||||
t.Fatalf("Wrong docker_version: should be '%s', not '%s'", VERSION, image.DockerVersion)
|
||||
if image.DockerVersion != docker.VERSION {
|
||||
t.Fatalf("Wrong docker_version: should be '%s', not '%s'", docker.VERSION, image.DockerVersion)
|
||||
}
|
||||
images, err := graph.Map()
|
||||
if err != nil {
|
||||
@@ -90,14 +120,14 @@ func TestGraphCreate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
graph := tempGraph(t)
|
||||
defer os.RemoveAll(graph.Root)
|
||||
graph, _ := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image := &Image{
|
||||
ID: GenerateID(),
|
||||
image := &docker.Image{
|
||||
ID: docker.GenerateID(),
|
||||
Comment: "testing",
|
||||
Created: time.Now(),
|
||||
}
|
||||
@@ -122,45 +152,10 @@ func TestRegister(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMount(t *testing.T) {
|
||||
graph := tempGraph(t)
|
||||
defer os.RemoveAll(graph.Root)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image, err := graph.Create(archive, nil, "Testing", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tmp, err := ioutil.TempDir("", "docker-test-graph-mount-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
rootfs := path.Join(tmp, "rootfs")
|
||||
if err := os.MkdirAll(rootfs, 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rw := path.Join(tmp, "rw")
|
||||
if err := os.MkdirAll(rw, 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := graph.driver.Get(image.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// FIXME: test for mount contents
|
||||
defer func() {
|
||||
if err := graph.driver.Cleanup(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Test that an image can be deleted by its shorthand prefix
|
||||
func TestDeletePrefix(t *testing.T) {
|
||||
graph := tempGraph(t)
|
||||
defer os.RemoveAll(graph.Root)
|
||||
graph, _ := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
img := createTestImage(graph, t)
|
||||
if err := graph.Delete(utils.TruncateID(img.ID)); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -168,7 +163,7 @@ func TestDeletePrefix(t *testing.T) {
|
||||
assertNImages(graph, t, 0)
|
||||
}
|
||||
|
||||
func createTestImage(graph *Graph, t *testing.T) *Image {
|
||||
func createTestImage(graph *docker.Graph, t *testing.T) *docker.Image {
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -181,8 +176,8 @@ func createTestImage(graph *Graph, t *testing.T) *Image {
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
graph := tempGraph(t)
|
||||
defer os.RemoveAll(graph.Root)
|
||||
graph, _ := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -245,22 +240,22 @@ func TestByParent(t *testing.T) {
|
||||
archive2, _ := fakeTar()
|
||||
archive3, _ := fakeTar()
|
||||
|
||||
graph := tempGraph(t)
|
||||
defer os.RemoveAll(graph.Root)
|
||||
parentImage := &Image{
|
||||
ID: GenerateID(),
|
||||
graph, _ := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
parentImage := &docker.Image{
|
||||
ID: docker.GenerateID(),
|
||||
Comment: "parent",
|
||||
Created: time.Now(),
|
||||
Parent: "",
|
||||
}
|
||||
childImage1 := &Image{
|
||||
ID: GenerateID(),
|
||||
childImage1 := &docker.Image{
|
||||
ID: docker.GenerateID(),
|
||||
Comment: "child1",
|
||||
Created: time.Now(),
|
||||
Parent: parentImage.ID,
|
||||
}
|
||||
childImage2 := &Image{
|
||||
ID: GenerateID(),
|
||||
childImage2 := &docker.Image{
|
||||
ID: docker.GenerateID(),
|
||||
Comment: "child2",
|
||||
Created: time.Now(),
|
||||
Parent: parentImage.ID,
|
||||
@@ -279,7 +274,11 @@ func TestByParent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func assertNImages(graph *Graph, t *testing.T, n int) {
|
||||
/*
|
||||
* HELPER FUNCTIONS
|
||||
*/
|
||||
|
||||
func assertNImages(graph *docker.Graph, t *testing.T, n int) {
|
||||
if images, err := graph.Map(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if actualN := len(images); actualN != n {
|
||||
@@ -287,24 +286,25 @@ func assertNImages(graph *Graph, t *testing.T, n int) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* HELPER FUNCTIONS
|
||||
*/
|
||||
func tempGraph(t *testing.T) (*docker.Graph, graphdriver.Driver) {
|
||||
tmp, err := ioutil.TempDir("", "docker-graph-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
driver, err := graphdriver.New(tmp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
graph, err := docker.NewGraph(tmp, driver)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return graph, driver
|
||||
}
|
||||
|
||||
func tempGraph(t *testing.T) *Graph {
|
||||
tmp, err := ioutil.TempDir("", "docker-graph-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
backend, err := graphdriver.New(tmp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
graph, err := NewGraph(tmp, backend)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return graph
|
||||
func nukeGraph(graph *docker.Graph) {
|
||||
graph.Driver().Cleanup()
|
||||
os.RemoveAll(graph.Root)
|
||||
}
|
||||
|
||||
func testArchive(t *testing.T) archive.Archive {
|
||||
@@ -314,20 +314,3 @@ func testArchive(t *testing.T) archive.Archive {
|
||||
}
|
||||
return archive
|
||||
}
|
||||
|
||||
func fakeTar() (io.Reader, error) {
|
||||
content := []byte("Hello world!\n")
|
||||
buf := new(bytes.Buffer)
|
||||
tw := tar.NewWriter(buf)
|
||||
for _, name := range []string{"/etc/postgres/postgres.conf", "/etc/passwd", "/var/log/postgres/postgres.conf"} {
|
||||
hdr := new(tar.Header)
|
||||
hdr.Size = int64(len(content))
|
||||
hdr.Name = name
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tw.Write([]byte(content))
|
||||
}
|
||||
tw.Close()
|
||||
return buf, nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user