From 6a693176d6b2dbf4afd36ebda2aeb9495fdf42bc Mon Sep 17 00:00:00 2001 From: Liang-Chi Hsieh Date: Thu, 31 Oct 2013 23:49:30 +0800 Subject: [PATCH 01/15] skip the volumes mounted when deleting the volumes of container. --- server.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/server.go b/server.go index 6a5d0a3294..d790787d6c 100644 --- a/server.go +++ b/server.go @@ -1104,8 +1104,12 @@ func (srv *Server) ContainerDestroy(name string, removeVolume, removeLink bool) volumes := make(map[string]struct{}) // Store all the deleted containers volumes for _, volumeId := range container.Volumes { - volumeId = strings.TrimRight(volumeId, "/layer") - volumeId = filepath.Base(volumeId) + trim_volumeId := strings.TrimSuffix(volumeId, "/layer") + // Skip the volumes mounted + if trim_volumeId == volumeId { + continue + } + volumeId = filepath.Base(trim_volumeId) volumes[volumeId] = struct{}{} } if err := srv.runtime.Destroy(container); err != nil { From 1d7f22c0d4bb55cf797b303cc46b1944509e4085 Mon Sep 17 00:00:00 2001 From: Liang-Chi Hsieh Date: Wed, 13 Nov 2013 14:59:24 +0800 Subject: [PATCH 02/15] use Binds key in hostConfig to detect volumes mounted from external. --- server.go | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/server.go b/server.go index d790787d6c..b7f16fe9a4 100644 --- a/server.go +++ b/server.go @@ -1102,14 +1102,25 @@ func (srv *Server) ContainerDestroy(name string, removeVolume, removeLink bool) 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 { - trim_volumeId := strings.TrimSuffix(volumeId, "/layer") - // Skip the volumes mounted - if trim_volumeId == volumeId { - continue - } - volumeId = filepath.Base(trim_volumeId) + + // Skip the volumes mounted from external + if _, exists := binds[volumeId]; exists { + continue + } + + volumeId = strings.TrimSuffix(volumeId, "/layer") + volumeId = filepath.Base(volumeId) volumes[volumeId] = struct{}{} } if err := srv.runtime.Destroy(container); err != nil { From 0013aa7d9f74eab1c6d02cf9f7b35a1b9364b17a Mon Sep 17 00:00:00 2001 From: Xiuming Chen Date: Wed, 13 Nov 2013 01:29:00 -0800 Subject: [PATCH 03/15] Minor code simplification for Containers api --- server.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/server.go b/server.go index 6a5d0a3294..a83463144f 100644 --- a/server.go +++ b/server.go @@ -464,14 +464,11 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API if !container.State.Running && !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 From bc74f65068b6b80b657e07f4ab2d7b115aacae75 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Sat, 16 Nov 2013 21:11:34 +1000 Subject: [PATCH 04/15] make the docker commit help more copy&pasteable --- commands.go | 2 +- docs/sources/commandline/cli.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/commands.go b/commands.go index 710a311c08..18be5fc051 100644 --- a/commands.go +++ b/commands.go @@ -1360,7 +1360,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error { cmd := 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 \"") - 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 } diff --git a/docs/sources/commandline/cli.rst b/docs/sources/commandline/cli.rst index 37f371baad..7db2a309b7 100644 --- a/docs/sources/commandline/cli.rst +++ b/docs/sources/commandline/cli.rst @@ -157,7 +157,7 @@ by using the ``git://`` schema. -m="": Commit message -author="": Author (eg. "John Hannibal Smith " -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 +173,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 ................. From 2fe4467d73b1bfe4a64011ee9d6b6aaf8b244fa5 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 18 Nov 2013 18:39:02 -0800 Subject: [PATCH 05/15] Do ont truncate ID on docker rmi --- server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.go b/server.go index 79ead96a62..6860ea5a9b 100644 --- a/server.go +++ b/server.go @@ -1205,8 +1205,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 From ac821f2446628a9151587038f1d377f57985a452 Mon Sep 17 00:00:00 2001 From: unclejack Date: Sat, 19 Oct 2013 04:17:00 +0300 Subject: [PATCH 06/15] fix layer size computation: handle hard links This change makes docker compute layer size correctly. The old code isn't taking hard links into account. Layers could seem like they're up to 1-1.5x larger than they really were. --- image.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/image.go b/image.go index c600273c1a..f2d1b3e7ee 100644 --- a/image.go +++ b/image.go @@ -16,6 +16,7 @@ import ( "path/filepath" "strconv" "strings" + "syscall" "time" ) @@ -114,10 +115,22 @@ func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root str func StoreSize(img *Image, root string) error { layer := layerPath(root) + data := make(map[uint64]bool) var totalSize int64 = 0 filepath.Walk(layer, func(path string, fileInfo os.FileInfo, err error) error { - totalSize += fileInfo.Size() + size := fileInfo.Size() + if size == 0 { + return nil + } + + inode := fileInfo.Sys().(*syscall.Stat_t).Ino + if _, entryExists := data[inode]; entryExists { + return nil + } + data[inode] = false + + totalSize += size return nil }) img.Size = totalSize From 78c843c8eff64da5e12725bdb111b18e51d82606 Mon Sep 17 00:00:00 2001 From: unclejack Date: Tue, 19 Nov 2013 00:13:09 +0200 Subject: [PATCH 07/15] fix container size computation: handle hard links This change makes docker compute container size correctly. The old code isn't taking hard links into account. Containers could seem like they're up to 1-1.5x larger than they really were. --- container.go | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/container.go b/container.go index 9dac345447..0686d2f5aa 100644 --- a/container.go +++ b/container.go @@ -1559,20 +1559,46 @@ func validateID(id string) error { // GetSize, return real size, virtual size func (container *Container) GetSize() (int64, int64) { var sizeRw, sizeRootfs int64 + data := make(map[uint64]bool) filepath.Walk(container.rwPath(), func(path string, fileInfo os.FileInfo, err error) error { - if fileInfo != nil { - sizeRw += fileInfo.Size() + if fileInfo == nil { + return nil } + size := fileInfo.Size() + if size == 0 { + return nil + } + + inode := fileInfo.Sys().(*syscall.Stat_t).Ino + if _, entryExists := data[inode]; entryExists { + return nil + } + data[inode] = false + + sizeRw += size return nil }) + data = make(map[uint64]bool) _, 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() + if fileInfo == nil { + return nil } + size := fileInfo.Size() + if size == 0 { + return nil + } + + inode := fileInfo.Sys().(*syscall.Stat_t).Ino + if _, entryExists := data[inode]; entryExists { + return nil + } + data[inode] = false + + sizeRootfs += size return nil }) } From aeb304b37c190e7fab60c0db946d1fd5d8e9e9f3 Mon Sep 17 00:00:00 2001 From: Vitor Monteiro Date: Tue, 19 Nov 2013 17:50:38 +0000 Subject: [PATCH 08/15] Change to documentation for AWS AMI request Hi guys, it just might be me, but clicking the AMI from http://cloud-images.ubuntu.com/locator/ec2/ is broken to me. So I just did it via the normal Create Instance Wizard. I just though some people might have the same issue. Sorry if my markdown for links is screwed up, I went by the examples, since I'm used to the `[]()` traditional one. Cheers. --- docs/sources/installation/amazon.rst | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/sources/installation/amazon.rst b/docs/sources/installation/amazon.rst index 6d92046dbf..0c36dbfc33 100644 --- a/docs/sources/installation/amazon.rst +++ b/docs/sources/installation/amazon.rst @@ -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` 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` ) + * 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:** From 4f9f83d6c6e71f826489f04e497f4a384c49a2c1 Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Fri, 15 Nov 2013 16:11:05 -0800 Subject: [PATCH 09/15] Fix #2342. Harmonize information about ADD. Cross-link build info. --- docs/sources/commandline/cli.rst | 65 ++++++++++++++++++++++++-------- docs/sources/use/builder.rst | 52 ++++++++++++++++++++----- 2 files changed, 91 insertions(+), 26 deletions(-) diff --git a/docs/sources/commandline/cli.rst b/docs/sources/commandline/cli.rst index d0a8d83c0c..8068b881ce 100644 --- a/docs/sources/commandline/cli.rst +++ b/docs/sources/commandline/cli.rst @@ -88,31 +88,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 ` 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 ` 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 +163,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. diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst index b8dd95bad0..2035c276dd 100644 --- a/docs/sources/use/builder.rst +++ b/docs/sources/use/builder.rst @@ -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 ` 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": ["", ""]}'`` outside the builder. Refer to :ref:`port_redirection` for detailed information. +.. _dockerfile_env: + 3.6 ENV ------- @@ -203,6 +227,8 @@ with ``=`` 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 ```` 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 ====================== From d7e2fc898284fe29ed43f7b28eae56e7e052e854 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Thu, 21 Nov 2013 08:06:02 -0500 Subject: [PATCH 10/15] Lock state before we modify. When we start a container we lock state, we should do the same in stop. Detected via -race. --- container.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/container.go b/container.go index 16f45cb490..d6b73f57cb 100644 --- a/container.go +++ b/container.go @@ -1300,7 +1300,9 @@ func (container *Container) monitor() { } // Report status back + container.State.Lock() container.State.setStopped(exitCode) + container.State.Unlock() // Release the lock close(container.waitLock) From 75a7f4d90cde0295bcfb7213004abce8d4779b75 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 20 Nov 2013 17:41:25 -0800 Subject: [PATCH 11/15] Return status exit status 2 on usage, move parserun into commands.go, display usage on stderr --- commands.go | 288 ++++++++++++++++++++++++++++++++++++++++++++------- container.go | 213 ------------------------------------- 2 files changed, 252 insertions(+), 249 deletions(-) diff --git a/commands.go b/commands.go index a551d598c9..3473053e0d 100644 --- a/commands.go +++ b/commands.go @@ -22,6 +22,7 @@ import ( "net/url" "os" "os/signal" + "path" "path/filepath" "reflect" "regexp" @@ -119,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 } @@ -161,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") @@ -259,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 @@ -367,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 } @@ -390,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 } @@ -441,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 } @@ -493,7 +494,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 @@ -520,7 +521,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 @@ -563,7 +564,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 { @@ -649,7 +650,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { } 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 } @@ -700,7 +701,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 } @@ -732,7 +733,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 } @@ -774,7 +775,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 } @@ -810,7 +811,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") @@ -867,7 +868,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") @@ -901,7 +902,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 } @@ -923,7 +924,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 @@ -957,7 +958,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 } @@ -1019,7 +1020,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 @@ -1079,7 +1080,7 @@ 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 (by default filter out the intermediate images used to build)") noTrunc := cmd.Bool("notrunc", false, "Don't truncate output") @@ -1276,7 +1277,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.") @@ -1365,7 +1366,7 @@ 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 \"") flConfig := cmd.String("run", "", "Config automatically applied when the image is run. "+`(ex: {"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`) @@ -1417,7 +1418,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 @@ -1450,7 +1451,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") if err := cmd.Parse(args); err != nil { return nil } @@ -1467,7 +1468,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 } @@ -1493,7 +1494,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 } @@ -1520,7 +1521,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 { @@ -1575,7 +1576,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") @@ -1687,7 +1688,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 @@ -1720,8 +1721,223 @@ 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) { + + 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: , 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 +} + 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 } @@ -1932,7 +2148,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { } func (cli *DockerCli) CmdCp(args ...string) error { - cmd := Subcmd("cp", "CONTAINER:PATH HOSTPATH", "Copy files/folders from the PATH 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 } @@ -1967,7 +2183,7 @@ func (cli *DockerCli) CmdCp(args ...string) error { } func (cli *DockerCli) CmdSave(args ...string) error { - cmd := Subcmd("save", "IMAGE DESTINATION", "Save an image to a tar archive") + cmd := cli.Subcmd("save", "IMAGE DESTINATION", "Save an image to a tar archive") if err := cmd.Parse(args); err != nil { return err } @@ -1985,7 +2201,7 @@ func (cli *DockerCli) CmdSave(args ...string) error { } func (cli *DockerCli) CmdLoad(args ...string) error { - cmd := Subcmd("load", "SOURCE", "Load an image from a tar archive") + cmd := cli.Subcmd("load", "SOURCE", "Load an image from a tar archive") if err := cmd.Parse(args); err != nil { return err } @@ -2251,12 +2467,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 } diff --git a/container.go b/container.go index d6b73f57cb..a363d0095d 100644 --- a/container.go +++ b/container.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/json" "errors" - "flag" "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/term" @@ -156,218 +155,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: , 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 { From cb70eedfdad40538983f9610322262ce84e284a0 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 21 Nov 2013 12:11:25 -0800 Subject: [PATCH 12/15] Revert "Lock state before we modify." This reverts commit d7e2fc898284fe29ed43f7b28eae56e7e052e854. --- container.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/container.go b/container.go index d6b73f57cb..16f45cb490 100644 --- a/container.go +++ b/container.go @@ -1300,9 +1300,7 @@ func (container *Container) monitor() { } // Report status back - container.State.Lock() container.State.setStopped(exitCode) - container.State.Unlock() // Release the lock close(container.waitLock) From 362e9d6b3c7049d44c016ee72fe255b76ec80c5a Mon Sep 17 00:00:00 2001 From: Gabe Rosenhouse Date: Thu, 21 Nov 2013 12:45:01 -0800 Subject: [PATCH 13/15] Fix title on doc page for remote_api_client_libraries.rst --- docs/sources/api/remote_api_client_libraries.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/api/remote_api_client_libraries.rst b/docs/sources/api/remote_api_client_libraries.rst index f00ab1c2b5..45ce8ff9d1 100644 --- a/docs/sources/api/remote_api_client_libraries.rst +++ b/docs/sources/api/remote_api_client_libraries.rst @@ -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 From 3f17844b6ec3edb981653ca237d085909d5be671 Mon Sep 17 00:00:00 2001 From: Paul Morie Date: Thu, 21 Nov 2013 17:29:03 -0500 Subject: [PATCH 14/15] Fix typo in pullImage --- server.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server.go b/server.go index 39c6dfdc6d..c859997a7a 100644 --- a/server.go +++ b/server.go @@ -759,7 +759,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 @@ -776,13 +776,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) } @@ -790,12 +790,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 } } From 33e70864a2c4321bf6968d6d521c159a1b72b220 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 21 Nov 2013 12:21:03 -0800 Subject: [PATCH 15/15] Refactor State to be 100% thread safe --- commands.go | 4 +- container.go | 53 +++++++++++++----------- integration/commands_test.go | 16 ++++---- integration/container_test.go | 76 +++++++++++++++++------------------ integration/runtime_test.go | 18 ++++----- integration/utils_test.go | 2 +- links.go | 2 +- runtime.go | 12 +++--- server.go | 8 ++-- state.go | 43 ++++++++++++++++++-- 10 files changed, 139 insertions(+), 95 deletions(-) diff --git a/commands.go b/commands.go index a551d598c9..51e4c5f01c 100644 --- a/commands.go +++ b/commands.go @@ -1542,7 +1542,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") } @@ -2301,7 +2301,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 { diff --git a/container.go b/container.go index 16f45cb490..744fabee20 100644 --- a/container.go +++ b/container.go @@ -20,11 +20,14 @@ import ( "path/filepath" "strconv" "strings" + "sync" "syscall" "time" ) type Container struct { + sync.Mutex + root string ID string @@ -700,9 +703,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() { @@ -1031,7 +1035,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{}) @@ -1039,14 +1043,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() @@ -1063,11 +1067,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 @@ -1148,11 +1152,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{ @@ -1188,10 +1193,12 @@ func (container *Container) allocateNetwork() error { } } - portSpecs := make(map[Port]struct{}) - bindings := make(map[Port][]PortBinding) + var ( + portSpecs = make(map[Port]struct{}) + bindings = make(map[Port][]PortBinding) + ) - if !container.State.Ghost { + if !container.State.IsGhost() { if container.Config.ExposedPorts != nil { portSpecs = container.Config.ExposedPorts } @@ -1300,7 +1307,7 @@ func (container *Container) monitor() { } // Report status back - container.State.setStopped(exitCode) + container.State.SetStopped(exitCode) // Release the lock close(container.waitLock) @@ -1350,10 +1357,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 } @@ -1366,7 +1373,7 @@ func (container *Container) kill(sig int) error { } func (container *Container) Kill() error { - if !container.State.Running { + if !container.State.IsRunning() { return nil } @@ -1391,7 +1398,7 @@ func (container *Container) Kill() error { } func (container *Container) Stop(seconds int) error { - if !container.State.Running { + if !container.State.IsRunning() { return nil } @@ -1425,7 +1432,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 { diff --git a/integration/commands_test.go b/integration/commands_test.go index 440d8e5469..6f9cf973da 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -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) @@ -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") } diff --git a/integration/container_test.go b/integration/container_test.go index a8c21ef1ea..e23e3b4985 100644 --- a/integration/container_test.go +++ b/integration/container_test.go @@ -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") } @@ -352,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 { @@ -370,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") } } @@ -400,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") } } @@ -421,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) } } @@ -446,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 { @@ -454,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) } }) @@ -471,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 @@ -533,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 { @@ -543,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 @@ -577,8 +577,8 @@ 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{ @@ -592,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) } } @@ -741,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)") { @@ -757,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)") { @@ -785,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)) @@ -806,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)") { @@ -827,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.") } } @@ -871,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") } @@ -1176,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") } @@ -1210,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") } @@ -1666,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) } diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 320a3645b0..69559f332b 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -419,7 +419,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 +533,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 +543,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 +553,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 +577,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 +592,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 +638,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 +669,7 @@ func TestReloadContainerLinks(t *testing.T) { } runningCount := 0 for _, c := range runtime2.List() { - if c.State.Running { + if c.State.IsRunning() { runningCount++ } } diff --git a/integration/utils_test.go b/integration/utils_test.go index 278924edb7..1f47c45382 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -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) { diff --git a/links.go b/links.go index 3c689d0bf3..2fe255b4c5 100644 --- a/links.go +++ b/links.go @@ -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) } diff --git a/runtime.go b/runtime.go index f453a0a705..3c90616f16 100644 --- a/runtime.go +++ b/runtime.go @@ -100,8 +100,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 } @@ -136,7 +136,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 @@ -145,14 +145,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 } diff --git a/server.go b/server.go index 39c6dfdc6d..b4632339d8 100644 --- a/server.go +++ b/server.go @@ -694,7 +694,7 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API }, -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 != "" { @@ -1342,7 +1342,7 @@ 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{}) @@ -1504,7 +1504,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 @@ -1726,7 +1726,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") } diff --git a/state.go b/state.go index af7de9f4da..71775719f1 100644 --- a/state.go +++ b/state.go @@ -8,7 +8,7 @@ import ( ) type State struct { - sync.Mutex + sync.RWMutex Running bool Pid int ExitCode int @@ -19,6 +19,9 @@ 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") @@ -28,7 +31,38 @@ func (s *State) String() string { 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 @@ -36,7 +70,10 @@ func (s *State) setRunning(pid int) { s.StartedAt = time.Now() } -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()