Procházet zdrojové kódy

Merge branch 'master' into pluginflag

Conflicts:
	pkg/cgroups/cgroups.go
	pkg/libcontainer/nsinit/exec.go
	pkg/libcontainer/nsinit/init.go
	pkg/libcontainer/nsinit/mount.go
	runconfig/hostconfig.go
	runconfig/parse.go
	runtime/execdriver/driver.go
	runtime/execdriver/lxc/lxc_template.go
	runtime/execdriver/lxc/lxc_template_unit_test.go
	runtime/execdriver/native/default_template.go
	runtime/execdriver/native/driver.go

Docker-DCO-1.1-Signed-off-by: Michael Crosby <michael@crosbymichael.com> (github: crosbymichael)
Michael Crosby před 11 roky
rodič
revize
eab56ac007
100 změnil soubory, kde provedl 1681 přidání a 1539 odebrání
  1. 40 0
      CHANGELOG.md
  2. 38 25
      CONTRIBUTING.md
  3. 1 1
      Dockerfile
  4. 1 1
      VERSION
  5. 59 6
      api/client.go
  6. 3 4
      builtins/builtins.go
  7. 5 5
      contrib/completion/fish/docker.fish
  8. 1 1
      contrib/init/upstart/docker.conf
  9. 1 1
      docs/sources/articles/runmetrics.rst
  10. 1 1
      docs/sources/examples/mongodb.rst
  11. 1 1
      docs/sources/examples/postgresql_service.rst
  12. 1 1
      docs/sources/examples/running_riak_service.rst
  13. 6 0
      docs/sources/installation/binaries.rst
  14. 2 2
      docs/sources/reference/api/docker_remote_api_v1.9.rst
  15. 2 0
      docs/sources/reference/api/remote_api_client_libraries.rst
  16. 49 10
      docs/sources/reference/commandline/cli.rst
  17. 8 7
      docs/sources/reference/run.rst
  18. 2 0
      docs/sources/use/basics.rst
  19. 95 0
      docs/sources/use/chef.rst
  20. 1 0
      docs/sources/use/index.rst
  21. 2 2
      docs/sources/use/working_with_volumes.rst
  22. 1 1
      graph/graph.go
  23. 13 0
      hack/PACKAGERS.md
  24. 0 29
      hack/infrastructure/docker-ci/Dockerfile
  25. 0 1
      hack/infrastructure/docker-ci/MAINTAINERS
  26. 0 65
      hack/infrastructure/docker-ci/README.rst
  27. 0 1
      hack/infrastructure/docker-ci/VERSION
  28. 0 176
      hack/infrastructure/docker-ci/buildbot/github.py
  29. 0 161
      hack/infrastructure/docker-ci/buildbot/master.cfg
  30. 0 22
      hack/infrastructure/docker-ci/dcr/prod/docker-ci.yml
  31. 0 5
      hack/infrastructure/docker-ci/dcr/prod/settings.yml
  32. 0 22
      hack/infrastructure/docker-ci/dcr/stage/docker-ci.yml
  33. 0 5
      hack/infrastructure/docker-ci/dcr/stage/settings.yml
  34. 0 52
      hack/infrastructure/docker-ci/docker-coverage/gocoverage.sh
  35. 0 1
      hack/infrastructure/docker-ci/dockertest/docker
  36. 0 1
      hack/infrastructure/docker-ci/dockertest/docker-registry
  37. 0 13
      hack/infrastructure/docker-ci/dockertest/nightlyrelease
  38. 0 8
      hack/infrastructure/docker-ci/dockertest/project
  39. 0 61
      hack/infrastructure/docker-ci/functionaltests/test_index.py
  40. 0 27
      hack/infrastructure/docker-ci/functionaltests/test_registry.sh
  41. 0 12
      hack/infrastructure/docker-ci/nginx/nginx.conf
  42. 0 28
      hack/infrastructure/docker-ci/report/Dockerfile
  43. 0 130
      hack/infrastructure/docker-ci/report/deployment.py
  44. 0 145
      hack/infrastructure/docker-ci/report/report.py
  45. 0 54
      hack/infrastructure/docker-ci/setup.sh
  46. 0 12
      hack/infrastructure/docker-ci/testbuilder/Dockerfile
  47. 0 12
      hack/infrastructure/docker-ci/testbuilder/docker-registry.sh
  48. 0 18
      hack/infrastructure/docker-ci/testbuilder/docker.sh
  49. 0 40
      hack/infrastructure/docker-ci/testbuilder/testbuilder.sh
  50. 0 47
      hack/infrastructure/docker-ci/tool/backup.py
  51. 1 1
      hack/install.sh
  52. 1 1
      hack/make.sh
  53. 3 0
      hack/vendor.sh
  54. 28 0
      integration/buildfile_test.go
  55. 19 0
      integration/commands_test.go
  56. 1 1
      integration/container_test.go
  57. 3 2
      integration/server_test.go
  58. 15 0
      pkg/cgroups/apply_nosystemd.go
  59. 189 0
      pkg/cgroups/apply_raw.go
  60. 158 0
      pkg/cgroups/apply_systemd.go
  61. 11 163
      pkg/cgroups/cgroups.go
  62. 0 1
      pkg/iptables/iptables.go
  63. 23 0
      pkg/label/label.go
  64. 69 0
      pkg/label/label_selinux.go
  65. 10 6
      pkg/libcontainer/nsinit/exec.go
  66. 10 1
      pkg/libcontainer/nsinit/execin.go
  67. 7 1
      pkg/libcontainer/nsinit/init.go
  68. 13 9
      pkg/libcontainer/nsinit/mount.go
  69. 387 0
      pkg/selinux/selinux.go
  70. 64 0
      pkg/selinux/selinux_test.go
  71. 15 0
      pkg/systemd/booted.go
  72. 1 1
      pkg/systemd/listendfd.go
  73. 4 0
      registry/registry_test.go
  74. 11 0
      runconfig/config.go
  75. 1 1
      runconfig/config_test.go
  76. 2 1
      runconfig/hostconfig.go
  77. 9 2
      runconfig/merge.go
  78. 49 15
      runconfig/parse.go
  79. 2 1
      runconfig/parse_test.go
  80. 38 16
      runtime/container.go
  81. 4 0
      runtime/execdriver/driver.go
  82. 18 2
      runtime/execdriver/lxc/lxc_template.go
  83. 5 4
      runtime/execdriver/lxc/lxc_template_unit_test.go
  84. 1 1
      runtime/graphdriver/aufs/aufs.go
  85. 26 26
      runtime/graphdriver/aufs/aufs_test.go
  86. 3 3
      runtime/graphdriver/aufs/migrate.go
  87. 3 3
      runtime/graphdriver/btrfs/btrfs.go
  88. 23 22
      runtime/graphdriver/devmapper/deviceset.go
  89. 8 7
      runtime/graphdriver/devmapper/driver.go
  90. 10 10
      runtime/graphdriver/devmapper/driver_test.go
  91. 2 3
      runtime/graphdriver/driver.go
  92. 1 1
      runtime/graphdriver/vfs/driver.go
  93. 7 1
      runtime/networkdriver/bridge/driver.go
  94. 26 7
      runtime/networkdriver/portallocator/portallocator.go
  95. 16 0
      runtime/networkdriver/portallocator/portallocator_test.go
  96. 3 3
      runtime/runtime.go
  97. 3 0
      runtime/state.go
  98. 28 6
      server/buildfile.go
  99. 4 0
      server/server.go
  100. 13 0
      utils/utils.go

+ 40 - 0
CHANGELOG.md

@@ -1,5 +1,45 @@
 # Changelog
 
+## 0.9.1 (2014-03-24)
+
+#### Builder
+- Fix printing multiple messages on a single line. Fixes broken output during builds.
+
+#### Documentation
+- Fix external link on security of containers.
+
+#### Contrib
+- Fix init script cgroup mounting workarounds to be more similar to cgroupfs-mount and thus work properly.
+- Add variable for DOCKER_LOGFILE to sysvinit and use append instead of overwrite in opening the logfile.
+
+#### Hack
+- Generate md5 and sha256 hashes when building, and upload them via hack/release.sh.
+
+#### Remote API
+- Fix content-type detection in `docker cp`.
+
+#### Runtime
+- Use BSD raw mode on Darwin. Fixes nano, tmux and others.
+- Only unshare the mount namespace for execin.
+- Retry to retrieve the layer metadata up to 5 times for `docker pull`.
+- Merge existing config when committing.
+- Fix panic in monitor.
+- Disable daemon startup timeout.
+- Fix issue #4681: add loopback interface when networking is disabled.
+- Add failing test case for issue #4681.
+- Send SIGTERM to child, instead of SIGKILL.
+- Show the driver and the kernel version in `docker info` even when not in debug mode.
+- Always symlink /dev/ptmx for libcontainer. This fixes console related problems.
+- Fix issue caused by the absence of /etc/apparmor.d.
+- Don't leave empty cidFile behind when failing to create the container.
+- Improve deprecation message.
+- Fix attach exit on darwin.
+- devicemapper: improve handling of devicemapper devices (add per device lock, increase sleep time, unlock while sleeping).
+- devicemapper: succeed immediately when removing non-existing devices.
+- devicemapper: increase timeout in waitClose to 10 seconds.
+- Remove goroutine leak on error.
+- Update parseLxcInfo to comply with new lxc1.0 format.
+
 ## 0.9.0 (2014-03-10)
 
 #### Builder

+ 38 - 25
CONTRIBUTING.md

@@ -126,33 +126,46 @@ For more details see [MAINTAINERS.md](hack/MAINTAINERS.md)
 The sign-off is a simple line at the end of the explanation for the
 patch, which certifies that you wrote it or otherwise have the right to
 pass it on as an open-source patch.  The rules are pretty simple: if you
