mirror of
https://github.com/clearlinux/docker.git
synced 2026-08-21 13:18:00 +00:00
Compare commits
188 Commits
v0.7.0-rc6
...
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 | |||
| 66beafa9f3 | |||
| 8e5ab5bfca | |||
| 4f9f83d6c6 | |||
| 145c2008ae | |||
| aeb304b37c | |||
| 78c843c8ef | |||
| ac821f2446 | |||
| 2fe4467d73 | |||
| 4b80ec9aae | |||
| fef41ef7bf | |||
| fe302fbfd2 | |||
| 72d02ecdde | |||
| baa687bed2 | |||
| 30ea0bebce | |||
| bc74f65068 | |||
| 152459b727 | |||
| 27159ce6ba | |||
| 0013aa7d9f | |||
| 1d7f22c0d4 | |||
| 6a693176d6 | |||
| 6d420407ca |
@@ -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
|
||||
|
||||
|
||||
+3
-4
@@ -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
|
||||
|
||||
|
||||
@@ -254,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" {
|
||||
@@ -534,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
|
||||
@@ -967,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:]
|
||||
@@ -1036,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,
|
||||
@@ -1052,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,
|
||||
|
||||
+18
-11
@@ -71,17 +71,27 @@ func createSampleDir(t *testing.T, root string) {
|
||||
{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(path.Join(root, info.path), info.permissions); err != nil {
|
||||
if err := os.MkdirAll(p, info.permissions); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else if info.filetype == Regular {
|
||||
if err := ioutil.WriteFile(path.Join(root, info.path), []byte(info.contents), info.permissions); err != nil {
|
||||
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, path.Join(root, info.path)); err != nil {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -200,6 +210,9 @@ func TestChangesDirsMutated(t *testing.T) {
|
||||
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)
|
||||
@@ -225,8 +238,7 @@ func TestChangesDirsMutated(t *testing.T) {
|
||||
{"/symlinknew", ChangeAdd},
|
||||
}
|
||||
|
||||
i := 0
|
||||
for ; i < max(len(changes), len(expectedChanges)); i++ {
|
||||
for i := 0; i < max(len(changes), len(expectedChanges)); i++ {
|
||||
if i >= len(expectedChanges) {
|
||||
t.Fatalf("unexpected change %s\n", changes[i].String())
|
||||
}
|
||||
@@ -240,14 +252,9 @@ func TestChangesDirsMutated(t *testing.T) {
|
||||
} 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\n", expectedChanges[i].String())
|
||||
t.Fatalf("no change for expected change %s != %s\n", expectedChanges[i].String(), changes[i].String())
|
||||
}
|
||||
}
|
||||
for ; i < len(expectedChanges); i++ {
|
||||
}
|
||||
|
||||
os.RemoveAll(src)
|
||||
os.RemoveAll(dst)
|
||||
}
|
||||
|
||||
func TestApplyLayer(t *testing.T) {
|
||||
|
||||
+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{}{}
|
||||
|
||||
+395
-128
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
@@ -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
|
||||
}
|
||||
@@ -495,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
|
||||
@@ -522,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
|
||||
@@ -565,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 {
|
||||
@@ -577,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.")
|
||||
@@ -593,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{}
|
||||
@@ -641,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
|
||||
}
|
||||
@@ -699,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
|
||||
}
|
||||
@@ -731,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
|
||||
}
|
||||
@@ -773,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
|
||||
}
|
||||
@@ -809,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")
|
||||
|
||||
@@ -845,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)
|
||||
@@ -866,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")
|
||||
|
||||
@@ -900,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
|
||||
}
|
||||
@@ -922,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
|
||||
@@ -956,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
|
||||
}
|
||||
@@ -1018,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
|
||||
@@ -1078,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")
|
||||
@@ -1202,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 {
|
||||
@@ -1277,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.")
|
||||
@@ -1344,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))
|
||||
@@ -1366,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
|
||||
}
|
||||
@@ -1418,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
|
||||
@@ -1451,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
|
||||
}
|
||||
@@ -1468,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
|
||||
}
|
||||
@@ -1494,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
|
||||
}
|
||||
@@ -1521,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 {
|
||||
@@ -1543,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")
|
||||
}
|
||||
|
||||
@@ -1576,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")
|
||||
@@ -1632,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)
|
||||
@@ -1649,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
|
||||
|
||||
@@ -1688,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
|
||||
@@ -1721,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
|
||||
}
|
||||
@@ -1731,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)
|
||||
}
|
||||
|
||||
@@ -1775,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
|
||||
}
|
||||
@@ -1794,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)
|
||||
}
|
||||
}
|
||||
@@ -1830,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")
|
||||
@@ -1878,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)
|
||||
@@ -1904,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
|
||||
}
|
||||
@@ -1947,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]
|
||||
@@ -1967,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 {
|
||||
@@ -2078,7 +2340,7 @@ 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)
|
||||
return utils.DisplayJSONMessagesStream(resp.Body, out, cli.isTerminal)
|
||||
}
|
||||
if _, err := io.Copy(out, resp.Body); err != nil {
|
||||
return err
|
||||
@@ -2086,7 +2348,12 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, 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, "/")
|
||||
@@ -2116,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
|
||||
@@ -2217,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
|
||||
}
|
||||
@@ -2267,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 {
|
||||
|
||||
+33
-245
@@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/archive"
|
||||
"github.com/dotcloud/docker/graphdriver"
|
||||
@@ -18,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.
|
||||
|
||||
@@ -159,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 {
|
||||
@@ -710,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() {
|
||||
@@ -1046,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{})
|
||||
@@ -1054,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()
|
||||
@@ -1078,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
|
||||
@@ -1163,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{
|
||||
@@ -1203,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
|
||||
}
|
||||
@@ -1315,7 +1107,7 @@ func (container *Container) monitor() {
|
||||
}
|
||||
|
||||
// Report status back
|
||||
container.State.setStopped(exitCode)
|
||||
container.State.SetStopped(exitCode)
|
||||
|
||||
// Release the lock
|
||||
close(container.waitLock)
|
||||
@@ -1365,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
|
||||
}
|
||||
|
||||
@@ -1381,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
|
||||
}
|
||||
|
||||
@@ -1406,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
|
||||
}
|
||||
|
||||
@@ -1440,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 {
|
||||
@@ -1575,14 +1367,10 @@ func (container *Container) GetSize() (int64, int64) {
|
||||
}
|
||||
}
|
||||
|
||||
_, 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 _, err = os.Stat(container.RootfsPath()); err != nil {
|
||||
if sizeRootfs, err = utils.TreeSize(container.RootfsPath()); err != nil {
|
||||
sizeRootfs = -1
|
||||
}
|
||||
}
|
||||
return sizeRw, sizeRootfs
|
||||
}
|
||||
|
||||
+11
-11
@@ -25,20 +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("graph-driver", "", "For docker to use a specific graph driver")
|
||||
flGraphDriver := flag.String("s", "", "Force the docker runtime to use a specific storage driver")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
|
||||
+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
|
||||
|
||||
|
||||
+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
|
||||
|
||||
@@ -1171,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:
|
||||
|
||||
@@ -331,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:
|
||||
|
||||
@@ -392,18 +468,52 @@ To see how the docker:latest image was built:
|
||||
|
||||
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.
|
||||
@@ -412,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)
|
||||
@@ -519,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
|
||||
~~~~~~~~
|
||||
|
||||
@@ -528,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:
|
||||
|
||||
@@ -559,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``
|
||||
@@ -852,6 +981,17 @@ Known Issues (run -volumes-from)
|
||||
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``
|
||||
|
||||
@@ -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``.
|
||||
@@ -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
|
||||
|
||||
@@ -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,13 +18,14 @@ Contents:
|
||||
:maxdepth: 1
|
||||
|
||||
ubuntulinux
|
||||
binaries
|
||||
security
|
||||
upgrading
|
||||
kernel
|
||||
fedora
|
||||
archlinux
|
||||
gentoolinux
|
||||
vagrant
|
||||
windows
|
||||
amazon
|
||||
rackspace
|
||||
archlinux
|
||||
gentoolinux
|
||||
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
|
||||
@@ -58,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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -76,11 +76,11 @@ client commands.
|
||||
# Add the docker group if it doesn't already exist.
|
||||
sudo groupadd docker
|
||||
|
||||
# Add the user "ubuntu" 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
|
||||
sudo gpasswd -a ${USERNAME} docker
|
||||
|
||||
# Restart the docker daemon.
|
||||
sudo service docker restart
|
||||
|
||||
@@ -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,6 +338,7 @@ this optional but default, you could use a CMD:
|
||||
CMD ["-l", "-"]
|
||||
ENTRYPOINT ["/usr/bin/wc"]
|
||||
|
||||
.. _dockerfile_volume:
|
||||
|
||||
3.9 VOLUME
|
||||
----------
|
||||
@@ -322,6 +349,8 @@ The ``VOLUME`` instruction will create a mount point with the specified name and
|
||||
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
|
||||
---------
|
||||
|
||||
@@ -330,6 +359,8 @@ and mounting instructions via docker client, refer to :ref:`volume_def` document
|
||||
The ``USER`` instruction sets the username or UID to use when running
|
||||
the image.
|
||||
|
||||
.. _dockerfile_workdir:
|
||||
|
||||
3.11 WORKDIR
|
||||
------------
|
||||
|
||||
@@ -338,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
|
||||
======================
|
||||
|
||||
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.
|
||||
+5
-1
@@ -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)
|
||||
@@ -234,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{})
|
||||
|
||||
@@ -52,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
|
||||
}
|
||||
@@ -110,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,
|
||||
@@ -129,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
|
||||
}
|
||||
@@ -137,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 {
|
||||
@@ -177,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
|
||||
}
|
||||
@@ -353,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
|
||||
}
|
||||
|
||||
-297
@@ -1,297 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"errors"
|
||||
"github.com/dotcloud/docker/archive"
|
||||
"github.com/dotcloud/docker/graphdriver"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
graph := tempGraph(t)
|
||||
defer os.RemoveAll(graph.Root)
|
||||
// Root should exist
|
||||
if _, err := os.Stat(graph.Root); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Map() should be empty
|
||||
if l, err := graph.Map(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(l) != 0 {
|
||||
t.Fatalf("len(Map()) should return %d, not %d", 0, len(l))
|
||||
}
|
||||
}
|
||||
|
||||
// Test that Register can be interrupted cleanly without side effects
|
||||
func TestInterruptedRegister(t *testing.T) {
|
||||
graph := tempGraph(t)
|
||||
defer os.RemoveAll(graph.Root)
|
||||
badArchive, w := io.Pipe() // Use a pipe reader as a fake archive which never yields data
|
||||
image := &Image{
|
||||
ID: 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)
|
||||
if _, err := graph.Get(image.ID); err == nil {
|
||||
t.Fatal("Image should not exist after Register is interrupted")
|
||||
}
|
||||
// Registering the same image again should succeed if the first register was interrupted
|
||||
goodArchive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := graph.Register(nil, goodArchive, image); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image, err := graph.Create(archive, nil, "Testing", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := 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)
|
||||
}
|
||||
images, err := graph.Map()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if l := len(images); l != 1 {
|
||||
t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
|
||||
}
|
||||
if images[image.ID] == nil {
|
||||
t.Fatalf("Could not find image with id %s", image.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
graph := tempGraph(t)
|
||||
defer os.RemoveAll(graph.Root)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image := &Image{
|
||||
ID: GenerateID(),
|
||||
Comment: "testing",
|
||||
Created: time.Now(),
|
||||
}
|
||||
err = graph.Register(nil, archive, image)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if images, err := graph.Map(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if l := len(images); l != 1 {
|
||||
t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
|
||||
}
|
||||
if resultImg, err := graph.Get(image.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
if resultImg.ID != image.ID {
|
||||
t.Fatalf("Wrong image ID. Should be '%s', not '%s'", image.ID, resultImg.ID)
|
||||
}
|
||||
if resultImg.Comment != image.Comment {
|
||||
t.Fatalf("Wrong image comment. Should be '%s', not '%s'", image.Comment, resultImg.Comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test that an image can be deleted by its shorthand prefix
|
||||
func TestDeletePrefix(t *testing.T) {
|
||||
graph := tempGraph(t)
|
||||
defer os.RemoveAll(graph.Root)
|
||||
img := createTestImage(graph, t)
|
||||
if err := graph.Delete(utils.TruncateID(img.ID)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 0)
|
||||
}
|
||||
|
||||
func createTestImage(graph *Graph, t *testing.T) *Image {
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
img, err := graph.Create(archive, nil, "Test image", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
graph := tempGraph(t)
|
||||
defer os.RemoveAll(graph.Root)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 0)
|
||||
img, err := graph.Create(archive, nil, "Bla bla", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 1)
|
||||
if err := graph.Delete(img.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 0)
|
||||
|
||||
archive, err = fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Test 2 create (same name) / 1 delete
|
||||
img1, err := graph.Create(archive, nil, "Testing", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
archive, err = fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = graph.Create(archive, nil, "Testing", "", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 2)
|
||||
if err := graph.Delete(img1.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 1)
|
||||
|
||||
// Test delete wrong name
|
||||
if err := graph.Delete("Not_foo"); err == nil {
|
||||
t.Fatalf("Deleting wrong ID should return an error")
|
||||
}
|
||||
assertNImages(graph, t, 1)
|
||||
|
||||
archive, err = fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Test delete twice (pull -> rm -> pull -> rm)
|
||||
if err := graph.Register(nil, archive, img1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := graph.Delete(img1.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 1)
|
||||
}
|
||||
|
||||
func TestByParent(t *testing.T) {
|
||||
archive1, _ := fakeTar()
|
||||
archive2, _ := fakeTar()
|
||||
archive3, _ := fakeTar()
|
||||
|
||||
graph := tempGraph(t)
|
||||
defer os.RemoveAll(graph.Root)
|
||||
parentImage := &Image{
|
||||
ID: GenerateID(),
|
||||
Comment: "parent",
|
||||
Created: time.Now(),
|
||||
Parent: "",
|
||||
}
|
||||
childImage1 := &Image{
|
||||
ID: GenerateID(),
|
||||
Comment: "child1",
|
||||
Created: time.Now(),
|
||||
Parent: parentImage.ID,
|
||||
}
|
||||
childImage2 := &Image{
|
||||
ID: GenerateID(),
|
||||
Comment: "child2",
|
||||
Created: time.Now(),
|
||||
Parent: parentImage.ID,
|
||||
}
|
||||
_ = graph.Register(nil, archive1, parentImage)
|
||||
_ = graph.Register(nil, archive2, childImage1)
|
||||
_ = graph.Register(nil, archive3, childImage2)
|
||||
|
||||
byParent, err := graph.ByParent()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
numChildren := len(byParent[parentImage.ID])
|
||||
if numChildren != 2 {
|
||||
t.Fatalf("Expected 2 children, found %d", numChildren)
|
||||
}
|
||||
}
|
||||
|
||||
func assertNImages(graph *Graph, t *testing.T, n int) {
|
||||
if images, err := graph.Map(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if actualN := len(images); actualN != n {
|
||||
t.Fatalf("Expected %d images, found %d", n, actualN)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* HELPER FUNCTIONS
|
||||
*/
|
||||
|
||||
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 testArchive(t *testing.T) archive.Archive {
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -103,8 +103,12 @@ func (Driver) String() string {
|
||||
return "aufs"
|
||||
}
|
||||
|
||||
func (Driver) Status() [][2]string {
|
||||
return nil
|
||||
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
|
||||
|
||||
@@ -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)
|
||||
@@ -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")
|
||||
|
||||
@@ -38,6 +38,9 @@ func pathExists(pth string) bool {
|
||||
// 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
|
||||
}
|
||||
@@ -46,6 +49,14 @@ func (a *Driver) Migrate(pth string, setupInit func(p string) error) error {
|
||||
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 {
|
||||
|
||||
@@ -6,13 +6,10 @@ import (
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -105,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
|
||||
}
|
||||
|
||||
@@ -117,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
|
||||
}
|
||||
@@ -174,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)
|
||||
}
|
||||
|
||||
@@ -225,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)
|
||||
@@ -252,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
|
||||
}
|
||||
@@ -337,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -372,7 +368,7 @@ func (devices *DeviceSet) ResizePool(size int64) error {
|
||||
datafilename := path.Join(dirname, "data")
|
||||
metadatafilename := path.Join(dirname, "metadata")
|
||||
|
||||
datafile, err := os.OpenFile(datafilename, os.O_RDWR, 0)
|
||||
datafile, err := osOpenFile(datafilename, osORdWr, 0)
|
||||
if datafile == nil {
|
||||
return err
|
||||
}
|
||||
@@ -387,19 +383,19 @@ func (devices *DeviceSet) ResizePool(size int64) error {
|
||||
return fmt.Errorf("Can't shrink file")
|
||||
}
|
||||
|
||||
dataloopback := FindLoopDeviceFor(datafile)
|
||||
dataloopback := FindLoopDeviceFor(&osFile{File: datafile})
|
||||
if dataloopback == nil {
|
||||
return fmt.Errorf("Unable to find loopback mount for: %s", datafilename)
|
||||
}
|
||||
defer dataloopback.Close()
|
||||
|
||||
metadatafile, err := os.OpenFile(metadatafilename, os.O_RDWR, 0)
|
||||
metadatafile, err := osOpenFile(metadatafilename, osORdWr, 0)
|
||||
if metadatafile == nil {
|
||||
return err
|
||||
}
|
||||
defer metadatafile.Close()
|
||||
|
||||
metadataloopback := FindLoopDeviceFor(metadatafile)
|
||||
metadataloopback := FindLoopDeviceFor(&osFile{File: metadatafile})
|
||||
if metadataloopback == nil {
|
||||
return fmt.Errorf("Unable to find loopback mount for: %s", metadatafilename)
|
||||
}
|
||||
@@ -464,11 +460,11 @@ func (devices *DeviceSet) initDevmapper(doInit bool) 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:
|
||||
@@ -654,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 {
|
||||
@@ -680,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
|
||||
}
|
||||
@@ -708,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)
|
||||
}
|
||||
}
|
||||
@@ -752,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)
|
||||
@@ -779,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
|
||||
}
|
||||
|
||||
@@ -4,9 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type DevmapperLogger interface {
|
||||
@@ -49,7 +47,6 @@ var (
|
||||
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")
|
||||
@@ -86,7 +83,7 @@ type (
|
||||
|
||||
func (t *Task) destroy() {
|
||||
if t != nil {
|
||||
DmTaskDestory(t.unmanaged)
|
||||
DmTaskDestroy(t.unmanaged)
|
||||
runtime.SetFinalizer(t, nil)
|
||||
}
|
||||
}
|
||||
@@ -180,45 +177,44 @@ 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 *os.File) (uint64, uint64, error) {
|
||||
dev, inode, err := dmGetLoopbackBackingFile(file.Fd())
|
||||
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 *os.File) error {
|
||||
err := dmLoopbackSetCapacity(file.Fd())
|
||||
if err != 0 {
|
||||
func LoopbackSetCapacity(file *osFile) error {
|
||||
if err := DmLoopbackSetCapacity(file.Fd()); err != 0 {
|
||||
return ErrLoopbackSetCapacity
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func FindLoopDeviceFor(file *os.File) *os.File {
|
||||
func FindLoopDeviceFor(file *osFile) *osFile {
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
targetInode := stat.Sys().(*syscall.Stat_t).Ino
|
||||
targetDevice := stat.Sys().(*syscall.Stat_t).Dev
|
||||
targetInode := stat.Sys().(*sysStatT).Ino
|
||||
targetDevice := stat.Sys().(*sysStatT).Dev
|
||||
|
||||
for i := 0; true; i++ {
|
||||
path := fmt.Sprintf("/dev/loop%d", i)
|
||||
|
||||
file, err := os.OpenFile(path, os.O_RDWR, 0)
|
||||
file, err := osOpenFile(path, osORdWr, 0)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if osIsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -227,9 +223,9 @@ func FindLoopDeviceFor(file *os.File) *os.File {
|
||||
continue
|
||||
}
|
||||
|
||||
dev, inode, err := getLoopbackBackingFile(file)
|
||||
dev, inode, err := getLoopbackBackingFile(&osFile{File: file})
|
||||
if err == nil && dev == targetDevice && inode == targetInode {
|
||||
return file
|
||||
return &osFile{File: file}
|
||||
}
|
||||
|
||||
file.Close()
|
||||
@@ -289,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
|
||||
@@ -298,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
|
||||
@@ -328,7 +324,7 @@ func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func reloadPool(poolName string, dataFile *os.File, metadataFile *os.File) error {
|
||||
func reloadPool(poolName string, dataFile, metadataFile *osFile) error {
|
||||
task, err := createTask(DeviceReload, poolName)
|
||||
if task == nil {
|
||||
return err
|
||||
@@ -394,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,23 +240,22 @@ func dmTaskAddTargetFct(task *CDmTask,
|
||||
C.uint64_t(start), C.uint64_t(size), Cttype, Cparams))
|
||||
}
|
||||
|
||||
func dmGetLoopbackBackingFile(fd uintptr) (uint64, uint64, syscall.Errno) {
|
||||
func dmGetLoopbackBackingFileFct(fd uintptr) (uint64, uint64, sysErrno) {
|
||||
var lo64 C.struct_loop_info64
|
||||
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.LOOP_GET_STATUS64,
|
||||
_, _, err := sysSyscall(sysSysIoctl, fd, C.LOOP_GET_STATUS64,
|
||||
uintptr(unsafe.Pointer(&lo64)))
|
||||
return uint64(lo64.lo_device), uint64(lo64.lo_inode), err
|
||||
return uint64(lo64.lo_device), uint64(lo64.lo_inode), sysErrno(err)
|
||||
}
|
||||
|
||||
func dmLoopbackSetCapacity(fd uintptr) syscall.Errno {
|
||||
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.LOOP_SET_CAPACITY, 0)
|
||||
return err
|
||||
func dmLoopbackSetCapacityFct(fd uintptr) sysErrno {
|
||||
_, _, err := sysSyscall(sysSysIoctl, fd, C.LOOP_SET_CAPACITY, 0)
|
||||
return sysErrno(err)
|
||||
}
|
||||
|
||||
func dmGetBlockSizeFct(fd uintptr) (int64, syscall.Errno) {
|
||||
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 {
|
||||
@@ -275,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
|
||||
@@ -288,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)
|
||||
@@ -307,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 {
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/graphdriver"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
@@ -22,7 +21,7 @@ type Driver struct {
|
||||
home string
|
||||
}
|
||||
|
||||
func Init(home string) (graphdriver.Driver, error) {
|
||||
var Init = func(home string) (graphdriver.Driver, error) {
|
||||
deviceSet, err := NewDeviceSet(home, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -57,7 +56,7 @@ func (d *Driver) Cleanup() error {
|
||||
return d.DeviceSet.Shutdown()
|
||||
}
|
||||
|
||||
func (d *Driver) Create(id string, parent string) error {
|
||||
func (d *Driver) Create(id, parent string) error {
|
||||
if err := d.DeviceSet.AddDevice(id, parent); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -67,7 +66,7 @@ func (d *Driver) Create(id string, parent string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(path.Join(mp, "rootfs"), 0755); err != nil && !os.IsExist(err) {
|
||||
if err := osMkdirAll(path.Join(mp, "rootfs"), 0755); err != nil && !osIsExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -98,7 +97,7 @@ func (d *Driver) Get(id string) (string, error) {
|
||||
|
||||
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) {
|
||||
if err := osMkdirAll(mountPoint, 0755); err != nil && !osIsExist(err) {
|
||||
return err
|
||||
}
|
||||
// If mountpoint is already mounted, do nothing
|
||||
@@ -121,3 +120,7 @@ func (d *Driver) unmount(id, mountPoint string) error {
|
||||
// Unmount the device
|
||||
return d.DeviceSet.UnmountDevice(id, mountPoint, true)
|
||||
}
|
||||
|
||||
func (d *Driver) Exists(id string) bool {
|
||||
return d.Devices[id] != nil
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package devmapper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/graphdriver"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -12,7 +16,105 @@ func init() {
|
||||
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 {
|
||||
@@ -34,72 +136,521 @@ func newDriver(t *testing.T) *Driver {
|
||||
|
||||
func cleanup(d *Driver) {
|
||||
d.Cleanup()
|
||||
os.RemoveAll(d.home)
|
||||
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) {
|
||||
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 {
|
||||
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.
|
||||
|
||||
id := "foo"
|
||||
if err := driver.Create(id, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
// 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
|
||||
}
|
||||
dir, err := driver.Get(id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
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
|
||||
}
|
||||
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)
|
||||
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) {
|
||||
d := newDriver(t)
|
||||
defer cleanup(d)
|
||||
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) {
|
||||
d := newDriver(t)
|
||||
defer cleanup(d)
|
||||
denyAllDevmapper()
|
||||
denyAllSyscall()
|
||||
defer denyAllSyscall()
|
||||
defer denyAllDevmapper()
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
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) {
|
||||
d := newDriver(t)
|
||||
defer cleanup(d)
|
||||
denyAllDevmapper()
|
||||
denyAllSyscall()
|
||||
defer denyAllSyscall()
|
||||
defer denyAllDevmapper()
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
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
|
||||
}
|
||||
|
||||
if err := d.Remove("1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
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 os.RemoveAll(d.home)
|
||||
defer osRemoveAll(d.home)
|
||||
|
||||
mountPoints := make([]string, 2)
|
||||
|
||||
@@ -161,6 +712,7 @@ func TestCleanup(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNotMounted(t *testing.T) {
|
||||
t.Skip("FIXME: not a unit test")
|
||||
t.Skip("Not implemented")
|
||||
d := newDriver(t)
|
||||
defer cleanup(d)
|
||||
@@ -179,6 +731,7 @@ func TestNotMounted(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMounted(t *testing.T) {
|
||||
t.Skip("FIXME: not a unit test")
|
||||
d := newDriver(t)
|
||||
defer cleanup(d)
|
||||
|
||||
@@ -199,6 +752,7 @@ func TestMounted(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInitCleanedDriver(t *testing.T) {
|
||||
t.Skip("FIXME: not a unit test")
|
||||
d := newDriver(t)
|
||||
|
||||
if err := d.Create("1", ""); err != nil {
|
||||
@@ -225,6 +779,7 @@ func TestInitCleanedDriver(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMountMountedDriver(t *testing.T) {
|
||||
t.Skip("FIXME: not a unit test")
|
||||
d := newDriver(t)
|
||||
defer cleanup(d)
|
||||
|
||||
@@ -243,6 +798,7 @@ func TestMountMountedDriver(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetReturnsValidDevice(t *testing.T) {
|
||||
t.Skip("FIXME: not a unit test")
|
||||
d := newDriver(t)
|
||||
defer cleanup(d)
|
||||
|
||||
@@ -268,6 +824,7 @@ func TestGetReturnsValidDevice(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDriverGetSize(t *testing.T) {
|
||||
t.Skip("FIXME: not a unit test")
|
||||
t.Skipf("Size is currently not implemented")
|
||||
|
||||
d := newDriver(t)
|
||||
@@ -284,7 +841,7 @@ func TestDriverGetSize(t *testing.T) {
|
||||
|
||||
size := int64(1024)
|
||||
|
||||
f, err := os.Create(path.Join(mountPoint, "test_file"))
|
||||
f, err := osCreate(path.Join(mountPoint, "test_file"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -301,3 +858,15 @@ func TestDriverGetSize(t *testing.T) {
|
||||
// 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))
|
||||
}
|
||||
+9
-17
@@ -8,8 +8,6 @@ import (
|
||||
"path"
|
||||
)
|
||||
|
||||
var DefaultDriver string
|
||||
|
||||
type InitFunc func(root string) (Driver, error)
|
||||
|
||||
type Driver interface {
|
||||
@@ -19,6 +17,7 @@ type Driver interface {
|
||||
Remove(id string) error
|
||||
|
||||
Get(id string) (dir string, err error)
|
||||
Exists(id string) bool
|
||||
|
||||
Status() [][2]string
|
||||
|
||||
@@ -33,13 +32,14 @@ type Differ interface {
|
||||
}
|
||||
|
||||
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",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -63,14 +63,8 @@ func GetDriver(name, home string) (Driver, error) {
|
||||
return nil, fmt.Errorf("No such driver: %s", name)
|
||||
}
|
||||
|
||||
func New(root string) (Driver, error) {
|
||||
var driver Driver
|
||||
var lastError error
|
||||
|
||||
for _, name := range []string{
|
||||
os.Getenv("DOCKER_DRIVER"),
|
||||
DefaultDriver,
|
||||
} {
|
||||
func New(root string) (driver Driver, err error) {
|
||||
for _, name := range []string{os.Getenv("DOCKER_DRIVER"), DefaultDriver} {
|
||||
if name != "" {
|
||||
return GetDriver(name, root)
|
||||
}
|
||||
@@ -78,9 +72,8 @@ func New(root string) (Driver, error) {
|
||||
|
||||
// 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
|
||||
@@ -88,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,4 +1,4 @@
|
||||
package dummy
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
graphdriver.Register("dummy", Init)
|
||||
graphdriver.Register("vfs", Init)
|
||||
}
|
||||
|
||||
func Init(home string) (graphdriver.Driver, error) {
|
||||
@@ -24,7 +24,7 @@ type Driver struct {
|
||||
}
|
||||
|
||||
func (d *Driver) String() string {
|
||||
return "dummy"
|
||||
return "vfs"
|
||||
}
|
||||
|
||||
func (d *Driver) Status() [][2]string {
|
||||
@@ -84,3 +84,8 @@ func (d *Driver) Get(id string) (string, error) {
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -62,26 +62,52 @@ func LoadImage(root string) (*Image, error) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,18 +116,13 @@ func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root, ro
|
||||
return ioutil.WriteFile(jsonPath(root), jsonData, 0600)
|
||||
}
|
||||
// Otherwise, unmarshal the image
|
||||
jsonData, err := json.Marshal(img)
|
||||
if err != nil {
|
||||
if jsonData, err = json.Marshal(img); 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)
|
||||
}
|
||||
|
||||
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 (img *Image) TarLayer(compression archive.Compression) (archive.Archive, error) {
|
||||
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 := img.graph.driver.Get(img.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 {
|
||||
|
||||
@@ -289,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")
|
||||
}
|
||||
})
|
||||
@@ -319,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)
|
||||
@@ -329,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)
|
||||
}
|
||||
@@ -345,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)")
|
||||
}
|
||||
}
|
||||
@@ -454,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")
|
||||
}
|
||||
|
||||
@@ -534,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")
|
||||
}
|
||||
|
||||
@@ -596,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")
|
||||
}
|
||||
|
||||
@@ -629,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)
|
||||
@@ -665,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")
|
||||
}
|
||||
|
||||
|
||||
@@ -224,13 +224,13 @@ func TestCommitAutoRun(t *testing.T) {
|
||||
container1, _, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
|
||||
defer runtime.Destroy(container1)
|
||||
|
||||
if container1.State.Running {
|
||||
if container1.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
if err := container1.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container1.State.Running {
|
||||
if container1.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
|
||||
@@ -280,13 +280,13 @@ func TestCommitRun(t *testing.T) {
|
||||
container1, _, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
|
||||
defer runtime.Destroy(container1)
|
||||
|
||||
if container1.State.Running {
|
||||
if container1.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
if err := container1.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container1.State.Running {
|
||||
if container1.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
|
||||
@@ -330,6 +330,11 @@ func TestCommitRun(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStart(t *testing.T) {
|
||||
_, err1 := os.Stat("/sys/fs/cgroup/cpuacct,cpu")
|
||||
_, err2 := os.Stat("/sys/fs/cgroup/cpu,cpuacct")
|
||||
if err1 == nil || err2 == nil {
|
||||
t.Skip("Fixme. Setting cpu cgroup shares doesn't work in dind on a Fedora host. The lxc utils are confused by the cpu,cpuacct mount.")
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, _, _ := mkContainer(runtime, []string{"-m", "33554432", "-c", "1000", "-i", "_", "/bin/cat"}, t)
|
||||
@@ -347,7 +352,7 @@ func TestStart(t *testing.T) {
|
||||
// Give some time to the process to start
|
||||
container.WaitTimeout(500 * time.Millisecond)
|
||||
|
||||
if !container.State.Running {
|
||||
if !container.State.IsRunning() {
|
||||
t.Errorf("Container should be running")
|
||||
}
|
||||
if err := container.Start(); err == nil {
|
||||
@@ -365,13 +370,13 @@ func TestRun(t *testing.T) {
|
||||
container, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
if err := container.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
}
|
||||
@@ -395,7 +400,7 @@ func TestOutput(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(output) != "foobar" {
|
||||
t.Error(string(output))
|
||||
t.Fatalf("%s != %s", string(output), "foobar")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,8 +421,8 @@ func TestContainerNetwork(t *testing.T) {
|
||||
if err := container.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container.State.ExitCode != 0 {
|
||||
t.Errorf("Unexpected ping 127.0.0.1 exit code %d (expected 0)", container.State.ExitCode)
|
||||
if code := container.State.GetExitCode(); code != 0 {
|
||||
t.Fatalf("Unexpected ping 127.0.0.1 exit code %d (expected 0)", code)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,7 +446,7 @@ func TestKillDifferentUser(t *testing.T) {
|
||||
// there is a side effect I'm not seeing.
|
||||
// defer container.stdin.Close()
|
||||
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
if err := container.Start(); err != nil {
|
||||
@@ -449,7 +454,7 @@ func TestKillDifferentUser(t *testing.T) {
|
||||
}
|
||||
|
||||
setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
|
||||
for !container.State.Running {
|
||||
for !container.State.IsRunning() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
})
|
||||
@@ -466,11 +471,11 @@ func TestKillDifferentUser(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
container.Wait()
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
// Try stopping twice
|
||||
@@ -528,7 +533,7 @@ func TestKill(t *testing.T) {
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
if err := container.Start(); err != nil {
|
||||
@@ -538,17 +543,17 @@ func TestKill(t *testing.T) {
|
||||
// Give some time to lxc to spawn the process
|
||||
container.WaitTimeout(500 * time.Millisecond)
|
||||
|
||||
if !container.State.Running {
|
||||
if !container.State.IsRunning() {
|
||||
t.Errorf("Container should be running")
|
||||
}
|
||||
if err := container.Kill(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
container.Wait()
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
// Try stopping twice
|
||||
@@ -563,7 +568,7 @@ func TestExitCode(t *testing.T) {
|
||||
|
||||
trueContainer, _, err := runtime.Create(&docker.Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/true", ""},
|
||||
Cmd: []string{"/bin/true"},
|
||||
}, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -572,13 +577,13 @@ func TestExitCode(t *testing.T) {
|
||||
if err := trueContainer.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if trueContainer.State.ExitCode != 0 {
|
||||
t.Errorf("Unexpected exit code %d (expected 0)", trueContainer.State.ExitCode)
|
||||
if code := trueContainer.State.GetExitCode(); code != 0 {
|
||||
t.Fatalf("Unexpected exit code %d (expected 0)", code)
|
||||
}
|
||||
|
||||
falseContainer, _, err := runtime.Create(&docker.Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/false", ""},
|
||||
Cmd: []string{"/bin/false"},
|
||||
}, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -587,8 +592,8 @@ func TestExitCode(t *testing.T) {
|
||||
if err := falseContainer.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if falseContainer.State.ExitCode != 1 {
|
||||
t.Errorf("Unexpected exit code %d (expected 1)", falseContainer.State.ExitCode)
|
||||
if code := falseContainer.State.GetExitCode(); code != 1 {
|
||||
t.Fatalf("Unexpected exit code %d (expected 1)", code)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -736,7 +741,7 @@ func TestUser(t *testing.T) {
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
output, err = container.Output()
|
||||
if err != nil || container.State.ExitCode != 0 {
|
||||
if code := container.State.GetExitCode(); err != nil || code != 0 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(output), "uid=0(root) gid=0(root)") {
|
||||
@@ -752,12 +757,12 @@ func TestUser(t *testing.T) {
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil || container.State.ExitCode != 0 {
|
||||
if code := container.State.GetExitCode(); err != nil || code != 0 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
output, err = container.Output()
|
||||
if err != nil || container.State.ExitCode != 0 {
|
||||
if code := container.State.GetExitCode(); err != nil || code != 0 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(output), "uid=0(root) gid=0(root)") {
|
||||
@@ -780,8 +785,8 @@ func TestUser(t *testing.T) {
|
||||
output, err = container.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if container.State.ExitCode != 0 {
|
||||
t.Fatalf("Container exit code is invalid: %d\nOutput:\n%s\n", container.State.ExitCode, output)
|
||||
} else if code := container.State.GetExitCode(); code != 0 {
|
||||
t.Fatalf("Container exit code is invalid: %d\nOutput:\n%s\n", code, output)
|
||||
}
|
||||
if !strings.Contains(string(output), "uid=1(daemon) gid=1(daemon)") {
|
||||
t.Error(string(output))
|
||||
@@ -801,7 +806,7 @@ func TestUser(t *testing.T) {
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
output, err = container.Output()
|
||||
if err != nil || container.State.ExitCode != 0 {
|
||||
if code := container.State.GetExitCode(); err != nil || code != 0 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(output), "uid=1(daemon) gid=1(daemon)") {
|
||||
@@ -822,7 +827,7 @@ func TestUser(t *testing.T) {
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
output, err = container.Output()
|
||||
if container.State.ExitCode == 0 {
|
||||
if container.State.GetExitCode() == 0 {
|
||||
t.Fatal("Starting container with wrong uid should fail but it passed.")
|
||||
}
|
||||
}
|
||||
@@ -866,10 +871,10 @@ func TestMultipleContainers(t *testing.T) {
|
||||
container2.WaitTimeout(250 * time.Millisecond)
|
||||
|
||||
// If we are here, both containers should be running
|
||||
if !container1.State.Running {
|
||||
if !container1.State.IsRunning() {
|
||||
t.Fatal("Container not running")
|
||||
}
|
||||
if !container2.State.Running {
|
||||
if !container2.State.IsRunning() {
|
||||
t.Fatal("Container not running")
|
||||
}
|
||||
|
||||
@@ -1171,13 +1176,13 @@ func TestCopyVolumeUidGid(t *testing.T) {
|
||||
container1, _, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello && touch /hello/test.txt && chown daemon.daemon /hello"}, t)
|
||||
defer r.Destroy(container1)
|
||||
|
||||
if container1.State.Running {
|
||||
if container1.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
if err := container1.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container1.State.Running {
|
||||
if container1.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
|
||||
@@ -1205,13 +1210,13 @@ func TestCopyVolumeContent(t *testing.T) {
|
||||
container1, _, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello/local && echo hello > /hello/local/world"}, t)
|
||||
defer r.Destroy(container1)
|
||||
|
||||
if container1.State.Running {
|
||||
if container1.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
if err := container1.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container1.State.Running {
|
||||
if container1.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
|
||||
@@ -1661,17 +1666,17 @@ func TestRestartGhost(t *testing.T) {
|
||||
},
|
||||
"",
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := container.Kill(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
container.State.Ghost = true
|
||||
_, err = container.Output()
|
||||
container.State.SetGhost(true)
|
||||
|
||||
_, err = container.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
+268
-11
@@ -1,12 +1,17 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/dotcloud/docker"
|
||||
"github.com/dotcloud/docker/archive"
|
||||
"github.com/dotcloud/docker/graphdriver"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMount(t *testing.T) {
|
||||
@@ -41,19 +46,271 @@ func TestMount(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
//FIXME: duplicate
|
||||
func tempGraph(t *testing.T) (*docker.Graph, graphdriver.Driver) {
|
||||
tmp, err := ioutil.TempDir("", "docker-graph-")
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
driver, err := graphdriver.New(tmp)
|
||||
if err != nil {
|
||||
// Map() should be empty
|
||||
if l, err := graph.Map(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(l) != 0 {
|
||||
t.Fatalf("len(Map()) should return %d, not %d", 0, len(l))
|
||||
}
|
||||
graph, err := docker.NewGraph(tmp, driver)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return graph, driver
|
||||
}
|
||||
|
||||
// Test that Register can be interrupted cleanly without side effects
|
||||
func TestInterruptedRegister(t *testing.T) {
|
||||
graph, _ := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
badArchive, w := io.Pipe() // Use a pipe reader as a fake archive which never yields data
|
||||
image := &docker.Image{
|
||||
ID: docker.GenerateID(),
|
||||
Comment: "testing",
|
||||
Created: time.Now(),
|
||||
}
|
||||
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")
|
||||
}
|
||||
// Registering the same image again should succeed if the first register was interrupted
|
||||
goodArchive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := graph.Register(nil, goodArchive, image); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 nukeGraph(graph)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image, err := graph.Create(archive, nil, "Testing", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
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 != docker.VERSION {
|
||||
t.Fatalf("Wrong docker_version: should be '%s', not '%s'", docker.VERSION, image.DockerVersion)
|
||||
}
|
||||
images, err := graph.Map()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if l := len(images); l != 1 {
|
||||
t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
|
||||
}
|
||||
if images[image.ID] == nil {
|
||||
t.Fatalf("Could not find image with id %s", image.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
graph, _ := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image := &docker.Image{
|
||||
ID: docker.GenerateID(),
|
||||
Comment: "testing",
|
||||
Created: time.Now(),
|
||||
}
|
||||
err = graph.Register(nil, archive, image)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if images, err := graph.Map(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if l := len(images); l != 1 {
|
||||
t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
|
||||
}
|
||||
if resultImg, err := graph.Get(image.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
if resultImg.ID != image.ID {
|
||||
t.Fatalf("Wrong image ID. Should be '%s', not '%s'", image.ID, resultImg.ID)
|
||||
}
|
||||
if resultImg.Comment != image.Comment {
|
||||
t.Fatalf("Wrong image comment. Should be '%s', not '%s'", image.Comment, resultImg.Comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test that an image can be deleted by its shorthand prefix
|
||||
func TestDeletePrefix(t *testing.T) {
|
||||
graph, _ := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
img := createTestImage(graph, t)
|
||||
if err := graph.Delete(utils.TruncateID(img.ID)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 0)
|
||||
}
|
||||
|
||||
func createTestImage(graph *docker.Graph, t *testing.T) *docker.Image {
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
img, err := graph.Create(archive, nil, "Test image", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
graph, _ := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 0)
|
||||
img, err := graph.Create(archive, nil, "Bla bla", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 1)
|
||||
if err := graph.Delete(img.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 0)
|
||||
|
||||
archive, err = fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Test 2 create (same name) / 1 delete
|
||||
img1, err := graph.Create(archive, nil, "Testing", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
archive, err = fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = graph.Create(archive, nil, "Testing", "", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 2)
|
||||
if err := graph.Delete(img1.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 1)
|
||||
|
||||
// Test delete wrong name
|
||||
if err := graph.Delete("Not_foo"); err == nil {
|
||||
t.Fatalf("Deleting wrong ID should return an error")
|
||||
}
|
||||
assertNImages(graph, t, 1)
|
||||
|
||||
archive, err = fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Test delete twice (pull -> rm -> pull -> rm)
|
||||
if err := graph.Register(nil, archive, img1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := graph.Delete(img1.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 1)
|
||||
}
|
||||
|
||||
func TestByParent(t *testing.T) {
|
||||
archive1, _ := fakeTar()
|
||||
archive2, _ := fakeTar()
|
||||
archive3, _ := fakeTar()
|
||||
|
||||
graph, _ := tempGraph(t)
|
||||
defer nukeGraph(graph)
|
||||
parentImage := &docker.Image{
|
||||
ID: docker.GenerateID(),
|
||||
Comment: "parent",
|
||||
Created: time.Now(),
|
||||
Parent: "",
|
||||
}
|
||||
childImage1 := &docker.Image{
|
||||
ID: docker.GenerateID(),
|
||||
Comment: "child1",
|
||||
Created: time.Now(),
|
||||
Parent: parentImage.ID,
|
||||
}
|
||||
childImage2 := &docker.Image{
|
||||
ID: docker.GenerateID(),
|
||||
Comment: "child2",
|
||||
Created: time.Now(),
|
||||
Parent: parentImage.ID,
|
||||
}
|
||||
_ = graph.Register(nil, archive1, parentImage)
|
||||
_ = graph.Register(nil, archive2, childImage1)
|
||||
_ = graph.Register(nil, archive3, childImage2)
|
||||
|
||||
byParent, err := graph.ByParent()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
numChildren := len(byParent[parentImage.ID])
|
||||
if numChildren != 2 {
|
||||
t.Fatalf("Expected 2 children, found %d", numChildren)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 {
|
||||
t.Fatalf("Expected %d images, found %d", n, actualN)
|
||||
}
|
||||
}
|
||||
|
||||
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 nukeGraph(graph *docker.Graph) {
|
||||
graph.Driver().Cleanup()
|
||||
os.RemoveAll(graph.Root)
|
||||
}
|
||||
|
||||
func testArchive(t *testing.T) archive.Archive {
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return archive
|
||||
}
|
||||
|
||||
@@ -74,6 +74,9 @@ func layerArchive(tarfile string) (io.Reader, error) {
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Always use the same driver (vfs) for all integration tests.
|
||||
// To test other drivers, we need a dedicated driver validation suite.
|
||||
os.Setenv("DOCKER_DRIVER", "vfs")
|
||||
os.Setenv("TEST", "1")
|
||||
|
||||
// Hack to run sys init during unit testing
|
||||
@@ -419,7 +422,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*docker.Runtime, *doc
|
||||
}
|
||||
|
||||
setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
|
||||
for !container.State.Running {
|
||||
for !container.State.IsRunning() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
})
|
||||
@@ -533,7 +536,7 @@ func TestRestore(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !container2.State.Running {
|
||||
if !container2.State.IsRunning() {
|
||||
t.Fatalf("Container %v should appear as running but isn't", container2.ID)
|
||||
}
|
||||
|
||||
@@ -543,7 +546,7 @@ func TestRestore(t *testing.T) {
|
||||
if err := container2.WaitTimeout(2 * time.Second); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container2.State.Running = true
|
||||
container2.State.SetRunning(42)
|
||||
container2.ToDisk()
|
||||
|
||||
if len(runtime1.List()) != 2 {
|
||||
@@ -553,7 +556,7 @@ func TestRestore(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !container2.State.Running {
|
||||
if !container2.State.IsRunning() {
|
||||
t.Fatalf("Container %v should appear as running but isn't", container2.ID)
|
||||
}
|
||||
|
||||
@@ -577,7 +580,7 @@ func TestRestore(t *testing.T) {
|
||||
}
|
||||
runningCount := 0
|
||||
for _, c := range runtime2.List() {
|
||||
if c.State.Running {
|
||||
if c.State.IsRunning() {
|
||||
t.Errorf("Running container found: %v (%v)", c.ID, c.Path)
|
||||
runningCount++
|
||||
}
|
||||
@@ -592,7 +595,7 @@ func TestRestore(t *testing.T) {
|
||||
if err := container3.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container2.State.Running = false
|
||||
container2.State.SetStopped(0)
|
||||
}
|
||||
|
||||
func TestReloadContainerLinks(t *testing.T) {
|
||||
@@ -638,11 +641,11 @@ func TestReloadContainerLinks(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !container2.State.Running {
|
||||
if !container2.State.IsRunning() {
|
||||
t.Fatalf("Container %v should appear as running but isn't", container2.ID)
|
||||
}
|
||||
|
||||
if !container1.State.Running {
|
||||
if !container1.State.IsRunning() {
|
||||
t.Fatalf("Container %s should appear as running but isn't", container1.ID)
|
||||
}
|
||||
|
||||
@@ -669,7 +672,7 @@ func TestReloadContainerLinks(t *testing.T) {
|
||||
}
|
||||
runningCount := 0
|
||||
for _, c := range runtime2.List() {
|
||||
if c.State.Running {
|
||||
if c.State.IsRunning() {
|
||||
runningCount++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ func TestCreateRmVolumes(t *testing.T) {
|
||||
srv := mkServerFromEngine(eng, t)
|
||||
defer mkRuntimeFromEngine(eng, t).Nuke()
|
||||
|
||||
config, hostConfig, _, err := docker.ParseRun([]string{"-v", "/srv", unitTestImageID, "echo test"}, nil)
|
||||
config, hostConfig, _, err := docker.ParseRun([]string{"-v", "/srv", unitTestImageID, "echo", "test"}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -164,7 +164,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
|
||||
srv := mkServerFromEngine(eng, t)
|
||||
defer mkRuntimeFromEngine(eng, t).Nuke()
|
||||
|
||||
config, hostConfig, _, err := docker.ParseRun([]string{unitTestImageID, "/bin/cat"}, nil)
|
||||
config, hostConfig, _, err := docker.ParseRun([]string{"-i", unitTestImageID, "/bin/cat"}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -240,7 +240,7 @@ func TestRmi(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
config, hostConfig, _, err := docker.ParseRun([]string{unitTestImageID, "echo test"}, nil)
|
||||
config, hostConfig, _, err := docker.ParseRun([]string{unitTestImageID, "echo", "test"}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -256,6 +256,10 @@ func TestRmi(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := srv.ContainerWait(containerID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
imageID, err := srv.ContainerCommit(containerID, "test", "", "", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -277,6 +281,10 @@ func TestRmi(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := srv.ContainerWait(containerID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = srv.ContainerCommit(containerID, "test", "", "", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -111,7 +111,7 @@ func containerKill(eng *engine.Engine, id string, t utils.Fataler) {
|
||||
}
|
||||
|
||||
func containerRunning(eng *engine.Engine, id string, t utils.Fataler) bool {
|
||||
return getContainer(eng, id, t).State.Running
|
||||
return getContainer(eng, id, t).State.IsRunning()
|
||||
}
|
||||
|
||||
func containerAssertExists(eng *engine.Engine, id string, t utils.Fataler) {
|
||||
|
||||
@@ -21,7 +21,7 @@ func NewLink(parent, child *Container, name, bridgeInterface string) (*Link, err
|
||||
if parent.ID == child.ID {
|
||||
return nil, fmt.Errorf("Cannot link to self: %s == %s", parent.ID, child.ID)
|
||||
}
|
||||
if !child.State.Running {
|
||||
if !child.State.IsRunning() {
|
||||
return nil, fmt.Errorf("Cannot link to a non running container: %s AS %s", child.Name, name)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,14 +11,51 @@ type NameChecker interface {
|
||||
}
|
||||
|
||||
var (
|
||||
colors = [...]string{"white", "silver", "gray", "black", "blue", "green", "cyan", "yellow", "gold", "orange", "brown", "red", "violet", "pink", "magenta", "purple", "maroon", "crimson", "plum", "fuchsia", "lavender", "slate", "navy", "azure", "aqua", "olive", "teal", "lime", "beige", "tan", "sienna"}
|
||||
animals = [...]string{"ant", "bear", "bird", "cat", "chicken", "cow", "deer", "dog", "donkey", "duck", "fish", "fox", "frog", "horse", "kangaroo", "koala", "lemur", "lion", "lizard", "monkey", "octopus", "pig", "shark", "sheep", "sloth", "spider", "squirrel", "tiger", "toad", "weasel", "whale", "wolf"}
|
||||
left = [...]string{"happy", "jolly", "dreamy", "sad", "angry", "pensive", "focused", "sleepy", "grave", "distracted", "determined", "stoic", "stupefied", "sharp", "agitated", "cocky", "tender", "goofy", "furious", "desperate", "hopeful", "compassionate", "silly", "lonely", "condescending", "naughty", "kickass", "drunk", "boring", "nostalgic", "ecstatic", "insane", "cranky", "mad", "jovial", "sick", "hungry", "thirsty", "elegant", "backstabbing", "clever", "trusting", "loving", "suspicious", "berserk", "high", "romantic", "prickly", "evil"}
|
||||
// Docker 0.7.x generates names from notable scientists and hackers.
|
||||
//
|
||||
// Ada Lovelace invented the first algorithm. http://en.wikipedia.org/wiki/Ada_Lovelace (thanks James Turnbull)
|
||||
// Alan Turing was a founding father of computer science. http://en.wikipedia.org/wiki/Alan_Turing.
|
||||
// Albert Einstein invented the general theory of relativity. http://en.wikipedia.org/wiki/Albert_Einstein
|
||||
// Ambroise Pare invented modern surgery. http://en.wikipedia.org/wiki/Ambroise_Par%C3%A9
|
||||
// Archimedes was a physicist, engineer and mathematician who invented too many things to list them here. http://en.wikipedia.org/wiki/Archimedes
|
||||
// Benjamin Franklin is famous for his experiments in electricity and the invention of the lightning rod.
|
||||
// Charles Babbage invented the concept of a programmable computer. http://en.wikipedia.org/wiki/Charles_Babbage.
|
||||
// Charles Darwin established the principles of natural evolution. http://en.wikipedia.org/wiki/Charles_Darwin.
|
||||
// Dennis Ritchie and Ken Thompson created UNIX and the C programming language. http://en.wikipedia.org/wiki/Dennis_Ritchie http://en.wikipedia.org/wiki/Ken_Thompson
|
||||
// Douglas Engelbart gave the mother of all demos: http://en.wikipedia.org/wiki/Douglas_Engelbart
|
||||
// Emmett Brown invented time travel. http://en.wikipedia.org/wiki/Emmett_Brown (thanks Brian Goff)
|
||||
// Enrico Fermi invented the first nuclear reactor. http://en.wikipedia.org/wiki/Enrico_Fermi.
|
||||
// Euclid invented geometry. http://en.wikipedia.org/wiki/Euclid
|
||||
// Galileo was a founding father of modern astronomy, and faced politics and obscurantism to establish scientific truth. http://en.wikipedia.org/wiki/Galileo_Galilei
|
||||
// Henry Poincare made fundamental contributions in several fields of mathematics. http://en.wikipedia.org/wiki/Henri_Poincar%C3%A9
|
||||
// Isaac Newton invented classic mechanics and modern optics. http://en.wikipedia.org/wiki/Isaac_Newton
|
||||
// John McCarthy invented LISP: http://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist)
|
||||
// Leonardo Da Vinci invented too many things to list here. http://en.wikipedia.org/wiki/Leonardo_da_Vinci.
|
||||
// Linus Torvalds invented Linux and Git. http://en.wikipedia.org/wiki/Linus_Torvalds
|
||||
// Louis Pasteur discovered vaccination, fermentation and pasteurization. http://en.wikipedia.org/wiki/Louis_Pasteur.
|
||||
// Malcolm McLean invented the modern shipping container: http://en.wikipedia.org/wiki/Malcom_McLean
|
||||
// Marie Curie discovered radioactivity. http://en.wikipedia.org/wiki/Marie_Curie.
|
||||
// Muhammad ibn Jābir al-Ḥarrānī al-Battānī was a founding father of astronomy. http://en.wikipedia.org/wiki/Mu%E1%B8%A5ammad_ibn_J%C4%81bir_al-%E1%B8%A4arr%C4%81n%C4%AB_al-Batt%C4%81n%C4%AB
|
||||
// Niels Bohr is the father of quantum theory. http://en.wikipedia.org/wiki/Niels_Bohr.
|
||||
// Nikola Tesla invented the AC electric system and every gaget ever used by a James Bond villain. http://en.wikipedia.org/wiki/Nikola_Tesla
|
||||
// Pierre de Fermat pioneered several aspects of modern mathematics. http://en.wikipedia.org/wiki/Pierre_de_Fermat
|
||||
// Richard Feynmann was a key contributor to quantum mechanics and particle physics. http://en.wikipedia.org/wiki/Richard_Feynman
|
||||
// Rob Pike was a key contributor to Unix, Plan 9, the X graphic system, utf-8, and the Go programming language. http://en.wikipedia.org/wiki/Rob_Pike
|
||||
// Stephen Hawking pioneered the field of cosmology by combining general relativity and quantum mechanics. http://en.wikipedia.org/wiki/Stephen_Hawking
|
||||
// Steve Wozniak invented the Apple I and Apple II. http://en.wikipedia.org/wiki/Steve_Wozniak
|
||||
// Werner Heisenberg was a founding father of quantum mechanics. http://en.wikipedia.org/wiki/Werner_Heisenberg
|
||||
// William Shockley, Walter Houser Brattain and John Bardeen co-invented the transistor (thanks Brian Goff).
|
||||
// http://en.wikipedia.org/wiki/John_Bardeen
|
||||
// http://en.wikipedia.org/wiki/Walter_Houser_Brattain
|
||||
// http://en.wikipedia.org/wiki/William_Shockley
|
||||
right = [...]string{"lovelace", "franklin", "tesla", "einstein", "bohr", "davinci", "pasteur", "nobel", "curie", "darwin", "turing", "ritchie", "torvalds", "pike", "thompson", "wozniak", "galileo", "euclide", "newton", "fermat", "archimede", "poincare", "heisenberg", "feynmann", "hawkings", "fermi", "pare", "mccarthy", "engelbart", "babbage", "albattani", "ptolemy", "bell", "wright", "lumiere", "morse", "mclean", "brown", "bardeen", "brattain", "shockley"}
|
||||
)
|
||||
|
||||
func GenerateRandomName(checker NameChecker) (string, error) {
|
||||
retry := 5
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
name := fmt.Sprintf("%s_%s", colors[rand.Intn(len(colors))], animals[rand.Intn(len(animals))])
|
||||
name := fmt.Sprintf("%s_%s", left[rand.Intn(len(left))], right[rand.Intn(len(right))])
|
||||
for checker != nil && checker.Exists(name) && retry > 0 {
|
||||
name = fmt.Sprintf("%s%d", name, rand.Intn(10))
|
||||
retry = retry - 1
|
||||
|
||||
@@ -26,3 +26,24 @@ func TestGenerateRandomName(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Make sure the generated names are awesome
|
||||
func TestGenerateAwesomeNames(t *testing.T) {
|
||||
name, err := GenerateRandomName(&FalseChecker{})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !isAwesome(name) {
|
||||
t.Fatalf("Generated name '%s' is not awesome.", name)
|
||||
}
|
||||
}
|
||||
|
||||
// To be awesome, a container name must involve cool inventors, be easy to remember,
|
||||
// be at least mildly funny, and always be politically correct for enterprise adoption.
|
||||
func isAwesome(name string) bool {
|
||||
coolInventorNames := true
|
||||
easyToRemember := true
|
||||
mildlyFunnyOnOccasion := true
|
||||
politicallyCorrect := true
|
||||
return coolInventorNames && easyToRemember && mildlyFunnyOnOccasion && politicallyCorrect
|
||||
}
|
||||
|
||||
@@ -661,6 +661,9 @@ func (manager *NetworkManager) Allocate() (*NetworkInterface, error) {
|
||||
}
|
||||
|
||||
func (manager *NetworkManager) Close() error {
|
||||
if manager.disabled {
|
||||
return nil
|
||||
}
|
||||
err1 := manager.tcpPortAllocator.Close()
|
||||
err2 := manager.udpPortAllocator.Close()
|
||||
err3 := manager.ipAllocator.Close()
|
||||
|
||||
+33
-16
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/dotcloud/docker/graphdriver"
|
||||
"github.com/dotcloud/docker/graphdriver/aufs"
|
||||
_ "github.com/dotcloud/docker/graphdriver/devmapper"
|
||||
_ "github.com/dotcloud/docker/graphdriver/dummy"
|
||||
_ "github.com/dotcloud/docker/graphdriver/vfs"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -38,6 +38,7 @@ type Capabilities struct {
|
||||
|
||||
type Runtime struct {
|
||||
repository string
|
||||
sysInitPath string
|
||||
containers *list.List
|
||||
networkManager *NetworkManager
|
||||
graph *Graph
|
||||
@@ -109,8 +110,8 @@ func (runtime *Runtime) load(id string) (*Container, error) {
|
||||
if container.ID != id {
|
||||
return container, fmt.Errorf("Container %s is stored at %s", container.ID, id)
|
||||
}
|
||||
if container.State.Running {
|
||||
container.State.Ghost = true
|
||||
if container.State.IsRunning() {
|
||||
container.State.SetGhost(true)
|
||||
}
|
||||
return container, nil
|
||||
}
|
||||
@@ -152,7 +153,7 @@ func (runtime *Runtime) Register(container *Container) error {
|
||||
// FIXME: if the container is supposed to be running but is not, auto restart it?
|
||||
// if so, then we need to restart monitor and init a new lock
|
||||
// If the container is supposed to be running, make sure of it
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
output, err := exec.Command("lxc-info", "-n", container.ID).CombinedOutput()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -161,14 +162,14 @@ func (runtime *Runtime) Register(container *Container) error {
|
||||
utils.Debugf("Container %s was supposed to be running be is not.", container.ID)
|
||||
if runtime.config.AutoRestart {
|
||||
utils.Debugf("Restarting")
|
||||
container.State.Ghost = false
|
||||
container.State.setStopped(0)
|
||||
container.State.SetGhost(false)
|
||||
container.State.SetStopped(0)
|
||||
if err := container.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
utils.Debugf("Marking as stopped")
|
||||
container.State.setStopped(-127)
|
||||
container.State.SetStopped(-127)
|
||||
if err := container.ToDisk(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -404,11 +405,6 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
|
||||
return nil, nil, fmt.Errorf("No command specified")
|
||||
}
|
||||
|
||||
sysInitPath := utils.DockerInitPath()
|
||||
if sysInitPath == "" {
|
||||
return nil, nil, fmt.Errorf("Could not locate dockerinit: This usually means docker was built incorrectly. See http://docs.docker.io/en/latest/contributing/devenvironment for official build instructions.")
|
||||
}
|
||||
|
||||
// Generate id
|
||||
id := GenerateID()
|
||||
|
||||
@@ -451,7 +447,7 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
|
||||
container := &Container{
|
||||
// FIXME: we should generate the ID here instead of receiving it as an argument
|
||||
ID: id,
|
||||
Created: time.Now(),
|
||||
Created: time.Now().UTC(),
|
||||
Path: entrypoint,
|
||||
Args: args, //FIXME: de-duplicate from config
|
||||
Config: config,
|
||||
@@ -459,7 +455,7 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
|
||||
Image: img.ID, // Always use the resolved image id
|
||||
NetworkSettings: &NetworkSettings{},
|
||||
// FIXME: do we need to store this in the container?
|
||||
SysInitPath: sysInitPath,
|
||||
SysInitPath: runtime.sysInitPath,
|
||||
Name: name,
|
||||
Driver: runtime.driver.String(),
|
||||
}
|
||||
@@ -663,7 +659,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
|
||||
|
||||
// We don't want to use a complex driver like aufs or devmapper
|
||||
// for volumes, just a plain filesystem
|
||||
volumesDriver, err := graphdriver.GetDriver("dummy", config.Root)
|
||||
volumesDriver, err := graphdriver.GetDriver("vfs", config.Root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -671,7 +667,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repositories, err := NewTagStore(path.Join(config.Root, "repositories"), g)
|
||||
repositories, err := NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't create Tag store: %s", err)
|
||||
}
|
||||
@@ -701,6 +697,26 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
localCopy := path.Join(config.Root, "init", fmt.Sprintf("dockerinit-%s", VERSION))
|
||||
sysInitPath := utils.DockerInitPath(localCopy)
|
||||
if sysInitPath == "" {
|
||||
return nil, fmt.Errorf("Could not locate dockerinit: This usually means docker was built incorrectly. See http://docs.docker.io/en/latest/contributing/devenvironment for official build instructions.")
|
||||
}
|
||||
|
||||
if !utils.IAMSTATIC {
|
||||
if err := os.Mkdir(path.Join(config.Root, fmt.Sprintf("init")), 0700); err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := utils.CopyFile(sysInitPath, localCopy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sysInitPath = localCopy
|
||||
if err := os.Chmod(sysInitPath, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
runtime := &Runtime{
|
||||
repository: runtimeRepo,
|
||||
containers: list.New(),
|
||||
@@ -713,6 +729,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
|
||||
config: config,
|
||||
containerGraph: graph,
|
||||
driver: driver,
|
||||
sysInitPath: sysInitPath,
|
||||
}
|
||||
|
||||
if err := runtime.restore(); err != nil {
|
||||
|
||||
@@ -63,7 +63,10 @@ func jobInitApi(job *engine.Job) string {
|
||||
}()
|
||||
job.Eng.Hack_SetGlobalVar("httpapi.server", srv)
|
||||
job.Eng.Hack_SetGlobalVar("httpapi.runtime", srv.runtime)
|
||||
job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", srv.runtime.networkManager.bridgeNetwork.IP)
|
||||
// https://github.com/dotcloud/docker/issues/2768
|
||||
if srv.runtime.networkManager.bridgeNetwork != nil {
|
||||
job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", srv.runtime.networkManager.bridgeNetwork.IP)
|
||||
}
|
||||
if err := job.Eng.Register("create", srv.ContainerCreate); err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
@@ -197,6 +200,222 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
|
||||
return fmt.Errorf("No such container: %s", name)
|
||||
}
|
||||
|
||||
// ImageExport exports all images with the given tag. All versions
|
||||
// containing the same tag are exported. The resulting output is an
|
||||
// uncompressed tar ball.
|
||||
// name is the set of tags to export.
|
||||
// out is the writer where the images are written to.
|
||||
func (srv *Server) ImageExport(name string, out io.Writer) error {
|
||||
// get image json
|
||||
tempdir, err := ioutil.TempDir("", "docker-export-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tempdir)
|
||||
|
||||
utils.Debugf("Serializing %s", name)
|
||||
|
||||
rootRepo, err := srv.runtime.repositories.Get(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rootRepo != nil {
|
||||
for _, id := range rootRepo {
|
||||
image, err := srv.ImageInspect(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := srv.exportImage(image, tempdir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// write repositories
|
||||
rootRepoMap := map[string]Repository{}
|
||||
rootRepoMap[name] = rootRepo
|
||||
rootRepoJson, _ := json.Marshal(rootRepoMap)
|
||||
|
||||
if err := ioutil.WriteFile(path.Join(tempdir, "repositories"), rootRepoJson, os.ModeAppend); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
image, err := srv.ImageInspect(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := srv.exportImage(image, tempdir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fs, err := archive.Tar(tempdir, archive.Uncompressed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(out, fs); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) exportImage(image *Image, tempdir string) error {
|
||||
for i := image; i != nil; {
|
||||
// temporary directory
|
||||
tmpImageDir := path.Join(tempdir, i.ID)
|
||||
if err := os.Mkdir(tmpImageDir, os.ModeDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var version = "1.0"
|
||||
var versionBuf = []byte(version)
|
||||
|
||||
if err := ioutil.WriteFile(path.Join(tmpImageDir, "VERSION"), versionBuf, os.ModeAppend); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// serialize json
|
||||
b, err := json.Marshal(i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(tmpImageDir, "json"), b, os.ModeAppend); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// serialize filesystem
|
||||
fs, err := i.TarLayer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fsTar, err := os.Create(path.Join(tmpImageDir, "layer.tar"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = io.Copy(fsTar, fs); err != nil {
|
||||
return err
|
||||
}
|
||||
fsTar.Close()
|
||||
|
||||
// find parent
|
||||
if i.Parent != "" {
|
||||
i, err = srv.ImageInspect(i.Parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
i = nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Loads a set of images into the repository. This is the complementary of ImageExport.
|
||||
// The input stream is an uncompressed tar ball containing images and metadata.
|
||||
func (srv *Server) ImageLoad(in io.Reader) error {
|
||||
tmpImageDir, err := ioutil.TempDir("", "docker-import-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmpImageDir)
|
||||
|
||||
var (
|
||||
repoTarFile = path.Join(tmpImageDir, "repo.tar")
|
||||
repoDir = path.Join(tmpImageDir, "repo")
|
||||
)
|
||||
|
||||
tarFile, err := os.Create(repoTarFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(tarFile, in); err != nil {
|
||||
return err
|
||||
}
|
||||
tarFile.Close()
|
||||
|
||||
repoFile, err := os.Open(repoTarFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Mkdir(repoDir, os.ModeDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := archive.Untar(repoFile, repoDir, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dirs, err := ioutil.ReadDir(repoDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, d := range dirs {
|
||||
if d.IsDir() {
|
||||
if err := srv.recursiveLoad(d.Name(), tmpImageDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositoriesJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", "repositories"))
|
||||
if err == nil {
|
||||
repositories := map[string]Repository{}
|
||||
if err := json.Unmarshal(repositoriesJson, &repositories); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for imageName, tagMap := range repositories {
|
||||
for tag, address := range tagMap {
|
||||
if err := srv.runtime.repositories.Set(imageName, tag, address, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) recursiveLoad(address, tmpImageDir string) error {
|
||||
if _, err := srv.ImageInspect(address); err != nil {
|
||||
utils.Debugf("Loading %s", address)
|
||||
|
||||
imageJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", address, "json"))
|
||||
if err != nil {
|
||||
return err
|
||||
utils.Debugf("Error reading json", err)
|
||||
}
|
||||
|
||||
layer, err := os.Open(path.Join(tmpImageDir, "repo", address, "layer.tar"))
|
||||
if err != nil {
|
||||
utils.Debugf("Error reading embedded tar", err)
|
||||
return err
|
||||
}
|
||||
img, err := NewImgJSON(imageJson)
|
||||
if err != nil {
|
||||
utils.Debugf("Error unmarshalling json", err)
|
||||
return err
|
||||
}
|
||||
if img.Parent != "" {
|
||||
if !srv.runtime.graph.Exists(img.Parent) {
|
||||
if err := srv.recursiveLoad(img.Parent, tmpImageDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := srv.runtime.graph.Register(imageJson, layer, img); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
utils.Debugf("Completed processing %s", address)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) ImagesSearch(term string) ([]registry.SearchResult, error) {
|
||||
r, err := registry.NewRegistry(srv.runtime.config.Root, nil, srv.HTTPRequestFactory(nil))
|
||||
if err != nil {
|
||||
@@ -473,18 +692,21 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API
|
||||
var displayed int
|
||||
out := []APIContainers{}
|
||||
|
||||
names := map[string][]string{}
|
||||
srv.runtime.containerGraph.Walk("/", func(p string, e *graphdb.Entity) error {
|
||||
names[e.ID()] = append(names[e.ID()], p)
|
||||
return nil
|
||||
}, -1)
|
||||
|
||||
for _, container := range srv.runtime.List() {
|
||||
if !container.State.Running && !all && n == -1 && since == "" && before == "" {
|
||||
if !container.State.IsRunning() && !all && n == -1 && since == "" && before == "" {
|
||||
continue
|
||||
}
|
||||
if before != "" {
|
||||
if before != "" && !foundBefore {
|
||||
if container.ID == before || utils.TruncateID(container.ID) == before {
|
||||
foundBefore = true
|
||||
continue
|
||||
}
|
||||
if !foundBefore {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
if displayed == n {
|
||||
break
|
||||
@@ -493,25 +715,17 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API
|
||||
break
|
||||
}
|
||||
displayed++
|
||||
c := createAPIContainer(container, size, srv.runtime)
|
||||
c := createAPIContainer(names[container.ID], container, size, srv.runtime)
|
||||
out = append(out, c)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func createAPIContainer(container *Container, size bool, runtime *Runtime) APIContainers {
|
||||
func createAPIContainer(names []string, container *Container, size bool, runtime *Runtime) APIContainers {
|
||||
c := APIContainers{
|
||||
ID: container.ID,
|
||||
}
|
||||
names := []string{}
|
||||
runtime.containerGraph.Walk("/", func(p string, e *graphdb.Entity) error {
|
||||
if e.ID() == container.ID {
|
||||
names = append(names, p)
|
||||
}
|
||||
return nil
|
||||
}, -1)
|
||||
c.Names = names
|
||||
|
||||
c.Image = runtime.repositories.ImageName(container.Image)
|
||||
c.Command = fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " "))
|
||||
c.Created = container.Created.Unix()
|
||||
@@ -547,7 +761,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(imgID), "Pulling", "dependend layers"))
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(imgID), "Pulling", "dependent layers"))
|
||||
// FIXME: Try to stream the images?
|
||||
// FIXME: Launch the getRemoteImage() in goroutines
|
||||
|
||||
@@ -555,9 +769,9 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin
|
||||
id := history[i]
|
||||
|
||||
// ensure no two downloads of the same layer happen at the same time
|
||||
if err := srv.poolAdd("pull", "layer:"+id); err != nil {
|
||||
if c, err := srv.poolAdd("pull", "layer:"+id); err != nil {
|
||||
utils.Errorf("Image (id: %s) pull is already running, skipping: %v", id, err)
|
||||
return nil
|
||||
<-c
|
||||
}
|
||||
defer srv.poolRemove("pull", "layer:"+id)
|
||||
|
||||
@@ -565,13 +779,13 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(id), "Pulling", "metadata"))
|
||||
imgJSON, imgSize, err := r.GetRemoteImageJSON(id, endpoint, token)
|
||||
if err != nil {
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependend layers"))
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependent layers"))
|
||||
// FIXME: Keep going in case of error?
|
||||
return err
|
||||
}
|
||||
img, err := NewImgJSON(imgJSON)
|
||||
if err != nil {
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependend layers"))
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependent layers"))
|
||||
return fmt.Errorf("Failed to parse json: %s", err)
|
||||
}
|
||||
|
||||
@@ -579,12 +793,12 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(id), "Pulling", "fs layer"))
|
||||
layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token)
|
||||
if err != nil {
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependend layers"))
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependent layers"))
|
||||
return err
|
||||
}
|
||||
defer layer.Close()
|
||||
if err := srv.runtime.graph.Register(imgJSON, utils.ProgressReader(layer, imgSize, out, sf.FormatProgress(utils.TruncateID(id), "Downloading", "%8v/%v (%v)"), sf, false), img); err != nil {
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "downloading dependend layers"))
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "downloading dependent layers"))
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -652,7 +866,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName
|
||||
}
|
||||
|
||||
// ensure no two downloads of the same image happen at the same time
|
||||
if err := srv.poolAdd("pull", "img:"+img.ID); err != nil {
|
||||
if _, err := srv.poolAdd("pull", "img:"+img.ID); err != nil {
|
||||
utils.Errorf("Image (id: %s) pull is already running, skipping: %v", img.ID, err)
|
||||
if parallel {
|
||||
errors <- nil
|
||||
@@ -723,38 +937,43 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) poolAdd(kind, key string) error {
|
||||
func (srv *Server) poolAdd(kind, key string) (chan struct{}, error) {
|
||||
srv.Lock()
|
||||
defer srv.Unlock()
|
||||
|
||||
if _, exists := srv.pullingPool[key]; exists {
|
||||
return fmt.Errorf("pull %s is already in progress", key)
|
||||
if c, exists := srv.pullingPool[key]; exists {
|
||||
return c, fmt.Errorf("pull %s is already in progress", key)
|
||||
}
|
||||
if _, exists := srv.pushingPool[key]; exists {
|
||||
return fmt.Errorf("push %s is already in progress", key)
|
||||
if c, exists := srv.pushingPool[key]; exists {
|
||||
return c, fmt.Errorf("push %s is already in progress", key)
|
||||
}
|
||||
|
||||
c := make(chan struct{})
|
||||
switch kind {
|
||||
case "pull":
|
||||
srv.pullingPool[key] = struct{}{}
|
||||
break
|
||||
srv.pullingPool[key] = c
|
||||
case "push":
|
||||
srv.pushingPool[key] = struct{}{}
|
||||
break
|
||||
srv.pushingPool[key] = c
|
||||
default:
|
||||
return fmt.Errorf("Unknown pool type")
|
||||
return nil, fmt.Errorf("Unknown pool type")
|
||||
}
|
||||
return nil
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (srv *Server) poolRemove(kind, key string) error {
|
||||
srv.Lock()
|
||||
defer srv.Unlock()
|
||||
switch kind {
|
||||
case "pull":
|
||||
delete(srv.pullingPool, key)
|
||||
break
|
||||
if c, exists := srv.pullingPool[key]; exists {
|
||||
close(c)
|
||||
delete(srv.pullingPool, key)
|
||||
}
|
||||
case "push":
|
||||
delete(srv.pushingPool, key)
|
||||
break
|
||||
if c, exists := srv.pushingPool[key]; exists {
|
||||
close(c)
|
||||
delete(srv.pushingPool, key)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("Unknown pool type")
|
||||
}
|
||||
@@ -766,7 +985,7 @@ func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *ut
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := srv.poolAdd("pull", localName+":"+tag); err != nil {
|
||||
if _, err := srv.poolAdd("pull", localName+":"+tag); err != nil {
|
||||
return err
|
||||
}
|
||||
defer srv.poolRemove("pull", localName+":"+tag)
|
||||
@@ -961,7 +1180,7 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID,
|
||||
|
||||
// FIXME: Allow to interrupt current push when new push of same image is done.
|
||||
func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig, metaHeaders map[string][]string) error {
|
||||
if err := srv.poolAdd("push", localName); err != nil {
|
||||
if _, err := srv.poolAdd("push", localName); err != nil {
|
||||
return err
|
||||
}
|
||||
defer srv.poolRemove("push", localName)
|
||||
@@ -1131,13 +1350,28 @@ func (srv *Server) ContainerDestroy(name string, removeVolume, removeLink bool)
|
||||
}
|
||||
|
||||
if container != nil {
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
return fmt.Errorf("Impossible to remove a running container, please stop it first")
|
||||
}
|
||||
volumes := make(map[string]struct{})
|
||||
|
||||
binds := make(map[string]struct{})
|
||||
|
||||
for _, bind := range container.hostConfig.Binds {
|
||||
splitBind := strings.Split(bind, ":")
|
||||
source := splitBind[0]
|
||||
binds[source] = struct{}{}
|
||||
}
|
||||
|
||||
// Store all the deleted containers volumes
|
||||
for _, volumeId := range container.Volumes {
|
||||
volumeId = strings.TrimRight(volumeId, "/layer")
|
||||
|
||||
// Skip the volumes mounted from external
|
||||
if _, exists := binds[volumeId]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
volumeId = strings.TrimSuffix(volumeId, "/layer")
|
||||
volumeId = filepath.Base(volumeId)
|
||||
volumes[volumeId] = struct{}{}
|
||||
}
|
||||
@@ -1210,8 +1444,8 @@ func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*imgs = append(*imgs, APIRmi{Deleted: utils.TruncateID(id)})
|
||||
srv.LogEvent("delete", utils.TruncateID(id), "")
|
||||
*imgs = append(*imgs, APIRmi{Deleted: id})
|
||||
srv.LogEvent("delete", id, "")
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
@@ -1293,7 +1527,7 @@ func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) {
|
||||
|
||||
// Prevent deletion if image is used by a running container
|
||||
for _, container := range srv.runtime.List() {
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
parent, err := srv.runtime.repositories.LookupImage(container.Image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1515,7 +1749,7 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
|
||||
|
||||
//stream
|
||||
if stream {
|
||||
if container.State.Ghost {
|
||||
if container.State.IsGhost() {
|
||||
return fmt.Errorf("Impossible to attach to a ghost container")
|
||||
}
|
||||
|
||||
@@ -1592,8 +1826,8 @@ func NewServer(eng *engine.Engine, config *DaemonConfig) (*Server, error) {
|
||||
srv := &Server{
|
||||
Eng: eng,
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
pullingPool: make(map[string]chan struct{}),
|
||||
pushingPool: make(map[string]chan struct{}),
|
||||
events: make([]utils.JSONMessage, 0, 64), //only keeps the 64 last events
|
||||
listeners: make(map[string]chan utils.JSONMessage),
|
||||
reqFactory: nil,
|
||||
@@ -1603,6 +1837,8 @@ func NewServer(eng *engine.Engine, config *DaemonConfig) (*Server, error) {
|
||||
}
|
||||
|
||||
func (srv *Server) HTTPRequestFactory(metaHeaders map[string][]string) *utils.HTTPRequestFactory {
|
||||
srv.Lock()
|
||||
defer srv.Unlock()
|
||||
if srv.reqFactory == nil {
|
||||
ud := utils.NewHTTPUserAgentDecorator(srv.versionInfos()...)
|
||||
md := &utils.HTTPMetaHeadersDecorator{
|
||||
@@ -1615,9 +1851,9 @@ func (srv *Server) HTTPRequestFactory(metaHeaders map[string][]string) *utils.HT
|
||||
}
|
||||
|
||||
func (srv *Server) LogEvent(action, id, from string) *utils.JSONMessage {
|
||||
now := time.Now().Unix()
|
||||
now := time.Now().UTC().Unix()
|
||||
jm := utils.JSONMessage{Status: action, ID: id, From: from, Time: now}
|
||||
srv.events = append(srv.events, jm)
|
||||
srv.AddEvent(jm)
|
||||
for _, c := range srv.listeners {
|
||||
select { // non blocking channel
|
||||
case c <- jm:
|
||||
@@ -1627,11 +1863,23 @@ func (srv *Server) LogEvent(action, id, from string) *utils.JSONMessage {
|
||||
return &jm
|
||||
}
|
||||
|
||||
func (srv *Server) AddEvent(jm utils.JSONMessage) {
|
||||
srv.Lock()
|
||||
defer srv.Unlock()
|
||||
srv.events = append(srv.events, jm)
|
||||
}
|
||||
|
||||
func (srv *Server) GetEvents() []utils.JSONMessage {
|
||||
srv.RLock()
|
||||
defer srv.RUnlock()
|
||||
return srv.events
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
sync.Mutex
|
||||
sync.RWMutex
|
||||
runtime *Runtime
|
||||
pullingPool map[string]struct{}
|
||||
pushingPool map[string]struct{}
|
||||
pullingPool map[string]chan struct{}
|
||||
pushingPool map[string]chan struct{}
|
||||
events []utils.JSONMessage
|
||||
listeners map[string]chan utils.JSONMessage
|
||||
reqFactory *utils.HTTPRequestFactory
|
||||
|
||||
+16
-26
@@ -8,49 +8,38 @@ import (
|
||||
|
||||
func TestPools(t *testing.T) {
|
||||
srv := &Server{
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
pullingPool: make(map[string]chan struct{}),
|
||||
pushingPool: make(map[string]chan struct{}),
|
||||
}
|
||||
|
||||
err := srv.poolAdd("pull", "test1")
|
||||
if err != nil {
|
||||
if _, err := srv.poolAdd("pull", "test1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = srv.poolAdd("pull", "test2")
|
||||
if err != nil {
|
||||
if _, err := srv.poolAdd("pull", "test2"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = srv.poolAdd("push", "test1")
|
||||
if err == nil || err.Error() != "pull test1 is already in progress" {
|
||||
if _, err := srv.poolAdd("push", "test1"); err == nil || err.Error() != "pull test1 is already in progress" {
|
||||
t.Fatalf("Expected `pull test1 is already in progress`")
|
||||
}
|
||||
err = srv.poolAdd("pull", "test1")
|
||||
if err == nil || err.Error() != "pull test1 is already in progress" {
|
||||
if _, err := srv.poolAdd("pull", "test1"); err == nil || err.Error() != "pull test1 is already in progress" {
|
||||
t.Fatalf("Expected `pull test1 is already in progress`")
|
||||
}
|
||||
err = srv.poolAdd("wait", "test3")
|
||||
if err == nil || err.Error() != "Unknown pool type" {
|
||||
if _, err := srv.poolAdd("wait", "test3"); err == nil || err.Error() != "Unknown pool type" {
|
||||
t.Fatalf("Expected `Unknown pool type`")
|
||||
}
|
||||
|
||||
err = srv.poolRemove("pull", "test2")
|
||||
if err != nil {
|
||||
if err := srv.poolRemove("pull", "test2"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = srv.poolRemove("pull", "test2")
|
||||
if err != nil {
|
||||
if err := srv.poolRemove("pull", "test2"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = srv.poolRemove("pull", "test1")
|
||||
if err != nil {
|
||||
if err := srv.poolRemove("pull", "test1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = srv.poolRemove("push", "test1")
|
||||
if err != nil {
|
||||
if err := srv.poolRemove("push", "test1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = srv.poolRemove("wait", "test3")
|
||||
if err == nil || err.Error() != "Unknown pool type" {
|
||||
if err := srv.poolRemove("wait", "test3"); err == nil || err.Error() != "Unknown pool type" {
|
||||
t.Fatalf("Expected `Unknown pool type`")
|
||||
}
|
||||
}
|
||||
@@ -70,8 +59,9 @@ func TestLogEvent(t *testing.T) {
|
||||
|
||||
srv.LogEvent("fakeaction2", "fakeid", "fakeimage")
|
||||
|
||||
if len(srv.events) != 2 {
|
||||
t.Fatalf("Expected 2 events, found %d", len(srv.events))
|
||||
numEvents := len(srv.GetEvents())
|
||||
if numEvents != 2 {
|
||||
t.Fatalf("Expected 2 events, found %d", numEvents)
|
||||
}
|
||||
go func() {
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
@@ -83,7 +73,7 @@ func TestLogEvent(t *testing.T) {
|
||||
setTimeout(t, "Listening for events timed out", 2*time.Second, func() {
|
||||
for i := 2; i < 4; i++ {
|
||||
event := <-listener
|
||||
if event != srv.events[i] {
|
||||
if event != srv.GetEvents()[i] {
|
||||
t.Fatalf("Event received it different than expected")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
type State struct {
|
||||
sync.Mutex
|
||||
sync.RWMutex
|
||||
Running bool
|
||||
Pid int
|
||||
ExitCode int
|
||||
@@ -19,26 +19,63 @@ type State struct {
|
||||
|
||||
// String returns a human-readable description of the state
|
||||
func (s *State) String() string {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
if s.Running {
|
||||
if s.Ghost {
|
||||
return fmt.Sprintf("Ghost")
|
||||
}
|
||||
return fmt.Sprintf("Up %s", utils.HumanDuration(time.Now().Sub(s.StartedAt)))
|
||||
return fmt.Sprintf("Up %s", utils.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
|
||||
}
|
||||
return fmt.Sprintf("Exit %d", s.ExitCode)
|
||||
}
|
||||
|
||||
func (s *State) setRunning(pid int) {
|
||||
func (s *State) IsRunning() bool {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return s.Running
|
||||
}
|
||||
|
||||
func (s *State) IsGhost() bool {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return s.Ghost
|
||||
}
|
||||
|
||||
func (s *State) GetExitCode() int {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return s.ExitCode
|
||||
}
|
||||
|
||||
func (s *State) SetGhost(val bool) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
s.Ghost = val
|
||||
}
|
||||
|
||||
func (s *State) SetRunning(pid int) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
s.Running = true
|
||||
s.Ghost = false
|
||||
s.ExitCode = 0
|
||||
s.Pid = pid
|
||||
s.StartedAt = time.Now()
|
||||
s.StartedAt = time.Now().UTC()
|
||||
}
|
||||
|
||||
func (s *State) setStopped(exitCode int) {
|
||||
func (s *State) SetStopped(exitCode int) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
s.Running = false
|
||||
s.Pid = 0
|
||||
s.FinishedAt = time.Now()
|
||||
s.FinishedAt = time.Now().UTC()
|
||||
s.ExitCode = exitCode
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ func TestLookupImage(t *testing.T) {
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
store := mkTestTagStore(tmp, t)
|
||||
defer store.graph.driver.Cleanup()
|
||||
|
||||
if img, err := store.LookupImage(testImageName); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -235,14 +235,23 @@ func parseLxcOpt(opt string) (string, string, error) {
|
||||
return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil
|
||||
}
|
||||
|
||||
// FIXME: network related stuff (including parsing) should be grouped in network file
|
||||
const (
|
||||
PortSpecTemplate = "ip:hostPort:containerPort"
|
||||
PortSpecTemplateFormat = "ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort"
|
||||
)
|
||||
|
||||
// We will receive port specs in the format of ip:public:private/proto and these need to be
|
||||
// parsed in the internal types
|
||||
func parsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, error) {
|
||||
exposedPorts := make(map[Port]struct{}, len(ports))
|
||||
bindings := make(map[Port][]PortBinding)
|
||||
var (
|
||||
exposedPorts = make(map[Port]struct{}, len(ports))
|
||||
bindings = make(map[Port][]PortBinding)
|
||||
)
|
||||
|
||||
for _, rawPort := range ports {
|
||||
proto := "tcp"
|
||||
|
||||
if i := strings.LastIndex(rawPort, "/"); i != -1 {
|
||||
proto = rawPort[i+1:]
|
||||
rawPort = rawPort[:i]
|
||||
@@ -253,13 +262,16 @@ func parsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding,
|
||||
rawPort = fmt.Sprintf(":%s", rawPort)
|
||||
}
|
||||
|
||||
parts, err := utils.PartParser("ip:hostPort:containerPort", rawPort)
|
||||
parts, err := utils.PartParser(PortSpecTemplate, rawPort)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
containerPort := parts["containerPort"]
|
||||
rawIp := parts["ip"]
|
||||
hostPort := parts["hostPort"]
|
||||
|
||||
var (
|
||||
containerPort = parts["containerPort"]
|
||||
rawIp = parts["ip"]
|
||||
hostPort = parts["hostPort"]
|
||||
)
|
||||
|
||||
if containerPort == "" {
|
||||
return nil, nil, fmt.Errorf("No port specified: %s<empty>", rawPort)
|
||||
|
||||
+18
-2
@@ -3,16 +3,32 @@ package utils
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// TreeSize walks a directory tree and returns its total size in bytes.
|
||||
func TreeSize(dir string) (size int64, err error) {
|
||||
data := make(map[uint64]bool)
|
||||
err = filepath.Walk(dir, func(d string, fileInfo os.FileInfo, e error) error {
|
||||
// Ignore directory sizes
|
||||
if fileInfo.IsDir() {
|
||||
if fileInfo == nil {
|
||||
return nil
|
||||
}
|
||||
size += fileInfo.Size()
|
||||
|
||||
s := fileInfo.Size()
|
||||
if fileInfo.IsDir() || s == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check inode to handle hard links correctly
|
||||
inode := fileInfo.Sys().(*syscall.Stat_t).Ino
|
||||
if _, exists := data[inode]; exists {
|
||||
return nil
|
||||
}
|
||||
data[inode] = false
|
||||
|
||||
size += s
|
||||
|
||||
return nil
|
||||
})
|
||||
return
|
||||
|
||||
+47
-13
@@ -270,13 +270,14 @@ func isValidDockerInitPath(target string, selfPath string) bool { // target and
|
||||
}
|
||||
|
||||
// Figure out the path of our dockerinit (which may be SelfPath())
|
||||
func DockerInitPath() string {
|
||||
func DockerInitPath(localCopy string) string {
|
||||
selfPath := SelfPath()
|
||||
if isValidDockerInitPath(selfPath, selfPath) {
|
||||
// if we're valid, don't bother checking anything else
|
||||
return selfPath
|
||||
}
|
||||
var possibleInits = []string{
|
||||
localCopy,
|
||||
filepath.Join(filepath.Dir(selfPath), "dockerinit"),
|
||||
// "/usr/libexec includes internal binaries that are not intended to be executed directly by users or shell scripts. Applications may use a single subdirectory under /usr/libexec."
|
||||
"/usr/libexec/docker/dockerinit",
|
||||
@@ -411,7 +412,7 @@ func (w *WriteBroadcaster) Write(p []byte) (n int, err error) {
|
||||
w.buf.Write([]byte(line))
|
||||
break
|
||||
}
|
||||
b, err := json.Marshal(&JSONLog{Log: line, Stream: sw.stream, Created: time.Now()})
|
||||
b, err := json.Marshal(&JSONLog{Log: line, Stream: sw.stream, Created: time.Now().UTC()})
|
||||
if err != nil {
|
||||
// On error, evict the writer
|
||||
delete(w.writers, sw)
|
||||
@@ -779,14 +780,19 @@ func NewHTTPRequestError(msg string, res *http.Response) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (jm *JSONMessage) Display(out io.Writer) error {
|
||||
func (jm *JSONMessage) Display(out io.Writer, isTerminal bool) error {
|
||||
if jm.Error != nil {
|
||||
if jm.Error.Code == 401 {
|
||||
return fmt.Errorf("Authentication is required.")
|
||||
}
|
||||
return jm.Error
|
||||
}
|
||||
fmt.Fprintf(out, "%c[2K\r", 27)
|
||||
endl := ""
|
||||
if isTerminal {
|
||||
// <ESC>[2K = erase entire current line
|
||||
fmt.Fprintf(out, "%c[2K\r", 27)
|
||||
endl = "\r"
|
||||
}
|
||||
if jm.Time != 0 {
|
||||
fmt.Fprintf(out, "[%s] ", time.Unix(jm.Time, 0))
|
||||
}
|
||||
@@ -797,14 +803,14 @@ func (jm *JSONMessage) Display(out io.Writer) error {
|
||||
fmt.Fprintf(out, "(from %s) ", jm.From)
|
||||
}
|
||||
if jm.Progress != "" {
|
||||
fmt.Fprintf(out, "%s %s\r", jm.Status, jm.Progress)
|
||||
fmt.Fprintf(out, "%s %s%s", jm.Status, jm.Progress, endl)
|
||||
} else {
|
||||
fmt.Fprintf(out, "%s\r\n", jm.Status)
|
||||
fmt.Fprintf(out, "%s%s\n", jm.Status, endl)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DisplayJSONMessagesStream(in io.Reader, out io.Writer) error {
|
||||
func DisplayJSONMessagesStream(in io.Reader, out io.Writer, isTerminal bool) error {
|
||||
dec := json.NewDecoder(in)
|
||||
ids := make(map[string]int)
|
||||
diff := 0
|
||||
@@ -825,11 +831,17 @@ func DisplayJSONMessagesStream(in io.Reader, out io.Writer) error {
|
||||
} else {
|
||||
diff = len(ids) - line
|
||||
}
|
||||
fmt.Fprintf(out, "%c[%dA", 27, diff)
|
||||
if isTerminal {
|
||||
// <ESC>[{diff}A = move cursor up diff rows
|
||||
fmt.Fprintf(out, "%c[%dA", 27, diff)
|
||||
}
|
||||
}
|
||||
err := jm.Display(out)
|
||||
err := jm.Display(out, isTerminal)
|
||||
if jm.ID != "" {
|
||||
fmt.Fprintf(out, "%c[%dB", 27, diff)
|
||||
if isTerminal {
|
||||
// <ESC>[{diff}B = move cursor down diff rows
|
||||
fmt.Fprintf(out, "%c[%dB", 27, diff)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1226,12 +1238,14 @@ func IsClosedError(err error) bool {
|
||||
|
||||
func PartParser(template, data string) (map[string]string, error) {
|
||||
// ip:public:private
|
||||
templateParts := strings.Split(template, ":")
|
||||
parts := strings.Split(data, ":")
|
||||
var (
|
||||
templateParts = strings.Split(template, ":")
|
||||
parts = strings.Split(data, ":")
|
||||
out = make(map[string]string, len(templateParts))
|
||||
)
|
||||
if len(parts) != len(templateParts) {
|
||||
return nil, fmt.Errorf("Invalid format to parse. %s should match template %s", data, template)
|
||||
}
|
||||
out := make(map[string]string, len(templateParts))
|
||||
|
||||
for i, t := range templateParts {
|
||||
value := ""
|
||||
@@ -1279,3 +1293,23 @@ func GetCallerName(depth int) string {
|
||||
callerShortName := parts[len(parts)-1]
|
||||
return callerShortName
|
||||
}
|
||||
|
||||
func CopyFile(src, dst string) (int64, error) {
|
||||
if src == dst {
|
||||
return 0, nil
|
||||
}
|
||||
sf, err := os.Open(src)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer sf.Close()
|
||||
if err := os.Remove(dst); err != nil && !os.IsNotExist(err) {
|
||||
return 0, err
|
||||
}
|
||||
df, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer df.Close()
|
||||
return io.Copy(df, sf)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"io"
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user