-can certify the below:
+can certify the below (from
+[developercertificate.org](http://developercertificate.org/)):
 
 ```
-Docker Developer Certificate of Origin 1.1
-
-By making a contribution to the Docker Project ("Project"), I represent and
-warrant that:
-
-a. The contribution was created in whole or in part by me and I have the right
-to submit the contribution on my own behalf or on behalf of a third party who
-has authorized me to submit this contribution to the Project; or
-
-b. The contribution is based upon previous work that, to the best of my
-knowledge, is covered under an appropriate open source license and I have the
-right and authorization to submit that work with modifications, whether
-created in whole or in part by me, under the same open source license (unless
-I am permitted to submit under a different license) that I have identified in
-the contribution; or
-
-c. The contribution was provided directly to me by some other person who
-represented and warranted (a) or (b) and I have not modified it.
-
-d. I understand and agree that this Project and the contribution are publicly
-known and that a record of the contribution (including all personal
-information I submit with it, including my sign-off record) is maintained
-indefinitely and may be redistributed consistent with this Project or the open
-source license(s) involved.
+Developer Certificate of Origin
+Version 1.1
+
+Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
+660 York Street, Suite 102,
+San Francisco, CA 94110 USA
+
+Everyone is permitted to copy and distribute verbatim copies of this
+license document, but changing it is not allowed.
+
+
+Developer's Certificate of Origin 1.1
+
+By making a contribution to this project, I certify that:
+
+(a) The contribution was created in whole or in part by me and I
+    have the right to submit it under the open source license
+    indicated in the file; or
+
+(b) The contribution is based upon previous work that, to the best
+    of my knowledge, is covered under an appropriate open source
+    license and I have the right under that license to submit that
+    work with modifications, whether created in whole or in part
+    by me, under the same open source license (unless I am
+    permitted to submit under a different license), as indicated
+    in the file; or
+
+(c) The contribution was provided directly to me by some other
+    person who certified (a), (b) or (c) and I have not modified
+    it.
+
+(d) I understand and agree that this project and the contribution
+    are public and that a record of the contribution (including all
+    personal information I submit with it, including my sign-off) is
+    maintained indefinitely and may be redistributed consistent with
+    this project or the open source license(s) involved.
 ```
 
 then you just add a line to every git commit message:

+ 1 - 1
Dockerfile

@@ -87,7 +87,7 @@ RUN	git config --global user.email 'docker-dummy@example.com'
 
 VOLUME	/var/lib/docker
 WORKDIR	/go/src/github.com/dotcloud/docker
-ENV	DOCKER_BUILDTAGS	apparmor
+ENV	DOCKER_BUILDTAGS	apparmor selinux
 
 # Wrap all commands in the "docker-in-docker" script to allow nested containers
 ENTRYPOINT	["hack/dind"]

+ 1 - 1
VERSION

@@ -1 +1 @@
-0.9.0-dev
+0.9.1-dev

+ 59 - 6
api/client.go

@@ -208,6 +208,15 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
 	}
 	// Upload the build context
 	v := &url.Values{}
+
+	//Check if the given image name can be resolved
+	if *tag != "" {
+		repository, _ := utils.ParseRepositoryTag(*tag)
+		if _, _, err := registry.ResolveRepositoryName(repository); err != nil {
+			return err
+		}
+	}
+
 	v.Set("t", *tag)
 
 	if *suppressOutput {
@@ -498,8 +507,8 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
 }
 
 func (cli *DockerCli) CmdStop(args ...string) error {
-	cmd := cli.Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container (Send SIGTERM)")
-	nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Number of seconds to wait for the container to stop.")
+	cmd := cli.Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container (Send SIGTERM, and then SIGKILL after grace period)")
+	nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Number of seconds to wait for the container to stop before killing it.")
 	if err := cmd.Parse(args); err != nil {
 		return nil
 	}
@@ -526,7 +535,7 @@ func (cli *DockerCli) CmdStop(args ...string) error {
 
 func (cli *DockerCli) CmdRestart(args ...string) error {
 	cmd := cli.Subcmd("restart", "[OPTIONS] CONTAINER [CONTAINER...]", "Restart a running container")
-	nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Number of seconds to wait for the container to stop. Default=10")
+	nSeconds := cmd.Int([]string{"t", "-time"}, 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
 	}
@@ -1003,6 +1012,14 @@ func (cli *DockerCli) CmdImport(args ...string) error {
 		repository, tag = utils.ParseRepositoryTag(cmd.Arg(1))
 	}
 	v := url.Values{}
+
+	if repository != "" {
+		//Check if the given image name can be resolved
+		if _, _, err := registry.ResolveRepositoryName(repository); err != nil {
+			return err
+		}
+	}
+
 	v.Set("repo", repository)
 	v.Set("tag", tag)
 	v.Set("fromSrc", src)
@@ -1453,6 +1470,13 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
 		return nil
 	}
 
+	//Check if the given image name can be resolved
+	if repository != "" {
+		if _, _, err := registry.ResolveRepositoryName(repository); err != nil {
+			return err
+		}
+	}
+
 	v := url.Values{}
 	v.Set("container", name)
 	v.Set("repo", repository)
@@ -1741,6 +1765,11 @@ func (cli *DockerCli) CmdTag(args ...string) error {
 	}
 
 	v := url.Values{}
+
+	//Check if the given image name can be resolved
+	if _, _, err := registry.ResolveRepositoryName(repository); err != nil {
+		return err
+	}
 	v.Set("repo", repository)
 	v.Set("tag", tag)
 
@@ -2044,7 +2073,9 @@ func (cli *DockerCli) CmdCp(args ...string) error {
 }
 
 func (cli *DockerCli) CmdSave(args ...string) error {
-	cmd := cli.Subcmd("save", "IMAGE", "Save an image to a tar archive (streamed to stdout)")
+	cmd := cli.Subcmd("save", "IMAGE", "Save an image to a tar archive (streamed to stdout by default)")
+	outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT")
+
 	if err := cmd.Parse(args); err != nil {
 		return err
 	}
@@ -2054,8 +2085,18 @@ func (cli *DockerCli) CmdSave(args ...string) error {
 		return nil
 	}
 
+	var (
+		output io.Writer = cli.out
+		err    error
+	)
+	if *outfile != "" {
+		output, err = os.Create(*outfile)
+		if err != nil {
+			return err
+		}
+	}
 	image := cmd.Arg(0)
-	if err := cli.stream("GET", "/images/"+image+"/get", nil, cli.out, nil); err != nil {
+	if err := cli.stream("GET", "/images/"+image+"/get", nil, output, nil); err != nil {
 		return err
 	}
 	return nil
@@ -2063,6 +2104,8 @@ func (cli *DockerCli) CmdSave(args ...string) error {
 
 func (cli *DockerCli) CmdLoad(args ...string) error {
 	cmd := cli.Subcmd("load", "", "Load an image from a tar archive on STDIN")
+	infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN")
+
 	if err := cmd.Parse(args); err != nil {
 		return err
 	}
@@ -2072,7 +2115,17 @@ func (cli *DockerCli) CmdLoad(args ...string) error {
 		return nil
 	}
 
-	if err := cli.stream("POST", "/images/load", cli.in, cli.out, nil); err != nil {
+	var (
+		input io.Reader = cli.in
+		err   error
+	)
+	if *infile != "" {
+		input, err = os.Open(*infile)
+		if err != nil {
+			return err
+		}
+	}
+	if err := cli.stream("POST", "/images/load", input, cli.out, nil); err != nil {
 		return err
 	}
 	return nil

+ 3 - 4
builtins/builtins.go

@@ -1,10 +1,9 @@
 package builtins
 
 import (
-	"github.com/dotcloud/docker/engine"
-
 	"github.com/dotcloud/docker/api"
-	"github.com/dotcloud/docker/runtime/networkdriver/lxc"
+	"github.com/dotcloud/docker/engine"
+	"github.com/dotcloud/docker/runtime/networkdriver/bridge"
 	"github.com/dotcloud/docker/server"
 )
 
@@ -35,5 +34,5 @@ func remote(eng *engine.Engine) {
 //
 func daemon(eng *engine.Engine) {
 	eng.Register("initserver", server.InitServer)
-	eng.Register("init_networkdriver", lxc.InitDriver)
+	eng.Register("init_networkdriver", bridge.InitDriver)
 }

+ 5 - 5
contrib/completion/fish/docker.fish

@@ -26,20 +26,20 @@ end
 function __fish_print_docker_containers --description 'Print a list of docker containers' -a select
     switch $select
         case running
-            docker ps -a --no-trunc | awk 'NR>1' | awk 'BEGIN {FS="  +"}; $5 ~ "^Up" {print $1 "\n" $(NF-1)}' | tr ',' '\n'
+            docker ps -a --no-trunc | command awk 'NR>1' | command awk 'BEGIN {FS="  +"}; $5 ~ "^Up" {print $1 "\n" $(NF-1)}' | tr ',' '\n'
         case stopped
-            docker ps -a --no-trunc | awk 'NR>1' | awk 'BEGIN {FS="  +"}; $5 ~ "^Exit" {print $1 "\n" $(NF-1)}' | tr ',' '\n'
+            docker ps -a --no-trunc | command awk 'NR>1' | command awk 'BEGIN {FS="  +"}; $5 ~ "^Exit" {print $1 "\n" $(NF-1)}' | tr ',' '\n'
         case all
-            docker ps -a --no-trunc | awk 'NR>1' | awk 'BEGIN {FS="  +"}; {print $1 "\n" $(NF-1)}' | tr ',' '\n'
+            docker ps -a --no-trunc | command awk 'NR>1' | command awk 'BEGIN {FS="  +"}; {print $1 "\n" $(NF-1)}' | tr ',' '\n'
     end
 end
 
 function __fish_print_docker_images --description 'Print a list of docker images'
-    docker images | awk 'NR>1' | grep -v '<none>' | awk '{print $1":"$2}'
+    docker images | command awk 'NR>1' | command grep -v '<none>' | command awk '{print $1":"$2}'
 end
 
 function __fish_print_docker_repositories --description 'Print a list of docker repositories'
-    docker images | awk 'NR>1' | grep -v '<none>' | awk '{print $1}' | sort | uniq
+    docker images | command awk 'NR>1' | command grep -v '<none>' | command awk '{print $1}' | sort | uniq
 end
 
 # common options

+ 1 - 1
contrib/init/upstart/docker.conf

@@ -37,5 +37,5 @@ script
 	if [ -f /etc/default/$UPSTART_JOB ]; then
 		. /etc/default/$UPSTART_JOB
 	fi
-	"$DOCKER" -d $DOCKER_OPTS
+	exec "$DOCKER" -d $DOCKER_OPTS
 end script

+ 1 - 1
docs/sources/articles/runmetrics.rst

@@ -63,7 +63,7 @@ For Docker containers using cgroups, the container name will be the
 full ID or long ID of the container. If a container shows up as
 ae836c95b4c3 in ``docker ps``, its long ID might be something like
 ``ae836c95b4c3c9e9179e0e91015512da89fdec91612f63cebae57df9a5444c79``. You
-can look it up with ``docker inspect`` or ``docker ps -notrunc``.
+can look it up with ``docker inspect`` or ``docker ps --no-trunc``.
 
 Putting everything together to look at the memory metrics for a Docker
 container, take a look at ``/sys/fs/cgroup/memory/lxc/<longid>/``.

+ 1 - 1
docs/sources/examples/mongodb.rst

@@ -47,7 +47,7 @@ divert ``/sbin/initctl`` to ``/bin/true`` so it thinks everything is working.
 
     # Hack for initctl not being available in Ubuntu
     RUN dpkg-divert --local --rename --add /sbin/initctl
-    RUN ln -s /bin/true /sbin/initctl
+    RUN ln -sf /bin/true /sbin/initctl
 
 Afterwards we'll be able to update our apt repositories and install MongoDB
 

+ 1 - 1
docs/sources/examples/postgresql_service.rst

@@ -37,7 +37,7 @@ And run the PostgreSQL server container (in the foreground):
 
 .. code-block:: bash
 
-    $ sudo docker run --rm -P -name pg_test eg_postgresql
+    $ sudo docker run --rm -P --name pg_test eg_postgresql
 
 There are  2 ways to connect to the PostgreSQL server. We can use 
 :ref:`working_with_links_names`, or we can access it from our host (or the network).

+ 1 - 1
docs/sources/examples/running_riak_service.rst

@@ -88,7 +88,7 @@ Almost there. Next, we add a hack to get us by the lack of ``initctl``:
     # Hack for initctl
     # See: https://github.com/dotcloud/docker/issues/1024
     RUN dpkg-divert --local --rename --add /sbin/initctl
-    RUN ln -s /bin/true /sbin/initctl
+    RUN ln -sf /bin/true /sbin/initctl
 
 Then, we expose the Riak Protocol Buffers and HTTP interfaces, along with SSH:
 

+ 6 - 0
docs/sources/installation/binaries.rst

@@ -29,6 +29,12 @@ To run properly, docker needs the following software to be installed at runtime:
 - iptables version 1.4 or later
 - Git version 1.7 or later
 - XZ Utils 4.9 or later
+- a `properly mounted
+  <https://github.com/tianon/cgroupfs-mount/blob/master/cgroupfs-mount>`_
+  cgroupfs hierarchy (having a single, all-encompassing "cgroup" mount point `is
+  <https://github.com/dotcloud/docker/issues/2683>`_ `not
+  <https://github.com/dotcloud/docker/issues/3485>`_ `sufficient
+  <https://github.com/dotcloud/docker/issues/4568>`_)
 
 
 Check kernel dependencies

+ 2 - 2
docs/sources/reference/api/docker_remote_api_v1.9.rst

@@ -432,7 +432,7 @@ Stop a container
 
            HTTP/1.1 204 OK
 
-        :query t: number of seconds to wait for the container to stop
+        :query t: number of seconds to wait before killing the container
         :statuscode 204: no error
         :statuscode 404: no such container
         :statuscode 500: server error
@@ -457,7 +457,7 @@ Restart a container
 
            HTTP/1.1 204 OK
 
-        :query t: number of seconds to wait for the container to stop
+        :query t: number of seconds to wait before killing the container
         :statuscode 204: no error
         :statuscode 404: no such container
         :statuscode 500: server error

+ 2 - 0
docs/sources/reference/api/remote_api_client_libraries.rst

@@ -49,3 +49,5 @@ and we will add the libraries here.
 +----------------------+----------------+--------------------------------------------+----------+
 | Perl                 | Net::Docker    | https://metacpan.org/pod/Net::Docker       | Active   |
 +----------------------+----------------+--------------------------------------------+----------+
+| Perl                 | Eixo::Docker   | https://github.com/alambike/eixo-docker    | Active   |
++----------------------+----------------+--------------------------------------------+----------+

+ 49 - 10
docs/sources/reference/commandline/cli.rst

@@ -881,10 +881,32 @@ Known Issues (kill)
 
 ::
 
-    Usage: docker load < repository.tar
+    Usage: docker load 
+
+    Load an image from a tar archive on STDIN
+
+      -i, --input="": Read from a tar archive file, instead of STDIN
+
+Loads a tarred repository from a file or the standard input stream.
+Restores both images and tags.
+
+.. code-block:: bash
+
+   $ sudo docker images
+   REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
+   $ sudo docker load < busybox.tar
+   $ sudo docker images
+   REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
+   busybox             latest              769b9341d937        7 weeks ago         2.489 MB
+   $ sudo docker load --input fedora.tar
+   $ sudo docker images
+   REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
+   busybox             latest              769b9341d937        7 weeks ago         2.489 MB
+   fedora              rawhide             0d20aec6529d        7 weeks ago         387 MB
+   fedora              20                  58394af37342        7 weeks ago         385.5 MB
+   fedora              heisenbug           58394af37342        7 weeks ago         385.5 MB
+   fedora              latest              58394af37342        7 weeks ago         385.5 MB
 
-    Loads a tarred repository from the standard input stream.
-    Restores both images and tags.
 
 .. _cli_login:
 
@@ -1145,7 +1167,7 @@ image is removed.
       --volumes-from="": Mount all volumes from the given container(s)
       --entrypoint="": Overwrite the default entrypoint set by the image
       -w, --workdir="": Working directory inside the container
-      --lxc-conf=[]: Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
+      --lxc-conf=[]: (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
       --sig-proxy=true: Proxify all received signal to the process (even in non-tty mode)
       --expose=[]: Expose a port from the container without publishing it to your host
       --link="": Add link to another container (name:alias)
@@ -1317,10 +1339,27 @@ This example shows 5 containers that might be set up to test a web application c
 
 ::
 
-    Usage: docker save image > repository.tar
+    Usage: docker save IMAGE
+
+    Save an image to a tar archive (streamed to stdout by default)
+
+      -o, --output="": Write to an file, instead of STDOUT
+
+
+Produces a tarred repository to the standard output stream.
+Contains all parent layers, and all tags + versions, or specified repo:tag.
+
+.. code-block:: bash
+
+   $ sudo docker save busybox > busybox.tar
+   $ ls -sh b.tar
+   2.7M b.tar
+   $ sudo docker save --output busybox.tar busybox
+   $ ls -sh b.tar
+   2.7M b.tar
+   $ sudo docker save -o fedora-all.tar fedora
+   $ sudo docker save -o fedora-latest.tar fedora:latest
 
-    Streams a tarred repository to the standard output stream.
-    Contains all parent layers, and all tags + versions.
 
 .. _cli_search:
 
@@ -1360,11 +1399,11 @@ This example shows 5 containers that might be set up to test a web application c
 
     Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
 
-    Stop a running container (Send SIGTERM)
+    Stop a running container (Send SIGTERM, and then SIGKILL after grace period)
 
-      -t, --time=10: Number of seconds to wait for the container to stop.
+      -t, --time=10: Number of seconds to wait for the container to stop before killing it.
 
-The main process inside the container will receive SIGTERM.
+The main process inside the container will receive SIGTERM, and after a grace period, SIGKILL
 
 .. _cli_tag:
 

+ 8 - 7
docs/sources/reference/run.rst

@@ -194,7 +194,7 @@ Runtime Privilege and LXC Configuration
 ::
 
    --privileged=false: Give extended privileges to this container
-   --lxc-conf=[]: Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
+   --lxc-conf=[]: (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
 
 By default, Docker containers are "unprivileged" and cannot, for
 example, run a Docker daemon inside a Docker container. This is
@@ -211,12 +211,13 @@ host. Additional information about running with ``--privileged`` is
 available on the `Docker Blog
 <http://blog.docker.io/2013/09/docker-can-now-run-within-docker/>`_.
 
-An operator can also specify LXC options using one or more
-``--lxc-conf`` parameters. These can be new parameters or override
-existing parameters from the lxc-template.go_. Note that in the
-future, a given host's Docker daemon may not use LXC, so this is an
-implementation-specific configuration meant for operators already
-familiar with using LXC directly.
+If the Docker daemon was started using the ``lxc`` exec-driver
+(``docker -d --exec-driver=lxc``) then the operator can also specify
+LXC options using one or more ``--lxc-conf`` parameters. These can be
+new parameters or override existing parameters from the lxc-template.go_.
+Note that in the future, a given host's Docker daemon may not use LXC,
+so this is an implementation-specific configuration meant for operators
+already familiar with using LXC directly.
 
 .. _lxc-template.go: https://github.com/dotcloud/docker/blob/master/execdriver/lxc/lxc_template.go
 

+ 2 - 0
docs/sources/use/basics.rst

@@ -40,6 +40,8 @@ Repository to a local image cache.
    short form of the image ID. These short image IDs are the first 12
    characters of the full image ID - which can be found using ``docker
    inspect`` or ``docker images --no-trunc=true``
+   
+   **If you're using OS X** then you shouldn't use ``sudo``
 
 Running an interactive shell
 ----------------------------

+ 95 - 0
docs/sources/use/chef.rst

@@ -0,0 +1,95 @@
+:title: Chef Usage
+:description: Installation and using Docker via Chef
+:keywords: chef, installation, usage, docker, documentation
+
+.. _install_using_chef:
+
+Using Chef
+=============
+
+.. note::
+
+   Please note this is a community contributed installation path. The
+   only 'official' installation is using the :ref:`ubuntu_linux`
+   installation path. This version may sometimes be out of date.
+
+Requirements
+------------
+
+To use this guide you'll need a working installation of 
+`Chef <http://www.getchef.com/>`_. This cookbook supports a variety of 
+operating systems.
+
+Installation
+------------
+
+The cookbook is available on the `Chef Community Site
+<community.opscode.com/cookbooks/docker>`_ and can be installed
+using your favorite cookbook dependency manager.
+
+The source can be found on `GitHub
+<https://github.com/bflad/chef-docker>`_.
+
+Usage
+-----
+
+The cookbook provides recipes for installing Docker, configuring init
+for Docker, and resources for managing images and containers.
+It supports almost all Docker functionality.
+
+Installation
+~~~~~~~~~~~~
+
+.. code-block:: ruby
+
+  include_recipe 'docker'
+
+Images
+~~~~~~
+
+The next step is to pull a Docker image. For this, we have a resource:
+
+.. code-block:: ruby
+
+  docker_image 'samalba/docker-registry'
+
+This is equivalent to running:
+
+.. code-block:: bash
+
+  docker pull samalba/docker-registry
+
+There are attributes available to control how long the cookbook
+will allow for downloading (5 minute default).
+
+To remove images you no longer need:
+
+.. code-block:: ruby
+
+  docker_image 'samalba/docker-registry' do
+    action :remove
+  end
+
+Containers
+~~~~~~~~~~
+
+Now you have an image where you can run commands within a container
+managed by Docker.
+
+.. code-block:: ruby
+
+  docker_container 'samalba/docker-registry' do
+    detach true
+    port '5000:5000'
+    env 'SETTINGS_FLAVOR=local'
+    volume '/mnt/docker:/docker-storage'
+  end
+
+This is equivalent to running the following command, but under upstart:
+
+.. code-block:: bash
+
+  docker run --detach=true --publish='5000:5000' --env='SETTINGS_FLAVOR=local' --volume='/mnt/docker:/docker-storage' samalba/docker-registry
+
+The resources will accept a single string or an array of values
+for any docker flags that allow multiple values.

+ 1 - 0
docs/sources/use/index.rst

@@ -20,4 +20,5 @@ Contents:
    working_with_volumes
    working_with_links_names
    ambassador_pattern_linking
+   chef
    puppet

+ 2 - 2
docs/sources/use/working_with_volumes.rst

@@ -129,7 +129,7 @@ because they are external to images.
 Instead you can use ``--volumes-from`` to start a new container that can access the
 data-container's volume. For example::
 
-    $ sudo docker run -rm --volumes-from DATA -v $(pwd):/backup busybox tar cvf /backup/backup.tar /data
+    $ sudo docker run --rm --volumes-from DATA -v $(pwd):/backup busybox tar cvf /backup/backup.tar /data
 
 * ``--rm`` - remove the container when it exits
 * ``--volumes-from DATA`` - attach to the volumes shared by the ``DATA`` container
@@ -140,7 +140,7 @@ data-container's volume. For example::
 Then to restore to the same container, or another that you've made elsewhere::
 
     # create a new data container
-    $ sudo docker run -v /data -name DATA2 busybox true
+    $ sudo docker run -v /data --name DATA2 busybox true
     # untar the backup files into the new container's data volume
     $ sudo docker run --rm --volumes-from DATA2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar
     data/

+ 1 - 1
graph/graph.go

@@ -189,7 +189,7 @@ func (graph *Graph) Register(jsonData []byte, layerData archive.ArchiveReader, i
 	}
 
 	// Create root filesystem in the driver
-	if err := graph.driver.Create(img.ID, img.Parent); err != nil {
+	if err := graph.driver.Create(img.ID, img.Parent, ""); err != nil {
 		return fmt.Errorf("Driver %s failed to create image rootfs %s: %s", graph.driver, img.ID, err)
 	}
 	// Mount the root filesystem so we can apply the diff/layer

+ 13 - 0
hack/PACKAGERS.md

@@ -177,6 +177,13 @@ export DOCKER_BUILDTAGS='exclude_graphdriver_aufs'
 
 NOTE: if you need to set more than one build tag, space separate them.
 
+If you're building a binary that may need to be used on platforms that include
+SELinux, you will need to set `DOCKER_BUILDTAGS` as follows:
+
+```bash
+export DOCKER_BUILDTAGS='selinux'
+```
+
 ### Static Daemon
 
 If it is feasible within the constraints of your distribution, you should
@@ -259,6 +266,12 @@ installed and available at runtime:
 
 * iptables version 1.4 or later
 * XZ Utils version 4.9 or later
+* a [properly
+  mounted](https://github.com/tianon/cgroupfs-mount/blob/master/cgroupfs-mount)
+  cgroupfs hierarchy (having a single, all-encompassing "cgroup" mount point
+  [is](https://github.com/dotcloud/docker/issues/2683)
+  [not](https://github.com/dotcloud/docker/issues/3485)
+  [sufficient](https://github.com/dotcloud/docker/issues/4568))
 
 Additionally, the Docker client needs the following software to be installed and
 available at runtime:

+ 0 - 29
hack/infrastructure/docker-ci/Dockerfile

@@ -1,29 +0,0 @@
-# DOCKER-VERSION: 0.7.6
-# AUTHOR:         Daniel Mizyrycki <daniel@dotcloud.com>
-# DESCRIPTION:    docker-ci continuous integration service
-# TO_BUILD:       docker build -t docker-ci/docker-ci .
-# TO_RUN:         docker run --rm -i -t -p 8000:80 -p 2222:22 -v /run:/var/socket \
-#                     -v /data/docker-ci:/data/docker-ci docker-ci/docker-ci
-
-from ubuntu:12.04
-maintainer Daniel Mizyrycki <daniel@dotcloud.com>
-
-ENV DEBIAN_FRONTEND noninteractive
-RUN echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > \
-    /etc/apt/sources.list; apt-get update
-RUN apt-get install -y --no-install-recommends  python2.7 python-dev \
-    libevent-dev git supervisor ssh rsync less vim sudo gcc wget nginx
-RUN cd /tmp; wget http://python-distribute.org/distribute_setup.py
-RUN cd /tmp; python distribute_setup.py; easy_install pip; rm distribute_setup.py
-
-RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9
-RUN echo 'deb http://get.docker.io/ubuntu docker main' > \
-      /etc/apt/sources.list.d/docker.list; apt-get update
-RUN apt-get install -y lxc-docker-0.8.0
-RUN pip install SQLAlchemy==0.7.10 buildbot buildbot-slave pyopenssl boto
-RUN ln -s /var/socket/docker.sock /run/docker.sock
-
-ADD . /docker-ci
-RUN /docker-ci/setup.sh
-
-ENTRYPOINT ["supervisord", "-n"]

+ 0 - 1
hack/infrastructure/docker-ci/MAINTAINERS

@@ -1 +0,0 @@
-Daniel Mizyrycki <daniel@dotcloud.com> (@mzdaniel)

+ 0 - 65
hack/infrastructure/docker-ci/README.rst

@@ -1,65 +0,0 @@
-=========
-docker-ci
-=========
-
-This directory contains docker-ci continuous integration system.
-As expected, it is a fully dockerized and deployed using
-docker-container-runner.
-docker-ci is based on Buildbot, a continuous integration system designed
-to automate the build/test cycle. By automatically rebuilding and testing
-the tree each time something has changed, build problems are pinpointed
-quickly, before other developers are inconvenienced by the failure.
-We are running buildbot at Rackspace to verify docker and docker-registry
-pass tests, and check for coverage code details.
-
-docker-ci instance is at https://docker-ci.docker.io/waterfall
-
-Inside docker-ci container we have the following directory structure:
-
-/docker-ci                       source code of docker-ci
-/data/backup/docker-ci/          daily backup (replicated over S3)
-/data/docker-ci/coverage/{docker,docker-registry}/    mapped to host volumes
-/data/buildbot/{master,slave}/   main docker-ci buildbot config and database
-/var/socket/{docker.sock}        host volume access to docker socket
-
-
-Production deployment
-=====================
-
-::
-
-  # Clone docker-ci repository
-  git clone https://github.com/dotcloud/docker
-  cd docker/hack/infrastructure/docker-ci
-
-  export DOCKER_PROD=[PRODUCTION_SERVER_IP]
-
-  # Create data host volume. (only once)
-  docker -H $DOCKER_PROD run -v /home:/data ubuntu:12.04 \
-    mkdir -p /data/docker-ci/coverage/docker
-  docker -H $DOCKER_PROD run -v /home:/data ubuntu:12.04 \
-    mkdir -p /data/docker-ci/coverage/docker-registry
-  docker -H $DOCKER_PROD run -v /home:/data ubuntu:12.04 \
-    chown -R 1000.1000 /data/docker-ci
-
-  # dcr deployment. Define credentials and special environment dcr variables
-  # ( retrieved at /hack/infrastructure/docker-ci/dcr/prod/docker-ci.yml )
-  export WEB_USER=[DOCKER-CI-WEBSITE-USERNAME]
-  export WEB_IRC_PWD=[DOCKER-CI-WEBSITE-PASSWORD]
-  export BUILDBOT_PWD=[BUILDSLAVE_PASSWORD]
-  export AWS_ACCESS_KEY=[DOCKER_RELEASE_S3_ACCESS]
-  export AWS_SECRET_KEY=[DOCKER_RELEASE_S3_SECRET]
-  export GPG_PASSPHRASE=[DOCKER_RELEASE_PASSPHRASE]
-  export BACKUP_AWS_ID=[S3_BUCKET_CREDENTIAL_ACCESS]
-  export BACKUP_AWS_SECRET=[S3_BUCKET_CREDENTIAL_SECRET]
-  export SMTP_USER=[MAILGUN_SMTP_USERNAME]
-  export SMTP_PWD=[MAILGUN_SMTP_PASSWORD]
-  export EMAIL_RCP=[EMAIL_FOR_BUILD_ERRORS]
-
-  # Build docker-ci and testbuilder docker images
-  docker -H $DOCKER_PROD build -t docker-ci/docker-ci .
-  (cd testbuilder; docker -H $DOCKER_PROD build --rm -t docker-ci/testbuilder .)
-
-  # Run docker-ci container ( assuming no previous container running )
-  (cd dcr/prod; dcr docker-ci.yml start)
-  (cd dcr/prod; dcr docker-ci.yml register docker-ci.docker.io)

+ 0 - 1
hack/infrastructure/docker-ci/VERSION

@@ -1 +0,0 @@
-0.5.6

+ 0 - 176
hack/infrastructure/docker-ci/buildbot/github.py

@@ -1,176 +0,0 @@
-# This file is part of Buildbot.  Buildbot is free software: you can
-# redistribute it and/or modify it under the terms of the GNU General Public
-# License as published by the Free Software Foundation, version 2.
-#
-# This program is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
-# details.
-#
-# You should have received a copy of the GNU General Public License along with
-# this program; if not, write to the Free Software Foundation, Inc., 51
-# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# Copyright Buildbot Team Members
-
-#!/usr/bin/env python
-"""
-github_buildbot.py is based on git_buildbot.py
-
-github_buildbot.py will determine the repository information from the JSON
-HTTP POST it receives from github.com and build the appropriate repository.
-If your github repository is private, you must add a ssh key to the github
-repository for the user who initiated the build on the buildslave.
-
-"""
-
-import re
-import datetime
-from twisted.python import log
-import calendar
-
-try:
-    import json
-    assert json
-except ImportError:
-    import simplejson as json
-
-# python is silly about how it handles timezones
-class fixedOffset(datetime.tzinfo):
-    """
-    fixed offset timezone
-    """
-    def __init__(self, minutes, hours, offsetSign = 1):
-        self.minutes = int(minutes) * offsetSign
-        self.hours   = int(hours)   * offsetSign
-        self.offset  = datetime.timedelta(minutes = self.minutes,
-                                         hours   = self.hours)
-
-    def utcoffset(self, dt):
-        return self.offset
-
-    def dst(self, dt):
-        return datetime.timedelta(0)
-    
-def convertTime(myTestTimestamp):
-    #"1970-01-01T00:00:00+00:00"
-    # Normalize myTestTimestamp
-    if myTestTimestamp[-1] == 'Z':
-        myTestTimestamp = myTestTimestamp[:-1] + '-00:00'
-    matcher = re.compile(r'(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)([-+])(\d\d):(\d\d)')
-    result  = matcher.match(myTestTimestamp)
-    (year, month, day, hour, minute, second, offsetsign, houroffset, minoffset) = \
-        result.groups()
-    if offsetsign == '+':
-        offsetsign = 1
-    else:
-        offsetsign = -1
-    
-    offsetTimezone = fixedOffset( minoffset, houroffset, offsetsign )
-    myDatetime = datetime.datetime( int(year),
-                                    int(month),
-                                    int(day),
-                                    int(hour),
-                                    int(minute),
-                                    int(second),
-                                    0,
-                                    offsetTimezone)
-    return calendar.timegm( myDatetime.utctimetuple() )
-
-def getChanges(request, options = None):
-        """
-        Reponds only to POST events and starts the build process
-        
-        :arguments:
-            request
-                the http request object
-        """
-        payload = json.loads(request.args['payload'][0])
-        import urllib,datetime
-        fname = str(datetime.datetime.now()).replace(' ','_').replace(':','-')[:19]
-        # Github event debug
-        # open('github_{0}.json'.format(fname),'w').write(json.dumps(json.loads(urllib.unquote(request.args['payload'][0])), sort_keys = True, indent = 2))
-
-        if 'pull_request' in payload:
-            user = payload['pull_request']['user']['login']
-            repo = payload['pull_request']['head']['repo']['name']
-            repo_url = payload['pull_request']['head']['repo']['html_url']
-        else:
-            user = payload['repository']['owner']['name']
-            repo = payload['repository']['name']
-            repo_url = payload['repository']['url']
-        project = request.args.get('project', None)
-        if project:
-            project = project[0]
-        elif project is None:
-            project = ''
-        # This field is unused:
-        #private = payload['repository']['private']
-        changes = process_change(payload, user, repo, repo_url, project)
-        log.msg("Received %s changes from github" % len(changes))
-        return (changes, 'git')
-
-def process_change(payload, user, repo, repo_url, project):
-        """
-        Consumes the JSON as a python object and actually starts the build.
-        
-        :arguments:
-            payload
-                Python Object that represents the JSON sent by GitHub Service
-                Hook.
-        """
-        changes = []
-
-        newrev = payload['after'] if 'after' in payload else payload['pull_request']['head']['sha']
-        refname = payload['ref'] if 'ref' in payload else payload['pull_request']['head']['ref']
-
-        # We only care about regular heads, i.e. branches
-        match = re.match(r"^(refs\/heads\/|)([^/]+)$", refname)
-        if not match:
-            log.msg("Ignoring refname `%s': Not a branch" % refname)
-            return []
-
-        branch = match.groups()[1]
-        if re.match(r"^0*$", newrev):
-            log.msg("Branch `%s' deleted, ignoring" % branch)
-            return []
-        else: 
-            if 'pull_request' in payload:
-                if payload['action'] == 'closed':
-                    log.msg("PR#{} closed, ignoring".format(payload['number']))
-                    return []
-                changes = [{
-                    'category'   : 'github_pullrequest',
-                    'who'        : '{0} - PR#{1}'.format(user,payload['number']),
-                    'files'      : [],
-                    'comments'   : payload['pull_request']['title'],
-                    'revision'   : newrev,
-                    'when'       : convertTime(payload['pull_request']['updated_at']),
-                    'branch'     : branch,
-                    'revlink'    : '{0}/commit/{1}'.format(repo_url,newrev),
-                    'repository' : repo_url,
-                    'project'  : project  }]
-                return changes
-            for commit in payload['commits']:
-                files = []
-                if 'added' in commit:
-                    files.extend(commit['added'])
-                if 'modified' in commit:
-                    files.extend(commit['modified'])
-                if 'removed' in commit:
-                    files.extend(commit['removed'])
-                when =  convertTime( commit['timestamp'])
-                log.msg("New revision: %s" % commit['id'][:8])
-                chdict = dict(
-                    who      = commit['author']['name'] 
-                                + " <" + commit['author']['email'] + ">",
-                    files    = files,
-                    comments = commit['message'], 
-                    revision = commit['id'],
-                    when     = when,
-                    branch   = branch,
-                    revlink  = commit['url'], 
-                    repository = repo_url,
-                    project  = project)
-                changes.append(chdict) 
-            return changes

+ 0 - 161
hack/infrastructure/docker-ci/buildbot/master.cfg

@@ -1,161 +0,0 @@
-import os, re
-from buildbot.buildslave import BuildSlave
-from buildbot.schedulers.forcesched import ForceScheduler
-from buildbot.schedulers.basic import SingleBranchScheduler
-from buildbot.schedulers.timed import Nightly
-from buildbot.changes import filter
-from buildbot.config import BuilderConfig
-from buildbot.process.factory import BuildFactory
-from buildbot.process.properties import Property
-from buildbot.steps.shell import ShellCommand
-from buildbot.status import html, words
-from buildbot.status.web import authz, auth
-from buildbot.status.mail import MailNotifier
-
-
-def ENV(x):
-    '''Promote an environment variable for global use returning its value'''
-    retval = os.environ.get(x, '')
-    globals()[x] = retval
-    return retval
-
-
-class TestCommand(ShellCommand):
-    '''Extend ShellCommand with optional summary logs'''
-    def __init__(self, *args, **kwargs):
-        super(TestCommand, self).__init__(*args, **kwargs)
-
-    def createSummary(self, log):
-        exit_status = re.sub(r'.+\n\+ exit (\d+).+',
-            r'\1', log.getText()[-100:], flags=re.DOTALL)
-        if exit_status != '0':
-            return
-        # Infer coverage path from log
-        if '+ COVERAGE_PATH' in log.getText():
-            path = re.sub(r'.+\+ COVERAGE_PATH=((.+?)-\d+).+',
-              r'\2/\1', log.getText(), flags=re.DOTALL)
-            url = '{}coverage/{}/index.html'.format(c['buildbotURL'], path)
-            self.addURL('coverage', url)
-        elif 'COVERAGE_FILE' in log.getText():
-            path = re.sub(r'.+\+ COVERAGE_FILE=((.+?)-\d+).+',
-              r'\2/\1', log.getText(), flags=re.DOTALL)
-            url = '{}coverage/{}/index.html'.format(c['buildbotURL'], path)
-            self.addURL('coverage', url)
-
-
-PORT_WEB = 8000      # Buildbot webserver port
-PORT_GITHUB = 8011   # Buildbot github hook port
-PORT_MASTER = 9989   # Port where buildbot master listen buildworkers
-
-BUILDBOT_URL = '//localhost:{}/'.format(PORT_WEB)
-DOCKER_REPO = 'https://github.com/docker-test/docker'
-DOCKER_TEST_ARGV = 'HEAD {}'.format(DOCKER_REPO)
-REGISTRY_REPO = 'https://github.com/docker-test/docker-registry'
-REGISTRY_TEST_ARGV = 'HEAD {}'.format(REGISTRY_REPO)
-if ENV('DEPLOYMENT') == 'staging':
-    BUILDBOT_URL = "//docker-ci-stage.docker.io/"
-if ENV('DEPLOYMENT') == 'production':
-    BUILDBOT_URL = '//docker-ci.docker.io/'
-    DOCKER_REPO = 'https://github.com/dotcloud/docker'
-    DOCKER_TEST_ARGV = ''
-    REGISTRY_REPO = 'https://github.com/dotcloud/docker-registry'
-    REGISTRY_TEST_ARGV = ''
-
-# Credentials set by setup.sh from deployment.py
-ENV('WEB_USER')
-ENV('WEB_IRC_PWD')
-ENV('BUILDBOT_PWD')
-ENV('SMTP_USER')
-ENV('SMTP_PWD')
-ENV('EMAIL_RCP')
-ENV('IRC_CHANNEL')
-
-
-c = BuildmasterConfig = {}
-
-c['title'] = "docker-ci"
-c['titleURL'] = "waterfall"
-c['buildbotURL'] = BUILDBOT_URL
-c['db'] = {'db_url':"sqlite:///state.sqlite"}
-c['slaves'] = [BuildSlave('buildworker', BUILDBOT_PWD)]
-c['slavePortnum'] = PORT_MASTER
-
-
-# Schedulers
-c['schedulers'] = [ForceScheduler(name='trigger', builderNames=[
-    'docker', 'docker-registry', 'nightlyrelease', 'backup'])]
-c['schedulers'] += [SingleBranchScheduler(name="docker", treeStableTimer=None,
-    change_filter=filter.ChangeFilter(branch='master',
-    repository=DOCKER_REPO), builderNames=['docker'])]
-c['schedulers'] += [SingleBranchScheduler(name="registry", treeStableTimer=None,
-    change_filter=filter.ChangeFilter(branch='master',
-    repository=REGISTRY_REPO), builderNames=['docker-registry'])]
-c['schedulers'] += [SingleBranchScheduler(name='docker-pr', treeStableTimer=None,
-    change_filter=filter.ChangeFilter(category='github_pullrequest',
-    project='docker'), builderNames=['docker-pr'])]
-c['schedulers'] += [SingleBranchScheduler(name='docker-registry-pr', treeStableTimer=None,
-    change_filter=filter.ChangeFilter(category='github_pullrequest',
-    project='docker-registry'), builderNames=['docker-registry-pr'])]
-c['schedulers'] += [Nightly(name='daily', branch=None, builderNames=[
-    'nightlyrelease', 'backup'], hour=7, minute=00)]
-
-
-# Builders
-
-# Backup
-factory = BuildFactory()
-factory.addStep(TestCommand(description='backup', logEnviron=False,
-    usePTY=True, command='/docker-ci/tool/backup.py'))
-c['builders'] = [BuilderConfig(name='backup',slavenames=['buildworker'],
-    factory=factory)]
-
-# Docker test
-factory = BuildFactory()
-factory.addStep(TestCommand(description='docker', logEnviron=False,
-    usePTY=True, command='/docker-ci/dockertest/docker {}'.format(DOCKER_TEST_ARGV)))
-c['builders'] += [BuilderConfig(name='docker',slavenames=['buildworker'],
-    factory=factory)]
-
-# Docker pull request test
-factory = BuildFactory()
-factory.addStep(TestCommand(description='docker-pr', logEnviron=False,
-    usePTY=True, command=['/docker-ci/dockertest/docker',
-    Property('revision'), Property('repository'), Property('branch')]))
-c['builders'] += [BuilderConfig(name='docker-pr',slavenames=['buildworker'],
-    factory=factory)]
-
-# docker-registry test
-factory = BuildFactory()
-factory.addStep(TestCommand(description='docker-registry', logEnviron=False,
-    usePTY=True, command='/docker-ci/dockertest/docker-registry {}'.format(REGISTRY_TEST_ARGV)))
-c['builders'] += [BuilderConfig(name='docker-registry',slavenames=['buildworker'],
-    factory=factory)]
-
-# Docker registry pull request test
-factory = BuildFactory()
-factory.addStep(TestCommand(description='docker-registry-pr', logEnviron=False,
-    usePTY=True, command=['/docker-ci/dockertest/docker-registry',
-    Property('revision'), Property('repository'), Property('branch')]))
-c['builders'] += [BuilderConfig(name='docker-registry-pr',slavenames=['buildworker'],
-    factory=factory)]
-
-# Docker nightly release
-factory = BuildFactory()
-factory.addStep(ShellCommand(description='NightlyRelease',logEnviron=False,
-    usePTY=True, command=['/docker-ci/dockertest/nightlyrelease']))
-c['builders'] += [BuilderConfig(name='nightlyrelease',slavenames=['buildworker'],
-    factory=factory)]
-
-# Status
-authz_cfg = authz.Authz(auth=auth.BasicAuth([(WEB_USER, WEB_IRC_PWD)]),
-    forceBuild='auth')
-c['status'] = [html.WebStatus(http_port=PORT_WEB, authz=authz_cfg)]
-c['status'].append(html.WebStatus(http_port=PORT_GITHUB, allowForce=True,
-    change_hook_dialects={ 'github': True }))
-c['status'].append(MailNotifier(fromaddr='docker-test@docker.io',
-    sendToInterestedUsers=False, extraRecipients=[EMAIL_RCP],
-    mode='failing', relayhost='smtp.mailgun.org', smtpPort=587, useTls=True,
-    smtpUser=SMTP_USER, smtpPassword=SMTP_PWD))
-c['status'].append(words.IRC("irc.freenode.net", "dockerqabot",
-    channels=[IRC_CHANNEL], password=WEB_IRC_PWD, allowForce=True,
-    notify_events={'exception':1, 'successToFailure':1, 'failureToSuccess':1}))

+ 0 - 22
hack/infrastructure/docker-ci/dcr/prod/docker-ci.yml

@@ -1,22 +0,0 @@
-docker-ci:
-  image: "docker-ci/docker-ci"
-  release_name: "docker-ci-0.5.6"
-  ports: ["80","2222:22","8011:8011"]
-  register: "80"
-  volumes: ["/run:/var/socket","/home/docker-ci:/data/docker-ci"]
-  command: []
-  env:
-    - "DEPLOYMENT=production"
-    - "IRC_CHANNEL=docker-testing"
-    - "BACKUP_BUCKET=backup-ci"
-    - "$WEB_USER"
-    - "$WEB_IRC_PWD"
-    - "$BUILDBOT_PWD"
-    - "$AWS_ACCESS_KEY"
-    - "$AWS_SECRET_KEY"
-    - "$GPG_PASSPHRASE"
-    - "$BACKUP_AWS_ID"
-    - "$BACKUP_AWS_SECRET"
-    - "$SMTP_USER"
-    - "$SMTP_PWD"
-    - "$EMAIL_RCP"

+ 0 - 5
hack/infrastructure/docker-ci/dcr/prod/settings.yml

@@ -1,5 +0,0 @@
-default:
-  hipaches: ['192.168.100.67:6379']
-  daemons: ['192.168.100.67:4243']
-  use_ssh: False
-

+ 0 - 22
hack/infrastructure/docker-ci/dcr/stage/docker-ci.yml

@@ -1,22 +0,0 @@
-docker-ci:
-  image: "docker-ci/docker-ci"
-  release_name: "docker-ci-stage"
-  ports: ["80","2222:22","8011:8011"]
-  register: "80"
-  volumes: ["/run:/var/socket","/home/docker-ci:/data/docker-ci"]
-  command: []
-  env:
-    - "DEPLOYMENT=staging"
-    - "IRC_CHANNEL=docker-testing-staging"
-    - "BACKUP_BUCKET=ci-backup-stage"
-    - "$BACKUP_AWS_ID"
-    - "$BACKUP_AWS_SECRET"
-    - "$WEB_USER"
-    - "$WEB_IRC_PWD"
-    - "$BUILDBOT_PWD"
-    - "$AWS_ACCESS_KEY"
-    - "$AWS_SECRET_KEY"
-    - "$GPG_PASSPHRASE"
-    - "$SMTP_USER"
-    - "$SMTP_PWD"
-    - "$EMAIL_RCP"

+ 0 - 5
hack/infrastructure/docker-ci/dcr/stage/settings.yml

@@ -1,5 +0,0 @@
-default:
-  hipaches: ['192.168.100.65:6379']
-  daemons: ['192.168.100.65:4243']
-  use_ssh: False
-

+ 0 - 52
hack/infrastructure/docker-ci/docker-coverage/gocoverage.sh

@@ -1,52 +0,0 @@
-#!/bin/bash
-
-export PATH='/go/bin':$PATH
-export DOCKER_PATH='/go/src/github.com/dotcloud/docker'
-
-# Signal coverage report name, parsed by docker-ci
-set -x
-COVERAGE_PATH=$(date +"docker-%Y%m%d%H%M%S")
-set +x
-
-REPORTS="/data/$COVERAGE_PATH"
-INDEX="$REPORTS/index.html"
-
-# Test docker
-cd $DOCKER_PATH
-./hack/make.sh test; exit_status=$?
-PROFILE_PATH="$(ls -d $DOCKER_PATH/bundles/* | sed -n '$ p')/test/coverprofiles"
-
-if [ "$exit_status" -eq "0" ]; then
-    # Download coverage dependencies
-    go get github.com/axw/gocov/gocov
-    go get -u github.com/matm/gocov-html
-
-    # Create coverage report
-    mkdir -p $REPORTS
-    cd $PROFILE_PATH
-    cat > $INDEX << "EOF"
-<!DOCTYPE html><head><meta charset="utf-8">
-<script type="text/javascript" src="//tablesorter.com/jquery-latest.js"></script>
-<script type="text/javascript" src="//tablesorter.com/__jquery.tablesorter.min.js"></script>
-<script type="text/javascript">$(document).ready(function() {
-$("table").tablesorter({ sortForce: [[1,0]] }); });</script>
-<style>table,th,td{border:1px solid black;}</style>
-<title>Docker Coverage Report</title>
-</head><body>
-<h1><strong>Docker Coverage Report</strong></h1>
-<table class="tablesorter">
-<thead><tr><th>package</th><th>pct</th></tr></thead><tbody>
-EOF
-    for profile in *; do
-        gocov convert $profile | gocov-html >$REPORTS/$profile.html
-        echo "<tr><td><a href=\"${profile}.html\">$profile</a></td><td>" >> $INDEX
-        go tool cover -func=$profile | sed -En '$ s/.+\t(.+)/\1/p' >> $INDEX
-        echo "</td></tr>" >> $INDEX
-    done
-    echo "</tbody></table></body></html>" >> $INDEX
-fi
-
-# Signal test and coverage result, parsed by docker-ci
-set -x
-exit $exit_status
-

+ 0 - 1
hack/infrastructure/docker-ci/dockertest/docker

@@ -1 +0,0 @@
-project

+ 0 - 1
hack/infrastructure/docker-ci/dockertest/docker-registry

@@ -1 +0,0 @@
-project

+ 0 - 13
hack/infrastructure/docker-ci/dockertest/nightlyrelease

@@ -1,13 +0,0 @@
-#!/usr/bin/env bash
-
-if [ "$DEPLOYMENT" == "production" ]; then
-    AWS_S3_BUCKET='test.docker.io'
-else
-    AWS_S3_BUCKET='get-staging.docker.io'
-fi
-
-docker run --rm --privileged -v /run:/var/socket \
-    -e AWS_S3_BUCKET=$AWS_S3_BUCKET -e AWS_ACCESS_KEY=$AWS_ACCESS_KEY \
-    -e AWS_SECRET_KEY=$AWS_SECRET_KEY -e GPG_PASSPHRASE=$GPG_PASSPHRASE \
-    -e DOCKER_RELEASE=1 -e DEPLOYMENT=$DEPLOYMENT docker-ci/testbuilder docker
-

+ 0 - 8
hack/infrastructure/docker-ci/dockertest/project

@@ -1,8 +0,0 @@
-#!/usr/bin/env bash
-set -x
-
-PROJECT_NAME=$(basename $0)
-
-docker run --rm -u sysadmin -e DEPLOYMENT=$DEPLOYMENT -v /run:/var/socket \
-    -v /home/docker-ci/coverage/$PROJECT_NAME:/data docker-ci/testbuilder $PROJECT_NAME $1 $2 $3
-

+ 0 - 61
hack/infrastructure/docker-ci/functionaltests/test_index.py

@@ -1,61 +0,0 @@
-#!/usr/bin/python
-
-import os
-username, password = os.environ['DOCKER_CREDS'].split(':')
-
-from selenium import webdriver
-from selenium.webdriver.common.by import By
-from selenium.webdriver.common.keys import Keys
-from selenium.webdriver.support.ui import Select
-from selenium.common.exceptions import NoSuchElementException
-import unittest, time, re
-
-class Docker(unittest.TestCase):
-    def setUp(self):
-        self.driver = webdriver.PhantomJS()
-        self.driver.implicitly_wait(30)
-        self.base_url = "http://www.docker.io/"
-        self.verificationErrors = []
-        self.accept_next_alert = True
-
-    def test_docker(self):
-        driver = self.driver
-        print "Login into {0} as login user {1} ...".format(self.base_url,username)
-        driver.get(self.base_url + "/")
-        driver.find_element_by_link_text("INDEX").click()
-        driver.find_element_by_link_text("login").click()
-        driver.find_element_by_id("id_username").send_keys(username)
-        driver.find_element_by_id("id_password").send_keys(password)
-        print "Checking login user ..."
-        driver.find_element_by_css_selector("input[type=\"submit\"]").click()
-        try: self.assertEqual("test", driver.find_element_by_css_selector("h3").text)
-        except AssertionError as e: self.verificationErrors.append(str(e))
-        print "Login user {0} found".format(username)
-
-    def is_element_present(self, how, what):
-        try: self.driver.find_element(by=how, value=what)
-        except NoSuchElementException, e: return False
-        return True
-
-    def is_alert_present(self):
-        try: self.driver.switch_to_alert()
-        except NoAlertPresentException, e: return False
-        return True
-
-    def close_alert_and_get_its_text(self):
-        try:
-            alert = self.driver.switch_to_alert()
-            alert_text = alert.text
-            if self.accept_next_alert:
-                alert.accept()
-            else:
-                alert.dismiss()
-            return alert_text
-        finally: self.accept_next_alert = True
-
-    def tearDown(self):
-        self.driver.quit()
-        self.assertEqual([], self.verificationErrors)
-
-if __name__ == "__main__":
-    unittest.main()

+ 0 - 27
hack/infrastructure/docker-ci/functionaltests/test_registry.sh

@@ -1,27 +0,0 @@
-#!/bin/sh
-
-set -x
-
-# Cleanup
-rm -rf docker-registry
-
-# Setup the environment
-export SETTINGS_FLAVOR=test
-export DOCKER_REGISTRY_CONFIG=config_test.yml
-export PYTHONPATH=$(pwd)/docker-registry/test
-
-# Get latest docker registry
-git clone -q https://github.com/dotcloud/docker-registry.git
-cd docker-registry
-sed -Ei "s#(boto_bucket: ).+#\1_env:S3_BUCKET#" config_test.yml
-
-# Get dependencies
-pip install -q -r requirements.txt
-pip install -q -r test-requirements.txt
-pip install -q tox
-
-# Run registry tests
-tox || exit 1
-python -m unittest discover -p s3.py -s test || exit 1
-python -m unittest discover -p workflow.py -s test
-

+ 0 - 12
hack/infrastructure/docker-ci/nginx/nginx.conf

@@ -1,12 +0,0 @@
-server {
-    listen              80;
-    root /data/docker-ci;
-
-    location / {
-        proxy_pass http://localhost:8000/;
-    }
-
-    location /coverage {
-        root /data/docker-ci;
-    }
-}

+ 0 - 28
hack/infrastructure/docker-ci/report/Dockerfile

@@ -1,28 +0,0 @@
-# VERSION:        0.22
-# DOCKER-VERSION  0.6.3
-# AUTHOR:         Daniel Mizyrycki <daniel@dotcloud.com>
-# DESCRIPTION:    Generate docker-ci daily report
-# COMMENTS:       The build process is initiated by deployment.py
-                  Report configuration is passed through ./credentials.json at
-#                 deployment time.
-# TO_BUILD:       docker build -t report .
-# TO_DEPLOY:      docker run report
-
-from ubuntu:12.04
-maintainer Daniel Mizyrycki <daniel@dotcloud.com>
-
-env PYTHONPATH /report
-
-
-# Add report dependencies
-run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > \
-    /etc/apt/sources.list
-run apt-get update; apt-get install -y python2.7 python-pip ssh rsync
-
-# Set San Francisco timezone
-run echo "America/Los_Angeles" >/etc/timezone
-run dpkg-reconfigure --frontend noninteractive tzdata
-
-# Add report code and set default container command
-add . /report
-cmd "/report/report.py"

+ 0 - 130
hack/infrastructure/docker-ci/report/deployment.py

@@ -1,130 +0,0 @@
-#!/usr/bin/env python
-
-'''Deploy docker-ci report container on Digital Ocean.
-Usage:
-    export CONFIG_JSON='
-        { "DROPLET_NAME":        "Digital_Ocean_dropplet_name",
-          "DO_CLIENT_ID":        "Digital_Ocean_client_id",
-          "DO_API_KEY":          "Digital_Ocean_api_key",
-          "DOCKER_KEY_ID":       "Digital_Ocean_ssh_key_id",
-          "DOCKER_CI_KEY_PATH":  "docker-ci_private_key_path",
-          "DOCKER_CI_PUB":       "$(cat docker-ci_ssh_public_key.pub)",
-          "DOCKER_CI_ADDRESS"    "user@docker-ci_fqdn_server",
-          "SMTP_USER":           "SMTP_server_user",
-          "SMTP_PWD":            "SMTP_server_password",
-          "EMAIL_SENDER":        "Buildbot_mailing_sender",
-          "EMAIL_RCP":           "Buildbot_mailing_receipient" }'
-    python deployment.py
-'''
-
-import re, json, requests, base64
-from fabric import api
-from fabric.api import cd, run, put, sudo
-from os import environ as env
-from time import sleep
-from datetime import datetime
-
-# Populate environment variables
-CONFIG = json.loads(env['CONFIG_JSON'])
-for key in CONFIG:
-    env[key] = CONFIG[key]
-
-# Load DOCKER_CI_KEY
-env['DOCKER_CI_KEY'] = open(env['DOCKER_CI_KEY_PATH']).read()
-
-DROPLET_NAME = env.get('DROPLET_NAME','report')
-TIMEOUT = 120            # Seconds before timeout droplet creation
-IMAGE_ID = 1004145       # Docker on Ubuntu 13.04
-REGION_ID = 4            # New York 2
-SIZE_ID = 66             # memory 512MB
-DO_IMAGE_USER = 'root'   # Image user on Digital Ocean
-API_URL = 'https://api.digitalocean.com/'
-
-
-class digital_ocean():
-
-    def __init__(self, key, client):
-        '''Set default API parameters'''
-        self.key = key
-        self.client = client
-        self.api_url = API_URL
-
-    def api(self, cmd_path, api_arg={}):
-        '''Make api call'''
-        api_arg.update({'api_key':self.key, 'client_id':self.client})
-        resp = requests.get(self.api_url + cmd_path, params=api_arg).text
-        resp = json.loads(resp)
-        if resp['status'] != 'OK':
-            raise Exception(resp['error_message'])
-        return resp
-
-    def droplet_data(self, name):
-        '''Get droplet data'''
-        data = self.api('droplets')
-        data = [droplet for droplet in data['droplets']
-            if droplet['name'] == name]
-        return data[0] if data else {}
-
-def json_fmt(data):
-    '''Format json output'''
-    return json.dumps(data, sort_keys = True, indent = 2)
-
-
-do = digital_ocean(env['DO_API_KEY'], env['DO_CLIENT_ID'])
-
-# Get DROPLET_NAME data
-data = do.droplet_data(DROPLET_NAME)
-
-# Stop processing if DROPLET_NAME exists on Digital Ocean
-if data:
-    print ('Droplet: {} already deployed. Not further processing.'
-        .format(DROPLET_NAME))
-    exit(1)
-
-# Create droplet
-do.api('droplets/new', {'name':DROPLET_NAME, 'region_id':REGION_ID,
-    'image_id':IMAGE_ID, 'size_id':SIZE_ID,
-    'ssh_key_ids':[env['DOCKER_KEY_ID']]})
-
-# Wait for droplet to be created.
-start_time = datetime.now()
-while (data.get('status','') != 'active' and (
- datetime.now()-start_time).seconds < TIMEOUT):
-    data = do.droplet_data(DROPLET_NAME)
-    print data['status']
-    sleep(3)
-
-# Wait for the machine to boot
-sleep(15)
-
-# Get droplet IP
-ip = str(data['ip_address'])
-print 'droplet: {}    ip: {}'.format(DROPLET_NAME, ip)
-
-api.env.host_string = ip
-api.env.user = DO_IMAGE_USER
-api.env.key_filename = env['DOCKER_CI_KEY_PATH']
-
-# Correct timezone
-sudo('echo "America/Los_Angeles" >/etc/timezone')
-sudo('dpkg-reconfigure --frontend noninteractive tzdata')
-
-# Load JSON_CONFIG environment for Dockerfile
-CONFIG_JSON= base64.b64encode(
-    '{{"DOCKER_CI_PUB":     "{DOCKER_CI_PUB}",'
-    '  "DOCKER_CI_KEY":     "{DOCKER_CI_KEY}",'
-    '  "DOCKER_CI_ADDRESS": "{DOCKER_CI_ADDRESS}",'
-    '  "SMTP_USER":         "{SMTP_USER}",'
-    '  "SMTP_PWD":          "{SMTP_PWD}",'
-    '  "EMAIL_SENDER":      "{EMAIL_SENDER}",'
-    '  "EMAIL_RCP":         "{EMAIL_RCP}"}}'.format(**env))
-
-run('mkdir -p /data/report')
-put('./', '/data/report')
-with cd('/data/report'):
-    run('chmod 700 report.py')
-    run('echo "{}" > credentials.json'.format(CONFIG_JSON))
-    run('docker build -t report .')
-    run('rm credentials.json')
-    run("echo -e '30 09 * * * /usr/bin/docker run report\n' |"
-        " /usr/bin/crontab -")

+ 0 - 145
hack/infrastructure/docker-ci/report/report.py

@@ -1,145 +0,0 @@
-#!/usr/bin/python
-
-'''CONFIG_JSON is a json encoded string base64 environment variable. It is used
-to clone docker-ci database, generate docker-ci report and submit it by email.
-CONFIG_JSON data comes from the file /report/credentials.json inserted in this
-container by deployment.py:
-
-{ "DOCKER_CI_PUB":       "$(cat docker-ci_ssh_public_key.pub)",
-  "DOCKER_CI_KEY":       "$(cat docker-ci_ssh_private_key.key)",
-  "DOCKER_CI_ADDRESS":   "user@docker-ci_fqdn_server",
-  "SMTP_USER":           "SMTP_server_user",
-  "SMTP_PWD":            "SMTP_server_password",
-  "EMAIL_SENDER":        "Buildbot_mailing_sender",
-  "EMAIL_RCP":           "Buildbot_mailing_receipient" }  '''
-
-import os, re, json, sqlite3, datetime, base64
-import smtplib
-from datetime import timedelta
-from subprocess import call
-from os import environ as env
-
-TODAY = datetime.date.today()
-
-# Load credentials to the environment
-env['CONFIG_JSON'] = base64.b64decode(open('/report/credentials.json').read())
-
-# Remove SSH private key as it needs more processing
-CONFIG = json.loads(re.sub(r'("DOCKER_CI_KEY".+?"(.+?)",)','',
-    env['CONFIG_JSON'], flags=re.DOTALL))
-
-# Populate environment variables
-for key in CONFIG:
-    env[key] = CONFIG[key]
-
-# Load SSH private key
-env['DOCKER_CI_KEY'] = re.sub('^.+"DOCKER_CI_KEY".+?"(.+?)".+','\\1',
-    env['CONFIG_JSON'],flags=re.DOTALL)
-
-# Prevent rsync to validate host on first connection to docker-ci
-os.makedirs('/root/.ssh')
-open('/root/.ssh/id_rsa','w').write(env['DOCKER_CI_KEY'])
-os.chmod('/root/.ssh/id_rsa',0600)
-open('/root/.ssh/config','w').write('StrictHostKeyChecking no\n')
-
-
-# Sync buildbot database from docker-ci
-call('rsync {}:/data/buildbot/master/state.sqlite .'.format(
-    env['DOCKER_CI_ADDRESS']), shell=True)
-
-class SQL:
-    def __init__(self, database_name):
-        sql = sqlite3.connect(database_name)
-        # Use column names as keys for fetchall rows
-        sql.row_factory = sqlite3.Row
-        sql = sql.cursor()
-        self.sql = sql
-
-    def query(self,query_statement):
-        return self.sql.execute(query_statement).fetchall()
-
-sql = SQL("state.sqlite")
-
-
-class Report():
-
-    def __init__(self,period='',date=''):
-        self.data = []
-        self.period = 'date' if not period else period
-        self.date = str(TODAY) if not date else date
-        self.compute()
-
-    def compute(self):
-        '''Compute report'''
-        if self.period == 'week':
-            self.week_report(self.date)
-        else:
-            self.date_report(self.date)
-
-
-    def date_report(self,date):
-        '''Create a date test report'''
-        builds = []
-        # Get a queryset with all builds from date
-        rows = sql.query('SELECT * FROM builds JOIN buildrequests'
-            ' WHERE builds.brid=buildrequests.id and'
-            ' date(start_time, "unixepoch", "localtime") = "{0}"'
-            ' GROUP BY number'.format(date))
-        build_names = sorted(set([row['buildername'] for row in rows]))
-        # Create a report build line for a given build
-        for build_name in build_names:
-            tried = len([row['buildername']
-                for row in rows if row['buildername'] == build_name])
-            fail_tests = [row['buildername'] for row in rows if (
-                row['buildername'] == build_name and row['results'] != 0)]
-            fail = len(fail_tests)
-            fail_details = ''
-            fail_pct = int(100.0*fail/tried) if  tried != 0 else 100
-            builds.append({'name': build_name, 'tried': tried, 'fail': fail,
-                'fail_pct': fail_pct, 'fail_details':fail_details})
-        if builds:
-            self.data.append({'date': date, 'builds': builds})
-
-
-    def week_report(self,date):
-        '''Add the week's date test reports to report.data'''
-        date = datetime.datetime.strptime(date,'%Y-%m-%d').date()
-        last_monday = date - datetime.timedelta(days=date.weekday())
-        week_dates = [last_monday + timedelta(days=x) for x in range(7,-1,-1)]
-        for date in week_dates:
-            self.date_report(str(date))
-
-    def render_text(self):
-        '''Return rendered report in text format'''
-        retval = ''
-        fail_tests = {}
-        for builds in self.data:
-            retval += 'Test date: {0}\n'.format(builds['date'],retval)
-            table = ''
-            for build in builds['builds']:
-                table += ('Build {name:15}   Tried: {tried:4}   '
-                    ' Failures: {fail:4} ({fail_pct}%)\n'.format(**build))
-                if build['name'] in fail_tests:
-                    fail_tests[build['name']] += build['fail_details']
-                else:
-                    fail_tests[build['name']] = build['fail_details']
-            retval += '{0}\n'.format(table)
-            retval += '\n    Builds failing'
-            for fail_name in fail_tests:
-                retval += '\n' + fail_name + '\n'
-                for (fail_id,fail_url,rn_tests,nr_errors,log_errors,
-                 tracelog_errors) in fail_tests[fail_name]:
-                    retval += fail_url + '\n'
-            retval += '\n\n'
-        return retval
-
-
-# Send email
-smtp_from = env['EMAIL_SENDER']
-subject = '[docker-ci] Daily report for {}'.format(str(TODAY))
-msg = "From: {}\r\nTo: {}\r\nSubject: {}\r\n\r\n".format(
-    smtp_from, env['EMAIL_RCP'], subject)
-msg = msg + Report('week').render_text()
-server = smtplib.SMTP_SSL('smtp.mailgun.org')
-server.login(env['SMTP_USER'], env['SMTP_PWD'])
-server.sendmail(smtp_from, env['EMAIL_RCP'], msg)

+ 0 - 54
hack/infrastructure/docker-ci/setup.sh

@@ -1,54 +0,0 @@
-#!/usr/bin/env bash
-
-# Set timezone
-echo "GMT" >/etc/timezone
-dpkg-reconfigure --frontend noninteractive tzdata
-
-# Set ssh superuser
-mkdir -p /data/buildbot /var/run/sshd /run
-useradd -m -d /home/sysadmin -s /bin/bash -G sudo,docker -p '*' sysadmin
-sed -Ei 's/(\%sudo.*) ALL/\1 NOPASSWD:ALL/' /etc/sudoers
-cd /home/sysadmin
-mkdir .ssh
-chmod 700 .ssh
-cat > .ssh/authorized_keys << 'EOF'
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC7ALVhwQ68q1SjrKaAduOuOEAcWmb8kDZf5qA7T1fM8AP07EDC7nSKRJ8PXUBGTOQfxm89coJDuSJsTAZ+1PvglXhA0Mq6+knc6ZrZY+SuZlDIDAk4TOdVPoDZnmR1YW2McxHkhcGIOKeC8MMig5NeEjtgQwXzauUSPqeh8HMlLZRMooFYyyluIpn7NaCLzyWjwAQz2s3KyI7VE7hl+ncCrW86v+dciEdwqtzNoUMFb3iDpPxaiCl3rv+SB7co/5eUDTs1FZvUcYMXKQuf8R+2ZKzXOpwr0Zs8sKQXvXavCeWykwGgXLBjVkvrDcHuDD6UXCW63UKgmRECpLZaMBVIIRWLEEgTS5OSQTcxpMVe5zUW6sDvXHTcdPwWrcn1dE9F/0vLC0HJ4ADKelLX5zyTpmXGbuZuntIf1JO67D/K/P++uV1rmVIH+zgtOf23w5rX2zKb4BSTqP0sv61pmWV7MEVoEz6yXswcTjS92tb775v7XLU9vKAkt042ORFdE4/++hejhL/Lj52IRgjt1CJZHZsR9JywJZrz3kYuf8eU2J2FYh0Cpz5gmf0f+12Rt4HztnZxGPP4KuMa66e4+hpx1jynjMZ7D5QUnNYEmuvJByopn8HSluuY/kS5MMyZCZtJLEPGX4+yECX0Di/S0vCRl2NyqfCBqS+yXXT5SA1nFw== docker-test@docker.io
-EOF
-chmod 600 .ssh/authorized_keys
-chown -R sysadmin .ssh
-
-# Fix docker group id for use of host dockerd by sysadmin
-sed -Ei 's/(docker:x:)[^:]+/\1999/' /etc/group
-
-# Create buildbot configuration
-cd /data/buildbot; buildbot create-master master
-cp -a /data/buildbot/master/master.cfg.sample \
-    /data/buildbot/master/master.cfg
-cd /data/buildbot; \
-    buildslave create-slave slave localhost:9989 buildworker pass
-cp /docker-ci/buildbot/master.cfg /data/buildbot/master
-
-# Patch github webstatus to capture pull requests
-cp /docker-ci/buildbot/github.py /usr/local/lib/python2.7/dist-packages/buildbot/status/web/hooks
-chown -R sysadmin.sysadmin /data
-
-# Create nginx configuration
-rm /etc/nginx/sites-enabled/default
-cp /docker-ci/nginx/nginx.conf /etc/nginx/conf.d/buildbot.conf
-/bin/echo -e '\ndaemon off;\n' >> /etc/nginx/nginx.conf
-
-# Set supervisord buildbot, nginx and sshd processes
-/bin/echo -e "\
-[program:buildmaster]\n\
-command=twistd --nodaemon --no_save -y buildbot.tac\n\
-directory=/data/buildbot/master\n\
-user=sysadmin\n\n\
-[program:buildworker]\n\
-command=twistd --nodaemon --no_save -y buildbot.tac\n\
-directory=/data/buildbot/slave\n\
-user=sysadmin\n" > \
-    /etc/supervisor/conf.d/buildbot.conf
-/bin/echo -e "[program:nginx]\ncommand=/usr/sbin/nginx\n" > \
-    /etc/supervisor/conf.d/nginx.conf
-/bin/echo -e "[program:sshd]\ncommand=/usr/sbin/sshd -D\n" > \
-    /etc/supervisor/conf.d/sshd.conf

+ 0 - 12
hack/infrastructure/docker-ci/testbuilder/Dockerfile

@@ -1,12 +0,0 @@
-# TO_BUILD:      docker build --no-cache -t docker-ci/testbuilder .
-# TO_RUN:        docker run --rm -u sysadmin \
-#                -v /run:/var/socket docker-ci/testbuilder docker-registry
-#
-
-FROM docker-ci/docker-ci
-ENV HOME /home/sysadmin
-
-RUN mkdir /testbuilder
-ADD . /testbuilder
-
-ENTRYPOINT ["/testbuilder/testbuilder.sh"]

+ 0 - 12
hack/infrastructure/docker-ci/testbuilder/docker-registry.sh

@@ -1,12 +0,0 @@
-#!/usr/bin/env bash
-set -x
-set -e
-PROJECT_PATH=$1
-
-# Build the docker project
-cd /data/$PROJECT_PATH
-sg docker -c "docker build -q -t registry ."
-cd test; sg docker -c "docker build -q -t docker-registry-test ."
-
-# Run the tests
-sg docker -c "docker run --rm -v /home/docker-ci/coverage/docker-registry:/data docker-registry-test"

+ 0 - 18
hack/infrastructure/docker-ci/testbuilder/docker.sh

@@ -1,18 +0,0 @@
-#!/usr/bin/env bash
-set -x
-set -e
-PROJECT_PATH=$1
-
-# Build the docker project
-cd /data/$PROJECT_PATH
-sg docker -c "docker build -q -t docker ."
-
-if [ "$DOCKER_RELEASE" == "1" ]; then
-    # Do nightly release
-    echo sg docker -c "docker run --rm --privileged -v /run:/var/socket -e AWS_S3_BUCKET=$AWS_S3_BUCKET -e AWS_ACCESS_KEY= -e AWS_SECRET_KEY= -e GPG_PASSPHRASE= docker hack/release.sh"
-    set +x
-    sg docker -c "docker run --rm --privileged -v /run:/var/socket -e AWS_S3_BUCKET=$AWS_S3_BUCKET -e AWS_ACCESS_KEY=$AWS_ACCESS_KEY -e AWS_SECRET_KEY=$AWS_SECRET_KEY -e GPG_PASSPHRASE=$GPG_PASSPHRASE docker hack/release.sh"
-else
-    # Run the tests
-    sg docker -c "docker run --rm --privileged -v /home/docker-ci/coverage/docker:/data docker ./hack/infrastructure/docker-ci/docker-coverage/gocoverage.sh"
-fi

+ 0 - 40
hack/infrastructure/docker-ci/testbuilder/testbuilder.sh

@@ -1,40 +0,0 @@
-#!/usr/bin/env bash
-# Download,  build and run a docker project tests
-# Environment variables: DEPLOYMENT
-
-cat $0
-set -e
-set -x
-
-PROJECT=$1
-COMMIT=${2-HEAD}
-REPO=${3-https://github.com/dotcloud/$PROJECT}
-BRANCH=${4-master}
-REPO_PROJ="https://github.com/docker-test/$PROJECT"
-if [ "$DEPLOYMENT" == "production" ]; then
-    REPO_PROJ="https://github.com/dotcloud/$PROJECT"
-fi
-set +x
-
-# Generate a random string of $1 characters
-function random {
-    cat /dev/urandom | tr -cd 'a-f0-9' | head -c $1
-}
-
-PROJECT_PATH="$PROJECT-tmp-$(random 12)"
-
-# Set docker-test git user
-set -x
-git config --global user.email "docker-test@docker.io"
-git config --global user.name "docker-test"
-
-# Fetch project
-git clone -q $REPO_PROJ -b master /data/$PROJECT_PATH
-cd /data/$PROJECT_PATH
-echo "Git commit: $(git rev-parse HEAD)"
-git fetch -q $REPO $BRANCH
-git merge --no-edit $COMMIT
-
-# Build the project dockertest
-/testbuilder/$PROJECT.sh $PROJECT_PATH
-rm -rf /data/$PROJECT_PATH

+ 0 - 47
hack/infrastructure/docker-ci/tool/backup.py

@@ -1,47 +0,0 @@
-#!/usr/bin/env python
-
-import os,sys,json
-from datetime import datetime
-from filecmp import cmp
-from subprocess import check_call
-from boto.s3.key import Key
-from boto.s3.connection import S3Connection
-
-def ENV(x):
-    '''Promote an environment variable for global use returning its value'''
-    retval = os.environ.get(x, '')
-    globals()[x] = retval
-    return retval
-
-ROOT_PATH = '/data/backup/docker-ci'
-TODAY = str(datetime.today())[:10]
-BACKUP_FILE = '{}/docker-ci_{}.tgz'.format(ROOT_PATH, TODAY)
-BACKUP_LINK = '{}/docker-ci.tgz'.format(ROOT_PATH)
-ENV('BACKUP_BUCKET')
-ENV('BACKUP_AWS_ID')
-ENV('BACKUP_AWS_SECRET')
-
-'''Create full master buildbot backup, avoiding duplicates'''
-# Ensure backup path exist
-if not os.path.exists(ROOT_PATH):
-    os.makedirs(ROOT_PATH)
-# Make actual backups
-check_call('/bin/tar czf {} -C /data --exclude=backup --exclude=buildbot/slave'
-    ' . 1>/dev/null 2>&1'.format(BACKUP_FILE),shell=True)
-# remove previous dump if it is the same as the latest
-if (os.path.exists(BACKUP_LINK) and cmp(BACKUP_FILE, BACKUP_LINK) and
- os.path._resolve_link(BACKUP_LINK) != BACKUP_FILE):
-    os.unlink(os.path._resolve_link(BACKUP_LINK))
-# Recreate backup link pointing to latest backup
-try:
-    os.unlink(BACKUP_LINK)
-except:
-    pass
-os.symlink(BACKUP_FILE, BACKUP_LINK)
-
-# Make backup on S3
-bucket = S3Connection(BACKUP_AWS_ID,BACKUP_AWS_SECRET).get_bucket(BACKUP_BUCKET)
-k = Key(bucket)
-k.key = BACKUP_FILE
-k.set_contents_from_filename(BACKUP_FILE)
-bucket.copy_key(os.path.basename(BACKUP_LINK),BACKUP_BUCKET,BACKUP_FILE[1:])

+ 1 - 1
hack/install.sh

@@ -85,7 +85,7 @@ case "$lsb_dist" in
 			fi
 		}
 		
-		# TODO remove this section once device-mapper lands
+		# aufs is preferred over devicemapper; try to ensure the driver is available.
 		if ! grep -q aufs /proc/filesystems && ! $sh_c 'modprobe aufs'; then
 			kern_extras="linux-image-extra-$(uname -r)"
 			

+ 1 - 1
hack/make.sh

@@ -89,7 +89,7 @@ LDFLAGS='
 '
 LDFLAGS_STATIC='-linkmode external'
 EXTLDFLAGS_STATIC='-static'
-BUILDFLAGS=( -a -tags "netgo $DOCKER_BUILDTAGS" )
+BUILDFLAGS=( -a -tags "netgo static_build $DOCKER_BUILDTAGS" )
 
 # A few more flags that are specific just to building a completely-static binary (see hack/make/binary)
 # PLEASE do not use these anywhere else.

+ 3 - 0
hack/vendor.sh

@@ -58,3 +58,6 @@ mv src/code.google.com/p/go/src/pkg/archive/tar tmp-tar
 rm -rf src/code.google.com/p/go
 mkdir -p src/code.google.com/p/go/src/pkg/archive
 mv tmp-tar src/code.google.com/p/go/src/pkg/archive/tar
+
+clone git github.com/godbus/dbus cb98efbb933d8389ab549a060e880ea3c375d213
+clone git github.com/coreos/go-systemd 4c14ed39b8a643ac44b4f95b5a53c00e94261475

+ 28 - 0
integration/buildfile_test.go

@@ -311,6 +311,16 @@ RUN [ "$(cat /testfile)" = 'test!' ]
 		},
 		nil,
 	},
+	{
+		`
+FROM {IMAGE}
+# what \
+RUN mkdir /testing
+RUN touch /testing/other
+`,
+		nil,
+		nil,
+	},
 }
 
 // FIXME: test building with 2 successive overlapping ADD commands
@@ -998,3 +1008,21 @@ func TestBuildOnBuildForbiddenMaintainerTrigger(t *testing.T) {
 		t.Fatal("Error should not be nil")
 	}
 }
+
+// gh #2446
+func TestBuildAddToSymlinkDest(t *testing.T) {
+	eng := NewTestEngine(t)
+	defer nuke(mkRuntimeFromEngine(eng, t))
+
+	_, err := buildImage(testContextTemplate{`
+        from {IMAGE}
+        run mkdir /foo
+        run ln -s /foo /bar
+        add foo /bar/
+        run stat /bar/foo
+        `,
+		[][2]string{{"foo", "HEYO"}}, nil}, t, eng, true)
+	if err != nil {
+		t.Fatal(err)
+	}
+}

+ 19 - 0
integration/commands_test.go

@@ -252,6 +252,25 @@ func TestRunWorkdirExists(t *testing.T) {
 	}
 }
 
+// TestRunWorkdirExistsAndIsFile checks that if 'docker run -w' with existing file can be detected
+func TestRunWorkdirExistsAndIsFile(t *testing.T) {
+
+	cli := api.NewDockerCli(nil, nil, ioutil.Discard, testDaemonProto, testDaemonAddr, nil)
+	defer cleanup(globalEngine, t)
+
+	c := make(chan struct{})
+	go func() {
+		defer close(c)
+		if err := cli.CmdRun("-w", "/bin/cat", unitTestImageID, "pwd"); err == nil {
+			t.Fatal("should have failed to run when using /bin/cat as working dir.")
+		}
+	}()
+
+	setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
+		<-c
+	})
+}
+
 func TestRunExit(t *testing.T) {
 	stdin, stdinPipe := io.Pipe()
 	stdout, stdoutPipe := io.Pipe()

+ 1 - 1
integration/container_test.go

@@ -1553,7 +1553,7 @@ func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) {
 	runtime := mkRuntimeFromEngine(eng, t)
 	defer nuke(runtime)
 
-	config, hc, _, err := runconfig.Parse([]string{"-n=false", GetTestImage(runtime).ID, "ip", "addr", "show"}, nil)
+	config, hc, _, err := runconfig.Parse([]string{"-n=false", GetTestImage(runtime).ID, "ip", "addr", "show", "up"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}

+ 3 - 2
integration/server_test.go

@@ -416,7 +416,7 @@ func TestRestartKillWait(t *testing.T) {
 	})
 }
 
-func TestCreateStartRestartKillStartKillRm(t *testing.T) {
+func TestCreateStartRestartStopStartKillRm(t *testing.T) {
 	eng := NewTestEngine(t)
 	srv := mkServerFromEngine(eng, t)
 	defer mkRuntimeFromEngine(eng, t).Nuke()
@@ -456,7 +456,8 @@ func TestCreateStartRestartKillStartKillRm(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	job = eng.Job("kill", id)
+	job = eng.Job("stop", id)
+	job.SetenvInt("t", 15)
 	if err := job.Run(); err != nil {
 		t.Fatal(err)
 	}

+ 15 - 0
pkg/cgroups/apply_nosystemd.go

@@ -0,0 +1,15 @@
+// +build !linux
+
+package cgroups
+
+import (
+	"fmt"
+)
+
+func useSystemd() bool {
+	return false
+}
+
+func systemdApply(c *Cgroup, pid int) (ActiveCgroup, error) {
+	return nil, fmt.Errorf("Systemd not supported")
+}

+ 189 - 0
pkg/cgroups/apply_raw.go

@@ -0,0 +1,189 @@
+package cgroups
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"strconv"
+)
+
+type rawCgroup struct {
+	root   string
+	cgroup string
+}
+
+func rawApply(c *Cgroup, pid int) (ActiveCgroup, error) {
+	// We have two implementation of cgroups support, one is based on
+	// systemd and the dbus api, and one is based on raw cgroup fs operations
+	// following the pre-single-writer model docs at:
+	// http://www.freedesktop.org/wiki/Software/systemd/PaxControlGroups/
+	//
+	// we can pick any subsystem to find the root
+
+	cgroupRoot, err := FindCgroupMountpoint("cpu")
+	if err != nil {
+		return nil, err
+	}
+	cgroupRoot = filepath.Dir(cgroupRoot)
+
+	if _, err := os.Stat(cgroupRoot); err != nil {
+		return nil, fmt.Errorf("cgroups fs not found")
+	}
+
+	cgroup := c.Name
+	if c.Parent != "" {
+		cgroup = filepath.Join(c.Parent, cgroup)
+	}
+
+	raw := &rawCgroup{
+		root:   cgroupRoot,
+		cgroup: cgroup,
+	}
+
+	if err := raw.setupDevices(c, pid); err != nil {
+		return nil, err
+	}
+	if err := raw.setupMemory(c, pid); err != nil {
+		return nil, err
+	}
+	if err := raw.setupCpu(c, pid); err != nil {
+		return nil, err
+	}
+	return raw, nil
+}
+
+func (raw *rawCgroup) path(subsystem string) (string, error) {
+	initPath, err := GetInitCgroupDir(subsystem)
+	if err != nil {
+		return "", err
+	}
+	return filepath.Join(raw.root, subsystem, initPath, raw.cgroup), nil
+}
+
+func (raw *rawCgroup) join(subsystem string, pid int) (string, error) {
+	path, err := raw.path(subsystem)
+	if err != nil {
+		return "", err
+	}
+	if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) {
+		return "", err
+	}
+	if err := writeFile(path, "cgroup.procs", strconv.Itoa(pid)); err != nil {
+		return "", err
+	}
+	return path, nil
+}
+
+func (raw *rawCgroup) setupDevices(c *Cgroup, pid int) (err error) {
+	if !c.DeviceAccess {
+		dir, err := raw.join("devices", pid)
+		if err != nil {
+			return err
+		}
+
+		defer func() {
+			if err != nil {
+				os.RemoveAll(dir)
+			}
+		}()
+
+		if err := writeFile(dir, "devices.deny", "a"); err != nil {
+			return err
+		}
+
+		allow := []string{
+			// /dev/null, zero, full
+			"c 1:3 rwm",
+			"c 1:5 rwm",
+			"c 1:7 rwm",
+
+			// consoles
+			"c 5:1 rwm",
+			"c 5:0 rwm",
+			"c 4:0 rwm",
+			"c 4:1 rwm",
+
+			// /dev/urandom,/dev/random
+			"c 1:9 rwm",
+			"c 1:8 rwm",
+
+			// /dev/pts/ - pts namespaces are "coming soon"
+			"c 136:* rwm",
+			"c 5:2 rwm",
+
+			// tuntap
+			"c 10:200 rwm",
+		}
+
+		for _, val := range allow {
+			if err := writeFile(dir, "devices.allow", val); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func (raw *rawCgroup) setupMemory(c *Cgroup, pid int) (err error) {
+	if c.Memory != 0 || c.MemorySwap != 0 {
+		dir, err := raw.join("memory", pid)
+		if err != nil {
+			return err
+		}
+		defer func() {
+			if err != nil {
+				os.RemoveAll(dir)
+			}
+		}()
+
+		if c.Memory != 0 {
+			if err := writeFile(dir, "memory.limit_in_bytes", strconv.FormatInt(c.Memory, 10)); err != nil {
+				return err
+			}
+			if err := writeFile(dir, "memory.soft_limit_in_bytes", strconv.FormatInt(c.Memory, 10)); err != nil {
+				return err
+			}
+		}
+		// By default, MemorySwap is set to twice the size of RAM.
+		// If you want to omit MemorySwap, set it to `-1'.
+		if c.MemorySwap != -1 {
+			if err := writeFile(dir, "memory.memsw.limit_in_bytes", strconv.FormatInt(c.Memory*2, 10)); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func (raw *rawCgroup) setupCpu(c *Cgroup, pid int) (err error) {
+	// We always want to join the cpu group, to allow fair cpu scheduling
+	// on a container basis
+	dir, err := raw.join("cpu", pid)
+	if err != nil {
+		return err
+	}
+	if c.CpuShares != 0 {
+		if err := writeFile(dir, "cpu.shares", strconv.FormatInt(c.CpuShares, 10)); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (raw *rawCgroup) Cleanup() error {
+	get := func(subsystem string) string {
+		path, _ := raw.path(subsystem)
+		return path
+	}
+
+	for _, path := range []string{
+		get("memory"),
+		get("devices"),
+		get("cpu"),
+	} {
+		if path != "" {
+			os.RemoveAll(path)
+		}
+	}
+	return nil
+}

+ 158 - 0
pkg/cgroups/apply_systemd.go

@@ -0,0 +1,158 @@
+// +build linux
+
+package cgroups
+
+import (
+	"fmt"
+	systemd1 "github.com/coreos/go-systemd/dbus"
+	"github.com/dotcloud/docker/pkg/systemd"
+	"github.com/godbus/dbus"
+	"path/filepath"
+	"strings"
+	"sync"
+)
+
+type systemdCgroup struct {
+}
+
+var (
+	connLock              sync.Mutex
+	theConn               *systemd1.Conn
+	hasStartTransientUnit bool
+)
+
+func useSystemd() bool {
+	if !systemd.SdBooted() {
+		return false
+	}
+
+	connLock.Lock()
+	defer connLock.Unlock()
+
+	if theConn == nil {
+		var err error
+		theConn, err = systemd1.New()
+		if err != nil {
+			return false
+		}
+
+		// Assume we have StartTransientUnit
+		hasStartTransientUnit = true
+
+		// But if we get UnknownMethod error we don't
+		if _, err := theConn.StartTransientUnit("test.scope", "invalid"); err != nil {
+			if dbusError, ok := err.(dbus.Error); ok {
+				if dbusError.Name == "org.freedesktop.DBus.Error.UnknownMethod" {
+					hasStartTransientUnit = false
+				}
+			}
+		}
+	}
+
+	return hasStartTransientUnit
+}
+
+type DeviceAllow struct {
+	Node        string
+	Permissions string
+}
+
+func getIfaceForUnit(unitName string) string {
+	if strings.HasSuffix(unitName, ".scope") {
+		return "Scope"
+	}
+	if strings.HasSuffix(unitName, ".service") {
+		return "Service"
+	}
+	return "Unit"
+}
+
+func systemdApply(c *Cgroup, pid int) (ActiveCgroup, error) {
+	unitName := c.Parent + "-" + c.Name + ".scope"
+	slice := "system.slice"
+
+	var properties []systemd1.Property
+
+	for _, v := range c.UnitProperties {
+		switch v[0] {
+		case "Slice":
+			slice = v[1]
+		default:
+			return nil, fmt.Errorf("Unknown unit propery %s", v[0])
+		}
+	}
+
+	properties = append(properties,
+		systemd1.Property{"Slice", dbus.MakeVariant(slice)},
+		systemd1.Property{"Description", dbus.MakeVariant("docker container " + c.Name)},
+		systemd1.Property{"PIDs", dbus.MakeVariant([]uint32{uint32(pid)})})
+
+	if !c.DeviceAccess {
+		properties = append(properties,
+			systemd1.Property{"DevicePolicy", dbus.MakeVariant("strict")},
+			systemd1.Property{"DeviceAllow", dbus.MakeVariant([]DeviceAllow{
+				{"/dev/null", "rwm"},
+				{"/dev/zero", "rwm"},
+				{"/dev/full", "rwm"},
+				{"/dev/random", "rwm"},
+				{"/dev/urandom", "rwm"},
+				{"/dev/tty", "rwm"},
+				{"/dev/console", "rwm"},
+				{"/dev/tty0", "rwm"},
+				{"/dev/tty1", "rwm"},
+				{"/dev/pts/ptmx", "rwm"},
+				// There is no way to add /dev/pts/* here atm, so we hack this manually below
+				// /dev/pts/* (how to add this?)
+				// Same with tuntap, which doesn't exist as a node most of the time
+			})})
+	}
+
+	if c.Memory != 0 {
+		properties = append(properties,
+			systemd1.Property{"MemoryLimit", dbus.MakeVariant(uint64(c.Memory))})
+	}
+	// TODO: MemorySwap not available in systemd
+
+	if c.CpuShares != 0 {
+		properties = append(properties,
+			systemd1.Property{"CPUShares", dbus.MakeVariant(uint64(c.CpuShares))})
+	}
+
+	if _, err := theConn.StartTransientUnit(unitName, "replace", properties...); err != nil {
+		return nil, err
+	}
+
+	// To work around the lack of /dev/pts/* support above we need to manually add these
+	// so, ask systemd for the cgroup used
+	props, err := theConn.GetUnitTypeProperties(unitName, getIfaceForUnit(unitName))
+	if err != nil {
+		return nil, err
+	}
+
+	cgroup := props["ControlGroup"].(string)
+
+	if !c.DeviceAccess {
+		mountpoint, err := FindCgroupMountpoint("devices")
+		if err != nil {
+			return nil, err
+		}
+
+		path := filepath.Join(mountpoint, cgroup)
+
+		// /dev/pts/*
+		if err := writeFile(path, "devices.allow", "c 136:* rwm"); err != nil {
+			return nil, err
+		}
+		// tuntap
+		if err := writeFile(path, "devices.allow", "c 10:200 rwm"); err != nil {
+			return nil, err
+		}
+	}
+
+	return &systemdCgroup{}, nil
+}
+
+func (c *systemdCgroup) Cleanup() error {
+	// systemd cleans up, we don't need to do anything
+	return nil
+}

+ 11 - 163
pkg/cgroups/cgroups.go

@@ -8,7 +8,6 @@ import (
 	"io/ioutil"
 	"os"
 	"path/filepath"
-	"strconv"
 	"strings"
 )
 
@@ -21,6 +20,12 @@ type Cgroup struct {
 	MemorySwap   int64  `json:"memory_swap,omitempty"`   // Total memory usage (memory + swap); set `-1' to disable swap
 	CpuShares    int64  `json:"cpu_shares,omitempty"`    // CPU shares (relative weight vs. other containers)
 	CpusetCpus   string `json:"cpuset_cpus,omitempty"`   // CPU to use
+
+	UnitProperties [][2]string `json:"unit_properties,omitempty"` // systemd unit properties
+}
+
+type ActiveCgroup interface {
+	Cleanup() error
 }
 
 // https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt
@@ -63,49 +68,6 @@ func GetInitCgroupDir(subsystem string) (string, error) {
 	return parseCgroupFile(subsystem, f)
 }
 
-func (c *Cgroup) Path(root, subsystem string) (string, error) {
-	cgroup := c.Name
-	if c.Parent != "" {
-		cgroup = filepath.Join(c.Parent, cgroup)
-	}
-	initPath, err := GetInitCgroupDir(subsystem)
-	if err != nil {
-		return "", err
-	}
-	return filepath.Join(root, subsystem, initPath, cgroup), nil
-}
-
-func (c *Cgroup) Join(root, subsystem string, pid int) (string, error) {
-	path, err := c.Path(root, subsystem)
-	if err != nil {
-		return "", err
-	}
-	if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) {
-		return "", err
-	}
-	if err := writeFile(path, "tasks", strconv.Itoa(pid)); err != nil {
-		return "", err
-	}
-	return path, nil
-}
-
-func (c *Cgroup) Cleanup(root string) error {
-	get := func(subsystem string) string {
-		path, _ := c.Path(root, subsystem)
-		return path
-	}
-
-	for _, path := range []string{
-		get("memory"),
-		get("devices"),
-		get("cpu"),
-		get("cpuset"),
-	} {
-		os.RemoveAll(path)
-	}
-	return nil
-}
-
 func parseCgroupFile(subsystem string, r io.Reader) (string, error) {
 	s := bufio.NewScanner(r)
 	for s.Scan() {
@@ -127,131 +89,17 @@ func writeFile(dir, file, data string) error {
 	return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700)
 }
 
-func (c *Cgroup) Apply(pid int) error {
+func (c *Cgroup) Apply(pid int) (ActiveCgroup, error) {
 	// We have two implementation of cgroups support, one is based on
 	// systemd and the dbus api, and one is based on raw cgroup fs operations
 	// following the pre-single-writer model docs at:
 	// http://www.freedesktop.org/wiki/Software/systemd/PaxControlGroups/
-	//
-	// we can pick any subsystem to find the root
-	cgroupRoot, err := FindCgroupMountpoint("cpu")
-	if err != nil {
-		return err
-	}
-	cgroupRoot = filepath.Dir(cgroupRoot)
-
-	if _, err := os.Stat(cgroupRoot); err != nil {
-		return fmt.Errorf("cgroups fs not found")
-	}
-	if err := c.setupDevices(cgroupRoot, pid); err != nil {
-		return err
-	}
-	if err := c.setupMemory(cgroupRoot, pid); err != nil {
-		return err
-	}
-	if err := c.setupCpu(cgroupRoot, pid); err != nil {
-		return err
-	}
-	if err := c.setupCpuset(cgroupRoot, pid); err != nil {
-		return err
-	}
-	return nil
-}
-
-func (c *Cgroup) setupDevices(cgroupRoot string, pid int) (err error) {
-	if !c.DeviceAccess {
-		dir, err := c.Join(cgroupRoot, "devices", pid)
-		if err != nil {
-			return err
-		}
-
-		defer func() {
-			if err != nil {
-				os.RemoveAll(dir)
-			}
-		}()
-
-		if err := writeFile(dir, "devices.deny", "a"); err != nil {
-			return err
-		}
 
-		allow := []string{
-			// /dev/null, zero, full
-			"c 1:3 rwm",
-			"c 1:5 rwm",
-			"c 1:7 rwm",
-
-			// consoles
-			"c 5:1 rwm",
-			"c 5:0 rwm",
-			"c 4:0 rwm",
-			"c 4:1 rwm",
-
-			// /dev/urandom,/dev/random
-			"c 1:9 rwm",
-			"c 1:8 rwm",
-
-			// /dev/pts/ - pts namespaces are "coming soon"
-			"c 136:* rwm",
-			"c 5:2 rwm",
-
-			// tuntap
-			"c 10:200 rwm",
-		}
-
-		for _, val := range allow {
-			if err := writeFile(dir, "devices.allow", val); err != nil {
-				return err
-			}
-		}
+	if useSystemd() {
+		return systemdApply(c, pid)
+	} else {
+		return rawApply(c, pid)
 	}
-	return nil
-}
-
-func (c *Cgroup) setupMemory(cgroupRoot string, pid int) (err error) {
-	if c.Memory != 0 || c.MemorySwap != 0 {
-		dir, err := c.Join(cgroupRoot, "memory", pid)
-		if err != nil {
-			return err
-		}
-		defer func() {
-			if err != nil {
-				os.RemoveAll(dir)
-			}
-		}()
-
-		if c.Memory != 0 {
-			if err := writeFile(dir, "memory.limit_in_bytes", strconv.FormatInt(c.Memory, 10)); err != nil {
-				return err
-			}
-			if err := writeFile(dir, "memory.soft_limit_in_bytes", strconv.FormatInt(c.Memory, 10)); err != nil {
-				return err
-			}
-		}
-		// By default, MemorySwap is set to twice the size of RAM.
-		// If you want to omit MemorySwap, set it to `-1'.
-		if c.MemorySwap != -1 {
-			if err := writeFile(dir, "memory.memsw.limit_in_bytes", strconv.FormatInt(c.Memory*2, 10)); err != nil {
-				return err
-			}
-		}
-	}
-	return nil
-}
-
-func (c *Cgroup) setupCpu(cgroupRoot string, pid int) (err error) {
-	// We always want to join the cpu group, to allow fair cpu scheduling
-	// on a container basis
-	dir, err := c.Join(cgroupRoot, "cpu", pid)
-	if err != nil {
-		return err
-	}
-	if c.CpuShares != 0 {
-		if err := writeFile(dir, "cpu.shares", strconv.FormatInt(c.CpuShares, 10)); err != nil {
-			return err
-		}
-	}
-	return nil
 }
 
 func (c *Cgroup) setupCpuset(cgroupRoot string, pid int) (err error) {

+ 0 - 1
pkg/iptables/iptables.go

@@ -66,7 +66,6 @@ func (c *Chain) Forward(action Action, ip net.IP, port int, proto, dest_addr str
 		"-p", proto,
 		"-d", daddr,
 		"--dport", strconv.Itoa(port),
-		"!", "-i", c.Bridge,
 		"-j", "DNAT",
 		"--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port))); err != nil {
 		return err

+ 23 - 0
pkg/label/label.go

@@ -0,0 +1,23 @@
+// +build !selinux !linux
+
+package label
+
+func GenLabels(options string) (string, string, error) {
+	return "", "", nil
+}
+
+func FormatMountLabel(src string, MountLabel string) string {
+	return src
+}
+
+func SetProcessLabel(processLabel string) error {
+	return nil
+}
+
+func SetFileLabel(path string, fileLabel string) error {
+	return nil
+}
+
+func GetPidCon(pid int) (string, error) {
+	return "", nil
+}

+ 69 - 0
pkg/label/label_selinux.go

@@ -0,0 +1,69 @@
+// +build selinux,linux
+
+package label
+
+import (
+	"fmt"
+	"github.com/dotcloud/docker/pkg/selinux"
+	"strings"
+)
+
+func GenLabels(options string) (string, string, error) {
+	processLabel, mountLabel := selinux.GetLxcContexts()
+	var err error
+	if processLabel == "" { // SELinux is disabled
+		return "", "", err
+	}
+	s := strings.Fields(options)
+	l := len(s)
+	if l > 0 {
+		pcon := selinux.NewContext(processLabel)
+		for i := 0; i < l; i++ {
+			o := strings.Split(s[i], "=")
+			pcon[o[0]] = o[1]
+		}
+		processLabel = pcon.Get()
+		mountLabel, err = selinux.CopyLevel(processLabel, mountLabel)
+	}
+	return processLabel, mountLabel, err
+}
+
+func FormatMountLabel(src string, MountLabel string) string {
+	var mountLabel string
+	if src != "" {
+		mountLabel = src
+		if MountLabel != "" {
+			mountLabel = fmt.Sprintf("%s,context=\"%s\"", mountLabel, MountLabel)
+		}
+	} else {
+		if MountLabel != "" {
+			mountLabel = fmt.Sprintf("context=\"%s\"", MountLabel)
+		}
+	}
+	return mountLabel
+}
+
+func SetProcessLabel(processLabel string) error {
+	if selinux.SelinuxEnabled() {
+		return selinux.Setexeccon(processLabel)
+	}
+	return nil
+}
+
+func GetProcessLabel() (string, error) {
+	if selinux.SelinuxEnabled() {
+		return selinux.Getexeccon()
+	}
+	return "", nil
+}
+
+func SetFileLabel(path string, fileLabel string) error {
+	if selinux.SelinuxEnabled() && fileLabel != "" {
+		return selinux.Setfilecon(path, fileLabel)
+	}
+	return nil
+}
+
+func GetPidCon(pid int) (string, error) {
+	return selinux.Getpidcon(pid)
+}

+ 10 - 6
pkg/libcontainer/nsinit/exec.go

@@ -7,6 +7,7 @@ import (
 	"os/exec"
 	"syscall"
 
+	"github.com/dotcloud/docker/pkg/cgroups"
 	"github.com/dotcloud/docker/pkg/libcontainer"
 	"github.com/dotcloud/docker/pkg/libcontainer/network"
 	"github.com/dotcloud/docker/pkg/system"
@@ -62,10 +63,15 @@ func (ns *linuxNs) Exec(container *libcontainer.Container, term Terminal, args [
 	// Do this before syncing with child so that no children
 	// can escape the cgroup
 	ns.logger.Println("setting cgroups")
-	if err := ns.SetupCgroups(container, command.Process.Pid); err != nil {
+	activeCgroup, err := ns.SetupCgroups(container, command.Process.Pid)
+	if err != nil {
 		command.Process.Kill()
 		return -1, err
 	}
+	if activeCgroup != nil {
+		defer activeCgroup.Cleanup()
+	}
+
 	ns.logger.Println("setting up network")
 	if err := ns.InitializeNetworking(container, command.Process.Pid, syncPipe); err != nil {
 		command.Process.Kill()
@@ -86,13 +92,11 @@ func (ns *linuxNs) Exec(container *libcontainer.Container, term Terminal, args [
 	return status, err
 }
 
-func (ns *linuxNs) SetupCgroups(container *libcontainer.Container, nspid int) error {
+func (ns *linuxNs) SetupCgroups(container *libcontainer.Container, nspid int) (cgroups.ActiveCgroup, error) {
 	if container.Cgroups != nil {
-		if err := container.Cgroups.Apply(nspid); err != nil {
-			return err
-		}
+		return container.Cgroups.Apply(nspid)
 	}
-	return nil
+	return nil, nil
 }
 
 func (ns *linuxNs) InitializeNetworking(container *libcontainer.Container, nspid int, pipe *SyncPipe) error {

+ 10 - 1
pkg/libcontainer/nsinit/execin.go

@@ -4,6 +4,7 @@ package nsinit
 
 import (
 	"fmt"
+	"github.com/dotcloud/docker/pkg/label"
 	"github.com/dotcloud/docker/pkg/libcontainer"
 	"github.com/dotcloud/docker/pkg/system"
 	"os"
@@ -32,7 +33,11 @@ func (ns *linuxNs) ExecIn(container *libcontainer.Container, nspid int, args []s
 		closeFds()
 		return -1, err
 	}
-
+	processLabel, err := label.GetPidCon(nspid)
+	if err != nil {
+		closeFds()
+		return -1, err
+	}
 	// foreach namespace fd, use setns to join an existing container's namespaces
 	for _, fd := range fds {
 		if fd > 0 {
@@ -80,6 +85,10 @@ dropAndExec:
 	if err := finalizeNamespace(container); err != nil {
 		return -1, err
 	}
+	err = label.SetProcessLabel(processLabel)
+	if err != nil {
+		return -1, err
+	}
 	if err := system.Execv(args[0], args[0:], container.Env); err != nil {
 		return -1, err
 	}

+ 7 - 1
pkg/libcontainer/nsinit/init.go

@@ -5,8 +5,10 @@ package nsinit
 import (
 	"fmt"
 	"os"
+	"runtime"
 	"syscall"
 
+	"github.com/dotcloud/docker/pkg/label"
 	"github.com/dotcloud/docker/pkg/libcontainer"
 	"github.com/dotcloud/docker/pkg/libcontainer/apparmor"
 	"github.com/dotcloud/docker/pkg/libcontainer/capabilities"
@@ -61,7 +63,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol
 		return fmt.Errorf("setup networking %s", err)
 	}
 	ns.logger.Println("setup mount namespace")
-	if err := setupNewMountNamespace(rootfs, container.Mounts, console, container.ReadonlyFs, container.NoPivotRoot); err != nil {
+	if err := setupNewMountNamespace(rootfs, container.Mounts, console, container.ReadonlyFs, container.NoPivotRoot, container.Context["mount_label"]); err != nil {
 		return fmt.Errorf("setup mount namespace %s", err)
 	}
 	if err := system.Sethostname(container.Hostname); err != nil {
@@ -77,6 +79,10 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol
 			return err
 		}
 	}
+	runtime.LockOSThread()
+	if err := label.SetProcessLabel(container.Context["process_label"]); err != nil {
+		return fmt.Errorf("SetProcessLabel label %s", err)
+	}
 	ns.logger.Printf("execing %s\n", args[0])
 	return system.Execv(args[0], args[0:], container.Env)
 }

+ 13 - 9
pkg/libcontainer/nsinit/mount.go

@@ -4,6 +4,7 @@ package nsinit
 
 import (
 	"fmt"
+	"github.com/dotcloud/docker/pkg/label"
 	"github.com/dotcloud/docker/pkg/libcontainer"
 	"github.com/dotcloud/docker/pkg/system"
 	"io/ioutil"
@@ -20,7 +21,7 @@ const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NOD
 //
 // There is no need to unmount the new mounts because as soon as the mount namespace
 // is no longer in use, the mounts will be removed automatically
-func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, console string, readonly, noPivotRoot bool) error {
+func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, console string, readonly, noPivotRoot bool, mountLabel string) error {
 	flag := syscall.MS_PRIVATE
 	if noPivotRoot {
 		flag = syscall.MS_SLAVE
@@ -31,7 +32,7 @@ func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, cons
 	if err := system.Mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil {
 		return fmt.Errorf("mouting %s as bind %s", rootfs, err)
 	}
-	if err := mountSystem(rootfs); err != nil {
+	if err := mountSystem(rootfs, mountLabel); err != nil {
 		return fmt.Errorf("mount system %s", err)
 	}
 
@@ -59,7 +60,7 @@ func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, cons
 	if err := setupDev(rootfs); err != nil {
 		return err
 	}
-	if err := setupPtmx(rootfs, console); err != nil {
+	if err := setupPtmx(rootfs, console, mountLabel); err != nil {
 		return err
 	}
 	if err := system.Chdir(rootfs); err != nil {
@@ -197,7 +198,7 @@ func setupDev(rootfs string) error {
 }
 
 // setupConsole ensures that the container has a proper /dev/console setup
-func setupConsole(rootfs, console string) error {
+func setupConsole(rootfs, console string, mountLabel string) error {
 	oldMask := system.Umask(0000)
 	defer system.Umask(oldMask)
 
@@ -221,6 +222,9 @@ func setupConsole(rootfs, console string) error {
 	if err := system.Mknod(dest, (st.Mode&^07777)|0600, int(st.Rdev)); err != nil {
 		return fmt.Errorf("mknod %s %s", dest, err)
 	}
+	if err := label.SetFileLabel(console, mountLabel); err != nil {
+		return fmt.Errorf("SetFileLabel Failed %s %s", dest, err)
+	}
 	if err := system.Mount(console, dest, "bind", syscall.MS_BIND, ""); err != nil {
 		return fmt.Errorf("bind %s to %s %s", console, dest, err)
 	}
@@ -229,7 +233,7 @@ func setupConsole(rootfs, console string) error {
 
 // mountSystem sets up linux specific system mounts like sys, proc, shm, and devpts
 // inside the mount namespace
-func mountSystem(rootfs string) error {
+func mountSystem(rootfs string, mountLabel string) error {
 	for _, m := range []struct {
 		source string
 		path   string
@@ -239,8 +243,8 @@ func mountSystem(rootfs string) error {
 	}{
 		{source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags},
 		{source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags},
-		{source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: "mode=1777,size=65536k"},
-		{source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: "newinstance,ptmxmode=0666,mode=620,gid=5"},
+		{source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1755,size=65536k", mountLabel)},
+		{source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)},
 	} {
 		if err := os.MkdirAll(m.path, 0755); err != nil && !os.IsExist(err) {
 			return fmt.Errorf("mkdirall %s %s", m.path, err)
@@ -254,7 +258,7 @@ func mountSystem(rootfs string) error {
 
 // setupPtmx adds a symlink to pts/ptmx for /dev/ptmx and
 // finishes setting up /dev/console
-func setupPtmx(rootfs, console string) error {
+func setupPtmx(rootfs, console string, mountLabel string) error {
 	ptmx := filepath.Join(rootfs, "dev/ptmx")
 	if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) {
 		return err
@@ -263,7 +267,7 @@ func setupPtmx(rootfs, console string) error {
 		return fmt.Errorf("symlink dev ptmx %s", err)
 	}
 	if console != "" {
-		if err := setupConsole(rootfs, console); err != nil {
+		if err := setupConsole(rootfs, console, mountLabel); err != nil {
 			return err
 		}
 	}

+ 387 - 0
pkg/selinux/selinux.go

@@ -0,0 +1,387 @@
+package selinux
+
+import (
+	"bufio"
+	"crypto/rand"
+	"encoding/binary"
+	"fmt"
+	"github.com/dotcloud/docker/pkg/mount"
+	"github.com/dotcloud/docker/pkg/system"
+	"io"
+	"os"
+	"regexp"
+	"strconv"
+	"strings"
+	"syscall"
+)
+
+const (
+	Enforcing        = 1
+	Permissive       = 0
+	Disabled         = -1
+	selinuxDir       = "/etc/selinux/"
+	selinuxConfig    = selinuxDir + "config"
+	selinuxTypeTag   = "SELINUXTYPE"
+	selinuxTag       = "SELINUX"
+	selinuxPath      = "/sys/fs/selinux"
+	xattrNameSelinux = "security.selinux"
+	stRdOnly         = 0x01
+)
+
+var (
+	assignRegex           = regexp.MustCompile(`^([^=]+)=(.*)$`)
+	spaceRegex            = regexp.MustCompile(`^([^=]+) (.*)$`)
+	mcsList               = make(map[string]bool)
+	selinuxfs             = "unknown"
+	selinuxEnabled        = false
+	selinuxEnabledChecked = false
+)
+
+type SELinuxContext map[string]string
+
+func GetSelinuxMountPoint() string {
+	if selinuxfs != "unknown" {
+		return selinuxfs
+	}
+	selinuxfs = ""
+
+	mounts, err := mount.GetMounts()
+	if err != nil {
+		return selinuxfs
+	}
+	for _, mount := range mounts {
+		if mount.Fstype == "selinuxfs" {
+			selinuxfs = mount.Mountpoint
+			break
+		}
+	}
+	if selinuxfs != "" {
+		var buf syscall.Statfs_t
+		syscall.Statfs(selinuxfs, &buf)
+		if (buf.Flags & stRdOnly) == 1 {
+			selinuxfs = ""
+		}
+	}
+	return selinuxfs
+}
+
+func SelinuxEnabled() bool {
+	if selinuxEnabledChecked {
+		return selinuxEnabled
+	}
+	selinuxEnabledChecked = true
+	if fs := GetSelinuxMountPoint(); fs != "" {
+		if con, _ := Getcon(); con != "kernel" {
+			selinuxEnabled = true
+		}
+	}
+	return selinuxEnabled
+}
+
+func ReadConfig(target string) (value string) {
+	var (
+		val, key string
+		bufin    *bufio.Reader
+	)
+
+	in, err := os.Open(selinuxConfig)
+	if err != nil {
+		return ""
+	}
+	defer in.Close()
+
+	bufin = bufio.NewReader(in)
+
+	for done := false; !done; {
+		var line string
+		if line, err = bufin.ReadString('\n'); err != nil {
+			if err != io.EOF {
+				return ""
+			}
+			done = true
+		}
+		line = strings.TrimSpace(line)
+		if len(line) == 0 {
+			// Skip blank lines
+			continue
+		}
+		if line[0] == ';' || line[0] == '#' {
+			// Skip comments
+			continue
+		}
+		if groups := assignRegex.FindStringSubmatch(line); groups != nil {
+			key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2])
+			if key == target {
+				return strings.Trim(val, "\"")
+			}
+		}
+	}
+	return ""
+}
+
+func GetSELinuxPolicyRoot() string {
+	return selinuxDir + ReadConfig(selinuxTypeTag)
+}
+
+func readCon(name string) (string, error) {
+	var val string
+
+	in, err := os.Open(name)
+	if err != nil {
+		return "", err
+	}
+	defer in.Close()
+
+	_, err = fmt.Fscanf(in, "%s", &val)
+	return val, err
+}
+
+func Setfilecon(path string, scon string) error {
+	return system.Lsetxattr(path, xattrNameSelinux, []byte(scon), 0)
+}
+
+func Getfilecon(path string) (string, error) {
+	var scon []byte
+
+	cnt, err := syscall.Getxattr(path, xattrNameSelinux, scon)
+	scon = make([]byte, cnt)
+	cnt, err = syscall.Getxattr(path, xattrNameSelinux, scon)
+	return string(scon), err
+}
+
+func Setfscreatecon(scon string) error {
+	return writeCon("/proc/self/attr/fscreate", scon)
+}
+
+func Getfscreatecon() (string, error) {
+	return readCon("/proc/self/attr/fscreate")
+}
+
+func Getcon() (string, error) {
+	return readCon("/proc/self/attr/current")
+}
+
+func Getpidcon(pid int) (string, error) {
+	return readCon(fmt.Sprintf("/proc/%d/attr/current", pid))
+}
+
+func Getexeccon() (string, error) {
+	return readCon("/proc/self/attr/exec")
+}
+
+func writeCon(name string, val string) error {
+	if !SelinuxEnabled() {
+		return nil
+	}
+	out, err := os.OpenFile(name, os.O_WRONLY, 0)
+	if err != nil {
+		return err
+	}
+	defer out.Close()
+
+	if val != "" {
+		_, err = out.Write([]byte(val))
+	} else {
+		_, err = out.Write(nil)
+	}
+	return err
+}
+
+func Setexeccon(scon string) error {
+	return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid()), scon)
+}
+
+func (c SELinuxContext) Get() string {
+	return fmt.Sprintf("%s:%s:%s:%s", c["user"], c["role"], c["type"], c["level"])
+}
+
+func NewContext(scon string) SELinuxContext {
+	c := make(SELinuxContext)
+
+	if len(scon) != 0 {
+		con := strings.SplitN(scon, ":", 4)
+		c["user"] = con[0]
+		c["role"] = con[1]
+		c["type"] = con[2]
+		c["level"] = con[3]
+	}
+	return c
+}
+
+func SelinuxGetEnforce() int {
+	var enforce int
+
+	enforceS, err := readCon(fmt.Sprintf("%s/enforce", selinuxPath))
+	if err != nil {
+		return -1
+	}
+
+	enforce, err = strconv.Atoi(string(enforceS))
+	if err != nil {
+		return -1
+	}
+	return enforce
+}
+
+func SelinuxGetEnforceMode() int {
+	switch ReadConfig(selinuxTag) {
+	case "enforcing":
+		return Enforcing
+	case "permissive":
+		return Permissive
+	}
+	return Disabled
+}
+
+func mcsAdd(mcs string) {
+	mcsList[mcs] = true
+}
+
+func mcsDelete(mcs string) {
+	mcsList[mcs] = false
+}
+
+func mcsExists(mcs string) bool {
+	return mcsList[mcs]
+}
+
+func IntToMcs(id int, catRange uint32) string {
+	var (
+		SETSIZE = int(catRange)
+		TIER    = SETSIZE
+		ORD     = id
+	)
+
+	if id < 1 || id > 523776 {
+		return ""
+	}
+
+	for ORD > TIER {
+		ORD = ORD - TIER
+		TIER -= 1
+	}
+	TIER = SETSIZE - TIER
+	ORD = ORD + TIER
+	return fmt.Sprintf("s0:c%d,c%d", TIER, ORD)
+}
+
+func uniqMcs(catRange uint32) string {
+	var (
+		n      uint32
+		c1, c2 uint32
+		mcs    string
+	)
+
+	for {
+		binary.Read(rand.Reader, binary.LittleEndian, &n)
+		c1 = n % catRange
+		binary.Read(rand.Reader, binary.LittleEndian, &n)
+		c2 = n % catRange
+		if c1 == c2 {
+			continue
+		} else {
+			if c1 > c2 {
+				t := c1
+				c1 = c2
+				c2 = t
+			}
+		}
+		mcs = fmt.Sprintf("s0:c%d,c%d", c1, c2)
+		if mcsExists(mcs) {
+			continue
+		}
+		mcsAdd(mcs)
+		break
+	}
+	return mcs
+}
+
+func FreeContext(con string) {
+	if con != "" {
+		scon := NewContext(con)
+		mcsDelete(scon["level"])
+	}
+}
+
+func GetLxcContexts() (processLabel string, fileLabel string) {
+	var (
+		val, key string
+		bufin    *bufio.Reader
+	)
+
+	if !SelinuxEnabled() {
+		return "", ""
+	}
+	lxcPath := fmt.Sprintf("%s/content/lxc_contexts", GetSELinuxPolicyRoot())
+	fileLabel = "system_u:object_r:svirt_sandbox_file_t:s0"
+	processLabel = "system_u:system_r:svirt_lxc_net_t:s0"
+
+	in, err := os.Open(lxcPath)
+	if err != nil {
+		goto exit
+	}
+	defer in.Close()
+
+	bufin = bufio.NewReader(in)
+
+	for done := false; !done; {
+		var line string
+		if line, err = bufin.ReadString('\n'); err != nil {
+			if err == io.EOF {
+				done = true
+			} else {
+				goto exit
+			}
+		}
+		line = strings.TrimSpace(line)
+		if len(line) == 0 {
+			// Skip blank lines
+			continue
+		}
+		if line[0] == ';' || line[0] == '#' {
+			// Skip comments
+			continue
+		}
+		if groups := assignRegex.FindStringSubmatch(line); groups != nil {
+			key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2])
+			if key == "process" {
+				processLabel = strings.Trim(val, "\"")
+			}
+			if key == "file" {
+				fileLabel = strings.Trim(val, "\"")
+			}
+		}
+	}
+exit:
+	mcs := IntToMcs(os.Getpid(), 1024)
+	scon := NewContext(processLabel)
+	scon["level"] = mcs
+	processLabel = scon.Get()
+	scon = NewContext(fileLabel)
+	scon["level"] = mcs
+	fileLabel = scon.Get()
+	return processLabel, fileLabel
+}
+
+func SecurityCheckContext(val string) error {
+	return writeCon(fmt.Sprintf("%s.context", selinuxPath), val)
+}
+
+func CopyLevel(src, dest string) (string, error) {
+	if !SelinuxEnabled() {
+		return "", nil
+	}
+	if src == "" {
+		return "", nil
+	}
+	if err := SecurityCheckContext(src); err != nil {
+		return "", err
+	}
+	if err := SecurityCheckContext(dest); err != nil {
+		return "", err
+	}
+	scon := NewContext(src)
+	tcon := NewContext(dest)
+	tcon["level"] = scon["level"]
+	return tcon.Get(), nil
+}

+ 64 - 0
pkg/selinux/selinux_test.go

@@ -0,0 +1,64 @@
+package selinux_test
+
+import (
+	"github.com/dotcloud/docker/pkg/selinux"
+	"os"
+	"testing"
+)
+
+func testSetfilecon(t *testing.T) {
+	if selinux.SelinuxEnabled() {
+		tmp := "selinux_test"
+		out, _ := os.OpenFile(tmp, os.O_WRONLY, 0)
+		out.Close()
+		err := selinux.Setfilecon(tmp, "system_u:object_r:bin_t:s0")
+		if err == nil {
+			t.Log(selinux.Getfilecon(tmp))
+		} else {
+			t.Log("Setfilecon failed")
+			t.Fatal(err)
+		}
+		os.Remove(tmp)
+	}
+}
+
+func TestSELinux(t *testing.T) {
+	var (
+		err            error
+		plabel, flabel string
+	)
+
+	if selinux.SelinuxEnabled() {
+		t.Log("Enabled")
+		plabel, flabel = selinux.GetLxcContexts()
+		t.Log(plabel)
+		t.Log(flabel)
+		plabel, flabel = selinux.GetLxcContexts()
+		t.Log(plabel)
+		t.Log(flabel)
+		t.Log("getenforce ", selinux.SelinuxGetEnforce())
+		t.Log("getenforcemode ", selinux.SelinuxGetEnforceMode())
+		pid := os.Getpid()
+		t.Log("PID:%d MCS:%s\n", pid, selinux.IntToMcs(pid, 1023))
+		t.Log(selinux.Getcon())
+		t.Log(selinux.Getfilecon("/etc/passwd"))
+		err = selinux.Setfscreatecon("unconfined_u:unconfined_r:unconfined_t:s0")
+		if err == nil {
+			t.Log(selinux.Getfscreatecon())
+		} else {
+			t.Log("setfscreatecon failed", err)
+			t.Fatal(err)
+		}
+		err = selinux.Setfscreatecon("")
+		if err == nil {
+			t.Log(selinux.Getfscreatecon())
+		} else {
+			t.Log("setfscreatecon failed", err)
+			t.Fatal(err)
+		}
+		t.Log(selinux.Getpidcon(1))
+		t.Log(selinux.GetSelinuxMountPoint())
+	} else {
+		t.Log("Disabled")
+	}
+}

+ 15 - 0
pkg/systemd/booted.go

@@ -0,0 +1,15 @@
+package systemd
+
+import (
+	"os"
+)
+
+// Conversion to Go of systemd's sd_booted()
+func SdBooted() bool {
+	s, err := os.Stat("/run/systemd/system")
+	if err != nil {
+		return false
+	}
+
+	return s.IsDir()
+}

+ 1 - 1
pkg/systemd/listendfd.go

@@ -5,7 +5,7 @@ import (
 	"net"
 	"strconv"
 
-	"github.com/dotcloud/docker/pkg/systemd/activation"
+	"github.com/coreos/go-systemd/activation"
 )
 
 // ListenFD returns the specified socket activated files as a slice of

+ 4 - 0
registry/registry_test.go

@@ -206,4 +206,8 @@ func TestValidRepositoryName(t *testing.T) {
 		t.Log("Repository name should be invalid")
 		t.Fail()
 	}
+	if err := validateRepositoryName("docker///docker"); err == nil {
+		t.Log("Repository name should be invalid")
+		t.Fail()
+	}
 }

+ 11 - 0
runconfig/config.go

@@ -1,8 +1,10 @@
 package runconfig
 
 import (
+	"encoding/json"
 	"github.com/dotcloud/docker/engine"
 	"github.com/dotcloud/docker/nat"
+	"github.com/dotcloud/docker/runtime/execdriver"
 )
 
 // Note: the Config structure should hold only portable information about the container.
@@ -34,9 +36,17 @@ type Config struct {
 	Entrypoint      []string
 	NetworkDisabled bool
 	OnBuild         []string
+	Context         execdriver.Context
 }
 
 func ContainerConfigFromJob(job *engine.Job) *Config {
+	var context execdriver.Context
+	val := job.Getenv("Context")
+	if val != "" {
+		if err := json.Unmarshal([]byte(val), &context); err != nil {
+			panic(err)
+		}
+	}
 	config := &Config{
 		Hostname:        job.Getenv("Hostname"),
 		Domainname:      job.Getenv("Domainname"),
@@ -54,6 +64,7 @@ func ContainerConfigFromJob(job *engine.Job) *Config {
 		VolumesFrom:     job.Getenv("VolumesFrom"),
 		WorkingDir:      job.Getenv("WorkingDir"),
 		NetworkDisabled: job.GetenvBool("NetworkDisabled"),
+		Context:         context,
 	}
 	job.GetenvJson("ExposedPorts", &config.ExposedPorts)
 	job.GetenvJson("Volumes", &config.Volumes)

+ 1 - 1
runconfig/config_test.go

@@ -247,7 +247,7 @@ func TestMerge(t *testing.T) {
 	volumesUser := make(map[string]struct{})
 	volumesUser["/test3"] = struct{}{}
 	configUser := &Config{
-		Dns:       []string{"3.3.3.3"},
+		Dns:       []string{"2.2.2.2", "3.3.3.3"},
 		PortSpecs: []string{"3333:2222", "3333:3333"},
 		Env:       []string{"VAR2=3", "VAR3=3"},
 		Volumes:   volumesUser,

+ 2 - 1
runconfig/hostconfig.go

@@ -3,12 +3,13 @@ package runconfig
 import (
 	"github.com/dotcloud/docker/engine"
 	"github.com/dotcloud/docker/nat"
+	"github.com/dotcloud/docker/utils"
 )
 
 type HostConfig struct {
 	Binds           []string
 	ContainerIDFile string
-	LxcConf         []KeyValuePair
+	LxcConf         []utils.KeyValuePair
 	Privileged      bool
 	PortBindings    nat.PortMap
 	Links           []string

+ 9 - 2
runconfig/merge.go

@@ -97,8 +97,15 @@ func Merge(userConf, imageConf *Config) error {
 	if userConf.Dns == nil || len(userConf.Dns) == 0 {
 		userConf.Dns = imageConf.Dns
 	} else {
-		//duplicates aren't an issue here
-		userConf.Dns = append(userConf.Dns, imageConf.Dns...)
+		dnsSet := make(map[string]struct{}, len(userConf.Dns))
+		for _, dns := range userConf.Dns {
+			dnsSet[dns] = struct{}{}
+		}
+		for _, dns := range imageConf.Dns {
+			if _, exists := dnsSet[dns]; !exists {
+				userConf.Dns = append(userConf.Dns, dns)
+			}
+		}
 	}
 	if userConf.DnsSearch == nil || len(userConf.DnsSearch) == 0 {
 		userConf.DnsSearch = imageConf.DnsSearch

+ 49 - 15
runconfig/parse.go

@@ -4,8 +4,10 @@ import (
 	"fmt"
 	"github.com/dotcloud/docker/nat"
 	"github.com/dotcloud/docker/opts"
+	"github.com/dotcloud/docker/pkg/label"
 	flag "github.com/dotcloud/docker/pkg/mflag"
 	"github.com/dotcloud/docker/pkg/sysinfo"
+	"github.com/dotcloud/docker/runtime/execdriver"
 	"github.com/dotcloud/docker/utils"
 	"io/ioutil"
 	"path"
@@ -32,6 +34,10 @@ func ParseSubcommand(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo)
 }
 
 func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Config, *HostConfig, *flag.FlagSet, error) {
+	var (
+		processLabel string
+		mountLabel   string
+	)
 	var (
 		// FIXME: use utils.ListOpts for attach and volumes?
 		flAttach  = opts.NewListOpts(opts.ValidateAttach)
@@ -61,6 +67,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
 		flUser            = cmd.String([]string{"u", "-user"}, "", "Username or UID")
 		flWorkingDir      = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container")
 		flCpuShares       = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
+		flLabelOptions    = cmd.String([]string{"Z", "-label"}, "", "Options to pass to underlying labeling system")
 
 		// For documentation purpose
 		_ = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxify all received signal to the process (even in non-tty mode)")
@@ -77,7 +84,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
 	cmd.Var(&flDns, []string{"#dns", "-dns"}, "Set custom dns servers")
 	cmd.Var(&flDnsSearch, []string{"-dns-search"}, "Set custom dns search domains")
 	cmd.Var(&flVolumesFrom, []string{"#volumes-from", "-volumes-from"}, "Mount volumes from the specified container(s)")
-	cmd.Var(&flLxcOpts, []string{"#lxc-conf", "#-lxc-conf"}, "Add custom lxc options --lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
+	cmd.Var(&flLxcOpts, []string{"#lxc-conf", "#-lxc-conf"}, "(lxc exec-driver only) Add custom lxc options --lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
 	cmd.Var(&flDriverOpts, []string{"o", "-opt"}, "Add custom driver options")
 
 	if err := cmd.Parse(args); err != nil {
@@ -152,7 +159,16 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
 		entrypoint = []string{*flEntrypoint}
 	}
 
-	lxcConf, err := parseLxcConfOpts(flLxcOpts)
+	if !*flPrivileged {
+		pLabel, mLabel, e := label.GenLabels(*flLabelOptions)
+		if e != nil {
+			return nil, nil, cmd, fmt.Errorf("Invalid security labels : %s", e)
+		}
+		processLabel = pLabel
+		mountLabel = mLabel
+	}
+
+	lxcConf, err := parseKeyValueOpts(flLxcOpts)
 	if err != nil {
 		return nil, nil, cmd, err
 	}
@@ -206,6 +222,15 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
 		VolumesFrom:     strings.Join(flVolumesFrom.GetAll(), ","),
 		Entrypoint:      entrypoint,
 		WorkingDir:      *flWorkingDir,
+		Context: execdriver.Context{
+			"mount_label":   mountLabel,
+			"process_label": processLabel,
+		},
+	}
+
+	driverOptions, err := parseDriverOpts(flDriverOpts)
+	if err != nil {
+		return nil, nil, cmd, err
 	}
 
 	pluginOptions, err := parseDriverOpts(flDriverOpts)
@@ -221,7 +246,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
 		PortBindings:    portBindings,
 		Links:           flLinks.GetAll(),
 		PublishAllPorts: *flPublishAll,
-		DriverOptions:   pluginOptions,
+		DriverOptions:   driverOptions,
 	}
 
 	if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit {
@@ -236,24 +261,33 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
 	return config, hostConfig, cmd, nil
 }
 
-func parseLxcConfOpts(opts opts.ListOpts) ([]KeyValuePair, error) {
-	out := make([]KeyValuePair, opts.Len())
-	for i, o := range opts.GetAll() {
-		k, v, err := parseLxcOpt(o)
-		if err != nil {
-			return nil, err
+// options will come in the format of name.key=value or name.option
+func parseDriverOpts(opts opts.ListOpts) (map[string][]string, error) {
+	out := make(map[string][]string, len(opts.GetAll()))
+	for _, o := range opts.GetAll() {
+		parts := strings.SplitN(o, ".", 2)
+		if len(parts) < 2 {
+			return nil, fmt.Errorf("invalid opt format %s", o)
+		}
+		values, exists := out[parts[0]]
+		if !exists {
+			values = []string{}
 		}
-		out[i] = KeyValuePair{Key: k, Value: v}
+		out[parts[0]] = append(values, parts[1])
 	}
 	return out, nil
 }
 
-func parseLxcOpt(opt string) (string, string, error) {
-	parts := strings.SplitN(opt, "=", 2)
-	if len(parts) != 2 {
-		return "", "", fmt.Errorf("Unable to parse lxc conf option: %s", opt)
+func parseKeyValueOpts(opts opts.ListOpts) ([]utils.KeyValuePair, error) {
+	out := make([]utils.KeyValuePair, opts.Len())
+	for i, o := range opts.GetAll() {
+		k, v, err := utils.ParseKeyValueOpt(o)
+		if err != nil {
+			return nil, err
+		}
+		out[i] = utils.KeyValuePair{Key: k, Value: v}
 	}
-	return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil
+	return out, nil
 }
 
 // options will come in the format of name.type=value

+ 2 - 1
runconfig/parse_test.go

@@ -1,6 +1,7 @@
 package runconfig
 
 import (
+	"github.com/dotcloud/docker/utils"
 	"testing"
 )
 
@@ -8,7 +9,7 @@ func TestParseLxcConfOpt(t *testing.T) {
 	opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "}
 
 	for _, o := range opts {
-		k, v, err := parseLxcOpt(o)
+		k, v, err := utils.ParseKeyValueOpt(o)
 		if err != nil {
 			t.FailNow()
 		}

+ 38 - 16
runtime/container.go

@@ -404,6 +404,7 @@ func populateCommand(c *Container) {
 		User:       c.Config.User,
 		Config:     driverConfig,
 		Resources:  resources,
+		Context:    c.Config.Context,
 	}
 	c.command.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
 }
@@ -537,8 +538,18 @@ func (container *Container) Start() (err error) {
 
 	if container.Config.WorkingDir != "" {
 		container.Config.WorkingDir = path.Clean(container.Config.WorkingDir)
-		if err := os.MkdirAll(path.Join(container.basefs, container.Config.WorkingDir), 0755); err != nil {
-			return nil
+
+		pthInfo, err := os.Stat(path.Join(container.basefs, container.Config.WorkingDir))
+		if err != nil {
+			if !os.IsNotExist(err) {
+				return err
+			}
+			if err := os.MkdirAll(path.Join(container.basefs, container.Config.WorkingDir), 0755); err != nil {
+				return err
+			}
+		}
+		if pthInfo != nil && !pthInfo.IsDir() {
+			return fmt.Errorf("Cannot mkdir: %s is not a directory", container.Config.WorkingDir)
 		}
 	}
 
@@ -905,12 +916,20 @@ func (container *Container) Stop(seconds int) error {
 
 	// 1. Send a SIGTERM
 	if err := container.KillSig(15); err != nil {
-		return err
+		utils.Debugf("Error sending kill SIGTERM: %s", err)
+		log.Print("Failed to send SIGTERM to the process, force killing")
+		if err := container.KillSig(9); err != nil {
+			return err
+		}
 	}
 
 	// 2. Wait for the process to exit on its own
 	if err := container.WaitTimeout(time.Duration(seconds) * time.Second); err != nil {
-		return err
+		log.Printf("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.ID, seconds)
+		// 3. If it doesn't, then send SIGKILL
+		if err := container.Kill(); err != nil {
+			return err
+		}
 	}
 	return nil
 }
@@ -952,10 +971,11 @@ func (container *Container) ExportRw() (archive.Archive, error) {
 		return nil, err
 	}
 	return utils.NewReadCloserWrapper(archive, func() error {
-		err := archive.Close()
-		container.Unmount()
-		return err
-	}), nil
+			err := archive.Close()
+			container.Unmount()
+			return err
+		}),
+		nil
 }
 
 func (container *Container) Export() (archive.Archive, error) {
@@ -969,10 +989,11 @@ func (container *Container) Export() (archive.Archive, error) {
 		return nil, err
 	}
 	return utils.NewReadCloserWrapper(archive, func() error {
-		err := archive.Close()
-		container.Unmount()
-		return err
-	}), nil
+			err := archive.Close()
+			container.Unmount()
+			return err
+		}),
+		nil
 }
 
 func (container *Container) WaitTimeout(timeout time.Duration) error {
@@ -1121,10 +1142,11 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) {
 		return nil, err
 	}
 	return utils.NewReadCloserWrapper(archive, func() error {
-		err := archive.Close()
-		container.Unmount()
-		return err
-	}), nil
+			err := archive.Close()
+			container.Unmount()
+			return err
+		}),
+		nil
 }
 
 // Returns true if the container exposes a certain port

+ 4 - 0
runtime/execdriver/driver.go

@@ -7,6 +7,10 @@ import (
 	"os/exec"
 )
 
+// Context is a generic key value pair that allows
+// arbatrary data to be sent
+type Context map[string]string
+
 var (
 	ErrNotRunning              = errors.New("Process could not be started")
 	ErrWaitTimeoutReached      = errors.New("Wait timeout reached")

+ 18 - 2
runtime/execdriver/lxc/lxc_template.go

@@ -1,6 +1,7 @@
 package lxc
 
 import (
+	"github.com/dotcloud/docker/pkg/label"
 	"github.com/dotcloud/docker/runtime/execdriver"
 	"strings"
 	"text/template"
@@ -29,6 +30,10 @@ lxc.pts = 1024
 
 # disable the main console
 lxc.console = none
+{{if getProcessLabel .Context}}
+lxc.se_context = {{ getProcessLabel .Context}}
+{{$MOUNTLABEL := getMountLabel .Context}}
+{{end}}
 
 # no controlling tty at all
 lxc.tty = 1
@@ -85,8 +90,8 @@ lxc.mount.entry = sysfs {{escapeFstabSpaces $ROOTFS}}/sys sysfs nosuid,nodev,noe
 lxc.mount.entry = {{.Console}} {{escapeFstabSpaces $ROOTFS}}/dev/console none bind,rw 0 0
 {{end}}
 
-lxc.mount.entry = devpts {{escapeFstabSpaces $ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,nosuid,noexec 0 0
-lxc.mount.entry = shm {{escapeFstabSpaces $ROOTFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0
+lxc.mount.entry = devpts {{escapeFstabSpaces $ROOTFS}}/dev/pts devpts {{formatMountLabel "newinstance,ptmxmode=0666,nosuid,noexec" "$MOUNTLABEL"}} 0 0
+lxc.mount.entry = shm {{escapeFstabSpaces $ROOTFS}}/dev/shm tmpfs {{formatMountLabel "size=65536k,nosuid,nodev,noexec" "$MOUNTLABEL"}} 0 0
 
 {{range $value := .Mounts}}
 {{if $value.Writable}}
@@ -142,11 +147,22 @@ func getMemorySwap(v *execdriver.Resources) int64 {
 	return v.Memory * 2
 }
 
+func getProcessLabel(c execdriver.Context) string {
+	return c["process_label"]
+}
+
+func getMountLabel(c execdriver.Context) string {
+	return c["mount_label"]
+}
+
 func init() {
 	var err error
 	funcMap := template.FuncMap{
 		"getMemorySwap":     getMemorySwap,
+		"getProcessLabel":   getProcessLabel,
+		"getMountLabel":     getMountLabel,
 		"escapeFstabSpaces": escapeFstabSpaces,
+		"formatMountLabel":  label.FormatMountLabel,
 	}
 	LxcTemplateCompiled, err = template.New("lxc").Funcs(funcMap).Parse(LxcTemplate)
 	if err != nil {

+ 5 - 4
runtime/execdriver/lxc/lxc_template_unit_test.go

@@ -75,10 +75,11 @@ func TestCustomLxcConfig(t *testing.T) {
 	command := &execdriver.Command{
 		ID:         "1",
 		Privileged: false,
-		Config: map[string][]string{"lxc": {
-			"lxc.utsname = docker",
-			"lxc.cgroup.cpuset.cpus = 0,1",
-		},
+		Config: map[string][]string{
+			"lxc": {
+				"lxc.utsname = docker",
+				"lxc.cgroup.cpuset.cpus = 0,1",
+			},
 		},
 		Network: &execdriver.Network{
 			Mtu:       1500,

+ 1 - 1
runtime/graphdriver/aufs/aufs.go

@@ -134,7 +134,7 @@ func (a Driver) Exists(id string) bool {
 
 // Three folders are created for each id
 // mnt, layers, and diff
-func (a *Driver) Create(id, parent string) error {
+func (a *Driver) Create(id, parent string, mountLabel string) error {
 	if err := a.createDirsFor(id); err != nil {
 		return err
 	}

+ 26 - 26
runtime/graphdriver/aufs/aufs_test.go

@@ -90,7 +90,7 @@ func TestCreateNewDir(t *testing.T) {
 	d := newDriver(t)
 	defer os.RemoveAll(tmp)
 
-	if err := d.Create("1", ""); err != nil {
+	if err := d.Create("1", "", ""); err != nil {
 		t.Fatal(err)
 	}
 }
@@ -99,7 +99,7 @@ func TestCreateNewDirStructure(t *testing.T) {
 	d := newDriver(t)
 	defer os.RemoveAll(tmp)
 
-	if err := d.Create("1", ""); err != nil {
+	if err := d.Create("1", "", ""); err != nil {
 		t.Fatal(err)
 	}
 
@@ -120,7 +120,7 @@ func TestRemoveImage(t *testing.T) {
 	d := newDriver(t)
 	defer os.RemoveAll(tmp)
 
-	if err := d.Create("1", ""); err != nil {
+	if err := d.Create("1", "", ""); err != nil {
 		t.Fatal(err)
 	}
 
@@ -145,7 +145,7 @@ func TestGetWithoutParent(t *testing.T) {
 	d := newDriver(t)
 	defer os.RemoveAll(tmp)
 
-	if err := d.Create("1", ""); err != nil {
+	if err := d.Create("1", "", ""); err != nil {
 		t.Fatal(err)
 	}
 
@@ -172,7 +172,7 @@ func TestCleanupWithDir(t *testing.T) {
 	d := newDriver(t)
 	defer os.RemoveAll(tmp)
 
-	if err := d.Create("1", ""); err != nil {
+	if err := d.Create("1", "", ""); err != nil {
 		t.Fatal(err)
 	}
 
@@ -185,7 +185,7 @@ func TestMountedFalseResponse(t *testing.T) {
 	d := newDriver(t)
 	defer os.RemoveAll(tmp)
 
-	if err := d.Create("1", ""); err != nil {
+	if err := d.Create("1", "", ""); err != nil {
 		t.Fatal(err)
 	}
 
@@ -204,10 +204,10 @@ func TestMountedTrueReponse(t *testing.T) {
 	defer os.RemoveAll(tmp)
 	defer d.Cleanup()
 
-	if err := d.Create("1", ""); err != nil {
+	if err := d.Create("1", "", ""); err != nil {
 		t.Fatal(err)
 	}
-	if err := d.Create("2", "1"); err != nil {
+	if err := d.Create("2", "1", ""); err != nil {
 		t.Fatal(err)
 	}
 
@@ -230,10 +230,10 @@ func TestMountWithParent(t *testing.T) {
 	d := newDriver(t)
 	defer os.RemoveAll(tmp)
 
-	if err := d.Create("1", ""); err != nil {
+	if err := d.Create("1", "", ""); err != nil {
 		t.Fatal(err)
 	}
-	if err := d.Create("2", "1"); err != nil {
+	if err := d.Create("2", "1", ""); err != nil {
 		t.Fatal(err)
 	}
 
@@ -261,10 +261,10 @@ func TestRemoveMountedDir(t *testing.T) {
 	d := newDriver(t)
 	defer os.RemoveAll(tmp)
 
-	if err := d.Create("1", ""); err != nil {
+	if err := d.Create("1", "", ""); err != nil {
 		t.Fatal(err)
 	}
-	if err := d.Create("2", "1"); err != nil {
+	if err := d.Create("2", "1", ""); err != nil {
 		t.Fatal(err)
 	}
 
@@ -300,7 +300,7 @@ func TestCreateWithInvalidParent(t *testing.T) {
 	d := newDriver(t)
 	defer os.RemoveAll(tmp)
 
-	if err := d.Create("1", "docker"); err == nil {
+	if err := d.Create("1", "docker", ""); err == nil {
 		t.Fatalf("Error should not be nil with parent does not exist")
 	}
 }
@@ -309,7 +309,7 @@ func TestGetDiff(t *testing.T) {
 	d := newDriver(t)
 	defer os.RemoveAll(tmp)
 
-	if err := d.Create("1", ""); err != nil {
+	if err := d.Create("1", "", ""); err != nil {
 		t.Fatal(err)
 	}
 
@@ -343,10 +343,10 @@ func TestChanges(t *testing.T) {
 	d := newDriver(t)
 	defer os.RemoveAll(tmp)
 
-	if err := d.Create("1", ""); err != nil {
+	if err := d.Create("1", "", ""); err != nil {
 		t.Fatal(err)
 	}
-	if err := d.Create("2", "1"); err != nil {
+	if err := d.Create("2", "1", ""); err != nil {
 		t.Fatal(err)
 	}
 
@@ -392,7 +392,7 @@ func TestChanges(t *testing.T) {
 		t.Fatalf("Change kind should be ChangeAdd got %s", change.Kind)
 	}
 
-	if err := d.Create("3", "2"); err != nil {
+	if err := d.Create("3", "2", ""); err != nil {
 		t.Fatal(err)
 	}
 	mntPoint, err = d.Get("3")
@@ -437,7 +437,7 @@ func TestDiffSize(t *testing.T) {
 	d := newDriver(t)
 	defer os.RemoveAll(tmp)
 
-	if err := d.Create("1", ""); err != nil {
+	if err := d.Create("1", "", ""); err != nil {
 		t.Fatal(err)
 	}
 
@@ -479,7 +479,7 @@ func TestChildDiffSize(t *testing.T) {
 	defer os.RemoveAll(tmp)
 	defer d.Cleanup()
 
-	if err := d.Create("1", ""); err != nil {
+	if err := d.Create("1", "", ""); err != nil {
 		t.Fatal(err)
 	}
 
@@ -515,7 +515,7 @@ func TestChildDiffSize(t *testing.T) {
 		t.Fatalf("Expected size to be %d got %d", size, diffSize)
 	}
 
-	if err := d.Create("2", "1"); err != nil {
+	if err := d.Create("2", "1", ""); err != nil {
 		t.Fatal(err)
 	}
 
@@ -534,7 +534,7 @@ func TestExists(t *testing.T) {
 	defer os.RemoveAll(tmp)
 	defer d.Cleanup()
 
-	if err := d.Create("1", ""); err != nil {
+	if err := d.Create("1", "", ""); err != nil {
 		t.Fatal(err)
 	}
 
@@ -552,7 +552,7 @@ func TestStatus(t *testing.T) {
 	defer os.RemoveAll(tmp)
 	defer d.Cleanup()
 
-	if err := d.Create("1", ""); err != nil {
+	if err := d.Create("1", "", ""); err != nil {
 		t.Fatal(err)
 	}
 
@@ -581,7 +581,7 @@ func TestApplyDiff(t *testing.T) {
 	defer os.RemoveAll(tmp)
 	defer d.Cleanup()
 
-	if err := d.Create("1", ""); err != nil {
+	if err := d.Create("1", "", ""); err != nil {
 		t.Fatal(err)
 	}
 
@@ -607,10 +607,10 @@ func TestApplyDiff(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if err := d.Create("2", ""); err != nil {
+	if err := d.Create("2", "", ""); err != nil {
 		t.Fatal(err)
 	}
-	if err := d.Create("3", "2"); err != nil {
+	if err := d.Create("3", "2", ""); err != nil {
 		t.Fatal(err)
 	}
 
@@ -656,7 +656,7 @@ func TestMountMoreThan42Layers(t *testing.T) {
 		}
 		current = hash(current)
 
-		if err := d.Create(current, parent); err != nil {
+		if err := d.Create(current, parent, ""); err != nil {
 			t.Logf("Current layer %d", i)
 			t.Fatal(err)
 		}

+ 3 - 3
runtime/graphdriver/aufs/migrate.go

@@ -77,7 +77,7 @@ func (a *Driver) migrateContainers(pth string, setupInit func(p string) error) e
 				}
 
 				initID := fmt.Sprintf("%s-init", id)
-				if err := a.Create(initID, metadata.Image); err != nil {
+				if err := a.Create(initID, metadata.Image, ""); err != nil {
 					return err
 				}
 
@@ -90,7 +90,7 @@ func (a *Driver) migrateContainers(pth string, setupInit func(p string) error) e
 					return err
 				}
 
-				if err := a.Create(id, initID); err != nil {
+				if err := a.Create(id, initID, ""); err != nil {
 					return err
 				}
 			}
@@ -144,7 +144,7 @@ func (a *Driver) migrateImage(m *metadata, pth string, migrated map[string]bool)
 			return err
 		}
 		if !a.Exists(m.ID) {
-			if err := a.Create(m.ID, m.ParentID); err != nil {
+			if err := a.Create(m.ID, m.ParentID, ""); err != nil {
 				return err
 			}
 		}

+ 3 - 3
runtime/graphdriver/btrfs/btrfs.go

@@ -80,7 +80,7 @@ func getDirFd(dir *C.DIR) uintptr {
 	return uintptr(C.dirfd(dir))
 }
 
-func subvolCreate(path, name string) error {
+func subvolCreate(path, name string, mountLabel string) error {
 	dir, err := openDir(path)
 	if err != nil {
 		return err
@@ -155,13 +155,13 @@ func (d *Driver) subvolumesDirId(id string) string {
 	return path.Join(d.subvolumesDir(), id)
 }
 
-func (d *Driver) Create(id string, parent string) error {
+func (d *Driver) Create(id string, parent string, mountLabel string) error {
 	subvolumes := path.Join(d.home, "subvolumes")
 	if err := os.MkdirAll(subvolumes, 0700); err != nil {
 		return err
 	}
 	if parent == "" {
-		if err := subvolCreate(subvolumes, id); err != nil {
+		if err := subvolCreate(subvolumes, id, mountLabel); err != nil {
 			return err
 		}
 	} else {

+ 23 - 22
runtime/graphdriver/devmapper/deviceset.go

@@ -6,6 +6,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"github.com/dotcloud/docker/pkg/label"
 	"github.com/dotcloud/docker/utils"
 	"io"
 	"io/ioutil"
@@ -14,6 +15,7 @@ import (
 	"strconv"
 	"strings"
 	"sync"
+	"syscall"
 	"time"
 )
 
@@ -677,6 +679,12 @@ func (devices *DeviceSet) deactivateDevice(hash string) error {
 	utils.Debugf("[devmapper] deactivateDevice(%s)", hash)
 	defer utils.Debugf("[devmapper] deactivateDevice END")
 
+	// Wait for the unmount to be effective,
+	// by watching the value of Info.OpenCount for the device
+	if err := devices.waitClose(hash); err != nil {
+		utils.Errorf("Warning: error waiting for device %s to close: %s\n", hash, err)
+	}
+
 	info := devices.Devices[hash]
 	if info == nil {
 		return fmt.Errorf("Unknown device %s", hash)
@@ -799,24 +807,18 @@ func (devices *DeviceSet) Shutdown() error {
 	for _, info := range devices.Devices {
 		info.lock.Lock()
 		if info.mountCount > 0 {
-			if err := sysUnmount(info.mountPath, 0); err != nil {
+			// We use MNT_DETACH here in case it is still busy in some running
+			// container. This means it'll go away from the global scope directly,
+			// and the device will be released when that container dies.
+			if err := sysUnmount(info.mountPath, syscall.MNT_DETACH); err != nil {
 				utils.Debugf("Shutdown unmounting %s, error: %s\n", info.mountPath, err)
 			}
-		}
-		info.lock.Unlock()
-	}
-
-	for _, d := range devices.Devices {
-		d.lock.Lock()
 
-		if err := devices.waitClose(d.Hash); err != nil {
-			utils.Errorf("Warning: error waiting for device %s to unmount: %s\n", d.Hash, err)
-		}
-		if err := devices.deactivateDevice(d.Hash); err != nil {
-			utils.Debugf("Shutdown deactivate %s , error: %s\n", d.Hash, err)
+			if err := devices.deactivateDevice(info.Hash); err != nil {
+				utils.Debugf("Shutdown deactivate %s , error: %s\n", info.Hash, err)
+			}
 		}
-
-		d.lock.Unlock()
+		info.lock.Unlock()
 	}
 
 	if err := devices.deactivatePool(); err != nil {
@@ -826,7 +828,7 @@ func (devices *DeviceSet) Shutdown() error {
 	return nil
 }
 
-func (devices *DeviceSet) MountDevice(hash, path string) error {
+func (devices *DeviceSet) MountDevice(hash, path string, mountLabel string) error {
 	devices.Lock()
 	defer devices.Unlock()
 
@@ -858,9 +860,11 @@ func (devices *DeviceSet) MountDevice(hash, path string) error {
 
 	var flags uintptr = sysMsMgcVal
 
-	err := sysMount(info.DevName(), path, "ext4", flags, "discard")
+	mountOptions := label.FormatMountLabel("discard", mountLabel)
+	err := sysMount(info.DevName(), path, "ext4", flags, mountOptions)
 	if err != nil && err == sysEInval {
-		err = sysMount(info.DevName(), path, "ext4", flags, "")
+		mountOptions = label.FormatMountLabel(mountLabel, "")
+		err = sysMount(info.DevName(), path, "ext4", flags, mountOptions)
 	}
 	if err != nil {
 		return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err)
@@ -920,14 +924,11 @@ func (devices *DeviceSet) UnmountDevice(hash string, mode UnmountMode) error {
 		return err
 	}
 	utils.Debugf("[devmapper] Unmount done")
-	// Wait for the unmount to be effective,
-	// by watching the value of Info.OpenCount for the device
-	if err := devices.waitClose(hash); err != nil {
+
+	if err := devices.deactivateDevice(hash); err != nil {
 		return err
 	}
 
-	devices.deactivateDevice(hash)
-
 	info.mountPath = ""
 
 	return nil

+ 8 - 7
runtime/graphdriver/devmapper/driver.go

@@ -22,7 +22,8 @@ func init() {
 
 type Driver struct {
 	*DeviceSet
-	home string
+	home       string
+	MountLabel string
 }
 
 var Init = func(home string) (graphdriver.Driver, error) {
@@ -60,13 +61,13 @@ func (d *Driver) Cleanup() error {
 	return d.DeviceSet.Shutdown()
 }
 
-func (d *Driver) Create(id, parent string) error {
+func (d *Driver) Create(id, parent string, mountLabel string) error {
+	d.MountLabel = mountLabel
 	if err := d.DeviceSet.AddDevice(id, parent); err != nil {
 		return err
 	}
-
 	mp := path.Join(d.home, "mnt", id)
-	if err := d.mount(id, mp); err != nil {
+	if err := d.mount(id, mp, d.MountLabel); err != nil {
 		return err
 	}
 
@@ -116,7 +117,7 @@ func (d *Driver) Remove(id string) error {
 
 func (d *Driver) Get(id string) (string, error) {
 	mp := path.Join(d.home, "mnt", id)
-	if err := d.mount(id, mp); err != nil {
+	if err := d.mount(id, mp, d.MountLabel); err != nil {
 		return "", err
 	}
 
@@ -129,13 +130,13 @@ func (d *Driver) Put(id string) {
 	}
 }
 
-func (d *Driver) mount(id, mountPoint string) error {
+func (d *Driver) mount(id, mountPoint string, mountLabel string) error {
 	// Create the target directories if they don't exist
 	if err := osMkdirAll(mountPoint, 0755); err != nil && !osIsExist(err) {
 		return err
 	}
 	// Mount the device
-	return d.DeviceSet.MountDevice(id, mountPoint)
+	return d.DeviceSet.MountDevice(id, mountPoint, mountLabel)
 }
 
 func (d *Driver) Exists(id string) bool {

+ 10 - 10
runtime/graphdriver/devmapper/driver_test.go

@@ -494,7 +494,7 @@ func TestDriverCreate(t *testing.T) {
 			"?ioctl.loopctlgetfree",
 		)
 
-		if err := d.Create("1", ""); err != nil {
+		if err := d.Create("1", "", ""); err != nil {
 			t.Fatal(err)
 		}
 		calls.Assert(t,
@@ -612,7 +612,7 @@ func TestDriverRemove(t *testing.T) {
 			"?ioctl.loopctlgetfree",
 		)
 
-		if err := d.Create("1", ""); err != nil {
+		if err := d.Create("1", "", ""); err != nil {
 			t.Fatal(err)
 		}
 
@@ -668,7 +668,7 @@ func TestCleanup(t *testing.T) {
 
 	mountPoints := make([]string, 2)
 
-	if err := d.Create("1", ""); err != nil {
+	if err := d.Create("1", "", ""); err != nil {
 		t.Fatal(err)
 	}
 	// Mount the id
@@ -678,7 +678,7 @@ func TestCleanup(t *testing.T) {
 	}
 	mountPoints[0] = p
 
-	if err := d.Create("2", "1"); err != nil {
+	if err := d.Create("2", "1", ""); err != nil {
 		t.Fatal(err)
 	}
 
@@ -731,7 +731,7 @@ func TestNotMounted(t *testing.T) {
 	d := newDriver(t)
 	defer cleanup(d)
 
-	if err := d.Create("1", ""); err != nil {
+	if err := d.Create("1", "", ""); err != nil {
 		t.Fatal(err)
 	}
 
@@ -749,7 +749,7 @@ func TestMounted(t *testing.T) {
 	d := newDriver(t)
 	defer cleanup(d)
 
-	if err := d.Create("1", ""); err != nil {
+	if err := d.Create("1", "", ""); err != nil {
 		t.Fatal(err)
 	}
 	if _, err := d.Get("1"); err != nil {
@@ -769,7 +769,7 @@ func TestInitCleanedDriver(t *testing.T) {
 	t.Skip("FIXME: not a unit test")
 	d := newDriver(t)
 
-	if err := d.Create("1", ""); err != nil {
+	if err := d.Create("1", "", ""); err != nil {
 		t.Fatal(err)
 	}
 	if _, err := d.Get("1"); err != nil {
@@ -797,7 +797,7 @@ func TestMountMountedDriver(t *testing.T) {
 	d := newDriver(t)
 	defer cleanup(d)
 
-	if err := d.Create("1", ""); err != nil {
+	if err := d.Create("1", "", ""); err != nil {
 		t.Fatal(err)
 	}
 
@@ -816,7 +816,7 @@ func TestGetReturnsValidDevice(t *testing.T) {
 	d := newDriver(t)
 	defer cleanup(d)
 
-	if err := d.Create("1", ""); err != nil {
+	if err := d.Create("1", "", ""); err != nil {
 		t.Fatal(err)
 	}
 
@@ -844,7 +844,7 @@ func TestDriverGetSize(t *testing.T) {
 	d := newDriver(t)
 	defer cleanup(d)
 
-	if err := d.Create("1", ""); err != nil {
+	if err := d.Create("1", "", ""); err != nil {
 		t.Fatal(err)
 	}
 

+ 2 - 3
runtime/graphdriver/driver.go

@@ -13,7 +13,7 @@ type InitFunc func(root string) (Driver, error)
 type Driver interface {
 	String() string
 
-	Create(id, parent string) error
+	Create(id, parent string, mountLabel string) error
 	Remove(id string) error
 
 	Get(id string) (dir string, err error)
@@ -39,10 +39,9 @@ var (
 	// Slice of drivers that should be used in an order
 	priority = []string{
 		"aufs",
+		"btrfs",
 		"devicemapper",
 		"vfs",
-		// experimental, has to be enabled manually for now
-		"btrfs",
 	}
 )
 

+ 1 - 1
runtime/graphdriver/vfs/driver.go

@@ -42,7 +42,7 @@ func copyDir(src, dst string) error {
 	return nil
 }
 
-func (d *Driver) Create(id string, parent string) error {
+func (d *Driver) Create(id string, parent string, mountLabel string) error {
 	dir := d.dir(id)
 	if err := os.MkdirAll(path.Dir(dir), 0700); err != nil {
 		return err

+ 7 - 1
runtime/networkdriver/lxc/driver.go → runtime/networkdriver/bridge/driver.go

@@ -1,4 +1,4 @@
-package lxc
+package bridge
 
 import (
 	"fmt"
@@ -93,6 +93,12 @@ func InitDriver(job *engine.Job) engine.Status {
 		network = addr.(*net.IPNet)
 	} else {
 		network = addr.(*net.IPNet)
+		// validate that the bridge ip matches the ip specified by BridgeIP
+		if bridgeIP != "" {
+			if !network.IP.Equal(net.ParseIP(bridgeIP)) {
+				return job.Errorf("bridge ip (%s) does not match existing bridge configuration %s", network.IP, bridgeIP)
+			}
+		}
 	}
 
 	// Configure iptables for link support

+ 26 - 7
runtime/networkdriver/portallocator/portallocator.go

@@ -100,22 +100,30 @@ func ReleaseAll() error {
 }
 
 func registerDynamicPort(ip net.IP, proto string) (int, error) {
-	allocated := defaultAllocatedPorts[proto]
-
-	port := nextPort(proto)
-	if port > EndPortRange {
-		return 0, ErrPortExceedsRange
-	}
 
 	if !equalsDefault(ip) {
 		registerIP(ip)
 
 		ipAllocated := otherAllocatedPorts[ip.String()][proto]
+
+		port, err := findNextPort(proto, ipAllocated)
+		if err != nil {
+			return 0, err
+		}
 		ipAllocated.Push(port)
+		return port, nil
+
 	} else {
+
+		allocated := defaultAllocatedPorts[proto]
+
+		port, err := findNextPort(proto, allocated)
+		if err != nil {
+			return 0, err
+		}
 		allocated.Push(port)
+		return port, nil
 	}
-	return port, nil
 }
 
 func registerSetPort(ip net.IP, proto string, port int) error {
@@ -142,6 +150,17 @@ func equalsDefault(ip net.IP) bool {
 	return ip == nil || ip.Equal(defaultIP)
 }
 
+func findNextPort(proto string, allocated *collections.OrderedIntSet) (int, error) {
+	port := nextPort(proto)
+	for allocated.Exists(port) {
+		port = nextPort(proto)
+	}
+	if port > EndPortRange {
+		return 0, ErrPortExceedsRange
+	}
+	return port, nil
+}
+
 func nextPort(proto string) int {
 	c := currentDynamicPort[proto] + 1
 	currentDynamicPort[proto] = c

+ 16 - 0
runtime/networkdriver/portallocator/portallocator_test.go

@@ -181,4 +181,20 @@ func TestPortAllocation(t *testing.T) {
 	if _, err := RequestPort(ip, "tcp", 80); err != nil {
 		t.Fatal(err)
 	}
+
+	port, err = RequestPort(ip, "tcp", 0)
+	if err != nil {
+		t.Fatal(err)
+	}
+	port2, err := RequestPort(ip, "tcp", port+1)
+	if err != nil {
+		t.Fatal(err)
+	}
+	port3, err := RequestPort(ip, "tcp", 0)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if port3 == port2 {
+		t.Fatal("Requesting a dynamic port should never allocate a used port")
+	}
 }

+ 3 - 3
runtime/runtime.go

@@ -17,7 +17,7 @@ import (
 	"github.com/dotcloud/docker/runtime/execdriver/lxc"
 	"github.com/dotcloud/docker/runtime/graphdriver"
 	_ "github.com/dotcloud/docker/runtime/graphdriver/vfs"
-	_ "github.com/dotcloud/docker/runtime/networkdriver/lxc"
+	_ "github.com/dotcloud/docker/runtime/networkdriver/bridge"
 	"github.com/dotcloud/docker/runtime/networkdriver/portallocator"
 	"github.com/dotcloud/docker/utils"
 	"io"
@@ -467,7 +467,7 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe
 	}
 
 	initID := fmt.Sprintf("%s-init", container.ID)
-	if err := runtime.driver.Create(initID, img.ID); err != nil {
+	if err := runtime.driver.Create(initID, img.ID, config.Context["mount_label"]); err != nil {
 		return nil, nil, err
 	}
 	initPath, err := runtime.driver.Get(initID)
@@ -480,7 +480,7 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe
 		return nil, nil, err
 	}
 
-	if err := runtime.driver.Create(container.ID, initID); err != nil {
+	if err := runtime.driver.Create(container.ID, initID, config.Context["mount_label"]); err != nil {
 		return nil, nil, err
 	}
 	resolvConf, err := utils.GetResolvConf()

+ 3 - 0
runtime/state.go

@@ -28,6 +28,9 @@ func (s *State) String() string {
 		}
 		return fmt.Sprintf("Up %s", utils.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
 	}
+	if s.FinishedAt.IsZero() {
+		return ""
+	}
 	return fmt.Sprintf("Exited (%d) %s ago", s.ExitCode, utils.HumanDuration(time.Now().UTC().Sub(s.FinishedAt)))
 }
 

+ 28 - 6
server/buildfile.go

@@ -395,9 +395,18 @@ func (b *buildFile) checkPathForAddition(orig string) error {
 
 func (b *buildFile) addContext(container *runtime.Container, orig, dest string, remote bool) error {
 	var (
+		err      error
 		origPath = path.Join(b.contextPath, orig)
 		destPath = path.Join(container.RootfsPath(), dest)
 	)
+
+	if destPath != container.RootfsPath() {
+		destPath, err = utils.FollowSymlinkInScope(destPath, container.RootfsPath())
+		if err != nil {
+			return err
+		}
+	}
+
 	// Preserve the trailing '/'
 	if strings.HasSuffix(dest, "/") {
 		destPath = destPath + "/"
@@ -736,20 +745,19 @@ func (b *buildFile) Build(context io.Reader) (string, error) {
 	if len(fileBytes) == 0 {
 		return "", ErrDockerfileEmpty
 	}
-	dockerfile := string(fileBytes)
-	dockerfile = lineContinuation.ReplaceAllString(dockerfile, "")
-	stepN := 0
+	var (
+		dockerfile = lineContinuation.ReplaceAllString(stripComments(fileBytes), "")
+		stepN      = 0
+	)
 	for _, line := range strings.Split(dockerfile, "\n") {
 		line = strings.Trim(strings.Replace(line, "\t", " ", -1), " \t\r\n")
-		// Skip comments and empty line
-		if len(line) == 0 || line[0] == '#' {
+		if len(line) == 0 {
 			continue
 		}
 		if err := b.BuildStep(fmt.Sprintf("%d", stepN), line); err != nil {
 			return "", err
 		}
 		stepN += 1
-
 	}
 	if b.image != "" {
 		fmt.Fprintf(b.outStream, "Successfully built %s\n", utils.TruncateID(b.image))
@@ -786,6 +794,20 @@ func (b *buildFile) BuildStep(name, expression string) error {
 	return nil
 }
 
+func stripComments(raw []byte) string {
+	var (
+		out   []string
+		lines = strings.Split(string(raw), "\n")
+	)
+	for _, l := range lines {
+		if len(l) == 0 || l[0] == '#' {
+			continue
+		}
+		out = append(out, l)
+	}
+	return strings.Join(out, "\n")
+}
+
 func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeCache, rm bool, outOld io.Writer, sf *utils.StreamFormatter, auth *registry.AuthConfig, authConfigFile *registry.ConfigFile) BuildFile {
 	return &buildFile{
 		runtime:       srv.runtime,

+ 4 - 0
server/server.go

@@ -222,6 +222,10 @@ func (srv *Server) Events(job *engine.Job) engine.Status {
 
 	listener := make(chan utils.JSONMessage)
 	srv.Lock()
+	if old, ok := srv.listeners[from]; ok {
+		delete(srv.listeners, from)
+		close(old)
+	}
 	srv.listeners[from] = listener
 	srv.Unlock()
 	job.Stdout.Write(nil) // flush

+ 13 - 0
utils/utils.go

@@ -25,6 +25,11 @@ import (
 	"time"
 )
 
+type KeyValuePair struct {
+	Key   string
+	Value string
+}
+
 // A common interface to access the Fatal method of
 // both testing.B and testing.T.
 type Fataler interface {
@@ -1071,3 +1076,11 @@ func ReadSymlinkedDirectory(path string) (string, error) {
 	}
 	return realPath, nil
 }
+
+func ParseKeyValueOpt(opt string) (string, string, error) {
+	parts := strings.SplitN(opt, "=", 2)
+	if len(parts) != 2 {
+		return "", "", fmt.Errorf("Unable to parse key/value option: %s", opt)
+	}
+	return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil
+}

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů