From e8740685ceb3ad8637532e7ddffb84ea55d4fc27 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 6 Feb 2014 14:13:03 -0800 Subject: [PATCH 001/384] Remove linux specific calls Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- archive/archive.go | 25 +++++++++++-------- .../{stat_darwin.go => start_unsupported.go} | 6 ++++- archive/stat_linux.go | 7 ++++++ 3 files changed, 27 insertions(+), 11 deletions(-) rename archive/{stat_darwin.go => start_unsupported.go} (72%) diff --git a/archive/archive.go b/archive/archive.go index b1400c2210..3a1c111ea2 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -5,6 +5,7 @@ import ( "bytes" "compress/bzip2" "compress/gzip" + "errors" "fmt" "github.com/dotcloud/docker/utils" "io" @@ -17,14 +18,18 @@ import ( "syscall" ) -type Archive io.Reader +type ( + Archive io.Reader + Compression int + TarOptions struct { + Includes []string + Compression Compression + } +) -type Compression int - -type TarOptions struct { - Includes []string - Compression Compression -} +var ( + ErrNotImplemented = errors.New("Function not implemented") +) const ( Uncompressed Compression = iota @@ -236,14 +241,14 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader *tar.Reader) return fmt.Errorf("Unhandled tar header type %d\n", hdr.Typeflag) } - if err := syscall.Lchown(path, hdr.Uid, hdr.Gid); err != nil { + if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil { return err } // There is no LChmod, so ignore mode for symlink. Also, this // must happen after chown, as that can modify the file mode if hdr.Typeflag != tar.TypeSymlink { - if err := syscall.Chmod(path, uint32(hdr.Mode&07777)); err != nil { + if err := os.Chmod(path, os.FileMode(hdr.Mode&07777)); err != nil { return err } } @@ -251,7 +256,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader *tar.Reader) ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} // syscall.UtimesNano doesn't support a NOFOLLOW flag atm, and if hdr.Typeflag != tar.TypeSymlink { - if err := syscall.UtimesNano(path, ts); err != nil { + if err := UtimesNano(path, ts); err != nil { return err } } else { diff --git a/archive/stat_darwin.go b/archive/start_unsupported.go similarity index 72% rename from archive/stat_darwin.go rename to archive/start_unsupported.go index 32203299dd..834eda8c65 100644 --- a/archive/stat_darwin.go +++ b/archive/start_unsupported.go @@ -13,5 +13,9 @@ func getLastModification(stat *syscall.Stat_t) syscall.Timespec { } func LUtimesNano(path string, ts []syscall.Timespec) error { - return nil + return ErrNotImplemented +} + +func UtimesNano(path string, ts []syscall.Timespec) error { + return ErrNotImplemented } diff --git a/archive/stat_linux.go b/archive/stat_linux.go index 2f7a520ccd..f87a99c55a 100644 --- a/archive/stat_linux.go +++ b/archive/stat_linux.go @@ -30,3 +30,10 @@ func LUtimesNano(path string, ts []syscall.Timespec) error { return nil } + +func UtimesNano(path string, ts []syscall.Timespec) error { + if err := syscall.UtimesNano(path, ts); err != nil { + return err + } + return nil +} From 547ac421995860f99d1d0803c3c1e7ea51dc681e Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 7 Feb 2014 00:44:14 +0000 Subject: [PATCH 002/384] Add Freebsd client support Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- pkg/term/termios_bsd.go | 67 +++++++++++++++++++++++++++++++++++++++++ utils/signal_freebsd.go | 42 ++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 pkg/term/termios_bsd.go create mode 100644 utils/signal_freebsd.go diff --git a/pkg/term/termios_bsd.go b/pkg/term/termios_bsd.go new file mode 100644 index 0000000000..9acf9dfe15 --- /dev/null +++ b/pkg/term/termios_bsd.go @@ -0,0 +1,67 @@ +package term + +import ( + "syscall" + "unsafe" +) + +const ( + getTermios = syscall.TIOCGETA + setTermios = syscall.TIOCSETA + + IGNBRK = syscall.IGNBRK + PARMRK = syscall.PARMRK + INLCR = syscall.INLCR + IGNCR = syscall.IGNCR + ECHONL = syscall.ECHONL + CSIZE = syscall.CSIZE + ICRNL = syscall.ICRNL + ISTRIP = syscall.ISTRIP + PARENB = syscall.PARENB + ECHO = syscall.ECHO + ICANON = syscall.ICANON + ISIG = syscall.ISIG + IXON = syscall.IXON + BRKINT = syscall.BRKINT + INPCK = syscall.INPCK + OPOST = syscall.OPOST + CS8 = syscall.CS8 + IEXTEN = syscall.IEXTEN +) + +type Termios struct { + Iflag uint32 + Oflag uint32 + Cflag uint32 + Lflag uint32 + Cc [20]byte + Ispeed uint32 + Ospeed uint32 +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd uintptr) (*State, error) { + var oldState State + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios))); err != 0 { + return nil, err + } + // C.makeraw() + // return &oldState, nil + + newState := oldState.termios + newState.Iflag &^= (IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON) + newState.Oflag &^= OPOST + newState.Lflag &^= (ECHO | ECHONL | ICANON | ISIG | IEXTEN) + newState.Cflag &^= (CSIZE | PARENB) + newState.Cflag |= CS8 + newState.Cc[syscall.VMIN] = 1 + newState.Cc[syscall.VTIME] = 0 + + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 { + return nil, err + } + + return &oldState, nil +} diff --git a/utils/signal_freebsd.go b/utils/signal_freebsd.go new file mode 100644 index 0000000000..65a700e894 --- /dev/null +++ b/utils/signal_freebsd.go @@ -0,0 +1,42 @@ +package utils + +import ( + "os" + "os/signal" + "syscall" +) + +func CatchAll(sigc chan os.Signal) { + signal.Notify(sigc, + syscall.SIGABRT, + syscall.SIGALRM, + syscall.SIGBUS, + syscall.SIGCHLD, + syscall.SIGCONT, + syscall.SIGFPE, + syscall.SIGHUP, + syscall.SIGILL, + syscall.SIGINT, + syscall.SIGIO, + syscall.SIGIOT, + syscall.SIGKILL, + syscall.SIGPIPE, + syscall.SIGPROF, + syscall.SIGQUIT, + syscall.SIGSEGV, + syscall.SIGSTOP, + syscall.SIGSYS, + syscall.SIGTERM, + syscall.SIGTRAP, + syscall.SIGTSTP, + syscall.SIGTTIN, + syscall.SIGTTOU, + syscall.SIGURG, + syscall.SIGUSR1, + syscall.SIGUSR2, + syscall.SIGVTALRM, + syscall.SIGWINCH, + syscall.SIGXCPU, + syscall.SIGXFSZ, + ) +} From 3b969aad4af430372d5cac1e700f75dba8a38a13 Mon Sep 17 00:00:00 2001 From: Wes Morgan Date: Thu, 30 Jan 2014 12:00:18 -0700 Subject: [PATCH 003/384] merge existing config when committing Fixes #1141 Docker-DCO-1.1-Signed-off-by: Wes Morgan (github: cap10morgan) --- docs/sources/reference/commandline/cli.rst | 61 ++++++++++++++++------ integration/server_test.go | 57 ++++++++++++++++++++ server.go | 11 ++-- 3 files changed, 110 insertions(+), 19 deletions(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 564ea8a034..1f83859c5c 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -83,7 +83,7 @@ Commands -v, --version=false: Print version information and quit --mtu=0: Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if no default route is available -The Docker daemon is the persistent process that manages containers. Docker uses the same binary for both the +The Docker daemon is the persistent process that manages containers. Docker uses the same binary for both the daemon and client. To run the daemon you provide the ``-d`` flag. To force Docker to use devicemapper as the storage driver, use ``docker -d -s devicemapper``. @@ -93,10 +93,10 @@ To set the DNS server for all Docker containers, use ``docker -d -dns 8.8.8.8``. To run the daemon with debug output, use ``docker -d -D``. The docker client will also honor the ``DOCKER_HOST`` environment variable to set -the ``-H`` flag for the client. +the ``-H`` flag for the client. :: - + docker -H tcp://0.0.0.0:4243 ps # or export DOCKER_HOST="tcp://0.0.0.0:4243" @@ -130,7 +130,7 @@ You can find examples of using systemd socket activation with docker and systemd You can detach from the container again (and leave it running) with ``CTRL-c`` (for a quiet exit) or ``CTRL-\`` to get a stacktrace of -the Docker client when it quits. When you detach from the container's +the Docker client when it quits. When you detach from the container's process the exit code will be returned to the client. To stop a container, use ``docker stop``. @@ -292,7 +292,7 @@ by using the ``git://`` schema. -m, --message="": Commit message -a, --author="": Author (eg. "John Hannibal Smith " - --run="": Configuration to be applied when the image is launched with `docker run`. + --run="": Configuration changes to be applied when the image is launched with `docker run`. (ex: -run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}') .. _cli_commit_examples: @@ -304,14 +304,14 @@ Commit an existing container $ sudo docker ps ID IMAGE COMMAND CREATED STATUS PORTS - c3f279d17e0a ubuntu:12.04 /bin/bash 7 days ago Up 25 hours - 197387f1b436 ubuntu:12.04 /bin/bash 7 days ago Up 25 hours + c3f279d17e0a ubuntu:12.04 /bin/bash 7 days ago Up 25 hours + 197387f1b436 ubuntu:12.04 /bin/bash 7 days ago Up 25 hours $ docker commit c3f279d17e0a SvenDowideit/testimage:version3 f5283438590d $ docker images | head REPOSITORY TAG ID CREATED VIRTUAL SIZE SvenDowideit/testimage version3 f5283438590d 16 seconds ago 335.7 MB - + Change the command that a container runs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -333,11 +333,40 @@ run ``ls /etc``. apt host.conf lsb-base rc2.d ... +Merged configs example +...................... + +Say you have a Dockerfile like so: + +.. code-block:: bash + + ENV MYVAR foobar + RUN apt-get install openssh + EXPOSE 22 + CMD ["/usr/sbin/sshd -D"] + ... + +If you run that, make some changes, and then commit, Docker will merge the environment variable and exposed port configuration settings with any that you specify in the -run= option. This is a change from Docker 0.8.0 and prior where no attempt was made to preserve any existing configuration on commit. + +.. code-block:: bash + + $ docker build -t me/foo . + $ docker run -t -i me/foo /bin/bash + foo-container$ [make changes in the container] + foo-container$ exit + $ docker commit -run='{"Cmd": ["ls"]}' [container-id] me/bar + ... + +The me/bar image will now have port 22 exposed, MYVAR env var set to 'foobar', and its default command will be ["ls"]. + +Note that this is currently a shallow merge. So, for example, if you had specified a new port spec in the -run= config above, that would have clobbered the 'EXPOSE 22' setting from the parent container. + Full -run example ................. The ``--run`` JSON hash changes the ``Config`` section when running ``docker inspect CONTAINERID`` -or ``config`` when running ``docker inspect IMAGEID``. +or ``config`` when running ``docker inspect IMAGEID``. Existing configuration key-values that are +not overridden in the JSON hash will be merged in. (Multiline is okay within a single quote ``'``) @@ -653,7 +682,7 @@ Displaying image hierarchy Usage: docker import URL|- [REPOSITORY[:TAG]] - Create an empty filesystem image and import the contents of the tarball + Create an empty filesystem image and import the contents of the tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then optionally tag it. At this time, the URL must start with ``http`` and point to a single @@ -918,7 +947,7 @@ Running ``docker ps`` showing 2 linked containers. $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES - 4c01db0b339c ubuntu:12.04 bash 17 seconds ago Up 16 seconds webapp + 4c01db0b339c ubuntu:12.04 bash 17 seconds ago Up 16 seconds webapp d7886598dbe2 crosbymichael/redis:latest /redis-server --dir 33 minutes ago Up 33 minutes 6379/tcp redis,webapp/db fd2645e2e2b5 busybox:latest top 10 days ago Ghost insane_ptolemy @@ -1022,7 +1051,7 @@ containers will not be deleted. Remove one or more images -f, --force=false: Force - + Removing tagged images ~~~~~~~~~~~~~~~~~~~~~~ @@ -1101,8 +1130,8 @@ Once the container is stopped it still exists and can be started back up. See ` The ``docker run`` command can be used in combination with ``docker commit`` to :ref:`change the command that a container runs `. -See :ref:`port_redirection` for more detailed information about the ``--expose``, -``-p``, ``-P`` and ``--link`` parameters, and :ref:`working_with_links_names` for +See :ref:`port_redirection` for more detailed information about the ``--expose``, +``-p``, ``-P`` and ``--link`` parameters, and :ref:`working_with_links_names` for specific examples using ``--link``. Known Issues (run -volumes-from) @@ -1182,8 +1211,8 @@ starting your container. $ sudo docker run -t -i -v /var/run/docker.sock:/var/run/docker.sock -v ./static-docker:/usr/bin/docker busybox sh -By bind-mounting the docker unix socket and statically linked docker binary -(such as that provided by https://get.docker.io), you give the container +By bind-mounting the docker unix socket and statically linked docker binary +(such as that provided by https://get.docker.io), you give the container the full access to create and manipulate the host's docker daemon. .. code-block:: bash diff --git a/integration/server_test.go b/integration/server_test.go index 1247e8d2d8..915119f65c 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -219,6 +219,63 @@ func TestCommit(t *testing.T) { } } +func TestMergeConfigOnCommit(t *testing.T) { + eng := NewTestEngine(t) + runtime := mkRuntimeFromEngine(eng, t) + defer runtime.Nuke() + + container1, _, _ := mkContainer(runtime, []string{"-e", "FOO=bar", unitTestImageID, "echo test > /tmp/foo"}, t) + defer runtime.Destroy(container1) + + config, _, _, err := runconfig.Parse([]string{container1.ID, "cat /tmp/foo"}, nil) + if err != nil { + t.Error(err) + } + + job := eng.Job("commit", container1.ID) + job.Setenv("repo", "testrepo") + job.Setenv("tag", "testtag") + job.SetenvJson("config", config) + var newId string + job.Stdout.AddString(&newId) + if err := job.Run(); err != nil { + t.Error(err) + } + + container2, _, _ := mkContainer(runtime, []string{newId}, t) + defer runtime.Destroy(container2) + + job = eng.Job("inspect", container1.Name, "container") + baseContainer, _ := job.Stdout.AddEnv() + if err := job.Run(); err != nil { + t.Error(err) + } + + job = eng.Job("inspect", container2.Name, "container") + commitContainer, _ := job.Stdout.AddEnv() + if err := job.Run(); err != nil { + t.Error(err) + } + + baseConfig := baseContainer.GetSubEnv("Config") + commitConfig := commitContainer.GetSubEnv("Config") + + if commitConfig.Get("Env") != baseConfig.Get("Env") { + t.Fatalf("Env config in committed container should be %v, was %v", + baseConfig.Get("Env"), commitConfig.Get("Env")) + } + + if baseConfig.Get("Cmd") != "[\"echo test \\u003e /tmp/foo\"]" { + t.Fatalf("Cmd in base container should be [\"echo test \\u003e /tmp/foo\"], was %s", + baseConfig.Get("Cmd")) + } + + if commitConfig.Get("Cmd") != "[\"cat /tmp/foo\"]" { + t.Fatalf("Cmd in committed container should be [\"cat /tmp/foo\"], was %s", + commitConfig.Get("Cmd")) + } +} + func TestRestartKillWait(t *testing.T) { eng := NewTestEngine(t) srv := mkServerFromEngine(eng, t) diff --git a/server.go b/server.go index e6176e18bd..a0bfd286c5 100644 --- a/server.go +++ b/server.go @@ -1043,12 +1043,17 @@ func (srv *Server) ContainerCommit(job *engine.Job) engine.Status { if container == nil { return job.Errorf("No such container: %s", name) } - var config runconfig.Config - if err := job.GetenvJson("config", &config); err != nil { + var config = container.Config + var newConfig runconfig.Config + if err := job.GetenvJson("config", &newConfig); err != nil { return job.Error(err) } - img, err := srv.runtime.Commit(container, job.Getenv("repo"), job.Getenv("tag"), job.Getenv("comment"), job.Getenv("author"), &config) + if err := runconfig.Merge(&newConfig, config); err != nil { + return job.Error(err) + } + + img, err := srv.runtime.Commit(container, job.Getenv("repo"), job.Getenv("tag"), job.Getenv("comment"), job.Getenv("author"), &newConfig) if err != nil { return job.Error(err) } From b39d02b611f1cc0af283f417b73bf0d36f26277a Mon Sep 17 00:00:00 2001 From: Darren Shepherd Date: Mon, 3 Mar 2014 21:53:57 -0700 Subject: [PATCH 004/384] Support hairpin NAT without going through docker server Hairpin NAT is currently done by passing through the docker server. If two containers on the same box try to access each other through exposed ports and using the host IP the current iptables rules will not match the DNAT and thus the traffic goes to 'docker -d' This change drops the restriction that DNAT traffic must not originate from docker0. It should be safe to drop this restriction because the DOCKER chain is already gated by jumps that check for the destination address to be a local address. Docker-DCO-1.1-Signed-off-by: Darren Shepherd (github: ibuildthecloud) --- pkg/iptables/iptables.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/iptables/iptables.go b/pkg/iptables/iptables.go index 4cdd67ef7c..1f25952bd9 100644 --- a/pkg/iptables/iptables.go +++ b/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 From 2aeccdd3bb98760ab10e834c6c134bb76f664910 Mon Sep 17 00:00:00 2001 From: Alexey Kotlyarov Date: Tue, 4 Mar 2014 14:45:14 +1100 Subject: [PATCH 005/384] Create directories for tar files with relaxed permissions Docker-DCO-1.1-Signed-off-by: Alexey Kotlyarov (github: koterpillar) --- archive/archive.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archive/archive.go b/archive/archive.go index 72bd31a281..c15a9d153b 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -403,7 +403,7 @@ func Untar(archive io.Reader, dest string, options *TarOptions) error { parent := filepath.Dir(hdr.Name) parentPath := filepath.Join(dest, parent) if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { - err = os.MkdirAll(parentPath, 600) + err = os.MkdirAll(parentPath, 0777) if err != nil { return err } From 28a545d294cac3b2e1f4266f5099bd2c5ddb342f Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Fri, 14 Feb 2014 16:56:21 +1000 Subject: [PATCH 006/384] Show some ENV / local updated baseimage tricks that use an apt-cacher-ng proxy to make debian based installations instant Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- .../sources/examples/apt-cacher-ng.Dockerfile | 15 +++ docs/sources/examples/apt-cacher-ng.rst | 104 ++++++++++++++++++ docs/sources/examples/index.rst | 1 + 3 files changed, 120 insertions(+) create mode 100644 docs/sources/examples/apt-cacher-ng.Dockerfile create mode 100644 docs/sources/examples/apt-cacher-ng.rst diff --git a/docs/sources/examples/apt-cacher-ng.Dockerfile b/docs/sources/examples/apt-cacher-ng.Dockerfile new file mode 100644 index 0000000000..fcc326815d --- /dev/null +++ b/docs/sources/examples/apt-cacher-ng.Dockerfile @@ -0,0 +1,15 @@ +# +# BUILD docker build -t apt-cacher . +# RUN docker run -d -p 3142:3142 -name apt-cacher-run apt-cacher +# +# and then you can run containers with: +# docker run -t -i -rm -e http_proxy http://dockerhost:3142/ debian bash +# +FROM ubuntu +MAINTAINER SvenDowideit@docker.com + +VOLUME ["/var/cache/apt-cacher-ng"] +RUN apt-get update ; apt-get install -yq apt-cacher-ng + +EXPOSE 3142 +CMD chmod 777 /var/cache/apt-cacher-ng ; /etc/init.d/apt-cacher-ng start ; tail -f /var/log/apt-cacher-ng/* diff --git a/docs/sources/examples/apt-cacher-ng.rst b/docs/sources/examples/apt-cacher-ng.rst new file mode 100644 index 0000000000..f828e4e1e7 --- /dev/null +++ b/docs/sources/examples/apt-cacher-ng.rst @@ -0,0 +1,104 @@ +:title: Running an apt-cacher-ng service +:description: Installing and running an apt-cacher-ng service +:keywords: docker, example, package installation, networking, debian, ubuntu + +.. _running_apt-cacher-ng_service: + +Apt-Cacher-ng Service +===================== + +.. include:: example_header.inc + + +When you have multiple Docker servers, or build unrelated Docker containers +which can't make use of the Docker build cache, it can be useful to have a +caching proxy for your packages. This container makes the second download of +any package almost instant. + +Use the following Dockerfile: + +.. literalinclude:: apt-cacher-ng.Dockerfile + +To build the image using: + +.. code-block:: bash + + $ sudo docker build -rm -t eg_apt_cacher_ng . + +Then run it, mapping the exposed port to one on the host + +.. code-block:: bash + + $ sudo docker run -d -p 3142:3142 -name test_apt_cacher_ng eg_apt_cacher_ng + +To see the logfiles that are 'tailed' in the default command, you can use: + +.. code-block:: bash + + $ sudo docker logs -f test_apt_cacher_ng + +To get your Debian based containers to use the proxy, you can do one of 3 things + +1. Set and environment variable: ``http_proxy=http://dockerhost:3142/`` +2. Add an apt Proxy setting ``echo 'Acquire::http { Proxy "http://dockerhost:3142"; };' >> /etc/apt/conf.d/01proxy`` +3. Change your sources.list entries to start with ``http://dockerhost:3142/`` + +Option 1 will work for running a container, so is good for testing, but will +break any non-apt http requests, like ``curl``, ``wget`` and more.: + +.. code-block:: bash + + $ sudo docker run -rm -t -i -e http_proxy=http://dockerhost:3142/ debian bash + +Or you can inject the settings safely into your apt configuration in a local +version of a common base: + +.. code-block:: bash + + FROM ubuntu + + RUN echo 'Acquire::http { Proxy "http://dockerhost:3142"; };' >> /etc/apt/apt.conf.d/01proxy + + RUN apt-get update ; apt-get install vim git + + # docker build -t my_ubuntu . + +Option 3 is the least portable, but there will be times when you might need to +do it - and you can do it from your Dockerfile too. + +Apt-cacher-ng has some tools that allow you to manage the repository, and they +can be used by leveraging the ``VOLUME``, and the image we built to run the +service: + +.. code-block:: bash + + $ sudo docker run -rm -t -i --volumes-from test_apt_cacher_ng eg_apt_cacher_ng bash + + $$ /usr/lib/apt-cacher-ng/distkill.pl + Scanning /var/cache/apt-cacher-ng, please wait... + Found distributions: + bla, taggedcount: 0 + 1. precise-security (36 index files) + 2. wheezy (25 index files) + 3. precise-updates (36 index files) + 4. precise (36 index files) + 5. wheezy-updates (18 index files) + + Found architectures: + 6. amd64 (36 index files) + 7. i386 (24 index files) + + WARNING: The removal action may wipe out whole directories containing + index files. Select d to see detailed list. + + (Number nn: tag distribution or architecture nn; 0: exit; d: show details; r: remove tagged; q: quit): q + + +Finally, clean up after your test by stopping and removing the container, and +then removing the image. + +.. code-block:: bash + + $ sudo docker stop test_apt_cacher_ng + $ sudo docker rm test_apt_cacher_ng + $ sudo docker rmi eg_apt_cacher_ng diff --git a/docs/sources/examples/index.rst b/docs/sources/examples/index.rst index cf9ed9340a..383d0760f7 100644 --- a/docs/sources/examples/index.rst +++ b/docs/sources/examples/index.rst @@ -26,3 +26,4 @@ to more substantial services like those which you might find in production. using_supervisord cfengine_process_management python_web_app + apt-cacher-ng From cadd94f44c6f6e276a5b028a2935b5e352408d3b Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Tue, 18 Feb 2014 12:55:14 +1000 Subject: [PATCH 007/384] implement pharvey's suggestions Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/examples/apt-cacher-ng.rst | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/sources/examples/apt-cacher-ng.rst b/docs/sources/examples/apt-cacher-ng.rst index f828e4e1e7..0fb55720f2 100644 --- a/docs/sources/examples/apt-cacher-ng.rst +++ b/docs/sources/examples/apt-cacher-ng.rst @@ -37,20 +37,13 @@ To see the logfiles that are 'tailed' in the default command, you can use: $ sudo docker logs -f test_apt_cacher_ng -To get your Debian based containers to use the proxy, you can do one of 3 things +To get your Debian based containers to use the proxy, you can do one of three things -1. Set and environment variable: ``http_proxy=http://dockerhost:3142/`` -2. Add an apt Proxy setting ``echo 'Acquire::http { Proxy "http://dockerhost:3142"; };' >> /etc/apt/conf.d/01proxy`` +1. Add an apt Proxy setting ``echo 'Acquire::http { Proxy "http://dockerhost:3142"; };' >> /etc/apt/conf.d/01proxy`` +2. Set and environment variable: ``http_proxy=http://dockerhost:3142/`` 3. Change your sources.list entries to start with ``http://dockerhost:3142/`` -Option 1 will work for running a container, so is good for testing, but will -break any non-apt http requests, like ``curl``, ``wget`` and more.: - -.. code-block:: bash - - $ sudo docker run -rm -t -i -e http_proxy=http://dockerhost:3142/ debian bash - -Or you can inject the settings safely into your apt configuration in a local +**Option 1** injects the settings safely into your apt configuration in a local version of a common base: .. code-block:: bash @@ -63,7 +56,14 @@ version of a common base: # docker build -t my_ubuntu . -Option 3 is the least portable, but there will be times when you might need to +**Option 2** is good for testing, but will +break other HTTP clients which obey ``http_proxy``, such as ``curl``, ``wget`` and others: + +.. code-block:: bash + + $ sudo docker run -rm -t -i -e http_proxy=http://dockerhost:3142/ debian bash + +**Option 3** is the least portable, but there will be times when you might need to do it - and you can do it from your Dockerfile too. Apt-cacher-ng has some tools that allow you to manage the repository, and they From 1cdd775f5d95c4da2895da85b00ffa2917bbf9b0 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 6 Mar 2014 15:12:09 +0100 Subject: [PATCH 008/384] DeviceMapper: Succeed immediately when removing non-existant devices We've seen situations where removal of "ID-init" failed during container deletion (EBUSY), after removal of "ID" has succeeded. This caused the container delete operation to fail, and on the next delete attempt the removal of "ID" failed immediately with "does not exist". Ideally we should not fail the ID-init removal, but its also non-ideal to allow a state where the container is half-removed and we cannot make progress deleting the container. So, we silently ignore not-exist errors on device removal. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- graphdriver/devmapper/driver.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/graphdriver/devmapper/driver.go b/graphdriver/devmapper/driver.go index 4d414f9a75..8c5a19eea0 100644 --- a/graphdriver/devmapper/driver.go +++ b/graphdriver/devmapper/driver.go @@ -90,6 +90,13 @@ func (d *Driver) Create(id, parent string) error { } func (d *Driver) Remove(id string) error { + if !d.DeviceSet.HasDevice(id) { + // Consider removing a non-existing device a no-op + // This is useful to be able to progress on container removal + // if the underlying device has gone away due to earlier errors + return nil + } + // Sink the float from create in case no Get() call was made if err := d.DeviceSet.UnmountDevice(id, UnmountSink); err != nil { return err From 3e8a02a9399618917194b37435f5eed9ff86fe2f Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 6 Mar 2014 18:14:56 +0100 Subject: [PATCH 009/384] devmapper: Add per-device lock We currently use a global lock to protect global data (like the Devices map) as well as device data itself and access to (non-threadsafe) libdevmapper. This commit also adds a per-device lock, which will allow per-device operations to temporarily release the global lock while e.g. waiting. The per-device lock will make sure that nothing else accesses that device while we're operating on it. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- graphdriver/devmapper/deviceset.go | 39 +++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/graphdriver/devmapper/deviceset.go b/graphdriver/devmapper/deviceset.go index 303e363e92..f1a0e47d03 100644 --- a/graphdriver/devmapper/deviceset.go +++ b/graphdriver/devmapper/deviceset.go @@ -39,6 +39,13 @@ type DevInfo struct { // first get (since we need to mount to set up the device // a bit first). floating bool `json:"-"` + + // The global DeviceSet lock guarantees that we serialize all + // the calls to libdevmapper (which is not threadsafe), but we + // sometimes release that lock while sleeping. In that case + // this per-device lock is still held, protecting against + // other accesses to the device that we're doing the wait on. + lock sync.Mutex `json:"-"` } type MetaData struct { @@ -47,7 +54,7 @@ type MetaData struct { type DeviceSet struct { MetaData - sync.Mutex + sync.Mutex // Protects Devices map and serializes calls into libdevmapper root string devicePrefix string TransactionId uint64 @@ -569,6 +576,9 @@ func (devices *DeviceSet) AddDevice(hash, baseHash string) error { return fmt.Errorf("Error adding device for '%s': can't find device for parent '%s'", hash, baseHash) } + baseInfo.lock.Lock() + defer baseInfo.lock.Unlock() + deviceId := devices.allocateDeviceId() if err := devices.createSnapDevice(devices.getPoolDevName(), deviceId, baseInfo.Name(), baseInfo.DeviceId); err != nil { @@ -636,6 +646,14 @@ func (devices *DeviceSet) DeleteDevice(hash string) error { devices.Lock() defer devices.Unlock() + info := devices.Devices[hash] + if info == nil { + return fmt.Errorf("Unknown device %s", hash) + } + + info.lock.Lock() + defer info.lock.Unlock() + return devices.deleteDevice(hash) } @@ -773,20 +791,26 @@ func (devices *DeviceSet) Shutdown() error { defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix) for _, info := range devices.Devices { + info.lock.Lock() if info.mountCount > 0 { if err := sysUnmount(info.mountPath, 0); 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) } + + d.lock.Unlock() } if err := devices.deactivatePool(); err != nil { @@ -805,6 +829,9 @@ func (devices *DeviceSet) MountDevice(hash, path string) error { return fmt.Errorf("Unknown device %s", hash) } + info.lock.Lock() + defer info.lock.Unlock() + if info.mountCount > 0 { if path != info.mountPath { return fmt.Errorf("Trying to mount devmapper device in multple places (%s, %s)", info.mountPath, path) @@ -851,6 +878,9 @@ func (devices *DeviceSet) UnmountDevice(hash string, mode UnmountMode) error { return fmt.Errorf("UnmountDevice: no such device %s\n", hash) } + info.lock.Lock() + defer info.lock.Unlock() + if mode == UnmountFloat { if info.floating { return fmt.Errorf("UnmountDevice: can't float floating reference %s\n", hash) @@ -920,6 +950,10 @@ func (devices *DeviceSet) HasActivatedDevice(hash string) bool { if info == nil { return false } + + info.lock.Lock() + defer info.lock.Unlock() + devinfo, _ := getInfo(info.Name()) return devinfo != nil && devinfo.Exists != 0 } @@ -974,6 +1008,9 @@ func (devices *DeviceSet) GetDeviceStatus(hash string) (*DevStatus, error) { return nil, fmt.Errorf("No device %s", hash) } + info.lock.Lock() + defer info.lock.Unlock() + status := &DevStatus{ DeviceId: info.DeviceId, Size: info.Size, From 81f148be566ab2b17810ad4be61a5d8beac8330f Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 6 Mar 2014 18:25:43 +0100 Subject: [PATCH 010/384] devmapper: Increase sleep times and unlock while sleeping We've seen some cases in the wild where waiting for unmount/deactivate of devmapper devices taking a long time (several seconds). So, we increase the sleeps to 10 seconds before we timeout. For instance: https://github.com/dotcloud/docker/issues/4389 But, in order to not keep other processes blocked we unlock the global dm lock while waiting to allow other devices to continue working. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- graphdriver/devmapper/deviceset.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/graphdriver/devmapper/deviceset.go b/graphdriver/devmapper/deviceset.go index f1a0e47d03..f6b26655a3 100644 --- a/graphdriver/devmapper/deviceset.go +++ b/graphdriver/devmapper/deviceset.go @@ -701,7 +701,7 @@ func (devices *DeviceSet) deactivateDevice(hash string) error { func (devices *DeviceSet) removeDeviceAndWait(devname string) error { var err error - for i := 0; i < 10; i++ { + for i := 0; i < 1000; i++ { devices.sawBusy = false err = removeDevice(devname) if err == nil { @@ -713,7 +713,9 @@ func (devices *DeviceSet) removeDeviceAndWait(devname string) error { // If we see EBUSY it may be a transient error, // sleep a bit a retry a few times. - time.Sleep(5 * time.Millisecond) + devices.Unlock() + time.Sleep(10 * time.Millisecond) + devices.Lock() } if err != nil { return err @@ -746,7 +748,9 @@ func (devices *DeviceSet) waitRemove(devname string) error { break } - time.Sleep(1 * time.Millisecond) + devices.Unlock() + time.Sleep(10 * time.Millisecond) + devices.Lock() } if i == 1000 { return fmt.Errorf("Timeout while waiting for device %s to be removed", devname) From 188dea9e0e8b968b28a288192c357343dcc5834c Mon Sep 17 00:00:00 2001 From: Walter Leibbrandt Date: Thu, 6 Mar 2014 23:33:08 +0200 Subject: [PATCH 011/384] Fixed installmirrors ref Nested inline markup is not (yet) possible: http://stackoverflow.com/a/9645684 --- docs/sources/installation/ubuntulinux.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index 416d56765e..c459f33d3c 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -64,7 +64,7 @@ Installation an earlier version, you will need to follow them again. Docker is available as a Debian package, which makes installation -easy. **See the :ref:`installmirrors` section below if you are not in +easy. **See the** :ref:`installmirrors` **section below if you are not in the United States.** Other sources of the Debian packages may be faster for you to install. From 3729ece2ea1c4aad286b7535a7c137045a9da107 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 7 Mar 2014 02:20:59 +0000 Subject: [PATCH 012/384] improve alpha sort in mflag Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- pkg/mflag/flag.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/pkg/mflag/flag.go b/pkg/mflag/flag.go index 7125c030ed..8b3d61e816 100644 --- a/pkg/mflag/flag.go +++ b/pkg/mflag/flag.go @@ -286,9 +286,24 @@ type Flag struct { DefValue string // default value (as text); for usage message } +type flagSlice []string + +func (p flagSlice) Len() int { return len(p) } +func (p flagSlice) Less(i, j int) bool { + pi, pj := p[i], p[j] + if pi[0] == '-' { + pi = pi[1:] + } + if pj[0] == '-' { + pj = pj[1:] + } + return pi < pj +} +func (p flagSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } + // sortFlags returns the flags as a slice in lexicographical sorted order. func sortFlags(flags map[string]*Flag) []*Flag { - var list sort.StringSlice + var list flagSlice for _, f := range flags { fName := strings.TrimPrefix(f.Names[0], "#") if len(f.Names) == 1 { @@ -307,7 +322,7 @@ func sortFlags(flags map[string]*Flag) []*Flag { list = append(list, fName) } } - list.Sort() + sort.Sort(list) result := make([]*Flag, len(list)) for i, name := range list { result[i] = flags[name] From aceb10b1e51d1d9016b25fa5275e5a4f02772c57 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 6 Mar 2014 22:26:47 -0700 Subject: [PATCH 013/384] Resync the DCO text with upstream at http://developercertificate.org/ ``` Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. ``` Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- CONTRIBUTING.md | 53 ++++++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c03c5d0d9c..0e8b98122f 100644 --- a/CONTRIBUTING.md +++ b/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 +Developer Certificate of Origin +Version 1.1 -By making a contribution to the Docker Project ("Project"), I represent and -warrant that: +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA -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 +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. -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. +Developer's Certificate of Origin 1.1 -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. +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: From 0a819380c52cbfcc9d674475913267a33b249e00 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 6 Mar 2014 23:16:26 -0700 Subject: [PATCH 014/384] Clarify how to update the docs branch in the RELEASE-CHECKLIST with concrete instructions Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/RELEASE-CHECKLIST.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/hack/RELEASE-CHECKLIST.md b/hack/RELEASE-CHECKLIST.md index 84a0ff70e1..d6c91a5e43 100644 --- a/hack/RELEASE-CHECKLIST.md +++ b/hack/RELEASE-CHECKLIST.md @@ -175,19 +175,23 @@ release is uploaded to get.docker.io! ### 10. Go to github to merge the `bump_$VERSION` branch into release -Don't delete the leftover branch just yet, as we will need it for the next step. - -### 11. Go to github to merge the `bump_$VERSION` branch into docs - -Merging the pull request to the docs branch will automatically -update the documentation on the "latest" revision of the docs. You -should see the updated docs 5-10 minutes after the merge. The docs -will appear on http://docs.docker.io/. For more information about -documentation releases, see `docs/README.md`. - Don't forget to push that pretty blue button to delete the leftover branch afterwards! +### 11. Update the docs branch + +```bash +git checkout docs +git fetch +git reset --hard origin/release +git push -f origin docs +``` + +Updating the docs branch will automatically update the documentation on the +"latest" revision of the docs. You should see the updated docs 5-10 minutes +after the merge. The docs will appear on http://docs.docker.io/. For more +information about documentation releases, see `docs/README.md`. + ### 12. Create a new pull request to merge release back into master ```bash From 661cf32e4f996385ec7791e93ea8edea80fd2e37 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 6 Mar 2014 23:26:18 -0700 Subject: [PATCH 015/384] Note within the RELEASE-CHECKLIST that "origin" is assumed to be upstream Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/RELEASE-CHECKLIST.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/hack/RELEASE-CHECKLIST.md b/hack/RELEASE-CHECKLIST.md index 84a0ff70e1..6e23489a42 100644 --- a/hack/RELEASE-CHECKLIST.md +++ b/hack/RELEASE-CHECKLIST.md @@ -6,6 +6,21 @@ So you're in charge of a Docker release? Cool. Here's what to do. If your experience deviates from this document, please document the changes to keep it up-to-date. +It is important to note that this document assumes that the git remote in your +repository that corresponds to "https://github.com/dotcloud/docker" is named +"origin". If yours is not (for example, if you've chosen to name it "upstream" +or something similar instead), be sure to adjust the listed snippets for your +local environment accordingly. If you are not sure what your upstream remote is +named, use a command like `git remote -v` to find out. + +If you don't have an upstream remote, you can add one easily using something +like: + +```bash +git remote add origin https://github.com/dotcloud/docker.git +git remote add YOURUSER git@github.com:YOURUSER/docker.git +``` + ### 1. Pull from master and create a release branch ```bash From d61938d2b879fe0ccbf46b4a7ca8dd2eb537eee9 Mon Sep 17 00:00:00 2001 From: Tom Fotherby Date: Fri, 7 Mar 2014 16:41:11 +0000 Subject: [PATCH 016/384] Correct Docker run --host param to --hostname --- docs/sources/reference/commandline/cli.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 2e49cd5ca5..2404e29b29 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -1096,7 +1096,7 @@ image is removed. --cidfile="": Write the container ID to the file -d, --detach=false: Detached mode: Run container in the background, print new container id -e, --env=[]: Set environment variables - -h, --host="": Container host name + -h, --hostname="": Container host name -i, --interactive=false: Keep stdin open even if not attached --privileged=false: Give extended privileges to this container -m, --memory="": Memory limit (format: , where unit = b, k, m or g) From bc086a9cd61b2d15fbef9db3cb53c7f3650fda48 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 7 Mar 2014 20:07:17 +0000 Subject: [PATCH 017/384] fix string in docker images Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- server.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server.go b/server.go index 024b8aa3c4..91c58f62e2 100644 --- a/server.go +++ b/server.go @@ -1011,7 +1011,11 @@ func (srv *Server) Containers(job *engine.Job) engine.Status { out.Set("Id", container.ID) out.SetList("Names", names[container.ID]) out.Set("Image", srv.runtime.repositories.ImageName(container.Image)) - out.Set("Command", fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " "))) + if len(container.Args) > 0 { + out.Set("Command", fmt.Sprintf("\"%s %s\"", container.Path, strings.Join(container.Args, " "))) + } else { + out.Set("Command", fmt.Sprintf("\"%s\"", container.Path)) + } out.SetInt64("Created", container.Created.Unix()) out.Set("Status", container.State.String()) str, err := container.NetworkSettings.PortMappingAPI().ToListString() From 7da37fec13a0097284ffbbe05514de477cd98677 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 7 Mar 2014 23:39:03 +0000 Subject: [PATCH 018/384] handle capital Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- pkg/mflag/flag.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/mflag/flag.go b/pkg/mflag/flag.go index 8b3d61e816..f16f641341 100644 --- a/pkg/mflag/flag.go +++ b/pkg/mflag/flag.go @@ -290,7 +290,7 @@ type flagSlice []string func (p flagSlice) Len() int { return len(p) } func (p flagSlice) Less(i, j int) bool { - pi, pj := p[i], p[j] + pi, pj := strings.ToLower(p[i]), strings.ToLower(p[j]) if pi[0] == '-' { pi = pi[1:] } From d04f4d836c6b2a9266350a1b0e284949dcc6510a Mon Sep 17 00:00:00 2001 From: unclejack Date: Sat, 8 Mar 2014 17:36:18 +0200 Subject: [PATCH 019/384] upgrade packages after debootstrap This makes mkimage-debootstrap upgrade packages after retrieving updated lists of packages. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- contrib/mkimage-debootstrap.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/mkimage-debootstrap.sh b/contrib/mkimage-debootstrap.sh index bf89600973..33ba7b07cb 100755 --- a/contrib/mkimage-debootstrap.sh +++ b/contrib/mkimage-debootstrap.sh @@ -219,6 +219,7 @@ if [ -z "$strictDebootstrap" ]; then # make sure our packages lists are as up to date as we can get them sudo chroot . apt-get update + sudo chroot . apt-get dist-upgrade -y fi if [ "$justTar" ]; then From 59acb8c83d3f21ae82c540774653ccd0a46f1a1f Mon Sep 17 00:00:00 2001 From: Scott Collier Date: Sat, 8 Mar 2014 16:32:00 -0600 Subject: [PATCH 020/384] Adding the new options to the `docker ps` documentation. URL of documentation page is: http://docs.docker.io/en/latest/reference/commandline/cli/#ps Docker-DCO-1.1-Signed-off-by: Scott Collier (github: scollier) --- docs/sources/reference/commandline/cli.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 2e49cd5ca5..5b43e45eb4 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -933,8 +933,14 @@ new output from the container's stdout and stderr. List containers -a, --all=false: Show all containers. Only running containers are shown by default. + --before-id="": Show only container created before Id, include non-running ones. + -l, --latest=false: Show only the latest created container, include non-running ones. + -n=-1: Show n last created containers, include non-running ones. --no-trunc=false: Don't truncate output -q, --quiet=false: Only display numeric IDs + -s, --size=false: Display sizes, not to be used with -q + --since-id="": Show only containers created since Id, include non-running ones. + Running ``docker ps`` showing 2 linked containers. From c000cb64712349141596318dea2a8de2462c8f81 Mon Sep 17 00:00:00 2001 From: Johannes 'fish' Ziemke Date: Thu, 27 Feb 2014 13:47:59 +0100 Subject: [PATCH 021/384] Add authenticated TLS support for API Docker-DCO-1.1-Signed-off-by: Johannes 'fish' Ziemke (github: discordianfish) --- api/client.go | 22 ++- api/server.go | 39 +++++- contrib/host-integration/manager.go | 2 +- docker/docker.go | 70 +++++++++- docs/sources/examples/https.rst | 126 ++++++++++++++++++ docs/sources/examples/index.rst | 1 + docs/sources/reference/commandline/cli.rst | 5 + integration/commands_test.go | 44 +++--- integration/fixtures/https/ca.pem | 23 ++++ integration/fixtures/https/client-cert.pem | 73 ++++++++++ integration/fixtures/https/client-key.pem | 16 +++ .../fixtures/https/client-rogue-cert.pem | 73 ++++++++++ .../fixtures/https/client-rogue-key.pem | 16 +++ integration/fixtures/https/server-cert.pem | 76 +++++++++++ integration/fixtures/https/server-key.pem | 16 +++ .../fixtures/https/server-rogue-cert.pem | 76 +++++++++++ .../fixtures/https/server-rogue-key.pem | 16 +++ integration/https_test.go | 82 ++++++++++++ integration/runtime_test.go | 86 ++++++++++-- 19 files changed, 812 insertions(+), 50 deletions(-) create mode 100644 docs/sources/examples/https.rst create mode 100644 integration/fixtures/https/ca.pem create mode 100644 integration/fixtures/https/client-cert.pem create mode 100644 integration/fixtures/https/client-key.pem create mode 100644 integration/fixtures/https/client-rogue-cert.pem create mode 100644 integration/fixtures/https/client-rogue-key.pem create mode 100644 integration/fixtures/https/server-cert.pem create mode 100644 integration/fixtures/https/server-key.pem create mode 100644 integration/fixtures/https/server-rogue-cert.pem create mode 100644 integration/fixtures/https/server-rogue-key.pem create mode 100644 integration/https_test.go diff --git a/api/client.go b/api/client.go index 338a5b0de1..b8ac0f94c7 100644 --- a/api/client.go +++ b/api/client.go @@ -3,6 +3,7 @@ package api import ( "bufio" "bytes" + "crypto/tls" "encoding/base64" "encoding/json" "errors" @@ -57,8 +58,8 @@ func (cli *DockerCli) getMethod(name string) (func(...string) error, bool) { return method.Interface().(func(...string) error), true } -func ParseCommands(proto, addr string, args ...string) error { - cli := NewDockerCli(os.Stdin, os.Stdout, os.Stderr, proto, addr) +func ParseCommands(proto, addr string, tlsConfig *tls.Config, args ...string) error { + cli := NewDockerCli(os.Stdin, os.Stdout, os.Stderr, proto, addr, tlsConfig) if len(args) > 0 { method, exists := cli.getMethod(args[0]) @@ -2026,6 +2027,13 @@ func (cli *DockerCli) CmdLoad(args ...string) error { return nil } +func (cli *DockerCli) dial() (net.Conn, error) { + if cli.tlsConfig != nil && cli.proto != "unix" { + return tls.Dial(cli.proto, cli.addr, cli.tlsConfig) + } + return net.Dial(cli.proto, cli.addr) +} + func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) { params := bytes.NewBuffer(nil) if data != nil { @@ -2078,7 +2086,7 @@ func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo b } else if method == "POST" { req.Header.Set("Content-Type", "plain/text") } - dial, err := net.Dial(cli.proto, cli.addr) + dial, err := cli.dial() if err != nil { if strings.Contains(err.Error(), "connection refused") { return nil, -1, ErrConnectionRefused @@ -2140,7 +2148,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h } } - dial, err := net.Dial(cli.proto, cli.addr) + dial, err := cli.dial() if err != nil { if strings.Contains(err.Error(), "connection refused") { return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?") @@ -2196,7 +2204,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea req.Header.Set("Content-Type", "plain/text") req.Host = cli.addr - dial, err := net.Dial(cli.proto, cli.addr) + dial, err := cli.dial() if err != nil { if strings.Contains(err.Error(), "connection refused") { return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?") @@ -2388,7 +2396,7 @@ func readBody(stream io.ReadCloser, statusCode int, err error) ([]byte, int, err return body, statusCode, nil } -func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli { +func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string, tlsConfig *tls.Config) *DockerCli { var ( isTerminal = false terminalFd uintptr @@ -2412,6 +2420,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *Doc err: err, isTerminal: isTerminal, terminalFd: terminalFd, + tlsConfig: tlsConfig, } } @@ -2424,4 +2433,5 @@ type DockerCli struct { err io.Writer isTerminal bool terminalFd uintptr + tlsConfig *tls.Config } diff --git a/api/server.go b/api/server.go index 8fc6b4f68b..d12381a70a 100644 --- a/api/server.go +++ b/api/server.go @@ -4,6 +4,8 @@ import ( "bufio" "bytes" "code.google.com/p/go.net/websocket" + "crypto/tls" + "crypto/x509" "encoding/base64" "encoding/json" "expvar" @@ -1129,9 +1131,8 @@ func changeGroup(addr string, nameOrGid string) error { // ListenAndServe sets up the required http.Server and gets it listening for // each addr passed in and does protocol specific checking. -func ListenAndServe(proto, addr string, eng *engine.Engine, logging, enableCors bool, dockerVersion string, socketGroup string) error { - r, err := createRouter(eng, logging, enableCors, dockerVersion) - +func ListenAndServe(proto, addr string, job *engine.Job) error { + r, err := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("Version")) if err != nil { return err } @@ -1151,17 +1152,43 @@ func ListenAndServe(proto, addr string, eng *engine.Engine, logging, enableCors return err } + if proto != "unix" && (job.GetenvBool("Tls") || job.GetenvBool("TlsVerify")) { + tlsCert := job.Getenv("TlsCert") + tlsKey := job.Getenv("TlsKey") + cert, err := tls.LoadX509KeyPair(tlsCert, tlsKey) + if err != nil { + return fmt.Errorf("Couldn't load X509 key pair (%s, %s): %s. Key encrypted?", + tlsCert, tlsKey, err) + } + tlsConfig := &tls.Config{ + NextProtos: []string{"http/1.1"}, + Certificates: []tls.Certificate{cert}, + } + if job.GetenvBool("TlsVerify") { + certPool := x509.NewCertPool() + file, err := ioutil.ReadFile(job.Getenv("TlsCa")) + if err != nil { + return fmt.Errorf("Couldn't read CA certificate: %s", err) + } + certPool.AppendCertsFromPEM(file) + + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + tlsConfig.ClientCAs = certPool + } + l = tls.NewListener(l, tlsConfig) + } + // Basic error and sanity checking switch proto { case "tcp": - if !strings.HasPrefix(addr, "127.0.0.1") { + if !strings.HasPrefix(addr, "127.0.0.1") && !job.GetenvBool("TlsVerify") { log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") } case "unix": if err := os.Chmod(addr, 0660); err != nil { return err } - + socketGroup := job.Getenv("SocketGroup") if socketGroup != "" { if err := changeGroup(addr, socketGroup); err != nil { if socketGroup == "docker" { @@ -1197,7 +1224,7 @@ func ServeApi(job *engine.Job) engine.Status { protoAddrParts := strings.SplitN(protoAddr, "://", 2) go func() { log.Printf("Listening for HTTP on %s (%s)\n", protoAddrParts[0], protoAddrParts[1]) - chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("Version"), job.Getenv("SocketGroup")) + chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], job) }() } diff --git a/contrib/host-integration/manager.go b/contrib/host-integration/manager.go index 6742ee4d7c..2798a5d06f 100644 --- a/contrib/host-integration/manager.go +++ b/contrib/host-integration/manager.go @@ -70,7 +70,7 @@ func main() { bufErr := bytes.NewBuffer(nil) // Instanciate the Docker CLI - cli := docker.NewDockerCli(nil, bufOut, bufErr, "unix", "/var/run/docker.sock") + cli := docker.NewDockerCli(nil, bufOut, bufErr, "unix", "/var/run/docker.sock", false, nil) // Retrieve the container info if err := cli.CmdInspect(flag.Arg(0)); err != nil { // As of docker v0.6.3, CmdInspect always returns nil diff --git a/docker/docker.go b/docker/docker.go index 2aa10dbe54..0c017eca9a 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -1,7 +1,10 @@ package main import ( + "crypto/tls" + "crypto/x509" "fmt" + "io/ioutil" "log" "os" "strings" @@ -16,6 +19,16 @@ import ( "github.com/dotcloud/docker/utils" ) +const ( + defaultCaFile = "ca.pem" + defaultKeyFile = "key.pem" + defaultCertFile = "cert.pem" +) + +var ( + dockerConfDir = os.Getenv("HOME") + "/.docker/" +) + func main() { if selfPath := utils.SelfPath(); strings.Contains(selfPath, ".dockerinit") { // Running in init mode @@ -43,6 +56,11 @@ func main() { flExecDriver = flag.String([]string{"e", "-exec-driver"}, "native", "Force the docker runtime to use a specific exec driver") flHosts = opts.NewListOpts(api.ValidateHost) flMtu = flag.Int([]string{"#mtu", "-mtu"}, 0, "Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if no default route is available") + flTls = flag.Bool([]string{"-tls"}, false, "Use TLS; implied by tls-verify flags") + flTlsVerify = flag.Bool([]string{"-tlsverify"}, false, "Use TLS and verify the remote (daemon: verify client, client: verify daemon)") + flCa = flag.String([]string{"-tlscacert"}, dockerConfDir+defaultCaFile, "Trust only remotes providing a certificate signed by the CA given here") + flCert = flag.String([]string{"-tlscert"}, dockerConfDir+defaultCertFile, "Path to TLS certificate file") + flKey = flag.String([]string{"-tlskey"}, dockerConfDir+defaultKeyFile, "Path to TLS key file") ) flag.Var(&flDns, []string{"#dns", "-dns"}, "Force docker to use specific DNS servers") flag.Var(&flHosts, []string{"H", "-host"}, "tcp://host:port, unix://path/to/socket, fd://* or fd://socketfd to use in daemon mode. Multiple sockets can be specified") @@ -73,6 +91,7 @@ func main() { if *flDebug { os.Setenv("DEBUG", "1") } + if *flDaemon { if flag.NArg() != 0 { flag.Usage() @@ -140,6 +159,12 @@ func main() { job.SetenvBool("EnableCors", *flEnableCors) job.Setenv("Version", dockerversion.VERSION) job.Setenv("SocketGroup", *flSocketGroup) + + job.SetenvBool("Tls", *flTls) + job.SetenvBool("TlsVerify", *flTlsVerify) + job.Setenv("TlsCa", *flCa) + job.Setenv("TlsCert", *flCert) + job.Setenv("TlsKey", *flKey) if err := job.Run(); err != nil { log.Fatal(err) } @@ -148,14 +173,53 @@ func main() { log.Fatal("Please specify only one -H") } protoAddrParts := strings.SplitN(flHosts.GetAll()[0], "://", 2) - if err := api.ParseCommands(protoAddrParts[0], protoAddrParts[1], flag.Args()...); err != nil { - if sterr, ok := err.(*utils.StatusError); ok { + + var ( + errc error + tlsConfig tls.Config + ) + tlsConfig.InsecureSkipVerify = true + + // If we should verify the server, we need to load a trusted ca + if *flTlsVerify { + *flTls = true + certPool := x509.NewCertPool() + file, err := ioutil.ReadFile(*flCa) + if err != nil { + log.Fatalf("Couldn't read ca cert %s: %s", *flCa, err) + } + certPool.AppendCertsFromPEM(file) + tlsConfig.RootCAs = certPool + tlsConfig.InsecureSkipVerify = false + } + + // If tls is enabled, try to load and send client certificates + if *flTls || *flTlsVerify { + _, errCert := os.Stat(*flCert) + _, errKey := os.Stat(*flKey) + if errCert == nil && errKey == nil { + *flTls = true + cert, err := tls.LoadX509KeyPair(*flCert, *flKey) + if err != nil { + log.Fatalf("Couldn't load X509 key pair: %s. Key encrypted?", err) + } + tlsConfig.Certificates = []tls.Certificate{cert} + } + } + + if *flTls || *flTlsVerify { + errc = api.ParseCommands(protoAddrParts[0], protoAddrParts[1], &tlsConfig, flag.Args()...) + } else { + errc = api.ParseCommands(protoAddrParts[0], protoAddrParts[1], nil, flag.Args()...) + } + if errc != nil { + if sterr, ok := errc.(*utils.StatusError); ok { if sterr.Status != "" { log.Println(sterr.Status) } os.Exit(sterr.StatusCode) } - log.Fatal(err) + log.Fatal(errc) } } } diff --git a/docs/sources/examples/https.rst b/docs/sources/examples/https.rst new file mode 100644 index 0000000000..7a221ed951 --- /dev/null +++ b/docs/sources/examples/https.rst @@ -0,0 +1,126 @@ +:title: Docker HTTPS Setup +:description: How to setup docker with https +:keywords: docker, example, https, daemon + +.. _running_docker_https: + +Running Docker with https +========================= + +By default, Docker runs via a non-networked Unix socket. It can also optionally +communicate using a HTTP socket. + +If you need Docker reachable via the network in a safe manner, you can enable +TLS by specifying the `tlsverify` flag and pointing Docker's `tlscacert` flag to a +trusted CA certificate. + +In daemon mode, it will only allow connections from clients authenticated by a +certificate signed by that CA. In client mode, it will only connect to servers +with a certificate signed by that CA. + +.. warning:: + + Using TLS and managing a CA is an advanced topic. Please make you self familiar + with openssl, x509 and tls before using it in production. + +Create a CA, server and client keys with OpenSSL +------------------------------------------------ + +First, initialize the CA serial file and generate CA private and public keys: + +.. code-block:: bash + + $ echo 01 > ca.srl + $ openssl genrsa -des3 -out ca-key.pem + $ openssl req -new -x509 -days 365 -key ca-key.pem -out ca.pem + +Now that we have a CA, you can create a server key and certificate signing request. +Make sure that `"Common Name (e.g. server FQDN or YOUR name)"` matches the hostname you will use +to connect to Docker or just use '*' for a certificate valid for any hostname: + +.. code-block:: bash + + $ openssl genrsa -des3 -out server-key.pem + $ openssl req -new -key server-key.pem -out server.csr + +Next we're going to sign the key with our CA: + +.. code-block:: bash + + $ openssl x509 -req -days 365 -in server.csr -CA ca.pem -CAkey ca-key.pem \ + -out server-cert.pem + +For client authentication, create a client key and certificate signing request: + +.. code-block:: bash + + $ openssl genrsa -des3 -out client-key.pem + $ openssl req -new -key client-key.pem -out client.csr + + +To make the key suitable for client authentication, create a extensions config file: + +.. code-block:: bash + + $ echo extendedKeyUsage = clientAuth > extfile.cnf + +Now sign the key: + +.. code-block:: bash + + $ openssl x509 -req -days 365 -in client.csr -CA ca.pem -CAkey ca-key.pem \ + -out client-cert.pem -extfile extfile.cnf + +Finally you need to remove the passphrase from the client and server key: + +.. code-block:: bash + + $ openssl rsa -in server-key.pem -out server-key.pem + $ openssl rsa -in client-key.pem -out client-key.pem + +Now you can make the Docker daemon only accept connections from clients providing +a certificate trusted by our CA: + +.. code-block:: bash + + $ sudo docker -d --tlsverify --tlscacert=ca.pem --tlscert=server-cert.pem --tlskey=server-key.pem \ + -H=0.0.0.0:4243 + +To be able to connect to Docker and validate its certificate, you now need to provide your client keys, +certificates and trusted CA: + +.. code-block:: bash + + $ docker --tlsverify --tlscacert=ca.pem --tlscert=client-cert.pem --tlskey=client-key.pem \ + -H=dns-name-of-docker-host:4243 + +.. warning:: + + As shown in the example above, you don't have to run the ``docker`` + client with ``sudo`` or the ``docker`` group when you use + certificate authentication. That means anyone with the keys can + give any instructions to your Docker daemon, giving them root + access to the machine hosting the daemon. Guard these keys as you + would a root password! + +Other modes +----------- +If you don't want to have complete two-way authentication, you can run Docker in +various other modes by mixing the flags. + +Daemon modes +~~~~~~~~~~~~ +- tlsverify, tlscacert, tlscert, tlskey set: Authenticate clients +- tls, tlscert, tlskey: Do not authenticate clients + +Client modes +~~~~~~~~~~~~ +- tls: Authenticate server based on public/default CA pool +- tlsverify, tlscacert: Authenticate server based on given CA +- tls, tlscert, tlskey: Authenticate with client certificate, do not authenticate + server based on given CA +- tlsverify, tlscacert, tlscert, tlskey: Authenticate with client certificate, + authenticate server based on given CA + +The client will send its client certificate if found, so you just need to drop +your keys into `~/.docker/.pem` diff --git a/docs/sources/examples/index.rst b/docs/sources/examples/index.rst index cf9ed9340a..6dcc40a1c7 100644 --- a/docs/sources/examples/index.rst +++ b/docs/sources/examples/index.rst @@ -26,3 +26,4 @@ to more substantial services like those which you might find in production. using_supervisord cfengine_process_management python_web_app + https diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index c7ce421d88..8b4bafc2bd 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -86,6 +86,11 @@ Commands -s, --storage-driver="": Force the docker runtime to use a specific storage driver -e, --exec-driver="native": Force the docker runtime to use a specific exec driver -v, --version=false: Print version information and quit + --tls=false: Use TLS; implied by tls-verify flags + --tlscacert="~/.docker/ca.pem": Trust only remotes providing a certificate signed by the CA given here + --tlscert="~/.docker/cert.pem": Path to TLS certificate file + --tlskey="~/.docker/key.pem": Path to TLS key file + --tlsverify=false: Use TLS and verify the remote (daemon: verify client, client: verify daemon) --mtu=0: Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if no default route is available The Docker daemon is the persistent process that manages containers. Docker uses the same binary for both the diff --git a/integration/commands_test.go b/integration/commands_test.go index 9f7a41384c..838295af4f 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -120,7 +120,7 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error func TestRunHostname(t *testing.T) { stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + cli := api.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) c := make(chan struct{}) @@ -165,7 +165,7 @@ func TestRunHostname(t *testing.T) { func TestRunWorkdir(t *testing.T) { stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + cli := api.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) c := make(chan struct{}) @@ -210,7 +210,7 @@ func TestRunWorkdir(t *testing.T) { func TestRunWorkdirExists(t *testing.T) { stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + cli := api.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) c := make(chan struct{}) @@ -255,7 +255,7 @@ func TestRunExit(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) c1 := make(chan struct{}) @@ -308,7 +308,7 @@ func TestRunDisconnect(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) c1 := make(chan struct{}) @@ -354,7 +354,7 @@ func TestRunDisconnectTty(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) c1 := make(chan struct{}) @@ -406,7 +406,7 @@ func TestRunAttachStdin(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) ch := make(chan struct{}) @@ -470,7 +470,7 @@ func TestRunDetach(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) ch := make(chan struct{}) @@ -517,7 +517,7 @@ func TestAttachDetach(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) ch := make(chan struct{}) @@ -550,7 +550,7 @@ func TestAttachDetach(t *testing.T) { stdin, stdinPipe = io.Pipe() stdout, stdoutPipe = io.Pipe() - cli = api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + cli = api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) ch = make(chan struct{}) go func() { @@ -598,7 +598,7 @@ func TestAttachDetachTruncatedID(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) // Discard the CmdRun output @@ -616,7 +616,7 @@ func TestAttachDetachTruncatedID(t *testing.T) { stdin, stdinPipe = io.Pipe() stdout, stdoutPipe = io.Pipe() - cli = api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + cli = api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) ch := make(chan struct{}) go func() { @@ -663,7 +663,7 @@ func TestAttachDisconnect(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) go func() { @@ -732,7 +732,7 @@ func TestAttachDisconnect(t *testing.T) { func TestRunAutoRemove(t *testing.T) { t.Skip("Fixme. Skipping test for now, race condition") stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + cli := api.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) c := make(chan struct{}) @@ -768,7 +768,7 @@ func TestRunAutoRemove(t *testing.T) { func TestCmdLogs(t *testing.T) { t.Skip("Test not impemented") - cli := api.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr) + cli := api.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) if err := cli.CmdRun(unitTestImageID, "sh", "-c", "ls -l"); err != nil { @@ -786,7 +786,7 @@ func TestCmdLogs(t *testing.T) { // Expected behaviour: error out when attempting to bind mount non-existing source paths func TestRunErrorBindNonExistingSource(t *testing.T) { - cli := api.NewDockerCli(nil, nil, ioutil.Discard, testDaemonProto, testDaemonAddr) + cli := api.NewDockerCli(nil, nil, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) c := make(chan struct{}) @@ -806,7 +806,7 @@ func TestRunErrorBindNonExistingSource(t *testing.T) { func TestImagesViz(t *testing.T) { stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + cli := api.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) image := buildTestImages(t, globalEngine) @@ -856,7 +856,7 @@ func TestImagesViz(t *testing.T) { func TestImagesTree(t *testing.T) { stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + cli := api.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) image := buildTestImages(t, globalEngine) @@ -939,7 +939,7 @@ func TestRunCidFile(t *testing.T) { } tmpCidFile := path.Join(tmpDir, "cid") - cli := api.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + cli := api.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) c := make(chan struct{}) @@ -989,7 +989,7 @@ func TestContainerOrphaning(t *testing.T) { defer os.RemoveAll(tmpDir) // setup a CLI and server - cli := api.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr) + cli := api.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) srv := mkServerFromEngine(globalEngine, t) @@ -1049,8 +1049,8 @@ func TestCmdKill(t *testing.T) { var ( stdin, stdinPipe = io.Pipe() stdout, stdoutPipe = io.Pipe() - cli = api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) - cli2 = api.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr) + cli = api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli2 = api.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) ) defer cleanup(globalEngine, t) diff --git a/integration/fixtures/https/ca.pem b/integration/fixtures/https/ca.pem new file mode 100644 index 0000000000..6825d6d1bd --- /dev/null +++ b/integration/fixtures/https/ca.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID0TCCAzqgAwIBAgIJAP2r7GqEJwSnMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD +VQQGEwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMG +A1UEChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTERMA8GA1UEAxMI +Y2hhbmdlbWUxETAPBgNVBCkTCGNoYW5nZW1lMR8wHQYJKoZIhvcNAQkBFhBtYWls +QGhvc3QuZG9tYWluMB4XDTEzMTIwMzE2NTYzMFoXDTIzMTIwMTE2NTYzMFowgaIx +CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2Nv +MRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24xETAPBgNVBAsTCGNoYW5nZW1lMREwDwYD +VQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEW +EG1haWxAaG9zdC5kb21haW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALAn +0xDw+5y7ZptQacq66pUhRu82JP2WU6IDgo5QUtNU6/CX5PwQATe/OnYTZQFbksxp +AU9boG0FCkgxfsgPYXEuZxVEGKI2fxfKHOZZI8mrkWmj6eWU/0cvCjGVc9rTITP5 +sNQvg+hORyVDdNp2IdsbMJayiB3AQYMFx3vSDOMTAgMBAAGjggELMIIBBzAdBgNV +HQ4EFgQUZu7DFz09q0QBa2+ymRm9qgK1NPswgdcGA1UdIwSBzzCBzIAUZu7DFz09 +q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJD +QTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24x +ETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMI +Y2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW6CCQD9q+xq +hCcEpzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAF8fJKKM+/oOdnNi +zEd0M1+PmZOyqvjYQn/2ZR8UHH6Imgc/OPQKZXf0bVE1Txc/DaUNn9Isd1SuCuaE +ic3vAIYYU7PmgeNN6vwec48V96T7jr+GAi6AVMhQEc2hHCfVtx11Xx+x6aHDZzJt +Zxtf5lL6KSO9Y+EFwM+rju6hm5hW +-----END CERTIFICATE----- diff --git a/integration/fixtures/https/client-cert.pem b/integration/fixtures/https/client-cert.pem new file mode 100644 index 0000000000..c05ed47c2c --- /dev/null +++ b/integration/fixtures/https/client-cert.pem @@ -0,0 +1,73 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3 (0x3) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=changeme/name=changeme/emailAddress=mail@host.domain + Validity + Not Before: Dec 4 14:17:54 2013 GMT + Not After : Dec 2 14:17:54 2023 GMT + Subject: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=client/name=changeme/emailAddress=mail@host.domain + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:ca:c9:05:d0:09:4e:3e:a4:fc:d5:14:f4:a5:e8: + 34:d3:6b:51:e3:f3:62:ea:a1:f0:e8:ed:c4:2a:bc: + f0:4f:ca:07:df:e3:88:fa:f4:21:99:35:0e:3d:ea: + b0:86:e7:c4:d2:8a:83:2b:42:b8:ec:a3:99:62:70: + 81:46:cc:fc:a5:1d:d2:63:e8:eb:07:25:9a:e2:25: + 6d:11:56:f2:1a:51:a1:b6:3e:1c:57:32:e9:7b:2c: + aa:1b:cc:97:2d:89:2d:b1:c9:5e:35:28:4d:7c:fa: + 65:31:3e:f7:70:dd:6e:0b:3c:58:af:a8:2e:24:c0: + 7e:4e:78:7d:0a:9e:8f:42:43 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + Easy-RSA Generated Certificate + X509v3 Subject Key Identifier: + DE:42:EF:2D:98:A3:6C:A8:AA:E0:8C:71:2C:9D:64:23:A9:E2:7E:81 + X509v3 Authority Key Identifier: + keyid:66:EE:C3:17:3D:3D:AB:44:01:6B:6F:B2:99:19:BD:AA:02:B5:34:FB + DirName:/C=US/ST=CA/L=SanFrancisco/O=Fort-Funston/OU=changeme/CN=changeme/name=changeme/emailAddress=mail@host.domain + serial:FD:AB:EC:6A:84:27:04:A7 + + X509v3 Extended Key Usage: + TLS Web Client Authentication + X509v3 Key Usage: + Digital Signature + Signature Algorithm: sha1WithRSAEncryption + 1c:44:26:ea:e1:66:25:cb:e4:8e:57:1c:f6:b9:17:22:62:40: + 12:90:8f:3b:b2:61:7a:54:94:8f:b1:20:0b:bf:a3:51:e3:fa: + 1c:a1:be:92:3a:d0:76:44:c0:57:83:ab:6a:e4:1a:45:49:a4: + af:39:0d:60:32:fc:3a:be:d7:fb:5d:99:7a:1f:87:e7:d5:ab: + 84:a2:5e:90:d8:bf:fa:89:6d:32:26:02:5e:31:35:68:7f:31: + f5:6b:51:46:bc:af:70:ed:5a:09:7d:ec:b2:48:4f:fe:c5:2f: + 56:04:ad:f6:c1:d2:2a:e4:6a:c4:87:fe:08:35:c5:38:cb:5e: + 4a:c4 +-----BEGIN CERTIFICATE----- +MIIEFTCCA36gAwIBAgIBAzANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMCVVMx +CzAJBgNVBAgTAkNBMRUwEwYDVQQHEwxTYW5GcmFuY2lzY28xFTATBgNVBAoTDEZv +cnQtRnVuc3RvbjERMA8GA1UECxMIY2hhbmdlbWUxETAPBgNVBAMTCGNoYW5nZW1l +MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRv +bWFpbjAeFw0xMzEyMDQxNDE3NTRaFw0yMzEyMDIxNDE3NTRaMIGgMQswCQYDVQQG +EwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMGA1UE +ChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTEPMA0GA1UEAxMGY2xp +ZW50MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0 +LmRvbWFpbjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAyskF0AlOPqT81RT0 +peg002tR4/Ni6qHw6O3EKrzwT8oH3+OI+vQhmTUOPeqwhufE0oqDK0K47KOZYnCB +Rsz8pR3SY+jrByWa4iVtEVbyGlGhtj4cVzLpeyyqG8yXLYktscleNShNfPplMT73 +cN1uCzxYr6guJMB+Tnh9Cp6PQkMCAwEAAaOCAVkwggFVMAkGA1UdEwQCMAAwLQYJ +YIZIAYb4QgENBCAWHkVhc3ktUlNBIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNV +HQ4EFgQU3kLvLZijbKiq4IxxLJ1kI6nifoEwgdcGA1UdIwSBzzCBzIAUZu7DFz09 +q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJD +QTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24x +ETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMI +Y2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW6CCQD9q+xq +hCcEpzATBgNVHSUEDDAKBggrBgEFBQcDAjALBgNVHQ8EBAMCB4AwDQYJKoZIhvcN +AQEFBQADgYEAHEQm6uFmJcvkjlcc9rkXImJAEpCPO7JhelSUj7EgC7+jUeP6HKG+ +kjrQdkTAV4OrauQaRUmkrzkNYDL8Or7X+12Zeh+H59WrhKJekNi/+oltMiYCXjE1 +aH8x9WtRRryvcO1aCX3sskhP/sUvVgSt9sHSKuRqxIf+CDXFOMteSsQ= +-----END CERTIFICATE----- diff --git a/integration/fixtures/https/client-key.pem b/integration/fixtures/https/client-key.pem new file mode 100644 index 0000000000..b5c15f8dc7 --- /dev/null +++ b/integration/fixtures/https/client-key.pem @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAMrJBdAJTj6k/NUU +9KXoNNNrUePzYuqh8OjtxCq88E/KB9/jiPr0IZk1Dj3qsIbnxNKKgytCuOyjmWJw +gUbM/KUd0mPo6wclmuIlbRFW8hpRobY+HFcy6XssqhvMly2JLbHJXjUoTXz6ZTE+ +93Ddbgs8WK+oLiTAfk54fQqej0JDAgMBAAECgYBOFEzKp2qbMEexe9ofL2N3rDDh +xkrl8OijpzkLA6i78BxMFn4dsnZlWUpciMrjhsYAExkiRRSS+QMMJimAq1jzQqc3 +FAQV2XGYwkd0cUn7iZGvfNnEPysjsfyYQM+m+sT0ATj4BZjVShC6kkSjTdm1leLN +OSvcHdcu3Xxg9ufF0QJBAPYdnNt5sIndt2WECePuRVi+uF4mlxTobFY0fjn26yhC +4RsnhhD3Vldygo9gvnkwrAZYaALGSPBewes2InxvjA8CQQDS7erKiNXpwoqz5XiU +SVEsIIVTdWzBjGbIqMOu/hUwM5FK4j6JTBks0aTGMyh0YV9L1EzM0X79J29JahCe +iQKNAkBKNMOGqTpBV0hko1sYDk96YobUXG5RL4L6uvkUIQ7mJMQam+AgXXL7Ctuy +v0iu4a38e8tgisiTMP7nHHtpaXihAkAOiN54/lzfMsykANgCP9scE1GcoqbP34Dl +qttxH4kOPT9xzY1JoLjLYdbc4YGUI3GRpBt2sajygNkmUey7P+2xAkBBsVCZFvTw +qHvOpPS2kX5ml5xoc/QAHK9N7kR+X7XFYx82RTVSqJEK4lPb+aEWn+CjiIewO4Q5 +ksDFuNxAzbhl +-----END PRIVATE KEY----- diff --git a/integration/fixtures/https/client-rogue-cert.pem b/integration/fixtures/https/client-rogue-cert.pem new file mode 100644 index 0000000000..21ae4bd579 --- /dev/null +++ b/integration/fixtures/https/client-rogue-cert.pem @@ -0,0 +1,73 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 2 (0x2) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=CA, L=SanFrancisco, O=Evil Inc, OU=changeme, CN=changeme/name=changeme/emailAddress=mail@host.domain + Validity + Not Before: Feb 24 17:54:59 2014 GMT + Not After : Feb 22 17:54:59 2024 GMT + Subject: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=client/name=changeme/emailAddress=mail@host.domain + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:e8:e2:2c:b8:d4:db:89:50:4f:47:1e:68:db:f7: + e4:cc:47:41:63:75:03:37:50:7a:a8:4d:27:36:d5: + 15:01:08:b6:cf:56:f7:56:6d:3d:f9:e2:8d:1a:5d: + bf:a0:24:5e:07:55:8e:d0:dc:f1:fa:19:87:1d:d6: + b6:58:82:2e:ba:69:6d:e9:d9:c8:16:0d:1d:59:7f: + f4:8e:58:10:01:3d:21:14:16:3c:ec:cd:8c:b7:0e: + e6:7b:77:b4:f9:90:a5:17:01:bb:84:c6:b2:12:87: + 70:eb:9f:6d:4f:d0:68:8b:96:c0:e7:0b:51:b4:9d: + 1d:7b:6c:7b:be:89:6b:88:8b + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + Easy-RSA Generated Certificate + X509v3 Subject Key Identifier: + 9E:F8:49:D0:A2:76:30:5C:AB:2B:8A:B5:8D:C6:45:1F:A7:F8:CF:85 + X509v3 Authority Key Identifier: + keyid:DC:A5:F1:76:DB:4E:CD:8E:EF:B1:23:56:1D:92:80:99:74:3B:EA:6F + DirName:/C=US/ST=CA/L=SanFrancisco/O=Evil Inc/OU=changeme/CN=changeme/name=changeme/emailAddress=mail@host.domain + serial:E7:21:1E:18:41:1B:96:83 + + X509v3 Extended Key Usage: + TLS Web Client Authentication + X509v3 Key Usage: + Digital Signature + Signature Algorithm: sha1WithRSAEncryption + 48:76:c0:18:fa:0a:ee:4e:1a:ec:02:9d:d4:83:ca:94:54:a1: + 3f:51:2f:3e:4b:95:c3:42:9b:71:a0:4b:d9:af:47:23:b9:1c: + fb:85:ba:76:e2:09:cb:65:bb:d2:7d:44:3d:4b:67:ba:80:83: + be:a8:ed:c4:b9:ea:1a:1b:c7:59:3b:d9:5c:0d:46:d8:c9:92: + cb:10:c5:f2:1a:38:a4:aa:07:2c:e3:84:16:79:c7:95:09:e3: + 01:d2:15:a2:77:0b:8b:bf:94:04:e9:7f:c0:cd:e6:2e:64:cd: + 1e:a3:32:ec:11:cc:62:ce:c7:4e:cd:ad:48:5c:b1:b8:e9:76: + b3:f9 +-----BEGIN CERTIFICATE----- +MIIEDTCCA3agAwIBAgIBAjANBgkqhkiG9w0BAQUFADCBnjELMAkGA1UEBhMCVVMx +CzAJBgNVBAgTAkNBMRUwEwYDVQQHEwxTYW5GcmFuY2lzY28xETAPBgNVBAoTCEV2 +aWwgSW5jMREwDwYDVQQLEwhjaGFuZ2VtZTERMA8GA1UEAxMIY2hhbmdlbWUxETAP +BgNVBCkTCGNoYW5nZW1lMR8wHQYJKoZIhvcNAQkBFhBtYWlsQGhvc3QuZG9tYWlu +MB4XDTE0MDIyNDE3NTQ1OVoXDTI0MDIyMjE3NTQ1OVowgaAxCzAJBgNVBAYTAlVT +MQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUwEwYDVQQKEwxG +b3J0LUZ1bnN0b24xETAPBgNVBAsTCGNoYW5nZW1lMQ8wDQYDVQQDEwZjbGllbnQx +ETAPBgNVBCkTCGNoYW5nZW1lMR8wHQYJKoZIhvcNAQkBFhBtYWlsQGhvc3QuZG9t +YWluMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDo4iy41NuJUE9HHmjb9+TM +R0FjdQM3UHqoTSc21RUBCLbPVvdWbT354o0aXb+gJF4HVY7Q3PH6GYcd1rZYgi66 +aW3p2cgWDR1Zf/SOWBABPSEUFjzszYy3DuZ7d7T5kKUXAbuExrISh3Drn21P0GiL +lsDnC1G0nR17bHu+iWuIiwIDAQABo4IBVTCCAVEwCQYDVR0TBAIwADAtBglghkgB +hvhCAQ0EIBYeRWFzeS1SU0EgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQW +BBSe+EnQonYwXKsrirWNxkUfp/jPhTCB0wYDVR0jBIHLMIHIgBTcpfF2207Nju+x +I1YdkoCZdDvqb6GBpKSBoTCBnjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRUw +EwYDVQQHEwxTYW5GcmFuY2lzY28xETAPBgNVBAoTCEV2aWwgSW5jMREwDwYDVQQL +EwhjaGFuZ2VtZTERMA8GA1UEAxMIY2hhbmdlbWUxETAPBgNVBCkTCGNoYW5nZW1l +MR8wHQYJKoZIhvcNAQkBFhBtYWlsQGhvc3QuZG9tYWluggkA5yEeGEEbloMwEwYD +VR0lBAwwCgYIKwYBBQUHAwIwCwYDVR0PBAQDAgeAMA0GCSqGSIb3DQEBBQUAA4GB +AEh2wBj6Cu5OGuwCndSDypRUoT9RLz5LlcNCm3GgS9mvRyO5HPuFunbiCctlu9J9 +RD1LZ7qAg76o7cS56hobx1k72VwNRtjJkssQxfIaOKSqByzjhBZ5x5UJ4wHSFaJ3 +C4u/lATpf8DN5i5kzR6jMuwRzGLOx07NrUhcsbjpdrP5 +-----END CERTIFICATE----- diff --git a/integration/fixtures/https/client-rogue-key.pem b/integration/fixtures/https/client-rogue-key.pem new file mode 100644 index 0000000000..53c122ab70 --- /dev/null +++ b/integration/fixtures/https/client-rogue-key.pem @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAOjiLLjU24lQT0ce +aNv35MxHQWN1AzdQeqhNJzbVFQEIts9W91ZtPfnijRpdv6AkXgdVjtDc8foZhx3W +tliCLrppbenZyBYNHVl/9I5YEAE9IRQWPOzNjLcO5nt3tPmQpRcBu4TGshKHcOuf +bU/QaIuWwOcLUbSdHXtse76Ja4iLAgMBAAECgYADs+TmI2xCKKa6CL++D5jxrohZ +nnionnz0xBVFh+nHlG3jqgxQsXf0yydXLfpn/2wHTdLxezHVuiYt0UYg7iD0CglW ++IjcgMebzyjLeYqYOE5llPlMvhp2HoEMYJNb+7bRrZ1WCITbu+Su0w1cgA7Cs+Ej +VlfvGzN+qqnDThRUYQJBAPY0sMWZJKly8QhUmUvmcXdPczzSOf6Mm7gc5LR6wzxd +vW7syuqk50qjqVqFpN81vCV7GoDxRUWbTM9ftf7JGFkCQQDyJc/1RMygE2o+enU1 +6UBxJyclXITEYtDn8aoEpLNc7RakP1WoPUKjZOnjkcoKcIkFNkSPeCfQujrb5f3F +MkuDAkByAI/hzzmkpK5rFxEsjfX4Mve/L/DepyjrpaVY1IdWimlO1aJX6CeY7hNa +8QsYt/74s/nfvtg+lNyKIV1aLq9xAkB+WSSNgfyTeg3x08vc+Xxajmdqoz/TiQwg +OoTQL3A3iK5LvZBgXLasszcnOycFE3srcQmNItEDpGiZ3QPxJTEpAkEA45EE9NMJ +SA7EGWSFlbz4f4u4oBeiDiJRJbGGfAyVxZlpCWUjPpg9+swsWoFEOjnGYaChAMk5 +nrOdMf15T6QF7Q== +-----END PRIVATE KEY----- diff --git a/integration/fixtures/https/server-cert.pem b/integration/fixtures/https/server-cert.pem new file mode 100644 index 0000000000..08abfd1a3b --- /dev/null +++ b/integration/fixtures/https/server-cert.pem @@ -0,0 +1,76 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4 (0x4) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=changeme/name=changeme/emailAddress=mail@host.domain + Validity + Not Before: Dec 4 15:01:20 2013 GMT + Not After : Dec 2 15:01:20 2023 GMT + Subject: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=*/name=changeme/emailAddress=mail@host.domain + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:c1:ff:7d:30:6f:64:4a:b1:92:b1:71:d1:c1:74: + e2:1d:db:2d:11:24:e1:00:d4:00:ae:6f:c8:9e:ae: + 67:b3:4a:bd:f7:e6:9e:57:6d:19:4c:3c:23:94:2d: + 3d:d6:63:84:d8:fa:76:2b:38:12:c1:ed:20:9d:32: + e0:e8:c2:bf:9a:77:70:04:3f:7f:ca:8c:2c:82:d6: + 3d:25:5c:02:1a:4f:64:93:03:dd:9c:42:97:5e:09: + 49:af:f0:c2:e1:30:08:0e:21:46:95:d1:13:59:c0: + c8:76:be:94:0d:8b:43:67:21:33:b2:08:60:9d:76: + a8:05:32:1e:f9:95:09:14:75 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Cert Type: + SSL Server + Netscape Comment: + Easy-RSA Generated Server Certificate + X509v3 Subject Key Identifier: + 14:02:FD:FD:DD:13:38:E0:71:EA:D1:BE:C0:0E:89:1A:2D:B6:19:06 + X509v3 Authority Key Identifier: + keyid:66:EE:C3:17:3D:3D:AB:44:01:6B:6F:B2:99:19:BD:AA:02:B5:34:FB + DirName:/C=US/ST=CA/L=SanFrancisco/O=Fort-Funston/OU=changeme/CN=changeme/name=changeme/emailAddress=mail@host.domain + serial:FD:AB:EC:6A:84:27:04:A7 + + X509v3 Extended Key Usage: + TLS Web Server Authentication + X509v3 Key Usage: + Digital Signature, Key Encipherment + Signature Algorithm: sha1WithRSAEncryption + 40:0f:10:39:c4:b7:0f:0d:2f:bf:d2:16:cc:8e:d3:9a:fb:8b: + ce:4b:7b:0d:48:77:ce:f1:fe:d5:8f:ea:b1:71:ed:49:1d:9f: + 23:3a:16:d4:70:7c:c5:29:bf:e4:90:34:d0:f0:00:24:f4:e4: + df:2c:c3:83:01:66:61:c9:a8:ab:29:e7:98:6d:27:89:4a:76: + c9:2e:19:8e:fe:6e:d5:f8:99:11:0e:97:67:4b:34:e3:1e:e3: + 9f:35:00:a5:32:f9:b5:2c:f2:e0:c5:2e:cc:81:bd:18:dd:5c: + 12:c8:6b:fa:0c:17:74:30:55:f6:6e:20:9a:6c:1e:09:b4:0c: + 15:42 +-----BEGIN CERTIFICATE----- +MIIEKjCCA5OgAwIBAgIBBDANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMCVVMx +CzAJBgNVBAgTAkNBMRUwEwYDVQQHEwxTYW5GcmFuY2lzY28xFTATBgNVBAoTDEZv +cnQtRnVuc3RvbjERMA8GA1UECxMIY2hhbmdlbWUxETAPBgNVBAMTCGNoYW5nZW1l +MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRv +bWFpbjAeFw0xMzEyMDQxNTAxMjBaFw0yMzEyMDIxNTAxMjBaMIGbMQswCQYDVQQG +EwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMGA1UE +ChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTEKMAgGA1UEAxQBKjER +MA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21h +aW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMH/fTBvZEqxkrFx0cF04h3b +LREk4QDUAK5vyJ6uZ7NKvffmnldtGUw8I5QtPdZjhNj6dis4EsHtIJ0y4OjCv5p3 +cAQ/f8qMLILWPSVcAhpPZJMD3ZxCl14JSa/wwuEwCA4hRpXRE1nAyHa+lA2LQ2ch +M7IIYJ12qAUyHvmVCRR1AgMBAAGjggFzMIIBbzAJBgNVHRMEAjAAMBEGCWCGSAGG ++EIBAQQEAwIGQDA0BglghkgBhvhCAQ0EJxYlRWFzeS1SU0EgR2VuZXJhdGVkIFNl +cnZlciBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUFAL9/d0TOOBx6tG+wA6JGi22GQYw +gdcGA1UdIwSBzzCBzIAUZu7DFz09q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJ +BgNVBAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUw +EwYDVQQKEwxGb3J0LUZ1bnN0b24xETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQD +EwhjaGFuZ2VtZTERMA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1h +aWxAaG9zdC5kb21haW6CCQD9q+xqhCcEpzATBgNVHSUEDDAKBggrBgEFBQcDATAL +BgNVHQ8EBAMCBaAwDQYJKoZIhvcNAQEFBQADgYEAQA8QOcS3Dw0vv9IWzI7TmvuL +zkt7DUh3zvH+1Y/qsXHtSR2fIzoW1HB8xSm/5JA00PAAJPTk3yzDgwFmYcmoqynn +mG0niUp2yS4Zjv5u1fiZEQ6XZ0s04x7jnzUApTL5tSzy4MUuzIG9GN1cEshr+gwX +dDBV9m4gmmweCbQMFUI= +-----END CERTIFICATE----- diff --git a/integration/fixtures/https/server-key.pem b/integration/fixtures/https/server-key.pem new file mode 100644 index 0000000000..c269320ef0 --- /dev/null +++ b/integration/fixtures/https/server-key.pem @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMH/fTBvZEqxkrFx +0cF04h3bLREk4QDUAK5vyJ6uZ7NKvffmnldtGUw8I5QtPdZjhNj6dis4EsHtIJ0y +4OjCv5p3cAQ/f8qMLILWPSVcAhpPZJMD3ZxCl14JSa/wwuEwCA4hRpXRE1nAyHa+ +lA2LQ2chM7IIYJ12qAUyHvmVCRR1AgMBAAECgYAmwckb9RUfSwyYgLm8IYLPHiuJ +wkllZfVg5Bo7gXJcQnFjZmJ56uTj8xvUjZlODIHM63TSO5ibv6kFXtXKCqZGd2M+ +wGbhZ0f+2GvKcwMmJERnIQjuoNaYSQLT0tM0VB9Iz0rJlZC+tzPZ+5pPqEumRdsS +IzWNXfF42AhcbwAQYQJBAPVXtMYIJc9EZsz86ZcQiMPWUpCX5vnRmtwL8kKyR8D5 +4KfYeiowyFffSRMMcclwNHq7TgSXN+nIXM9WyzyzwikCQQDKbNA28AgZp9aT54HP +WnbeE2pmt+uk/zl/BtxJSoK6H+69Jec+lf7EgL7HgOWYRSNot4uQWu8IhsHLTiUq ++0FtAkEAqwlRxRy4/x24bP+D+QRV0/D97j93joFJbE4Hved7jlSlAV4xDGilwlyv +HNB4Iu5OJ6Gcaibhm+FKkmD3noHSwQJBAIpu3fokLzX0bS+bDFBU6qO3HXX/47xj ++tsfQvkwZrSI8AkU6c8IX0HdVhsz0FBRQAT2ORDQz1XCarfxykNZrwUCQQCGCBIc +BBCWzhHlswlGidWJg3HqqO6hPPClEr3B5G87oCsdeYwiO23XT6rUnoJXfJHp6oCW +5nCwDu5ZTP+khltg +-----END PRIVATE KEY----- diff --git a/integration/fixtures/https/server-rogue-cert.pem b/integration/fixtures/https/server-rogue-cert.pem new file mode 100644 index 0000000000..28feba6656 --- /dev/null +++ b/integration/fixtures/https/server-rogue-cert.pem @@ -0,0 +1,76 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3 (0x3) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=CA, L=SanFrancisco, O=Evil Inc, OU=changeme, CN=changeme/name=changeme/emailAddress=mail@host.domain + Validity + Not Before: Feb 28 18:49:31 2014 GMT + Not After : Feb 26 18:49:31 2024 GMT + Subject: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=localhost/name=changeme/emailAddress=mail@host.domain + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:d1:08:58:24:60:a1:69:65:4b:76:46:8f:88:75: + 7c:49:3a:d8:03:cc:5b:58:c5:d1:bb:e5:f9:54:b9: + 75:65:df:7e:bb:fb:54:d4:b2:e9:6f:58:a2:a4:84: + 43:94:77:24:81:38:36:36:f0:66:65:26:e5:5b:2a: + 14:1c:a9:ae:57:7f:75:00:23:14:4b:61:58:e4:82: + aa:15:97:94:bd:50:35:0d:5d:18:18:ed:10:6a:bb: + d3:64:5a:eb:36:98:5b:58:a7:fe:67:48:c1:6c:3f: + 51:2f:02:65:96:54:77:9b:34:f9:a7:d2:63:54:6a: + 9e:02:5c:be:65:98:a4:b4:b5 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Cert Type: + SSL Server + Netscape Comment: + Easy-RSA Generated Server Certificate + X509v3 Subject Key Identifier: + 1F:E0:57:CA:CB:76:C9:C4:86:B9:EA:69:17:C0:F3:51:CE:95:40:EC + X509v3 Authority Key Identifier: + keyid:DC:A5:F1:76:DB:4E:CD:8E:EF:B1:23:56:1D:92:80:99:74:3B:EA:6F + DirName:/C=US/ST=CA/L=SanFrancisco/O=Evil Inc/OU=changeme/CN=changeme/name=changeme/emailAddress=mail@host.domain + serial:E7:21:1E:18:41:1B:96:83 + + X509v3 Extended Key Usage: + TLS Web Server Authentication + X509v3 Key Usage: + Digital Signature, Key Encipherment + Signature Algorithm: sha1WithRSAEncryption + 04:93:0e:28:01:94:18:f0:8c:7c:d3:0c:ad:e9:b7:46:b1:30: + 65:ed:68:7c:8c:91:cd:1a:86:66:87:4a:4f:c0:97:bc:f7:85: + 4b:38:79:31:b2:65:88:b1:76:16:9e:80:93:38:f4:b9:eb:65: + 00:6d:bb:89:e0:a1:bf:95:5e:80:13:8e:01:73:d3:f1:08:73: + 85:a5:33:75:0b:42:8a:a3:07:09:35:ef:d7:c6:58:eb:60:a3: + 06:89:a0:53:99:e2:aa:41:90:e0:1a:d2:12:4b:48:7d:c3:9c: + ad:bd:0e:5e:5f:f7:09:0c:5d:7c:86:24:dd:92:d5:b3:14:06: + c7:9f +-----BEGIN CERTIFICATE----- +MIIEKjCCA5OgAwIBAgIBAzANBgkqhkiG9w0BAQUFADCBnjELMAkGA1UEBhMCVVMx +CzAJBgNVBAgTAkNBMRUwEwYDVQQHEwxTYW5GcmFuY2lzY28xETAPBgNVBAoTCEV2 +aWwgSW5jMREwDwYDVQQLEwhjaGFuZ2VtZTERMA8GA1UEAxMIY2hhbmdlbWUxETAP +BgNVBCkTCGNoYW5nZW1lMR8wHQYJKoZIhvcNAQkBFhBtYWlsQGhvc3QuZG9tYWlu +MB4XDTE0MDIyODE4NDkzMVoXDTI0MDIyNjE4NDkzMVowgaMxCzAJBgNVBAYTAlVT +MQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUwEwYDVQQKEwxG +b3J0LUZ1bnN0b24xETAPBgNVBAsTCGNoYW5nZW1lMRIwEAYDVQQDEwlsb2NhbGhv +c3QxETAPBgNVBCkTCGNoYW5nZW1lMR8wHQYJKoZIhvcNAQkBFhBtYWlsQGhvc3Qu +ZG9tYWluMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRCFgkYKFpZUt2Ro+I +dXxJOtgDzFtYxdG75flUuXVl3367+1TUsulvWKKkhEOUdySBODY28GZlJuVbKhQc +qa5Xf3UAIxRLYVjkgqoVl5S9UDUNXRgY7RBqu9NkWus2mFtYp/5nSMFsP1EvAmWW +VHebNPmn0mNUap4CXL5lmKS0tQIDAQABo4IBbzCCAWswCQYDVR0TBAIwADARBglg +hkgBhvhCAQEEBAMCBkAwNAYJYIZIAYb4QgENBCcWJUVhc3ktUlNBIEdlbmVyYXRl +ZCBTZXJ2ZXIgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFB/gV8rLdsnEhrnqaRfA81HO +lUDsMIHTBgNVHSMEgcswgciAFNyl8XbbTs2O77EjVh2SgJl0O+pvoYGkpIGhMIGe +MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNj +bzERMA8GA1UEChMIRXZpbCBJbmMxETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQD +EwhjaGFuZ2VtZTERMA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1h +aWxAaG9zdC5kb21haW6CCQDnIR4YQRuWgzATBgNVHSUEDDAKBggrBgEFBQcDATAL +BgNVHQ8EBAMCBaAwDQYJKoZIhvcNAQEFBQADgYEABJMOKAGUGPCMfNMMrem3RrEw +Ze1ofIyRzRqGZodKT8CXvPeFSzh5MbJliLF2Fp6Akzj0uetlAG27ieChv5VegBOO +AXPT8QhzhaUzdQtCiqMHCTXv18ZY62CjBomgU5niqkGQ4BrSEktIfcOcrb0OXl/3 +CQxdfIYk3ZLVsxQGx58= +-----END CERTIFICATE----- diff --git a/integration/fixtures/https/server-rogue-key.pem b/integration/fixtures/https/server-rogue-key.pem new file mode 100644 index 0000000000..10f7c65001 --- /dev/null +++ b/integration/fixtures/https/server-rogue-key.pem @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBANEIWCRgoWllS3ZG +j4h1fEk62APMW1jF0bvl+VS5dWXffrv7VNSy6W9YoqSEQ5R3JIE4NjbwZmUm5Vsq +FByprld/dQAjFEthWOSCqhWXlL1QNQ1dGBjtEGq702Ra6zaYW1in/mdIwWw/US8C +ZZZUd5s0+afSY1RqngJcvmWYpLS1AgMBAAECgYAJXh9dGfuB1qlIFqduDR3RxlJR +8UGSu+LHUeoXkuwg8aAjWoMVuSLe+5DmYIsKx0AajmNXmPRtyg1zRXJ7SltmubJ8 +6qQVDsRk6biMdkpkl6a9Gk2av40psD9/VPGxagEoop7IKYhf3AeKPvPiwVB2qFrl +1aYMZm0aMR55pgRajQJBAOk8IsJDf0beooDZXVdv/oe4hcbM9fxO8Cn3qzoGImqD +37LL+PCzDP7AEV3fk43SsZDeSk+LDX+h0o9nPyhzHasCQQDlb3aDgcQY9NaGLUWO +moOCB3148eBVcAwCocu+OSkf7sbQdvXxgThBOrZl11wwRIMQqh99c2yeUwj+tELl +3VcfAkBZTiNpCvtDIaBLge9RuZpWUXs3wec2cutWxnSTxSGMc25GQf/R+l0xdk2w +ChmvpktDUzpU9sN2aXn8WuY+EMX9AkEApbLpUbKPUELLB958RLA819TW/lkZXjrs +wZ3eSoR3ufM1rOqtVvyvBxUDE+wETWu9iHSFB5Ir2PA5J9JCGkbPmwJAFI1ndfBj +iuyU93nFX0p+JE2wVHKx4dMzKCearNKiJh/lGDtUq3REGgamTNUnG8RAITUbxFs+ +Z1hrIq8xYl2LOQ== +-----END PRIVATE KEY----- diff --git a/integration/https_test.go b/integration/https_test.go new file mode 100644 index 0000000000..a1c855e1a9 --- /dev/null +++ b/integration/https_test.go @@ -0,0 +1,82 @@ +package docker + +import ( + "crypto/tls" + "crypto/x509" + "github.com/dotcloud/docker/api" + "io/ioutil" + "testing" + "time" +) + +const ( + errBadCertificate = "remote error: bad certificate" + errCaUnknown = "x509: certificate signed by unknown authority" +) + +func getTlsConfig(certFile, keyFile string, t *testing.T) *tls.Config { + certPool := x509.NewCertPool() + file, err := ioutil.ReadFile("fixtures/https/ca.pem") + if err != nil { + t.Fatal(err) + } + certPool.AppendCertsFromPEM(file) + + cert, err := tls.LoadX509KeyPair("fixtures/https/"+certFile, "fixtures/https/"+keyFile) + if err != nil { + t.Fatalf("Couldn't load X509 key pair: %s", err) + } + tlsConfig := &tls.Config{ + RootCAs: certPool, + Certificates: []tls.Certificate{cert}, + } + return tlsConfig +} + +// TestHttpsInfo connects via two-way authenticated HTTPS to the info endpoint +func TestHttpsInfo(t *testing.T) { + cli := api.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, + testDaemonHttpsAddr, getTlsConfig("client-cert.pem", "client-key.pem", t)) + + setTimeout(t, "Reading command output time out", 10*time.Second, func() { + if err := cli.CmdInfo(); err != nil { + t.Fatal(err) + } + }) +} + +// TestHttpsInfoRogueCert connects via two-way authenticated HTTPS to the info endpoint +// by using a rogue client certificate and checks that it fails with the expected error. +func TestHttpsInfoRogueCert(t *testing.T) { + cli := api.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, + testDaemonHttpsAddr, getTlsConfig("client-rogue-cert.pem", "client-rogue-key.pem", t)) + + setTimeout(t, "Reading command output time out", 10*time.Second, func() { + err := cli.CmdInfo() + if err == nil { + t.Fatal("Expected error but got nil") + } + if err.Error() != errBadCertificate { + t.Fatalf("Expected error: %s, got instead: %s", errBadCertificate, err) + } + }) +} + +// TestHttpsInfoRogueServerCert connects via two-way authenticated HTTPS to the info endpoint +// which provides a rogue server certificate and checks that it fails with the expected error +func TestHttpsInfoRogueServerCert(t *testing.T) { + cli := api.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, + testDaemonRogueHttpsAddr, getTlsConfig("client-cert.pem", "client-key.pem", t)) + + setTimeout(t, "Reading command output time out", 10*time.Second, func() { + err := cli.CmdInfo() + if err == nil { + t.Fatal("Expected error but got nil") + } + + if err.Error() != errCaUnknown { + t.Fatalf("Expected error: %s, got instead: %s", errBadCertificate, err) + } + + }) +} diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 6003c89b51..07d0fc285b 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -24,21 +24,26 @@ import ( ) const ( - unitTestImageName = "docker-test-image" - unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 - unitTestImageIDShort = "83599e29c455" - unitTestNetworkBridge = "testdockbr0" - unitTestStoreBase = "/var/lib/docker/unit-tests" - testDaemonAddr = "127.0.0.1:4270" - testDaemonProto = "tcp" + unitTestImageName = "docker-test-image" + unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 + unitTestImageIDShort = "83599e29c455" + unitTestNetworkBridge = "testdockbr0" + unitTestStoreBase = "/var/lib/docker/unit-tests" + testDaemonAddr = "127.0.0.1:4270" + testDaemonProto = "tcp" + testDaemonHttpsProto = "tcp" + testDaemonHttpsAddr = "localhost:4271" + testDaemonRogueHttpsAddr = "localhost:4272" ) var ( // FIXME: globalRuntime is deprecated by globalEngine. All tests should be converted. - globalRuntime *docker.Runtime - globalEngine *engine.Engine - startFds int - startGoroutines int + globalRuntime *docker.Runtime + globalEngine *engine.Engine + globalHttpsEngine *engine.Engine + globalRogueHttpsEngine *engine.Engine + startFds int + startGoroutines int ) // FIXME: nuke() is deprecated by Runtime.Nuke() @@ -117,8 +122,10 @@ func init() { // (no tests are run directly in the base) setupBaseImage() - // Create the "global runtime" with a long-running daemon for integration tests + // Create the "global runtime" with a long-running daemons for integration tests spawnGlobalDaemon() + spawnLegitHttpsDaemon() + spawnRogueHttpsDaemon() startFds, startGoroutines = utils.GetTotalUsedFds(), runtime.NumGoroutine() } @@ -170,6 +177,61 @@ func spawnGlobalDaemon() { } } +func spawnLegitHttpsDaemon() { + if globalHttpsEngine != nil { + return + } + globalHttpsEngine = spawnHttpsDaemon(testDaemonHttpsAddr, "fixtures/https/ca.pem", + "fixtures/https/server-cert.pem", "fixtures/https/server-key.pem") +} + +func spawnRogueHttpsDaemon() { + if globalRogueHttpsEngine != nil { + return + } + globalRogueHttpsEngine = spawnHttpsDaemon(testDaemonRogueHttpsAddr, "fixtures/https/ca.pem", + "fixtures/https/server-rogue-cert.pem", "fixtures/https/server-rogue-key.pem") +} + +func spawnHttpsDaemon(addr, cacert, cert, key string) *engine.Engine { + t := log.New(os.Stderr, "", 0) + root, err := newTestDirectory(unitTestStoreBase) + if err != nil { + t.Fatal(err) + } + // FIXME: here we don't use NewTestEngine because it calls initserver with Autorestart=false, + // and we want to set it to true. + + eng := newTestEngine(t, true, root) + + // Spawn a Daemon + go func() { + utils.Debugf("Spawning https daemon for integration tests") + listenURL := &url.URL{ + Scheme: testDaemonHttpsProto, + Host: addr, + } + job := eng.Job("serveapi", listenURL.String()) + job.SetenvBool("Logging", true) + job.SetenvBool("Tls", true) + job.SetenvBool("TlsVerify", true) + job.Setenv("TlsCa", cacert) + job.Setenv("TlsCert", cert) + job.Setenv("TlsKey", key) + if err := job.Run(); err != nil { + log.Fatalf("Unable to spawn the test daemon: %s", err) + } + }() + + // Give some time to ListenAndServer to actually start + time.Sleep(time.Second) + + if err := eng.Job("acceptconnections").Run(); err != nil { + log.Fatalf("Unable to accept connections for test api: %s", err) + } + return eng +} + // FIXME: test that ImagePull(json=true) send correct json output func GetTestImage(runtime *docker.Runtime) *docker.Image { From 694c8e7dfca16fabf63e6fdcdce4151c8440a7a3 Mon Sep 17 00:00:00 2001 From: Scott Collier Date: Sat, 8 Mar 2014 17:23:06 -0600 Subject: [PATCH 022/384] Adding options to `docker restart` documentation URL of page is: http://docs.docker.io/en/latest/reference/commandline/cli/#restart Docker-DCO-1.1-Signed-off-by: Scott Collier (github: scollier) --- docs/sources/reference/commandline/cli.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 5b43e45eb4..6fe9b3dfea 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -991,6 +991,8 @@ The last container is marked as a ``Ghost`` container. It is a container that wa Restart a running container + -t, --time=10: Number of seconds to try to stop for before killing the container. Once killed it will then be restarted. Default=10 + .. _cli_rm: ``rm`` From df9b99aca0b8a65da866aa5696b9f45df3b92e50 Mon Sep 17 00:00:00 2001 From: Fabio Falci Date: Sun, 9 Mar 2014 01:49:36 +0000 Subject: [PATCH 023/384] Remove manual http cookie management Since docker uses cookiejar it doesn't need to manage cookies manually anymore. Managing cookie was duplicating it. Docker-DCO-1.1-Signed-off-by: Fabio Falci (github: fabiofalci) --- registry/registry.go | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index 543dcea383..cc2e985c31 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -149,20 +149,6 @@ func ExpandAndVerifyRegistryUrl(hostname string) (string, error) { return endpoint, nil } -func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) { - for _, cookie := range c.Jar.Cookies(req.URL) { - req.AddCookie(cookie) - } - res, err := c.Do(req) - if err != nil { - return nil, err - } - if len(res.Cookies()) > 0 { - c.Jar.SetCookies(req.URL, res.Cookies()) - } - return res, err -} - func setTokenAuth(req *http.Request, token []string) { if req.Header.Get("Authorization") == "" { // Don't override req.Header.Set("Authorization", "Token "+strings.Join(token, ",")) @@ -177,7 +163,7 @@ func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]s return nil, err } setTokenAuth(req, token) - res, err := doWithCookies(r.client, req) + res, err := r.client.Do(req) if err != nil { return nil, err } @@ -212,7 +198,7 @@ func (r *Registry) LookupRemoteImage(imgID, registry string, token []string) boo return false } setTokenAuth(req, token) - res, err := doWithCookies(r.client, req) + res, err := r.client.Do(req) if err != nil { utils.Errorf("Error in LookupRemoteImage %s", err) return false @@ -229,7 +215,7 @@ func (r *Registry) GetRemoteImageJSON(imgID, registry string, token []string) ([ return nil, -1, fmt.Errorf("Failed to download json: %s", err) } setTokenAuth(req, token) - res, err := doWithCookies(r.client, req) + res, err := r.client.Do(req) if err != nil { return nil, -1, fmt.Errorf("Failed to download json: %s", err) } @@ -256,7 +242,7 @@ func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string) ( return nil, fmt.Errorf("Error while getting from the server: %s\n", err) } setTokenAuth(req, token) - res, err := doWithCookies(r.client, req) + res, err := r.client.Do(req) if err != nil { return nil, err } @@ -282,7 +268,7 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [ return nil, err } setTokenAuth(req, token) - res, err := doWithCookies(r.client, req) + res, err := r.client.Do(req) if err != nil { return nil, err } @@ -388,7 +374,7 @@ func (r *Registry) PushImageChecksumRegistry(imgData *ImgData, registry string, req.Header.Set("X-Docker-Checksum", imgData.Checksum) req.Header.Set("X-Docker-Checksum-Payload", imgData.ChecksumPayload) - res, err := doWithCookies(r.client, req) + res, err := r.client.Do(req) if err != nil { return fmt.Errorf("Failed to upload metadata: %s", err) } @@ -424,7 +410,7 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis req.Header.Add("Content-type", "application/json") setTokenAuth(req, token) - res, err := doWithCookies(r.client, req) + res, err := r.client.Do(req) if err != nil { return fmt.Errorf("Failed to upload metadata: %s", err) } @@ -460,7 +446,7 @@ func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registr req.ContentLength = -1 req.TransferEncoding = []string{"chunked"} setTokenAuth(req, token) - res, err := doWithCookies(r.client, req) + res, err := r.client.Do(req) if err != nil { return "", "", fmt.Errorf("Failed to upload layer: %s", err) } @@ -497,7 +483,7 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token req.Header.Add("Content-type", "application/json") setTokenAuth(req, token) req.ContentLength = int64(len(revision)) - res, err := doWithCookies(r.client, req) + res, err := r.client.Do(req) if err != nil { return err } From b24be254fa042e62e1a08af9105ac92da13a6336 Mon Sep 17 00:00:00 2001 From: Rovanion Luckey Date: Fri, 7 Mar 2014 15:51:52 +0100 Subject: [PATCH 024/384] All caps variables in normal bash should be avoided not to accidentally collide with environment variables. Docker-DCO-1.1-Signed-off-by: Rovanion Luckey (github: Rovanion) --- docs/sources/examples/hello_world.rst | 32 +++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/sources/examples/hello_world.rst b/docs/sources/examples/hello_world.rst index 63362e7d7b..b8538debb9 100644 --- a/docs/sources/examples/hello_world.rst +++ b/docs/sources/examples/hello_world.rst @@ -52,8 +52,8 @@ This command will run a simple ``echo`` command, that will echo ``hello world`` **Explanation:** -- **"sudo"** execute the following commands as user *root* -- **"docker run"** run a command in a new container +- **"sudo"** execute the following commands as user *root* +- **"docker run"** run a command in a new container - **"busybox"** is the image we are running the command in. - **"/bin/echo"** is the command we want to run in the container - **"hello world"** is the input for the echo command @@ -67,9 +67,9 @@ See the example in action .. raw:: html @@ -92,7 +92,7 @@ we stop it. .. code-block:: bash - CONTAINER_ID=$(sudo docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done") + container_id=$(sudo docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done") We are going to run a simple hello world daemon in a new container made from the ``ubuntu`` image. @@ -104,22 +104,22 @@ made from the ``ubuntu`` image. - **"while true; do echo hello world; sleep 1; done"** is the mini script we want to run, that will just print hello world once a second until we stop it. -- **$CONTAINER_ID** the output of the run command will return a +- **$container_id** the output of the run command will return a container id, we can use in future commands to see what is going on with this process. .. code-block:: bash - sudo docker logs $CONTAINER_ID + sudo docker logs $container_id Check the logs make sure it is working correctly. - **"docker logs**" This will return the logs for a container -- **$CONTAINER_ID** The Id of the container we want the logs for. +- **$container_id** The Id of the container we want the logs for. .. code-block:: bash - sudo docker attach -sig-proxy=false $CONTAINER_ID + sudo docker attach -sig-proxy=false $container_id Attach to the container to see the results in real-time. @@ -127,7 +127,7 @@ Attach to the container to see the results in real-time. process to see what is going on. - **"-sig-proxy=false"** Do not forward signals to the container; allows us to exit the attachment using Control-C without stopping the container. -- **$CONTAINER_ID** The Id of the container we want to attach too. +- **$container_id** The Id of the container we want to attach too. Exit from the container attachment by pressing Control-C. @@ -141,12 +141,12 @@ Check the process list to make sure it is running. .. code-block:: bash - sudo docker stop $CONTAINER_ID + sudo docker stop $container_id Stop the container, since we don't need it anymore. - **"docker stop"** This stops a container -- **$CONTAINER_ID** The Id of the container we want to stop. +- **$container_id** The Id of the container we want to stop. .. code-block:: bash @@ -162,9 +162,9 @@ See the example in action .. raw:: html From 12bd83182dff24a800eee6e7e93beeab30b480b7 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 7 Mar 2014 15:22:23 -0800 Subject: [PATCH 025/384] Move daemon config into sub pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- config.go => daemonconfig/config.go | 8 ++++---- runtime.go | 7 ++++--- server.go | 5 +++-- 3 files changed, 11 insertions(+), 9 deletions(-) rename config.go => daemonconfig/config.go (93%) diff --git a/config.go b/daemonconfig/config.go similarity index 93% rename from config.go rename to daemonconfig/config.go index 19aad9ed4a..0aee7e78ba 100644 --- a/config.go +++ b/daemonconfig/config.go @@ -1,4 +1,4 @@ -package docker +package daemonconfig import ( "net" @@ -13,7 +13,7 @@ const ( ) // FIXME: separate runtime configuration from http api configuration -type DaemonConfig struct { +type Config struct { Pidfile string Root string AutoRestart bool @@ -32,8 +32,8 @@ type DaemonConfig struct { // ConfigFromJob creates and returns a new DaemonConfig object // by parsing the contents of a job's environment. -func DaemonConfigFromJob(job *engine.Job) *DaemonConfig { - config := &DaemonConfig{ +func ConfigFromJob(job *engine.Job) *Config { + config := &Config{ Pidfile: job.Getenv("Pidfile"), Root: job.Getenv("Root"), AutoRestart: job.GetenvBool("AutoRestart"), diff --git a/runtime.go b/runtime.go index 84f11e87b2..d1aeef4f97 100644 --- a/runtime.go +++ b/runtime.go @@ -4,6 +4,7 @@ import ( "container/list" "fmt" "github.com/dotcloud/docker/archive" + "github.com/dotcloud/docker/daemonconfig" "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/execdriver" @@ -53,7 +54,7 @@ type Runtime struct { volumes *Graph srv *Server eng *engine.Engine - config *DaemonConfig + config *daemonconfig.Config containerGraph *graphdb.Database driver graphdriver.Driver execDriver execdriver.Driver @@ -624,7 +625,7 @@ func (runtime *Runtime) RegisterLink(parent, child *Container, alias string) err } // FIXME: harmonize with NewGraph() -func NewRuntime(config *DaemonConfig, eng *engine.Engine) (*Runtime, error) { +func NewRuntime(config *daemonconfig.Config, eng *engine.Engine) (*Runtime, error) { runtime, err := NewRuntimeFromDirectory(config, eng) if err != nil { return nil, err @@ -632,7 +633,7 @@ func NewRuntime(config *DaemonConfig, eng *engine.Engine) (*Runtime, error) { return runtime, nil } -func NewRuntimeFromDirectory(config *DaemonConfig, eng *engine.Engine) (*Runtime, error) { +func NewRuntimeFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (*Runtime, error) { // Set the default driver graphdriver.DefaultDriver = config.GraphDriver diff --git a/server.go b/server.go index d824d78d7a..70ee7f241b 100644 --- a/server.go +++ b/server.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/auth" + "github.com/dotcloud/docker/daemonconfig" "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/pkg/graphdb" @@ -34,7 +35,7 @@ import ( // The signals SIGINT, SIGQUIT and SIGTERM are intercepted for cleanup. func InitServer(job *engine.Job) engine.Status { job.Logf("Creating server") - srv, err := NewServer(job.Eng, DaemonConfigFromJob(job)) + srv, err := NewServer(job.Eng, daemonconfig.ConfigFromJob(job)) if err != nil { return job.Error(err) } @@ -2318,7 +2319,7 @@ func (srv *Server) ContainerCopy(job *engine.Job) engine.Status { return job.Errorf("No such container: %s", name) } -func NewServer(eng *engine.Engine, config *DaemonConfig) (*Server, error) { +func NewServer(eng *engine.Engine, config *daemonconfig.Config) (*Server, error) { runtime, err := NewRuntime(config, eng) if err != nil { return nil, err From 82a5439835b0bff3ab3dfb169415948dae504d56 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 7 Mar 2014 17:36:47 -0800 Subject: [PATCH 026/384] Move image into sub pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 3 +- graph.go | 59 ++++++++++++++++++----------------- image/graph.go | 11 +++++++ image.go => image/image.go | 52 ++++++++---------------------- integration/api_test.go | 3 +- integration/buildfile_test.go | 3 +- integration/commands_test.go | 3 +- integration/graph_test.go | 39 ++++++++++++----------- integration/runtime_test.go | 3 +- runtime.go | 5 +-- server.go | 29 ++++++++--------- tags.go | 5 +-- tags_unit_test.go | 3 +- utils/utils.go | 29 +++++++++++++++++ 14 files changed, 136 insertions(+), 111 deletions(-) create mode 100644 image/graph.go rename image.go => image/image.go (86%) diff --git a/container.go b/container.go index 50332f27de..9c1a28c98a 100644 --- a/container.go +++ b/container.go @@ -8,6 +8,7 @@ import ( "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/links" "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/runconfig" @@ -992,7 +993,7 @@ func (container *Container) Changes() ([]archive.Change, error) { return container.runtime.Changes(container) } -func (container *Container) GetImage() (*Image, error) { +func (container *Container) GetImage() (*image.Image, error) { if container.runtime == nil { return nil, fmt.Errorf("Can't get image of unregistered container") } diff --git a/graph.go b/graph.go index 43af2c278a..d164760d4c 100644 --- a/graph.go +++ b/graph.go @@ -5,6 +5,7 @@ import ( "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" "io" @@ -79,20 +80,20 @@ func (graph *Graph) Exists(id string) bool { } // Get returns the image with the given id, or an error if the image doesn't exist. -func (graph *Graph) Get(name string) (*Image, error) { +func (graph *Graph) Get(name string) (*image.Image, error) { id, err := graph.idIndex.Get(name) if err != nil { return nil, err } // FIXME: return nil when the image doesn't exist, instead of an error - img, err := LoadImage(graph.imageRoot(id)) + img, err := image.LoadImage(graph.ImageRoot(id)) if err != nil { return nil, err } if img.ID != id { return nil, fmt.Errorf("Image stored at '%s' has wrong id '%s'", id, img.ID) } - img.graph = graph + img.SetGraph(graph) if img.Size < 0 { rootfs, err := graph.driver.Get(img.ID) @@ -119,7 +120,7 @@ func (graph *Graph) Get(name string) (*Image, error) { } img.Size = size - if err := img.SaveSize(graph.imageRoot(id)); err != nil { + if err := img.SaveSize(graph.ImageRoot(id)); err != nil { return nil, err } } @@ -127,9 +128,9 @@ func (graph *Graph) Get(name string) (*Image, error) { } // Create creates a new image and registers it in the graph. -func (graph *Graph) Create(layerData archive.ArchiveReader, container *Container, comment, author string, config *runconfig.Config) (*Image, error) { - img := &Image{ - ID: GenerateID(), +func (graph *Graph) Create(layerData archive.ArchiveReader, container *Container, comment, author string, config *runconfig.Config) (*image.Image, error) { + img := &image.Image{ + ID: utils.GenerateRandomID(), Comment: comment, Created: time.Now().UTC(), DockerVersion: dockerversion.VERSION, @@ -151,7 +152,7 @@ func (graph *Graph) Create(layerData archive.ArchiveReader, container *Container // Register imports a pre-existing image into the graph. // FIXME: pass img as first argument -func (graph *Graph) Register(jsonData []byte, layerData archive.ArchiveReader, img *Image) (err error) { +func (graph *Graph) Register(jsonData []byte, layerData archive.ArchiveReader, img *image.Image) (err error) { defer func() { // If any error occurs, remove the new dir from the driver. // Don't check for errors since the dir might not have been created. @@ -160,7 +161,7 @@ func (graph *Graph) Register(jsonData []byte, layerData archive.ArchiveReader, i graph.driver.Remove(img.ID) } }() - if err := ValidateID(img.ID); err != nil { + if err := utils.ValidateID(img.ID); err != nil { return err } // (This is a convenience to save time. Race conditions are taken care of by os.Rename) @@ -171,7 +172,7 @@ func (graph *Graph) Register(jsonData []byte, layerData archive.ArchiveReader, i // Ensure that the image root does not exist on the filesystem // when it is not registered in the graph. // This is common when you switch from one graph driver to another - if err := os.RemoveAll(graph.imageRoot(img.ID)); err != nil && !os.IsNotExist(err) { + if err := os.RemoveAll(graph.ImageRoot(img.ID)); err != nil && !os.IsNotExist(err) { return err } @@ -197,12 +198,12 @@ func (graph *Graph) Register(jsonData []byte, layerData archive.ArchiveReader, i return fmt.Errorf("Driver %s failed to get image rootfs %s: %s", graph.driver, img.ID, err) } defer graph.driver.Put(img.ID) - img.graph = graph - if err := StoreImage(img, jsonData, layerData, tmp, rootfs); err != nil { + img.SetGraph(graph) + if err := image.StoreImage(img, jsonData, layerData, tmp, rootfs); err != nil { return err } // Commit - if err := os.Rename(tmp, graph.imageRoot(img.ID)); err != nil { + if err := os.Rename(tmp, graph.ImageRoot(img.ID)); err != nil { return err } graph.idIndex.Add(img.ID) @@ -233,7 +234,7 @@ func (graph *Graph) TempLayerArchive(id string, compression archive.Compression, // Mktemp creates a temporary sub-directory inside the graph's filesystem. func (graph *Graph) Mktemp(id string) (string, error) { - dir := path.Join(graph.Root, "_tmp", GenerateID()) + dir := path.Join(graph.Root, "_tmp", utils.GenerateRandomID()) if err := os.MkdirAll(dir, 0700); err != nil { return "", err } @@ -320,7 +321,7 @@ func (graph *Graph) Delete(name string) error { return err } graph.idIndex.Delete(id) - err = os.Rename(graph.imageRoot(id), tmp) + err = os.Rename(graph.ImageRoot(id), tmp) if err != nil { return err } @@ -331,9 +332,9 @@ func (graph *Graph) Delete(name string) error { } // Map returns a list of all images in the graph, addressable by ID. -func (graph *Graph) Map() (map[string]*Image, error) { - images := make(map[string]*Image) - err := graph.walkAll(func(image *Image) { +func (graph *Graph) Map() (map[string]*image.Image, error) { + images := make(map[string]*image.Image) + err := graph.walkAll(func(image *image.Image) { images[image.ID] = image }) if err != nil { @@ -344,7 +345,7 @@ func (graph *Graph) Map() (map[string]*Image, error) { // walkAll iterates over each image in the graph, and passes it to a handler. // The walking order is undetermined. -func (graph *Graph) walkAll(handler func(*Image)) error { +func (graph *Graph) walkAll(handler func(*image.Image)) error { files, err := ioutil.ReadDir(graph.Root) if err != nil { return err @@ -364,17 +365,17 @@ func (graph *Graph) walkAll(handler func(*Image)) error { // If an image of id ID has 3 children images, then the value for key ID // will be a list of 3 images. // If an image has no children, it will not have an entry in the table. -func (graph *Graph) ByParent() (map[string][]*Image, error) { - byParent := make(map[string][]*Image) - err := graph.walkAll(func(image *Image) { - parent, err := graph.Get(image.Parent) +func (graph *Graph) ByParent() (map[string][]*image.Image, error) { + byParent := make(map[string][]*image.Image) + err := graph.walkAll(func(img *image.Image) { + parent, err := graph.Get(img.Parent) if err != nil { return } if children, exists := byParent[parent.ID]; exists { - byParent[parent.ID] = append(children, image) + byParent[parent.ID] = append(children, img) } else { - byParent[parent.ID] = []*Image{image} + byParent[parent.ID] = []*image.Image{img} } }) return byParent, err @@ -382,13 +383,13 @@ func (graph *Graph) ByParent() (map[string][]*Image, error) { // Heads returns all heads in the graph, keyed by id. // A head is an image which is not the parent of another image in the graph. -func (graph *Graph) Heads() (map[string]*Image, error) { - heads := make(map[string]*Image) +func (graph *Graph) Heads() (map[string]*image.Image, error) { + heads := make(map[string]*image.Image) byParent, err := graph.ByParent() if err != nil { return nil, err } - err = graph.walkAll(func(image *Image) { + err = graph.walkAll(func(image *image.Image) { // If it's not in the byParent lookup table, then // it's not a parent -> so it's a head! if _, exists := byParent[image.ID]; !exists { @@ -398,7 +399,7 @@ func (graph *Graph) Heads() (map[string]*Image, error) { return heads, err } -func (graph *Graph) imageRoot(id string) string { +func (graph *Graph) ImageRoot(id string) string { return path.Join(graph.Root, id) } diff --git a/image/graph.go b/image/graph.go new file mode 100644 index 0000000000..857c09edd9 --- /dev/null +++ b/image/graph.go @@ -0,0 +1,11 @@ +package image + +import ( + "github.com/dotcloud/docker/graphdriver" +) + +type Graph interface { + Get(id string) (*Image, error) + ImageRoot(id string) string + Driver() graphdriver.Driver +} diff --git a/image.go b/image/image.go similarity index 86% rename from image.go rename to image/image.go index fa5b65787c..e091879049 100644 --- a/image.go +++ b/image/image.go @@ -1,20 +1,16 @@ -package docker +package image import ( - "crypto/rand" - "encoding/hex" "encoding/json" "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" - "io" "io/ioutil" "os" "path" "strconv" - "strings" "time" ) @@ -30,8 +26,9 @@ type Image struct { Config *runconfig.Config `json:"config,omitempty"` Architecture string `json:"architecture,omitempty"` OS string `json:"os,omitempty"` - graph *Graph Size int64 + + graph Graph } func LoadImage(root string) (*Image, error) { @@ -45,7 +42,7 @@ func LoadImage(root string) (*Image, error) { if err := json.Unmarshal(jsonData, img); err != nil { return nil, err } - if err := ValidateID(img.ID); err != nil { + if err := utils.ValidateID(img.ID); err != nil { return nil, err } @@ -72,7 +69,7 @@ func StoreImage(img *Image, jsonData []byte, layerData archive.ArchiveReader, ro var ( size int64 err error - driver = img.graph.driver + driver = img.graph.Driver() ) if err := os.MkdirAll(layer, 0755); err != nil { return err @@ -136,6 +133,10 @@ func StoreImage(img *Image, jsonData []byte, layerData archive.ArchiveReader, ro return nil } +func (img *Image) SetGraph(graph Graph) { + img.graph = graph +} + // SaveSize stores the current `size` value of `img` in the directory `root`. func (img *Image) SaveSize(root string) error { if err := ioutil.WriteFile(path.Join(root, "layersize"), []byte(strconv.Itoa(int(img.Size))), 0600); err != nil { @@ -153,7 +154,7 @@ func (img *Image) TarLayer() (arch archive.Archive, err error) { if img.graph == nil { return nil, fmt.Errorf("Can't load storage driver for unregistered image %s", img.ID) } - driver := img.graph.driver + driver := img.graph.Driver() if differ, ok := driver.(graphdriver.Differ); ok { return differ.Diff(img.ID) } @@ -201,33 +202,6 @@ func (img *Image) TarLayer() (arch archive.Archive, err error) { }), nil } -func ValidateID(id string) error { - if id == "" { - return fmt.Errorf("Image id can't be empty") - } - if strings.Contains(id, ":") { - return fmt.Errorf("Invalid character in image id: ':'") - } - return nil -} - -func GenerateID() string { - for { - id := make([]byte, 32) - if _, err := io.ReadFull(rand.Reader, id); err != nil { - panic(err) // This shouldn't happen - } - value := hex.EncodeToString(id) - // if we try to parse the truncated for as an int and we don't have - // an error then the value is all numberic and causes issues when - // used as a hostname. ref #3869 - if _, err := strconv.Atoi(utils.TruncateID(value)); err == nil { - continue - } - return value - } -} - // Image includes convenience proxy functions to its graph // These functions will return an error if the image is not registered // (ie. if image.graph == nil) @@ -274,16 +248,16 @@ func (img *Image) root() (string, error) { if img.graph == nil { return "", fmt.Errorf("Can't lookup root of unregistered image") } - return img.graph.imageRoot(img.ID), nil + return img.graph.ImageRoot(img.ID), nil } -func (img *Image) getParentsSize(size int64) int64 { +func (img *Image) GetParentsSize(size int64) int64 { parentImage, err := img.GetParent() if err != nil || parentImage == nil { return size } size += parentImage.Size - return parentImage.getParentsSize(size) + return parentImage.GetParentsSize(size) } // Depth returns the number of parents for a diff --git a/integration/api_test.go b/integration/api_test.go index cb92d89858..c050b4934d 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -9,6 +9,7 @@ import ( "github.com/dotcloud/docker/api" "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" @@ -287,7 +288,7 @@ func TestGetImagesByName(t *testing.T) { } assertHttpNotError(r, t) - img := &docker.Image{} + img := &image.Image{} if err := json.Unmarshal(r.Body.Bytes(), img); err != nil { t.Fatal(err) } diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index efab9707ec..e5084d4355 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -5,6 +5,7 @@ import ( "github.com/dotcloud/docker" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/utils" "io/ioutil" "net" @@ -350,7 +351,7 @@ func TestBuild(t *testing.T) { } } -func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, useCache bool) (*docker.Image, error) { +func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, useCache bool) (*image.Image, error) { if eng == nil { eng = NewTestEngine(t) runtime := mkRuntimeFromEngine(eng, t) diff --git a/integration/commands_test.go b/integration/commands_test.go index 9f7a41384c..6d3ac86347 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -6,6 +6,7 @@ import ( "github.com/dotcloud/docker" "github.com/dotcloud/docker/api" "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/pkg/term" "github.com/dotcloud/docker/utils" "io" @@ -902,7 +903,7 @@ func TestImagesTree(t *testing.T) { }) } -func buildTestImages(t *testing.T, eng *engine.Engine) *docker.Image { +func buildTestImages(t *testing.T, eng *engine.Engine) *image.Image { var testBuilder = testContextTemplate{ ` diff --git a/integration/graph_test.go b/integration/graph_test.go index ff1c0d9361..4fd612b5ac 100644 --- a/integration/graph_test.go +++ b/integration/graph_test.go @@ -6,6 +6,7 @@ import ( "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -67,8 +68,8 @@ func TestInterruptedRegister(t *testing.T) { graph, _ := tempGraph(t) defer nukeGraph(graph) badArchive, w := io.Pipe() // Use a pipe reader as a fake archive which never yields data - image := &docker.Image{ - ID: docker.GenerateID(), + image := &image.Image{ + ID: utils.GenerateRandomID(), Comment: "testing", Created: time.Now(), } @@ -96,18 +97,18 @@ func TestGraphCreate(t *testing.T) { if err != nil { t.Fatal(err) } - image, err := graph.Create(archive, nil, "Testing", "", nil) + img, err := graph.Create(archive, nil, "Testing", "", nil) if err != nil { t.Fatal(err) } - if err := docker.ValidateID(image.ID); err != nil { + if err := utils.ValidateID(img.ID); err != nil { t.Fatal(err) } - if image.Comment != "Testing" { - t.Fatalf("Wrong comment: should be '%s', not '%s'", "Testing", image.Comment) + if img.Comment != "Testing" { + t.Fatalf("Wrong comment: should be '%s', not '%s'", "Testing", img.Comment) } - if image.DockerVersion != dockerversion.VERSION { - t.Fatalf("Wrong docker_version: should be '%s', not '%s'", dockerversion.VERSION, image.DockerVersion) + if img.DockerVersion != dockerversion.VERSION { + t.Fatalf("Wrong docker_version: should be '%s', not '%s'", dockerversion.VERSION, img.DockerVersion) } images, err := graph.Map() if err != nil { @@ -115,8 +116,8 @@ func TestGraphCreate(t *testing.T) { } else if l := len(images); l != 1 { t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l) } - if images[image.ID] == nil { - t.Fatalf("Could not find image with id %s", image.ID) + if images[img.ID] == nil { + t.Fatalf("Could not find image with id %s", img.ID) } } @@ -127,8 +128,8 @@ func TestRegister(t *testing.T) { if err != nil { t.Fatal(err) } - image := &docker.Image{ - ID: docker.GenerateID(), + image := &image.Image{ + ID: utils.GenerateRandomID(), Comment: "testing", Created: time.Now(), } @@ -164,7 +165,7 @@ func TestDeletePrefix(t *testing.T) { assertNImages(graph, t, 0) } -func createTestImage(graph *docker.Graph, t *testing.T) *docker.Image { +func createTestImage(graph *docker.Graph, t *testing.T) *image.Image { archive, err := fakeTar() if err != nil { t.Fatal(err) @@ -243,20 +244,20 @@ func TestByParent(t *testing.T) { graph, _ := tempGraph(t) defer nukeGraph(graph) - parentImage := &docker.Image{ - ID: docker.GenerateID(), + parentImage := &image.Image{ + ID: utils.GenerateRandomID(), Comment: "parent", Created: time.Now(), Parent: "", } - childImage1 := &docker.Image{ - ID: docker.GenerateID(), + childImage1 := &image.Image{ + ID: utils.GenerateRandomID(), Comment: "child1", Created: time.Now(), Parent: parentImage.ID, } - childImage2 := &docker.Image{ - ID: docker.GenerateID(), + childImage2 := &image.Image{ + ID: utils.GenerateRandomID(), Comment: "child2", Created: time.Now(), Parent: parentImage.ID, diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 1e912c1bb4..a79f84365a 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/dotcloud/docker" "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/sysinit" @@ -172,7 +173,7 @@ func spawnGlobalDaemon() { // FIXME: test that ImagePull(json=true) send correct json output -func GetTestImage(runtime *docker.Runtime) *docker.Image { +func GetTestImage(runtime *docker.Runtime) *image.Image { imgs, err := runtime.Graph().Map() if err != nil { log.Fatalf("Unable to get the test image: %s", err) diff --git a/runtime.go b/runtime.go index d1aeef4f97..81bc9cbded 100644 --- a/runtime.go +++ b/runtime.go @@ -15,6 +15,7 @@ import ( _ "github.com/dotcloud/docker/graphdriver/btrfs" _ "github.com/dotcloud/docker/graphdriver/devmapper" _ "github.com/dotcloud/docker/graphdriver/vfs" + "github.com/dotcloud/docker/image" _ "github.com/dotcloud/docker/networkdriver/lxc" "github.com/dotcloud/docker/networkdriver/portallocator" "github.com/dotcloud/docker/pkg/graphdb" @@ -396,7 +397,7 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe } // Generate id - id := GenerateID() + id := utils.GenerateRandomID() if name == "" { name, err = generateRandomName(runtime) @@ -539,7 +540,7 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe // Commit creates a new filesystem image from the current state of a container. // The image can optionally be tagged into a repository -func (runtime *Runtime) Commit(container *Container, repository, tag, comment, author string, config *runconfig.Config) (*Image, error) { +func (runtime *Runtime) Commit(container *Container, repository, tag, comment, author string, config *runconfig.Config) (*image.Image, error) { // FIXME: freeze the container before copying it to avoid data corruption? // FIXME: this shouldn't be in commands. if err := container.Mount(); err != nil { diff --git a/server.go b/server.go index 70ee7f241b..37402ee502 100644 --- a/server.go +++ b/server.go @@ -8,6 +8,7 @@ import ( "github.com/dotcloud/docker/daemonconfig" "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/pkg/graphdb" "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/runconfig" @@ -362,8 +363,8 @@ func (srv *Server) ImageExport(job *engine.Job) engine.Status { return engine.StatusOK } -func (srv *Server) exportImage(image *Image, tempdir string) error { - for i := image; i != nil; { +func (srv *Server) exportImage(img *image.Image, tempdir string) error { + for i := img; i != nil; { // temporary directory tmpImageDir := path.Join(tempdir, i.ID) if err := os.Mkdir(tmpImageDir, os.ModeDir); err != nil { @@ -580,7 +581,7 @@ func (srv *Server) recursiveLoad(address, tmpImageDir string) error { utils.Debugf("Error reading embedded tar", err) return err } - img, err := NewImgJSON(imageJson) + img, err := image.NewImgJSON(imageJson) if err != nil { utils.Debugf("Error unmarshalling json", err) return err @@ -690,7 +691,7 @@ func (srv *Server) ImagesViz(job *engine.Job) engine.Status { job.Stdout.Write([]byte("digraph docker {\n")) var ( - parentImage *Image + parentImage *image.Image err error ) for _, image := range images { @@ -722,7 +723,7 @@ func (srv *Server) ImagesViz(job *engine.Job) engine.Status { func (srv *Server) Images(job *engine.Job) engine.Status { var ( - allImages map[string]*Image + allImages map[string]*image.Image err error ) if job.GetenvBool("all") { @@ -757,7 +758,7 @@ func (srv *Server) Images(job *engine.Job) engine.Status { out.Set("Id", image.ID) out.SetInt64("Created", image.Created.Unix()) out.SetInt64("Size", image.Size) - out.SetInt64("VirtualSize", image.getParentsSize(0)+image.Size) + out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size) lookup[id] = out } @@ -778,7 +779,7 @@ func (srv *Server) Images(job *engine.Job) engine.Status { out.Set("Id", image.ID) out.SetInt64("Created", image.Created.Unix()) out.SetInt64("Size", image.Size) - out.SetInt64("VirtualSize", image.getParentsSize(0)+image.Size) + out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size) outs.Add(out) } } @@ -838,7 +839,7 @@ func (srv *Server) ImageHistory(job *engine.Job) engine.Status { return job.Errorf("Usage: %s IMAGE", job.Name) } name := job.Args[0] - image, err := srv.runtime.repositories.LookupImage(name) + foundImage, err := srv.runtime.repositories.LookupImage(name) if err != nil { return job.Error(err) } @@ -855,7 +856,7 @@ func (srv *Server) ImageHistory(job *engine.Job) engine.Status { } outs := engine.NewTable("Created", 0) - err = image.WalkHistory(func(img *Image) error { + err = foundImage.WalkHistory(func(img *image.Image) error { out := &engine.Env{} out.Set("Id", img.ID) out.SetInt64("Created", img.Created.Unix()) @@ -1098,7 +1099,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin // FIXME: Keep going in case of error? return err } - img, err := NewImgJSON(imgJSON) + img, err := image.NewImgJSON(imgJSON) if err != nil { out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil)) return fmt.Errorf("Failed to parse json: %s", err) @@ -1946,7 +1947,7 @@ func (srv *Server) canDeleteImage(imgID string) error { return err } - if err := parent.WalkHistory(func(p *Image) error { + if err := parent.WalkHistory(func(p *image.Image) error { if imgID == p.ID { return fmt.Errorf("Conflict, cannot delete %s because the container %s is using it", utils.TruncateID(imgID), utils.TruncateID(container.ID)) } @@ -1958,7 +1959,7 @@ func (srv *Server) canDeleteImage(imgID string) error { return nil } -func (srv *Server) ImageGetCached(imgID string, config *runconfig.Config) (*Image, error) { +func (srv *Server) ImageGetCached(imgID string, config *runconfig.Config) (*image.Image, error) { // Retrieve all images images, err := srv.runtime.graph.Map() @@ -1976,7 +1977,7 @@ func (srv *Server) ImageGetCached(imgID string, config *runconfig.Config) (*Imag } // Loop on the children of the given image and check the config - var match *Image + var match *image.Image for elem := range imageMap[imgID] { img, err := srv.runtime.graph.Get(elem) if err != nil { @@ -2242,7 +2243,7 @@ func (srv *Server) ContainerInspect(name string) (*Container, error) { return nil, fmt.Errorf("No such container: %s", name) } -func (srv *Server) ImageInspect(name string) (*Image, error) { +func (srv *Server) ImageInspect(name string) (*image.Image, error) { if image, err := srv.runtime.repositories.LookupImage(name); err == nil && image != nil { return image, nil } diff --git a/tags.go b/tags.go index 92c32b1ff5..27e19cd671 100644 --- a/tags.go +++ b/tags.go @@ -3,6 +3,7 @@ package docker import ( "encoding/json" "fmt" + "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/utils" "io/ioutil" "os" @@ -65,7 +66,7 @@ func (store *TagStore) Reload() error { return nil } -func (store *TagStore) LookupImage(name string) (*Image, error) { +func (store *TagStore) LookupImage(name string) (*image.Image, error) { // FIXME: standardize on returning nil when the image doesn't exist, and err for everything else // (so we can pass all errors here) repos, tag := utils.ParseRepositoryTag(name) @@ -195,7 +196,7 @@ func (store *TagStore) Get(repoName string) (Repository, error) { return nil, nil } -func (store *TagStore) GetImage(repoName, tagOrID string) (*Image, error) { +func (store *TagStore) GetImage(repoName, tagOrID string) (*image.Image, error) { repo, err := store.Get(repoName) if err != nil { return nil, err diff --git a/tags_unit_test.go b/tags_unit_test.go index b6236280a8..8ee913f527 100644 --- a/tags_unit_test.go +++ b/tags_unit_test.go @@ -2,6 +2,7 @@ package docker import ( "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/utils" "os" "path" @@ -30,7 +31,7 @@ func mkTestTagStore(root string, t *testing.T) *TagStore { if err != nil { t.Fatal(err) } - img := &Image{ID: testImageID} + img := &image.Image{ID: testImageID} // FIXME: this fails on Darwin with: // tags_unit_test.go:36: mkdir /var/folders/7g/b3ydb5gx4t94ndr_cljffbt80000gq/T/docker-test569b-tRunner-075013689/vfs/dir/foo/etc/postgres: permission denied if err := graph.Register(nil, archive, img); err != nil { diff --git a/utils/utils.go b/utils/utils.go index 07b8f6a3d0..e4cb04f39c 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -2,6 +2,7 @@ package utils import ( "bytes" + "crypto/rand" "crypto/sha1" "crypto/sha256" "encoding/hex" @@ -493,6 +494,34 @@ func TruncateID(id string) string { return id[:shortLen] } +// GenerateRandomID returns an unique id +func GenerateRandomID() string { + for { + id := make([]byte, 32) + if _, err := io.ReadFull(rand.Reader, id); err != nil { + panic(err) // This shouldn't happen + } + value := hex.EncodeToString(id) + // if we try to parse the truncated for as an int and we don't have + // an error then the value is all numberic and causes issues when + // used as a hostname. ref #3869 + if _, err := strconv.Atoi(TruncateID(value)); err == nil { + continue + } + return value + } +} + +func ValidateID(id string) error { + if id == "" { + return fmt.Errorf("Id can't be empty") + } + if strings.Contains(id, ":") { + return fmt.Errorf("Invalid character in id: ':'") + } + return nil +} + // Code c/c from io.Copy() modified to handle escape sequence func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) { buf := make([]byte, 32*1024) From 01b6b2be73a6f40e0179e0217385eea6b41100a5 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 7 Mar 2014 18:04:38 -0800 Subject: [PATCH 027/384] Move graph and tags to graph sub pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- graph.go => graph/graph.go | 14 ++++----- tags.go => graph/tags.go | 2 +- tags_unit_test.go => graph/tags_unit_test.go | 23 ++++++++++++++- integration/graph_test.go | 24 ++++++++-------- runtime.go | 30 +++++++++++++------- server.go | 11 +++---- utils_test.go | 24 ---------------- volumes.go | 4 +-- 8 files changed, 70 insertions(+), 62 deletions(-) rename graph.go => graph/graph.go (97%) rename tags.go => graph/tags.go (99%) rename tags_unit_test.go => graph/tags_unit_test.go (76%) delete mode 100644 utils_test.go diff --git a/graph.go b/graph/graph.go similarity index 97% rename from graph.go rename to graph/graph.go index d164760d4c..01659b549f 100644 --- a/graph.go +++ b/graph/graph.go @@ -1,4 +1,4 @@ -package docker +package graph import ( "fmt" @@ -128,7 +128,7 @@ func (graph *Graph) Get(name string) (*image.Image, error) { } // Create creates a new image and registers it in the graph. -func (graph *Graph) Create(layerData archive.ArchiveReader, container *Container, comment, author string, config *runconfig.Config) (*image.Image, error) { +func (graph *Graph) Create(layerData archive.ArchiveReader, containerID, containerImage, comment, author string, containerConfig, config *runconfig.Config) (*image.Image, error) { img := &image.Image{ ID: utils.GenerateRandomID(), Comment: comment, @@ -139,10 +139,10 @@ func (graph *Graph) Create(layerData archive.ArchiveReader, container *Container Architecture: runtime.GOARCH, OS: runtime.GOOS, } - if container != nil { - img.Parent = container.Image - img.Container = container.ID - img.ContainerConfig = *container.Config + if containerID != "" { + img.Parent = containerImage + img.Container = containerID + img.ContainerConfig = *containerConfig } if err := graph.Register(nil, layerData, img); err != nil { return nil, err @@ -247,7 +247,7 @@ func (graph *Graph) Mktemp(id string) (string, error) { // // This extra layer is used by all containers as the top-most ro layer. It protects // the container from unwanted side-effects on the rw layer. -func setupInitLayer(initLayer string) error { +func SetupInitLayer(initLayer string) error { for pth, typ := range map[string]string{ "/dev/pts": "dir", "/dev/shm": "dir", diff --git a/tags.go b/graph/tags.go similarity index 99% rename from tags.go rename to graph/tags.go index 27e19cd671..524e1a1f9d 100644 --- a/tags.go +++ b/graph/tags.go @@ -1,4 +1,4 @@ -package docker +package graph import ( "encoding/json" diff --git a/tags_unit_test.go b/graph/tags_unit_test.go similarity index 76% rename from tags_unit_test.go rename to graph/tags_unit_test.go index 8ee913f527..153f94db3d 100644 --- a/tags_unit_test.go +++ b/graph/tags_unit_test.go @@ -1,9 +1,13 @@ -package docker +package graph import ( + "bytes" "github.com/dotcloud/docker/graphdriver" + _ "github.com/dotcloud/docker/graphdriver/vfs" // import the vfs driver so it is used in the tests "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" + "io" "os" "path" "testing" @@ -14,6 +18,23 @@ const ( testImageID = "foo" ) +func fakeTar() (io.Reader, error) { + content := []byte("Hello world!\n") + buf := new(bytes.Buffer) + tw := tar.NewWriter(buf) + for _, name := range []string{"/etc/postgres/postgres.conf", "/etc/passwd", "/var/log/postgres/postgres.conf"} { + hdr := new(tar.Header) + hdr.Size = int64(len(content)) + hdr.Name = name + if err := tw.WriteHeader(hdr); err != nil { + return nil, err + } + tw.Write([]byte(content)) + } + tw.Close() + return buf, nil +} + func mkTestTagStore(root string, t *testing.T) *TagStore { driver, err := graphdriver.New(root) if err != nil { diff --git a/integration/graph_test.go b/integration/graph_test.go index 4fd612b5ac..e575a252f3 100644 --- a/integration/graph_test.go +++ b/integration/graph_test.go @@ -2,9 +2,9 @@ package docker import ( "errors" - "github.com/dotcloud/docker" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/dockerversion" + "github.com/dotcloud/docker/graph" "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/utils" @@ -25,7 +25,7 @@ func TestMount(t *testing.T) { if err != nil { t.Fatal(err) } - image, err := graph.Create(archive, nil, "Testing", "", nil) + image, err := graph.Create(archive, "", "", "Testing", "", nil, nil) if err != nil { t.Fatal(err) } @@ -97,7 +97,7 @@ func TestGraphCreate(t *testing.T) { if err != nil { t.Fatal(err) } - img, err := graph.Create(archive, nil, "Testing", "", nil) + img, err := graph.Create(archive, "", "", "Testing", "", nil, nil) if err != nil { t.Fatal(err) } @@ -165,12 +165,12 @@ func TestDeletePrefix(t *testing.T) { assertNImages(graph, t, 0) } -func createTestImage(graph *docker.Graph, t *testing.T) *image.Image { +func createTestImage(graph *graph.Graph, t *testing.T) *image.Image { archive, err := fakeTar() if err != nil { t.Fatal(err) } - img, err := graph.Create(archive, nil, "Test image", "", nil) + img, err := graph.Create(archive, "", "", "Test image", "", nil, nil) if err != nil { t.Fatal(err) } @@ -185,7 +185,7 @@ func TestDelete(t *testing.T) { t.Fatal(err) } assertNImages(graph, t, 0) - img, err := graph.Create(archive, nil, "Bla bla", "", nil) + img, err := graph.Create(archive, "", "", "Bla bla", "", nil, nil) if err != nil { t.Fatal(err) } @@ -200,7 +200,7 @@ func TestDelete(t *testing.T) { t.Fatal(err) } // Test 2 create (same name) / 1 delete - img1, err := graph.Create(archive, nil, "Testing", "", nil) + img1, err := graph.Create(archive, "", "", "Testing", "", nil, nil) if err != nil { t.Fatal(err) } @@ -208,7 +208,7 @@ func TestDelete(t *testing.T) { if err != nil { t.Fatal(err) } - if _, err = graph.Create(archive, nil, "Testing", "", nil); err != nil { + if _, err = graph.Create(archive, "", "", "Testing", "", nil, nil); err != nil { t.Fatal(err) } assertNImages(graph, t, 2) @@ -280,7 +280,7 @@ func TestByParent(t *testing.T) { * HELPER FUNCTIONS */ -func assertNImages(graph *docker.Graph, t *testing.T, n int) { +func assertNImages(graph *graph.Graph, t *testing.T, n int) { if images, err := graph.Map(); err != nil { t.Fatal(err) } else if actualN := len(images); actualN != n { @@ -288,7 +288,7 @@ func assertNImages(graph *docker.Graph, t *testing.T, n int) { } } -func tempGraph(t *testing.T) (*docker.Graph, graphdriver.Driver) { +func tempGraph(t *testing.T) (*graph.Graph, graphdriver.Driver) { tmp, err := ioutil.TempDir("", "docker-graph-") if err != nil { t.Fatal(err) @@ -297,14 +297,14 @@ func tempGraph(t *testing.T) (*docker.Graph, graphdriver.Driver) { if err != nil { t.Fatal(err) } - graph, err := docker.NewGraph(tmp, driver) + graph, err := graph.NewGraph(tmp, driver) if err != nil { t.Fatal(err) } return graph, driver } -func nukeGraph(graph *docker.Graph) { +func nukeGraph(graph *graph.Graph) { graph.Driver().Cleanup() os.RemoveAll(graph.Root) } diff --git a/runtime.go b/runtime.go index 81bc9cbded..2608701b9b 100644 --- a/runtime.go +++ b/runtime.go @@ -10,6 +10,7 @@ import ( "github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/execdriver/lxc" "github.com/dotcloud/docker/execdriver/native" + "github.com/dotcloud/docker/graph" "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/graphdriver/aufs" _ "github.com/dotcloud/docker/graphdriver/btrfs" @@ -48,11 +49,11 @@ type Runtime struct { repository string sysInitPath string containers *list.List - graph *Graph - repositories *TagStore + graph *graph.Graph + repositories *graph.TagStore idIndex *utils.TruncIndex sysInfo *sysinfo.SysInfo - volumes *Graph + volumes *graph.Graph srv *Server eng *engine.Engine config *daemonconfig.Config @@ -486,7 +487,7 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe } defer runtime.driver.Put(initID) - if err := setupInitLayer(initPath); err != nil { + if err := graph.SetupInitLayer(initPath); err != nil { return nil, nil, err } @@ -555,7 +556,16 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a defer rwTar.Close() // Create a new image from the container's base layers + a new layer from container changes - img, err := runtime.graph.Create(rwTar, container, comment, author, config) + var ( + containerID, containerImage string + containerConfig *runconfig.Config + ) + if container != nil { + containerID = container.ID + containerImage = container.Image + containerConfig = container.Config + } + img, err := runtime.graph.Create(rwTar, containerID, containerImage, comment, author, containerConfig, config) if err != nil { return nil, err } @@ -654,13 +664,13 @@ func NewRuntimeFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (* if ad, ok := driver.(*aufs.Driver); ok { utils.Debugf("Migrating existing containers") - if err := ad.Migrate(config.Root, setupInitLayer); err != nil { + if err := ad.Migrate(config.Root, graph.SetupInitLayer); err != nil { return nil, err } } utils.Debugf("Creating images graph") - g, err := NewGraph(path.Join(config.Root, "graph"), driver) + g, err := graph.NewGraph(path.Join(config.Root, "graph"), driver) if err != nil { return nil, err } @@ -672,12 +682,12 @@ func NewRuntimeFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (* return nil, err } utils.Debugf("Creating volumes graph") - volumes, err := NewGraph(path.Join(config.Root, "volumes"), volumesDriver) + volumes, err := graph.NewGraph(path.Join(config.Root, "volumes"), volumesDriver) if err != nil { return nil, err } utils.Debugf("Creating repository list") - repositories, err := NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g) + repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g) if err != nil { return nil, fmt.Errorf("Couldn't create Tag store: %s", err) } @@ -878,7 +888,7 @@ func (runtime *Runtime) Nuke() error { // which need direct access to runtime.graph. // Once the tests switch to using engine and jobs, this method // can go away. -func (runtime *Runtime) Graph() *Graph { +func (runtime *Runtime) Graph() *graph.Graph { return runtime.graph } diff --git a/server.go b/server.go index 37402ee502..5c28b262dc 100644 --- a/server.go +++ b/server.go @@ -8,6 +8,7 @@ import ( "github.com/dotcloud/docker/daemonconfig" "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/graph" "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/pkg/graphdb" "github.com/dotcloud/docker/registry" @@ -334,7 +335,7 @@ func (srv *Server) ImageExport(job *engine.Job) engine.Status { } // write repositories - rootRepoMap := map[string]Repository{} + rootRepoMap := map[string]graph.Repository{} rootRepoMap[name] = rootRepo rootRepoJson, _ := json.Marshal(rootRepoMap) @@ -547,7 +548,7 @@ func (srv *Server) ImageLoad(job *engine.Job) engine.Status { repositoriesJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", "repositories")) if err == nil { - repositories := map[string]Repository{} + repositories := map[string]graph.Repository{} if err := json.Unmarshal(repositoriesJson, &repositories); err != nil { return job.Error(err) } @@ -1617,7 +1618,7 @@ func (srv *Server) ImageImport(job *engine.Job) engine.Status { defer progressReader.Close() archive = progressReader } - img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil) + img, err := srv.runtime.graph.Create(archive, "", "", "Imported from "+src, "", nil, nil) if err != nil { return job.Error(err) } @@ -1664,7 +1665,7 @@ func (srv *Server) ContainerCreate(job *engine.Job) engine.Status { if srv.runtime.graph.IsNotExist(err) { _, tag := utils.ParseRepositoryTag(config.Image) if tag == "" { - tag = DEFAULTTAG + tag = graph.DEFAULTTAG } return job.Errorf("No such image: %s (tag: %s)", config.Image, tag) } @@ -1837,7 +1838,7 @@ func (srv *Server) DeleteImage(name string, imgs *engine.Table, first, force boo repoName, tag = utils.ParseRepositoryTag(name) if tag == "" { - tag = DEFAULTTAG + tag = graph.DEFAULTTAG } img, err := srv.runtime.repositories.LookupImage(name) diff --git a/utils_test.go b/utils_test.go deleted file mode 100644 index 31fa12b6ad..0000000000 --- a/utils_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package docker - -import ( - "bytes" - "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" - "io" -) - -func fakeTar() (io.Reader, error) { - content := []byte("Hello world!\n") - buf := new(bytes.Buffer) - tw := tar.NewWriter(buf) - for _, name := range []string{"/etc/postgres/postgres.conf", "/etc/passwd", "/var/log/postgres/postgres.conf"} { - hdr := new(tar.Header) - hdr.Size = int64(len(content)) - hdr.Name = name - if err := tw.WriteHeader(hdr); err != nil { - return nil, err - } - tw.Write([]byte(content)) - } - tw.Close() - return buf, nil -} diff --git a/volumes.go b/volumes.go index 9f76e3698b..8684ff4e59 100644 --- a/volumes.go +++ b/volumes.go @@ -216,7 +216,7 @@ func createVolumes(container *Container) error { return err } - volumesDriver := container.runtime.volumes.driver + volumesDriver := container.runtime.volumes.Driver() // Create the requested volumes if they don't exist for volPath := range container.Config.Volumes { volPath = filepath.Clean(volPath) @@ -246,7 +246,7 @@ func createVolumes(container *Container) error { // Do not pass a container as the parameter for the volume creation. // The graph driver using the container's information ( Image ) to // create the parent. - c, err := container.runtime.volumes.Create(nil, nil, "", "", nil) + c, err := container.runtime.volumes.Create(nil, "", "", "", "", nil, nil) if err != nil { return err } From 36c3614fdde079fad178390f651945fba884668a Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 7 Mar 2014 18:42:29 -0800 Subject: [PATCH 028/384] Move runtime and container into sub pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- buildfile.go | 17 +- integration/api_test.go | 4 +- integration/commands_test.go | 10 +- integration/runtime_test.go | 14 +- integration/utils_test.go | 15 +- container.go => runtime/container.go | 40 ++-- .../container_unit_test.go | 6 +- runtime.go => runtime/runtime.go | 51 ++++- runtime/server.go | 9 + sorter.go => runtime/sorter.go | 2 +- state.go => runtime/state.go | 2 +- runtime/utils.go | 44 +++++ volumes.go => runtime/volumes.go | 2 +- server.go | 181 +++++++++--------- utils.go | 40 ---- 15 files changed, 251 insertions(+), 186 deletions(-) rename container.go => runtime/container.go (96%) rename container_unit_test.go => runtime/container_unit_test.go (96%) rename runtime.go => runtime/runtime.go (96%) create mode 100644 runtime/server.go rename sorter.go => runtime/sorter.go (97%) rename state.go => runtime/state.go (98%) create mode 100644 runtime/utils.go rename volumes.go => runtime/volumes.go (99%) diff --git a/buildfile.go b/buildfile.go index 6fae6a24a5..160db4d434 100644 --- a/buildfile.go +++ b/buildfile.go @@ -10,6 +10,7 @@ import ( "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/runconfig" + "github.com/dotcloud/docker/runtime" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -34,7 +35,7 @@ type BuildFile interface { } type buildFile struct { - runtime *Runtime + runtime *runtime.Runtime srv *Server image string @@ -74,9 +75,9 @@ func (b *buildFile) clearTmp(containers map[string]struct{}) { } func (b *buildFile) CmdFrom(name string) error { - image, err := b.runtime.repositories.LookupImage(name) + image, err := b.runtime.Repositories().LookupImage(name) if err != nil { - if b.runtime.graph.IsNotExist(err) { + if b.runtime.Graph().IsNotExist(err) { remote, tag := utils.ParseRepositoryTag(name) pullRegistryAuth := b.authConfig if len(b.configFile.Configs) > 0 { @@ -96,7 +97,7 @@ func (b *buildFile) CmdFrom(name string) error { if err := job.Run(); err != nil { return err } - image, err = b.runtime.repositories.LookupImage(name) + image, err = b.runtime.Repositories().LookupImage(name) if err != nil { return err } @@ -110,7 +111,7 @@ func (b *buildFile) CmdFrom(name string) error { b.config = image.Config } if b.config.Env == nil || len(b.config.Env) == 0 { - b.config.Env = append(b.config.Env, "HOME=/", "PATH="+defaultPathEnv) + b.config.Env = append(b.config.Env, "HOME=/", "PATH="+runtime.DefaultPathEnv) } // Process ONBUILD triggers if they exist if nTriggers := len(b.config.OnBuild); nTriggers != 0 { @@ -371,7 +372,7 @@ func (b *buildFile) checkPathForAddition(orig string) error { return nil } -func (b *buildFile) addContext(container *Container, orig, dest string, remote bool) error { +func (b *buildFile) addContext(container *runtime.Container, orig, dest string, remote bool) error { var ( origPath = path.Join(b.contextPath, orig) destPath = path.Join(container.BasefsPath(), dest) @@ -604,7 +605,7 @@ func (sf *StderrFormater) Write(buf []byte) (int, error) { return len(buf), err } -func (b *buildFile) create() (*Container, error) { +func (b *buildFile) create() (*runtime.Container, error) { if b.image == "" { return nil, fmt.Errorf("Please provide a source image with `from` prior to run") } @@ -625,7 +626,7 @@ func (b *buildFile) create() (*Container, error) { return c, nil } -func (b *buildFile) run(c *Container) error { +func (b *buildFile) run(c *runtime.Container) error { var errCh chan error if b.verbose { diff --git a/integration/api_test.go b/integration/api_test.go index c050b4934d..bac4efea53 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -5,12 +5,12 @@ import ( "bytes" "encoding/json" "fmt" - "github.com/dotcloud/docker" "github.com/dotcloud/docker/api" "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/runconfig" + "github.com/dotcloud/docker/runtime" "github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" "io" @@ -600,7 +600,7 @@ func TestGetContainersByName(t *testing.T) { t.Fatal(err) } assertHttpNotError(r, t) - outContainer := &docker.Container{} + outContainer := &runtime.Container{} if err := json.Unmarshal(r.Body.Bytes(), outContainer); err != nil { t.Fatal(err) } diff --git a/integration/commands_test.go b/integration/commands_test.go index 6d3ac86347..46f623bedf 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -3,11 +3,11 @@ package docker import ( "bufio" "fmt" - "github.com/dotcloud/docker" "github.com/dotcloud/docker/api" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/pkg/term" + "github.com/dotcloud/docker/runtime" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -36,7 +36,7 @@ func closeWrap(args ...io.Closer) error { return nil } -func setRaw(t *testing.T, c *docker.Container) *term.State { +func setRaw(t *testing.T, c *runtime.Container) *term.State { pty, err := c.GetPtyMaster() if err != nil { t.Fatal(err) @@ -48,7 +48,7 @@ func setRaw(t *testing.T, c *docker.Container) *term.State { return state } -func unsetRaw(t *testing.T, c *docker.Container, state *term.State) { +func unsetRaw(t *testing.T, c *runtime.Container, state *term.State) { pty, err := c.GetPtyMaster() if err != nil { t.Fatal(err) @@ -56,8 +56,8 @@ func unsetRaw(t *testing.T, c *docker.Container, state *term.State) { term.RestoreTerminal(pty.Fd(), state) } -func waitContainerStart(t *testing.T, timeout time.Duration) *docker.Container { - var container *docker.Container +func waitContainerStart(t *testing.T, timeout time.Duration) *runtime.Container { + var container *runtime.Container setTimeout(t, "Waiting for the container to be started timed out", timeout, func() { for { diff --git a/integration/runtime_test.go b/integration/runtime_test.go index a79f84365a..dd478c289e 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -3,11 +3,11 @@ package docker import ( "bytes" "fmt" - "github.com/dotcloud/docker" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/runconfig" + "github.com/dotcloud/docker/runtime" "github.com/dotcloud/docker/sysinit" "github.com/dotcloud/docker/utils" "io" @@ -16,7 +16,7 @@ import ( "net/url" "os" "path/filepath" - "runtime" + goruntime "runtime" "strconv" "strings" "syscall" @@ -36,14 +36,14 @@ const ( var ( // FIXME: globalRuntime is deprecated by globalEngine. All tests should be converted. - globalRuntime *docker.Runtime + globalRuntime *runtime.Runtime globalEngine *engine.Engine startFds int startGoroutines int ) // FIXME: nuke() is deprecated by Runtime.Nuke() -func nuke(runtime *docker.Runtime) error { +func nuke(runtime *runtime.Runtime) error { return runtime.Nuke() } @@ -120,7 +120,7 @@ func init() { // Create the "global runtime" with a long-running daemon for integration tests spawnGlobalDaemon() - startFds, startGoroutines = utils.GetTotalUsedFds(), runtime.NumGoroutine() + startFds, startGoroutines = utils.GetTotalUsedFds(), goruntime.NumGoroutine() } func setupBaseImage() { @@ -173,7 +173,7 @@ func spawnGlobalDaemon() { // FIXME: test that ImagePull(json=true) send correct json output -func GetTestImage(runtime *docker.Runtime) *image.Image { +func GetTestImage(runtime *runtime.Runtime) *image.Image { imgs, err := runtime.Graph().Map() if err != nil { log.Fatalf("Unable to get the test image: %s", err) @@ -357,7 +357,7 @@ func TestGet(t *testing.T) { } -func startEchoServerContainer(t *testing.T, proto string) (*docker.Runtime, *docker.Container, string) { +func startEchoServerContainer(t *testing.T, proto string) (*runtime.Runtime, *runtime.Container, string) { var ( err error id string diff --git a/integration/utils_test.go b/integration/utils_test.go index 05d73df52a..88f2cc49c3 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -18,6 +18,7 @@ import ( "github.com/dotcloud/docker/builtins" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/runconfig" + "github.com/dotcloud/docker/runtime" "github.com/dotcloud/docker/utils" ) @@ -27,7 +28,7 @@ import ( // Create a temporary runtime suitable for unit testing. // Call t.Fatal() at the first error. -func mkRuntime(f utils.Fataler) *docker.Runtime { +func mkRuntime(f utils.Fataler) *runtime.Runtime { eng := newTestEngine(f, false, "") return mkRuntimeFromEngine(eng, f) // FIXME: @@ -139,7 +140,7 @@ func assertHttpError(r *httptest.ResponseRecorder, t utils.Fataler) { } } -func getContainer(eng *engine.Engine, id string, t utils.Fataler) *docker.Container { +func getContainer(eng *engine.Engine, id string, t utils.Fataler) *runtime.Container { runtime := mkRuntimeFromEngine(eng, t) c := runtime.Get(id) if c == nil { @@ -160,14 +161,14 @@ func mkServerFromEngine(eng *engine.Engine, t utils.Fataler) *docker.Server { return srv } -func mkRuntimeFromEngine(eng *engine.Engine, t utils.Fataler) *docker.Runtime { +func mkRuntimeFromEngine(eng *engine.Engine, t utils.Fataler) *runtime.Runtime { iRuntime := eng.Hack_GetGlobalVar("httpapi.runtime") if iRuntime == nil { panic("Legacy runtime field not set in engine") } - runtime, ok := iRuntime.(*docker.Runtime) + runtime, ok := iRuntime.(*runtime.Runtime) if !ok { - panic("Legacy runtime field in engine does not cast to *docker.Runtime") + panic("Legacy runtime field in engine does not cast to *runtime.Runtime") } return runtime } @@ -249,7 +250,7 @@ func readFile(src string, t *testing.T) (content string) { // dynamically replaced by the current test image. // The caller is responsible for destroying the container. // Call t.Fatal() at the first error. -func mkContainer(r *docker.Runtime, args []string, t *testing.T) (*docker.Container, *runconfig.HostConfig, error) { +func mkContainer(r *runtime.Runtime, args []string, t *testing.T) (*runtime.Container, *runconfig.HostConfig, error) { config, hc, _, err := runconfig.Parse(args, nil) defer func() { if err != nil && t != nil { @@ -280,7 +281,7 @@ func mkContainer(r *docker.Runtime, args []string, t *testing.T) (*docker.Contai // and return its standard output as a string. // The image name (eg. the XXX in []string{"-i", "-t", "XXX", "bash"}, is dynamically replaced by the current test image. // If t is not nil, call t.Fatal() at the first error. Otherwise return errors normally. -func runContainer(eng *engine.Engine, r *docker.Runtime, args []string, t *testing.T) (output string, err error) { +func runContainer(eng *engine.Engine, r *runtime.Runtime, args []string, t *testing.T) (output string, err error) { defer func() { if err != nil && t != nil { t.Fatal(err) diff --git a/container.go b/runtime/container.go similarity index 96% rename from container.go rename to runtime/container.go index 9c1a28c98a..813147e508 100644 --- a/container.go +++ b/runtime/container.go @@ -1,4 +1,4 @@ -package docker +package runtime import ( "encoding/json" @@ -24,7 +24,7 @@ import ( "time" ) -const defaultPathEnv = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" +const DefaultPathEnv = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" var ( ErrNotATTY = errors.New("The PTY is not a file") @@ -174,7 +174,7 @@ func (container *Container) ToDisk() (err error) { if err != nil { return } - return container.writeHostConfig() + return container.WriteHostConfig() } func (container *Container) readHostConfig() error { @@ -193,7 +193,7 @@ func (container *Container) readHostConfig() error { return json.Unmarshal(data, container.hostConfig) } -func (container *Container) writeHostConfig() (err error) { +func (container *Container) WriteHostConfig() (err error) { data, err := json.Marshal(container.hostConfig) if err != nil { return @@ -451,7 +451,7 @@ func (container *Container) Start() (err error) { // Setup environment env := []string{ "HOME=/", - "PATH=" + defaultPathEnv, + "PATH=" + DefaultPathEnv, "HOSTNAME=" + container.Config.Hostname, } @@ -693,7 +693,7 @@ func (container *Container) allocateNetwork() error { return err } container.Config.PortSpecs = nil - if err := container.writeHostConfig(); err != nil { + if err := container.WriteHostConfig(); err != nil { return err } } @@ -751,7 +751,7 @@ func (container *Container) allocateNetwork() error { } bindings[port] = binding } - container.writeHostConfig() + container.WriteHostConfig() container.NetworkSettings.Ports = bindings @@ -850,7 +850,7 @@ func (container *Container) cleanup() { } } -func (container *Container) kill(sig int) error { +func (container *Container) KillSig(sig int) error { container.Lock() defer container.Unlock() @@ -866,7 +866,7 @@ func (container *Container) Kill() error { } // 1. Send SIGKILL - if err := container.kill(9); err != nil { + if err := container.KillSig(9); err != nil { return err } @@ -891,10 +891,10 @@ func (container *Container) Stop(seconds int) error { } // 1. Send a SIGTERM - if err := container.kill(15); err != nil { + if err := container.KillSig(15); err != nil { utils.Debugf("Error sending kill SIGTERM: %s", err) log.Print("Failed to send SIGTERM to the process, force killing") - if err := container.kill(9); err != nil { + if err := container.KillSig(9); err != nil { return err } } @@ -1141,3 +1141,21 @@ func (container *Container) GetPtyMaster() (*os.File, error) { } return ttyConsole.Master(), nil } + +func (container *Container) HostConfig() *runconfig.HostConfig { + return container.hostConfig +} + +func (container *Container) SetHostConfig(hostConfig *runconfig.HostConfig) { + container.hostConfig = hostConfig +} + +func (container *Container) DisableLink(name string) { + if container.activeLinks != nil { + if link, exists := container.activeLinks[name]; exists { + link.Disable() + } else { + utils.Debugf("Could not find active link for %s", name) + } + } +} diff --git a/container_unit_test.go b/runtime/container_unit_test.go similarity index 96% rename from container_unit_test.go rename to runtime/container_unit_test.go index 3877b7f0da..fba036ca50 100644 --- a/container_unit_test.go +++ b/runtime/container_unit_test.go @@ -1,4 +1,4 @@ -package docker +package runtime import ( "github.com/dotcloud/docker/nat" @@ -132,14 +132,14 @@ func TestParseNetworkOptsUdp(t *testing.T) { } func TestGetFullName(t *testing.T) { - name, err := getFullName("testing") + name, err := GetFullContainerName("testing") if err != nil { t.Fatal(err) } if name != "/testing" { t.Fatalf("Expected /testing got %s", name) } - if _, err := getFullName(""); err == nil { + if _, err := GetFullContainerName(""); err == nil { t.Fatal("Error should not be nil") } } diff --git a/runtime.go b/runtime/runtime.go similarity index 96% rename from runtime.go rename to runtime/runtime.go index 2608701b9b..c11c309ad8 100644 --- a/runtime.go +++ b/runtime/runtime.go @@ -1,4 +1,4 @@ -package docker +package runtime import ( "container/list" @@ -40,7 +40,7 @@ import ( const MaxImageDepth = 127 var ( - defaultDns = []string{"8.8.8.8", "8.8.4.4"} + DefaultDns = []string{"8.8.8.8", "8.8.4.4"} validContainerNameChars = `[a-zA-Z0-9_.-]` validContainerNamePattern = regexp.MustCompile(`^/?` + validContainerNameChars + `+$`) ) @@ -54,7 +54,7 @@ type Runtime struct { idIndex *utils.TruncIndex sysInfo *sysinfo.SysInfo volumes *graph.Graph - srv *Server + srv Server eng *engine.Engine config *daemonconfig.Config containerGraph *graphdb.Database @@ -500,8 +500,7 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe } if len(config.Dns) == 0 && len(runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) { - //"WARNING: Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns - runtime.config.Dns = defaultDns + runtime.config.Dns = DefaultDns } // If custom dns exists, then create a resolv.conf for the container @@ -578,7 +577,7 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a return img, nil } -func getFullName(name string) (string, error) { +func GetFullContainerName(name string) (string, error) { if name == "" { return "", fmt.Errorf("Container name cannot be empty") } @@ -589,7 +588,7 @@ func getFullName(name string) (string, error) { } func (runtime *Runtime) GetByName(name string) (*Container, error) { - fullName, err := getFullName(name) + fullName, err := GetFullContainerName(name) if err != nil { return nil, err } @@ -605,7 +604,7 @@ func (runtime *Runtime) GetByName(name string) (*Container, error) { } func (runtime *Runtime) Children(name string) (map[string]*Container, error) { - name, err := getFullName(name) + name, err := GetFullContainerName(name) if err != nil { return nil, err } @@ -892,6 +891,42 @@ func (runtime *Runtime) Graph() *graph.Graph { return runtime.graph } +func (runtime *Runtime) Repositories() *graph.TagStore { + return runtime.repositories +} + +func (runtime *Runtime) Config() *daemonconfig.Config { + return runtime.config +} + +func (runtime *Runtime) SystemConfig() *sysinfo.SysInfo { + return runtime.sysInfo +} + +func (runtime *Runtime) SystemInitPath() string { + return runtime.sysInitPath +} + +func (runtime *Runtime) GraphDriver() graphdriver.Driver { + return runtime.driver +} + +func (runtime *Runtime) ExecutionDriver() execdriver.Driver { + return runtime.execDriver +} + +func (runtime *Runtime) Volumes() *graph.Graph { + return runtime.volumes +} + +func (runtime *Runtime) ContainerGraph() *graphdb.Database { + return runtime.containerGraph +} + +func (runtime *Runtime) SetServer(server Server) { + runtime.srv = server +} + // History is a convenience type for storing a list of containers, // ordered by creation date. type History []*Container diff --git a/runtime/server.go b/runtime/server.go new file mode 100644 index 0000000000..0d2b71dea7 --- /dev/null +++ b/runtime/server.go @@ -0,0 +1,9 @@ +package runtime + +import ( + "github.com/dotcloud/docker/utils" +) + +type Server interface { + LogEvent(action, id, from string) *utils.JSONMessage +} diff --git a/sorter.go b/runtime/sorter.go similarity index 97% rename from sorter.go rename to runtime/sorter.go index b49ac58c24..c5af772dae 100644 --- a/sorter.go +++ b/runtime/sorter.go @@ -1,4 +1,4 @@ -package docker +package runtime import "sort" diff --git a/state.go b/runtime/state.go similarity index 98% rename from state.go rename to runtime/state.go index 1dc92af204..cce6912b46 100644 --- a/state.go +++ b/runtime/state.go @@ -1,4 +1,4 @@ -package docker +package runtime import ( "fmt" diff --git a/runtime/utils.go b/runtime/utils.go new file mode 100644 index 0000000000..b343b5b10e --- /dev/null +++ b/runtime/utils.go @@ -0,0 +1,44 @@ +package runtime + +import ( + "github.com/dotcloud/docker/nat" + "github.com/dotcloud/docker/pkg/namesgenerator" + "github.com/dotcloud/docker/runconfig" +) + +func migratePortMappings(config *runconfig.Config, hostConfig *runconfig.HostConfig) error { + if config.PortSpecs != nil { + ports, bindings, err := nat.ParsePortSpecs(config.PortSpecs) + if err != nil { + return err + } + config.PortSpecs = nil + if len(bindings) > 0 { + if hostConfig == nil { + hostConfig = &runconfig.HostConfig{} + } + hostConfig.PortBindings = bindings + } + + if config.ExposedPorts == nil { + config.ExposedPorts = make(nat.PortSet, len(ports)) + } + for k, v := range ports { + config.ExposedPorts[k] = v + } + } + return nil +} + +type checker struct { + runtime *Runtime +} + +func (c *checker) Exists(name string) bool { + return c.runtime.containerGraph.Exists("/" + name) +} + +// Generate a random and unique name +func generateRandomName(runtime *Runtime) (string, error) { + return namesgenerator.GenerateRandomName(&checker{runtime}) +} diff --git a/volumes.go b/runtime/volumes.go similarity index 99% rename from volumes.go rename to runtime/volumes.go index 8684ff4e59..1a548eca47 100644 --- a/volumes.go +++ b/runtime/volumes.go @@ -1,4 +1,4 @@ -package docker +package runtime import ( "fmt" diff --git a/server.go b/server.go index 5c28b262dc..85d56afdb6 100644 --- a/server.go +++ b/server.go @@ -13,6 +13,7 @@ import ( "github.com/dotcloud/docker/pkg/graphdb" "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/runconfig" + "github.com/dotcloud/docker/runtime" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -24,7 +25,7 @@ import ( "os/signal" "path" "path/filepath" - "runtime" + goruntime "runtime" "strconv" "strings" "sync" @@ -41,9 +42,9 @@ func InitServer(job *engine.Job) engine.Status { if err != nil { return job.Error(err) } - if srv.runtime.config.Pidfile != "" { + if srv.runtime.Config().Pidfile != "" { job.Logf("Creating pidfile") - if err := utils.CreatePidFile(srv.runtime.config.Pidfile); err != nil { + if err := utils.CreatePidFile(srv.runtime.Config().Pidfile); err != nil { // FIXME: do we need fatal here instead of returning a job error? log.Fatal(err) } @@ -54,7 +55,7 @@ func InitServer(job *engine.Job) engine.Status { go func() { sig := <-c log.Printf("Received signal '%v', exiting\n", sig) - utils.RemovePidFile(srv.runtime.config.Pidfile) + utils.RemovePidFile(srv.runtime.Config().Pidfile) srv.Close() os.Exit(0) }() @@ -181,10 +182,10 @@ func (srv *Server) ContainerKill(job *engine.Job) engine.Status { if err := container.Kill(); err != nil { return job.Errorf("Cannot kill container %s: %s", name, err) } - srv.LogEvent("kill", container.ID, srv.runtime.repositories.ImageName(container.Image)) + srv.LogEvent("kill", container.ID, srv.runtime.Repositories().ImageName(container.Image)) } else { // Otherwise, just send the requested signal - if err := container.kill(int(sig)); err != nil { + if err := container.KillSig(int(sig)); err != nil { return job.Errorf("Cannot kill container %s: %s", name, err) } // FIXME: Add event for signals @@ -293,7 +294,7 @@ func (srv *Server) ContainerExport(job *engine.Job) engine.Status { return job.Errorf("%s: %s", name, err) } // FIXME: factor job-specific LogEvent to engine.Job.Run() - srv.LogEvent("export", container.ID, srv.runtime.repositories.ImageName(container.Image)) + srv.LogEvent("export", container.ID, srv.runtime.Repositories().ImageName(container.Image)) return engine.StatusOK } return job.Errorf("No such container: %s", name) @@ -318,7 +319,7 @@ func (srv *Server) ImageExport(job *engine.Job) engine.Status { utils.Debugf("Serializing %s", name) - rootRepo, err := srv.runtime.repositories.Get(name) + rootRepo, err := srv.runtime.Repositories().Get(name) if err != nil { return job.Error(err) } @@ -494,7 +495,7 @@ func (srv *Server) Build(job *engine.Job) engine.Status { return job.Error(err) } if repoName != "" { - srv.runtime.repositories.Set(repoName, tag, id, false) + srv.runtime.Repositories().Set(repoName, tag, id, false) } return engine.StatusOK } @@ -555,7 +556,7 @@ func (srv *Server) ImageLoad(job *engine.Job) engine.Status { for imageName, tagMap := range repositories { for tag, address := range tagMap { - if err := srv.runtime.repositories.Set(imageName, tag, address, true); err != nil { + if err := srv.runtime.Repositories().Set(imageName, tag, address, true); err != nil { return job.Error(err) } } @@ -588,13 +589,13 @@ func (srv *Server) recursiveLoad(address, tmpImageDir string) error { return err } if img.Parent != "" { - if !srv.runtime.graph.Exists(img.Parent) { + if !srv.runtime.Graph().Exists(img.Parent) { if err := srv.recursiveLoad(img.Parent, tmpImageDir); err != nil { return err } } } - if err := srv.runtime.graph.Register(imageJson, layer, img); err != nil { + if err := srv.runtime.Graph().Register(imageJson, layer, img); err != nil { return err } } @@ -650,7 +651,7 @@ func (srv *Server) ImageInsert(job *engine.Job) engine.Status { sf := utils.NewStreamFormatter(job.GetenvBool("json")) out := utils.NewWriteFlusher(job.Stdout) - img, err := srv.runtime.repositories.LookupImage(name) + img, err := srv.runtime.Repositories().LookupImage(name) if err != nil { return job.Error(err) } @@ -661,7 +662,7 @@ func (srv *Server) ImageInsert(job *engine.Job) engine.Status { } defer file.Body.Close() - config, _, _, err := runconfig.Parse([]string{img.ID, "echo", "insert", url, path}, srv.runtime.sysInfo) + config, _, _, err := runconfig.Parse([]string{img.ID, "echo", "insert", url, path}, srv.runtime.SystemConfig()) if err != nil { return job.Error(err) } @@ -685,7 +686,7 @@ func (srv *Server) ImageInsert(job *engine.Job) engine.Status { } func (srv *Server) ImagesViz(job *engine.Job) engine.Status { - images, _ := srv.runtime.graph.Map() + images, _ := srv.runtime.Graph().Map() if images == nil { return engine.StatusOK } @@ -709,7 +710,7 @@ func (srv *Server) ImagesViz(job *engine.Job) engine.Status { reporefs := make(map[string][]string) - for name, repository := range srv.runtime.repositories.Repositories { + for name, repository := range srv.runtime.Repositories().Repositories { for tag, id := range repository { reporefs[utils.TruncateID(id)] = append(reporefs[utils.TruncateID(id)], fmt.Sprintf("%s:%s", name, tag)) } @@ -728,22 +729,22 @@ func (srv *Server) Images(job *engine.Job) engine.Status { err error ) if job.GetenvBool("all") { - allImages, err = srv.runtime.graph.Map() + allImages, err = srv.runtime.Graph().Map() } else { - allImages, err = srv.runtime.graph.Heads() + allImages, err = srv.runtime.Graph().Heads() } if err != nil { return job.Error(err) } lookup := make(map[string]*engine.Env) - for name, repository := range srv.runtime.repositories.Repositories { + for name, repository := range srv.runtime.Repositories().Repositories { if job.Getenv("filter") != "" { if match, _ := path.Match(job.Getenv("filter"), name); !match { continue } } for tag, id := range repository { - image, err := srv.runtime.graph.Get(id) + image, err := srv.runtime.Graph().Get(id) if err != nil { log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err) continue @@ -793,7 +794,7 @@ func (srv *Server) Images(job *engine.Job) engine.Status { } func (srv *Server) DockerInfo(job *engine.Job) engine.Status { - images, _ := srv.runtime.graph.Map() + images, _ := srv.runtime.Graph().Map() var imgcount int if images == nil { imgcount = 0 @@ -809,21 +810,21 @@ func (srv *Server) DockerInfo(job *engine.Job) engine.Status { initPath := utils.DockerInitPath("") if initPath == "" { // if that fails, we'll just return the path from the runtime - initPath = srv.runtime.sysInitPath + initPath = srv.runtime.SystemInitPath() } v := &engine.Env{} v.SetInt("Containers", len(srv.runtime.List())) v.SetInt("Images", imgcount) - v.Set("Driver", srv.runtime.driver.String()) - v.SetJson("DriverStatus", srv.runtime.driver.Status()) - v.SetBool("MemoryLimit", srv.runtime.sysInfo.MemoryLimit) - v.SetBool("SwapLimit", srv.runtime.sysInfo.SwapLimit) - v.SetBool("IPv4Forwarding", !srv.runtime.sysInfo.IPv4ForwardingDisabled) + v.Set("Driver", srv.runtime.GraphDriver().String()) + v.SetJson("DriverStatus", srv.runtime.GraphDriver().Status()) + v.SetBool("MemoryLimit", srv.runtime.SystemConfig().MemoryLimit) + v.SetBool("SwapLimit", srv.runtime.SystemConfig().SwapLimit) + v.SetBool("IPv4Forwarding", !srv.runtime.SystemConfig().IPv4ForwardingDisabled) v.SetBool("Debug", os.Getenv("DEBUG") != "") v.SetInt("NFd", utils.GetTotalUsedFds()) - v.SetInt("NGoroutines", runtime.NumGoroutine()) - v.Set("ExecutionDriver", srv.runtime.execDriver.Name()) + v.SetInt("NGoroutines", goruntime.NumGoroutine()) + v.Set("ExecutionDriver", srv.runtime.ExecutionDriver().Name()) v.SetInt("NEventsListener", len(srv.listeners)) v.Set("KernelVersion", kernelVersion) v.Set("IndexServerAddress", auth.IndexServerAddress()) @@ -840,13 +841,13 @@ func (srv *Server) ImageHistory(job *engine.Job) engine.Status { return job.Errorf("Usage: %s IMAGE", job.Name) } name := job.Args[0] - foundImage, err := srv.runtime.repositories.LookupImage(name) + foundImage, err := srv.runtime.Repositories().LookupImage(name) if err != nil { return job.Error(err) } lookupMap := make(map[string][]string) - for name, repository := range srv.runtime.repositories.Repositories { + for name, repository := range srv.runtime.Repositories().Repositories { for tag, id := range repository { // If the ID already has a reverse lookup, do not update it unless for "latest" if _, exists := lookupMap[id]; !exists { @@ -891,7 +892,7 @@ func (srv *Server) ContainerTop(job *engine.Job) engine.Status { if !container.State.IsRunning() { return job.Errorf("Container %s is not running", name) } - pids, err := srv.runtime.execDriver.GetPidsForContainer(container.ID) + pids, err := srv.runtime.ExecutionDriver().GetPidsForContainer(container.ID) if err != nil { return job.Error(err) } @@ -984,7 +985,7 @@ func (srv *Server) Containers(job *engine.Job) engine.Status { outs := engine.NewTable("Created", 0) names := map[string][]string{} - srv.runtime.containerGraph.Walk("/", func(p string, e *graphdb.Entity) error { + srv.runtime.ContainerGraph().Walk("/", func(p string, e *graphdb.Entity) error { names[e.ID()] = append(names[e.ID()], p) return nil }, -1) @@ -1009,7 +1010,7 @@ func (srv *Server) Containers(job *engine.Job) engine.Status { out := &engine.Env{} out.Set("Id", container.ID) out.SetList("Names", names[container.ID]) - out.Set("Image", srv.runtime.repositories.ImageName(container.Image)) + out.Set("Image", srv.runtime.Repositories().ImageName(container.Image)) if len(container.Args) > 0 { out.Set("Command", fmt.Sprintf("\"%s %s\"", container.Path, strings.Join(container.Args, " "))) } else { @@ -1067,7 +1068,7 @@ func (srv *Server) ImageTag(job *engine.Job) engine.Status { if len(job.Args) == 3 { tag = job.Args[2] } - if err := srv.runtime.repositories.Set(job.Args[1], tag, job.Args[0], job.GetenvBool("force")); err != nil { + if err := srv.runtime.Repositories().Set(job.Args[1], tag, job.Args[0], job.GetenvBool("force")); err != nil { return job.Error(err) } return engine.StatusOK @@ -1092,7 +1093,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin } defer srv.poolRemove("pull", "layer:"+id) - if !srv.runtime.graph.Exists(id) { + if !srv.runtime.Graph().Exists(id) { out.Write(sf.FormatProgress(utils.TruncateID(id), "Pulling metadata", nil)) imgJSON, imgSize, err := r.GetRemoteImageJSON(id, endpoint, token) if err != nil { @@ -1114,7 +1115,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin return err } defer layer.Close() - if err := srv.runtime.graph.Register(imgJSON, utils.ProgressReader(layer, imgSize, out, sf, false, utils.TruncateID(id), "Downloading"), img); err != nil { + if err := srv.runtime.Graph().Register(imgJSON, utils.ProgressReader(layer, imgSize, out, sf, false, utils.TruncateID(id), "Downloading"), img); err != nil { out.Write(sf.FormatProgress(utils.TruncateID(id), "Error downloading dependent layers", nil)) return err } @@ -1249,11 +1250,11 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName if askedTag != "" && tag != askedTag { continue } - if err := srv.runtime.repositories.Set(localName, tag, id, true); err != nil { + if err := srv.runtime.Repositories().Set(localName, tag, id, true); err != nil { return err } } - if err := srv.runtime.repositories.Save(); err != nil { + if err := srv.runtime.Repositories().Save(); err != nil { return err } @@ -1374,7 +1375,7 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]string, map[stri tagsByImage[id] = append(tagsByImage[id], tag) - for img, err := srv.runtime.graph.Get(id); img != nil; img, err = img.GetParent() { + for img, err := srv.runtime.Graph().Get(id); img != nil; img, err = img.GetParent() { if err != nil { return nil, nil, err } @@ -1481,7 +1482,7 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, ep string, token []string, sf *utils.StreamFormatter) (checksum string, err error) { out = utils.NewWriteFlusher(out) - jsonRaw, err := ioutil.ReadFile(path.Join(srv.runtime.graph.Root, imgID, "json")) + jsonRaw, err := ioutil.ReadFile(path.Join(srv.runtime.Graph().Root, imgID, "json")) if err != nil { return "", fmt.Errorf("Cannot retrieve the path for {%s}: %s", imgID, err) } @@ -1500,7 +1501,7 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, return "", err } - layerData, err := srv.runtime.graph.TempLayerArchive(imgID, archive.Uncompressed, sf, out) + layerData, err := srv.runtime.Graph().TempLayerArchive(imgID, archive.Uncompressed, sf, out) if err != nil { return "", fmt.Errorf("Failed to generate layer archive: %s", err) } @@ -1552,17 +1553,17 @@ func (srv *Server) ImagePush(job *engine.Job) engine.Status { return job.Error(err) } - img, err := srv.runtime.graph.Get(localName) + img, err := srv.runtime.Graph().Get(localName) r, err2 := registry.NewRegistry(authConfig, srv.HTTPRequestFactory(metaHeaders), endpoint) if err2 != nil { return job.Error(err2) } if err != nil { - reposLen := len(srv.runtime.repositories.Repositories[localName]) + reposLen := len(srv.runtime.Repositories().Repositories[localName]) job.Stdout.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", localName, reposLen)) // If it fails, try to get the repository - if localRepo, exists := srv.runtime.repositories.Repositories[localName]; exists { + if localRepo, exists := srv.runtime.Repositories().Repositories[localName]; exists { if err := srv.pushRepository(r, job.Stdout, localName, remoteName, localRepo, sf); err != nil { return job.Error(err) } @@ -1618,13 +1619,13 @@ func (srv *Server) ImageImport(job *engine.Job) engine.Status { defer progressReader.Close() archive = progressReader } - img, err := srv.runtime.graph.Create(archive, "", "", "Imported from "+src, "", nil, nil) + img, err := srv.runtime.Graph().Create(archive, "", "", "Imported from "+src, "", nil, nil) if err != nil { return job.Error(err) } // Optionally register the image at REPO/TAG if repo != "" { - if err := srv.runtime.repositories.Set(repo, tag, img.ID, true); err != nil { + if err := srv.runtime.Repositories().Set(repo, tag, img.ID, true); err != nil { return job.Error(err) } } @@ -1643,11 +1644,11 @@ func (srv *Server) ContainerCreate(job *engine.Job) engine.Status { if config.Memory != 0 && config.Memory < 524288 { return job.Errorf("Minimum memory limit allowed is 512k") } - if config.Memory > 0 && !srv.runtime.sysInfo.MemoryLimit { + if config.Memory > 0 && !srv.runtime.SystemConfig().MemoryLimit { job.Errorf("Your kernel does not support memory limit capabilities. Limitation discarded.\n") config.Memory = 0 } - if config.Memory > 0 && !srv.runtime.sysInfo.SwapLimit { + if config.Memory > 0 && !srv.runtime.SystemConfig().SwapLimit { job.Errorf("Your kernel does not support swap limit capabilities. Limitation discarded.\n") config.MemorySwap = -1 } @@ -1655,14 +1656,14 @@ func (srv *Server) ContainerCreate(job *engine.Job) engine.Status { if err != nil { return job.Error(err) } - if !config.NetworkDisabled && len(config.Dns) == 0 && len(srv.runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) { - job.Errorf("Local (127.0.0.1) DNS resolver found in resolv.conf and containers can't use it. Using default external servers : %v\n", defaultDns) - config.Dns = defaultDns + if !config.NetworkDisabled && len(config.Dns) == 0 && len(srv.runtime.Config().Dns) == 0 && utils.CheckLocalDns(resolvConf) { + job.Errorf("Local (127.0.0.1) DNS resolver found in resolv.conf and containers can't use it. Using default external servers : %v\n", runtime.DefaultDns) + config.Dns = runtime.DefaultDns } container, buildWarnings, err := srv.runtime.Create(config, name) if err != nil { - if srv.runtime.graph.IsNotExist(err) { + if srv.runtime.Graph().IsNotExist(err) { _, tag := utils.ParseRepositoryTag(config.Image) if tag == "" { tag = graph.DEFAULTTAG @@ -1671,10 +1672,10 @@ func (srv *Server) ContainerCreate(job *engine.Job) engine.Status { } return job.Error(err) } - if !container.Config.NetworkDisabled && srv.runtime.sysInfo.IPv4ForwardingDisabled { + if !container.Config.NetworkDisabled && srv.runtime.SystemConfig().IPv4ForwardingDisabled { job.Errorf("IPv4 forwarding is disabled.\n") } - srv.LogEvent("create", container.ID, srv.runtime.repositories.ImageName(container.Image)) + srv.LogEvent("create", container.ID, srv.runtime.Repositories().ImageName(container.Image)) // FIXME: this is necessary because runtime.Create might return a nil container // with a non-nil error. This should not happen! Once it's fixed we // can remove this workaround. @@ -1702,7 +1703,7 @@ func (srv *Server) ContainerRestart(job *engine.Job) engine.Status { if err := container.Restart(int(t)); err != nil { return job.Errorf("Cannot restart container %s: %s\n", name, err) } - srv.LogEvent("restart", container.ID, srv.runtime.repositories.ImageName(container.Image)) + srv.LogEvent("restart", container.ID, srv.runtime.Repositories().ImageName(container.Image)) } else { return job.Errorf("No such container: %s\n", name) } @@ -1724,7 +1725,7 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { if container == nil { return job.Errorf("No such link: %s", name) } - name, err := getFullName(name) + name, err := runtime.GetFullContainerName(name) if err != nil { job.Error(err) } @@ -1732,21 +1733,17 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { if parent == "/" { return job.Errorf("Conflict, cannot remove the default name of the container") } - pe := srv.runtime.containerGraph.Get(parent) + pe := srv.runtime.ContainerGraph().Get(parent) if pe == nil { return job.Errorf("Cannot get parent %s for name %s", parent, name) } parentContainer := srv.runtime.Get(pe.ID()) - if parentContainer != nil && parentContainer.activeLinks != nil { - if link, exists := parentContainer.activeLinks[n]; exists { - link.Disable() - } else { - utils.Debugf("Could not find active link for %s", name) - } + if parentContainer != nil { + parentContainer.DisableLink(n) } - if err := srv.runtime.containerGraph.Delete(name); err != nil { + if err := srv.runtime.ContainerGraph().Delete(name); err != nil { return job.Error(err) } return engine.StatusOK @@ -1765,13 +1762,13 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { if err := srv.runtime.Destroy(container); err != nil { return job.Errorf("Cannot destroy container %s: %s", name, err) } - srv.LogEvent("destroy", container.ID, srv.runtime.repositories.ImageName(container.Image)) + srv.LogEvent("destroy", container.ID, srv.runtime.Repositories().ImageName(container.Image)) if removeVolume { var ( volumes = make(map[string]struct{}) binds = make(map[string]struct{}) - usedVolumes = make(map[string]*Container) + usedVolumes = make(map[string]*runtime.Container) ) // the volume id is always the base of the path @@ -1780,7 +1777,7 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { } // populate bind map so that they can be skipped and not removed - for _, bind := range container.hostConfig.Binds { + for _, bind := range container.HostConfig().Binds { source := strings.Split(bind, ":")[0] // TODO: refactor all volume stuff, all of it // this is very important that we eval the link @@ -1819,7 +1816,7 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { log.Printf("The volume %s is used by the container %s. Impossible to remove it. Skipping.\n", volumeId, c.ID) continue } - if err := srv.runtime.volumes.Delete(volumeId); err != nil { + if err := srv.runtime.Volumes().Delete(volumeId); err != nil { return job.Errorf("Error calling volumes.Delete(%q): %v", volumeId, err) } } @@ -1841,9 +1838,9 @@ func (srv *Server) DeleteImage(name string, imgs *engine.Table, first, force boo tag = graph.DEFAULTTAG } - img, err := srv.runtime.repositories.LookupImage(name) + img, err := srv.runtime.Repositories().LookupImage(name) if err != nil { - if r, _ := srv.runtime.repositories.Get(repoName); r != nil { + if r, _ := srv.runtime.Repositories().Get(repoName); r != nil { return fmt.Errorf("No such image: %s:%s", repoName, tag) } return fmt.Errorf("No such image: %s", name) @@ -1854,14 +1851,14 @@ func (srv *Server) DeleteImage(name string, imgs *engine.Table, first, force boo tag = "" } - byParents, err := srv.runtime.graph.ByParent() + byParents, err := srv.runtime.Graph().ByParent() if err != nil { return err } //If delete by id, see if the id belong only to one repository if repoName == "" { - for _, repoAndTag := range srv.runtime.repositories.ByID()[img.ID] { + for _, repoAndTag := range srv.runtime.Repositories().ByID()[img.ID] { parsedRepo, parsedTag := utils.ParseRepositoryTag(repoAndTag) if repoName == "" || repoName == parsedRepo { repoName = parsedRepo @@ -1884,7 +1881,7 @@ func (srv *Server) DeleteImage(name string, imgs *engine.Table, first, force boo //Untag the current image for _, tag := range tags { - tagDeleted, err := srv.runtime.repositories.Delete(repoName, tag) + tagDeleted, err := srv.runtime.Repositories().Delete(repoName, tag) if err != nil { return err } @@ -1895,16 +1892,16 @@ func (srv *Server) DeleteImage(name string, imgs *engine.Table, first, force boo srv.LogEvent("untag", img.ID, "") } } - tags = srv.runtime.repositories.ByID()[img.ID] + tags = srv.runtime.Repositories().ByID()[img.ID] if (len(tags) <= 1 && repoName == "") || len(tags) == 0 { if len(byParents[img.ID]) == 0 { if err := srv.canDeleteImage(img.ID); err != nil { return err } - if err := srv.runtime.repositories.DeleteAll(img.ID); err != nil { + if err := srv.runtime.Repositories().DeleteAll(img.ID); err != nil { return err } - if err := srv.runtime.graph.Delete(img.ID); err != nil { + if err := srv.runtime.Graph().Delete(img.ID); err != nil { return err } out := &engine.Env{} @@ -1943,7 +1940,7 @@ func (srv *Server) ImageDelete(job *engine.Job) engine.Status { func (srv *Server) canDeleteImage(imgID string) error { for _, container := range srv.runtime.List() { - parent, err := srv.runtime.repositories.LookupImage(container.Image) + parent, err := srv.runtime.Repositories().LookupImage(container.Image) if err != nil { return err } @@ -1963,7 +1960,7 @@ func (srv *Server) canDeleteImage(imgID string) error { func (srv *Server) ImageGetCached(imgID string, config *runconfig.Config) (*image.Image, error) { // Retrieve all images - images, err := srv.runtime.graph.Map() + images, err := srv.runtime.Graph().Map() if err != nil { return nil, err } @@ -1980,7 +1977,7 @@ func (srv *Server) ImageGetCached(imgID string, config *runconfig.Config) (*imag // Loop on the children of the given image and check the config var match *image.Image for elem := range imageMap[imgID] { - img, err := srv.runtime.graph.Get(elem) + img, err := srv.runtime.Graph().Get(elem) if err != nil { return nil, err } @@ -1993,7 +1990,7 @@ func (srv *Server) ImageGetCached(imgID string, config *runconfig.Config) (*imag return match, nil } -func (srv *Server) RegisterLinks(container *Container, hostConfig *runconfig.HostConfig) error { +func (srv *Server) RegisterLinks(container *runtime.Container, hostConfig *runconfig.HostConfig) error { runtime := srv.runtime if hostConfig != nil && hostConfig.Links != nil { @@ -2017,7 +2014,7 @@ func (srv *Server) RegisterLinks(container *Container, hostConfig *runconfig.Hos // After we load all the links into the runtime // set them to nil on the hostconfig hostConfig.Links = nil - if err := container.writeHostConfig(); err != nil { + if err := container.WriteHostConfig(); err != nil { return err } } @@ -2065,13 +2062,13 @@ func (srv *Server) ContainerStart(job *engine.Job) engine.Status { if err := srv.RegisterLinks(container, hostConfig); err != nil { return job.Error(err) } - container.hostConfig = hostConfig + container.SetHostConfig(hostConfig) container.ToDisk() } if err := container.Start(); err != nil { return job.Errorf("Cannot start container %s: %s", name, err) } - srv.LogEvent("start", container.ID, runtime.repositories.ImageName(container.Image)) + srv.LogEvent("start", container.ID, runtime.Repositories().ImageName(container.Image)) return engine.StatusOK } @@ -2091,7 +2088,7 @@ func (srv *Server) ContainerStop(job *engine.Job) engine.Status { if err := container.Stop(int(t)); err != nil { return job.Errorf("Cannot stop container %s: %s\n", name, err) } - srv.LogEvent("stop", container.ID, srv.runtime.repositories.ImageName(container.Image)) + srv.LogEvent("stop", container.ID, srv.runtime.Repositories().ImageName(container.Image)) } else { return job.Errorf("No such container: %s\n", name) } @@ -2237,7 +2234,7 @@ func (srv *Server) ContainerAttach(job *engine.Job) engine.Status { return engine.StatusOK } -func (srv *Server) ContainerInspect(name string) (*Container, error) { +func (srv *Server) ContainerInspect(name string) (*runtime.Container, error) { if container := srv.runtime.Get(name); container != nil { return container, nil } @@ -2245,7 +2242,7 @@ func (srv *Server) ContainerInspect(name string) (*Container, error) { } func (srv *Server) ImageInspect(name string) (*image.Image, error) { - if image, err := srv.runtime.repositories.LookupImage(name); err == nil && image != nil { + if image, err := srv.runtime.Repositories().LookupImage(name); err == nil && image != nil { return image, nil } return nil, fmt.Errorf("No such image: %s", name) @@ -2280,9 +2277,9 @@ func (srv *Server) JobInspect(job *engine.Job) engine.Status { return job.Error(errContainer) } object = &struct { - *Container + *runtime.Container HostConfig *runconfig.HostConfig - }{container, container.hostConfig} + }{container, container.HostConfig()} default: return job.Errorf("Unknown kind: %s", kind) } @@ -2322,7 +2319,7 @@ func (srv *Server) ContainerCopy(job *engine.Job) engine.Status { } func NewServer(eng *engine.Engine, config *daemonconfig.Config) (*Server, error) { - runtime, err := NewRuntime(config, eng) + runtime, err := runtime.NewRuntime(config, eng) if err != nil { return nil, err } @@ -2335,7 +2332,7 @@ func NewServer(eng *engine.Engine, config *daemonconfig.Config) (*Server, error) listeners: make(map[string]chan utils.JSONMessage), running: true, } - runtime.srv = srv + runtime.SetServer(srv) return srv, nil } @@ -2403,7 +2400,7 @@ func (srv *Server) Close() error { type Server struct { sync.RWMutex - runtime *Runtime + runtime *runtime.Runtime pullingPool map[string]chan struct{} pushingPool map[string]chan struct{} events []utils.JSONMessage diff --git a/utils.go b/utils.go index ef666b0de1..0fda006860 100644 --- a/utils.go +++ b/utils.go @@ -2,9 +2,6 @@ package docker import ( "github.com/dotcloud/docker/archive" - "github.com/dotcloud/docker/nat" - "github.com/dotcloud/docker/pkg/namesgenerator" - "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" ) @@ -12,45 +9,8 @@ type Change struct { archive.Change } -func migratePortMappings(config *runconfig.Config, hostConfig *runconfig.HostConfig) error { - if config.PortSpecs != nil { - ports, bindings, err := nat.ParsePortSpecs(config.PortSpecs) - if err != nil { - return err - } - config.PortSpecs = nil - if len(bindings) > 0 { - if hostConfig == nil { - hostConfig = &runconfig.HostConfig{} - } - hostConfig.PortBindings = bindings - } - - if config.ExposedPorts == nil { - config.ExposedPorts = make(nat.PortSet, len(ports)) - } - for k, v := range ports { - config.ExposedPorts[k] = v - } - } - return nil -} - // Links come in the format of // name:alias func parseLink(rawLink string) (map[string]string, error) { return utils.PartParser("name:alias", rawLink) } - -type checker struct { - runtime *Runtime -} - -func (c *checker) Exists(name string) bool { - return c.runtime.containerGraph.Exists("/" + name) -} - -// Generate a random and unique name -func generateRandomName(runtime *Runtime) (string, error) { - return namesgenerator.GenerateRandomName(&checker{runtime}) -} From 47edf3e8bf759270c243711eaed1faa1d4d61f35 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Sun, 9 Mar 2014 23:16:15 -0700 Subject: [PATCH 029/384] Add IsRunning to server interface Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runtime/server.go | 1 + 1 file changed, 1 insertion(+) diff --git a/runtime/server.go b/runtime/server.go index 0d2b71dea7..a74c4d1200 100644 --- a/runtime/server.go +++ b/runtime/server.go @@ -6,4 +6,5 @@ import ( type Server interface { LogEvent(action, id, from string) *utils.JSONMessage + IsRunning() bool // returns true if the server is currently in operation } From 50082f792be22b97dcc79afe666289ecefbc3864 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sun, 9 Mar 2014 23:16:42 -0600 Subject: [PATCH 030/384] Fix a few packaging bugs, including and especially a temporary patch to our upstart script to mount cgroups properly Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- .../init/{sysvinit => sysvinit-debian}/docker | 42 ++++-- contrib/init/sysvinit-debian/docker.default | 13 ++ contrib/init/upstart/docker.conf | 13 +- hack/make/ubuntu | 131 +++++++++--------- 4 files changed, 123 insertions(+), 76 deletions(-) rename contrib/init/{sysvinit => sysvinit-debian}/docker (61%) create mode 100644 contrib/init/sysvinit-debian/docker.default diff --git a/contrib/init/sysvinit/docker b/contrib/init/sysvinit-debian/docker similarity index 61% rename from contrib/init/sysvinit/docker rename to contrib/init/sysvinit-debian/docker index 2d79c4d4c0..510683a459 100755 --- a/contrib/init/sysvinit/docker +++ b/contrib/init/sysvinit-debian/docker @@ -14,13 +14,15 @@ # VMs, bare metal, OpenStack clusters, public clouds and more. ### END INIT INFO +export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin + BASE=$(basename $0) +# modify these in /etc/default/$BASE (/etc/default/docker) DOCKER=/usr/bin/$BASE DOCKER_PIDFILE=/var/run/$BASE.pid DOCKER_OPTS= - -PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin +DOCKER_DESC="Docker" # Get lsb functions . /lib/lsb/init-functions @@ -30,8 +32,8 @@ if [ -f /etc/default/$BASE ]; then fi # see also init_is_upstart in /lib/lsb/init-functions (which isn't available in Ubuntu 12.04, or we'd use it) -if [ -x /sbin/initctl ] && /sbin/initctl version 2>/dev/null | /bin/grep -q upstart; then - log_failure_msg "Docker is managed via upstart, try using service $BASE $1" +if [ -x /sbin/initctl ] && /sbin/initctl version 2>/dev/null | grep -q upstart; then + log_failure_msg "$DOCKER_DESC is managed via upstart, try using service $BASE $1" exit 1 fi @@ -43,7 +45,7 @@ fi fail_unless_root() { if [ "$(id -u)" != '0' ]; then - log_failure_msg "Docker must be run as root" + log_failure_msg "$DOCKER_DESC must be run as root" exit 1 fi } @@ -51,21 +53,37 @@ fail_unless_root() { case "$1" in start) fail_unless_root - log_begin_msg "Starting Docker: $BASE" - mount | grep cgroup >/dev/null || mount -t cgroup none /sys/fs/cgroup 2>/dev/null + + if ! grep -q cgroup /proc/mounts; then + # rough approximation of cgroupfs-mount + mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup + for sys in $(cut -d' ' -f1 /proc/cgroups); do + mkdir -p /sys/fs/cgroup/$sys + if ! mount -n -t cgroup -o $sys cgroup /sys/fs/cgroup/$sys 2>/dev/null; then + rmdir /sys/fs/cgroup/$sys 2>/dev/null || true + fi + done + fi + + touch /var/log/docker.log + chgrp docker /var/log/docker.log + + log_begin_msg "Starting $DOCKER_DESC: $BASE" start-stop-daemon --start --background \ + --no-close \ --exec "$DOCKER" \ --pidfile "$DOCKER_PIDFILE" \ - -- -d -p "$DOCKER_PIDFILE" \ - $DOCKER_OPTS + -- \ + -d -p "$DOCKER_PIDFILE" \ + $DOCKER_OPTS \ + > /var/log/docker.log 2>&1 log_end_msg $? ;; stop) fail_unless_root - log_begin_msg "Stopping Docker: $BASE" - start-stop-daemon --stop \ - --pidfile "$DOCKER_PIDFILE" + log_begin_msg "Stopping $DOCKER_DESC: $BASE" + start-stop-daemon --stop --pidfile "$DOCKER_PIDFILE" log_end_msg $? ;; diff --git a/contrib/init/sysvinit-debian/docker.default b/contrib/init/sysvinit-debian/docker.default new file mode 100644 index 0000000000..d5110b5e2f --- /dev/null +++ b/contrib/init/sysvinit-debian/docker.default @@ -0,0 +1,13 @@ +# Docker Upstart and SysVinit configuration file + +# Customize location of Docker binary (especially for development testing). +#DOCKER="/usr/local/bin/docker" + +# Use DOCKER_OPTS to modify the daemon startup options. +#DOCKER_OPTS="-dns 8.8.8.8 -dns 8.8.4.4" + +# If you need Docker to use an HTTP proxy, it can also be specified here. +#export http_proxy="http://127.0.0.1:3128/" + +# This is also a handy place to tweak where Docker's temporary files go. +#export TMPDIR="/mnt/bigdrive/docker-tmp" diff --git a/contrib/init/upstart/docker.conf b/contrib/init/upstart/docker.conf index ee8a447c27..e2cc4536e1 100644 --- a/contrib/init/upstart/docker.conf +++ b/contrib/init/upstart/docker.conf @@ -1,15 +1,26 @@ description "Docker daemon" -start on filesystem and started lxc-net +start on filesystem stop on runlevel [!2345] respawn script + # modify these in /etc/default/$UPSTART_JOB (/etc/default/docker) DOCKER=/usr/bin/$UPSTART_JOB DOCKER_OPTS= if [ -f /etc/default/$UPSTART_JOB ]; then . /etc/default/$UPSTART_JOB fi + if ! grep -q cgroup /proc/mounts; then + # rough approximation of cgroupfs-mount + mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup + for sys in $(cut -d' ' -f1 /proc/cgroups); do + mkdir -p /sys/fs/cgroup/$sys + if ! mount -n -t cgroup -o $sys cgroup /sys/fs/cgroup/$sys 2>/dev/null; then + rmdir /sys/fs/cgroup/$sys 2>/dev/null || true + fi + done + fi "$DOCKER" -d $DOCKER_OPTS end script diff --git a/hack/make/ubuntu b/hack/make/ubuntu index 602d4ac1ad..ebc12f27ec 100644 --- a/hack/make/ubuntu +++ b/hack/make/ubuntu @@ -29,42 +29,36 @@ bundle_ubuntu() { cp contrib/udev/80-docker.rules $DIR/etc/udev/rules.d/ # Include our init scripts - mkdir -p $DIR/etc - cp -R contrib/init/upstart $DIR/etc/init - cp -R contrib/init/sysvinit $DIR/etc/init.d - mkdir -p $DIR/lib/systemd - cp -R contrib/init/systemd $DIR/lib/systemd/system - + mkdir -p $DIR/etc/init + cp contrib/init/upstart/docker.conf $DIR/etc/init/ + mkdir -p $DIR/etc/init.d + cp contrib/init/sysvinit-debian/docker $DIR/etc/init.d/ mkdir -p $DIR/etc/default - cat > $DIR/etc/default/docker <<'EOF' -# Docker Upstart and SysVinit configuration file - -# Customize location of Docker binary (especially for development testing). -#DOCKER="/usr/local/bin/docker" - -# Use DOCKER_OPTS to modify the daemon startup options. -#DOCKER_OPTS="-dns 8.8.8.8" - -# If you need Docker to use an HTTP proxy, it can also be specified here. -#export http_proxy=http://127.0.0.1:3128/ -EOF + cp contrib/init/sysvinit-debian/docker.default $DIR/etc/default/docker + mkdir -p $DIR/lib/systemd/system + cp contrib/init/systemd/docker.service $DIR/lib/systemd/system/ # Copy the binary # This will fail if the binary bundle hasn't been built mkdir -p $DIR/usr/bin - # Copy the binary - # This will fail if the binary bundle hasn't been built cp $DEST/../binary/docker-$VERSION $DIR/usr/bin/docker # Generate postinst/prerm/postrm scripts - cat > /tmp/postinst <<'EOF' + cat > $DEST/postinst <<'EOF' #!/bin/sh set -e set -u -getent group docker > /dev/null || groupadd --system docker || true +if [ "$1" = 'configure' ] && [ -z "$2" ]; then + if ! getent group docker > /dev/null; then + groupadd --system docker + fi +fi -update-rc.d docker defaults > /dev/null || true +if ! { [ -x /sbin/initctl ] && /sbin/initctl version 2>/dev/null | grep -q upstart; }; then + # we only need to do this if upstart isn't in charge + update-rc.d docker defaults > /dev/null || true +fi if [ -n "$2" ]; then _dh_action=restart else @@ -74,7 +68,7 @@ service docker $_dh_action 2>/dev/null || true #DEBHELPER# EOF - cat > /tmp/prerm <<'EOF' + cat > $DEST/prerm <<'EOF' #!/bin/sh set -e set -u @@ -83,7 +77,7 @@ service docker stop 2>/dev/null || true #DEBHELPER# EOF - cat > /tmp/postrm <<'EOF' + cat > $DEST/postrm <<'EOF' #!/bin/sh set -e set -u @@ -101,50 +95,61 @@ fi #DEBHELPER# EOF # TODO swaths of these were borrowed from debhelper's auto-inserted stuff, because we're still using fpm - we need to use debhelper instead, and somehow reconcile Ubuntu that way - chmod +x /tmp/postinst /tmp/prerm + chmod +x $DEST/postinst $DEST/prerm $DEST/postrm ( + # switch directories so we create *.deb in the right folder cd $DEST + + # create lxc-docker-VERSION package fpm -s dir -C $DIR \ - --name lxc-docker-$VERSION --version $PKGVERSION \ - --after-install /tmp/postinst \ - --before-remove /tmp/prerm \ - --after-remove /tmp/postrm \ - --architecture "$PACKAGE_ARCHITECTURE" \ - --prefix / \ - --depends iptables \ - --deb-recommends aufs-tools \ - --deb-recommends ca-certificates \ - --deb-recommends git \ - --deb-recommends xz-utils \ - --description "$PACKAGE_DESCRIPTION" \ - --maintainer "$PACKAGE_MAINTAINER" \ - --conflicts docker \ - --conflicts docker.io \ - --conflicts lxc-docker-virtual-package \ - --provides lxc-docker \ - --provides lxc-docker-virtual-package \ - --replaces lxc-docker \ - --replaces lxc-docker-virtual-package \ - --url "$PACKAGE_URL" \ - --license "$PACKAGE_LICENSE" \ - --config-files /etc/udev/rules.d/80-docker.rules \ - --config-files /etc/init/docker.conf \ - --config-files /etc/init.d/docker \ - --config-files /etc/default/docker \ - --deb-compression gz \ - -t deb . + --name lxc-docker-$VERSION --version $PKGVERSION \ + --after-install $DEST/postinst \ + --before-remove $DEST/prerm \ + --after-remove $DEST/postrm \ + --architecture "$PACKAGE_ARCHITECTURE" \ + --prefix / \ + --depends iptables \ + --deb-recommends aufs-tools \ + --deb-recommends ca-certificates \ + --deb-recommends git \ + --deb-recommends xz-utils \ + --deb-suggests cgroup-lite \ + --description "$PACKAGE_DESCRIPTION" \ + --maintainer "$PACKAGE_MAINTAINER" \ + --conflicts docker \ + --conflicts docker.io \ + --conflicts lxc-docker-virtual-package \ + --provides lxc-docker \ + --provides lxc-docker-virtual-package \ + --replaces lxc-docker \ + --replaces lxc-docker-virtual-package \ + --url "$PACKAGE_URL" \ + --license "$PACKAGE_LICENSE" \ + --config-files /etc/udev/rules.d/80-docker.rules \ + --config-files /etc/init/docker.conf \ + --config-files /etc/init.d/docker \ + --config-files /etc/default/docker \ + --deb-compression gz \ + -t deb . + # TODO replace "Suggests: cgroup-lite" with "Recommends: cgroupfs-mount | cgroup-lite" once cgroupfs-mount is available + + # create empty lxc-docker wrapper package fpm -s empty \ - --name lxc-docker --version $PKGVERSION \ - --architecture "$PACKAGE_ARCHITECTURE" \ - --depends lxc-docker-$VERSION \ - --description "$PACKAGE_DESCRIPTION" \ - --maintainer "$PACKAGE_MAINTAINER" \ - --url "$PACKAGE_URL" \ - --license "$PACKAGE_LICENSE" \ - --deb-compression gz \ - -t deb + --name lxc-docker --version $PKGVERSION \ + --architecture "$PACKAGE_ARCHITECTURE" \ + --depends lxc-docker-$VERSION \ + --description "$PACKAGE_DESCRIPTION" \ + --maintainer "$PACKAGE_MAINTAINER" \ + --url "$PACKAGE_URL" \ + --license "$PACKAGE_LICENSE" \ + --deb-compression gz \ + -t deb ) + + # clean up after ourselves so we have a clean output directory + rm $DEST/postinst $DEST/prerm $DEST/postrm + rm -r $DIR } bundle_ubuntu From 3e4d0857bf560296801e68b1b218654954c952e9 Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Mon, 10 Mar 2014 12:48:07 -0700 Subject: [PATCH 031/384] Removing myself from doc maintainers due to other obligations eliminating time to maintain docs. Docker-DCO-1.1-Signed-off-by: Andy Rothfusz (github: metalivedev) --- docs/MAINTAINERS | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/MAINTAINERS b/docs/MAINTAINERS index e816670419..52505fab00 100644 --- a/docs/MAINTAINERS +++ b/docs/MAINTAINERS @@ -1,3 +1,2 @@ -Andy Rothfusz (@metalivedev) James Turnbull (@jamtur01) Sven Dowideit (@SvenDowideit) From 0ebf5d0ab3a3999a7e2efe619ef194ff3817e3a3 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 10 Mar 2014 20:24:15 +0000 Subject: [PATCH 032/384] move a unit test file Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- commands_unit_test.go | 160 --------------------------------------- runconfig/config_test.go | 154 +++++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 160 deletions(-) delete mode 100644 commands_unit_test.go diff --git a/commands_unit_test.go b/commands_unit_test.go deleted file mode 100644 index 60d8d60398..0000000000 --- a/commands_unit_test.go +++ /dev/null @@ -1,160 +0,0 @@ -package docker - -import ( - "github.com/dotcloud/docker/runconfig" - "strings" - "testing" -) - -func parse(t *testing.T, args string) (*runconfig.Config, *runconfig.HostConfig, error) { - config, hostConfig, _, err := runconfig.Parse(strings.Split(args+" ubuntu bash", " "), nil) - return config, hostConfig, err -} - -func mustParse(t *testing.T, args string) (*runconfig.Config, *runconfig.HostConfig) { - config, hostConfig, err := parse(t, args) - if err != nil { - t.Fatal(err) - } - return config, hostConfig -} - -func TestParseRunLinks(t *testing.T) { - if _, hostConfig := mustParse(t, "-link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" { - t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links) - } - if _, hostConfig := mustParse(t, "-link a:b -link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" { - t.Fatalf("Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v", hostConfig.Links) - } - if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 { - t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links) - } - - if _, _, err := parse(t, "-link a"); err == nil { - t.Fatalf("Error parsing links. `-link a` should be an error but is not") - } - if _, _, err := parse(t, "-link"); err == nil { - t.Fatalf("Error parsing links. `-link` should be an error but is not") - } -} - -func TestParseRunAttach(t *testing.T) { - if config, _ := mustParse(t, "-a stdin"); !config.AttachStdin || config.AttachStdout || config.AttachStderr { - t.Fatalf("Error parsing attach flags. Expect only Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr) - } - if config, _ := mustParse(t, "-a stdin -a stdout"); !config.AttachStdin || !config.AttachStdout || config.AttachStderr { - t.Fatalf("Error parsing attach flags. Expect only Stdin and Stdout enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr) - } - if config, _ := mustParse(t, "-a stdin -a stdout -a stderr"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr { - t.Fatalf("Error parsing attach flags. Expect all attach enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr) - } - if config, _ := mustParse(t, ""); config.AttachStdin || !config.AttachStdout || !config.AttachStderr { - t.Fatalf("Error parsing attach flags. Expect Stdin disabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr) - } - - if _, _, err := parse(t, "-a"); err == nil { - t.Fatalf("Error parsing attach flags, `-a` should be an error but is not") - } - if _, _, err := parse(t, "-a invalid"); err == nil { - t.Fatalf("Error parsing attach flags, `-a invalid` should be an error but is not") - } - if _, _, err := parse(t, "-a invalid -a stdout"); err == nil { - t.Fatalf("Error parsing attach flags, `-a stdout -a invalid` should be an error but is not") - } - if _, _, err := parse(t, "-a stdout -a stderr -d"); err == nil { - t.Fatalf("Error parsing attach flags, `-a stdout -a stderr -d` should be an error but is not") - } - if _, _, err := parse(t, "-a stdin -d"); err == nil { - t.Fatalf("Error parsing attach flags, `-a stdin -d` should be an error but is not") - } - if _, _, err := parse(t, "-a stdout -d"); err == nil { - t.Fatalf("Error parsing attach flags, `-a stdout -d` should be an error but is not") - } - if _, _, err := parse(t, "-a stderr -d"); err == nil { - t.Fatalf("Error parsing attach flags, `-a stderr -d` should be an error but is not") - } - if _, _, err := parse(t, "-d -rm"); err == nil { - t.Fatalf("Error parsing attach flags, `-d -rm` should be an error but is not") - } -} - -func TestParseRunVolumes(t *testing.T) { - if config, hostConfig := mustParse(t, "-v /tmp"); hostConfig.Binds != nil { - t.Fatalf("Error parsing volume flags, `-v /tmp` should not mount-bind anything. Received %v", hostConfig.Binds) - } else if _, exists := config.Volumes["/tmp"]; !exists { - t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes) - } - - if config, hostConfig := mustParse(t, "-v /tmp -v /var"); hostConfig.Binds != nil { - t.Fatalf("Error parsing volume flags, `-v /tmp -v /var` should not mount-bind anything. Received %v", hostConfig.Binds) - } else if _, exists := config.Volumes["/tmp"]; !exists { - t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Recevied %v", config.Volumes) - } else if _, exists := config.Volumes["/var"]; !exists { - t.Fatalf("Error parsing volume flags, `-v /var` is missing from volumes. Received %v", config.Volumes) - } - - if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp" { - t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp` should mount-bind /hostTmp into /containeTmp. Received %v", hostConfig.Binds) - } else if _, exists := config.Volumes["/containerTmp"]; !exists { - t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes) - } - - if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /hostVar:/containerVar"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp" || hostConfig.Binds[1] != "/hostVar:/containerVar" { - t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /hostVar:/containerVar` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds) - } else if _, exists := config.Volumes["/containerTmp"]; !exists { - t.Fatalf("Error parsing volume flags, `-v /containerTmp` is missing from volumes. Received %v", config.Volumes) - } else if _, exists := config.Volumes["/containerVar"]; !exists { - t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes) - } - - if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp:ro" || hostConfig.Binds[1] != "/hostVar:/containerVar:rw" { - t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds) - } else if _, exists := config.Volumes["/containerTmp"]; !exists { - t.Fatalf("Error parsing volume flags, `-v /containerTmp` is missing from volumes. Received %v", config.Volumes) - } else if _, exists := config.Volumes["/containerVar"]; !exists { - t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes) - } - - if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /containerVar"); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != "/hostTmp:/containerTmp" { - t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /containerVar` should mount-bind only /hostTmp into /containeTmp. Received %v", hostConfig.Binds) - } else if _, exists := config.Volumes["/containerTmp"]; !exists { - t.Fatalf("Error parsing volume flags, `-v /containerTmp` is missing from volumes. Received %v", config.Volumes) - } else if _, exists := config.Volumes["/containerVar"]; !exists { - t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes) - } - - if config, hostConfig := mustParse(t, ""); hostConfig.Binds != nil { - t.Fatalf("Error parsing volume flags, without volume, nothing should be mount-binded. Received %v", hostConfig.Binds) - } else if len(config.Volumes) != 0 { - t.Fatalf("Error parsing volume flags, without volume, no volume should be present. Received %v", config.Volumes) - } - - if _, _, err := parse(t, "-v /"); err == nil { - t.Fatalf("Expected error, but got none") - } - - if _, _, err := parse(t, "-v /:/"); err == nil { - t.Fatalf("Error parsing volume flags, `-v /:/` should fail but didn't") - } - if _, _, err := parse(t, "-v"); err == nil { - t.Fatalf("Error parsing volume flags, `-v` should fail but didn't") - } - if _, _, err := parse(t, "-v /tmp:"); err == nil { - t.Fatalf("Error parsing volume flags, `-v /tmp:` should fail but didn't") - } - if _, _, err := parse(t, "-v /tmp:ro"); err == nil { - t.Fatalf("Error parsing volume flags, `-v /tmp:ro` should fail but didn't") - } - if _, _, err := parse(t, "-v /tmp::"); err == nil { - t.Fatalf("Error parsing volume flags, `-v /tmp::` should fail but didn't") - } - if _, _, err := parse(t, "-v :"); err == nil { - t.Fatalf("Error parsing volume flags, `-v :` should fail but didn't") - } - if _, _, err := parse(t, "-v ::"); err == nil { - t.Fatalf("Error parsing volume flags, `-v ::` should fail but didn't") - } - if _, _, err := parse(t, "-v /tmp:/tmp:/tmp:/tmp"); err == nil { - t.Fatalf("Error parsing volume flags, `-v /tmp:/tmp:/tmp:/tmp` should fail but didn't") - } -} diff --git a/runconfig/config_test.go b/runconfig/config_test.go index 3ef31491fc..40d53fa2f4 100644 --- a/runconfig/config_test.go +++ b/runconfig/config_test.go @@ -2,9 +2,163 @@ package runconfig import ( "github.com/dotcloud/docker/nat" + "strings" "testing" ) +func parse(t *testing.T, args string) (*Config, *HostConfig, error) { + config, hostConfig, _, err := Parse(strings.Split(args+" ubuntu bash", " "), nil) + return config, hostConfig, err +} + +func mustParse(t *testing.T, args string) (*Config, *HostConfig) { + config, hostConfig, err := parse(t, args) + if err != nil { + t.Fatal(err) + } + return config, hostConfig +} + +func TestParseRunLinks(t *testing.T) { + if _, hostConfig := mustParse(t, "-link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" { + t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links) + } + if _, hostConfig := mustParse(t, "-link a:b -link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" { + t.Fatalf("Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v", hostConfig.Links) + } + if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 { + t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links) + } + + if _, _, err := parse(t, "-link a"); err == nil { + t.Fatalf("Error parsing links. `-link a` should be an error but is not") + } + if _, _, err := parse(t, "-link"); err == nil { + t.Fatalf("Error parsing links. `-link` should be an error but is not") + } +} + +func TestParseRunAttach(t *testing.T) { + if config, _ := mustParse(t, "-a stdin"); !config.AttachStdin || config.AttachStdout || config.AttachStderr { + t.Fatalf("Error parsing attach flags. Expect only Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr) + } + if config, _ := mustParse(t, "-a stdin -a stdout"); !config.AttachStdin || !config.AttachStdout || config.AttachStderr { + t.Fatalf("Error parsing attach flags. Expect only Stdin and Stdout enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr) + } + if config, _ := mustParse(t, "-a stdin -a stdout -a stderr"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr { + t.Fatalf("Error parsing attach flags. Expect all attach enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr) + } + if config, _ := mustParse(t, ""); config.AttachStdin || !config.AttachStdout || !config.AttachStderr { + t.Fatalf("Error parsing attach flags. Expect Stdin disabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr) + } + + if _, _, err := parse(t, "-a"); err == nil { + t.Fatalf("Error parsing attach flags, `-a` should be an error but is not") + } + if _, _, err := parse(t, "-a invalid"); err == nil { + t.Fatalf("Error parsing attach flags, `-a invalid` should be an error but is not") + } + if _, _, err := parse(t, "-a invalid -a stdout"); err == nil { + t.Fatalf("Error parsing attach flags, `-a stdout -a invalid` should be an error but is not") + } + if _, _, err := parse(t, "-a stdout -a stderr -d"); err == nil { + t.Fatalf("Error parsing attach flags, `-a stdout -a stderr -d` should be an error but is not") + } + if _, _, err := parse(t, "-a stdin -d"); err == nil { + t.Fatalf("Error parsing attach flags, `-a stdin -d` should be an error but is not") + } + if _, _, err := parse(t, "-a stdout -d"); err == nil { + t.Fatalf("Error parsing attach flags, `-a stdout -d` should be an error but is not") + } + if _, _, err := parse(t, "-a stderr -d"); err == nil { + t.Fatalf("Error parsing attach flags, `-a stderr -d` should be an error but is not") + } + if _, _, err := parse(t, "-d -rm"); err == nil { + t.Fatalf("Error parsing attach flags, `-d -rm` should be an error but is not") + } +} + +func TestParseRunVolumes(t *testing.T) { + if config, hostConfig := mustParse(t, "-v /tmp"); hostConfig.Binds != nil { + t.Fatalf("Error parsing volume flags, `-v /tmp` should not mount-bind anything. Received %v", hostConfig.Binds) + } else if _, exists := config.Volumes["/tmp"]; !exists { + t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes) + } + + if config, hostConfig := mustParse(t, "-v /tmp -v /var"); hostConfig.Binds != nil { + t.Fatalf("Error parsing volume flags, `-v /tmp -v /var` should not mount-bind anything. Received %v", hostConfig.Binds) + } else if _, exists := config.Volumes["/tmp"]; !exists { + t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Recevied %v", config.Volumes) + } else if _, exists := config.Volumes["/var"]; !exists { + t.Fatalf("Error parsing volume flags, `-v /var` is missing from volumes. Received %v", config.Volumes) + } + + if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp" { + t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp` should mount-bind /hostTmp into /containeTmp. Received %v", hostConfig.Binds) + } else if _, exists := config.Volumes["/containerTmp"]; !exists { + t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes) + } + + if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /hostVar:/containerVar"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp" || hostConfig.Binds[1] != "/hostVar:/containerVar" { + t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /hostVar:/containerVar` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds) + } else if _, exists := config.Volumes["/containerTmp"]; !exists { + t.Fatalf("Error parsing volume flags, `-v /containerTmp` is missing from volumes. Received %v", config.Volumes) + } else if _, exists := config.Volumes["/containerVar"]; !exists { + t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes) + } + + if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp:ro" || hostConfig.Binds[1] != "/hostVar:/containerVar:rw" { + t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds) + } else if _, exists := config.Volumes["/containerTmp"]; !exists { + t.Fatalf("Error parsing volume flags, `-v /containerTmp` is missing from volumes. Received %v", config.Volumes) + } else if _, exists := config.Volumes["/containerVar"]; !exists { + t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes) + } + + if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /containerVar"); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != "/hostTmp:/containerTmp" { + t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /containerVar` should mount-bind only /hostTmp into /containeTmp. Received %v", hostConfig.Binds) + } else if _, exists := config.Volumes["/containerTmp"]; !exists { + t.Fatalf("Error parsing volume flags, `-v /containerTmp` is missing from volumes. Received %v", config.Volumes) + } else if _, exists := config.Volumes["/containerVar"]; !exists { + t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes) + } + + if config, hostConfig := mustParse(t, ""); hostConfig.Binds != nil { + t.Fatalf("Error parsing volume flags, without volume, nothing should be mount-binded. Received %v", hostConfig.Binds) + } else if len(config.Volumes) != 0 { + t.Fatalf("Error parsing volume flags, without volume, no volume should be present. Received %v", config.Volumes) + } + + if _, _, err := parse(t, "-v /"); err == nil { + t.Fatalf("Expected error, but got none") + } + + if _, _, err := parse(t, "-v /:/"); err == nil { + t.Fatalf("Error parsing volume flags, `-v /:/` should fail but didn't") + } + if _, _, err := parse(t, "-v"); err == nil { + t.Fatalf("Error parsing volume flags, `-v` should fail but didn't") + } + if _, _, err := parse(t, "-v /tmp:"); err == nil { + t.Fatalf("Error parsing volume flags, `-v /tmp:` should fail but didn't") + } + if _, _, err := parse(t, "-v /tmp:ro"); err == nil { + t.Fatalf("Error parsing volume flags, `-v /tmp:ro` should fail but didn't") + } + if _, _, err := parse(t, "-v /tmp::"); err == nil { + t.Fatalf("Error parsing volume flags, `-v /tmp::` should fail but didn't") + } + if _, _, err := parse(t, "-v :"); err == nil { + t.Fatalf("Error parsing volume flags, `-v :` should fail but didn't") + } + if _, _, err := parse(t, "-v ::"); err == nil { + t.Fatalf("Error parsing volume flags, `-v ::` should fail but didn't") + } + if _, _, err := parse(t, "-v /tmp:/tmp:/tmp:/tmp"); err == nil { + t.Fatalf("Error parsing volume flags, `-v /tmp:/tmp:/tmp:/tmp` should fail but didn't") + } +} + func TestCompare(t *testing.T) { volumes1 := make(map[string]struct{}) volumes1["/test1"] = struct{}{} From d648708d02134c3dc6788ad21325224d849b3b8f Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 10 Mar 2014 21:06:27 +0000 Subject: [PATCH 033/384] remove utils.go Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- pkg/opts/opts.go | 12 +----------- server.go | 2 +- utils.go | 16 ---------------- 3 files changed, 2 insertions(+), 28 deletions(-) delete mode 100644 utils.go diff --git a/pkg/opts/opts.go b/pkg/opts/opts.go index a1b8752bad..4f5897c796 100644 --- a/pkg/opts/opts.go +++ b/pkg/opts/opts.go @@ -92,22 +92,12 @@ func ValidateAttach(val string) (string, error) { } func ValidateLink(val string) (string, error) { - if _, err := parseLink(val); err != nil { + if _, err := utils.PartParser("name:alias", val); err != nil { return val, err } return val, nil } -// FIXME: this is a duplicate of docker.utils.parseLink. -// it can't be moved to a separate links/ package because -// links depends on Container which is defined in the core. -// -// Links come in the format of -// name:alias -func parseLink(rawLink string) (map[string]string, error) { - return utils.PartParser("name:alias", rawLink) -} - func ValidatePath(val string) (string, error) { var containerPath string diff --git a/server.go b/server.go index 85d56afdb6..52f5f14c0a 100644 --- a/server.go +++ b/server.go @@ -1995,7 +1995,7 @@ func (srv *Server) RegisterLinks(container *runtime.Container, hostConfig *runco if hostConfig != nil && hostConfig.Links != nil { for _, l := range hostConfig.Links { - parts, err := parseLink(l) + parts, err := utils.PartParser("name:alias", l) if err != nil { return err } diff --git a/utils.go b/utils.go deleted file mode 100644 index 0fda006860..0000000000 --- a/utils.go +++ /dev/null @@ -1,16 +0,0 @@ -package docker - -import ( - "github.com/dotcloud/docker/archive" - "github.com/dotcloud/docker/utils" -) - -type Change struct { - archive.Change -} - -// Links come in the format of -// name:alias -func parseLink(rawLink string) (map[string]string, error) { - return utils.PartParser("name:alias", rawLink) -} From d7646f934a268bf071a16439880ba2bba608426e Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 10 Mar 2014 14:08:26 -0700 Subject: [PATCH 034/384] Change version to v0.9.0 Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index ac39a106c4..c70836ca5c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.0 +0.9.0-dev From fde5f573d39020476c08ed25fac0a6306f7b18cc Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 10 Mar 2014 21:10:23 +0000 Subject: [PATCH 035/384] move opts out of pkg because it's related to docker Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- docker/docker.go | 2 +- {pkg/opts => opts}/opts.go | 0 {pkg/opts => opts}/opts_test.go | 0 runconfig/parse.go | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) rename {pkg/opts => opts}/opts.go (100%) rename {pkg/opts => opts}/opts_test.go (100%) diff --git a/docker/docker.go b/docker/docker.go index 2aa10dbe54..b783c6da02 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -10,8 +10,8 @@ import ( "github.com/dotcloud/docker/builtins" "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/opts" flag "github.com/dotcloud/docker/pkg/mflag" - "github.com/dotcloud/docker/pkg/opts" "github.com/dotcloud/docker/sysinit" "github.com/dotcloud/docker/utils" ) diff --git a/pkg/opts/opts.go b/opts/opts.go similarity index 100% rename from pkg/opts/opts.go rename to opts/opts.go diff --git a/pkg/opts/opts_test.go b/opts/opts_test.go similarity index 100% rename from pkg/opts/opts_test.go rename to opts/opts_test.go diff --git a/runconfig/parse.go b/runconfig/parse.go index fb08c068b2..d481da8d3b 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -3,8 +3,8 @@ package runconfig import ( "fmt" "github.com/dotcloud/docker/nat" + "github.com/dotcloud/docker/opts" flag "github.com/dotcloud/docker/pkg/mflag" - "github.com/dotcloud/docker/pkg/opts" "github.com/dotcloud/docker/pkg/sysinfo" "github.com/dotcloud/docker/utils" "io/ioutil" From 6f70ed3a742162c4cf374a2c2bbd094eed3b043b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 10 Mar 2014 21:23:29 +0000 Subject: [PATCH 036/384] remove useless lock move job to server and remove version.go Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- builtins/builtins.go | 1 - server.go | 34 +++++++++++++++++++++++++--------- version.go | 32 -------------------------------- 3 files changed, 25 insertions(+), 42 deletions(-) delete mode 100644 version.go diff --git a/builtins/builtins.go b/builtins/builtins.go index 5b146cd20f..ba3f41b1ca 100644 --- a/builtins/builtins.go +++ b/builtins/builtins.go @@ -36,5 +36,4 @@ func remote(eng *engine.Engine) { func daemon(eng *engine.Engine) { eng.Register("initserver", docker.InitServer) eng.Register("init_networkdriver", lxc.InitDriver) - eng.Register("version", docker.GetVersion) } diff --git a/server.go b/server.go index 52f5f14c0a..1619a16e52 100644 --- a/server.go +++ b/server.go @@ -85,6 +85,7 @@ func InitServer(job *engine.Job) engine.Status { "search": srv.ImagesSearch, "changes": srv.ContainerChanges, "top": srv.ContainerTop, + "version": srv.DockerVersion, "load": srv.ImageLoad, "build": srv.Build, "pull": srv.ImagePull, @@ -836,6 +837,22 @@ func (srv *Server) DockerInfo(job *engine.Job) engine.Status { return engine.StatusOK } +func (srv *Server) DockerVersion(job *engine.Job) engine.Status { + v := &engine.Env{} + v.Set("Version", dockerversion.VERSION) + v.Set("GitCommit", dockerversion.GITCOMMIT) + v.Set("GoVersion", goruntime.Version()) + v.Set("Os", goruntime.GOOS) + v.Set("Arch", goruntime.GOARCH) + if kernelVersion, err := utils.GetKernelVersion(); err == nil { + v.Set("KernelVersion", kernelVersion.String()) + } + if _, err := v.WriteTo(job.Stdout); err != nil { + return job.Error(err) + } + return engine.StatusOK +} + func (srv *Server) ImageHistory(job *engine.Job) engine.Status { if n := len(job.Args); n != 1 { return job.Errorf("Usage: %s IMAGE", job.Name) @@ -2337,16 +2354,15 @@ func NewServer(eng *engine.Engine, config *daemonconfig.Config) (*Server, error) } func (srv *Server) HTTPRequestFactory(metaHeaders map[string][]string) *utils.HTTPRequestFactory { - srv.Lock() - defer srv.Unlock() - v := dockerVersion() httpVersion := make([]utils.VersionInfo, 0, 4) - httpVersion = append(httpVersion, &simpleVersionInfo{"docker", v.Get("Version")}) - httpVersion = append(httpVersion, &simpleVersionInfo{"go", v.Get("GoVersion")}) - httpVersion = append(httpVersion, &simpleVersionInfo{"git-commit", v.Get("GitCommit")}) - httpVersion = append(httpVersion, &simpleVersionInfo{"kernel", v.Get("KernelVersion")}) - httpVersion = append(httpVersion, &simpleVersionInfo{"os", v.Get("Os")}) - httpVersion = append(httpVersion, &simpleVersionInfo{"arch", v.Get("Arch")}) + httpVersion = append(httpVersion, &simpleVersionInfo{"docker", dockerversion.VERSION}) + httpVersion = append(httpVersion, &simpleVersionInfo{"go", goruntime.Version()}) + httpVersion = append(httpVersion, &simpleVersionInfo{"git-commit", dockerversion.GITCOMMIT}) + if kernelVersion, err := utils.GetKernelVersion(); err == nil { + httpVersion = append(httpVersion, &simpleVersionInfo{"kernel", kernelVersion.String()}) + } + httpVersion = append(httpVersion, &simpleVersionInfo{"os", goruntime.GOOS}) + httpVersion = append(httpVersion, &simpleVersionInfo{"arch", goruntime.GOARCH}) ud := utils.NewHTTPUserAgentDecorator(httpVersion...) md := &utils.HTTPMetaHeadersDecorator{ Headers: metaHeaders, diff --git a/version.go b/version.go deleted file mode 100644 index d88def9619..0000000000 --- a/version.go +++ /dev/null @@ -1,32 +0,0 @@ -package docker - -import ( - "github.com/dotcloud/docker/dockerversion" - "github.com/dotcloud/docker/engine" - "github.com/dotcloud/docker/utils" - "runtime" -) - -func GetVersion(job *engine.Job) engine.Status { - if _, err := dockerVersion().WriteTo(job.Stdout); err != nil { - job.Errorf("%s", err) - return engine.StatusErr - } - return engine.StatusOK -} - -// dockerVersion returns detailed version information in the form of a queriable -// environment. -func dockerVersion() *engine.Env { - v := &engine.Env{} - v.Set("Version", dockerversion.VERSION) - v.Set("GitCommit", dockerversion.GITCOMMIT) - v.Set("GoVersion", runtime.Version()) - v.Set("Os", runtime.GOOS) - v.Set("Arch", runtime.GOARCH) - // FIXME:utils.GetKernelVersion should only be needed here - if kernelVersion, err := utils.GetKernelVersion(); err == nil { - v.Set("KernelVersion", kernelVersion.String()) - } - return v -} From 802407a099705a91017fbaa1b6820f145f580d86 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 10 Mar 2014 13:21:30 -0700 Subject: [PATCH 037/384] Update vendor for kr/pty Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- vendor/src/github.com/kr/pty/doc.go | 5 ++ vendor/src/github.com/kr/pty/pty_freebsd.go | 53 +++++++++++++++++++ .../src/github.com/kr/pty/pty_unsupported.go | 27 ++++++++++ 3 files changed, 85 insertions(+) create mode 100644 vendor/src/github.com/kr/pty/pty_freebsd.go create mode 100644 vendor/src/github.com/kr/pty/pty_unsupported.go diff --git a/vendor/src/github.com/kr/pty/doc.go b/vendor/src/github.com/kr/pty/doc.go index 491c060b28..190cfbea92 100644 --- a/vendor/src/github.com/kr/pty/doc.go +++ b/vendor/src/github.com/kr/pty/doc.go @@ -2,9 +2,14 @@ package pty import ( + "errors" "os" ) +// ErrUnsupported is returned if a function is not +// available on the current platform. +var ErrUnsupported = errors.New("unsupported") + // Opens a pty and its corresponding tty. func Open() (pty, tty *os.File, err error) { return open() diff --git a/vendor/src/github.com/kr/pty/pty_freebsd.go b/vendor/src/github.com/kr/pty/pty_freebsd.go new file mode 100644 index 0000000000..13b64d722e --- /dev/null +++ b/vendor/src/github.com/kr/pty/pty_freebsd.go @@ -0,0 +1,53 @@ +package pty + +import ( + "os" + "strconv" + "syscall" + "unsafe" +) + +const ( + sys_TIOCGPTN = 0x4004740F + sys_TIOCSPTLCK = 0x40045431 +) + +func open() (pty, tty *os.File, err error) { + p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0) + if err != nil { + return nil, nil, err + } + + sname, err := ptsname(p) + if err != nil { + return nil, nil, err + } + + t, err := os.OpenFile(sname, os.O_RDWR|syscall.O_NOCTTY, 0) + if err != nil { + return nil, nil, err + } + return p, t, nil +} + +func ptsname(f *os.File) (string, error) { + var n int + err := ioctl(f.Fd(), sys_TIOCGPTN, &n) + if err != nil { + return "", err + } + return "/dev/pts/" + strconv.Itoa(n), nil +} + +func ioctl(fd uintptr, cmd uintptr, data *int) error { + _, _, e := syscall.Syscall( + syscall.SYS_IOCTL, + fd, + cmd, + uintptr(unsafe.Pointer(data)), + ) + if e != 0 { + return syscall.ENOTTY + } + return nil +} diff --git a/vendor/src/github.com/kr/pty/pty_unsupported.go b/vendor/src/github.com/kr/pty/pty_unsupported.go new file mode 100644 index 0000000000..d4958b3583 --- /dev/null +++ b/vendor/src/github.com/kr/pty/pty_unsupported.go @@ -0,0 +1,27 @@ +// +build !linux,!darwin,!freebsd + +package pty + +import ( + "os" +) + +func open() (pty, tty *os.File, err error) { + return nil, nil, ErrUnsupported +} + +func ptsname(f *os.File) (string, error) { + return "", ErrUnsupported +} + +func grantpt(f *os.File) error { + return ErrUnsupported +} + +func unlockpt(f *os.File) error { + return ErrUnsupported +} + +func ioctl(fd, cmd, ptr uintptr) error { + return ErrUnsupported +} From 6ccfb7fb9af207a9999c60e57d1c9486ca949a5e Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 10 Mar 2014 13:25:00 -0700 Subject: [PATCH 038/384] Update bsd specs Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- Dockerfile | 2 +- archive/{start_unsupported.go => stat_unsupported.go} | 4 ++-- pkg/term/{termios_bsd.go => termios_freebsd.go} | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename archive/{start_unsupported.go => stat_unsupported.go} (87%) rename pkg/term/{termios_bsd.go => termios_freebsd.go} (100%) diff --git a/Dockerfile b/Dockerfile index 9929a10f3c..7fad3d56a1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -68,7 +68,7 @@ ENV GOPATH /go:/go/src/github.com/dotcloud/docker/vendor RUN cd /usr/local/go/src && ./make.bash --no-clean 2>&1 # Compile Go for cross compilation -ENV DOCKER_CROSSPLATFORMS linux/386 linux/arm darwin/amd64 darwin/386 +ENV DOCKER_CROSSPLATFORMS linux/386 linux/arm darwin/amd64 darwin/386 freebsd/amd64 freebsd/386 freebsd/arm # (set an explicit GOARM of 5 for maximum compatibility) ENV GOARM 5 RUN cd /usr/local/go/src && bash -xc 'for platform in $DOCKER_CROSSPLATFORMS; do GOOS=${platform%/*} GOARCH=${platform##*/} ./make.bash --no-clean 2>&1; done' diff --git a/archive/start_unsupported.go b/archive/stat_unsupported.go similarity index 87% rename from archive/start_unsupported.go rename to archive/stat_unsupported.go index 834eda8c65..004fa0f0a4 100644 --- a/archive/start_unsupported.go +++ b/archive/stat_unsupported.go @@ -5,11 +5,11 @@ package archive import "syscall" func getLastAccess(stat *syscall.Stat_t) syscall.Timespec { - return stat.Atimespec + return syscall.Timespec{} } func getLastModification(stat *syscall.Stat_t) syscall.Timespec { - return stat.Mtimespec + return syscall.Timespec{} } func LUtimesNano(path string, ts []syscall.Timespec) error { diff --git a/pkg/term/termios_bsd.go b/pkg/term/termios_freebsd.go similarity index 100% rename from pkg/term/termios_bsd.go rename to pkg/term/termios_freebsd.go From 3c25302a0b9fae2c3fff9262b2ae9fa5f6f04db7 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 10 Mar 2014 15:34:38 -0700 Subject: [PATCH 039/384] Update vendor.sh with new kr/pty revision Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- hack/vendor.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/vendor.sh b/hack/vendor.sh index 184cb750a5..ac996dde12 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -39,7 +39,7 @@ clone() { echo done } -clone git github.com/kr/pty 3b1f6487b +clone git github.com/kr/pty 98c7b80083 clone git github.com/gorilla/context 708054d61e5 From 03211ecce07ab64f5263232e1aa3c6248530c5b4 Mon Sep 17 00:00:00 2001 From: srid Date: Mon, 10 Mar 2014 16:50:29 -0700 Subject: [PATCH 040/384] nsinit: prefix errors with their source Docker-DCO-1.1-Signed-off-by: Sridhar Ratnakumar (github: srid) --- pkg/libcontainer/nsinit/nsinit/main.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/libcontainer/nsinit/nsinit/main.go b/pkg/libcontainer/nsinit/nsinit/main.go index 61921c59a3..916be6624e 100644 --- a/pkg/libcontainer/nsinit/nsinit/main.go +++ b/pkg/libcontainer/nsinit/nsinit/main.go @@ -33,11 +33,11 @@ func main() { } container, err := loadContainer() if err != nil { - log.Fatal(err) + log.Fatalf("Unable to load container: %s", err) } ns, err := newNsInit() if err != nil { - log.Fatal(err) + log.Fatalf("Unable to initialize nsinit: %s", err) } switch flag.Arg(0) { @@ -46,7 +46,7 @@ func main() { nspid, err := readPid() if err != nil { if !os.IsNotExist(err) { - log.Fatal(err) + log.Fatalf("Unable to read pid: %s", err) } } if nspid > 0 { @@ -56,7 +56,7 @@ func main() { exitCode, err = ns.Exec(container, term, flag.Args()[1:]) } if err != nil { - log.Fatal(err) + log.Fatalf("Failed to exec: %s", err) } os.Exit(exitCode) case "init": // this is executed inside of the namespace to setup the container @@ -69,10 +69,10 @@ func main() { } syncPipe, err := nsinit.NewSyncPipeFromFd(0, uintptr(pipeFd)) if err != nil { - log.Fatal(err) + log.Fatalf("Unable to create sync pipe: %s", err) } if err := ns.Init(container, cwd, console, syncPipe, flag.Args()[1:]); err != nil { - log.Fatal(err) + log.Fatalf("Unable to initialize for container: %s", err) } default: log.Fatalf("command not supported for nsinit %s", flag.Arg(0)) From 8d88ea0c15b7ce7fd2b0b695c498a7ffa0f2bc87 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 10 Mar 2014 17:16:58 -0700 Subject: [PATCH 041/384] Merge auth package within registry Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- api/client.go | 27 +++++++++++++-------------- api/server.go | 22 +++++++++++----------- auth/MAINTAINERS | 3 --- buildfile.go | 7 +++---- integration/auth_test.go | 12 ++++++------ {auth => registry}/auth.go | 2 +- {auth => registry}/auth_test.go | 2 +- registry/registry.go | 19 +++++++++---------- registry/registry_test.go | 5 ++--- server.go | 23 +++++++++++------------ 10 files changed, 57 insertions(+), 65 deletions(-) delete mode 100644 auth/MAINTAINERS rename {auth => registry}/auth.go (99%) rename {auth => registry}/auth_test.go (99%) diff --git a/api/client.go b/api/client.go index 10075ae613..59ff17fe44 100644 --- a/api/client.go +++ b/api/client.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "github.com/dotcloud/docker/archive" - "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/nat" @@ -229,7 +228,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { // 'docker login': login / register a user to registry service. func (cli *DockerCli) CmdLogin(args ...string) error { - cmd := cli.Subcmd("login", "[OPTIONS] [SERVER]", "Register or Login to a docker registry server, if no server is specified \""+auth.IndexServerAddress()+"\" is the default.") + cmd := cli.Subcmd("login", "[OPTIONS] [SERVER]", "Register or Login to a docker registry server, if no server is specified \""+registry.IndexServerAddress()+"\" is the default.") var username, password, email string @@ -240,7 +239,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { if err != nil { return nil } - serverAddress := auth.IndexServerAddress() + serverAddress := registry.IndexServerAddress() if len(cmd.Args()) > 0 { serverAddress = cmd.Arg(0) } @@ -266,7 +265,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { cli.LoadConfigFile() authconfig, ok := cli.configFile.Configs[serverAddress] if !ok { - authconfig = auth.AuthConfig{} + authconfig = registry.AuthConfig{} } if username == "" { @@ -311,7 +310,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], false) if statusCode == 401 { delete(cli.configFile.Configs, serverAddress) - auth.SaveConfig(cli.configFile) + registry.SaveConfig(cli.configFile) return err } if err != nil { @@ -320,10 +319,10 @@ func (cli *DockerCli) CmdLogin(args ...string) error { var out2 engine.Env err = out2.Decode(stream) if err != nil { - cli.configFile, _ = auth.LoadConfig(os.Getenv("HOME")) + cli.configFile, _ = registry.LoadConfig(os.Getenv("HOME")) return err } - auth.SaveConfig(cli.configFile) + registry.SaveConfig(cli.configFile) if out2.Get("Status") != "" { fmt.Fprintf(cli.out, "%s\n", out2.Get("Status")) } @@ -1008,7 +1007,7 @@ func (cli *DockerCli) CmdPush(args ...string) error { // Custom repositories can have different rules, and we must also // allow pushing by image ID. if len(strings.SplitN(name, "/", 2)) == 1 { - username := cli.configFile.Configs[auth.IndexServerAddress()].Username + username := cli.configFile.Configs[registry.IndexServerAddress()].Username if username == "" { username = "" } @@ -1016,7 +1015,7 @@ func (cli *DockerCli) CmdPush(args ...string) error { } v := url.Values{} - push := func(authConfig auth.AuthConfig) error { + push := func(authConfig registry.AuthConfig) error { buf, err := json.Marshal(authConfig) if err != nil { return err @@ -1075,7 +1074,7 @@ func (cli *DockerCli) CmdPull(args ...string) error { v.Set("fromImage", remote) v.Set("tag", *tag) - pull := func(authConfig auth.AuthConfig) error { + pull := func(authConfig registry.AuthConfig) error { buf, err := json.Marshal(authConfig) if err != nil { return err @@ -2058,8 +2057,8 @@ func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo b if passAuthInfo { cli.LoadConfigFile() // Resolve the Auth config relevant for this server - authConfig := cli.configFile.ResolveAuthConfig(auth.IndexServerAddress()) - getHeaders := func(authConfig auth.AuthConfig) (map[string][]string, error) { + authConfig := cli.configFile.ResolveAuthConfig(registry.IndexServerAddress()) + getHeaders := func(authConfig registry.AuthConfig) (map[string][]string, error) { buf, err := json.Marshal(authConfig) if err != nil { return nil, err @@ -2340,7 +2339,7 @@ func (cli *DockerCli) Subcmd(name, signature, description string) *flag.FlagSet } func (cli *DockerCli) LoadConfigFile() (err error) { - cli.configFile, err = auth.LoadConfig(os.Getenv("HOME")) + cli.configFile, err = registry.LoadConfig(os.Getenv("HOME")) if err != nil { fmt.Fprintf(cli.err, "WARNING: %s\n", err) } @@ -2422,7 +2421,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *Doc type DockerCli struct { proto string addr string - configFile *auth.ConfigFile + configFile *registry.ConfigFile in io.ReadCloser out io.Writer err io.Writer diff --git a/api/server.go b/api/server.go index 6fafe60f9f..048c989540 100644 --- a/api/server.go +++ b/api/server.go @@ -8,12 +8,12 @@ import ( "encoding/json" "expvar" "fmt" - "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/pkg/listenbuffer" "github.com/dotcloud/docker/pkg/systemd" "github.com/dotcloud/docker/pkg/user" "github.com/dotcloud/docker/pkg/version" + "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/utils" "github.com/gorilla/mux" "io" @@ -381,13 +381,13 @@ func postImagesCreate(eng *engine.Engine, version version.Version, w http.Respon job *engine.Job ) authEncoded := r.Header.Get("X-Registry-Auth") - authConfig := &auth.AuthConfig{} + authConfig := ®istry.AuthConfig{} if authEncoded != "" { authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { // for a pull it is not an error if no auth was given // to increase compatibility with the existing api it is defaulting to be empty - authConfig = &auth.AuthConfig{} + authConfig = ®istry.AuthConfig{} } } if image != "" { //pull @@ -429,7 +429,7 @@ func getImagesSearch(eng *engine.Engine, version version.Version, w http.Respons } var ( authEncoded = r.Header.Get("X-Registry-Auth") - authConfig = &auth.AuthConfig{} + authConfig = ®istry.AuthConfig{} metaHeaders = map[string][]string{} ) @@ -438,7 +438,7 @@ func getImagesSearch(eng *engine.Engine, version version.Version, w http.Respons if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { // for a search it is not an error if no auth was given // to increase compatibility with the existing api it is defaulting to be empty - authConfig = &auth.AuthConfig{} + authConfig = ®istry.AuthConfig{} } } for k, v := range r.Header { @@ -494,7 +494,7 @@ func postImagesPush(eng *engine.Engine, version version.Version, w http.Response if err := parseForm(r); err != nil { return err } - authConfig := &auth.AuthConfig{} + authConfig := ®istry.AuthConfig{} authEncoded := r.Header.Get("X-Registry-Auth") if authEncoded != "" { @@ -502,7 +502,7 @@ func postImagesPush(eng *engine.Engine, version version.Version, w http.Response authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { // to increase compatibility to existing api it is defaulting to be empty - authConfig = &auth.AuthConfig{} + authConfig = ®istry.AuthConfig{} } } else { // the old format is supported for compatibility if there was no authConfig header @@ -823,9 +823,9 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite } var ( authEncoded = r.Header.Get("X-Registry-Auth") - authConfig = &auth.AuthConfig{} + authConfig = ®istry.AuthConfig{} configFileEncoded = r.Header.Get("X-Registry-Config") - configFile = &auth.ConfigFile{} + configFile = ®istry.ConfigFile{} job = eng.Job("build") ) @@ -838,7 +838,7 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { // for a pull it is not an error if no auth was given // to increase compatibility with the existing api it is defaulting to be empty - authConfig = &auth.AuthConfig{} + authConfig = ®istry.AuthConfig{} } } @@ -847,7 +847,7 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite if err := json.NewDecoder(configFileJson).Decode(configFile); err != nil { // for a pull it is not an error if no auth was given // to increase compatibility with the existing api it is defaulting to be empty - configFile = &auth.ConfigFile{} + configFile = ®istry.ConfigFile{} } } diff --git a/auth/MAINTAINERS b/auth/MAINTAINERS deleted file mode 100644 index bf3984f5f9..0000000000 --- a/auth/MAINTAINERS +++ /dev/null @@ -1,3 +0,0 @@ -Sam Alba (@samalba) -Joffrey Fuhrer (@shin-) -Ken Cochrane (@kencochrane) diff --git a/buildfile.go b/buildfile.go index 160db4d434..959b085685 100644 --- a/buildfile.go +++ b/buildfile.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "github.com/dotcloud/docker/archive" - "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/runtime" @@ -49,8 +48,8 @@ type buildFile struct { utilizeCache bool rm bool - authConfig *auth.AuthConfig - configFile *auth.ConfigFile + authConfig *registry.AuthConfig + configFile *registry.ConfigFile tmpContainers map[string]struct{} tmpImages map[string]struct{} @@ -793,7 +792,7 @@ func (b *buildFile) BuildStep(name, expression string) error { return nil } -func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeCache, rm bool, outOld io.Writer, sf *utils.StreamFormatter, auth *auth.AuthConfig, authConfigFile *auth.ConfigFile) BuildFile { +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, srv: srv, diff --git a/integration/auth_test.go b/integration/auth_test.go index c5bdabace2..1d9d450573 100644 --- a/integration/auth_test.go +++ b/integration/auth_test.go @@ -4,7 +4,7 @@ import ( "crypto/rand" "encoding/hex" "fmt" - "github.com/dotcloud/docker/auth" + "github.com/dotcloud/docker/registry" "os" "strings" "testing" @@ -18,13 +18,13 @@ import ( func TestLogin(t *testing.T) { os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com") defer os.Setenv("DOCKER_INDEX_URL", "") - authConfig := &auth.AuthConfig{ + authConfig := ®istry.AuthConfig{ Username: "unittester", Password: "surlautrerivejetattendrai", Email: "noise+unittester@docker.com", ServerAddress: "https://indexstaging-docker.dotcloud.com/v1/", } - status, err := auth.Login(authConfig, nil) + status, err := registry.Login(authConfig, nil) if err != nil { t.Fatal(err) } @@ -41,13 +41,13 @@ func TestCreateAccount(t *testing.T) { } token := hex.EncodeToString(tokenBuffer)[:12] username := "ut" + token - authConfig := &auth.AuthConfig{ + authConfig := ®istry.AuthConfig{ Username: username, Password: "test42", Email: fmt.Sprintf("docker-ut+%s@example.com", token), ServerAddress: "https://indexstaging-docker.dotcloud.com/v1/", } - status, err := auth.Login(authConfig, nil) + status, err := registry.Login(authConfig, nil) if err != nil { t.Fatal(err) } @@ -59,7 +59,7 @@ func TestCreateAccount(t *testing.T) { t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status) } - status, err = auth.Login(authConfig, nil) + status, err = registry.Login(authConfig, nil) if err == nil { t.Fatalf("Expected error but found nil instead") } diff --git a/auth/auth.go b/registry/auth.go similarity index 99% rename from auth/auth.go rename to registry/auth.go index 4417dd0f7a..4fdd51fda4 100644 --- a/auth/auth.go +++ b/registry/auth.go @@ -1,4 +1,4 @@ -package auth +package registry import ( "encoding/base64" diff --git a/auth/auth_test.go b/registry/auth_test.go similarity index 99% rename from auth/auth_test.go rename to registry/auth_test.go index 2335072609..3cb1a9ac4b 100644 --- a/auth/auth_test.go +++ b/registry/auth_test.go @@ -1,4 +1,4 @@ -package auth +package registry import ( "io/ioutil" diff --git a/registry/registry.go b/registry/registry.go index cc2e985c31..dbf5d539ff 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -6,7 +6,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -27,7 +26,7 @@ var ( ) func pingRegistryEndpoint(endpoint string) (bool, error) { - if endpoint == auth.IndexServerAddress() { + if endpoint == IndexServerAddress() { // Skip the check, we now this one is valid // (and we never want to fallback to http in case of error) return false, nil @@ -103,7 +102,7 @@ func ResolveRepositoryName(reposName string) (string, string, error) { nameParts[0] != "localhost" { // This is a Docker Index repos (ex: samalba/hipache or ubuntu) err := validateRepositoryName(reposName) - return auth.IndexServerAddress(), reposName, err + return IndexServerAddress(), reposName, err } if len(nameParts) < 2 { // There is a dot in repos name (and no registry address) @@ -601,7 +600,7 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat func (r *Registry) SearchRepositories(term string) (*SearchResults, error) { utils.Debugf("Index server: %s", r.indexEndpoint) - u := auth.IndexServerAddress() + "search?q=" + url.QueryEscape(term) + u := IndexServerAddress() + "search?q=" + url.QueryEscape(term) req, err := r.reqFactory.NewRequest("GET", u, nil) if err != nil { return nil, err @@ -627,12 +626,12 @@ func (r *Registry) SearchRepositories(term string) (*SearchResults, error) { return result, err } -func (r *Registry) GetAuthConfig(withPasswd bool) *auth.AuthConfig { +func (r *Registry) GetAuthConfig(withPasswd bool) *AuthConfig { password := "" if withPasswd { password = r.authConfig.Password } - return &auth.AuthConfig{ + return &AuthConfig{ Username: r.authConfig.Username, Password: password, Email: r.authConfig.Email, @@ -668,12 +667,12 @@ type ImgData struct { type Registry struct { client *http.Client - authConfig *auth.AuthConfig + authConfig *AuthConfig reqFactory *utils.HTTPRequestFactory indexEndpoint string } -func NewRegistry(authConfig *auth.AuthConfig, factory *utils.HTTPRequestFactory, indexEndpoint string) (r *Registry, err error) { +func NewRegistry(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, indexEndpoint string) (r *Registry, err error) { httpTransport := &http.Transport{ DisableKeepAlives: true, Proxy: http.ProxyFromEnvironment, @@ -693,13 +692,13 @@ func NewRegistry(authConfig *auth.AuthConfig, factory *utils.HTTPRequestFactory, // If we're working with a standalone private registry over HTTPS, send Basic Auth headers // alongside our requests. - if indexEndpoint != auth.IndexServerAddress() && strings.HasPrefix(indexEndpoint, "https://") { + if indexEndpoint != IndexServerAddress() && strings.HasPrefix(indexEndpoint, "https://") { standalone, err := pingRegistryEndpoint(indexEndpoint) if err != nil { return nil, err } if standalone { - utils.Debugf("Endpoint %s is eligible for private registry auth. Enabling decorator.", indexEndpoint) + utils.Debugf("Endpoint %s is eligible for private registry registry. Enabling decorator.", indexEndpoint) dec := utils.NewHTTPAuthDecorator(authConfig.Username, authConfig.Password) factory.AddDecorator(dec) } diff --git a/registry/registry_test.go b/registry/registry_test.go index 82a27a166f..f21814c791 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -1,7 +1,6 @@ package registry import ( - "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/utils" "strings" "testing" @@ -14,7 +13,7 @@ var ( ) func spawnTestRegistry(t *testing.T) *Registry { - authConfig := &auth.AuthConfig{} + authConfig := &AuthConfig{} r, err := NewRegistry(authConfig, utils.NewHTTPRequestFactory(), makeURL("/v1/")) if err != nil { t.Fatal(err) @@ -137,7 +136,7 @@ func TestResolveRepositoryName(t *testing.T) { if err != nil { t.Fatal(err) } - assertEqual(t, ep, auth.IndexServerAddress(), "Expected endpoint to be index server address") + assertEqual(t, ep, IndexServerAddress(), "Expected endpoint to be index server address") assertEqual(t, repo, "fooo/bar", "Expected resolved repo to be foo/bar") u := makeURL("")[7:] diff --git a/server.go b/server.go index 52f5f14c0a..0cf78eefa3 100644 --- a/server.go +++ b/server.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "github.com/dotcloud/docker/archive" - "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/daemonconfig" "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" @@ -199,19 +198,19 @@ func (srv *Server) ContainerKill(job *engine.Job) engine.Status { func (srv *Server) Auth(job *engine.Job) engine.Status { var ( err error - authConfig = &auth.AuthConfig{} + authConfig = ®istry.AuthConfig{} ) job.GetenvJson("authConfig", authConfig) // TODO: this is only done here because auth and registry need to be merged into one pkg - if addr := authConfig.ServerAddress; addr != "" && addr != auth.IndexServerAddress() { + if addr := authConfig.ServerAddress; addr != "" && addr != registry.IndexServerAddress() { addr, err = registry.ExpandAndVerifyRegistryUrl(addr) if err != nil { return job.Error(err) } authConfig.ServerAddress = addr } - status, err := auth.Login(authConfig, srv.HTTPRequestFactory(nil)) + status, err := registry.Login(authConfig, srv.HTTPRequestFactory(nil)) if err != nil { return job.Error(err) } @@ -431,8 +430,8 @@ func (srv *Server) Build(job *engine.Job) engine.Status { suppressOutput = job.GetenvBool("q") noCache = job.GetenvBool("nocache") rm = job.GetenvBool("rm") - authConfig = &auth.AuthConfig{} - configFile = &auth.ConfigFile{} + authConfig = ®istry.AuthConfig{} + configFile = ®istry.ConfigFile{} tag string context io.ReadCloser ) @@ -611,12 +610,12 @@ func (srv *Server) ImagesSearch(job *engine.Job) engine.Status { var ( term = job.Args[0] metaHeaders = map[string][]string{} - authConfig = &auth.AuthConfig{} + authConfig = ®istry.AuthConfig{} ) job.GetenvJson("authConfig", authConfig) job.GetenvJson("metaHeaders", metaHeaders) - r, err := registry.NewRegistry(authConfig, srv.HTTPRequestFactory(metaHeaders), auth.IndexServerAddress()) + r, err := registry.NewRegistry(authConfig, srv.HTTPRequestFactory(metaHeaders), registry.IndexServerAddress()) if err != nil { return job.Error(err) } @@ -827,7 +826,7 @@ func (srv *Server) DockerInfo(job *engine.Job) engine.Status { v.Set("ExecutionDriver", srv.runtime.ExecutionDriver().Name()) v.SetInt("NEventsListener", len(srv.listeners)) v.Set("KernelVersion", kernelVersion) - v.Set("IndexServerAddress", auth.IndexServerAddress()) + v.Set("IndexServerAddress", registry.IndexServerAddress()) v.Set("InitSha1", dockerversion.INITSHA1) v.Set("InitPath", initPath) if _, err := v.WriteTo(job.Stdout); err != nil { @@ -1312,7 +1311,7 @@ func (srv *Server) ImagePull(job *engine.Job) engine.Status { localName = job.Args[0] tag string sf = utils.NewStreamFormatter(job.GetenvBool("json")) - authConfig = &auth.AuthConfig{} + authConfig = ®istry.AuthConfig{} metaHeaders map[string][]string ) if len(job.Args) > 1 { @@ -1350,7 +1349,7 @@ func (srv *Server) ImagePull(job *engine.Job) engine.Status { return job.Error(err) } - if endpoint == auth.IndexServerAddress() { + if endpoint == registry.IndexServerAddress() { // If pull "index.docker.io/foo/bar", it's stored locally under "foo/bar" localName = remoteName } @@ -1531,7 +1530,7 @@ func (srv *Server) ImagePush(job *engine.Job) engine.Status { var ( localName = job.Args[0] sf = utils.NewStreamFormatter(job.GetenvBool("json")) - authConfig = &auth.AuthConfig{} + authConfig = ®istry.AuthConfig{} metaHeaders map[string][]string ) From c5632622391921160687f3e0155bdfe3d3cfc07d Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 10 Mar 2014 13:38:17 -0700 Subject: [PATCH 042/384] Move signal to pkg Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- api/client.go | 13 +-- {utils => pkg/signal}/signal.go | 2 +- {utils => pkg/signal}/signal_darwin.go | 2 +- .../signal/signal_freebsd.go | 7 +- pkg/signal/signal_linux.go | 87 +++++++++++++++++++ 5 files changed, 97 insertions(+), 14 deletions(-) rename {utils => pkg/signal}/signal.go (87%) rename {utils => pkg/signal}/signal_darwin.go (97%) rename utils/signal_linux.go => pkg/signal/signal_freebsd.go (85%) create mode 100644 pkg/signal/signal_linux.go diff --git a/api/client.go b/api/client.go index 10075ae613..5e110d49f5 100644 --- a/api/client.go +++ b/api/client.go @@ -13,6 +13,7 @@ import ( "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/nat" flag "github.com/dotcloud/docker/pkg/mflag" + "github.com/dotcloud/docker/pkg/signal" "github.com/dotcloud/docker/pkg/term" "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/runconfig" @@ -24,7 +25,7 @@ import ( "net/http/httputil" "net/url" "os" - "os/signal" + gosignal "os/signal" "path" "reflect" "regexp" @@ -533,7 +534,7 @@ func (cli *DockerCli) CmdRestart(args ...string) error { func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal { sigc := make(chan os.Signal, 1) - utils.CatchAll(sigc) + signal.CatchAll(sigc) go func() { for s := range sigc { if s == syscall.SIGCHLD { @@ -581,7 +582,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { if !container.Config.Tty { sigc := cli.forwardAllSignals(cmd.Arg(0)) - defer utils.StopCatch(sigc) + defer signal.StopCatch(sigc) } var in io.ReadCloser @@ -1614,7 +1615,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error { if *proxy && !container.Config.Tty { sigc := cli.forwardAllSignals(cmd.Arg(0)) - defer utils.StopCatch(sigc) + defer signal.StopCatch(sigc) } if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, in, cli.out, cli.err, nil); err != nil { @@ -1818,7 +1819,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { if sigProxy { sigc := cli.forwardAllSignals(runResult.Get("Id")) - defer utils.StopCatch(sigc) + defer signal.StopCatch(sigc) } var ( @@ -2320,7 +2321,7 @@ func (cli *DockerCli) monitorTtySize(id string) error { cli.resizeTty(id) sigchan := make(chan os.Signal, 1) - signal.Notify(sigchan, syscall.SIGWINCH) + gosignal.Notify(sigchan, syscall.SIGWINCH) go func() { for _ = range sigchan { cli.resizeTty(id) diff --git a/utils/signal.go b/pkg/signal/signal.go similarity index 87% rename from utils/signal.go rename to pkg/signal/signal.go index 0cac7d113f..6f9874bd01 100644 --- a/utils/signal.go +++ b/pkg/signal/signal.go @@ -1,4 +1,4 @@ -package utils +package signal import ( "os" diff --git a/utils/signal_darwin.go b/pkg/signal/signal_darwin.go similarity index 97% rename from utils/signal_darwin.go rename to pkg/signal/signal_darwin.go index 28730db8e5..22a60ae18d 100644 --- a/utils/signal_darwin.go +++ b/pkg/signal/signal_darwin.go @@ -1,4 +1,4 @@ -package utils +package signal import ( "os" diff --git a/utils/signal_linux.go b/pkg/signal/signal_freebsd.go similarity index 85% rename from utils/signal_linux.go rename to pkg/signal/signal_freebsd.go index 26cfd56967..d27782217f 100644 --- a/utils/signal_linux.go +++ b/pkg/signal/signal_freebsd.go @@ -1,4 +1,4 @@ -package utils +package signal import ( "os" @@ -12,7 +12,6 @@ func CatchAll(sigc chan os.Signal) { syscall.SIGALRM, syscall.SIGBUS, syscall.SIGCHLD, - syscall.SIGCLD, syscall.SIGCONT, syscall.SIGFPE, syscall.SIGHUP, @@ -22,12 +21,9 @@ func CatchAll(sigc chan os.Signal) { syscall.SIGIOT, syscall.SIGKILL, syscall.SIGPIPE, - syscall.SIGPOLL, syscall.SIGPROF, - syscall.SIGPWR, syscall.SIGQUIT, syscall.SIGSEGV, - syscall.SIGSTKFLT, syscall.SIGSTOP, syscall.SIGSYS, syscall.SIGTERM, @@ -35,7 +31,6 @@ func CatchAll(sigc chan os.Signal) { syscall.SIGTSTP, syscall.SIGTTIN, syscall.SIGTTOU, - syscall.SIGUNUSED, syscall.SIGURG, syscall.SIGUSR1, syscall.SIGUSR2, diff --git a/pkg/signal/signal_linux.go b/pkg/signal/signal_linux.go new file mode 100644 index 0000000000..b6b25d518b --- /dev/null +++ b/pkg/signal/signal_linux.go @@ -0,0 +1,87 @@ +package signal + +import ( + "os" + "os/signal" + "syscall" +) + +var signalMap = map[string]syscall.Signal{} + +/* + syscall.SIGABRT, + syscall.SIGALRM, + syscall.SIGBUS, + syscall.SIGCHLD, + syscall.SIGCLD, + syscall.SIGCONT, + syscall.SIGFPE, + syscall.SIGHUP, + syscall.SIGILL, + syscall.SIGINT, + syscall.SIGIO, + syscall.SIGIOT, + syscall.SIGKILL, + syscall.SIGPIPE, + syscall.SIGPOLL, + syscall.SIGPROF, + syscall.SIGPWR, + syscall.SIGQUIT, + syscall.SIGSEGV, + syscall.SIGSTKFLT, + syscall.SIGSTOP, + syscall.SIGSYS, + syscall.SIGTERM, + syscall.SIGTRAP, + syscall.SIGTSTP, + syscall.SIGTTIN, + syscall.SIGTTOU, + syscall.SIGUNUSED, + syscall.SIGURG, + syscall.SIGUSR1, + syscall.SIGUSR2, + syscall.SIGVTALRM, + syscall.SIGWINCH, + syscall.SIGXCPU, + syscall.SIGXFSZ, +*/ + +func CatchAll(sigc chan os.Signal) { + signal.Notify(sigc, + syscall.SIGABRT, + syscall.SIGALRM, + syscall.SIGBUS, + syscall.SIGCHLD, + syscall.SIGCLD, + syscall.SIGCONT, + syscall.SIGFPE, + syscall.SIGHUP, + syscall.SIGILL, + syscall.SIGINT, + syscall.SIGIO, + syscall.SIGIOT, + syscall.SIGKILL, + syscall.SIGPIPE, + syscall.SIGPOLL, + syscall.SIGPROF, + syscall.SIGPWR, + syscall.SIGQUIT, + syscall.SIGSEGV, + syscall.SIGSTKFLT, + syscall.SIGSTOP, + syscall.SIGSYS, + syscall.SIGTERM, + syscall.SIGTRAP, + syscall.SIGTSTP, + syscall.SIGTTIN, + syscall.SIGTTOU, + syscall.SIGUNUSED, + syscall.SIGURG, + syscall.SIGUSR1, + syscall.SIGUSR2, + syscall.SIGVTALRM, + syscall.SIGWINCH, + syscall.SIGXCPU, + syscall.SIGXFSZ, + ) +} From 10dc16dcd3aa82be256e5072a25dcf18af8e3844 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 10 Mar 2014 13:50:16 -0700 Subject: [PATCH 043/384] Create portable signalMap Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- pkg/signal/signal.go | 8 +++ pkg/signal/signal_darwin.go | 70 +++++++++---------- pkg/signal/signal_freebsd.go | 68 +++++++++--------- pkg/signal/signal_linux.go | 116 ++++++++++--------------------- pkg/signal/signal_unsupported.go | 9 +++ 5 files changed, 121 insertions(+), 150 deletions(-) create mode 100644 pkg/signal/signal_unsupported.go diff --git a/pkg/signal/signal.go b/pkg/signal/signal.go index 6f9874bd01..a673222628 100644 --- a/pkg/signal/signal.go +++ b/pkg/signal/signal.go @@ -5,6 +5,14 @@ import ( "os/signal" ) +func CatchAll(sigc chan os.Signal) { + handledSigs := []os.Signal{} + for _, s := range signalMap { + handledSigs = append(handledSigs, s) + } + signal.Notify(sigc, handledSigs...) +} + func StopCatch(sigc chan os.Signal) { signal.Stop(sigc) close(sigc) diff --git a/pkg/signal/signal_darwin.go b/pkg/signal/signal_darwin.go index 22a60ae18d..b290a8b53d 100644 --- a/pkg/signal/signal_darwin.go +++ b/pkg/signal/signal_darwin.go @@ -1,44 +1,40 @@ package signal import ( - "os" - "os/signal" "syscall" ) -func CatchAll(sigc chan os.Signal) { - signal.Notify(sigc, - syscall.SIGABRT, - syscall.SIGALRM, - syscall.SIGBUS, - syscall.SIGCHLD, - syscall.SIGCONT, - syscall.SIGEMT, - syscall.SIGFPE, - syscall.SIGHUP, - syscall.SIGILL, - syscall.SIGINFO, - syscall.SIGINT, - syscall.SIGIO, - syscall.SIGIOT, - syscall.SIGKILL, - syscall.SIGPIPE, - syscall.SIGPROF, - syscall.SIGQUIT, - syscall.SIGSEGV, - syscall.SIGSTOP, - syscall.SIGSYS, - syscall.SIGTERM, - syscall.SIGTRAP, - syscall.SIGTSTP, - syscall.SIGTTIN, - syscall.SIGTTOU, - syscall.SIGURG, - syscall.SIGUSR1, - syscall.SIGUSR2, - syscall.SIGVTALRM, - syscall.SIGWINCH, - syscall.SIGXCPU, - syscall.SIGXFSZ, - ) +var signalMap = map[string]syscall.Signal{ + "ABRT": syscall.SIGABRT, + "ALRM": syscall.SIGALRM, + "BUG": syscall.SIGBUS, + "CHLD": syscall.SIGCHLD, + "CONT": syscall.SIGCONT, + "EMT": syscall.SIGEMT, + "FPE": syscall.SIGFPE, + "HUP": syscall.SIGHUP, + "ILL": syscall.SIGILL, + "INFO": syscall.SIGINFO, + "INT": syscall.SIGINT, + "IO": syscall.SIGIO, + "IOT": syscall.SIGIOT, + "KILL": syscall.SIGKILL, + "PIPE": syscall.SIGPIPE, + "PROF": syscall.SIGPROF, + "QUIT": syscall.SIGQUIT, + "SEGV": syscall.SIGSEGV, + "STOP": syscall.SIGSTOP, + "SYS": syscall.SIGSYS, + "TERM": syscall.SIGTERM, + "TRAP": syscall.SIGTRAP, + "TSTP": syscall.SIGTSTP, + "TTIN": syscall.SIGTTIN, + "TTOU": syscall.SIGTTOU, + "URG": syscall.SIGURG, + "USR1": syscall.SIGUSR1, + "USR2": syscall.SIGUSR2, + "VTALRM": syscall.SIGVTALRM, + "WINCH": syscall.SIGWINCH, + "XCPU": syscall.SIGXCPU, + "XFSZ": syscall.SIGXFSZ, } diff --git a/pkg/signal/signal_freebsd.go b/pkg/signal/signal_freebsd.go index d27782217f..b7e3ff4f7c 100644 --- a/pkg/signal/signal_freebsd.go +++ b/pkg/signal/signal_freebsd.go @@ -6,37 +6,39 @@ import ( "syscall" ) -func CatchAll(sigc chan os.Signal) { - signal.Notify(sigc, - syscall.SIGABRT, - syscall.SIGALRM, - syscall.SIGBUS, - syscall.SIGCHLD, - syscall.SIGCONT, - syscall.SIGFPE, - syscall.SIGHUP, - syscall.SIGILL, - syscall.SIGINT, - syscall.SIGIO, - syscall.SIGIOT, - syscall.SIGKILL, - syscall.SIGPIPE, - syscall.SIGPROF, - syscall.SIGQUIT, - syscall.SIGSEGV, - syscall.SIGSTOP, - syscall.SIGSYS, - syscall.SIGTERM, - syscall.SIGTRAP, - syscall.SIGTSTP, - syscall.SIGTTIN, - syscall.SIGTTOU, - syscall.SIGURG, - syscall.SIGUSR1, - syscall.SIGUSR2, - syscall.SIGVTALRM, - syscall.SIGWINCH, - syscall.SIGXCPU, - syscall.SIGXFSZ, - ) +var signalMap = map[string]syscall.Signal{ + "ABRT": syscall.SIGABRT, + "ALRM": syscall.SIGALRM, + "BUF": syscall.SIGBUS, + "CHLD": syscall.SIGCHLD, + "CONT": syscall.SIGCONT, + "EMT": syscall.SIGEMT, + "FPE": syscall.SIGFPE, + "HUP": syscall.SIGHUP, + "ILL": syscall.SIGILL, + "INFO": syscall.SIGINFO, + "INT": syscall.SIGINT, + "IO": syscall.SIGIO, + "IOT": syscall.SIGIOT, + "KILL": syscall.SIGKILL, + "LWP": syscall.SIGLWP, + "PIPE": syscall.SIGPIPE, + "PROF": syscall.SIGPROF, + "QUIT": syscall.SIGQUIT, + "SEGV": syscall.SIGSEGV, + "STOP": syscall.SIGSTOP, + "SYS": syscall.SIGSYS, + "TERM": syscall.SIGTERM, + "THR": syscall.SIGTHR, + "TRAP": syscall.SIGTRAP, + "TSTP": syscall.SIGTSTP, + "TTIN": syscall.SIGTTIN, + "TTOU": syscall.SIGTTOU, + "URG": syscall.SIGURG, + "USR1": syscall.SIGUSR1, + "USR2": syscall.SIGUSR2, + "VTALRM": syscall.SIGVTALRM, + "WINCH": syscall.SIGWINCH, + "XCPU": syscall.SIGXCPU, + "XFSZ": syscall.SIGXFSZ, } diff --git a/pkg/signal/signal_linux.go b/pkg/signal/signal_linux.go index b6b25d518b..cd8cb83e42 100644 --- a/pkg/signal/signal_linux.go +++ b/pkg/signal/signal_linux.go @@ -1,87 +1,43 @@ package signal import ( - "os" - "os/signal" "syscall" ) -var signalMap = map[string]syscall.Signal{} - -/* - syscall.SIGABRT, - syscall.SIGALRM, - syscall.SIGBUS, - syscall.SIGCHLD, - syscall.SIGCLD, - syscall.SIGCONT, - syscall.SIGFPE, - syscall.SIGHUP, - syscall.SIGILL, - syscall.SIGINT, - syscall.SIGIO, - syscall.SIGIOT, - syscall.SIGKILL, - syscall.SIGPIPE, - syscall.SIGPOLL, - syscall.SIGPROF, - syscall.SIGPWR, - syscall.SIGQUIT, - syscall.SIGSEGV, - syscall.SIGSTKFLT, - syscall.SIGSTOP, - syscall.SIGSYS, - syscall.SIGTERM, - syscall.SIGTRAP, - syscall.SIGTSTP, - syscall.SIGTTIN, - syscall.SIGTTOU, - syscall.SIGUNUSED, - syscall.SIGURG, - syscall.SIGUSR1, - syscall.SIGUSR2, - syscall.SIGVTALRM, - syscall.SIGWINCH, - syscall.SIGXCPU, - syscall.SIGXFSZ, -*/ - -func CatchAll(sigc chan os.Signal) { - signal.Notify(sigc, - syscall.SIGABRT, - syscall.SIGALRM, - syscall.SIGBUS, - syscall.SIGCHLD, - syscall.SIGCLD, - syscall.SIGCONT, - syscall.SIGFPE, - syscall.SIGHUP, - syscall.SIGILL, - syscall.SIGINT, - syscall.SIGIO, - syscall.SIGIOT, - syscall.SIGKILL, - syscall.SIGPIPE, - syscall.SIGPOLL, - syscall.SIGPROF, - syscall.SIGPWR, - syscall.SIGQUIT, - syscall.SIGSEGV, - syscall.SIGSTKFLT, - syscall.SIGSTOP, - syscall.SIGSYS, - syscall.SIGTERM, - syscall.SIGTRAP, - syscall.SIGTSTP, - syscall.SIGTTIN, - syscall.SIGTTOU, - syscall.SIGUNUSED, - syscall.SIGURG, - syscall.SIGUSR1, - syscall.SIGUSR2, - syscall.SIGVTALRM, - syscall.SIGWINCH, - syscall.SIGXCPU, - syscall.SIGXFSZ, - ) +var signalMap = map[string]syscall.Signal{ + "ABRT": syscall.SIGABRT, + "ALRM": syscall.SIGALRM, + "BUS": syscall.SIGBUS, + "CHLD": syscall.SIGCHLD, + "CLD": syscall.SIGCLD, + "CONT": syscall.SIGCONT, + "FPE": syscall.SIGFPE, + "HUP": syscall.SIGHUP, + "ILL": syscall.SIGILL, + "INT": syscall.SIGINT, + "IO": syscall.SIGIO, + "IOT": syscall.SIGIOT, + "KILL": syscall.SIGKILL, + "PIPE": syscall.SIGPIPE, + "POLL": syscall.SIGPOLL, + "PROF": syscall.SIGPROF, + "PWR": syscall.SIGPWR, + "QUIT": syscall.SIGQUIT, + "SEGV": syscall.SIGSEGV, + "STKFLT": syscall.SIGSTKFLT, + "STOP": syscall.SIGSTOP, + "SYS": syscall.SIGSYS, + "TERM": syscall.SIGTERM, + "TRAP": syscall.SIGTRAP, + "TSTP": syscall.SIGTSTP, + "TTIN": syscall.SIGTTIN, + "TTOU": syscall.SIGTTOU, + "UNUSED": syscall.SIGUNUSED, + "URG": syscall.SIGURG, + "USR1": syscall.SIGUSR1, + "USR2": syscall.SIGUSR2, + "VTALRM": syscall.SIGVTALRM, + "WINCH": syscall.SIGWINCH, + "XCPU": syscall.SIGXCPU, + "XFSZ": syscall.SIGXFSZ, } diff --git a/pkg/signal/signal_unsupported.go b/pkg/signal/signal_unsupported.go new file mode 100644 index 0000000000..2c49a0b0f6 --- /dev/null +++ b/pkg/signal/signal_unsupported.go @@ -0,0 +1,9 @@ +// +build !linux,!darwin,!freebsd + +package signal + +import ( + "syscall" +) + +var signalMap = map[string]syscall.Signal{} From 157f24ca77a38f7c5c2b22322a2a353d5098a21e Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 10 Mar 2014 14:22:27 -0700 Subject: [PATCH 044/384] Make docker use the signal pkg with strings Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- api/client.go | 12 +++++- pkg/signal/signal.go | 2 +- pkg/signal/signal_darwin.go | 64 ++++++++++++++++---------------- pkg/signal/signal_freebsd.go | 2 +- pkg/signal/signal_linux.go | 2 +- pkg/signal/signal_unsupported.go | 2 +- server.go | 63 ++++++++++--------------------- 7 files changed, 66 insertions(+), 81 deletions(-) diff --git a/api/client.go b/api/client.go index 5e110d49f5..35ce5c6969 100644 --- a/api/client.go +++ b/api/client.go @@ -540,7 +540,17 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal { if s == syscall.SIGCHLD { continue } - if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%d", cid, s), nil, false)); err != nil { + var sig string + for sigStr, sigN := range signal.SignalMap { + if sigN == s { + sig = sigStr + break + } + } + if sig == "" { + utils.Errorf("Unsupported signal: %d. Discarding.", s) + } + if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, false)); err != nil { utils.Debugf("Error sending signal: %s", err) } } diff --git a/pkg/signal/signal.go b/pkg/signal/signal.go index a673222628..63337542d7 100644 --- a/pkg/signal/signal.go +++ b/pkg/signal/signal.go @@ -7,7 +7,7 @@ import ( func CatchAll(sigc chan os.Signal) { handledSigs := []os.Signal{} - for _, s := range signalMap { + for _, s := range SignalMap { handledSigs = append(handledSigs, s) } signal.Notify(sigc, handledSigs...) diff --git a/pkg/signal/signal_darwin.go b/pkg/signal/signal_darwin.go index b290a8b53d..fcd3a8f2c9 100644 --- a/pkg/signal/signal_darwin.go +++ b/pkg/signal/signal_darwin.go @@ -4,37 +4,37 @@ import ( "syscall" ) -var signalMap = map[string]syscall.Signal{ - "ABRT": syscall.SIGABRT, - "ALRM": syscall.SIGALRM, - "BUG": syscall.SIGBUS, - "CHLD": syscall.SIGCHLD, - "CONT": syscall.SIGCONT, - "EMT": syscall.SIGEMT, - "FPE": syscall.SIGFPE, - "HUP": syscall.SIGHUP, - "ILL": syscall.SIGILL, - "INFO": syscall.SIGINFO, - "INT": syscall.SIGINT, - "IO": syscall.SIGIO, - "IOT": syscall.SIGIOT, - "KILL": syscall.SIGKILL, - "PIPE": syscall.SIGPIPE, - "PROF": syscall.SIGPROF, - "QUIT": syscall.SIGQUIT, - "SEGV": syscall.SIGSEGV, - "STOP": syscall.SIGSTOP, - "SYS": syscall.SIGSYS, - "TERM": syscall.SIGTERM, - "TRAP": syscall.SIGTRAP, - "TSTP": syscall.SIGTSTP, - "TTIN": syscall.SIGTTIN, - "TTOU": syscall.SIGTTOU, - "URG": syscall.SIGURG, - "USR1": syscall.SIGUSR1, - "USR2": syscall.SIGUSR2, +var SignalMap = map[string]syscall.Signal{ + "ABRT": syscall.SIGABRT, + "ALRM": syscall.SIGALRM, + "BUG": syscall.SIGBUS, + "CHLD": syscall.SIGCHLD, + "CONT": syscall.SIGCONT, + "EMT": syscall.SIGEMT, + "FPE": syscall.SIGFPE, + "HUP": syscall.SIGHUP, + "ILL": syscall.SIGILL, + "INFO": syscall.SIGINFO, + "INT": syscall.SIGINT, + "IO": syscall.SIGIO, + "IOT": syscall.SIGIOT, + "KILL": syscall.SIGKILL, + "PIPE": syscall.SIGPIPE, + "PROF": syscall.SIGPROF, + "QUIT": syscall.SIGQUIT, + "SEGV": syscall.SIGSEGV, + "STOP": syscall.SIGSTOP, + "SYS": syscall.SIGSYS, + "TERM": syscall.SIGTERM, + "TRAP": syscall.SIGTRAP, + "TSTP": syscall.SIGTSTP, + "TTIN": syscall.SIGTTIN, + "TTOU": syscall.SIGTTOU, + "URG": syscall.SIGURG, + "USR1": syscall.SIGUSR1, + "USR2": syscall.SIGUSR2, "VTALRM": syscall.SIGVTALRM, - "WINCH": syscall.SIGWINCH, - "XCPU": syscall.SIGXCPU, - "XFSZ": syscall.SIGXFSZ, + "WINCH": syscall.SIGWINCH, + "XCPU": syscall.SIGXCPU, + "XFSZ": syscall.SIGXFSZ, } diff --git a/pkg/signal/signal_freebsd.go b/pkg/signal/signal_freebsd.go index b7e3ff4f7c..da042d7e72 100644 --- a/pkg/signal/signal_freebsd.go +++ b/pkg/signal/signal_freebsd.go @@ -6,7 +6,7 @@ import ( "syscall" ) -var signalMap = map[string]syscall.Signal{ +var SignalMap = map[string]syscall.Signal{ "ABRT": syscall.SIGABRT, "ALRM": syscall.SIGALRM, "BUF": syscall.SIGBUS, diff --git a/pkg/signal/signal_linux.go b/pkg/signal/signal_linux.go index cd8cb83e42..a62f79d4af 100644 --- a/pkg/signal/signal_linux.go +++ b/pkg/signal/signal_linux.go @@ -4,7 +4,7 @@ import ( "syscall" ) -var signalMap = map[string]syscall.Signal{ +var SignalMap = map[string]syscall.Signal{ "ABRT": syscall.SIGABRT, "ALRM": syscall.SIGALRM, "BUS": syscall.SIGBUS, diff --git a/pkg/signal/signal_unsupported.go b/pkg/signal/signal_unsupported.go index 2c49a0b0f6..99f9465970 100644 --- a/pkg/signal/signal_unsupported.go +++ b/pkg/signal/signal_unsupported.go @@ -6,4 +6,4 @@ import ( "syscall" ) -var signalMap = map[string]syscall.Signal{} +var SignalMap = map[string]syscall.Signal{} diff --git a/server.go b/server.go index d824d78d7a..610b3ccfba 100644 --- a/server.go +++ b/server.go @@ -8,6 +8,7 @@ import ( "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/pkg/graphdb" + "github.com/dotcloud/docker/pkg/signal" "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" @@ -18,7 +19,7 @@ import ( "net/url" "os" "os/exec" - "os/signal" + gosignal "os/signal" "path" "path/filepath" "runtime" @@ -47,7 +48,7 @@ func InitServer(job *engine.Job) engine.Status { } job.Logf("Setting up signal traps") c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) + gosignal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) go func() { sig := <-c log.Printf("Received signal '%v', exiting\n", sig) @@ -122,56 +123,30 @@ func (v *simpleVersionInfo) Version() string { // for the container to exit. // If a signal is given, then just send it to the container and return. func (srv *Server) ContainerKill(job *engine.Job) engine.Status { - signalMap := map[string]syscall.Signal{ - "HUP": syscall.SIGHUP, - "INT": syscall.SIGINT, - "QUIT": syscall.SIGQUIT, - "ILL": syscall.SIGILL, - "TRAP": syscall.SIGTRAP, - "ABRT": syscall.SIGABRT, - "BUS": syscall.SIGBUS, - "FPE": syscall.SIGFPE, - "KILL": syscall.SIGKILL, - "USR1": syscall.SIGUSR1, - "SEGV": syscall.SIGSEGV, - "USR2": syscall.SIGUSR2, - "PIPE": syscall.SIGPIPE, - "ALRM": syscall.SIGALRM, - "TERM": syscall.SIGTERM, - //"STKFLT": syscall.SIGSTKFLT, - "CHLD": syscall.SIGCHLD, - "CONT": syscall.SIGCONT, - "STOP": syscall.SIGSTOP, - "TSTP": syscall.SIGTSTP, - "TTIN": syscall.SIGTTIN, - "TTOU": syscall.SIGTTOU, - "URG": syscall.SIGURG, - "XCPU": syscall.SIGXCPU, - "XFSZ": syscall.SIGXFSZ, - "VTALRM": syscall.SIGVTALRM, - "PROF": syscall.SIGPROF, - "WINCH": syscall.SIGWINCH, - "IO": syscall.SIGIO, - //"PWR": syscall.SIGPWR, - "SYS": syscall.SIGSYS, - } - if n := len(job.Args); n < 1 || n > 2 { return job.Errorf("Usage: %s CONTAINER [SIGNAL]", job.Name) } - name := job.Args[0] - var sig uint64 + var ( + name = job.Args[0] + sig uint64 + err error + ) + + // If we have a signal, look at it. Otherwise, do nothing if len(job.Args) == 2 && job.Args[1] != "" { - sig = uint64(signalMap[job.Args[1]]) - if sig == 0 { - var err error - // The largest legal signal is 31, so let's parse on 5 bits - sig, err = strconv.ParseUint(job.Args[1], 10, 5) - if err != nil { + // Check if we passed the singal as a number: + // The largest legal signal is 31, so let's parse on 5 bits + sig, err = strconv.ParseUint(job.Args[1], 10, 5) + if err != nil { + // The signal is not a number, treat it as a string + sig = uint64(signal.SignalMap[job.Args[1]]) + if sig == 0 { return job.Errorf("Invalid signal: %s", job.Args[1]) } + } } + if container := srv.runtime.Get(name); container != nil { // If no signal is passed, or SIGKILL, perform regular Kill (SIGKILL + wait()) if sig == 0 || syscall.Signal(sig) == syscall.SIGKILL { From 8301fc8e56503d5a0ea2316a0778faf4cf5f5f1e Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 26 Feb 2014 23:20:58 +0000 Subject: [PATCH 045/384] move git clone from daemon to client Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) add a little doc --- api/client.go | 26 ++++++++++++++++++---- docs/sources/reference/commandline/cli.rst | 16 ++++++++----- utils/utils.go | 2 +- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/api/client.go b/api/client.go index 7f8921d9e1..6dabeac835 100644 --- a/api/client.go +++ b/api/client.go @@ -24,6 +24,7 @@ import ( "net/http/httputil" "net/url" "os" + "os/exec" gosignal "os/signal" "path" "reflect" @@ -168,17 +169,34 @@ func (cli *DockerCli) CmdBuild(args ...string) error { return err } context, err = archive.Generate("Dockerfile", string(dockerfile)) - } else if utils.IsURL(cmd.Arg(0)) || utils.IsGIT(cmd.Arg(0)) { + } else if utils.IsURL(cmd.Arg(0)) && !utils.IsGIT(cmd.Arg(0)) { isRemote = true } else { - if _, err := os.Stat(cmd.Arg(0)); err != nil { + root := cmd.Arg(0) + if utils.IsGIT(root) { + remoteURL := cmd.Arg(0) + if !strings.HasPrefix(remoteURL, "git://") && !strings.HasPrefix(remoteURL, "git@") && !utils.IsURL(remoteURL) { + remoteURL = "https://" + remoteURL + } + + root, err = ioutil.TempDir("", "docker-build-git") + if err != nil { + return err + } + defer os.RemoveAll(root) + + if output, err := exec.Command("git", "clone", "--recursive", remoteURL, root).CombinedOutput(); err != nil { + return fmt.Errorf("Error trying to use git: %s (%s)", err, output) + } + } + if _, err := os.Stat(root); err != nil { return err } - filename := path.Join(cmd.Arg(0), "Dockerfile") + filename := path.Join(root, "Dockerfile") if _, err = os.Stat(filename); os.IsNotExist(err) { return fmt.Errorf("no Dockerfile found in %s", cmd.Arg(0)) } - context, err = archive.Tar(cmd.Arg(0), archive.Uncompressed) + context, err = archive.Tar(root, archive.Uncompressed) } var body io.Reader // Setup an upload progress bar diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 2404e29b29..6fe2e2943e 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -202,12 +202,16 @@ Examples: --no-cache: Do not use the cache when building the image. --rm=true: Remove intermediate containers after a successful build -The files at ``PATH`` or ``URL`` are called the "context" of the build. The -build process may refer to any of the files in the context, for example when -using an :ref:`ADD ` instruction. When a single ``Dockerfile`` -is given as ``URL``, then no context is set. When a Git repository is set as -``URL``, then the repository is used as the context. Git repositories are -cloned with their submodules (`git clone --recursive`). +The files at ``PATH`` or ``URL`` are called the "context" of the build. +The build process may refer to any of the files in the context, for example when +using an :ref:`ADD ` instruction. +When a single ``Dockerfile`` is given as ``URL``, then no context is set. + +When a Git repository is set as ``URL``, then the repository is used as the context. +The Git repository is cloned with its submodules (`git clone --recursive`). +A fresh git clone occurs in a temporary directory on your local host, and then this +is sent to the Docker daemon as the context. +This way, your local user credentials and vpn's etc can be used to access private repositories .. _cli_build_examples: diff --git a/utils/utils.go b/utils/utils.go index e4cb04f39c..57a8200a7c 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -714,7 +714,7 @@ func IsURL(str string) bool { } func IsGIT(str string) bool { - return strings.HasPrefix(str, "git://") || strings.HasPrefix(str, "github.com/") + return strings.HasPrefix(str, "git://") || strings.HasPrefix(str, "github.com/") || strings.HasPrefix(str, "git@github.com:") || (strings.HasSuffix(str, ".git") && IsURL(str)) } // GetResolvConf opens and read the content of /etc/resolv.conf. From 0d6275b298ebb9161c2f55d4b4ac0f87603a11cd Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 5 Mar 2014 01:54:08 +0000 Subject: [PATCH 046/384] if client has no git, use server Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api/client.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/client.go b/api/client.go index 6dabeac835..4854c19013 100644 --- a/api/client.go +++ b/api/client.go @@ -161,6 +161,8 @@ func (cli *DockerCli) CmdBuild(args ...string) error { err error ) + _, err = exec.LookPath("git") + hasGit := err == nil if cmd.Arg(0) == "-" { // As a special case, 'docker build -' will build from an empty context with the // contents of stdin as a Dockerfile @@ -169,7 +171,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { return err } context, err = archive.Generate("Dockerfile", string(dockerfile)) - } else if utils.IsURL(cmd.Arg(0)) && !utils.IsGIT(cmd.Arg(0)) { + } else if utils.IsURL(cmd.Arg(0)) && (!utils.IsGIT(cmd.Arg(0)) || !hasGit) { isRemote = true } else { root := cmd.Arg(0) From 18ea183ea0abd18cde0d17e57fc0fa9b19cfab08 Mon Sep 17 00:00:00 2001 From: Dolph Mathews Date: Mon, 10 Mar 2014 21:51:19 -0500 Subject: [PATCH 047/384] spelling correction s/singal/signal/ Docker-DCO-1.1-Signed-off-by: Dolph Mathews (github: dolph) --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index f557c02b64..48bb6f9805 100644 --- a/server.go +++ b/server.go @@ -138,7 +138,7 @@ func (srv *Server) ContainerKill(job *engine.Job) engine.Status { // If we have a signal, look at it. Otherwise, do nothing if len(job.Args) == 2 && job.Args[1] != "" { - // Check if we passed the singal as a number: + // Check if we passed the signal as a number: // The largest legal signal is 31, so let's parse on 5 bits sig, err = strconv.ParseUint(job.Args[1], 10, 5) if err != nil { From 915d967f556bc7bb3faea34db8a06ea64fd5de92 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 10 Mar 2014 20:26:45 -0700 Subject: [PATCH 048/384] Update email + add self to pkg/signal Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- MAINTAINERS | 2 +- contrib/host-integration/Dockerfile.dev | 2 +- contrib/host-integration/Dockerfile.min | 2 +- docs/sources/reference/builder.rst | 2 +- execdriver/MAINTAINERS | 2 +- pkg/libcontainer/MAINTAINERS | 2 +- pkg/netlink/MAINTAINERS | 2 +- pkg/signal/MAINTAINERS | 2 ++ pkg/term/MAINTAINERS | 2 +- 9 files changed, 10 insertions(+), 8 deletions(-) create mode 100644 pkg/signal/MAINTAINERS diff --git a/MAINTAINERS b/MAINTAINERS index 49d14ba0bd..d1f4d15491 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1,5 +1,5 @@ Solomon Hykes (@shykes) -Guillaume Charmes (@creack) +Guillaume J. Charmes (@creack) Victor Vieux (@vieux) Michael Crosby (@crosbymichael) .travis.yml: Tianon Gravi (@tianon) diff --git a/contrib/host-integration/Dockerfile.dev b/contrib/host-integration/Dockerfile.dev index 161416e750..800216532f 100644 --- a/contrib/host-integration/Dockerfile.dev +++ b/contrib/host-integration/Dockerfile.dev @@ -6,7 +6,7 @@ # FROM ubuntu:12.10 -MAINTAINER Guillaume J. Charmes +MAINTAINER Guillaume J. Charmes RUN apt-get update && apt-get install -y wget git mercurial diff --git a/contrib/host-integration/Dockerfile.min b/contrib/host-integration/Dockerfile.min index 1a7b3a9d82..60bb89b986 100644 --- a/contrib/host-integration/Dockerfile.min +++ b/contrib/host-integration/Dockerfile.min @@ -1,4 +1,4 @@ FROM busybox -MAINTAINER Guillaume J. Charmes +MAINTAINER Guillaume J. Charmes ADD manager /usr/bin/ ENTRYPOINT ["/usr/bin/manager"] diff --git a/docs/sources/reference/builder.rst b/docs/sources/reference/builder.rst index 9f7a816801..3c48939c82 100644 --- a/docs/sources/reference/builder.rst +++ b/docs/sources/reference/builder.rst @@ -481,7 +481,7 @@ For example you might add something like this: # VERSION 0.0.1 FROM ubuntu - MAINTAINER Guillaume J. Charmes + MAINTAINER Guillaume J. Charmes # make sure the package repository is up to date RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list diff --git a/execdriver/MAINTAINERS b/execdriver/MAINTAINERS index e53d933d47..1cb551364d 100644 --- a/execdriver/MAINTAINERS +++ b/execdriver/MAINTAINERS @@ -1,2 +1,2 @@ Michael Crosby (@crosbymichael) -Guillaume Charmes (@creack) +Guillaume J. Charmes (@creack) diff --git a/pkg/libcontainer/MAINTAINERS b/pkg/libcontainer/MAINTAINERS index e53d933d47..1cb551364d 100644 --- a/pkg/libcontainer/MAINTAINERS +++ b/pkg/libcontainer/MAINTAINERS @@ -1,2 +1,2 @@ Michael Crosby (@crosbymichael) -Guillaume Charmes (@creack) +Guillaume J. Charmes (@creack) diff --git a/pkg/netlink/MAINTAINERS b/pkg/netlink/MAINTAINERS index e53d933d47..1cb551364d 100644 --- a/pkg/netlink/MAINTAINERS +++ b/pkg/netlink/MAINTAINERS @@ -1,2 +1,2 @@ Michael Crosby (@crosbymichael) -Guillaume Charmes (@creack) +Guillaume J. Charmes (@creack) diff --git a/pkg/signal/MAINTAINERS b/pkg/signal/MAINTAINERS new file mode 100644 index 0000000000..3300331598 --- /dev/null +++ b/pkg/signal/MAINTAINERS @@ -0,0 +1,2 @@ +Guillaume J. Charmes (@creack) + diff --git a/pkg/term/MAINTAINERS b/pkg/term/MAINTAINERS index 48d4d91b2a..15b8ac3729 100644 --- a/pkg/term/MAINTAINERS +++ b/pkg/term/MAINTAINERS @@ -1,2 +1,2 @@ -Guillaume Charmes (@creack) +Guillaume J. Charmes (@creack) Solomon Hykes (@shykes) From b21f8872cc684c95a2e30cec9f7c744a78a819f8 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Tue, 11 Mar 2014 01:21:59 -0600 Subject: [PATCH 049/384] Fix init script cgroup mounting workarounds to be more similar to cgroupfs-mount and thus work properly Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- contrib/init/sysvinit-debian/docker | 34 ++++++++++++++++++++--------- contrib/init/upstart/docker.conf | 33 +++++++++++++++++++--------- 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/contrib/init/sysvinit-debian/docker b/contrib/init/sysvinit-debian/docker index 510683a459..62e8f5f0a5 100755 --- a/contrib/init/sysvinit-debian/docker +++ b/contrib/init/sysvinit-debian/docker @@ -50,20 +50,34 @@ fail_unless_root() { fi } +cgroupfs_mount() { + # see also https://github.com/tianon/cgroupfs-mount/blob/master/cgroupfs-mount + if grep -v '^#' /etc/fstab | grep -q cgroup \ + || [ ! -e /proc/cgroups ] \ + || [ ! -d /sys/fs/cgroup ]; then + return + fi + if ! mountpoint -q /sys/fs/cgroup; then + mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup + fi + ( + cd /sys/fs/cgroup + for sys in $(awk '!/^#/ { if ($4 == 1) print $1 }' /proc/cgroups); do + mkdir -p $sys + if ! mountpoint -q $sys; then + if ! mount -n -t cgroup -o $sys cgroup $sys; then + rmdir $sys || true + fi + fi + done + ) +} + case "$1" in start) fail_unless_root - if ! grep -q cgroup /proc/mounts; then - # rough approximation of cgroupfs-mount - mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup - for sys in $(cut -d' ' -f1 /proc/cgroups); do - mkdir -p /sys/fs/cgroup/$sys - if ! mount -n -t cgroup -o $sys cgroup /sys/fs/cgroup/$sys 2>/dev/null; then - rmdir /sys/fs/cgroup/$sys 2>/dev/null || true - fi - done - fi + cgroupfs_mount touch /var/log/docker.log chgrp docker /var/log/docker.log diff --git a/contrib/init/upstart/docker.conf b/contrib/init/upstart/docker.conf index e2cc4536e1..047f21c092 100644 --- a/contrib/init/upstart/docker.conf +++ b/contrib/init/upstart/docker.conf @@ -5,6 +5,29 @@ stop on runlevel [!2345] respawn +pre-start script + # see also https://github.com/tianon/cgroupfs-mount/blob/master/cgroupfs-mount + if grep -v '^#' /etc/fstab | grep -q cgroup \ + || [ ! -e /proc/cgroups ] \ + || [ ! -d /sys/fs/cgroup ]; then + exit 0 + fi + if ! mountpoint -q /sys/fs/cgroup; then + mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup + fi + ( + cd /sys/fs/cgroup + for sys in $(awk '!/^#/ { if ($4 == 1) print $1 }' /proc/cgroups); do + mkdir -p $sys + if ! mountpoint -q $sys; then + if ! mount -n -t cgroup -o $sys cgroup $sys; then + rmdir $sys || true + fi + fi + done + ) +end script + script # modify these in /etc/default/$UPSTART_JOB (/etc/default/docker) DOCKER=/usr/bin/$UPSTART_JOB @@ -12,15 +35,5 @@ script if [ -f /etc/default/$UPSTART_JOB ]; then . /etc/default/$UPSTART_JOB fi - if ! grep -q cgroup /proc/mounts; then - # rough approximation of cgroupfs-mount - mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup - for sys in $(cut -d' ' -f1 /proc/cgroups); do - mkdir -p /sys/fs/cgroup/$sys - if ! mount -n -t cgroup -o $sys cgroup /sys/fs/cgroup/$sys 2>/dev/null; then - rmdir /sys/fs/cgroup/$sys 2>/dev/null || true - fi - done - fi "$DOCKER" -d $DOCKER_OPTS end script From 76dc670f413de64361a8bb3efa3381331e796b21 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Tue, 11 Mar 2014 01:40:31 -0600 Subject: [PATCH 050/384] Add variable for DOCKER_LOGFILE to sysvinit and use append instead of overwrite in opening the logfile Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- contrib/init/sysvinit-debian/docker | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/contrib/init/sysvinit-debian/docker b/contrib/init/sysvinit-debian/docker index 62e8f5f0a5..67f0d2807f 100755 --- a/contrib/init/sysvinit-debian/docker +++ b/contrib/init/sysvinit-debian/docker @@ -21,6 +21,7 @@ BASE=$(basename $0) # modify these in /etc/default/$BASE (/etc/default/docker) DOCKER=/usr/bin/$BASE DOCKER_PIDFILE=/var/run/$BASE.pid +DOCKER_LOGFILE=/var/log/$BASE.log DOCKER_OPTS= DOCKER_DESC="Docker" @@ -79,8 +80,8 @@ case "$1" in cgroupfs_mount - touch /var/log/docker.log - chgrp docker /var/log/docker.log + touch "$DOCKER_LOGFILE" + chgrp docker "$DOCKER_LOGFILE" log_begin_msg "Starting $DOCKER_DESC: $BASE" start-stop-daemon --start --background \ @@ -90,7 +91,7 @@ case "$1" in -- \ -d -p "$DOCKER_PIDFILE" \ $DOCKER_OPTS \ - > /var/log/docker.log 2>&1 + >> "$DOCKER_LOGFILE" 2>&1 log_end_msg $? ;; From b348ee0fd0e4f8d2cb453a08f2b0d174550a339d Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Tue, 11 Mar 2014 20:28:26 +1000 Subject: [PATCH 051/384] add Net::Docker CPAN module Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/reference/api/remote_api_client_libraries.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/reference/api/remote_api_client_libraries.rst b/docs/sources/reference/api/remote_api_client_libraries.rst index 9bab343bf5..e58c7ced39 100644 --- a/docs/sources/reference/api/remote_api_client_libraries.rst +++ b/docs/sources/reference/api/remote_api_client_libraries.rst @@ -45,3 +45,5 @@ and we will add the libraries here. +----------------------+----------------+--------------------------------------------+----------+ | PHP | Docker-PHP | http://stage1.github.io/docker-php/ | Active | +----------------------+----------------+--------------------------------------------+----------+ +| Perl | Net::Docker | https://metacpan.org/pod/Net::Docker | Active | ++----------------------+----------------+--------------------------------------------+----------+ From b2cd89056f5c49746ee668946ce4e1771f3ce368 Mon Sep 17 00:00:00 2001 From: Kato Kazuyoshi Date: Tue, 11 Mar 2014 22:45:47 +0900 Subject: [PATCH 052/384] Like signal_linux.go, we don't have import os and os/signal Docker-DCO-1.1-Signed-off-by: Kato Kazuyoshi (github: kzys) --- pkg/signal/signal_freebsd.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/signal/signal_freebsd.go b/pkg/signal/signal_freebsd.go index da042d7e72..102e918486 100644 --- a/pkg/signal/signal_freebsd.go +++ b/pkg/signal/signal_freebsd.go @@ -1,8 +1,6 @@ package signal import ( - "os" - "os/signal" "syscall" ) From 07c35b41a5a93f31111d47afcb1e6d2926b492a0 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 5 Mar 2014 10:40:55 +0100 Subject: [PATCH 053/384] Move execdriver construction into execdriver/execdrivers This can't be in execdriver (dependency loop) but should not be hardcoded inside runtime.go either. So we put it in a subpackage. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- execdriver/execdrivers/execdrivers.go | 23 +++++++++++++++++++++++ runtime/runtime.go | 20 +++----------------- 2 files changed, 26 insertions(+), 17 deletions(-) create mode 100644 execdriver/execdrivers/execdrivers.go diff --git a/execdriver/execdrivers/execdrivers.go b/execdriver/execdrivers/execdrivers.go new file mode 100644 index 0000000000..95b2fc634d --- /dev/null +++ b/execdriver/execdrivers/execdrivers.go @@ -0,0 +1,23 @@ +package execdrivers + +import ( + "fmt" + "github.com/dotcloud/docker/execdriver" + "github.com/dotcloud/docker/execdriver/lxc" + "github.com/dotcloud/docker/execdriver/native" + "github.com/dotcloud/docker/pkg/sysinfo" + "path" +) + +func NewDriver(name, root string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) { + switch name { + case "lxc": + // we want to five the lxc driver the full docker root because it needs + // to access and write config and template files in /var/lib/docker/containers/* + // to be backwards compatible + return lxc.NewDriver(root, sysInfo.AppArmor) + case "native": + return native.NewDriver(path.Join(root, "execdriver", "native")) + } + return nil, fmt.Errorf("unknown exec driver %s", name) +} diff --git a/runtime/runtime.go b/runtime/runtime.go index c11c309ad8..72245a4555 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -8,8 +8,8 @@ import ( "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/execdriver" + "github.com/dotcloud/docker/execdriver/execdrivers" "github.com/dotcloud/docker/execdriver/lxc" - "github.com/dotcloud/docker/execdriver/native" "github.com/dotcloud/docker/graph" "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/graphdriver/aufs" @@ -732,22 +732,8 @@ func NewRuntimeFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (* sysInitPath = localCopy } - var ( - ed execdriver.Driver - sysInfo = sysinfo.New(false) - ) - - switch config.ExecDriver { - case "lxc": - // we want to five the lxc driver the full docker root because it needs - // to access and write config and template files in /var/lib/docker/containers/* - // to be backwards compatible - ed, err = lxc.NewDriver(config.Root, sysInfo.AppArmor) - case "native": - ed, err = native.NewDriver(path.Join(config.Root, "execdriver", "native")) - default: - return nil, fmt.Errorf("unknown exec driver %s", config.ExecDriver) - } + sysInfo := sysinfo.New(false) + ed, err := execdrivers.NewDriver(config.ExecDriver, config.Root, sysInfo) if err != nil { return nil, err } From 15e52ccaadea996b409e2f62bcbdb1a088619e35 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 5 Mar 2014 20:45:18 -0800 Subject: [PATCH 054/384] Add deprecation warning for -t on pull Closes #3410 Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- api/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/client.go b/api/client.go index 338a5b0de1..4d3b49d96e 100644 --- a/api/client.go +++ b/api/client.go @@ -1041,8 +1041,8 @@ func (cli *DockerCli) CmdPush(args ...string) error { } func (cli *DockerCli) CmdPull(args ...string) error { - cmd := cli.Subcmd("pull", "NAME", "Pull an image or a repository from the registry") - tag := cmd.String([]string{"t", "-tag"}, "", "Download tagged image in repository") + cmd := cli.Subcmd("pull", "NAME[:TAG]", "Pull an image or a repository from the registry") + tag := cmd.String([]string{"#t", "#-tag"}, "", "Download tagged image in repository") if err := cmd.Parse(args); err != nil { return nil } From 721562f29685ebf3f3698113cf0ce8000c02e606 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 11 Mar 2014 11:39:28 -0700 Subject: [PATCH 055/384] Remove goroutine leak upon error Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- execdriver/lxc/driver.go | 3 +++ server.go | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 765a52ee43..b398cb1a37 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -168,6 +168,9 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba // Poll lxc for RUNNING status pid, err := d.waitForStart(c, waitLock) if err != nil { + if c.Process != nil { + c.Process.Kill() + } return -1, err } c.ContainerPid = pid diff --git a/server.go b/server.go index 48bb6f9805..d6a4036faf 100644 --- a/server.go +++ b/server.go @@ -2384,7 +2384,13 @@ func (srv *Server) IsRunning() bool { } func (srv *Server) Close() error { + if srv == nil { + return nil + } srv.SetRunning(false) + if srv.runtime == nil { + return nil + } return srv.runtime.Close() } From fd0737df2c8ec8f0a4b4d8f20b2ad6e4c96adbd3 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 11 Mar 2014 12:08:32 -0700 Subject: [PATCH 056/384] Update parseLxcInfo to comply with new lxc1.0 format Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- execdriver/lxc/info.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execdriver/lxc/info.go b/execdriver/lxc/info.go index 3b2ea0d07f..27b4c58604 100644 --- a/execdriver/lxc/info.go +++ b/execdriver/lxc/info.go @@ -36,7 +36,7 @@ func parseLxcInfo(raw string) (*lxcInfo, error) { if len(parts) < 2 { continue } - switch strings.TrimSpace(parts[0]) { + switch strings.ToLower(strings.TrimSpace(parts[0])) { case "state": info.Running = strings.TrimSpace(parts[1]) == "RUNNING" case "pid": From 95e5910ab23e3ef7b0154f64e1c6ae01bc647ab3 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 11 Mar 2014 17:10:05 -0700 Subject: [PATCH 057/384] Fix attach exit on darwin Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- api/client.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/api/client.go b/api/client.go index 599d82b39a..3dc64e6d55 100644 --- a/api/client.go +++ b/api/client.go @@ -28,7 +28,7 @@ import ( "path" "reflect" "regexp" - "runtime" + goruntime "runtime" "strconv" "strings" "syscall" @@ -367,7 +367,7 @@ func (cli *DockerCli) CmdVersion(args ...string) error { if dockerversion.VERSION != "" { fmt.Fprintf(cli.out, "Client version: %s\n", dockerversion.VERSION) } - fmt.Fprintf(cli.out, "Go version (client): %s\n", runtime.Version()) + fmt.Fprintf(cli.out, "Go version (client): %s\n", goruntime.Version()) if dockerversion.GITCOMMIT != "" { fmt.Fprintf(cli.out, "Git commit (client): %s\n", dockerversion.GITCOMMIT) } @@ -2249,7 +2249,12 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea if setRawTerminal && cli.isTerminal { term.RestoreTerminal(cli.terminalFd, oldState) } - in.Close() + // For some reason this Close call blocks on darwin.. + // As the client exists right after, simply discard the close + // until we find a better solution. + if goruntime.GOOS != "darwin" { + in.Close() + } } }() From baa70e975186bb0ee8a4b16b9374cb6e794f8975 Mon Sep 17 00:00:00 2001 From: Scott Collier Date: Sat, 8 Mar 2014 16:32:00 -0600 Subject: [PATCH 058/384] Adding the new options to the `docker ps` documentation. URL of documentation page is: http://docs.docker.io/en/latest/reference/commandline/cli/#ps Docker-DCO-1.1-Signed-off-by: Scott Collier (github: scollier) Adding the new options to the `docker ps` documentation. URL of documentation page is: http://docs.docker.io/en/latest/reference/commandline/cli/#ps Docker-DCO-1.1-Signed-off-by: Scott Collier (github: scollier) Adding CLI options to the commandline reference documentation. URLs of pages are: http://docs.docker.io/en/latest/reference/commandline/cli/ Docker-DCO-1.1-Signed-off-by: Scott Collier (github: scollier) changing indention Docker-DCO-1.1-Signed-off-by: Scott Collier (github: scollier) --- docs/sources/reference/commandline/cli.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 6fe9b3dfea..07789cf4bd 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -80,7 +80,9 @@ Commands -g, --graph="/var/lib/docker": Path to use as the root of the docker runtime --icc=true: Enable inter-container communication --ip="0.0.0.0": Default IP address to use when binding container ports + --ip-forward=true: Disable enabling of net.ipv4.ip_forward --iptables=true: Disable docker's addition of iptables rules + --mtu=0: Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if not default route is available -p, --pidfile="/var/run/docker.pid": Path to use for daemon PID file -r, --restart=true: Restart previously running containers -s, --storage-driver="": Force the docker runtime to use a specific storage driver @@ -967,6 +969,8 @@ The last container is marked as a ``Ghost`` container. It is a container that wa Pull an image or a repository from the registry + -t, --tag="": Download tagged image in repository + .. _cli_push: @@ -1005,6 +1009,7 @@ The last container is marked as a ``Ghost`` container. It is a container that wa Remove one or more containers -l, --link="": Remove the link instead of the actual container -f, --force=false: Force removal of running container + -v, --volumes=false: Remove the volumes associated to the container Known Issues (rm) ~~~~~~~~~~~~~~~~~ From f0eb227548427f6fc829f2b270ad83d22bd90c69 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 12 Mar 2014 00:51:46 +0000 Subject: [PATCH 059/384] improve deprecation message Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- pkg/mflag/example/example.go | 3 ++- pkg/mflag/flag.go | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/pkg/mflag/example/example.go b/pkg/mflag/example/example.go index ed940e8d70..ce9dd30e4c 100644 --- a/pkg/mflag/example/example.go +++ b/pkg/mflag/example/example.go @@ -13,7 +13,8 @@ var ( func init() { flag.Bool([]string{"#hp", "#-halp"}, false, "display the halp") - flag.BoolVar(&b, []string{"b"}, false, "a simple bool") + flag.BoolVar(&b, []string{"b", "#bal", "#bol", "-bal"}, false, "a simple bool") + flag.BoolVar(&b, []string{"g", "#gil"}, false, "a simple bool") flag.BoolVar(&b2, []string{"#-bool"}, false, "a simple bool") flag.IntVar(&i, []string{"-integer", "-number"}, -1, "a simple integer") flag.StringVar(&str, []string{"s", "#hidden", "-string"}, "", "a simple string") //-s -hidden and --string will work, but -hidden won't be in the usage diff --git a/pkg/mflag/flag.go b/pkg/mflag/flag.go index f16f641341..fc732d23a0 100644 --- a/pkg/mflag/flag.go +++ b/pkg/mflag/flag.go @@ -820,9 +820,20 @@ func (f *FlagSet) parseOne() (bool, string, error) { f.actual = make(map[string]*Flag) } f.actual[name] = flag - for _, n := range flag.Names { + for i, n := range flag.Names { if n == fmt.Sprintf("#%s", name) { - fmt.Fprintf(f.out(), "Warning: '-%s' is deprecated, it will be removed soon. See usage.\n", name) + replacement := "" + for j := i; j < len(flag.Names); j++ { + if flag.Names[j][0] != '#' { + replacement = flag.Names[j] + break + } + } + if replacement != "" { + fmt.Fprintf(f.out(), "Warning: '-%s' is deprecated, it will be replaced by '-%s' soon. See usage.\n", name, replacement) + } else { + fmt.Fprintf(f.out(), "Warning: '-%s' is deprecated, it will be removed soon. See usage.\n", name) + } } } return true, "", nil From 7178b285a334ae202c9b6022a4917fd51733f1d1 Mon Sep 17 00:00:00 2001 From: Scott Collier Date: Sat, 8 Mar 2014 18:07:19 -0600 Subject: [PATCH 060/384] Adding CLI options to the commandline reference documentation. Fixing bad DCO sig URLs of pages are: http://docs.docker.io/en/latest/reference/commandline/cli/ Docker-DCO-1.1-Signed-off-by: Scott Collier (github: scollier) --- docs/sources/reference/commandline/cli.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 07789cf4bd..b00d5c0b95 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -969,7 +969,9 @@ The last container is marked as a ``Ghost`` container. It is a container that wa Pull an image or a repository from the registry +<<<<<<< HEAD -t, --tag="": Download tagged image in repository +>>>>>>> b47d9c5... Adding CLI options to the commandline reference documentation. .. _cli_push: From fbf74eb079f6a96e006e703cb36c434206757fe6 Mon Sep 17 00:00:00 2001 From: Scott Collier Date: Tue, 11 Mar 2014 20:00:16 -0500 Subject: [PATCH 061/384] Removing HEAD tag from last commit Docker-DCO-1.1-Signed-off-by: Scott Collier (github: scollier) --- docs/sources/reference/commandline/cli.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index b00d5c0b95..07789cf4bd 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -969,9 +969,7 @@ The last container is marked as a ``Ghost`` container. It is a container that wa Pull an image or a repository from the registry -<<<<<<< HEAD -t, --tag="": Download tagged image in repository ->>>>>>> b47d9c5... Adding CLI options to the commandline reference documentation. .. _cli_push: From b7ae9984fb541e8b53d09016e78ff352ce310c25 Mon Sep 17 00:00:00 2001 From: Ken ICHIKAWA Date: Wed, 12 Mar 2014 13:44:01 +0900 Subject: [PATCH 062/384] Update daemon docs and help messages for --iptables and --ip-forward Fix docs and help messages of --iptables and --ip-forward to describe the true case behaviour Docker-DCO-1.1-Signed-off-by: Ken ICHIKAWA (github: ichik1) --- docker/docker.go | 4 ++-- docs/sources/reference/commandline/cli.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/docker.go b/docker/docker.go index b783c6da02..cc4d40f3ac 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -35,8 +35,8 @@ func main() { flSocketGroup = flag.String([]string{"G", "-group"}, "docker", "Group to assign the unix socket specified by -H when running in daemon mode; use '' (the empty string) to disable setting of a group") flEnableCors = flag.Bool([]string{"#api-enable-cors", "-api-enable-cors"}, false, "Enable CORS headers in the remote API") flDns = opts.NewListOpts(opts.ValidateIp4Address) - flEnableIptables = flag.Bool([]string{"#iptables", "-iptables"}, true, "Disable docker's addition of iptables rules") - flEnableIpForward = flag.Bool([]string{"#ip-forward", "-ip-forward"}, true, "Disable enabling of net.ipv4.ip_forward") + flEnableIptables = flag.Bool([]string{"#iptables", "-iptables"}, true, "Enable Docker's addition of iptables rules") + flEnableIpForward = flag.Bool([]string{"#ip-forward", "-ip-forward"}, true, "Enable net.ipv4.ip_forward") flDefaultIp = flag.String([]string{"#ip", "-ip"}, "0.0.0.0", "Default IP address to use when binding container ports") flInterContainerComm = flag.Bool([]string{"#icc", "-icc"}, true, "Enable inter-container communication") flGraphDriver = flag.String([]string{"s", "-storage-driver"}, "", "Force the docker runtime to use a specific storage driver") diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index da4472ed85..692f63f447 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -80,8 +80,8 @@ Commands -g, --graph="/var/lib/docker": Path to use as the root of the docker runtime --icc=true: Enable inter-container communication --ip="0.0.0.0": Default IP address to use when binding container ports - --ip-forward=true: Disable enabling of net.ipv4.ip_forward - --iptables=true: Disable docker's addition of iptables rules + --ip-forward=true: Enable net.ipv4.ip_forward + --iptables=true: Enable Docker's addition of iptables rules --mtu=0: Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if not default route is available -p, --pidfile="/var/run/docker.pid": Path to use for daemon PID file -r, --restart=true: Restart previously running containers From e1c48fa56007e1db028f7f83bfbf79c3d05feccd Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 12 Mar 2014 01:17:38 -0600 Subject: [PATCH 063/384] Clean up the "go test" output from "make test" to be much more readable/scannable Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/make.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/make.sh b/hack/make.sh index 63edca4d4c..d7f8aaeed2 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -125,7 +125,7 @@ go_test_dir() { testcover=( -cover -coverprofile "$coverprofile" $coverpkg ) fi ( - set -x + echo '+ go test' $TESTFLAGS "github.com/dotcloud/docker${dir#.}" cd "$dir" go test ${testcover[@]} -ldflags "$LDFLAGS" "${BUILDFLAGS[@]}" $TESTFLAGS ) From 99b6364790e59a70d57949792fa31014637c93ee Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 12 Mar 2014 01:18:12 -0600 Subject: [PATCH 064/384] Exclude more "definitely not unit tested Go source code" directories from hack/make/test Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/make.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/hack/make.sh b/hack/make.sh index d7f8aaeed2..994da8d9ad 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -136,7 +136,15 @@ go_test_dir() { # output, one per line. find_dirs() { find -not \( \ - \( -wholename './vendor' -o -wholename './integration' -o -wholename './contrib' -o -wholename './pkg/mflag/example' \) \ + \( \ + -wholename './vendor' \ + -o -wholename './integration' \ + -o -wholename './contrib' \ + -o -wholename './pkg/mflag/example' \ + -o -wholename './.git' \ + -o -wholename './bundles' \ + -o -wholename './docs' \ + \) \ -prune \ \) -name "$1" -print0 | xargs -0n1 dirname | sort -u } From 8bf63d532622e22070b92ee71dcb972ace4b9b40 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Wed, 12 Mar 2014 22:02:24 +1000 Subject: [PATCH 065/384] fixes suggested by James Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/examples/apt-cacher-ng.Dockerfile | 8 ++++---- docs/sources/examples/apt-cacher-ng.rst | 12 +++++------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/docs/sources/examples/apt-cacher-ng.Dockerfile b/docs/sources/examples/apt-cacher-ng.Dockerfile index fcc326815d..a189d28d86 100644 --- a/docs/sources/examples/apt-cacher-ng.Dockerfile +++ b/docs/sources/examples/apt-cacher-ng.Dockerfile @@ -1,9 +1,9 @@ # -# BUILD docker build -t apt-cacher . -# RUN docker run -d -p 3142:3142 -name apt-cacher-run apt-cacher +# Build: docker build -t apt-cacher . +# Run: docker run -d -p 3142:3142 -name apt-cacher-run apt-cacher # # and then you can run containers with: -# docker run -t -i -rm -e http_proxy http://dockerhost:3142/ debian bash +# docker run -t -i -rm -e http_proxy http://dockerhost:3142/ debian bash # FROM ubuntu MAINTAINER SvenDowideit@docker.com @@ -11,5 +11,5 @@ MAINTAINER SvenDowideit@docker.com VOLUME ["/var/cache/apt-cacher-ng"] RUN apt-get update ; apt-get install -yq apt-cacher-ng -EXPOSE 3142 +EXPOSE 3142 CMD chmod 777 /var/cache/apt-cacher-ng ; /etc/init.d/apt-cacher-ng start ; tail -f /var/log/apt-cacher-ng/* diff --git a/docs/sources/examples/apt-cacher-ng.rst b/docs/sources/examples/apt-cacher-ng.rst index 0fb55720f2..ed22c33d05 100644 --- a/docs/sources/examples/apt-cacher-ng.rst +++ b/docs/sources/examples/apt-cacher-ng.rst @@ -37,11 +37,11 @@ To see the logfiles that are 'tailed' in the default command, you can use: $ sudo docker logs -f test_apt_cacher_ng -To get your Debian based containers to use the proxy, you can do one of three things +To get your Debian-based containers to use the proxy, you can do one of three things 1. Add an apt Proxy setting ``echo 'Acquire::http { Proxy "http://dockerhost:3142"; };' >> /etc/apt/conf.d/01proxy`` -2. Set and environment variable: ``http_proxy=http://dockerhost:3142/`` -3. Change your sources.list entries to start with ``http://dockerhost:3142/`` +2. Set an environment variable: ``http_proxy=http://dockerhost:3142/`` +3. Change your ``sources.list`` entries to start with ``http://dockerhost:3142/`` **Option 1** injects the settings safely into your apt configuration in a local version of a common base: @@ -49,9 +49,7 @@ version of a common base: .. code-block:: bash FROM ubuntu - RUN echo 'Acquire::http { Proxy "http://dockerhost:3142"; };' >> /etc/apt/apt.conf.d/01proxy - RUN apt-get update ; apt-get install vim git # docker build -t my_ubuntu . @@ -64,10 +62,10 @@ break other HTTP clients which obey ``http_proxy``, such as ``curl``, ``wget`` a $ sudo docker run -rm -t -i -e http_proxy=http://dockerhost:3142/ debian bash **Option 3** is the least portable, but there will be times when you might need to -do it - and you can do it from your Dockerfile too. +do it and you can do it from your ``Dockerfile`` too. Apt-cacher-ng has some tools that allow you to manage the repository, and they -can be used by leveraging the ``VOLUME``, and the image we built to run the +can be used by leveraging the ``VOLUME`` instruction, and the image we built to run the service: .. code-block:: bash From 2cfcf42d50b469abfb0e13245726371d445b76e4 Mon Sep 17 00:00:00 2001 From: unclejack Date: Fri, 28 Feb 2014 18:42:20 +0200 Subject: [PATCH 066/384] retry to retrieve metadata on failure during pull This makes Docker retry to retrieve the JSON metadata for the layers. Docker will make 5 attempts to retrieve the metadata before failing and it will increase the delay between attempts after each failed attempt. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- server.go | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/server.go b/server.go index d6a4036faf..4876f31bae 100644 --- a/server.go +++ b/server.go @@ -1086,16 +1086,32 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin if !srv.runtime.Graph().Exists(id) { out.Write(sf.FormatProgress(utils.TruncateID(id), "Pulling metadata", nil)) - imgJSON, imgSize, err := r.GetRemoteImageJSON(id, endpoint, token) - if err != nil { - out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil)) - // FIXME: Keep going in case of error? - return err - } - img, err := image.NewImgJSON(imgJSON) - if err != nil { - out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil)) - return fmt.Errorf("Failed to parse json: %s", err) + var ( + imgJSON []byte + imgSize int + err error + img *image.Image + ) + retries := 5 + for j := 1; j <= retries; j++ { + imgJSON, imgSize, err = r.GetRemoteImageJSON(id, endpoint, token) + if err != nil && j == retries { + out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil)) + return err + } else if err != nil { + time.Sleep(time.Duration(j) * 500 * time.Millisecond) + continue + } + img, err = image.NewImgJSON(imgJSON) + if err != nil && j == retries { + out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil)) + return fmt.Errorf("Failed to parse json: %s", err) + } else if err != nil { + time.Sleep(time.Duration(j) * 500 * time.Millisecond) + continue + } else { + break + } } // Get the layer From 2a5e1abaa93b0081446420cae9d6d7d3892b6d75 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Wed, 12 Mar 2014 15:42:10 +0100 Subject: [PATCH 067/384] Update port_redirection.rst Fix flags --- docs/sources/use/port_redirection.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/use/port_redirection.rst b/docs/sources/use/port_redirection.rst index 38d6b98841..ba244adadb 100644 --- a/docs/sources/use/port_redirection.rst +++ b/docs/sources/use/port_redirection.rst @@ -128,7 +128,7 @@ The ``client`` then links to the ``server``: .. code-block:: bash # Link - docker run -name client -link server:linked-server + docker run --name client --link server:linked-server ``client`` locally refers to ``server`` as ``linked-server``. The following environment variables, among others, are available on From a56d1b93a1a16ac482b5e30773664f2538949c53 Mon Sep 17 00:00:00 2001 From: unclejack Date: Wed, 12 Mar 2014 17:58:53 +0200 Subject: [PATCH 068/384] don't leave empty cidFile behind This makes `--cidfile` clean up empty container ID files. These are left behind when creating the container fails. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- api/client.go | 16 +++++++++++++++- integration/commands_test.go | 31 ++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/api/client.go b/api/client.go index 599d82b39a..adaf2dfd4e 100644 --- a/api/client.go +++ b/api/client.go @@ -1763,7 +1763,21 @@ func (cli *DockerCli) CmdRun(args ...string) error { if containerIDFile, err = os.Create(hostConfig.ContainerIDFile); err != nil { return fmt.Errorf("Failed to create the container ID file: %s", err) } - defer containerIDFile.Close() + defer func() { + containerIDFile.Close() + var ( + cidFileInfo os.FileInfo + err error + ) + if cidFileInfo, err = os.Stat(hostConfig.ContainerIDFile); err != nil { + return + } + if cidFileInfo.Size() == 0 { + if err := os.Remove(hostConfig.ContainerIDFile); err != nil { + fmt.Printf("failed to remove CID file '%s': %s \n", hostConfig.ContainerIDFile, err) + } + } + }() } containerValues := url.Values{} diff --git a/integration/commands_test.go b/integration/commands_test.go index 46f623bedf..d226cd7133 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -931,7 +931,7 @@ run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ] // #2098 - Docker cidFiles only contain short version of the containerId //sudo docker run --cidfile /tmp/docker_test.cid ubuntu echo "test" // TestRunCidFile tests that run --cidfile returns the longid -func TestRunCidFile(t *testing.T) { +func TestRunCidFileCheckIDLength(t *testing.T) { stdout, stdoutPipe := io.Pipe() tmpDir, err := ioutil.TempDir("", "TestRunCidFile") @@ -980,6 +980,35 @@ func TestRunCidFile(t *testing.T) { } +// Ensure that CIDFile gets deleted if it's empty +// Perform this test by making `docker run` fail +func TestRunCidFileCleanupIfEmpty(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "TestRunCidFile") + if err != nil { + t.Fatal(err) + } + tmpCidFile := path.Join(tmpDir, "cid") + + cli := api.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr) + defer cleanup(globalEngine, t) + + c := make(chan struct{}) + go func() { + defer close(c) + if err := cli.CmdRun("--cidfile", tmpCidFile, unitTestImageID); err == nil { + t.Fatal("running without a command should haveve failed") + } + if _, err := os.Stat(tmpCidFile); err == nil { + t.Fatalf("empty CIDFile '%s' should've been deleted", tmpCidFile) + } + }() + defer os.RemoveAll(tmpDir) + + setTimeout(t, "CmdRun timed out", 5*time.Second, func() { + <-c + }) +} + func TestContainerOrphaning(t *testing.T) { // setup a temporary directory From 6a325f1c7a243689ecf01f257ac7afb95fea7ec2 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 12 Mar 2014 11:13:24 -0700 Subject: [PATCH 069/384] Fix issue when /etc/apparmor.d does not exists Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- pkg/libcontainer/apparmor/setup.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/libcontainer/apparmor/setup.go b/pkg/libcontainer/apparmor/setup.go index e07759cc64..4e1c95143a 100644 --- a/pkg/libcontainer/apparmor/setup.go +++ b/pkg/libcontainer/apparmor/setup.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "os" "os/exec" + "path" ) const DefaultProfilePath = "/etc/apparmor.d/docker" @@ -85,6 +86,11 @@ func InstallDefaultProfile() error { return nil } + // Make sure /etc/apparmor.d exists + if err := os.MkdirAll(path.Dir(DefaultProfilePath), 0755); err != nil { + return err + } + if err := ioutil.WriteFile(DefaultProfilePath, []byte(DefaultProfile), 0644); err != nil { return err } From 471aa870f57e66128b08ec28e908fb1faacdfd40 Mon Sep 17 00:00:00 2001 From: Ken ICHIKAWA Date: Wed, 12 Mar 2014 14:52:39 +0900 Subject: [PATCH 070/384] Remove duplicated description of --mtu commit baa70e975186bb0ee8a4b16b9374cb6e794f8975 duplicates the description of --mtu. This patch removes the duplicated description. Docker-DCO-1.1-Signed-off-by: Ken ICHIKAWA (github: ichik1) --- docs/sources/reference/commandline/cli.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 0e3f30ede2..d146c18a15 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -82,7 +82,6 @@ Commands --ip="0.0.0.0": Default IP address to use when binding container ports --ip-forward=true: Enable net.ipv4.ip_forward --iptables=true: Enable Docker's addition of iptables rules - --mtu=0: Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if not default route is available -p, --pidfile="/var/run/docker.pid": Path to use for daemon PID file -r, --restart=true: Restart previously running containers -s, --storage-driver="": Force the docker runtime to use a specific storage driver From 841fcad0ba03ff71a27ad2892fab3fdc83d071d8 Mon Sep 17 00:00:00 2001 From: Ken ICHIKAWA Date: Thu, 13 Mar 2014 15:19:42 +0900 Subject: [PATCH 071/384] Add missing options -t and -v to images subcommand doc Docker-DCO-1.1-Signed-off-by: Ken ICHIKAWA (github: ichik1) --- docs/sources/reference/commandline/cli.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index d146c18a15..e65bd930ae 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -608,8 +608,8 @@ To see how the ``docker:latest`` image was built: -a, --all=false: Show all images (by default filter out the intermediate images used to build) --no-trunc=false: Don't truncate output -q, --quiet=false: Only show numeric IDs - --tree=false: Output graph in tree format - --viz=false: Output graph in graphviz format + -t, --tree=false: Output graph in tree format + -v, --viz=false: Output graph in graphviz format Listing the most recently created images ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 73596b00e053fedbf42f7abb87728e7176e5a95c Mon Sep 17 00:00:00 2001 From: Phillip Alexander Date: Wed, 12 Mar 2014 20:01:27 -0700 Subject: [PATCH 072/384] Fix boilerplate text in Apache license This commit updates the Apache license boilerplate with actual information. The Apache license appendix (designed to be removed before publication) states: ``` APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!)... ``` Additionally, the copyright year was not included. Copyright notices must reflect the current year. This commit updates the listed year to 2014. see: http://www.copyright.gov/circs/circ01.pdf for more info Docker-DCO-1.1-Signed-off-by: Phillip Alexander (github: phillipalexander) --- LICENSE | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/LICENSE b/LICENSE index d645695673..27448585ad 100644 --- a/LICENSE +++ b/LICENSE @@ -176,18 +176,7 @@ END OF TERMS AND CONDITIONS - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] + Copyright 2014 Docker, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 9a0d7fe0182da541cc99eab9a4930616792e95c3 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 13 Mar 2014 17:40:34 +0000 Subject: [PATCH 073/384] use mock for search Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- registry/registry.go | 2 +- registry/registry_mock_test.go | 7 ++++++- registry/registry_test.go | 6 ++++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index dbf5d539ff..346132bcc5 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -600,7 +600,7 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat func (r *Registry) SearchRepositories(term string) (*SearchResults, error) { utils.Debugf("Index server: %s", r.indexEndpoint) - u := IndexServerAddress() + "search?q=" + url.QueryEscape(term) + u := r.indexEndpoint + "search?q=" + url.QueryEscape(term) req, err := r.reqFactory.NewRequest("GET", u, nil) if err != nil { return nil, err diff --git a/registry/registry_mock_test.go b/registry/registry_mock_test.go index 6eb94b63cc..dd5da6bd50 100644 --- a/registry/registry_mock_test.go +++ b/registry/registry_mock_test.go @@ -321,7 +321,12 @@ func handlerAuth(w http.ResponseWriter, r *http.Request) { } func handlerSearch(w http.ResponseWriter, r *http.Request) { - writeResponse(w, "{}", 200) + result := &SearchResults{ + Query: "fakequery", + NumResults: 1, + Results: []SearchResult{{Name: "fakeimage", StarCount: 42}}, + } + writeResponse(w, result, 200) } func TestPing(t *testing.T) { diff --git a/registry/registry_test.go b/registry/registry_test.go index f21814c791..ebfb99b4c3 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -186,14 +186,16 @@ func TestPushImageJSONIndex(t *testing.T) { func TestSearchRepositories(t *testing.T) { r := spawnTestRegistry(t) - results, err := r.SearchRepositories("supercalifragilisticepsialidocious") + results, err := r.SearchRepositories("fakequery") if err != nil { t.Fatal(err) } if results == nil { t.Fatal("Expected non-nil SearchResults object") } - assertEqual(t, results.NumResults, 0, "Expected 0 search results") + assertEqual(t, results.NumResults, 1, "Expected 1 search results") + assertEqual(t, results.Query, "fakequery", "Expected 'fakequery' as query") + assertEqual(t, results.Results[0].StarCount, 42, "Expected 'fakeimage' a ot hae 42 stars") } func TestValidRepositoryName(t *testing.T) { From 44fe8cbbd174b5d85d4a063ed270f6b9d2279b70 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 13 Mar 2014 11:46:02 -0600 Subject: [PATCH 074/384] Update to double-dash everywhere These were found using `git grep -nE '[^-a-zA-Z0-9<>]-[a-zA-Z0-9]{2}'` (fair warning: _many_ false positives there). Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- Dockerfile | 6 +- api/client.go | 2 +- contrib/completion/fish/docker.fish | 4 +- contrib/completion/zsh/_docker | 2 +- contrib/desktop-integration/data/Dockerfile | 6 +- .../desktop-integration/iceweasel/Dockerfile | 8 +- contrib/init/sysvinit-debian/docker.default | 2 +- contrib/mkseccomp.pl | 2 +- .../sources/examples/apt-cacher-ng.Dockerfile | 4 +- docs/sources/examples/apt-cacher-ng.rst | 8 +- docs/sources/examples/hello_world.rst | 4 +- docs/sources/examples/postgresql_service.rst | 10 +-- docs/sources/examples/python_web_app.rst | 2 +- .../examples/running_redis_service.rst | 2 +- docs/sources/examples/running_ssh_service.rst | 4 +- docs/sources/installation/ubuntulinux.rst | 4 +- .../reference/api/docker_remote_api.rst | 2 +- .../reference/api/docker_remote_api_v1.10.rst | 4 +- .../reference/api/docker_remote_api_v1.2.rst | 4 +- .../reference/api/docker_remote_api_v1.3.rst | 4 +- .../reference/api/docker_remote_api_v1.4.rst | 4 +- .../reference/api/docker_remote_api_v1.5.rst | 4 +- .../reference/api/docker_remote_api_v1.6.rst | 4 +- .../reference/api/docker_remote_api_v1.7.rst | 4 +- .../reference/api/docker_remote_api_v1.8.rst | 4 +- .../reference/api/docker_remote_api_v1.9.rst | 4 +- docs/sources/reference/builder.rst | 4 +- docs/sources/reference/commandline/cli.rst | 42 +++++----- docs/sources/reference/run.rst | 80 +++++++++---------- .../use/ambassador_pattern_linking.rst | 28 +++---- docs/sources/use/basics.rst | 2 +- docs/sources/use/networking.rst | 4 +- docs/sources/use/port_redirection.rst | 4 +- docs/sources/use/working_with_links_names.rst | 16 ++-- docs/sources/use/working_with_volumes.rst | 18 ++--- docs/sources/use/workingwithrepository.rst | 2 +- hack/RELEASE-CHECKLIST.md | 4 +- hack/dind | 4 +- hack/infrastructure/docker-ci/Dockerfile | 4 +- hack/infrastructure/docker-ci/README.rst | 4 +- .../docker-ci/dockertest/nightlyrelease | 2 +- .../docker-ci/dockertest/project | 2 +- .../docker-ci/testbuilder/Dockerfile | 4 +- .../docker-ci/testbuilder/docker-registry.sh | 6 +- .../docker-ci/testbuilder/docker.sh | 8 +- hack/release.sh | 2 +- integration/commands_test.go | 2 +- integration/container_test.go | 4 +- integration/server_test.go | 2 +- runconfig/config_test.go | 16 ++-- runconfig/parse.go | 4 +- 51 files changed, 188 insertions(+), 188 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7fad3d56a1..42438e3946 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,13 +6,13 @@ # docker build -t docker . # # # Mount your source in an interactive container for quick testing: -# docker run -v `pwd`:/go/src/github.com/dotcloud/docker -privileged -i -t docker bash +# docker run -v `pwd`:/go/src/github.com/dotcloud/docker --privileged -i -t docker bash # # # Run the test suite: -# docker run -privileged docker hack/make.sh test +# docker run --privileged docker hack/make.sh test # # # Publish a release: -# docker run -privileged \ +# docker run --privileged \ # -e AWS_S3_BUCKET=baz \ # -e AWS_ACCESS_KEY=foo \ # -e AWS_SECRET_KEY=bar \ diff --git a/api/client.go b/api/client.go index 6049a892c1..3c605d4af9 100644 --- a/api/client.go +++ b/api/client.go @@ -1409,7 +1409,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error { cmd := cli.Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes") flComment := cmd.String([]string{"m", "-message"}, "", "Commit message") flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (eg. \"John Hannibal Smith \"") - flConfig := cmd.String([]string{"#run", "-run"}, "", "Config automatically applied when the image is run. "+`(ex: -run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`) + flConfig := cmd.String([]string{"#run", "-run"}, "", "Config automatically applied when the image is run. "+`(ex: --run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`) if err := cmd.Parse(args); err != nil { return nil } diff --git a/contrib/completion/fish/docker.fish b/contrib/completion/fish/docker.fish index 2629533aac..b0c5f38a96 100644 --- a/contrib/completion/fish/docker.fish +++ b/contrib/completion/fish/docker.fish @@ -79,7 +79,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from build' -s t -l tag -d ' complete -c docker -f -n '__fish_docker_no_subcommand' -a commit -d "Create a new image from a container's changes" complete -c docker -A -f -n '__fish_seen_subcommand_from commit' -s a -l author -d 'Author (eg. "John Hannibal Smith "' complete -c docker -A -f -n '__fish_seen_subcommand_from commit' -s m -l message -d 'Commit message' -complete -c docker -A -f -n '__fish_seen_subcommand_from commit' -l run -d 'Config automatically applied when the image is run. (ex: -run=\'{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}\')' +complete -c docker -A -f -n '__fish_seen_subcommand_from commit' -l run -d 'Config automatically applied when the image is run. (ex: --run=\'{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}\')' complete -c docker -A -f -n '__fish_seen_subcommand_from commit' -a '(__fish_print_docker_containers all)' -d "Container" # cp @@ -202,7 +202,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l expose -d 'Expo complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s h -l hostname -d 'Container host name' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s i -l interactive -d 'Keep stdin open even if not attached' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l link -d 'Add link to another container (name:alias)' -complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l lxc-conf -d 'Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"' +complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l lxc-conf -d 'Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s m -l memory -d 'Memory limit (format: , where unit = b, k, m or g)' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s n -l networking -d 'Enable networking for this container' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l name -d 'Assign a name to the container' diff --git a/contrib/completion/zsh/_docker b/contrib/completion/zsh/_docker index 8b50bac01b..a379fd40f8 100755 --- a/contrib/completion/zsh/_docker +++ b/contrib/completion/zsh/_docker @@ -174,7 +174,7 @@ __docker_subcommand () { (ps) _arguments '-a[Show all containers. Only running containers are shown by default]' \ '-h[Show help]' \ - '-before-id=-[Show only container created before Id, include non-running one]:containers:__docker_containers' \ + '--before-id=-[Show only container created before Id, include non-running one]:containers:__docker_containers' \ '-n=-[Show n last created containers, include non-running one]:n:(1 5 10 25 50)' ;; (tag) diff --git a/contrib/desktop-integration/data/Dockerfile b/contrib/desktop-integration/data/Dockerfile index a9843a52ad..76846af912 100644 --- a/contrib/desktop-integration/data/Dockerfile +++ b/contrib/desktop-integration/data/Dockerfile @@ -9,13 +9,13 @@ # wget http://raw.github.com/dotcloud/docker/master/contrib/desktop-integration/data/Dockerfile # # # Build data image -# docker build -t data -rm . +# docker build -t data . # # # Create a data container. (eg: iceweasel-data) -# docker run -name iceweasel-data data true +# docker run --name iceweasel-data data true # # # List data from it -# docker run -volumes-from iceweasel-data busybox ls -al /data +# docker run --volumes-from iceweasel-data busybox ls -al /data docker-version 0.6.5 diff --git a/contrib/desktop-integration/iceweasel/Dockerfile b/contrib/desktop-integration/iceweasel/Dockerfile index 721cc6d2cf..f9f58c9ca5 100644 --- a/contrib/desktop-integration/iceweasel/Dockerfile +++ b/contrib/desktop-integration/iceweasel/Dockerfile @@ -10,16 +10,16 @@ # wget http://raw.github.com/dotcloud/docker/master/contrib/desktop-integration/iceweasel/Dockerfile # # # Build iceweasel image -# docker build -t iceweasel -rm . +# docker build -t iceweasel . # # # Run stateful data-on-host iceweasel. For ephemeral, remove -v /data/iceweasel:/data # docker run -v /data/iceweasel:/data -v /tmp/.X11-unix:/tmp/.X11-unix \ -# -v /dev/snd:/dev/snd -lxc-conf='lxc.cgroup.devices.allow = c 116:* rwm' \ +# -v /dev/snd:/dev/snd --lxc-conf='lxc.cgroup.devices.allow = c 116:* rwm' \ # -e DISPLAY=unix$DISPLAY iceweasel # # # To run stateful dockerized data containers -# docker run -volumes-from iceweasel-data -v /tmp/.X11-unix:/tmp/.X11-unix \ -# -v /dev/snd:/dev/snd -lxc-conf='lxc.cgroup.devices.allow = c 116:* rwm' \ +# docker run --volumes-from iceweasel-data -v /tmp/.X11-unix:/tmp/.X11-unix \ +# -v /dev/snd:/dev/snd --lxc-conf='lxc.cgroup.devices.allow = c 116:* rwm' \ # -e DISPLAY=unix$DISPLAY iceweasel docker-version 0.6.5 diff --git a/contrib/init/sysvinit-debian/docker.default b/contrib/init/sysvinit-debian/docker.default index d5110b5e2f..14e660175b 100644 --- a/contrib/init/sysvinit-debian/docker.default +++ b/contrib/init/sysvinit-debian/docker.default @@ -4,7 +4,7 @@ #DOCKER="/usr/local/bin/docker" # Use DOCKER_OPTS to modify the daemon startup options. -#DOCKER_OPTS="-dns 8.8.8.8 -dns 8.8.4.4" +#DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4" # If you need Docker to use an HTTP proxy, it can also be specified here. #export http_proxy="http://127.0.0.1:3128/" diff --git a/contrib/mkseccomp.pl b/contrib/mkseccomp.pl index 5c583cc3d3..28d0645af0 100755 --- a/contrib/mkseccomp.pl +++ b/contrib/mkseccomp.pl @@ -10,7 +10,7 @@ # can configure the list of syscalls. When run, this script produces output # which, when stored in a file, can be passed to docker as follows: # -# docker run -lxc-conf="lxc.seccomp=$file" +# docker run --lxc-conf="lxc.seccomp=$file" # # The included sample file shows how to cut about a quarter of all syscalls, # which affecting most applications. diff --git a/docs/sources/examples/apt-cacher-ng.Dockerfile b/docs/sources/examples/apt-cacher-ng.Dockerfile index a189d28d86..3b7862bb58 100644 --- a/docs/sources/examples/apt-cacher-ng.Dockerfile +++ b/docs/sources/examples/apt-cacher-ng.Dockerfile @@ -1,9 +1,9 @@ # # Build: docker build -t apt-cacher . -# Run: docker run -d -p 3142:3142 -name apt-cacher-run apt-cacher +# Run: docker run -d -p 3142:3142 --name apt-cacher-run apt-cacher # # and then you can run containers with: -# docker run -t -i -rm -e http_proxy http://dockerhost:3142/ debian bash +# docker run -t -i --rm -e http_proxy http://dockerhost:3142/ debian bash # FROM ubuntu MAINTAINER SvenDowideit@docker.com diff --git a/docs/sources/examples/apt-cacher-ng.rst b/docs/sources/examples/apt-cacher-ng.rst index ed22c33d05..dd844d4ef1 100644 --- a/docs/sources/examples/apt-cacher-ng.rst +++ b/docs/sources/examples/apt-cacher-ng.rst @@ -23,13 +23,13 @@ To build the image using: .. code-block:: bash - $ sudo docker build -rm -t eg_apt_cacher_ng . + $ sudo docker build -t eg_apt_cacher_ng . Then run it, mapping the exposed port to one on the host .. code-block:: bash - $ sudo docker run -d -p 3142:3142 -name test_apt_cacher_ng eg_apt_cacher_ng + $ sudo docker run -d -p 3142:3142 --name test_apt_cacher_ng eg_apt_cacher_ng To see the logfiles that are 'tailed' in the default command, you can use: @@ -59,7 +59,7 @@ break other HTTP clients which obey ``http_proxy``, such as ``curl``, ``wget`` a .. code-block:: bash - $ sudo docker run -rm -t -i -e http_proxy=http://dockerhost:3142/ debian bash + $ sudo docker run --rm -t -i -e http_proxy=http://dockerhost:3142/ debian bash **Option 3** is the least portable, but there will be times when you might need to do it and you can do it from your ``Dockerfile`` too. @@ -70,7 +70,7 @@ service: .. code-block:: bash - $ sudo docker run -rm -t -i --volumes-from test_apt_cacher_ng eg_apt_cacher_ng bash + $ sudo docker run --rm -t -i --volumes-from test_apt_cacher_ng eg_apt_cacher_ng bash $$ /usr/lib/apt-cacher-ng/distkill.pl Scanning /var/cache/apt-cacher-ng, please wait... diff --git a/docs/sources/examples/hello_world.rst b/docs/sources/examples/hello_world.rst index b8538debb9..507056da85 100644 --- a/docs/sources/examples/hello_world.rst +++ b/docs/sources/examples/hello_world.rst @@ -119,13 +119,13 @@ Check the logs make sure it is working correctly. .. code-block:: bash - sudo docker attach -sig-proxy=false $container_id + sudo docker attach --sig-proxy=false $container_id Attach to the container to see the results in real-time. - **"docker attach**" This will allow us to attach to a background process to see what is going on. -- **"-sig-proxy=false"** Do not forward signals to the container; allows +- **"--sig-proxy=false"** Do not forward signals to the container; allows us to exit the attachment using Control-C without stopping the container. - **$container_id** The Id of the container we want to attach too. diff --git a/docs/sources/examples/postgresql_service.rst b/docs/sources/examples/postgresql_service.rst index 5a2323471b..66b0fd7aa5 100644 --- a/docs/sources/examples/postgresql_service.rst +++ b/docs/sources/examples/postgresql_service.rst @@ -37,24 +37,24 @@ 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). -.. note:: The ``-rm`` removes the container and its image when the container +.. note:: The ``--rm`` removes the container and its image when the container exists successfully. Using container linking ^^^^^^^^^^^^^^^^^^^^^^^ Containers can be linked to another container's ports directly using -``-link remote_name:local_alias`` in the client's ``docker run``. This will +``--link remote_name:local_alias`` in the client's ``docker run``. This will set a number of environment variables that can then be used to connect: .. code-block:: bash - $ sudo docker run -rm -t -i -link pg_test:pg eg_postgresql bash + $ sudo docker run --rm -t -i --link pg_test:pg eg_postgresql bash postgres@7ef98b1b7243:/$ psql -h $PG_PORT_5432_TCP_ADDR -p $PG_PORT_5432_TCP_PORT -d docker -U docker --password @@ -104,7 +104,7 @@ configuration and data: .. code-block:: bash - docker run -rm --volumes-from pg_test -t -i busybox sh + docker run --rm --volumes-from pg_test -t -i busybox sh / # ls bin etc lib linuxrc mnt proc run sys usr diff --git a/docs/sources/examples/python_web_app.rst b/docs/sources/examples/python_web_app.rst index 5b8e3f6b4b..33c038f9ab 100644 --- a/docs/sources/examples/python_web_app.rst +++ b/docs/sources/examples/python_web_app.rst @@ -51,7 +51,7 @@ try things out, and then exit when you're done. .. code-block:: bash - $ sudo docker run -i -t -name pybuilder_run shykes/pybuilder bash + $ sudo docker run -i -t --name pybuilder_run shykes/pybuilder bash $$ URL=http://github.com/shykes/helloflask/archive/master.tar.gz $$ /usr/local/bin/buildapp $URL diff --git a/docs/sources/examples/running_redis_service.rst b/docs/sources/examples/running_redis_service.rst index c9424867a4..50f1471f17 100644 --- a/docs/sources/examples/running_redis_service.rst +++ b/docs/sources/examples/running_redis_service.rst @@ -49,7 +49,7 @@ use a container link to provide access to our Redis database. Create your web application container ------------------------------------- -Next we can create a container for our application. We're going to use the ``-link`` +Next we can create a container for our application. We're going to use the ``--link`` flag to create a link to the ``redis`` container we've just created with an alias of ``db``. This will create a secure tunnel to the ``redis`` container and expose the Redis instance running inside that container to only this container. diff --git a/docs/sources/examples/running_ssh_service.rst b/docs/sources/examples/running_ssh_service.rst index d27799bee7..4161275019 100644 --- a/docs/sources/examples/running_ssh_service.rst +++ b/docs/sources/examples/running_ssh_service.rst @@ -19,14 +19,14 @@ Build the image using: .. code-block:: bash - $ sudo docker build -rm -t eg_sshd . + $ sudo docker build -t eg_sshd . Then run it. You can then use ``docker port`` to find out what host port the container's port 22 is mapped to: .. code-block:: bash - $ sudo docker run -d -P -name test_sshd eg_sshd + $ sudo docker run -d -P --name test_sshd eg_sshd $ sudo docker port test_sshd 22 0.0.0.0:49154 diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index c459f33d3c..6e79fb8cbc 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -309,9 +309,9 @@ daemon for the containers: sudo nano /etc/default/docker --- # Add: - DOCKER_OPTS="-dns 8.8.8.8" + DOCKER_OPTS="--dns 8.8.8.8" # 8.8.8.8 could be replaced with a local DNS server, such as 192.168.1.1 - # multiple DNS servers can be specified: -dns 8.8.8.8 -dns 192.168.1.1 + # multiple DNS servers can be specified: --dns 8.8.8.8 --dns 192.168.1.1 The Docker daemon has to be restarted: diff --git a/docs/sources/reference/api/docker_remote_api.rst b/docs/sources/reference/api/docker_remote_api.rst index e1071bf085..93558fa974 100644 --- a/docs/sources/reference/api/docker_remote_api.rst +++ b/docs/sources/reference/api/docker_remote_api.rst @@ -203,7 +203,7 @@ What's new .. http:get:: /images/viz - This URI no longer exists. The ``images -viz`` output is now generated in + This URI no longer exists. The ``images --viz`` output is now generated in the client, using the ``/images/json`` data. v1.6 diff --git a/docs/sources/reference/api/docker_remote_api_v1.10.rst b/docs/sources/reference/api/docker_remote_api_v1.10.rst index ed63525e7e..20af253f0e 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.10.rst +++ b/docs/sources/reference/api/docker_remote_api_v1.10.rst @@ -1276,8 +1276,8 @@ In this version of the API, /attach, uses hijacking to transport stdin, stdout a 3.3 CORS Requests ----------------- -To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode. +To enable cross origin requests to the remote api add the flag "--api-enable-cors" when running docker in daemon mode. .. code-block:: bash - docker -d -H="192.168.1.9:4243" -api-enable-cors + docker -d -H="192.168.1.9:4243" --api-enable-cors diff --git a/docs/sources/reference/api/docker_remote_api_v1.2.rst b/docs/sources/reference/api/docker_remote_api_v1.2.rst index 1ae2db696f..80f76a3de9 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.2.rst +++ b/docs/sources/reference/api/docker_remote_api_v1.2.rst @@ -1045,7 +1045,7 @@ In this version of the API, /attach, uses hijacking to transport stdin, stdout a 3.3 CORS Requests ----------------- -To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode. +To enable cross origin requests to the remote api add the flag "--api-enable-cors" when running docker in daemon mode. - docker -d -H="tcp://192.168.1.9:4243" -api-enable-cors + docker -d -H="tcp://192.168.1.9:4243" --api-enable-cors diff --git a/docs/sources/reference/api/docker_remote_api_v1.3.rst b/docs/sources/reference/api/docker_remote_api_v1.3.rst index cb4c54642d..2b17a37a4d 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.3.rst +++ b/docs/sources/reference/api/docker_remote_api_v1.3.rst @@ -1124,7 +1124,7 @@ In this version of the API, /attach, uses hijacking to transport stdin, stdout a 3.3 CORS Requests ----------------- -To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode. +To enable cross origin requests to the remote api add the flag "--api-enable-cors" when running docker in daemon mode. - docker -d -H="192.168.1.9:4243" -api-enable-cors + docker -d -H="192.168.1.9:4243" --api-enable-cors diff --git a/docs/sources/reference/api/docker_remote_api_v1.4.rst b/docs/sources/reference/api/docker_remote_api_v1.4.rst index 39c8839653..ff5aaa7a74 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.4.rst +++ b/docs/sources/reference/api/docker_remote_api_v1.4.rst @@ -1168,9 +1168,9 @@ In this version of the API, /attach, uses hijacking to transport stdin, stdout a 3.3 CORS Requests ----------------- -To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode. +To enable cross origin requests to the remote api add the flag "--api-enable-cors" when running docker in daemon mode. .. code-block:: bash - docker -d -H="192.168.1.9:4243" -api-enable-cors + docker -d -H="192.168.1.9:4243" --api-enable-cors diff --git a/docs/sources/reference/api/docker_remote_api_v1.5.rst b/docs/sources/reference/api/docker_remote_api_v1.5.rst index 0cdbaf747a..d4440e4423 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.5.rst +++ b/docs/sources/reference/api/docker_remote_api_v1.5.rst @@ -1137,8 +1137,8 @@ In this version of the API, /attach, uses hijacking to transport stdin, stdout a 3.3 CORS Requests ----------------- -To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode. +To enable cross origin requests to the remote api add the flag "--api-enable-cors" when running docker in daemon mode. .. code-block:: bash - docker -d -H="192.168.1.9:4243" -api-enable-cors + docker -d -H="192.168.1.9:4243" --api-enable-cors diff --git a/docs/sources/reference/api/docker_remote_api_v1.6.rst b/docs/sources/reference/api/docker_remote_api_v1.6.rst index a9ddfb2c13..cfc37084b8 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.6.rst +++ b/docs/sources/reference/api/docker_remote_api_v1.6.rst @@ -1274,9 +1274,9 @@ In this version of the API, /attach, uses hijacking to transport stdin, stdout a 3.3 CORS Requests ----------------- -To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode. +To enable cross origin requests to the remote api add the flag "--api-enable-cors" when running docker in daemon mode. .. code-block:: bash - docker -d -H="192.168.1.9:4243" -api-enable-cors + docker -d -H="192.168.1.9:4243" --api-enable-cors diff --git a/docs/sources/reference/api/docker_remote_api_v1.7.rst b/docs/sources/reference/api/docker_remote_api_v1.7.rst index cacd7ab6f7..1bafaddfc5 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.7.rst +++ b/docs/sources/reference/api/docker_remote_api_v1.7.rst @@ -1254,9 +1254,9 @@ In this version of the API, /attach, uses hijacking to transport stdin, stdout a 3.3 CORS Requests ----------------- -To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode. +To enable cross origin requests to the remote api add the flag "--api-enable-cors" when running docker in daemon mode. .. code-block:: bash - docker -d -H="192.168.1.9:4243" -api-enable-cors + docker -d -H="192.168.1.9:4243" --api-enable-cors diff --git a/docs/sources/reference/api/docker_remote_api_v1.8.rst b/docs/sources/reference/api/docker_remote_api_v1.8.rst index b752f2f8a4..16492dde76 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.8.rst +++ b/docs/sources/reference/api/docker_remote_api_v1.8.rst @@ -1287,8 +1287,8 @@ In this version of the API, /attach, uses hijacking to transport stdin, stdout a 3.3 CORS Requests ----------------- -To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode. +To enable cross origin requests to the remote api add the flag "--api-enable-cors" when running docker in daemon mode. .. code-block:: bash - docker -d -H="192.168.1.9:4243" -api-enable-cors + docker -d -H="192.168.1.9:4243" --api-enable-cors diff --git a/docs/sources/reference/api/docker_remote_api_v1.9.rst b/docs/sources/reference/api/docker_remote_api_v1.9.rst index 9430ff370d..27812457bb 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.9.rst +++ b/docs/sources/reference/api/docker_remote_api_v1.9.rst @@ -1288,8 +1288,8 @@ In this version of the API, /attach, uses hijacking to transport stdin, stdout a 3.3 CORS Requests ----------------- -To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode. +To enable cross origin requests to the remote api add the flag "--api-enable-cors" when running docker in daemon mode. .. code-block:: bash - docker -d -H="192.168.1.9:4243" -api-enable-cors + docker -d -H="192.168.1.9:4243" --api-enable-cors diff --git a/docs/sources/reference/builder.rst b/docs/sources/reference/builder.rst index 3c48939c82..0d8d750a04 100644 --- a/docs/sources/reference/builder.rst +++ b/docs/sources/reference/builder.rst @@ -193,7 +193,7 @@ well. When used in the shell or exec formats, the ``CMD`` instruction sets the command to be executed when running the image. This is -functionally equivalent to running ``docker commit -run '{"Cmd": +functionally equivalent to running ``docker commit --run '{"Cmd": }'`` outside the builder. If you use the *shell* form of the CMD, then the ```` will @@ -235,7 +235,7 @@ override the default specified in CMD. ``EXPOSE [...]`` The ``EXPOSE`` instruction exposes ports for use within links. This is -functionally equivalent to running ``docker commit -run '{"PortSpecs": +functionally equivalent to running ``docker commit --run '{"PortSpecs": ["", ""]}'`` outside the builder. Refer to :ref:`port_redirection` for detailed information. diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index e65bd930ae..83f05947c2 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -52,7 +52,7 @@ Sometimes this can use a more complex value string, as for ``-v``:: Strings and Integers ~~~~~~~~~~~~~~~~~~~~ -Options like ``-name=""`` expect a string, and they can only be +Options like ``--name=""`` expect a string, and they can only be specified once. Options like ``-c=0`` expect an integer, and they can only be specified once. @@ -94,7 +94,7 @@ daemon and client. To run the daemon you provide the ``-d`` flag. To force Docker to use devicemapper as the storage driver, use ``docker -d -s devicemapper``. -To set the DNS server for all Docker containers, use ``docker -d -dns 8.8.8.8``. +To set the DNS server for all Docker containers, use ``docker -d --dns 8.8.8.8``. To run the daemon with debug output, use ``docker -d -D``. @@ -305,7 +305,7 @@ by using the ``git://`` schema. -m, --message="": Commit message -a, --author="": Author (eg. "John Hannibal Smith " --run="": Configuration changes to be applied when the image is launched with `docker run`. - (ex: -run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}') + (ex: --run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}') .. _cli_commit_examples: @@ -335,9 +335,9 @@ run ``ls /etc``. .. code-block:: bash - $ docker run -t -name test ubuntu ls + $ docker run -t --name test ubuntu ls bin boot dev etc home lib lib64 media mnt opt proc root run sbin selinux srv sys tmp usr var - $ docker commit -run='{"Cmd": ["ls","/etc"]}' test test2 + $ docker commit --run='{"Cmd": ["ls","/etc"]}' test test2 933d16de9e70005304c1717b5c6f2f39d6fd50752834c6f34a155c70790011eb $ docker run -t test2 adduser.conf gshadow login.defs rc0.d @@ -358,7 +358,7 @@ Say you have a Dockerfile like so: CMD ["/usr/sbin/sshd -D"] ... -If you run that, make some changes, and then commit, Docker will merge the environment variable and exposed port configuration settings with any that you specify in the -run= option. This is a change from Docker 0.8.0 and prior where no attempt was made to preserve any existing configuration on commit. +If you run that, make some changes, and then commit, Docker will merge the environment variable and exposed port configuration settings with any that you specify in the --run= option. This is a change from Docker 0.8.0 and prior where no attempt was made to preserve any existing configuration on commit. .. code-block:: bash @@ -366,14 +366,14 @@ If you run that, make some changes, and then commit, Docker will merge the envir $ docker run -t -i me/foo /bin/bash foo-container$ [make changes in the container] foo-container$ exit - $ docker commit -run='{"Cmd": ["ls"]}' [container-id] me/bar + $ docker commit --run='{"Cmd": ["ls"]}' [container-id] me/bar ... The me/bar image will now have port 22 exposed, MYVAR env var set to 'foobar', and its default command will be ["ls"]. -Note that this is currently a shallow merge. So, for example, if you had specified a new port spec in the -run= config above, that would have clobbered the 'EXPOSE 22' setting from the parent container. +Note that this is currently a shallow merge. So, for example, if you had specified a new port spec in the --run= config above, that would have clobbered the 'EXPOSE 22' setting from the parent container. -Full -run example +Full --run example ................. The ``--run`` JSON hash changes the ``Config`` section when running ``docker inspect CONTAINERID`` @@ -384,7 +384,7 @@ not overridden in the JSON hash will be merged in. .. code-block:: bash - $ sudo docker commit -run=' + $ sudo docker commit --run=' { "Entrypoint" : null, "Privileged" : false, @@ -516,16 +516,16 @@ Show events in the past from a specified time .. code-block:: bash - $ sudo docker events -since 1378216169 + $ sudo docker events --since 1378216169 [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop - $ sudo docker events -since '2013-09-03' + $ sudo docker events --since '2013-09-03' [2013-09-03 15:49:26 +0200 CEST] 4386fb97867d: (from 12de384bfb10) start [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop - $ sudo docker events -since '2013-09-03 15:49:29 +0200 CEST' + $ sudo docker events --since '2013-09-03 15:49:29 +0200 CEST' [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop @@ -829,7 +829,7 @@ text output: .. code-block:: bash - $ sudo docker inspect -format='{{range $p, $conf := .NetworkSettings.Ports}} {{$p}} -> {{(index $conf 0).HostPort}} {{end}}' $INSTANCE_ID + $ sudo docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}} {{$p}} -> {{(index $conf 0).HostPort}} {{end}}' $INSTANCE_ID Find a Specific Port Mapping ............................ @@ -844,7 +844,7 @@ we ask for the ``HostPort`` field to get the public address. .. code-block:: bash - $ sudo docker inspect -format='{{(index (index .NetworkSettings.Ports "8787/tcp") 0).HostPort}}' $INSTANCE_ID + $ sudo docker inspect --format='{{(index (index .NetworkSettings.Ports "8787/tcp") 0).HostPort}}' $INSTANCE_ID Get config .......... @@ -856,7 +856,7 @@ to convert config object into JSON .. code-block:: bash - $ sudo docker inspect -format='{{json .config}}' $INSTANCE_ID + $ sudo docker inspect --format='{{json .config}}' $INSTANCE_ID .. _cli_kill: @@ -1151,7 +1151,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=[]: 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) @@ -1171,7 +1171,7 @@ See :ref:`port_redirection` for more detailed information about the ``--expose`` ``-p``, ``-P`` and ``--link`` parameters, and :ref:`working_with_links_names` for specific examples using ``--link``. -Known Issues (run -volumes-from) +Known Issues (run --volumes-from) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * :issue:`2702`: "lxc-start: Permission denied - failed to mount" @@ -1199,7 +1199,7 @@ error. Docker will close this file when ``docker run`` exits. This will *not* work, because by default, most potentially dangerous kernel capabilities are dropped; including ``cap_sys_admin`` (which is -required to mount filesystems). However, the ``-privileged`` flag will +required to mount filesystems). However, the ``--privileged`` flag will allow it to run: .. code-block:: bash @@ -1211,7 +1211,7 @@ allow it to run: none 1.9G 0 1.9G 0% /mnt -The ``-privileged`` flag gives *all* capabilities to the container, +The ``--privileged`` flag gives *all* capabilities to the container, and it also lifts all the limitations enforced by the ``device`` cgroup controller. In other words, the container can then do almost everything that the host can do. This flag exists to allow special @@ -1313,7 +1313,7 @@ This example shows 5 containers that might be set up to test a web application c 2. Start a pre-prepared ``riakserver`` image, give the container name ``riak`` and expose port ``8098`` to any containers that link to it; 3. Start the ``appserver`` image, restricting its memory usage to 100MB, setting two environment variables ``DEVELOPMENT`` and ``BRANCH`` and bind-mounting the current directory (``$(pwd)``) in the container in read-only mode as ``/app/bin``; 4. Start the ``webserver``, mapping port ``443`` in the container to port ``1443`` on the Docker server, setting the DNS server to ``dns.dev.org``, creating a volume to put the log files into (so we can access it from another container), then importing the files from the volume exposed by the ``static`` container, and linking to all exposed ports from ``riak`` and ``app``. Lastly, we set the hostname to ``web.sven.dev.org`` so its consistent with the pre-generated SSL certificate; -5. Finally, we create a container that runs ``tail -f access.log`` using the logs volume from the ``web`` container, setting the workdir to ``/var/log/httpd``. The ``-rm`` option means that when the container exits, the container's layer is removed. +5. Finally, we create a container that runs ``tail -f access.log`` using the logs volume from the ``web`` container, setting the workdir to ``/var/log/httpd``. The ``--rm`` option means that when the container exits, the container's layer is removed. .. _cli_save: diff --git a/docs/sources/reference/run.rst b/docs/sources/reference/run.rst index d8de280671..8637ac3071 100644 --- a/docs/sources/reference/run.rst +++ b/docs/sources/reference/run.rst @@ -80,7 +80,7 @@ through network connections or shared volumes because the container is no longer listening to the commandline where you executed ``docker run``. You can reattach to a detached container with ``docker`` :ref:`cli_attach`. If you choose to run a container in the detached -mode, then you cannot use the ``-rm`` option. +mode, then you cannot use the ``--rm`` option. Foreground .......... @@ -92,10 +92,10 @@ error. It can even pretend to be a TTY (this is what most commandline executables expect) and pass along signals. All of that is configurable:: - -a=[] : Attach to ``stdin``, ``stdout`` and/or ``stderr`` - -t=false : Allocate a pseudo-tty - -sig-proxy=true: Proxify all received signal to the process (even in non-tty mode) - -i=false : Keep STDIN open even if not attached + -a=[] : Attach to ``stdin``, ``stdout`` and/or ``stderr`` + -t=false : Allocate a pseudo-tty + --sig-proxy=true: Proxify all received signal to the process (even in non-tty mode) + -i=false : Keep STDIN open even if not attached If you do not specify ``-a`` then Docker will `attach everything (stdin,stdout,stderr) @@ -112,7 +112,7 @@ as well as persistent standard input (``stdin``), so you'll use ``-i Container Identification ------------------------ -Name (-name) +Name (--name) ............ The operator can identify a container in three ways: @@ -122,7 +122,7 @@ The operator can identify a container in three ways: * Name ("evil_ptolemy") The UUID identifiers come from the Docker daemon, and if you do not -assign a name to the container with ``-name`` then the daemon will +assign a name to the container with ``--name`` then the daemon will also generate a random string name too. The name can become a handy way to add meaning to a container since you can use this name when defining :ref:`links ` (or any other place @@ -137,7 +137,7 @@ container ID out to a file of your choosing. This is similar to how some programs might write out their process ID to a file (you've seen them as PID files):: - -cidfile="": Write the container ID to the file + --cidfile="": Write the container ID to the file Network Settings ---------------- @@ -145,7 +145,7 @@ Network Settings :: -n=true : Enable networking for this container - -dns=[] : Set custom dns servers for the container + --dns=[] : Set custom dns servers for the container By default, all containers have networking enabled and they can make any outgoing connections. The operator can completely disable @@ -154,9 +154,9 @@ networking. In cases like this, you would perform I/O through files or STDIN/STDOUT only. Your container will use the same DNS servers as the host by default, -but you can override this with ``-dns``. +but you can override this with ``--dns``. -Clean Up (-rm) +Clean Up (--rm) -------------- By default a container's file system persists even after the container @@ -165,9 +165,9 @@ final state) and you retain all your data by default. But if you are running short-term **foreground** processes, these container file systems can really pile up. If instead you'd like Docker to **automatically clean up the container and remove the file system when -the container exits**, you can add the ``-rm`` flag:: +the container exits**, you can add the ``--rm`` flag:: - -rm=false: Automatically remove the container when it exits (incompatible with -d) + --rm=false: Automatically remove the container when it exits (incompatible with -d) Runtime Constraints on CPU and Memory @@ -193,8 +193,8 @@ 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" + --privileged=false: Give extended privileges to this container + --lxc-conf=[]: 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 @@ -203,16 +203,16 @@ but a "privileged" container is given access to all devices (see lxc-template.go_ and documentation on `cgroups devices `_). -When the operator executes ``docker run -privileged``, Docker will +When the operator executes ``docker run --privileged``, Docker will enable to access to all devices on the host as well as set some configuration in AppArmor to allow the container nearly all the same access to the host as processes running outside containers on the -host. Additional information about running with ``-privileged`` is +host. Additional information about running with ``--privileged`` is available on the `Docker Blog `_. An operator can also specify LXC options using one or more -``-lxc-conf`` parameters. These can be new parameters or override +``--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 @@ -260,7 +260,7 @@ ENTRYPOINT (Default Command to Execute at Runtime :: - -entrypoint="": Overwrite the default entrypoint set by the image + --entrypoint="": Overwrite the default entrypoint set by the image The ENTRYPOINT of an image is similar to a ``COMMAND`` because it specifies what executable to run when the container starts, but it is @@ -274,12 +274,12 @@ runtime by using a string to specify the new ``ENTRYPOINT``. Here is an example of how to run a shell in a container that has been set up to automatically run something else (like ``/usr/bin/redis-server``):: - docker run -i -t -entrypoint /bin/bash example/redis + docker run -i -t --entrypoint /bin/bash example/redis or two examples of how to pass more parameters to that ENTRYPOINT:: - docker run -i -t -entrypoint /bin/bash example/redis -c ls -l - docker run -i -t -entrypoint /usr/bin/redis-cli example/redis --help + docker run -i -t --entrypoint /bin/bash example/redis -c ls -l + docker run -i -t --entrypoint /usr/bin/redis-cli example/redis --help EXPOSE (Incoming Ports) @@ -290,16 +290,16 @@ providing the ``EXPOSE`` instruction to give a hint to the operator about what incoming ports might provide services. The following options work with or override the ``Dockerfile``'s exposed defaults:: - -expose=[]: Expose a port from the container + --expose=[]: Expose a port from the container without publishing it to your host - -P=false : Publish all exposed ports to the host interfaces - -p=[] : Publish a container's port to the host (format: - ip:hostPort:containerPort | ip::containerPort | - hostPort:containerPort) - (use 'docker port' to see the actual mapping) - -link="" : Add link to another container (name:alias) + -P=false : Publish all exposed ports to the host interfaces + -p=[] : Publish a container's port to the host (format: + ip:hostPort:containerPort | ip::containerPort | + hostPort:containerPort) + (use 'docker port' to see the actual mapping) + --link="" : Add link to another container (name:alias) -As mentioned previously, ``EXPOSE`` (and ``-expose``) make a port +As mentioned previously, ``EXPOSE`` (and ``--expose``) make a port available **in** a container for incoming connections. The port number on the inside of the container (where the service listens) does not need to be the same number as the port exposed on the outside of the @@ -308,16 +308,16 @@ have an HTTP service listening on port 80 (and so you ``EXPOSE 80`` in the ``Dockerfile``), but outside the container the port might be 42800. To help a new client container reach the server container's internal -port operator ``-expose``'d by the operator or ``EXPOSE``'d by the +port operator ``--expose``'d by the operator or ``EXPOSE``'d by the developer, the operator has three choices: start the server container -with ``-P`` or ``-p,`` or start the client container with ``-link``. +with ``-P`` or ``-p,`` or start the client container with ``--link``. If the operator uses ``-P`` or ``-p`` then Docker will make the exposed port accessible on the host and the ports will be available to any client that can reach the host. To find the map between the host ports and the exposed ports, use ``docker port``) -If the operator uses ``-link`` when starting the new client container, +If the operator uses ``--link`` when starting the new client container, then the client container can access the exposed port via a private networking interface. Docker will set some environment variables in the client container to help indicate which interface and port to use. @@ -329,7 +329,7 @@ The operator can **set any environment variable** in the container by using one or more ``-e`` flags, even overriding those already defined by the developer with a Dockefile ``ENV``:: - $ docker run -e "deep=purple" -rm ubuntu /bin/bash -c export + $ docker run -e "deep=purple" --rm ubuntu /bin/bash -c export declare -x HOME="/" declare -x HOSTNAME="85bc26a0e200" declare -x OLDPWD @@ -341,13 +341,13 @@ developer with a Dockefile ``ENV``:: Similarly the operator can set the **hostname** with ``-h``. -``-link name:alias`` also sets environment variables, using the +``--link name:alias`` also sets environment variables, using the *alias* string to define environment variables within the container that give the IP and PORT information for connecting to the service container. Let's imagine we have a container running Redis:: # Start the service container, named redis-name - $ docker run -d -name redis-name dockerfiles/redis + $ docker run -d --name redis-name dockerfiles/redis 4241164edf6f5aca5b0e9e4c9eccd899b0b8080c64c0cd26efe02166c73208f3 # The redis-name container exposed port 6379 @@ -361,12 +361,12 @@ container. Let's imagine we have a container running Redis:: Yet we can get information about the Redis container's exposed ports -with ``-link``. Choose an alias that will form a valid environment +with ``--link``. Choose an alias that will form a valid environment variable! :: - $ docker run -rm -link redis-name:redis_alias -entrypoint /bin/bash dockerfiles/redis -c export + $ docker run --rm --link redis-name:redis_alias --entrypoint /bin/bash dockerfiles/redis -c export declare -x HOME="/" declare -x HOSTNAME="acda7f7b1cdc" declare -x OLDPWD @@ -383,7 +383,7 @@ variable! And we can use that information to connect from another container as a client:: - $ docker run -i -t -rm -link redis-name:redis_alias -entrypoint /bin/bash dockerfiles/redis -c '/redis-stable/src/redis-cli -h $REDIS_ALIAS_PORT_6379_TCP_ADDR -p $REDIS_ALIAS_PORT_6379_TCP_PORT' + $ docker run -i -t --rm --link redis-name:redis_alias --entrypoint /bin/bash dockerfiles/redis -c '/redis-stable/src/redis-cli -h $REDIS_ALIAS_PORT_6379_TCP_ADDR -p $REDIS_ALIAS_PORT_6379_TCP_PORT' 172.17.0.32:6379> VOLUME (Shared Filesystems) @@ -393,7 +393,7 @@ VOLUME (Shared Filesystems) -v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "container-dir" is missing, then docker creates a new volume. - -volumes-from="": Mount all volumes from the given container(s) + --volumes-from="": Mount all volumes from the given container(s) The volumes commands are complex enough to have their own documentation in section :ref:`volume_def`. A developer can define one diff --git a/docs/sources/use/ambassador_pattern_linking.rst b/docs/sources/use/ambassador_pattern_linking.rst index e7cdbd7c96..bbd5816768 100644 --- a/docs/sources/use/ambassador_pattern_linking.rst +++ b/docs/sources/use/ambassador_pattern_linking.rst @@ -43,26 +43,26 @@ Start actual redis server on one Docker host .. code-block:: bash - big-server $ docker run -d -name redis crosbymichael/redis + big-server $ docker run -d --name redis crosbymichael/redis Then add an ambassador linked to the redis server, mapping a port to the outside world .. code-block:: bash - big-server $ docker run -d -link redis:redis -name redis_ambassador -p 6379:6379 svendowideit/ambassador + big-server $ docker run -d --link redis:redis --name redis_ambassador -p 6379:6379 svendowideit/ambassador On the other host, you can set up another ambassador setting environment variables for each remote port we want to proxy to the ``big-server`` .. code-block:: bash - client-server $ docker run -d -name redis_ambassador -expose 6379 -e REDIS_PORT_6379_TCP=tcp://192.168.1.52:6379 svendowideit/ambassador + client-server $ docker run -d --name redis_ambassador --expose 6379 -e REDIS_PORT_6379_TCP=tcp://192.168.1.52:6379 svendowideit/ambassador Then on the ``client-server`` host, you can use a redis client container to talk to the remote redis server, just by linking to the local redis ambassador. .. code-block:: bash - client-server $ docker run -i -t -rm -link redis_ambassador:redis relateiq/redis-cli + client-server $ docker run -i -t --rm --link redis_ambassador:redis relateiq/redis-cli redis 172.17.0.160:6379> ping PONG @@ -79,19 +79,19 @@ On the docker host (192.168.1.52) that redis will run on: .. code-block:: bash # start actual redis server - $ docker run -d -name redis crosbymichael/redis + $ docker run -d --name redis crosbymichael/redis # get a redis-cli container for connection testing $ docker pull relateiq/redis-cli # test the redis server by talking to it directly - $ docker run -t -i -rm -link redis:redis relateiq/redis-cli + $ docker run -t -i --rm --link redis:redis relateiq/redis-cli redis 172.17.0.136:6379> ping PONG ^D # add redis ambassador - $ docker run -t -i -link redis:redis -name redis_ambassador -p 6379:6379 busybox sh + $ docker run -t -i --link redis:redis --name redis_ambassador -p 6379:6379 busybox sh in the redis_ambassador container, you can see the linked redis containers's env @@ -119,7 +119,7 @@ This environment is used by the ambassador socat script to expose redis to the w $ docker rm redis_ambassador $ sudo ./contrib/mkimage-unittest.sh - $ docker run -t -i -link redis:redis -name redis_ambassador -p 6379:6379 docker-ut sh + $ docker run -t -i --link redis:redis --name redis_ambassador -p 6379:6379 docker-ut sh $ socat TCP4-LISTEN:6379,fork,reuseaddr TCP4:172.17.0.136:6379 @@ -127,7 +127,7 @@ then ping the redis server via the ambassador .. code-block::bash - $ docker run -i -t -rm -link redis_ambassador:redis relateiq/redis-cli + $ docker run -i -t --rm --link redis_ambassador:redis relateiq/redis-cli redis 172.17.0.160:6379> ping PONG @@ -136,7 +136,7 @@ Now goto a different server .. code-block:: bash $ sudo ./contrib/mkimage-unittest.sh - $ docker run -t -i -expose 6379 -name redis_ambassador docker-ut sh + $ docker run -t -i --expose 6379 --name redis_ambassador docker-ut sh $ socat TCP4-LISTEN:6379,fork,reuseaddr TCP4:192.168.1.52:6379 @@ -145,7 +145,7 @@ and get the redis-cli image so we can talk over the ambassador bridge .. code-block:: bash $ docker pull relateiq/redis-cli - $ docker run -i -t -rm -link redis_ambassador:redis relateiq/redis-cli + $ docker run -i -t --rm --link redis_ambassador:redis relateiq/redis-cli redis 172.17.0.160:6379> ping PONG @@ -157,7 +157,7 @@ When you start the container, it uses a small ``sed`` script to parse out the (p link environment variables to set up the port forwarding. On the remote host, you need to set the variable using the ``-e`` command line option. -``-expose 1234 -e REDIS_PORT_1234_TCP=tcp://192.168.1.52:6379`` will forward the +``--expose 1234 -e REDIS_PORT_1234_TCP=tcp://192.168.1.52:6379`` will forward the local ``1234`` port to the remote IP and port - in this case ``192.168.1.52:6379``. @@ -171,9 +171,9 @@ local ``1234`` port to the remote IP and port - in this case ``192.168.1.52:6379 # docker build -t SvenDowideit/ambassador . # docker tag SvenDowideit/ambassador ambassador # then to run it (on the host that has the real backend on it) - # docker run -t -i -link redis:redis -name redis_ambassador -p 6379:6379 ambassador + # docker run -t -i --link redis:redis --name redis_ambassador -p 6379:6379 ambassador # on the remote host, you can set up another ambassador - # docker run -t -i -name redis_ambassador -expose 6379 sh + # docker run -t -i --name redis_ambassador --expose 6379 sh FROM docker-ut MAINTAINER SvenDowideit@home.org.au diff --git a/docs/sources/use/basics.rst b/docs/sources/use/basics.rst index 24c22bba39..447366f55a 100644 --- a/docs/sources/use/basics.rst +++ b/docs/sources/use/basics.rst @@ -39,7 +39,7 @@ Repository to a local image cache. 12 character hash ``539c0211cd76: Download complete`` which is the short form of the image ID. These short image IDs are the first 12 characters of the full image ID - which can be found using ``docker - inspect`` or ``docker images -notrunc=true`` + inspect`` or ``docker images --no-trunc=true`` Running an interactive shell ---------------------------- diff --git a/docs/sources/use/networking.rst b/docs/sources/use/networking.rst index c00c608550..59c63ed674 100644 --- a/docs/sources/use/networking.rst +++ b/docs/sources/use/networking.rst @@ -121,8 +121,8 @@ Container intercommunication The value of the Docker daemon's ``icc`` parameter determines whether containers can communicate with each other over the bridge network. -- The default, ``-icc=true`` allows containers to communicate with each other. -- ``-icc=false`` means containers are isolated from each other. +- The default, ``--icc=true`` allows containers to communicate with each other. +- ``--icc=false`` means containers are isolated from each other. Docker uses ``iptables`` under the hood to either accept or drop communication between containers. diff --git a/docs/sources/use/port_redirection.rst b/docs/sources/use/port_redirection.rst index ba244adadb..cf5c2100a9 100644 --- a/docs/sources/use/port_redirection.rst +++ b/docs/sources/use/port_redirection.rst @@ -114,14 +114,14 @@ exposure, is possible because ``client`` is started after ``server`` has been started. Here is a full example. On ``server``, the port of interest is -exposed. The exposure is done either through the ``-expose`` parameter +exposed. The exposure is done either through the ``--expose`` parameter to the ``docker run`` command, or the ``EXPOSE`` build command in a Dockerfile: .. code-block:: bash # Expose port 80 - docker run -expose 80 --name server + docker run --expose 80 --name server The ``client`` then links to the ``server``: diff --git a/docs/sources/use/working_with_links_names.rst b/docs/sources/use/working_with_links_names.rst index 1b0e9f6914..dc370c01c9 100644 --- a/docs/sources/use/working_with_links_names.rst +++ b/docs/sources/use/working_with_links_names.rst @@ -19,14 +19,14 @@ Container Naming .. versionadded:: v0.6.5 -You can now name your container by using the ``-name`` flag. If no +You can now name your container by using the ``--name`` flag. If no name is provided, Docker will automatically generate a name. You can see this name using the ``docker ps`` command. .. code-block:: bash - # format is "sudo docker run -name " - $ sudo docker run -name test ubuntu /bin/bash + # format is "sudo docker run --name " + $ sudo docker run --name test ubuntu /bin/bash # the flag "-a" Show all containers. Only running containers are shown by default. $ sudo docker ps -a @@ -41,9 +41,9 @@ Links: service discovery for docker .. versionadded:: v0.6.5 Links allow containers to discover and securely communicate with each -other by using the flag ``-link name:alias``. Inter-container +other by using the flag ``--link name:alias``. Inter-container communication can be disabled with the daemon flag -``-icc=false``. With this flag set to ``false``, Container A cannot +``--icc=false``. With this flag set to ``false``, Container A cannot access Container B unless explicitly allowed via a link. This is a huge win for securing your containers. When two containers are linked together Docker creates a parent child relationship between the @@ -63,7 +63,7 @@ based on that image and run it as a daemon. .. code-block:: bash - $ sudo docker run -d -name redis crosbymichael/redis + $ sudo docker run -d --name redis crosbymichael/redis We can issue all the commands that you would expect using the name ``redis``; start, stop, attach, using the name for our container. The @@ -77,9 +77,9 @@ we need to establish a link. .. code-block:: bash - $ sudo docker run -t -i -link redis:db -name webapp ubuntu bash + $ sudo docker run -t -i --link redis:db --name webapp ubuntu bash -When you specified ``-link redis:db`` you are telling Docker to link +When you specified ``--link redis:db`` you are telling Docker to link the container named ``redis`` into this new container with the alias ``db``. Environment variables are prefixed with the alias so that the parent container can access network and environment information from diff --git a/docs/sources/use/working_with_volumes.rst b/docs/sources/use/working_with_volumes.rst index 755be009e3..02f4e71b13 100644 --- a/docs/sources/use/working_with_volumes.rst +++ b/docs/sources/use/working_with_volumes.rst @@ -42,14 +42,14 @@ two new volumes:: This command will create the new container with two new volumes that exits instantly (``true`` is pretty much the smallest, simplest program that you can run). Once created you can mount its volumes in any other -container using the ``-volumes-from`` option; irrespective of whether the +container using the ``--volumes-from`` option; irrespective of whether the container is running or not. Or, you can use the VOLUME instruction in a Dockerfile to add one or more new volumes to any container created from that image:: # BUILD-USING: docker build -t data . - # RUN-USING: docker run -name DATA data + # RUN-USING: docker run --name DATA data FROM busybox VOLUME ["/var/volume1", "/var/volume2"] CMD ["/bin/true"] @@ -63,19 +63,19 @@ Data Volume Container, and then to mount the data from it. Create a named container with volumes to share (``/var/volume1`` and ``/var/volume2``):: - $ docker run -v /var/volume1 -v /var/volume2 -name DATA busybox true + $ docker run -v /var/volume1 -v /var/volume2 --name DATA busybox true Then mount those data volumes into your application containers:: - $ docker run -t -i -rm -volumes-from DATA -name client1 ubuntu bash + $ docker run -t -i --rm --volumes-from DATA --name client1 ubuntu bash -You can use multiple ``-volumes-from`` parameters to bring together multiple +You can use multiple ``--volumes-from`` parameters to bring together multiple data volumes from multiple containers. Interestingly, you can mount the volumes that came from the ``DATA`` container in yet another container via the ``client1`` middleman container:: - $ docker run -t -i -rm -volumes-from client1 -name client2 ubuntu bash + $ docker run -t -i --rm --volumes-from client1 --name client2 ubuntu bash This allows you to abstract the actual data source from users of that data, similar to :ref:`ambassador_pattern_linking `. @@ -131,7 +131,7 @@ data-container's volume. For example:: $ sudo docker run -rm --volumes-from DATA -v $(pwd):/backup busybox tar cvf /backup/backup.tar /data -* ``-rm`` - remove the container when it exits +* ``--rm`` - remove the container when it exits * ``--volumes-from DATA`` - attach to the volumes shared by the ``DATA`` container * ``-v $(pwd):/backup`` - bind mount the current directory into the container; to write the tar file to * ``busybox`` - a small simpler image - good for quick maintenance @@ -142,11 +142,11 @@ 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 # 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 + $ sudo docker run --rm --volumes-from DATA2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar data/ data/sven.txt # compare to the original container - $ sudo docker run -rm --volumes-from DATA -v `pwd`:/backup busybox ls /data + $ sudo docker run --rm --volumes-from DATA -v `pwd`:/backup busybox ls /data sven.txt diff --git a/docs/sources/use/workingwithrepository.rst b/docs/sources/use/workingwithrepository.rst index cbde932cde..c126361f8c 100644 --- a/docs/sources/use/workingwithrepository.rst +++ b/docs/sources/use/workingwithrepository.rst @@ -74,7 +74,7 @@ name or description: Search the docker index for images - -notrunc=false: Don't truncate output + --no-trunc=false: Don't truncate output $ sudo docker search centos Found 25 results matching your query ("centos") NAME DESCRIPTION diff --git a/hack/RELEASE-CHECKLIST.md b/hack/RELEASE-CHECKLIST.md index 6ef5d9cf58..2920e52917 100644 --- a/hack/RELEASE-CHECKLIST.md +++ b/hack/RELEASE-CHECKLIST.md @@ -139,7 +139,7 @@ docker run \ -e AWS_ACCESS_KEY \ -e AWS_SECRET_KEY \ -e GPG_PASSPHRASE \ - -i -t -privileged \ + -i -t --privileged \ docker \ hack/release.sh ``` @@ -173,7 +173,7 @@ docker run \ -e AWS_ACCESS_KEY \ -e AWS_SECRET_KEY \ -e GPG_PASSPHRASE \ - -i -t -privileged \ + -i -t --privileged \ docker \ hack/release.sh ``` diff --git a/hack/dind b/hack/dind index eff656b0e0..94147f5324 100755 --- a/hack/dind +++ b/hack/dind @@ -5,7 +5,7 @@ # See the blog post: http://blog.docker.io/2013/09/docker-can-now-run-within-docker/ # # This script should be executed inside a docker container in privilieged mode -# ('docker run -privileged', introduced in docker 0.6). +# ('docker run --privileged', introduced in docker 0.6). # Usage: dind CMD [ARG...] @@ -17,7 +17,7 @@ CGROUP=/sys/fs/cgroup mountpoint -q $CGROUP || mount -n -t tmpfs -o uid=0,gid=0,mode=0755 cgroup $CGROUP || { - echo "Could not make a tmpfs mount. Did you use -privileged?" + echo "Could not make a tmpfs mount. Did you use --privileged?" exit 1 } diff --git a/hack/infrastructure/docker-ci/Dockerfile b/hack/infrastructure/docker-ci/Dockerfile index fd795f4d45..789c794f54 100644 --- a/hack/infrastructure/docker-ci/Dockerfile +++ b/hack/infrastructure/docker-ci/Dockerfile @@ -1,8 +1,8 @@ # DOCKER-VERSION: 0.7.6 # AUTHOR: Daniel Mizyrycki # DESCRIPTION: docker-ci continuous integration service -# TO_BUILD: docker build -rm -t docker-ci/docker-ci . -# TO_RUN: docker run -rm -i -t -p 8000:80 -p 2222:22 -v /run:/var/socket \ +# 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 diff --git a/hack/infrastructure/docker-ci/README.rst b/hack/infrastructure/docker-ci/README.rst index 3e429ffdd5..07c1ffcec0 100644 --- a/hack/infrastructure/docker-ci/README.rst +++ b/hack/infrastructure/docker-ci/README.rst @@ -57,8 +57,8 @@ Production deployment export EMAIL_RCP=[EMAIL_FOR_BUILD_ERRORS] # Build docker-ci and testbuilder docker images - docker -H $DOCKER_PROD build -rm -t docker-ci/docker-ci . - (cd testbuilder; docker -H $DOCKER_PROD build -rm -t docker-ci/testbuilder .) + 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) diff --git a/hack/infrastructure/docker-ci/dockertest/nightlyrelease b/hack/infrastructure/docker-ci/dockertest/nightlyrelease index 475b088065..cface6c125 100755 --- a/hack/infrastructure/docker-ci/dockertest/nightlyrelease +++ b/hack/infrastructure/docker-ci/dockertest/nightlyrelease @@ -6,7 +6,7 @@ else AWS_S3_BUCKET='get-staging.docker.io' fi -docker run -rm -privileged -v /run:/var/socket \ +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 diff --git a/hack/infrastructure/docker-ci/dockertest/project b/hack/infrastructure/docker-ci/dockertest/project index 160f2d5d59..8131ab533a 100755 --- a/hack/infrastructure/docker-ci/dockertest/project +++ b/hack/infrastructure/docker-ci/dockertest/project @@ -3,6 +3,6 @@ set -x PROJECT_NAME=$(basename $0) -docker run -rm -u sysadmin -e DEPLOYMENT=$DEPLOYMENT -v /run:/var/socket \ +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 diff --git a/hack/infrastructure/docker-ci/testbuilder/Dockerfile b/hack/infrastructure/docker-ci/testbuilder/Dockerfile index a008da6843..8fa9b4c797 100644 --- a/hack/infrastructure/docker-ci/testbuilder/Dockerfile +++ b/hack/infrastructure/docker-ci/testbuilder/Dockerfile @@ -1,5 +1,5 @@ -# TO_BUILD: docker build -rm -no-cache -t docker-ci/testbuilder . -# TO_RUN: docker run -rm -u sysadmin \ +# 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 # diff --git a/hack/infrastructure/docker-ci/testbuilder/docker-registry.sh b/hack/infrastructure/docker-ci/testbuilder/docker-registry.sh index 72087462ad..a73704c50b 100755 --- a/hack/infrastructure/docker-ci/testbuilder/docker-registry.sh +++ b/hack/infrastructure/docker-ci/testbuilder/docker-registry.sh @@ -5,8 +5,8 @@ PROJECT_PATH=$1 # Build the docker project cd /data/$PROJECT_PATH -sg docker -c "docker build -q -rm -t registry ." -cd test; sg docker -c "docker build -q -rm -t docker-registry-test ." +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" +sg docker -c "docker run --rm -v /home/docker-ci/coverage/docker-registry:/data docker-registry-test" diff --git a/hack/infrastructure/docker-ci/testbuilder/docker.sh b/hack/infrastructure/docker-ci/testbuilder/docker.sh index b365dd7eaf..c8f3c18eb9 100755 --- a/hack/infrastructure/docker-ci/testbuilder/docker.sh +++ b/hack/infrastructure/docker-ci/testbuilder/docker.sh @@ -5,14 +5,14 @@ PROJECT_PATH=$1 # Build the docker project cd /data/$PROJECT_PATH -sg docker -c "docker build -q -rm -t docker ." +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" + 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" + 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" + sg docker -c "docker run --rm --privileged -v /home/docker-ci/coverage/docker:/data docker ./hack/infrastructure/docker-ci/docker-coverage/gocoverage.sh" fi diff --git a/hack/release.sh b/hack/release.sh index 50913dd395..c380d2239a 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -31,7 +31,7 @@ docker run -e AWS_S3_BUCKET=get-staging.docker.io \ -e AWS_ACCESS_KEY=AKI1234... \ -e AWS_SECRET_KEY=sEs4mE... \ -e GPG_PASSPHRASE=m0resEs4mE... \ - -i -t -privileged \ + -i -t --privileged \ docker ./hack/release.sh EOF exit 1 diff --git a/integration/commands_test.go b/integration/commands_test.go index d226cd7133..dba15842c7 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -739,7 +739,7 @@ func TestRunAutoRemove(t *testing.T) { c := make(chan struct{}) go func() { defer close(c) - if err := cli.CmdRun("-rm", unitTestImageID, "hostname"); err != nil { + if err := cli.CmdRun("--rm", unitTestImageID, "hostname"); err != nil { t.Fatal(err) } }() diff --git a/integration/container_test.go b/integration/container_test.go index 4efb95a2a1..c32a8bcff7 100644 --- a/integration/container_test.go +++ b/integration/container_test.go @@ -1580,7 +1580,7 @@ func TestPrivilegedCanMknod(t *testing.T) { eng := NewTestEngine(t) runtime := mkRuntimeFromEngine(eng, t) defer runtime.Nuke() - if output, err := runContainer(eng, runtime, []string{"-privileged", "_", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok"}, t); output != "ok\n" { + if output, err := runContainer(eng, runtime, []string{"--privileged", "_", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok"}, t); output != "ok\n" { t.Fatalf("Could not mknod into privileged container %s %v", output, err) } } @@ -1589,7 +1589,7 @@ func TestPrivilegedCanMount(t *testing.T) { eng := NewTestEngine(t) runtime := mkRuntimeFromEngine(eng, t) defer runtime.Nuke() - if output, _ := runContainer(eng, runtime, []string{"-privileged", "_", "sh", "-c", "mount -t tmpfs none /tmp && echo ok"}, t); output != "ok\n" { + if output, _ := runContainer(eng, runtime, []string{"--privileged", "_", "sh", "-c", "mount -t tmpfs none /tmp && echo ok"}, t); output != "ok\n" { t.Fatal("Could not mount into privileged container") } } diff --git a/integration/server_test.go b/integration/server_test.go index e9781777e1..54ee9a77a9 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -203,7 +203,7 @@ func TestCreateRmRunning(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - config, hostConfig, _, err := runconfig.Parse([]string{"-name", "foo", unitTestImageID, "sleep 300"}, nil) + config, hostConfig, _, err := runconfig.Parse([]string{"--name", "foo", unitTestImageID, "sleep 300"}, nil) if err != nil { t.Fatal(err) } diff --git a/runconfig/config_test.go b/runconfig/config_test.go index 40d53fa2f4..46e4691b93 100644 --- a/runconfig/config_test.go +++ b/runconfig/config_test.go @@ -20,21 +20,21 @@ func mustParse(t *testing.T, args string) (*Config, *HostConfig) { } func TestParseRunLinks(t *testing.T) { - if _, hostConfig := mustParse(t, "-link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" { + if _, hostConfig := mustParse(t, "--link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" { t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links) } - if _, hostConfig := mustParse(t, "-link a:b -link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" { + if _, hostConfig := mustParse(t, "--link a:b --link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" { t.Fatalf("Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v", hostConfig.Links) } if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 { t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links) } - if _, _, err := parse(t, "-link a"); err == nil { - t.Fatalf("Error parsing links. `-link a` should be an error but is not") + if _, _, err := parse(t, "--link a"); err == nil { + t.Fatalf("Error parsing links. `--link a` should be an error but is not") } - if _, _, err := parse(t, "-link"); err == nil { - t.Fatalf("Error parsing links. `-link` should be an error but is not") + if _, _, err := parse(t, "--link"); err == nil { + t.Fatalf("Error parsing links. `--link` should be an error but is not") } } @@ -73,8 +73,8 @@ func TestParseRunAttach(t *testing.T) { if _, _, err := parse(t, "-a stderr -d"); err == nil { t.Fatalf("Error parsing attach flags, `-a stderr -d` should be an error but is not") } - if _, _, err := parse(t, "-d -rm"); err == nil { - t.Fatalf("Error parsing attach flags, `-d -rm` should be an error but is not") + if _, _, err := parse(t, "-d --rm"); err == nil { + t.Fatalf("Error parsing attach flags, `-d --rm` should be an error but is not") } } diff --git a/runconfig/parse.go b/runconfig/parse.go index d481da8d3b..2138f4e68c 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -15,7 +15,7 @@ import ( var ( ErrInvalidWorikingDirectory = fmt.Errorf("The working directory is invalid. It needs to be an absolute path.") ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d") - ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: -rm and -d") + ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d") ) //FIXME Only used in tests @@ -74,7 +74,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf cmd.Var(&flExpose, []string{"#expose", "-expose"}, "Expose a port from the container without publishing it to your host") cmd.Var(&flDns, []string{"#dns", "-dns"}, "Set custom dns servers") 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"}, "Add custom lxc options --lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"") if err := cmd.Parse(args); err != nil { return nil, nil, cmd, err From 029aac96396f5a9d76adf5e4675d27321273dfbd Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 13 Mar 2014 11:11:02 -0700 Subject: [PATCH 075/384] Use BSD raw mode on darwin. Fixes nano, tmux and others Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- pkg/term/termios_darwin.go | 39 ++++++++++++++++++++++++------------- pkg/term/termios_freebsd.go | 2 -- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/pkg/term/termios_darwin.go b/pkg/term/termios_darwin.go index 24e79de4b2..11cd70d10b 100644 --- a/pkg/term/termios_darwin.go +++ b/pkg/term/termios_darwin.go @@ -9,16 +9,24 @@ const ( getTermios = syscall.TIOCGETA setTermios = syscall.TIOCSETA - ECHO = 0x00000008 - ONLCR = 0x2 - ISTRIP = 0x20 - INLCR = 0x40 - ISIG = 0x80 - IGNCR = 0x80 - ICANON = 0x100 - ICRNL = 0x100 - IXOFF = 0x400 - IXON = 0x200 + IGNBRK = syscall.IGNBRK + PARMRK = syscall.PARMRK + INLCR = syscall.INLCR + IGNCR = syscall.IGNCR + ECHONL = syscall.ECHONL + CSIZE = syscall.CSIZE + ICRNL = syscall.ICRNL + ISTRIP = syscall.ISTRIP + PARENB = syscall.PARENB + ECHO = syscall.ECHO + ICANON = syscall.ICANON + ISIG = syscall.ISIG + IXON = syscall.IXON + BRKINT = syscall.BRKINT + INPCK = syscall.INPCK + OPOST = syscall.OPOST + CS8 = syscall.CS8 + IEXTEN = syscall.IEXTEN ) type Termios struct { @@ -41,10 +49,13 @@ func MakeRaw(fd uintptr) (*State, error) { } newState := oldState.termios - newState.Iflag &^= (ISTRIP | INLCR | IGNCR | IXON | IXOFF) - newState.Iflag |= ICRNL - newState.Oflag |= ONLCR - newState.Lflag &^= (ECHO | ICANON | ISIG) + newState.Iflag &^= (IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON) + newState.Oflag &^= OPOST + newState.Lflag &^= (ECHO | ECHONL | ICANON | ISIG | IEXTEN) + newState.Cflag &^= (CSIZE | PARENB) + newState.Cflag |= CS8 + newState.Cc[syscall.VMIN] = 1 + newState.Cc[syscall.VTIME] = 0 if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 { return nil, err diff --git a/pkg/term/termios_freebsd.go b/pkg/term/termios_freebsd.go index 9acf9dfe15..ed3659572c 100644 --- a/pkg/term/termios_freebsd.go +++ b/pkg/term/termios_freebsd.go @@ -47,8 +47,6 @@ func MakeRaw(fd uintptr) (*State, error) { if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios))); err != 0 { return nil, err } - // C.makeraw() - // return &oldState, nil newState := oldState.termios newState.Iflag &^= (IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON) From 636959e20ae368e470a5c2420aae4528245b2cf6 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 3 Mar 2014 19:19:00 +0100 Subject: [PATCH 076/384] Move .dockerenv parsing to lxc driver This is not needed for e.g. the native driver Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- execdriver/lxc/driver.go | 4 ++++ execdriver/lxc/init.go | 30 ++++++++++++++++++++++++++++++ sysinit/sysinit.go | 30 ------------------------------ 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index b398cb1a37..9abec8ac3f 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -21,6 +21,10 @@ const DriverName = "lxc" func init() { execdriver.RegisterInitFunc(DriverName, func(args *execdriver.InitArgs) error { + if err := setupEnv(args); err != nil { + return err + } + if err := setupHostname(args); err != nil { return err } diff --git a/execdriver/lxc/init.go b/execdriver/lxc/init.go index e138915212..0f134088a3 100644 --- a/execdriver/lxc/init.go +++ b/execdriver/lxc/init.go @@ -1,17 +1,47 @@ package lxc import ( + "encoding/json" "fmt" "github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/pkg/netlink" "github.com/dotcloud/docker/pkg/user" "github.com/syndtr/gocapability/capability" + "io/ioutil" "net" "os" "strings" "syscall" ) +// Clear environment pollution introduced by lxc-start +func setupEnv(args *execdriver.InitArgs) error { + // Get env + var env []string + content, err := ioutil.ReadFile(".dockerenv") + if err != nil { + return fmt.Errorf("Unable to load environment variables: %v", err) + } + if err := json.Unmarshal(content, &env); err != nil { + return fmt.Errorf("Unable to unmarshal environment variables: %v", err) + } + // Propagate the plugin-specific container env variable + env = append(env, "container="+os.Getenv("container")) + + args.Env = env + + os.Clearenv() + for _, kv := range args.Env { + parts := strings.SplitN(kv, "=", 2) + if len(parts) == 1 { + parts = append(parts, "") + } + os.Setenv(parts[0], parts[1]) + } + + return nil +} + func setupHostname(args *execdriver.InitArgs) error { hostname := getEnv(args, "HOSTNAME") if hostname == "" { diff --git a/sysinit/sysinit.go b/sysinit/sysinit.go index c84c05982c..56508b105d 100644 --- a/sysinit/sysinit.go +++ b/sysinit/sysinit.go @@ -1,33 +1,16 @@ package sysinit import ( - "encoding/json" "flag" "fmt" "github.com/dotcloud/docker/execdriver" _ "github.com/dotcloud/docker/execdriver/lxc" _ "github.com/dotcloud/docker/execdriver/native" - "io/ioutil" "log" "os" - "strings" ) -// Clear environment pollution introduced by lxc-start -func setupEnv(args *execdriver.InitArgs) { - os.Clearenv() - for _, kv := range args.Env { - parts := strings.SplitN(kv, "=", 2) - if len(parts) == 1 { - parts = append(parts, "") - } - os.Setenv(parts[0], parts[1]) - } -} - func executeProgram(args *execdriver.InitArgs) error { - setupEnv(args) - dockerInitFct, err := execdriver.GetInitFunc(args.Driver) if err != nil { panic(err) @@ -59,25 +42,12 @@ func SysInit() { ) flag.Parse() - // Get env - var env []string - content, err := ioutil.ReadFile(".dockerenv") - if err != nil { - log.Fatalf("Unable to load environment variables: %v", err) - } - if err := json.Unmarshal(content, &env); err != nil { - log.Fatalf("Unable to unmarshal environment variables: %v", err) - } - // Propagate the plugin-specific container env variable - env = append(env, "container="+os.Getenv("container")) - args := &execdriver.InitArgs{ User: *user, Gateway: *gateway, Ip: *ip, WorkDir: *workDir, Privileged: *privileged, - Env: env, Args: flag.Args(), Mtu: *mtu, Driver: *driver, From 6c266c4b42eeabe2d433a994753d86637fe52a0b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 3 Mar 2014 16:15:29 +0100 Subject: [PATCH 077/384] Move all bind-mounts in the container inside the namespace This moves the bind mounts like /.dockerinit, /etc/hostname, volumes, etc into the container namespace, by setting them up using lxc. This is useful to avoid littering the global namespace with a lot of mounts that are internal to each container and are not generally needed on the outside. In particular, it seems that having a lot of mounts is problematic wrt scaling to a lot of containers on systems where the root filesystem is mounted --rshared. Note that the "private" option is only supported by the native driver, as lxc doesn't support setting this. This is not a huge problem, but it does mean that some mounts are unnecessarily shared inside the container if you're using the lxc driver. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- execdriver/driver.go | 8 ++++ execdriver/execdrivers/execdrivers.go | 4 +- execdriver/lxc/lxc_template.go | 8 ++++ execdriver/native/default_template.go | 4 ++ execdriver/native/driver.go | 10 ++-- pkg/libcontainer/container.go | 10 ++++ pkg/libcontainer/nsinit/init.go | 2 +- pkg/libcontainer/nsinit/mount.go | 20 +++++++- runtime/container.go | 6 +-- runtime/runtime.go | 2 +- runtime/volumes.go | 68 +++++++-------------------- 11 files changed, 78 insertions(+), 64 deletions(-) diff --git a/execdriver/driver.go b/execdriver/driver.go index ec8f48f52d..ff37b6bc5b 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -97,6 +97,13 @@ type Resources struct { CpuShares int64 `json:"cpu_shares"` } +type Mount struct { + Source string `json:"source"` + Destination string `json:"destination"` + Writable bool `json:"writable"` + Private bool `json:"private"` +} + // Process wrapps an os/exec.Cmd to add more metadata type Command struct { exec.Cmd `json:"-"` @@ -114,6 +121,7 @@ type Command struct { Network *Network `json:"network"` // if network is nil then networking is disabled Config []string `json:"config"` // generic values that specific drivers can consume Resources *Resources `json:"resources"` + Mounts []Mount `json:"mounts"` Terminal Terminal `json:"-"` // standard or tty terminal Console string `json:"-"` // dev/console path diff --git a/execdriver/execdrivers/execdrivers.go b/execdriver/execdrivers/execdrivers.go index 95b2fc634d..7486d649c1 100644 --- a/execdriver/execdrivers/execdrivers.go +++ b/execdriver/execdrivers/execdrivers.go @@ -9,7 +9,7 @@ import ( "path" ) -func NewDriver(name, root string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) { +func NewDriver(name, root, initPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) { switch name { case "lxc": // we want to five the lxc driver the full docker root because it needs @@ -17,7 +17,7 @@ func NewDriver(name, root string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, // to be backwards compatible return lxc.NewDriver(root, sysInfo.AppArmor) case "native": - return native.NewDriver(path.Join(root, "execdriver", "native")) + return native.NewDriver(path.Join(root, "execdriver", "native"), initPath) } return nil, fmt.Errorf("unknown exec driver %s", name) } diff --git a/execdriver/lxc/lxc_template.go b/execdriver/lxc/lxc_template.go index 1181396a18..84cd4e442e 100644 --- a/execdriver/lxc/lxc_template.go +++ b/execdriver/lxc/lxc_template.go @@ -88,6 +88,14 @@ lxc.mount.entry = {{.Console}} {{escapeFstabSpaces $ROOTFS}}/dev/console none bi 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 +{{range $value := .Mounts}} +{{if $value.Writable}} +lxc.mount.entry = {{$value.Source}} {{escapeFstabSpaces $ROOTFS}}/{{escapeFstabSpaces $value.Destination}} none bind,rw 0 0 +{{else}} +lxc.mount.entry = {{$value.Source}} {{escapeFstabSpaces $ROOTFS}}/{{escapeFstabSpaces $value.Destination}} none bind,ro 0 0 +{{end}} +{{end}} + {{if .Privileged}} {{if .AppArmor}} lxc.aa_profile = unconfined diff --git a/execdriver/native/default_template.go b/execdriver/native/default_template.go index 6e7d597b7b..2798f3b084 100644 --- a/execdriver/native/default_template.go +++ b/execdriver/native/default_template.go @@ -48,6 +48,10 @@ func createContainer(c *execdriver.Command) *libcontainer.Container { // check to see if we are running in ramdisk to disable pivot root container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != "" + for _, m := range c.Mounts { + container.Mounts = append(container.Mounts, libcontainer.Mount{m.Source, m.Destination, m.Writable, m.Private}) + } + return container } diff --git a/execdriver/native/driver.go b/execdriver/native/driver.go index 452e802523..f6c7242620 100644 --- a/execdriver/native/driver.go +++ b/execdriver/native/driver.go @@ -55,10 +55,11 @@ func init() { } type driver struct { - root string + root string + initPath string } -func NewDriver(root string) (*driver, error) { +func NewDriver(root, initPath string) (*driver, error) { if err := os.MkdirAll(root, 0700); err != nil { return nil, err } @@ -66,7 +67,8 @@ func NewDriver(root string) (*driver, error) { return nil, err } return &driver{ - root: root, + root: root, + initPath: initPath, }, nil } @@ -210,7 +212,7 @@ func (d *dockerCommandFactory) Create(container *libcontainer.Container, console // we need to join the rootfs because nsinit will setup the rootfs and chroot initPath := filepath.Join(d.c.Rootfs, d.c.InitPath) - d.c.Path = initPath + d.c.Path = d.driver.initPath d.c.Args = append([]string{ initPath, "-driver", DriverName, diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index a777da58a4..14b4b65db7 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -23,6 +23,7 @@ type Container struct { Networks []*Network `json:"networks,omitempty"` // nil for host's network stack Cgroups *cgroups.Cgroup `json:"cgroups,omitempty"` // cgroups Context Context `json:"context,omitempty"` // generic context for specific options (apparmor, selinux) + Mounts []Mount `json:"mounts,omitempty"` } // Network defines configuration for a container's networking stack @@ -36,3 +37,12 @@ type Network struct { Gateway string `json:"gateway,omitempty"` Mtu int `json:"mtu,omitempty"` } + +// Bind mounts from the host system to the container +// +type Mount struct { + Source string `json:"source"` // Source path, in the host namespace + Destination string `json:"destination"` // Destination path, in the container + Writable bool `json:"writable"` + Private bool `json:"private"` +} diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 336fc1eaaf..5d47b95057 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -51,7 +51,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol if err := system.ParentDeathSignal(); err != nil { return fmt.Errorf("parent death signal %s", err) } - if err := setupNewMountNamespace(rootfs, console, container.ReadonlyFs, container.NoPivotRoot); err != nil { + if err := setupNewMountNamespace(rootfs, container.Mounts, console, container.ReadonlyFs, container.NoPivotRoot); err != nil { return fmt.Errorf("setup mount namespace %s", err) } if err := setupNetwork(container, context); err != nil { diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index 83577cfa8c..562ae25a59 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/nsinit/mount.go @@ -4,6 +4,7 @@ package nsinit import ( "fmt" + "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/system" "io/ioutil" "os" @@ -19,7 +20,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, console string, readonly, noPivotRoot bool) error { +func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, console string, readonly, noPivotRoot bool) error { flag := syscall.MS_PRIVATE if noPivotRoot { flag = syscall.MS_SLAVE @@ -38,6 +39,23 @@ func setupNewMountNamespace(rootfs, console string, readonly, noPivotRoot bool) if err := mountSystem(rootfs); err != nil { return fmt.Errorf("mount system %s", err) } + + for _, m := range bindMounts { + flags := syscall.MS_BIND | syscall.MS_REC + if !m.Writable { + flags = flags | syscall.MS_RDONLY + } + dest := filepath.Join(rootfs, m.Destination) + if err := system.Mount(m.Source, dest, "bind", uintptr(flags), ""); err != nil { + return fmt.Errorf("mounting %s into %s %s", m.Source, dest, err) + } + if m.Private { + if err := system.Mount("", dest, "none", uintptr(syscall.MS_PRIVATE), ""); err != nil { + return fmt.Errorf("mounting %s private %s", dest, err) + } + } + } + if err := copyDevNodes(rootfs); err != nil { return fmt.Errorf("copy dev nodes %s", err) } diff --git a/runtime/container.go b/runtime/container.go index 813147e508..2a98149f27 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -529,13 +529,13 @@ func (container *Container) Start() (err error) { return err } + populateCommand(container) + container.command.Env = env + if err := mountVolumesForContainer(container, envPath); err != nil { return err } - populateCommand(container) - container.command.Env = env - // Setup logging of stdout and stderr to disk if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { return err diff --git a/runtime/runtime.go b/runtime/runtime.go index 72245a4555..28e7bbd1e4 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -733,7 +733,7 @@ func NewRuntimeFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (* } sysInfo := sysinfo.New(false) - ed, err := execdrivers.NewDriver(config.ExecDriver, config.Root, sysInfo) + ed, err := execdrivers.NewDriver(config.ExecDriver, config.Root, sysInitPath, sysInfo) if err != nil { return nil, err } diff --git a/runtime/volumes.go b/runtime/volumes.go index 1a548eca47..81a305f72c 100644 --- a/runtime/volumes.go +++ b/runtime/volumes.go @@ -3,6 +3,7 @@ package runtime import ( "fmt" "github.com/dotcloud/docker/archive" + "github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/pkg/mount" "github.com/dotcloud/docker/utils" "io/ioutil" @@ -55,70 +56,33 @@ func mountVolumesForContainer(container *Container, envPath string) error { return err } - // Mount docker specific files into the containers root fs - if err := mount.Mount(runtime.sysInitPath, filepath.Join(root, "/.dockerinit"), "none", "bind,ro"); err != nil { - return err - } - if err := mount.Mount(envPath, filepath.Join(root, "/.dockerenv"), "none", "bind,ro"); err != nil { - return err - } - if err := mount.Mount(container.ResolvConfPath, filepath.Join(root, "/etc/resolv.conf"), "none", "bind,ro"); err != nil { - return err + mounts := []execdriver.Mount{ + {runtime.sysInitPath, "/.dockerinit", false, true}, + {envPath, "/.dockerenv", false, true}, + {container.ResolvConfPath, "/etc/resolv.conf", false, true}, } if container.HostnamePath != "" && container.HostsPath != "" { - if err := mount.Mount(container.HostnamePath, filepath.Join(root, "/etc/hostname"), "none", "bind,ro"); err != nil { - return err - } - if err := mount.Mount(container.HostsPath, filepath.Join(root, "/etc/hosts"), "none", "bind,ro"); err != nil { - return err - } + mounts = append(mounts, execdriver.Mount{container.HostnamePath, "/etc/hostname", false, true}) + mounts = append(mounts, execdriver.Mount{container.HostsPath, "/etc/hosts", false, true}) } // Mount user specified volumes + // Note, these are not private because you may want propagation of (un)mounts from host + // volumes. For instance if you use -v /usr:/usr and the host later mounts /usr/share you + // want this new mount in the container for r, v := range container.Volumes { - mountAs := "ro" - if container.VolumesRW[r] { - mountAs = "rw" - } - - r = filepath.Join(root, r) - if p, err := utils.FollowSymlinkInScope(r, root); err != nil { - return err - } else { - r = p - } - - if err := mount.Mount(v, r, "none", fmt.Sprintf("bind,%s", mountAs)); err != nil { - return err - } + mounts = append(mounts, execdriver.Mount{v, r, container.VolumesRW[r], false}) } + + container.command.Mounts = mounts + return nil } func unmountVolumesForContainer(container *Container) { - var ( - root = container.RootfsPath() - mounts = []string{ - root, - filepath.Join(root, "/.dockerinit"), - filepath.Join(root, "/.dockerenv"), - filepath.Join(root, "/etc/resolv.conf"), - } - ) - - if container.HostnamePath != "" && container.HostsPath != "" { - mounts = append(mounts, filepath.Join(root, "/etc/hostname"), filepath.Join(root, "/etc/hosts")) - } - - for r := range container.Volumes { - mounts = append(mounts, filepath.Join(root, r)) - } - - for i := len(mounts) - 1; i >= 0; i-- { - if lastError := mount.Unmount(mounts[i]); lastError != nil { - log.Printf("Failed to umount %v: %v", mounts[i], lastError) - } + if err := mount.Unmount(container.RootfsPath()); err != nil { + log.Printf("Failed to umount container: %v", err) } } From bf1b27dfcc6c4e049706d7d104e1abc5c330815d Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 4 Mar 2014 10:16:09 +0100 Subject: [PATCH 078/384] Don't use separate bind mount for container Since we're not not mounting anything but the base filesystem outside the container we no longer need the separate bind mount at /var/lib/docker/container/$id/root in order to see the base filesystem without extra mounts. So, we drop this and mount (again) the container root directly at the real basefs mountpoint. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- buildfile.go | 2 +- integration/utils_test.go | 2 +- runtime/container.go | 10 +--------- runtime/runtime.go | 2 -- runtime/volumes.go | 32 ++------------------------------ 5 files changed, 5 insertions(+), 43 deletions(-) diff --git a/buildfile.go b/buildfile.go index 959b085685..e4b3d28e9c 100644 --- a/buildfile.go +++ b/buildfile.go @@ -374,7 +374,7 @@ func (b *buildFile) checkPathForAddition(orig string) error { func (b *buildFile) addContext(container *runtime.Container, orig, dest string, remote bool) error { var ( origPath = path.Join(b.contextPath, orig) - destPath = path.Join(container.BasefsPath(), dest) + destPath = path.Join(container.RootfsPath(), dest) ) // Preserve the trailing '/' if strings.HasSuffix(dest, "/") { diff --git a/integration/utils_test.go b/integration/utils_test.go index 88f2cc49c3..53b4674df7 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -71,7 +71,7 @@ func containerFileExists(eng *engine.Engine, id, dir string, t utils.Fataler) bo t.Fatal(err) } defer c.Unmount() - if _, err := os.Stat(path.Join(c.BasefsPath(), dir)); err != nil { + if _, err := os.Stat(path.Join(c.RootfsPath(), dir)); err != nil { if os.IsNotExist(err) { return false } diff --git a/runtime/container.go b/runtime/container.go index 2a98149f27..ee545db201 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -532,7 +532,7 @@ func (container *Container) Start() (err error) { populateCommand(container) container.command.Env = env - if err := mountVolumesForContainer(container, envPath); err != nil { + if err := setupMountsForContainer(container, envPath); err != nil { return err } @@ -843,8 +843,6 @@ func (container *Container) cleanup() { } } - unmountVolumesForContainer(container) - if err := container.Unmount(); err != nil { log.Printf("%v: Failed to umount filesystem: %v", container.ID, err) } @@ -1039,12 +1037,6 @@ func (container *Container) EnvConfigPath() (string, error) { // This method must be exported to be used from the lxc template // This directory is only usable when the container is running func (container *Container) RootfsPath() string { - return path.Join(container.root, "root") -} - -// This is the stand-alone version of the root fs, without any additional mounts. -// This directory is usable whenever the container is mounted (and not unmounted) -func (container *Container) BasefsPath() string { return container.basefs } diff --git a/runtime/runtime.go b/runtime/runtime.go index 28e7bbd1e4..b364d1d270 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -174,7 +174,6 @@ func (runtime *Runtime) Register(container *Container) error { runtime.execDriver.Kill(command, 9) } // ensure that the filesystem is also unmounted - unmountVolumesForContainer(container) if err := container.Unmount(); err != nil { utils.Debugf("ghost unmount error %s", err) } @@ -185,7 +184,6 @@ func (runtime *Runtime) Register(container *Container) error { utils.Debugf("Container %s was supposed to be running but is not.", container.ID) if runtime.config.AutoRestart { utils.Debugf("Restarting") - unmountVolumesForContainer(container) if err := container.Unmount(); err != nil { utils.Debugf("restart unmount error %s", err) } diff --git a/runtime/volumes.go b/runtime/volumes.go index 81a305f72c..9cb66aae44 100644 --- a/runtime/volumes.go +++ b/runtime/volumes.go @@ -4,10 +4,8 @@ import ( "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/execdriver" - "github.com/dotcloud/docker/pkg/mount" "github.com/dotcloud/docker/utils" "io/ioutil" - "log" "os" "path/filepath" "strings" @@ -35,29 +33,9 @@ func prepareVolumesForContainer(container *Container) error { return nil } -func mountVolumesForContainer(container *Container, envPath string) error { - // Setup the root fs as a bind mount of the base fs - var ( - root = container.RootfsPath() - runtime = container.runtime - ) - if err := os.MkdirAll(root, 0755); err != nil && !os.IsExist(err) { - return nil - } - - // Create a bind mount of the base fs as a place where we can add mounts - // without affecting the ability to access the base fs - if err := mount.Mount(container.basefs, root, "none", "bind,rw"); err != nil { - return err - } - - // Make sure the root fs is private so the mounts here don't propagate to basefs - if err := mount.ForceMount(root, root, "none", "private"); err != nil { - return err - } - +func setupMountsForContainer(container *Container, envPath string) error { mounts := []execdriver.Mount{ - {runtime.sysInitPath, "/.dockerinit", false, true}, + {container.runtime.sysInitPath, "/.dockerinit", false, true}, {envPath, "/.dockerenv", false, true}, {container.ResolvConfPath, "/etc/resolv.conf", false, true}, } @@ -80,12 +58,6 @@ func mountVolumesForContainer(container *Container, envPath string) error { return nil } -func unmountVolumesForContainer(container *Container) { - if err := mount.Unmount(container.RootfsPath()); err != nil { - log.Printf("Failed to umount container: %v", err) - } -} - func applyVolumesFrom(container *Container) error { if container.Config.VolumesFrom != "" { for _, containerSpec := range strings.Split(container.Config.VolumesFrom, ",") { From 6fc83eefd9e8d78044a51250d2ad185513fddd27 Mon Sep 17 00:00:00 2001 From: Charlie Lewis Date: Thu, 13 Mar 2014 11:15:52 -0700 Subject: [PATCH 079/384] add a breakathon for testing Docker-DCO-1.1-Signed-off-by: Charlie Lewis (github: cglewis) --- hack/RELEASE-CHECKLIST.md | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/hack/RELEASE-CHECKLIST.md b/hack/RELEASE-CHECKLIST.md index 6ef5d9cf58..1eb1646f25 100644 --- a/hack/RELEASE-CHECKLIST.md +++ b/hack/RELEASE-CHECKLIST.md @@ -178,7 +178,23 @@ docker run \ hack/release.sh ``` -### 9. Apply tag +### 9. Breakathon + +Spend several days along with the community explicitly investing time and +resources to try and break Docker in every possible way, documenting any +findings pertinent to the release. This time should be spent testing and +finding ways in which the release might have caused various features or upgrade +environments to have issues, not coding. During this time, the release is in +code freeze, and any additional code changes will be pushed out to the next +release. + +It should include various levels of breaking Docker, beyond just using Docker +by the book. + +Any issues found may still remain issues for this release, but they should be +documented and give appropriate warnings. + +### 10. Apply tag ```bash git tag -a $VERSION -m $VERSION bump_$VERSION @@ -188,12 +204,12 @@ git push origin $VERSION It's very important that we don't make the tag until after the official release is uploaded to get.docker.io! -### 10. Go to github to merge the `bump_$VERSION` branch into release +### 11. Go to github to merge the `bump_$VERSION` branch into release Don't forget to push that pretty blue button to delete the leftover branch afterwards! -### 11. Update the docs branch +### 12. Update the docs branch ```bash git checkout docs @@ -207,7 +223,7 @@ Updating the docs branch will automatically update the documentation on the after the merge. The docs will appear on http://docs.docker.io/. For more information about documentation releases, see `docs/README.md`. -### 12. Create a new pull request to merge release back into master +### 13. Create a new pull request to merge release back into master ```bash git checkout master @@ -225,7 +241,7 @@ echo "https://github.com/dotcloud/docker/compare/master...merge_release_$VERSION Again, get two maintainers to validate, then merge, then push that pretty blue button to delete your branch. -### 13. Rejoice and Evangelize! +### 14. Rejoice and Evangelize! Congratulations! You're done. From ab26c16b32420011b0aee3de1a3bce5a0afd6f4d Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 13 Mar 2014 13:58:09 -0700 Subject: [PATCH 080/384] Fix EXPOSE cache miss issue Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- buildfile.go | 21 ++++++++++++++++++--- runconfig/merge.go | 1 + runtime/runtime.go | 1 - server.go | 1 - 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/buildfile.go b/buildfile.go index 959b085685..dc9039f8d1 100644 --- a/buildfile.go +++ b/buildfile.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "github.com/dotcloud/docker/archive" + "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/runtime" @@ -304,8 +305,22 @@ func (b *buildFile) CmdEntrypoint(args string) error { } func (b *buildFile) CmdExpose(args string) error { - ports := strings.Split(args, " ") - b.config.PortSpecs = append(ports, b.config.PortSpecs...) + portsTab := strings.Split(args, " ") + + if b.config.ExposedPorts == nil { + b.config.ExposedPorts = make(nat.PortSet) + } + ports, _, err := nat.ParsePortSpecs(append(portsTab, b.config.PortSpecs...)) + if err != nil { + return err + } + for port := range ports { + if _, exists := b.config.ExposedPorts[port]; !exists { + b.config.ExposedPorts[port] = struct{}{} + } + } + b.config.PortSpecs = nil + return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports)) } @@ -686,12 +701,12 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error { b.tmpContainers[container.ID] = struct{}{} fmt.Fprintf(b.outStream, " ---> Running in %s\n", utils.TruncateID(container.ID)) id = container.ID + if err := container.Mount(); err != nil { return err } defer container.Unmount() } - container := b.runtime.Get(id) if container == nil { return fmt.Errorf("An error occured while creating the container") diff --git a/runconfig/merge.go b/runconfig/merge.go index a8d677baa8..3b91aa2af0 100644 --- a/runconfig/merge.go +++ b/runconfig/merge.go @@ -64,6 +64,7 @@ func Merge(userConf, imageConf *Config) error { } } } + if !userConf.Tty { userConf.Tty = imageConf.Tty } diff --git a/runtime/runtime.go b/runtime/runtime.go index 72245a4555..e54acd15d3 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -542,7 +542,6 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe // The image can optionally be tagged into a repository func (runtime *Runtime) Commit(container *Container, repository, tag, comment, author string, config *runconfig.Config) (*image.Image, error) { // FIXME: freeze the container before copying it to avoid data corruption? - // FIXME: this shouldn't be in commands. if err := container.Mount(); err != nil { return nil, err } diff --git a/server.go b/server.go index d4d6a39158..75fa633e8f 100644 --- a/server.go +++ b/server.go @@ -1970,7 +1970,6 @@ func (srv *Server) canDeleteImage(imgID string) error { } func (srv *Server) ImageGetCached(imgID string, config *runconfig.Config) (*image.Image, error) { - // Retrieve all images images, err := srv.runtime.Graph().Map() if err != nil { From 747275d30c4d4eb25ca798394cc04db00912adb2 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 13 Mar 2014 14:31:09 -0700 Subject: [PATCH 081/384] Always symlink /dev/ptmx for libcontainer Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/mount.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index 83577cfa8c..072188ecd8 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/nsinit/mount.go @@ -46,10 +46,8 @@ func setupNewMountNamespace(rootfs, console string, readonly, noPivotRoot bool) if err := setupDev(rootfs); err != nil { return err } - if console != "" { - if err := setupPtmx(rootfs, console); err != nil { - return err - } + if err := setupPtmx(rootfs, console); err != nil { + return err } if err := system.Chdir(rootfs); err != nil { return fmt.Errorf("chdir into %s %s", rootfs, err) @@ -245,8 +243,10 @@ func setupPtmx(rootfs, console string) error { if err := os.Symlink("pts/ptmx", ptmx); err != nil { return fmt.Errorf("symlink dev ptmx %s", err) } - if err := setupConsole(rootfs, console); err != nil { - return err + if console != "" { + if err := setupConsole(rootfs, console); err != nil { + return err + } } return nil } From 3fa99b35b05d9159d6f7f4c7465dec747da2c4e1 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 12 Mar 2014 18:04:14 -0700 Subject: [PATCH 082/384] Don't kill by pid for other drivers Closes #4575 Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runtime/runtime.go | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/runtime/runtime.go b/runtime/runtime.go index b364d1d270..16117b9788 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -160,22 +160,16 @@ func (runtime *Runtime) Register(container *Container) error { if container.State.IsGhost() { utils.Debugf("killing ghost %s", container.ID) - existingPid := container.State.Pid container.State.SetGhost(false) container.State.SetStopped(0) + // We only have to handle this for lxc because the other drivers will ensure that + // no ghost processes are left when docker dies if container.ExecDriver == "" || strings.Contains(container.ExecDriver, "lxc") { lxc.KillLxc(container.ID, 9) - } else { - command := &execdriver.Command{ - ID: container.ID, + if err := container.Unmount(); err != nil { + utils.Debugf("ghost unmount error %s", err) } - command.Process = &os.Process{Pid: existingPid} - runtime.execDriver.Kill(command, 9) - } - // ensure that the filesystem is also unmounted - if err := container.Unmount(); err != nil { - utils.Debugf("ghost unmount error %s", err) } } From cbd2a30cd6185d1469f82f8b6693d6158c93d54a Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 13 Mar 2014 15:18:08 -0700 Subject: [PATCH 083/384] Update libcontainer readme and todo list Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/README.md | 119 ++++++++++++++++++++++--------------- pkg/libcontainer/TODO.md | 8 +-- 2 files changed, 72 insertions(+), 55 deletions(-) diff --git a/pkg/libcontainer/README.md b/pkg/libcontainer/README.md index d6e4dedd63..2c85111b97 100644 --- a/pkg/libcontainer/README.md +++ b/pkg/libcontainer/README.md @@ -16,54 +16,77 @@ process are specified in this file. The configuration is used for each process Sample `container.json` file: ```json { - "hostname": "koye", - "tty": true, - "environment": [ - "HOME=/", - "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", - "container=docker", - "TERM=xterm-256color" - ], - "namespaces": [ - "NEWIPC", - "NEWNS", - "NEWPID", - "NEWUTS", - "NEWNET" - ], - "capabilities": [ - "SETPCAP", - "SYS_MODULE", - "SYS_RAWIO", - "SYS_PACCT", - "SYS_ADMIN", - "SYS_NICE", - "SYS_RESOURCE", - "SYS_TIME", - "SYS_TTY_CONFIG", - "MKNOD", - "AUDIT_WRITE", - "AUDIT_CONTROL", - "MAC_OVERRIDE", - "MAC_ADMIN", - "NET_ADMIN" - ], - "networks": [{ - "type": "veth", - "context": { - "bridge": "docker0", - "prefix": "dock" - }, - "address": "172.17.0.100/16", - "gateway": "172.17.42.1", - "mtu": 1500 - } - ], - "cgroups": { - "name": "docker-koye", - "parent": "docker", - "memory": 5248000 - } + "hostname" : "koye", + "networks" : [ + { + "gateway" : "172.17.42.1", + "context" : { + "bridge" : "docker0", + "prefix" : "veth" + }, + "address" : "172.17.0.2/16", + "type" : "veth", + "mtu" : 1500 + } + ], + "cgroups" : { + "parent" : "docker", + "name" : "11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620" + }, + "tty" : true, + "environment" : [ + "HOME=/", + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "HOSTNAME=11bb30683fb0", + "TERM=xterm" + ], + "capabilities" : [ + "SETPCAP", + "SYS_MODULE", + "SYS_RAWIO", + "SYS_PACCT", + "SYS_ADMIN", + "SYS_NICE", + "SYS_RESOURCE", + "SYS_TIME", + "SYS_TTY_CONFIG", + "MKNOD", + "AUDIT_WRITE", + "AUDIT_CONTROL", + "MAC_OVERRIDE", + "MAC_ADMIN", + "NET_ADMIN" + ], + "context" : { + "apparmor_profile" : "docker-default" + }, + "mounts" : [ + { + "source" : "/var/lib/docker/containers/11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620/resolv.conf", + "writable" : false, + "destination" : "/etc/resolv.conf", + "private" : true + }, + { + "source" : "/var/lib/docker/containers/11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620/hostname", + "writable" : false, + "destination" : "/etc/hostname", + "private" : true + }, + { + "source" : "/var/lib/docker/containers/11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620/hosts", + "writable" : false, + "destination" : "/etc/hosts", + "private" : true + } + ], + "namespaces" : [ + "NEWNS", + "NEWUTS", + "NEWIPC", + "NEWPID", + "NEWNET" + ] } ``` diff --git a/pkg/libcontainer/TODO.md b/pkg/libcontainer/TODO.md index f18c0b4c51..87224db85d 100644 --- a/pkg/libcontainer/TODO.md +++ b/pkg/libcontainer/TODO.md @@ -1,17 +1,11 @@ #### goals * small and simple - line count is not everything but less code is better -* clean lines between what we do in the pkg * provide primitives for working with namespaces not cater to every option * extend via configuration not by features - host networking, no networking, veth network can be accomplished via adjusting the container.json, nothing to do with code #### tasks -* proper tty for a new process in an existing container -* use exec or raw syscalls for new process in existing container -* setup proper user in namespace if specified -* implement hook or clean interface for cgroups +* reexec or raw syscalls for new process in existing container * example configs for different setups (host networking, boot init) * improve pkg documentation with comments * testing - this is hard in a low level pkg but we could do some, maybe -* pivot root * selinux -* apparmor From 03f0ec35ae31420dd6a56883535056087b1a75dd Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 13 Mar 2014 22:26:42 +0000 Subject: [PATCH 084/384] as you could have multiple messages per line with streams, don't \r Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- utils/jsonmessage.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/jsonmessage.go b/utils/jsonmessage.go index 9050dda746..f84cc42c78 100644 --- a/utils/jsonmessage.go +++ b/utils/jsonmessage.go @@ -85,7 +85,7 @@ func (jm *JSONMessage) Display(out io.Writer, isTerminal bool) error { return jm.Error } var endl string - if isTerminal { + if isTerminal && jm.Stream == "" { // [2K = erase entire current line fmt.Fprintf(out, "%c[2K\r", 27) endl = "\r" From 6411ee6d24d256e15909ea68b845b354dc51c4ed Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 13 Mar 2014 16:19:28 -0700 Subject: [PATCH 085/384] Have the exec driver and kernel version in non-debug mode in `docker info` Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- api/client.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api/client.go b/api/client.go index 6049a892c1..e6a74a3b53 100644 --- a/api/client.go +++ b/api/client.go @@ -432,7 +432,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { fmt.Fprintf(cli.out, "Containers: %d\n", remoteInfo.GetInt("Containers")) fmt.Fprintf(cli.out, "Images: %d\n", remoteInfo.GetInt("Images")) - fmt.Fprintf(cli.out, "Driver: %s\n", remoteInfo.Get("Driver")) + fmt.Fprintf(cli.out, "Storage Driver: %s\n", remoteInfo.Get("Driver")) var driverStatus [][2]string if err := remoteInfo.GetJson("DriverStatus", &driverStatus); err != nil { return err @@ -440,14 +440,15 @@ func (cli *DockerCli) CmdInfo(args ...string) error { for _, pair := range driverStatus { fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1]) } + fmt.Fprintf(cli.out, "Execution Driver: %s\n", remoteInfo.Get("ExecutionDriver")) + fmt.Fprintf(cli.out, "Kernel Version: %s\n", remoteInfo.Get("KernelVersion")) + if remoteInfo.GetBool("Debug") || os.Getenv("DEBUG") != "" { fmt.Fprintf(cli.out, "Debug mode (server): %v\n", remoteInfo.GetBool("Debug")) fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "") fmt.Fprintf(cli.out, "Fds: %d\n", remoteInfo.GetInt("NFd")) fmt.Fprintf(cli.out, "Goroutines: %d\n", remoteInfo.GetInt("NGoroutines")) - fmt.Fprintf(cli.out, "Execution Driver: %s\n", remoteInfo.Get("ExecutionDriver")) fmt.Fprintf(cli.out, "EventsListeners: %d\n", remoteInfo.GetInt("NEventsListener")) - fmt.Fprintf(cli.out, "Kernel Version: %s\n", remoteInfo.Get("KernelVersion")) if initSha1 := remoteInfo.Get("InitSha1"); initSha1 != "" { fmt.Fprintf(cli.out, "Init SHA1: %s\n", initSha1) From 7b89af2a08b65fac064603cb3b5eb8e091e2c076 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 13 Mar 2014 14:03:03 -0700 Subject: [PATCH 086/384] Add unit test for expose cache Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- integration/buildfile_test.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index e5084d4355..f4ed61aaff 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -6,6 +6,7 @@ import ( "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/image" + "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/utils" "io/ioutil" "net" @@ -492,7 +493,7 @@ func TestBuildExpose(t *testing.T) { t.Fatal(err) } - if img.Config.PortSpecs[0] != "4243" { + if _, exists := img.Config.ExposedPorts[nat.NewPort("tcp", "4243")]; !exists { t.Fail() } } @@ -594,6 +595,17 @@ func TestBuildImageWithCache(t *testing.T) { checkCacheBehavior(t, template, true) } +func TestBuildExposeWithCache(t *testing.T) { + template := testContextTemplate{` + from {IMAGE} + maintainer dockerio + expose 80 + run echo hello + `, + nil, nil} + checkCacheBehavior(t, template, true) +} + func TestBuildImageWithoutCache(t *testing.T) { template := testContextTemplate{` from {IMAGE} @@ -877,7 +889,7 @@ func TestBuildInheritance(t *testing.T) { } // from parent - if img.Config.PortSpecs[0] != "4243" { + if _, exists := img.Config.ExposedPorts[nat.NewPort("tcp", "4243")]; !exists { t.Fail() } } From c349c9d14a1c4bf04f35f3f5c62b0bb92614bc81 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 14 Mar 2014 00:47:13 +0000 Subject: [PATCH 087/384] create the cli obj before calling parseCommand Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api/client.go | 4 +--- docker/docker.go | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/api/client.go b/api/client.go index 858c2bcf25..715f58ab06 100644 --- a/api/client.go +++ b/api/client.go @@ -57,9 +57,7 @@ func (cli *DockerCli) getMethod(name string) (func(...string) error, bool) { return method.Interface().(func(...string) error), true } -func ParseCommands(proto, addr string, args ...string) error { - cli := NewDockerCli(os.Stdin, os.Stdout, os.Stderr, proto, addr) - +func (cli *DockerCli) ParseCommands(args ...string) error { if len(args) > 0 { method, exists := cli.getMethod(args[0]) if !exists { diff --git a/docker/docker.go b/docker/docker.go index cc4d40f3ac..749857a640 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -148,7 +148,8 @@ func main() { log.Fatal("Please specify only one -H") } protoAddrParts := strings.SplitN(flHosts.GetAll()[0], "://", 2) - if err := api.ParseCommands(protoAddrParts[0], protoAddrParts[1], flag.Args()...); err != nil { + cli := api.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, protoAddrParts[0], protoAddrParts[1]) + if err := cli.ParseCommands(flag.Args()...); err != nil { if sterr, ok := err.(*utils.StatusError); ok { if sterr.Status != "" { log.Println(sterr.Status) From f6efcf20943e656ca977f1fd0ae5197f5757dff4 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 13 Mar 2014 22:35:09 -0600 Subject: [PATCH 088/384] Fix sphinx header underline warnings I introduced... Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- docs/sources/reference/commandline/cli.rst | 4 ++-- docs/sources/reference/run.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 83f05947c2..2371ed1b5f 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -374,7 +374,7 @@ The me/bar image will now have port 22 exposed, MYVAR env var set to 'foobar', a Note that this is currently a shallow merge. So, for example, if you had specified a new port spec in the --run= config above, that would have clobbered the 'EXPOSE 22' setting from the parent container. Full --run example -................. +.................. The ``--run`` JSON hash changes the ``Config`` section when running ``docker inspect CONTAINERID`` or ``config`` when running ``docker inspect IMAGEID``. Existing configuration key-values that are @@ -1172,7 +1172,7 @@ See :ref:`port_redirection` for more detailed information about the ``--expose`` specific examples using ``--link``. Known Issues (run --volumes-from) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * :issue:`2702`: "lxc-start: Permission denied - failed to mount" could indicate a permissions problem with AppArmor. Please see the diff --git a/docs/sources/reference/run.rst b/docs/sources/reference/run.rst index 8637ac3071..0b4f7eebf4 100644 --- a/docs/sources/reference/run.rst +++ b/docs/sources/reference/run.rst @@ -113,7 +113,7 @@ Container Identification ------------------------ Name (--name) -............ +............. The operator can identify a container in three ways: @@ -157,7 +157,7 @@ Your container will use the same DNS servers as the host by default, but you can override this with ``--dns``. Clean Up (--rm) --------------- +--------------- By default a container's file system persists even after the container exits. This makes debugging a lot easier (since you can inspect the From ae47f709ca6a6c29b769191ceabb10e59a0408b1 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 13 Mar 2014 22:35:31 -0600 Subject: [PATCH 089/384] Make sphinx warnings fatal in Travis Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8a43d9a462..b8e4d43fcc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,6 @@ before_script: script: - hack/travis/dco.py - hack/travis/gofmt.py - - make -sC docs SPHINXOPTS=-q docs man + - make -sC docs SPHINXOPTS=-qW docs man # vim:set sw=2 ts=2: From 5239aa1f11c32f3befc25fb2fa8a0ecf75ec4bf6 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 11 Mar 2014 10:40:06 -0700 Subject: [PATCH 090/384] Move server and buildfile into server pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- builtins/builtins.go | 4 +-- buildfile.go => server/buildfile.go | 30 +------------------ server.go => server/server.go | 6 ++-- .../server_unit_test.go | 2 +- utils/streamformatter.go | 29 ++++++++++++++++++ 5 files changed, 36 insertions(+), 35 deletions(-) rename buildfile.go => server/buildfile.go (96%) rename server.go => server/server.go (99%) rename server_unit_test.go => server/server_unit_test.go (99%) diff --git a/builtins/builtins.go b/builtins/builtins.go index ba3f41b1ca..eb4a0be874 100644 --- a/builtins/builtins.go +++ b/builtins/builtins.go @@ -3,9 +3,9 @@ package builtins import ( "github.com/dotcloud/docker/engine" - "github.com/dotcloud/docker" "github.com/dotcloud/docker/api" "github.com/dotcloud/docker/networkdriver/lxc" + "github.com/dotcloud/docker/server" ) func Register(eng *engine.Engine) { @@ -34,6 +34,6 @@ func remote(eng *engine.Engine) { // These components should be broken off into plugins of their own. // func daemon(eng *engine.Engine) { - eng.Register("initserver", docker.InitServer) + eng.Register("initserver", server.InitServer) eng.Register("init_networkdriver", lxc.InitDriver) } diff --git a/buildfile.go b/server/buildfile.go similarity index 96% rename from buildfile.go rename to server/buildfile.go index da72be60fb..af6702cc1d 100644 --- a/buildfile.go +++ b/server/buildfile.go @@ -1,4 +1,4 @@ -package docker +package server import ( "crypto/sha256" @@ -591,34 +591,6 @@ func (b *buildFile) CmdAdd(args string) error { return nil } -type StdoutFormater struct { - io.Writer - *utils.StreamFormatter -} - -func (sf *StdoutFormater) Write(buf []byte) (int, error) { - formattedBuf := sf.StreamFormatter.FormatStream(string(buf)) - n, err := sf.Writer.Write(formattedBuf) - if n != len(formattedBuf) { - return n, io.ErrShortWrite - } - return len(buf), err -} - -type StderrFormater struct { - io.Writer - *utils.StreamFormatter -} - -func (sf *StderrFormater) Write(buf []byte) (int, error) { - formattedBuf := sf.StreamFormatter.FormatStream("\033[91m" + string(buf) + "\033[0m") - n, err := sf.Writer.Write(formattedBuf) - if n != len(formattedBuf) { - return n, io.ErrShortWrite - } - return len(buf), err -} - func (b *buildFile) create() (*runtime.Container, error) { if b.image == "" { return nil, fmt.Errorf("Please provide a source image with `from` prior to run") diff --git a/server.go b/server/server.go similarity index 99% rename from server.go rename to server/server.go index 75fa633e8f..eb9a3a396b 100644 --- a/server.go +++ b/server/server.go @@ -1,4 +1,4 @@ -package docker +package server import ( "encoding/json" @@ -456,11 +456,11 @@ func (srv *Server) Build(job *engine.Job) engine.Status { sf := utils.NewStreamFormatter(job.GetenvBool("json")) b := NewBuildFile(srv, - &StdoutFormater{ + &utils.StdoutFormater{ Writer: job.Stdout, StreamFormatter: sf, }, - &StderrFormater{ + &utils.StderrFormater{ Writer: job.Stdout, StreamFormatter: sf, }, diff --git a/server_unit_test.go b/server/server_unit_test.go similarity index 99% rename from server_unit_test.go rename to server/server_unit_test.go index 6a90ca5892..b471c5c581 100644 --- a/server_unit_test.go +++ b/server/server_unit_test.go @@ -1,4 +1,4 @@ -package docker +package server import ( "github.com/dotcloud/docker/utils" diff --git a/utils/streamformatter.go b/utils/streamformatter.go index 8876fa5cb7..d2758d3ca6 100644 --- a/utils/streamformatter.go +++ b/utils/streamformatter.go @@ -3,6 +3,7 @@ package utils import ( "encoding/json" "fmt" + "io" ) type StreamFormatter struct { @@ -90,3 +91,31 @@ func (sf *StreamFormatter) Used() bool { func (sf *StreamFormatter) Json() bool { return sf.json } + +type StdoutFormater struct { + io.Writer + *StreamFormatter +} + +func (sf *StdoutFormater) Write(buf []byte) (int, error) { + formattedBuf := sf.StreamFormatter.FormatStream(string(buf)) + n, err := sf.Writer.Write(formattedBuf) + if n != len(formattedBuf) { + return n, io.ErrShortWrite + } + return len(buf), err +} + +type StderrFormater struct { + io.Writer + *StreamFormatter +} + +func (sf *StderrFormater) Write(buf []byte) (int, error) { + formattedBuf := sf.StreamFormatter.FormatStream("\033[91m" + string(buf) + "\033[0m") + n, err := sf.Writer.Write(formattedBuf) + if n != len(formattedBuf) { + return n, io.ErrShortWrite + } + return len(buf), err +} From 8cf0b80a7843633018b66a35d9a55f30814a56b6 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 11 Mar 2014 10:44:23 -0700 Subject: [PATCH 091/384] Update integration tests for server pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- integration/buildfile_test.go | 12 ++++++------ integration/server_test.go | 4 ++-- integration/utils_test.go | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index f4ed61aaff..7f6e69ece3 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -2,11 +2,11 @@ package docker import ( "fmt" - "github.com/dotcloud/docker" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/nat" + "github.com/dotcloud/docker/server" "github.com/dotcloud/docker/utils" "io/ioutil" "net" @@ -384,7 +384,7 @@ func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, u } dockerfile := constructDockerfile(context.dockerfile, ip, port) - buildfile := docker.NewBuildFile(srv, ioutil.Discard, ioutil.Discard, false, useCache, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil) + buildfile := server.NewBuildFile(srv, ioutil.Discard, ioutil.Discard, false, useCache, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil) id, err := buildfile.Build(context.Archive(dockerfile, t)) if err != nil { return nil, err @@ -799,7 +799,7 @@ func TestForbiddenContextPath(t *testing.T) { } dockerfile := constructDockerfile(context.dockerfile, ip, port) - buildfile := docker.NewBuildFile(srv, ioutil.Discard, ioutil.Discard, false, true, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil) + buildfile := server.NewBuildFile(srv, ioutil.Discard, ioutil.Discard, false, true, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil) _, err = buildfile.Build(context.Archive(dockerfile, t)) if err == nil { @@ -845,7 +845,7 @@ func TestBuildADDFileNotFound(t *testing.T) { } dockerfile := constructDockerfile(context.dockerfile, ip, port) - buildfile := docker.NewBuildFile(mkServerFromEngine(eng, t), ioutil.Discard, ioutil.Discard, false, true, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil) + buildfile := server.NewBuildFile(mkServerFromEngine(eng, t), ioutil.Discard, ioutil.Discard, false, true, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil) _, err = buildfile.Build(context.Archive(dockerfile, t)) if err == nil { @@ -917,8 +917,8 @@ func TestBuildFails(t *testing.T) { func TestBuildFailsDockerfileEmpty(t *testing.T) { _, err := buildImage(testContextTemplate{``, nil, nil}, t, nil, true) - if err != docker.ErrDockerfileEmpty { - t.Fatal("Expected: %v, got: %v", docker.ErrDockerfileEmpty, err) + if err != server.ErrDockerfileEmpty { + t.Fatal("Expected: %v, got: %v", server.ErrDockerfileEmpty, err) } } diff --git a/integration/server_test.go b/integration/server_test.go index 54ee9a77a9..49bd15e36f 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -1,9 +1,9 @@ package docker import ( - "github.com/dotcloud/docker" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/runconfig" + "github.com/dotcloud/docker/server" "strings" "testing" "time" @@ -739,7 +739,7 @@ func TestListContainers(t *testing.T) { } } -func assertContainerList(srv *docker.Server, all bool, limit int, since, before string, expected []string) bool { +func assertContainerList(srv *server.Server, all bool, limit int, since, before string, expected []string) bool { job := srv.Eng.Job("containers") job.SetenvBool("all", all) job.SetenvInt("limit", limit) diff --git a/integration/utils_test.go b/integration/utils_test.go index 53b4674df7..8ad6ccb123 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -14,11 +14,11 @@ import ( "testing" "time" - "github.com/dotcloud/docker" "github.com/dotcloud/docker/builtins" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/runtime" + "github.com/dotcloud/docker/server" "github.com/dotcloud/docker/utils" ) @@ -149,14 +149,14 @@ func getContainer(eng *engine.Engine, id string, t utils.Fataler) *runtime.Conta return c } -func mkServerFromEngine(eng *engine.Engine, t utils.Fataler) *docker.Server { +func mkServerFromEngine(eng *engine.Engine, t utils.Fataler) *server.Server { iSrv := eng.Hack_GetGlobalVar("httpapi.server") if iSrv == nil { panic("Legacy server field not set in engine") } - srv, ok := iSrv.(*docker.Server) + srv, ok := iSrv.(*server.Server) if !ok { - panic("Legacy server field in engine does not cast to *docker.Server") + panic("Legacy server field in engine does not cast to *server.Server") } return srv } From 7294392c729de4c5884eb967f192b34a1d8857a7 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 13 Mar 2014 10:35:16 -0700 Subject: [PATCH 092/384] Add initial logging to libcontainer Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- execdriver/native/driver.go | 9 +++-- pkg/libcontainer/nsinit/command.go | 3 +- pkg/libcontainer/nsinit/exec.go | 16 +++++++-- pkg/libcontainer/nsinit/execin.go | 3 ++ pkg/libcontainer/nsinit/init.go | 11 ++++-- pkg/libcontainer/nsinit/nsinit.go | 5 ++- pkg/libcontainer/nsinit/nsinit/main.go | 48 +++++++++++++++++++------- 7 files changed, 74 insertions(+), 21 deletions(-) diff --git a/execdriver/native/driver.go b/execdriver/native/driver.go index f6c7242620..989f2ff376 100644 --- a/execdriver/native/driver.go +++ b/execdriver/native/driver.go @@ -10,6 +10,7 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer/nsinit" "github.com/dotcloud/docker/pkg/system" "io/ioutil" + "log" "os" "os/exec" "path/filepath" @@ -27,7 +28,8 @@ func init() { execdriver.RegisterInitFunc(DriverName, func(args *execdriver.InitArgs) error { var ( container *libcontainer.Container - ns = nsinit.NewNsInit(&nsinit.DefaultCommandFactory{}, &nsinit.DefaultStateWriter{args.Root}) + logger = log.New(ioutil.Discard, "[nsinit] ", log.LstdFlags) + ns = nsinit.NewNsInit(&nsinit.DefaultCommandFactory{}, &nsinit.DefaultStateWriter{args.Root}, logger) ) f, err := os.Open(filepath.Join(args.Root, "container.json")) if err != nil { @@ -85,8 +87,9 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba c: c, dsw: &nsinit.DefaultStateWriter{filepath.Join(d.root, c.ID)}, } - ns = nsinit.NewNsInit(factory, stateWriter) - args = append([]string{c.Entrypoint}, c.Arguments...) + logger = log.New(ioutil.Discard, "[nsinit] ", log.LstdFlags) + ns = nsinit.NewNsInit(factory, stateWriter, logger) + args = append([]string{c.Entrypoint}, c.Arguments...) ) if err := d.createContainerRoot(c.ID); err != nil { return -1, err diff --git a/pkg/libcontainer/nsinit/command.go b/pkg/libcontainer/nsinit/command.go index 5546065b6d..1d7c591ee5 100644 --- a/pkg/libcontainer/nsinit/command.go +++ b/pkg/libcontainer/nsinit/command.go @@ -1,6 +1,7 @@ package nsinit import ( + "fmt" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/system" "os" @@ -25,7 +26,7 @@ func (c *DefaultCommandFactory) Create(container *libcontainer.Container, consol // get our binary name from arg0 so we can always reexec ourself command := exec.Command(os.Args[0], append([]string{ "-console", console, - "-pipe", "3", + "-pipe", fmt.Sprint(pipe.Fd()), "-root", c.Root, "init"}, args...)...) diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 4963f126e9..074492ae31 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -28,6 +28,7 @@ func (ns *linuxNs) Exec(container *libcontainer.Container, term Terminal, args [ } if container.Tty { + ns.logger.Println("creating master and console") master, console, err = system.CreateMasterAndConsole() if err != nil { return -1, err @@ -36,31 +37,40 @@ func (ns *linuxNs) Exec(container *libcontainer.Container, term Terminal, args [ } command := ns.commandFactory.Create(container, console, syncPipe.child, args) + ns.logger.Println("attach terminal to command") if err := term.Attach(command); err != nil { return -1, err } defer term.Close() + ns.logger.Println("starting command") if err := command.Start(); err != nil { return -1, err } + ns.logger.Printf("writting pid %d to file\n", command.Process.Pid) if err := ns.stateWriter.WritePid(command.Process.Pid); err != nil { command.Process.Kill() return -1, err } - defer ns.stateWriter.DeletePid() + defer func() { + ns.logger.Println("removing pid file") + ns.stateWriter.DeletePid() + }() // 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 { command.Process.Kill() return -1, err } + ns.logger.Println("setting up network") if err := ns.InitializeNetworking(container, command.Process.Pid, syncPipe); err != nil { command.Process.Kill() return -1, err } + ns.logger.Println("closing sync pipe with child") // Sync with child syncPipe.Close() @@ -69,7 +79,9 @@ func (ns *linuxNs) Exec(container *libcontainer.Container, term Terminal, args [ return -1, err } } - return command.ProcessState.Sys().(syscall.WaitStatus).ExitStatus(), nil + status := command.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() + ns.logger.Printf("process exited with status %d\n", status) + return status, err } func (ns *linuxNs) SetupCgroups(container *libcontainer.Container, nspid int) error { diff --git a/pkg/libcontainer/nsinit/execin.go b/pkg/libcontainer/nsinit/execin.go index 488fe0e248..39df4761a0 100644 --- a/pkg/libcontainer/nsinit/execin.go +++ b/pkg/libcontainer/nsinit/execin.go @@ -14,6 +14,7 @@ import ( // ExecIn uses an existing pid and joins the pid's namespaces with the new command. func (ns *linuxNs) ExecIn(container *libcontainer.Container, nspid int, args []string) (int, error) { + ns.logger.Println("unshare namespaces") for _, ns := range container.Namespaces { if err := system.Unshare(ns.Value); err != nil { return -1, err @@ -33,6 +34,7 @@ func (ns *linuxNs) ExecIn(container *libcontainer.Container, nspid int, args []s // foreach namespace fd, use setns to join an existing container's namespaces for _, fd := range fds { if fd > 0 { + ns.logger.Printf("setns on %d\n", fd) if err := system.Setns(fd, 0); err != nil { closeFds() return -1, fmt.Errorf("setns %s", err) @@ -44,6 +46,7 @@ func (ns *linuxNs) ExecIn(container *libcontainer.Container, nspid int, args []s // if the container has a new pid and mount namespace we need to // remount proc and sys to pick up the changes if container.Namespaces.Contains("NEWNS") && container.Namespaces.Contains("NEWPID") { + ns.logger.Println("forking to remount /proc and /sys") pid, err := system.Fork() if err != nil { return -1, err diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 5d47b95057..6b05905133 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -29,9 +29,11 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol syncPipe.Close() return err } + ns.logger.Println("received context from parent") syncPipe.Close() if console != "" { + ns.logger.Printf("setting up %s as console\n", console) slave, err := system.OpenTerminal(console, syscall.O_RDWR) if err != nil { return fmt.Errorf("open terminal %s", err) @@ -51,6 +53,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol if err := system.ParentDeathSignal(); err != nil { return fmt.Errorf("parent death signal %s", err) } + ns.logger.Println("setup mount namespace") if err := setupNewMountNamespace(rootfs, container.Mounts, console, container.ReadonlyFs, container.NoPivotRoot); err != nil { return fmt.Errorf("setup mount namespace %s", err) } @@ -64,9 +67,13 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol return fmt.Errorf("finalize namespace %s", err) } - if err := apparmor.ApplyProfile(os.Getpid(), container.Context["apparmor_profile"]); err != nil { - return err + if profile := container.Context["apparmor_profile"]; profile != "" { + ns.logger.Printf("setting apparmor prifile %s\n", profile) + if err := apparmor.ApplyProfile(os.Getpid(), profile); err != nil { + return err + } } + ns.logger.Printf("execing %s\n", args[0]) return system.Execv(args[0], args[0:], container.Env) } diff --git a/pkg/libcontainer/nsinit/nsinit.go b/pkg/libcontainer/nsinit/nsinit.go index f09a130aa2..c308692af6 100644 --- a/pkg/libcontainer/nsinit/nsinit.go +++ b/pkg/libcontainer/nsinit/nsinit.go @@ -2,6 +2,7 @@ package nsinit import ( "github.com/dotcloud/docker/pkg/libcontainer" + "log" ) // NsInit is an interface with the public facing methods to provide high level @@ -16,11 +17,13 @@ type linuxNs struct { root string commandFactory CommandFactory stateWriter StateWriter + logger *log.Logger } -func NewNsInit(command CommandFactory, state StateWriter) NsInit { +func NewNsInit(command CommandFactory, state StateWriter, logger *log.Logger) NsInit { return &linuxNs{ commandFactory: command, stateWriter: state, + logger: logger, } } diff --git a/pkg/libcontainer/nsinit/nsinit/main.go b/pkg/libcontainer/nsinit/nsinit/main.go index 916be6624e..df32d0b49e 100644 --- a/pkg/libcontainer/nsinit/nsinit/main.go +++ b/pkg/libcontainer/nsinit/nsinit/main.go @@ -5,6 +5,7 @@ import ( "flag" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/nsinit" + "io" "io/ioutil" "log" "os" @@ -13,14 +14,15 @@ import ( ) var ( - root, console string - pipeFd int + root, console, logs string + pipeFd int ) func registerFlags() { flag.StringVar(&console, "console", "", "console (pty slave) path") flag.IntVar(&pipeFd, "pipe", 0, "sync pipe fd") flag.StringVar(&root, "root", ".", "root for storing configuration data") + flag.StringVar(&logs, "log", "none", "set stderr or a filepath to enable logging") flag.Parse() } @@ -35,7 +37,12 @@ func main() { if err != nil { log.Fatalf("Unable to load container: %s", err) } - ns, err := newNsInit() + l, err := getLogger("[exec] ") + if err != nil { + log.Fatal(err) + } + + ns, err := newNsInit(l) if err != nil { log.Fatalf("Unable to initialize nsinit: %s", err) } @@ -46,7 +53,7 @@ func main() { nspid, err := readPid() if err != nil { if !os.IsNotExist(err) { - log.Fatalf("Unable to read pid: %s", err) + l.Fatalf("Unable to read pid: %s", err) } } if nspid > 0 { @@ -56,26 +63,26 @@ func main() { exitCode, err = ns.Exec(container, term, flag.Args()[1:]) } if err != nil { - log.Fatalf("Failed to exec: %s", err) + l.Fatalf("Failed to exec: %s", err) } os.Exit(exitCode) case "init": // this is executed inside of the namespace to setup the container cwd, err := os.Getwd() if err != nil { - log.Fatal(err) + l.Fatal(err) } if flag.NArg() < 2 { - log.Fatalf("wrong number of argments %d", flag.NArg()) + l.Fatalf("wrong number of argments %d", flag.NArg()) } syncPipe, err := nsinit.NewSyncPipeFromFd(0, uintptr(pipeFd)) if err != nil { - log.Fatalf("Unable to create sync pipe: %s", err) + l.Fatalf("Unable to create sync pipe: %s", err) } if err := ns.Init(container, cwd, console, syncPipe, flag.Args()[1:]); err != nil { - log.Fatalf("Unable to initialize for container: %s", err) + l.Fatalf("Unable to initialize for container: %s", err) } default: - log.Fatalf("command not supported for nsinit %s", flag.Arg(0)) + l.Fatalf("command not supported for nsinit %s", flag.Arg(0)) } } @@ -105,6 +112,23 @@ func readPid() (int, error) { return pid, nil } -func newNsInit() (nsinit.NsInit, error) { - return nsinit.NewNsInit(&nsinit.DefaultCommandFactory{root}, &nsinit.DefaultStateWriter{root}), nil +func newNsInit(l *log.Logger) (nsinit.NsInit, error) { + return nsinit.NewNsInit(&nsinit.DefaultCommandFactory{root}, &nsinit.DefaultStateWriter{root}, l), nil +} + +func getLogger(prefix string) (*log.Logger, error) { + var w io.Writer + switch logs { + case "", "none": + w = ioutil.Discard + case "stderr": + w = os.Stderr + default: // we have a filepath + f, err := os.OpenFile(logs, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0755) + if err != nil { + return nil, err + } + w = f + } + return log.New(w, prefix, log.LstdFlags), nil } From 0e863a584a6edfa1c3ec383c586b646663b66bc7 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 13 Mar 2014 10:43:15 -0700 Subject: [PATCH 093/384] Add stderr log ouput if in debug Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- execdriver/native/driver.go | 20 +++++++++++++++----- pkg/libcontainer/nsinit/command.go | 3 +-- pkg/libcontainer/nsinit/exec.go | 1 + pkg/libcontainer/nsinit/init.go | 3 ++- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/execdriver/native/driver.go b/execdriver/native/driver.go index 989f2ff376..9b49fd156f 100644 --- a/execdriver/native/driver.go +++ b/execdriver/native/driver.go @@ -9,6 +9,7 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer/apparmor" "github.com/dotcloud/docker/pkg/libcontainer/nsinit" "github.com/dotcloud/docker/pkg/system" + "io" "io/ioutil" "log" "os" @@ -28,8 +29,7 @@ func init() { execdriver.RegisterInitFunc(DriverName, func(args *execdriver.InitArgs) error { var ( container *libcontainer.Container - logger = log.New(ioutil.Discard, "[nsinit] ", log.LstdFlags) - ns = nsinit.NewNsInit(&nsinit.DefaultCommandFactory{}, &nsinit.DefaultStateWriter{args.Root}, logger) + ns = nsinit.NewNsInit(&nsinit.DefaultCommandFactory{}, &nsinit.DefaultStateWriter{args.Root}, createLogger("")) ) f, err := os.Open(filepath.Join(args.Root, "container.json")) if err != nil { @@ -87,9 +87,8 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba c: c, dsw: &nsinit.DefaultStateWriter{filepath.Join(d.root, c.ID)}, } - logger = log.New(ioutil.Discard, "[nsinit] ", log.LstdFlags) - ns = nsinit.NewNsInit(factory, stateWriter, logger) - args = append([]string{c.Entrypoint}, c.Arguments...) + ns = nsinit.NewNsInit(factory, stateWriter, createLogger(os.Getenv("DEBUG"))) + args = append([]string{c.Entrypoint}, c.Arguments...) ) if err := d.createContainerRoot(c.ID); err != nil { return -1, err @@ -254,3 +253,14 @@ func (d *dockerStateWriter) WritePid(pid int) error { func (d *dockerStateWriter) DeletePid() error { return d.dsw.DeletePid() } + +func createLogger(debug string) *log.Logger { + var w io.Writer + // if we are in debug mode set the logger to stderr + if debug != "" { + w = os.Stderr + } else { + w = ioutil.Discard + } + return log.New(w, "[libcontainer] ", log.LstdFlags) +} diff --git a/pkg/libcontainer/nsinit/command.go b/pkg/libcontainer/nsinit/command.go index 1d7c591ee5..5546065b6d 100644 --- a/pkg/libcontainer/nsinit/command.go +++ b/pkg/libcontainer/nsinit/command.go @@ -1,7 +1,6 @@ package nsinit import ( - "fmt" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/system" "os" @@ -26,7 +25,7 @@ func (c *DefaultCommandFactory) Create(container *libcontainer.Container, consol // get our binary name from arg0 so we can always reexec ourself command := exec.Command(os.Args[0], append([]string{ "-console", console, - "-pipe", fmt.Sprint(pipe.Fd()), + "-pipe", "3", "-root", c.Root, "init"}, args...)...) diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 074492ae31..61286cc13c 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -26,6 +26,7 @@ func (ns *linuxNs) Exec(container *libcontainer.Container, term Terminal, args [ if err != nil { return -1, err } + ns.logger.Printf("created sync pipe parent fd %d child fd %d\n", syncPipe.parent.Fd(), syncPipe.child.Fd()) if container.Tty { ns.logger.Println("creating master and console") diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 6b05905133..e165de3a8f 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -24,6 +24,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol } // We always read this as it is a way to sync with the parent as well + ns.logger.Printf("reading from sync pipe fd %d\n", syncPipe.child.Fd()) context, err := syncPipe.ReadFromParent() if err != nil { syncPipe.Close() @@ -68,7 +69,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol } if profile := container.Context["apparmor_profile"]; profile != "" { - ns.logger.Printf("setting apparmor prifile %s\n", profile) + ns.logger.Printf("setting apparmor profile %s\n", profile) if err := apparmor.ApplyProfile(os.Getpid(), profile); err != nil { return err } From a41f6d936754f66d1786fa5b840278443da8d93c Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 14 Mar 2014 17:35:41 +0000 Subject: [PATCH 094/384] update godoc and add MAINTAINERS for mflags Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- pkg/mflag/MAINTAINERS | 1 + pkg/mflag/flag.go | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 pkg/mflag/MAINTAINERS diff --git a/pkg/mflag/MAINTAINERS b/pkg/mflag/MAINTAINERS new file mode 100644 index 0000000000..ceeb0cfd18 --- /dev/null +++ b/pkg/mflag/MAINTAINERS @@ -0,0 +1 @@ +Victor Vieux (@vieux) diff --git a/pkg/mflag/flag.go b/pkg/mflag/flag.go index fc732d23a0..ed6fad3b46 100644 --- a/pkg/mflag/flag.go +++ b/pkg/mflag/flag.go @@ -10,7 +10,7 @@ Define flags using flag.String(), Bool(), Int(), etc. This declares an integer flag, -f or --flagname, stored in the pointer ip, with type *int. - import "flag" + import "flag /github.com/dotcloud/docker/pkg/mflag" var ip = flag.Int([]string{"f", "-flagname"}, 1234, "help message for flagname") If you like, you can bind the flag to a variable using the Var() functions. var flagvar int @@ -23,6 +23,18 @@ flag.Var(&flagVal, []string{"name"}, "help message for flagname") For such flags, the default value is just the initial value of the variable. + You can also add "deprecated" flags, they are still usable, bur are not shown + in the usage and will display a warning when you try to use them: + var ip = flag.Int([]string{"f", "#flagname", "-flagname"}, 1234, "help message for flagname") + this will display: `Warning: '-flagname' is deprecated, it will be replaced by '--flagname' soon. See usage.` and + var ip = flag.Int([]string{"f", "#flagname"}, 1234, "help message for flagname") + will display: `Warning: '-t' is deprecated, it will be removed soon. See usage.` + + You can also group one letter flags, bif you declare + var v = flag.Bool([]string{"v", "-verbose"}, false, "help message for verbose") + var s = flag.Bool([]string{"s", "-slow"}, false, "help message for slow") + you will be able to use the -vs or -sv + After all flags are defined, call flag.Parse() to parse the command line into the defined flags. From 123ebf905367f1da0d9480153d08912d58b721fc Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 14 Mar 2014 18:16:14 +0000 Subject: [PATCH 095/384] update TestCreateRmRunning Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- integration/server_test.go | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/integration/server_test.go b/integration/server_test.go index 54ee9a77a9..d5abff264e 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -210,8 +210,15 @@ func TestCreateRmRunning(t *testing.T) { id := createTestContainer(eng, config, t) - job := eng.Job("containers") - job.SetenvBool("all", true) + job := eng.Job("start", id) + if err := job.ImportEnv(hostConfig); err != nil { + t.Fatal(err) + } + if err := job.Run(); err != nil { + t.Fatal(err) + } + + job = eng.Job("containers") outs, err := job.Stdout.AddListTable() if err != nil { t.Fatal(err) @@ -224,19 +231,24 @@ func TestCreateRmRunning(t *testing.T) { t.Errorf("Expected 1 container, %v found", len(outs.Data)) } - job = eng.Job("start", id) - if err := job.ImportEnv(hostConfig); err != nil { + // Test cannot remove running container + job = eng.Job("container_delete", id) + job.SetenvBool("forceRemove", false) + if err := job.Run(); err == nil { + t.Fatal("Expected container delete to fail") + } + + job = eng.Job("containers") + outs, err = job.Stdout.AddListTable() + if err != nil { t.Fatal(err) } if err := job.Run(); err != nil { t.Fatal(err) } - // Test cannot remove running container - job = eng.Job("container_delete", id) - job.SetenvBool("forceRemove", false) - if err := job.Run(); err == nil { - t.Fatal("Expected container delete to fail") + if len(outs.Data) != 1 { + t.Errorf("Expected 1 container, %v found", len(outs.Data)) } // Test can force removal of running container From 50b12708e35c300d93e8f8e8d21f79101fc185d3 Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Fri, 14 Mar 2014 14:37:09 -0700 Subject: [PATCH 096/384] Updating base svg to remove references to aufs and lxc. Exported new png's from layers. Docker-DCO-1.1-Signed-off-by: Andy Rothfusz (github: metalivedev) --- .../images/docker-filesystems-busyboxrw.png | Bin 121141 -> 113106 bytes .../images/docker-filesystems-debian.png | Bin 77822 -> 64585 bytes .../images/docker-filesystems-debianrw.png | Bin 94218 -> 80992 bytes .../images/docker-filesystems-generic.png | Bin 78384 -> 67894 bytes .../images/docker-filesystems-multilayer.png | Bin 127744 -> 104199 bytes .../images/docker-filesystems-multiroot.png | Bin 72247 -> 63920 bytes .../terms/images/docker-filesystems.svg | 73 ++---------------- 7 files changed, 6 insertions(+), 67 deletions(-) diff --git a/docs/sources/terms/images/docker-filesystems-busyboxrw.png b/docs/sources/terms/images/docker-filesystems-busyboxrw.png index ad41c940e4767ed22b89923af94f564f8094b7e6..9ff8487b897c617371105e795d4514bd8cee7332 100644 GIT binary patch delta 59052 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

hWDvjzNL$i?-M z1}q4|U>yk@5c#Jo1oa1a6@XaGJNBP?ZOg^J^N)ckUXSL!ooXCQtsqw-B&jh>XB$8;J!5tZSB5b(kX(fNtx?U!bTOH1)m+# zMibVP^>b<#_NnC=1mfn~zH^dkv*fe5@SJMoF5igKQ$sx8oVMF~0T^oYrJ-c$_n;Iq zoe7`gjbXu+?*5HMV~(~obhI%qWhL%SlctUAIBw$hbIr7c6adEW^{wJ1&`LqO% zj%=83z1t|6>{C>|cB0|;iomAM*6G>t89wp#Q)q%agq~I3aaAsiJOM_TJ6#uyd_%Oy z7i?PlqqME75M1v;+y3WB)xZ+eAMHxe|LE8lY$nOw9As$#*?5b^8~nX z|GzMSF^Wh+wrHpR{`_b1z!o@}^g-`Idh=>3ASU+T%=f8@CNUJ*p6Opve&%82rM?U4 zEeJG94fw9fkIzR~V_}(6hl*(FSv|8UgsoLw;A&Jb4%6r`ITwRGq-Je$w?!`$W|Hv# zi@YRKMT|zu`vbo5zgsR%o=-bq3t zi3x{rp;4j{h%w?*w$Z29wzc&b0rJx>rH^U!=+1}?bB|x{};M9f^EyZURrB4Uu zt8<6@02Ef9S8y^IdTR`PweO)5|L40_{jU2B>wZrhDdOgc#6l_jcV@+VF_HQ#N5+lA z`OT8irH8dGei`l3j?V^}6@%A%-JbyTV2``)x}jsPt*x`nrT8K{?C21W;_AdU$?LXi z!N}8E_DS0LLi)|=%2;e6Wt`ZQB>F7{dj>7AxS)18AIXX2HPZ(S>2^goiI2&JGlBZm zLI+S7O$ep^#7)HLfun+1e*2%a?zq5t=^YDOMsTz4xI-`bM6+@abySE7F+R zxibbmV%NfJ|AQZ@TyDU5)=u+hw%6LfdwLbNdCTK@9OCE#2?TTT7rVCC4bW0zg33CA z0{4(GwPH-f8kbLX zi^V^~hY6nwWPz1tjPwsIk|;FHEW(C&UJk1II1RFaO#s%Swm3ECu1FUgCUqp zP57bDqXV^W3|^JtL{y=yYe_4&f4=lf_&uoIU!H|$wo4G3Vs1wQyI0|#&92NtOPUY- z0avnh-=;Wj0+zF~SeJ((-W>0Pg*CRwnYkTme4G=l2qHozd0L`Efm6K_sHXGj`*GBf zLdFyB#~@!x)%qcA5ORfD5-VZTBAXjHO^T)xfv|OTD!sNJB8mW9R5SB@W62-+fVIK7 zWPg>C#@|lX@8Su7DOX&h$4d6>Qm<2Z)k`(AbI!!9g*b3&&sEb8i1%*gztiEwdY5eP zZ%4!l&_qHxlSN@W%P>+2+Q%;M=Dux1hu`UnM!EE59px}ydH+~Qmz1N(EV>GqVbQ!6 zM1Rh0riH2)&|8j}MQ8t(HxkjHRdsJ`WIJWA1Lc?D(trU5(&WjZV@uo4(Ew+@YrEgK@NsH@Ou1I8L!F80}-?00+Q<~=2sxGG;3EPTPoA(f;A%P{Ps!AC&*`dYy z;G7R+wWuTiVUE2rUCVu&%%TG&a9rk%_olj?FbA7!K)PrSK6PLbUGog2-qKHf-ZAD| z1Dr}#5RhtJOGcuVf=^vWkf=Qg66d6XYYsrYb->+>g~`@sKo~+xVBnR(JjJi4zk8#I zP#$`{pVWkAIx@8gwnpcBBSH};9i|@6!m9-3zETukuC;NwN92VNAG*T#U%Woq>N^a5 z@_Ji*?ePpds8!~6pXTnbW>7|F?PTZ6|Z3RDkCy2&1rQjwsjcuAp8F=KI*5&Lhc|W1eu;F^y;&Z&b4M&{J(L zsE9graGp@Zf?H0@0!LG1yLo^Xmur*sV)f_}j3~ssfF-L%6WJXl}z?@XWI+Bi(x;or+pQ{bwI9k{2i8G0(Sm34v8Rcb;6a<)mmxRP98-K8V=( zTR?YE&{Yx?KcbUg#%%1hZx**lIRgH{3%y8c0_-InpO{+1!KLgssT_V$$-4=EguMvI zd8--48X^(yMa0oYsm@3-Grp;4u6t$iuiRs>&RUL2MOmz0%4XTq%%?#OjblLlg$$dN;Gla2nL|PKnT={fft@U@ysCYBV^15fF z^nm%ET2@HuiaO1OEw|D#5fZbbp@}sz5#lb?j}U8z8E*P3KDCD{u-Q64u+1K?&|zIU z#*SxoR9>&!OOH|`oEDfDe@tthNTtm@hV!M|T24V65BhxcDsJD=1Q0oDZi_xy>>nJX zv1rLKGwr_BXl*NLT8d}`M1VihOxDSS=k~O78 z(4?6~N=?u3ESSgK%31-{sO-jTu) zbZ2LUCe*=Jd@BBG>uINveh=1FS^@Y*Fr4&aR>8!==aIU_l=(nZ)lE1|+dPBpbZ~TM(7-tUw-G*y&HPA*XXV)M0uDdn zaeFA|kxmi5S`%xA6aSIozyF&oU1d&EmmG}K_2E%CwP52x_-J@UcXi$ zVKnE=W~dzgfEh>i$<@a{w$IYgVa?9t0hr!pp~_nn@9x|Gvh05bj-|MPD&Gn{D+biX?quZtC@hWYt#h-YQ-P)g{|gIt*uaf@ z&d&%t@mF&qGK}H!;$;oZ>ZE-d&o)@Jb3AIRIg7A1R0CQ*C;oWj4HTp;1ke(H^0wv9 zXrJNsT~bf41e`uL-LGMtM$DmYB#-in7stX6aA)!2?`cI}#HBqM=lZPfr2q*yQy`8q zomO|7FxMiVmyF>ls!ZYH>Jq~p1#F%E4R3o)61?s3I48GsXlz8(kK8%@DdQU(AZ0Wa zU&>Wru(i!gus2)GX(Ck)C=Izqkrj?dUn7|-GeDTb=6F*?#S7K8RL}loipoLyKWqCI zlGEKX6j7i&J$UlED)z-dtg*q*z}dK$#!rYEf4H}*whV;l2VbQ z>7FTU;)Fl-U9s3s+M)IfB(5IfpIOf~|X^%vd1%XqcJN#5d4WF1c?J%ca45LriW4;?oU(J5D%MGN^ z`KW)porZ;@``f*4;lu`)q3pzpC4bgeKZv>b;mK zYj0_t`yZ_nJ;4YS!}i)C$0;ytH-nT3vP@c{D-%io`A^ROa;x>DxhXDhxGB0=LJiry z6W&hHZ2n%UQu)wpv)zoN0p23fNip4}ID_^zH1w8x03hGE{_S&SlI6tV$l7_{1_!ngg}J1+bqc^t}xE;+nya$#-{6&5_x>Fw+qUf{*JRgZ+qUb}WNWe}O}6dHoNPDQ)_XtSwSMnE zaIbUr+55Uay7o;oy3VRGYNND)p=8(ed<-A$uORC7ghHlTkwV^wH4u`#kTSqwXaoz} zn*QH_Jn$^XiBkPZs`vSAwYloI{SIVl4P!xNeP(9?`p;%+Sq%Z%(J^3#+Xos{TK)>{ zqPOco0k1S66dJDnE?L5X)upI1k&E`@Mk~7MCVv9#gypky0^asu2JOqiBDsFQ`C&Wn zBjoM=RF7f_q_^3&{ak7>YF8bs6~BDp?Q@UF0+pPK-ZjNaLK34QTgNoi#FAd;{(eZ5jA_HZA9ojK zTIIB55?t)7$L4pb&R>u8QxnH)Auf`Y7GSv*0u>kvP=RS>#TCZ*Gd*a-*EdUrGSC40 z28&MWQ@6@zZR1}`=x{{>4Khu--&$w5V0myJ^nhj#p01qjc0}igGQSQO`Dmx0!o?2n zjBMV$+aqvyZ@`Z%63LwuGv) z*rgJs4_lwq*?O>8eh&b`j@K;;$F9^$4z4`27B@?tIXjFw#GsS4D|u;YB>A)VhrS7G z2S*a+p%O?3pJu6dN=*OC_|E0mDq&{VY2P_rp}a$bd0RQ zSIXufgAEoTTQ_;*n1OWLSdET#sg`q}9zrdMhuAk~lNy&zRi(Vo1 zLqLJsiXkXLDFw=HP?k%&3r>Y-Usa^-EF8_XSe6HZ#dKk62r~6UZoP+^S*;(YjM~T= zL>XO$&+1D}c$tl%0M{}KKR%CYb~5>=?7$3YD7S|;s68i~rV_xMB~T;}`!V{TA6!Y0pHH}UupV}u+nLir z`d4sjt5^dvoGPPGLFT{nDUTs-M$)eQm0t44?5lqkSsHxA;D6(4r3|8$({|;AFQ9KTW>Fog^+iPL zv}W{kXR8zjApX)1aZe(^+*K6x7}z(M$f(Q3CByy9C$Wpr;0~ma!4^MzW2T~r zg2Nl=%J$jH$*9E@wOv4)U>xm~=kK$D22GGWf0F=Tv&=7flmh2TKy!x3n-9*c?q1s; z`$Ern*$pC%_U<$yH}DdnmTNX>R!?V!+hHj82QAJzPv@EzVF+ouaayHOgUeAujX&JK zTxjnu8g_%Sp4wgAHb4q)8HZSM>L&C3s_32NKAI!6kW}I6_|G2n)#K0N}S?S;Xt0)=^=m|Dia=J z-!Aw)cXlf;E*Qr3^~PL7A^YDZl&lJrN}#l!R3Cf)C~$H+YsC2{r!Y}z$IRV`sPSp+ z#_JLvjauu{^!`LN9Rp%81AgRrU=b^}@z{7@_Ua)hucPlCVb|T)qnDJP$2F`kDw5TL zX@7*|(V0p{l#k_Um&kd|Rlm`)O29G$1$=zQrx&Qb%}iVmNNfaj#a`Kc{JJWqjfCl| z3f<{8fQpbCXbCq&gox(ydfL%l2K2^-Rua&ZKXz!K7p4t)PFnjf+Vh(H#-7~Ih53f_ zD68333^U4T#~aR9H@ccVK(|2KZ%kxbme1v-tY#n^CJCE0Q(jyW4=$ej<`vu-`fnv} zXNo~^xIu7BNR!y9 z7$VUZv&XuZ?*A8&L|9no{z7308VRh`|GAz(lU7DS8l2o&a4HP(kb(Jkv;uWv`sN$L zjt)lw{~maulEJ zlWqW)4cC=NjA?2-%-iZraBfARi9W^>*dG4L`IJezrN~>SGEt0%1}kwlXQFW7Ub3SS z5hs0l@%Qgck7Dv34T?#bhg5Dc8(5RK0BpM3Os~@Y?ngtxg4NZ-eBCiTXh|qzVeUSA zlPew2zq61V&BxI$>a(KbQBhW*iFW^!sJQF>cy3(Vxg56e196=-XS!-;eCopH}N_@b*CFO!r~EGyJ&I zvswE)*lDib{s=A_y`>2!|Je^x;YgyEku`}n<>|yHwCQb5S&45b<&u+_wK*skMQy!i z0<<*qj9%dE3>M17t8`Z@1HwQRwYE;JNJM?SY;^f_{axzDmVa zZm}O|_m=SoSg=4y?F-|xj)eY=$7ehBkkoT?EPDiWV*z(nt$8; zrIWtoU}s`uwN&)6-qTL-G54QLr0X_Kt%v9H%g1aI$wcqsx2w-wYeHAbF1H+qCZ!2-^v4r@0pdlN-=eS86H(t8DEM=#1HE_@b59H{mo7Zn9s%ZtG= z)@Pr-*`2mUxAhj~fi3@6)o2aGMFt8?oCM0ep3uXf#L=7(=gl6*FX=6(_p*_Fehll$;Uc%|o{Ntm__u1MsER&`#(OTN%`N^Vbhy&Z9E)tG0 zc?>GsPmchSB+3#a>crZBjv8sbnxEq@?iiw+0tn%t>y+j*uoS|F(CoT#L)k3Yc^l%x zP@=7^0e#scWxv2CsG2VOXtO$jo^zY^vK_j}PCQOOLxs0&123zBy}43NR}R_Suz`s4 z*+y;JL8;9c{6CAH-<89WZk6y$xtWq=0g2j!`b6pAC%|3poHh&c&wOZbUUZ-sXa_jr zU9nLa9%-KCX#^MVvvECjrtz2bkp6X!>2ii>)} z8gua>F7Z}8H=fGT)hMIGyzK=lbym>Fn`l_cNt3#k6maED7QYZi$K4ssRJNv{)1HXi zC5Yefxg0^g1%PSf#h>?E{tLzXs@V67~@d@M`pNIp$&G-jOc zIk(EBEc3#sZS~cOl&W0K`EnPatl_#@s;0yivi_UI>+9iQ(1!jx1oPFnAE!0|+Kq_% zkQ!ZX;kT1ha4TKe1~I#N%V&4s&?3YjDpHz~7BRmnIGm>Z!U_f{lE4SzLlVQ&wwqv8JjP3Hl4xH7w@3s$d4 zVY-JuO>Z^K21y23*d6w=G#h>)71#G6Q1*gPrwKt+C5YnUx;?pqIbi_{?v1o%a&Dqe z$G(!1=Mjg?_GBI*U~YDKS#(H^v}EN(Q0&w$;OH{+)a3Ywxgljz+igU;&W?tLnZPZQ zdS?68k3wE$K)p_ewvRIz#{<_6?36HZsB>Bj$Uj4Xeb6(xCZ)C!x{r7Spo27Ed-~j7A2*p6D}V&k z1x>#{r_3FW9WU<3kSLj6%b!VAA^}@yKvOOo*se=lz<#@_E9y4SBwY~4l33({NSzqV zc@4JSV2WAx2weTr2uJ3;m>uYfPVW2;f)#bqhJRuv);rSvh9k2bMzgSrOAT*YZS{~h zy$wv~FMNGao!%3@1c$VU6iDb;8kF%1o3SFyR3d$VMFGO+gKTlL88z=-h%~H*OZ*^(#BB3#)e#e6>oUxOnfkR!<;N2 zQ-Vg)LCWrSG&#V7qNMUi(TAQ||2m|mXi`q(p(6=a|13J5 zZX=a@2QX`gi6@n>e2~Pf!K9sgjafD7&~w?1NWH%nE{*>clbItIHcsN9d%x&l6bQ&J z2HuCptpBpE7+A+dg{UV>$fya@82t3gmS}xN?w= zl}n`uuP3ysY)n4&Pz{D>KLY-v9)Jh(zn$vV*5BuHP)*z6)A%-d|g`+;`A7)tL?uB~~ONstMZz>^?FHGHv zcHL&bHbKKxN-;*q))e{QcelvKa=B0vD2VRL5j%u}@FvA8QZPx9zUF-9_G+H~xzcC@(sx;)7F>dWM}s9^Qo>)?+fg5)~Y;hg3wu zXl%ag%D#cZ*uWA-yC!Cf;F5&8*$}U-6kbEh<&G|S$TSOSl1DKYsMFCjIG%BsD-khT zUyha%%~ta4{hEdyA(n!cu>rPaa1l`=Om597&}M%vSz{Upo%D;9V5!G77mF`Y5bHaj zkJ?TKr(F$y)ACph*tSU3YkTtgr+ky*k~j{kVoF6|iIlKi2GjeKS#7?1(C@k})Z_vJ z50XJ0JFo4^9Yl?nIFIsDdixCEd)$4UiQ6gAHjQDi4GS$y+AD4T^gyV@2*&H0gcM&( zj~l%!%`v9f5n6top%PSxxM-Ss!W4oIrqabikzV-`RtpS^@gKHy4@?JI$#4VhXUT!P zLQo@%ERYm>D*BP?dF!LaD`tKbW=L>its_4kuUpXeuJh?rxe8bOlYspQFV0o77pBdT}@7`WJ zfhe+qN;JAaw8nziZx4Q;wIr&Yhu%u5*4!ym4NS*tlY>XK1V#=Kgu7AREBao>O|1OK z)olx@Z{hkMY-<@tq?vnQ`8uoe#qd!4IvERgLH)|Z^KpMy3`5G1fHz<`q{ZONGuCJU znF2ilc-uW<7>sy5gEV4dmTqwYks7~%TR~O1J>8B~iVszq_^wX@V(m7jybxn~q>a~a znSt<)4%I??K=+YZk-YA|W|XGfC4*7^)w;iNXJDn|>GuJ(ZB9}if0i`y9= z7LI|?l?K~RYLiXUfp-_oU4+!KA+bckDNu6m_^2`zrG>-=ZlVrece_NE;Dxz)}! z>3i>Tx=NYUnqcaKo;GLtPdPQ4PvT?OgE?8a9TLO$+vgvr59M8yWoNL?t+|`0LLuNf zi+5C)(2b2x;9uuZ)mUnpQqEdlCYVg#ii>bcvk~31xuZ)Bsk;g*Moa}v%WHo&wlbaO znSFo3p#6q~RX1xUwJU{|5B;36oQmur=+h9TiK|%oeKGZrx}{aqbBo|kH|aPY+Aa>a z7&7`!usjDPR7E-zE^acuNibtab_c8!F$;0xBORR1@1)tU6+I;sNt;25CsDq2n!&Lc zcJHr+A$eb`9qhl`kRsZ^r4aND2b214h7d^?2Gsz;kC_o4sZs8`j{ztwBl=p6h7uuB z^wNFoi)$f0Atj2dB178XGVc)k+f3hY(t;e|26R#M$D-Ei_^+1GwA@ycC;$j}hAkLY zyN<|NehTaFq5~z0I?vQJvy$np!kM8oNMFK(!WDzzkf@U*tzbmUt+2nJTWJu^`L;Rt zf$+`l8Kg6s)fUq08fLX}P&I{Q0(W5)U6go|Xaa3#dk7sL{Ne|aVHeD) z<&VBNe<_vkzZwrda@EPTzK^zfHAa>B13Cxs+9$Tf*2=Jo0pR9Xyg0+^>uFSQG{Il* zsv&T?gR1lgfA!eMNdR7rr2<6VP+ebf%LJ&Bu6@b92~Uh7$Ri^o!i?7nyk6sBby#8! z*CDSvXTLzh^cSje<7yKi2OyiC$X=5rE>rLzN?wHc+!|F8T_=Kst{NJJg3nHG6+dYe zB#_9pE_tgYWbAB2dSE-RyhXZCH-Qh)W!t|qSuNOF$S@=e4xry4YJ8e|6Qa=*c5XVk z6a^noGRDVm$aFV#PFZm(mw;8;!F=25+s5DBGKgFmd+d`-_nsg0RW1D2&Hf{JNu54M zM!Z)Y66r`><*fzw5C$JE8V(_bGrcwqCw&ovuM^3ZulEFbrn_-OIZ>Lj4kT7oYj9Z( ztS9g81~UmA19R8c31o=6O;P;^;~hXEx(s;&lNNf*-_L1bHvCw>wN^G1hl;_5&1aN9 z0T5IbA}z)R67+R$MD(ICUngVVb@J;A6iy`Mkw(XSZ)7rTF)1V^_zz&DVIS1*jGzF!1!RTE*nNDx7vQ%2OFBgQ@rjv zDRGAOYTj04kB1Reyux5jWu}$3C=qa`L=VS}slBU&MfbC!Ay^t1kABhbP-&6F`LBdJ zlif@QQEJcFG`+PsQ=nXK6gu7>hJ=?RTILCYz2l306$daK% zhsvt%ee&;5(nYFLluG%nVEz97Xw_671V|*cCxXsurP*X|P8$r0i_xvc2QDjEVc+j5 zJNEu5b0;Gr&R2*`fQ(%PU3LoMWP0h`h)IO9eE$K%FfTM(gVcU3BPf7^HWBN??u+me zTAwLE6x8brDnNhSOsYCyZCNB$DP1{9(iUdQ8)1BCJ_jzk0*%2F&z`0+_q=`s--7pQ zMFM5pKw{cAGUF14MCskb5qpqYqPSYdXv>K&t6rmcL?(81Co{Mu97zd9_9-r{@4z}Y#oh(N(>qutU%dWbXi!Z!7VzU~i^c?z zZc6m8WKa>566`h~fFF5(|5LlMSS}3A@aFcaHtqwOS|x%$wAj>-Na24lRXc;o%a1kp zw~koBGe_h@??}UX;NZq6+9C$mifm^0q2LQU;Wo9`^`%jE?I!*|Osf+J+mM1DW5n@| zR`7z=wTwW!QFqI(d#1do)Pq(S0aQ@?U{&M$i^LY=WEXCL{!64#tVT*}Z)|W?lz~o> zQ=4M+o{0?W&%OAcr7A-pRw%t51CmBCqct-*<+s|BD=ep2i)=LuC@4%F-P|frk?#2i zr2H$zK6j%f5wvFjz9H72q(c=7DLw@lQn(Z~tpn((ey0h`QWZgeBnHn{F`Vak zPk97Xo&*5UDo^s1d;H_P3J@aWvpI~Gp`jah*q=IxCqf%Vw zV=m^Z#`OkeO?82al$EM%tE0IR!Q(^H&+(C*lih)d=8D>nB-ASOC1cJ=?uspxh-L<^ zJ7VJblbN7Df+p^Sc-X>XbkOPfm#cu@W z(FBbEJ{;ipPQG1SqZXi&F|2(XwHs~Wjy!!n4`fxGN(2AR z9V!}yThy(d?_}|>0}go|{ruZ;SCV~M4OGzM_K~2;lq^!Lq*&cp%CUeOtxy2qk|29? zNZ#R1RhGK7RBb-CSXFd=ufDbYW{i79CjSOayz*?j&1fKG^2h8(Xe5)t`BipEPqUT5 zTpmiSLijRBZ%^X0Lq$M_w9E6%8<{ua&Bytq+p1>l1YvwlI~s*!GU{6ugo(7^cKPel zQ@hhSoL?8R4pTFW&UUPENx-B-*589$r4ZGUWJ3>49_(f;2Wt|={Tm&2zbV23a`LC&-zjnh{BV7P32=y2y9Enf5d2*p-bzI_%J8Q=?SKkDI1>XZ~f=lz)xBFpWdDRP6=cv z)*QVrR#|UWr0FhECl*Qmw@%GoUfyh6q=Bbt%xtBmQsfkBKNu9x0(&=~fc%6)NDkD2GEzHV zu68Z1k6ej^pkJw2%I&Q>nyIM29cHq_Cf$KEd_5iOCb$|m;CU5!cLyD%w$-L%9xGHI zCF?etL>>^4Oj^qAl!&^TPNcBYuMg2S{bR(rszw(Bz5Ir9W`I=H9fl$d6OS42kUgO` zWe@F1a+OLl8VSvJe=hyuS*=( zni=`LlWbz~%N$W3AP(b$95=(eDnf%(8Gw;orF8>}MZ>7A2V0ik6Pq_wmPx))=#fSb zaJJX^`*;4hRk+&G3>g8_?uoD8IfO{iA!z_b_+ zcGb{A5_VGQv}EsCx8nW|0I0tR`Zs`#3|8DGgp;XIE}X&ja3;L>_h+9u%9rUZsfRb0 z2zhcD+IlFtn?LLU_cpo0-lCT%fbjkSLQ2nBno@5(DD**nbse9;gyzxVgJLyDWYeth zB$O2SBV&6C!L}u>*LQcnOP2WP(UdhxR@$ZRj-vGr7TT`C3ra5lFDh|W{Ll@AxZ2#?Uq9xAD-^4uB(@DggR()|fP zgy_RQ7p`Q|;!?N#hxw84|7&5Rwo#RV(wkOq>=tH=-TrSp?&UrSWW66vGZoMi1W@^k zdOzI6SMMTy0rr{pFKnS;6KR_Xu*v>MLibOr9q$_?qC)UQ2bPJF$9lF_!tlM(j>p97N+dvQ>C zjb~*d7IZSIQIlo#j3n3f&d$M5xS$WK#jOR%;6leChl&t~`00Hqh)pc4I6f`hf?R>%oO}WUypq+AH?(-8!%rTMa{?|4=9jkEE^84S$sD*DnF|F zbJajj?&wggW$leX(X7}b{6ouMFom2LdLKe)Ye6zOdEK2&f~#wIG^5t%C|DLwRaT5Q z6_jSr;3@OIS0t-3As;EdvnQ7vDlPR$$-;cM`;(ZJP9P)xm2v#6rVbmwO>z1pPY%jH{$;BT5(4`RhpsBSG*B#g z+{S(Wam|rFyu0dOKUufBqh?ij3D+Ea@Y1a6P$m@_t!T5w&*$r{yGkoWN9M)0dJ#f9 zng_@Tk!j>hPt1W`X9 z&?AZ5_=r@S?RHwYY_IN`U9tUqSnjG&fcsDy50!EQj-L&rHdP4M#+u4!M-Q8|>v_KD zn|V}WOZo>%toynl zsRIGl5~@lY7Ix9iFW1g2;uD>mAH&^GSJPQ(0j`HLzN?)7?YysK45a8Xg!>bDplk6- zERM?6BKZZ%@ zz86RN;?N}ir*4rur=sXI8qJO-&jWUwdj7#zX|-O2e)>orOrGadub3ezU~n?)-ShOc zXtPdl)vbo?SqI*?uGQpX_~ZWCP>DR_3r1E=(rO0|f;ft@EEE#T+b6eEmq*)7*PAxi zPB~+{AW&Hrz6s4eKsnrWas(!Nh6_vzI~=i)NjdSEPL ziXC$IL_5|kYEK3fhY8dF)?df;uk7U%Ag(9A@fiF_pZ03%jB8Qdl`p2OD#2w&`3Jv8 zAd<*)BVvFPL#^db_n-xc{$H4Y_+%o8&6iPBK5}y)*hjye)#FrSv%!;0hCHzq3WN=l zzTAx0n@_B^#5W$FoB8SqLIvygvE=o2k+xJb(al;7B0^DiLzoC%*BDAKzhx37JklMC z$eLmO$I?Z_x#HHbTb>f87#{d2(S0Wbpz~_bs#Hl8fjfqSuz1soJtvbfekKO`Q`T1+UB}2_PHoRQI#%jnFOUB|7po;7f3$FKwM2e#$ z{P%$3+gZ#+9FJ$l|0=cr37#RM5w6nyK(s^&POx;)H&iip7?3?fjNpAbwFeT*4Hzv( z9)jP1RQsRyS`X!<<5Qx=J^6namt7XxOl`3+T0tC|kjR*&{r(YNv#+ zU{#sA!^{3SqxJD|yV&X=O6e7gM-zN>m*UJnr)sds0^;)9@WIsz{{0Fr2%TJyr94{? zK=UEvhin=wSAw=4Kj?=knSY57MKSVt!~pT#FS3FoGpIPC`AS$d=eH>caSlX!>m?B#9qaq*`2x68@TFO&DVtQz? z7Tme~&03J^ZXszw@|2(1dMO__d7qA5?ddY?$TWpZWp<6_iuzC<8n^7KueI>o?FWzx zpH`z09h%aD?;V9@Ix|;%m(2=J((|>HkTS6d{lA*6CtX1-s0A{^+D;Z0zQTIb)01rl zfQ5f;bG2qZ(4H85-4oY9cKK7ppBIUSy48sQa5irKS^oj`z(8sN4x!B5;W`|EX5E%I z8@Y%*nkBU@__W3CyfqAI(S#bO0@!qB=`QbYdV@P-kxU4GUGdv%$)!&$>zIG-$A zrVzk~hA9odEyg4c?Vll1269Juzf;In>-jxCZr$Z@MKiX!w0Q=hM-E#5_pC{Q;K?)% zSA%f%hZAMpeH*r{530CxIulVv>=Tf=*61s<2^5lngzZeLbz=vp7lN|DPppJndWik+ zrM045s#7v8c}G!X>X!D(R5Ana2|riZIgb-csf%U@Vl0$};-)pa5lvphog1k$L&g7i zwYv*{$E$1}Vic+7{~r0B{W!ttUIqT+r>GUCyZ~u-750~?D@olsi;-TMLMaZf751AH z`hyP%S;{1@|4w|8_&+y*kJ^59a4?qOTGsRk{dEPhpXqekG1OOszSpY0F@ci1cwG)ZE zV?iD{CvyD$d*p1j{t7jMskB2tI2_PNF7mM97SNMMG7w{0F!px`IDe|5qXW?e;UHz# zW+z+(b?cP7yNBQ2H3l)8wL$D$w(THMG?Mb$NF-wyM$yP2#)M# zzj(wQiu{Bqis^Ybm7sUq$_rTWnmVm(oVVy64Y_ zbq_kfRuP_~HxH5P+_S~hPV}W~b`>@Llj+dv3Zvnss zjatBbgXl{*?~#>xbVKj~2fUiX2>czop4cV%wU;DF1oh2 zWp>@9KHbKD`o83f-I}?C!-9 z{w4LYn}h&!8pU$}GAQf<_qwx%@Bo@e#b%UFL!%hU74FGlqNGc-L?LkjT!!QJ4ZUr78>0y5US6@AsFZQCwOAxy9- zZ9>D8#&y5IM$nJ^&{$lf)vf)hsHOR1h_wMv3JcZOPmA*7GI8+Fm$P>0TL@#Ng!kmp z#~YC}7iEqfTY4oHa&dFu;>zAi3pb$JcJv=>3&QXF5D9zno2R8y5bs^~;ClWdZ9%g0 zf^F>-;Bm68AZMVcXT>#BlrHUP=p?&r@mNX=J+HJLN+-pr9%k2lg^Bo zFVN3TK}E%Ue7F$`r1u}Fia4fI8W8Grq+(%EqvDbIFbN6>oaq5k37uu2)J9qQ|hgs0;)ttOOXg_=QzP z`U7;Ce}~#}=9b2Kg3)5wTG_R^VY-?kKW|BL`Dk;g0=C2+8kJ|`RO^{M4I`A>&yIh6 z%MWRq>8u6`#gIU=&id>;Z7voyXJCKExg&;Qu7t=^N^0`4JI>JOao|aTN$tyz(x|u1 z8JG3Ta?rJv3BQD-1mNAf?`O``un4gq6IwH#T6Mr1S&$GQ$ryFsI!fDOp1u3duuVP& z^g(+q8-sj()&qGYMycJc@$G_t(|V@YA0fFlWZU;qiwCg5Op7N0?)5f1y&dgW8?C>w zGs`7GByxaYHGF~0QZ*wo;y#hDM^ZkE(I)Lbb;ySr*UMeI(#+u(_~t_R2<^aS+a^4< z0TA4{Yhd4n50wyPprq>Lga;Web&rYshf$xwU$j2MB>H-*$YWn0 zA)Pc1UgHs5p$1o?M(;~ZJc{FTFg<)58Nn%Rv_Ba^$iEr0@{*E1{=#TY-RchIt#$In zGZ+u#eJx-!J(;g@pSYicE^(331ODvsdp_+&vuHh>)`&|*8vcYr!0rq#mdJv|j5Vp5J2}&&Ox#@{o3A)m z|Dwj%3Kl<$zp3Y^bk)Ce0ob*V1_2U63mN9-wB%p2y`P)m59337y3>G!K$PTnOQDTyRaTomJKgiem8Ui3 zQsM(WIG{+y>`QYek-Q+e!T2G9+**`Ig+}xc+=U~IcuJ*+r0tH)pR>*1*mg$|4Jf}J zCCQ-C#SYWQ);_Y7DGY)2H6?y}agFh$)IMj+<16lYI$G#uirC#F>h5O>xPxUP+zu%- zn6mu;a?4ftIbp~{YcvNIBr@}Cc(G>&-Pv5p)W#9m0HuI{KtaIMPB_>5%RPVIf6M0d zzn#RB(O_wL(HBg(-kuUE{Pv63tksi?a21v$02w3&astQmp4ce$PrXks1(pH(%;_Yw zipzI8&hBfP67p&jbYDj9OKKUUv-Rk7&Cb+Cz%nthG%b-No%^3@=RsO&GsKMmL^T6F zsxw@|rn-@*@>z1e>Z9?a`#nbAa6UQNDlv>Bob5`cV=b>z8)5CQCR8Gs#1fbHvRVtS zX}h1J$iUhshI(mZE^9+ualvc_J0g5-<3BFC-`&$86PFXb;HHmJ*xxvOs#r}x4(FU# z#Pq=Csq^Ky9=NtI_=~2iKn(EO=5%JG_Jy75YU^tj%Y2mOJtdnSQke6r_`v}2vs~Km z@dz}tJ5NPXEKFoi_68D2?iD8IRaUWI!Pr%<1Ytuj=^cxN^oGMu)b zjiV2Er93LSa~GM&TI|C@S`AvUw*v|jcRfsVYrM(jr&i%|W+q+AcL z>pwyJOiYq8)WX@3hfsv)Hq&9v-GRS<0S=eJ2sAllBECkED3r*C|Ft6a_PfjJq+SvD zG`hm>z6L4QD&e~_1RA}gzT8EO6Rbvu(E8#6@}4~dST`LqmDI_i7G9aITL_`grJEtgBYS6KlcX|N&XjPUIAI+zwxv&X##ZD^F?p^8Ko9fe`@e=rGyo3bi z*uccne^Zi3mF<(4!I}RDZy+AgceCm`XlXFXyv*Wi(H>7Q{E5v(?umBnpM&4#d5+%` za1&|O7F9NwM*Dr&MOk^K)d1-7DJx=>p?m-dGvqSGF)v3XheQI>FB70UUK|WUzULU3 zNwrj!*?A0GkBz~PHqj{C|8_!VpN^n~im>1U~$!#4qgYU2Jp3TWjNJVTnYkNK4ZkWa)XUcl!KzkM{TT+bzYAqpbVi ziw=1`KV3b>5yb%7^B!J%;lSEYfJA7Yh?$`4nTsxq*c&~X!RSMVYiNoffQWKaJ!O>JA} z`_gS?+(|-CSb64OGa5QeN&nUTs9&jriQ}v6e+KLMTxi`|^R>b4;e}z*Ak})COK6>b zd#%H2qvmb6W6%3T=;>0;=W3U)Cyc5PdZa0+2zK_HvA6wGsuLnn84x=>pdxEzXVHpX>Z`LMzaPRr_EQP$WqLCbV&@&pHtQ->)9pUcEf z1dQb2-_FCmj9xGqUuGPBPP_du`4~Oy_anqqJ_4q3~E!>+SBg zdTGr1*;n4l(z}u%Lx&*@nW$Z+a5f$6!C_fsE0qauy+^n$jYvgq3)24^;iT z-qeg4Y}D|u-Ju$EBRr@$jysJ++)wT2ytJ|_F1k@6PgUb}45mxjw{=y_!GxlL_@OYa zZZ&sOkzCqUSwDeap$fkYk_#5SabO(L5vrie8RCqq@0RCW*yM$&VHR_Wi=zjS7M7i4 z-u(Owz9BZ!K&|*0i$dPFm2p!UyV>7Awl%2vzti1Cl}!;UKgU4s(`m%?$)(M?_J8qv zLw_2emGLMjEYSLD|6ehJLvaAs!AKP{IgM3I6my7BQEO$RWFr|sNAxC2qe^Fk*YjeX zrOoxI8xr+x8(jGQY&7P3VFYY4Mq?SsMIUhd{1~46e@qk`@eJNA3qDQ=3Cw8r)Qv^{0A|E*2s z^6Xwjx3<(3pXkk0d&vJvssx?kwYv-ON@`af={dR}h6H|c|jV7dgzK$L<< zjl|-0*^dx#J)D?O5`K3})~Miuu+wk<8BnF`x-%HLZ;(p~ZZ?q=io6w2@v<7((cky` znVik-TsF!g$UmPEwj1oXjl-Erj>~ z$olH2ESKo(m+qF7?gl|hx=WGnZlt?;=#*4S5ClO$8l<~HO6gEa=?3ZghP!@Wto8lr zx~}W<&YU@CpS|~)5&b|joDN(|=ylLmilD5fCA~f4tF?xM-@YC<{IK@+_B{8^v5(ch zryG1jts+DAkFA!+8X;dj07taF3DPX)tb z=Z3KL+~Jg7MJ_$sUbDLbc~ot9f0ls)`9Fj8(!Y-Hb~UWhIs!>jiNgD49(2Cn~_$TOLz zW|GuXA2<*diepQ&iW&rf`Pck^RQt2Iq{h?-m5*yhUi)+HE;Nkq(BkDRz8be(A1^jI zCpyoR&Hw&uocs4o4gyATd7&F345!47q^o!gAu}pZpDs9a|zS127Kx3E*qomlv<>R7o zJ%N{*M_w;pm(>eVdnz;gW+hM-Nmd92_zsGtSnM<=JhdyKlyFxrMuPtNqYmJ1-<~fp zNX#5Cqji~F8kdur_tE>NkM-LOPe{qbPn7DLM zVwogiN142u)6zgnF)==k@zaeQhd=6XrzJLLVJEJ_J3l zugyjiypRy3V$A!6Q!QBC6nZC+cO`Xc-A2OSAp05msoJhW5wuhjL9`qc$Dhg3&JeTt z{rJz@>n%Dj4x{U@J8GrcaQjY;LQYUIvP$EnX+BAXoE%UgF zkot@nD0*I#FEJUR^(U25|a^C=lS+Cme0GEc2^GJ z;oRU5F{pUOo5D=|P#9;DCUUpqqrFWZz;-bYt)r`=t%y=%XUv`Or(`jDHu ze45IpqoUX3w!UF$obwk;23X8mb=w23(q!X^UR|7>1){mEkC0wB*!A=yVHgY9D5w(~ z-CWr8=ZL;9*T#BY?GJe&p*56Ml!p7KmHxCdQ?|J*5SijaR9@)yYF5vGA|2z{5z7zh z7YSw=MS18el%kTov|+NOEQ@+Sjh`o3rXhs#I8E?^s@bv4$UyH-0q++^2)jzo)_8RF z>x!INb8YURj=rC-9HWJ4$Xn?2+t96*F2$TgqYeeoT)z#dd;ktYB+^DxIFr{OWSU>b z2xidtxf~yv$<^8QETyuUXo-?Nx2=L6`s1Gx*w2)0o8REY^a>S})Y30KxBanlzrWbP zG*xNRF`p+LwUqES+H`ELntZ-Gj7vU2^CMSMfkVK=7poN$$n|D{kMZAjPkHIgvlT{y z5m7WCC3So~lIGj2z!nY+z{!{&X>v4TkpKjD{g~DZ_QH$$ZPBvj_+l#XU1LAoP|31tYc5Nq?4V+xnVscAZZ7)^I^$L&8OPh*5g3~~CF<)j zVGT<3iV48ffIqS^nl*cS2F;qdx3#tPz>QIgyIY|c#lESy26a7|k z6A9iIXLAQSvbFTas)~lv8_)B#IC-$?BF%)uRqc#;>NBMZe{pvZwnC|5$Gguym`+*& zCFMyXQ5$e$xasQdjEUuOiUmv*exCf#ovjp`I2-Mbg3Vp?DobZg8la#L{Ep3=3w8Do zu%RoWfwoQY3B?PKM%Ue$a@RuT^mf#>*x~my(j>E`#N(91-<{qRnvZ@MnX8NQYV%uN zC2e`w8pHURk_u@`MxLv_kh9}cWu!!#exrqH1qG4e*n8*wLpM9_R)m8;FQxu2`AEG| z4oEUkB_UIub&g;Lf~(~w+orfW_Qt13XqYDzVdL6rMC-~1L{P(if#m?m{c2~rw1bFUmm77ZZ?fp+)?}183ijXiIQk4@ zaHlSq>=Ll(KblSBcoKBuIPIuyf?MCV>{v54?!u>iN_FhefsP$U)gXE9n&ipu@i5!W zimx2ZOeu^{ZTdTQ!v(unrE_KRo)*g>g=dh$;;eO})zj^+7WLBaqiq`xP51z`i9WzD z?_`H&bxP>UmP7dUMFx|Q7mAoZyh&L^X;(EE5q$o>!uaE}EP+WN+5gyCN8Q@(9xi&F zDQb($i;PMAe8bssrc94&f4un`h?(c@$L=W6cYOCQyL|Be#s6UT?;`=ziaEK*+{`zy&5S6}8 zhvDcKwjToh)O9*ms=MvY_?V*i5%tRZi&mRlMj!InbWY~ys=v;QLn%|kL;H)QKfhox zH2}hH`ZGY{24L_St;S5dS{Dy?o5UMYI0vjLJc|!&hjGH~M+sOcAZ(ps;rLwf-2r-rBZr<=NG6K~CO7>qL2?>?yt za&Urr;BMf;HauArH&<92-EfU!96FTrEMH@snRj!?GtwX?EZ2o&?R?{|e)e9Owx36f^#G5*~U=Gl= zRC?NT<_IPOa7mxCP_7?C-Gfc>V*!oFp8!l@bWUy-=cxKj=V&-P^WabaaffW&pix2eE-|a3c7oH=NHo8rV4J@H z3n8p`55U`1$Cmw&B|U5y4fGRzq?_w9{q5J*LjyEK)O)=6v!&^ z#e;$$b{SZ5DiDyl-7lP=@0#Ubudm0bSJS2)J=a1p6PK%hUA$(iP;ZI7CAEkP_IA@d z$n`Z9S3Sslh1BbnCNs2+l2*E9U2vmyVqSInpwEx;)|tA@S=2+7HH(C2`KWrgMsy?t z*PS7L6Qjkb_KU(HlDmP_x=8-C^NcIaRuN^j(+%$*z}{p9-H|jwUK|Yjw<1dFT;r6Y z=K9O+x4Z#@$c*zr8$0`!kRzo&xw>g|!IK1vIFZ=~gEVr*plwo3B~t1gM&a zH2zlDpC4hdj&3UyaC2F2hdmfWaj_tm1`JB%oPI5<0A+ue^yc_u$% zml$-s*ySeU#ff5Th9m5SDn-2i_U7I1K)SF2Gpo>S!dZk&iW_Ko*R;WA)%o{CV!Z{hNWiDjgd?f=>BT+&*QVxbpitH_qKygiQAo zvwI!#Yn0zNUoQP29;Ow`A|UH*R>|+Hnnb^}tRqoKjF9_3z8naP@~yG{PP^BmFjcA7 zQ|z_h=<|;mj0=cew;Z+`1nx|g%!9g{bT^!$84t|GmKJ@ngjvL(Uj3e(6?n4peS7ks z^JHW^29as(c%iFeY@yY~!HB3w^jEB>3s^k2ru^Rw9rj(UhD&Z{+OE-DJKtzyssysI z(i(m5j*?x0(*2IyjcyLrv{iB3KoQJGee}?m@J7-IRr*E>;C24QafO99t zLXRV=n?bYeB zAas9w2~h09)hnR0az5rewsncC{(eybHfeuUPYW zc6Xc4tE`8*M{6^$mwwBFX;4+@VRhH8!yJ#8EWS8;#1770SgJ#C!-H+(iQ=ZUpYxNv zIBLY718ikC=ckLVwLByd7Rh7-hyC>@sq{JWT&oRtRp;h=v){;ybx`m3VGzH{HAGYj zypXK=1mN}}taNtiEv5gLB02r*dPBybm^fr^>1Wn@9(ZY>D2gh;_Jfkc79!lM7g5Q`rVg4C(=5cwQaF z7}~S47_a(<=k}@l*}``?wwUhay`QHPy8~*-&l>VKVztH-VAYNtC#N-n4j~RbO6VgT z{`ah2qtFnCSbqEixG)mIeB?nexWnd!X6*Ms>z>$OGu4+(ZXCO@ZV^>APF_9ry4veo zNpv=YTIz~Q$x_8K^zxd`Tsnm}Ile*A%h7xmUOVd-VwF(Pu5XTSd+zUL&eH?`09cew zCyD=xY1PURP35QHZTs2oUyE7Y@}m7NO^xoyw2;?m)&TH9xVbS1d+oVkqoFlPtcFeL zJdsc+&;RtP6ARqx`3;ak%8!2ob~LvPZmh{okPix);7pvN{+P*Y_OJrSIQEgPD%ni) z1@~z3?8Qpo%|pdS=_>ZtGLN=KJD0h8VgW)C0)pV{w zK@$~g;+Vm^&OXZJ^~-P*h~M76OHS=8p0BN*9mn~+Ig;!XFXOhvMBlm7u)Gqqg)3Ot zn*FoQ|6(gV95L=}cNRleL_y(B5-xm7$$qvXJ~k$%dIvxxc-UVs4|AO;ZhCXEJUD!n z;N(@HZB{$4>tNn(Zz-sGq+#%AK3Ut&-cU7a@cj`5XJ zu86Oj6R0}}JUl$)utsQ~jDKfm%~y|?QC*z5q4r^#}PFI&xA90*c zzH|L3sAi&6r*g2jS7w-yO}kg2Nkoc15+1eWtloL~e(maX^9D467uc|g<&LPU!;U}i zGf897w+8yp6r(tezB*W;Ow5gD5H%X-gppBEi=<>^*H!ZloUmUWUj2-sqB0R$6Zs#e z^w#4s&veT1=fvrSp6YHPV~Wr;Wri8ONafnB{s3b}pPLKd%Qrvm7HYe{*-id>AJM2}yj( zGPV(WqpviNY`v98PC!Q@&XG&H@W*i)321MZj? z@&*RN0xw=%53cpa$v3yOJS48V>FK=A?Vr54sP>!XwDs6M-%n3^@pv zJn!|9mX4Xx&iwY*GC<)aVrN!7=?h1)R^VGCO(J`LdCfQGxSWtUG<0hphK%Lsdb;t- znXTfP>R@9ayEi-@uoL zyANq*S_Sk2H#TBg7>(^PLBW#q@I-^^&cCOvuZ}~zQ4HjerHfuiMdX^4qfx=AD~nOB zAT6zzdQJESoJLZ)KRyTW_|HnFu27b5t{$R;?Gr>YJRkXNEBxo!;& zUBUOA%cbPMR?oJ%=@&Q3}bUWMc?>I67hGA%{=eKJE%DsqN9WHxnj zme43)yOVzBd&9i&RiVB9yGY=$0ol2r)=?l)9MZm7PnGEaHZwDeV!a?uaormvcW3AO zN+AY@Ct`0TZimwa8v=zVmQPyV^MKTfae8vn%sASNS`u%I96p8NKIVD;qetNPe69tFcY>Yf49+Pe|xFjKXjMO+6OYu7woN>_Q z$%mlV#;=nr1KTlor{!0qb99wU`J~i?Wu1bT7&`A+ocSe_X1>=k1@v0N*eZ|TRImql z?93A^{{B@p(w(mzi6aS~Y_xqCfV`PmSi4ihP^zITx1dW^ZS65n507u3k0hf_!O?De z1{&oH78aHn1QhH5I9Q*oY}IltinW}a0>rfH{7Hs(&VfJe_D(?McT3cg_yhY<=Ax^_ zgGJ{=X=tJ&Aqs;KwY8|}iTI~#k;8@ToX}i~vuU}VYcY%y?#x*NiwX<~H{){oY@_b7 zXM9Dk4JXA zBOr-{i|aF&izho16cU=Zo+waE^$4N~SB24>B~(^cwv?Ce&x8L;2-i6rg=1D$8rMQe zIl&np9g`A^UQaGQW^V~Gujp!UB4lGD1&N?$ES38_uMjL|g|+m{R8>EgPwrMG1uQdl#SK zZn;GSc`7(0sE{yMO;tT~sXw0k>|gkKm01c`4RR_G@gzPwo{VS^rbb}kFK^HFalf}z z;$ni&o*f4#B#g=1&B`knNq~SIF^OQlGz@O#3I% zBv_QfL`4V7QA^)b;S-5ojUf{?!iA|EB}}E(k3-iGRpz|yhWc*1P1)%acPh2>l) zyJM#}UX`~9SHUzgrq3wdz(=H!L-<^DRxvs@rYP4qBD2a|s>tySnq#uQ5Dj%H`6Ki^ zZZ4ek^Mk(CI{W00IoL2&Ev?a$fB!_*_V)JJ)=yANG;}jFGldI{5NX!ZK7Qm7elu0< zTU}iZ-C9)3z08E~>A07&NY$_DNsX3ub$yoL^g9+sOe~dBfx8x^%A5R$o>6ySYRu3I z1(o5L86$UP!{vb1Z!Al~`-|NcKD*h@XScsAF%&AFz?xXOL5EMw3HJ+R9#Zou3yJBj{%_nl5d7JzWb1R&5bUkpv{y8~w#ienZIFt%4 zBD8WRCnvf#W+6RdVp|g$M=D3dmBEzt!PPiPddx?7)jMU_-yZhj9zOaE%^tPgng`n@ zK*2w!V^^iqM(w+OhH>XaE4(5SLCj1xVpeM6i|Ml4g8tZOX^DHTzI-VHC%6GI&RPPI zZE!T=a|uhr-7$)IKyhiQ&E@6gocbko!8OQj&jmrcK>yPpZ;>G+Fa4z;{iR0{ZOnB4 z%>E7X-(}&4GoGt#cCe&fS;w{yA9>mMI9TEux_+^xH2hOaI{)3=<+7>rZR>|*ex2e1 zsP-a9N1fMy1`3Uo%1?NxVF}jOgTrIUuqs{lii(PZHb8Kq>FO$=;vpg7PC%yR1`SR} z07DMAH5u~5z8%crm5z)aTjYv?M4=E|S<5?SV4!1bsjE^dWGV!Ac_u$J-OC;9E?9Mk zz0#t3%ZtQ^CqX+IJVe~y%4ksE6d=oiNlA(nyf58(?Axbuw*H~cF%D_5;ZKxt8=|=C zIAU|lpORcE?ZpM9miPKJ5EmOp#mza*)J2CQ1&97Og_eM;lZd4@|Jg7K{cr^;@QI%y zqN3PdXYo6l>FewB2G8o4@2vHuIvF#bVWba7+#UQra`!|^@4>r#o4{D*O#8ziw46fF z(3n>`Rc?)I=Oo;2CKFv1~&|e26yfI@>13n+WQJ&6F2_D7yO5NsjHao>x+bRTjN05Kk|`ne(g=n zVnRYR8b%Kf28LZaEnib+W}~H9$mae1tvwLUuU*b|v_P^gH{8~eQ@E3X3s2&KgM-@v zI*LKk$B(}eS11}v8qATtaO%PHXN}q0qT+wfH??qhI|W_(gj}Sqhm7`wh)2Q4o|%Te zBrh_ZW)#SW+m$d@z?YHJCVI#cBAA>hv-|N7f0H8`_qUjtonHVPS#`XfO_ZWGf3^1m z%Ee%9T3lA8mH{y#{@>adfVH`b1p(I!Qsb?r*wcwRZ|AawIt<4bUhlo^WTYssuh)8^ zv5$kJ-|>Aqq%}-0bWM%9g8NcjT=|OIk&}|17(XQ293n2;RO@aDTVrlG&cpk@@PVIw z{jqzXfS&zVwe&)vf~J?>i|q3QSsw}5gZ^T@<1%oKj7n-~@S4OH;;A3<{i3?URYwyL zXm|=>l5j~u!FwE-bB_4HhF-19pS38g^I4*5@BNCgE;mrFWv7+w>+2J&v4Su{otMg< z2aoM^?QWHqFs`n(YXoM(`DP*PK`=vKEEl#bQpu;UUa>DU5jZ~QV=yqJcx=LBG0;DC z*uDFvjMp-PL*?@G7daj+9=V*6^*@jEc&mq98eMm+jLHjWJas`!zb#RexVhcFDKIMDU% z6qoW+%NBMv*>qORx8l-9=f|RZ%$E0jQhY~mkR85mWxZBi$%S51`_q4l`P=?4y}#aq z`|}90R#qj*FFdhEXpQ2ossaay>1_wzx**^4($&-+2 zV~NV1V4e3*(8~rIF`d6y?CC9yM14AEb?}?uN)%x9!SBOyc~aZvB*<`1PTaYoSrCXED=TFI$A!N{L_}VZ-6%8@(!A8+ z(adyo(%SO!HbI7l}J5i{7JDMfH^|iD#U8(xH6e=ij zxXC5pPhx@B|Mq~iDe@TlFqiR3g8t^_RQ?*eGlHzqerJH$4i1d|k%{Q8@CH>&bbRjQ z!C9fG<0Y7{^={QDPctGU2lW*q^24S|_$?-u(PpO+hDt?xxU~969~|Wsm%aVMp$~(( zi3N7|*w4!NnFKB%bOY9GcMtPa$o+Cv8tj@Sh&RqefXrU6h9xb8z@ySk|2yn>5W~R2 z`aKJjWN%o~{Q<>DI@iFqQH!dk!VT$D{B_1xRiGD7tEjMDEP*s4ZVZtHuMoHVVB8%j zPu?rE_ct>h5L8{0aZkp~&Ppe#i29qiWIavO)0L1^RIEW#N^bZ_Q6m;4Gr2KZy}hTm z*W^oJ@G<+7s0TBQdM(-Z?sP{Pc9oP79VjQ~ve-eDt!_N_V`3s!Q9+@Ko1Z@+$;~%B zX&gZGzrwb*w$PJyzq9X7tKAXpAcGEbV+LerSNYDc%ZU0j0`wT}d3!8B=i_4<%^ zOVe=py#b~}8Et7L$rZxfX}vBBNa9 z;W8bF;Wy>_X8S)s+s>wXb4Ir2(+1U6Nr0r%8y*4WeUrI-Kg_}s@{$6Z8iZ8?IeC@M zeE@yC9}+AnJ)Mi6kIxewG-{ga>T60(GE##&%)z+o2-uV&BzuR4P!;f~k;0DnpW!VM zb9DEGp~zm-O6)|@5HPN2O2Di+OpLAQdT6{PsQGvhVSfov{e0gFAqa}02*S7X@Y~H` zLUL(ob8COYvGw)wE35e%|NKSdj0&?NBC=ho_)UBPx~s@nxe06CQoTX-7pn$4CzyjR zje@!ydhm%oQC%#HQPZ_HL zlq8^bSO8f5KJER=T36UyyDi=lyX2TA*P~YYK!6)RshTj3D;Ll74h@=IFKi%)cR1_ynWR-llE z3&(v1!0W=|)Rg9KZqW5M1MCSQ(b~TXBW{y+a%lT-Ss8RL3|LIi#!<$peBxoW9-%vP+ zTw6J9MS6a(IweK@K8Ap4FEB9h_pZMJLqU5!Oe#A3^6JV6bc;gE#cH2vVF1ly{vqWB zDPLou5Rc`N)juB>uWIqe5oNQp+ zJwu*uFN1E~vtau0D2t0$-y>1~`rBMWUXQIGRbXB1!RM}iNkFBk!w2Vz-WC9m<3|QT z)bF1U0_>`(NwC!lqfEd-;=Bmi4--Gc?H%KswM|sp*cfC>XBsFTg)t`z*G!4%^&0`* z-j{1LRDER&T%4tS{yGF-rO)QQmX?{VbtGhHbSzzIoWiB9B$7xe`>ExIcvdMkZq>rs zWPNi<#5XjjCran)B?t(Iw{KSh*AQc64MVlE;1v|r@G2G-y88!F?O0kl-#=1b!a$@` zbar-zu4Axh&R~yU{Zdjg2fUKfgNIN>8uqSTi@!ITP!YAC0qhuNW{-2jK zo`G+U1?+tH;jJ&M6F{k>oAyAG7E?GFv{Ie;x-fTmXs{FHRgZ&3ot3-LUxtmkdR=d} z)xl~bz&Z#Txs~kI=(^`@#;fcP41alfAog&BlsPh(OqW|wP_UQ9WA$AaP$H-$q#$P` zr-Voc8t#+} zK|B{RG+f+KtISCb;oaEji#BMm)W-yLA9QDDk4luq##m}Vi28|`7-eNx7#s}XCs=Th z#en%z8poEHCF++l-4L?Ia#ar3vU(PP) z^WmPM5M}j<&(^O(feIR{aGm)11smNMc$SXcB4cBf#(;k=!KBS^+Td#+p41z1usH<^ zMn-M_wkL~hX3O;c(ELX|04|MtX1WhBYlTLX$u;vpq!@@Nm>WDYDhfRX>b6j8`IWu8RwxMiTI^&()g3;nUHozRyie+MUgtFT%mBcf479 z0B_XY!ow~#hw`4xZ*QwQFB0+OIyS1rB=mO?vaQ%UC1lsEXMN=`Ft2VB(w>|9Nsj|IrllL(*Ek!tM%zeA z>RLG^RJ8dkx5&`Sax8r7asnpofJ@wZgZh=pD6Xe=Pg3ED;96fTKO`W(MTYU1U*b*P z7@9sHbnX*6k4I612Cdo_P%%HJ%C6vdqED=JV9rF$ougPE+We*4US*+=e;*o_mQ;-C z__t*bHq`cKHAF{wbGh=hoTFxP^6KLG&A*WQ{`l#8n^6`PxX#@dR2OSV+2?z!0$270 z1)AmF=yWGP=?v$Z25s6v!zm3@5 z-rjL0z&{%Aj>xFDM6)i}r8%oxkZJbArhID=6x0Zc4R5noqTLTSj&ac)Xak9p7 zs8~(ae){XeVfjl*C(d6u6jDbv1Bs<2C4sKJE_sE(0w z-t~CTTy39{N<_I^SJ_j#<8p@-mb1xex86cybKSx3>R!4mg3#U zq*$TO5~fEn{?ejrRa#9J>9oPA59&BLz+wz&eXStzsYMu_RfyYsGLu)g2@j-D#u@`-$h`ZPNmXE#FE-YL9qY&7%skwLw?p0Lg0`3DM zGKWq?3BjEu0PaI=kvLH)u}X!9f8Cl)hX+wx(>3Z}mIO99{a9|qBTL;w!k68M!f~mt z=^hS--Sd@qg+241WDFVZZI=g0=2uxcp4%y)?m&z9+8Z3T9}UhXUGfl~)DGYExs(Ej zfO&89zuw;7I{Si1TUP^y0az&*q#0C^KCLDJOJIvyW-H@Ca@cE&0zReWF- zo{16x;K!Cjs3m#zw~y&pgsPwNE|=i*Ot-$-RUMt$yR3c&d7DYVQOcO{5ak?n+{b^d z*KZ1i$c>5nLkO6(ua-_mcT!0!N-_Ru6txQna2s+Sv^1N~w));4{nXVWM1HspBjo4j zgDAy}f3=`Y>eXJZhK*XgZ;oWT3xl=+6;L>2oakVS@D2_RmTbmzL<_+Qgu&Fz%;uik z6FC_-_nI`kBCA)?*B5lzC!5jJ)m#`nau%1^#dSHB?HiAR5HUFslz)drfBP@<|DQJ3 zmv=6eo%HKC`N&8TJ_UGBq|U#hB2{dhgCiophUZ%pR_RGw_QZrzx+x1Q+h@ho4bvFz zKYAHD)ub5dJzdFXyQ|I4RIw+gaIi-%og*P(8B-zu`|wCOxSUe(YS!eojpLCK^Z}DX z2m=We7-c`ZP-#4|K@rh;$hb{Am0`@R?7a{R5WKJNybE+Dq=TP}NHeMQ zZU@6gbn7u|;M=DdE#XfML&_>ue2llz5OpTzsx-!P<_`)6CBP7jorZl`7B zxW3TjRKokJ+OV>0@&|erNkMY#>$jA5qdjzjARZmh=lg}hC<3-RajaSzyj0h{D zP>^YNpkoVIu;1>?W#FIkyp~o(Py=@kXb?X7`JuLq3jc@&+d|Bt@4?H%bCOYC z&+nBa?KesN`6!SWX8lLgMH9 zJ(AAOw?Eoie&lMX=?nnM*TL0I3edaHv~_O)B~>o#tHL^PGPVl~3*8cLZ*fV<&_C}l9>0q=N>;oB&vpy%HF?AUyUbz7@%A72ka0eB z_3kOH(#H-JO@}!URKO!h&ErXs zJy*0I%CbfjNKH-ALD%2Ddq3(O(&n^`?8b)M4oa{502JjF6%D#f zN={xaGj4+xPS=OiiBa zzv0`fx!twyhqG_frN_Imgz9Aq&1)v@y-_t5<-iI>#=?4Xvlo7SO$(G2z}#yTc&AZy z8B8BHuQwJJrslBD1Rj~fBnHoRQ-j19UO;5V1Bstyqp;y^#NViI+*}B@T4Rxqj;a}I zBEo+B_@Ur;byAp`nW>kdi^2qSdw}mtY(_>#x}~J0FY4?jH$-6tZGjlPbaZov^F0qW z5~#skWP1GnzNGa$s5HKX7%vPBe-!BP{-fkmD)5f|hFBU)3O(vq zlI2=#;lG=DDo?@PE^989`}$A*ZmgvjD)*f~F!XSvdU!`|@6AX1ogl)f+1L!;I5_Ox z@6Ffpg8O1vh=x1Csuh_%#7HZM0&!67^7?F-02I^z;nSQ1Uzksp=|u<8e1b!y!7@z0 zzpGxDA=-VNEirKLy9;t@GOeC}1e-%@YL~t{n#t*WXip_bB3C`7D%bAX>CH{q;_J5I8S~brYJGXdfoi!NFcTE%oN5vbk=q z$!6c*IO}p5#qRy{C@wBBtIo_+7)6f&co|f_?tlYBnRE(eb|6s$hKFdD>2*%FhK>$Y zw5)(iL;G2y3bka@@8aMi!PBSybDf>0Z`0FfRXNj#-D+#4x9)Bpk#T}ii&Be>R1ygu z+(j4)9sv>Z*-ZJ**YfgT&$!94Yj}B*PByUo;z{79(>P>{)5L^L6x(G}o@>-gjZZK*wT6jkHjg_74Nz+C{yL5T7;8+LsN5;?yZVCCfn>Z#fSLkjZW%norts^9nJAj77`kOi6O5NHIPsQDO?S>L=N zDXBCm&%_8OmVkXqg(4$WKaL=^e-6MI6gW>nJrA_Er8B;+lSHkQCTnSrUz4?3!< zpGXLaB47FWabKRkemgsx?&Vtm#|%K&6@@Tscw*lMw6SFnkNg32(=Ng;uUASJylRZUkZjXq7Wq1B;iytGrMjtmXnWH!+^{J zZhiuLuk>9-gY{wUccDqgn+TjDQU4Wlt25b4n9qYsF8IBf)0Y>S!|PJUYItGopNk@^ zy7B{%W$;H4kbIv$#Tb^2Tk-vi+OYuy2^nCD(p9?z`UwI6JY?K@dV0Uw0~bdZ7Oq2E zTLYfKL~zhUfye_qo*%%!eufC?bW+lRc>P)CXs|Vsn`maGToI{~%X!jLnJW^5LP?pr zG(J4iJ6dZa8^qPKF6FkzFkzyu8P3$+K27;r6UaN4bBh*--4P1$ ztLvCZNDnFrBzGy)G)^P%Bqx<~Nns%&gaGpu5~{kpao)T6 zg99wHN-ZMl@`VgcJwd}n4kTjaAR)Fl7X4^^cJJ!hSK0B9VB>x8sroDHb@bfUp_K&8 zDO(H+mX!J~EFlCAXcQ~UY&zUhuM?qygVPpoSuxv?;r0CFloW;Bk|S(98;JzRx!_Y_ zFU^c0yr}mA&Q*|i7rf8n{PDv)CJFb7m7815dcJHNey=eASdsv>pu)kJSy)`d!onyT zoPO`ng7k|H!@0XX+5}43-XYiej)GB2dS=WpAThwv=|1?iYBc;E|8g(x<^mW|61v_o zN@}a9Y&|214AGf4j+Tp!)opjC`ch#S1K2X@I!KFcO*$Qwq< zsS`ZhQlPg2r0WTig~gv}0ALL4ZEfk{B((iru+LWX{d8PZP0C{1KT+V&R@X#?mfbh2 zG+yI?(sXIkxlTy|=+96{vVpSpR$kz5`B z`W}VG?3rPOYF2kC?Fi-|3jslpBXGEMROlWXS!gpHpV{x_h*OE#j0KyKz&ev?q;om~ zZ)V<%WeW|NKg|F+YndtH`}510f;%GXdhpbXrk9!eknn$9kdfu|<9BgNcw^Q>f#9e~m%W z;$8V~9xXBwaCJT#hg=gp?3_`jzm57jrkCl2MJb+H{qBPd9*z)ZsDvYFI_|HHEYQ`} z4Kab~xazQB`RG|@I`54Qi0swr6Yl3+equ0_>Qs*Ent`YtN z0ku}cF4l*}PlyQ>K@cko9xNcqyPtBYWTgN@Ie^O!?yiy-G}4GcoP)n9Fngj~uWtLG zHh+bf1i4MIz#sm92U&9+7Yi}`Fex5uSZjPdq2F6Fz0k9GF=}=qj2bt$3`@)KS2pAM zpbJ^%%b`BzGBLmS^`FxlAof1&l>j6tD~N;3u}Wbu=>07UR7nC;!wEB>MitbG%C- z6-6{kKVM1NS6BcI;QEO~nz{V>X9wIgFAZwtipK$GN(@V4>kdKZYeU=(l#+^Ic+0x8 zQ`PQIOn`}He~Y@5l22!VNqP5um#OQe(v{BnjP4uhSzyMVD8zVa=WL<01vpf_7_(AKD40*s0@)Izr1aKwOf@MPoXpcr}7hKuk<={}Z|99ve z0zjC6b6NFTL%h!sz)}496U-qfJQ@@{wr6pW7wgSFw~1;_O@&_@n$B;W=F0vGFurF9 zn%bN1p4BCiK$k(K#!SEY?Jw~?kuqpy6)qAQzN^Tg?Pg|W4lb?Z1Ms@f1bY@#R|nwO z`21)RHK)kNm68EgJ6u{G6qKm{4+C`eEE)d|!YFXEFU$+!i^8#3;p#Z*s7k+BE*^>M zm1#x8>PtjG$@gvXqKzj$-opD(ZyS;zH5LR5*592;(t$LeI3kDll$_(2fo;m3Ys6df z^18KmZ1Io;UXQP5VrGvfKwe-1>3Ad2- z;FaO`ECGgL=fdaA5l2Dm|?*^5CrpJ8uKo3^e4*mtw9mE$-b>_l;yK-X$(Y;2Skf4Ds@dMx`=Z&2$*7{UVt9_xOI zM$!MO?K|VKeBb{sWE6!@K1qs{NM>2tGb19KY#BwAy>2cU5sJtvt0HBU71=VglTG#> zA@jz~@3{JXAN*hcNB;-^@AdNH0nYop&g(pn@qWLLE|9o8545-co{!}=+z1T|n@%AmMQcC_qoS(Hhx=oR&FAq)FIrtd z0)eP}qLy^!Pu4{_*-i*ct^~-~x(FC$XVdKM$4rjXMvhZY-(yk+u`r8 zTUz)>4i&F~eCf7^PK?`A`I{s4^(J(3cmcmt$q{8w`lz>ZGdFV7TxLq9Y9*!*-}itXmt@@%dWeM3xfcX+2ImiuR} z(N`xEQ1=&ODX~(H9O{itOHEn%>E7zNTqC#Pmy+h@=2&9s&!2b?60ee|{~n0}>Z$e0 z(0Qh*-zTIu+GdeRd~#pq+n6T`LEaHQI5SC`6TeGKR%?U96VI}pKJ%@vMrLZ2NW1dL zVqOHb+roU!e^We_daqAb3gRdCNc}8`A$^xMZ#9 z%^UO9{=B=h?5wPX9XHj3>t6|;7W5IN61I<;Q;&RV$rr;_a|I;Z?-u)h6nd`PlKN-o zpG>~vw$xO$B_wnTC;*Vj?M)0g1dy5cGNZV)><^VD{xQj~j@eLB%8Pp)K`scYXXk>| z9D8v8=`4yV=)hZeu0U158-%0DtPT$XuQDvE`R8kRJ0%e>ex@KmerPW&vGhAJuG>H0%8uibanhzcO!j&$6u;hQI+vD1_%%rp1^QwL(JSXJcI6tE6_1RJ z&Wlaa+A3_XlTH}_U9YzLn&GQ`iUzv=16nfzYwBtn`q zGviBb|0TQiid9o@;~~tpAsueHq!*&YR>7+>03FypNbInJ6B#x$6GDN1=&A!?YczD( zI}1&0wYXwsjG$sblrl%A{bM)fP~=tW)T zFD(C~Ea<~T4#K|G{zmd=FJE?Id1+Mvj+z3%#?JdcR~J{RxQ@wdJA3u6{ zKz|H@5|~H*#nfV_cpLVnz&0z&Z{X<()%j3v6ZtyfD9C>R2v2ER(bm-h%-N_YPUe@p zYtUi33h|1>>c&(sHAn*I9u5XVOb~Vn2c85G7LM=0bc$FhB^w)C9Gt2S6iXHbj$p>Q z=t)LKM&I{nc6H7xS56_)bw4xuuRF6r?M#~S8QQK{AL*zI7b59sHO3S6a&t>ie7@3( z4&bvH)XcXvxpueL%Ue~kR4UHVhkL!29FhJ+d(muSE(DmOzPU=!#By--S6PsP54n9u zlNgx;0@6qoRSoZfPBf-HKyuM&s%SuH-F*d|*iB_viG`u!6;6QZFZ(5Z|5%+_ z3i87X{(lA{emKv*bsnuW9bOmlIdg-QRJKY5B2=olutM9`5KkttCy+B0?yV_4-X;%c zj+pN4-Ldar60jdJn%YJw0qe*u;CE|F3PX2hJBU`TC4d|rKl{%=PJbo#f|78QyDk1M z%5!j>`vd~7p}zk0W`IRVr1haKF?a@|kr9afaFk&|kdVLz39@a8#m-DGlZby6uSp;n z4aCE^lGE^?OCh1J&LCrQ7H^f(Srrv7>TP3(RM>`zxsQ@+ zU8Pma*cVjl|E@As47I|%==5J_&W(W#J3AgN0I}5*OPja}ogi$|KY(|(i6kVzq$o7M z{B=HXnE0mpOlx?=lu~1!ki|km0ySia^ylD4dX+DkZ{OygK#`sJm#ms1ph15;?sU}? z?*zx`>BhiH4KRx>HALPs9f}i+eF);|iJ$mY<}N1bu{!oPD(W9Si+UR1w=j8+W)>*^ z9hoI@b7mG6P6oQVU+55jA0MpVrnAf)NUo3ap0n#LTJLd=6&SHLR6>iauzo0^|CS_# z8Zt>w$lue)!=Z;@E7iC!_cWhxd1uO=31t3eMIKkhPcY923ogNDTUuDWorYd%7Oc7y z@O(6}=i~Yf+dUP&guzniwDKK^Oo)9_Vb?p>zqNe%ve-bEuaU7EVE#nIkyKPap^owwGdWO`Bo`oD$xryy>~5 zAx1?Iz%*nr!Ubw+opk|RGN1;403Z#eb6tQMOh9%u1g%qsRlpRJAnv(FH4O>;YYPQD zUGk;Palpvq%R!5R_<^QgwxYm3j%WWFIyX8f_6Hnyux^g5gEd>}@v{^FJq2>^i=`$?qgqH-!Hbdwh2h&KlH(XFIVGN%@6W26(DNA*YGwcNg`Kv)Mt87R8XV^p0KM(=1bsnE}5_ypobq?$rP;*ITx=YOSPZdAQ0 zaepF6A^mqe00E5puX4|y1V(1KbFv8R&f6LJ%%pXVjE3KUZj3Asl$N#QV}I5r`>&yr zQFrG$S(LRjv?jOrKHS7#<1)9AhNWlYHf%WD1lfQG6DmTyEf-9~bh>HO` zmoqV_p{&PsFi(_#&Q$YJ!N2h1c2+a6I=s@-(vKDlFTuStVE64+3;9oMR@S1UmDRKn z65>rbgSI6f@8GxDQLzm1I6Blvf{3~-k_9z9TZ3J81CNKicy@LCbn&A%)!tmEP|7ME z)8|2eh<)A961^E3`lzS(ln|8nsZUQ+VMCIWla~R7WSoUDU}?!1B=%9y#l=OEjh%gn zicx5Z19?eDKl!cFr$5i~=m=%rK!VbVur>unZf@Y5Cu(bh4u&zo4EK78m-mxh zdwaV%OeG0S@dp~(GoiP@8$Zsw+R*;$62MsuKwrvBc>0&n^L9di+c#3eBb_vduLUVn zMyi7SqoQuBRLbiZ-Sz`X;CONtCu*I6X-?6tt*z&xqN2=pIB8jm`{yxrx8}q7#YJ|w z41q-37DVV~W@{a0)s{^1h@mQITha0IYZjVa&u2^$2t^h3lrzv382_dJtr0Z$Pzvy@ zjE866AYyH@ksMQwrmo6Ru#5&{d&ML=CMF1zZUzMfz59#DqJgAj6CDivG#-S~!WO$# zfx;r9RU!AVd{o{DNbYfn(QEB`&vSAgM^ZC0UxA#(vu2PAik`GS&&ozF>>1rZ* zNz{wA;R0!x#|Ea-peT33gi=<`Fa4mROAf&Qjz|v~SP=CeOkm^!-lV0~`E1O+sD<9U zML9V+8L_dk&19qHPeVFybB$J>BPc$9>Gt$^l*0D!Cfp&Duv&^jcQktwaPtt_mmWXE z`eKdJ3FN(Fh<`=-PG5IV4?k*TWCTKEzVj*+%C6#QVimi}N{#`#dw;f&#YXJTl(`{LTI*SYUp{oM%)I z`}_NAp=OvxUL13qXVRcMC7l!q4+~$scp?0{ZHBno9u*dTkSKEU6O$Au3|)0yw;Wxr zP@bO_@Y<+WKgrgutxP;UJ2f`^{M>c(2dyi~BVgtW%iLF(#GwYN3YO!TK29mX z+T46?3PNomOng9rFl%Y$InQe*78J@WYrKFdzm`uVn(xeE1VuM|DYPX7^aP)f(M8qN zRO^W_5_>+|z{6>s4e`TN7y#34gJXLKeJ0vzF_=Xb>4Zl>ApE%2?{5FK zhfGm-EdbS})lgF)Q*%H^sP=LUHT2!8rJ-hLBq%U&sW>n1C?;fwosETqG(?GsiTgap zr7lJW1|syxgh^F{X@Sk;;6TA&?n^Jr$M=|S(=$e3Zl7sy{S@C?t5K7XC@<+4(!y#a z4JvV8^*lUEk{~CmtfRaTNJH$m1=~R>9U96WqF4V@IIQLG@4p)hmaM!;Q1xpf&4%<0A^Ie^iK{K?OUFxx+B!U91cfigOIvZ*Q5{hP!(!TYR90on7J znf+-KseN=+&O%;3aI@<0U{5tW;3UAXM`1rx&_-c^(MZK&zW26+A_RXH2n8jDUtTVc zzr%2(o2RCh_H6`|%V&-9h;QHz?Q`xCYN_?nrPE~L%)DRn7@o*<_GIxTNib@tCnJa@ zl=uKtHnjy3(gN;-(Gddq4FmQ^XAhT9;+yxVCiRb7Yw4*vKAYT84;RuBawlQ4@XnSuf5ud z-1*!rmy}@UmA(|QR>_os!x3D{uh}m{g9JB6r23)7nuqv$8xs*yaZ5!d%yp?h57kmj zktCEcn=r2LOy*Z@y>d8C$?rVKtLwg%ZGAY+84LZ z86~BVZO{yV1wT#-uRKYLa`}vw=qu;tiR#|5-4d@g`jEM+fg9$x2-NKT)R(g4R#hL| zF{?EY%kAa-XX^J3#j#@x@%%Pwm9^tdmn4x?n@PRT$QW7L`}fJXtpOW#Fq`78KopxN zhtY3BQ^H-j2igBbN4vJ9%2=y!aH`7%u-+fzE}rY?kou(fK*%8bU=JPby-{q)IMep? zYczYAX;^r8_5j$ohR8@7UuFixi%3lxJ|QSLxL8kHI~#CSuQxrmn>V{g{E06OJ;>ZY zh)dngEMvyqMpczMO{I2~BGeM+ig#8$D+c4@GQv5uN+<7D`w=aJ{?#-1#JSy&9mH=W zV8suqzU2b_!Xi<}$%acp0s_Nq#^~kIlE5RkeAjM}9zT0;tDN~O!zj_!Z^g6i_iLfe zy`G+Xl@O@u79bOtg{b5t6Vlm&t*fwxyu7>$=v>_(2$Kw}pIFD^b_LiK;a#?@edw@I z;95&i#1R+a#j9XW-++_Gq8s)2Es4Us;uF z(3+YYPTlMsGmxtpk`VT#O@JOjW4cn6SEJH)c;S*4pii|Au#6l$8C z=6TE9og|r4oa}WoC`->B<)N0?xE(aKY?2rm`Dg^2&+9;h2q6LCp+M=c-|3))72#1n zXK&y9y|r7s%=oygt91vzj-FyLdQ2~;Yr-b^2Q0I4l&q}m9oRPoxoK&Zj2W=N#Wd#; zv(zN0U~Ot|_m#SSy+kKU?VLoW?vPalUXA6jSI$Z6YEfcE>0wrGz}0km?9*7#TCO)SD_Q>zEb}47Moid|3Xq zOZ5$z*ZQe(Toj zT3TAs3@9zFqi$5Lkl=lTo1wAkX>+z((F5xanOnWO1RuOp1!35^bYrtGD$Vd=#bz@I z+eQGSHhlAa9YM%gU?fW@$4>%KRQS@R^z-M>r)otthlReRW6a`hZf;F>*XqXM(?`N1 z`VO8kmZ?j;efN&qwB*tFa(sOJf#WCxB8~n-M2tu%*xD{AXZ-wXxwAUH8RhadPlM8? zy*tx$y?e0BVyk>KZFx}8-XLFor8SWQJH;GaT-o=bdz%3sk1tbACP(;)uwj&d`Y8K3 zZzKkQZjx?R52rFbB%S=W_gab(uOwFVKDu4sP%uht{^GDKx#{ueJG)dCnv-LEF!wIP z_a5o6wRh-!PfEG*JB)Xs zOr=lkE||GEkoD@};PpG+x3JkTV6)M-w4CZ$SXjtDdXjAkD$pcT`m)r89&~1{tvTuF z!hEVkUFXoquE`1QgSAc5t|EP|N{HCd5~JLIH8eDsqGyQ^FNF9A;#C4v!r99_JSMcK zPW7@X|9*8Q{Z>*Yw}AnSe$6|Xb$+lXtNE6?9`LlKUif_0`*WRd;vjY}#=+Zd(%Cvl zkd5=$oe{&Z$)FpV{_*2SF!!mjPgz;03f{orri#+-q!z0MAsd5)nV|=w`OX=v-))P$ zCMPGilSQ1Zr=jYnlHc_#lG+H*JeB@?N5?4iA}RLjOqFSiniBQY1Y47)?(8(Rj;H$7 zHQb%H={$39lk?Mu>E?fxyUw`}|ImjPTwG#ed;D^Z2;y}dyQ>8DWRD4S2okzwUv(7 z&!W&KZ@|3D_%zaq+Q13#47-(ZZ5qy}iMZ2L(L|C?+N*Yo%g)WCWikkNAzl`J9_3CLz(ebEi}UVpQJCmp{lw zJ&%dK!Xczf9v1d+hfr1KqMT*oYrr_ut~0c)ZdtX6=9VqM>pb#Ye!RU=u{a-sAV0zS zbt*z8IH?L!U2CxIIjqKO9vf~>`Tu)9e?Ii;xG3g+#b-i3N!;T$wlwr69_ETSZqyHo zt&|sRzJCTroe_KBEdM#3?4VgPl$Mr`V~wHzmjk9_%5;4-Y_#!bCz_faXQJugRXS++ z$-Uv1`>0RNyo>ewj11=O4lg*KVAiFXU&hAjj=PBR2U9a26tNmRJm~gk;4kz#z!iPj z-|rJV@w@+x@Wh0+>r@CU2Or;Oe)zN@$o~3(;4a=nC6H9!i?D4Xj!{!TbQevsTXOS$ z@z;s|<~BrMJ}CIO(4UvGPmrp@E3CA>N`FR4pZSj9Wit$uDeCFz3H#aAbtT-G8kx)D zVv4Y-9Jq)e2Rb@Bf*ugBs|yMVorqx#>&b*Y{mWGIkTCZ{)Mw*^sF*0LvW$$(Ifyle zAXZdQC_aO9(qn5I(Z9T<2yI&$c5#v|=;7{bawd<|_h9jz0Rhi=F@zull43I=X{mgO zW%Iv%`{rKz?VInP0(}xPs(b3{>Z2ZDr6NM85x*+T{SscbS$jjdr-Vk7%OPRA3^ULE z0Ra`(E5yi#J@yptntam3Wd761-oL+EZvnJ?Sk0{u6EVoqMQNE4<1qe zSPulL=7ax*s}?pWT>qtCqW`6F|6hl_3%B{11|r`za07kEusu9`J^`@NhKW0aJtXFrY*FeC;Hs{qNth z^4;?lQB1Ledy(mqCI>PFNLuIL2O?4E$(EX|LV#skP$GPiUZb-9ERHlFMOjU}x-RT0WGwzky{h85O= zCx_G7fE)Yi2}Ej~Qlayf<Sd;iaoONv73jec4z3fPx0DUwQKm?*Csx!@uT%|C$Kj#EAY#hg!4{ca-o z?omHgD!x;=Cb?z*b2-6ty40zU`L^|}fES%t&}fzq9xNgq>*=~Zj`71e;YcuqC5mmz&CQ;Cc~WJJpq7(Fq!L`jFqDusG2 zBj!@a!tTjDOO+0IbRLD>Ys-Z|*wNUq=VKGfV!!C7Avthn&)lR;5i(HXBgKXB+d&48 zWKuQvVlc^PBTHx+i6QD+ao}i_2g*?v1zg(F$BILy^ppBDa@N^_4q~#U#sVP~HWQ;5MmBF1F43Qs(R z9We)MN}4RsWHFT6(m8(?vzGn_!@()PTbhGr`wMLx??*8noNu@8b67HJ2r=aagOSQI zY-Fh~!So6J+0tc##*{PX??-*W^+H*r^PICK408(vsyYOn-w<k`uI%d~=86%xu*)0|vaIu-fs(``W zF+)QCH7O1|mLZ8eeHC)PgiD*~+&E`a!zuBUEhRG?EHN7RraAvOmm6)2X9>ON;qY&D z|NXMI>xu>}I9j~1cHC%UCWN%>Py@!6K2g0Hcvidg?vBNAp=)>HnhwnQ5pWC0qkW`b zZT$JGpOIPcn=Cdc87KC0bKF)fXioL^owEsQO_!X< znO{-Kc4qqIZ%z(o5l^r}pc6;L=J!r)P^>Opra+___N-BM%5UpXnix+&?_;X0bdHuR zz%5Nz8Pm*o!+g&%4wllz)xswqzF?wN_8%HNEnwhqq^vcMWFnKx(^I?5(JHz9EkNzT zN5?4;iKqCF-o}3KZ^MA!`6YVmA~L+3*xUCOxE<g?NghrAy3dW_e6}@yotF1=b5)WEEQ#2n&e8o2=R^|!bvdwvPdAs3e(&F zOR}{Ye@wZ6xo3)8I_9sSUQ4D*qu5SQ+UbE64g-T2TlS#f{zh!hS$pbu5=$sgkLU zItp&l;QD|`=Fzj|%RldfYBe!MyO-2SwyJ4hsmEyVm~`y-qFB+7L-vr6w}(QA4D8v&&s zY(SL0S`Rmg7Qua$6L)joPnN!%3Zj*cbkzbs6^{@6tV(Hg7jePC`fc*RY*S3rId|Aq z;5A%y(ZC)(n)Ghvtc1b4r zW67=6ZCaTXsD?92r_f31J?>SHVd_DvfYH)ACR#vRNa7 zUINs9*mrR4)c>p3YiI?-UH0+do25sFFk@6-~eo*3*6xQdxR7MdQT%q_}OMS0=y zH>Si6VL~v8H=FDx`jTznpWOc1!9j798(h4S`8}?3PM;O@1L`jvLV3SWZy9B0a0a&3lq}uw zY(5Rj`)FY(Y4WE!%7+j#EJ?-e78$lY$I8oMH1;Oy^tKN`zcWmjq?t@yIEemt;&EI`JcdLW zRv$U-6{$A|I*`cMq=&$wg_xv!(BJ#XJicj)_nR%rF>WMAa&0v-^RMd5zB+L=#GD`% zTvl1wPWKJ~HeK_Iyqjl5dq3>?ch<3G`1z4xh)p^nk@pLAFIl zfjx+UDp4qZ)A^`2){k=Ku*#vTu$4RF-KwRCH66x?yN_mjM-~tn+xmwJ@jv~MkbCcSi0eGdnhLyA< z6O8-g+Q2d9@N29MVMpdGZ|S7-34OpNJ9w63L9riWzaRsR!kDF;M4M*V{kL4d0~?i- zvHV)!q;4I7=7*n}qjL+NzT}Wtt1FrP8CvJ>YZ6z9)eH8p)IxgE05bDY_*Q%f+S0`h zIIWT$mLXM)Y%~P@2znE9G*dHwYCJ|>OnsgMOqYzK&U$R?URvYKB=zu|KQg(Wk*1Xg zaOSpDGN<8S$)9W4CrTk_L8Q3i3hgNr;8;=MU~ztnM&~P_{#nANIOLm_Ag+F;K-U4V z_?Kjs#uGJaA7~r*fbH@1xly{v0@6BH4`WIzaw*8NlXmQQm*u4i}kvJKLhV zqM(IB`vLEB)8D$mV-|fFH};dYvsxu~bhN)TB^}(p1a0*;$$&vJ;;!%3ae{3us2s%s zW!aZgXpP**EoX8{XYqvp&|FYe3+C(_)zzUMG41q3pq4acV9IJ|19=+2uuZEfro?MU%ep>(Dm;J&3CBwS+Y6$Oi7?>tAMB;xH}&L|2rdo zxicC0-vQ7SXXBnlc;YE-fFs*3+bg-u5Y}|bXJ-N#t5bgAeN=?c*oqO)pP~8Qg68`H zXe9eJz)(#kVoSp!NpG=>zb|rdt7A40Cw15LUTL_FCC6;n`1YseS36HyyzpOP(%+87 z>XncZL3jezUk{MkQ%q?nbeiZlPBz<3>0H000NOQU(_r`&{3^`|N)8LG(aUvxc-Y4_ z(w70MyMWEUCY~M>3-{WgAy-p5j#Vzn&AhB~B#9Hsaw<<8+EcIIpkPRfunl`)J3~ z;l@XgU`fHxvsprY>P|8p`BLVj1Z~z<2uwZUQbSn!h>3+&)*k@<(uIJr#`#;v{vqdt4IO;VFMB{ZRkvqR9j-P*>!WiVb_ zNkU5GY-1(3Q`=pbCVj^n$xcWiG!URvS=qeFIV*YQlYQNNfaUe#Dp!4Iu1@Ayo81e{M#Jq8}VZ}1t70QAjKPBw2_-PmS8hf#;MI3(I3P1zC<2__-%w5lV9u9J#QyTNzWuFz zu&QHuL8)+xIqT~F7(2GPP9CnNp$=C>FMN2)sw*9MNAwFWfp}{tcZH9CpND3p$Z}o- z4{$P6D7&Co9vw)nzDF!(dJWOz{2mxAtS5YFnD3PjIk>=RAc;y6Z0CkmW(ZZ&ln>Yd zLBTLbETbEFV;k%rW{IT>#%YTVxn^311cfieMeI(Y&=MRFsdIhR((Y4HW+-NFJ>WTb zES*^D=ZdF=6y?a*%qOG56jn~RCk7e?^wf|K_!H106YMevJ%$aC9{&U1? zrpYV*7(*M~(sWF>{EZ*5w9Fn&V^k(IGdYUhq7(il(fro@pcIkH;L3F=U#Wpb`}*Bq zL#UL21BKdNHm)}~m`g$Qt%ek2eE#ULV{GOqz;n`z3PamV$XV z(T~Iax*wuj0^vD{Ruj&)bw7V~jB;u>j#N0u7kiHQ7`DZ=l>8+zdr_xyaTF8rg`aIC zQirft$(SGQ65liprAkc|O6TSQSpDMYF%R+AW06*3oU9`4$6XxgYWV7Y_sy(K!8?;7 z@NX!}$XqcF*^RU;`rv-+`NkowyJsvkHvAT{WE!MTujj4tq?-JGleh2hAI(!dqeyYm zYpL=K=r0bJ7`H2a6)ph0a$qUBG$c<-g)ienuI_Q*5VBV-x-S=oPF#k-VG$Os8n7N( zyF(z0S6V5?g+i?WZDqfR`hai2v%@Y~9|T02oc^uUqbBWt9D82cMl^jv{Xuyxmk@sF zvAojsxQ`}RldPru%ZG&CrW2hs2duNN(HVqY5c#fba`=-G6o^;k4KkM7y38tIe3S%m zsbiBVz#JW}SW9z)r~3-FX7Lb|t#_SAGV)sg!ieQ{-{5A_@ps6UBR@D$(hMxZLaQ}? zN!O=|!R-Fm&&+Ei%o>eGpY(6_&_2Us%eM};Ro{<9IC-*(%%m9S69Cr9Bx(hmCOa~g zv$*9{x#j)Y@g?v*=Ne`=uu(2-O^a?{EIm@CHY2Aw?|#h_%|!J5jgV=943c+mc3Jv+R1EYoQ9})A(T@s*RBAzL> z$jGqTgBtT%A=1-TU;(h@C0&FqJT53wcN82K54XYBD>$rgnekXM=yPSz>j;K;#vzff zM=j5Yjh2@(=-uFHn`MD!p~Y@6sV>)(W2-$}Kfz|gIb5F@`&7tI4tk}MEPX#aeYUWx z`W9U1!I2Morl>RuZ@$u!K!5s;E0Bi3nSwa_C5Nt_*Iog59A8OCiW%Uv16B*{Oq6`Ulo)sWL|!m zGGVri#&H39C*^Tz|NMXw3MEB_44=>xoz*n!#!btGr21A zo#56At(D2c-o3LX<+mSK<41IrmNbvP9fr~e$xJ?!H>_i zv?haZx2RHdLE$zWVmFu@r=}?mu_vnX#FbCi+7~I??!DD99pJpY?TK8O3qIvTW4ntS zDz*LcgN4%M*aekW#V2$07k-Q-NtyLjZn2|cbflcf-`HRf4Q;1;UiyBL6`clzI%h*w z6QH!m`z$9p6=@Y(oFb=xDjqC9Ma2tc^(gUV8{USvX+IBR!^7qYV7k;koHFq|M^r+58c3I*=@Qxkl}Bg|wiNRNzP`l@>6D!^t< ztM-?niq4oTTlX(bZgutRuz~66)rf%(jxdzjyp+Vjeb4xXghj(7XZRq5xE`9s9$E9PHlaqmZg!o` z!i;h~#JhLO==@cj``diZK;%`rT}Nvd7VU+zRLpUEA1d{C+z=RKzCdml*!emtHGbXI z$2eJ0p=yCiYcEr>x%lvx6)uF4Zl_ucr@f0?C)~csm@5G3dDF;2RfSYyL8&~>szejY1^?jes?LlJ_{3t!hFs<3p>OH^ z`3}T0y)9uHoWYrw@rhG&(1CJ|DqO=GZje*G<{LDR{5P^sya9ciaS37|%VAECQkc&S zRl0}W9}R*w-|P`!hvDQW6)zq%S_F~kqCV4GF;y7ou7rA_9Qfy>S&V0GJI}7w?N~`x zF}2KtjftnN#t}vSYT(kCM2}mZzA5egqp)hrrCPt6qfya^QI+Pu{%2i+zjiE;`2Zw% zx!%KV9jXNKiQIQ~H}FwxV){8lPM%S}yFxs_A&f*4YUBg5m(rn{Lhq+uceg5occdV@ z;0n989ebEq9@gUf`G@et6B#>)5}#9v*agK4s)&FRi9~{0QShG=OlF$V4 zoRk+I;1tykp^^39U8-w@9LUJbVa15J)e7Z;a@716bq0QQWpr%hJU<`DyJ3gKqsh+b z)ridV2pJl-Dt(;a2|3ouIUw7ag2{#v^a@qth|4nDhNJ&joEb|A1uS?UfkE_Em%1VM z4##ZV?~Lqr!GnQ16{~!7SY$8A(&>ZlJz)$0hZ8S#MbUOoLXS&i6i;umwkl2S%UMVD zb=O(oSqU_lcTqNHYVg-K(CHZTc%K$+@x;|azDny{xP~T|LoX)qEB4-`6mzQ@5MNE# z>{H2zMOb+`i^NxnVHz&~(@`-@h|H2wU1T+r27~A9mkid)rws5RROpP@@|Xwmd$|Jo3WtE1_Ce2gHDq=paOV+c9+vQCZg>cT&F+sU$_DZ4C z(;+!ANX%-+Iqk*WbtgFG{WKMUs<+p_c{uCuXLV)UZ8j33@iVR zF8nv=<{-AKXFDXW-~cs#ad13FsgpFgpJn`0(2X6G0){n>y81m{Zb$O}_%P>_L~p0y z8#XLo3t_iewJ)XO8#QRtbofL@6D-~-utj91x8qu{^P=v z*W!axNz0*`>mV}VqJX}g=W|j~UJRyO493Ewu))8${57O1(oKugkyu`zHB>{{OxSB~ z!6MOZT~jFUBA-ELWaz46S$9wcot_%B*fse-kN;QgL^@QErZ0}fqgp@Jt6I-11o)+o z6@~Pj-AVMq20emDB}qt$x3lIG49_&niNnd>7q!B?9s}^X?)F_FmxcZsE7=Lf)}NpM zFajY9u3iJ)Jqqg|ufX$B@ybpeJ17i{xId1CzrEFdxgS%a>SVu=vr=IQyemh6;SM`VWZ6GJsq#L#*<32D1RvTI~5 zDSU_%tP31?*?S*tGff;e5K|EBK1go`f!C6}__FO!V_Tw!DlEQriHR(n2Br;rwtMlV zSgB1Oy4JTbEq_Qe4kx^2QYUDulFv!UAZUv)BT}asl$8w_pQ$B=hrPu=#%K$0_%Zsk zkhL9Q2)os~Xrik@D>Ch7ef=yu3`5k|+1;kAe;%NQbDbkQ#uN;&h#7hukg&ctFSKZS zH~1wjt#F)&Vk6+W2KVCChh?L1rlnHEdJsY;uvxHS(_w8g!S zTZN#e;(H8yxT4Z4a?BvfXiO~BV{$;k(5~mY-B@@KHQV9|`lcjmdSd^yVhWq7YB9Kv zQUFsuy4S+uPe)tC8tf zQ$z5zKz-hH03FkBPOamuE2{9^t&}AE8^@!Fx{Ee22_EdU^psZOw|hV%Wn8WE0|T%# zt1wf~1y{GSfS(#_QuAJluF7kAC$*q!4he`f3L76~7OEU5&1dT8$TqH9xmZ$Sk2~_$ zJJsE%TT+JN*9?_c4dK#)?~P?SitHtV`AU6${Av~|*q+H_#_;%5ZaL~OaAelh&(k4n z`2^GX!&_MQtQ~?rS*Ouc`Wky23S>VxCyOFN`l{iUH}x3-e+(jE^qy<)&*i6F7^<;aWbdqA9iJ-Y^bz30 z3$=%blF#brIk;#@NQ_5~Yj9YZl`N833R6yD zT>c%?hlJU#cZ5DwTEcw8ASt0gQWFix%DndH)~;x0x&ArJLCv*a3^nNU8l}>xKQS9< z=}q9EF<~R`ES-nJ41`EK=U=YqIKFoN*;Abul3v6Eteg9Xt;@onfQOvnu|%>y8(v>Og;}CVsA6H{aXAUBwk&RBk+HgJOn9|A|MSipe;;Q* zd_&#PBKRJDhsviN9;VjzFfQz)&rV$q1OcL;cD;-v@fFdwx+ib3mB00`6|v!+h?<_< zSy$8-Sk6lvSO1-_6F^`9?ADa$z342*2eR*D717t0NP$)R*P1*q-B*pE&BveqLt>Ap ziB>2{g-;v#J&_stkxy?JD$j6Z6`Rc}-hk*n>WZ9L&#Ygd^`#VE?SwGo0v8nGJbTrl+WxUpjJrMH;cFZu%NTmIDh`@Ii8&SPRqLeB8K0OTx3huf@2WSu@9wCWLnorl zWQF3+m|UWYfvLtnWL!q8)jlX9=lT6!O>MBmm@%iP1He&Ut1aK$Lr^wJSD>FN?9x5B z_1gmh138VzzkAN-=Z5pg$ceyhmMv}60nC4)Mr&w_263SEc8Vk-ea4F$2Rw82aOoBF zJr|C)Q%r%~2Tu1WhgE)R3Ex*=Dw+C8$?%Rc(DhKL;B>ZqlsY$3gy72Bvi4XoB_UX% z!{_Ko*QFt4uXkV5$$3%h!8^eC8!r>wt#_T13L;ZJ;@_f+^Xy&B$ zxEP1F!S?R9Dx_nj*-P+A$2@7&Cl6Y(NDuX%Y-@0MGEuZ3?j$+O$MDJM`A7XNIkg|K8naBLp7}L#Eqcv zXCsHv^(KOfR`8zP!ZaYXtABFsRd&2a?`(u9@o7f7~0QnIbqcA&_O$IR9JoKkRNV+g4afuK9w$q|Ot$0AQDv*+@;?XX2a3xQK7VFtMW9gh_8(zVo4%1_pI zU@vTSBoQzY3kC79#6O<4L&~Hw+iTw^D12l3l>Vu^v}_IJ%lYKmE|B<7pMHv_B*E#R zk?!Pn({&hMM>$SH)c~%*(GPnja+u9HZnbFBw1igqF4dzion_Y2C1vD5*Rz+>Ihy@v zmL!b`zoMDF=RP1h@=GRz66;yK)HC~@3Q@iewsw`}# zgBP-@_Pp(FyzaX!z%{)ZH|L?248E>~o0-XceAY1uu2_Gk zQH_N3P*~Qg!H~e`HLUF57>i;osly))AGunt0OgxF`B_yQ^_|E24R)mjKYS?>{(AU% zItPvCmPg|`$&Upfv{m!jR7gh1@{&R9P;O#aFiiAKtu@sg5k)SFM$gu&U59G(gkAPr zyzl?r97)f*-HfNv0s6u z?Eh{6LWVFkvI$EX-6&90TYW!7s34gK0 zh|i)p4m00zWybYyx5Ep+Y^s#i+GjvG(OW6%XX`+y5;B1!myeGUSX>%qAz|8Qy?8pe z>(M5$`(D`F6>S`As!%Qn^KASSw1QAjwmA94iwBfMvx&BVn^EZ=iUik)7N*-@!jH@` zP+`hI747VkheZM9+hc08>qmt%B*$L97TM*4?~{t=;v-7rDwi=u^_?5K`hdaO0wEOM z#FU3s!>_QVt*x4tF_)oO$SNBi-g(nW5^9adR*^Iz%!(cXB(6raq=dt+{A=$`3@oV> z&v(c1*W6=58T-f=mY;pvCVA6!4KI@O_}l046jucbdb&#^&bv-G7bD~b!U52J;$9xv`_v70_6kR@BQ!J`fr+!!l@*(<4?&24%FM;~ zuQfk(FJV|x*1ovE2*t-zeoJ)U4}?So&BOV;>1@`lEEG7sTpht#Db&ni@mK*0JQr>Far_u0##|g z^xUm)r2DsvOkIUv3T53Hr%(B_g+*#dJ>=j{tXrS&>wq;0nI=d`wh|7LgPaF^V&~tD zU!Mf{+v**cR^6q7G#mA@Mm2Gp%VR<=t|NQ_-J%sb=cn^l#W;BdsWfFVD`&;!3p{D^B zcE|+XVN=Lok&mFwsBxq8Q=UxKG=9u&9iy2xn@BhDW;{I%AmB|prN_y`RsqEF-zsXKTc5R|AK1Ue&9pB+$>foK*UAbnO^50jS;06k z@;_EbO)#Y7aAB6mn$Gz#ZPgo43xFXNMN)a8Yd~;#Mw|8nJBG3KAjl|%cq(eFpRq=> z_q09@Cj&~sMTj&dQ4~Q3B4s_qv11!g%j%&hlYpEqJCJ73x6_V9{y?_ExFQ#|z3eE*7`V)*?P02H^m9KZ%od1I?-@ zU-Xu!f{4JFLu@7LY(YIgSAHlKx?vGJZCs%;xQ--B^CK1^oH1YZwhtb91>dyRDh&Vb1(S=}hl; z3%bZoG?#qv@|Ixp^WRaA2>@@W+ZzSwwV=}R12>o7e2rzDgTmB?3 zkfHA=viI4}3!9+q6X-9}rsZdc5&pY=)YcKGWvus5jThtJE9)=B*RX9x^4`EE=hrdJ9Br5CrZdz8c`-e z1y{a8SBPr-4-YodOjDC@m?b8&H*NW{XbhUI?%E>Wzd|LI2&NqWJS_aA=>hbjlUtOL z+@Iz^e|T~mWHF=q-8cD!6;WH1)P|CtcUX0zv4?L5hwNTEyvdSz-AXQ?+Pg3iw@NTY zrIR8JW9pdN)j?ui!473euSJrF zSg{mE^YrEkfNltJ=$`Mc=v16NZ9lhTqLQl7=~u3aU??>3r6I!SqW*`Bd8JULE9TI> zXPO_ZmI;J{+xPex-_NV6sz*XyjsIC593Lzi6S`CP9!a7xbEKXi%)rY0bdJb-)YnvE z-3)w=vWnpjOsZ=pimy?d<=%0W*MTdh(*x)Ih30uQfmmxb!E^CnoUWK?-W{oj_3nmA z2__szr?cjN9p_?V6iNRirRjisvJ8{YkPLc8Hj1UUy1(_tnw=< zlXnJi78p#y2zvLur>t4{V#a`ZWvJeX+cD^sAjU!V&bf_35x}X>&OWuZb#L?T9vXj| z(XTKL*K0zd#pWG{TOM*V9TLf?F{0HV!fs>R3X5+mV3KVOaLE#vn_M&ArHXtiZk&le zZa>gI@}`!@(#D4rocuF;35gJN3TmC$x{-114l-jlsL+y%IF?1;bhVpY5}pbU&clMb z5xQ;qnKRi@fr>*S*}Nwvp*una%P25x*l!6ll;#?U=@Vmoy?u7+WZ^EDcU!7nlbaE7 zAR`D<^syC-?CeoU=q`y~to=tY9$uWctuSl2!~;bc87g_V$;7wHfMo-ADyh&stbD@a z`TK3-Ov8uXIi63GF$}`j&XbV2{fJ_Sk;Aw>Uo&PN@ZxU29?D!-myXQ#`p%r*^pC+% z=eX~$B=xk8Jp#~zq0e^s5LXUZ=vpDvq?5?Twrz|hMFKkOtXe@Xmx|-k8Y(TlovvKT zC`F8PH20wTcISQ!B*d@XrOBAs&!jjbN1)jZOHsFxreFxGlw3Vra^^R9Ri|?xD2*Bk zPN)!QJOHhm$I*5;>Vvn=Tt_Kl&VFgM`dJ<&Zns0~P4$&!M565YGmV6t{6lHv(HOx* z+67qZ{%4a^O*= z?MBx9ahY_*GL&4oR?R(c5f$WUTce(p)@pzqMlPGDo(3CzbSEm!eNDiJ3ighT$H%KQ z&bhcB&fN(2g8?P-0zKsbrb%d66)Rw^55}mwayS{hf&D90aIkdB% zN`f9kt?eLDlQ}SYF`Rc=6A*sb&k3y3{Q zx}Lybfp_uqKRUifOS&G5?df7ZwMn^{69gVfW7nzZBD%dq?X8$qQDNs~u8xM( z@>FIs5Y#xszYQakvx8#x)kCpVil3t1TmzLn2W;l8b~UJi1%B4S1|o zqgpRuc9tAVwgFgoN_iY?B|0#*Y!SU9-^u0B169p7Z;LIdy{lg5_-6Sgz$w}#Ur0ey zy|#X(l49d6u#@fVck3BNGR)9gEt$i*r(h{R@uO(UlYw)NK#}{WAf{$Vwe35u%Dgvc z(uHEBmKYL@2yEH`jC;q6y9LKro8qC@y?+C6SKL11wmx?>)71o6Ad&wb99LpxZ#;?V zPjsRvFZc5$#Lb5uHDgUc&Q%&8MYYk4Lz;vjrShXRlsXvvIW=@+pbh1+$zenTKN|Z% zY*6bsW+PyhT1n|9m7Er~?Uqn1t%Hj&Yg+u^XpFdvroP_8(Jax;{1uNb`lJ&(6*sm) zmXCc!E)qe;NTKq=N-HAPXdieScK)}NYhQ^KOGdLi;AjjkadI6X`u}DE5l`9^KI_YC zx^E^mUiX`BL~0+l$uh#*f>uAV)~k5S2rfL*kise=Qg23JmqU)s47Lcu*{5d)z#K#EGDl&ffVU)8lyEh~9Mbcx&61>BNrvN36nWRj~PQ zktV=pH|w~)k*4xidw^@Qj{uwCev5=v+iy)i0T;;U7Hib~%l-aR<-&=JV#Dx1uYQB# z4QP|WbGT>veg>DD)hGC%ZsAAS4_a`B%Fb_PsCiqx?jW)8OLy$v<1vTgEbC!7m3c`a zef*E8A!KRMMm!|WgxDT_zvO$Xvokoa3Z{+8bz%AzSJlEg>^NIznQIBfwpcNt9wpUD?Go4R)qGLa zwJcLq3q`btGnH{z{7TqyDcDw93U)l2(xQA?5uZPtN?bDtqf2!t+KY*Ui)MrLszo-= zPm2klQ$JBN5n9l=ZkGSP?VJbK+8=CWhS+jEX^pVGl2fR!Eajx12v~OGt zm^LQfy8a`ZA>Ov%R%htYQ_ChY7K4O3VE9*$INK$Ni0^%=o^nX@7R|5bc%r`9xpc*% zMedq=?F##&$mIo|XYI69u)Nm(+_SB)$zK`DuVzQ%g-Xf^u4y*cjZi?&@Sit;_g-SF z+&g8lE$;>OHT)U4oYh1l5;N1|hpvFlXsM{TAAQ;yU@Q3X`hHnvzEnz5C6im$kmI&0 zz@|qloaEnM6h(6Ug7739e6lM4m6p;fB>h?V=$@_zspY^DEqyV=rs^_m7E722JGh-D zD|20bl9h$txWqpEGdU;!13~leU2aGL4|ft1MilWC;jWSZ>O=qM+QX#2*&NUAp0ckl z!Ye-y7}M_NpuKwVgR+o^qf7e0p`#mCY_u2S04i!KO)}~Xu6L!z5T|t0FHb4Y;@Q9$ z3NhgyP_>8=CtRy+j1YCjkf04RR)F;fs)1D7D(ai7knahj>o-ul8XS zCCBKid22gR4)d>7YEPr#^Vs7^7QCV~LE98Nn zIZ2XGdj`jhR@zS3Y&eBUQ+e|&O$;G!|z;HmN@@dW_jCkDY;}ICUS@C@^Nu`!)@I>WggLRMuk7) zK9fgo!fTaJKT{GOKKBe>Wjok?s_YZ{5j~&x+Hf6hBFJ&`9Uie$DrUJjs_Y1KYY)1- zR~O4@o5z#5_esZM-zKa0Ku)VF^TuaW%~pVp*)@P@+RG_`!?Ur+r<=5qsyi zrS4wO@Pk?<4)+<6!RnGPXv|zz$;Vp!q*DM@3p-j^+z5YV#iu-jd0%UOzv@rXWjhgg z;1UxfEGMz%Z7{b+h4g#{K{O`i(Z@B@E2tQA%Ny6*m2KX7%ozbSNXA(EqL8GA{nKKH za;Q@W=ZQ6pIOSA~aFoUVn+K?0|5+!$SU!TXeK?#bWo~)-C)N+$D(9gN$gE##8vw$W za`h*tVRtvf`^D>3{Qg)WWa06EpBLI0(eLvEmn{!J%I^64%%#h%avJZQZsSL$do`0b zld8eiI}*;~nk`jEUG+n{6gBw=&%`glOH=R|=i62Uz+Y2$s$~9^0yYrj0HWFEifAD0RJsuO*c7P zL(fr^gK#e>iaP%Bj1(gyPjxN5cn0=|y=+i=-}SW|dq z)Bwu&xW3a80ojEg3X`>aoTjem@r~2k$YX0IB!unRyc@oaN14&42BQ_Q^RbTZS?hN) z%63RszyB<)N;=A#W%()4N2ts#7o(oJsT2^*J3n{l4`3c1;&cqE8{3Pi00r&Pz7{O(wgn<%yOJ}&T)_*p$ zK{}(5lq%Vv4b}U_mJOgM`{-{zH(`lM&T?tj%ya?5I3dX)7sYF{BR>rS|I7kn(bCZ) zPiIUfXcUgf8_-h-fa!cfHD~`zNI{?AfuRPk+oS17F3j@uVr)9M5c#79{Yq;iO<3Tn zh1 zd%Ww@V?erPV}&jA*`h)63zXceZXX!C+GxIjEORs>BD_K$%>Q;OTo~;)64(X=MTbYz zra*2`(C+O7aRZS}hf4Gh->Nrlv}iIvaMj7{{Dy*d87rqfj%t5$w2xWD_pgh*Ts`?! z2#P92@QTK+`+>7eAA;r!cf(u3f2V(xc>uw!X=!ua^ohmYz@cZ>(>RKuum6!qKokqs zDPLG5vNv1sFhIE{&Pruz`m>guD>9%8>1?IH6ZySbs!_*EGFf`%%ZNi23eE5mR7HqS*!NoErWey11ep7I ztJPZl!gpxXZ7@Qg(n1riXi6n@@x9tMMLNF(HO}wKO-Lu3YJZzB7#?xt+p6yv$cQ@7)Kfg938ihX ze$egl{+prR`z&*7Dw3t;@|h~%StgPyrEhmPe`_64i7`AeaIS%$n6lvjRL-{c+jnn2 zGBVfwD3#qYWwcJP{?zxk)?UQW&G&ZLBl$ZYT|-pwi9JVmoq7H^SItVmJ|dyw^C1~=%6qv~-zo(cZqIyM z2KjGM8&;zf<)h)B31=1bwjRxy6TX@ui!UrPTJr=jUJf2ZC|O0$FnWlk;4URlBFN~P z0x3ZY+XN&SrSF$e2SCyZun&1NxxqD}E}TG=WIvvhl~DwMpuIbAio03~I-jIBw zWsa!v2TcAAdlsZP;)(}k9Hod%4E&#Q)yOM9_=zy5)9*K>Gm79gzb8(9 zb`@tGuSjj((Du8`D_=Buk$Pmbr1E`N{^?7 zXWF#|LMG5HQgd(T{$30coJ?G0jGkecod#yDyX!XDJ=r9b}GWG=)>78iv{*<0SoycNDGA zpe*X|6GofUvcB@uFZ;?SP(dy1+UNqx9S7hqt_>M)Vf2{Ns-=+3p#FsW*fR;Fs`cpgtxQh*r zYtOf=Z<#er&MhAVv7-g$3%Bo<>4c=LvKiYa#}tt(G{f!Y1>*K;tBJWzjo9b<7PS`5 z3}F`?wldX$*I5)b%WU8<0G7oD?f;E?T$mlp?iPFySB3hOr&85gP9SM!#utd*RI z_Z?Fo`}iZh%q4t}uN$|VH{gC`L;t4fn=_|05aJGU==*yH4{~fgb{l8WMadfIRbFlB zs)nU}-#2ey`YN>Kkhrp~ze}%z6-2(Kvm2%nSz7{y(|Qm9BFX$8lg1~c6GaUZQJa-h zp2oh$M!lW`fH2{^V9?I*!;+cYA=71MpKJq=)A8lyM$kp?xHosZdHbIfrd z`si|FQvcQ7eE<7MvOaNKbLQ)vp%t$}DdF?A8q; zlL6H}%D68wsVzD_p(7!!>kCz=?k|_m3svWyAQuhbsHIO7=LUy3Mj@zyUroZ?3vn>J z@BWMc*OKv1LbEQF36l~A{fgulPEAZl7oSxHRCQ5^=m`sWFvG1^&(o(Q~ z4+wuOqV4&(S#TOBw&Dh>fyB7nW=<9;5wMaCXeofDL?2bef8b3_v9bQ8+qgE2MOLbU z568gj=$qoTQ%J>>PQ8*ewqbj@4x2?6_h$^R;(7~TwIbT*lGhM-j4CojOrbYlH@t`r zhg4PR*8Sl3HX}~L`qWpGM4!)|-;&2`w16cLA5uq*9`P`Jgpik!pR{4DPgB5lH26>Oq7#6`VS6VV2`am7q|-s3A^$Jf=u5tYWT*(vDBS*QcAd z?Up#PH(I9^e`O|K^{6o&!fqgbCoo77O)mZp65~`;Uu)CL%@)_fVAeTdI)B5>1H@tF z!TsFhh_JVn1zkp88hk-4#YTnd->o}t^N6vM$q;3)7N1G@^cp5^FR#4lvl82b)?SDc z`4FAo_P&$LgAfqRmjCS4rW6cLOM)$5Nd~usi1Z%G1V7;RLZPv=QAEL`jP%8)?G$8D z2*h09ko#%I1~etg%{5TM2+zj=K?mm1^}1Xz8(yf7XX5s=9h2kB?zeJ3ga%%#KS{xORhO?EE@?f5 zUWG=i^cvXLKQ}L0uaX*(gtXGF&Soh^NA;d<8Teigd0BMh$(dY=JAD8Lg$8!XTFn=e zBMP`$KDuh&V5>yJz^&reoUgByb6edM58tmq9UqB-o$F#l2C*JQUQXd<-Ka)m1Fz8z zVqCQ%ly6^P0=;@bx|Tz0E}7h(6WCAw&Fd#<<}l=B*xvMg&^f)sNCVAn&?%@3-^E== z!WXRoohAkK)v@bG(bNW$SzRO)iwz8v|8aTGM-4-p@1!VR=(v&p`|Y0i*9^EWA)s_T zP<-fA`iO^wheB-L(y$eb9|Nx*#u4V<$BkFr7l!2F;So+T@GB)vKItO9+2(UnGPja? z@Pmh8hir25LAz*n?u+20>js*R4qW^nFO76rsv6?>K9j#&Wtdq0|Co4P^4`dq<9ww*V8?{=C9O<1LF{05a!C@9R^pCWT^c&(QH)s1qzy z1SNLDO*kV1fj_>;_X12*nSz_7 z^)+-;Ebg%3mu*+$OQ9Jm#YF`XIFcsR4OO5X?cgpKt`zKv^{XRrs@B9{>Yqy#8sj|M|DT>%`aQ4yq zdF$PuDHL~$f(5Y{e;y>3|BLZ&0^2G5o(~v;x`>*?zEuGm;VLVpn%YO3+!BrlRIpn~b0o2C^Zt-9){0|ow z6b$5JeYbb~%kXsayd@ivGNuSc<7=;pn4EKqv{pF@+65cGwm=+^5L~*4liGSEFl9>4M#JGX2}NFa;X4t0IrCz$b z_MK{+LMW4(F5H9vGJfQ(1s4jq$J7QV+=sgYuiaL5a~z#I0GcMjB^ew>L-_s%74ycG zqH5ULz6<){Yz?QPiRblX7+oI5)k2ay@O}NBU286D5Y_B9N6U2Lo_qP} zs_vPBGrOjAWECzgH-<`{y7RXfI*)Wm4Q0;uM02x14AQKs!ob^~5E~fD@gQ8XXfu#D zo&%95^!mn02mB3!7Ek#FkEWSdTzXfL)^7W~#m=M~=gRe6U5R)pSGh14gHFFT4w~@sht6`V$gQdu6G!}UB)8}!wkqS-lg~7cFCQed=LCn1 z3BTXJ|8EE5jKT$3b=lZ>b2S8g;Xz4UcUKWOY<=ZtLgnycV2pa@_m^9A3|EfIks#cm zZ&=9bI`{d6AU5nTm+h2IH<+OB?r9+vuvL_^;gm#mus@#7$CwMp54$moi91CepS)jm zx2(S&*F?h1qAuYFvpdLlx6`4a!Fl>6ROhEQ9L|@d-;qeV`QkZ`@#f;0HU$=U8*!u( zNxN8o7%}F9yD%e9QeE{s9V8>d4r)xMZ7A<7rDsf4p$J)|O(QZDC$!E+*3y3DUbUCl_k6Lu}G76Vx!}K4ACFf zj&(kMLPP8Rk;-s%^>P7d;KOzQk>=0BIgHi|9HM zL6p^u(^h_B_GFBKuXT}5Q)4p4l`b|@={f$Q=Ti3*taR2`Y>_26kNlI}<;@gmEdx7v zkm^!@6zFn|*`GN~|4GxS76~QSr(AOFi&+0;sYK>5o!En!m4?>Rk+Dec^p|!(*mH0ALEyC~L$`msKg%Q4kq6n6X!ZGK}z47C1zcDwtd8 z7i_eEFsap+OO@Ne*mdIZTO2ucVp#;v?nUWygG}6!Ia-Pm$00XJf$EiNKY@6{nXI_Zg&m%zWYnM^GyEh<<(80ONaNV!7qfuJC;k>W|LK`T6g|$_*DBe7z)2= zoJ?{aRn(e=EGF{bxxn;sbrI0~I1Pjy-d7V5h8YGla)iLuSjpaZczdU-s`=umPy7X znRXr`D+8*uLkApA0*bQZowzzlYJXG-03@>wk14qk)}srqMKe=1VQKrtjgg<>MEoYZ zYtN>Mka5gp;JRDRTseFsZjuU?Raw<3c*qiqOeqRXC*U(&@Wf9SqkNv8|NiuFtl)Ou zLY9f7f}s-ys49H9E#TJtpF+vk+FbW$=~kd|<9n4!5w0$$C(VYJbypiHhMgXVoNSvt zCwPP!U%1a}R+?dcBePin2E0g#Q5M`XmZI%gZbRbn0GG+yPe0zo>M70!KlUj6*2~{=I>w$@!{}`7-^&nmLFTZ!;urxsz z>G=}C7B$=2rd}!mD||S|@GlVx6CfnbaFvyn!zS}4*M5+N`Ch{-pKml9Ha>_X>a9Ow zS}(2mAA9*8u5JxwQlRdhyE&uhvE{-~MX)9|U)>luxdtx2$$CkZ1qJ`o7EZw8-JNLb7n?=RHk1p@UK0|1d+1%-z;`qF$|8;@0Dq*=(vj{LZ$=mJ(eaz>0!L)`J>8<^$vIW@-0uy%Z| z7MsmmKg!X`L%Kajkylpgk^Rc8LYL~Lg}`iY|CRECF+X#~J*tvr)6y?cWNj>s5kR^7 z?zjn%BY9m?dqxnmj+lPt`Nw*7we0o%rSWMC_8Y&K*C#o>U5^YZBbWWjN#krDSL+pS znzvg_^sTKl!~zP_0gGvjxpRTCjcGPS13N*#{sbR!-d4m;d$M*eM*~)oywWmr4zfQL zGM+@OLsvH6r?7yy%?C&IgSGsS7DzNrc+9)~pgGoh{%dsb9y;gP|5ER2*} z9g32zueQX{#0d^;YAMyNXsg zVSG7^z4BBwgbTU7iygIBQEyyc(YU3fQ~4I-IV9CC^3gaVsvq5tjN{AyIn&5QI-FmHGap<hv$6SCBq3j-G$GQmKWL(9pM__s~2V|JKUn4~@ANJa|!sKo5sr};lT z1SQ05?0ZgFTsIQUQ4a0NlWV>B^o6)FUdA%X+YYbobXl8dcZgS~HaY=}d{tK0;6glk z_@RE)s=?@#s@}lMTwFW8WVB{q$mgd&O2d9j5*BUE$X%y^-HM zk^Ka8qEwN+ED|t-RR$xeGo|=f1Gpi=K9;TB)@Z?dK3ZbYEL(lKX&e~v#iDe0EwTo~ zxb%h0qEVVy@cVIZ&|~qA{nI3SAXRaBv+eKQvF0)s84VD2o&wR2UjNcNdC`M9XL|O9 zt+Df{qF6Qg2*Py5Z4uvs%;Oo3KV^C<|EGX(uBGag5jcC~vlx7Ugg(fuQ{8{N(_#_yS{W0h|e&B;= z!7J|mQZ{b<>vnTdmVVDvC+k!ohdqSkw%YM82c?erswbaIlO`r(2SSU-LzI{cNIOeW zU?H$cZXdpQC;sv=Zg>^U`UZKI;rwyofB{@D2aXMipM{>AAa%Mdee!Fbx`a}|Co9pv z)`)3Isy;{W@{?fXrjs4_*}KZ9@p4$68u@HQW$!FoA&ag zy4a_}fCYU(64fmcZ$h=m$Ji8!P>bhl%UMt*Z9afl_7_h09F^~y&x0ZsrW14$>m~ot zv0Q`2)cE2Y-YY8NzA}PU6`D?C-etTp{qi)?d?>mZBIzD_IB=z4mj!a|d!GUTn==|2 zU+59yGcQs)z6}Xl1yqwMZpuCn$-nJIVdp4%PbE~-FnC=oS)sJIg7B~&RWOtUF@r6k zGIc)-mZuw`EG`uXkrk+HA{tObE*E`f-m@sM8UB75Mtfy1&nS?kDu)6^m0Va`n}Edr z*p{giYw&Nko$0l%Gu zSwZ!J{j}o7{aDX0FW&T}Ur`d@-^d237}Z#qV@3}Ym;5?V{y(JKGK}47@g)U^1bVA` zdqx^3H>$*}+WL3D&XX<1%;2F}9Lx!Rli_5!kGO@kSp~5(fSlsfCb3f>?wOLjp^On} z=pck!rDeywoShP65Q;eCb!p;)m9V9hG#igamPAFXf4-`?-jcY}=5Zs_<=>9bPyrz+ z({tQ4FaTsbN_ysT<9Bf(!gR+KZ(p5bPkE8GFv!mzpz!-M{z4Sz`J#~i7F{LiDFv+y-@O4s_kjpA-n4bVZv9_xD$3t9@KUZm!)#|!azwz?d_c@ zxc(_k0|5N*&kR!Z8nWx-a#WF`2xxNks-4ov`$W7z_jS3tF83D%?-R1VDJxc2u&)_R z%k}g;-_}NUeyba>iq?f6)LtuFuW9>|pwkewuRFPv1luTWot1CjfND?MtKoqQCqbFE zitj24dRo>@d?Z2&JSeGSD5c-Ebe^lz* z!P~RZ#IqH9*trXX;Ka2b8k&dvvW>FTY0QvPhnr!1O*Poew97?C{ks8-1Wfa3{ z3J7tj? zErwqvwq8eG*~Od5(G~~T(;+3>G{?1SEwi3lGaUgijmqcnllz{PNGV4%U8qd_p@9NATYCy%4b_wla-= z|Fxs3X!crC4U2{}iLzKAvdbKJKu~btT5Z&;;IYIk{6FNV z%Hf_E#FOYh>C{urxSv}L{Yh0M<&OCTisc9=9d?u^#(;Yf(X;tuFXD-$(oa~GR75pY z?riXA!Kx7nitI$pQo!Y1KyGUPP^W%@klhj=8ead(URFdXX8%THv6BGee+t+#lCr-AkhXEk(aXrc$? z62rR}dA@|nvPs~e9k4t~Z#+x2QkTQ>{hlxzhovb8)ystxl$wwymT=7#0k+iwUT<+~ z0sRsrp`(}TbfRj|7yHy1s znqv-QH>=$!g#RX7bzlLVSLQxAS{M)Tb7I9GG8$@^|?+H3^j} z+sb;C*hCvBJ|7Lf?^WAqI23BD8%~PYXlVBd(0jxe8WP;bk99D<#>Df+IGTyb+Keoq zf0O!p{;?&pLX1L%&2We@G2CT&V8iE#ObQ@E^&0RIOM0FrH2H&`@onnw#4m5*#~nXY zXeK#55M_`e!TM12<(Hb=Go!Bc$IWEQPdzSBiv%eTn zgM;mHiI*59CHg*7NKhG4R7Y?*T+T_t`H;uKn|ZzsWwM@{AH=^9B+g8}KH7NxIg>CW zP z_-xZZ;Uir&JfRWR7!;DoEXKxJ{#Vc!+;(a0*~zkMaQ{|3_a~nVtIeDD=d+E; z7RN4XLC#s1Kw*OU@sONR$t>?ehaax&0IFDJu>Xw7I%^9dR8=R$hi~K?Lp!&iYD65G zK$k3ac{oI-iG?4Yn!b+DyBps9hRdm)YjN;=ap;Q{O4s9pf*Om2M_Du~rhawrvVwS{ zyg_)+r{$mTi;kIlg^~y7pL0-%AAe}q{KI!iy|UmG&RM#Y(g`&>Y_>dhVKn#vBlxIB z)t^(p7acBhm}pvMX;OR5K7V`&*RY76@$o=l1u3rwg|g!z+v+vn#<14EHMpw!v2Sqb2%BAjLVR6)#jEtlS~5EvbA@-BC^xB&UQ5Tdq3R#{W) zn(Ly|-gFy-MOJO`;)d5T)cFNy9UPBshJ>_v*@-{=cCDs(y-yx}gi-2seMznki@bgH z_A=tVUb5EKaJk4?4jYi*1cH*@1K$e&~UO=lRN!uAzm{6XmT@ zt|^y9RxcGw$E)pI!He$(@#*>H#P-LdstryMOg z*3P;J;A0(CO1?i&>xu{uv9o=) zYPOb}jfS)L{MH%Q9)s1l=Vsa0t6mUf5oDt+0v^lttv=t&tD)oIt4WgH6w=KV7$+B?)b2CC3o%H7I?{Y5G=h9@CbgXiIp)|r z#=J6k`u;;f=;I9D>`uzMi?P^)Jdx_VPyW<4DVuriUPTL^(?{|1@&|K-2<)s2qcv-7 zL|K^ua9pw0nJY=yf9ZdwOQB9}AXV$-={3DwS%cTWpS;q$waJe5rVr#C#g9C8L= zDf-{|KEFNJfoK4TbuSseo^eFhB1ohcI0r+;)saSi&n|}7)=93wi1HX~$kX5CVGK&M zOxA4e`p}~W zzt#TnC3TDG&Wct6P>_~PZR8xpfyx=xHbVhv#}(=ECi`TcXbib5@nsg-?=(#iNeIwz z0a}&CAB{GGUXKZ4hQFOY(&WK*kLfH4?~CZ0TdcIi({H!X%GB^%+H_eWF$A>8nL{P)1dZ9C zV(I;_OBuE9OjfAGW@8J+nD@Q(=zo5M$KIGM$Xh>AbG}2Zsz1%SBB)cxzb!C};P~y9 z8wQ+qL7y;76jIQ2U`9%^0`8=*Zo{u$jo{pd=;ZE*dYO5oI{FsB7tKA!3}pLy>oF9FK5D?u4K2BGaA5RHoy&Gf-X)> zw**|2d*?rVP=F}KfcSLUaety7bt%_#?V~>+ypp|%Hoa|5QClm^_|d{>ZN6UcE(F`d zGTUV&mqLQFic_T@cX^tMSK{vo1103s06je8gx91#=JoS8*}m}4k=={Q^kkZ=zSY*< zPby?f51$g-nTK&0LF-mnIG0oM7p?>N$RUA*WiPVR2C8!etlBoOogZ82+F!_hUpXw4 zL)NM%h=ot#f6c6$nB|W-fez@cjE?whZ+F-l4aFDoM99m^*A*cfcteReu<2VG`6I>m zk&R{(JF&=_z6(-?1ZTteSAAJB(p)C>U!_{EDBM&D7L7a^050yM~KpKsJ-@fGDEvPGM+kO^oWh(5xq> zybbShw}ZzAB1ZM;fa-n;hA?;BUroO6-DgEYK#e2@ZnH%HLsu!v`}350Q{fl6$GR|2 z-*?m3As;mJQGL-byu_Q!>W?-Yvsus_@w!p*W9uqscX3?xqK;S)KhGJ_m&<5`S3yn3 zY9%fuw?oRd1cfs>2q1kQ{IM|9WNhQ<42^&6gD5dwP0V(XKBz# z50CAdDUxollayj{EvCBmSoZUG8gH~zA6-jjzv?ejiG5;z$>eIoxwQ`Q-0oZB&6D3w-|Vg(S)rX5 zdRs(q7zaA_(K3fcpJau68>U~M%7jKZe!i#n=4xn(Qqc{JAJo1IdY=5Sl6pDe1Vod~ z;)rjYc^|z*=QEmmg?P4h0I7hjL54Iwp-}d|GML&bhHvz+MvkKD_lW}AmO}@2V?7n} zAYcH_Wy@-|BfWfkl74JRLY>qY^*WNo8*5b4?CHu38Z?;p=e*D5Osz3-ppfd_8KQeR zZ#zZ|s_AyjF;UTnliyy?*N_CNzfDQK^4!qtAx+YR1vfK=8&Te|YO+1#X{7zQ*y9ip zJ`mN9mQ2C|P2+wde@ZNXQZDB_6!*kSwEhR~gar||lzG&?(ii-lwUzDbArzB*&^dAo zM$0vn_dD!aTK84ku^j>w6syydi+2oBWO_Ui5-7owcsA{2k~Hvcy@62#*{5qT17Wv6 zmS<;`+R66D)#8QImp3VNiYs-&-=D6_S@=6(JMG{Ae^}+SB8?8qPgY{Hdbk+96J#p~ z_p6*DxvmiF*>qtGhI>&+2`rS4Lpqri)C3AtaTvwToYt~%FAjsoZ>*|}mF7*qm3t-q zr7nIo^>}Gs_nzX^762W?aH>~ezGp4C^p-}#8L%s-V^vQFqcUAf4MzfQ^qH@3$sWNN zBZN^{WEiDvct|)n?V0soDD|ripp4t3b*!yVZ?^LDUuUa{DZ5_Pgkl9#lZ9O^-*q~& z#2T!2vV}X&APT{o-XTHwqZkm~J+Y@OJZ0+2lLarBfB&|N0!a49ET)Xnup?(NhOD1p zcre@j88onxl+;JrmZ!S95`^e9Lj7eaek7;zC2ROf7LJ8tbIh+yf7ltf31ey7*qo`1 zD`F|>i@mA8LfLy5Qf9bh_o0xAqq}0U+#ZZHsBt;ua%U9zxIy)b!cn>xlBaTH|CgK6 zq{JazBE4Y1|Ghie9#dTBr>|FxbfFaC{JTW*|ImcxF1?#kr83$Y)lx%_1SqMmZkQ+y6oQB zq@(l^{i@Z%foUHR6{}}x&ZaNyYEaPCf)|g@0BAcm&PEeUm;=u@v(=ydEjF`VdvBhx z3_|bHon)fz6v1H zzDLdI@x?oX^di=#?@uZU`(Q>=tG5=&yy6R*C?Zp<1gEcv-X-tP{lRGx>&|s7`#$Zz zbIw0=5tXMrdPQtqR~edoCBurPMFk;PlMw6!sR8qThr1`XoHhYuehCQ^1>pJcp(}Sx>@^DT3rvZz5%7AK zhoY!(=GF0~0{Ow3UJ_(a*>*np*2_134s4mK`3}3Bf)fw!Am}PcYPG>c4q&-F@^ywY zu3y!R98)e=UzdH0?yNp-K_O)h@F$$xpyYIL&~TWINYrn>7OOED=@N^SGzqg}g6Ed@ewjUTyC#wIDh_+*&;L zhQ8K$b7eMIX&ZlY>VN~-zjMKf=h2?LCn2a4YMEJyD)tzEd6la0zRDn9(SshdOR?9v zuOL&H6yNL0-&b7R{jm|4UEoO#qKBBJFVH>al;&blzHEvr>ntMpSJGA>e{qdiNc$f7 z;fZP#rBxj=Va}@t4*IlHKjNzXqA#D+ zhy-Gi{}INY&1>topq)$0Wi#1B1pWT>33!;T)LdiHOD3{^F|(13jTv^zi=2?4}ie0y1!Ed#Kgd6n~s}1oL*ZhxGoCd zF%+Gs*8y|Nu@4zajtO_u$HJNxD$y=Kb*mH*r+C@HF?2&0J{=Uh? z1D}2&&baK{%Su8xAoamUO9lh~l-^h8+7-bmGO;Ua7ri^avn%xYCED|J2ZbYBf>^+2 z?*iCG=Wg{d67?N`otac}-v~K7D2EhrED>|_aizca#76FN&_q2@U=l4fb^F~VN_Fvm zudkQ)n6XpOht@dCSt%O&pShRs&&L5!VJufE40{f0TQIfoz?+gEiz;GMgv?!!JhdLi zrxZnjiFW^a%@^tukMRY1w>=8X3k${r+rwv6;P4)^08hvTUVN3NMyG)WpWUiS$CYl7 zdX&e{+h4yiV(I1nA3OoOcU&C~-h}#$RVog`2_lNQV~sfiBZfZ$o(mdYLiOkSYeIaq z2)a!;n-c&F#ZBlGt`dad8*SFJvNtYgKGU2|$EVgp?1bc~no&juvXRf8Sd4 z-*jBrye6{Ia&D-I1l?GRBk_3S;|5%C)=2dp_=d>_uKmffzCh1F>WtMmq@ zo^Gy26MEbOr{M(B#4ZRZWT<)rD`T*B7Y%QqHDE%)u4?BBuFNU|z+UT&4V=nMD}n?O zfQF8?pI^KF?Ap5@Mn#_}lk@h!wqlWu42>VK`r@_=5QB&Z1~w*<2Mj)CEL>2az7(st zP;eTSIZRhbpQxGb7iIIKUCEcsH2NvngPLIkmen=eI`$peTeYxfU7W;olk_EG{%5 zIvIcp4eb^MoI17Cr}G-u&GnE{6o_az^KgLZ@cg?QQrv3b>QTOaD3rq|_(cFw?-kOx z3iASf-kOtuezp=Ay2$39{;8a|yYT}Q0`Py6KW}5fB!j8eaUpp6i9N11y) zgV6wpc2=V4foLWSn@P#VR#LXEHyCKe;aw=%#tpQhB%y#VP0323oU=17FV();w^wlM z%D%1n&fR9fkyrZyuC&crN~16_CRnBv0~TRSKl zn!(M<)<%yD_GV3DBkS4Q+xJYVK^88TRI6+<40c4sjE-tVX*r<-RzEzjr^;7p*&)pD zGeB1iX<`D~)Wy6XD!fi-ou+_NA_DfIpatB~Pd#sH47p_qP`~gwe<6Bh#1=cj?_^=9 zljPfUQ{BrFrRicWhp{lHmT{htGKuf&E9kMqC>f8LLB)`UTLR7qFPS)%#;>|}&|(K5 zsE;>|A5;SpbAPtn5z!FUVv(A7fge=1{HS+Gjsm6%qvG3IM!%YUF!2_#znillN;(EK zL`{wkv3*?8>ovN48h{nuF*`n?o^XAmLs}wV{t4r#jZ5mq(amzvvraNqZm>OBu8n`* z^13LfpEQk#V1W@I+wCGS*R)HvyqQs$nv?+7pl{%-F+xeyL=xIFMcf3sb2@C@Zfv{I zcLGRx;RAWW9C<{Oq3;J|s>MSeZF(R&zjMk(#dG%GgroA+`T58@N+ikugHHcN20lp0 z1i)5*yM)%Nj7nRmzof?J*+bPz7m@>tn!9dzlJTe@&wkiM_E5+#yOE^6i8&B~ap`85 z@s1c$#2i0H@H7@*#Xu&n7$jLW1x&3~oSDCZuWSduirM)Toh&bq8g zTr`uUQuENK^L{wUMZ-q3TOUVM&yKk4tj1=Im>)_0RGV! z7Mt@!%=Bs%2NsecIfzlX_m1gr9IN(DHc7Ydvc{kqcB5 z!MISLESa|}0t6$T464qGTpSWZ9J2Owym6#?SOqKsm*7v97)B8yT;BU65UJ~FPA?Mr zQ0ezvzMie&Qlv1;@WU~l8X%6J{Mj;jbZ%H8R*J<~GBF^-8Uf4zC2=njaKp6hoT+&O ztdCNWc{J?cRNVgB2hxk|$rm?GXY_ML6=MTIga+*OdsaS8A$Z&uw~&w)J@7`@tMAkG8n9k#+82binnr~ULS`7HQ!>#jG4p(6ZCW)jk`&%2lOnL21>3Q3{YY>T z#_YD7)+=dg9o}18nZx>pHVIL`Y}pRF!S(#^Gi}`>YjIgU@IXAw%lu+zZj8pL9iITY zHgrFg=;_+ox$(>^uY66erGstX@0Z?Fvmz|A;`(4803xS_$gOMH6%!#ooW<;K`sfGr zknshISh&|E#_uW>P@?@stb?cRvhD}F-T6!MW3sS|0B*cFn&X!`$NT& zT&0(oQ$YpfZAr|T{zeX^7(@>s>YK{2?oIStEmT@;DBbCz`0P%?1)nSeLXI#QYvLia zG_c+PB_khYz2M-fCNAkY35oAi1MPnt4rQz;oBeS%MSh;C)bDcJJQKu^on-FL`i@77pf;?^w`o zU;%^~YZi25UQU5hWs-^>+dH2P9E#yUxxcSOhG!V`MG2cm!jb z9!Fj?yf@wV;_o?vmsaMB%h*|IPG?Wd5$NpmMx4>8s8vtf296=f=~DhsqR~lBz|b(J z>(ECa=6u(d>g9|j=)uMzf8NULhj>(V=>F-bkytCU>XO_XEO(#APw*1QI`52jYa!Ep z%kh$XYk2KIlj(YkYpkNBW#1xwkq7hTC ze?r8j|F-kDSfLOW3D1c?w3$|}82Cp_s^e*dO|j(iqOWk@Ihc?gaeV0&Q6{PWE^NPL zy|+i{|B|&{Ms*A-n9s4K${BbV9TB*DKiYrLEFlR9MoFuh*41wN zky*lnR#gTPt_r`AI19U3Fn!=eKvJcEE50|RVg6nDyTHXskg*~|R{G}_7B}XD=QfZk ze7PZ&;icHv$z%tsZie*FMwZl6Fjuih;T-tV^;RIS#hP5~#AWIN>U^4_qZ?5B1qbe+ zN@DbrsY|Y#F4Bq%fo!1?5_F16DY~1}u)=SjAny<&!S7{*A6wU}W&2e@I6RJfq5sP=XI9-4rr7>RyOd1>ss2QLCGRg{( zYC9LTCZX5M;^#eBt<#grVfFZSGwF3TR6{@O(KHmeQv1d~>mPhzK6(%WT@`XD z8zq=ZIN6`jYGYaW6=l&~NhAMN&_tTURjN=K`><4xSe3~6hhuOY{v&TOFCe!bnx`T6z;b9(iYmwEz|4o2za z{T;RAR&O14!eQAbP!|Fhaf+&rmVXrVQBZ(9${-yeNmU2d3cPT?=`~pl+#db>4hZMG zllt`!uXiNBw#!Lv{VmSyw8QOyL5Z<9ZvZKGT8cp|6@4X7Cb9topNV$mP}N03UN#VM z^ON(U)9AQTAlJ^)5MXmJcrx>%N3Vt#`s%5v7Hwr)Qc^MJk6y%m0?Uk2Vr2q28IfeX zdp4JwaDDI3=^Z;PMRL|c*k>=5fPO;;z3L?TG3Uq6D}l~4F)DfVW-V)yCg`Jlk*J0i zOl=1+NGmotq?zplz5{`|tW|WrZ_K{W+!{``$hDG+dQZXW;*I?S#^D1GCNBXk3&k)X zexJKi`sOq`{`Bd3+VTGD^XDA;0U3gR6V_V$Eph*gMKPft%t6nu6I;HoDW7FYI) z+!^a%$ZqW%3|&Oys@J&LfyJGzj*Oxu-fL>I30h;2{ozBK%uw@D(-uur%#wUm7!BJo z7w};JX*IdnQ6hER_;uu!Ql@O91exGhXZ}IF$ua7sk{|CmKdQS3soi8&<9C4++3bS<+Xsx$!pVu+x>S?sF<6?{~WlF(-p((fnK4#te-Mt?_eoXs4-?paot|S++{YTSJ ztmOpIYl88*ZHk_{rk>2l8YXvnI)fAmO$M_0C$j26@5iFr$;TX}uFvOI!XDV)_snmsU`?I>1mMrKj%`r!aYl4lmB%SEbnaUnUe}~YB zcP~tMVo^tpes-{{ej(?F`79-r+m#dKCh%AqoZK+q9m#bu@!$ihpld#<&As%Ch)E^L z4X&F^{mhh%eTJ--vS&8ic(gwb1dkQ;V|MWGD0;xbV5kjHIT0P8Xa`EXxKYVD>R(zS z6nAFKrT2_%$%Qmc9lpNSFK-V>h8F@6BU@GV6P}E6*Xips-R!=>aXE;~IaBwIIrgfvFE;hEA^cm#-UP>E^O z1DHGxunje^R;rh87nD4Ulq$vgHW56S&fI zM58eUlI%r<&z+4K>8z;Jaxf{V7!g3W9ztww4eg%{WG1WjLFko#nNw~3gNG6wlC)}T z`Tt?+E7-CM*0o={yBnlKx}>GMyIUlrI~Ls`NGT_2?8;)+AzWRL? zNccX*{#*S00lCjxeo6p@H0B({amp{YvyIKu*TZagwlfSF^X7jl&09LbVBMhBcSyp? zUuvPK63R5?%t-&`GGEWO9R9?1@YtV}q39K959kc05qyaOx8?6oWS)*Iib&zjNk3@I`0+oX}4G96!GFF{-v zzwu_)^2FBs_iV+SeVfw;*Sb&bHT5vnW4~FY@W3_c&|_4;nbTwT5wTp?;2M(q5vo>3 z?41yHI#PZg#ey8a`ziaS`YXR_+?wPMV9$R~h_`->vMIYd5dX z#>VC?D3!zjZowXGa`I!OJRaMP1W^GV`dqhOPAtL5a!fcl`ADb0KlAn(hvQ@-BVQ0O zu>C&KdV-h7*IQI`FE20eJpxZR+y^UUfoW7!_J%F|YP(ha=@o6};4hl0cebDd#U3>* zk2ipkV&neo*WZBXk zklQ7#eLz>jF6Z2)vdSBQy^m&I{_hY*%?x6r4XvN%^5mVhe(Aq1J?HR~J*e`1ey(H+ z(_`HyGO~vonZomCu_WFG|Fbvkz$F%m^+*Tdrk5UgPIA>du9E+4blv@7XY{#64HS2x ztkyb$NVtjPuuR8+CU?L2JUIHBBZW(63%JNPSbu56lJk0czDh(8E;PE@7esP#A1$SL z!oUosG85cI>+y!vKVE8Zejw1P?)N;ZzFS%c!*Ul%7Z+}Y7^IcyqKmaYJTL;DJ7=w1 z%};+Hzo_Us9TJ|*R8$BQ8Jk5{;_(!r#xw=%jY>5qMY&}Irxs|#JS)f6yCuzRJstPS z)I0NB#i+Nb>O_I#`xbR(N({3bn3O7b4zwc26U$7hC*9_$-UJAr%s`jhmSN1Svxd??{q@4sXuY|MeOq^5=mQCr z5f<9a!$$8P>K!jA`P0!*2n76~&V%aoj5awlzWtq*mr5C7Uxi-YZy<;=NLnhRK4_pStEsDGLw zbI;B7%(S@}%CVIT?Ys5eJI}{#ty7>Ze&VDeeJG`X;Ne+t5l|SkT}vja8@;Qq%m8Y4 zrsa3~BZ5ckI^;pCquFsOx(!UO^?L8!xI*C@%W(fOK%TENZpA`^H=Xt6S|;=P-t;d!z`HeGJi>@|`{n-u!0rw3=gbCu41 z=V~o|bNrXDp|Oz&!po*7-md-Yuu1-%csmk~SEYX1?xc0v{FUbqC;;m>Fd$_nGelOu@tln~`^=cViTt$A$ z6@r}{&GF8&;q=5$-4R@-i=4=?Kh$Gbqa-Ayq^oVGXrAYP(FDKCVoGvl;Ohf8zn|4 z=!qI%&jS0u^5BQiGBI(7l2-|DJLnDba#7={xW6zKP#89*{aLmnhOGewJX?|vpf&ai+<6rRc)+($8zzej9GbjOfMi?u=O4_j^>WNc3N*Ywx@TdJC*UD zT##Jg0?uv})e;0nSv*TmJTSDlq3JbWR;EG`USS}!n7$|=x3s-ftnoNkPWA_%wa11# z5Bzs|Y-jlKNlAt3K28^SYWM(2^CaM`crY3REna)u!)Y(Ji`Cocancst#Hf#azPzHf zb}|!Tp{x7T`)>GodBz8GHnsZ4e_ICEKK;G@@aWYCy!am;G$J>2$V{WlL*?P3N=sqH zF>VXzrpNDQhzJ7?i(NN3^ht~cyeK4V$&?y(_NZBW2~@7TNj!xPir_lCTix*>8vQ_qo=%i7h+5tI-r4iKvW_COyylNR3f<>^3dLLCkSn`qgswm#jrB4Ic>Y=kzFb<%*djbg5)Afx zczxxt=sR{lm{S@{=h$=&G|(wOb&Wu0d3kU|h2PD(t-gJ@@W>KKk8gD8i(QbHectFD z{QT}WibsWVQ!LS0d{ybJOa?umB(fXmGvtN(CzUP19YGBnkBI2ND}@;d;QRoLEBAim zA!>AEeD>bh-uWMQ;6LWTV`)WZ8zZ%x zu;Dw8SWx(^bRA4uD3Qq$a7X{!uWkjgM#47$%5K!_0743%`*m=n(9xgBwPhAIf!F2k>1mre7<*$zKetauIb-@ z145e1XFOIhduXY6lrnGw7AejB5T1(+3Q;7!aW7qz&yPgwSu?pBt2Q95JVZO_kmCV0 zbkV}zO=OPQO0xOfL1>$5Ol=dl(QH)d1y`6yBTBI`(a>_{@6+v0ePbm(A_M?5;j>ePDiqbA7}}o7W|G~4&WMdfDLhMqWWHE! zmBDMj;8g8BTPVQdS8moFu7p7@q)W&7CU}ur#>MNw0}!DQ&~yj8-5)P@tQLad zNEceZrj6%JgN;UR$2$fI@F77O57%c#G5E21ZOc?x)&6(qx@TsiAeLqcMn6Kr^gV*d ze?x0M+Pm4`=yOb}MZz8FKgx!7xBhT@cQy@betu5^n{9V}1=SYZU70wQmn@g1@T)qW zhsTxOyPgpMkrJ7~fjks8sd;gIGP(j$%Lp`0c?+(UdrvmRc{&nBbNcTo*?h0}F9+X0 zauT_k?`1y#8TE$DwkEOg4guil>h7)~DCxvFF! zY=HryV+n#K-`>1o1bzMb>Tcvq4%9(lnPBR>1T z7135oE#H$G!dX`Gv{*T_kH_Et2~+*3H8iJ~>q4HsioG)Q`Syu0wsO6?2vu8IEXLT2 z`_WxicYjQvoK?hc>gQ%Y6bxaFc^EE51=cQbee%KPV6JLIB7c%FgZpFk!~N9(&QI`R zCU_9$(nO<^2`snxKV0txg0~w|f+ib}DJ1{la{bO?9#(<>4RV?H4_UJDuWS^HuDf+b zB@AkH`b1hKBy)sXOErq)f7`9-nfT+>iai_(PL}1GMf{y^Dvv~)E})yrWSb}@d>H_; ze6Zb>7Yb?CRtwb%Xsd7j*{lj5R_V3=9Rl^Bg}F_6pU=(_TP}&7Q%GLjzh!S2wfWY* z3rg2l&TsYdmu*0s3%Txu{Fb+_fwW*u@l#0v)r7#rb_rkC2A8U5sttk$PVfKs6}B9t zhD&K2aIQDtE)X!R_v=@I^Yg|AP!3i|r8lUv|H|)qyiCT}EC}}0g2$g^{`9|vR^OYX zT!{!)7f;UwX#Q#ogjHV0vCg*!*#3S$xl;1A!elti0^t-=EibFB6b;+6?_}anZ?2DG zb!#F|o^N3=R8%r7h)GGs`OL5x;ZxK*L~-aUt&a^0Bla|4Mty-x)9WA@&6hQp*{|sq z9Ph(NU$7(&zd2e8b??Z^$HN45UPPWV?)Z0~ z8xH-pA7%DCaWCsnYwiy&RJwX#HqPkrMjJ_uhEUemZK;D6IfyFbqI=psF0^d8F#;Cf zNXGDd(titf>+}IdZw+j|7cIz7i4=MsncI|VlY*DG4EKRbxWtnEiW zo~A%E_!>e?n{4(x8P=&VCetWV&7}+^7|-T7&Tve>omTPaz9Xfw8_V2cW5wh_rJ}2a{Ti3YE*eRJFB8fvR<_(g9p1t!S6o61_4M3^c2i zbdF2A#iV9F$G+y1P;4ynu?EMsbz^K&gDB;~{F_Lu$HtF1QbWmVM!(O4E03OhLaYM9 zORLgfWKADtzc}4`qihkPElP`zzp#|P;f0nw^EQNrWigoC$b_ldR8Z|R`C+D@j^J(H zSFfE_4=9!cG8eE~ns;M;HemNz2umLayzBVRxZzyZJeL+ScH&YyXn z|Bb!&z5E6#uIev<^kpPgnC7UaqDkM;w|>>3^(aGLQ~s~~utWQlcqwjmat)ULq>I#& z>`xoED17;_A81%u)^|_NBizFvxH182Pb`S8o142`E=LO~`GvPeKGsdT8D zyxtWtZ_hmpLZ#Oq-l#K*l{P$Q12$$6I@tkF4ULl zsqJb5&ws(AP*CY7Yhnja5=Cv2ChhSKeSMK_0c4PrD|Rlf=6kSe!Gwp0ze{Nmf_~3L z1EESr$>4Q}j*E(N{y0~*C=;ljy;Nm`)l%mu6Og@8F~Ak$Hq3K?zY0timz6LvR3yz& zXMIVbMWm6GuM8b-#Z~RxxG*?bskvdtOQuhaNY48n-fbWE;uCuJ=fR*j<^6(EBi1OmDIQ%9b6!{cOfOUc2hqF(-1Jo8=9J%S4+3H zu@HO3p)ti9b1N!dEBhQb;P96)BhJ+xamdI?$5fKte1K4lf znGQx*ovP4QP5|+XVSOM~cTh{1$e;Xg#^X8h`m_OsjfV3X^?T)=C*{@CzeVV@M8Y+s z_MFCrFbZtYn<|g$HX3G72QN2viXI|sZX|>rp-H_v6D?Mg;`=&r=8dKb6U~{$yp=pZ|gi?Q`&Q z|9BYVCeg@JC2D~88j2Zy-)ESXy1^CTybDOqUquGaqm@#bcCaU@m z$}4nJDJYPxHsQfZrNG|b-&5r0c+inFt`4|qS_Uj+?*pSYPecj{)FG;Akh}Oih@g6`n za^f_dVW>G$JmQpie59!+AEyxNnDf5)Q7%XPxF+8+zWw17t8!!Wrh6GKQjUw5gaQf) zI%(iVqw9p@wDg8RnhfKen^V4@q{}wDH%=i9e5ix_HVM7)3bZN|dOOh_y@3z@)=%9o zxzw0uicyon+xYWeiK^GPLQwaldGbKk9^pJzgD?g7U^ZeV)B-BDmJBmbiulm8XIj) z?*@y0&ECev?l?xh{i1sGC0vlIJJsu4on2qIx&NE`=?@a>QRpJcb3 zb?yUkqrM0e%YOSgjmXjbF8Wna$YxOBzmABLj?gcf)b|L)RIu=0qE_-{y4RO@VhtK9 z{RrfWa5+VRzUS@Xb-ND{!EfCA@!#tI5c0hq8C+QrDM?C`p-d*k4g@fnGM}Fw00nt@ z7ZQ-{!N7-fZdNYx*Lt$?j(rQ8>5+^&J8E(ZDrpjs9?xXF;S%sTptI4vss1hDVO_AM z57wejS>V+Ml@qM#v@}YCDI2R)rLYRx5Y^8)39)P@Kg#dAn?0TMTJ`tc&DsKPDH%*~ zSUdB$m~n$b7#hOB z$XKQGOCOIA2WNA-=A*{lei~9J6Ecz*VhYGYp9oyex8%kJ-1oPj#l-Dd=7>o)&j-J} zA@P1oM#i5b7(w^;M>@mA_p*}cGbuWQ6CCVH=Q$Qh!-hZt42mwZMb1PQ`t=V;j;ZhL zzrC>=VC_tDKLQRSFoMT@e(9SP=1`PjkW1D>wHvq73;BkpH+#lB-G}-bgmOuAnV>Ln zK-ReyyP#!GR{JUPWfXWgNe^KZuL-mou<3Vx+x!ZU!>yzvhHxkp#YTF2ni?W>IK!I0 zq+R-ka?$IFLOF6Ag@&T8HL5BymO^EfKO$2?$y#wW2?M7U$*8(g93{e98?(QVep2!+ zVl>=j2iUv9z%1N8SR}@vaM#o_d*p^9Qwj6n9V?`+C5ME3Ppbg;&>)*hn;RGcaP6I) z!$#aje4VDDbPfv<;P$k$Q6)#D3X($0zp)m0go00bv{~^jBitvtJ&#n_BK-T^gM~Ww zGM_#0T54$It3&yYhQ!I=OhIGn(dxcg#HzA035EVVh>ve$VG~&n18@2i1e4C~n7y|2 zPG#J@oi_lcDXF^p`?~R?UGd{-h`5{_(l8mRB{5O9FVf=JwgJ)Xp-o$K$6uNjJctam4HVR`C6%1w!vf!Wfe8jwt z#_M)8$)ZB!7+4?1&HFF^$ucnEXsfF;88&-3bbO&#i3fMsXG>v39(e3P%o5n#+*}~& z`1n|6c4p=rYHcwDp)!*bZvFjx+H4IiBeG$SV_g2+nC`rPlbC~nvW%S@cI7a=$)-_8 zI04yCtKd%UxWr?gsAa%8-4SJ-!VsU0y?ue@4a^+F2t6+MNWlso$gW8?I@q zFP5d9qI}`%XhTT84dSWd@L(H_=B%Wh#mKL+iE8TF-^r;^>o3%l${uqS z;sH`1TIBoN$X(sMV>gE=5^;MsDUyM)8mg*N#?fAqVmfIyk&kd45f0+y{UjwEBJYmq zVXUhUiDQ^Do?U1%M6%^k-ry>&3tw!gatJ11(Aa{w z(fXq}6dqg#N~Rw&>Qyc@rhpbd9uJSAejBMr8f$LQUBG!zCoWe_5L zQT`T+U}i@9?YHY{+k)%an*vt-P!6sSUDpd7u^e1D1NX?ji43Jh-;KM&A%QpQiqK&s zOSIr5Kts;3S=J$2)Jnho!SiSGJ7aBgDjmg0grj)TYDvn#UCC4^D@J#LV$k^CWYK4T-^vJNuFz#q=n6O-4cr>_G5k zD*29O@|4zSlT(OcfKrOSLmSkON!`Ax6gJ3B`nFi0dhfP`m?~A@-a0=ugwf{VgTZxZJc-Lg= zHa>ZPgedeZiI&}@8K11l69E!bmFwinf682nNg-9?lunHsIN%~qFdkU+(dK)6Dn4ZG zlMTbsGFg*548%`fIdv)I{OJ$h7Os_9k1!vTLAZ`ux+2|9xrqu95&HKNSag*iiVL@z zZty^8CxpcZUsPHDdUMK7uHJ2$wPQ`YJw7KO@5G-7o=RqsF;FNT*nWS+^+#F>*d0^N)K>tP)W}Rsu76<&KpRR7NQgT_z?bI6=?j zOHjlp&j}BgKJz(+73Ya~45!xAtEsJBOaLfEMe1y4OT_SFdZ7X%Q3xun&nG>TsR@p&<{&Z)zBwU~ZT0*&MJw{|o%j3(_{PEXF|0UR z=<8P>2k#lSurN0_yu7*+g%Z05cE>9XY7mY8)KiOAY*obdfBR~7{f%efmdVy?6hrLX z#Ra?uFzi7Mm$4H%Fvy}0QG{Zt3GYr+$|TN{WoQi2elOIs&TI;mBurTp%%mGV(PAX{ zRXQ9}`9wD{iN^n|$wA#cHBpIP*^+a4@OEFlx^2U97lxpx6!e73Ms8};8oJXFbv-?=MDTI(FDp10&!+&v>w%Sqh> zwmbUk9i8`9fhx~`>Oot~xrgg6{D@~~LEVRovTvDAv@|q^Jv}@`u&}V4^_7(3z`|dP z{95?oKrka;XJEYs6%v`ZVf%MO7@ zswP0-bHqn-@e(4>qo#)Ik4`p)>d4vbMKEg&O>XdFgpc;< z(Oy0t_H5^-hJYv4W%+WerAv-%5p!T`kJ9|S* z+v^PChAsLWRe<@m9O_qr76Sq}Prk(5HlNW|w>2_$MuJX`|Eg=1Mu)7TbQL;Z?ChPj zzd(H-$`Yz`z%fKQyXc;u(d?|c-DzlmNdEq0bV(*+=;U{@ul4A8`hCFf@5?PrOx>qm zL>!!@tANjWfwa$$)l8j3iVEF~o;RFd71Rn?3=v5H6_uGp^M-4FJi;wB#WRGJWJLz0 zPsw^6DS90YRqCaqAy5_)_r5*D72j;4uLBT)_$TqzWWztskq~8382&D3M+p8UY`Esw zN>*x){n`Yz{Nm6-mCY(Ph5OsL=?+VwZ+FLXB`S>Z+6Uh7+w(4`vGzX25J&}lsFa1M z7dRmUD9(e^IL$jFox3#qN>NQsJ}V#`PgrSi-W1(1RVf;M<;F+s+}tB_va$<~mX>ZY zou;Q=hOEIYtyL>CxN>J!)TEp~eW;zg12F_uF3bG(nZhJ8q@-=4vdLZHxb{q;vgbiGuzEDG5(Nbd*gA@PCBhqBVd|q=#Gt?cQxSYL?~wSx}wnmgI-5pZ!SNpRqnh zd>70`)RB(f(W{r7CZiSDoiE=NP6p*6HI*UHR9a+O76x}HAwJsM!o-92h6y@0sSYi_!7*d$`9F5%lcx?}isbMERoI&I0~) zx982Lt*2YHf7^y&a>3%mXfVir7dAGj6KkQ1_{La08K}EkZ~iI$9fGC{Van3KKsc2_ z`L}uNkKC0WeK$~BE_0P|N@b;+WW%5V?BJjV4*!PKb7CAjR+b(1Pb4q+Qb$a1sSsi5H;Kx z@1M_G{pY?vGkA46lY?;8wIw!AzcYf`<}elw99%oi4&YI??D)J7Wae~%asSbpfXy4p9-5Yt#~#ev0g zqt)#)T3l`O3!0rNZrIq30MDzn79XDYg+IfYCT=Aqiz-vaA5;$8eeh6tKZP>IC7SuL zRz6}|SrQsX$=`!#&f9(HrwqCH(j$LL6%l9;z967}b+6)t3?=jhtsypPJmKdXuX8MR zVKf#Y44R4LXRzVkjhr7-P(s|)+zQSb@6W=#-7+DFxy%~A5+!SCb}+KgbjlEYzC9%J zP|k_3#G$`!7xe}n&-%Y$P>ArWWDC8kZFoQmU4jMSCQmSEqI?EnxX)a%20Z};+uWiU z7_G0U5O;hEoBTKdAwee#_GtA9cKzml&}DyR+nKom?{f~8TFo8HuzYGa5+3YKh1i&G zGeN@4N5!@j%9elK)QrOn`chqkQ{nV-k^B8 zZ-Lp`z2!>8j+}wC1VuceN!3~QvyEK52(YF>`T3!;l3HO#y0Z78^6$+8M8+B$&2GBF zWZ8yP?QCs#Ztw4Xj9a{_&i8dd4vYFa{%|l&;P-sG9^!hk(rN)!VmR0jtdReC%kufT z*ivw!;H|!CL=FYrNLCCM9o>S*A>p?g8>#^}hJBbfibh68WiBq*-WP=AgpmhOQ0gb^ zH$ah{Ei&n!pTZ^$ddBGr^vY{LVq!*W)xIEkK51v6MzSl?;Q)>ckP3tPiCO?y;+S8B zYNvqRftwtzOGO|a>c~g|I__Vf+!-dh(me_&1a(>B%l_$o4#Sb{``<9Z17k9M-z8Uv zL$3TSyp~tR^EQI1D;l9IDWcMMis>RN1zjIsiW&6Fk`9V!fYt|3q*!oKj~~`f@c)NXZ7e_ zosKECb0#dA75ni!Y8lL%t*8+Wty1C~ zX=`T(fbSa|=5};+48IEWxkD15Fdf%3GQtA91CjaQ#Kb8cWNT;3v^@^Zaq?eP6^-I9W%j%1hILiaO&yM0H35U2-X4cmz1NC6q0Qk_l7nn;cjm_PW z&hQWf4*y={%24@~F>kMlF{<>^Jj3Uq&pSs;owN*yrACI|_3F!et}>xbr`?$_->Cub zrNiTRg5p9f6N(4@DdoP-yyO76L*=1n9E=vytHol%gC5!P`JW|H!qCp3(1jr93rQCL zM=QPUrvQ&u&QK#&G5Jc|wb^``$}C89nF*jf@=5qiKU%(0ubL<*2>JZtV@QmY zLEU$`7?ULVv;ZCsv1In9zf5x$4}s0BsNa52yxuO4@{dQQ{Nz{tGG=IY8H8fNxAQ7x>ZN@xui z9~;T|-R<2bg7Q0l)7A6sTVqSy()|1{c%-B)FPHzOb(p=qVa02MK@3hj{vYP`O1zXZ z0A4xBDae%gD~#j(=jLt`jjFTAL1Cfj-*hg$HZpCgeX%o_JTxOSwL~7hl#vEbX;xNl zSqXCBkeg7ezk%gQ+Kgt9Jbo5z#qGscMY>pg7R*KwP)}B5oO(nEbj{Ajc9s0w!pMkZ z*sD1?tLTzLj2Qm~84R^R3H z{%BF{8i9Ifm!yqk&KRIE?UGwtW!4O2LJt{~Jeka2cRC{OduynBhQcDVV9$#r7QMdR z>ifC?JcENF5b(I;`?TEfCn_rH6L=1m^{kS^ieJ6b5dEOKI$l90ru_u~j0-{QvEU;> zKEWYajBX7^Hu~0&WK%E0ygA02Dq3Vio2zFtmRwOa`6X3X!wW3#L6rIyc;$R;cf7X3t9~&pMsI=D zdhT`52NU!2FLxWgC@QY57pfrT+CK9_6m|!YwKWPiW~U2(errCdG<%gBN^EVhGp@Da z;U_>y0P%;1h3I?k!*0KMr|3*%)CR5n1^?}jZ{bL3rnM(iVIHWE*l!|6r*~RzH8akB z)m&U~<7~_UelPJn;O{k|rheqI#jP{Ym3?w$R0uejML{Sx?)4)f0o~hSRw$!u0E)mk z<-rB^aTedJ8u65`fcN<1H`DRrPbv{Ke%5P?{2o}vXy^p8?o#cw#6$#pl)`%<)}Cn; zzM1*y?jebY%D#RCw4bmKJV?C%ps?0uwDIFVt)QwfGe6!c&u;SAFR&khjQf)@c;AAN z5-jw4HCc#wQSIU4@7;h`BO4w-BmM!?QMhGk&&wCFnk{Tfkg8ai0@BZsk*^EjJ&Q#K z?QMHJ@G!y%YA&*%CDK=4k#t2aKQv_Xw-^UPsl)=4q#p~74@vBE7QkZ=8C|NwyN#sj z>5GPCWg*sRKO9kfH9GsVvJ!9d?K98g1q3$yp{+_lr3}mWgq(L2$c~%PC>*kU-ng|} z;{Fd{QdG170n=3A8GX>YOi((q`o#zKqPT|(fPrXrze6zhrbAw_qXtFG)=H-rI-y0J z={HXg0zbY|&iWJxl{1h2RQ)^Dt4YP`nCIs2iB#>FK6P3kCZXPEn$;My%u zU2W9t@e2w#$AlH14ScN$ak;r>%HrZ(&rvw^E9Rh;#+nu*#>Q5bBSI#3c1D|B4|33& zCxX^+R4B;S%Ole2-)5_^#FXs#u3+d+j&@ju5@>u$F3M_XOo)w6P3L`L*4w1N>Gozp z#WDCuoZuBP_Png5!qHkdbOV_>#i-PYG7ZnwoZB$!THoGAShok>G<4^mEYDAmz6ZD(zgODqbUHt* zPjOjkX@Tco@J~UHi4~c&Mz!P{BqV^-_tUXqV=$O+Pk|A*9{T=HDnyepo1N#*3Wp7R>2)5n3{wQpOM}{#eymk zHiT_QQfm~>$HapACpHcXha5d{$OlYh)6vCE&{?CFyN`?;uPD&Q2HBuwGt>nd6SI@W|k` z*1z*R{a4xZIX4OT5*!4mPY( zS$(%X$o$1_)zYF~N#Z)tcOcf^+A)|O&A~~-FxHKcj4(Kb($VagG*5&I?}JJL|1hCS z_)Ayqa9my~!bl-wqP?)NyL$*(hR;DfK<#63cJ@l4Oagi1@k;B%dyaY>#87qBX5auD zksB5k7EH_@E|{UA#Ti1p$Z$ex!IBi6WAe2j1S5kJ&H)t-t{*5|{#)gB>-4G`kQ>w^ zkJa0(8lURv2J7VPpb1kv-)|w2>w6qB+$t6h(LUDh*V=|5x$aK!wwnk9kTTONcKljf zGtU{M-xZ9T48}x-&sT@W6!Cw#M={32N^i2kI?7nT(F}`eDcpt5NW%riJ);7J- z7SH49CP%UY7DLQh|C{WoCg}#zXJd(Q)9LkYLM~T2T^4-qgSlVWByTop71msDSnm@^ z<(Q=&Phn0)**b10_4TRd*u0jxmwbj2Xlw?@*ztbg)X zn|SPngnAI7Nq2U(IxG&f4~^;pbns8rjN@a7DW<&*G0PEoJpnUXTcw7&nMm;P@XD5J zQNXaxgm!e|`XYf?VQa@XRmxKASX4V^y}LjO*q@r$t0*NpGMPScB%2g`e6K@Pvbt26 zwYF8jx&I5U^S?c?&KMNX5OB(Td!zAoEi5crudc2JRak>mgFXY`cVvJEkK`Xee$dcM zNOaFbYCBiJ>Q|V;oS2fRw&C53L5fI%fw5$LHkT!k`|$Ei&!4IgOwj&q0%eO^OS3`) z0dfV;Ey||i#+ut9jz!(|#LL@oy$VFOxFKbXjA9vSZcxTnCRbEPBY&&6qvt3As zX>zywHrHG(J+Y;A3q|I|A^ID!oiHPzLC3l&7DC4ekdTH4F$NIz=#Xrfin zX!g(8VY)bwGLglQ$?htMMEZ3q|2N5f6xhKc7JZtJZ^&7lL;hsCk1(?(YWgs!g2?s*-W ziQDvKngRQPq%}Cn5GXy)dcRvDObB$pNmC=`2Z+nvmR!^UDKE*ZXY}vr&w!%B7sHUU z>{O-^Q1X0*5`a<$-RywVvfPk!a~`;4Ir&!9gvsqBinB4C(^>H06~Dm({gv#onS%8j z1iGDm{x9Um3TYe7sxSD>m6(O3q`2BYH*xW&kQFsQOlfFTeK<4Ebaufe9ImsQXFZ!O z)oBL(T*~66_mr4SxUahuIj^*IH~_pOV-n14W`3yBC51HLk)jR{egV;ran!CB81wp+ zFKrqr7d*t=i6Tbx8=yS?p&Svt&sHyZCW0x2*ADU+a`{2{Rux&X&-HxT%ISd$^eVyK z>xkuAyKxv_275s8I5be{XtKm%CM6w?GNzN6Xr{FBk6wP(_C4lL7g}`Cl-dn~sp6u6 zrPJ0n)!saz_a3~xMDk5zmC{xdt0*syYLY5(24%eU=P(oic~s|%j~FN@f65j?6V%XX ziHU-OFo2|ZX<;GL`*Ls6*~sYchDJ9mbnY@J)d#7H*M0hX&&|X%B&DKqf&=*h+`~49 zp{%!BL>Mk2c&{T++7c5NsZ9HVo9?5zt+{!XE6N9l9xlprP+9ChYcNdg5GSR@!`s^4 zS}O{H0m`#Q-twE^2oZAE)|zepW1Jp1J+&Abm^jAg;7pG2TxFn0V0=zqA|(4I;Munm z2w`z@Oa4<|K<5O?=6WXWd7nbyD+PRTuXA%N&u${t1EC;+4G7VCx--d;Ed>`&Llzdy zO5ghLu(KTV4-zZFEqQ5w1YpH!Uwa92*o<3FGlhMxwf=`1oZa+; zT})ETy$UcUP^Alp5rlp+?s|v_WE=hjhU*Z?K2o=@NuV{8`!bODrZcRvBEsoAP4-#w z*l8T9&e0N_60vhoCUnORF*uCgd5UD$yQ0nHFYn0W^lJjauzp zNu$Gb=_$Z$JM!#n2X!&%9kgF0u~oYEd>F3u}whdzybl?^z9 zhJ4S!G*oJ~C3=QH? z(r<6`J)Z6>7iv-Fo}X<#nX0bal_4*m-XNrPcS3{1o0unP1o|vF&-#DJG2GtaIK~Vm zY~>1k5EE(jMJQ~4>AUlPfF)XZjRwdoE3eerFILaL%2z>@aIc%qN^-v3=r;~F_O((v zJ5?}fi!>OrLSs<$-T#|Td@R{=k+m2ZF+Dj+Sdoap7u4p5cw=Kxaou92t;~XaT_8;n zkELo803#;B(O8dT|#(bBCSj8Qc|p!Cj^fG5$W+9Huu=30=lKEy`YMd)nQ>r zcUN&0CD}l5v<*&sm<>{T3Bd8+=JrQWlHLr4Ly1;-!bS|0j{wPs_`B2H9H4-np8kYL z$m^6E47*qm;CGFWqJgXUjHB_<_EQf`roryG0$Bzw1>5b2x_aK#<{EVcmKlt&@RIKx z`UYs#gwQK>i4i@cB2`tn2$wcZR({ktyu9cx=#I$!QUtr9q78(48AFzf)12Ah!3>uw;C+L)M_xDj#+ z#tO?o(Sqt3ct##xW(rP?M+fmywxsmN1K<4;x!IuM**UWqOM2ooLMZF+_r3{+`L=YR zC$lD|OZdD}`5t5rq69(Z4?zk%Wx-F|I;LaqI)orz&tcY(|Eml|erwAekynt=myb5I zaBT-oMvh;*WM66xjtlp|83(rkV3|yU_o;HG5+EfWc z9Vzr1@EYz5MSjD=a+q0Kdf@yY7a+3L69WWf;L&YpJ+7*)1|yL@l zZ#KyzA~yfbR|H!$C4En_w4q6^`9Uef_ocTBL>>+B2%F<)o3z1XU=|ltl==T{1cR8= z%WV@LH0^MW4YWcBEzLU%luldRQGl$Ugha;vFqiy@x|s+fI$YGmsJ0;I?0B>6+Ii0ue}8_i!4VD8l;l^8og}_m53?OB?4^mP+-JK9=+ z1ZX&g$`W%((&ghjkFoTiQkjJFmohIHc+^N`B_;Q4UWadf#njM2ssJrj1v)qZmY@I- z1xh2H!~$-6H3V$NHRGd$og#L2uizAg*MUh5{(U+c0>(z=ueTztK*BK$LLL|{c$E&@ znmt1XO}`%mLfs}O!-7whNFHl)oONVjwdDjf2 zBK9!yfkZjoJr)Dg)8|iLfQnKdYGJoQS2(SNc@X3b9C``@Ce-|BEHfq4g6LSy{L<2&N=zXiYQ&&f z?_G8W`{q5JG6W)0QZj2cJ~isS%I<8&yVITWi9# z=S00jXn=-Z^PTIzG_%1k5|LtFXY-PN_{bT5#gW0Y_^e&;;jH^VIG&#E9*Q!XmN5{H z9317ftW1F44q>WiLH^J#qb@ponbEe+^d%oWdeNGC22e;-!s3G`q*gZ zKy#f@dl6x6Z&;oMcIh2trE%1qB3K0`aqoZObmEm5T{0c#VFqZZ2<=#2+CAqr#h|toi;?VI6Q_)Dt$j*-S z-Wbi3l8}&yXLYs*d!hH=J^XQPW8+7^#(kIF2>1Q7t&{+>o;_D#2IvkCgf%KBw)pWH)k(ZFB;@Pu6=vwB| z)lLlUY>C^@A?F|rhb|krJr`)czE3FrcOpnpzxwluXVHB%?H9h_&Hf%p3RtcZSYG|R zxUkY=K2k*0%Wp$+ez^tiF2sB?iE7C1`ugD52#UhDg$pT3H6S>eDGzZ<@T<;Lxd)8?a z!9ajH_IYZb3+erfU!@fl-jt-IE5qd$BLj#}H|xZv-Ib0H4oE+ZWPLS>q}5)S7q8vF zEYjk-vb}Pfx*T2UdmEF5Cx;hM{sRKc*nigwt7K0c2h`woK5TNbHK_&k zvG|Uqg^+YQ*g5Vz^z3Wa5kxCUqVUs}+=A%I)(A$N=ZWtxGOT?;B7gySU`QyRvF zfGPnJD{8jvqL+$_Y%3YZ-a{r-mz2}U#KgoiH&=%PM-HCcAO|CY zG>m9SkuzN2Y{mBG>U;p0V&CTG=5G2Z%D3c9mhtTGHu5;{FA_~y-r7zJ99?g60d+wV z+!;kUIBz1fh^M?d4@e$XWMsyCSt-yyX$8du=Ho@!h&53=`Wk#N_`bb|;lI5EfXzSK zond=OTJ5-U2QYRYMIE4o3QaA+)9p}yBcrOtO>98wGxNSNfL^S;*f~YYjcQsuF+H3H z&d7|QrXlnB+?boUsn5b;Mu^7am^C@*rAdsY=1{2$9h0bMU?mUF+0hP-;&^eapuV00 zcJ<_B`9wo*?h=rtF;7p)Q$~P5@d3J#NarzG#=BadKh=iNc4k65GBW(qS8e<;*s`*i zx!j*V#hk7sKmD-Wu{l<7x;+ZaDxl4t`k`6^D)M`>&qxde`Sd`%=<_-J+t&d4G0F1Y z#i5Pw`BmK-RBF_Y!_+fG&VWm*sd+VCpjFz8L7#-g@~I3*aR(=uS|U{0ADyw|;@y;7 zSRyDWi-Ueog`|8{@5}!fmc)Z1yE7AQ`Q~hqjr%Vu_rcx0uzqJ<|i{cv9mrEo=Bkp>^*vD1E^V15z=V)k#`sq2bKWtp<^+PMc$AZ^R{zOJY zidWRTCGA(J{nQX4=i%xIYsJ1NYZWyf$U%KJ=jbS3y;56UcBnR#X9?=<|59_A?7S7M zNlTmX)|A{c|wm6QxlJd65LUx3> zKRWKP?R3?`-=!tTvR+zgdV+jhSh>g-fNsvsJb-cm+Vf+Gp*O+lk?jDjbsES-K1cU# zS8q6P_^Os;b={s5N5wP6ax?Q%m?j_%4G$4o2;Akob&s^8~BqH4D}yE2KuCd{%)v*`AJ@#wCznPx8b6+KwyB; z@^rQ7p^OIc9iN*vBcMAzBoaRRoD$ZNF?^Z*lo}85<@B2>@9N5v0tJ)#<*t&W14#uI zNz{WDpa6m{M375w!h0a$B78v*<^EHS6{@Fa;i(}n1$ahS*2NFK2?NgUiP6y`4_KQ6 z>Eg&H9#A>4-0eiWu&HpHFIsH)DR#k%FhK!phR~`$NOBPcF59D z6OfZK3x2~%V`F<`FHU(K&@j88XJ@O-t3Ro!PiGFt^bPRuHircHc;vx0^U&S zu3-uJUTAFH?YUOdmYAVFJwg0^_ta*?FNaO1z>mJQAfT`qJ*!ueush`K%x+*`|wZ z@2}&ebskc1QqFx|P+|!eJRE(&j6OeRyQ_E~T`4*6_<>4h^ozhU_Rjv!rUmCsyEjYE zB+zp}8>DnP5s&2>2rS>QPywJlC{g}x=YlfEv;AUEJoBs9 zN>VaPxWITd@3Kw=j0{Yt-mhwEwCC3_tsv-eJS4?ebUzAYiMkx@c0RX0&DFLbaGs2F zl&?>bnsBhAu3{u77{>e_z@)QLy$uo`o}|W;wZw`GjEkE_k^_g-?ed64@Mw$0zUa?< zv$o?h=Kb%wSg}NIft`!0a+p6UsNfevAV68Bwzjse$N53FEKua*r$IN4h6;U3S+dBKhSTvW0n^mCSSh1D{S$3UO=eK z>GO5S8|6SMs?eDC_5##Zh5F&Hk0~NnA|gtrYRSC=aiu^CmHo;@DI1gs3Ak-j{w>sF zJ7@=CY5u!T+5BCy!1vFd#a6X+GXRw8d3%n5&t*X71@VR;`vIfEvuO=I>#B&fG^%0D#U=A&958+P8ykK&a-(AfXHl^M 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 FzW|!*nrZ+5 diff --git a/docs/sources/terms/images/docker-filesystems-debian.png b/docs/sources/terms/images/docker-filesystems-debian.png index 823a215d3e9266dbbc0c3dba45b78af448aef58a..61e5ddb2e3a460588a64f855340a2734ed1890dc 100644 GIT binary patch literal 64585 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 O_)$?*#}+Ew^Zg%0Dq2qf literal 77822 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

0`#w!YETWN1Te~4 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%0A&L%AOUy)WH{s|_v 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

NnHHdLEO7MJkxz<|%*$2+J1ms>9*bkkr_} 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 ao#3hd4z0M|RI-J@kFtV>e1YskzyAepr0k>s literal 72247 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 diff --git a/docs/sources/terms/images/docker-filesystems.svg b/docs/sources/terms/images/docker-filesystems.svg index dc4dc8e687..054402db4c 100644 --- a/docs/sources/terms/images/docker-filesystems.svg +++ b/docs/sources/terms/images/docker-filesystems.svg @@ -11,7 +11,7 @@ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:export-ydpi="90" inkscape:export-xdpi="90" - inkscape:export-filename="/Users/arothfusz/src/metalivedev/dockerclone/docs/sources/terms/images/docker-filesystems-busyboxrw.png" + inkscape:export-filename="/Users/arothfusz/src/metalivedev/dockerclone/docs/sources/terms/images/docker-filesystems-multilayer.png" sodipodi:docname="docker-filesystems.svg" width="800" height="600" @@ -26,10 +26,10 @@ inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="0.82666667" - inkscape:cx="346.87978" + inkscape:cx="495.95588" inkscape:cy="300" inkscape:document-units="px" - inkscape:current-layer="layer2" + inkscape:current-layer="layer13" showgrid="false" width="800px" inkscape:window-width="1327" @@ -98,6 +98,32 @@ + + + + + + @@ -1190,9 +1216,39 @@ inkscape:connector-curvature="0" /> + + + referencesparentimage + From 39037a91f85a4a072e5aa7e585d8c2f6b211df8a Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 14 Mar 2014 11:42:01 -0700 Subject: [PATCH 098/384] Send sigterm to child instead of sigkill Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/init.go | 4 +++- pkg/system/calls_linux.go | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 5d47b95057..c702c79018 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -48,7 +48,9 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol return fmt.Errorf("setctty %s", err) } } - if err := system.ParentDeathSignal(); err != nil { + // this is our best effort to let the process know that the parent has died and that it + // should it should act on it how it sees fit + if err := system.ParentDeathSignal(uintptr(syscall.SIGTERM)); err != nil { return fmt.Errorf("parent death signal %s", err) } if err := setupNewMountNamespace(rootfs, container.Mounts, console, container.ReadonlyFs, container.NoPivotRoot); err != nil { diff --git a/pkg/system/calls_linux.go b/pkg/system/calls_linux.go index bf667c535b..43c00ed554 100644 --- a/pkg/system/calls_linux.go +++ b/pkg/system/calls_linux.go @@ -115,8 +115,8 @@ func Mknod(path string, mode uint32, dev int) error { return syscall.Mknod(path, mode, dev) } -func ParentDeathSignal() error { - if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, uintptr(syscall.SIGKILL), 0); err != 0 { +func ParentDeathSignal(sig uintptr) error { + if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, sig, 0); err != 0 { return err } return nil From 2ba0861ad359477ad81346a81f1bac09cb5e2eb2 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 14 Mar 2014 17:20:22 -0600 Subject: [PATCH 099/384] Add Sam's Go "dockerclient" to the list of Client Libraries Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- docs/sources/reference/api/remote_api_client_libraries.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/reference/api/remote_api_client_libraries.rst b/docs/sources/reference/api/remote_api_client_libraries.rst index e58c7ced39..f74dd416bc 100644 --- a/docs/sources/reference/api/remote_api_client_libraries.rst +++ b/docs/sources/reference/api/remote_api_client_libraries.rst @@ -41,6 +41,8 @@ and we will add the libraries here. +----------------------+----------------+--------------------------------------------+----------+ | Go | go-dockerclient| https://github.com/fsouza/go-dockerclient | Active | +----------------------+----------------+--------------------------------------------+----------+ +| Go | dockerclient | https://github.com/samalba/dockerclient | Active | ++----------------------+----------------+--------------------------------------------+----------+ | PHP | Alvine | http://pear.alvine.io/ (alpha) | Active | +----------------------+----------------+--------------------------------------------+----------+ | PHP | Docker-PHP | http://stage1.github.io/docker-php/ | Active | From 5583774e29911bbd42181e8db2ece08761677cf3 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Sat, 15 Mar 2014 11:34:49 -0400 Subject: [PATCH 100/384] Fix spelling of benchmark test Docker-DCO-1.1-Signed-off-by: Paul Nasrat (github: pnasrat) --- integration/container_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/container_test.go b/integration/container_test.go index c32a8bcff7..010883a709 100644 --- a/integration/container_test.go +++ b/integration/container_test.go @@ -1109,7 +1109,7 @@ func TestEntrypointNoCmd(t *testing.T) { } } -func BenchmarkRunSequencial(b *testing.B) { +func BenchmarkRunSequential(b *testing.B) { runtime := mkRuntime(b) defer nuke(runtime) for i := 0; i < b.N; i++ { From 853c5e258fc9a3d8420e62aaed4817179073610a Mon Sep 17 00:00:00 2001 From: zqh Date: Sun, 16 Mar 2014 01:07:22 +0800 Subject: [PATCH 101/384] Update nodejs_web_app.rst the address of epel rpm has change to http://dl.fedoraproject.... --- docs/sources/examples/nodejs_web_app.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/examples/nodejs_web_app.rst b/docs/sources/examples/nodejs_web_app.rst index 68c073da7b..a9e9b1c5e3 100644 --- a/docs/sources/examples/nodejs_web_app.rst +++ b/docs/sources/examples/nodejs_web_app.rst @@ -91,7 +91,7 @@ To install the right package for CentOS, we’ll use the instructions from the .. code-block:: bash # Enable EPEL for Node.js - RUN rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm + RUN rpm -Uvh http://dl.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm # Install Node.js and npm RUN yum install -y npm From 054b85a7b25e46935c0d91f544aac69dc3497468 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sat, 15 Mar 2014 14:00:35 -0600 Subject: [PATCH 102/384] Add proper support for relative WORKDIR instructions Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- integration/buildfile_test.go | 17 +++++++++++++++++ server/buildfile.go | 9 ++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index 7f6e69ece3..9c986d74c2 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -441,6 +441,23 @@ func TestBuildUser(t *testing.T) { } } +func TestBuildRelativeWorkdir(t *testing.T) { + img, err := buildImage(testContextTemplate{` + FROM {IMAGE} + RUN [ "$PWD" = '/' ] + WORKDIR /test1 + RUN [ "$PWD" = '/test1' ] + WORKDIR test2 + RUN [ "$PWD" = '/test1/test2' ] + `, nil, nil}, t, nil, true) + if err != nil { + t.Fatal(err) + } + if img.Config.WorkingDir != "/test1/test2" { + t.Fail() + } +} + func TestBuildEnv(t *testing.T) { img, err := buildImage(testContextTemplate{` from {IMAGE} diff --git a/server/buildfile.go b/server/buildfile.go index af6702cc1d..5d5fda4d8e 100644 --- a/server/buildfile.go +++ b/server/buildfile.go @@ -338,7 +338,14 @@ func (b *buildFile) CmdCopy(args string) error { } func (b *buildFile) CmdWorkdir(workdir string) error { - b.config.WorkingDir = workdir + if workdir[0] == '/' { + b.config.WorkingDir = workdir + } else { + if b.config.WorkingDir == "" { + b.config.WorkingDir = "/" + } + b.config.WorkingDir = filepath.Join(b.config.WorkingDir, workdir) + } return b.commit("", b.config.Cmd, fmt.Sprintf("WORKDIR %v", workdir)) } From 65051f4215e493928a211c411f775ee1cc7a763f Mon Sep 17 00:00:00 2001 From: Vladimir Rutsky Date: Sun, 16 Mar 2014 18:35:13 +0400 Subject: [PATCH 103/384] Fix external link on security of containers Docker-DCO-1.1-Signed-off-by: Vladimir Rutsky (github: rutsky) --- docs/sources/articles/security.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/articles/security.rst b/docs/sources/articles/security.rst index 3dc5780e85..e738e9a847 100644 --- a/docs/sources/articles/security.rst +++ b/docs/sources/articles/security.rst @@ -7,7 +7,7 @@ Docker Security =============== - *Adapted from* `Containers & Docker: How Secure are They? `_ + *Adapted from* `Containers & Docker: How Secure are They? `_ There are three major areas to consider when reviewing Docker security: @@ -261,7 +261,7 @@ with Docker, since everything is provided by the kernel anyway. For more context and especially for comparisons with VMs and other container systems, please also see the `original blog post -`_. +`_. .. _blogsecurity: http://blog.docker.io/2013/08/containers-docker-how-secure-are-they/ From e32965dbb13973f61ba1c0496c8136cc8c9273a2 Mon Sep 17 00:00:00 2001 From: Erik Hollensbe Date: Sun, 16 Mar 2014 16:28:13 +0000 Subject: [PATCH 104/384] In `docker ps`, sort by port instead of unsorted. Docker-DCO-1.1-Signed-off-by: Erik Hollensbe (github: erikh) --- api/common.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/common.go b/api/common.go index 10e7ddb4ae..5e5d2c5767 100644 --- a/api/common.go +++ b/api/common.go @@ -25,6 +25,8 @@ func ValidateHost(val string) (string, error) { //TODO remove, used on < 1.5 in getContainersJSON func displayablePorts(ports *engine.Table) string { result := []string{} + ports.SetKey("PublicPort") + ports.Sort() for _, port := range ports.Data { if port.Get("IP") == "" { result = append(result, fmt.Sprintf("%d/%s", port.GetInt("PublicPort"), port.Get("Type"))) From 9e69a042c50a3706c847addd68469dfe3eb698a0 Mon Sep 17 00:00:00 2001 From: Erik Hollensbe Date: Sun, 16 Mar 2014 17:48:46 +0000 Subject: [PATCH 105/384] Fix `docker cp` trying to untar files that do not exist. Docker-DCO-1.1-Signed-off-by: Erik Hollensbe (github: erikh) --- api/server.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/api/server.go b/api/server.go index 048c989540..2a5dacd9ea 100644 --- a/api/server.go +++ b/api/server.go @@ -894,6 +894,9 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp if copyData.Get("Resource") == "" { return fmt.Errorf("Path cannot be empty") } + + origResource := copyData.Get("Resource") + if copyData.Get("Resource")[0] == '/' { copyData.Set("Resource", copyData.Get("Resource")[1:]) } @@ -904,6 +907,8 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp utils.Errorf("%s", err.Error()) if strings.Contains(err.Error(), "No such container") { w.WriteHeader(http.StatusNotFound) + } else if strings.Contains(err.Error(), "no such file or directory") { + return fmt.Errorf("Could not find the file %s in container %s", origResource, vars["name"]) } } return nil From 681d1d2f61b7c76da6612a5d24d41d5bd98e4df5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Mon, 17 Mar 2014 15:36:46 +0100 Subject: [PATCH 106/384] Include instruction for AMI 2014.03 Include instructions to install Docker from Amazon's Software Repository on new AMI 2014.03 (Release Candidate) Docker-DCO-1.1-Signed-off-by: Sebastien Stormacq (github: sebsto) --- docs/sources/installation/amazon.rst | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/sources/installation/amazon.rst b/docs/sources/installation/amazon.rst index b5465e25f8..b062a15e1e 100644 --- a/docs/sources/installation/amazon.rst +++ b/docs/sources/installation/amazon.rst @@ -9,6 +9,7 @@ Amazon EC2 There are several ways to install Docker on AWS EC2: +* :ref:`amazonquickstart_new` or * :ref:`amazonquickstart` or * :ref:`amazonstandard` @@ -61,6 +62,37 @@ for every Docker command. Once you've got Docker installed, you're ready to try it out -- head on over to the :doc:`../use/basics` or :doc:`../examples/index` section. +.. _amazonquickstart_new: + +Amazon QuickStart (Release Candidate - March 2014) +-------------------------------------------------- + +Amazon just published new Docker-ready AMIs (2014.03 Release Candidate). Docker packages +can now be installed from Amazon's provided Software Repository. + +1. **Choose an image:** + + * Launch the `Create Instance Wizard + `_ menu + on your AWS Console. + + * Click the ``Community AMI`` menu option on the left side + + * Search for '2014.03' and select one of the Amazon provided AMI, for example ``amzn-ami-pv-2014.03.rc-0.x86_64-ebs`` + + * For testing you can use the default (possibly free) + ``t1.micro`` instance (more info on `pricing + `_). + + * Click the ``Next: Configure Instance Details`` button at the bottom right. + +2. After a few more standard choices where defaults are probably ok, your Amazon + Linux instance should be running! + +3. SSH to your instance to install Docker : ``ssh -i ec2-user@`` + +4. Once connected to the instance, type ``sudo yum install -y docker ; sudo service docker start`` to install and start Docker + .. _amazonstandard: Standard Ubuntu Installation From 90b283c39a36f34ac97f9eb0d66da3a0a9992caa Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 17 Mar 2014 17:56:21 +0000 Subject: [PATCH 107/384] fix content-type detection in docker cp Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/server.go b/api/server.go index 048c989540..5c7b0b7a05 100644 --- a/api/server.go +++ b/api/server.go @@ -883,7 +883,7 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp var copyData engine.Env - if contentType := r.Header.Get("Content-Type"); contentType == "application/json" { + if contentType := r.Header.Get("Content-Type"); MatchesContentType(contentType, "application/json") { if err := copyData.Decode(r.Body); err != nil { return err } From 128381e0f0372f10f88a847087aa91a972770c4b Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Mon, 17 Mar 2014 10:16:34 -0700 Subject: [PATCH 108/384] refactor(libcontainer): rename to CapabilitiesMask The Capabilities field on libcontainer is actually used as a mask. Rename the field so that this is more clear. Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- execdriver/native/default_template.go | 4 +-- pkg/libcontainer/README.md | 2 +- pkg/libcontainer/capabilities/capabilities.go | 8 +++--- pkg/libcontainer/container.go | 26 +++++++++---------- pkg/libcontainer/container.json | 2 +- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/execdriver/native/default_template.go b/execdriver/native/default_template.go index 2798f3b084..5351911427 100644 --- a/execdriver/native/default_template.go +++ b/execdriver/native/default_template.go @@ -36,7 +36,7 @@ func createContainer(c *execdriver.Command) *libcontainer.Container { container.Cgroups.Name = c.ID if c.Privileged { - container.Capabilities = nil + container.CapabilitiesMask = nil container.Cgroups.DeviceAccess = true container.Context["apparmor_profile"] = "unconfined" } @@ -59,7 +59,7 @@ func createContainer(c *execdriver.Command) *libcontainer.Container { // the libcontainer configuration file func getDefaultTemplate() *libcontainer.Container { return &libcontainer.Container{ - Capabilities: libcontainer.Capabilities{ + CapabilitiesMask: libcontainer.Capabilities{ libcontainer.GetCapability("SETPCAP"), libcontainer.GetCapability("SYS_MODULE"), libcontainer.GetCapability("SYS_RAWIO"), diff --git a/pkg/libcontainer/README.md b/pkg/libcontainer/README.md index 2c85111b97..e967f6d76d 100644 --- a/pkg/libcontainer/README.md +++ b/pkg/libcontainer/README.md @@ -40,7 +40,7 @@ Sample `container.json` file: "HOSTNAME=11bb30683fb0", "TERM=xterm" ], - "capabilities" : [ + "capabilities_mask" : [ "SETPCAP", "SYS_MODULE", "SYS_RAWIO", diff --git a/pkg/libcontainer/capabilities/capabilities.go b/pkg/libcontainer/capabilities/capabilities.go index 3c6d752496..fbf73538e0 100644 --- a/pkg/libcontainer/capabilities/capabilities.go +++ b/pkg/libcontainer/capabilities/capabilities.go @@ -9,7 +9,7 @@ import ( // DropCapabilities drops capabilities for the current process based // on the container's configuration. func DropCapabilities(container *libcontainer.Container) error { - if drop := getCapabilities(container); len(drop) > 0 { + if drop := getCapabilitiesMask(container); len(drop) > 0 { c, err := capability.NewPid(os.Getpid()) if err != nil { return err @@ -23,10 +23,10 @@ func DropCapabilities(container *libcontainer.Container) error { return nil } -// getCapabilities returns the specific cap values for the libcontainer types -func getCapabilities(container *libcontainer.Container) []capability.Cap { +// getCapabilitiesMask returns the specific cap mask values for the libcontainer types +func getCapabilitiesMask(container *libcontainer.Container) []capability.Cap { drop := []capability.Cap{} - for _, c := range container.Capabilities { + for _, c := range container.CapabilitiesMask { drop = append(drop, c.Value) } return drop diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index 14b4b65db7..c7cac35428 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -11,19 +11,19 @@ type Context map[string]string // Container defines configuration options for how a // container is setup inside a directory and how a process should be executed type Container struct { - Hostname string `json:"hostname,omitempty"` // hostname - ReadonlyFs bool `json:"readonly_fs,omitempty"` // set the containers rootfs as readonly - NoPivotRoot bool `json:"no_pivot_root,omitempty"` // this can be enabled if you are running in ramdisk - User string `json:"user,omitempty"` // user to execute the process as - WorkingDir string `json:"working_dir,omitempty"` // current working directory - Env []string `json:"environment,omitempty"` // environment to set - Tty bool `json:"tty,omitempty"` // setup a proper tty or not - Namespaces Namespaces `json:"namespaces,omitempty"` // namespaces to apply - Capabilities Capabilities `json:"capabilities,omitempty"` // capabilities to drop - Networks []*Network `json:"networks,omitempty"` // nil for host's network stack - Cgroups *cgroups.Cgroup `json:"cgroups,omitempty"` // cgroups - Context Context `json:"context,omitempty"` // generic context for specific options (apparmor, selinux) - Mounts []Mount `json:"mounts,omitempty"` + Hostname string `json:"hostname,omitempty"` // hostname + ReadonlyFs bool `json:"readonly_fs,omitempty"` // set the containers rootfs as readonly + NoPivotRoot bool `json:"no_pivot_root,omitempty"` // this can be enabled if you are running in ramdisk + User string `json:"user,omitempty"` // user to execute the process as + WorkingDir string `json:"working_dir,omitempty"` // current working directory + Env []string `json:"environment,omitempty"` // environment to set + Tty bool `json:"tty,omitempty"` // setup a proper tty or not + Namespaces Namespaces `json:"namespaces,omitempty"` // namespaces to apply + CapabilitiesMask Capabilities `json:"capabilities_mask,omitempty"` // capabilities to drop + Networks []*Network `json:"networks,omitempty"` // nil for host's network stack + Cgroups *cgroups.Cgroup `json:"cgroups,omitempty"` // cgroups + Context Context `json:"context,omitempty"` // generic context for specific options (apparmor, selinux) + Mounts []Mount `json:"mounts,omitempty"` } // Network defines configuration for a container's networking stack diff --git a/pkg/libcontainer/container.json b/pkg/libcontainer/container.json index 83e407467c..f045315a41 100644 --- a/pkg/libcontainer/container.json +++ b/pkg/libcontainer/container.json @@ -14,7 +14,7 @@ "NEWUTS", "NEWNET" ], - "capabilities": [ + "capabilities_mask": [ "SETPCAP", "SYS_MODULE", "SYS_RAWIO", From ad7e7d612390d09d3a54fd82dda9687deb3b0cbe Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Mon, 17 Mar 2014 11:07:29 -0700 Subject: [PATCH 109/384] chore(libcontainer): small grammar fix in types_test Someone probably got really used to typing er on the end of contain :) Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- pkg/libcontainer/types_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/libcontainer/types_test.go b/pkg/libcontainer/types_test.go index 52b85a4db9..9735937b76 100644 --- a/pkg/libcontainer/types_test.go +++ b/pkg/libcontainer/types_test.go @@ -30,6 +30,6 @@ func TestCapabilitiesContains(t *testing.T) { t.Fatal("capabilities should not contain SYS_ADMIN") } if !caps.Contains("MKNOD") { - t.Fatal("capabilities should container MKNOD but does not") + t.Fatal("capabilities should contain MKNOD but does not") } } From a62c7215c6c4675a4f99b63871d89198b211c260 Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Sat, 15 Mar 2014 14:48:32 -0400 Subject: [PATCH 110/384] Update fedora.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It looks like ``wmdocker`` does not have an update for Fedora 20: ``` ~❯ pkgwat releases wmdocker Starting new HTTPS connection (1): apps.fedoraproject.org +---------------+----------------+-----------------+ | release | stable_version | testing_version | +---------------+----------------+-----------------+ | Rawhide | 1.5-12.fc21 | None | | Fedora 20 | None | None | | Fedora 19 | None | None | | Fedora EPEL 7 | None | None | | Fedora EPEL 6 | None | None | | Fedora EPEL 5 | None | None | +---------------+----------------+-----------------+ ``` I'm not sure if the owner awjb is intending to create an F20 update or not, but either way -- these docs are incorrect as currently written. Docker-DCO-1.1-Signed-off-by: Ralph Bean (github: ralphbean) --- docs/sources/installation/fedora.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sources/installation/fedora.rst b/docs/sources/installation/fedora.rst index 7e0aee78fd..3b95f04f7f 100644 --- a/docs/sources/installation/fedora.rst +++ b/docs/sources/installation/fedora.rst @@ -23,15 +23,15 @@ The ``docker-io`` package provides Docker on Fedora. If you have the (unrelated) ``docker`` package installed already, it will conflict with ``docker-io``. There's a `bug report`_ filed for it. -To proceed with ``docker-io`` installation on Fedora 19, please remove -``docker`` first. +To proceed with ``docker-io`` installation on Fedora 19 or Fedora 20, please +remove ``docker`` first. .. code-block:: bash sudo yum -y remove docker -For Fedora 20 and later, the ``wmdocker`` package will provide the same -functionality as ``docker`` and will also not conflict with ``docker-io``. +For Fedora 21 and later, the ``wmdocker`` package will provide the same +functionality as the old ``docker`` and will also not conflict with ``docker-io``. .. code-block:: bash From 5921b186d17b172f205f3b0b6bda1f3a4e650d3f Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 17 Mar 2014 18:36:15 +0000 Subject: [PATCH 111/384] display command display in docker ps Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- runtime/container.go | 12 ++++++++++++ server/server.go | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/runtime/container.go b/runtime/container.go index ee545db201..72ee104d8b 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -402,6 +402,18 @@ func populateCommand(c *Container) { c.command.SysProcAttr = &syscall.SysProcAttr{Setsid: true} } +func (container *Container) ArgsAsString() string { + var args []string + for _, arg := range container.Args { + if strings.Contains(arg, " ") { + args = append(args, fmt.Sprintf("'%s'", arg)) + } else { + args = append(args, arg) + } + } + return strings.Join(args, " ") +} + func (container *Container) Start() (err error) { container.Lock() defer container.Unlock() diff --git a/server/server.go b/server/server.go index eb9a3a396b..69b65ce4a5 100644 --- a/server/server.go +++ b/server/server.go @@ -1003,7 +1003,7 @@ func (srv *Server) Containers(job *engine.Job) engine.Status { out.SetList("Names", names[container.ID]) out.Set("Image", srv.runtime.Repositories().ImageName(container.Image)) if len(container.Args) > 0 { - out.Set("Command", fmt.Sprintf("\"%s %s\"", container.Path, strings.Join(container.Args, " "))) + out.Set("Command", fmt.Sprintf("\"%s %s\"", container.Path, container.ArgsAsString())) } else { out.Set("Command", fmt.Sprintf("\"%s\"", container.Path)) } From 2230c9b9a735d731cc2fee4137633eb98b9da9d5 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 14 Mar 2014 14:03:23 -0700 Subject: [PATCH 112/384] Move networking drivers into runtime top level pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- builtins/builtins.go | 2 +- daemonconfig/config.go | 2 +- .../networkdriver}/ipallocator/allocator.go | 2 +- .../networkdriver}/ipallocator/allocator_test.go | 0 {networkdriver => runtime/networkdriver}/lxc/driver.go | 8 ++++---- {networkdriver => runtime/networkdriver}/network.go | 0 {networkdriver => runtime/networkdriver}/network_test.go | 0 .../networkdriver}/portallocator/portallocator.go | 0 .../networkdriver}/portallocator/portallocator_test.go | 0 .../networkdriver}/portmapper/mapper.go | 0 .../networkdriver}/portmapper/mapper_test.go | 0 {networkdriver => runtime/networkdriver}/utils.go | 0 runtime/runtime.go | 4 ++-- 13 files changed, 9 insertions(+), 9 deletions(-) rename {networkdriver => runtime/networkdriver}/ipallocator/allocator.go (98%) rename {networkdriver => runtime/networkdriver}/ipallocator/allocator_test.go (100%) rename {networkdriver => runtime/networkdriver}/lxc/driver.go (98%) rename {networkdriver => runtime/networkdriver}/network.go (100%) rename {networkdriver => runtime/networkdriver}/network_test.go (100%) rename {networkdriver => runtime/networkdriver}/portallocator/portallocator.go (100%) rename {networkdriver => runtime/networkdriver}/portallocator/portallocator_test.go (100%) rename {networkdriver => runtime/networkdriver}/portmapper/mapper.go (100%) rename {networkdriver => runtime/networkdriver}/portmapper/mapper_test.go (100%) rename {networkdriver => runtime/networkdriver}/utils.go (100%) diff --git a/builtins/builtins.go b/builtins/builtins.go index eb4a0be874..86f3973c62 100644 --- a/builtins/builtins.go +++ b/builtins/builtins.go @@ -4,7 +4,7 @@ import ( "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/api" - "github.com/dotcloud/docker/networkdriver/lxc" + "github.com/dotcloud/docker/runtime/networkdriver/lxc" "github.com/dotcloud/docker/server" ) diff --git a/daemonconfig/config.go b/daemonconfig/config.go index 0aee7e78ba..b26d3eec3a 100644 --- a/daemonconfig/config.go +++ b/daemonconfig/config.go @@ -4,7 +4,7 @@ import ( "net" "github.com/dotcloud/docker/engine" - "github.com/dotcloud/docker/networkdriver" + "github.com/dotcloud/docker/runtime/networkdriver" ) const ( diff --git a/networkdriver/ipallocator/allocator.go b/runtime/networkdriver/ipallocator/allocator.go similarity index 98% rename from networkdriver/ipallocator/allocator.go rename to runtime/networkdriver/ipallocator/allocator.go index 1c5a7b4cc2..2950e37003 100644 --- a/networkdriver/ipallocator/allocator.go +++ b/runtime/networkdriver/ipallocator/allocator.go @@ -3,7 +3,7 @@ package ipallocator import ( "encoding/binary" "errors" - "github.com/dotcloud/docker/networkdriver" + "github.com/dotcloud/docker/runtime/networkdriver" "github.com/dotcloud/docker/pkg/collections" "net" "sync" diff --git a/networkdriver/ipallocator/allocator_test.go b/runtime/networkdriver/ipallocator/allocator_test.go similarity index 100% rename from networkdriver/ipallocator/allocator_test.go rename to runtime/networkdriver/ipallocator/allocator_test.go diff --git a/networkdriver/lxc/driver.go b/runtime/networkdriver/lxc/driver.go similarity index 98% rename from networkdriver/lxc/driver.go rename to runtime/networkdriver/lxc/driver.go index 6185c42752..746bcfb5b0 100644 --- a/networkdriver/lxc/driver.go +++ b/runtime/networkdriver/lxc/driver.go @@ -3,10 +3,10 @@ package lxc import ( "fmt" "github.com/dotcloud/docker/engine" - "github.com/dotcloud/docker/networkdriver" - "github.com/dotcloud/docker/networkdriver/ipallocator" - "github.com/dotcloud/docker/networkdriver/portallocator" - "github.com/dotcloud/docker/networkdriver/portmapper" + "github.com/dotcloud/docker/runtime/networkdriver" + "github.com/dotcloud/docker/runtime/networkdriver/ipallocator" + "github.com/dotcloud/docker/runtime/networkdriver/portallocator" + "github.com/dotcloud/docker/runtime/networkdriver/portmapper" "github.com/dotcloud/docker/pkg/iptables" "github.com/dotcloud/docker/pkg/netlink" "github.com/dotcloud/docker/utils" diff --git a/networkdriver/network.go b/runtime/networkdriver/network.go similarity index 100% rename from networkdriver/network.go rename to runtime/networkdriver/network.go diff --git a/networkdriver/network_test.go b/runtime/networkdriver/network_test.go similarity index 100% rename from networkdriver/network_test.go rename to runtime/networkdriver/network_test.go diff --git a/networkdriver/portallocator/portallocator.go b/runtime/networkdriver/portallocator/portallocator.go similarity index 100% rename from networkdriver/portallocator/portallocator.go rename to runtime/networkdriver/portallocator/portallocator.go diff --git a/networkdriver/portallocator/portallocator_test.go b/runtime/networkdriver/portallocator/portallocator_test.go similarity index 100% rename from networkdriver/portallocator/portallocator_test.go rename to runtime/networkdriver/portallocator/portallocator_test.go diff --git a/networkdriver/portmapper/mapper.go b/runtime/networkdriver/portmapper/mapper.go similarity index 100% rename from networkdriver/portmapper/mapper.go rename to runtime/networkdriver/portmapper/mapper.go diff --git a/networkdriver/portmapper/mapper_test.go b/runtime/networkdriver/portmapper/mapper_test.go similarity index 100% rename from networkdriver/portmapper/mapper_test.go rename to runtime/networkdriver/portmapper/mapper_test.go diff --git a/networkdriver/utils.go b/runtime/networkdriver/utils.go similarity index 100% rename from networkdriver/utils.go rename to runtime/networkdriver/utils.go diff --git a/runtime/runtime.go b/runtime/runtime.go index 32584cbf6e..092b5a8130 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -17,8 +17,8 @@ import ( _ "github.com/dotcloud/docker/graphdriver/devmapper" _ "github.com/dotcloud/docker/graphdriver/vfs" "github.com/dotcloud/docker/image" - _ "github.com/dotcloud/docker/networkdriver/lxc" - "github.com/dotcloud/docker/networkdriver/portallocator" + _ "github.com/dotcloud/docker/runtime/networkdriver/lxc" + "github.com/dotcloud/docker/runtime/networkdriver/portallocator" "github.com/dotcloud/docker/pkg/graphdb" "github.com/dotcloud/docker/pkg/sysinfo" "github.com/dotcloud/docker/runconfig" From af385151ceedde097eda8a5195b431e8076cf76b Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 14 Mar 2014 14:07:32 -0700 Subject: [PATCH 113/384] Move execdrivers into runtime top level pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runtime/container.go | 2 +- {execdriver => runtime/execdriver}/MAINTAINERS | 0 {execdriver => runtime/execdriver}/driver.go | 0 .../execdriver}/execdrivers/execdrivers.go | 6 +++--- {execdriver => runtime/execdriver}/lxc/driver.go | 2 +- {execdriver => runtime/execdriver}/lxc/info.go | 0 {execdriver => runtime/execdriver}/lxc/info_test.go | 0 {execdriver => runtime/execdriver}/lxc/init.go | 2 +- {execdriver => runtime/execdriver}/lxc/lxc_init_linux.go | 0 .../execdriver}/lxc/lxc_init_unsupported.go | 0 {execdriver => runtime/execdriver}/lxc/lxc_template.go | 2 +- .../execdriver}/lxc/lxc_template_unit_test.go | 2 +- .../execdriver}/native/default_template.go | 2 +- {execdriver => runtime/execdriver}/native/driver.go | 2 +- {execdriver => runtime/execdriver}/native/info.go | 0 {execdriver => runtime/execdriver}/native/term.go | 2 +- {execdriver => runtime/execdriver}/pipes.go | 0 {execdriver => runtime/execdriver}/termconsole.go | 0 runtime/runtime.go | 6 +++--- runtime/volumes.go | 2 +- sysinit/sysinit.go | 6 +++--- 21 files changed, 18 insertions(+), 18 deletions(-) rename {execdriver => runtime/execdriver}/MAINTAINERS (100%) rename {execdriver => runtime/execdriver}/driver.go (100%) rename {execdriver => runtime/execdriver}/execdrivers/execdrivers.go (79%) rename {execdriver => runtime/execdriver}/lxc/driver.go (99%) rename {execdriver => runtime/execdriver}/lxc/info.go (100%) rename {execdriver => runtime/execdriver}/lxc/info_test.go (100%) rename {execdriver => runtime/execdriver}/lxc/init.go (98%) rename {execdriver => runtime/execdriver}/lxc/lxc_init_linux.go (100%) rename {execdriver => runtime/execdriver}/lxc/lxc_init_unsupported.go (100%) rename {execdriver => runtime/execdriver}/lxc/lxc_template.go (98%) rename {execdriver => runtime/execdriver}/lxc/lxc_template_unit_test.go (98%) rename {execdriver => runtime/execdriver}/native/default_template.go (98%) rename {execdriver => runtime/execdriver}/native/driver.go (99%) rename {execdriver => runtime/execdriver}/native/info.go (100%) rename {execdriver => runtime/execdriver}/native/term.go (94%) rename {execdriver => runtime/execdriver}/pipes.go (100%) rename {execdriver => runtime/execdriver}/termconsole.go (100%) diff --git a/runtime/container.go b/runtime/container.go index ee545db201..f4de40a16a 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -6,7 +6,7 @@ import ( "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/engine" - "github.com/dotcloud/docker/execdriver" + "github.com/dotcloud/docker/runtime/execdriver" "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/links" diff --git a/execdriver/MAINTAINERS b/runtime/execdriver/MAINTAINERS similarity index 100% rename from execdriver/MAINTAINERS rename to runtime/execdriver/MAINTAINERS diff --git a/execdriver/driver.go b/runtime/execdriver/driver.go similarity index 100% rename from execdriver/driver.go rename to runtime/execdriver/driver.go diff --git a/execdriver/execdrivers/execdrivers.go b/runtime/execdriver/execdrivers/execdrivers.go similarity index 79% rename from execdriver/execdrivers/execdrivers.go rename to runtime/execdriver/execdrivers/execdrivers.go index 7486d649c1..29fa5b44f9 100644 --- a/execdriver/execdrivers/execdrivers.go +++ b/runtime/execdriver/execdrivers/execdrivers.go @@ -2,9 +2,9 @@ package execdrivers import ( "fmt" - "github.com/dotcloud/docker/execdriver" - "github.com/dotcloud/docker/execdriver/lxc" - "github.com/dotcloud/docker/execdriver/native" + "github.com/dotcloud/docker/runtime/execdriver" + "github.com/dotcloud/docker/runtime/execdriver/lxc" + "github.com/dotcloud/docker/runtime/execdriver/native" "github.com/dotcloud/docker/pkg/sysinfo" "path" ) diff --git a/execdriver/lxc/driver.go b/runtime/execdriver/lxc/driver.go similarity index 99% rename from execdriver/lxc/driver.go rename to runtime/execdriver/lxc/driver.go index 9abec8ac3f..fa2ecf9d77 100644 --- a/execdriver/lxc/driver.go +++ b/runtime/execdriver/lxc/driver.go @@ -2,7 +2,7 @@ package lxc import ( "fmt" - "github.com/dotcloud/docker/execdriver" + "github.com/dotcloud/docker/runtime/execdriver" "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/utils" "io/ioutil" diff --git a/execdriver/lxc/info.go b/runtime/execdriver/lxc/info.go similarity index 100% rename from execdriver/lxc/info.go rename to runtime/execdriver/lxc/info.go diff --git a/execdriver/lxc/info_test.go b/runtime/execdriver/lxc/info_test.go similarity index 100% rename from execdriver/lxc/info_test.go rename to runtime/execdriver/lxc/info_test.go diff --git a/execdriver/lxc/init.go b/runtime/execdriver/lxc/init.go similarity index 98% rename from execdriver/lxc/init.go rename to runtime/execdriver/lxc/init.go index 0f134088a3..946c8c930f 100644 --- a/execdriver/lxc/init.go +++ b/runtime/execdriver/lxc/init.go @@ -3,7 +3,7 @@ package lxc import ( "encoding/json" "fmt" - "github.com/dotcloud/docker/execdriver" + "github.com/dotcloud/docker/runtime/execdriver" "github.com/dotcloud/docker/pkg/netlink" "github.com/dotcloud/docker/pkg/user" "github.com/syndtr/gocapability/capability" diff --git a/execdriver/lxc/lxc_init_linux.go b/runtime/execdriver/lxc/lxc_init_linux.go similarity index 100% rename from execdriver/lxc/lxc_init_linux.go rename to runtime/execdriver/lxc/lxc_init_linux.go diff --git a/execdriver/lxc/lxc_init_unsupported.go b/runtime/execdriver/lxc/lxc_init_unsupported.go similarity index 100% rename from execdriver/lxc/lxc_init_unsupported.go rename to runtime/execdriver/lxc/lxc_init_unsupported.go diff --git a/execdriver/lxc/lxc_template.go b/runtime/execdriver/lxc/lxc_template.go similarity index 98% rename from execdriver/lxc/lxc_template.go rename to runtime/execdriver/lxc/lxc_template.go index 84cd4e442e..db55287522 100644 --- a/execdriver/lxc/lxc_template.go +++ b/runtime/execdriver/lxc/lxc_template.go @@ -1,7 +1,7 @@ package lxc import ( - "github.com/dotcloud/docker/execdriver" + "github.com/dotcloud/docker/runtime/execdriver" "strings" "text/template" ) diff --git a/execdriver/lxc/lxc_template_unit_test.go b/runtime/execdriver/lxc/lxc_template_unit_test.go similarity index 98% rename from execdriver/lxc/lxc_template_unit_test.go rename to runtime/execdriver/lxc/lxc_template_unit_test.go index 99d6e636f5..ae66371836 100644 --- a/execdriver/lxc/lxc_template_unit_test.go +++ b/runtime/execdriver/lxc/lxc_template_unit_test.go @@ -3,7 +3,7 @@ package lxc import ( "bufio" "fmt" - "github.com/dotcloud/docker/execdriver" + "github.com/dotcloud/docker/runtime/execdriver" "io/ioutil" "math/rand" "os" diff --git a/execdriver/native/default_template.go b/runtime/execdriver/native/default_template.go similarity index 98% rename from execdriver/native/default_template.go rename to runtime/execdriver/native/default_template.go index 5351911427..0c382059e9 100644 --- a/execdriver/native/default_template.go +++ b/runtime/execdriver/native/default_template.go @@ -2,7 +2,7 @@ package native import ( "fmt" - "github.com/dotcloud/docker/execdriver" + "github.com/dotcloud/docker/runtime/execdriver" "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/pkg/libcontainer" "os" diff --git a/execdriver/native/driver.go b/runtime/execdriver/native/driver.go similarity index 99% rename from execdriver/native/driver.go rename to runtime/execdriver/native/driver.go index 9b49fd156f..ff6c541cf9 100644 --- a/execdriver/native/driver.go +++ b/runtime/execdriver/native/driver.go @@ -3,7 +3,7 @@ package native import ( "encoding/json" "fmt" - "github.com/dotcloud/docker/execdriver" + "github.com/dotcloud/docker/runtime/execdriver" "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/apparmor" diff --git a/execdriver/native/info.go b/runtime/execdriver/native/info.go similarity index 100% rename from execdriver/native/info.go rename to runtime/execdriver/native/info.go diff --git a/execdriver/native/term.go b/runtime/execdriver/native/term.go similarity index 94% rename from execdriver/native/term.go rename to runtime/execdriver/native/term.go index ec69820f75..0d5298d388 100644 --- a/execdriver/native/term.go +++ b/runtime/execdriver/native/term.go @@ -5,7 +5,7 @@ package native import ( - "github.com/dotcloud/docker/execdriver" + "github.com/dotcloud/docker/runtime/execdriver" "io" "os" "os/exec" diff --git a/execdriver/pipes.go b/runtime/execdriver/pipes.go similarity index 100% rename from execdriver/pipes.go rename to runtime/execdriver/pipes.go diff --git a/execdriver/termconsole.go b/runtime/execdriver/termconsole.go similarity index 100% rename from execdriver/termconsole.go rename to runtime/execdriver/termconsole.go diff --git a/runtime/runtime.go b/runtime/runtime.go index 092b5a8130..f75a4df048 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -7,9 +7,9 @@ import ( "github.com/dotcloud/docker/daemonconfig" "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" - "github.com/dotcloud/docker/execdriver" - "github.com/dotcloud/docker/execdriver/execdrivers" - "github.com/dotcloud/docker/execdriver/lxc" + "github.com/dotcloud/docker/runtime/execdriver" + "github.com/dotcloud/docker/runtime/execdriver/execdrivers" + "github.com/dotcloud/docker/runtime/execdriver/lxc" "github.com/dotcloud/docker/graph" "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/graphdriver/aufs" diff --git a/runtime/volumes.go b/runtime/volumes.go index 9cb66aae44..1bbb14a369 100644 --- a/runtime/volumes.go +++ b/runtime/volumes.go @@ -3,7 +3,7 @@ package runtime import ( "fmt" "github.com/dotcloud/docker/archive" - "github.com/dotcloud/docker/execdriver" + "github.com/dotcloud/docker/runtime/execdriver" "github.com/dotcloud/docker/utils" "io/ioutil" "os" diff --git a/sysinit/sysinit.go b/sysinit/sysinit.go index 56508b105d..50c858296f 100644 --- a/sysinit/sysinit.go +++ b/sysinit/sysinit.go @@ -3,9 +3,9 @@ package sysinit import ( "flag" "fmt" - "github.com/dotcloud/docker/execdriver" - _ "github.com/dotcloud/docker/execdriver/lxc" - _ "github.com/dotcloud/docker/execdriver/native" + "github.com/dotcloud/docker/runtime/execdriver" + _ "github.com/dotcloud/docker/runtime/execdriver/lxc" + _ "github.com/dotcloud/docker/runtime/execdriver/native" "log" "os" ) From 96c4816cef592a98a235010924bb2417c8451079 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 14 Mar 2014 14:11:43 -0700 Subject: [PATCH 114/384] Move graphdrivers into runtime top level pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- contrib/docker-device-tool/device_tool.go | 2 +- graph/graph.go | 2 +- graph/tags_unit_test.go | 4 ++-- image/graph.go | 2 +- image/image.go | 2 +- integration/graph_test.go | 2 +- runtime/container.go | 2 +- {graphdriver => runtime/graphdriver}/aufs/aufs.go | 2 +- {graphdriver => runtime/graphdriver}/aufs/aufs_test.go | 2 +- {graphdriver => runtime/graphdriver}/aufs/dirs.go | 0 {graphdriver => runtime/graphdriver}/aufs/migrate.go | 0 {graphdriver => runtime/graphdriver}/aufs/mount.go | 0 .../graphdriver}/aufs/mount_linux.go | 0 .../graphdriver}/aufs/mount_unsupported.go | 0 {graphdriver => runtime/graphdriver}/btrfs/btrfs.go | 2 +- .../graphdriver}/btrfs/dummy_unsupported.go | 0 .../graphdriver}/devmapper/attach_loopback.go | 0 .../graphdriver}/devmapper/deviceset.go | 0 .../graphdriver}/devmapper/devmapper.go | 0 .../graphdriver}/devmapper/devmapper_doc.go | 0 .../graphdriver}/devmapper/devmapper_log.go | 0 .../graphdriver}/devmapper/devmapper_test.go | 0 .../graphdriver}/devmapper/devmapper_wrapper.go | 0 .../graphdriver}/devmapper/driver.go | 2 +- .../graphdriver}/devmapper/driver_test.go | 2 +- .../graphdriver}/devmapper/ioctl.go | 0 .../graphdriver}/devmapper/mount.go | 0 {graphdriver => runtime/graphdriver}/devmapper/sys.go | 0 {graphdriver => runtime/graphdriver}/driver.go | 0 {graphdriver => runtime/graphdriver}/vfs/driver.go | 2 +- runtime/runtime.go | 10 +++++----- 31 files changed, 19 insertions(+), 19 deletions(-) rename {graphdriver => runtime/graphdriver}/aufs/aufs.go (99%) rename {graphdriver => runtime/graphdriver}/aufs/aufs_test.go (99%) rename {graphdriver => runtime/graphdriver}/aufs/dirs.go (100%) rename {graphdriver => runtime/graphdriver}/aufs/migrate.go (100%) rename {graphdriver => runtime/graphdriver}/aufs/mount.go (100%) rename {graphdriver => runtime/graphdriver}/aufs/mount_linux.go (100%) rename {graphdriver => runtime/graphdriver}/aufs/mount_unsupported.go (100%) rename {graphdriver => runtime/graphdriver}/btrfs/btrfs.go (98%) rename {graphdriver => runtime/graphdriver}/btrfs/dummy_unsupported.go (100%) rename {graphdriver => runtime/graphdriver}/devmapper/attach_loopback.go (100%) rename {graphdriver => runtime/graphdriver}/devmapper/deviceset.go (100%) rename {graphdriver => runtime/graphdriver}/devmapper/devmapper.go (100%) rename {graphdriver => runtime/graphdriver}/devmapper/devmapper_doc.go (100%) rename {graphdriver => runtime/graphdriver}/devmapper/devmapper_log.go (100%) rename {graphdriver => runtime/graphdriver}/devmapper/devmapper_test.go (100%) rename {graphdriver => runtime/graphdriver}/devmapper/devmapper_wrapper.go (100%) rename {graphdriver => runtime/graphdriver}/devmapper/driver.go (98%) rename {graphdriver => runtime/graphdriver}/devmapper/driver_test.go (99%) rename {graphdriver => runtime/graphdriver}/devmapper/ioctl.go (100%) rename {graphdriver => runtime/graphdriver}/devmapper/mount.go (100%) rename {graphdriver => runtime/graphdriver}/devmapper/sys.go (100%) rename {graphdriver => runtime/graphdriver}/driver.go (100%) rename {graphdriver => runtime/graphdriver}/vfs/driver.go (97%) diff --git a/contrib/docker-device-tool/device_tool.go b/contrib/docker-device-tool/device_tool.go index 4d1ee0cea5..12c762a7f3 100644 --- a/contrib/docker-device-tool/device_tool.go +++ b/contrib/docker-device-tool/device_tool.go @@ -3,7 +3,7 @@ package main import ( "flag" "fmt" - "github.com/dotcloud/docker/graphdriver/devmapper" + "github.com/dotcloud/docker/runtime/graphdriver/devmapper" "os" "path" "sort" diff --git a/graph/graph.go b/graph/graph.go index 01659b549f..f71b8a003e 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/dockerversion" - "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/runtime/graphdriver" "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" diff --git a/graph/tags_unit_test.go b/graph/tags_unit_test.go index 153f94db3d..cae5c2916e 100644 --- a/graph/tags_unit_test.go +++ b/graph/tags_unit_test.go @@ -2,8 +2,8 @@ package graph import ( "bytes" - "github.com/dotcloud/docker/graphdriver" - _ "github.com/dotcloud/docker/graphdriver/vfs" // import the vfs driver so it is used in the tests + "github.com/dotcloud/docker/runtime/graphdriver" + _ "github.com/dotcloud/docker/runtime/graphdriver/vfs" // import the vfs driver so it is used in the tests "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" diff --git a/image/graph.go b/image/graph.go index 857c09edd9..dd0136b00e 100644 --- a/image/graph.go +++ b/image/graph.go @@ -1,7 +1,7 @@ package image import ( - "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/runtime/graphdriver" ) type Graph interface { diff --git a/image/image.go b/image/image.go index e091879049..b2ddb03b0b 100644 --- a/image/image.go +++ b/image/image.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" "github.com/dotcloud/docker/archive" - "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/runtime/graphdriver" "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" "io/ioutil" diff --git a/integration/graph_test.go b/integration/graph_test.go index e575a252f3..ea9ddc7ae9 100644 --- a/integration/graph_test.go +++ b/integration/graph_test.go @@ -5,7 +5,7 @@ import ( "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/graph" - "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/runtime/graphdriver" "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/utils" "io" diff --git a/runtime/container.go b/runtime/container.go index f4de40a16a..3c7aa22751 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -7,7 +7,7 @@ import ( "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/runtime/execdriver" - "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/runtime/graphdriver" "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/links" "github.com/dotcloud/docker/nat" diff --git a/graphdriver/aufs/aufs.go b/runtime/graphdriver/aufs/aufs.go similarity index 99% rename from graphdriver/aufs/aufs.go rename to runtime/graphdriver/aufs/aufs.go index a15cf6b273..83a6579bc6 100644 --- a/graphdriver/aufs/aufs.go +++ b/runtime/graphdriver/aufs/aufs.go @@ -24,7 +24,7 @@ import ( "bufio" "fmt" "github.com/dotcloud/docker/archive" - "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/runtime/graphdriver" mountpk "github.com/dotcloud/docker/pkg/mount" "github.com/dotcloud/docker/utils" "os" diff --git a/graphdriver/aufs/aufs_test.go b/runtime/graphdriver/aufs/aufs_test.go similarity index 99% rename from graphdriver/aufs/aufs_test.go rename to runtime/graphdriver/aufs/aufs_test.go index 6002bec5a1..cb417c3b26 100644 --- a/graphdriver/aufs/aufs_test.go +++ b/runtime/graphdriver/aufs/aufs_test.go @@ -5,7 +5,7 @@ import ( "encoding/hex" "fmt" "github.com/dotcloud/docker/archive" - "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/runtime/graphdriver" "io/ioutil" "os" "path" diff --git a/graphdriver/aufs/dirs.go b/runtime/graphdriver/aufs/dirs.go similarity index 100% rename from graphdriver/aufs/dirs.go rename to runtime/graphdriver/aufs/dirs.go diff --git a/graphdriver/aufs/migrate.go b/runtime/graphdriver/aufs/migrate.go similarity index 100% rename from graphdriver/aufs/migrate.go rename to runtime/graphdriver/aufs/migrate.go diff --git a/graphdriver/aufs/mount.go b/runtime/graphdriver/aufs/mount.go similarity index 100% rename from graphdriver/aufs/mount.go rename to runtime/graphdriver/aufs/mount.go diff --git a/graphdriver/aufs/mount_linux.go b/runtime/graphdriver/aufs/mount_linux.go similarity index 100% rename from graphdriver/aufs/mount_linux.go rename to runtime/graphdriver/aufs/mount_linux.go diff --git a/graphdriver/aufs/mount_unsupported.go b/runtime/graphdriver/aufs/mount_unsupported.go similarity index 100% rename from graphdriver/aufs/mount_unsupported.go rename to runtime/graphdriver/aufs/mount_unsupported.go diff --git a/graphdriver/btrfs/btrfs.go b/runtime/graphdriver/btrfs/btrfs.go similarity index 98% rename from graphdriver/btrfs/btrfs.go rename to runtime/graphdriver/btrfs/btrfs.go index 592e058458..b0530be92b 100644 --- a/graphdriver/btrfs/btrfs.go +++ b/runtime/graphdriver/btrfs/btrfs.go @@ -11,7 +11,7 @@ import "C" import ( "fmt" - "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/runtime/graphdriver" "os" "path" "syscall" diff --git a/graphdriver/btrfs/dummy_unsupported.go b/runtime/graphdriver/btrfs/dummy_unsupported.go similarity index 100% rename from graphdriver/btrfs/dummy_unsupported.go rename to runtime/graphdriver/btrfs/dummy_unsupported.go diff --git a/graphdriver/devmapper/attach_loopback.go b/runtime/graphdriver/devmapper/attach_loopback.go similarity index 100% rename from graphdriver/devmapper/attach_loopback.go rename to runtime/graphdriver/devmapper/attach_loopback.go diff --git a/graphdriver/devmapper/deviceset.go b/runtime/graphdriver/devmapper/deviceset.go similarity index 100% rename from graphdriver/devmapper/deviceset.go rename to runtime/graphdriver/devmapper/deviceset.go diff --git a/graphdriver/devmapper/devmapper.go b/runtime/graphdriver/devmapper/devmapper.go similarity index 100% rename from graphdriver/devmapper/devmapper.go rename to runtime/graphdriver/devmapper/devmapper.go diff --git a/graphdriver/devmapper/devmapper_doc.go b/runtime/graphdriver/devmapper/devmapper_doc.go similarity index 100% rename from graphdriver/devmapper/devmapper_doc.go rename to runtime/graphdriver/devmapper/devmapper_doc.go diff --git a/graphdriver/devmapper/devmapper_log.go b/runtime/graphdriver/devmapper/devmapper_log.go similarity index 100% rename from graphdriver/devmapper/devmapper_log.go rename to runtime/graphdriver/devmapper/devmapper_log.go diff --git a/graphdriver/devmapper/devmapper_test.go b/runtime/graphdriver/devmapper/devmapper_test.go similarity index 100% rename from graphdriver/devmapper/devmapper_test.go rename to runtime/graphdriver/devmapper/devmapper_test.go diff --git a/graphdriver/devmapper/devmapper_wrapper.go b/runtime/graphdriver/devmapper/devmapper_wrapper.go similarity index 100% rename from graphdriver/devmapper/devmapper_wrapper.go rename to runtime/graphdriver/devmapper/devmapper_wrapper.go diff --git a/graphdriver/devmapper/driver.go b/runtime/graphdriver/devmapper/driver.go similarity index 98% rename from graphdriver/devmapper/driver.go rename to runtime/graphdriver/devmapper/driver.go index 8c5a19eea0..33c7a0f483 100644 --- a/graphdriver/devmapper/driver.go +++ b/runtime/graphdriver/devmapper/driver.go @@ -4,7 +4,7 @@ package devmapper import ( "fmt" - "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/runtime/graphdriver" "github.com/dotcloud/docker/utils" "io/ioutil" "os" diff --git a/graphdriver/devmapper/driver_test.go b/runtime/graphdriver/devmapper/driver_test.go similarity index 99% rename from graphdriver/devmapper/driver_test.go rename to runtime/graphdriver/devmapper/driver_test.go index 68699f208e..9af71a00b3 100644 --- a/graphdriver/devmapper/driver_test.go +++ b/runtime/graphdriver/devmapper/driver_test.go @@ -4,7 +4,7 @@ package devmapper import ( "fmt" - "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/runtime/graphdriver" "io/ioutil" "path" "runtime" diff --git a/graphdriver/devmapper/ioctl.go b/runtime/graphdriver/devmapper/ioctl.go similarity index 100% rename from graphdriver/devmapper/ioctl.go rename to runtime/graphdriver/devmapper/ioctl.go diff --git a/graphdriver/devmapper/mount.go b/runtime/graphdriver/devmapper/mount.go similarity index 100% rename from graphdriver/devmapper/mount.go rename to runtime/graphdriver/devmapper/mount.go diff --git a/graphdriver/devmapper/sys.go b/runtime/graphdriver/devmapper/sys.go similarity index 100% rename from graphdriver/devmapper/sys.go rename to runtime/graphdriver/devmapper/sys.go diff --git a/graphdriver/driver.go b/runtime/graphdriver/driver.go similarity index 100% rename from graphdriver/driver.go rename to runtime/graphdriver/driver.go diff --git a/graphdriver/vfs/driver.go b/runtime/graphdriver/vfs/driver.go similarity index 97% rename from graphdriver/vfs/driver.go rename to runtime/graphdriver/vfs/driver.go index 21da63878a..10a7b223a4 100644 --- a/graphdriver/vfs/driver.go +++ b/runtime/graphdriver/vfs/driver.go @@ -2,7 +2,7 @@ package vfs import ( "fmt" - "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/runtime/graphdriver" "os" "os/exec" "path" diff --git a/runtime/runtime.go b/runtime/runtime.go index f75a4df048..25edd774d8 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -11,11 +11,11 @@ import ( "github.com/dotcloud/docker/runtime/execdriver/execdrivers" "github.com/dotcloud/docker/runtime/execdriver/lxc" "github.com/dotcloud/docker/graph" - "github.com/dotcloud/docker/graphdriver" - "github.com/dotcloud/docker/graphdriver/aufs" - _ "github.com/dotcloud/docker/graphdriver/btrfs" - _ "github.com/dotcloud/docker/graphdriver/devmapper" - _ "github.com/dotcloud/docker/graphdriver/vfs" + "github.com/dotcloud/docker/runtime/graphdriver" + "github.com/dotcloud/docker/runtime/graphdriver/aufs" + _ "github.com/dotcloud/docker/runtime/graphdriver/btrfs" + _ "github.com/dotcloud/docker/runtime/graphdriver/devmapper" + _ "github.com/dotcloud/docker/runtime/graphdriver/vfs" "github.com/dotcloud/docker/image" _ "github.com/dotcloud/docker/runtime/networkdriver/lxc" "github.com/dotcloud/docker/runtime/networkdriver/portallocator" From edd8d2d3511b0b632149d1c1f2cfd2bad2df4679 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 14 Mar 2014 01:02:28 +0000 Subject: [PATCH 115/384] add no prune to rmi Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api/client.go | 8 ++++++-- api/server.go | 1 + server/server.go | 8 ++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/api/client.go b/api/client.go index 8ee61f6c22..1aceed50e7 100644 --- a/api/client.go +++ b/api/client.go @@ -817,8 +817,9 @@ func (cli *DockerCli) CmdPort(args ...string) error { // 'docker rmi IMAGE' removes all images with the name IMAGE func (cli *DockerCli) CmdRmi(args ...string) error { var ( - cmd = cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images") - force = cmd.Bool([]string{"f", "-force"}, false, "Force") + cmd = cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images") + force = cmd.Bool([]string{"f", "-force"}, false, "Force") + noprune = cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents") ) if err := cmd.Parse(args); err != nil { return nil @@ -832,6 +833,9 @@ func (cli *DockerCli) CmdRmi(args ...string) error { if *force { v.Set("force", "1") } + if *noprune { + v.Set("noprune", "1") + } var encounteredError error for _, name := range cmd.Args() { diff --git a/api/server.go b/api/server.go index 2d878b957a..774e3131ef 100644 --- a/api/server.go +++ b/api/server.go @@ -624,6 +624,7 @@ func deleteImages(eng *engine.Engine, version version.Version, w http.ResponseWr var job = eng.Job("image_delete", vars["name"]) streamJSON(job, w, false) job.Setenv("force", r.Form.Get("force")) + job.Setenv("noprune", r.Form.Get("noprune")) return job.Run() } diff --git a/server/server.go b/server/server.go index eb9a3a396b..736d54ae52 100644 --- a/server/server.go +++ b/server/server.go @@ -1839,7 +1839,7 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { return engine.StatusOK } -func (srv *Server) DeleteImage(name string, imgs *engine.Table, first, force bool) error { +func (srv *Server) DeleteImage(name string, imgs *engine.Table, first, force, noprune bool) error { var ( repoName, tag string tags = []string{} @@ -1920,8 +1920,8 @@ func (srv *Server) DeleteImage(name string, imgs *engine.Table, first, force boo out.Set("Deleted", img.ID) imgs.Add(out) srv.LogEvent("delete", img.ID, "") - if img.Parent != "" { - err := srv.DeleteImage(img.Parent, imgs, false, force) + if img.Parent != "" && !noprune { + err := srv.DeleteImage(img.Parent, imgs, false, force, noprune) if first { return err } @@ -1938,7 +1938,7 @@ func (srv *Server) ImageDelete(job *engine.Job) engine.Status { return job.Errorf("Usage: %s IMAGE", job.Name) } imgs := engine.NewTable("", 0) - if err := srv.DeleteImage(job.Args[0], imgs, true, job.GetenvBool("force")); err != nil { + if err := srv.DeleteImage(job.Args[0], imgs, true, job.GetenvBool("force"), job.GetenvBool("noprune")); err != nil { return job.Error(err) } if len(imgs.Data) == 0 { From afcaaffd0bb84d0c97f4a3ef54f4d35ba3942f65 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 14 Mar 2014 01:04:35 +0000 Subject: [PATCH 116/384] update doc Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- docs/sources/reference/api/docker_remote_api.rst | 1 + docs/sources/reference/api/docker_remote_api_v1.10.rst | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/sources/reference/api/docker_remote_api.rst b/docs/sources/reference/api/docker_remote_api.rst index 93558fa974..ca7463f351 100644 --- a/docs/sources/reference/api/docker_remote_api.rst +++ b/docs/sources/reference/api/docker_remote_api.rst @@ -50,6 +50,7 @@ What's new **New!** You can now use the force parameter to force delete of an image, even if it's tagged in multiple repositories. + **New!** You can now use the noprune parameter to prevent the deletion of parent images .. http:delete:: /containers/(id) diff --git a/docs/sources/reference/api/docker_remote_api_v1.10.rst b/docs/sources/reference/api/docker_remote_api_v1.10.rst index 20af253f0e..649f58196e 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.10.rst +++ b/docs/sources/reference/api/docker_remote_api_v1.10.rst @@ -931,6 +931,7 @@ Remove an image ] :query force: 1/True/true or 0/False/false, default false + :query noprune: 1/True/true or 0/False/false, default false :statuscode 200: no error :statuscode 404: no such image :statuscode 409: conflict From a18d08177c1b11fd1e88c27d78a7256b5d498d64 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 14 Mar 2014 17:13:11 +0000 Subject: [PATCH 117/384] Add missing client doc Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- docs/sources/reference/commandline/cli.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 6d55a0aedc..f302862b9e 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -1092,6 +1092,7 @@ containers will not be deleted. Remove one or more images -f, --force=false: Force + --no-prune=false: Do not delete untagged parents Removing tagged images ~~~~~~~~~~~~~~~~~~~~~~ From 2bddcd68b4b927d36ffadd80e098f6d4ae2cf5d6 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 14 Mar 2014 15:07:52 -0700 Subject: [PATCH 118/384] Gofmt imports Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- graph/graph.go | 2 +- graph/tags_unit_test.go | 2 +- image/image.go | 2 +- integration/graph_test.go | 2 +- runtime/container.go | 4 ++-- runtime/execdriver/execdrivers/execdrivers.go | 2 +- runtime/execdriver/lxc/driver.go | 2 +- runtime/execdriver/lxc/init.go | 2 +- runtime/execdriver/native/default_template.go | 2 +- runtime/execdriver/native/driver.go | 2 +- runtime/graphdriver/aufs/aufs.go | 2 +- runtime/networkdriver/ipallocator/allocator.go | 2 +- runtime/networkdriver/lxc/driver.go | 4 ++-- runtime/runtime.go | 10 +++++----- 14 files changed, 20 insertions(+), 20 deletions(-) diff --git a/graph/graph.go b/graph/graph.go index f71b8a003e..4349cac129 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -4,9 +4,9 @@ import ( "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/dockerversion" - "github.com/dotcloud/docker/runtime/graphdriver" "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/runconfig" + "github.com/dotcloud/docker/runtime/graphdriver" "github.com/dotcloud/docker/utils" "io" "io/ioutil" diff --git a/graph/tags_unit_test.go b/graph/tags_unit_test.go index cae5c2916e..17773912cf 100644 --- a/graph/tags_unit_test.go +++ b/graph/tags_unit_test.go @@ -2,9 +2,9 @@ package graph import ( "bytes" + "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/runtime/graphdriver" _ "github.com/dotcloud/docker/runtime/graphdriver/vfs" // import the vfs driver so it is used in the tests - "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" "io" diff --git a/image/image.go b/image/image.go index b2ddb03b0b..33503bad5a 100644 --- a/image/image.go +++ b/image/image.go @@ -4,8 +4,8 @@ import ( "encoding/json" "fmt" "github.com/dotcloud/docker/archive" - "github.com/dotcloud/docker/runtime/graphdriver" "github.com/dotcloud/docker/runconfig" + "github.com/dotcloud/docker/runtime/graphdriver" "github.com/dotcloud/docker/utils" "io/ioutil" "os" diff --git a/integration/graph_test.go b/integration/graph_test.go index ea9ddc7ae9..5602b3938d 100644 --- a/integration/graph_test.go +++ b/integration/graph_test.go @@ -5,8 +5,8 @@ import ( "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/graph" - "github.com/dotcloud/docker/runtime/graphdriver" "github.com/dotcloud/docker/image" + "github.com/dotcloud/docker/runtime/graphdriver" "github.com/dotcloud/docker/utils" "io" "io/ioutil" diff --git a/runtime/container.go b/runtime/container.go index 3c7aa22751..9b138c89c1 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -6,12 +6,12 @@ import ( "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/engine" - "github.com/dotcloud/docker/runtime/execdriver" - "github.com/dotcloud/docker/runtime/graphdriver" "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/links" "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/runconfig" + "github.com/dotcloud/docker/runtime/execdriver" + "github.com/dotcloud/docker/runtime/graphdriver" "github.com/dotcloud/docker/utils" "io" "io/ioutil" diff --git a/runtime/execdriver/execdrivers/execdrivers.go b/runtime/execdriver/execdrivers/execdrivers.go index 29fa5b44f9..9e277c86df 100644 --- a/runtime/execdriver/execdrivers/execdrivers.go +++ b/runtime/execdriver/execdrivers/execdrivers.go @@ -2,10 +2,10 @@ package execdrivers import ( "fmt" + "github.com/dotcloud/docker/pkg/sysinfo" "github.com/dotcloud/docker/runtime/execdriver" "github.com/dotcloud/docker/runtime/execdriver/lxc" "github.com/dotcloud/docker/runtime/execdriver/native" - "github.com/dotcloud/docker/pkg/sysinfo" "path" ) diff --git a/runtime/execdriver/lxc/driver.go b/runtime/execdriver/lxc/driver.go index fa2ecf9d77..b7311cc1ff 100644 --- a/runtime/execdriver/lxc/driver.go +++ b/runtime/execdriver/lxc/driver.go @@ -2,8 +2,8 @@ package lxc import ( "fmt" - "github.com/dotcloud/docker/runtime/execdriver" "github.com/dotcloud/docker/pkg/cgroups" + "github.com/dotcloud/docker/runtime/execdriver" "github.com/dotcloud/docker/utils" "io/ioutil" "log" diff --git a/runtime/execdriver/lxc/init.go b/runtime/execdriver/lxc/init.go index 946c8c930f..a64bca15b2 100644 --- a/runtime/execdriver/lxc/init.go +++ b/runtime/execdriver/lxc/init.go @@ -3,9 +3,9 @@ package lxc import ( "encoding/json" "fmt" - "github.com/dotcloud/docker/runtime/execdriver" "github.com/dotcloud/docker/pkg/netlink" "github.com/dotcloud/docker/pkg/user" + "github.com/dotcloud/docker/runtime/execdriver" "github.com/syndtr/gocapability/capability" "io/ioutil" "net" diff --git a/runtime/execdriver/native/default_template.go b/runtime/execdriver/native/default_template.go index 0c382059e9..e76be6ebec 100644 --- a/runtime/execdriver/native/default_template.go +++ b/runtime/execdriver/native/default_template.go @@ -2,9 +2,9 @@ package native import ( "fmt" - "github.com/dotcloud/docker/runtime/execdriver" "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/runtime/execdriver" "os" ) diff --git a/runtime/execdriver/native/driver.go b/runtime/execdriver/native/driver.go index ff6c541cf9..bf7e8ccdec 100644 --- a/runtime/execdriver/native/driver.go +++ b/runtime/execdriver/native/driver.go @@ -3,12 +3,12 @@ package native import ( "encoding/json" "fmt" - "github.com/dotcloud/docker/runtime/execdriver" "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/apparmor" "github.com/dotcloud/docker/pkg/libcontainer/nsinit" "github.com/dotcloud/docker/pkg/system" + "github.com/dotcloud/docker/runtime/execdriver" "io" "io/ioutil" "log" diff --git a/runtime/graphdriver/aufs/aufs.go b/runtime/graphdriver/aufs/aufs.go index 83a6579bc6..6f05ddd025 100644 --- a/runtime/graphdriver/aufs/aufs.go +++ b/runtime/graphdriver/aufs/aufs.go @@ -24,8 +24,8 @@ import ( "bufio" "fmt" "github.com/dotcloud/docker/archive" - "github.com/dotcloud/docker/runtime/graphdriver" mountpk "github.com/dotcloud/docker/pkg/mount" + "github.com/dotcloud/docker/runtime/graphdriver" "github.com/dotcloud/docker/utils" "os" "os/exec" diff --git a/runtime/networkdriver/ipallocator/allocator.go b/runtime/networkdriver/ipallocator/allocator.go index 2950e37003..70a7028bbe 100644 --- a/runtime/networkdriver/ipallocator/allocator.go +++ b/runtime/networkdriver/ipallocator/allocator.go @@ -3,8 +3,8 @@ package ipallocator import ( "encoding/binary" "errors" - "github.com/dotcloud/docker/runtime/networkdriver" "github.com/dotcloud/docker/pkg/collections" + "github.com/dotcloud/docker/runtime/networkdriver" "net" "sync" ) diff --git a/runtime/networkdriver/lxc/driver.go b/runtime/networkdriver/lxc/driver.go index 746bcfb5b0..827de2a609 100644 --- a/runtime/networkdriver/lxc/driver.go +++ b/runtime/networkdriver/lxc/driver.go @@ -3,12 +3,12 @@ package lxc import ( "fmt" "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/pkg/iptables" + "github.com/dotcloud/docker/pkg/netlink" "github.com/dotcloud/docker/runtime/networkdriver" "github.com/dotcloud/docker/runtime/networkdriver/ipallocator" "github.com/dotcloud/docker/runtime/networkdriver/portallocator" "github.com/dotcloud/docker/runtime/networkdriver/portmapper" - "github.com/dotcloud/docker/pkg/iptables" - "github.com/dotcloud/docker/pkg/netlink" "github.com/dotcloud/docker/utils" "io/ioutil" "log" diff --git a/runtime/runtime.go b/runtime/runtime.go index 25edd774d8..677d52acc5 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -7,21 +7,21 @@ import ( "github.com/dotcloud/docker/daemonconfig" "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/graph" + "github.com/dotcloud/docker/image" + "github.com/dotcloud/docker/pkg/graphdb" + "github.com/dotcloud/docker/pkg/sysinfo" + "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/runtime/execdriver" "github.com/dotcloud/docker/runtime/execdriver/execdrivers" "github.com/dotcloud/docker/runtime/execdriver/lxc" - "github.com/dotcloud/docker/graph" "github.com/dotcloud/docker/runtime/graphdriver" "github.com/dotcloud/docker/runtime/graphdriver/aufs" _ "github.com/dotcloud/docker/runtime/graphdriver/btrfs" _ "github.com/dotcloud/docker/runtime/graphdriver/devmapper" _ "github.com/dotcloud/docker/runtime/graphdriver/vfs" - "github.com/dotcloud/docker/image" _ "github.com/dotcloud/docker/runtime/networkdriver/lxc" "github.com/dotcloud/docker/runtime/networkdriver/portallocator" - "github.com/dotcloud/docker/pkg/graphdb" - "github.com/dotcloud/docker/pkg/sysinfo" - "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" "io" "io/ioutil" From 8b5cf51d600dc4f3611cf063c52cf3448e7b01e5 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 14 Mar 2014 22:33:41 -0400 Subject: [PATCH 119/384] Disable automatic killing of containers when docker stop fails Docker-DCO-1.1-Signed-off-by: Brian Goff (github: cpuguy83) --- api/client.go | 6 +++--- .../sources/reference/api/docker_remote_api_v1.9.rst | 4 ++-- docs/sources/reference/commandline/cli.rst | 6 +++--- runtime/container.go | 12 ++---------- 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/api/client.go b/api/client.go index 715f58ab06..6191f1b001 100644 --- a/api/client.go +++ b/api/client.go @@ -477,8 +477,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, 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.") + 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.") if err := cmd.Parse(args); err != nil { return nil } @@ -505,7 +505,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 try to stop for before killing the container. Once killed it will then be restarted. Default=10") + nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Number of seconds to wait for the container to stop. Default=10") if err := cmd.Parse(args); err != nil { return nil } diff --git a/docs/sources/reference/api/docker_remote_api_v1.9.rst b/docs/sources/reference/api/docker_remote_api_v1.9.rst index 27812457bb..def38edd55 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.9.rst +++ b/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 before killing the container + :query t: number of seconds to wait for the container to stop :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 before killing the container + :query t: number of seconds to wait for the container to stop :statuscode 204: no error :statuscode 404: no such container :statuscode 500: server error diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 2371ed1b5f..c780445179 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -1366,11 +1366,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, and then SIGKILL after grace period) + Stop a running container (Send SIGTERM) - -t, --time=10: Number of seconds to wait for the container to stop before killing it. + -t, --time=10: Number of seconds to wait for the container to stop. -The main process inside the container will receive SIGTERM, and after a grace period, SIGKILL +The main process inside the container will receive SIGTERM. .. _cli_tag: diff --git a/runtime/container.go b/runtime/container.go index ee545db201..acda4db169 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -890,20 +890,12 @@ func (container *Container) Stop(seconds int) error { // 1. Send a SIGTERM if err := container.KillSig(15); err != nil { - 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 - } + return err } // 2. Wait for the process to exit on its own if err := container.WaitTimeout(time.Duration(seconds) * time.Second); err != nil { - 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 err } return nil } From 326f6a4b4d942bf75ecb003e5c0d439bcb9d67cf Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 17 Mar 2014 19:17:40 +0000 Subject: [PATCH 120/384] fix tests Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- integration/commands_test.go | 2 +- integration/server_test.go | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/integration/commands_test.go b/integration/commands_test.go index dba15842c7..5804d9f351 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -1062,7 +1062,7 @@ func TestContainerOrphaning(t *testing.T) { // remove the second image by name resp := engine.NewTable("", 0) - if err := srv.DeleteImage(imageName, resp, true, false); err == nil { + if err := srv.DeleteImage(imageName, resp, true, false, false); err == nil { t.Fatal("Expected error, got none") } diff --git a/integration/server_test.go b/integration/server_test.go index 2455c766e3..a401f1306e 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -36,7 +36,7 @@ func TestImageTagImageDelete(t *testing.T) { t.Errorf("Expected %d images, %d found", nExpected, nActual) } - if err := srv.DeleteImage("utest/docker:tag2", engine.NewTable("", 0), true, false); err != nil { + if err := srv.DeleteImage("utest/docker:tag2", engine.NewTable("", 0), true, false, false); err != nil { t.Fatal(err) } @@ -48,7 +48,7 @@ func TestImageTagImageDelete(t *testing.T) { t.Errorf("Expected %d images, %d found", nExpected, nActual) } - if err := srv.DeleteImage("utest:5000/docker:tag3", engine.NewTable("", 0), true, false); err != nil { + if err := srv.DeleteImage("utest:5000/docker:tag3", engine.NewTable("", 0), true, false, false); err != nil { t.Fatal(err) } @@ -57,7 +57,7 @@ func TestImageTagImageDelete(t *testing.T) { nExpected = len(initialImages.Data[0].GetList("RepoTags")) + 1 nActual = len(images.Data[0].GetList("RepoTags")) - if err := srv.DeleteImage("utest:tag1", engine.NewTable("", 0), true, false); err != nil { + if err := srv.DeleteImage("utest:tag1", engine.NewTable("", 0), true, false, false); err != nil { t.Fatal(err) } @@ -579,7 +579,7 @@ func TestRmi(t *testing.T) { t.Fatalf("Expected 2 new images, found %d.", images.Len()-initialImages.Len()) } - if err = srv.DeleteImage(imageID, engine.NewTable("", 0), true, false); err != nil { + if err = srv.DeleteImage(imageID, engine.NewTable("", 0), true, false, false); err != nil { t.Fatal(err) } @@ -815,7 +815,7 @@ func TestDeleteTagWithExistingContainers(t *testing.T) { // Try to remove the tag imgs := engine.NewTable("", 0) - if err := srv.DeleteImage("utest:tag1", imgs, true, false); err != nil { + if err := srv.DeleteImage("utest:tag1", imgs, true, false, false); err != nil { t.Fatal(err) } From 1dfc44073399aadb226c1b4c1909fb15c033d44a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 17 Mar 2014 19:33:40 +0000 Subject: [PATCH 121/384] fix panic in monitor Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- runtime/container.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/container.go b/runtime/container.go index 9b138c89c1..5241c5cfe5 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -785,7 +785,7 @@ func (container *Container) monitor(callback execdriver.StartCallback) error { utils.Errorf("Error running container: %s", err) } - if container.runtime.srv.IsRunning() { + if container.runtime != nil && container.runtime.srv != nil && container.runtime.srv.IsRunning() { container.State.SetStopped(exitCode) // FIXME: there is a race condition here which causes this to fail during the unit tests. From 53c5b1856d91093fed1c2d3f038d227f5fdef4b8 Mon Sep 17 00:00:00 2001 From: Timothy Hobbs Date: Sat, 15 Mar 2014 23:04:36 +0100 Subject: [PATCH 122/384] Add failing test case for issue #4681 Add a failing test case for an issue where docker is not creating a loopback device if networking is dissabled. Docker-DCO-1.1-Signed-off-by: Timothy Hobbs (github: https://github.com/timthelion) --- integration/container_test.go | 69 ++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/integration/container_test.go b/integration/container_test.go index 010883a709..663b350638 100644 --- a/integration/container_test.go +++ b/integration/container_test.go @@ -434,28 +434,6 @@ func TestOutput(t *testing.T) { } } -func TestContainerNetwork(t *testing.T) { - runtime := mkRuntime(t) - defer nuke(runtime) - container, _, err := runtime.Create( - &runconfig.Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"ping", "-c", "1", "127.0.0.1"}, - }, - "", - ) - if err != nil { - t.Fatal(err) - } - defer runtime.Destroy(container) - if err := container.Run(); err != nil { - t.Fatal(err) - } - if code := container.State.GetExitCode(); code != 0 { - t.Fatalf("Unexpected ping 127.0.0.1 exit code %d (expected 0)", code) - } -} - func TestKillDifferentUser(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) @@ -1523,6 +1501,53 @@ func TestVolumesFromWithVolumes(t *testing.T) { } } +func TestContainerNetwork(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + container, _, err := runtime.Create( + &runconfig.Config{ + Image: GetTestImage(runtime).ID, + // If I change this to ping 8.8.8.8 it fails. Any idea why? - timthelion + Cmd: []string{"ping", "-c", "1", "127.0.0.1"}, + }, + "", + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + if err := container.Run(); err != nil { + t.Fatal(err) + } + if code := container.State.GetExitCode(); code != 0 { + t.Fatalf("Unexpected ping 127.0.0.1 exit code %d (expected 0)", code) + } +} + +// Issue #4681 +func TestLoopbackFunctionsWhenNetworkingIsDissabled(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + container, _, err := runtime.Create( + &runconfig.Config{ + Image: GetTestImage(runtime).ID, + Cmd: []string{"ping", "-c", "1", "127.0.0.1"}, + NetworkDisabled: true, + }, + "", + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + if err := container.Run(); err != nil { + t.Fatal(err) + } + if code := container.State.GetExitCode(); code != 0 { + t.Fatalf("Unexpected ping 127.0.0.1 exit code %d (expected 0)", code) + } +} + func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) { eng := NewTestEngine(t) runtime := mkRuntimeFromEngine(eng, t) From 353df19ab7009f6555dee506841ae0b690a08768 Mon Sep 17 00:00:00 2001 From: Timothy Hobbs Date: Sun, 16 Mar 2014 01:01:31 +0100 Subject: [PATCH 123/384] Fix issue #4681 - No loopback interface within container when networking is disabled. Docker-DCO-1.1-Signed-off-by: Timothy Hobbs (github: https://github.com/timthelion) Remove loopback code from veth strategy Docker-DCO-1.1-Signed-off-by: Timothy Hobbs (github: https://github.com/timthelion) Looback strategy: Get rid of uneeded code in Create Docker-DCO-1.1-Signed-off-by: Timothy Hobbs (github: https://github.com/timthelion) Use append when building network strategy list Docker-DCO-1.1-Signed-off-by: Timothy Hobbs (github: https://github.com/timthelion) Swap loopback and veth strategies in Networks list Docker-DCO-1.1-Signed-off-by: Timothy Hobbs (github: https://github.com/timthelion) Revert "Swap loopback and veth strategies in Networks list" This reverts commit 3b8b2c8454171d79bed5e9a80165172617e92fc7. Docker-DCO-1.1-Signed-off-by: Timothy Hobbs (github: https://github.com/timthelion) When initializing networks, only return from the loop if there is an error Docker-DCO-1.1-Signed-off-by: Timothy Hobbs (github: https://github.com/timthelion) --- pkg/libcontainer/network/loopback.go | 24 +++++++++++++ pkg/libcontainer/network/strategy.go | 3 +- pkg/libcontainer/network/veth.go | 6 ---- pkg/libcontainer/nsinit/init.go | 6 +++- runtime/execdriver/native/default_template.go | 35 +++++++++++++------ 5 files changed, 56 insertions(+), 18 deletions(-) create mode 100644 pkg/libcontainer/network/loopback.go diff --git a/pkg/libcontainer/network/loopback.go b/pkg/libcontainer/network/loopback.go new file mode 100644 index 0000000000..6215061dc2 --- /dev/null +++ b/pkg/libcontainer/network/loopback.go @@ -0,0 +1,24 @@ +package network + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/libcontainer" +) + +// Loopback is a network strategy that provides a basic loopback device +type Loopback struct { +} + +func (l *Loopback) Create(n *libcontainer.Network, nspid int, context libcontainer.Context) error { + return nil +} + +func (l *Loopback) Initialize(config *libcontainer.Network, context libcontainer.Context) error { + if err := SetMtu("lo", config.Mtu); err != nil { + return fmt.Errorf("set lo mtu to %d %s", config.Mtu, err) + } + if err := InterfaceUp("lo"); err != nil { + return fmt.Errorf("lo up %s", err) + } + return nil +} diff --git a/pkg/libcontainer/network/strategy.go b/pkg/libcontainer/network/strategy.go index 234fcc0aa2..693790d280 100644 --- a/pkg/libcontainer/network/strategy.go +++ b/pkg/libcontainer/network/strategy.go @@ -10,7 +10,8 @@ var ( ) var strategies = map[string]NetworkStrategy{ - "veth": &Veth{}, + "veth": &Veth{}, + "loopback": &Loopback{}, } // NetworkStrategy represents a specific network configuration for diff --git a/pkg/libcontainer/network/veth.go b/pkg/libcontainer/network/veth.go index 3ab1b2393b..3df0cd61ee 100644 --- a/pkg/libcontainer/network/veth.go +++ b/pkg/libcontainer/network/veth.go @@ -68,12 +68,6 @@ func (v *Veth) Initialize(config *libcontainer.Network, context libcontainer.Con if err := InterfaceUp("eth0"); err != nil { return fmt.Errorf("eth0 up %s", err) } - if err := SetMtu("lo", config.Mtu); err != nil { - return fmt.Errorf("set lo mtu to %d %s", config.Mtu, err) - } - if err := InterfaceUp("lo"); err != nil { - return fmt.Errorf("lo up %s", err) - } if config.Gateway != "" { if err := SetDefaultGateway(config.Gateway); err != nil { return fmt.Errorf("set gateway to %s %s", config.Gateway, err) diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index e329becbdf..117ae875ed 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -134,7 +134,11 @@ func setupNetwork(container *libcontainer.Container, context libcontainer.Contex if err != nil { return err } - return strategy.Initialize(config, context) + + err1 := strategy.Initialize(config, context) + if err1 != nil { + return err1 + } } return nil } diff --git a/runtime/execdriver/native/default_template.go b/runtime/execdriver/native/default_template.go index e76be6ebec..47b19c9d66 100644 --- a/runtime/execdriver/native/default_template.go +++ b/runtime/execdriver/native/default_template.go @@ -19,19 +19,34 @@ func createContainer(c *execdriver.Command) *libcontainer.Container { container.WorkingDir = c.WorkingDir container.Env = c.Env + loopbackNetwork := libcontainer.Network{ + // Using constants here because + // when networking is disabled + // These settings simply don't exist: + // https://github.com/dotcloud/docker/blob/c7ea6e5da80af3d9ba7558f876efbf0801d988d8/runtime/container.go#L367 + Mtu: 1500, + Address: fmt.Sprintf("%s/%d", "127.0.0.1", 0), + Gateway: "localhost", + Type: "loopback", + Context: libcontainer.Context{}, + } + + container.Networks = []*libcontainer.Network{ + &loopbackNetwork, + } + if c.Network != nil { - container.Networks = []*libcontainer.Network{ - { - Mtu: c.Network.Mtu, - Address: fmt.Sprintf("%s/%d", c.Network.IPAddress, c.Network.IPPrefixLen), - Gateway: c.Network.Gateway, - Type: "veth", - Context: libcontainer.Context{ - "prefix": "veth", - "bridge": c.Network.Bridge, - }, + vethNetwork := libcontainer.Network{ + Mtu: c.Network.Mtu, + Address: fmt.Sprintf("%s/%d", c.Network.IPAddress, c.Network.IPPrefixLen), + Gateway: c.Network.Gateway, + Type: "veth", + Context: libcontainer.Context{ + "prefix": "veth", + "bridge": c.Network.Bridge, }, } + container.Networks = append(container.Networks, &vethNetwork) } container.Cgroups.Name = c.ID From 659b719aa66e7ed0c3104d3704fa61035050ad82 Mon Sep 17 00:00:00 2001 From: Timothy Hobbs Date: Sun, 16 Mar 2014 20:52:27 +0100 Subject: [PATCH 124/384] Refactor out interface specific information from execdriver.Network Docker-DCO-1.1-Signed-off-by: Timothy Hobbs (github: https://github.com/timthelion) --- runtime/container.go | 8 ++++++-- runtime/execdriver/driver.go | 10 +++++++--- runtime/execdriver/lxc/driver.go | 10 ++++++---- runtime/execdriver/lxc/lxc_template.go | 6 +++--- runtime/execdriver/lxc/lxc_template_unit_test.go | 8 ++++++++ runtime/execdriver/native/default_template.go | 14 +++++--------- 6 files changed, 35 insertions(+), 21 deletions(-) diff --git a/runtime/container.go b/runtime/container.go index 2a30715206..73bad67d6a 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -364,14 +364,18 @@ func populateCommand(c *Container) { driverConfig []string ) + en = &execdriver.Network{ + Mtu: c.runtime.config.Mtu, + Interface: nil, + } + if !c.Config.NetworkDisabled { network := c.NetworkSettings - en = &execdriver.Network{ + en.Interface = &execdriver.NetworkInterface{ Gateway: network.Gateway, Bridge: network.Bridge, IPAddress: network.IPAddress, IPPrefixLen: network.IPPrefixLen, - Mtu: c.runtime.config.Mtu, } } diff --git a/runtime/execdriver/driver.go b/runtime/execdriver/driver.go index ff37b6bc5b..23e31ee8d9 100644 --- a/runtime/execdriver/driver.go +++ b/runtime/execdriver/driver.go @@ -84,11 +84,15 @@ type Driver interface { // Network settings of the container type Network struct { + Interface *NetworkInterface `json:"interface"` // if interface is nil then networking is disabled + Mtu int `json:"mtu"` +} + +type NetworkInterface struct { Gateway string `json:"gateway"` IPAddress string `json:"ip"` Bridge string `json:"bridge"` IPPrefixLen int `json:"ip_prefix_len"` - Mtu int `json:"mtu"` } type Resources struct { @@ -118,8 +122,8 @@ type Command struct { WorkingDir string `json:"working_dir"` ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver Tty bool `json:"tty"` - Network *Network `json:"network"` // if network is nil then networking is disabled - Config []string `json:"config"` // generic values that specific drivers can consume + Network *Network `json:"network"` + Config []string `json:"config"` // generic values that specific drivers can consume Resources *Resources `json:"resources"` Mounts []Mount `json:"mounts"` diff --git a/runtime/execdriver/lxc/driver.go b/runtime/execdriver/lxc/driver.go index b7311cc1ff..086e35f643 100644 --- a/runtime/execdriver/lxc/driver.go +++ b/runtime/execdriver/lxc/driver.go @@ -98,13 +98,15 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba DriverName, } - if c.Network != nil { + if c.Network.Interface != nil { params = append(params, - "-g", c.Network.Gateway, - "-i", fmt.Sprintf("%s/%d", c.Network.IPAddress, c.Network.IPPrefixLen), - "-mtu", strconv.Itoa(c.Network.Mtu), + "-g", c.Network.Interface.Gateway, + "-i", fmt.Sprintf("%s/%d", c.Network.Interface.IPAddress, c.Network.Interface.IPPrefixLen), ) } + params = append(params, + "-mtu", strconv.Itoa(c.Network.Mtu), + ) if c.User != "" { params = append(params, "-u", c.User) diff --git a/runtime/execdriver/lxc/lxc_template.go b/runtime/execdriver/lxc/lxc_template.go index db55287522..ce9d90469f 100644 --- a/runtime/execdriver/lxc/lxc_template.go +++ b/runtime/execdriver/lxc/lxc_template.go @@ -7,17 +7,17 @@ import ( ) const LxcTemplate = ` -{{if .Network}} +{{if .Network.Interface}} # network configuration lxc.network.type = veth -lxc.network.link = {{.Network.Bridge}} +lxc.network.link = {{.Network.Interface.Bridge}} lxc.network.name = eth0 -lxc.network.mtu = {{.Network.Mtu}} {{else}} # network is disabled (-n=false) lxc.network.type = empty lxc.network.flags = up {{end}} +lxc.network.mtu = {{.Network.Mtu}} # root filesystem {{$ROOTFS := .Rootfs}} diff --git a/runtime/execdriver/lxc/lxc_template_unit_test.go b/runtime/execdriver/lxc/lxc_template_unit_test.go index ae66371836..e613adf7a9 100644 --- a/runtime/execdriver/lxc/lxc_template_unit_test.go +++ b/runtime/execdriver/lxc/lxc_template_unit_test.go @@ -43,6 +43,10 @@ func TestLXCConfig(t *testing.T) { Memory: int64(mem), CpuShares: int64(cpu), }, + Network: &execdriver.Network{ + Mtu: 1500, + Interface: nil, + }, } p, err := driver.generateLXCConfig(command) if err != nil { @@ -75,6 +79,10 @@ func TestCustomLxcConfig(t *testing.T) { "lxc.utsname = docker", "lxc.cgroup.cpuset.cpus = 0,1", }, + Network: &execdriver.Network{ + Mtu: 1500, + Interface: nil, + }, } p, err := driver.generateLXCConfig(command) diff --git a/runtime/execdriver/native/default_template.go b/runtime/execdriver/native/default_template.go index 47b19c9d66..d744ab382f 100644 --- a/runtime/execdriver/native/default_template.go +++ b/runtime/execdriver/native/default_template.go @@ -20,11 +20,7 @@ func createContainer(c *execdriver.Command) *libcontainer.Container { container.Env = c.Env loopbackNetwork := libcontainer.Network{ - // Using constants here because - // when networking is disabled - // These settings simply don't exist: - // https://github.com/dotcloud/docker/blob/c7ea6e5da80af3d9ba7558f876efbf0801d988d8/runtime/container.go#L367 - Mtu: 1500, + Mtu: c.Network.Mtu, Address: fmt.Sprintf("%s/%d", "127.0.0.1", 0), Gateway: "localhost", Type: "loopback", @@ -35,15 +31,15 @@ func createContainer(c *execdriver.Command) *libcontainer.Container { &loopbackNetwork, } - if c.Network != nil { + if c.Network.Interface != nil { vethNetwork := libcontainer.Network{ Mtu: c.Network.Mtu, - Address: fmt.Sprintf("%s/%d", c.Network.IPAddress, c.Network.IPPrefixLen), - Gateway: c.Network.Gateway, + Address: fmt.Sprintf("%s/%d", c.Network.Interface.IPAddress, c.Network.Interface.IPPrefixLen), + Gateway: c.Network.Interface.Gateway, Type: "veth", Context: libcontainer.Context{ "prefix": "veth", - "bridge": c.Network.Bridge, + "bridge": c.Network.Interface.Bridge, }, } container.Networks = append(container.Networks, &vethNetwork) From 25218f9b239784e6f38550a6e320bce56aaca3e1 Mon Sep 17 00:00:00 2001 From: Isabel Jimenez Date: Mon, 17 Mar 2014 01:40:36 -0700 Subject: [PATCH 125/384] adding configuration for timeout and disable it by default Docker-DCO-1.1-Signed-off-by: Isabel Jimenez (github: jimenez) --- api/server.go | 5 ++--- pkg/listenbuffer/buffer.go | 27 ++++++--------------------- 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/api/server.go b/api/server.go index 048c989540..8871fb06f8 100644 --- a/api/server.go +++ b/api/server.go @@ -26,7 +26,6 @@ import ( "strconv" "strings" "syscall" - "time" ) var ( @@ -1130,7 +1129,7 @@ func changeGroup(addr string, nameOrGid string) error { // ListenAndServe sets up the required http.Server and gets it listening for // each addr passed in and does protocol specific checking. -func ListenAndServe(proto, addr string, eng *engine.Engine, logging, enableCors bool, dockerVersion string, socketGroup string) error { +func ListenAndServe(proto, addr string, eng *engine.Engine, logging, enableCors bool, dockerVersion, socketGroup string) error { r, err := createRouter(eng, logging, enableCors, dockerVersion) if err != nil { @@ -1147,7 +1146,7 @@ func ListenAndServe(proto, addr string, eng *engine.Engine, logging, enableCors } } - l, err := listenbuffer.NewListenBuffer(proto, addr, activationLock, 15*time.Minute) + l, err := listenbuffer.NewListenBuffer(proto, addr, activationLock) if err != nil { return err } diff --git a/pkg/listenbuffer/buffer.go b/pkg/listenbuffer/buffer.go index c350805a7d..17572c8a0e 100644 --- a/pkg/listenbuffer/buffer.go +++ b/pkg/listenbuffer/buffer.go @@ -5,15 +5,10 @@ */ package listenbuffer -import ( - "fmt" - "net" - "time" -) +import "net" -// NewListenBuffer returns a listener listening on addr with the protocol. It sets the -// timeout to wait on first connection before an error is returned -func NewListenBuffer(proto, addr string, activate chan struct{}, timeout time.Duration) (net.Listener, error) { +// NewListenBuffer returns a listener listening on addr with the protocol. +func NewListenBuffer(proto, addr string, activate chan struct{}) (net.Listener, error) { wrapped, err := net.Listen(proto, addr) if err != nil { return nil, err @@ -22,7 +17,6 @@ func NewListenBuffer(proto, addr string, activate chan struct{}, timeout time.Du return &defaultListener{ wrapped: wrapped, activate: activate, - timeout: timeout, }, nil } @@ -30,7 +24,6 @@ type defaultListener struct { wrapped net.Listener // the real listener to wrap ready bool // is the listner ready to start accpeting connections activate chan struct{} - timeout time.Duration // how long to wait before we consider this an error } func (l *defaultListener) Close() error { @@ -47,15 +40,7 @@ func (l *defaultListener) Accept() (net.Conn, error) { if l.ready { return l.wrapped.Accept() } - - select { - case <-time.After(l.timeout): - // close the connection so any clients are disconnected - l.Close() - return nil, fmt.Errorf("timeout (%s) reached waiting for listener to become ready", l.timeout.String()) - case <-l.activate: - l.ready = true - return l.Accept() - } - panic("unreachable") + <-l.activate + l.ready = true + return l.Accept() } From 782eb5f03a9dde970172895da37be8c656fef3bd Mon Sep 17 00:00:00 2001 From: Andrea Turli Date: Mon, 17 Mar 2014 23:11:46 +0100 Subject: [PATCH 126/384] add softlayer installation instructions Docker-DCO-1.1-Signed-off-by: Andrea Turli (github: andreaturli) --- docs/sources/installation/index.rst | 1 + docs/sources/installation/softlayer.rst | 27 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 docs/sources/installation/softlayer.rst diff --git a/docs/sources/installation/index.rst b/docs/sources/installation/index.rst index 39c1f6a292..ae0e9196fa 100644 --- a/docs/sources/installation/index.rst +++ b/docs/sources/installation/index.rst @@ -30,4 +30,5 @@ Contents: amazon rackspace google + softlayer binaries diff --git a/docs/sources/installation/softlayer.rst b/docs/sources/installation/softlayer.rst new file mode 100644 index 0000000000..ff65029f62 --- /dev/null +++ b/docs/sources/installation/softlayer.rst @@ -0,0 +1,27 @@ +:title: Installation on IBM SoftLayer +:description: Please note this project is currently under heavy development. It should not be used in production. +:keywords: IBM SoftLayer, virtualization, cloud, docker, documentation, installation + +IBM SoftLayer +============= + +.. include:: install_header.inc + +There are several ways to install Docker on IBM SoftLayer, but probably the simplest way is the following: + +IBM SoftLayer QuickStart +------------------------- + +1. Create an `IBM SoftLayer account `_. +2. Log in to the `SoftLayer Console `_. +3. Go to `Order Hourly Computing Instance `_ on your SoftLayer Console. +4. Create a new CCI using the default values for all the fields and choose: + +- *First Available* as ``Datacenter`` and +- *Ubuntu Linux 12.04 LTS Precise Pangolin - Minimal Install (64 bit)* as ``Operating System``. + +5. Click the *Continue Your Order* button at the bottom right and select *Go to checkout*. +6. Insert the required *User Metadata* and place the order. +7. Then continue with the :ref:`ubuntu_linux` instructions. + +Continue with the :ref:`hello_world` example. \ No newline at end of file From 8b159fca8a4bc8692c77db3536f7098a583270ad Mon Sep 17 00:00:00 2001 From: Andrea Turli Date: Mon, 17 Mar 2014 23:39:02 +0100 Subject: [PATCH 127/384] address comments from @jamtur01 Docker-DCO-1.1-Signed-off-by: Andrea Turli (github: andreaturli) --- docs/sources/installation/softlayer.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/sources/installation/softlayer.rst b/docs/sources/installation/softlayer.rst index ff65029f62..0fe3d6df5a 100644 --- a/docs/sources/installation/softlayer.rst +++ b/docs/sources/installation/softlayer.rst @@ -7,15 +7,13 @@ IBM SoftLayer .. include:: install_header.inc -There are several ways to install Docker on IBM SoftLayer, but probably the simplest way is the following: - IBM SoftLayer QuickStart ------------------------- 1. Create an `IBM SoftLayer account `_. 2. Log in to the `SoftLayer Console `_. -3. Go to `Order Hourly Computing Instance `_ on your SoftLayer Console. -4. Create a new CCI using the default values for all the fields and choose: +3. Go to `Order Hourly Computing Instance Wizard `_ on your SoftLayer Console. +4. Create a new *CloudLayer Computing Instance* (CCI) using the default values for all the fields and choose: - *First Available* as ``Datacenter`` and - *Ubuntu Linux 12.04 LTS Precise Pangolin - Minimal Install (64 bit)* as ``Operating System``. From 15a267b57d2394dd5cb697f9a80b6df4fc939a76 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 18 Mar 2014 01:34:43 +0000 Subject: [PATCH 128/384] add time since exit in docker ps Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- runtime/state.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/state.go b/runtime/state.go index cce6912b46..1c682acd26 100644 --- a/runtime/state.go +++ b/runtime/state.go @@ -28,7 +28,7 @@ func (s *State) String() string { } return fmt.Sprintf("Up %s", utils.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) } - return fmt.Sprintf("Exit %d", s.ExitCode) + return fmt.Sprintf("Exited (%d) %s ago", s.ExitCode, utils.HumanDuration(time.Now().UTC().Sub(s.FinishedAt))) } func (s *State) IsRunning() bool { From c1f492755b8774005b3627da8ee001ee0b2df4eb Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 17 Mar 2014 19:26:08 -0600 Subject: [PATCH 129/384] Improve WORKDIR test to cover more edge cases Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- integration/buildfile_test.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index 9c986d74c2..95d5abb8a7 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -445,16 +445,18 @@ func TestBuildRelativeWorkdir(t *testing.T) { img, err := buildImage(testContextTemplate{` FROM {IMAGE} RUN [ "$PWD" = '/' ] - WORKDIR /test1 + WORKDIR test1 RUN [ "$PWD" = '/test1' ] - WORKDIR test2 - RUN [ "$PWD" = '/test1/test2' ] + WORKDIR /test2 + RUN [ "$PWD" = '/test2' ] + WORKDIR test3 + RUN [ "$PWD" = '/test2/test3' ] `, nil, nil}, t, nil, true) if err != nil { t.Fatal(err) } - if img.Config.WorkingDir != "/test1/test2" { - t.Fail() + if img.Config.WorkingDir != "/test2/test3" { + t.Fatalf("Expected workdir to be '/test2/test3', received '%s'", img.Config.WorkingDir) } } From 69299f041f78f8a86bf810142e740bbd72fe4b1b Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 17 Mar 2014 19:26:39 -0600 Subject: [PATCH 130/384] Add some documentation about relative WORKDIR values Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- docs/sources/reference/builder.rst | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/sources/reference/builder.rst b/docs/sources/reference/builder.rst index 0d8d750a04..1c8331e98f 100644 --- a/docs/sources/reference/builder.rst +++ b/docs/sources/reference/builder.rst @@ -407,7 +407,16 @@ the image. The ``WORKDIR`` instruction sets the working directory for the ``RUN``, ``CMD`` and ``ENTRYPOINT`` Dockerfile commands that follow it. -It can be used multiple times in the one Dockerfile. +It can be used multiple times in the one Dockerfile. If a relative path is +provided, it will be relative to the path of the previous ``WORKDIR`` +instruction. For example: + + WORKDIR /a + WORKDIR b + WORKDIR c + RUN pwd + +The output of the final ``pwd`` command in this Dockerfile would be ``/a/b/c``. 3.11 ONBUILD ------------ From 4b1513f9c394fbfdf21998db4318251b4e8b6bc0 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 17 Mar 2014 17:42:16 -0700 Subject: [PATCH 131/384] Only unshare the mount namespace for execin Fixes #4728 Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/execin.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/libcontainer/nsinit/execin.go b/pkg/libcontainer/nsinit/execin.go index 39df4761a0..f8b8931390 100644 --- a/pkg/libcontainer/nsinit/execin.go +++ b/pkg/libcontainer/nsinit/execin.go @@ -14,10 +14,12 @@ import ( // ExecIn uses an existing pid and joins the pid's namespaces with the new command. func (ns *linuxNs) ExecIn(container *libcontainer.Container, nspid int, args []string) (int, error) { - ns.logger.Println("unshare namespaces") - for _, ns := range container.Namespaces { - if err := system.Unshare(ns.Value); err != nil { - return -1, err + for _, nsv := range container.Namespaces { + // skip the PID namespace on unshare because it it not supported + if nsv.Key != "NEWPID" { + if err := system.Unshare(nsv.Value); err != nil { + return -1, err + } } } fds, err := ns.getNsFds(nspid, container) From 670ce98c60dbac1d46a59bd69bd20b569f4794f1 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Fri, 14 Mar 2014 14:23:54 -0400 Subject: [PATCH 132/384] graphdriver: build tags Enable build tags for all the graphdrivers to be excludable. As an example: ``` $ go build $ ls -l docker -rwxr-xr-x 1 vbatts vbatts 18400158 Mar 14 14:22 docker* $ go build -tags "exclude_graphdriver_aufs exclude_graphdriver_vfs exclude_graphdriver_devicemapper" $ ls -l docker -rwxr-xr-x 1 vbatts vbatts 17467068 Mar 14 14:22 docker* ``` Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- hack/PACKAGERS.md | 17 +++++++++++++++++ runtime/runtime.go | 11 +++-------- runtime/runtime_aufs.go | 22 ++++++++++++++++++++++ runtime/runtime_devicemapper.go | 7 +++++++ runtime/runtime_no_aufs.go | 11 +++++++++++ runtime/runtime_vfs.go | 7 +++++++ 6 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 runtime/runtime_aufs.go create mode 100644 runtime/runtime_devicemapper.go create mode 100644 runtime/runtime_no_aufs.go create mode 100644 runtime/runtime_vfs.go diff --git a/hack/PACKAGERS.md b/hack/PACKAGERS.md index 5dcb120689..0e513cd4fa 100644 --- a/hack/PACKAGERS.md +++ b/hack/PACKAGERS.md @@ -157,6 +157,23 @@ AppArmor, you will need to set `DOCKER_BUILDTAGS` as follows: export DOCKER_BUILDTAGS='apparmor' ``` +There are build tags for disabling graphdrivers as well. By default, support +for all graphdrivers are built in. + +To disable vfs +```bash +export DOCKER_BUILDTAGS='exclude_graphdriver_vfs' +``` + +To disable devicemapper +```bash +export DOCKER_BUILDTAGS='exclude_graphdriver_devicemapper' +``` +To disable aufs +```bash +export DOCKER_BUILDTAGS='exclude_graphdriver_aufs' +``` + ### Static Daemon If it is feasible within the constraints of your distribution, you should diff --git a/runtime/runtime.go b/runtime/runtime.go index 677d52acc5..be15cb562d 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -16,10 +16,7 @@ import ( "github.com/dotcloud/docker/runtime/execdriver/execdrivers" "github.com/dotcloud/docker/runtime/execdriver/lxc" "github.com/dotcloud/docker/runtime/graphdriver" - "github.com/dotcloud/docker/runtime/graphdriver/aufs" _ "github.com/dotcloud/docker/runtime/graphdriver/btrfs" - _ "github.com/dotcloud/docker/runtime/graphdriver/devmapper" - _ "github.com/dotcloud/docker/runtime/graphdriver/vfs" _ "github.com/dotcloud/docker/runtime/networkdriver/lxc" "github.com/dotcloud/docker/runtime/networkdriver/portallocator" "github.com/dotcloud/docker/utils" @@ -652,11 +649,9 @@ func NewRuntimeFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (* return nil, err } - if ad, ok := driver.(*aufs.Driver); ok { - utils.Debugf("Migrating existing containers") - if err := ad.Migrate(config.Root, graph.SetupInitLayer); err != nil { - return nil, err - } + // Migrate the container if it is aufs and aufs is enabled + if err = migrateIfAufs(driver, config.Root); err != nil { + return nil, err } utils.Debugf("Creating images graph") diff --git a/runtime/runtime_aufs.go b/runtime/runtime_aufs.go new file mode 100644 index 0000000000..5a32615df5 --- /dev/null +++ b/runtime/runtime_aufs.go @@ -0,0 +1,22 @@ +// +build !exclude_graphdriver_aufs + +package runtime + +import ( + "github.com/dotcloud/docker/graph" + "github.com/dotcloud/docker/runtime/graphdriver" + "github.com/dotcloud/docker/runtime/graphdriver/aufs" + "github.com/dotcloud/docker/utils" +) + +// Given the graphdriver ad, if it is aufs, then migrate it. +// If aufs driver is not built, this func is a noop. +func migrateIfAufs(driver graphdriver.Driver, root string) error { + if ad, ok := driver.(*aufs.Driver); ok { + utils.Debugf("Migrating existing containers") + if err := ad.Migrate(root, graph.SetupInitLayer); err != nil { + return err + } + } + return nil +} diff --git a/runtime/runtime_devicemapper.go b/runtime/runtime_devicemapper.go new file mode 100644 index 0000000000..5b418b377a --- /dev/null +++ b/runtime/runtime_devicemapper.go @@ -0,0 +1,7 @@ +// +build !exclude_graphdriver_devicemapper + +package runtime + +import ( + _ "github.com/dotcloud/docker/runtime/graphdriver/devmapper" +) diff --git a/runtime/runtime_no_aufs.go b/runtime/runtime_no_aufs.go new file mode 100644 index 0000000000..05a01fe151 --- /dev/null +++ b/runtime/runtime_no_aufs.go @@ -0,0 +1,11 @@ +// +build exclude_graphdriver_aufs + +package runtime + +import ( + "github.com/dotcloud/docker/runtime/graphdriver" +) + +func migrateIfAufs(driver graphdriver.Driver, root string) error { + return nil +} diff --git a/runtime/runtime_vfs.go b/runtime/runtime_vfs.go new file mode 100644 index 0000000000..e1db736083 --- /dev/null +++ b/runtime/runtime_vfs.go @@ -0,0 +1,7 @@ +// +build !exclude_graphdriver_vfs + +package runtime + +import ( + _ "github.com/dotcloud/docker/runtime/graphdriver/vfs" +) From 448b64164df7795cdbd9be0d663269e6e4e4beeb Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Fri, 14 Mar 2014 16:10:07 -0400 Subject: [PATCH 133/384] runtime: no build tags for vfs driver Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- hack/PACKAGERS.md | 5 ----- runtime/runtime.go | 1 + runtime/runtime_vfs.go | 7 ------- 3 files changed, 1 insertion(+), 12 deletions(-) delete mode 100644 runtime/runtime_vfs.go diff --git a/hack/PACKAGERS.md b/hack/PACKAGERS.md index 0e513cd4fa..5afa381005 100644 --- a/hack/PACKAGERS.md +++ b/hack/PACKAGERS.md @@ -160,11 +160,6 @@ export DOCKER_BUILDTAGS='apparmor' There are build tags for disabling graphdrivers as well. By default, support for all graphdrivers are built in. -To disable vfs -```bash -export DOCKER_BUILDTAGS='exclude_graphdriver_vfs' -``` - To disable devicemapper ```bash export DOCKER_BUILDTAGS='exclude_graphdriver_devicemapper' diff --git a/runtime/runtime.go b/runtime/runtime.go index be15cb562d..43230488a2 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -17,6 +17,7 @@ import ( "github.com/dotcloud/docker/runtime/execdriver/lxc" "github.com/dotcloud/docker/runtime/graphdriver" _ "github.com/dotcloud/docker/runtime/graphdriver/btrfs" + _ "github.com/dotcloud/docker/runtime/graphdriver/vfs" _ "github.com/dotcloud/docker/runtime/networkdriver/lxc" "github.com/dotcloud/docker/runtime/networkdriver/portallocator" "github.com/dotcloud/docker/utils" diff --git a/runtime/runtime_vfs.go b/runtime/runtime_vfs.go deleted file mode 100644 index e1db736083..0000000000 --- a/runtime/runtime_vfs.go +++ /dev/null @@ -1,7 +0,0 @@ -// +build !exclude_graphdriver_vfs - -package runtime - -import ( - _ "github.com/dotcloud/docker/runtime/graphdriver/vfs" -) From e5cbb5c906d37b14dbf0180d253f58f8995de571 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Tue, 18 Mar 2014 22:29:11 +1000 Subject: [PATCH 134/384] add env hint Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/use/working_with_links_names.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/sources/use/working_with_links_names.rst b/docs/sources/use/working_with_links_names.rst index dc370c01c9..4acb6079c1 100644 --- a/docs/sources/use/working_with_links_names.rst +++ b/docs/sources/use/working_with_links_names.rst @@ -112,8 +112,16 @@ Accessing the network information along with the environment of the child container allows us to easily connect to the Redis service on the specific IP and port in the environment. +.. note:: + These Environment variables are only set for the first process in + the container. Similarly, some daemons (such as ``sshd``) will + scrub them when spawning shells for connection. + + You can work around this by storing the initial ``env`` in a file, + or looking at ``/proc/1/environ``. + Running ``docker ps`` shows the 2 containers, and the ``webapp/db`` -alias name for the redis container. +alias name for the Redis container. .. code-block:: bash From a70beda1ecfb049a3f80ad5b159ba51d653fd067 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 18 Mar 2014 15:52:00 +0100 Subject: [PATCH 135/384] devmapper: Increase timeout in waitClose to 10sec As reported in https://github.com/dotcloud/docker/issues/4389 we're currently seeing timeouts in waitClose on some systems. We already bumped the timeout in waitRemove() in https://github.com/dotcloud/docker/issues/4504. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- runtime/graphdriver/devmapper/deviceset.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/runtime/graphdriver/devmapper/deviceset.go b/runtime/graphdriver/devmapper/deviceset.go index f6b26655a3..4d33e243e0 100644 --- a/runtime/graphdriver/devmapper/deviceset.go +++ b/runtime/graphdriver/devmapper/deviceset.go @@ -729,7 +729,7 @@ func (devices *DeviceSet) removeDeviceAndWait(devname string) error { // waitRemove blocks until either: // a) the device registered at - is removed, -// or b) the 1 second timeout expires. +// or b) the 10 second timeout expires. func (devices *DeviceSet) waitRemove(devname string) error { utils.Debugf("[deviceset %s] waitRemove(%s)", devices.devicePrefix, devname) defer utils.Debugf("[deviceset %s] waitRemove(%s) END", devices.devicePrefix, devname) @@ -760,7 +760,7 @@ func (devices *DeviceSet) waitRemove(devname string) error { // waitClose blocks until either: // a) the device registered at - is closed, -// or b) the 1 second timeout expires. +// or b) the 10 second timeout expires. func (devices *DeviceSet) waitClose(hash string) error { info := devices.Devices[hash] if info == nil { @@ -778,7 +778,9 @@ func (devices *DeviceSet) waitClose(hash string) error { if devinfo.OpenCount == 0 { break } - time.Sleep(1 * time.Millisecond) + devices.Unlock() + time.Sleep(10 * time.Millisecond) + devices.Lock() } if i == 1000 { return fmt.Errorf("Timeout while waiting for device %s to close", hash) From ae1dd52b19d075c2f4ba75cf0549c644725834e8 Mon Sep 17 00:00:00 2001 From: Viktor Vojnovski Date: Tue, 18 Mar 2014 17:10:22 +0100 Subject: [PATCH 136/384] Update ubuntulinux.rst Adding the Docker repository key fails if port 11371 not open. This would probably work for more people. --- docs/sources/installation/ubuntulinux.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index 6e79fb8cbc..6998be8571 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -72,7 +72,7 @@ First add the Docker repository key to your local keychain. .. code-block:: bash - sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9 + sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9 Add the Docker repository to your apt sources list, update and install the ``lxc-docker`` package. From f7b6fbbd7664634481c8519e58844d572423f3e1 Mon Sep 17 00:00:00 2001 From: Andy Kipp Date: Wed, 12 Mar 2014 17:22:57 -0400 Subject: [PATCH 137/384] Prevent dynamic allocation of previously allocated ports Docker-DCO-1.1-Signed-off-by: Andy Kipp (github: kippandrew) --- .../portallocator/portallocator.go | 32 +++++++++++++++---- .../portallocator/portallocator_test.go | 7 ++++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/runtime/networkdriver/portallocator/portallocator.go b/runtime/networkdriver/portallocator/portallocator.go index 71cac82703..18ae9469e5 100644 --- a/runtime/networkdriver/portallocator/portallocator.go +++ b/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,16 @@ func equalsDefault(ip net.IP) bool { return ip == nil || ip.Equal(defaultIP) } +func findNextPort(proto string, allocated *collections.OrderedIntSet) (int, error) { + port := 0 + for port = nextPort(proto); 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 diff --git a/runtime/networkdriver/portallocator/portallocator_test.go b/runtime/networkdriver/portallocator/portallocator_test.go index 603bd03bd7..3f3afa657b 100644 --- a/runtime/networkdriver/portallocator/portallocator_test.go +++ b/runtime/networkdriver/portallocator/portallocator_test.go @@ -181,4 +181,11 @@ func TestPortAllocation(t *testing.T) { if _, err := RequestPort(ip, "tcp", 80); err != nil { t.Fatal(err) } + + port, err = RequestPort(ip, "tcp", 0) + port2, err := RequestPort(ip, "tcp", port+1) + port3, err := RequestPort(ip, "tcp", 0) + if port3 == port2 { + t.Fatal("A dynamic port should never allocate a used port") + } } From 7a1db291fcedd50ce99649e95109187c76da255c Mon Sep 17 00:00:00 2001 From: Andy Kipp Date: Wed, 12 Mar 2014 17:26:17 -0400 Subject: [PATCH 138/384] Better test error message Docker-DCO-1.1-Signed-off-by: Andy Kipp (github: kippandrew) --- runtime/networkdriver/portallocator/portallocator_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/networkdriver/portallocator/portallocator_test.go b/runtime/networkdriver/portallocator/portallocator_test.go index 3f3afa657b..356da855b6 100644 --- a/runtime/networkdriver/portallocator/portallocator_test.go +++ b/runtime/networkdriver/portallocator/portallocator_test.go @@ -186,6 +186,6 @@ func TestPortAllocation(t *testing.T) { port2, err := RequestPort(ip, "tcp", port+1) port3, err := RequestPort(ip, "tcp", 0) if port3 == port2 { - t.Fatal("A dynamic port should never allocate a used port") + t.Fatal("Requesting a dynamic port should never allocate a used port") } } From 73c416a20db8fe48302a6cf0db4c1c0585ed0739 Mon Sep 17 00:00:00 2001 From: Andy Kipp Date: Thu, 13 Mar 2014 13:30:07 -0400 Subject: [PATCH 139/384] Be more explicit in finding next port to allocate Docker-DCO-1.1-Signed-off-by: Andy Kipp (github: kippandrew) --- runtime/networkdriver/portallocator/portallocator.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/runtime/networkdriver/portallocator/portallocator.go b/runtime/networkdriver/portallocator/portallocator.go index 18ae9469e5..4d698f2de2 100644 --- a/runtime/networkdriver/portallocator/portallocator.go +++ b/runtime/networkdriver/portallocator/portallocator.go @@ -151,8 +151,9 @@ func equalsDefault(ip net.IP) bool { } func findNextPort(proto string, allocated *collections.OrderedIntSet) (int, error) { - port := 0 - for port = nextPort(proto); allocated.Exists(port); port = nextPort(proto) { + port := nextPort(proto) + for allocated.Exists(port) { + port = nextPort(proto) } if port > EndPortRange { return 0, ErrPortExceedsRange From 555416fd02b9e062385dcdaf0c4b9f5de61df388 Mon Sep 17 00:00:00 2001 From: Andy Kipp Date: Tue, 18 Mar 2014 13:29:24 -0400 Subject: [PATCH 140/384] Add err checks for port allocator tests Docker-DCO-1.1-Signed-off-by: Andy Kipp (github: kippandrew) --- .../networkdriver/portallocator/portallocator_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/runtime/networkdriver/portallocator/portallocator_test.go b/runtime/networkdriver/portallocator/portallocator_test.go index 356da855b6..f01bcfc99e 100644 --- a/runtime/networkdriver/portallocator/portallocator_test.go +++ b/runtime/networkdriver/portallocator/portallocator_test.go @@ -183,8 +183,17 @@ func TestPortAllocation(t *testing.T) { } 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") } From 85a62d9b779bfb351e159f38c2fc95900a0532cd Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Thu, 13 Mar 2014 15:10:35 -0400 Subject: [PATCH 141/384] btrfs: build tags Default to the same build behavior, but allow a go build tag to disable building of the btrfs graphdriver go build -tags no_btrfs' ... $ go build $ objdump -S docker | grep btrfs | wc -l 194 $ go build -tags no_btrfs $ objdump -S docker | grep btrfs | wc -l 1 # that is a comment ;-) Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- runtime/runtime.go | 1 - runtime/runtime_btfs.go | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 runtime/runtime_btfs.go diff --git a/runtime/runtime.go b/runtime/runtime.go index 43230488a2..4408e13902 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -16,7 +16,6 @@ import ( "github.com/dotcloud/docker/runtime/execdriver/execdrivers" "github.com/dotcloud/docker/runtime/execdriver/lxc" "github.com/dotcloud/docker/runtime/graphdriver" - _ "github.com/dotcloud/docker/runtime/graphdriver/btrfs" _ "github.com/dotcloud/docker/runtime/graphdriver/vfs" _ "github.com/dotcloud/docker/runtime/networkdriver/lxc" "github.com/dotcloud/docker/runtime/networkdriver/portallocator" diff --git a/runtime/runtime_btfs.go b/runtime/runtime_btfs.go new file mode 100644 index 0000000000..5e941386c3 --- /dev/null +++ b/runtime/runtime_btfs.go @@ -0,0 +1,7 @@ +// +build !no_btrfs + +package runtime + +import ( + _ "github.com/dotcloud/docker/runtime/graphdriver/btrfs" +) From 5cfea26bcfc218ca72eac7115fa257833f28b9f2 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Thu, 13 Mar 2014 17:38:54 -0400 Subject: [PATCH 142/384] btrfs: build tags correct filename and make the tag more readable Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- runtime/{runtime_btfs.go => runtime_btrfs.go} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename runtime/{runtime_btfs.go => runtime_btrfs.go} (70%) diff --git a/runtime/runtime_btfs.go b/runtime/runtime_btrfs.go similarity index 70% rename from runtime/runtime_btfs.go rename to runtime/runtime_btrfs.go index 5e941386c3..c59b103ff9 100644 --- a/runtime/runtime_btfs.go +++ b/runtime/runtime_btrfs.go @@ -1,4 +1,4 @@ -// +build !no_btrfs +// +build !exclude_graphdriver_btrfs package runtime From 29c45e7f4fc616290e416f1b541e1739820af60c Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Thu, 13 Mar 2014 17:39:25 -0400 Subject: [PATCH 143/384] packagers: btrfs build tag docs Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- hack/PACKAGERS.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/hack/PACKAGERS.md b/hack/PACKAGERS.md index 5afa381005..297d1500db 100644 --- a/hack/PACKAGERS.md +++ b/hack/PACKAGERS.md @@ -160,15 +160,23 @@ export DOCKER_BUILDTAGS='apparmor' There are build tags for disabling graphdrivers as well. By default, support for all graphdrivers are built in. -To disable devicemapper +To disable btrfs: +```bash +export DOCKER_BUILDTAGS='exclude_graphdriver_btrfs' +``` + +To disable devicemapper: ```bash export DOCKER_BUILDTAGS='exclude_graphdriver_devicemapper' ``` -To disable aufs + +To disable aufs: ```bash export DOCKER_BUILDTAGS='exclude_graphdriver_aufs' ``` +NOTE: if you need to set more than one build tag, space seperate them. + ### Static Daemon If it is feasible within the constraints of your distribution, you should From c76def2dd23cf90fdc80224f08530205b6dcba73 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Fri, 14 Mar 2014 04:19:05 +0000 Subject: [PATCH 144/384] typo Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- hack/PACKAGERS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/PACKAGERS.md b/hack/PACKAGERS.md index 297d1500db..47e8413bf3 100644 --- a/hack/PACKAGERS.md +++ b/hack/PACKAGERS.md @@ -175,7 +175,7 @@ To disable aufs: export DOCKER_BUILDTAGS='exclude_graphdriver_aufs' ``` -NOTE: if you need to set more than one build tag, space seperate them. +NOTE: if you need to set more than one build tag, space separate them. ### Static Daemon From c1f2abd89958a8731211ceb2885eed238a3bea8d Mon Sep 17 00:00:00 2001 From: LK4D4 Date: Tue, 18 Mar 2014 21:38:56 +0400 Subject: [PATCH 145/384] Using names in docker ps --since-id/--before-id, resolves #3565 Also renames --since-id/--before-id to --since/--before and add errors on non-existent containers. Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- api/client.go | 4 ++-- contrib/completion/bash/docker | 9 +++------ contrib/completion/fish/docker.fish | 4 ++-- docs/sources/reference/commandline/cli.rst | 4 ++-- server/server.go | 23 +++++++++++++++++++--- 5 files changed, 29 insertions(+), 15 deletions(-) diff --git a/api/client.go b/api/client.go index 10dd9406dc..8f515639a7 100644 --- a/api/client.go +++ b/api/client.go @@ -1337,8 +1337,8 @@ func (cli *DockerCli) CmdPs(args ...string) error { all := cmd.Bool([]string{"a", "-all"}, false, "Show all containers. Only running containers are shown by default.") noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") nLatest := cmd.Bool([]string{"l", "-latest"}, false, "Show only the latest created container, include non-running ones.") - since := cmd.String([]string{"#sinceId", "-since-id"}, "", "Show only containers created since Id, include non-running ones.") - before := cmd.String([]string{"#beforeId", "-before-id"}, "", "Show only container created before Id, include non-running ones.") + since := cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show only containers created since Id or Name, include non-running ones.") + before := cmd.String([]string{"#beforeId", "#-before-id", "-before"}, "", "Show only container created before Id or Name, include non-running ones.") last := cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running ones.") if err := cmd.Parse(args); err != nil { diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index 1449330986..e6a191d32b 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -392,11 +392,8 @@ _docker_port() _docker_ps() { case "$prev" in - --since-id|--before-id) - COMPREPLY=( $( compgen -W "$( __docker_q ps -a -q )" -- "$cur" ) ) - # TODO replace this with __docker_containers_all - # see https://github.com/dotcloud/docker/issues/3565 - return + --since|--before) + __docker_containers_all ;; -n) return @@ -407,7 +404,7 @@ _docker_ps() case "$cur" in -*) - COMPREPLY=( $( compgen -W "-q --quiet -s --size -a --all --no-trunc -l --latest --since-id --before-id -n" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "-q --quiet -s --size -a --all --no-trunc -l --latest --since --before -n" -- "$cur" ) ) ;; *) ;; diff --git a/contrib/completion/fish/docker.fish b/contrib/completion/fish/docker.fish index b0c5f38a96..9c4339fe2b 100644 --- a/contrib/completion/fish/docker.fish +++ b/contrib/completion/fish/docker.fish @@ -154,13 +154,13 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from port' -a '(__fish_print # ps complete -c docker -f -n '__fish_docker_no_subcommand' -a ps -d 'List containers' complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -s a -l all -d 'Show all containers. Only running containers are shown by default.' -complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -l before-id -d 'Show only container created before Id, include non-running ones.' +complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -l before -d 'Show only container created before Id or Name, include non-running ones.' complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -s l -l latest -d 'Show only the latest created container, include non-running ones.' complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -s n -d 'Show n last created containers, include non-running ones.' complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -l no-trunc -d "Don't truncate output" complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -s q -l quiet -d 'Only display numeric IDs' complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -s s -l size -d 'Display sizes' -complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -l since-id -d 'Show only containers created since Id, include non-running ones.' +complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -l since -d 'Show only containers created since Id or Name, include non-running ones.' # pull complete -c docker -f -n '__fish_docker_no_subcommand' -a pull -d 'Pull an image or a repository from the docker registry server' diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index dc6529ab6a..ebcf021115 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -967,13 +967,13 @@ new output from the container's stdout and stderr. List containers -a, --all=false: Show all containers. Only running containers are shown by default. - --before-id="": Show only container created before Id, include non-running ones. + --before="": Show only container created before Id or Name, include non-running ones. -l, --latest=false: Show only the latest created container, include non-running ones. -n=-1: Show n last created containers, include non-running ones. --no-trunc=false: Don't truncate output -q, --quiet=false: Only display numeric IDs -s, --size=false: Display sizes, not to be used with -q - --since-id="": Show only containers created since Id, include non-running ones. + --since="": Show only containers created since Id or Name, include non-running ones. Running ``docker ps`` showing 2 linked containers. diff --git a/server/server.go b/server/server.go index 93fc7b0bb1..e6243971a4 100644 --- a/server/server.go +++ b/server/server.go @@ -981,12 +981,27 @@ func (srv *Server) Containers(job *engine.Job) engine.Status { return nil }, -1) + var beforeCont, sinceCont *runtime.Container + if before != "" { + beforeCont = srv.runtime.Get(before) + if beforeCont == nil { + return job.Error(fmt.Errorf("Could not find container with name or id %s", before)) + } + } + + if since != "" { + sinceCont = srv.runtime.Get(since) + if sinceCont == nil { + return job.Error(fmt.Errorf("Could not find container with name or id %s", since)) + } + } + for _, container := range srv.runtime.List() { if !container.State.IsRunning() && !all && n <= 0 && since == "" && before == "" { continue } if before != "" && !foundBefore { - if container.ID == before || utils.TruncateID(container.ID) == before { + if container.ID == beforeCont.ID { foundBefore = true } continue @@ -994,8 +1009,10 @@ func (srv *Server) Containers(job *engine.Job) engine.Status { if n > 0 && displayed == n { break } - if container.ID == since || utils.TruncateID(container.ID) == since { - break + if since != "" { + if container.ID == sinceCont.ID { + break + } } displayed++ out := &engine.Env{} From e27c635c06dcad61ce7185d95debef9ef7c9f7e0 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 18 Mar 2014 16:26:21 -0400 Subject: [PATCH 146/384] Add upstart nofile/noproc similar to systemd init Docker-DCO-1.1-Signed-off-by: Brian Goff (github: cpuguy83) --- contrib/init/upstart/docker.conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contrib/init/upstart/docker.conf b/contrib/init/upstart/docker.conf index 047f21c092..907a536c9c 100644 --- a/contrib/init/upstart/docker.conf +++ b/contrib/init/upstart/docker.conf @@ -2,6 +2,8 @@ description "Docker daemon" start on filesystem stop on runlevel [!2345] +limit nofile 524288 1048576 +limit nproc 524288 1048576 respawn From f52b2fdcbb29258c5b492fdb2479d473fcb42ca0 Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Mon, 3 Mar 2014 14:41:38 -0800 Subject: [PATCH 147/384] libcontainer/network: add netns strategy Docker-DCO-1.1-Signed-off-by: Johan Euphrosine (github: proppy) --- pkg/libcontainer/network/netns.go | 42 ++++++++++++++++++++++++++++ pkg/libcontainer/network/strategy.go | 2 ++ 2 files changed, 44 insertions(+) create mode 100644 pkg/libcontainer/network/netns.go diff --git a/pkg/libcontainer/network/netns.go b/pkg/libcontainer/network/netns.go new file mode 100644 index 0000000000..3eb8ee587a --- /dev/null +++ b/pkg/libcontainer/network/netns.go @@ -0,0 +1,42 @@ +package network + +import ( + "fmt" + "os" + "syscall" + + "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/system" +) + +// crosbymichael: could make a network strategy that instead of returning veth pair names it returns a pid to an existing network namespace +type NetNS struct { +} + +func (v *NetNS) Create(n *libcontainer.Network, nspid int, context libcontainer.Context) error { + nsname, exists := n.Context["nsname"] + + if !exists { + return fmt.Errorf("nspath does not exist in network context") + } + + context["nspath"] = fmt.Sprintf("/var/run/netns/%s", nsname) + return nil +} + +func (v *NetNS) Initialize(config *libcontainer.Network, context libcontainer.Context) error { + nspath, exists := context["nspath"] + if !exists { + return fmt.Errorf("nspath does not exist in network context") + } + + f, err := os.OpenFile(nspath, os.O_RDONLY, 0) + if err != nil { + return fmt.Errorf("failed get network namespace fd: %v", err) + } + + if err := system.Setns(f.Fd(), syscall.CLONE_NEWNET); err != nil { + return fmt.Errorf("failed to setns current network namespace: %v", err) + } + return nil +} diff --git a/pkg/libcontainer/network/strategy.go b/pkg/libcontainer/network/strategy.go index 693790d280..e41ecc3ea6 100644 --- a/pkg/libcontainer/network/strategy.go +++ b/pkg/libcontainer/network/strategy.go @@ -2,6 +2,7 @@ package network import ( "errors" + "github.com/dotcloud/docker/pkg/libcontainer" ) @@ -12,6 +13,7 @@ var ( var strategies = map[string]NetworkStrategy{ "veth": &Veth{}, "loopback": &Loopback{}, + "netns": &NetNS{}, } // NetworkStrategy represents a specific network configuration for From b10b950b110c93db34a399753706dc79c71b94d3 Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Mon, 3 Mar 2014 21:46:49 -0800 Subject: [PATCH 148/384] libcontainer/nsinit/init: move mount namespace after network Docker-DCO-1.1-Signed-off-by: Johan Euphrosine (github: proppy) --- pkg/libcontainer/nsinit/init.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 117ae875ed..c39928d459 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -4,6 +4,9 @@ package nsinit import ( "fmt" + "os" + "syscall" + "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/apparmor" "github.com/dotcloud/docker/pkg/libcontainer/capabilities" @@ -11,8 +14,6 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer/utils" "github.com/dotcloud/docker/pkg/system" "github.com/dotcloud/docker/pkg/user" - "os" - "syscall" ) // Init is the init process that first runs inside a new namespace to setup mounts, users, networking, @@ -56,13 +57,13 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol if err := system.ParentDeathSignal(uintptr(syscall.SIGTERM)); err != nil { return fmt.Errorf("parent death signal %s", err) } + if err := setupNetwork(container, context); err != nil { + 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 { return fmt.Errorf("setup mount namespace %s", err) } - if err := setupNetwork(container, context); err != nil { - return fmt.Errorf("setup networking %s", err) - } if err := system.Sethostname(container.Hostname); err != nil { return fmt.Errorf("sethostname %s", err) } From f58757a699cf47c3b8770e13f371e1bbf493b5b1 Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Mon, 3 Mar 2014 21:47:03 -0800 Subject: [PATCH 149/384] libcontainer: goimports Docker-DCO-1.1-Signed-off-by: Johan Euphrosine (github: proppy) --- pkg/libcontainer/nsinit/exec.go | 7 ++++--- pkg/libcontainer/nsinit/nsinit/main.go | 8 ++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 61286cc13c..6e902d1916 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -3,12 +3,13 @@ package nsinit import ( - "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/network" - "github.com/dotcloud/docker/pkg/system" "os" "os/exec" "syscall" + + "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/network" + "github.com/dotcloud/docker/pkg/system" ) // Exec performes setup outside of a namespace so that a container can be diff --git a/pkg/libcontainer/nsinit/nsinit/main.go b/pkg/libcontainer/nsinit/nsinit/main.go index df32d0b49e..3725fb5534 100644 --- a/pkg/libcontainer/nsinit/nsinit/main.go +++ b/pkg/libcontainer/nsinit/nsinit/main.go @@ -3,14 +3,18 @@ package main import ( "encoding/json" "flag" - "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/nsinit" "io" "io/ioutil" "log" "os" "path/filepath" "strconv" + + "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/nsinit" + + "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/nsinit" ) var ( From 041ae08a2c8f6f345ac0d7f5fea4b79655e28dc5 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 18 Mar 2014 16:24:45 -0700 Subject: [PATCH 150/384] Add image size to history docs Fixes #3147 Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- docs/sources/reference/commandline/cli.rst | 37 +++++----------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index dc6529ab6a..089c1e14b2 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -569,35 +569,14 @@ To see how the ``docker:latest`` image was built: .. code-block:: bash $ docker history docker - ID CREATED CREATED BY - docker:latest 19 hours ago /bin/sh -c #(nop) ADD . in /go/src/github.com/dotcloud/docker - cf5f2467662d 2 weeks ago /bin/sh -c #(nop) ENTRYPOINT ["hack/dind"] - 3538fbe372bf 2 weeks ago /bin/sh -c #(nop) WORKDIR /go/src/github.com/dotcloud/docker - 7450f65072e5 2 weeks ago /bin/sh -c #(nop) VOLUME /var/lib/docker - b79d62b97328 2 weeks ago /bin/sh -c apt-get install -y -q lxc - 36714852a550 2 weeks ago /bin/sh -c apt-get install -y -q iptables - 8c4c706df1d6 2 weeks ago /bin/sh -c /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEYn' > /.s3cfg - b89989433c48 2 weeks ago /bin/sh -c pip install python-magic - a23e640d85b5 2 weeks ago /bin/sh -c pip install s3cmd - 41f54fec7e79 2 weeks ago /bin/sh -c apt-get install -y -q python-pip - d9bc04add907 2 weeks ago /bin/sh -c apt-get install -y -q reprepro dpkg-sig - e74f4760fa70 2 weeks ago /bin/sh -c gem install --no-rdoc --no-ri fpm - 1e43224726eb 2 weeks ago /bin/sh -c apt-get install -y -q ruby1.9.3 rubygems libffi-dev - 460953ae9d7f 2 weeks ago /bin/sh -c #(nop) ENV GOPATH=/go:/go/src/github.com/dotcloud/docker/vendor - 8b63eb1d666b 2 weeks ago /bin/sh -c #(nop) ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/goroot/bin - 3087f3bcedf2 2 weeks ago /bin/sh -c #(nop) ENV GOROOT=/goroot - 635840d198e5 2 weeks ago /bin/sh -c cd /goroot/src && ./make.bash - 439f4a0592ba 2 weeks ago /bin/sh -c curl -s https://go.googlecode.com/files/go1.1.2.src.tar.gz | tar -v -C / -xz && mv /go /goroot - 13967ed36e93 2 weeks ago /bin/sh -c #(nop) ENV CGO_ENABLED=0 - bf7424458437 2 weeks ago /bin/sh -c apt-get install -y -q build-essential - a89ec997c3bf 2 weeks ago /bin/sh -c apt-get install -y -q mercurial - b9f165c6e749 2 weeks ago /bin/sh -c apt-get install -y -q git - 17a64374afa7 2 weeks ago /bin/sh -c apt-get install -y -q curl - d5e85dc5b1d8 2 weeks ago /bin/sh -c apt-get update - 13e642467c11 2 weeks ago /bin/sh -c echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > /etc/apt/sources.list - ae6dde92a94e 2 weeks ago /bin/sh -c #(nop) MAINTAINER Solomon Hykes - ubuntu:12.04 6 months ago - + IMAGE CREATED CREATED BY SIZE + 3e23a5875458790b7a806f95f7ec0d0b2a5c1659bfc899c89f939f6d5b8f7094 8 days ago /bin/sh -c #(nop) ENV LC_ALL=C.UTF-8 0 B + 8578938dd17054dce7993d21de79e96a037400e8d28e15e7290fea4f65128a36 8 days ago /bin/sh -c dpkg-reconfigure locales && locale-gen C.UTF-8 && /usr/sbin/update-locale LANG=C.UTF-8 1.245 MB + be51b77efb42f67a5e96437b3e102f81e0a1399038f77bf28cea0ed23a65cf60 8 days ago /bin/sh -c apt-get update && apt-get install -y git libxml2-dev python build-essential make gcc python-dev locales python-pip 338.3 MB + 4b137612be55ca69776c7f30c2d2dd0aa2e7d72059820abf3e25b629f887a084 6 weeks ago /bin/sh -c #(nop) ADD jessie.tar.xz in / 121 MB + 750d58736b4b6cc0f9a9abe8f258cef269e3e9dceced1146503522be9f985ada 6 weeks ago /bin/sh -c #(nop) MAINTAINER Tianon Gravi - mkimage-debootstrap.sh -t jessie.tar.xz jessie http://http.debian.net/debian 0 B + 511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158 9 months ago 0 B + .. _cli_images: ``images`` From 5dbfe310fe624c66714f8c5017692f528af4c87f Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Tue, 18 Mar 2014 16:25:26 -0700 Subject: [PATCH 151/384] libcontainer: remove duplicate imports Docker-DCO-1.1-Signed-off-by: Johan Euphrosine (github: proppy) --- pkg/libcontainer/nsinit/nsinit/main.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/libcontainer/nsinit/nsinit/main.go b/pkg/libcontainer/nsinit/nsinit/main.go index 3725fb5534..37aa784981 100644 --- a/pkg/libcontainer/nsinit/nsinit/main.go +++ b/pkg/libcontainer/nsinit/nsinit/main.go @@ -12,9 +12,6 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/nsinit" - - "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/nsinit" ) var ( From 3b1d590269466217ddf203edcef295f6cec9fcee Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 18 Mar 2014 23:12:39 +0000 Subject: [PATCH 152/384] cleanup container.stop Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- runtime/container.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/runtime/container.go b/runtime/container.go index 35b01deac7..6194a19c8c 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -886,11 +886,8 @@ func (container *Container) Kill() error { // 2. Wait for the process to die, in last resort, try to kill the process directly if err := container.WaitTimeout(10 * time.Second); err != nil { - if container.command == nil { - return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", utils.TruncateID(container.ID)) - } - log.Printf("Container %s failed to exit within 10 seconds of lxc-kill %s - trying direct SIGKILL", "SIGKILL", utils.TruncateID(container.ID)) - if err := container.runtime.Kill(container, 9); err != nil { + log.Printf("Container %s failed to exit within 10 seconds of kill - trying direct SIGKILL", utils.TruncateID(container.ID)) + if err := syscall.Kill(container.State.Pid, 9); err != nil { return err } } From 38a3fc3e0e9b56400b2c7d2fce3bfc7b15395d14 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 18 Mar 2014 17:07:45 -0700 Subject: [PATCH 153/384] Add sudo clause if your using osx or tcp Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- docs/sources/examples/example_header.inc | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sources/examples/example_header.inc b/docs/sources/examples/example_header.inc index 0621b39794..5841141e59 100644 --- a/docs/sources/examples/example_header.inc +++ b/docs/sources/examples/example_header.inc @@ -4,4 +4,5 @@ * This example assumes you have Docker running in daemon mode. For more information please see :ref:`running_examples`. * **If you don't like sudo** then see :ref:`dockergroup` + * **If you're using OS X or docker via TCP** then you shouldn't use `sudo` From 7822b053cbd2288b6c8d9c51a8f495368bc77f35 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 18 Mar 2014 17:24:06 -0700 Subject: [PATCH 154/384] Be explicit about binding to all interfaces in redis example Fixes #4021 Moved to debian because the redis installed in ubuntu is really old and does not support args via the cli. Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- docs/sources/examples/running_redis_service.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/examples/running_redis_service.rst b/docs/sources/examples/running_redis_service.rst index 50f1471f17..5a5a1b003f 100644 --- a/docs/sources/examples/running_redis_service.rst +++ b/docs/sources/examples/running_redis_service.rst @@ -18,11 +18,11 @@ Firstly, we create a ``Dockerfile`` for our new Redis image. .. code-block:: bash - FROM ubuntu:12.10 - RUN apt-get update - RUN apt-get -y install redis-server + FROM debian:jessie + RUN apt-get update && apt-get install -y redis-server EXPOSE 6379 ENTRYPOINT ["/usr/bin/redis-server"] + CMD ["--bind", "0.0.0.0"] Next we build an image from our ``Dockerfile``. Replace ```` with your own user name. From 92194f613e37aeb8a387920c3bee42480da0d2ac Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Wed, 19 Mar 2014 14:12:47 +1000 Subject: [PATCH 155/384] use this horrible complex bit of shell to make sure that curl doesn't hand the poor user a broken docker client Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/installation/mac.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/sources/installation/mac.rst b/docs/sources/installation/mac.rst index 5139324d0b..f4c771cf9f 100644 --- a/docs/sources/installation/mac.rst +++ b/docs/sources/installation/mac.rst @@ -65,11 +65,12 @@ Run the following commands to get it downloaded and set up: .. code-block:: bash - # Get the file - curl -o docker https://get.docker.io/builds/Darwin/x86_64/docker-latest - - # Mark it executable - chmod +x docker + # Get the docker client file + DIR=$(mktemp -d) && \ + curl -f -o $DIR/ld.tgz https://get.docker.io/builds/Darwin/x86_64/docker-latest.tgz && \ + gunzip $DIR/ld.tgz && \ + tar xvf $DIR/ld.tar -C $DIR/ && \ + cp $DIR/usr/local/bin/docker ./docker # Set the environment variable for the docker daemon export DOCKER_HOST=tcp://127.0.0.1:4243 From 53dc2d67fb65037d9891e2fa0f6559d5e4e2ddcc Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Wed, 19 Mar 2014 14:24:49 +1000 Subject: [PATCH 156/384] mention the tgz - other people might like to know Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/installation/binaries.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/sources/installation/binaries.rst b/docs/sources/installation/binaries.rst index bfdfbe211f..fe03d21859 100644 --- a/docs/sources/installation/binaries.rst +++ b/docs/sources/installation/binaries.rst @@ -49,6 +49,9 @@ Get the docker binary: wget https://get.docker.io/builds/Linux/x86_64/docker-latest -O docker chmod +x docker +.. note:: + If you have trouble downloading the binary, you can also get the smaller + compressed release file: https://get.docker.io/builds/Linux/x86_64/docker-latest.tgz Run the docker daemon --------------------- From 4fd82db4beba03a126dfc557c86d5d52e9066dae Mon Sep 17 00:00:00 2001 From: Viktor Vojnovski Date: Wed, 19 Mar 2014 00:00:48 +0100 Subject: [PATCH 157/384] refactor($hack,$docs): be consistent in apt-key keyserver URI usage, as done in #4740 In #4740, the apt-key call in docs is changed to use the keyserver port 80 instead of port 11371, as the previous call would fail with a restrictive firewall or proxy. This commit extends the change to all apt-key calls in the repository. Docker-DCO-1.1-Signed-off-by: Viktor Vojnovski (github: vojnovski) --- docs/sources/examples/postgresql_service.Dockerfile | 2 +- docs/sources/installation/ubuntulinux.rst | 2 +- hack/infrastructure/docker-ci/Dockerfile | 2 +- hack/install.sh | 4 ++-- hack/release.sh | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/sources/examples/postgresql_service.Dockerfile b/docs/sources/examples/postgresql_service.Dockerfile index af1423f258..219a537882 100644 --- a/docs/sources/examples/postgresql_service.Dockerfile +++ b/docs/sources/examples/postgresql_service.Dockerfile @@ -7,7 +7,7 @@ MAINTAINER SvenDowideit@docker.com # Add the PostgreSQL PGP key to verify their Debian packages. # It should be the same key as https://www.postgresql.org/media/keys/ACCC4CF8.asc -RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8 +RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8 # Add PostgreSQL's repository. It contains the most recent stable release # of PostgreSQL, ``9.3``. diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index 6998be8571..a163c62da7 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -144,7 +144,7 @@ First add the Docker repository key to your local keychain. .. code-block:: bash - sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9 + sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9 Add the Docker repository to your apt sources list, update and install the ``lxc-docker`` package. diff --git a/hack/infrastructure/docker-ci/Dockerfile b/hack/infrastructure/docker-ci/Dockerfile index 789c794f54..5c6eec9663 100644 --- a/hack/infrastructure/docker-ci/Dockerfile +++ b/hack/infrastructure/docker-ci/Dockerfile @@ -16,7 +16,7 @@ RUN apt-get install -y --no-install-recommends python2.7 python-dev \ 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 keyserver.ubuntu.com --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9 +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 diff --git a/hack/install.sh b/hack/install.sh index 65e34f9659..1fa8a47480 100755 --- a/hack/install.sh +++ b/hack/install.sh @@ -111,9 +111,9 @@ case "$lsb_dist" in ( set -x if [ "https://get.docker.io/" = "$url" ]; then - $sh_c "apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9" + $sh_c "apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9" elif [ "https://test.docker.io/" = "$url" ]; then - $sh_c "apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 740B314AE3941731B942C66ADF4FD13717AAD7D6" + $sh_c "apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 740B314AE3941731B942C66ADF4FD13717AAD7D6" else $sh_c "$curl ${url}gpg | apt-key add -" fi diff --git a/hack/release.sh b/hack/release.sh index c380d2239a..1f249a5c5e 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -246,7 +246,7 @@ EOF # Add the repository to your APT sources echo deb $(s3_url)/ubuntu docker main > /etc/apt/sources.list.d/docker.list # Then import the repository key -apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9 +apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9 # Install docker apt-get update ; apt-get install -y lxc-docker From fbfac21ed4de550ce72d993810dc07a2c4877a88 Mon Sep 17 00:00:00 2001 From: Daniel Norberg Date: Fri, 7 Feb 2014 11:48:14 -0500 Subject: [PATCH 158/384] configurable dns search domains Add a --dns-search parameter and a DnsSearch configuration field for specifying dns search domains. Docker-DCO-1.1-Signed-off-by: Daniel Norberg (github: danielnorberg) --- daemonconfig/config.go | 4 ++ docker/docker.go | 3 + docs/sources/reference/commandline/cli.rst | 9 ++- opts/opts.go | 13 ++++ opts/opts_test.go | 54 ++++++++++++++++ runconfig/compare.go | 6 ++ runconfig/config.go | 4 ++ runconfig/config_test.go | 16 +++++ runconfig/merge.go | 6 ++ runconfig/parse.go | 3 + runtime/runtime.go | 17 ++++- utils/utils.go | 74 ++++++++++++++-------- utils/utils_test.go | 51 +++++++++++++++ 13 files changed, 230 insertions(+), 30 deletions(-) diff --git a/daemonconfig/config.go b/daemonconfig/config.go index b26d3eec3a..6cb3659e18 100644 --- a/daemonconfig/config.go +++ b/daemonconfig/config.go @@ -18,6 +18,7 @@ type Config struct { Root string AutoRestart bool Dns []string + DnsSearch []string EnableIptables bool EnableIpForward bool DefaultIp net.IP @@ -49,6 +50,9 @@ func ConfigFromJob(job *engine.Job) *Config { if dns := job.GetenvList("Dns"); dns != nil { config.Dns = dns } + if dnsSearch := job.GetenvList("DnsSearch"); dnsSearch != nil { + config.DnsSearch = dnsSearch + } if mtu := job.GetenvInt("Mtu"); mtu != 0 { config.Mtu = mtu } else { diff --git a/docker/docker.go b/docker/docker.go index 749857a640..e62b9494d5 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -35,6 +35,7 @@ func main() { flSocketGroup = flag.String([]string{"G", "-group"}, "docker", "Group to assign the unix socket specified by -H when running in daemon mode; use '' (the empty string) to disable setting of a group") flEnableCors = flag.Bool([]string{"#api-enable-cors", "-api-enable-cors"}, false, "Enable CORS headers in the remote API") flDns = opts.NewListOpts(opts.ValidateIp4Address) + flDnsSearch = opts.NewListOpts(opts.ValidateDomain) flEnableIptables = flag.Bool([]string{"#iptables", "-iptables"}, true, "Enable Docker's addition of iptables rules") flEnableIpForward = flag.Bool([]string{"#ip-forward", "-ip-forward"}, true, "Enable net.ipv4.ip_forward") flDefaultIp = flag.String([]string{"#ip", "-ip"}, "0.0.0.0", "Default IP address to use when binding container ports") @@ -45,6 +46,7 @@ func main() { flMtu = flag.Int([]string{"#mtu", "-mtu"}, 0, "Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if no default route is available") ) flag.Var(&flDns, []string{"#dns", "-dns"}, "Force docker to use specific DNS servers") + flag.Var(&flDnsSearch, []string{"-dns-search"}, "Force Docker to use specific DNS search domains") flag.Var(&flHosts, []string{"H", "-host"}, "tcp://host:port, unix://path/to/socket, fd://* or fd://socketfd to use in daemon mode. Multiple sockets can be specified") flag.Parse() @@ -115,6 +117,7 @@ func main() { job.Setenv("Root", realRoot) job.SetenvBool("AutoRestart", *flAutoRestart) job.SetenvList("Dns", flDns.GetAll()) + job.SetenvList("DnsSearch", flDnsSearch.GetAll()) job.SetenvBool("EnableIptables", *flEnableIptables) job.SetenvBool("EnableIpForward", *flEnableIpForward) job.Setenv("BridgeIface", *bridgeName) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 0f33b05ec4..7b16a7d2ec 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -77,6 +77,7 @@ Commands --bip="": Use this CIDR notation address for the network bridge's IP, not compatible with -b -d, --daemon=false: Enable daemon mode --dns=[]: Force docker to use specific DNS servers + --dns-search=[]: Force Docker to use specific DNS search domains -g, --graph="/var/lib/docker": Path to use as the root of the docker runtime --icc=true: Enable inter-container communication --ip="0.0.0.0": Default IP address to use when binding container ports @@ -96,6 +97,8 @@ To force Docker to use devicemapper as the storage driver, use ``docker -d -s de To set the DNS server for all Docker containers, use ``docker -d --dns 8.8.8.8``. +To set the a DNS search domain for all Docker containers, use ``docker -d --dns-search example.com``. + To run the daemon with debug output, use ``docker -d -D``. To use lxc as the execution driver, use ``docker -d -e lxc``. @@ -396,6 +399,7 @@ not overridden in the JSON hash will be merged in. "VolumesFrom" : "", "Cmd" : ["cat", "-e", "/etc/resolv.conf"], "Dns" : ["8.8.8.8", "8.8.4.4"], + "DnsSearch" : ["example.com"], "MemorySwap" : 0, "AttachStdin" : false, "AttachStderr" : false, @@ -1131,6 +1135,7 @@ image is removed. -t, --tty=false: Allocate a pseudo-tty -u, --user="": Username or UID --dns=[]: Set custom dns servers for the container + --dns-search=[]: Set custom DNS search domains for the container -v, --volume=[]: Create a bind mount to a directory or file with: [host-path]:[container-path]:[rw|ro]. If a directory "container-path" is missing, then docker creates a new volume. --volumes-from="": Mount all volumes from the given container(s) --entrypoint="": Overwrite the default entrypoint set by the image @@ -1288,7 +1293,7 @@ A complete example $ sudo docker run -d --name static static-web-files sh $ sudo docker run -d --expose=8098 --name riak riakserver $ sudo docker run -d -m 100m -e DEVELOPMENT=1 -e BRANCH=example-code -v $(pwd):/app/bin:ro --name app appserver - $ sudo docker run -d -p 1443:443 --dns=dns.dev.org -v /var/log/httpd --volumes-from static --link riak --link app -h www.sven.dev.org --name web webserver + $ sudo docker run -d -p 1443:443 --dns=dns.dev.org --dns-search=dev.org -v /var/log/httpd --volumes-from static --link riak --link app -h www.sven.dev.org --name web webserver $ sudo docker run -t -i --rm --volumes-from web -w /var/log/httpd busybox tail -f access.log This example shows 5 containers that might be set up to test a web application change: @@ -1296,7 +1301,7 @@ This example shows 5 containers that might be set up to test a web application c 1. Start a pre-prepared volume image ``static-web-files`` (in the background) that has CSS, image and static HTML in it, (with a ``VOLUME`` instruction in the ``Dockerfile`` to allow the web server to use those files); 2. Start a pre-prepared ``riakserver`` image, give the container name ``riak`` and expose port ``8098`` to any containers that link to it; 3. Start the ``appserver`` image, restricting its memory usage to 100MB, setting two environment variables ``DEVELOPMENT`` and ``BRANCH`` and bind-mounting the current directory (``$(pwd)``) in the container in read-only mode as ``/app/bin``; -4. Start the ``webserver``, mapping port ``443`` in the container to port ``1443`` on the Docker server, setting the DNS server to ``dns.dev.org``, creating a volume to put the log files into (so we can access it from another container), then importing the files from the volume exposed by the ``static`` container, and linking to all exposed ports from ``riak`` and ``app``. Lastly, we set the hostname to ``web.sven.dev.org`` so its consistent with the pre-generated SSL certificate; +4. Start the ``webserver``, mapping port ``443`` in the container to port ``1443`` on the Docker server, setting the DNS server to ``dns.dev.org`` and DNS search domain to ``dev.org``, creating a volume to put the log files into (so we can access it from another container), then importing the files from the volume exposed by the ``static`` container, and linking to all exposed ports from ``riak`` and ``app``. Lastly, we set the hostname to ``web.sven.dev.org`` so its consistent with the pre-generated SSL certificate; 5. Finally, we create a container that runs ``tail -f access.log`` using the logs volume from the ``web`` container, setting the workdir to ``/var/log/httpd``. The ``--rm`` option means that when the container exits, the container's layer is removed. diff --git a/opts/opts.go b/opts/opts.go index 4f5897c796..b2f21db30b 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -136,3 +136,16 @@ func ValidateIp4Address(val string) (string, error) { } return "", fmt.Errorf("%s is not an ip4 address", val) } + +func ValidateDomain(val string) (string, error) { + alpha := regexp.MustCompile(`[a-zA-Z]`) + if alpha.FindString(val) == "" { + return "", fmt.Errorf("%s is not a valid domain", val) + } + re := regexp.MustCompile(`^(:?(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))(:?\.(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])))*)\.?\s*$`) + var ns = re.FindSubmatch([]byte(val)) + if len(ns) > 0 { + return string(ns[1]), nil + } + return "", fmt.Errorf("%s is not a valid domain", val) +} diff --git a/opts/opts_test.go b/opts/opts_test.go index a5c1fac9ca..299cbfe503 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -22,3 +22,57 @@ func TestValidateIP4(t *testing.T) { } } + +func TestValidateDomain(t *testing.T) { + valid := []string{ + `a`, + `a.`, + `1.foo`, + `17.foo`, + `foo.bar`, + `foo.bar.baz`, + `foo.bar.`, + `foo.bar.baz`, + `foo1.bar2`, + `foo1.bar2.baz`, + `1foo.2bar.`, + `1foo.2bar.baz`, + `foo-1.bar-2`, + `foo-1.bar-2.baz`, + `foo-1.bar-2.`, + `foo-1.bar-2.baz`, + `1-foo.2-bar`, + `1-foo.2-bar.baz`, + `1-foo.2-bar.`, + `1-foo.2-bar.baz`, + } + + invalid := []string{ + ``, + `.`, + `17`, + `17.`, + `.17`, + `17-.`, + `17-.foo`, + `.foo`, + `foo-.bar`, + `-foo.bar`, + `foo.bar-`, + `foo.bar-.baz`, + `foo.-bar`, + `foo.-bar.baz`, + } + + for _, domain := range valid { + if ret, err := ValidateDomain(domain); err != nil || ret == "" { + t.Fatalf("ValidateDomain(`"+domain+"`) got %s %s", ret, err) + } + } + + for _, domain := range invalid { + if ret, err := ValidateDomain(domain); err == nil || ret != "" { + t.Fatalf("ValidateDomain(`"+domain+"`) got %s %s", ret, err) + } + } +} diff --git a/runconfig/compare.go b/runconfig/compare.go index c09f897716..6ed7405246 100644 --- a/runconfig/compare.go +++ b/runconfig/compare.go @@ -20,6 +20,7 @@ func Compare(a, b *Config) bool { } if len(a.Cmd) != len(b.Cmd) || len(a.Dns) != len(b.Dns) || + len(a.DnsSearch) != len(b.DnsSearch) || len(a.Env) != len(b.Env) || len(a.PortSpecs) != len(b.PortSpecs) || len(a.ExposedPorts) != len(b.ExposedPorts) || @@ -38,6 +39,11 @@ func Compare(a, b *Config) bool { return false } } + for i := 0; i < len(a.DnsSearch); i++ { + if a.DnsSearch[i] != b.DnsSearch[i] { + return false + } + } for i := 0; i < len(a.Env); i++ { if a.Env[i] != b.Env[i] { return false diff --git a/runconfig/config.go b/runconfig/config.go index 9faa823a57..e961d659d7 100644 --- a/runconfig/config.go +++ b/runconfig/config.go @@ -26,6 +26,7 @@ type Config struct { Env []string Cmd []string Dns []string + DnsSearch []string Image string // Name of the image as it was passed by the operator (eg. could be symbolic) Volumes map[string]struct{} VolumesFrom string @@ -68,6 +69,9 @@ func ContainerConfigFromJob(job *engine.Job) *Config { if Dns := job.GetenvList("Dns"); Dns != nil { config.Dns = Dns } + if DnsSearch := job.GetenvList("DnsSearch"); DnsSearch != nil { + config.DnsSearch = DnsSearch + } if Entrypoint := job.GetenvList("Entrypoint"); Entrypoint != nil { config.Entrypoint = Entrypoint } diff --git a/runconfig/config_test.go b/runconfig/config_test.go index 46e4691b93..84846e5b1d 100644 --- a/runconfig/config_test.go +++ b/runconfig/config_test.go @@ -164,6 +164,7 @@ func TestCompare(t *testing.T) { volumes1["/test1"] = struct{}{} config1 := Config{ Dns: []string{"1.1.1.1", "2.2.2.2"}, + DnsSearch: []string{"foo", "bar"}, PortSpecs: []string{"1111:1111", "2222:2222"}, Env: []string{"VAR1=1", "VAR2=2"}, VolumesFrom: "11111111", @@ -171,6 +172,7 @@ func TestCompare(t *testing.T) { } config2 := Config{ Dns: []string{"0.0.0.0", "2.2.2.2"}, + DnsSearch: []string{"foo", "bar"}, PortSpecs: []string{"1111:1111", "2222:2222"}, Env: []string{"VAR1=1", "VAR2=2"}, VolumesFrom: "11111111", @@ -178,6 +180,7 @@ func TestCompare(t *testing.T) { } config3 := Config{ Dns: []string{"1.1.1.1", "2.2.2.2"}, + DnsSearch: []string{"foo", "bar"}, PortSpecs: []string{"0000:0000", "2222:2222"}, Env: []string{"VAR1=1", "VAR2=2"}, VolumesFrom: "11111111", @@ -185,6 +188,7 @@ func TestCompare(t *testing.T) { } config4 := Config{ Dns: []string{"1.1.1.1", "2.2.2.2"}, + DnsSearch: []string{"foo", "bar"}, PortSpecs: []string{"0000:0000", "2222:2222"}, Env: []string{"VAR1=1", "VAR2=2"}, VolumesFrom: "22222222", @@ -194,11 +198,20 @@ func TestCompare(t *testing.T) { volumes2["/test2"] = struct{}{} config5 := Config{ Dns: []string{"1.1.1.1", "2.2.2.2"}, + DnsSearch: []string{"foo", "bar"}, PortSpecs: []string{"0000:0000", "2222:2222"}, Env: []string{"VAR1=1", "VAR2=2"}, VolumesFrom: "11111111", Volumes: volumes2, } + config6 := Config{ + Dns: []string{"1.1.1.1", "2.2.2.2"}, + DnsSearch: []string{"foos", "bars"}, + PortSpecs: []string{"1111:1111", "2222:2222"}, + Env: []string{"VAR1=1", "VAR2=2"}, + VolumesFrom: "11111111", + Volumes: volumes1, + } if Compare(&config1, &config2) { t.Fatalf("Compare should return false, Dns are different") } @@ -211,6 +224,9 @@ func TestCompare(t *testing.T) { if Compare(&config1, &config5) { t.Fatalf("Compare should return false, Volumes are different") } + if Compare(&config1, &config6) { + t.Fatalf("Compare should return false, DnsSearch are different") + } if !Compare(&config1, &config1) { t.Fatalf("Compare should return true") } diff --git a/runconfig/merge.go b/runconfig/merge.go index 3b91aa2af0..34faaf75e7 100644 --- a/runconfig/merge.go +++ b/runconfig/merge.go @@ -100,6 +100,12 @@ func Merge(userConf, imageConf *Config) error { //duplicates aren't an issue here userConf.Dns = append(userConf.Dns, imageConf.Dns...) } + if userConf.DnsSearch == nil || len(userConf.DnsSearch) == 0 { + userConf.DnsSearch = imageConf.DnsSearch + } else { + //duplicates aren't an issue here + userConf.DnsSearch = append(userConf.DnsSearch, imageConf.DnsSearch...) + } if userConf.Entrypoint == nil || len(userConf.Entrypoint) == 0 { userConf.Entrypoint = imageConf.Entrypoint } diff --git a/runconfig/parse.go b/runconfig/parse.go index 2138f4e68c..cc33188ad5 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -42,6 +42,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf flPublish opts.ListOpts flExpose opts.ListOpts flDns opts.ListOpts + flDnsSearch = opts.NewListOpts(opts.ValidateDomain) flVolumesFrom opts.ListOpts flLxcOpts opts.ListOpts @@ -73,6 +74,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf cmd.Var(&flPublish, []string{"p", "-publish"}, fmt.Sprintf("Publish a container's port to the host (format: %s) (use 'docker port' to see the actual mapping)", nat.PortSpecTemplateFormat)) cmd.Var(&flExpose, []string{"#expose", "-expose"}, "Expose a port from the container without publishing it to your host") 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\"") @@ -196,6 +198,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf Env: flEnv.GetAll(), Cmd: runCmd, Dns: flDns.GetAll(), + DnsSearch: flDnsSearch.GetAll(), Image: image, Volumes: flVolumes.GetMap(), VolumesFrom: strings.Join(flVolumesFrom.GetAll(), ","), diff --git a/runtime/runtime.go b/runtime/runtime.go index 4408e13902..38a1beccd2 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -493,13 +493,19 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe } // If custom dns exists, then create a resolv.conf for the container - if len(config.Dns) > 0 || len(runtime.config.Dns) > 0 { - var dns []string + if len(config.Dns) > 0 || len(runtime.config.Dns) > 0 || len(config.DnsSearch) > 0 || len(runtime.config.DnsSearch) > 0 { + dns := utils.GetNameservers(resolvConf) + dnsSearch := utils.GetSearchDomains(resolvConf) if len(config.Dns) > 0 { dns = config.Dns - } else { + } else if len(runtime.config.Dns) > 0 { dns = runtime.config.Dns } + if len(config.DnsSearch) > 0 { + dnsSearch = config.DnsSearch + } else if len(runtime.config.DnsSearch) > 0 { + dnsSearch = runtime.config.DnsSearch + } container.ResolvConfPath = path.Join(container.root, "resolv.conf") f, err := os.Create(container.ResolvConfPath) if err != nil { @@ -511,6 +517,11 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe return nil, nil, err } } + if len(dnsSearch) > 0 { + if _, err := f.Write([]byte("search " + strings.Join(dnsSearch, " ") + "\n")); err != nil { + return nil, nil, err + } + } } else { container.ResolvConfPath = "/etc/resolv.conf" } diff --git a/utils/utils.go b/utils/utils.go index 57a8200a7c..2702555973 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -731,54 +731,78 @@ func GetResolvConf() ([]byte, error) { // CheckLocalDns looks into the /etc/resolv.conf, // it returns true if there is a local nameserver or if there is no nameserver. func CheckLocalDns(resolvConf []byte) bool { - var parsedResolvConf = StripComments(resolvConf, []byte("#")) - if !bytes.Contains(parsedResolvConf, []byte("nameserver")) { - return true - } - for _, ip := range [][]byte{ - []byte("127.0.0.1"), - []byte("127.0.1.1"), - } { - if bytes.Contains(parsedResolvConf, ip) { - return true + for _, line := range GetLines(resolvConf, []byte("#")) { + if !bytes.Contains(line, []byte("nameserver")) { + continue } + for _, ip := range [][]byte{ + []byte("127.0.0.1"), + []byte("127.0.1.1"), + } { + if bytes.Contains(line, ip) { + return true + } + } + return false } - return false + return true } -// StripComments parses input into lines and strips away comments. -func StripComments(input []byte, commentMarker []byte) []byte { +// GetLines parses input into lines and strips away comments. +func GetLines(input []byte, commentMarker []byte) [][]byte { lines := bytes.Split(input, []byte("\n")) - var output []byte + var output [][]byte for _, currentLine := range lines { var commentIndex = bytes.Index(currentLine, commentMarker) if commentIndex == -1 { - output = append(output, currentLine...) + output = append(output, currentLine) } else { - output = append(output, currentLine[:commentIndex]...) + output = append(output, currentLine[:commentIndex]) } - output = append(output, []byte("\n")...) } return output } +// GetNameservers returns nameservers (if any) listed in /etc/resolv.conf +func GetNameservers(resolvConf []byte) []string { + nameservers := []string{} + re := regexp.MustCompile(`^\s*nameserver\s*(([0-9]+\.){3}([0-9]+))\s*$`) + for _, line := range GetLines(resolvConf, []byte("#")) { + var ns = re.FindSubmatch(line) + if len(ns) > 0 { + nameservers = append(nameservers, string(ns[1])) + } + } + return nameservers +} + // GetNameserversAsCIDR returns nameservers (if any) listed in // /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32") // This function's output is intended for net.ParseCIDR func GetNameserversAsCIDR(resolvConf []byte) []string { - var parsedResolvConf = StripComments(resolvConf, []byte("#")) nameservers := []string{} - re := regexp.MustCompile(`^\s*nameserver\s*(([0-9]+\.){3}([0-9]+))\s*$`) - for _, line := range bytes.Split(parsedResolvConf, []byte("\n")) { - var ns = re.FindSubmatch(line) - if len(ns) > 0 { - nameservers = append(nameservers, string(ns[1])+"/32") - } + for _, nameserver := range GetNameservers(resolvConf) { + nameservers = append(nameservers, nameserver+"/32") } - return nameservers } +// GetSearchDomains returns search domains (if any) listed in /etc/resolv.conf +// If more than one search line is encountered, only the contents of the last +// one is returned. +func GetSearchDomains(resolvConf []byte) []string { + re := regexp.MustCompile(`^\s*search\s*(([^\s]+\s*)*)$`) + domains := []string{} + for _, line := range GetLines(resolvConf, []byte("#")) { + match := re.FindSubmatch(line) + if match == nil { + continue + } + domains = strings.Fields(string(match[1])) + } + return domains +} + // FIXME: Change this not to receive default value as parameter func ParseHost(defaultHost string, defaultUnix, addr string) (string, error) { var ( diff --git a/utils/utils_test.go b/utils/utils_test.go index 444d2a2428..177d3667e1 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -444,6 +444,30 @@ func TestParsePortMapping(t *testing.T) { } } +func TestGetNameservers(t *testing.T) { + for resolv, result := range map[string][]string{` +nameserver 1.2.3.4 +nameserver 40.3.200.10 +search example.com`: {"1.2.3.4", "40.3.200.10"}, + `search example.com`: {}, + `nameserver 1.2.3.4 +search example.com +nameserver 4.30.20.100`: {"1.2.3.4", "4.30.20.100"}, + ``: {}, + ` nameserver 1.2.3.4 `: {"1.2.3.4"}, + `search example.com +nameserver 1.2.3.4 +#nameserver 4.3.2.1`: {"1.2.3.4"}, + `search example.com +nameserver 1.2.3.4 # not 4.3.2.1`: {"1.2.3.4"}, + } { + test := GetNameservers([]byte(resolv)) + if !StrSlicesEqual(test, result) { + t.Fatalf("Wrong nameserver string {%s} should be %v. Input: %s", test, result, resolv) + } + } +} + func TestGetNameserversAsCIDR(t *testing.T) { for resolv, result := range map[string][]string{` nameserver 1.2.3.4 @@ -468,6 +492,33 @@ nameserver 1.2.3.4 # not 4.3.2.1`: {"1.2.3.4/32"}, } } +func TestGetSearchDomains(t *testing.T) { + for resolv, result := range map[string][]string{ + `search example.com`: {"example.com"}, + `search example.com # ignored`: {"example.com"}, + ` search example.com `: {"example.com"}, + ` search example.com # ignored`: {"example.com"}, + `search foo.example.com example.com`: {"foo.example.com", "example.com"}, + ` search foo.example.com example.com `: {"foo.example.com", "example.com"}, + ` search foo.example.com example.com # ignored`: {"foo.example.com", "example.com"}, + ``: {}, + `# ignored`: {}, + `nameserver 1.2.3.4 +search foo.example.com example.com`: {"foo.example.com", "example.com"}, + `nameserver 1.2.3.4 +search dup1.example.com dup2.example.com +search foo.example.com example.com`: {"foo.example.com", "example.com"}, + `nameserver 1.2.3.4 +search foo.example.com example.com +nameserver 4.30.20.100`: {"foo.example.com", "example.com"}, + } { + test := GetSearchDomains([]byte(resolv)) + if !StrSlicesEqual(test, result) { + t.Fatalf("Wrong search domain string {%s} should be %v. Input: %s", test, result, resolv) + } + } +} + func StrSlicesEqual(a, b []string) bool { if len(a) != len(b) { return false From ec3257921da9da0d37df76e26a842f8f4775def0 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 16 Mar 2014 21:35:01 +0100 Subject: [PATCH 159/384] Docker-DCO-1.1-Signed-off-by: Aditya (github: netroy) document `DisableNetwork` config flag --- docs/sources/reference/api/docker_remote_api_v1.10.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sources/reference/api/docker_remote_api_v1.10.rst b/docs/sources/reference/api/docker_remote_api_v1.10.rst index 20af253f0e..4fa9a04c03 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.10.rst +++ b/docs/sources/reference/api/docker_remote_api_v1.10.rst @@ -136,6 +136,7 @@ Create a container }, "VolumesFrom":"", "WorkingDir":"", + "DisableNetwork": false, "ExposedPorts":{ "22/tcp": {} } From 5127732c7911988c81eda7bb31ac77fc1dd36ac2 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Wed, 19 Mar 2014 14:30:13 -0400 Subject: [PATCH 160/384] docker save: --output flag for those that do not care to redirect stdout Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- api/client.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/api/client.go b/api/client.go index 8f515639a7..343a24078c 100644 --- a/api/client.go +++ b/api/client.go @@ -2044,6 +2044,8 @@ 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)") + outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT") + if err := cmd.Parse(args); err != nil { return err } @@ -2053,8 +2055,16 @@ func (cli *DockerCli) CmdSave(args ...string) error { return nil } + var output io.Writer = cli.out + var 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 From 367a679b9270dd9ec6bd647998b6ffe594cfa6ab Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Wed, 19 Mar 2014 14:34:12 -0400 Subject: [PATCH 161/384] images: assurance and debug info on image layers when pushing or saving layers, report sizes for validation. And ensure that the files written are sync'ed. Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- archive/archive.go | 3 +++ server/server.go | 11 +++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/archive/archive.go b/archive/archive.go index eace5a5158..2fac18e99f 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -617,6 +617,9 @@ func NewTempArchive(src Archive, dir string) (*TempArchive, error) { if _, err := io.Copy(f, src); err != nil { return nil, err } + if err = f.Sync(); err != nil { + return nil, err + } if _, err := f.Seek(0, 0); err != nil { return nil, err } diff --git a/server/server.go b/server/server.go index e6243971a4..840a70357d 100644 --- a/server/server.go +++ b/server/server.go @@ -378,10 +378,15 @@ func (srv *Server) exportImage(img *image.Image, tempdir string) error { if err != nil { return err } - if _, err = io.Copy(fsTar, fs); err != nil { + if written, err := io.Copy(fsTar, fs); err != nil { + return err + } else { + utils.Debugf("rendered layer for %s of [%d] size", i.ID, written) + } + + if err = fsTar.Close(); err != nil { return err } - fsTar.Close() // find parent if i.Parent != "" { @@ -1537,6 +1542,8 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, defer os.RemoveAll(layerData.Name()) // Send the layer + utils.Debugf("rendered layer for %s of [%d] size", imgData.ID, layerData.Size) + checksum, checksumPayload, err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf, false, utils.TruncateID(imgData.ID), "Pushing"), ep, token, jsonRaw) if err != nil { return "", err From e93a16ab48f75311aab155548f32776cbd21dfe6 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Wed, 19 Mar 2014 14:47:20 -0400 Subject: [PATCH 162/384] docker save: add and improve docs add usage examples for `docker save ...` Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- api/client.go | 2 +- docs/sources/reference/commandline/cli.rst | 23 +++++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/api/client.go b/api/client.go index 343a24078c..5a69700704 100644 --- a/api/client.go +++ b/api/client.go @@ -2043,7 +2043,7 @@ 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 { diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 0f33b05ec4..294e1d0544 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -1307,10 +1307,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: From 48cb2f03177732823b4091fd3ddd44b2bef2c58e Mon Sep 17 00:00:00 2001 From: LK4D4 Date: Wed, 19 Mar 2014 21:01:20 +0400 Subject: [PATCH 163/384] Remove duplication of Dns in config merging. Fixes #4714 Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- runconfig/config_test.go | 2 +- runconfig/merge.go | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/runconfig/config_test.go b/runconfig/config_test.go index 46e4691b93..8bbad9effa 100644 --- a/runconfig/config_test.go +++ b/runconfig/config_test.go @@ -231,7 +231,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, diff --git a/runconfig/merge.go b/runconfig/merge.go index 3b91aa2af0..79e3951271 100644 --- a/runconfig/merge.go +++ b/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.Entrypoint == nil || len(userConf.Entrypoint) == 0 { userConf.Entrypoint = imageConf.Entrypoint From 698ca9f38d7ccee2c36b98821c74114b95db631b Mon Sep 17 00:00:00 2001 From: Daniel Norberg Date: Wed, 19 Mar 2014 15:20:36 -0400 Subject: [PATCH 164/384] fix typo in documentation Docker-DCO-1.1-Signed-off-by: Daniel Norberg (github: danielnorberg) --- docs/sources/reference/commandline/cli.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 7b16a7d2ec..c483551b46 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -97,7 +97,7 @@ To force Docker to use devicemapper as the storage driver, use ``docker -d -s de To set the DNS server for all Docker containers, use ``docker -d --dns 8.8.8.8``. -To set the a DNS search domain for all Docker containers, use ``docker -d --dns-search example.com``. +To set the DNS search domain for all Docker containers, use ``docker -d --dns-search example.com``. To run the daemon with debug output, use ``docker -d -D``. From c657603c612650117b4def976ff40d98ba7c3a21 Mon Sep 17 00:00:00 2001 From: Daniel Norberg Date: Wed, 19 Mar 2014 16:00:46 -0400 Subject: [PATCH 165/384] variable declaration cleanup Docker-DCO-1.1-Signed-off-by: Daniel Norberg (github: danielnorberg) --- opts/opts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opts/opts.go b/opts/opts.go index b2f21db30b..67f1c8fd48 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -143,7 +143,7 @@ func ValidateDomain(val string) (string, error) { return "", fmt.Errorf("%s is not a valid domain", val) } re := regexp.MustCompile(`^(:?(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))(:?\.(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])))*)\.?\s*$`) - var ns = re.FindSubmatch([]byte(val)) + ns := re.FindSubmatch([]byte(val)) if len(ns) > 0 { return string(ns[1]), nil } From 78a0105eaf80ed85e2ee236632a2cc16998228f9 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Wed, 19 Mar 2014 17:09:12 -0400 Subject: [PATCH 166/384] api/client: var style tweak Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- api/client.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/api/client.go b/api/client.go index 5a69700704..e7abf04cb5 100644 --- a/api/client.go +++ b/api/client.go @@ -2055,8 +2055,10 @@ func (cli *DockerCli) CmdSave(args ...string) error { return nil } - var output io.Writer = cli.out - var err error + var ( + output io.Writer = cli.out + err error + ) if *outfile != "" { output, err = os.Create(*outfile) if err != nil { From 4434dcee89f7d0d0239f6b492b24e940cdbafb21 Mon Sep 17 00:00:00 2001 From: unclejack Date: Wed, 19 Mar 2014 23:23:45 +0200 Subject: [PATCH 167/384] fix failing test to use kill instead of stop TestCreateStartRestartStopStartKillRm was failing because stop has been changed to not kill containers. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- integration/server_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/integration/server_test.go b/integration/server_test.go index a401f1306e..617f81fa4d 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -416,7 +416,7 @@ func TestRestartKillWait(t *testing.T) { }) } -func TestCreateStartRestartStopStartKillRm(t *testing.T) { +func TestCreateStartRestartKillStartKillRm(t *testing.T) { eng := NewTestEngine(t) srv := mkServerFromEngine(eng, t) defer mkRuntimeFromEngine(eng, t).Nuke() @@ -456,8 +456,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) { t.Fatal(err) } - job = eng.Job("stop", id) - job.SetenvInt("t", 15) + job = eng.Job("kill", id) if err := job.Run(); err != nil { t.Fatal(err) } From f3765f96cfb37f6ea9f925f0d3174fe18c4152be Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Thu, 20 Mar 2014 09:08:52 +1000 Subject: [PATCH 168/384] add a link to the security documentation when we mention the docker group (or -G) Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/articles/security.rst | 2 ++ docs/sources/installation/binaries.rst | 3 ++- docs/sources/installation/ubuntulinux.rst | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/sources/articles/security.rst b/docs/sources/articles/security.rst index e738e9a847..ec2ab9bffd 100644 --- a/docs/sources/articles/security.rst +++ b/docs/sources/articles/security.rst @@ -82,6 +82,8 @@ when some applications start to misbehave. Control Groups have been around for a while as well: the code was started in 2006, and initially merged in kernel 2.6.24. +.. _dockersecurity_daemon: + Docker Daemon Attack Surface ---------------------------- diff --git a/docs/sources/installation/binaries.rst b/docs/sources/installation/binaries.rst index bfdfbe211f..a070599338 100644 --- a/docs/sources/installation/binaries.rst +++ b/docs/sources/installation/binaries.rst @@ -77,7 +77,8 @@ always run as the root user, but if you run the ``docker`` client as a user in the *docker* group then you don't need to add ``sudo`` to all the client commands. -.. warning:: The *docker* group is root-equivalent. +.. warning:: The *docker* group (or the group specified with ``-G``) is + root-equivalent; see :ref:`dockersecurity_daemon` details. Upgrades diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index 6998be8571..776090bff5 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -186,7 +186,7 @@ client commands. As of 0.9.0, you can specify that a group other than ``docker`` should own the Unix socket with the ``-G`` option. .. warning:: The *docker* group (or the group specified with ``-G``) is - root-equivalent. + root-equivalent; see :ref:`dockersecurity_daemon` details. **Example:** From 179e2c92d8d02d029d8aa54d53edb82b3fbcea2b Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Sun, 16 Mar 2014 21:10:59 +0000 Subject: [PATCH 169/384] Generate md5 and sha265 hashes when building, and upload them in hack/release.sh Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- hack/make/binary | 8 ++++ hack/release.sh | 122 +++++++++++++++++++++++++++++------------------ 2 files changed, 84 insertions(+), 46 deletions(-) mode change 100644 => 100755 hack/make/binary diff --git a/hack/make/binary b/hack/make/binary old mode 100644 new mode 100755 index 7272b1ede0..dee7d98dc6 --- a/hack/make/binary +++ b/hack/make/binary @@ -11,3 +11,11 @@ go build \ " \ ./docker echo "Created binary: $DEST/docker-$VERSION" + +if command -v md5sum &> /dev/null; then + md5sum "$DEST/docker-$VERSION" > "$DEST/docker-$VERSION.md5" +fi +if command -v sha256sum &> /dev/null; then + sha256sum "$DEST/docker-$VERSION" > "$DEST/docker-$VERSION.sha256" +fi + diff --git a/hack/release.sh b/hack/release.sh index c380d2239a..edcee98f38 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -55,33 +55,16 @@ RELEASE_BUNDLES=( if [ "$1" != '--release-regardless-of-test-failure' ]; then RELEASE_BUNDLES=( test "${RELEASE_BUNDLES[@]}" ) fi - -if ! ./hack/make.sh "${RELEASE_BUNDLES[@]}"; then - echo >&2 - echo >&2 'The build or tests appear to have failed.' - echo >&2 - echo >&2 'You, as the release maintainer, now have a couple options:' - echo >&2 '- delay release and fix issues' - echo >&2 '- delay release and fix issues' - echo >&2 '- did we mention how important this is? issues need fixing :)' - echo >&2 - echo >&2 'As a final LAST RESORT, you (because only you, the release maintainer,' - echo >&2 ' really knows all the hairy problems at hand with the current release' - echo >&2 ' issues) may bypass this checking by running this script again with the' - echo >&2 ' single argument of "--release-regardless-of-test-failure", which will skip' - echo >&2 ' running the test suite, and will only build the binaries and packages. Please' - echo >&2 ' avoid using this if at all possible.' - echo >&2 - echo >&2 'Regardless, we cannot stress enough the scarcity with which this bypass' - echo >&2 ' should be used. If there are release issues, we should always err on the' - echo >&2 ' side of caution.' - echo >&2 - exit 1 -fi - + VERSION=$(cat VERSION) BUCKET=$AWS_S3_BUCKET +# These are the 2 keys we've used to sign the deb's +# release (get.docker.io +# GPG_KEY="36A1D7869245C8950F966E92D8576A8BA88D21E9" +# test (test.docker.io) +# GPG_KEY="740B314AE3941731B942C66ADF4FD13717AAD7D6" + setup_s3() { # Try creating the bucket. Ignore errors (it might already exist). s3cmd mb s3://$BUCKET 2>/dev/null || true @@ -114,12 +97,40 @@ s3_url() { esac } +build_all() { + if ! ./hack/make.sh "${RELEASE_BUNDLES[@]}"; then + echo >&2 + echo >&2 'The build or tests appear to have failed.' + echo >&2 + echo >&2 'You, as the release maintainer, now have a couple options:' + echo >&2 '- delay release and fix issues' + echo >&2 '- delay release and fix issues' + echo >&2 '- did we mention how important this is? issues need fixing :)' + echo >&2 + echo >&2 'As a final LAST RESORT, you (because only you, the release maintainer,' + echo >&2 ' really knows all the hairy problems at hand with the current release' + echo >&2 ' issues) may bypass this checking by running this script again with the' + echo >&2 ' single argument of "--release-regardless-of-test-failure", which will skip' + echo >&2 ' running the test suite, and will only build the binaries and packages. Please' + echo >&2 ' avoid using this if at all possible.' + echo >&2 + echo >&2 'Regardless, we cannot stress enough the scarcity with which this bypass' + echo >&2 ' should be used. If there are release issues, we should always err on the' + echo >&2 ' side of caution.' + echo >&2 + exit 1 + fi +} + release_build() { GOOS=$1 GOARCH=$2 - BINARY=bundles/$VERSION/cross/$GOOS/$GOARCH/docker-$VERSION - TGZ=bundles/$VERSION/tgz/$GOOS/$GOARCH/docker-$VERSION.tgz + SOURCE_DIR=bundles/$VERSION/cross/$GOOS/$GOARCH + BINARY=docker-$VERSION + BINARY_MD5=docker-$VERSION.md5 + BINARY_SHA256=docker-$VERSION.sha256 + TGZ=docker-$VERSION.tgz # we need to map our GOOS and GOARCH to uname values # see https://en.wikipedia.org/wiki/Uname @@ -172,17 +183,29 @@ release_build() { fi echo "Uploading $BINARY to $S3OS/$S3ARCH/docker-$VERSION" - s3cmd --follow-symlinks --preserve --acl-public put $BINARY $S3DIR/docker-$VERSION + s3cmd --follow-symlinks --preserve --acl-public put $SOURCE_DIR/$BINARY $S3DIR/$BINARY + + echo "Uploading $BINARY_MD5 to $S3OS/$S3ARCH/docker-$VERSION.md5" + s3cmd --follow-symlinks --preserve --acl-public put $SOURCE_DIR/$BINARY_MD5 $S3DIR/$BINARY_MD5 + + echo "Uploading $BINARY_BINARY_SHA256 to $S3OS/$S3ARCH/docker-$VERSION.sha256" + s3cmd --follow-symlinks --preserve --acl-public put $SOURCE_DIR/$BINARY_SHA256 $S3DIR/$BINARY_SHA256 echo "Uploading $TGZ to $S3OS/$S3ARCH/docker-$VERSION.tgz" - s3cmd --follow-symlinks --preserve --acl-public put $TGZ $S3DIR/docker-$VERSION.tgz + s3cmd --follow-symlinks --preserve --acl-public put $SOURCE_DIR/$TGZ $S3DIR/$TGZ if [ -z "$NOLATEST" ]; then - echo "Copying $S3OS/$S3ARCH/docker-$VERSION to $S3OS/$S3ARCH/docker-latest" - s3cmd --acl-public cp $S3DIR/docker-$VERSION $S3DIR/docker-latest + echo "Copying $S3DIR/$BINARY to $S3DIR/docker-latest" + s3cmd --acl-public cp $S3DIR/$BINARY $S3DIR/docker-latest - echo "Copying $S3OS/$S3ARCH/docker-$VERSION.tgz to $S3OS/$S3ARCH/docker-latest.tgz" - s3cmd --acl-public cp $S3DIR/docker-$VERSION.tgz $S3DIR/docker-latest.tgz + echo "Copying $S3DIR/$BINARY_MD5 to $S3DIR/docker-latest.md5" + s3cmd --acl-public cp $S3DIR/$BINARY_MD5 $S3DIR/docker-latest.md5 + + echo "Copying $S3DIR/$BINARY_SHA256 to $S3DIR/docker-latest.sha256" + s3cmd --acl-public cp $S3DIR/$BINARY_SHA256 $S3DIR/docker-latest.sha256 + + echo "Copying $S3DIR/$TGZ $S3DIR/docker-latest.tgz" + s3cmd --acl-public cp $S3DIR/$TGZ $S3DIR/docker-latest.tgz fi } @@ -194,21 +217,8 @@ release_ubuntu() { echo >&2 './hack/make.sh must be run before release_ubuntu' exit 1 } - # Make sure that we have our keys - mkdir -p /.gnupg/ + s3cmd sync s3://$BUCKET/ubuntu/.gnupg/ /.gnupg/ || true - gpg --list-keys releasedocker >/dev/null || { - gpg --gen-key --batch </dev/null || { + gpg --gen-key --batch < Date: Thu, 20 Mar 2014 09:35:58 +1000 Subject: [PATCH 170/384] whitespace-blind Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- hack/make/binary | 1 - hack/release.sh | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/hack/make/binary b/hack/make/binary index dee7d98dc6..f220d2dae6 100755 --- a/hack/make/binary +++ b/hack/make/binary @@ -18,4 +18,3 @@ fi if command -v sha256sum &> /dev/null; then sha256sum "$DEST/docker-$VERSION" > "$DEST/docker-$VERSION.sha256" fi - diff --git a/hack/release.sh b/hack/release.sh index edcee98f38..76acad4991 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -60,7 +60,7 @@ VERSION=$(cat VERSION) BUCKET=$AWS_S3_BUCKET # These are the 2 keys we've used to sign the deb's -# release (get.docker.io +# release (get.docker.io) # GPG_KEY="36A1D7869245C8950F966E92D8576A8BA88D21E9" # test (test.docker.io) # GPG_KEY="740B314AE3941731B942C66ADF4FD13717AAD7D6" @@ -342,7 +342,6 @@ main() { release_test } - main echo From e38e977a0410b754b6f318ff973dc15e6d756023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Djibril=20Kon=C3=A9?= Date: Tue, 18 Mar 2014 21:18:36 +0100 Subject: [PATCH 171/384] Harmonize / across all name-related commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docker-DCO-1.1-Signed-off-by: Djibril Koné (github: enokd) Harmonize / across all name-related commands Docker-DCO-1.1-Signed-off-by: Djibril Koné (github: enokd) Harmonize / across all name-related commands:Return an error when repeated / Docker-DCO-1.1-Signed-off-by: Djibril Koné (github: enokd) --- api/client.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/api/client.go b/api/client.go index 10dd9406dc..07a3d25e5c 100644 --- a/api/client.go +++ b/api/client.go @@ -1452,6 +1452,11 @@ func (cli *DockerCli) CmdCommit(args ...string) error { return nil } + re := regexp.MustCompile("/{2}") + if re.MatchString(repository) { + return fmt.Errorf("Error: Bad image name. Please rename your image in the format /") + } + v := url.Values{} v.Set("container", name) v.Set("repo", repository) From a8cc6ebb181abf58b12ed6ee037711f0b2f1eff2 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Thu, 20 Mar 2014 09:59:54 +1000 Subject: [PATCH 172/384] I'm not looking forward to documenting cli arguments that may or may not show depending on what plugins / drviers you choose Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/reference/commandline/cli.rst | 2 +- docs/sources/reference/run.rst | 15 ++++++++------- runconfig/parse.go | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index d398b16e53..5f228f55b4 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -1145,7 +1145,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) diff --git a/docs/sources/reference/run.rst b/docs/sources/reference/run.rst index 0b4f7eebf4..d2fe449c22 100644 --- a/docs/sources/reference/run.rst +++ b/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 `_. -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 diff --git a/runconfig/parse.go b/runconfig/parse.go index cc33188ad5..c2591722d5 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -76,7 +76,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\"") if err := cmd.Parse(args); err != nil { return nil, nil, cmd, err From 62eb23aed50c9c820836c4b3f515cba2660b5c20 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Thu, 20 Mar 2014 10:18:08 +1000 Subject: [PATCH 173/384] missed a bug Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- hack/release.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hack/release.sh b/hack/release.sh index 76acad4991..d42fd41ee9 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -173,8 +173,8 @@ release_build() { S3DIR=s3://$BUCKET/builds/$S3OS/$S3ARCH - if [ ! -x "$BINARY" ]; then - echo >&2 "error: can't find $BINARY - was it compiled properly?" + if [ ! -x "$SOURCE_DIR/$BINARY" ]; then + echo >&2 "error: can't find $SOURCE_DIR/$BINARY - was it compiled properly?" exit 1 fi if [ ! -f "$TGZ" ]; then @@ -188,7 +188,7 @@ release_build() { echo "Uploading $BINARY_MD5 to $S3OS/$S3ARCH/docker-$VERSION.md5" s3cmd --follow-symlinks --preserve --acl-public put $SOURCE_DIR/$BINARY_MD5 $S3DIR/$BINARY_MD5 - echo "Uploading $BINARY_BINARY_SHA256 to $S3OS/$S3ARCH/docker-$VERSION.sha256" + echo "Uploading $BINARY_SHA256 to $S3OS/$S3ARCH/docker-$VERSION.sha256" s3cmd --follow-symlinks --preserve --acl-public put $SOURCE_DIR/$BINARY_SHA256 $S3DIR/$BINARY_SHA256 echo "Uploading $TGZ to $S3OS/$S3ARCH/docker-$VERSION.tgz" From 6b46a09186b6a53959d567014b8d0e1cff761bc8 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 19 Mar 2014 19:58:39 -0600 Subject: [PATCH 174/384] Fix a lot of the sha256 and md5 stuff to be more DRY and extendible, and on more things (specifically, the tgz files too) Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/make.sh | 21 ++++++++ hack/make/binary | 9 +--- hack/make/dynbinary | 6 ++- hack/make/tgz | 2 + hack/release.sh | 121 ++++++++++++++++++++++++++------------------ 5 files changed, 100 insertions(+), 59 deletions(-) diff --git a/hack/make.sh b/hack/make.sh index 994da8d9ad..b77e9b7f44 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -149,6 +149,27 @@ find_dirs() { \) -name "$1" -print0 | xargs -0n1 dirname | sort -u } +hash_files() { + while [ $# -gt 0 ]; do + f="$1" + shift + dir="$(dirname "$f")" + base="$(basename "$f")" + for hashAlgo in md5 sha256; do + if command -v "${hashAlgo}sum" &> /dev/null; then + ( + # subshell and cd so that we get output files like: + # $HASH docker-$VERSION + # instead of: + # $HASH /go/src/github.com/.../$VERSION/binary/docker-$VERSION + cd "$dir" + "${hashAlgo}sum" "$base" > "$base.$hashAlgo" + ) + fi + done + done +} + bundle() { bundlescript=$1 bundle=$(basename $bundlescript) diff --git a/hack/make/binary b/hack/make/binary index f220d2dae6..7b4d7b5b5b 100755 --- a/hack/make/binary +++ b/hack/make/binary @@ -3,7 +3,7 @@ DEST=$1 go build \ - -o $DEST/docker-$VERSION \ + -o "$DEST/docker-$VERSION" \ "${BUILDFLAGS[@]}" \ -ldflags " $LDFLAGS @@ -12,9 +12,4 @@ go build \ ./docker echo "Created binary: $DEST/docker-$VERSION" -if command -v md5sum &> /dev/null; then - md5sum "$DEST/docker-$VERSION" > "$DEST/docker-$VERSION.md5" -fi -if command -v sha256sum &> /dev/null; then - sha256sum "$DEST/docker-$VERSION" > "$DEST/docker-$VERSION.sha256" -fi +hash_files "$DEST/docker-$VERSION" diff --git a/hack/make/dynbinary b/hack/make/dynbinary index d4f583fb62..75cffe3dcc 100644 --- a/hack/make/dynbinary +++ b/hack/make/dynbinary @@ -5,7 +5,7 @@ DEST=$1 if [ -z "$DOCKER_CLIENTONLY" ]; then # dockerinit still needs to be a static binary, even if docker is dynamic go build \ - -o $DEST/dockerinit-$VERSION \ + -o "$DEST/dockerinit-$VERSION" \ "${BUILDFLAGS[@]}" \ -ldflags " $LDFLAGS @@ -14,7 +14,9 @@ if [ -z "$DOCKER_CLIENTONLY" ]; then " \ ./dockerinit echo "Created binary: $DEST/dockerinit-$VERSION" - ln -sf dockerinit-$VERSION $DEST/dockerinit + ln -sf "dockerinit-$VERSION" "$DEST/dockerinit" + + hash_files "$DEST/dockerinit-$VERSION" sha1sum= if command -v sha1sum &> /dev/null; then diff --git a/hack/make/tgz b/hack/make/tgz index 5d03306322..120339976b 100644 --- a/hack/make/tgz +++ b/hack/make/tgz @@ -23,6 +23,8 @@ for d in "$CROSS/"*/*; do tar --numeric-owner --owner 0 -C "$DEST/build" -czf "$TGZ" usr + hash_files "$TGZ" + rm -rf "$DEST/build" echo "Created tgz: $TGZ" diff --git a/hack/release.sh b/hack/release.sh index 46a93af70c..6f9df8c7e6 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -122,91 +122,113 @@ build_all() { fi } +upload_release_build() { + src="$1" + dst="$2" + latest="$3" + + echo + echo "Uploading $src" + echo " to $dst" + echo + s3cmd --follow-symlinks --preserve --acl-public put "$src" "$dst" + if [ "$latest" ]; then + echo + echo "Copying to $latest" + echo + s3cmd --acl-public cp "$dst" "$latest" + fi + + # get hash files too (see hash_files() in hack/make.sh) + for hashAlgo in md5 sha256; do + if [ -e "$src.$hashAlgo" ]; then + echo + echo "Uploading $src.$hashAlgo" + echo " to $dst.$hashAlgo" + echo + s3cmd --follow-symlinks --preserve --acl-public --mime-type='text/plain' put "$src.$hashAlgo" "$dst.$hashAlgo" + if [ "$latest" ]; then + echo + echo "Copying to $latest.$hashAlgo" + echo + s3cmd --acl-public cp "$dst.$hashAlgo" "$latest.$hashAlgo" + fi + fi + done +} + release_build() { GOOS=$1 GOARCH=$2 - SOURCE_DIR=bundles/$VERSION/cross/$GOOS/$GOARCH - BINARY=docker-$VERSION - BINARY_MD5=docker-$VERSION.md5 - BINARY_SHA256=docker-$VERSION.sha256 - TGZ=docker-$VERSION.tgz + binDir=bundles/$VERSION/cross/$GOOS/$GOARCH + tgzDir=bundles/$VERSION/tgz/$GOOS/$GOARCH + binary=docker-$VERSION + tgz=docker-$VERSION.tgz + + latestBase= + if [ -z "$NOLATEST" ]; then + latestBase=docker-latest + fi # we need to map our GOOS and GOARCH to uname values # see https://en.wikipedia.org/wiki/Uname # ie, GOOS=linux -> "uname -s"=Linux - S3OS=$GOOS - case "$S3OS" in + s3Os=$GOOS + case "$s3Os" in darwin) - S3OS=Darwin + s3Os=Darwin ;; freebsd) - S3OS=FreeBSD + s3Os=FreeBSD ;; linux) - S3OS=Linux + s3Os=Linux ;; *) - echo >&2 "error: can't convert $S3OS to an appropriate value for 'uname -s'" + echo >&2 "error: can't convert $s3Os to an appropriate value for 'uname -s'" exit 1 ;; esac - S3ARCH=$GOARCH - case "$S3ARCH" in + s3Arch=$GOARCH + case "$s3Arch" in amd64) - S3ARCH=x86_64 + s3Arch=x86_64 ;; 386) - S3ARCH=i386 + s3Arch=i386 ;; arm) - S3ARCH=armel + s3Arch=armel # someday, we might potentially support mutliple GOARM values, in which case we might get armhf here too ;; *) - echo >&2 "error: can't convert $S3ARCH to an appropriate value for 'uname -m'" + echo >&2 "error: can't convert $s3Arch to an appropriate value for 'uname -m'" exit 1 ;; esac - S3DIR=s3://$BUCKET/builds/$S3OS/$S3ARCH + s3Dir=s3://$BUCKET/builds/$s3Os/$s3Arch + latest= + latestTgz= + if [ "$latestBase" ]; then + latest="$s3Dir/$latestBase" + latestTgz="$s3Dir/$latestBase.tgz" + fi - if [ ! -x "$SOURCE_DIR/$BINARY" ]; then - echo >&2 "error: can't find $SOURCE_DIR/$BINARY - was it compiled properly?" + if [ ! -x "$binDir/$binary" ]; then + echo >&2 "error: can't find $binDir/$binary - was it compiled properly?" exit 1 fi - if [ ! -f "$TGZ" ]; then - echo >&2 "error: can't find $TGZ - was it packaged properly?" + if [ ! -f "$tgzDir/$tgz" ]; then + echo >&2 "error: can't find $tgzDir/$tgz - was it packaged properly?" exit 1 fi - echo "Uploading $BINARY to $S3OS/$S3ARCH/docker-$VERSION" - s3cmd --follow-symlinks --preserve --acl-public put $SOURCE_DIR/$BINARY $S3DIR/$BINARY - - echo "Uploading $BINARY_MD5 to $S3OS/$S3ARCH/docker-$VERSION.md5" - s3cmd --follow-symlinks --preserve --acl-public put $SOURCE_DIR/$BINARY_MD5 $S3DIR/$BINARY_MD5 - - echo "Uploading $BINARY_SHA256 to $S3OS/$S3ARCH/docker-$VERSION.sha256" - s3cmd --follow-symlinks --preserve --acl-public put $SOURCE_DIR/$BINARY_SHA256 $S3DIR/$BINARY_SHA256 - - echo "Uploading $TGZ to $S3OS/$S3ARCH/docker-$VERSION.tgz" - s3cmd --follow-symlinks --preserve --acl-public put $SOURCE_DIR/$TGZ $S3DIR/$TGZ - - if [ -z "$NOLATEST" ]; then - echo "Copying $S3DIR/$BINARY to $S3DIR/docker-latest" - s3cmd --acl-public cp $S3DIR/$BINARY $S3DIR/docker-latest - - echo "Copying $S3DIR/$BINARY_MD5 to $S3DIR/docker-latest.md5" - s3cmd --acl-public cp $S3DIR/$BINARY_MD5 $S3DIR/docker-latest.md5 - - echo "Copying $S3DIR/$BINARY_SHA256 to $S3DIR/docker-latest.sha256" - s3cmd --acl-public cp $S3DIR/$BINARY_SHA256 $S3DIR/docker-latest.sha256 - - echo "Copying $S3DIR/$TGZ $S3DIR/docker-latest.tgz" - s3cmd --acl-public cp $S3DIR/$TGZ $S3DIR/docker-latest.tgz - fi + upload_release_build "$binDir/$binary" "$s3Dir/$binary" "$latest" + upload_release_build "$tgzDir/$tgz" "$s3Dir/$tgz" "$latestTgz" } # Upload the 'ubuntu' bundle to S3: @@ -217,8 +239,6 @@ release_ubuntu() { echo >&2 './hack/make.sh must be run before release_ubuntu' exit 1 } - - s3cmd sync s3://$BUCKET/ubuntu/.gnupg/ /.gnupg/ || true # Sign our packages dpkg-sig -g "--passphrase $GPG_PASSPHRASE" -k releasedocker \ @@ -318,10 +338,11 @@ release_test() { setup_gpg() { # Make sure that we have our keys mkdir -p /.gnupg/ + s3cmd sync s3://$BUCKET/ubuntu/.gnupg/ /.gnupg/ || true gpg --list-keys releasedocker >/dev/null || { gpg --gen-key --batch < Date: Thu, 20 Mar 2014 17:32:59 +0100 Subject: [PATCH 175/384] devicemapper: Better/faster shutdown Right now shutdown is looping over *all* devicemapper devices and actively deactivating them, this is pretty slow if you have a lot of non-active containers. We instead only deactivate the devices that are mounted. We also do the shutdown unmount using MNT_DETACH which forces the unmount in the global namespace, even if it is busy because of some container having it mounted. This means the device will be freed when that container exits. Also, we move the call to waitClose to deactivateDevice because all callers of any of them call both anyway. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- runtime/graphdriver/devmapper/deviceset.go | 36 ++++++++++------------ 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/runtime/graphdriver/devmapper/deviceset.go b/runtime/graphdriver/devmapper/deviceset.go index 4d33e243e0..dfdb180bb2 100644 --- a/runtime/graphdriver/devmapper/deviceset.go +++ b/runtime/graphdriver/devmapper/deviceset.go @@ -14,6 +14,7 @@ import ( "strconv" "strings" "sync" + "syscall" "time" ) @@ -677,6 +678,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,26 +806,20 @@ 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) } + + if err := devices.deactivateDevice(info.Hash); err != nil { + utils.Debugf("Shutdown deactivate %s , error: %s\n", info.Hash, 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) - } - - d.lock.Unlock() - } - if err := devices.deactivatePool(); err != nil { utils.Debugf("Shutdown deactivate pool , error: %s\n", err) } @@ -920,14 +921,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 From fbd6fee4ab9b98f477f365307a641b879badd282 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Thu, 20 Mar 2014 13:09:34 -0400 Subject: [PATCH 176/384] Fix double single dash arg issues in docs Docker-DCO-1.1-Signed-off-by: Brian Goff (github: cpuguy83) --- docs/sources/articles/runmetrics.rst | 2 +- docs/sources/examples/postgresql_service.rst | 2 +- docs/sources/use/working_with_volumes.rst | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sources/articles/runmetrics.rst b/docs/sources/articles/runmetrics.rst index afb7f82e39..6b705fb737 100644 --- a/docs/sources/articles/runmetrics.rst +++ b/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//``. diff --git a/docs/sources/examples/postgresql_service.rst b/docs/sources/examples/postgresql_service.rst index 66b0fd7aa5..488e1530b2 100644 --- a/docs/sources/examples/postgresql_service.rst +++ b/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). diff --git a/docs/sources/use/working_with_volumes.rst b/docs/sources/use/working_with_volumes.rst index 02f4e71b13..d2f035dc84 100644 --- a/docs/sources/use/working_with_volumes.rst +++ b/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/ From ab0c9b385c47d818a2105c2114573f5beedbd3ba Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 20 Mar 2014 14:59:40 -0600 Subject: [PATCH 177/384] Remove the inotifywait hack from our upstart host-integration example that is no longer necessary Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- docs/sources/use/host_integration.rst | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/sources/use/host_integration.rst b/docs/sources/use/host_integration.rst index ed341cd4bc..cb920a5908 100644 --- a/docs/sources/use/host_integration.rst +++ b/docs/sources/use/host_integration.rst @@ -43,11 +43,6 @@ into it: stop on runlevel [!2345] respawn script - # Wait for docker to finish starting up first. - FILE=/var/run/docker.sock - while [ ! -e $FILE ] ; do - inotifywait -t 2 -e create $(dirname $FILE) - done /usr/bin/docker start -a redis_server end script From 8944fb2e9b07d5a764f8d48065b9afd73364f640 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 20 Mar 2014 21:51:28 +0000 Subject: [PATCH 178/384] rename lxc to bridge Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- builtins/builtins.go | 7 +++---- runtime/networkdriver/{lxc => bridge}/driver.go | 2 +- runtime/runtime.go | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) rename runtime/networkdriver/{lxc => bridge}/driver.go (99%) diff --git a/builtins/builtins.go b/builtins/builtins.go index 86f3973c62..10ee9b19e6 100644 --- a/builtins/builtins.go +++ b/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) } diff --git a/runtime/networkdriver/lxc/driver.go b/runtime/networkdriver/bridge/driver.go similarity index 99% rename from runtime/networkdriver/lxc/driver.go rename to runtime/networkdriver/bridge/driver.go index 827de2a609..41588b1c27 100644 --- a/runtime/networkdriver/lxc/driver.go +++ b/runtime/networkdriver/bridge/driver.go @@ -1,4 +1,4 @@ -package lxc +package bridge import ( "fmt" diff --git a/runtime/runtime.go b/runtime/runtime.go index 38a1beccd2..0d3468e350 100644 --- a/runtime/runtime.go +++ b/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" From f7b3e879fc3047ed93ac6b43cd9bf47a25f3d0fc Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 20 Mar 2014 22:58:02 +0000 Subject: [PATCH 179/384] Add initial plugin flag to pass lxc and native driver options Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runconfig/hostconfig.go | 2 ++ runconfig/parse.go | 18 ++++++++++++ runtime/container.go | 8 ++++-- runtime/execdriver/driver.go | 28 +++++++++---------- runtime/execdriver/lxc/lxc_template.go | 4 +-- .../execdriver/lxc/lxc_template_unit_test.go | 3 +- runtime/execdriver/native/driver.go | 6 ++-- 7 files changed, 46 insertions(+), 23 deletions(-) diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 6c8618ee81..8ee2288b4b 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -13,6 +13,7 @@ type HostConfig struct { PortBindings nat.PortMap Links []string PublishAllPorts bool + PluginOptions map[string][]string } type KeyValuePair struct { @@ -28,6 +29,7 @@ func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { } job.GetenvJson("LxcConf", &hostConfig.LxcConf) job.GetenvJson("PortBindings", &hostConfig.PortBindings) + job.GetenvJson("PluginOptions", &hostConfig.PluginOptions) if Binds := job.GetenvList("Binds"); Binds != nil { hostConfig.Binds = Binds } diff --git a/runconfig/parse.go b/runconfig/parse.go index cc33188ad5..afcaec304f 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -45,6 +45,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf flDnsSearch = opts.NewListOpts(opts.ValidateDomain) flVolumesFrom opts.ListOpts flLxcOpts opts.ListOpts + flPluginOpts opts.ListOpts flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)") flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: Run container in the background, print new container id") @@ -77,6 +78,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf 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(&flPluginOpts, []string{"-plugin"}, "Add custom plugin options") if err := cmd.Parse(args); err != nil { return nil, nil, cmd, err @@ -206,6 +208,8 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf WorkingDir: *flWorkingDir, } + pluginOptions := parsePluginOpts(flPluginOpts) + hostConfig := &HostConfig{ Binds: binds, ContainerIDFile: *flContainerIDFile, @@ -214,6 +218,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf PortBindings: portBindings, Links: flLinks.GetAll(), PublishAllPorts: *flPublishAll, + PluginOptions: pluginOptions, } if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit { @@ -247,3 +252,16 @@ func parseLxcOpt(opt string) (string, string, error) { } return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil } + +func parsePluginOpts(opts opts.ListOpts) map[string][]string { + out := make(map[string][]string, len(opts.GetAll())) + for _, o := range opts.GetAll() { + parts := strings.SplitN(o, " ", 2) + values, exists := out[parts[0]] + if !exists { + values = []string{} + } + out[parts[0]] = append(values, parts[1]) + } + return out +} diff --git a/runtime/container.go b/runtime/container.go index 6194a19c8c..488d905f4b 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -361,7 +361,7 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s func populateCommand(c *Container) { var ( en *execdriver.Network - driverConfig []string + driverConfig = c.hostConfig.PluginOptions ) en = &execdriver.Network{ @@ -379,11 +379,15 @@ func populateCommand(c *Container) { } } + // merge in the lxc conf options into the generic config map if lxcConf := c.hostConfig.LxcConf; lxcConf != nil { + lxc := driverConfig["lxc"] for _, pair := range lxcConf { - driverConfig = append(driverConfig, fmt.Sprintf("%s = %s", pair.Key, pair.Value)) + lxc = append(lxc, fmt.Sprintf("%s = %s", pair.Key, pair.Value)) } + driverConfig["lxc"] = lxc } + resources := &execdriver.Resources{ Memory: c.Config.Memory, MemorySwap: c.Config.MemorySwap, diff --git a/runtime/execdriver/driver.go b/runtime/execdriver/driver.go index 23e31ee8d9..2b7c367453 100644 --- a/runtime/execdriver/driver.go +++ b/runtime/execdriver/driver.go @@ -112,20 +112,20 @@ type Mount struct { type Command struct { exec.Cmd `json:"-"` - ID string `json:"id"` - Privileged bool `json:"privileged"` - User string `json:"user"` - Rootfs string `json:"rootfs"` // root fs of the container - InitPath string `json:"initpath"` // dockerinit - Entrypoint string `json:"entrypoint"` - Arguments []string `json:"arguments"` - WorkingDir string `json:"working_dir"` - ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver - Tty bool `json:"tty"` - Network *Network `json:"network"` - Config []string `json:"config"` // generic values that specific drivers can consume - Resources *Resources `json:"resources"` - Mounts []Mount `json:"mounts"` + ID string `json:"id"` + Privileged bool `json:"privileged"` + User string `json:"user"` + Rootfs string `json:"rootfs"` // root fs of the container + InitPath string `json:"initpath"` // dockerinit + Entrypoint string `json:"entrypoint"` + Arguments []string `json:"arguments"` + WorkingDir string `json:"working_dir"` + ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver + Tty bool `json:"tty"` + Network *Network `json:"network"` + Config map[string][]string `json:"config"` // generic values that specific drivers can consume + Resources *Resources `json:"resources"` + Mounts []Mount `json:"mounts"` Terminal Terminal `json:"-"` // standard or tty terminal Console string `json:"-"` // dev/console path diff --git a/runtime/execdriver/lxc/lxc_template.go b/runtime/execdriver/lxc/lxc_template.go index ce9d90469f..7979e4f284 100644 --- a/runtime/execdriver/lxc/lxc_template.go +++ b/runtime/execdriver/lxc/lxc_template.go @@ -118,8 +118,8 @@ lxc.cgroup.cpu.shares = {{.Resources.CpuShares}} {{end}} {{end}} -{{if .Config}} -{{range $value := .Config}} +{{if .Config.lxc}} +{{range $value := .Config.lxc}} {{$value}} {{end}} {{end}} diff --git a/runtime/execdriver/lxc/lxc_template_unit_test.go b/runtime/execdriver/lxc/lxc_template_unit_test.go index e613adf7a9..74cfd6229c 100644 --- a/runtime/execdriver/lxc/lxc_template_unit_test.go +++ b/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: []string{ + Config: map[string][]string{"lxc": { "lxc.utsname = docker", "lxc.cgroup.cpuset.cpus = 0,1", }, + }, Network: &execdriver.Network{ Mtu: 1500, Interface: nil, diff --git a/runtime/execdriver/native/driver.go b/runtime/execdriver/native/driver.go index bf7e8ccdec..0a09d324db 100644 --- a/runtime/execdriver/native/driver.go +++ b/runtime/execdriver/native/driver.go @@ -184,10 +184,8 @@ func (d *driver) removeContainerRoot(id string) error { func (d *driver) validateCommand(c *execdriver.Command) error { // we need to check the Config of the command to make sure that we // do not have any of the lxc-conf variables - for _, conf := range c.Config { - if strings.Contains(conf, "lxc") { - return fmt.Errorf("%s is not supported by the native driver", conf) - } + for _, conf := range c.Config["native"] { + log.Println(conf) } return nil } From c5f9c4bd6933c806490e4f7cb52557cee154dbed Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 20 Mar 2014 23:09:01 +0000 Subject: [PATCH 180/384] Dont use custom marshaling for caps and namespaces This also adds an enabled field to the types so that they can be easily toggled. Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/types.go | 77 ++++++++++----------------------- pkg/libcontainer/types_linux.go | 12 ++--- 2 files changed, 28 insertions(+), 61 deletions(-) diff --git a/pkg/libcontainer/types.go b/pkg/libcontainer/types.go index 94fe876554..87346348bc 100644 --- a/pkg/libcontainer/types.go +++ b/pkg/libcontainer/types.go @@ -1,7 +1,6 @@ package libcontainer import ( - "encoding/json" "errors" "github.com/syndtr/gocapability/capability" ) @@ -19,29 +18,30 @@ var ( namespaceList = Namespaces{} capabilityList = Capabilities{ - {Key: "SETPCAP", Value: capability.CAP_SETPCAP}, - {Key: "SYS_MODULE", Value: capability.CAP_SYS_MODULE}, - {Key: "SYS_RAWIO", Value: capability.CAP_SYS_RAWIO}, - {Key: "SYS_PACCT", Value: capability.CAP_SYS_PACCT}, - {Key: "SYS_ADMIN", Value: capability.CAP_SYS_ADMIN}, - {Key: "SYS_NICE", Value: capability.CAP_SYS_NICE}, - {Key: "SYS_RESOURCE", Value: capability.CAP_SYS_RESOURCE}, - {Key: "SYS_TIME", Value: capability.CAP_SYS_TIME}, - {Key: "SYS_TTY_CONFIG", Value: capability.CAP_SYS_TTY_CONFIG}, - {Key: "MKNOD", Value: capability.CAP_MKNOD}, - {Key: "AUDIT_WRITE", Value: capability.CAP_AUDIT_WRITE}, - {Key: "AUDIT_CONTROL", Value: capability.CAP_AUDIT_CONTROL}, - {Key: "MAC_OVERRIDE", Value: capability.CAP_MAC_OVERRIDE}, - {Key: "MAC_ADMIN", Value: capability.CAP_MAC_ADMIN}, - {Key: "NET_ADMIN", Value: capability.CAP_NET_ADMIN}, + {Key: "SETPCAP", Value: capability.CAP_SETPCAP, Enabled: true}, + {Key: "SYS_MODULE", Value: capability.CAP_SYS_MODULE, Enabled: true}, + {Key: "SYS_RAWIO", Value: capability.CAP_SYS_RAWIO, Enabled: true}, + {Key: "SYS_PACCT", Value: capability.CAP_SYS_PACCT, Enabled: true}, + {Key: "SYS_ADMIN", Value: capability.CAP_SYS_ADMIN, Enabled: true}, + {Key: "SYS_NICE", Value: capability.CAP_SYS_NICE, Enabled: true}, + {Key: "SYS_RESOURCE", Value: capability.CAP_SYS_RESOURCE, Enabled: true}, + {Key: "SYS_TIME", Value: capability.CAP_SYS_TIME, Enabled: true}, + {Key: "SYS_TTY_CONFIG", Value: capability.CAP_SYS_TTY_CONFIG, Enabled: true}, + {Key: "MKNOD", Value: capability.CAP_MKNOD, Enabled: true}, + {Key: "AUDIT_WRITE", Value: capability.CAP_AUDIT_WRITE, Enabled: true}, + {Key: "AUDIT_CONTROL", Value: capability.CAP_AUDIT_CONTROL, Enabled: true}, + {Key: "MAC_OVERRIDE", Value: capability.CAP_MAC_OVERRIDE, Enabled: true}, + {Key: "MAC_ADMIN", Value: capability.CAP_MAC_ADMIN, Enabled: true}, + {Key: "NET_ADMIN", Value: capability.CAP_NET_ADMIN, Enabled: true}, } ) type ( Namespace struct { - Key string - Value int - File string + Key string `json:"key,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Value int `json:"value,omitempty"` + File string `json:"file,omitempty"` } Namespaces []*Namespace ) @@ -50,23 +50,6 @@ func (ns *Namespace) String() string { return ns.Key } -func (ns *Namespace) MarshalJSON() ([]byte, error) { - return json.Marshal(ns.Key) -} - -func (ns *Namespace) UnmarshalJSON(src []byte) error { - var nsName string - if err := json.Unmarshal(src, &nsName); err != nil { - return err - } - ret := GetNamespace(nsName) - if ret == nil { - return ErrUnkownNamespace - } - *ns = *ret - return nil -} - func GetNamespace(key string) *Namespace { for _, ns := range namespaceList { if ns.Key == key { @@ -89,8 +72,9 @@ func (n Namespaces) Contains(ns string) bool { type ( Capability struct { - Key string - Value capability.Cap + Key string `json:"key,omitempty"` + Enabled bool `json:"enabled"` + Value capability.Cap `json:"value,omitempty"` } Capabilities []*Capability ) @@ -99,23 +83,6 @@ func (c *Capability) String() string { return c.Key } -func (c *Capability) MarshalJSON() ([]byte, error) { - return json.Marshal(c.Key) -} - -func (c *Capability) UnmarshalJSON(src []byte) error { - var capName string - if err := json.Unmarshal(src, &capName); err != nil { - return err - } - ret := GetCapability(capName) - if ret == nil { - return ErrUnkownCapability - } - *c = *ret - return nil -} - func GetCapability(key string) *Capability { for _, capp := range capabilityList { if capp.Key == key { diff --git a/pkg/libcontainer/types_linux.go b/pkg/libcontainer/types_linux.go index c14531df20..1f937e0c97 100644 --- a/pkg/libcontainer/types_linux.go +++ b/pkg/libcontainer/types_linux.go @@ -6,11 +6,11 @@ import ( func init() { namespaceList = Namespaces{ - {Key: "NEWNS", Value: syscall.CLONE_NEWNS, File: "mnt"}, - {Key: "NEWUTS", Value: syscall.CLONE_NEWUTS, File: "uts"}, - {Key: "NEWIPC", Value: syscall.CLONE_NEWIPC, File: "ipc"}, - {Key: "NEWUSER", Value: syscall.CLONE_NEWUSER, File: "user"}, - {Key: "NEWPID", Value: syscall.CLONE_NEWPID, File: "pid"}, - {Key: "NEWNET", Value: syscall.CLONE_NEWNET, File: "net"}, + {Key: "NEWNS", Value: syscall.CLONE_NEWNS, File: "mnt", Enabled: true}, + {Key: "NEWUTS", Value: syscall.CLONE_NEWUTS, File: "uts", Enabled: true}, + {Key: "NEWIPC", Value: syscall.CLONE_NEWIPC, File: "ipc", Enabled: true}, + {Key: "NEWUSER", Value: syscall.CLONE_NEWUSER, File: "user", Enabled: true}, + {Key: "NEWPID", Value: syscall.CLONE_NEWPID, File: "pid", Enabled: true}, + {Key: "NEWNET", Value: syscall.CLONE_NEWNET, File: "net", Enabled: true}, } } From 43c3ee3ba154e2480191ed3743391810f23f29af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Djibril=20Kon=C3=A9?= Date: Fri, 21 Mar 2014 00:40:58 +0100 Subject: [PATCH 181/384] Harmonize / across all name-related commands/Validate images names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docker-DCO-1.1-Signed-off-by: Djibril Koné (github: enokd) --- api/client.go | 26 +++++++++++++++++++++++--- registry/registry_test.go | 4 ++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/api/client.go b/api/client.go index 07a3d25e5c..aed1ccabc6 100644 --- a/api/client.go +++ b/api/client.go @@ -207,6 +207,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 { @@ -1002,6 +1011,12 @@ func (cli *DockerCli) CmdImport(args ...string) error { repository, tag = utils.ParseRepositoryTag(cmd.Arg(1)) } 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) v.Set("fromSrc", src) @@ -1452,9 +1467,9 @@ func (cli *DockerCli) CmdCommit(args ...string) error { return nil } - re := regexp.MustCompile("/{2}") - if re.MatchString(repository) { - return fmt.Errorf("Error: Bad image name. Please rename your image in the format /") + //Check if the given image name can be resolved + if _, _, err := registry.ResolveRepositoryName(repository); err != nil { + return err } v := url.Values{} @@ -1745,6 +1760,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) diff --git a/registry/registry_test.go b/registry/registry_test.go index ebfb99b4c3..c072da41c5 100644 --- a/registry/registry_test.go +++ b/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() + } } From 443a75d5f66e986e9d7740d3f2aaef080aef8ea0 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Mar 2014 00:10:24 +0000 Subject: [PATCH 182/384] Allow caps to be toggled in native driver with plugin flag Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/capabilities/capabilities.go | 4 +- pkg/libcontainer/types.go | 41 +++++++++++-------- runtime/execdriver/native/default_template.go | 31 ++++++++++++++ runtime/execdriver/native/driver.go | 12 ------ 4 files changed, 57 insertions(+), 31 deletions(-) diff --git a/pkg/libcontainer/capabilities/capabilities.go b/pkg/libcontainer/capabilities/capabilities.go index fbf73538e0..4b81e708c7 100644 --- a/pkg/libcontainer/capabilities/capabilities.go +++ b/pkg/libcontainer/capabilities/capabilities.go @@ -27,7 +27,9 @@ func DropCapabilities(container *libcontainer.Container) error { func getCapabilitiesMask(container *libcontainer.Container) []capability.Cap { drop := []capability.Cap{} for _, c := range container.CapabilitiesMask { - drop = append(drop, c.Value) + if !c.Enabled { + drop = append(drop, c.Value) + } } return drop } diff --git a/pkg/libcontainer/types.go b/pkg/libcontainer/types.go index 87346348bc..7751e850b6 100644 --- a/pkg/libcontainer/types.go +++ b/pkg/libcontainer/types.go @@ -18,21 +18,21 @@ var ( namespaceList = Namespaces{} capabilityList = Capabilities{ - {Key: "SETPCAP", Value: capability.CAP_SETPCAP, Enabled: true}, - {Key: "SYS_MODULE", Value: capability.CAP_SYS_MODULE, Enabled: true}, - {Key: "SYS_RAWIO", Value: capability.CAP_SYS_RAWIO, Enabled: true}, - {Key: "SYS_PACCT", Value: capability.CAP_SYS_PACCT, Enabled: true}, - {Key: "SYS_ADMIN", Value: capability.CAP_SYS_ADMIN, Enabled: true}, - {Key: "SYS_NICE", Value: capability.CAP_SYS_NICE, Enabled: true}, - {Key: "SYS_RESOURCE", Value: capability.CAP_SYS_RESOURCE, Enabled: true}, - {Key: "SYS_TIME", Value: capability.CAP_SYS_TIME, Enabled: true}, - {Key: "SYS_TTY_CONFIG", Value: capability.CAP_SYS_TTY_CONFIG, Enabled: true}, - {Key: "MKNOD", Value: capability.CAP_MKNOD, Enabled: true}, - {Key: "AUDIT_WRITE", Value: capability.CAP_AUDIT_WRITE, Enabled: true}, - {Key: "AUDIT_CONTROL", Value: capability.CAP_AUDIT_CONTROL, Enabled: true}, - {Key: "MAC_OVERRIDE", Value: capability.CAP_MAC_OVERRIDE, Enabled: true}, - {Key: "MAC_ADMIN", Value: capability.CAP_MAC_ADMIN, Enabled: true}, - {Key: "NET_ADMIN", Value: capability.CAP_NET_ADMIN, Enabled: true}, + {Key: "SETPCAP", Value: capability.CAP_SETPCAP, Enabled: false}, + {Key: "SYS_MODULE", Value: capability.CAP_SYS_MODULE, Enabled: false}, + {Key: "SYS_RAWIO", Value: capability.CAP_SYS_RAWIO, Enabled: false}, + {Key: "SYS_PACCT", Value: capability.CAP_SYS_PACCT, Enabled: false}, + {Key: "SYS_ADMIN", Value: capability.CAP_SYS_ADMIN, Enabled: false}, + {Key: "SYS_NICE", Value: capability.CAP_SYS_NICE, Enabled: false}, + {Key: "SYS_RESOURCE", Value: capability.CAP_SYS_RESOURCE, Enabled: false}, + {Key: "SYS_TIME", Value: capability.CAP_SYS_TIME, Enabled: false}, + {Key: "SYS_TTY_CONFIG", Value: capability.CAP_SYS_TTY_CONFIG, Enabled: false}, + {Key: "MKNOD", Value: capability.CAP_MKNOD, Enabled: false}, + {Key: "AUDIT_WRITE", Value: capability.CAP_AUDIT_WRITE, Enabled: false}, + {Key: "AUDIT_CONTROL", Value: capability.CAP_AUDIT_CONTROL, Enabled: false}, + {Key: "MAC_OVERRIDE", Value: capability.CAP_MAC_OVERRIDE, Enabled: false}, + {Key: "MAC_ADMIN", Value: capability.CAP_MAC_ADMIN, Enabled: false}, + {Key: "NET_ADMIN", Value: capability.CAP_NET_ADMIN, Enabled: false}, } ) @@ -86,7 +86,8 @@ func (c *Capability) String() string { func GetCapability(key string) *Capability { for _, capp := range capabilityList { if capp.Key == key { - return capp + cpy := *capp + return &cpy } } return nil @@ -95,10 +96,14 @@ func GetCapability(key string) *Capability { // Contains returns true if the specified Capability is // in the slice func (c Capabilities) Contains(capp string) bool { + return c.Get(capp) != nil +} + +func (c Capabilities) Get(capp string) *Capability { for _, cap := range c { if cap.Key == capp { - return true + return cap } } - return false + return nil } diff --git a/runtime/execdriver/native/default_template.go b/runtime/execdriver/native/default_template.go index d744ab382f..d47a5eb8cd 100644 --- a/runtime/execdriver/native/default_template.go +++ b/runtime/execdriver/native/default_template.go @@ -6,6 +6,7 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/runtime/execdriver" "os" + "strings" ) // createContainer populates and configures the container type with the @@ -63,9 +64,39 @@ func createContainer(c *execdriver.Command) *libcontainer.Container { container.Mounts = append(container.Mounts, libcontainer.Mount{m.Source, m.Destination, m.Writable, m.Private}) } + configureCustomOptions(container, c.Config["native"]) + return container } +// configureCustomOptions takes string commands from the user and allows modification of the +// container's default configuration. +// +// format: +// i.e: cap +MKNOD cap -NET_ADMIN +// i.e: cgroup devices.allow *:* +func configureCustomOptions(container *libcontainer.Container, opts []string) { + for _, opt := range opts { + parts := strings.Split(strings.TrimSpace(opt), " ") + switch parts[0] { + case "cap": + value := strings.TrimSpace(parts[1]) + c := container.CapabilitiesMask.Get(value[1:]) + if c == nil { + continue + } + switch value[0] { + case '-': + c.Enabled = false + case '+': + c.Enabled = true + default: + // do error here + } + } + } +} + // getDefaultTemplate returns the docker default for // the libcontainer configuration file func getDefaultTemplate() *libcontainer.Container { diff --git a/runtime/execdriver/native/driver.go b/runtime/execdriver/native/driver.go index 0a09d324db..0d9297191c 100644 --- a/runtime/execdriver/native/driver.go +++ b/runtime/execdriver/native/driver.go @@ -75,9 +75,6 @@ func NewDriver(root, initPath string) (*driver, error) { } func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { - if err := d.validateCommand(c); err != nil { - return -1, err - } var ( term nsinit.Terminal container = createContainer(c) @@ -181,15 +178,6 @@ func (d *driver) removeContainerRoot(id string) error { return os.RemoveAll(filepath.Join(d.root, id)) } -func (d *driver) validateCommand(c *execdriver.Command) error { - // we need to check the Config of the command to make sure that we - // do not have any of the lxc-conf variables - for _, conf := range c.Config["native"] { - log.Println(conf) - } - return nil -} - func getEnv(key string, env []string) string { for _, pair := range env { parts := strings.Split(pair, "=") From 70f3b9f4ce67ee54ec226814cdd26db01f69378d Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Mar 2014 00:23:34 +0000 Subject: [PATCH 183/384] Add ability to work with individual namespaces Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/command.go | 4 +++- pkg/libcontainer/types.go | 11 ++++++++--- runtime/execdriver/native/default_template.go | 16 ++++++++++++++-- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/pkg/libcontainer/nsinit/command.go b/pkg/libcontainer/nsinit/command.go index 5546065b6d..153a48ab59 100644 --- a/pkg/libcontainer/nsinit/command.go +++ b/pkg/libcontainer/nsinit/command.go @@ -39,7 +39,9 @@ func (c *DefaultCommandFactory) Create(container *libcontainer.Container, consol // flags on clone, unshare, and setns func GetNamespaceFlags(namespaces libcontainer.Namespaces) (flag int) { for _, ns := range namespaces { - flag |= ns.Value + if ns.Enabled { + flag |= ns.Value + } } return flag } diff --git a/pkg/libcontainer/types.go b/pkg/libcontainer/types.go index 7751e850b6..ffeb55a022 100644 --- a/pkg/libcontainer/types.go +++ b/pkg/libcontainer/types.go @@ -53,7 +53,8 @@ func (ns *Namespace) String() string { func GetNamespace(key string) *Namespace { for _, ns := range namespaceList { if ns.Key == key { - return ns + cpy := *ns + return &cpy } } return nil @@ -62,12 +63,16 @@ func GetNamespace(key string) *Namespace { // Contains returns true if the specified Namespace is // in the slice func (n Namespaces) Contains(ns string) bool { + return n.Get(ns) != nil +} + +func (n Namespaces) Get(ns string) *Namespace { for _, nsp := range n { if nsp.Key == ns { - return true + return nsp } } - return false + return nil } type ( diff --git a/runtime/execdriver/native/default_template.go b/runtime/execdriver/native/default_template.go index d47a5eb8cd..dbb7a45ae7 100644 --- a/runtime/execdriver/native/default_template.go +++ b/runtime/execdriver/native/default_template.go @@ -77,10 +77,12 @@ func createContainer(c *execdriver.Command) *libcontainer.Container { // i.e: cgroup devices.allow *:* func configureCustomOptions(container *libcontainer.Container, opts []string) { for _, opt := range opts { - parts := strings.Split(strings.TrimSpace(opt), " ") + var ( + parts = strings.Split(strings.TrimSpace(opt), " ") + value = strings.TrimSpace(parts[1]) + ) switch parts[0] { case "cap": - value := strings.TrimSpace(parts[1]) c := container.CapabilitiesMask.Get(value[1:]) if c == nil { continue @@ -93,6 +95,16 @@ func configureCustomOptions(container *libcontainer.Container, opts []string) { default: // do error here } + case "ns": + ns := container.Namespaces.Get(value[1:]) + switch value[0] { + case '-': + ns.Enabled = false + case '+': + ns.Enabled = true + default: + // error + } } } } From be5538d8a8820ac1192c7a5660e0d950927b42d0 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Mar 2014 00:48:17 +0000 Subject: [PATCH 184/384] Allow containers to join the net namespace of other conatiners Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/network/netns.go | 10 +------ runtime/execdriver/native/default_template.go | 28 +++++++++++++++++-- runtime/execdriver/native/driver.go | 14 ++++++---- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/pkg/libcontainer/network/netns.go b/pkg/libcontainer/network/netns.go index 3eb8ee587a..7e311f22d8 100644 --- a/pkg/libcontainer/network/netns.go +++ b/pkg/libcontainer/network/netns.go @@ -14,13 +14,7 @@ type NetNS struct { } func (v *NetNS) Create(n *libcontainer.Network, nspid int, context libcontainer.Context) error { - nsname, exists := n.Context["nsname"] - - if !exists { - return fmt.Errorf("nspath does not exist in network context") - } - - context["nspath"] = fmt.Sprintf("/var/run/netns/%s", nsname) + context["nspath"] = n.Context["nspath"] return nil } @@ -29,12 +23,10 @@ func (v *NetNS) Initialize(config *libcontainer.Network, context libcontainer.Co if !exists { return fmt.Errorf("nspath does not exist in network context") } - f, err := os.OpenFile(nspath, os.O_RDONLY, 0) if err != nil { return fmt.Errorf("failed get network namespace fd: %v", err) } - if err := system.Setns(f.Fd(), syscall.CLONE_NEWNET); err != nil { return fmt.Errorf("failed to setns current network namespace: %v", err) } diff --git a/runtime/execdriver/native/default_template.go b/runtime/execdriver/native/default_template.go index dbb7a45ae7..890e260ad2 100644 --- a/runtime/execdriver/native/default_template.go +++ b/runtime/execdriver/native/default_template.go @@ -6,12 +6,13 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/runtime/execdriver" "os" + "path/filepath" "strings" ) // createContainer populates and configures the container type with the // data provided by the execdriver.Command -func createContainer(c *execdriver.Command) *libcontainer.Container { +func (d *driver) createContainer(c *execdriver.Command) *libcontainer.Container { container := getDefaultTemplate() container.Hostname = getEnv("HOSTNAME", c.Env) @@ -64,7 +65,7 @@ func createContainer(c *execdriver.Command) *libcontainer.Container { container.Mounts = append(container.Mounts, libcontainer.Mount{m.Source, m.Destination, m.Writable, m.Private}) } - configureCustomOptions(container, c.Config["native"]) + d.configureCustomOptions(container, c.Config["native"]) return container } @@ -75,7 +76,8 @@ func createContainer(c *execdriver.Command) *libcontainer.Container { // format: // i.e: cap +MKNOD cap -NET_ADMIN // i.e: cgroup devices.allow *:* -func configureCustomOptions(container *libcontainer.Container, opts []string) { +// i.e: net join +func (d *driver) configureCustomOptions(container *libcontainer.Container, opts []string) { for _, opt := range opts { var ( parts = strings.Split(strings.TrimSpace(opt), " ") @@ -105,6 +107,26 @@ func configureCustomOptions(container *libcontainer.Container, opts []string) { default: // error } + case "net": + switch strings.TrimSpace(parts[1]) { + case "join": + var ( + id = strings.TrimSpace(parts[2]) + cmd = d.activeContainers[id] + nspath = filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "net") + ) + + container.Networks = append(container.Networks, &libcontainer.Network{ + Type: "netns", + Context: libcontainer.Context{ + "nspath": nspath, + }, + }) + default: + // error + } + default: + // error not defined } } } diff --git a/runtime/execdriver/native/driver.go b/runtime/execdriver/native/driver.go index 0d9297191c..b998db743d 100644 --- a/runtime/execdriver/native/driver.go +++ b/runtime/execdriver/native/driver.go @@ -57,8 +57,9 @@ func init() { } type driver struct { - root string - initPath string + root string + initPath string + activeContainers map[string]*execdriver.Command } func NewDriver(root, initPath string) (*driver, error) { @@ -69,15 +70,18 @@ func NewDriver(root, initPath string) (*driver, error) { return nil, err } return &driver{ - root: root, - initPath: initPath, + root: root, + initPath: initPath, + activeContainers: make(map[string]*execdriver.Command), }, nil } func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { + d.activeContainers[c.ID] = c + var ( term nsinit.Terminal - container = createContainer(c) + container = d.createContainer(c) factory = &dockerCommandFactory{c: c, driver: d} stateWriter = &dockerStateWriter{ callback: startCallback, From 79c11b19ecd506bd76db391b896cec0d4263183d Mon Sep 17 00:00:00 2001 From: alambike Date: Fri, 21 Mar 2014 03:13:06 +0100 Subject: [PATCH 185/384] Added Eixo::Docker to the list of libraries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docker-DCO-1.1-Signed-off-by: Javier Gómez (github: alambike) --- docs/sources/reference/api/remote_api_client_libraries.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/reference/api/remote_api_client_libraries.rst b/docs/sources/reference/api/remote_api_client_libraries.rst index f74dd416bc..4a445db36f 100644 --- a/docs/sources/reference/api/remote_api_client_libraries.rst +++ b/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 | ++----------------------+----------------+--------------------------------------------+----------+ From 4002eac8b8b4007de03e78dbd57232fac583d05b Mon Sep 17 00:00:00 2001 From: Ken ICHIKAWA Date: Wed, 19 Mar 2014 12:05:54 +0900 Subject: [PATCH 186/384] Fix since time exit display when s.FinishedAt is zero When s.FinishedAt is zero, the since time exit in docker ps doesn't display correct time. For example ``` Exited (0) 292.471209 years ago ``` This patch fixes the since time exit to display nothing if s.FinishedAt is zero. Docker-DCO-1.1-Signed-off-by: Ken ICHIKAWA (github: ichik1) --- runtime/state.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/runtime/state.go b/runtime/state.go index 1c682acd26..316b8a40f1 100644 --- a/runtime/state.go +++ b/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))) } From 7c726669cbcc0cfde12c6a9f03974bb672839271 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Mar 2014 08:10:07 +0000 Subject: [PATCH 187/384] Factor out the native driver config options Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runtime/container.go | 4 + .../execdriver/native/configuration/caps.go | 27 ++++ .../execdriver/native/configuration/net.go | 35 +++++ runtime/execdriver/native/configuration/ns.go | 26 ++++ .../execdriver/native/configuration/parse.go | 37 +++++ runtime/execdriver/native/create.go | 70 ++++++++++ runtime/execdriver/native/default_template.go | 126 ------------------ runtime/execdriver/native/driver.go | 12 +- 8 files changed, 207 insertions(+), 130 deletions(-) create mode 100644 runtime/execdriver/native/configuration/caps.go create mode 100644 runtime/execdriver/native/configuration/net.go create mode 100644 runtime/execdriver/native/configuration/ns.go create mode 100644 runtime/execdriver/native/configuration/parse.go create mode 100644 runtime/execdriver/native/create.go diff --git a/runtime/container.go b/runtime/container.go index 488d905f4b..1f0f82eebb 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -364,6 +364,10 @@ func populateCommand(c *Container) { driverConfig = c.hostConfig.PluginOptions ) + if driverConfig == nil { + driverConfig = make(map[string][]string) + } + en = &execdriver.Network{ Mtu: c.runtime.config.Mtu, Interface: nil, diff --git a/runtime/execdriver/native/configuration/caps.go b/runtime/execdriver/native/configuration/caps.go new file mode 100644 index 0000000000..f4de470684 --- /dev/null +++ b/runtime/execdriver/native/configuration/caps.go @@ -0,0 +1,27 @@ +package configuration + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/libcontainer" + "strings" +) + +// i.e: cap +MKNOD cap -NET_ADMIN +func parseCapOpt(container *libcontainer.Container, opts []string) error { + var ( + value = strings.TrimSpace(opts[0]) + c = container.CapabilitiesMask.Get(value[1:]) + ) + if c == nil { + return fmt.Errorf("%s is not a valid capability", value[1:]) + } + switch value[0] { + case '-': + c.Enabled = false + case '+': + c.Enabled = true + default: + return fmt.Errorf("%c is not a valid modifier for capabilities", value[0]) + } + return nil +} diff --git a/runtime/execdriver/native/configuration/net.go b/runtime/execdriver/native/configuration/net.go new file mode 100644 index 0000000000..cac7f658ba --- /dev/null +++ b/runtime/execdriver/native/configuration/net.go @@ -0,0 +1,35 @@ +package configuration + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/libcontainer" + "os/exec" + "path/filepath" + "strings" +) + +// i.e: net join +func parseNetOpt(container *libcontainer.Container, running map[string]*exec.Cmd, opts []string) error { + opt := strings.TrimSpace(opts[1]) + switch opt { + case "join": + var ( + id = strings.TrimSpace(opts[2]) + cmd = running[id] + ) + + if cmd == nil || cmd.Process == nil { + return fmt.Errorf("%s is not a valid running container to join", id) + } + nspath := filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "net") + container.Networks = append(container.Networks, &libcontainer.Network{ + Type: "netns", + Context: libcontainer.Context{ + "nspath": nspath, + }, + }) + default: + return fmt.Errorf("%s is not a valid network option", opt) + } + return nil +} diff --git a/runtime/execdriver/native/configuration/ns.go b/runtime/execdriver/native/configuration/ns.go new file mode 100644 index 0000000000..ff7f367196 --- /dev/null +++ b/runtime/execdriver/native/configuration/ns.go @@ -0,0 +1,26 @@ +package configuration + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/libcontainer" + "strings" +) + +func parseNsOpt(container *libcontainer.Container, opts []string) error { + var ( + value = strings.TrimSpace(opts[0]) + ns = container.Namespaces.Get(value[1:]) + ) + if ns == nil { + return fmt.Errorf("%s is not a valid namespace", value[1:]) + } + switch value[0] { + case '-': + ns.Enabled = false + case '+': + ns.Enabled = true + default: + return fmt.Errorf("%c is not a valid modifier for namespaces", value[0]) + } + return nil +} diff --git a/runtime/execdriver/native/configuration/parse.go b/runtime/execdriver/native/configuration/parse.go new file mode 100644 index 0000000000..08b98fbd12 --- /dev/null +++ b/runtime/execdriver/native/configuration/parse.go @@ -0,0 +1,37 @@ +package configuration + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/libcontainer" + "os/exec" + "strings" +) + +// configureCustomOptions takes string commands from the user and allows modification of the +// container's default configuration. +// +// format: <...value> +// i.e: cgroup devices.allow *:* +func ParseConfiguration(container *libcontainer.Container, running map[string]*exec.Cmd, opts []string) error { + for _, opt := range opts { + var ( + err error + parts = strings.Split(strings.TrimSpace(opt), " ") + ) + + switch parts[0] { + case "cap": + err = parseCapOpt(container, parts[1:]) + case "ns": + err = parseNsOpt(container, parts[1:]) + case "net": + err = parseNetOpt(container, running, parts[1:]) + default: + return fmt.Errorf("%s is not a valid configuration option for the native driver", parts[0]) + } + if err != nil { + return err + } + } + return nil +} diff --git a/runtime/execdriver/native/create.go b/runtime/execdriver/native/create.go new file mode 100644 index 0000000000..7118edc91e --- /dev/null +++ b/runtime/execdriver/native/create.go @@ -0,0 +1,70 @@ +package native + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/runtime/execdriver" + "github.com/dotcloud/docker/runtime/execdriver/native/configuration" + "os" +) + +// createContainer populates and configures the container type with the +// data provided by the execdriver.Command +func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Container, error) { + container := getDefaultTemplate() + + container.Hostname = getEnv("HOSTNAME", c.Env) + container.Tty = c.Tty + container.User = c.User + container.WorkingDir = c.WorkingDir + container.Env = c.Env + + loopbackNetwork := libcontainer.Network{ + Mtu: c.Network.Mtu, + Address: fmt.Sprintf("%s/%d", "127.0.0.1", 0), + Gateway: "localhost", + Type: "loopback", + Context: libcontainer.Context{}, + } + + container.Networks = []*libcontainer.Network{ + &loopbackNetwork, + } + + if c.Network.Interface != nil { + vethNetwork := libcontainer.Network{ + Mtu: c.Network.Mtu, + Address: fmt.Sprintf("%s/%d", c.Network.Interface.IPAddress, c.Network.Interface.IPPrefixLen), + Gateway: c.Network.Interface.Gateway, + Type: "veth", + Context: libcontainer.Context{ + "prefix": "veth", + "bridge": c.Network.Interface.Bridge, + }, + } + container.Networks = append(container.Networks, &vethNetwork) + } + + container.Cgroups.Name = c.ID + if c.Privileged { + container.CapabilitiesMask = nil + container.Cgroups.DeviceAccess = true + container.Context["apparmor_profile"] = "unconfined" + } + if c.Resources != nil { + container.Cgroups.CpuShares = c.Resources.CpuShares + container.Cgroups.Memory = c.Resources.Memory + container.Cgroups.MemorySwap = c.Resources.MemorySwap + } + // check to see if we are running in ramdisk to disable pivot root + container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != "" + + for _, m := range c.Mounts { + container.Mounts = append(container.Mounts, libcontainer.Mount{m.Source, m.Destination, m.Writable, m.Private}) + } + + if err := configuration.ParseConfiguration(container, d.activeContainers, c.Config["native"]); err != nil { + return nil, err + } + return container, nil +} diff --git a/runtime/execdriver/native/default_template.go b/runtime/execdriver/native/default_template.go index 890e260ad2..0dcd7db356 100644 --- a/runtime/execdriver/native/default_template.go +++ b/runtime/execdriver/native/default_template.go @@ -1,136 +1,10 @@ package native import ( - "fmt" "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/runtime/execdriver" - "os" - "path/filepath" - "strings" ) -// createContainer populates and configures the container type with the -// data provided by the execdriver.Command -func (d *driver) createContainer(c *execdriver.Command) *libcontainer.Container { - container := getDefaultTemplate() - - container.Hostname = getEnv("HOSTNAME", c.Env) - container.Tty = c.Tty - container.User = c.User - container.WorkingDir = c.WorkingDir - container.Env = c.Env - - loopbackNetwork := libcontainer.Network{ - Mtu: c.Network.Mtu, - Address: fmt.Sprintf("%s/%d", "127.0.0.1", 0), - Gateway: "localhost", - Type: "loopback", - Context: libcontainer.Context{}, - } - - container.Networks = []*libcontainer.Network{ - &loopbackNetwork, - } - - if c.Network.Interface != nil { - vethNetwork := libcontainer.Network{ - Mtu: c.Network.Mtu, - Address: fmt.Sprintf("%s/%d", c.Network.Interface.IPAddress, c.Network.Interface.IPPrefixLen), - Gateway: c.Network.Interface.Gateway, - Type: "veth", - Context: libcontainer.Context{ - "prefix": "veth", - "bridge": c.Network.Interface.Bridge, - }, - } - container.Networks = append(container.Networks, &vethNetwork) - } - - container.Cgroups.Name = c.ID - if c.Privileged { - container.CapabilitiesMask = nil - container.Cgroups.DeviceAccess = true - container.Context["apparmor_profile"] = "unconfined" - } - if c.Resources != nil { - container.Cgroups.CpuShares = c.Resources.CpuShares - container.Cgroups.Memory = c.Resources.Memory - container.Cgroups.MemorySwap = c.Resources.MemorySwap - } - // check to see if we are running in ramdisk to disable pivot root - container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != "" - - for _, m := range c.Mounts { - container.Mounts = append(container.Mounts, libcontainer.Mount{m.Source, m.Destination, m.Writable, m.Private}) - } - - d.configureCustomOptions(container, c.Config["native"]) - - return container -} - -// configureCustomOptions takes string commands from the user and allows modification of the -// container's default configuration. -// -// format: -// i.e: cap +MKNOD cap -NET_ADMIN -// i.e: cgroup devices.allow *:* -// i.e: net join -func (d *driver) configureCustomOptions(container *libcontainer.Container, opts []string) { - for _, opt := range opts { - var ( - parts = strings.Split(strings.TrimSpace(opt), " ") - value = strings.TrimSpace(parts[1]) - ) - switch parts[0] { - case "cap": - c := container.CapabilitiesMask.Get(value[1:]) - if c == nil { - continue - } - switch value[0] { - case '-': - c.Enabled = false - case '+': - c.Enabled = true - default: - // do error here - } - case "ns": - ns := container.Namespaces.Get(value[1:]) - switch value[0] { - case '-': - ns.Enabled = false - case '+': - ns.Enabled = true - default: - // error - } - case "net": - switch strings.TrimSpace(parts[1]) { - case "join": - var ( - id = strings.TrimSpace(parts[2]) - cmd = d.activeContainers[id] - nspath = filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "net") - ) - - container.Networks = append(container.Networks, &libcontainer.Network{ - Type: "netns", - Context: libcontainer.Context{ - "nspath": nspath, - }, - }) - default: - // error - } - default: - // error not defined - } - } -} - // getDefaultTemplate returns the docker default for // the libcontainer configuration file func getDefaultTemplate() *libcontainer.Container { diff --git a/runtime/execdriver/native/driver.go b/runtime/execdriver/native/driver.go index b998db743d..4acc4b388c 100644 --- a/runtime/execdriver/native/driver.go +++ b/runtime/execdriver/native/driver.go @@ -59,7 +59,7 @@ func init() { type driver struct { root string initPath string - activeContainers map[string]*execdriver.Command + activeContainers map[string]*exec.Cmd } func NewDriver(root, initPath string) (*driver, error) { @@ -72,16 +72,20 @@ func NewDriver(root, initPath string) (*driver, error) { return &driver{ root: root, initPath: initPath, - activeContainers: make(map[string]*execdriver.Command), + activeContainers: make(map[string]*exec.Cmd), }, nil } func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { - d.activeContainers[c.ID] = c + // take the Command and populate the libcontainer.Container from it + container, err := d.createContainer(c) + if err != nil { + return -1, err + } + d.activeContainers[c.ID] = &c.Cmd var ( term nsinit.Terminal - container = d.createContainer(c) factory = &dockerCommandFactory{c: c, driver: d} stateWriter = &dockerStateWriter{ callback: startCallback, From c9d7f858fd1c6e2d1287e28ee12952333b327c75 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Mar 2014 11:53:15 +0000 Subject: [PATCH 188/384] Change flag to -o and --opt Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runconfig/hostconfig.go | 4 ++-- runconfig/parse.go | 20 ++++++++++++------- runtime/container.go | 2 +- runtime/execdriver/native/configuration/fs.go | 19 ++++++++++++++++++ .../execdriver/native/configuration/parse.go | 3 +++ 5 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 runtime/execdriver/native/configuration/fs.go diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 8ee2288b4b..b564f98cd3 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -13,7 +13,7 @@ type HostConfig struct { PortBindings nat.PortMap Links []string PublishAllPorts bool - PluginOptions map[string][]string + DriverOptions map[string][]string } type KeyValuePair struct { @@ -29,7 +29,7 @@ func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { } job.GetenvJson("LxcConf", &hostConfig.LxcConf) job.GetenvJson("PortBindings", &hostConfig.PortBindings) - job.GetenvJson("PluginOptions", &hostConfig.PluginOptions) + job.GetenvJson("DriverOptions", &hostConfig.DriverOptions) if Binds := job.GetenvList("Binds"); Binds != nil { hostConfig.Binds = Binds } diff --git a/runconfig/parse.go b/runconfig/parse.go index afcaec304f..2f51dface2 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -45,7 +45,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf flDnsSearch = opts.NewListOpts(opts.ValidateDomain) flVolumesFrom opts.ListOpts flLxcOpts opts.ListOpts - flPluginOpts opts.ListOpts + flDriverOpts opts.ListOpts flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)") flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: Run container in the background, print new container id") @@ -77,8 +77,8 @@ 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(&flPluginOpts, []string{"-plugin"}, "Add custom plugin options") + cmd.Var(&flLxcOpts, []string{"#lxc-conf", "#-lxc-conf"}, "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 { return nil, nil, cmd, err @@ -208,7 +208,10 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf WorkingDir: *flWorkingDir, } - pluginOptions := parsePluginOpts(flPluginOpts) + pluginOptions, err := parseDriverOpts(flDriverOpts) + if err != nil { + return nil, nil, cmd, err + } hostConfig := &HostConfig{ Binds: binds, @@ -218,7 +221,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf PortBindings: portBindings, Links: flLinks.GetAll(), PublishAllPorts: *flPublishAll, - PluginOptions: pluginOptions, + DriverOptions: pluginOptions, } if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit { @@ -253,15 +256,18 @@ func parseLxcOpt(opt string) (string, string, error) { return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil } -func parsePluginOpts(opts opts.ListOpts) map[string][]string { +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[parts[0]] = append(values, parts[1]) } - return out + return out, nil } diff --git a/runtime/container.go b/runtime/container.go index 1f0f82eebb..ee5045e374 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -361,7 +361,7 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s func populateCommand(c *Container) { var ( en *execdriver.Network - driverConfig = c.hostConfig.PluginOptions + driverConfig = c.hostConfig.DriverOptions ) if driverConfig == nil { diff --git a/runtime/execdriver/native/configuration/fs.go b/runtime/execdriver/native/configuration/fs.go new file mode 100644 index 0000000000..76fb2f08da --- /dev/null +++ b/runtime/execdriver/native/configuration/fs.go @@ -0,0 +1,19 @@ +package configuration + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/libcontainer" + "strings" +) + +func parseFsOpts(container *libcontainer.Container, opts []string) error { + opt := strings.TrimSpace(opts[0]) + + switch opt { + case "readonly": + container.ReadonlyFs = true + default: + return fmt.Errorf("%s is not a valid filesystem option", opt) + } + return nil +} diff --git a/runtime/execdriver/native/configuration/parse.go b/runtime/execdriver/native/configuration/parse.go index 08b98fbd12..083fd43371 100644 --- a/runtime/execdriver/native/configuration/parse.go +++ b/runtime/execdriver/native/configuration/parse.go @@ -18,6 +18,9 @@ func ParseConfiguration(container *libcontainer.Container, running map[string]*e err error parts = strings.Split(strings.TrimSpace(opt), " ") ) + if len(parts) < 2 { + return fmt.Errorf("invalid native driver opt %s", opt) + } switch parts[0] { case "cap": From 146a212f71fe129f9d349c5c3e80ba4197e35850 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Mar 2014 12:38:50 +0000 Subject: [PATCH 189/384] Change syntax to use dots Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runconfig/parse.go | 3 +- .../execdriver/native/configuration/caps.go | 27 ----- runtime/execdriver/native/configuration/fs.go | 19 ---- .../execdriver/native/configuration/net.go | 35 ------ runtime/execdriver/native/configuration/ns.go | 26 ----- .../execdriver/native/configuration/parse.go | 101 +++++++++++++++--- 6 files changed, 87 insertions(+), 124 deletions(-) delete mode 100644 runtime/execdriver/native/configuration/caps.go delete mode 100644 runtime/execdriver/native/configuration/fs.go delete mode 100644 runtime/execdriver/native/configuration/net.go delete mode 100644 runtime/execdriver/native/configuration/ns.go diff --git a/runconfig/parse.go b/runconfig/parse.go index 2f51dface2..b03f8732ee 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -256,10 +256,11 @@ func parseLxcOpt(opt string) (string, string, error) { return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil } +// options will come in the format of name.type=value 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) + parts := strings.SplitN(o, ".", 2) if len(parts) < 2 { return nil, fmt.Errorf("invalid opt format %s", o) } diff --git a/runtime/execdriver/native/configuration/caps.go b/runtime/execdriver/native/configuration/caps.go deleted file mode 100644 index f4de470684..0000000000 --- a/runtime/execdriver/native/configuration/caps.go +++ /dev/null @@ -1,27 +0,0 @@ -package configuration - -import ( - "fmt" - "github.com/dotcloud/docker/pkg/libcontainer" - "strings" -) - -// i.e: cap +MKNOD cap -NET_ADMIN -func parseCapOpt(container *libcontainer.Container, opts []string) error { - var ( - value = strings.TrimSpace(opts[0]) - c = container.CapabilitiesMask.Get(value[1:]) - ) - if c == nil { - return fmt.Errorf("%s is not a valid capability", value[1:]) - } - switch value[0] { - case '-': - c.Enabled = false - case '+': - c.Enabled = true - default: - return fmt.Errorf("%c is not a valid modifier for capabilities", value[0]) - } - return nil -} diff --git a/runtime/execdriver/native/configuration/fs.go b/runtime/execdriver/native/configuration/fs.go deleted file mode 100644 index 76fb2f08da..0000000000 --- a/runtime/execdriver/native/configuration/fs.go +++ /dev/null @@ -1,19 +0,0 @@ -package configuration - -import ( - "fmt" - "github.com/dotcloud/docker/pkg/libcontainer" - "strings" -) - -func parseFsOpts(container *libcontainer.Container, opts []string) error { - opt := strings.TrimSpace(opts[0]) - - switch opt { - case "readonly": - container.ReadonlyFs = true - default: - return fmt.Errorf("%s is not a valid filesystem option", opt) - } - return nil -} diff --git a/runtime/execdriver/native/configuration/net.go b/runtime/execdriver/native/configuration/net.go deleted file mode 100644 index cac7f658ba..0000000000 --- a/runtime/execdriver/native/configuration/net.go +++ /dev/null @@ -1,35 +0,0 @@ -package configuration - -import ( - "fmt" - "github.com/dotcloud/docker/pkg/libcontainer" - "os/exec" - "path/filepath" - "strings" -) - -// i.e: net join -func parseNetOpt(container *libcontainer.Container, running map[string]*exec.Cmd, opts []string) error { - opt := strings.TrimSpace(opts[1]) - switch opt { - case "join": - var ( - id = strings.TrimSpace(opts[2]) - cmd = running[id] - ) - - if cmd == nil || cmd.Process == nil { - return fmt.Errorf("%s is not a valid running container to join", id) - } - nspath := filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "net") - container.Networks = append(container.Networks, &libcontainer.Network{ - Type: "netns", - Context: libcontainer.Context{ - "nspath": nspath, - }, - }) - default: - return fmt.Errorf("%s is not a valid network option", opt) - } - return nil -} diff --git a/runtime/execdriver/native/configuration/ns.go b/runtime/execdriver/native/configuration/ns.go deleted file mode 100644 index ff7f367196..0000000000 --- a/runtime/execdriver/native/configuration/ns.go +++ /dev/null @@ -1,26 +0,0 @@ -package configuration - -import ( - "fmt" - "github.com/dotcloud/docker/pkg/libcontainer" - "strings" -) - -func parseNsOpt(container *libcontainer.Container, opts []string) error { - var ( - value = strings.TrimSpace(opts[0]) - ns = container.Namespaces.Get(value[1:]) - ) - if ns == nil { - return fmt.Errorf("%s is not a valid namespace", value[1:]) - } - switch value[0] { - case '-': - ns.Enabled = false - case '+': - ns.Enabled = true - default: - return fmt.Errorf("%c is not a valid modifier for namespaces", value[0]) - } - return nil -} diff --git a/runtime/execdriver/native/configuration/parse.go b/runtime/execdriver/native/configuration/parse.go index 083fd43371..0003d724b3 100644 --- a/runtime/execdriver/native/configuration/parse.go +++ b/runtime/execdriver/native/configuration/parse.go @@ -4,9 +4,86 @@ import ( "fmt" "github.com/dotcloud/docker/pkg/libcontainer" "os/exec" + "path/filepath" "strings" ) +type Action func(*libcontainer.Container, interface{}, string) error + +var actions = map[string]Action{ + "cap.add": addCap, + "cap.drop": dropCap, + "fs.readonly": readonlyFs, + "ns.add": addNamespace, + "ns.drop": dropNamespace, + "net.join": joinNetNamespace, +} + +func addCap(container *libcontainer.Container, context interface{}, value string) error { + c := container.CapabilitiesMask.Get(value) + if c == nil { + return fmt.Errorf("%s is not a valid capability", value) + } + c.Enabled = true + return nil +} + +func dropCap(container *libcontainer.Container, context interface{}, value string) error { + c := container.CapabilitiesMask.Get(value) + if c == nil { + return fmt.Errorf("%s is not a valid capability", value) + } + c.Enabled = false + return nil +} + +func addNamespace(container *libcontainer.Container, context interface{}, value string) error { + ns := container.Namespaces.Get(value) + if ns == nil { + return fmt.Errorf("%s is not a valid namespace", value[1:]) + } + ns.Enabled = true + return nil +} + +func dropNamespace(container *libcontainer.Container, context interface{}, value string) error { + ns := container.Namespaces.Get(value) + if ns == nil { + return fmt.Errorf("%s is not a valid namespace", value[1:]) + } + ns.Enabled = false + return nil +} + +func readonlyFs(container *libcontainer.Container, context interface{}, value string) error { + switch value { + case "1", "true": + container.ReadonlyFs = true + default: + container.ReadonlyFs = false + } + return nil +} + +func joinNetNamespace(container *libcontainer.Container, context interface{}, value string) error { + var ( + running = context.(map[string]*exec.Cmd) + cmd = running[value] + ) + + if cmd == nil || cmd.Process == nil { + return fmt.Errorf("%s is not a valid running container to join", value) + } + nspath := filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "net") + container.Networks = append(container.Networks, &libcontainer.Network{ + Type: "netns", + Context: libcontainer.Context{ + "nspath": nspath, + }, + }) + return nil +} + // configureCustomOptions takes string commands from the user and allows modification of the // container's default configuration. // @@ -14,25 +91,17 @@ import ( // i.e: cgroup devices.allow *:* func ParseConfiguration(container *libcontainer.Container, running map[string]*exec.Cmd, opts []string) error { for _, opt := range opts { - var ( - err error - parts = strings.Split(strings.TrimSpace(opt), " ") - ) - if len(parts) < 2 { - return fmt.Errorf("invalid native driver opt %s", opt) + kv := strings.SplitN(opt, "=", 2) + if len(kv) < 2 { + return fmt.Errorf("invalid format for %s", opt) } - switch parts[0] { - case "cap": - err = parseCapOpt(container, parts[1:]) - case "ns": - err = parseNsOpt(container, parts[1:]) - case "net": - err = parseNetOpt(container, running, parts[1:]) - default: - return fmt.Errorf("%s is not a valid configuration option for the native driver", parts[0]) + action, exists := actions[kv[0]] + if !exists { + return fmt.Errorf("%s is not a valid option for the native driver", kv[0]) } - if err != nil { + + if err := action(container, running, kv[1]); err != nil { return err } } From 83618c2b81c561cd77fd70eca90b2b251f61fcc1 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Mar 2014 14:07:16 +0000 Subject: [PATCH 190/384] Add more native driver options Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- .../execdriver/native/configuration/parse.go | 80 +++++++++++++++++-- 1 file changed, 74 insertions(+), 6 deletions(-) diff --git a/runtime/execdriver/native/configuration/parse.go b/runtime/execdriver/native/configuration/parse.go index 0003d724b3..1733b94426 100644 --- a/runtime/execdriver/native/configuration/parse.go +++ b/runtime/execdriver/native/configuration/parse.go @@ -5,18 +5,70 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer" "os/exec" "path/filepath" + "strconv" "strings" ) type Action func(*libcontainer.Container, interface{}, string) error var actions = map[string]Action{ - "cap.add": addCap, - "cap.drop": dropCap, - "fs.readonly": readonlyFs, - "ns.add": addNamespace, - "ns.drop": dropNamespace, - "net.join": joinNetNamespace, + "cap.add": addCap, // add a cap + "cap.drop": dropCap, // drop a cap + + "ns.add": addNamespace, // add a namespace + "ns.drop": dropNamespace, // drop a namespace when cloning + + "net.join": joinNetNamespace, // join another containers net namespace + // "net.veth.mac": vethMacAddress, // set the mac address for the veth + + "cgroups.cpu_shares": cpuShares, // set the cpu shares + "cgroups.memory": memory, // set the memory limit + "cgroups.memory_swap": memorySwap, // set the memory swap limit + + "apparmor_profile": apparmorProfile, // set the apparmor profile to apply + + "fs.readonly": readonlyFs, // make the rootfs of the container read only +} + +func apparmorProfile(container *libcontainer.Container, context interface{}, value string) error { + container.Context["apparmor_profile"] = value + return nil +} + +func cpuShares(container *libcontainer.Container, context interface{}, value string) error { + if container.Cgroups == nil { + return fmt.Errorf("cannot set cgroups when they are disabled") + } + v, err := strconv.ParseInt(value, 0, 64) + if err != nil { + return err + } + container.Cgroups.CpuShares = v + return nil +} + +func memory(container *libcontainer.Container, context interface{}, value string) error { + if container.Cgroups == nil { + return fmt.Errorf("cannot set cgroups when they are disabled") + } + v, err := strconv.ParseInt(value, 0, 64) + if err != nil { + return err + } + container.Cgroups.Memory = v + return nil +} + +func memorySwap(container *libcontainer.Container, context interface{}, value string) error { + if container.Cgroups == nil { + return fmt.Errorf("cannot set cgroups when they are disabled") + } + v, err := strconv.ParseInt(value, 0, 64) + if err != nil { + return err + } + container.Cgroups.MemorySwap = v + return nil } func addCap(container *libcontainer.Container, context interface{}, value string) error { @@ -84,6 +136,22 @@ func joinNetNamespace(container *libcontainer.Container, context interface{}, va return nil } +func vethMacAddress(container *libcontainer.Container, context interface{}, value string) error { + var veth *libcontainer.Network + + for _, network := range container.Networks { + if network.Type == "veth" { + veth = network + break + } + } + if veth == nil { + return fmt.Errorf("not veth configured for container") + } + veth.Context["mac"] = value + return nil +} + // configureCustomOptions takes string commands from the user and allows modification of the // container's default configuration. // From 2c58a1e2886433a4266615b1f492f829e7a6f53f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Mar 2014 14:17:17 +0000 Subject: [PATCH 191/384] Change placement of readonly filesystem We need to change it to read only at the very end so that bound, copy dev nodes and other ops do not fail. Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/mount.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index 61a90125e0..19dacfaa17 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/nsinit/mount.go @@ -31,11 +31,6 @@ 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 readonly { - if err := system.Mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, ""); err != nil { - return fmt.Errorf("mounting %s as readonly %s", rootfs, err) - } - } if err := mountSystem(rootfs); err != nil { return fmt.Errorf("mount system %s", err) } @@ -81,6 +76,12 @@ func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, cons } } + if readonly { + if err := system.Mount("/", "/", "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, ""); err != nil { + return fmt.Errorf("mounting %s as readonly %s", rootfs, err) + } + } + system.Umask(0022) return nil From 708ecd7da2125a47abb9678ed382893c7b30f10f Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 12 Mar 2014 01:58:53 -0600 Subject: [PATCH 192/384] Add mention of mounting cgroupfs properly to PACKAGERS.md Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- docs/sources/installation/binaries.rst | 6 ++++++ hack/PACKAGERS.md | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/docs/sources/installation/binaries.rst b/docs/sources/installation/binaries.rst index bfdfbe211f..a367a1a94c 100644 --- a/docs/sources/installation/binaries.rst +++ b/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 + `_ + cgroupfs hierarchy (having a single, all-encompassing "cgroup" mount point `is + `_ `not + `_ `sufficient + `_) Check kernel dependencies diff --git a/hack/PACKAGERS.md b/hack/PACKAGERS.md index 5dcb120689..e525a838a1 100644 --- a/hack/PACKAGERS.md +++ b/hack/PACKAGERS.md @@ -239,6 +239,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: From 9a7be1b015a1ba79e5480d0ddddfa5954b994507 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Mar 2014 14:53:47 +0000 Subject: [PATCH 193/384] Add cpuset.cpus to cgroups and native driver options Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/cgroups/cgroups.go | 32 ++++++++++++++--- .../execdriver/native/configuration/parse.go | 35 +++++++++++++++---- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go index b40e1a31fa..9d485d1080 100644 --- a/pkg/cgroups/cgroups.go +++ b/pkg/cgroups/cgroups.go @@ -16,10 +16,11 @@ type Cgroup struct { Name string `json:"name,omitempty"` Parent string `json:"parent,omitempty"` - DeviceAccess bool `json:"device_access,omitempty"` // name of parent cgroup or slice - Memory int64 `json:"memory,omitempty"` // Memory limit (in bytes) - 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) + DeviceAccess bool `json:"device_access,omitempty"` // name of parent cgroup or slice + Memory int64 `json:"memory,omitempty"` // Memory limit (in bytes) + 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 } // https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt @@ -98,6 +99,7 @@ func (c *Cgroup) Cleanup(root string) error { get("memory"), get("devices"), get("cpu"), + get("cpuset"), } { os.RemoveAll(path) } @@ -150,6 +152,9 @@ func (c *Cgroup) Apply(pid int) error { if err := c.setupCpu(cgroupRoot, pid); err != nil { return err } + if err := c.setupCpuset(cgroupRoot, pid); err != nil { + return err + } return nil } @@ -248,3 +253,22 @@ func (c *Cgroup) setupCpu(cgroupRoot string, pid int) (err error) { } return nil } + +func (c *Cgroup) setupCpuset(cgroupRoot string, pid int) (err error) { + if c.CpusetCpus != "" { + dir, err := c.Join(cgroupRoot, "cpuset", pid) + if err != nil { + return err + } + defer func() { + if err != nil { + os.RemoveAll(dir) + } + }() + + if err := writeFile(dir, "cpuset.cpus", c.CpusetCpus); err != nil { + return err + } + } + return nil +} diff --git a/runtime/execdriver/native/configuration/parse.go b/runtime/execdriver/native/configuration/parse.go index 1733b94426..090cb29660 100644 --- a/runtime/execdriver/native/configuration/parse.go +++ b/runtime/execdriver/native/configuration/parse.go @@ -3,6 +3,7 @@ package configuration import ( "fmt" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/utils" "os/exec" "path/filepath" "strconv" @@ -19,17 +20,40 @@ var actions = map[string]Action{ "ns.drop": dropNamespace, // drop a namespace when cloning "net.join": joinNetNamespace, // join another containers net namespace - // "net.veth.mac": vethMacAddress, // set the mac address for the veth "cgroups.cpu_shares": cpuShares, // set the cpu shares "cgroups.memory": memory, // set the memory limit "cgroups.memory_swap": memorySwap, // set the memory swap limit + "cgroups.cpuset.cpus": cpusetCpus, // set the cpus used "apparmor_profile": apparmorProfile, // set the apparmor profile to apply "fs.readonly": readonlyFs, // make the rootfs of the container read only } +// GetSupportedActions returns a list of all the avaliable actions supported by the driver +// TODO: this should return a description also +func GetSupportedActions() []string { + var ( + i int + out = make([]string, len(actions)) + ) + for k := range actions { + out[i] = k + i++ + } + return out +} + +func cpusetCpus(container *libcontainer.Container, context interface{}, value string) error { + if container.Cgroups == nil { + return fmt.Errorf("cannot set cgroups when they are disabled") + } + container.Cgroups.CpusetCpus = value + + return nil +} + func apparmorProfile(container *libcontainer.Container, context interface{}, value string) error { container.Context["apparmor_profile"] = value return nil @@ -39,7 +63,7 @@ func cpuShares(container *libcontainer.Container, context interface{}, value str if container.Cgroups == nil { return fmt.Errorf("cannot set cgroups when they are disabled") } - v, err := strconv.ParseInt(value, 0, 64) + v, err := strconv.ParseInt(value, 10, 0) if err != nil { return err } @@ -51,7 +75,8 @@ func memory(container *libcontainer.Container, context interface{}, value string if container.Cgroups == nil { return fmt.Errorf("cannot set cgroups when they are disabled") } - v, err := strconv.ParseInt(value, 0, 64) + + v, err := utils.RAMInBytes(value) if err != nil { return err } @@ -138,7 +163,6 @@ func joinNetNamespace(container *libcontainer.Container, context interface{}, va func vethMacAddress(container *libcontainer.Container, context interface{}, value string) error { var veth *libcontainer.Network - for _, network := range container.Networks { if network.Type == "veth" { veth = network @@ -155,8 +179,7 @@ func vethMacAddress(container *libcontainer.Container, context interface{}, valu // configureCustomOptions takes string commands from the user and allows modification of the // container's default configuration. // -// format: <...value> -// i.e: cgroup devices.allow *:* +// TODO: this can be moved to a general utils or parser in pkg func ParseConfiguration(container *libcontainer.Container, running map[string]*exec.Cmd, opts []string) error { for _, opt := range opts { kv := strings.SplitN(opt, "=", 2) From 10fdbc0467d1be6c7c731d3f35590d87ee42f96f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 24 Mar 2014 07:16:40 +0000 Subject: [PATCH 194/384] Add unit test for lxc conf merge and native opts Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runtime/container.go | 10 +- runtime/execdriver/lxc/lxc_template.go | 2 +- .../execdriver/native/configuration/parse.go | 14 -- .../native/configuration/parse_test.go | 166 ++++++++++++++++++ runtime/execdriver/native/create.go | 66 ++++--- .../native/{ => template}/default_template.go | 7 +- runtime/utils.go | 20 +++ runtime/utils_test.go | 27 +++ 8 files changed, 265 insertions(+), 47 deletions(-) create mode 100644 runtime/execdriver/native/configuration/parse_test.go rename runtime/execdriver/native/{ => template}/default_template.go (88%) create mode 100644 runtime/utils_test.go diff --git a/runtime/container.go b/runtime/container.go index ee5045e374..be162c7a21 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -383,14 +383,8 @@ func populateCommand(c *Container) { } } - // merge in the lxc conf options into the generic config map - if lxcConf := c.hostConfig.LxcConf; lxcConf != nil { - lxc := driverConfig["lxc"] - for _, pair := range lxcConf { - lxc = append(lxc, fmt.Sprintf("%s = %s", pair.Key, pair.Value)) - } - driverConfig["lxc"] = lxc - } + // TODO: this can be removed after lxc-conf is fully deprecated + mergeLxcConfIntoOptions(c.hostConfig, driverConfig) resources := &execdriver.Resources{ Memory: c.Config.Memory, diff --git a/runtime/execdriver/lxc/lxc_template.go b/runtime/execdriver/lxc/lxc_template.go index 7979e4f284..c5ba876dce 100644 --- a/runtime/execdriver/lxc/lxc_template.go +++ b/runtime/execdriver/lxc/lxc_template.go @@ -120,7 +120,7 @@ lxc.cgroup.cpu.shares = {{.Resources.CpuShares}} {{if .Config.lxc}} {{range $value := .Config.lxc}} -{{$value}} +lxc.{{$value}} {{end}} {{end}} ` diff --git a/runtime/execdriver/native/configuration/parse.go b/runtime/execdriver/native/configuration/parse.go index 090cb29660..6d6c643919 100644 --- a/runtime/execdriver/native/configuration/parse.go +++ b/runtime/execdriver/native/configuration/parse.go @@ -31,20 +31,6 @@ var actions = map[string]Action{ "fs.readonly": readonlyFs, // make the rootfs of the container read only } -// GetSupportedActions returns a list of all the avaliable actions supported by the driver -// TODO: this should return a description also -func GetSupportedActions() []string { - var ( - i int - out = make([]string, len(actions)) - ) - for k := range actions { - out[i] = k - i++ - } - return out -} - func cpusetCpus(container *libcontainer.Container, context interface{}, value string) error { if container.Cgroups == nil { return fmt.Errorf("cannot set cgroups when they are disabled") diff --git a/runtime/execdriver/native/configuration/parse_test.go b/runtime/execdriver/native/configuration/parse_test.go new file mode 100644 index 0000000000..8001358766 --- /dev/null +++ b/runtime/execdriver/native/configuration/parse_test.go @@ -0,0 +1,166 @@ +package configuration + +import ( + "github.com/dotcloud/docker/runtime/execdriver/native/template" + "testing" +) + +func TestSetReadonlyRootFs(t *testing.T) { + var ( + container = template.New() + opts = []string{ + "fs.readonly=true", + } + ) + + if container.ReadonlyFs { + t.Fatal("container should not have a readonly rootfs by default") + } + if err := ParseConfiguration(container, nil, opts); err != nil { + t.Fatal(err) + } + + if !container.ReadonlyFs { + t.Fatal("container should have a readonly rootfs") + } +} + +func TestConfigurationsDoNotConflict(t *testing.T) { + var ( + container1 = template.New() + container2 = template.New() + opts = []string{ + "cap.add=NET_ADMIN", + } + ) + + if err := ParseConfiguration(container1, nil, opts); err != nil { + t.Fatal(err) + } + + if !container1.CapabilitiesMask.Get("NET_ADMIN").Enabled { + t.Fatal("container one should have NET_ADMIN enabled") + } + if container2.CapabilitiesMask.Get("NET_ADMIN").Enabled { + t.Fatal("container two should not have NET_ADMIN enabled") + } +} + +func TestCpusetCpus(t *testing.T) { + var ( + container = template.New() + opts = []string{ + "cgroups.cpuset.cpus=1,2", + } + ) + if err := ParseConfiguration(container, nil, opts); err != nil { + t.Fatal(err) + } + + if expected := "1,2"; container.Cgroups.CpusetCpus != expected { + t.Fatalf("expected %s got %s for cpuset.cpus", expected, container.Cgroups.CpusetCpus) + } +} + +func TestAppArmorProfile(t *testing.T) { + var ( + container = template.New() + opts = []string{ + "apparmor_profile=koye-the-protector", + } + ) + if err := ParseConfiguration(container, nil, opts); err != nil { + t.Fatal(err) + } + if expected := "koye-the-protector"; container.Context["apparmor_profile"] != expected { + t.Fatalf("expected profile %s got %s", expected, container.Context["apparmor_profile"]) + } +} + +func TestCpuShares(t *testing.T) { + var ( + container = template.New() + opts = []string{ + "cgroups.cpu_shares=1048", + } + ) + if err := ParseConfiguration(container, nil, opts); err != nil { + t.Fatal(err) + } + + if expected := int64(1048); container.Cgroups.CpuShares != expected { + t.Fatalf("expected cpu shares %d got %d", expected, container.Cgroups.CpuShares) + } +} + +func TestCgroupMemory(t *testing.T) { + var ( + container = template.New() + opts = []string{ + "cgroups.memory=500m", + } + ) + if err := ParseConfiguration(container, nil, opts); err != nil { + t.Fatal(err) + } + + if expected := int64(500 * 1024 * 1024); container.Cgroups.Memory != expected { + t.Fatalf("expected memory %d got %d", expected, container.Cgroups.Memory) + } +} + +func TestAddCap(t *testing.T) { + var ( + container = template.New() + opts = []string{ + "cap.add=MKNOD", + "cap.add=SYS_ADMIN", + } + ) + if err := ParseConfiguration(container, nil, opts); err != nil { + t.Fatal(err) + } + + if !container.CapabilitiesMask.Get("MKNOD").Enabled { + t.Fatal("container should have MKNOD enabled") + } + if !container.CapabilitiesMask.Get("SYS_ADMIN").Enabled { + t.Fatal("container should have SYS_ADMIN enabled") + } +} + +func TestDropCap(t *testing.T) { + var ( + container = template.New() + opts = []string{ + "cap.drop=MKNOD", + } + ) + // enabled all caps like in privileged mode + for _, c := range container.CapabilitiesMask { + c.Enabled = true + } + if err := ParseConfiguration(container, nil, opts); err != nil { + t.Fatal(err) + } + + if container.CapabilitiesMask.Get("MKNOD").Enabled { + t.Fatal("container should not have MKNOD enabled") + } +} + +func TestDropNamespace(t *testing.T) { + var ( + container = template.New() + opts = []string{ + "ns.drop=NEWNET", + } + ) + if err := ParseConfiguration(container, nil, opts); err != nil { + t.Fatal(err) + } + + if container.Namespaces.Get("NEWNET").Enabled { + t.Fatal("container should not have NEWNET enabled") + } +} diff --git a/runtime/execdriver/native/create.go b/runtime/execdriver/native/create.go index 7118edc91e..7e663f0555 100644 --- a/runtime/execdriver/native/create.go +++ b/runtime/execdriver/native/create.go @@ -5,30 +5,53 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/runtime/execdriver" "github.com/dotcloud/docker/runtime/execdriver/native/configuration" + "github.com/dotcloud/docker/runtime/execdriver/native/template" "os" ) // createContainer populates and configures the container type with the // data provided by the execdriver.Command func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Container, error) { - container := getDefaultTemplate() + container := template.New() container.Hostname = getEnv("HOSTNAME", c.Env) container.Tty = c.Tty container.User = c.User container.WorkingDir = c.WorkingDir container.Env = c.Env + container.Cgroups.Name = c.ID + // check to see if we are running in ramdisk to disable pivot root + container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != "" - loopbackNetwork := libcontainer.Network{ - Mtu: c.Network.Mtu, - Address: fmt.Sprintf("%s/%d", "127.0.0.1", 0), - Gateway: "localhost", - Type: "loopback", - Context: libcontainer.Context{}, + if err := d.createNetwork(container, c); err != nil { + return nil, err } + if c.Privileged { + if err := d.setPrivileged(container); err != nil { + return nil, err + } + } + if err := d.setupCgroups(container, c); err != nil { + return nil, err + } + if err := d.setupMounts(container, c); err != nil { + return nil, err + } + if err := configuration.ParseConfiguration(container, d.activeContainers, c.Config["native"]); err != nil { + return nil, err + } + return container, nil +} +func (d *driver) createNetwork(container *libcontainer.Container, c *execdriver.Command) error { container.Networks = []*libcontainer.Network{ - &loopbackNetwork, + { + Mtu: c.Network.Mtu, + Address: fmt.Sprintf("%s/%d", "127.0.0.1", 0), + Gateway: "localhost", + Type: "loopback", + Context: libcontainer.Context{}, + }, } if c.Network.Interface != nil { @@ -44,27 +67,30 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Container } container.Networks = append(container.Networks, &vethNetwork) } + return nil +} - container.Cgroups.Name = c.ID - if c.Privileged { - container.CapabilitiesMask = nil - container.Cgroups.DeviceAccess = true - container.Context["apparmor_profile"] = "unconfined" +func (d *driver) setPrivileged(container *libcontainer.Container) error { + for _, c := range container.CapabilitiesMask { + c.Enabled = true } + container.Cgroups.DeviceAccess = true + container.Context["apparmor_profile"] = "unconfined" + return nil +} + +func (d *driver) setupCgroups(container *libcontainer.Container, c *execdriver.Command) error { if c.Resources != nil { container.Cgroups.CpuShares = c.Resources.CpuShares container.Cgroups.Memory = c.Resources.Memory container.Cgroups.MemorySwap = c.Resources.MemorySwap } - // check to see if we are running in ramdisk to disable pivot root - container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != "" + return nil +} +func (d *driver) setupMounts(container *libcontainer.Container, c *execdriver.Command) error { for _, m := range c.Mounts { container.Mounts = append(container.Mounts, libcontainer.Mount{m.Source, m.Destination, m.Writable, m.Private}) } - - if err := configuration.ParseConfiguration(container, d.activeContainers, c.Config["native"]); err != nil { - return nil, err - } - return container, nil + return nil } diff --git a/runtime/execdriver/native/default_template.go b/runtime/execdriver/native/template/default_template.go similarity index 88% rename from runtime/execdriver/native/default_template.go rename to runtime/execdriver/native/template/default_template.go index 0dcd7db356..b9eb87713e 100644 --- a/runtime/execdriver/native/default_template.go +++ b/runtime/execdriver/native/template/default_template.go @@ -1,13 +1,12 @@ -package native +package template import ( "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/pkg/libcontainer" ) -// getDefaultTemplate returns the docker default for -// the libcontainer configuration file -func getDefaultTemplate() *libcontainer.Container { +// New returns the docker default configuration for libcontainer +func New() *libcontainer.Container { return &libcontainer.Container{ CapabilitiesMask: libcontainer.Capabilities{ libcontainer.GetCapability("SETPCAP"), diff --git a/runtime/utils.go b/runtime/utils.go index b343b5b10e..b983e67d41 100644 --- a/runtime/utils.go +++ b/runtime/utils.go @@ -1,9 +1,11 @@ package runtime import ( + "fmt" "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/pkg/namesgenerator" "github.com/dotcloud/docker/runconfig" + "strings" ) func migratePortMappings(config *runconfig.Config, hostConfig *runconfig.HostConfig) error { @@ -30,6 +32,24 @@ func migratePortMappings(config *runconfig.Config, hostConfig *runconfig.HostCon return nil } +func mergeLxcConfIntoOptions(hostConfig *runconfig.HostConfig, driverConfig map[string][]string) { + if hostConfig == nil { + return + } + + // merge in the lxc conf options into the generic config map + if lxcConf := hostConfig.LxcConf; lxcConf != nil { + lxc := driverConfig["lxc"] + for _, pair := range lxcConf { + // because lxc conf gets the driver name lxc.XXXX we need to trim it off + // and let the lxc driver add it back later if needed + parts := strings.SplitN(pair.Key, ".", 2) + lxc = append(lxc, fmt.Sprintf("%s=%s", parts[1], pair.Value)) + } + driverConfig["lxc"] = lxc + } +} + type checker struct { runtime *Runtime } diff --git a/runtime/utils_test.go b/runtime/utils_test.go new file mode 100644 index 0000000000..81c745c0d5 --- /dev/null +++ b/runtime/utils_test.go @@ -0,0 +1,27 @@ +package runtime + +import ( + "github.com/dotcloud/docker/runconfig" + "testing" +) + +func TestMergeLxcConfig(t *testing.T) { + var ( + hostConfig = &runconfig.HostConfig{ + LxcConf: []runconfig.KeyValuePair{ + {Key: "lxc.cgroups.cpuset", Value: "1,2"}, + }, + } + driverConfig = make(map[string][]string) + ) + + mergeLxcConfIntoOptions(hostConfig, driverConfig) + if l := len(driverConfig["lxc"]); l > 1 { + t.Fatalf("expected lxc options len of 1 got %d", l) + } + + cpuset := driverConfig["lxc"][0] + if expected := "cgroups.cpuset=1,2"; cpuset != expected { + t.Fatalf("expected %s got %s", expected, cpuset) + } +} From f1bd79ec97c125148c690d66ebd3ac5ab3f388b2 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 24 Mar 2014 12:03:41 +0000 Subject: [PATCH 195/384] Revert "fix failing test to use kill instead of stop" This reverts commit 4434dcee89f7d0d0239f6b492b24e940cdbafb21. Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- integration/server_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/integration/server_test.go b/integration/server_test.go index 617f81fa4d..a401f1306e 100644 --- a/integration/server_test.go +++ b/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) } From d503714285143013d9fa6932ee5775fd155d26d2 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 24 Mar 2014 12:03:56 +0000 Subject: [PATCH 196/384] Revert "Disable automatic killing of containers when docker stop fails" This reverts commit 8b5cf51d600dc4f3611cf063c52cf3448e7b01e5. Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- api/client.go | 6 +++--- .../sources/reference/api/docker_remote_api_v1.9.rst | 4 ++-- docs/sources/reference/commandline/cli.rst | 6 +++--- runtime/container.go | 12 ++++++++++-- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/api/client.go b/api/client.go index ae9e7cef19..b39b102330 100644 --- a/api/client.go +++ b/api/client.go @@ -498,8 +498,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 +526,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 } diff --git a/docs/sources/reference/api/docker_remote_api_v1.9.rst b/docs/sources/reference/api/docker_remote_api_v1.9.rst index def38edd55..27812457bb 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.9.rst +++ b/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 diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index d398b16e53..757f3b239b 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -1360,11 +1360,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: diff --git a/runtime/container.go b/runtime/container.go index 6194a19c8c..bff9aea968 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -903,12 +903,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 } From f41135bc11b9a4da896e5054a73afa112b2e835f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 24 Mar 2014 12:39:56 +0000 Subject: [PATCH 197/384] As far as I know this code is not used or maintained Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- hack/infrastructure/docker-ci/Dockerfile | 29 --- hack/infrastructure/docker-ci/MAINTAINERS | 1 - hack/infrastructure/docker-ci/README.rst | 65 ------- hack/infrastructure/docker-ci/VERSION | 1 - .../docker-ci/buildbot/github.py | 176 ------------------ .../docker-ci/buildbot/master.cfg | 161 ---------------- .../docker-ci/dcr/prod/docker-ci.yml | 22 --- .../docker-ci/dcr/prod/settings.yml | 5 - .../docker-ci/dcr/stage/docker-ci.yml | 22 --- .../docker-ci/dcr/stage/settings.yml | 5 - .../docker-ci/docker-coverage/gocoverage.sh | 52 ------ .../docker-ci/dockertest/docker | 1 - .../docker-ci/dockertest/docker-registry | 1 - .../docker-ci/dockertest/nightlyrelease | 13 -- .../docker-ci/dockertest/project | 8 - .../docker-ci/functionaltests/test_index.py | 61 ------ .../functionaltests/test_registry.sh | 27 --- .../infrastructure/docker-ci/nginx/nginx.conf | 12 -- .../docker-ci/report/Dockerfile | 28 --- .../docker-ci/report/deployment.py | 130 ------------- .../infrastructure/docker-ci/report/report.py | 145 --------------- hack/infrastructure/docker-ci/setup.sh | 54 ------ .../docker-ci/testbuilder/Dockerfile | 12 -- .../docker-ci/testbuilder/docker-registry.sh | 12 -- .../docker-ci/testbuilder/docker.sh | 18 -- .../docker-ci/testbuilder/testbuilder.sh | 40 ---- hack/infrastructure/docker-ci/tool/backup.py | 47 ----- 27 files changed, 1148 deletions(-) delete mode 100644 hack/infrastructure/docker-ci/Dockerfile delete mode 100644 hack/infrastructure/docker-ci/MAINTAINERS delete mode 100644 hack/infrastructure/docker-ci/README.rst delete mode 100644 hack/infrastructure/docker-ci/VERSION delete mode 100644 hack/infrastructure/docker-ci/buildbot/github.py delete mode 100644 hack/infrastructure/docker-ci/buildbot/master.cfg delete mode 100644 hack/infrastructure/docker-ci/dcr/prod/docker-ci.yml delete mode 100644 hack/infrastructure/docker-ci/dcr/prod/settings.yml delete mode 100644 hack/infrastructure/docker-ci/dcr/stage/docker-ci.yml delete mode 100644 hack/infrastructure/docker-ci/dcr/stage/settings.yml delete mode 100755 hack/infrastructure/docker-ci/docker-coverage/gocoverage.sh delete mode 120000 hack/infrastructure/docker-ci/dockertest/docker delete mode 120000 hack/infrastructure/docker-ci/dockertest/docker-registry delete mode 100755 hack/infrastructure/docker-ci/dockertest/nightlyrelease delete mode 100755 hack/infrastructure/docker-ci/dockertest/project delete mode 100755 hack/infrastructure/docker-ci/functionaltests/test_index.py delete mode 100755 hack/infrastructure/docker-ci/functionaltests/test_registry.sh delete mode 100644 hack/infrastructure/docker-ci/nginx/nginx.conf delete mode 100644 hack/infrastructure/docker-ci/report/Dockerfile delete mode 100755 hack/infrastructure/docker-ci/report/deployment.py delete mode 100755 hack/infrastructure/docker-ci/report/report.py delete mode 100755 hack/infrastructure/docker-ci/setup.sh delete mode 100644 hack/infrastructure/docker-ci/testbuilder/Dockerfile delete mode 100755 hack/infrastructure/docker-ci/testbuilder/docker-registry.sh delete mode 100755 hack/infrastructure/docker-ci/testbuilder/docker.sh delete mode 100755 hack/infrastructure/docker-ci/testbuilder/testbuilder.sh delete mode 100755 hack/infrastructure/docker-ci/tool/backup.py diff --git a/hack/infrastructure/docker-ci/Dockerfile b/hack/infrastructure/docker-ci/Dockerfile deleted file mode 100644 index 5c6eec9663..0000000000 --- a/hack/infrastructure/docker-ci/Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -# DOCKER-VERSION: 0.7.6 -# AUTHOR: Daniel Mizyrycki -# 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 - -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"] diff --git a/hack/infrastructure/docker-ci/MAINTAINERS b/hack/infrastructure/docker-ci/MAINTAINERS deleted file mode 100644 index 5dfc881420..0000000000 --- a/hack/infrastructure/docker-ci/MAINTAINERS +++ /dev/null @@ -1 +0,0 @@ -Daniel Mizyrycki (@mzdaniel) diff --git a/hack/infrastructure/docker-ci/README.rst b/hack/infrastructure/docker-ci/README.rst deleted file mode 100644 index 07c1ffcec0..0000000000 --- a/hack/infrastructure/docker-ci/README.rst +++ /dev/null @@ -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) diff --git a/hack/infrastructure/docker-ci/VERSION b/hack/infrastructure/docker-ci/VERSION deleted file mode 100644 index b49b25336d..0000000000 --- a/hack/infrastructure/docker-ci/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.5.6 diff --git a/hack/infrastructure/docker-ci/buildbot/github.py b/hack/infrastructure/docker-ci/buildbot/github.py deleted file mode 100644 index 5316e13282..0000000000 --- a/hack/infrastructure/docker-ci/buildbot/github.py +++ /dev/null @@ -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 diff --git a/hack/infrastructure/docker-ci/buildbot/master.cfg b/hack/infrastructure/docker-ci/buildbot/master.cfg deleted file mode 100644 index 75605da8ab..0000000000 --- a/hack/infrastructure/docker-ci/buildbot/master.cfg +++ /dev/null @@ -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})) diff --git a/hack/infrastructure/docker-ci/dcr/prod/docker-ci.yml b/hack/infrastructure/docker-ci/dcr/prod/docker-ci.yml deleted file mode 100644 index 523535446a..0000000000 --- a/hack/infrastructure/docker-ci/dcr/prod/docker-ci.yml +++ /dev/null @@ -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" diff --git a/hack/infrastructure/docker-ci/dcr/prod/settings.yml b/hack/infrastructure/docker-ci/dcr/prod/settings.yml deleted file mode 100644 index 9831afa6dd..0000000000 --- a/hack/infrastructure/docker-ci/dcr/prod/settings.yml +++ /dev/null @@ -1,5 +0,0 @@ -default: - hipaches: ['192.168.100.67:6379'] - daemons: ['192.168.100.67:4243'] - use_ssh: False - diff --git a/hack/infrastructure/docker-ci/dcr/stage/docker-ci.yml b/hack/infrastructure/docker-ci/dcr/stage/docker-ci.yml deleted file mode 100644 index 8eba84825c..0000000000 --- a/hack/infrastructure/docker-ci/dcr/stage/docker-ci.yml +++ /dev/null @@ -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" diff --git a/hack/infrastructure/docker-ci/dcr/stage/settings.yml b/hack/infrastructure/docker-ci/dcr/stage/settings.yml deleted file mode 100644 index a7d37acff3..0000000000 --- a/hack/infrastructure/docker-ci/dcr/stage/settings.yml +++ /dev/null @@ -1,5 +0,0 @@ -default: - hipaches: ['192.168.100.65:6379'] - daemons: ['192.168.100.65:4243'] - use_ssh: False - diff --git a/hack/infrastructure/docker-ci/docker-coverage/gocoverage.sh b/hack/infrastructure/docker-ci/docker-coverage/gocoverage.sh deleted file mode 100755 index fdacc290b4..0000000000 --- a/hack/infrastructure/docker-ci/docker-coverage/gocoverage.sh +++ /dev/null @@ -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" - - - - - -Docker Coverage Report - -

Docker Coverage Report

- - -EOF - for profile in *; do - gocov convert $profile | gocov-html >$REPORTS/$profile.html - echo "" >> $INDEX - done - echo "
packagepct
$profile" >> $INDEX - go tool cover -func=$profile | sed -En '$ s/.+\t(.+)/\1/p' >> $INDEX - echo "
" >> $INDEX -fi - -# Signal test and coverage result, parsed by docker-ci -set -x -exit $exit_status - diff --git a/hack/infrastructure/docker-ci/dockertest/docker b/hack/infrastructure/docker-ci/dockertest/docker deleted file mode 120000 index e3f094ee63..0000000000 --- a/hack/infrastructure/docker-ci/dockertest/docker +++ /dev/null @@ -1 +0,0 @@ -project \ No newline at end of file diff --git a/hack/infrastructure/docker-ci/dockertest/docker-registry b/hack/infrastructure/docker-ci/dockertest/docker-registry deleted file mode 120000 index e3f094ee63..0000000000 --- a/hack/infrastructure/docker-ci/dockertest/docker-registry +++ /dev/null @@ -1 +0,0 @@ -project \ No newline at end of file diff --git a/hack/infrastructure/docker-ci/dockertest/nightlyrelease b/hack/infrastructure/docker-ci/dockertest/nightlyrelease deleted file mode 100755 index cface6c125..0000000000 --- a/hack/infrastructure/docker-ci/dockertest/nightlyrelease +++ /dev/null @@ -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 - diff --git a/hack/infrastructure/docker-ci/dockertest/project b/hack/infrastructure/docker-ci/dockertest/project deleted file mode 100755 index 8131ab533a..0000000000 --- a/hack/infrastructure/docker-ci/dockertest/project +++ /dev/null @@ -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 - diff --git a/hack/infrastructure/docker-ci/functionaltests/test_index.py b/hack/infrastructure/docker-ci/functionaltests/test_index.py deleted file mode 100755 index fd002c81e8..0000000000 --- a/hack/infrastructure/docker-ci/functionaltests/test_index.py +++ /dev/null @@ -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() diff --git a/hack/infrastructure/docker-ci/functionaltests/test_registry.sh b/hack/infrastructure/docker-ci/functionaltests/test_registry.sh deleted file mode 100755 index 58642529cc..0000000000 --- a/hack/infrastructure/docker-ci/functionaltests/test_registry.sh +++ /dev/null @@ -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 - diff --git a/hack/infrastructure/docker-ci/nginx/nginx.conf b/hack/infrastructure/docker-ci/nginx/nginx.conf deleted file mode 100644 index 6649741134..0000000000 --- a/hack/infrastructure/docker-ci/nginx/nginx.conf +++ /dev/null @@ -1,12 +0,0 @@ -server { - listen 80; - root /data/docker-ci; - - location / { - proxy_pass http://localhost:8000/; - } - - location /coverage { - root /data/docker-ci; - } -} diff --git a/hack/infrastructure/docker-ci/report/Dockerfile b/hack/infrastructure/docker-ci/report/Dockerfile deleted file mode 100644 index 32600c4c58..0000000000 --- a/hack/infrastructure/docker-ci/report/Dockerfile +++ /dev/null @@ -1,28 +0,0 @@ -# VERSION: 0.22 -# DOCKER-VERSION 0.6.3 -# AUTHOR: Daniel Mizyrycki -# 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 - -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" diff --git a/hack/infrastructure/docker-ci/report/deployment.py b/hack/infrastructure/docker-ci/report/deployment.py deleted file mode 100755 index 5b2eaf3cab..0000000000 --- a/hack/infrastructure/docker-ci/report/deployment.py +++ /dev/null @@ -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 -") diff --git a/hack/infrastructure/docker-ci/report/report.py b/hack/infrastructure/docker-ci/report/report.py deleted file mode 100755 index 7018cabc27..0000000000 --- a/hack/infrastructure/docker-ci/report/report.py +++ /dev/null @@ -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) diff --git a/hack/infrastructure/docker-ci/setup.sh b/hack/infrastructure/docker-ci/setup.sh deleted file mode 100755 index 65a00f6dd0..0000000000 --- a/hack/infrastructure/docker-ci/setup.sh +++ /dev/null @@ -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 diff --git a/hack/infrastructure/docker-ci/testbuilder/Dockerfile b/hack/infrastructure/docker-ci/testbuilder/Dockerfile deleted file mode 100644 index 8fa9b4c797..0000000000 --- a/hack/infrastructure/docker-ci/testbuilder/Dockerfile +++ /dev/null @@ -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"] diff --git a/hack/infrastructure/docker-ci/testbuilder/docker-registry.sh b/hack/infrastructure/docker-ci/testbuilder/docker-registry.sh deleted file mode 100755 index a73704c50b..0000000000 --- a/hack/infrastructure/docker-ci/testbuilder/docker-registry.sh +++ /dev/null @@ -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" diff --git a/hack/infrastructure/docker-ci/testbuilder/docker.sh b/hack/infrastructure/docker-ci/testbuilder/docker.sh deleted file mode 100755 index c8f3c18eb9..0000000000 --- a/hack/infrastructure/docker-ci/testbuilder/docker.sh +++ /dev/null @@ -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 diff --git a/hack/infrastructure/docker-ci/testbuilder/testbuilder.sh b/hack/infrastructure/docker-ci/testbuilder/testbuilder.sh deleted file mode 100755 index 70701343c2..0000000000 --- a/hack/infrastructure/docker-ci/testbuilder/testbuilder.sh +++ /dev/null @@ -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 diff --git a/hack/infrastructure/docker-ci/tool/backup.py b/hack/infrastructure/docker-ci/tool/backup.py deleted file mode 100755 index 2db633e526..0000000000 --- a/hack/infrastructure/docker-ci/tool/backup.py +++ /dev/null @@ -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:]) From 68dd722e3c54995e609b2524bad501ab1d4d15d6 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 24 Mar 2014 14:15:04 +0000 Subject: [PATCH 198/384] Promote btrfs Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runtime/graphdriver/driver.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/runtime/graphdriver/driver.go b/runtime/graphdriver/driver.go index 89fd03a624..9ea7d1c94c 100644 --- a/runtime/graphdriver/driver.go +++ b/runtime/graphdriver/driver.go @@ -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", } ) From c7540b3e94d7712b6b91ba80de0155f20156f3f3 Mon Sep 17 00:00:00 2001 From: LK4D4 Date: Mon, 24 Mar 2014 22:31:05 +0400 Subject: [PATCH 199/384] Workaround for hanging events. Fixes #4804 Docker-DCO-1.1-Signed-off-by: LK4D4 (github: LK4D4) --- server/server.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/server.go b/server/server.go index 840a70357d..2cb3328d55 100644 --- a/server/server.go +++ b/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 From bb034c6b42c347ffdfae834960bf2386429e1980 Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Mon, 24 Mar 2014 21:38:57 -0400 Subject: [PATCH 200/384] Add Chef usage documentation Docker-DCO-1.1-Signed-off-by: Brian Flad (github: bflad) --- docs/sources/use/chef.rst | 95 ++++++++++++++++++++++++++++++++++++++ docs/sources/use/index.rst | 1 + 2 files changed, 96 insertions(+) create mode 100644 docs/sources/use/chef.rst diff --git a/docs/sources/use/chef.rst b/docs/sources/use/chef.rst new file mode 100644 index 0000000000..54755ad3b3 --- /dev/null +++ b/docs/sources/use/chef.rst @@ -0,0 +1,95 @@ +:title: Chef Usage +:description: Installating 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 `_. This cookbook supports a variety of +operating systems. + +Installation +------------ + +The cookbook is available on the `Chef Community Site +`_ and can be installed +using your favorite cookbook dependency manager. + +The source can be found on `GitHub +`_. + +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. diff --git a/docs/sources/use/index.rst b/docs/sources/use/index.rst index c1b7691cca..dcf6289b41 100644 --- a/docs/sources/use/index.rst +++ b/docs/sources/use/index.rst @@ -20,4 +20,5 @@ Contents: working_with_volumes working_with_links_names ambassador_pattern_linking + chef puppet From 84e1fdf35d9d6493d389a8e8be3ab41190004b30 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 24 Mar 2014 21:43:26 -0400 Subject: [PATCH 201/384] docker load: add --input flag for those that do not care to read from redirected stdin Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- api/client.go | 14 +++++++++++++- docs/sources/reference/commandline/cli.rst | 15 ++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/api/client.go b/api/client.go index f1a3f64f2a..f9e7445fd9 100644 --- a/api/client.go +++ b/api/client.go @@ -2075,6 +2075,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 } @@ -2084,7 +2086,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 diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index f4a5e0882f..2626280dd0 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -881,10 +881,19 @@ Known Issues (kill) :: - Usage: docker load < repository.tar + Usage: docker load - Loads a tarred repository from the standard input stream. - Restores both images and tags. + 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 the standard input stream. +Restores both images and tags. + +.. code-block:: bash + + $ sudo docker load < busybox.tar + $ sudo docker load --input busybox.tar .. _cli_login: From c84ff187c62060f20b7039a5005a44012898df7b Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Mon, 24 Mar 2014 22:29:54 -0400 Subject: [PATCH 202/384] Fix typo in Using Chef documentation description Docker-DCO-1.1-Signed-off-by: Brian Flad (github: bflad) --- docs/sources/use/chef.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/use/chef.rst b/docs/sources/use/chef.rst index 54755ad3b3..919eba7a8f 100644 --- a/docs/sources/use/chef.rst +++ b/docs/sources/use/chef.rst @@ -1,5 +1,5 @@ :title: Chef Usage -:description: Installating and using Docker via Chef +:description: Installation and using Docker via Chef :keywords: chef, installation, usage, docker, documentation .. _install_using_chef: From c6c7c03cddc852c42b9f047fbd5c2fb6cecf39eb Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 24 Mar 2014 23:31:59 -0400 Subject: [PATCH 203/384] docker load: doc clarification Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- docs/sources/reference/commandline/cli.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 2626280dd0..3c7bb47113 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -885,9 +885,9 @@ Known Issues (kill) Load an image from a tar archive on STDIN - -i, --input"": Read from a tar archive file, instead of STDIN + -i, --input="": Read from a tar archive file, instead of STDIN -Loads a tarred repository from the standard input stream. +Loads a tarred repository from a file or the standard input stream. Restores both images and tags. .. code-block:: bash From a5ccb5b28d7e24a379f77ab7619f296aa500c8dd Mon Sep 17 00:00:00 2001 From: Ryan Thomas Date: Tue, 25 Mar 2014 14:45:11 +1100 Subject: [PATCH 204/384] Docker-DCO-1.1-Signed-off-by: Ryan Thomas (github: rthomas) --- registry/registry.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/registry/registry.go b/registry/registry.go index 346132bcc5..01583f97c2 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -41,7 +41,10 @@ func pingRegistryEndpoint(endpoint string) (bool, error) { conn.SetDeadline(time.Now().Add(time.Duration(10) * time.Second)) return conn, nil } - httpTransport := &http.Transport{Dial: httpDial} + httpTransport := &http.Transport{ + Dial: httpDial, + Proxy: http.ProxyFromEnvironment, + } client := &http.Client{Transport: httpTransport} resp, err := client.Get(endpoint + "_ping") if err != nil { From 69087f2d2397b18d6dd2d7b994e24ea9814e4bcd Mon Sep 17 00:00:00 2001 From: noducks Date: Sat, 22 Mar 2014 14:12:15 +0000 Subject: [PATCH 205/384] Reminder for OSX users not to use SUDO Useful for those who haven't made it to the examples page yet. https://github.com/chadoh/docker/commit/dad4a998dc716e506c874ce0e792989b9df28748 Docker-DCO-1.1-Signed-off-by: No Ducks (github: noducks) --- docs/sources/use/basics.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/use/basics.rst b/docs/sources/use/basics.rst index 447366f55a..4164e706f7 100644 --- a/docs/sources/use/basics.rst +++ b/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 ---------------------------- From 293157b8b38dd5ea5fa49d90501cc3c86717da40 Mon Sep 17 00:00:00 2001 From: viirya Date: Sun, 23 Mar 2014 23:55:20 +0800 Subject: [PATCH 206/384] check if working dir is a directory and raise corresponding errors when making dir. Docker-DCO-1.1-Signed-off-by: Liang-Chi Hsieh (github: viirya) --- integration/commands_test.go | 19 +++++++++++++++++ runtime/container.go | 41 ++++++++++++++++++++++++------------ 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/integration/commands_test.go b/integration/commands_test.go index f1c5870755..7de7a227ea 100644 --- a/integration/commands_test.go +++ b/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() diff --git a/runtime/container.go b/runtime/container.go index 6194a19c8c..0e5e255bfc 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -535,8 +535,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) } } @@ -950,10 +960,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) { @@ -967,10 +978,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 { @@ -1119,10 +1131,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 From 8e434c314ef74618001cc95466c2b567fa0283e2 Mon Sep 17 00:00:00 2001 From: noducks Date: Tue, 25 Mar 2014 10:26:45 +0000 Subject: [PATCH 207/384] Force flag to prevent file already exists error. Docker-DCO-1.1-Signed-off-by: No Ducks (github: noducks) --- docs/sources/examples/mongodb.rst | 2 +- docs/sources/examples/running_riak_service.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/examples/mongodb.rst b/docs/sources/examples/mongodb.rst index 3e37d74c30..930ab2ea9d 100644 --- a/docs/sources/examples/mongodb.rst +++ b/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 diff --git a/docs/sources/examples/running_riak_service.rst b/docs/sources/examples/running_riak_service.rst index ae08a4b7f0..55e5e405c9 100644 --- a/docs/sources/examples/running_riak_service.rst +++ b/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: From 2517370088ad11765f99d75c16b58e93fe18f85a Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Tue, 25 Mar 2014 08:30:59 -0400 Subject: [PATCH 208/384] docker load: added example of a multiple tag image Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- docs/sources/reference/commandline/cli.rst | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 3c7bb47113..687ce2f305 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -892,8 +892,21 @@ 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 load --input 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 + .. _cli_login: From 576278102e0fa9166711f8cf23ec972fcccc085e Mon Sep 17 00:00:00 2001 From: Paul Annesley Date: Mon, 24 Mar 2014 21:21:37 -0700 Subject: [PATCH 209/384] install.sh (get.docker.io) aufs comment updated. devicemapper has landed, but the TODO hasn't been actioned presumably because aufs is still preferred over devicemapper when available[1]. Comment updated accordingly. Citation [1]: https://github.com/crosbymichael/docker/blob/267ca39921c35826ccbdb84fbbc0690bfef385d7/runtime/graphdriver/driver.go#L40-L46 Docker-DCO-1.1-Signed-off-by: Paul Annesley (github: pda) --- hack/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/install.sh b/hack/install.sh index 1fa8a47480..205b57ecc7 100755 --- a/hack/install.sh +++ b/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)" From d36176652ef8f0220a1cff5dc00933400c69a562 Mon Sep 17 00:00:00 2001 From: unclejack Date: Mon, 24 Mar 2014 19:43:40 +0200 Subject: [PATCH 210/384] Bump to version v0.9.1 Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) Conflicts: VERSION --- CHANGELOG.md | 40 ++++++++++++++++++++++++++++++++++++++++ VERSION | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40ba3d32ac..c8ea94361b 100644 --- a/CHANGELOG.md +++ b/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 diff --git a/VERSION b/VERSION index c70836ca5c..f374f6662e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.0-dev +0.9.1 From b2721e05ce4d6026855718ad9b01eb7dec797cd2 Mon Sep 17 00:00:00 2001 From: unclejack Date: Wed, 26 Mar 2014 00:18:45 +0200 Subject: [PATCH 211/384] Change version to 0.9.1-dev Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index f374f6662e..dc9bff91aa 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.1 +0.9.1-dev From baba9cde9542b480162d11bd30ca3a522fa6b4d0 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 26 Mar 2014 11:51:27 +0000 Subject: [PATCH 212/384] Return error when existing bridge does not match ip Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runtime/networkdriver/bridge/driver.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/runtime/networkdriver/bridge/driver.go b/runtime/networkdriver/bridge/driver.go index 41588b1c27..61e82dd481 100644 --- a/runtime/networkdriver/bridge/driver.go +++ b/runtime/networkdriver/bridge/driver.go @@ -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 From 61f7d967ed635098abaf42fc411866bb198b97c5 Mon Sep 17 00:00:00 2001 From: Justin Simonelis Date: Wed, 26 Mar 2014 10:24:16 -0400 Subject: [PATCH 213/384] typo fix --- docs/sources/reference/builder.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/reference/builder.rst b/docs/sources/reference/builder.rst index 1c8331e98f..6462512da0 100644 --- a/docs/sources/reference/builder.rst +++ b/docs/sources/reference/builder.rst @@ -49,7 +49,7 @@ to be created - so ``RUN cd /tmp`` will not have any effect on the next instructions. Whenever possible, Docker will re-use the intermediate images, -accelerating ``docker build`` significantly (indicated by ``Using cache``: +accelerating ``docker build`` significantly (indicated by ``Using cache``): .. code-block:: bash From 72dc19fd7d339321a347dce4ea39d59fd503a4cc Mon Sep 17 00:00:00 2001 From: Justin Simonelis Date: Wed, 26 Mar 2014 10:30:57 -0400 Subject: [PATCH 214/384] authors update --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index df091d5950..1c58d953f6 100644 --- a/AUTHORS +++ b/AUTHORS @@ -170,6 +170,7 @@ Julien Barbier Julien Dubois Justin Force Justin Plock +Justin Simonelis Karan Lyons Karl Grzeszczak Kawsar Saiyeed From 4746c761566d5d5d4754daf62d20c83cba0efee8 Mon Sep 17 00:00:00 2001 From: Paul Jimenez Date: Tue, 25 Mar 2014 16:29:58 -0400 Subject: [PATCH 215/384] Include contributed completions in ubuntu PPA Docker-DCO-1.1-Signed-off-by: Paul Jimenez (github: pjz) --- hack/make/ubuntu | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hack/make/ubuntu b/hack/make/ubuntu index ebc12f27ec..403a6c7652 100644 --- a/hack/make/ubuntu +++ b/hack/make/ubuntu @@ -38,6 +38,14 @@ bundle_ubuntu() { mkdir -p $DIR/lib/systemd/system cp contrib/init/systemd/docker.service $DIR/lib/systemd/system/ + # Include contributed completions + mkdir -p $DIR/etc/bash_completion.d + cp contrib/completion/bash/docker $DIR/etc/bash_completion.d/ + mkdir -p $DIR/usr/share/zsh/vendor-completions + cp contrib/completion/zsh/_docker $DIR/usr/share/zsh/vendor-completions/ + mkdir -p $DIR/etc/fish/completions + cp contrib/completion/fish/docker.fish $DIR/etc/fish/completions/ + # Copy the binary # This will fail if the binary bundle hasn't been built mkdir -p $DIR/usr/bin From e7f3234c1e4926c966f4c9e4cf08d9aae60d21bb Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 26 Mar 2014 09:05:21 -0700 Subject: [PATCH 216/384] Fix fish completion when having alias on awk or grep Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- contrib/completion/fish/docker.fish | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contrib/completion/fish/docker.fish b/contrib/completion/fish/docker.fish index 9c4339fe2b..ddec61cffa 100644 --- a/contrib/completion/fish/docker.fish +++ b/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 '' | awk '{print $1":"$2}' + docker images | command awk 'NR>1' | command grep -v '' | 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 '' | awk '{print $1}' | sort | uniq + docker images | command awk 'NR>1' | command grep -v '' | command awk '{print $1}' | sort | uniq end # common options From 4c4356692580afb3971094e322aea64abe0e2500 Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Tue, 18 Mar 2014 16:49:16 -0400 Subject: [PATCH 217/384] This patch adds SELinux labeling support. docker will run the process(es) within the container with an SELinux label and will label all of the content within the container with mount label. Any temporary file systems created within the container need to be mounted with the same mount label. The user can override the process label by specifying -Z With a string of space separated options. -Z "user=unconfined_u role=unconfined_r type=unconfined_t level=s0" Would cause the process label to run with unconfined_u:unconfined_r:unconfined_t:s0" By default the processes will run execute within the container as svirt_lxc_net_t. All of the content in the container as svirt_sandbox_file_t. The process mcs level is based of the PID of the docker process that is creating the container. If you run the container in --priv mode, the labeling will be disabled. Docker-DCO-1.1-Signed-off-by: Dan Walsh (github: rhatdan) --- Dockerfile | 2 +- graph/graph.go | 2 +- hack/PACKAGERS.md | 7 + pkg/label/label.go | 23 ++ pkg/label/label_selinux.go | 69 ++++ pkg/libcontainer/nsinit/execin.go | 11 +- pkg/libcontainer/nsinit/init.go | 8 +- pkg/libcontainer/nsinit/mount.go | 22 +- pkg/selinux/selinux.go | 387 ++++++++++++++++++ pkg/selinux/selinux_test.go | 64 +++ runconfig/config.go | 11 + runconfig/parse.go | 20 + runtime/container.go | 1 + runtime/execdriver/driver.go | 5 + runtime/execdriver/lxc/lxc_template.go | 20 +- runtime/execdriver/native/default_template.go | 2 + runtime/graphdriver/aufs/aufs.go | 2 +- runtime/graphdriver/aufs/aufs_test.go | 52 +-- runtime/graphdriver/aufs/migrate.go | 6 +- runtime/graphdriver/btrfs/btrfs.go | 6 +- runtime/graphdriver/devmapper/deviceset.go | 9 +- runtime/graphdriver/devmapper/driver.go | 15 +- runtime/graphdriver/devmapper/driver_test.go | 20 +- runtime/graphdriver/driver.go | 2 +- runtime/graphdriver/vfs/driver.go | 2 +- runtime/runtime.go | 4 +- 26 files changed, 700 insertions(+), 72 deletions(-) create mode 100644 pkg/label/label.go create mode 100644 pkg/label/label_selinux.go create mode 100644 pkg/selinux/selinux.go create mode 100644 pkg/selinux/selinux_test.go diff --git a/Dockerfile b/Dockerfile index 42438e3946..2de5b34171 100644 --- a/Dockerfile +++ b/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"] diff --git a/graph/graph.go b/graph/graph.go index 4349cac129..33aca486c6 100644 --- a/graph/graph.go +++ b/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 diff --git a/hack/PACKAGERS.md b/hack/PACKAGERS.md index 47e8413bf3..dc255c57ad 100644 --- a/hack/PACKAGERS.md +++ b/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 diff --git a/pkg/label/label.go b/pkg/label/label.go new file mode 100644 index 0000000000..ba1e9f48ea --- /dev/null +++ b/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 +} diff --git a/pkg/label/label_selinux.go b/pkg/label/label_selinux.go new file mode 100644 index 0000000000..300a8b6d14 --- /dev/null +++ b/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) +} diff --git a/pkg/libcontainer/nsinit/execin.go b/pkg/libcontainer/nsinit/execin.go index f8b8931390..9017af06e9 100644 --- a/pkg/libcontainer/nsinit/execin.go +++ b/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 } diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 117ae875ed..5aa5f9f5b5 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.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/libcontainer/apparmor" "github.com/dotcloud/docker/pkg/libcontainer/capabilities" @@ -12,6 +13,7 @@ import ( "github.com/dotcloud/docker/pkg/system" "github.com/dotcloud/docker/pkg/user" "os" + "runtime" "syscall" ) @@ -57,7 +59,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol return fmt.Errorf("parent death signal %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 := setupNetwork(container, context); err != nil { @@ -76,6 +78,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) } diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index 61a90125e0..796143c68e 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/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 @@ -36,7 +37,7 @@ func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, cons return fmt.Errorf("mounting %s as readonly %s", rootfs, err) } } - if err := mountSystem(rootfs); err != nil { + if err := mountSystem(rootfs, mountLabel); err != nil { return fmt.Errorf("mount system %s", err) } @@ -64,7 +65,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 { @@ -196,7 +197,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) @@ -220,6 +221,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) } @@ -228,7 +232,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 @@ -238,8 +242,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) @@ -253,7 +257,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 @@ -262,7 +266,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 } } diff --git a/pkg/selinux/selinux.go b/pkg/selinux/selinux.go new file mode 100644 index 0000000000..5236d3fb87 --- /dev/null +++ b/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 +} diff --git a/pkg/selinux/selinux_test.go b/pkg/selinux/selinux_test.go new file mode 100644 index 0000000000..6b59c1db11 --- /dev/null +++ b/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") + } +} diff --git a/runconfig/config.go b/runconfig/config.go index e961d659d7..c3ade575c5 100644 --- a/runconfig/config.go +++ b/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) diff --git a/runconfig/parse.go b/runconfig/parse.go index c2591722d5..23c66cd611 100644 --- a/runconfig/parse.go +++ b/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) @@ -60,6 +66,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)") @@ -150,6 +157,15 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf entrypoint = []string{*flEntrypoint} } + 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 := parseLxcConfOpts(flLxcOpts) if err != nil { return nil, nil, cmd, err @@ -204,6 +220,10 @@ 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, + }, } hostConfig := &HostConfig{ diff --git a/runtime/container.go b/runtime/container.go index bff9aea968..53d0aa666e 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -402,6 +402,7 @@ func populateCommand(c *Container) { User: c.Config.User, Config: driverConfig, Resources: resources, + Context: c.Config.Context, } c.command.SysProcAttr = &syscall.SysProcAttr{Setsid: true} } diff --git a/runtime/execdriver/driver.go b/runtime/execdriver/driver.go index 23e31ee8d9..dca889a82d 100644 --- a/runtime/execdriver/driver.go +++ b/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") @@ -121,6 +125,7 @@ type Command struct { Arguments []string `json:"arguments"` WorkingDir string `json:"working_dir"` ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver + Context Context `json:"context"` // generic context for specific options (apparmor, selinux) Tty bool `json:"tty"` Network *Network `json:"network"` Config []string `json:"config"` // generic values that specific drivers can consume diff --git a/runtime/execdriver/lxc/lxc_template.go b/runtime/execdriver/lxc/lxc_template.go index ce9d90469f..608fb22436 100644 --- a/runtime/execdriver/lxc/lxc_template.go +++ b/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 { diff --git a/runtime/execdriver/native/default_template.go b/runtime/execdriver/native/default_template.go index d744ab382f..7e1e9ed86e 100644 --- a/runtime/execdriver/native/default_template.go +++ b/runtime/execdriver/native/default_template.go @@ -18,6 +18,8 @@ func createContainer(c *execdriver.Command) *libcontainer.Container { container.User = c.User container.WorkingDir = c.WorkingDir container.Env = c.Env + container.Context["mount_label"] = c.Context["mount_label"] + container.Context["process_label"] = c.Context["process_label"] loopbackNetwork := libcontainer.Network{ Mtu: c.Network.Mtu, diff --git a/runtime/graphdriver/aufs/aufs.go b/runtime/graphdriver/aufs/aufs.go index 6f05ddd025..401bbd8c86 100644 --- a/runtime/graphdriver/aufs/aufs.go +++ b/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 } diff --git a/runtime/graphdriver/aufs/aufs_test.go b/runtime/graphdriver/aufs/aufs_test.go index cb417c3b26..9cfdebd160 100644 --- a/runtime/graphdriver/aufs/aufs_test.go +++ b/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) } diff --git a/runtime/graphdriver/aufs/migrate.go b/runtime/graphdriver/aufs/migrate.go index 6018342d6c..400e260797 100644 --- a/runtime/graphdriver/aufs/migrate.go +++ b/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 } } diff --git a/runtime/graphdriver/btrfs/btrfs.go b/runtime/graphdriver/btrfs/btrfs.go index b0530be92b..2a94a4089f 100644 --- a/runtime/graphdriver/btrfs/btrfs.go +++ b/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 { diff --git a/runtime/graphdriver/devmapper/deviceset.go b/runtime/graphdriver/devmapper/deviceset.go index dfdb180bb2..762e982208 100644 --- a/runtime/graphdriver/devmapper/deviceset.go +++ b/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" @@ -827,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() @@ -859,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) diff --git a/runtime/graphdriver/devmapper/driver.go b/runtime/graphdriver/devmapper/driver.go index 33c7a0f483..1324ddab81 100644 --- a/runtime/graphdriver/devmapper/driver.go +++ b/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 { diff --git a/runtime/graphdriver/devmapper/driver_test.go b/runtime/graphdriver/devmapper/driver_test.go index 9af71a00b3..4ca72db0ca 100644 --- a/runtime/graphdriver/devmapper/driver_test.go +++ b/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) } diff --git a/runtime/graphdriver/driver.go b/runtime/graphdriver/driver.go index 89fd03a624..7bea704682 100644 --- a/runtime/graphdriver/driver.go +++ b/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) diff --git a/runtime/graphdriver/vfs/driver.go b/runtime/graphdriver/vfs/driver.go index 10a7b223a4..fe09560f24 100644 --- a/runtime/graphdriver/vfs/driver.go +++ b/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 diff --git a/runtime/runtime.go b/runtime/runtime.go index 0d3468e350..35bcad9781 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -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() From 0fb01fd8fe376a3518b1050ab62f2b3370d62535 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 26 Mar 2014 13:55:45 +0000 Subject: [PATCH 218/384] Follow symlinks inside container root for build's ADD Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- integration/buildfile_test.go | 18 ++++++++++++++++++ server/buildfile.go | 9 +++++++++ 2 files changed, 27 insertions(+) diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index 95d5abb8a7..ae2282f53f 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -998,3 +998,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) + } +} diff --git a/server/buildfile.go b/server/buildfile.go index 5d5fda4d8e..6f95c2e593 100644 --- a/server/buildfile.go +++ b/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 + "/" From 67af7b3fb0b5e40a435b434c57291cb2989275ce Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 18 Mar 2014 17:50:40 -0700 Subject: [PATCH 219/384] Strip comments before parsing line continuations Fixes #3898 Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- integration/buildfile_test.go | 10 ++++++++++ server/buildfile.go | 25 +++++++++++++++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index 7f6e69ece3..23a1ff3d8e 100644 --- a/integration/buildfile_test.go +++ b/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 diff --git a/server/buildfile.go b/server/buildfile.go index af6702cc1d..309b854208 100644 --- a/server/buildfile.go +++ b/server/buildfile.go @@ -729,20 +729,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)) @@ -779,6 +778,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, From 097aef2ca938012a5b42e0032b30267e27a92265 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 27 Mar 2014 04:24:31 +0000 Subject: [PATCH 220/384] Fix commit and import when no repository is specified Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- api/client.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/api/client.go b/api/client.go index 1e6bf9d549..df3265a15a 100644 --- a/api/client.go +++ b/api/client.go @@ -1013,9 +1013,11 @@ func (cli *DockerCli) CmdImport(args ...string) error { } v := url.Values{} - //Check if the given image name can be resolved - if _, _, err := registry.ResolveRepositoryName(repository); err != nil { - return err + if repository != "" { + //Check if the given image name can be resolved + if _, _, err := registry.ResolveRepositoryName(repository); err != nil { + return err + } } v.Set("repo", repository) @@ -1469,8 +1471,10 @@ func (cli *DockerCli) CmdCommit(args ...string) error { } //Check if the given image name can be resolved - if _, _, err := registry.ResolveRepositoryName(repository); err != nil { - return err + if repository != "" { + if _, _, err := registry.ResolveRepositoryName(repository); err != nil { + return err + } } v := url.Values{} From 2d270c4f06dbc2ee1293e3f81f6922df248ef8eb Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 27 Mar 2014 08:25:01 +0000 Subject: [PATCH 221/384] Fix compile and unit test errors after merge Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/cgroups/apply_raw.go | 23 ++++++++++++++ pkg/cgroups/cgroups.go | 19 ------------ runconfig/hostconfig.go | 5 --- runconfig/parse.go | 42 -------------------------- runtime/container.go | 1 - runtime/execdriver/lxc/lxc_template.go | 25 ++++++++++----- runtime/utils_test.go | 3 +- 7 files changed, 43 insertions(+), 75 deletions(-) diff --git a/pkg/cgroups/apply_raw.go b/pkg/cgroups/apply_raw.go index 47a2a002b8..5fe317937a 100644 --- a/pkg/cgroups/apply_raw.go +++ b/pkg/cgroups/apply_raw.go @@ -49,6 +49,9 @@ func rawApply(c *Cgroup, pid int) (ActiveCgroup, error) { if err := raw.setupCpu(c, pid); err != nil { return nil, err } + if err := raw.setupCpuset(c, pid); err != nil { + return nil, err + } return raw, nil } @@ -170,6 +173,25 @@ func (raw *rawCgroup) setupCpu(c *Cgroup, pid int) (err error) { return nil } +func (raw *rawCgroup) setupCpuset(c *Cgroup, pid int) (err error) { + if c.CpusetCpus != "" { + dir, err := raw.join("cpuset", pid) + if err != nil { + return err + } + defer func() { + if err != nil { + os.RemoveAll(dir) + } + }() + + if err := writeFile(dir, "cpuset.cpus", c.CpusetCpus); err != nil { + return err + } + } + return nil +} + func (raw *rawCgroup) Cleanup() error { get := func(subsystem string) string { path, _ := raw.path(subsystem) @@ -180,6 +202,7 @@ func (raw *rawCgroup) Cleanup() error { get("memory"), get("devices"), get("cpu"), + get("cpuset"), } { if path != "" { os.RemoveAll(path) diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go index cdf268711a..5fe10346df 100644 --- a/pkg/cgroups/cgroups.go +++ b/pkg/cgroups/cgroups.go @@ -101,22 +101,3 @@ func (c *Cgroup) Apply(pid int) (ActiveCgroup, error) { return rawApply(c, pid) } } - -func (c *Cgroup) setupCpuset(cgroupRoot string, pid int) (err error) { - if c.CpusetCpus != "" { - dir, err := c.Join(cgroupRoot, "cpuset", pid) - if err != nil { - return err - } - defer func() { - if err != nil { - os.RemoveAll(dir) - } - }() - - if err := writeFile(dir, "cpuset.cpus", c.CpusetCpus); err != nil { - return err - } - } - return nil -} diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 1a9ffbada5..9a92258644 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -17,11 +17,6 @@ type HostConfig struct { DriverOptions map[string][]string } -type KeyValuePair struct { - Key string - Value string -} - func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { hostConfig := &HostConfig{ ContainerIDFile: job.Getenv("ContainerIDFile"), diff --git a/runconfig/parse.go b/runconfig/parse.go index b89d6c4683..a330c6c869 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -4,10 +4,8 @@ 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" @@ -34,10 +32,6 @@ 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) @@ -67,7 +61,6 @@ 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)") @@ -159,15 +152,6 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf entrypoint = []string{*flEntrypoint} } - 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 @@ -222,10 +206,6 @@ 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) @@ -233,11 +213,6 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf return nil, nil, cmd, err } - pluginOptions, err := parseDriverOpts(flDriverOpts) - if err != nil { - return nil, nil, cmd, err - } - hostConfig := &HostConfig{ Binds: binds, ContainerIDFile: *flContainerIDFile, @@ -289,20 +264,3 @@ func parseKeyValueOpts(opts opts.ListOpts) ([]utils.KeyValuePair, error) { } return out, nil } - -// options will come in the format of name.type=value -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[parts[0]] = append(values, parts[1]) - } - return out, nil -} diff --git a/runtime/container.go b/runtime/container.go index 656e9ae587..f37ffcd1e7 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -404,7 +404,6 @@ func populateCommand(c *Container) { User: c.Config.User, Config: driverConfig, Resources: resources, - Context: c.Config.Context, } c.command.SysProcAttr = &syscall.SysProcAttr{Setsid: true} } diff --git a/runtime/execdriver/lxc/lxc_template.go b/runtime/execdriver/lxc/lxc_template.go index 230518bd7f..67095383ec 100644 --- a/runtime/execdriver/lxc/lxc_template.go +++ b/runtime/execdriver/lxc/lxc_template.go @@ -30,9 +30,9 @@ lxc.pts = 1024 # disable the main console lxc.console = none -{{if getProcessLabel .Context}} -lxc.se_context = {{ getProcessLabel .Context}} -{{$MOUNTLABEL := getMountLabel .Context}} +{{if getProcessLabel .Config}} +lxc.se_context = {{ getProcessLabel .Config}} +{{$MOUNTLABEL := getMountLabel .Config}} {{end}} # no controlling tty at all @@ -147,12 +147,23 @@ func getMemorySwap(v *execdriver.Resources) int64 { return v.Memory * 2 } -func getProcessLabel(c execdriver.Context) string { - return c["process_label"] +func getProcessLabel(c map[string][]string) string { + return getLabel(c, "process") } -func getMountLabel(c execdriver.Context) string { - return c["mount_label"] +func getMountLabel(c map[string][]string) string { + return getLabel(c, "mount") +} + +func getLabel(c map[string][]string, name string) string { + label := c["label"] + for _, l := range label { + parts := strings.SplitN(l, "=", 2) + if parts[0] == name { + return parts[1] + } + } + return "" } func init() { diff --git a/runtime/utils_test.go b/runtime/utils_test.go index 81c745c0d5..833634cb47 100644 --- a/runtime/utils_test.go +++ b/runtime/utils_test.go @@ -2,13 +2,14 @@ package runtime import ( "github.com/dotcloud/docker/runconfig" + "github.com/dotcloud/docker/utils" "testing" ) func TestMergeLxcConfig(t *testing.T) { var ( hostConfig = &runconfig.HostConfig{ - LxcConf: []runconfig.KeyValuePair{ + LxcConf: []utils.KeyValuePair{ {Key: "lxc.cgroups.cpuset", Value: "1,2"}, }, } From bfa2141765c2a3866ca0f5237fc951f4c2db8b98 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 27 Mar 2014 08:57:01 +0000 Subject: [PATCH 222/384] Update lxc to use opts for selinux labels Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runtime/execdriver/lxc/driver.go | 24 ++++++++++++++++++++---- runtime/execdriver/lxc/lxc_template.go | 10 +++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/runtime/execdriver/lxc/driver.go b/runtime/execdriver/lxc/driver.go index 086e35f643..896f215366 100644 --- a/runtime/execdriver/lxc/driver.go +++ b/runtime/execdriver/lxc/driver.go @@ -3,6 +3,7 @@ package lxc import ( "fmt" "github.com/dotcloud/docker/pkg/cgroups" + "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/runtime/execdriver" "github.com/dotcloud/docker/utils" "io/ioutil" @@ -378,19 +379,34 @@ func rootIsShared() bool { } func (d *driver) generateLXCConfig(c *execdriver.Command) (string, error) { - root := path.Join(d.root, "containers", c.ID, "config.lxc") + var ( + process, mount string + root = path.Join(d.root, "containers", c.ID, "config.lxc") + labels = c.Config["label"] + ) fo, err := os.Create(root) if err != nil { return "", err } defer fo.Close() + if len(labels) > 0 { + process, mount, err = label.GenLabels(labels[0]) + if err != nil { + return "", err + } + } + if err := LxcTemplateCompiled.Execute(fo, struct { *execdriver.Command - AppArmor bool + AppArmor bool + ProcessLabel string + MountLabel string }{ - Command: c, - AppArmor: d.apparmor, + Command: c, + AppArmor: d.apparmor, + ProcessLabel: process, + MountLabel: mount, }); err != nil { return "", err } diff --git a/runtime/execdriver/lxc/lxc_template.go b/runtime/execdriver/lxc/lxc_template.go index 67095383ec..e5248375a8 100644 --- a/runtime/execdriver/lxc/lxc_template.go +++ b/runtime/execdriver/lxc/lxc_template.go @@ -30,9 +30,9 @@ lxc.pts = 1024 # disable the main console lxc.console = none -{{if getProcessLabel .Config}} -lxc.se_context = {{ getProcessLabel .Config}} -{{$MOUNTLABEL := getMountLabel .Config}} +{{if .ProcessLabel}} +lxc.se_context = {{ .ProcessLabel}} +{{$MOUNTLABEL := .MountLabel}} {{end}} # no controlling tty at all @@ -159,8 +159,8 @@ func getLabel(c map[string][]string, name string) string { label := c["label"] for _, l := range label { parts := strings.SplitN(l, "=", 2) - if parts[0] == name { - return parts[1] + if strings.TrimSpace(parts[0]) == name { + return strings.TrimSpace(parts[1]) } } return "" From 6c9a47f01c583e9c22b831eb426192148d29d792 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 27 Mar 2014 09:04:54 +0000 Subject: [PATCH 223/384] Update native driver to use labels from opts Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runtime/execdriver/native/create.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/runtime/execdriver/native/create.go b/runtime/execdriver/native/create.go index 7e663f0555..976416a8ca 100644 --- a/runtime/execdriver/native/create.go +++ b/runtime/execdriver/native/create.go @@ -2,6 +2,7 @@ package native import ( "fmt" + "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/runtime/execdriver" "github.com/dotcloud/docker/runtime/execdriver/native/configuration" @@ -37,6 +38,9 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Container if err := d.setupMounts(container, c); err != nil { return nil, err } + if err := d.setupLabels(container, c); err != nil { + return nil, err + } if err := configuration.ParseConfiguration(container, d.activeContainers, c.Config["native"]); err != nil { return nil, err } @@ -94,3 +98,16 @@ func (d *driver) setupMounts(container *libcontainer.Container, c *execdriver.Co } return nil } + +func (d *driver) setupLabels(container *libcontainer.Container, c *execdriver.Command) error { + labels := c.Config["label"] + if len(labels) > 0 { + process, mount, err := label.GenLabels(labels[0]) + if err != nil { + return err + } + container.Context["mount_label"] = mount + container.Context["process_label"] = process + } + return nil +} From ad3e71d5c7e01dca229d4077cf8b019d8085c33a Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 27 Mar 2014 11:06:32 -0600 Subject: [PATCH 224/384] Adjust TestOnlyLoopbackExistsWhenUsingDisableNetworkOption to ignore "DOWN" interfaces This fixes the following, which I've been seeing on all my machines for as long as I can remember: --- FAIL: TestOnlyLoopbackExistsWhenUsingDisableNetworkOption (0.36 seconds) container_test.go:1597: Wrong interface count in test container: expected [*: lo], got [1: lo 2: sit0] Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- integration/container_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/container_test.go b/integration/container_test.go index 663b350638..8ed5525c72 100644 --- a/integration/container_test.go +++ b/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) } From 73ee4879afd557a3ddd0740b0a281024060f2436 Mon Sep 17 00:00:00 2001 From: Michael Gorsuch Date: Thu, 27 Mar 2014 12:44:33 -0500 Subject: [PATCH 225/384] upstart: use exec here so upstart can monitor the process and not just a shell Docker-DCO-1.1-Signed-off-by: Michael Gorsuch (github: gorsuch) --- contrib/init/upstart/docker.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/init/upstart/docker.conf b/contrib/init/upstart/docker.conf index 907a536c9c..e27d77e145 100644 --- a/contrib/init/upstart/docker.conf +++ b/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 From d6c2188cae85549a40193273cb9893acefadf863 Mon Sep 17 00:00:00 2001 From: Ryan Thomas Date: Fri, 28 Mar 2014 06:31:04 +1100 Subject: [PATCH 226/384] Docker-DCO-1.1-Signed-off-by: Ryan Thomas (github: rthomas) --- registry/registry.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index 01583f97c2..182ec78a76 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -42,9 +42,9 @@ func pingRegistryEndpoint(endpoint string) (bool, error) { return conn, nil } httpTransport := &http.Transport{ - Dial: httpDial, - Proxy: http.ProxyFromEnvironment, - } + Dial: httpDial, + Proxy: http.ProxyFromEnvironment, + } client := &http.Client{Transport: httpTransport} resp, err := client.Get(endpoint + "_ping") if err != nil { From 9d2a77805139598b272ca6e5f55e3542e1221f26 Mon Sep 17 00:00:00 2001 From: Barnaby Gray Date: Thu, 27 Mar 2014 19:13:27 +0000 Subject: [PATCH 227/384] Update fish completions for docker master. Docker-DCO-1.1-Signed-off-by: Barnaby Gray (github: barnybug) --- contrib/completion/fish/docker.fish | 32 ++++++++++++++++------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/contrib/completion/fish/docker.fish b/contrib/completion/fish/docker.fish index ddec61cffa..edaa5ca8c6 100644 --- a/contrib/completion/fish/docker.fish +++ b/contrib/completion/fish/docker.fish @@ -39,23 +39,25 @@ function __fish_print_docker_images --description 'Print a list of docker images end function __fish_print_docker_repositories --description 'Print a list of docker repositories' - docker images | command awk 'NR>1' | command grep -v '' | command awk '{print $1}' | sort | uniq + docker images | command awk 'NR>1' | command grep -v '' | command awk '{print $1}' | command sort | command uniq end # common options complete -c docker -f -n '__fish_docker_no_subcommand' -s D -l debug -d 'Enable debug mode' +complete -c docker -f -n '__fish_docker_no_subcommand' -s G -l group -d "Group to assign the unix socket specified by -H when running in daemon mode; use '' (the empty string) to disable setting of a group" complete -c docker -f -n '__fish_docker_no_subcommand' -s H -l host -d 'tcp://host:port, unix://path/to/socket, fd://* or fd://socketfd to use in daemon mode. Multiple sockets can be specified' complete -c docker -f -n '__fish_docker_no_subcommand' -l api-enable-cors -d 'Enable CORS headers in the remote API' complete -c docker -f -n '__fish_docker_no_subcommand' -s b -l bridge -d "Attach containers to a pre-existing network bridge; use 'none' to disable container networking" complete -c docker -f -n '__fish_docker_no_subcommand' -l bip -d "Use this CIDR notation address for the network bridge's IP, not compatible with -b" complete -c docker -f -n '__fish_docker_no_subcommand' -s d -l daemon -d 'Enable daemon mode' complete -c docker -f -n '__fish_docker_no_subcommand' -l dns -d 'Force docker to use specific DNS servers' +complete -c docker -f -n '__fish_docker_no_subcommand' -s e -l exec-driver -d 'Force the docker runtime to use a specific exec driver' complete -c docker -f -n '__fish_docker_no_subcommand' -s g -l graph -d 'Path to use as the root of the docker runtime' complete -c docker -f -n '__fish_docker_no_subcommand' -l icc -d 'Enable inter-container communication' complete -c docker -f -n '__fish_docker_no_subcommand' -l ip -d 'Default IP address to use when binding container ports' complete -c docker -f -n '__fish_docker_no_subcommand' -l ip-forward -d 'Disable enabling of net.ipv4.ip_forward' complete -c docker -f -n '__fish_docker_no_subcommand' -l iptables -d "Disable docker's addition of iptables rules" -complete -c docker -f -n '__fish_docker_no_subcommand' -l mtu -d 'Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if not default route is available' +complete -c docker -f -n '__fish_docker_no_subcommand' -l mtu -d 'Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if no default route is available' complete -c docker -f -n '__fish_docker_no_subcommand' -s p -l pidfile -d 'Path to use for daemon PID file' complete -c docker -f -n '__fish_docker_no_subcommand' -s r -l restart -d 'Restart previously running containers' complete -c docker -f -n '__fish_docker_no_subcommand' -s s -l storage-driver -d 'Force the docker runtime to use a specific storage driver' @@ -71,7 +73,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from attach' -a '(__fish_pri # build complete -c docker -f -n '__fish_docker_no_subcommand' -a build -d 'Build a container from a Dockerfile' complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l no-cache -d 'Do not use cache when building the image' -complete -c docker -A -f -n '__fish_seen_subcommand_from build' -s q -l quiet -d 'Suppress verbose build output' +complete -c docker -A -f -n '__fish_seen_subcommand_from build' -s q -l quiet -d 'Suppress the verbose output generated by the containers' complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l rm -d 'Remove intermediate containers after a successful build' complete -c docker -A -f -n '__fish_seen_subcommand_from build' -s t -l tag -d 'Repository name (and optionally a tag) to be applied to the resulting image in case of success' @@ -79,7 +81,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from build' -s t -l tag -d ' complete -c docker -f -n '__fish_docker_no_subcommand' -a commit -d "Create a new image from a container's changes" complete -c docker -A -f -n '__fish_seen_subcommand_from commit' -s a -l author -d 'Author (eg. "John Hannibal Smith "' complete -c docker -A -f -n '__fish_seen_subcommand_from commit' -s m -l message -d 'Commit message' -complete -c docker -A -f -n '__fish_seen_subcommand_from commit' -l run -d 'Config automatically applied when the image is run. (ex: --run=\'{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}\')' +complete -c docker -A -f -n '__fish_seen_subcommand_from commit' -l run -d 'Config automatically applied when the image is run. (ex: -run=\'{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}\')' complete -c docker -A -f -n '__fish_seen_subcommand_from commit' -a '(__fish_print_docker_containers all)' -d "Container" # cp @@ -100,16 +102,16 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from export' -a '(__fish_pri # history complete -c docker -f -n '__fish_docker_no_subcommand' -a history -d 'Show the history of an image' complete -c docker -A -f -n '__fish_seen_subcommand_from history' -l no-trunc -d "Don't truncate output" -complete -c docker -A -f -n '__fish_seen_subcommand_from history' -s q -l quiet -d 'only show numeric IDs' +complete -c docker -A -f -n '__fish_seen_subcommand_from history' -s q -l quiet -d 'Only show numeric IDs' complete -c docker -A -f -n '__fish_seen_subcommand_from history' -a '(__fish_print_docker_images)' -d "Image" # images complete -c docker -f -n '__fish_docker_no_subcommand' -a images -d 'List images' -complete -c docker -A -f -n '__fish_seen_subcommand_from images' -s a -l all -d 'show all images (by default filter out the intermediate images used to build)' +complete -c docker -A -f -n '__fish_seen_subcommand_from images' -s a -l all -d 'Show all images (by default filter out the intermediate images used to build)' complete -c docker -A -f -n '__fish_seen_subcommand_from images' -l no-trunc -d "Don't truncate output" -complete -c docker -A -f -n '__fish_seen_subcommand_from images' -s q -l quiet -d 'only show numeric IDs' -complete -c docker -A -f -n '__fish_seen_subcommand_from images' -s t -l tree -d 'output graph in tree format' -complete -c docker -A -f -n '__fish_seen_subcommand_from images' -s v -l viz -d 'output graph in graphviz format' +complete -c docker -A -f -n '__fish_seen_subcommand_from images' -s q -l quiet -d 'Only show numeric IDs' +complete -c docker -A -f -n '__fish_seen_subcommand_from images' -s t -l tree -d 'Output graph in tree format' +complete -c docker -A -f -n '__fish_seen_subcommand_from images' -s v -l viz -d 'Output graph in graphviz format' complete -c docker -A -f -n '__fish_seen_subcommand_from images' -a '(__fish_print_docker_repositories)' -d "Repository" # import @@ -126,7 +128,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from insert' -a '(__fish_pri complete -c docker -f -n '__fish_docker_no_subcommand' -a inspect -d 'Return low-level information on a container' complete -c docker -A -f -n '__fish_seen_subcommand_from inspect' -s f -l format -d 'Format the output using the given go template.' complete -c docker -A -f -n '__fish_seen_subcommand_from inspect' -a '(__fish_print_docker_images)' -d "Image" -complete -c docker -A -f -n '__fish_seen_subcommand_from inspect' -a '(__fish_print_docker_containers running)' -d "Container" +complete -c docker -A -f -n '__fish_seen_subcommand_from inspect' -a '(__fish_print_docker_containers all)' -d "Container" # kill complete -c docker -f -n '__fish_docker_no_subcommand' -a kill -d 'Kill a running container' @@ -138,9 +140,9 @@ complete -c docker -f -n '__fish_docker_no_subcommand' -a load -d 'Load an image # login complete -c docker -f -n '__fish_docker_no_subcommand' -a login -d 'Register or Login to the docker registry server' -complete -c docker -A -f -n '__fish_seen_subcommand_from login' -s e -l email -d 'email' -complete -c docker -A -f -n '__fish_seen_subcommand_from login' -s p -l password -d 'password' -complete -c docker -A -f -n '__fish_seen_subcommand_from login' -s u -l username -d 'username' +complete -c docker -A -f -n '__fish_seen_subcommand_from login' -s e -l email -d 'Email' +complete -c docker -A -f -n '__fish_seen_subcommand_from login' -s p -l password -d 'Password' +complete -c docker -A -f -n '__fish_seen_subcommand_from login' -s u -l username -d 'Username' # logs complete -c docker -f -n '__fish_docker_no_subcommand' -a logs -d 'Fetch the logs of a container' @@ -180,12 +182,14 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from restart' -a '(__fish_pr # rm complete -c docker -f -n '__fish_docker_no_subcommand' -a rm -d 'Remove one or more containers' +complete -c docker -A -f -n '__fish_seen_subcommand_from rm' -s f -l force -d 'Force removal of running container' complete -c docker -A -f -n '__fish_seen_subcommand_from rm' -s l -l link -d 'Remove the specified link and not the underlying container' complete -c docker -A -f -n '__fish_seen_subcommand_from rm' -s v -l volumes -d 'Remove the volumes associated to the container' complete -c docker -A -f -n '__fish_seen_subcommand_from rm' -a '(__fish_print_docker_containers stopped)' -d "Container" # rmi complete -c docker -f -n '__fish_docker_no_subcommand' -a rmi -d 'Remove one or more images' +complete -c docker -A -f -n '__fish_seen_subcommand_from rmi' -s f -l force -d 'Force' complete -c docker -A -f -n '__fish_seen_subcommand_from rmi' -a '(__fish_print_docker_images)' -d "Image" # run @@ -202,7 +206,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l expose -d 'Expo complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s h -l hostname -d 'Container host name' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s i -l interactive -d 'Keep stdin open even if not attached' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l link -d 'Add link to another container (name:alias)' -complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l lxc-conf -d 'Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"' +complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l lxc-conf -d 'Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s m -l memory -d 'Memory limit (format: , where unit = b, k, m or g)' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s n -l networking -d 'Enable networking for this container' complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l name -d 'Assign a name to the container' From 4af79a36e283e94cb48442499534f996e27e0f29 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 12 Mar 2014 01:58:53 -0600 Subject: [PATCH 228/384] Add mention of mounting cgroupfs properly to PACKAGERS.md Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- docs/sources/installation/binaries.rst | 6 ++++++ hack/PACKAGERS.md | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/docs/sources/installation/binaries.rst b/docs/sources/installation/binaries.rst index a070599338..ae548e7657 100644 --- a/docs/sources/installation/binaries.rst +++ b/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 + `_ + cgroupfs hierarchy (having a single, all-encompassing "cgroup" mount point `is + `_ `not + `_ `sufficient + `_) Check kernel dependencies diff --git a/hack/PACKAGERS.md b/hack/PACKAGERS.md index dc255c57ad..7170c5ad25 100644 --- a/hack/PACKAGERS.md +++ b/hack/PACKAGERS.md @@ -266,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: From 7a3070a6000963d12be9dcd2698d911b848a33b6 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 13 Mar 2014 17:03:09 +0100 Subject: [PATCH 229/384] Add --opt arguments for drivers In order to handle special configuration for different drivers we make the Config field a map to string array. This lets us use it for lxc, by using the "lxc" key for those, and we can later extend it easily for other backend-specific options. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- runconfig/hostconfig.go | 10 ++--- runconfig/parse.go | 45 +++++++++++++------ runconfig/parse_test.go | 3 +- runtime/container.go | 14 +++--- runtime/execdriver/driver.go | 30 ++++++------- runtime/execdriver/lxc/lxc_template.go | 4 +- .../execdriver/lxc/lxc_template_unit_test.go | 8 ++-- runtime/execdriver/native/default_template.go | 1 + runtime/execdriver/native/driver.go | 7 ++- runtime/utils.go | 20 +++++++++ utils/utils.go | 13 ++++++ 11 files changed, 104 insertions(+), 51 deletions(-) diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 6c8618ee81..9a92258644 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -3,21 +3,18 @@ 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 PublishAllPorts bool -} - -type KeyValuePair struct { - Key string - Value string + DriverOptions map[string][]string } func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { @@ -28,6 +25,7 @@ func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { } job.GetenvJson("LxcConf", &hostConfig.LxcConf) job.GetenvJson("PortBindings", &hostConfig.PortBindings) + job.GetenvJson("DriverOptions", &hostConfig.DriverOptions) if Binds := job.GetenvList("Binds"); Binds != nil { hostConfig.Binds = Binds } diff --git a/runconfig/parse.go b/runconfig/parse.go index 23c66cd611..43aecdb753 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -51,6 +51,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf flDnsSearch = opts.NewListOpts(opts.ValidateDomain) flVolumesFrom opts.ListOpts flLxcOpts opts.ListOpts + flDriverOpts opts.ListOpts flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)") flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: Run container in the background, print new container id") @@ -83,7 +84,8 @@ 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"}, "(lxc exec-driver only) 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 { return nil, nil, cmd, err @@ -166,7 +168,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf mountLabel = mLabel } - lxcConf, err := parseLxcConfOpts(flLxcOpts) + lxcConf, err := parseKeyValueOpts(flLxcOpts) if err != nil { return nil, nil, cmd, err } @@ -226,6 +228,11 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf }, } + driverOptions, err := parseDriverOpts(flDriverOpts) + if err != nil { + return nil, nil, cmd, err + } + hostConfig := &HostConfig{ Binds: binds, ContainerIDFile: *flContainerIDFile, @@ -234,6 +241,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf PortBindings: portBindings, Links: flLinks.GetAll(), PublishAllPorts: *flPublishAll, + DriverOptions: driverOptions, } if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit { @@ -248,22 +256,31 @@ 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) } - out[i] = KeyValuePair{Key: k, Value: v} + values, exists := out[parts[0]] + if !exists { + values = []string{} + } + 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 } diff --git a/runconfig/parse_test.go b/runconfig/parse_test.go index 2b89e88ec3..fd28c4593e 100644 --- a/runconfig/parse_test.go +++ b/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() } diff --git a/runtime/container.go b/runtime/container.go index 4cf307d823..656e9ae587 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -361,9 +361,13 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s func populateCommand(c *Container) { var ( en *execdriver.Network - driverConfig []string + driverConfig = c.hostConfig.DriverOptions ) + if driverConfig == nil { + driverConfig = make(map[string][]string) + } + en = &execdriver.Network{ Mtu: c.runtime.config.Mtu, Interface: nil, @@ -379,11 +383,9 @@ func populateCommand(c *Container) { } } - if lxcConf := c.hostConfig.LxcConf; lxcConf != nil { - for _, pair := range lxcConf { - driverConfig = append(driverConfig, fmt.Sprintf("%s = %s", pair.Key, pair.Value)) - } - } + // TODO: this can be removed after lxc-conf is fully deprecated + mergeLxcConfIntoOptions(c.hostConfig, driverConfig) + resources := &execdriver.Resources{ Memory: c.Config.Memory, MemorySwap: c.Config.MemorySwap, diff --git a/runtime/execdriver/driver.go b/runtime/execdriver/driver.go index dca889a82d..096ea0790d 100644 --- a/runtime/execdriver/driver.go +++ b/runtime/execdriver/driver.go @@ -116,21 +116,21 @@ type Mount struct { type Command struct { exec.Cmd `json:"-"` - ID string `json:"id"` - Privileged bool `json:"privileged"` - User string `json:"user"` - Rootfs string `json:"rootfs"` // root fs of the container - InitPath string `json:"initpath"` // dockerinit - Entrypoint string `json:"entrypoint"` - Arguments []string `json:"arguments"` - WorkingDir string `json:"working_dir"` - ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver - Context Context `json:"context"` // generic context for specific options (apparmor, selinux) - Tty bool `json:"tty"` - Network *Network `json:"network"` - Config []string `json:"config"` // generic values that specific drivers can consume - Resources *Resources `json:"resources"` - Mounts []Mount `json:"mounts"` + ID string `json:"id"` + Privileged bool `json:"privileged"` + User string `json:"user"` + Rootfs string `json:"rootfs"` // root fs of the container + InitPath string `json:"initpath"` // dockerinit + Entrypoint string `json:"entrypoint"` + Arguments []string `json:"arguments"` + WorkingDir string `json:"working_dir"` + ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver + Context Context `json:"context"` // generic context for specific options (apparmor, selinux) + Tty bool `json:"tty"` + Network *Network `json:"network"` + Config map[string][]string `json:"config"` // generic values that specific drivers can consume + Resources *Resources `json:"resources"` + Mounts []Mount `json:"mounts"` Terminal Terminal `json:"-"` // standard or tty terminal Console string `json:"-"` // dev/console path diff --git a/runtime/execdriver/lxc/lxc_template.go b/runtime/execdriver/lxc/lxc_template.go index 608fb22436..f325ffcaef 100644 --- a/runtime/execdriver/lxc/lxc_template.go +++ b/runtime/execdriver/lxc/lxc_template.go @@ -123,8 +123,8 @@ lxc.cgroup.cpu.shares = {{.Resources.CpuShares}} {{end}} {{end}} -{{if .Config}} -{{range $value := .Config}} +{{if .Config.lxc}} +{{range $value := .Config.lxc}} {{$value}} {{end}} {{end}} diff --git a/runtime/execdriver/lxc/lxc_template_unit_test.go b/runtime/execdriver/lxc/lxc_template_unit_test.go index e613adf7a9..7f473a0502 100644 --- a/runtime/execdriver/lxc/lxc_template_unit_test.go +++ b/runtime/execdriver/lxc/lxc_template_unit_test.go @@ -75,9 +75,11 @@ func TestCustomLxcConfig(t *testing.T) { command := &execdriver.Command{ ID: "1", Privileged: false, - Config: []string{ - "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, diff --git a/runtime/execdriver/native/default_template.go b/runtime/execdriver/native/default_template.go index 7e1e9ed86e..e11f2de1cf 100644 --- a/runtime/execdriver/native/default_template.go +++ b/runtime/execdriver/native/default_template.go @@ -58,6 +58,7 @@ func createContainer(c *execdriver.Command) *libcontainer.Container { container.Cgroups.Memory = c.Resources.Memory container.Cgroups.MemorySwap = c.Resources.MemorySwap } + // check to see if we are running in ramdisk to disable pivot root container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != "" diff --git a/runtime/execdriver/native/driver.go b/runtime/execdriver/native/driver.go index bf7e8ccdec..db974cbf04 100644 --- a/runtime/execdriver/native/driver.go +++ b/runtime/execdriver/native/driver.go @@ -184,10 +184,9 @@ func (d *driver) removeContainerRoot(id string) error { func (d *driver) validateCommand(c *execdriver.Command) error { // we need to check the Config of the command to make sure that we // do not have any of the lxc-conf variables - for _, conf := range c.Config { - if strings.Contains(conf, "lxc") { - return fmt.Errorf("%s is not supported by the native driver", conf) - } + lxc := c.Config["lxc"] + if lxc != nil && len(lxc) > 0 { + return fmt.Errorf("lxc config options are not supported by the native driver") } return nil } diff --git a/runtime/utils.go b/runtime/utils.go index b343b5b10e..b983e67d41 100644 --- a/runtime/utils.go +++ b/runtime/utils.go @@ -1,9 +1,11 @@ package runtime import ( + "fmt" "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/pkg/namesgenerator" "github.com/dotcloud/docker/runconfig" + "strings" ) func migratePortMappings(config *runconfig.Config, hostConfig *runconfig.HostConfig) error { @@ -30,6 +32,24 @@ func migratePortMappings(config *runconfig.Config, hostConfig *runconfig.HostCon return nil } +func mergeLxcConfIntoOptions(hostConfig *runconfig.HostConfig, driverConfig map[string][]string) { + if hostConfig == nil { + return + } + + // merge in the lxc conf options into the generic config map + if lxcConf := hostConfig.LxcConf; lxcConf != nil { + lxc := driverConfig["lxc"] + for _, pair := range lxcConf { + // because lxc conf gets the driver name lxc.XXXX we need to trim it off + // and let the lxc driver add it back later if needed + parts := strings.SplitN(pair.Key, ".", 2) + lxc = append(lxc, fmt.Sprintf("%s=%s", parts[1], pair.Value)) + } + driverConfig["lxc"] = lxc + } +} + type checker struct { runtime *Runtime } diff --git a/utils/utils.go b/utils/utils.go index 2702555973..1fe2e87b4f 100644 --- a/utils/utils.go +++ b/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 +} From 7f7d8419a71d49b25e4d38196b36e93b568bb61d Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 14 Mar 2014 10:47:49 +0100 Subject: [PATCH 230/384] cgroups: Splity out Apply/Cleanup to separate file/interface This leaves only the generic cgroup helper functions in cgroups.go and will allow easy implementations of other cgroup managers. This also wires up the call to Cleanup the cgroup which was missing before. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- pkg/cgroups/apply_raw.go | 189 ++++++++++++++++++++++++++++++++ pkg/cgroups/cgroups.go | 171 +---------------------------- pkg/libcontainer/nsinit/exec.go | 16 ++- 3 files changed, 205 insertions(+), 171 deletions(-) create mode 100644 pkg/cgroups/apply_raw.go diff --git a/pkg/cgroups/apply_raw.go b/pkg/cgroups/apply_raw.go new file mode 100644 index 0000000000..bce96f4951 --- /dev/null +++ b/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, "tasks", 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 +} diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go index b40e1a31fa..f35556f712 100644 --- a/pkg/cgroups/cgroups.go +++ b/pkg/cgroups/cgroups.go @@ -8,7 +8,6 @@ import ( "io/ioutil" "os" "path/filepath" - "strconv" "strings" ) @@ -22,6 +21,10 @@ type Cgroup struct { CpuShares int64 `json:"cpu_shares,omitempty"` // CPU shares (relative weight vs. other containers) } +type ActiveCgroup interface { + Cleanup() error +} + // https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt func FindCgroupMountpoint(subsystem string) (string, error) { mounts, err := mount.GetMounts() @@ -62,48 +65,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"), - } { - os.RemoveAll(path) - } - return nil -} - func parseCgroupFile(subsystem string, r io.Reader) (string, error) { s := bufio.NewScanner(r) for s.Scan() { @@ -125,126 +86,6 @@ func writeFile(dir, file, data string) error { return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700) } -func (c *Cgroup) Apply(pid int) 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 - } - 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 - } - } - } - 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) Apply(pid int) (ActiveCgroup, error) { + return rawApply(c, pid) } diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 61286cc13c..a44faafe0e 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -3,6 +3,7 @@ package nsinit import ( + "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" @@ -61,10 +62,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() @@ -85,13 +91,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 { From 9294d7f2af6ecb7c18be11fb5043fad4a61d8f09 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 14 Mar 2014 11:45:29 +0100 Subject: [PATCH 231/384] cgroups: Join groups by writing to cgroups.procs, not tasks cgroups.procs moves all the threads of the process, and "tasks" just the one thread. I believe there is a risk that we move the main thread, but then we accidentally fork off one of the other threads if the go scheduler randomly switched to another thread. So, it seems safer (and more correct) to use cgroups.procs. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- pkg/cgroups/apply_raw.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cgroups/apply_raw.go b/pkg/cgroups/apply_raw.go index bce96f4951..47a2a002b8 100644 --- a/pkg/cgroups/apply_raw.go +++ b/pkg/cgroups/apply_raw.go @@ -68,7 +68,7 @@ func (raw *rawCgroup) join(subsystem string, pid int) (string, error) { if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) { return "", err } - if err := writeFile(path, "tasks", strconv.Itoa(pid)); err != nil { + if err := writeFile(path, "cgroup.procs", strconv.Itoa(pid)); err != nil { return "", err } return path, nil From d4725801b3401d04b3f35b5783bdc0fc362f7f00 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 21 Feb 2014 10:34:06 +0100 Subject: [PATCH 232/384] Vendor github.com/godbus/dbus and github.com/coreos/go-systemd We need this to do systemd API calls. We also add the static_build tag to make godbus not use os/user which is problematic for static builds. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- hack/make.sh | 2 +- hack/vendor.sh | 3 + .../github.com/coreos/go-systemd/.travis.yml | 8 + .../src/github.com/coreos/go-systemd/LICENSE | 191 ++++ .../github.com/coreos/go-systemd/README.md | 44 + .../coreos/go-systemd/activation/files.go | 56 ++ .../go-systemd/activation/files_test.go | 84 ++ .../coreos/go-systemd/activation/listeners.go | 38 + .../go-systemd/activation/listeners_test.go | 88 ++ .../github.com/coreos/go-systemd/dbus/dbus.go | 104 +++ .../coreos/go-systemd/dbus/dbus_test.go | 41 + .../coreos/go-systemd/dbus/methods.go | 354 ++++++++ .../coreos/go-systemd/dbus/methods_test.go | 314 +++++++ .../coreos/go-systemd/dbus/properties.go | 220 +++++ .../github.com/coreos/go-systemd/dbus/set.go | 26 + .../coreos/go-systemd/dbus/set_test.go | 26 + .../coreos/go-systemd/dbus/subscription.go | 249 ++++++ .../go-systemd/dbus/subscription_set.go | 32 + .../go-systemd/dbus/subscription_set_test.go | 67 ++ .../go-systemd/dbus/subscription_test.go | 90 ++ .../examples/activation/activation.go | 44 + .../examples/activation/httpserver/README.md | 19 + .../activation/httpserver/hello.service | 11 + .../activation/httpserver/hello.socket | 5 + .../activation/httpserver/httpserver.go | 26 + .../go-systemd/examples/activation/listen.go | 50 ++ .../go-systemd/fixtures/start-stop.service | 5 + .../fixtures/subscribe-events-set.service | 5 + .../fixtures/subscribe-events.service | 5 + .../coreos/go-systemd/journal/send.go | 168 ++++ vendor/src/github.com/coreos/go-systemd/test | 3 + vendor/src/github.com/godbus/dbus/LICENSE | 25 + .../github.com/godbus/dbus/README.markdown | 38 + .../godbus/dbus/_examples/eavesdrop.go | 30 + .../godbus/dbus/_examples/introspect.go | 21 + .../godbus/dbus/_examples/list-names.go | 27 + .../godbus/dbus/_examples/notification.go | 17 + .../github.com/godbus/dbus/_examples/prop.go | 68 ++ .../godbus/dbus/_examples/server.go | 45 + .../godbus/dbus/_examples/signal.go | 24 + vendor/src/github.com/godbus/dbus/auth.go | 253 ++++++ .../github.com/godbus/dbus/auth_external.go | 26 + .../src/github.com/godbus/dbus/auth_sha1.go | 102 +++ vendor/src/github.com/godbus/dbus/call.go | 147 ++++ vendor/src/github.com/godbus/dbus/conn.go | 601 +++++++++++++ .../src/github.com/godbus/dbus/conn_darwin.go | 21 + .../src/github.com/godbus/dbus/conn_other.go | 27 + .../src/github.com/godbus/dbus/conn_test.go | 199 +++++ vendor/src/github.com/godbus/dbus/dbus.go | 258 ++++++ vendor/src/github.com/godbus/dbus/decoder.go | 228 +++++ vendor/src/github.com/godbus/dbus/doc.go | 63 ++ vendor/src/github.com/godbus/dbus/encoder.go | 179 ++++ .../github.com/godbus/dbus/examples_test.go | 50 ++ vendor/src/github.com/godbus/dbus/export.go | 302 +++++++ vendor/src/github.com/godbus/dbus/homedir.go | 28 + .../github.com/godbus/dbus/homedir_dynamic.go | 15 + .../github.com/godbus/dbus/homedir_static.go | 45 + .../github.com/godbus/dbus/introspect/call.go | 27 + .../godbus/dbus/introspect/introspect.go | 80 ++ .../godbus/dbus/introspect/introspectable.go | 74 ++ vendor/src/github.com/godbus/dbus/message.go | 346 ++++++++ .../src/github.com/godbus/dbus/prop/prop.go | 264 ++++++ .../src/github.com/godbus/dbus/proto_test.go | 369 ++++++++ vendor/src/github.com/godbus/dbus/sig.go | 257 ++++++ vendor/src/github.com/godbus/dbus/sig_test.go | 70 ++ .../godbus/dbus/transport_darwin.go | 6 + .../godbus/dbus/transport_generic.go | 35 + .../github.com/godbus/dbus/transport_unix.go | 190 ++++ .../godbus/dbus/transport_unix_test.go | 49 ++ .../godbus/dbus/transport_unixcred.go | 22 + vendor/src/github.com/godbus/dbus/variant.go | 129 +++ .../github.com/godbus/dbus/variant_lexer.go | 284 ++++++ .../github.com/godbus/dbus/variant_parser.go | 817 ++++++++++++++++++ .../github.com/godbus/dbus/variant_test.go | 78 ++ 74 files changed, 8313 insertions(+), 1 deletion(-) create mode 100644 vendor/src/github.com/coreos/go-systemd/.travis.yml create mode 100644 vendor/src/github.com/coreos/go-systemd/LICENSE create mode 100644 vendor/src/github.com/coreos/go-systemd/README.md create mode 100644 vendor/src/github.com/coreos/go-systemd/activation/files.go create mode 100644 vendor/src/github.com/coreos/go-systemd/activation/files_test.go create mode 100644 vendor/src/github.com/coreos/go-systemd/activation/listeners.go create mode 100644 vendor/src/github.com/coreos/go-systemd/activation/listeners_test.go create mode 100644 vendor/src/github.com/coreos/go-systemd/dbus/dbus.go create mode 100644 vendor/src/github.com/coreos/go-systemd/dbus/dbus_test.go create mode 100644 vendor/src/github.com/coreos/go-systemd/dbus/methods.go create mode 100644 vendor/src/github.com/coreos/go-systemd/dbus/methods_test.go create mode 100644 vendor/src/github.com/coreos/go-systemd/dbus/properties.go create mode 100644 vendor/src/github.com/coreos/go-systemd/dbus/set.go create mode 100644 vendor/src/github.com/coreos/go-systemd/dbus/set_test.go create mode 100644 vendor/src/github.com/coreos/go-systemd/dbus/subscription.go create mode 100644 vendor/src/github.com/coreos/go-systemd/dbus/subscription_set.go create mode 100644 vendor/src/github.com/coreos/go-systemd/dbus/subscription_set_test.go create mode 100644 vendor/src/github.com/coreos/go-systemd/dbus/subscription_test.go create mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/activation.go create mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/README.md create mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.service create mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.socket create mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/httpserver.go create mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/listen.go create mode 100644 vendor/src/github.com/coreos/go-systemd/fixtures/start-stop.service create mode 100644 vendor/src/github.com/coreos/go-systemd/fixtures/subscribe-events-set.service create mode 100644 vendor/src/github.com/coreos/go-systemd/fixtures/subscribe-events.service create mode 100644 vendor/src/github.com/coreos/go-systemd/journal/send.go create mode 100755 vendor/src/github.com/coreos/go-systemd/test create mode 100644 vendor/src/github.com/godbus/dbus/LICENSE create mode 100644 vendor/src/github.com/godbus/dbus/README.markdown create mode 100644 vendor/src/github.com/godbus/dbus/_examples/eavesdrop.go create mode 100644 vendor/src/github.com/godbus/dbus/_examples/introspect.go create mode 100644 vendor/src/github.com/godbus/dbus/_examples/list-names.go create mode 100644 vendor/src/github.com/godbus/dbus/_examples/notification.go create mode 100644 vendor/src/github.com/godbus/dbus/_examples/prop.go create mode 100644 vendor/src/github.com/godbus/dbus/_examples/server.go create mode 100644 vendor/src/github.com/godbus/dbus/_examples/signal.go create mode 100644 vendor/src/github.com/godbus/dbus/auth.go create mode 100644 vendor/src/github.com/godbus/dbus/auth_external.go create mode 100644 vendor/src/github.com/godbus/dbus/auth_sha1.go create mode 100644 vendor/src/github.com/godbus/dbus/call.go create mode 100644 vendor/src/github.com/godbus/dbus/conn.go create mode 100644 vendor/src/github.com/godbus/dbus/conn_darwin.go create mode 100644 vendor/src/github.com/godbus/dbus/conn_other.go create mode 100644 vendor/src/github.com/godbus/dbus/conn_test.go create mode 100644 vendor/src/github.com/godbus/dbus/dbus.go create mode 100644 vendor/src/github.com/godbus/dbus/decoder.go create mode 100644 vendor/src/github.com/godbus/dbus/doc.go create mode 100644 vendor/src/github.com/godbus/dbus/encoder.go create mode 100644 vendor/src/github.com/godbus/dbus/examples_test.go create mode 100644 vendor/src/github.com/godbus/dbus/export.go create mode 100644 vendor/src/github.com/godbus/dbus/homedir.go create mode 100644 vendor/src/github.com/godbus/dbus/homedir_dynamic.go create mode 100644 vendor/src/github.com/godbus/dbus/homedir_static.go create mode 100644 vendor/src/github.com/godbus/dbus/introspect/call.go create mode 100644 vendor/src/github.com/godbus/dbus/introspect/introspect.go create mode 100644 vendor/src/github.com/godbus/dbus/introspect/introspectable.go create mode 100644 vendor/src/github.com/godbus/dbus/message.go create mode 100644 vendor/src/github.com/godbus/dbus/prop/prop.go create mode 100644 vendor/src/github.com/godbus/dbus/proto_test.go create mode 100644 vendor/src/github.com/godbus/dbus/sig.go create mode 100644 vendor/src/github.com/godbus/dbus/sig_test.go create mode 100644 vendor/src/github.com/godbus/dbus/transport_darwin.go create mode 100644 vendor/src/github.com/godbus/dbus/transport_generic.go create mode 100644 vendor/src/github.com/godbus/dbus/transport_unix.go create mode 100644 vendor/src/github.com/godbus/dbus/transport_unix_test.go create mode 100644 vendor/src/github.com/godbus/dbus/transport_unixcred.go create mode 100644 vendor/src/github.com/godbus/dbus/variant.go create mode 100644 vendor/src/github.com/godbus/dbus/variant_lexer.go create mode 100644 vendor/src/github.com/godbus/dbus/variant_parser.go create mode 100644 vendor/src/github.com/godbus/dbus/variant_test.go diff --git a/hack/make.sh b/hack/make.sh index b77e9b7f44..dbb9dbfdfd 100755 --- a/hack/make.sh +++ b/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. diff --git a/hack/vendor.sh b/hack/vendor.sh index ac996dde12..4200d90867 100755 --- a/hack/vendor.sh +++ b/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 diff --git a/vendor/src/github.com/coreos/go-systemd/.travis.yml b/vendor/src/github.com/coreos/go-systemd/.travis.yml new file mode 100644 index 0000000000..8c9f56e44a --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/.travis.yml @@ -0,0 +1,8 @@ +language: go +go: 1.2 + +install: + - echo "Skip install" + +script: + - ./test diff --git a/vendor/src/github.com/coreos/go-systemd/LICENSE b/vendor/src/github.com/coreos/go-systemd/LICENSE new file mode 100644 index 0000000000..37ec93a14f --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/LICENSE @@ -0,0 +1,191 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/src/github.com/coreos/go-systemd/README.md b/vendor/src/github.com/coreos/go-systemd/README.md new file mode 100644 index 0000000000..0ee09fec0a --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/README.md @@ -0,0 +1,44 @@ +# go-systemd + +Go bindings to systemd. The project has three packages: + +- activation - for writing and using socket activation from Go +- journal - for writing to systemd's logging service, journal +- dbus - for starting/stopping/inspecting running services and units + +Go docs for the entire project are here: + +http://godoc.org/github.com/coreos/go-systemd + +## Socket Activation + +An example HTTP server using socket activation can be quickly setup by +following this README on a Linux machine running systemd: + +https://github.com/coreos/go-systemd/tree/master/examples/activation/httpserver + +## Journal + +Using this package you can submit journal entries directly to systemd's journal taking advantage of features like indexed key/value pairs for each log entry. + +## D-Bus + +The D-Bus API lets you start, stop and introspect systemd units. The API docs are here: + +http://godoc.org/github.com/coreos/go-systemd/dbus + +### Debugging + +Create `/etc/dbus-1/system-local.conf` that looks like this: + +``` + + + + + + + +``` diff --git a/vendor/src/github.com/coreos/go-systemd/activation/files.go b/vendor/src/github.com/coreos/go-systemd/activation/files.go new file mode 100644 index 0000000000..74b4fc10f3 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/activation/files.go @@ -0,0 +1,56 @@ +/* +Copyright 2013 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package activation implements primitives for systemd socket activation. +package activation + +import ( + "os" + "strconv" + "syscall" +) + +// based on: https://gist.github.com/alberts/4640792 +const ( + listenFdsStart = 3 +) + +func Files(unsetEnv bool) []*os.File { + if unsetEnv { + // there is no way to unset env in golang os package for now + // https://code.google.com/p/go/issues/detail?id=6423 + defer os.Setenv("LISTEN_PID", "") + defer os.Setenv("LISTEN_FDS", "") + } + + pid, err := strconv.Atoi(os.Getenv("LISTEN_PID")) + if err != nil || pid != os.Getpid() { + return nil + } + + nfds, err := strconv.Atoi(os.Getenv("LISTEN_FDS")) + if err != nil || nfds == 0 { + return nil + } + + var files []*os.File + for fd := listenFdsStart; fd < listenFdsStart+nfds; fd++ { + syscall.CloseOnExec(fd) + files = append(files, os.NewFile(uintptr(fd), "LISTEN_FD_"+strconv.Itoa(fd))) + } + + return files +} diff --git a/vendor/src/github.com/coreos/go-systemd/activation/files_test.go b/vendor/src/github.com/coreos/go-systemd/activation/files_test.go new file mode 100644 index 0000000000..a1c6948fb2 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/activation/files_test.go @@ -0,0 +1,84 @@ +/* +Copyright 2013 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package activation + +import ( + "bytes" + "io" + "os" + "os/exec" + "testing" +) + +// correctStringWritten fails the text if the correct string wasn't written +// to the other side of the pipe. +func correctStringWritten(t *testing.T, r *os.File, expected string) bool { + bytes := make([]byte, len(expected)) + io.ReadAtLeast(r, bytes, len(expected)) + + if string(bytes) != expected { + t.Fatalf("Unexpected string %s", string(bytes)) + } + + return true +} + +// TestActivation forks out a copy of activation.go example and reads back two +// strings from the pipes that are passed in. +func TestActivation(t *testing.T) { + cmd := exec.Command("go", "run", "../examples/activation/activation.go") + + r1, w1, _ := os.Pipe() + r2, w2, _ := os.Pipe() + cmd.ExtraFiles = []*os.File{ + w1, + w2, + } + + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "LISTEN_FDS=2", "FIX_LISTEN_PID=1") + + err := cmd.Run() + if err != nil { + t.Fatalf(err.Error()) + } + + correctStringWritten(t, r1, "Hello world") + correctStringWritten(t, r2, "Goodbye world") +} + +func TestActivationNoFix(t *testing.T) { + cmd := exec.Command("go", "run", "../examples/activation/activation.go") + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "LISTEN_FDS=2") + + out, _ := cmd.CombinedOutput() + if bytes.Contains(out, []byte("No files")) == false { + t.Fatalf("Child didn't error out as expected") + } +} + +func TestActivationNoFiles(t *testing.T) { + cmd := exec.Command("go", "run", "../examples/activation/activation.go") + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "LISTEN_FDS=0", "FIX_LISTEN_PID=1") + + out, _ := cmd.CombinedOutput() + if bytes.Contains(out, []byte("No files")) == false { + t.Fatalf("Child didn't error out as expected") + } +} diff --git a/vendor/src/github.com/coreos/go-systemd/activation/listeners.go b/vendor/src/github.com/coreos/go-systemd/activation/listeners.go new file mode 100644 index 0000000000..cdb2cf4bb4 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/activation/listeners.go @@ -0,0 +1,38 @@ +/* +Copyright 2014 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package activation + +import ( + "fmt" + "net" +) + +// Listeners returns net.Listeners for all socket activated fds passed to this process. +func Listeners(unsetEnv bool) ([]net.Listener, error) { + files := Files(unsetEnv) + listeners := make([]net.Listener, len(files)) + + for i, f := range files { + var err error + listeners[i], err = net.FileListener(f) + if err != nil { + return nil, fmt.Errorf("Error setting up FileListener for fd %d: %s", f.Fd(), err.Error()) + } + } + + return listeners, nil +} diff --git a/vendor/src/github.com/coreos/go-systemd/activation/listeners_test.go b/vendor/src/github.com/coreos/go-systemd/activation/listeners_test.go new file mode 100644 index 0000000000..c3627d6d4d --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/activation/listeners_test.go @@ -0,0 +1,88 @@ +/* +Copyright 2014 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package activation + +import ( + "io" + "net" + "os" + "os/exec" + "testing" +) + +// correctStringWritten fails the text if the correct string wasn't written +// to the other side of the pipe. +func correctStringWrittenNet(t *testing.T, r net.Conn, expected string) bool { + bytes := make([]byte, len(expected)) + io.ReadAtLeast(r, bytes, len(expected)) + + if string(bytes) != expected { + t.Fatalf("Unexpected string %s", string(bytes)) + } + + return true +} + +// TestActivation forks out a copy of activation.go example and reads back two +// strings from the pipes that are passed in. +func TestListeners(t *testing.T) { + cmd := exec.Command("go", "run", "../examples/activation/listen.go") + + l1, err := net.Listen("tcp", ":9999") + if err != nil { + t.Fatalf(err.Error()) + } + l2, err := net.Listen("tcp", ":1234") + if err != nil { + t.Fatalf(err.Error()) + } + + t1 := l1.(*net.TCPListener) + t2 := l2.(*net.TCPListener) + + f1, _ := t1.File() + f2, _ := t2.File() + + cmd.ExtraFiles = []*os.File{ + f1, + f2, + } + + r1, err := net.Dial("tcp", "127.0.0.1:9999") + if err != nil { + t.Fatalf(err.Error()) + } + r1.Write([]byte("Hi")) + + r2, err := net.Dial("tcp", "127.0.0.1:1234") + if err != nil { + t.Fatalf(err.Error()) + } + r2.Write([]byte("Hi")) + + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "LISTEN_FDS=2", "FIX_LISTEN_PID=1") + + out, err := cmd.Output() + if err != nil { + println(string(out)) + t.Fatalf(err.Error()) + } + + correctStringWrittenNet(t, r1, "Hello world") + correctStringWrittenNet(t, r2, "Goodbye world") +} diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/dbus.go b/vendor/src/github.com/coreos/go-systemd/dbus/dbus.go new file mode 100644 index 0000000000..91d7112145 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/dbus/dbus.go @@ -0,0 +1,104 @@ +/* +Copyright 2013 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Integration with the systemd D-Bus API. See http://www.freedesktop.org/wiki/Software/systemd/dbus/ +package dbus + +import ( + "os" + "strconv" + "strings" + "sync" + + "github.com/godbus/dbus" +) + +const signalBuffer = 100 + +// ObjectPath creates a dbus.ObjectPath using the rules that systemd uses for +// serializing special characters. +func ObjectPath(path string) dbus.ObjectPath { + path = strings.Replace(path, ".", "_2e", -1) + path = strings.Replace(path, "-", "_2d", -1) + path = strings.Replace(path, "@", "_40", -1) + + return dbus.ObjectPath(path) +} + +// Conn is a connection to systemds dbus endpoint. +type Conn struct { + sysconn *dbus.Conn + sysobj *dbus.Object + jobListener struct { + jobs map[dbus.ObjectPath]chan string + sync.Mutex + } + subscriber struct { + updateCh chan<- *SubStateUpdate + errCh chan<- error + sync.Mutex + ignore map[dbus.ObjectPath]int64 + cleanIgnore int64 + } + dispatch map[string]func(dbus.Signal) +} + +// New() establishes a connection to the system bus and authenticates. +func New() (*Conn, error) { + c := new(Conn) + + if err := c.initConnection(); err != nil { + return nil, err + } + + c.initJobs() + return c, nil +} + +func (c *Conn) initConnection() error { + var err error + c.sysconn, err = dbus.SystemBusPrivate() + if err != nil { + return err + } + + // Only use EXTERNAL method, and hardcode the uid (not username) + // to avoid a username lookup (which requires a dynamically linked + // libc) + methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))} + + err = c.sysconn.Auth(methods) + if err != nil { + c.sysconn.Close() + return err + } + + err = c.sysconn.Hello() + if err != nil { + c.sysconn.Close() + return err + } + + c.sysobj = c.sysconn.Object("org.freedesktop.systemd1", dbus.ObjectPath("/org/freedesktop/systemd1")) + + // Setup the listeners on jobs so that we can get completions + c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, + "type='signal', interface='org.freedesktop.systemd1.Manager', member='JobRemoved'") + c.initSubscription() + c.initDispatch() + + return nil +} diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/dbus_test.go b/vendor/src/github.com/coreos/go-systemd/dbus/dbus_test.go new file mode 100644 index 0000000000..2e80f73ef7 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/dbus/dbus_test.go @@ -0,0 +1,41 @@ +/* +Copyright 2013 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package dbus + +import ( + "testing" +) + +// TestObjectPath ensures path encoding of the systemd rules works. +func TestObjectPath(t *testing.T) { + input := "/silly-path/to@a/unit..service" + output := ObjectPath(input) + expected := "/silly_2dpath/to_40a/unit_2e_2eservice" + + if string(output) != expected { + t.Fatalf("Output '%s' did not match expected '%s'", output, expected) + } +} + +// TestNew ensures that New() works without errors. +func TestNew(t *testing.T) { + _, err := New() + + if err != nil { + t.Fatal(err) + } +} diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/methods.go b/vendor/src/github.com/coreos/go-systemd/dbus/methods.go new file mode 100644 index 0000000000..11d5cda945 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/dbus/methods.go @@ -0,0 +1,354 @@ +/* +Copyright 2013 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package dbus + +import ( + "errors" + "github.com/godbus/dbus" +) + +func (c *Conn) initJobs() { + c.jobListener.jobs = make(map[dbus.ObjectPath]chan string) +} + +func (c *Conn) jobComplete(signal *dbus.Signal) { + var id uint32 + var job dbus.ObjectPath + var unit string + var result string + dbus.Store(signal.Body, &id, &job, &unit, &result) + c.jobListener.Lock() + out, ok := c.jobListener.jobs[job] + if ok { + out <- result + delete(c.jobListener.jobs, job) + } + c.jobListener.Unlock() +} + +func (c *Conn) startJob(job string, args ...interface{}) (<-chan string, error) { + c.jobListener.Lock() + defer c.jobListener.Unlock() + + ch := make(chan string, 1) + var path dbus.ObjectPath + err := c.sysobj.Call(job, 0, args...).Store(&path) + if err != nil { + return nil, err + } + c.jobListener.jobs[path] = ch + return ch, nil +} + +func (c *Conn) runJob(job string, args ...interface{}) (string, error) { + respCh, err := c.startJob(job, args...) + if err != nil { + return "", err + } + return <-respCh, nil +} + +// StartUnit enqeues a start job and depending jobs, if any (unless otherwise +// specified by the mode string). +// +// Takes the unit to activate, plus a mode string. The mode needs to be one of +// replace, fail, isolate, ignore-dependencies, ignore-requirements. If +// "replace" the call will start the unit and its dependencies, possibly +// replacing already queued jobs that conflict with this. If "fail" the call +// will start the unit and its dependencies, but will fail if this would change +// an already queued job. If "isolate" the call will start the unit in question +// and terminate all units that aren't dependencies of it. If +// "ignore-dependencies" it will start a unit but ignore all its dependencies. +// If "ignore-requirements" it will start a unit but only ignore the +// requirement dependencies. It is not recommended to make use of the latter +// two options. +// +// Result string: one of done, canceled, timeout, failed, dependency, skipped. +// done indicates successful execution of a job. canceled indicates that a job +// has been canceled before it finished execution. timeout indicates that the +// job timeout was reached. failed indicates that the job failed. dependency +// indicates that a job this job has been depending on failed and the job hence +// has been removed too. skipped indicates that a job was skipped because it +// didn't apply to the units current state. +func (c *Conn) StartUnit(name string, mode string) (string, error) { + return c.runJob("org.freedesktop.systemd1.Manager.StartUnit", name, mode) +} + +// StopUnit is similar to StartUnit but stops the specified unit rather +// than starting it. +func (c *Conn) StopUnit(name string, mode string) (string, error) { + return c.runJob("org.freedesktop.systemd1.Manager.StopUnit", name, mode) +} + +// ReloadUnit reloads a unit. Reloading is done only if the unit is already running and fails otherwise. +func (c *Conn) ReloadUnit(name string, mode string) (string, error) { + return c.runJob("org.freedesktop.systemd1.Manager.ReloadUnit", name, mode) +} + +// RestartUnit restarts a service. If a service is restarted that isn't +// running it will be started. +func (c *Conn) RestartUnit(name string, mode string) (string, error) { + return c.runJob("org.freedesktop.systemd1.Manager.RestartUnit", name, mode) +} + +// TryRestartUnit is like RestartUnit, except that a service that isn't running +// is not affected by the restart. +func (c *Conn) TryRestartUnit(name string, mode string) (string, error) { + return c.runJob("org.freedesktop.systemd1.Manager.TryRestartUnit", name, mode) +} + +// ReloadOrRestart attempts a reload if the unit supports it and use a restart +// otherwise. +func (c *Conn) ReloadOrRestartUnit(name string, mode string) (string, error) { + return c.runJob("org.freedesktop.systemd1.Manager.ReloadOrRestartUnit", name, mode) +} + +// ReloadOrTryRestart attempts a reload if the unit supports it and use a "Try" +// flavored restart otherwise. +func (c *Conn) ReloadOrTryRestartUnit(name string, mode string) (string, error) { + return c.runJob("org.freedesktop.systemd1.Manager.ReloadOrTryRestartUnit", name, mode) +} + +// StartTransientUnit() may be used to create and start a transient unit, which +// will be released as soon as it is not running or referenced anymore or the +// system is rebooted. name is the unit name including suffix, and must be +// unique. mode is the same as in StartUnit(), properties contains properties +// of the unit. +func (c *Conn) StartTransientUnit(name string, mode string, properties ...Property) (string, error) { + return c.runJob("org.freedesktop.systemd1.Manager.StartTransientUnit", name, mode, properties, make([]PropertyCollection, 0)) +} + +// KillUnit takes the unit name and a UNIX signal number to send. All of the unit's +// processes are killed. +func (c *Conn) KillUnit(name string, signal int32) { + c.sysobj.Call("org.freedesktop.systemd1.Manager.KillUnit", 0, name, "all", signal).Store() +} + +// getProperties takes the unit name and returns all of its dbus object properties, for the given dbus interface +func (c *Conn) getProperties(unit string, dbusInterface string) (map[string]interface{}, error) { + var err error + var props map[string]dbus.Variant + + path := ObjectPath("/org/freedesktop/systemd1/unit/" + unit) + if !path.IsValid() { + return nil, errors.New("invalid unit name: " + unit) + } + + obj := c.sysconn.Object("org.freedesktop.systemd1", path) + err = obj.Call("org.freedesktop.DBus.Properties.GetAll", 0, dbusInterface).Store(&props) + if err != nil { + return nil, err + } + + out := make(map[string]interface{}, len(props)) + for k, v := range props { + out[k] = v.Value() + } + + return out, nil +} + +// GetUnitProperties takes the unit name and returns all of its dbus object properties. +func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) { + return c.getProperties(unit, "org.freedesktop.systemd1.Unit") +} + +func (c *Conn) getProperty(unit string, dbusInterface string, propertyName string) (*Property, error) { + var err error + var prop dbus.Variant + + path := ObjectPath("/org/freedesktop/systemd1/unit/" + unit) + if !path.IsValid() { + return nil, errors.New("invalid unit name: " + unit) + } + + obj := c.sysconn.Object("org.freedesktop.systemd1", path) + err = obj.Call("org.freedesktop.DBus.Properties.Get", 0, dbusInterface, propertyName).Store(&prop) + if err != nil { + return nil, err + } + + return &Property{Name: propertyName, Value: prop}, nil +} + +func (c *Conn) GetUnitProperty(unit string, propertyName string) (*Property, error) { + return c.getProperty(unit, "org.freedesktop.systemd1.Unit", propertyName) +} + +// GetUnitTypeProperties returns the extra properties for a unit, specific to the unit type. +// Valid values for unitType: Service, Socket, Target, Device, Mount, Automount, Snapshot, Timer, Swap, Path, Slice, Scope +// return "dbus.Error: Unknown interface" if the unitType is not the correct type of the unit +func (c *Conn) GetUnitTypeProperties(unit string, unitType string) (map[string]interface{}, error) { + return c.getProperties(unit, "org.freedesktop.systemd1."+unitType) +} + +// SetUnitProperties() may be used to modify certain unit properties at runtime. +// Not all properties may be changed at runtime, but many resource management +// settings (primarily those in systemd.cgroup(5)) may. The changes are applied +// instantly, and stored on disk for future boots, unless runtime is true, in which +// case the settings only apply until the next reboot. name is the name of the unit +// to modify. properties are the settings to set, encoded as an array of property +// name and value pairs. +func (c *Conn) SetUnitProperties(name string, runtime bool, properties ...Property) error { + return c.sysobj.Call("SetUnitProperties", 0, name, runtime, properties).Store() +} + +func (c *Conn) GetUnitTypeProperty(unit string, unitType string, propertyName string) (*Property, error) { + return c.getProperty(unit, "org.freedesktop.systemd1." + unitType, propertyName) +} + +// ListUnits returns an array with all currently loaded units. Note that +// units may be known by multiple names at the same time, and hence there might +// be more unit names loaded than actual units behind them. +func (c *Conn) ListUnits() ([]UnitStatus, error) { + result := make([][]interface{}, 0) + err := c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnits", 0).Store(&result) + if err != nil { + return nil, err + } + + resultInterface := make([]interface{}, len(result)) + for i := range result { + resultInterface[i] = result[i] + } + + status := make([]UnitStatus, len(result)) + statusInterface := make([]interface{}, len(status)) + for i := range status { + statusInterface[i] = &status[i] + } + + err = dbus.Store(resultInterface, statusInterface...) + if err != nil { + return nil, err + } + + return status, nil +} + +type UnitStatus struct { + Name string // The primary unit name as string + Description string // The human readable description string + LoadState string // The load state (i.e. whether the unit file has been loaded successfully) + ActiveState string // The active state (i.e. whether the unit is currently started or not) + SubState string // The sub state (a more fine-grained version of the active state that is specific to the unit type, which the active state is not) + Followed string // A unit that is being followed in its state by this unit, if there is any, otherwise the empty string. + Path dbus.ObjectPath // The unit object path + JobId uint32 // If there is a job queued for the job unit the numeric job id, 0 otherwise + JobType string // The job type as string + JobPath dbus.ObjectPath // The job object path +} + +// EnableUnitFiles() may be used to enable one or more units in the system (by +// creating symlinks to them in /etc or /run). +// +// It takes a list of unit files to enable (either just file names or full +// absolute paths if the unit files are residing outside the usual unit +// search paths), and two booleans: the first controls whether the unit shall +// be enabled for runtime only (true, /run), or persistently (false, /etc). +// The second one controls whether symlinks pointing to other units shall +// be replaced if necessary. +// +// This call returns one boolean and an array with the changes made. The +// boolean signals whether the unit files contained any enablement +// information (i.e. an [Install]) section. The changes list consists of +// structures with three strings: the type of the change (one of symlink +// or unlink), the file name of the symlink and the destination of the +// symlink. +func (c *Conn) EnableUnitFiles(files []string, runtime bool, force bool) (bool, []EnableUnitFileChange, error) { + var carries_install_info bool + + result := make([][]interface{}, 0) + err := c.sysobj.Call("org.freedesktop.systemd1.Manager.EnableUnitFiles", 0, files, runtime, force).Store(&carries_install_info, &result) + if err != nil { + return false, nil, err + } + + resultInterface := make([]interface{}, len(result)) + for i := range result { + resultInterface[i] = result[i] + } + + changes := make([]EnableUnitFileChange, len(result)) + changesInterface := make([]interface{}, len(changes)) + for i := range changes { + changesInterface[i] = &changes[i] + } + + err = dbus.Store(resultInterface, changesInterface...) + if err != nil { + return false, nil, err + } + + return carries_install_info, changes, nil +} + +type EnableUnitFileChange struct { + Type string // Type of the change (one of symlink or unlink) + Filename string // File name of the symlink + Destination string // Destination of the symlink +} + +// DisableUnitFiles() may be used to disable one or more units in the system (by +// removing symlinks to them from /etc or /run). +// +// It takes a list of unit files to disable (either just file names or full +// absolute paths if the unit files are residing outside the usual unit +// search paths), and one boolean: whether the unit was enabled for runtime +// only (true, /run), or persistently (false, /etc). +// +// This call returns an array with the changes made. The changes list +// consists of structures with three strings: the type of the change (one of +// symlink or unlink), the file name of the symlink and the destination of the +// symlink. +func (c *Conn) DisableUnitFiles(files []string, runtime bool) ([]DisableUnitFileChange, error) { + result := make([][]interface{}, 0) + err := c.sysobj.Call("DisableUnitFiles", 0, files, runtime).Store(&result) + if err != nil { + return nil, err + } + + resultInterface := make([]interface{}, len(result)) + for i := range result { + resultInterface[i] = result[i] + } + + changes := make([]DisableUnitFileChange, len(result)) + changesInterface := make([]interface{}, len(changes)) + for i := range changes { + changesInterface[i] = &changes[i] + } + + err = dbus.Store(resultInterface, changesInterface...) + if err != nil { + return nil, err + } + + return changes, nil +} + +type DisableUnitFileChange struct { + Type string // Type of the change (one of symlink or unlink) + Filename string // File name of the symlink + Destination string // Destination of the symlink +} + +// Reload instructs systemd to scan for and reload unit files. This is +// equivalent to a 'systemctl daemon-reload'. +func (c *Conn) Reload() error { + return c.sysobj.Call("org.freedesktop.systemd1.Manager.Reload", 0).Store() +} diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/methods_test.go b/vendor/src/github.com/coreos/go-systemd/dbus/methods_test.go new file mode 100644 index 0000000000..9e2f22323f --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/dbus/methods_test.go @@ -0,0 +1,314 @@ +/* +Copyright 2013 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package dbus + +import ( + "fmt" + "github.com/guelfey/go.dbus" + "math/rand" + "os" + "path/filepath" + "reflect" + "testing" +) + +func setupConn(t *testing.T) *Conn { + conn, err := New() + if err != nil { + t.Fatal(err) + } + + return conn +} + +func setupUnit(target string, conn *Conn, t *testing.T) { + // Blindly stop the unit in case it is running + conn.StopUnit(target, "replace") + + // Blindly remove the symlink in case it exists + targetRun := filepath.Join("/run/systemd/system/", target) + err := os.Remove(targetRun) + + // 1. Enable the unit + abs, err := filepath.Abs("../fixtures/" + target) + if err != nil { + t.Fatal(err) + } + + fixture := []string{abs} + + install, changes, err := conn.EnableUnitFiles(fixture, true, true) + if err != nil { + t.Fatal(err) + } + + if install != false { + t.Fatal("Install was true") + } + + if len(changes) < 1 { + t.Fatalf("Expected one change, got %v", changes) + } + + if changes[0].Filename != targetRun { + t.Fatal("Unexpected target filename") + } +} + +// Ensure that basic unit starting and stopping works. +func TestStartStopUnit(t *testing.T) { + target := "start-stop.service" + conn := setupConn(t) + + setupUnit(target, conn, t) + + // 2. Start the unit + job, err := conn.StartUnit(target, "replace") + if err != nil { + t.Fatal(err) + } + + if job != "done" { + t.Fatal("Job is not done, %v", job) + } + + units, err := conn.ListUnits() + + var unit *UnitStatus + for _, u := range units { + if u.Name == target { + unit = &u + } + } + + if unit == nil { + t.Fatalf("Test unit not found in list") + } + + if unit.ActiveState != "active" { + t.Fatalf("Test unit not active") + } + + // 3. Stop the unit + job, err = conn.StopUnit(target, "replace") + if err != nil { + t.Fatal(err) + } + + units, err = conn.ListUnits() + + unit = nil + for _, u := range units { + if u.Name == target { + unit = &u + } + } + + if unit != nil { + t.Fatalf("Test unit found in list, should be stopped") + } +} + +// Enables a unit and then immediately tears it down +func TestEnableDisableUnit(t *testing.T) { + target := "enable-disable.service" + conn := setupConn(t) + + setupUnit(target, conn, t) + + abs, err := filepath.Abs("../fixtures/" + target) + if err != nil { + t.Fatal(err) + } + + path := filepath.Join("/run/systemd/system/", target) + + // 2. Disable the unit + changes, err := conn.DisableUnitFiles([]string{abs}, true) + if err != nil { + t.Fatal(err) + } + + if len(changes) != 1 { + t.Fatalf("Changes should include the path, %v", changes) + } + if changes[0].Filename != path { + t.Fatalf("Change should include correct filename, %+v", changes[0]) + } + if changes[0].Destination != "" { + t.Fatalf("Change destination should be empty, %+v", changes[0]) + } +} + +// TestGetUnitProperties reads the `-.mount` which should exist on all systemd +// systems and ensures that one of its properties is valid. +func TestGetUnitProperties(t *testing.T) { + conn := setupConn(t) + + unit := "-.mount" + + info, err := conn.GetUnitProperties(unit) + if err != nil { + t.Fatal(err) + } + + names := info["Wants"].([]string) + + if len(names) < 1 { + t.Fatal("/ is unwanted") + } + + if names[0] != "system.slice" { + t.Fatal("unexpected wants for /") + } + + prop, err := conn.GetUnitProperty(unit, "Wants") + if err != nil { + t.Fatal(err) + } + + if prop.Name != "Wants" { + t.Fatal("unexpected property name") + } + + val := prop.Value.Value().([]string) + if !reflect.DeepEqual(val, names) { + t.Fatal("unexpected property value") + } +} + +// TestGetUnitPropertiesRejectsInvalidName attempts to get the properties for a +// unit with an invalid name. This test should be run with --test.timeout set, +// as a fail will manifest as GetUnitProperties hanging indefinitely. +func TestGetUnitPropertiesRejectsInvalidName(t *testing.T) { + conn := setupConn(t) + + unit := "//invalid#$^/" + + _, err := conn.GetUnitProperties(unit) + if err == nil { + t.Fatal("Expected an error, got nil") + } + + _, err = conn.GetUnitProperty(unit, "Wants") + if err == nil { + t.Fatal("Expected an error, got nil") + } +} + +// TestSetUnitProperties changes a cgroup setting on the `tmp.mount` +// which should exist on all systemd systems and ensures that the +// property was set. +func TestSetUnitProperties(t *testing.T) { + conn := setupConn(t) + + unit := "tmp.mount" + + if err := conn.SetUnitProperties(unit, true, Property{"CPUShares", dbus.MakeVariant(uint64(1023))}); err != nil { + t.Fatal(err) + } + + info, err := conn.GetUnitTypeProperties(unit, "Mount") + if err != nil { + t.Fatal(err) + } + + value := info["CPUShares"].(uint64) + if value != 1023 { + t.Fatal("CPUShares of unit is not 1023, %s", value) + } +} + +// Ensure that basic transient unit starting and stopping works. +func TestStartStopTransientUnit(t *testing.T) { + conn := setupConn(t) + + props := []Property{ + PropExecStart([]string{"/bin/sleep", "400"}, false), + } + target := fmt.Sprintf("testing-transient-%d.service", rand.Int()) + + // Start the unit + job, err := conn.StartTransientUnit(target, "replace", props...) + if err != nil { + t.Fatal(err) + } + + if job != "done" { + t.Fatal("Job is not done, %v", job) + } + + units, err := conn.ListUnits() + + var unit *UnitStatus + for _, u := range units { + if u.Name == target { + unit = &u + } + } + + if unit == nil { + t.Fatalf("Test unit not found in list") + } + + if unit.ActiveState != "active" { + t.Fatalf("Test unit not active") + } + + // 3. Stop the unit + job, err = conn.StopUnit(target, "replace") + if err != nil { + t.Fatal(err) + } + + units, err = conn.ListUnits() + + unit = nil + for _, u := range units { + if u.Name == target { + unit = &u + } + } + + if unit != nil { + t.Fatalf("Test unit found in list, should be stopped") + } +} + +func TestConnJobListener(t *testing.T) { + target := "start-stop.service" + conn := setupConn(t) + + setupUnit(target, conn, t) + + jobSize := len(conn.jobListener.jobs) + + _, err := conn.StartUnit(target, "replace") + if err != nil { + t.Fatal(err) + } + + _, err = conn.StopUnit(target, "replace") + if err != nil { + t.Fatal(err) + } + + currentJobSize := len(conn.jobListener.jobs) + if jobSize != currentJobSize { + t.Fatal("JobListener jobs leaked") + } +} diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/properties.go b/vendor/src/github.com/coreos/go-systemd/dbus/properties.go new file mode 100644 index 0000000000..a06ccda761 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/dbus/properties.go @@ -0,0 +1,220 @@ +/* +Copyright 2013 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package dbus + +import ( + "github.com/godbus/dbus" +) + +// From the systemd docs: +// +// The properties array of StartTransientUnit() may take many of the settings +// that may also be configured in unit files. Not all parameters are currently +// accepted though, but we plan to cover more properties with future release. +// Currently you may set the Description, Slice and all dependency types of +// units, as well as RemainAfterExit, ExecStart for service units, +// TimeoutStopUSec and PIDs for scope units, and CPUAccounting, CPUShares, +// BlockIOAccounting, BlockIOWeight, BlockIOReadBandwidth, +// BlockIOWriteBandwidth, BlockIODeviceWeight, MemoryAccounting, MemoryLimit, +// DevicePolicy, DeviceAllow for services/scopes/slices. These fields map +// directly to their counterparts in unit files and as normal D-Bus object +// properties. The exception here is the PIDs field of scope units which is +// used for construction of the scope only and specifies the initial PIDs to +// add to the scope object. + +type Property struct { + Name string + Value dbus.Variant +} + +type PropertyCollection struct { + Name string + Properties []Property +} + +type execStart struct { + Path string // the binary path to execute + Args []string // an array with all arguments to pass to the executed command, starting with argument 0 + UncleanIsFailure bool // a boolean whether it should be considered a failure if the process exits uncleanly +} + +// PropExecStart sets the ExecStart service property. The first argument is a +// slice with the binary path to execute followed by the arguments to pass to +// the executed command. See +// http://www.freedesktop.org/software/systemd/man/systemd.service.html#ExecStart= +func PropExecStart(command []string, uncleanIsFailure bool) Property { + execStarts := []execStart{ + execStart{ + Path: command[0], + Args: command, + UncleanIsFailure: uncleanIsFailure, + }, + } + + return Property{ + Name: "ExecStart", + Value: dbus.MakeVariant(execStarts), + } +} + +// PropRemainAfterExit sets the RemainAfterExit service property. See +// http://www.freedesktop.org/software/systemd/man/systemd.service.html#RemainAfterExit= +func PropRemainAfterExit(b bool) Property { + return Property{ + Name: "RemainAfterExit", + Value: dbus.MakeVariant(b), + } +} + +// PropDescription sets the Description unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit#Description= +func PropDescription(desc string) Property { + return Property{ + Name: "Description", + Value: dbus.MakeVariant(desc), + } +} + +func propDependency(name string, units []string) Property { + return Property{ + Name: name, + Value: dbus.MakeVariant(units), + } +} + +// PropRequires sets the Requires unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requires= +func PropRequires(units ...string) Property { + return propDependency("Requires", units) +} + +// PropRequiresOverridable sets the RequiresOverridable unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresOverridable= +func PropRequiresOverridable(units ...string) Property { + return propDependency("RequiresOverridable", units) +} + +// PropRequisite sets the Requisite unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requisite= +func PropRequisite(units ...string) Property { + return propDependency("Requisite", units) +} + +// PropRequisiteOverridable sets the RequisiteOverridable unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequisiteOverridable= +func PropRequisiteOverridable(units ...string) Property { + return propDependency("RequisiteOverridable", units) +} + +// PropWants sets the Wants unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Wants= +func PropWants(units ...string) Property { + return propDependency("Wants", units) +} + +// PropBindsTo sets the BindsTo unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#BindsTo= +func PropBindsTo(units ...string) Property { + return propDependency("BindsTo", units) +} + +// PropRequiredBy sets the RequiredBy unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredBy= +func PropRequiredBy(units ...string) Property { + return propDependency("RequiredBy", units) +} + +// PropRequiredByOverridable sets the RequiredByOverridable unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredByOverridable= +func PropRequiredByOverridable(units ...string) Property { + return propDependency("RequiredByOverridable", units) +} + +// PropWantedBy sets the WantedBy unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#WantedBy= +func PropWantedBy(units ...string) Property { + return propDependency("WantedBy", units) +} + +// PropBoundBy sets the BoundBy unit property. See +// http://www.freedesktop.org/software/systemd/main/systemd.unit.html#BoundBy= +func PropBoundBy(units ...string) Property { + return propDependency("BoundBy", units) +} + +// PropConflicts sets the Conflicts unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Conflicts= +func PropConflicts(units ...string) Property { + return propDependency("Conflicts", units) +} + +// PropConflictedBy sets the ConflictedBy unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#ConflictedBy= +func PropConflictedBy(units ...string) Property { + return propDependency("ConflictedBy", units) +} + +// PropBefore sets the Before unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Before= +func PropBefore(units ...string) Property { + return propDependency("Before", units) +} + +// PropAfter sets the After unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#After= +func PropAfter(units ...string) Property { + return propDependency("After", units) +} + +// PropOnFailure sets the OnFailure unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#OnFailure= +func PropOnFailure(units ...string) Property { + return propDependency("OnFailure", units) +} + +// PropTriggers sets the Triggers unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Triggers= +func PropTriggers(units ...string) Property { + return propDependency("Triggers", units) +} + +// PropTriggeredBy sets the TriggeredBy unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#TriggeredBy= +func PropTriggeredBy(units ...string) Property { + return propDependency("TriggeredBy", units) +} + +// PropPropagatesReloadTo sets the PropagatesReloadTo unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#PropagatesReloadTo= +func PropPropagatesReloadTo(units ...string) Property { + return propDependency("PropagatesReloadTo", units) +} + +// PropRequiresMountsFor sets the RequiresMountsFor unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresMountsFor= +func PropRequiresMountsFor(units ...string) Property { + return propDependency("RequiresMountsFor", units) +} + +// PropSlice sets the Slice unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.resource-control.html#Slice= +func PropSlice(slice string) Property { + return Property{ + Name: "Slice", + Value: dbus.MakeVariant(slice), + } +} diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/set.go b/vendor/src/github.com/coreos/go-systemd/dbus/set.go new file mode 100644 index 0000000000..88378b29a1 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/dbus/set.go @@ -0,0 +1,26 @@ +package dbus + +type set struct { + data map[string]bool +} + +func (s *set) Add(value string) { + s.data[value] = true +} + +func (s *set) Remove(value string) { + delete(s.data, value) +} + +func (s *set) Contains(value string) (exists bool) { + _, exists = s.data[value] + return +} + +func (s *set) Length() (int) { + return len(s.data) +} + +func newSet() (*set) { + return &set{make(map[string] bool)} +} diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/set_test.go b/vendor/src/github.com/coreos/go-systemd/dbus/set_test.go new file mode 100644 index 0000000000..d8d174d0c4 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/dbus/set_test.go @@ -0,0 +1,26 @@ +package dbus + +import ( + "testing" +) + +// TestBasicSetActions asserts that Add & Remove behavior is correct +func TestBasicSetActions(t *testing.T) { + s := newSet() + + if s.Contains("foo") { + t.Fatal("set should not contain 'foo'") + } + + s.Add("foo") + + if !s.Contains("foo") { + t.Fatal("set should contain 'foo'") + } + + s.Remove("foo") + + if s.Contains("foo") { + t.Fatal("set should not contain 'foo'") + } +} diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/subscription.go b/vendor/src/github.com/coreos/go-systemd/dbus/subscription.go new file mode 100644 index 0000000000..3d896d896f --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/dbus/subscription.go @@ -0,0 +1,249 @@ +/* +Copyright 2013 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package dbus + +import ( + "errors" + "time" + + "github.com/godbus/dbus" +) + +const ( + cleanIgnoreInterval = int64(10 * time.Second) + ignoreInterval = int64(30 * time.Millisecond) +) + +// Subscribe sets up this connection to subscribe to all systemd dbus events. +// This is required before calling SubscribeUnits. When the connection closes +// systemd will automatically stop sending signals so there is no need to +// explicitly call Unsubscribe(). +func (c *Conn) Subscribe() error { + c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, + "type='signal',interface='org.freedesktop.systemd1.Manager',member='UnitNew'") + c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, + "type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'") + + err := c.sysobj.Call("org.freedesktop.systemd1.Manager.Subscribe", 0).Store() + if err != nil { + c.sysconn.Close() + return err + } + + return nil +} + +// Unsubscribe this connection from systemd dbus events. +func (c *Conn) Unsubscribe() error { + err := c.sysobj.Call("org.freedesktop.systemd1.Manager.Unsubscribe", 0).Store() + if err != nil { + c.sysconn.Close() + return err + } + + return nil +} + +func (c *Conn) initSubscription() { + c.subscriber.ignore = make(map[dbus.ObjectPath]int64) +} + +func (c *Conn) initDispatch() { + ch := make(chan *dbus.Signal, signalBuffer) + + c.sysconn.Signal(ch) + + go func() { + for { + signal := <-ch + switch signal.Name { + case "org.freedesktop.systemd1.Manager.JobRemoved": + c.jobComplete(signal) + + unitName := signal.Body[2].(string) + var unitPath dbus.ObjectPath + c.sysobj.Call("org.freedesktop.systemd1.Manager.GetUnit", 0, unitName).Store(&unitPath) + if unitPath != dbus.ObjectPath("") { + c.sendSubStateUpdate(unitPath) + } + case "org.freedesktop.systemd1.Manager.UnitNew": + c.sendSubStateUpdate(signal.Body[1].(dbus.ObjectPath)) + case "org.freedesktop.DBus.Properties.PropertiesChanged": + if signal.Body[0].(string) == "org.freedesktop.systemd1.Unit" { + // we only care about SubState updates, which are a Unit property + c.sendSubStateUpdate(signal.Path) + } + } + } + }() +} + +// Returns two unbuffered channels which will receive all changed units every +// interval. Deleted units are sent as nil. +func (c *Conn) SubscribeUnits(interval time.Duration) (<-chan map[string]*UnitStatus, <-chan error) { + return c.SubscribeUnitsCustom(interval, 0, func(u1, u2 *UnitStatus) bool { return *u1 != *u2 }, nil) +} + +// SubscribeUnitsCustom is like SubscribeUnits but lets you specify the buffer +// size of the channels, the comparison function for detecting changes and a filter +// function for cutting down on the noise that your channel receives. +func (c *Conn) SubscribeUnitsCustom(interval time.Duration, buffer int, isChanged func(*UnitStatus, *UnitStatus) bool, filterUnit func (string) bool) (<-chan map[string]*UnitStatus, <-chan error) { + old := make(map[string]*UnitStatus) + statusChan := make(chan map[string]*UnitStatus, buffer) + errChan := make(chan error, buffer) + + go func() { + for { + timerChan := time.After(interval) + + units, err := c.ListUnits() + if err == nil { + cur := make(map[string]*UnitStatus) + for i := range units { + if filterUnit != nil && filterUnit(units[i].Name) { + continue + } + cur[units[i].Name] = &units[i] + } + + // add all new or changed units + changed := make(map[string]*UnitStatus) + for n, u := range cur { + if oldU, ok := old[n]; !ok || isChanged(oldU, u) { + changed[n] = u + } + delete(old, n) + } + + // add all deleted units + for oldN := range old { + changed[oldN] = nil + } + + old = cur + + if len(changed) != 0 { + statusChan <- changed + } + } else { + errChan <- err + } + + <-timerChan + } + }() + + return statusChan, errChan +} + +type SubStateUpdate struct { + UnitName string + SubState string +} + +// SetSubStateSubscriber writes to updateCh when any unit's substate changes. +// Although this writes to updateCh on every state change, the reported state +// may be more recent than the change that generated it (due to an unavoidable +// race in the systemd dbus interface). That is, this method provides a good +// way to keep a current view of all units' states, but is not guaranteed to +// show every state transition they go through. Furthermore, state changes +// will only be written to the channel with non-blocking writes. If updateCh +// is full, it attempts to write an error to errCh; if errCh is full, the error +// passes silently. +func (c *Conn) SetSubStateSubscriber(updateCh chan<- *SubStateUpdate, errCh chan<- error) { + c.subscriber.Lock() + defer c.subscriber.Unlock() + c.subscriber.updateCh = updateCh + c.subscriber.errCh = errCh +} + +func (c *Conn) sendSubStateUpdate(path dbus.ObjectPath) { + c.subscriber.Lock() + defer c.subscriber.Unlock() + if c.subscriber.updateCh == nil { + return + } + + if c.shouldIgnore(path) { + return + } + + info, err := c.GetUnitProperties(string(path)) + if err != nil { + select { + case c.subscriber.errCh <- err: + default: + } + } + + name := info["Id"].(string) + substate := info["SubState"].(string) + + update := &SubStateUpdate{name, substate} + select { + case c.subscriber.updateCh <- update: + default: + select { + case c.subscriber.errCh <- errors.New("update channel full!"): + default: + } + } + + c.updateIgnore(path, info) +} + +// The ignore functions work around a wart in the systemd dbus interface. +// Requesting the properties of an unloaded unit will cause systemd to send a +// pair of UnitNew/UnitRemoved signals. Because we need to get a unit's +// properties on UnitNew (as that's the only indication of a new unit coming up +// for the first time), we would enter an infinite loop if we did not attempt +// to detect and ignore these spurious signals. The signal themselves are +// indistinguishable from relevant ones, so we (somewhat hackishly) ignore an +// unloaded unit's signals for a short time after requesting its properties. +// This means that we will miss e.g. a transient unit being restarted +// *immediately* upon failure and also a transient unit being started +// immediately after requesting its status (with systemctl status, for example, +// because this causes a UnitNew signal to be sent which then causes us to fetch +// the properties). + +func (c *Conn) shouldIgnore(path dbus.ObjectPath) bool { + t, ok := c.subscriber.ignore[path] + return ok && t >= time.Now().UnixNano() +} + +func (c *Conn) updateIgnore(path dbus.ObjectPath, info map[string]interface{}) { + c.cleanIgnore() + + // unit is unloaded - it will trigger bad systemd dbus behavior + if info["LoadState"].(string) == "not-found" { + c.subscriber.ignore[path] = time.Now().UnixNano() + ignoreInterval + } +} + +// without this, ignore would grow unboundedly over time +func (c *Conn) cleanIgnore() { + now := time.Now().UnixNano() + if c.subscriber.cleanIgnore < now { + c.subscriber.cleanIgnore = now + cleanIgnoreInterval + + for p, t := range c.subscriber.ignore { + if t < now { + delete(c.subscriber.ignore, p) + } + } + } +} diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/subscription_set.go b/vendor/src/github.com/coreos/go-systemd/dbus/subscription_set.go new file mode 100644 index 0000000000..2625786052 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/dbus/subscription_set.go @@ -0,0 +1,32 @@ +package dbus + +import ( + "time" +) + +// SubscriptionSet returns a subscription set which is like conn.Subscribe but +// can filter to only return events for a set of units. +type SubscriptionSet struct { + *set + conn *Conn +} + + +func (s *SubscriptionSet) filter(unit string) bool { + return !s.Contains(unit) +} + +// Subscribe starts listening for dbus events for all of the units in the set. +// Returns channels identical to conn.SubscribeUnits. +func (s *SubscriptionSet) Subscribe() (<-chan map[string]*UnitStatus, <-chan error) { + // TODO: Make fully evented by using systemd 209 with properties changed values + return s.conn.SubscribeUnitsCustom(time.Second, 0, + func(u1, u2 *UnitStatus) bool { return *u1 != *u2 }, + func(unit string) bool { return s.filter(unit) }, + ) +} + +// NewSubscriptionSet returns a new subscription set. +func (conn *Conn) NewSubscriptionSet() (*SubscriptionSet) { + return &SubscriptionSet{newSet(), conn} +} diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/subscription_set_test.go b/vendor/src/github.com/coreos/go-systemd/dbus/subscription_set_test.go new file mode 100644 index 0000000000..db600850c2 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/dbus/subscription_set_test.go @@ -0,0 +1,67 @@ +package dbus + +import ( + "testing" + "time" +) + +// TestSubscribeUnit exercises the basics of subscription of a particular unit. +func TestSubscriptionSetUnit(t *testing.T) { + target := "subscribe-events-set.service" + + conn, err := New() + + if err != nil { + t.Fatal(err) + } + + err = conn.Subscribe() + if err != nil { + t.Fatal(err) + } + + subSet := conn.NewSubscriptionSet() + evChan, errChan := subSet.Subscribe() + + subSet.Add(target) + setupUnit(target, conn, t) + + job, err := conn.StartUnit(target, "replace") + if err != nil { + t.Fatal(err) + } + + if job != "done" { + t.Fatal("Couldn't start", target) + } + + timeout := make(chan bool, 1) + go func() { + time.Sleep(3 * time.Second) + close(timeout) + }() + + for { + select { + case changes := <-evChan: + tCh, ok := changes[target] + + if !ok { + t.Fatal("Unexpected event %v", changes) + } + + if tCh.ActiveState == "active" && tCh.Name == target { + goto success + } + case err = <-errChan: + t.Fatal(err) + case <-timeout: + t.Fatal("Reached timeout") + } + } + +success: + return +} + + diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/subscription_test.go b/vendor/src/github.com/coreos/go-systemd/dbus/subscription_test.go new file mode 100644 index 0000000000..6f4d0b32a6 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/dbus/subscription_test.go @@ -0,0 +1,90 @@ +package dbus + +import ( + "testing" + "time" +) + +// TestSubscribe exercises the basics of subscription +func TestSubscribe(t *testing.T) { + conn, err := New() + + if err != nil { + t.Fatal(err) + } + + err = conn.Subscribe() + if err != nil { + t.Fatal(err) + } + + err = conn.Unsubscribe() + if err != nil { + t.Fatal(err) + } +} + +// TestSubscribeUnit exercises the basics of subscription of a particular unit. +func TestSubscribeUnit(t *testing.T) { + target := "subscribe-events.service" + + conn, err := New() + + if err != nil { + t.Fatal(err) + } + + err = conn.Subscribe() + if err != nil { + t.Fatal(err) + } + + err = conn.Unsubscribe() + if err != nil { + t.Fatal(err) + } + + evChan, errChan := conn.SubscribeUnits(time.Second) + + setupUnit(target, conn, t) + + job, err := conn.StartUnit(target, "replace") + if err != nil { + t.Fatal(err) + } + + if job != "done" { + t.Fatal("Couldn't start", target) + } + + timeout := make(chan bool, 1) + go func() { + time.Sleep(3 * time.Second) + close(timeout) + }() + + for { + select { + case changes := <-evChan: + tCh, ok := changes[target] + + // Just continue until we see our event. + if !ok { + continue + } + + if tCh.ActiveState == "active" && tCh.Name == target { + goto success + } + case err = <-errChan: + t.Fatal(err) + case <-timeout: + t.Fatal("Reached timeout") + } + } + +success: + return +} + + diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/activation.go b/vendor/src/github.com/coreos/go-systemd/examples/activation/activation.go new file mode 100644 index 0000000000..b3cf70ed84 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/examples/activation/activation.go @@ -0,0 +1,44 @@ +// Activation example used by the activation unit tests. +package main + +import ( + "fmt" + "os" + + "github.com/coreos/go-systemd/activation" +) + +func fixListenPid() { + if os.Getenv("FIX_LISTEN_PID") != "" { + // HACK: real systemd would set LISTEN_PID before exec'ing but + // this is too difficult in golang for the purpose of a test. + // Do not do this in real code. + os.Setenv("LISTEN_PID", fmt.Sprintf("%d", os.Getpid())) + } +} + +func main() { + fixListenPid() + + files := activation.Files(false) + + if len(files) == 0 { + panic("No files") + } + + if os.Getenv("LISTEN_PID") == "" || os.Getenv("LISTEN_FDS") == "" { + panic("Should not unset envs") + } + + files = activation.Files(true) + + if os.Getenv("LISTEN_PID") != "" || os.Getenv("LISTEN_FDS") != "" { + panic("Can not unset envs") + } + + // Write out the expected strings to the two pipes + files[0].Write([]byte("Hello world")) + files[1].Write([]byte("Goodbye world")) + + return +} diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/README.md b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/README.md new file mode 100644 index 0000000000..a350cca5e5 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/README.md @@ -0,0 +1,19 @@ +## socket activated http server + +This is a simple example of using socket activation with systemd to serve a +simple HTTP server on http://127.0.0.1:8076 + +To try it out `go get` the httpserver and run it under the systemd-activate helper + +``` +export GOPATH=`pwd` +go get github.com/coreos/go-systemd/examples/activation/httpserver +sudo /usr/lib/systemd/systemd-activate -l 127.0.0.1:8076 ./bin/httpserver +``` + +Then curl the URL and you will notice that it starts up: + +``` +curl 127.0.0.1:8076 +hello socket activated world! +``` diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.service b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.service new file mode 100644 index 0000000000..c8dea0f6b3 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.service @@ -0,0 +1,11 @@ +[Unit] +Description=Hello World HTTP +Requires=network.target +After=multi-user.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/httpserver + +[Install] +WantedBy=multi-user.target diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.socket b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.socket new file mode 100644 index 0000000000..723ed7ed92 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.socket @@ -0,0 +1,5 @@ +[Socket] +ListenStream=127.0.0.1:8076 + +[Install] +WantedBy=sockets.target diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/httpserver.go b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/httpserver.go new file mode 100644 index 0000000000..380c325d61 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/httpserver.go @@ -0,0 +1,26 @@ +package main + +import ( + "io" + "net/http" + + "github.com/coreos/go-systemd/activation" +) + +func HelloServer(w http.ResponseWriter, req *http.Request) { + io.WriteString(w, "hello socket activated world!\n") +} + +func main() { + listeners, err := activation.Listeners(true) + if err != nil { + panic(err) + } + + if len(listeners) != 1 { + panic("Unexpected number of socket activation fds") + } + + http.HandleFunc("/", HelloServer) + http.Serve(listeners[0], nil) +} diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/listen.go b/vendor/src/github.com/coreos/go-systemd/examples/activation/listen.go new file mode 100644 index 0000000000..5850a8b796 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/examples/activation/listen.go @@ -0,0 +1,50 @@ +// Activation example used by the activation unit tests. +package main + +import ( + "fmt" + "os" + + "github.com/coreos/go-systemd/activation" +) + +func fixListenPid() { + if os.Getenv("FIX_LISTEN_PID") != "" { + // HACK: real systemd would set LISTEN_PID before exec'ing but + // this is too difficult in golang for the purpose of a test. + // Do not do this in real code. + os.Setenv("LISTEN_PID", fmt.Sprintf("%d", os.Getpid())) + } +} + +func main() { + fixListenPid() + + listeners, _ := activation.Listeners(false) + + if len(listeners) == 0 { + panic("No listeners") + } + + if os.Getenv("LISTEN_PID") == "" || os.Getenv("LISTEN_FDS") == "" { + panic("Should not unset envs") + } + + listeners, err := activation.Listeners(true) + if err != nil { + panic(err) + } + + if os.Getenv("LISTEN_PID") != "" || os.Getenv("LISTEN_FDS") != "" { + panic("Can not unset envs") + } + + c0, _ := listeners[0].Accept() + c1, _ := listeners[1].Accept() + + // Write out the expected strings to the two pipes + c0.Write([]byte("Hello world")) + c1.Write([]byte("Goodbye world")) + + return +} diff --git a/vendor/src/github.com/coreos/go-systemd/fixtures/start-stop.service b/vendor/src/github.com/coreos/go-systemd/fixtures/start-stop.service new file mode 100644 index 0000000000..a1f8c36773 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/fixtures/start-stop.service @@ -0,0 +1,5 @@ +[Unit] +Description=start stop test + +[Service] +ExecStart=/bin/sleep 400 diff --git a/vendor/src/github.com/coreos/go-systemd/fixtures/subscribe-events-set.service b/vendor/src/github.com/coreos/go-systemd/fixtures/subscribe-events-set.service new file mode 100644 index 0000000000..a1f8c36773 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/fixtures/subscribe-events-set.service @@ -0,0 +1,5 @@ +[Unit] +Description=start stop test + +[Service] +ExecStart=/bin/sleep 400 diff --git a/vendor/src/github.com/coreos/go-systemd/fixtures/subscribe-events.service b/vendor/src/github.com/coreos/go-systemd/fixtures/subscribe-events.service new file mode 100644 index 0000000000..a1f8c36773 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/fixtures/subscribe-events.service @@ -0,0 +1,5 @@ +[Unit] +Description=start stop test + +[Service] +ExecStart=/bin/sleep 400 diff --git a/vendor/src/github.com/coreos/go-systemd/journal/send.go b/vendor/src/github.com/coreos/go-systemd/journal/send.go new file mode 100644 index 0000000000..a29bcbf0fa --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/journal/send.go @@ -0,0 +1,168 @@ +/* +Copyright 2013 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package journal provides write bindings to the systemd journal +package journal + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "os" + "strconv" + "strings" + "syscall" +) + +// Priority of a journal message +type Priority int + +const ( + PriEmerg Priority = iota + PriAlert + PriCrit + PriErr + PriWarning + PriNotice + PriInfo + PriDebug +) + +var conn net.Conn + +func init() { + var err error + conn, err = net.Dial("unixgram", "/run/systemd/journal/socket") + if err != nil { + conn = nil + } +} + +// Enabled returns true iff the systemd journal is available for logging +func Enabled() bool { + return conn != nil +} + +// Send a message to the systemd journal. vars is a map of journald fields to +// values. Fields must be composed of uppercase letters, numbers, and +// underscores, but must not start with an underscore. Within these +// restrictions, any arbitrary field name may be used. Some names have special +// significance: see the journalctl documentation +// (http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html) +// for more details. vars may be nil. +func Send(message string, priority Priority, vars map[string]string) error { + if conn == nil { + return journalError("could not connect to journald socket") + } + + data := new(bytes.Buffer) + appendVariable(data, "PRIORITY", strconv.Itoa(int(priority))) + appendVariable(data, "MESSAGE", message) + for k, v := range vars { + appendVariable(data, k, v) + } + + _, err := io.Copy(conn, data) + if err != nil && isSocketSpaceError(err) { + file, err := tempFd() + if err != nil { + return journalError(err.Error()) + } + _, err = io.Copy(file, data) + if err != nil { + return journalError(err.Error()) + } + + rights := syscall.UnixRights(int(file.Fd())) + + /* this connection should always be a UnixConn, but better safe than sorry */ + unixConn, ok := conn.(*net.UnixConn) + if !ok { + return journalError("can't send file through non-Unix connection") + } + unixConn.WriteMsgUnix([]byte{}, rights, nil) + } else if err != nil { + return journalError(err.Error()) + } + return nil +} + +func appendVariable(w io.Writer, name, value string) { + if !validVarName(name) { + journalError("variable name contains invalid character, ignoring") + } + if strings.ContainsRune(value, '\n') { + /* When the value contains a newline, we write: + * - the variable name, followed by a newline + * - the size (in 64bit little endian format) + * - the data, followed by a newline + */ + fmt.Fprintln(w, name) + binary.Write(w, binary.LittleEndian, uint64(len(value))) + fmt.Fprintln(w, value) + } else { + /* just write the variable and value all on one line */ + fmt.Fprintln(w, "%s=%s", name, value) + } +} + +func validVarName(name string) bool { + /* The variable name must be in uppercase and consist only of characters, + * numbers and underscores, and may not begin with an underscore. (from the docs) + */ + + valid := name[0] != '_' + for _, c := range name { + valid = valid && ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_' + } + return valid +} + +func isSocketSpaceError(err error) bool { + opErr, ok := err.(*net.OpError) + if !ok { + return false + } + + sysErr, ok := opErr.Err.(syscall.Errno) + if !ok { + return false + } + + return sysErr == syscall.EMSGSIZE || sysErr == syscall.ENOBUFS +} + +func tempFd() (*os.File, error) { + file, err := ioutil.TempFile("/dev/shm/", "journal.XXXXX") + if err != nil { + return nil, err + } + syscall.Unlink(file.Name()) + if err != nil { + return nil, err + } + return file, nil +} + +func journalError(s string) error { + s = "journal error: " + s + fmt.Fprintln(os.Stderr, s) + return errors.New(s) +} diff --git a/vendor/src/github.com/coreos/go-systemd/test b/vendor/src/github.com/coreos/go-systemd/test new file mode 100755 index 0000000000..6e043658ae --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/test @@ -0,0 +1,3 @@ +#!/bin/sh -e + +go test -v ./... diff --git a/vendor/src/github.com/godbus/dbus/LICENSE b/vendor/src/github.com/godbus/dbus/LICENSE new file mode 100644 index 0000000000..06b252bcbc --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2013, Georg Reinke () +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/src/github.com/godbus/dbus/README.markdown b/vendor/src/github.com/godbus/dbus/README.markdown new file mode 100644 index 0000000000..3ab2116651 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/README.markdown @@ -0,0 +1,38 @@ +dbus +---- + +dbus is a simple library that implements native Go client bindings for the +D-Bus message bus system. + +### Features + +* Complete native implementation of the D-Bus message protocol +* Go-like API (channels for signals / asynchronous method calls, Goroutine-safe connections) +* Subpackages that help with the introspection / property interfaces + +### Installation + +This packages requires Go 1.1. If you installed it and set up your GOPATH, just run: + +``` +go get github.com/godbus/dbus +``` + +If you want to use the subpackages, you can install them the same way. + +### Usage + +The complete package documentation and some simple examples are available at +[godoc.org](http://godoc.org/github.com/godbus/dbus). Also, the +[_examples](https://github.com/godbus/dbus/tree/master/_examples) directory +gives a short overview over the basic usage. + +Please note that the API is considered unstable for now and may change without +further notice. + +### License + +go.dbus is available under the Simplified BSD License; see LICENSE for the full +text. + +Nearly all of the credit for this library goes to github.com/guelfey/go.dbus. diff --git a/vendor/src/github.com/godbus/dbus/_examples/eavesdrop.go b/vendor/src/github.com/godbus/dbus/_examples/eavesdrop.go new file mode 100644 index 0000000000..11deef3cf8 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/_examples/eavesdrop.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "github.com/godbus/dbus" + "os" +) + +func main() { + conn, err := dbus.SessionBus() + if err != nil { + fmt.Fprintln(os.Stderr, "Failed to connect to session bus:", err) + os.Exit(1) + } + + for _, v := range []string{"method_call", "method_return", "error", "signal"} { + call := conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, + "eavesdrop='true',type='"+v+"'") + if call.Err != nil { + fmt.Fprintln(os.Stderr, "Failed to add match:", call.Err) + os.Exit(1) + } + } + c := make(chan *dbus.Message, 10) + conn.Eavesdrop(c) + fmt.Println("Listening for everything") + for v := range c { + fmt.Println(v) + } +} diff --git a/vendor/src/github.com/godbus/dbus/_examples/introspect.go b/vendor/src/github.com/godbus/dbus/_examples/introspect.go new file mode 100644 index 0000000000..a2af4e5f24 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/_examples/introspect.go @@ -0,0 +1,21 @@ +package main + +import ( + "encoding/json" + "github.com/godbus/dbus" + "github.com/godbus/dbus/introspect" + "os" +) + +func main() { + conn, err := dbus.SessionBus() + if err != nil { + panic(err) + } + node, err := introspect.Call(conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus")) + if err != nil { + panic(err) + } + data, _ := json.MarshalIndent(node, "", " ") + os.Stdout.Write(data) +} diff --git a/vendor/src/github.com/godbus/dbus/_examples/list-names.go b/vendor/src/github.com/godbus/dbus/_examples/list-names.go new file mode 100644 index 0000000000..ce1f7ec52e --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/_examples/list-names.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "github.com/godbus/dbus" + "os" +) + +func main() { + conn, err := dbus.SessionBus() + if err != nil { + fmt.Fprintln(os.Stderr, "Failed to connect to session bus:", err) + os.Exit(1) + } + + var s []string + err = conn.BusObject().Call("org.freedesktop.DBus.ListNames", 0).Store(&s) + if err != nil { + fmt.Fprintln(os.Stderr, "Failed to get list of owned names:", err) + os.Exit(1) + } + + fmt.Println("Currently owned names on the session bus:") + for _, v := range s { + fmt.Println(v) + } +} diff --git a/vendor/src/github.com/godbus/dbus/_examples/notification.go b/vendor/src/github.com/godbus/dbus/_examples/notification.go new file mode 100644 index 0000000000..5fe11d04c4 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/_examples/notification.go @@ -0,0 +1,17 @@ +package main + +import "github.com/godbus/dbus" + +func main() { + conn, err := dbus.SessionBus() + if err != nil { + panic(err) + } + obj := conn.Object("org.freedesktop.Notifications", "/org/freedesktop/Notifications") + call := obj.Call("org.freedesktop.Notifications.Notify", 0, "", uint32(0), + "", "Test", "This is a test of the DBus bindings for go.", []string{}, + map[string]dbus.Variant{}, int32(5000)) + if call.Err != nil { + panic(call.Err) + } +} diff --git a/vendor/src/github.com/godbus/dbus/_examples/prop.go b/vendor/src/github.com/godbus/dbus/_examples/prop.go new file mode 100644 index 0000000000..e3408c53e9 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/_examples/prop.go @@ -0,0 +1,68 @@ +package main + +import ( + "fmt" + "github.com/godbus/dbus" + "github.com/godbus/dbus/introspect" + "github.com/godbus/dbus/prop" + "os" +) + +type foo string + +func (f foo) Foo() (string, *dbus.Error) { + fmt.Println(f) + return string(f), nil +} + +func main() { + conn, err := dbus.SessionBus() + if err != nil { + panic(err) + } + reply, err := conn.RequestName("com.github.guelfey.Demo", + dbus.NameFlagDoNotQueue) + if err != nil { + panic(err) + } + if reply != dbus.RequestNameReplyPrimaryOwner { + fmt.Fprintln(os.Stderr, "name already taken") + os.Exit(1) + } + propsSpec := map[string]map[string]*prop.Prop{ + "com.github.guelfey.Demo": { + "SomeInt": { + int32(0), + true, + prop.EmitTrue, + func(c *prop.Change) *dbus.Error { + fmt.Println(c.Name, "changed to", c.Value) + return nil + }, + }, + }, + } + f := foo("Bar") + conn.Export(f, "/com/github/guelfey/Demo", "com.github.guelfey.Demo") + props := prop.New(conn, "/com/github/guelfey/Demo", propsSpec) + n := &introspect.Node{ + Name: "/com/github/guelfey/Demo", + Interfaces: []introspect.Interface{ + introspect.IntrospectData, + prop.IntrospectData, + { + Name: "com.github.guelfey.Demo", + Methods: introspect.Methods(f), + Properties: props.Introspection("com.github.guelfey.Demo"), + }, + }, + } + conn.Export(introspect.NewIntrospectable(n), "/com/github/guelfey/Demo", + "org.freedesktop.DBus.Introspectable") + fmt.Println("Listening on com.github.guelfey.Demo / /com/github/guelfey/Demo ...") + + c := make(chan *dbus.Signal) + conn.Signal(c) + for _ = range c { + } +} diff --git a/vendor/src/github.com/godbus/dbus/_examples/server.go b/vendor/src/github.com/godbus/dbus/_examples/server.go new file mode 100644 index 0000000000..32b7b291c7 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/_examples/server.go @@ -0,0 +1,45 @@ +package main + +import ( + "fmt" + "github.com/godbus/dbus" + "github.com/godbus/dbus/introspect" + "os" +) + +const intro = ` + + + + + + ` + introspect.IntrospectDataString + ` ` + +type foo string + +func (f foo) Foo() (string, *dbus.Error) { + fmt.Println(f) + return string(f), nil +} + +func main() { + conn, err := dbus.SessionBus() + if err != nil { + panic(err) + } + reply, err := conn.RequestName("com.github.guelfey.Demo", + dbus.NameFlagDoNotQueue) + if err != nil { + panic(err) + } + if reply != dbus.RequestNameReplyPrimaryOwner { + fmt.Fprintln(os.Stderr, "name already taken") + os.Exit(1) + } + f := foo("Bar!") + conn.Export(f, "/com/github/guelfey/Demo", "com.github.guelfey.Demo") + conn.Export(introspect.Introspectable(intro), "/com/github/guelfey/Demo", + "org.freedesktop.DBus.Introspectable") + fmt.Println("Listening on com.github.guelfey.Demo / /com/github/guelfey/Demo ...") + select {} +} diff --git a/vendor/src/github.com/godbus/dbus/_examples/signal.go b/vendor/src/github.com/godbus/dbus/_examples/signal.go new file mode 100644 index 0000000000..8f3f809759 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/_examples/signal.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "github.com/godbus/dbus" + "os" +) + +func main() { + conn, err := dbus.SessionBus() + if err != nil { + fmt.Fprintln(os.Stderr, "Failed to connect to session bus:", err) + os.Exit(1) + } + + conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, + "type='signal',path='/org/freedesktop/DBus',interface='org.freedesktop.DBus',sender='org.freedesktop.DBus'") + + c := make(chan *dbus.Signal, 10) + conn.Signal(c) + for v := range c { + fmt.Println(v) + } +} diff --git a/vendor/src/github.com/godbus/dbus/auth.go b/vendor/src/github.com/godbus/dbus/auth.go new file mode 100644 index 0000000000..98017b693e --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/auth.go @@ -0,0 +1,253 @@ +package dbus + +import ( + "bufio" + "bytes" + "errors" + "io" + "os" + "strconv" +) + +// AuthStatus represents the Status of an authentication mechanism. +type AuthStatus byte + +const ( + // AuthOk signals that authentication is finished; the next command + // from the server should be an OK. + AuthOk AuthStatus = iota + + // AuthContinue signals that additional data is needed; the next command + // from the server should be a DATA. + AuthContinue + + // AuthError signals an error; the server sent invalid data or some + // other unexpected thing happened and the current authentication + // process should be aborted. + AuthError +) + +type authState byte + +const ( + waitingForData authState = iota + waitingForOk + waitingForReject +) + +// Auth defines the behaviour of an authentication mechanism. +type Auth interface { + // Return the name of the mechnism, the argument to the first AUTH command + // and the next status. + FirstData() (name, resp []byte, status AuthStatus) + + // Process the given DATA command, and return the argument to the DATA + // command and the next status. If len(resp) == 0, no DATA command is sent. + HandleData(data []byte) (resp []byte, status AuthStatus) +} + +// Auth authenticates the connection, trying the given list of authentication +// mechanisms (in that order). If nil is passed, the EXTERNAL and +// DBUS_COOKIE_SHA1 mechanisms are tried for the current user. For private +// connections, this method must be called before sending any messages to the +// bus. Auth must not be called on shared connections. +func (conn *Conn) Auth(methods []Auth) error { + if methods == nil { + uid := strconv.Itoa(os.Getuid()) + methods = []Auth{AuthExternal(uid), AuthCookieSha1(uid, getHomeDir())} + } + in := bufio.NewReader(conn.transport) + err := conn.transport.SendNullByte() + if err != nil { + return err + } + err = authWriteLine(conn.transport, []byte("AUTH")) + if err != nil { + return err + } + s, err := authReadLine(in) + if err != nil { + return err + } + if len(s) < 2 || !bytes.Equal(s[0], []byte("REJECTED")) { + return errors.New("dbus: authentication protocol error") + } + s = s[1:] + for _, v := range s { + for _, m := range methods { + if name, data, status := m.FirstData(); bytes.Equal(v, name) { + var ok bool + err = authWriteLine(conn.transport, []byte("AUTH"), []byte(v), data) + if err != nil { + return err + } + switch status { + case AuthOk: + err, ok = conn.tryAuth(m, waitingForOk, in) + case AuthContinue: + err, ok = conn.tryAuth(m, waitingForData, in) + default: + panic("dbus: invalid authentication status") + } + if err != nil { + return err + } + if ok { + if conn.transport.SupportsUnixFDs() { + err = authWriteLine(conn, []byte("NEGOTIATE_UNIX_FD")) + if err != nil { + return err + } + line, err := authReadLine(in) + if err != nil { + return err + } + switch { + case bytes.Equal(line[0], []byte("AGREE_UNIX_FD")): + conn.EnableUnixFDs() + conn.unixFD = true + case bytes.Equal(line[0], []byte("ERROR")): + default: + return errors.New("dbus: authentication protocol error") + } + } + err = authWriteLine(conn.transport, []byte("BEGIN")) + if err != nil { + return err + } + go conn.inWorker() + go conn.outWorker() + return nil + } + } + } + } + return errors.New("dbus: authentication failed") +} + +// tryAuth tries to authenticate with m as the mechanism, using state as the +// initial authState and in for reading input. It returns (nil, true) on +// success, (nil, false) on a REJECTED and (someErr, false) if some other +// error occured. +func (conn *Conn) tryAuth(m Auth, state authState, in *bufio.Reader) (error, bool) { + for { + s, err := authReadLine(in) + if err != nil { + return err, false + } + switch { + case state == waitingForData && string(s[0]) == "DATA": + if len(s) != 2 { + err = authWriteLine(conn.transport, []byte("ERROR")) + if err != nil { + return err, false + } + continue + } + data, status := m.HandleData(s[1]) + switch status { + case AuthOk, AuthContinue: + if len(data) != 0 { + err = authWriteLine(conn.transport, []byte("DATA"), data) + if err != nil { + return err, false + } + } + if status == AuthOk { + state = waitingForOk + } + case AuthError: + err = authWriteLine(conn.transport, []byte("ERROR")) + if err != nil { + return err, false + } + } + case state == waitingForData && string(s[0]) == "REJECTED": + return nil, false + case state == waitingForData && string(s[0]) == "ERROR": + err = authWriteLine(conn.transport, []byte("CANCEL")) + if err != nil { + return err, false + } + state = waitingForReject + case state == waitingForData && string(s[0]) == "OK": + if len(s) != 2 { + err = authWriteLine(conn.transport, []byte("CANCEL")) + if err != nil { + return err, false + } + state = waitingForReject + } + conn.uuid = string(s[1]) + return nil, true + case state == waitingForData: + err = authWriteLine(conn.transport, []byte("ERROR")) + if err != nil { + return err, false + } + case state == waitingForOk && string(s[0]) == "OK": + if len(s) != 2 { + err = authWriteLine(conn.transport, []byte("CANCEL")) + if err != nil { + return err, false + } + state = waitingForReject + } + conn.uuid = string(s[1]) + return nil, true + case state == waitingForOk && string(s[0]) == "REJECTED": + return nil, false + case state == waitingForOk && (string(s[0]) == "DATA" || + string(s[0]) == "ERROR"): + + err = authWriteLine(conn.transport, []byte("CANCEL")) + if err != nil { + return err, false + } + state = waitingForReject + case state == waitingForOk: + err = authWriteLine(conn.transport, []byte("ERROR")) + if err != nil { + return err, false + } + case state == waitingForReject && string(s[0]) == "REJECTED": + return nil, false + case state == waitingForReject: + return errors.New("dbus: authentication protocol error"), false + default: + panic("dbus: invalid auth state") + } + } +} + +// authReadLine reads a line and separates it into its fields. +func authReadLine(in *bufio.Reader) ([][]byte, error) { + data, err := in.ReadBytes('\n') + if err != nil { + return nil, err + } + data = bytes.TrimSuffix(data, []byte("\r\n")) + return bytes.Split(data, []byte{' '}), nil +} + +// authWriteLine writes the given line in the authentication protocol format +// (elements of data separated by a " " and terminated by "\r\n"). +func authWriteLine(out io.Writer, data ...[]byte) error { + buf := make([]byte, 0) + for i, v := range data { + buf = append(buf, v...) + if i != len(data)-1 { + buf = append(buf, ' ') + } + } + buf = append(buf, '\r') + buf = append(buf, '\n') + n, err := out.Write(buf) + if err != nil { + return err + } + if n != len(buf) { + return io.ErrUnexpectedEOF + } + return nil +} diff --git a/vendor/src/github.com/godbus/dbus/auth_external.go b/vendor/src/github.com/godbus/dbus/auth_external.go new file mode 100644 index 0000000000..7e376d3ef6 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/auth_external.go @@ -0,0 +1,26 @@ +package dbus + +import ( + "encoding/hex" +) + +// AuthExternal returns an Auth that authenticates as the given user with the +// EXTERNAL mechanism. +func AuthExternal(user string) Auth { + return authExternal{user} +} + +// AuthExternal implements the EXTERNAL authentication mechanism. +type authExternal struct { + user string +} + +func (a authExternal) FirstData() ([]byte, []byte, AuthStatus) { + b := make([]byte, 2*len(a.user)) + hex.Encode(b, []byte(a.user)) + return []byte("EXTERNAL"), b, AuthOk +} + +func (a authExternal) HandleData(b []byte) ([]byte, AuthStatus) { + return nil, AuthError +} diff --git a/vendor/src/github.com/godbus/dbus/auth_sha1.go b/vendor/src/github.com/godbus/dbus/auth_sha1.go new file mode 100644 index 0000000000..df15b46119 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/auth_sha1.go @@ -0,0 +1,102 @@ +package dbus + +import ( + "bufio" + "bytes" + "crypto/rand" + "crypto/sha1" + "encoding/hex" + "os" +) + +// AuthCookieSha1 returns an Auth that authenticates as the given user with the +// DBUS_COOKIE_SHA1 mechanism. The home parameter should specify the home +// directory of the user. +func AuthCookieSha1(user, home string) Auth { + return authCookieSha1{user, home} +} + +type authCookieSha1 struct { + user, home string +} + +func (a authCookieSha1) FirstData() ([]byte, []byte, AuthStatus) { + b := make([]byte, 2*len(a.user)) + hex.Encode(b, []byte(a.user)) + return []byte("DBUS_COOKIE_SHA1"), b, AuthContinue +} + +func (a authCookieSha1) HandleData(data []byte) ([]byte, AuthStatus) { + challenge := make([]byte, len(data)/2) + _, err := hex.Decode(challenge, data) + if err != nil { + return nil, AuthError + } + b := bytes.Split(challenge, []byte{' '}) + if len(b) != 3 { + return nil, AuthError + } + context := b[0] + id := b[1] + svchallenge := b[2] + cookie := a.getCookie(context, id) + if cookie == nil { + return nil, AuthError + } + clchallenge := a.generateChallenge() + if clchallenge == nil { + return nil, AuthError + } + hash := sha1.New() + hash.Write(bytes.Join([][]byte{svchallenge, clchallenge, cookie}, []byte{':'})) + hexhash := make([]byte, 2*hash.Size()) + hex.Encode(hexhash, hash.Sum(nil)) + data = append(clchallenge, ' ') + data = append(data, hexhash...) + resp := make([]byte, 2*len(data)) + hex.Encode(resp, data) + return resp, AuthOk +} + +// getCookie searches for the cookie identified by id in context and returns +// the cookie content or nil. (Since HandleData can't return a specific error, +// but only whether an error occured, this function also doesn't bother to +// return an error.) +func (a authCookieSha1) getCookie(context, id []byte) []byte { + file, err := os.Open(a.home + "/.dbus-keyrings/" + string(context)) + if err != nil { + return nil + } + defer file.Close() + rd := bufio.NewReader(file) + for { + line, err := rd.ReadBytes('\n') + if err != nil { + return nil + } + line = line[:len(line)-1] + b := bytes.Split(line, []byte{' '}) + if len(b) != 3 { + return nil + } + if bytes.Equal(b[0], id) { + return b[2] + } + } +} + +// generateChallenge returns a random, hex-encoded challenge, or nil on error +// (see above). +func (a authCookieSha1) generateChallenge() []byte { + b := make([]byte, 16) + n, err := rand.Read(b) + if err != nil { + return nil + } + if n != 16 { + return nil + } + enc := make([]byte, 32) + hex.Encode(enc, b) + return enc +} diff --git a/vendor/src/github.com/godbus/dbus/call.go b/vendor/src/github.com/godbus/dbus/call.go new file mode 100644 index 0000000000..1d2fbc7efd --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/call.go @@ -0,0 +1,147 @@ +package dbus + +import ( + "errors" + "strings" +) + +// Call represents a pending or completed method call. +type Call struct { + Destination string + Path ObjectPath + Method string + Args []interface{} + + // Strobes when the call is complete. + Done chan *Call + + // After completion, the error status. If this is non-nil, it may be an + // error message from the peer (with Error as its type) or some other error. + Err error + + // Holds the response once the call is done. + Body []interface{} +} + +var errSignature = errors.New("dbus: mismatched signature") + +// Store stores the body of the reply into the provided pointers. It returns +// an error if the signatures of the body and retvalues don't match, or if +// the error status is not nil. +func (c *Call) Store(retvalues ...interface{}) error { + if c.Err != nil { + return c.Err + } + + return Store(c.Body, retvalues...) +} + +// Object represents a remote object on which methods can be invoked. +type Object struct { + conn *Conn + dest string + path ObjectPath +} + +// Call calls a method with (*Object).Go and waits for its reply. +func (o *Object) Call(method string, flags Flags, args ...interface{}) *Call { + return <-o.Go(method, flags, make(chan *Call, 1), args...).Done +} + +// GetProperty calls org.freedesktop.DBus.Properties.GetProperty on the given +// object. The property name must be given in interface.member notation. +func (o *Object) GetProperty(p string) (Variant, error) { + idx := strings.LastIndex(p, ".") + if idx == -1 || idx+1 == len(p) { + return Variant{}, errors.New("dbus: invalid property " + p) + } + + iface := p[:idx] + prop := p[idx+1:] + + result := Variant{} + err := o.Call("org.freedesktop.DBus.Properties.Get", 0, iface, prop).Store(&result) + + if err != nil { + return Variant{}, err + } + + return result, nil +} + +// Go calls a method with the given arguments asynchronously. It returns a +// Call structure representing this method call. The passed channel will +// return the same value once the call is done. If ch is nil, a new channel +// will be allocated. Otherwise, ch has to be buffered or Go will panic. +// +// If the flags include FlagNoReplyExpected, ch is ignored and a Call structure +// is returned of which only the Err member is valid. +// +// If the method parameter contains a dot ('.'), the part before the last dot +// specifies the interface on which the method is called. +func (o *Object) Go(method string, flags Flags, ch chan *Call, args ...interface{}) *Call { + iface := "" + i := strings.LastIndex(method, ".") + if i != -1 { + iface = method[:i] + } + method = method[i+1:] + msg := new(Message) + msg.Type = TypeMethodCall + msg.serial = o.conn.getSerial() + msg.Flags = flags & (FlagNoAutoStart | FlagNoReplyExpected) + msg.Headers = make(map[HeaderField]Variant) + msg.Headers[FieldPath] = MakeVariant(o.path) + msg.Headers[FieldDestination] = MakeVariant(o.dest) + msg.Headers[FieldMember] = MakeVariant(method) + if iface != "" { + msg.Headers[FieldInterface] = MakeVariant(iface) + } + msg.Body = args + if len(args) > 0 { + msg.Headers[FieldSignature] = MakeVariant(SignatureOf(args...)) + } + if msg.Flags&FlagNoReplyExpected == 0 { + if ch == nil { + ch = make(chan *Call, 10) + } else if cap(ch) == 0 { + panic("dbus: unbuffered channel passed to (*Object).Go") + } + call := &Call{ + Destination: o.dest, + Path: o.path, + Method: method, + Args: args, + Done: ch, + } + o.conn.callsLck.Lock() + o.conn.calls[msg.serial] = call + o.conn.callsLck.Unlock() + o.conn.outLck.RLock() + if o.conn.closed { + call.Err = ErrClosed + call.Done <- call + } else { + o.conn.out <- msg + } + o.conn.outLck.RUnlock() + return call + } + o.conn.outLck.RLock() + defer o.conn.outLck.RUnlock() + if o.conn.closed { + return &Call{Err: ErrClosed} + } + o.conn.out <- msg + return &Call{Err: nil} +} + +// Destination returns the destination that calls on o are sent to. +func (o *Object) Destination() string { + return o.dest +} + +// Path returns the path that calls on o are sent to. +func (o *Object) Path() ObjectPath { + return o.path +} diff --git a/vendor/src/github.com/godbus/dbus/conn.go b/vendor/src/github.com/godbus/dbus/conn.go new file mode 100644 index 0000000000..75dd22652a --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/conn.go @@ -0,0 +1,601 @@ +package dbus + +import ( + "errors" + "io" + "os" + "reflect" + "strings" + "sync" +) + +const defaultSystemBusAddress = "unix:path=/var/run/dbus/system_bus_socket" + +var ( + systemBus *Conn + systemBusLck sync.Mutex + sessionBus *Conn + sessionBusLck sync.Mutex +) + +// ErrClosed is the error returned by calls on a closed connection. +var ErrClosed = errors.New("dbus: connection closed by user") + +// Conn represents a connection to a message bus (usually, the system or +// session bus). +// +// Connections are either shared or private. Shared connections +// are shared between calls to the functions that return them. As a result, +// the methods Close, Auth and Hello must not be called on them. +// +// Multiple goroutines may invoke methods on a connection simultaneously. +type Conn struct { + transport + + busObj *Object + unixFD bool + uuid string + + names []string + namesLck sync.RWMutex + + serialLck sync.Mutex + nextSerial uint32 + serialUsed map[uint32]bool + + calls map[uint32]*Call + callsLck sync.RWMutex + + handlers map[ObjectPath]map[string]interface{} + handlersLck sync.RWMutex + + out chan *Message + closed bool + outLck sync.RWMutex + + signals []chan<- *Signal + signalsLck sync.Mutex + + eavesdropped chan<- *Message + eavesdroppedLck sync.Mutex +} + +// SessionBus returns a shared connection to the session bus, connecting to it +// if not already done. +func SessionBus() (conn *Conn, err error) { + sessionBusLck.Lock() + defer sessionBusLck.Unlock() + if sessionBus != nil { + return sessionBus, nil + } + defer func() { + if conn != nil { + sessionBus = conn + } + }() + conn, err = SessionBusPrivate() + if err != nil { + return + } + if err = conn.Auth(nil); err != nil { + conn.Close() + conn = nil + return + } + if err = conn.Hello(); err != nil { + conn.Close() + conn = nil + } + return +} + +// SessionBusPrivate returns a new private connection to the session bus. +func SessionBusPrivate() (*Conn, error) { + address := os.Getenv("DBUS_SESSION_BUS_ADDRESS") + if address != "" && address != "autolaunch:" { + return Dial(address) + } + + return sessionBusPlatform() +} + +// SystemBus returns a shared connection to the system bus, connecting to it if +// not already done. +func SystemBus() (conn *Conn, err error) { + systemBusLck.Lock() + defer systemBusLck.Unlock() + if systemBus != nil { + return systemBus, nil + } + defer func() { + if conn != nil { + systemBus = conn + } + }() + conn, err = SystemBusPrivate() + if err != nil { + return + } + if err = conn.Auth(nil); err != nil { + conn.Close() + conn = nil + return + } + if err = conn.Hello(); err != nil { + conn.Close() + conn = nil + } + return +} + +// SystemBusPrivate returns a new private connection to the system bus. +func SystemBusPrivate() (*Conn, error) { + address := os.Getenv("DBUS_SYSTEM_BUS_ADDRESS") + if address != "" { + return Dial(address) + } + return Dial(defaultSystemBusAddress) +} + +// Dial establishes a new private connection to the message bus specified by address. +func Dial(address string) (*Conn, error) { + tr, err := getTransport(address) + if err != nil { + return nil, err + } + return newConn(tr) +} + +// NewConn creates a new private *Conn from an already established connection. +func NewConn(conn io.ReadWriteCloser) (*Conn, error) { + return newConn(genericTransport{conn}) +} + +// newConn creates a new *Conn from a transport. +func newConn(tr transport) (*Conn, error) { + conn := new(Conn) + conn.transport = tr + conn.calls = make(map[uint32]*Call) + conn.out = make(chan *Message, 10) + conn.handlers = make(map[ObjectPath]map[string]interface{}) + conn.nextSerial = 1 + conn.serialUsed = map[uint32]bool{0: true} + conn.busObj = conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus") + return conn, nil +} + +// BusObject returns the object owned by the bus daemon which handles +// administrative requests. +func (conn *Conn) BusObject() *Object { + return conn.busObj +} + +// Close closes the connection. Any blocked operations will return with errors +// and the channels passed to Eavesdrop and Signal are closed. This method must +// not be called on shared connections. +func (conn *Conn) Close() error { + conn.outLck.Lock() + close(conn.out) + conn.closed = true + conn.outLck.Unlock() + conn.signalsLck.Lock() + for _, ch := range conn.signals { + close(ch) + } + conn.signalsLck.Unlock() + conn.eavesdroppedLck.Lock() + if conn.eavesdropped != nil { + close(conn.eavesdropped) + } + conn.eavesdroppedLck.Unlock() + return conn.transport.Close() +} + +// Eavesdrop causes conn to send all incoming messages to the given channel +// without further processing. Method replies, errors and signals will not be +// sent to the appropiate channels and method calls will not be handled. If nil +// is passed, the normal behaviour is restored. +// +// The caller has to make sure that ch is sufficiently buffered; +// if a message arrives when a write to ch is not possible, the message is +// discarded. +func (conn *Conn) Eavesdrop(ch chan<- *Message) { + conn.eavesdroppedLck.Lock() + conn.eavesdropped = ch + conn.eavesdroppedLck.Unlock() +} + +// getSerial returns an unused serial. +func (conn *Conn) getSerial() uint32 { + conn.serialLck.Lock() + defer conn.serialLck.Unlock() + n := conn.nextSerial + for conn.serialUsed[n] { + n++ + } + conn.serialUsed[n] = true + conn.nextSerial = n + 1 + return n +} + +// Hello sends the initial org.freedesktop.DBus.Hello call. This method must be +// called after authentication, but before sending any other messages to the +// bus. Hello must not be called for shared connections. +func (conn *Conn) Hello() error { + var s string + err := conn.busObj.Call("org.freedesktop.DBus.Hello", 0).Store(&s) + if err != nil { + return err + } + conn.namesLck.Lock() + conn.names = make([]string, 1) + conn.names[0] = s + conn.namesLck.Unlock() + return nil +} + +// inWorker runs in an own goroutine, reading incoming messages from the +// transport and dispatching them appropiately. +func (conn *Conn) inWorker() { + for { + msg, err := conn.ReadMessage() + if err == nil { + conn.eavesdroppedLck.Lock() + if conn.eavesdropped != nil { + select { + case conn.eavesdropped <- msg: + default: + } + conn.eavesdroppedLck.Unlock() + continue + } + conn.eavesdroppedLck.Unlock() + dest, _ := msg.Headers[FieldDestination].value.(string) + found := false + if dest == "" { + found = true + } else { + conn.namesLck.RLock() + if len(conn.names) == 0 { + found = true + } + for _, v := range conn.names { + if dest == v { + found = true + break + } + } + conn.namesLck.RUnlock() + } + if !found { + // Eavesdropped a message, but no channel for it is registered. + // Ignore it. + continue + } + switch msg.Type { + case TypeMethodReply, TypeError: + serial := msg.Headers[FieldReplySerial].value.(uint32) + conn.callsLck.Lock() + if c, ok := conn.calls[serial]; ok { + if msg.Type == TypeError { + name, _ := msg.Headers[FieldErrorName].value.(string) + c.Err = Error{name, msg.Body} + } else { + c.Body = msg.Body + } + c.Done <- c + conn.serialLck.Lock() + delete(conn.serialUsed, serial) + conn.serialLck.Unlock() + delete(conn.calls, serial) + } + conn.callsLck.Unlock() + case TypeSignal: + iface := msg.Headers[FieldInterface].value.(string) + member := msg.Headers[FieldMember].value.(string) + // as per http://dbus.freedesktop.org/doc/dbus-specification.html , + // sender is optional for signals. + sender, _ := msg.Headers[FieldSender].value.(string) + if iface == "org.freedesktop.DBus" && member == "NameLost" && + sender == "org.freedesktop.DBus" { + + name, _ := msg.Body[0].(string) + conn.namesLck.Lock() + for i, v := range conn.names { + if v == name { + copy(conn.names[i:], conn.names[i+1:]) + conn.names = conn.names[:len(conn.names)-1] + } + } + conn.namesLck.Unlock() + } + signal := &Signal{ + Sender: sender, + Path: msg.Headers[FieldPath].value.(ObjectPath), + Name: iface + "." + member, + Body: msg.Body, + } + conn.signalsLck.Lock() + for _, ch := range conn.signals { + // don't block trying to send a signal + select { + case ch <- signal: + default: + } + } + conn.signalsLck.Unlock() + case TypeMethodCall: + go conn.handleCall(msg) + } + } else if _, ok := err.(InvalidMessageError); !ok { + // Some read error occured (usually EOF); we can't really do + // anything but to shut down all stuff and returns errors to all + // pending replies. + conn.Close() + conn.callsLck.RLock() + for _, v := range conn.calls { + v.Err = err + v.Done <- v + } + conn.callsLck.RUnlock() + return + } + // invalid messages are ignored + } +} + +// Names returns the list of all names that are currently owned by this +// connection. The slice is always at least one element long, the first element +// being the unique name of the connection. +func (conn *Conn) Names() []string { + conn.namesLck.RLock() + // copy the slice so it can't be modified + s := make([]string, len(conn.names)) + copy(s, conn.names) + conn.namesLck.RUnlock() + return s +} + +// Object returns the object identified by the given destination name and path. +func (conn *Conn) Object(dest string, path ObjectPath) *Object { + return &Object{conn, dest, path} +} + +// outWorker runs in an own goroutine, encoding and sending messages that are +// sent to conn.out. +func (conn *Conn) outWorker() { + for msg := range conn.out { + err := conn.SendMessage(msg) + conn.callsLck.RLock() + if err != nil { + if c := conn.calls[msg.serial]; c != nil { + c.Err = err + c.Done <- c + } + conn.serialLck.Lock() + delete(conn.serialUsed, msg.serial) + conn.serialLck.Unlock() + } else if msg.Type != TypeMethodCall { + conn.serialLck.Lock() + delete(conn.serialUsed, msg.serial) + conn.serialLck.Unlock() + } + conn.callsLck.RUnlock() + } +} + +// Send sends the given message to the message bus. You usually don't need to +// use this; use the higher-level equivalents (Call / Go, Emit and Export) +// instead. If msg is a method call and NoReplyExpected is not set, a non-nil +// call is returned and the same value is sent to ch (which must be buffered) +// once the call is complete. Otherwise, ch is ignored and a Call structure is +// returned of which only the Err member is valid. +func (conn *Conn) Send(msg *Message, ch chan *Call) *Call { + var call *Call + + msg.serial = conn.getSerial() + if msg.Type == TypeMethodCall && msg.Flags&FlagNoReplyExpected == 0 { + if ch == nil { + ch = make(chan *Call, 5) + } else if cap(ch) == 0 { + panic("dbus: unbuffered channel passed to (*Conn).Send") + } + call = new(Call) + call.Destination, _ = msg.Headers[FieldDestination].value.(string) + call.Path, _ = msg.Headers[FieldPath].value.(ObjectPath) + iface, _ := msg.Headers[FieldInterface].value.(string) + member, _ := msg.Headers[FieldMember].value.(string) + call.Method = iface + "." + member + call.Args = msg.Body + call.Done = ch + conn.callsLck.Lock() + conn.calls[msg.serial] = call + conn.callsLck.Unlock() + conn.outLck.RLock() + if conn.closed { + call.Err = ErrClosed + call.Done <- call + } else { + conn.out <- msg + } + conn.outLck.RUnlock() + } else { + conn.outLck.RLock() + if conn.closed { + call = &Call{Err: ErrClosed} + } else { + conn.out <- msg + call = &Call{Err: nil} + } + conn.outLck.RUnlock() + } + return call +} + +// sendError creates an error message corresponding to the parameters and sends +// it to conn.out. +func (conn *Conn) sendError(e Error, dest string, serial uint32) { + msg := new(Message) + msg.Type = TypeError + msg.serial = conn.getSerial() + msg.Headers = make(map[HeaderField]Variant) + if dest != "" { + msg.Headers[FieldDestination] = MakeVariant(dest) + } + msg.Headers[FieldErrorName] = MakeVariant(e.Name) + msg.Headers[FieldReplySerial] = MakeVariant(serial) + msg.Body = e.Body + if len(e.Body) > 0 { + msg.Headers[FieldSignature] = MakeVariant(SignatureOf(e.Body...)) + } + conn.outLck.RLock() + if !conn.closed { + conn.out <- msg + } + conn.outLck.RUnlock() +} + +// sendReply creates a method reply message corresponding to the parameters and +// sends it to conn.out. +func (conn *Conn) sendReply(dest string, serial uint32, values ...interface{}) { + msg := new(Message) + msg.Type = TypeMethodReply + msg.serial = conn.getSerial() + msg.Headers = make(map[HeaderField]Variant) + if dest != "" { + msg.Headers[FieldDestination] = MakeVariant(dest) + } + msg.Headers[FieldReplySerial] = MakeVariant(serial) + msg.Body = values + if len(values) > 0 { + msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...)) + } + conn.outLck.RLock() + if !conn.closed { + conn.out <- msg + } + conn.outLck.RUnlock() +} + +// Signal registers the given channel to be passed all received signal messages. +// The caller has to make sure that ch is sufficiently buffered; if a message +// arrives when a write to c is not possible, it is discarded. +// +// Multiple of these channels can be registered at the same time. Passing a +// channel that already is registered will remove it from the list of the +// registered channels. +// +// These channels are "overwritten" by Eavesdrop; i.e., if there currently is a +// channel for eavesdropped messages, this channel receives all signals, and +// none of the channels passed to Signal will receive any signals. +func (conn *Conn) Signal(ch chan<- *Signal) { + conn.signalsLck.Lock() + conn.signals = append(conn.signals, ch) + conn.signalsLck.Unlock() +} + +// SupportsUnixFDs returns whether the underlying transport supports passing of +// unix file descriptors. If this is false, method calls containing unix file +// descriptors will return an error and emitted signals containing them will +// not be sent. +func (conn *Conn) SupportsUnixFDs() bool { + return conn.unixFD +} + +// Error represents a D-Bus message of type Error. +type Error struct { + Name string + Body []interface{} +} + +func (e Error) Error() string { + if len(e.Body) >= 1 { + s, ok := e.Body[0].(string) + if ok { + return s + } + } + return e.Name +} + +// Signal represents a D-Bus message of type Signal. The name member is given in +// "interface.member" notation, e.g. org.freedesktop.D-Bus.NameLost. +type Signal struct { + Sender string + Path ObjectPath + Name string + Body []interface{} +} + +// transport is a D-Bus transport. +type transport interface { + // Read and Write raw data (for example, for the authentication protocol). + io.ReadWriteCloser + + // Send the initial null byte used for the EXTERNAL mechanism. + SendNullByte() error + + // Returns whether this transport supports passing Unix FDs. + SupportsUnixFDs() bool + + // Signal the transport that Unix FD passing is enabled for this connection. + EnableUnixFDs() + + // Read / send a message, handling things like Unix FDs. + ReadMessage() (*Message, error) + SendMessage(*Message) error +} + +func getTransport(address string) (transport, error) { + var err error + var t transport + + m := map[string]func(string) (transport, error){ + "unix": newUnixTransport, + } + addresses := strings.Split(address, ";") + for _, v := range addresses { + i := strings.IndexRune(v, ':') + if i == -1 { + err = errors.New("dbus: invalid bus address (no transport)") + continue + } + f := m[v[:i]] + if f == nil { + err = errors.New("dbus: invalid bus address (invalid or unsupported transport)") + } + t, err = f(v[i+1:]) + if err == nil { + return t, nil + } + } + return nil, err +} + +// dereferenceAll returns a slice that, assuming that vs is a slice of pointers +// of arbitrary types, containes the values that are obtained from dereferencing +// all elements in vs. +func dereferenceAll(vs []interface{}) []interface{} { + for i := range vs { + v := reflect.ValueOf(vs[i]) + v = v.Elem() + vs[i] = v.Interface() + } + return vs +} + +// getKey gets a key from a the list of keys. Returns "" on error / not found... +func getKey(s, key string) string { + i := strings.Index(s, key) + if i == -1 { + return "" + } + if i+len(key)+1 >= len(s) || s[i+len(key)] != '=' { + return "" + } + j := strings.Index(s, ",") + if j == -1 { + j = len(s) + } + return s[i+len(key)+1 : j] +} diff --git a/vendor/src/github.com/godbus/dbus/conn_darwin.go b/vendor/src/github.com/godbus/dbus/conn_darwin.go new file mode 100644 index 0000000000..b67bb1b81d --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/conn_darwin.go @@ -0,0 +1,21 @@ +package dbus + +import ( + "errors" + "os/exec" +) + +func sessionBusPlatform() (*Conn, error) { + cmd := exec.Command("launchctl", "getenv", "DBUS_LAUNCHD_SESSION_BUS_SOCKET") + b, err := cmd.CombinedOutput() + + if err != nil { + return nil, err + } + + if len(b) == 0 { + return nil, errors.New("dbus: couldn't determine address of session bus") + } + + return Dial("unix:path=" + string(b[:len(b)-1])) +} diff --git a/vendor/src/github.com/godbus/dbus/conn_other.go b/vendor/src/github.com/godbus/dbus/conn_other.go new file mode 100644 index 0000000000..f74b8758d4 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/conn_other.go @@ -0,0 +1,27 @@ +// +build !darwin + +package dbus + +import ( + "bytes" + "errors" + "os/exec" +) + +func sessionBusPlatform() (*Conn, error) { + cmd := exec.Command("dbus-launch") + b, err := cmd.CombinedOutput() + + if err != nil { + return nil, err + } + + i := bytes.IndexByte(b, '=') + j := bytes.IndexByte(b, '\n') + + if i == -1 || j == -1 { + return nil, errors.New("dbus: couldn't determine address of session bus") + } + + return Dial(string(b[i+1 : j])) +} diff --git a/vendor/src/github.com/godbus/dbus/conn_test.go b/vendor/src/github.com/godbus/dbus/conn_test.go new file mode 100644 index 0000000000..a2b14e8cc4 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/conn_test.go @@ -0,0 +1,199 @@ +package dbus + +import "testing" + +func TestSessionBus(t *testing.T) { + _, err := SessionBus() + if err != nil { + t.Error(err) + } +} + +func TestSystemBus(t *testing.T) { + _, err := SystemBus() + if err != nil { + t.Error(err) + } +} + +func TestSend(t *testing.T) { + bus, err := SessionBus() + if err != nil { + t.Error(err) + } + ch := make(chan *Call, 1) + msg := &Message{ + Type: TypeMethodCall, + Flags: 0, + Headers: map[HeaderField]Variant{ + FieldDestination: MakeVariant(bus.Names()[0]), + FieldPath: MakeVariant(ObjectPath("/org/freedesktop/DBus")), + FieldInterface: MakeVariant("org.freedesktop.DBus.Peer"), + FieldMember: MakeVariant("Ping"), + }, + } + call := bus.Send(msg, ch) + <-ch + if call.Err != nil { + t.Error(call.Err) + } +} + +type server struct{} + +func (server) Double(i int64) (int64, *Error) { + return 2 * i, nil +} + +func BenchmarkCall(b *testing.B) { + b.StopTimer() + var s string + bus, err := SessionBus() + if err != nil { + b.Fatal(err) + } + name := bus.Names()[0] + obj := bus.BusObject() + b.StartTimer() + for i := 0; i < b.N; i++ { + err := obj.Call("org.freedesktop.DBus.GetNameOwner", 0, name).Store(&s) + if err != nil { + b.Fatal(err) + } + if s != name { + b.Errorf("got %s, wanted %s", s, name) + } + } +} + +func BenchmarkCallAsync(b *testing.B) { + b.StopTimer() + bus, err := SessionBus() + if err != nil { + b.Fatal(err) + } + name := bus.Names()[0] + obj := bus.BusObject() + c := make(chan *Call, 50) + done := make(chan struct{}) + go func() { + for i := 0; i < b.N; i++ { + v := <-c + if v.Err != nil { + b.Error(v.Err) + } + s := v.Body[0].(string) + if s != name { + b.Errorf("got %s, wanted %s", s, name) + } + } + close(done) + }() + b.StartTimer() + for i := 0; i < b.N; i++ { + obj.Go("org.freedesktop.DBus.GetNameOwner", 0, c, name) + } + <-done +} + +func BenchmarkServe(b *testing.B) { + b.StopTimer() + srv, err := SessionBus() + if err != nil { + b.Fatal(err) + } + cli, err := SessionBusPrivate() + if err != nil { + b.Fatal(err) + } + if err = cli.Auth(nil); err != nil { + b.Fatal(err) + } + if err = cli.Hello(); err != nil { + b.Fatal(err) + } + benchmarkServe(b, srv, cli) +} + +func BenchmarkServeAsync(b *testing.B) { + b.StopTimer() + srv, err := SessionBus() + if err != nil { + b.Fatal(err) + } + cli, err := SessionBusPrivate() + if err != nil { + b.Fatal(err) + } + if err = cli.Auth(nil); err != nil { + b.Fatal(err) + } + if err = cli.Hello(); err != nil { + b.Fatal(err) + } + benchmarkServeAsync(b, srv, cli) +} + +func BenchmarkServeSameConn(b *testing.B) { + b.StopTimer() + bus, err := SessionBus() + if err != nil { + b.Fatal(err) + } + + benchmarkServe(b, bus, bus) +} + +func BenchmarkServeSameConnAsync(b *testing.B) { + b.StopTimer() + bus, err := SessionBus() + if err != nil { + b.Fatal(err) + } + + benchmarkServeAsync(b, bus, bus) +} + +func benchmarkServe(b *testing.B, srv, cli *Conn) { + var r int64 + var err error + dest := srv.Names()[0] + srv.Export(server{}, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test") + obj := cli.Object(dest, "/org/guelfey/DBus/Test") + b.StartTimer() + for i := 0; i < b.N; i++ { + err = obj.Call("org.guelfey.DBus.Test.Double", 0, int64(i)).Store(&r) + if err != nil { + b.Fatal(err) + } + if r != 2*int64(i) { + b.Errorf("got %d, wanted %d", r, 2*int64(i)) + } + } +} + +func benchmarkServeAsync(b *testing.B, srv, cli *Conn) { + dest := srv.Names()[0] + srv.Export(server{}, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test") + obj := cli.Object(dest, "/org/guelfey/DBus/Test") + c := make(chan *Call, 50) + done := make(chan struct{}) + go func() { + for i := 0; i < b.N; i++ { + v := <-c + if v.Err != nil { + b.Fatal(v.Err) + } + i, r := v.Args[0].(int64), v.Body[0].(int64) + if 2*i != r { + b.Errorf("got %d, wanted %d", r, 2*i) + } + } + close(done) + }() + b.StartTimer() + for i := 0; i < b.N; i++ { + obj.Go("org.guelfey.DBus.Test.Double", 0, c, int64(i)) + } + <-done +} diff --git a/vendor/src/github.com/godbus/dbus/dbus.go b/vendor/src/github.com/godbus/dbus/dbus.go new file mode 100644 index 0000000000..2ce68735cd --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/dbus.go @@ -0,0 +1,258 @@ +package dbus + +import ( + "errors" + "reflect" + "strings" +) + +var ( + byteType = reflect.TypeOf(byte(0)) + boolType = reflect.TypeOf(false) + uint8Type = reflect.TypeOf(uint8(0)) + int16Type = reflect.TypeOf(int16(0)) + uint16Type = reflect.TypeOf(uint16(0)) + int32Type = reflect.TypeOf(int32(0)) + uint32Type = reflect.TypeOf(uint32(0)) + int64Type = reflect.TypeOf(int64(0)) + uint64Type = reflect.TypeOf(uint64(0)) + float64Type = reflect.TypeOf(float64(0)) + stringType = reflect.TypeOf("") + signatureType = reflect.TypeOf(Signature{""}) + objectPathType = reflect.TypeOf(ObjectPath("")) + variantType = reflect.TypeOf(Variant{Signature{""}, nil}) + interfacesType = reflect.TypeOf([]interface{}{}) + unixFDType = reflect.TypeOf(UnixFD(0)) + unixFDIndexType = reflect.TypeOf(UnixFDIndex(0)) +) + +// An InvalidTypeError signals that a value which cannot be represented in the +// D-Bus wire format was passed to a function. +type InvalidTypeError struct { + Type reflect.Type +} + +func (e InvalidTypeError) Error() string { + return "dbus: invalid type " + e.Type.String() +} + +// Store copies the values contained in src to dest, which must be a slice of +// pointers. It converts slices of interfaces from src to corresponding structs +// in dest. An error is returned if the lengths of src and dest or the types of +// their elements don't match. +func Store(src []interface{}, dest ...interface{}) error { + if len(src) != len(dest) { + return errors.New("dbus.Store: length mismatch") + } + + for i := range src { + if err := store(src[i], dest[i]); err != nil { + return err + } + } + return nil +} + +func store(src, dest interface{}) error { + if reflect.TypeOf(dest).Elem() == reflect.TypeOf(src) { + reflect.ValueOf(dest).Elem().Set(reflect.ValueOf(src)) + return nil + } else if hasStruct(dest) { + rv := reflect.ValueOf(dest).Elem() + switch rv.Kind() { + case reflect.Struct: + vs, ok := src.([]interface{}) + if !ok { + return errors.New("dbus.Store: type mismatch") + } + t := rv.Type() + ndest := make([]interface{}, 0, rv.NumField()) + for i := 0; i < rv.NumField(); i++ { + field := t.Field(i) + if field.PkgPath == "" && field.Tag.Get("dbus") != "-" { + ndest = append(ndest, rv.Field(i).Addr().Interface()) + } + } + if len(vs) != len(ndest) { + return errors.New("dbus.Store: type mismatch") + } + err := Store(vs, ndest...) + if err != nil { + return errors.New("dbus.Store: type mismatch") + } + case reflect.Slice: + sv := reflect.ValueOf(src) + if sv.Kind() != reflect.Slice { + return errors.New("dbus.Store: type mismatch") + } + rv.Set(reflect.MakeSlice(rv.Type(), sv.Len(), sv.Len())) + for i := 0; i < sv.Len(); i++ { + if err := store(sv.Index(i).Interface(), rv.Index(i).Addr().Interface()); err != nil { + return err + } + } + case reflect.Map: + sv := reflect.ValueOf(src) + if sv.Kind() != reflect.Map { + return errors.New("dbus.Store: type mismatch") + } + keys := sv.MapKeys() + rv.Set(reflect.MakeMap(sv.Type())) + for _, key := range keys { + v := reflect.New(sv.Type().Elem()) + if err := store(v, sv.MapIndex(key).Interface()); err != nil { + return err + } + rv.SetMapIndex(key, v.Elem()) + } + default: + return errors.New("dbus.Store: type mismatch") + } + return nil + } else { + return errors.New("dbus.Store: type mismatch") + } +} + +func hasStruct(v interface{}) bool { + t := reflect.TypeOf(v) + for { + switch t.Kind() { + case reflect.Struct: + return true + case reflect.Slice, reflect.Ptr, reflect.Map: + t = t.Elem() + default: + return false + } + } +} + +// An ObjectPath is an object path as defined by the D-Bus spec. +type ObjectPath string + +// IsValid returns whether the object path is valid. +func (o ObjectPath) IsValid() bool { + s := string(o) + if len(s) == 0 { + return false + } + if s[0] != '/' { + return false + } + if s[len(s)-1] == '/' && len(s) != 1 { + return false + } + // probably not used, but technically possible + if s == "/" { + return true + } + split := strings.Split(s[1:], "/") + for _, v := range split { + if len(v) == 0 { + return false + } + for _, c := range v { + if !isMemberChar(c) { + return false + } + } + } + return true +} + +// A UnixFD is a Unix file descriptor sent over the wire. See the package-level +// documentation for more information about Unix file descriptor passsing. +type UnixFD int32 + +// A UnixFDIndex is the representation of a Unix file descriptor in a message. +type UnixFDIndex uint32 + +// alignment returns the alignment of values of type t. +func alignment(t reflect.Type) int { + switch t { + case variantType: + return 1 + case objectPathType: + return 4 + case signatureType: + return 1 + case interfacesType: // sometimes used for structs + return 8 + } + switch t.Kind() { + case reflect.Uint8: + return 1 + case reflect.Uint16, reflect.Int16: + return 2 + case reflect.Uint32, reflect.Int32, reflect.String, reflect.Array, reflect.Slice, reflect.Map: + return 4 + case reflect.Uint64, reflect.Int64, reflect.Float64, reflect.Struct: + return 8 + case reflect.Ptr: + return alignment(t.Elem()) + } + return 1 +} + +// isKeyType returns whether t is a valid type for a D-Bus dict. +func isKeyType(t reflect.Type) bool { + switch t.Kind() { + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float64, + reflect.String: + + return true + } + return false +} + +// isValidInterface returns whether s is a valid name for an interface. +func isValidInterface(s string) bool { + if len(s) == 0 || len(s) > 255 || s[0] == '.' { + return false + } + elem := strings.Split(s, ".") + if len(elem) < 2 { + return false + } + for _, v := range elem { + if len(v) == 0 { + return false + } + if v[0] >= '0' && v[0] <= '9' { + return false + } + for _, c := range v { + if !isMemberChar(c) { + return false + } + } + } + return true +} + +// isValidMember returns whether s is a valid name for a member. +func isValidMember(s string) bool { + if len(s) == 0 || len(s) > 255 { + return false + } + i := strings.Index(s, ".") + if i != -1 { + return false + } + if s[0] >= '0' && s[0] <= '9' { + return false + } + for _, c := range s { + if !isMemberChar(c) { + return false + } + } + return true +} + +func isMemberChar(c rune) bool { + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || c == '_' +} diff --git a/vendor/src/github.com/godbus/dbus/decoder.go b/vendor/src/github.com/godbus/dbus/decoder.go new file mode 100644 index 0000000000..ef50dcab98 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/decoder.go @@ -0,0 +1,228 @@ +package dbus + +import ( + "encoding/binary" + "io" + "reflect" +) + +type decoder struct { + in io.Reader + order binary.ByteOrder + pos int +} + +// newDecoder returns a new decoder that reads values from in. The input is +// expected to be in the given byte order. +func newDecoder(in io.Reader, order binary.ByteOrder) *decoder { + dec := new(decoder) + dec.in = in + dec.order = order + return dec +} + +// align aligns the input to the given boundary and panics on error. +func (dec *decoder) align(n int) { + if dec.pos%n != 0 { + newpos := (dec.pos + n - 1) & ^(n - 1) + empty := make([]byte, newpos-dec.pos) + if _, err := io.ReadFull(dec.in, empty); err != nil { + panic(err) + } + dec.pos = newpos + } +} + +// Calls binary.Read(dec.in, dec.order, v) and panics on read errors. +func (dec *decoder) binread(v interface{}) { + if err := binary.Read(dec.in, dec.order, v); err != nil { + panic(err) + } +} + +func (dec *decoder) Decode(sig Signature) (vs []interface{}, err error) { + defer func() { + var ok bool + v := recover() + if err, ok = v.(error); ok { + if err == io.EOF || err == io.ErrUnexpectedEOF { + err = FormatError("unexpected EOF") + } + } + }() + vs = make([]interface{}, 0) + s := sig.str + for s != "" { + err, rem := validSingle(s, 0) + if err != nil { + return nil, err + } + v := dec.decode(s[:len(s)-len(rem)], 0) + vs = append(vs, v) + s = rem + } + return vs, nil +} + +func (dec *decoder) decode(s string, depth int) interface{} { + dec.align(alignment(typeFor(s))) + switch s[0] { + case 'y': + var b [1]byte + if _, err := dec.in.Read(b[:]); err != nil { + panic(err) + } + dec.pos++ + return b[0] + case 'b': + i := dec.decode("u", depth).(uint32) + switch { + case i == 0: + return false + case i == 1: + return true + default: + panic(FormatError("invalid value for boolean")) + } + case 'n': + var i int16 + dec.binread(&i) + dec.pos += 2 + return i + case 'i': + var i int32 + dec.binread(&i) + dec.pos += 4 + return i + case 'x': + var i int64 + dec.binread(&i) + dec.pos += 8 + return i + case 'q': + var i uint16 + dec.binread(&i) + dec.pos += 2 + return i + case 'u': + var i uint32 + dec.binread(&i) + dec.pos += 4 + return i + case 't': + var i uint64 + dec.binread(&i) + dec.pos += 8 + return i + case 'd': + var f float64 + dec.binread(&f) + dec.pos += 8 + return f + case 's': + length := dec.decode("u", depth).(uint32) + b := make([]byte, int(length)+1) + if _, err := io.ReadFull(dec.in, b); err != nil { + panic(err) + } + dec.pos += int(length) + 1 + return string(b[:len(b)-1]) + case 'o': + return ObjectPath(dec.decode("s", depth).(string)) + case 'g': + length := dec.decode("y", depth).(byte) + b := make([]byte, int(length)+1) + if _, err := io.ReadFull(dec.in, b); err != nil { + panic(err) + } + dec.pos += int(length) + 1 + sig, err := ParseSignature(string(b[:len(b)-1])) + if err != nil { + panic(err) + } + return sig + case 'v': + if depth >= 64 { + panic(FormatError("input exceeds container depth limit")) + } + var variant Variant + sig := dec.decode("g", depth).(Signature) + if len(sig.str) == 0 { + panic(FormatError("variant signature is empty")) + } + err, rem := validSingle(sig.str, 0) + if err != nil { + panic(err) + } + if rem != "" { + panic(FormatError("variant signature has multiple types")) + } + variant.sig = sig + variant.value = dec.decode(sig.str, depth+1) + return variant + case 'h': + return UnixFDIndex(dec.decode("u", depth).(uint32)) + case 'a': + if len(s) > 1 && s[1] == '{' { + ksig := s[2:3] + vsig := s[3 : len(s)-1] + v := reflect.MakeMap(reflect.MapOf(typeFor(ksig), typeFor(vsig))) + if depth >= 63 { + panic(FormatError("input exceeds container depth limit")) + } + length := dec.decode("u", depth).(uint32) + // Even for empty maps, the correct padding must be included + dec.align(8) + spos := dec.pos + for dec.pos < spos+int(length) { + dec.align(8) + if !isKeyType(v.Type().Key()) { + panic(InvalidTypeError{v.Type()}) + } + kv := dec.decode(ksig, depth+2) + vv := dec.decode(vsig, depth+2) + v.SetMapIndex(reflect.ValueOf(kv), reflect.ValueOf(vv)) + } + return v.Interface() + } + if depth >= 64 { + panic(FormatError("input exceeds container depth limit")) + } + length := dec.decode("u", depth).(uint32) + v := reflect.MakeSlice(reflect.SliceOf(typeFor(s[1:])), 0, int(length)) + // Even for empty arrays, the correct padding must be included + dec.align(alignment(typeFor(s[1:]))) + spos := dec.pos + for dec.pos < spos+int(length) { + ev := dec.decode(s[1:], depth+1) + v = reflect.Append(v, reflect.ValueOf(ev)) + } + return v.Interface() + case '(': + if depth >= 64 { + panic(FormatError("input exceeds container depth limit")) + } + dec.align(8) + v := make([]interface{}, 0) + s = s[1 : len(s)-1] + for s != "" { + err, rem := validSingle(s, 0) + if err != nil { + panic(err) + } + ev := dec.decode(s[:len(s)-len(rem)], depth+1) + v = append(v, ev) + s = rem + } + return v + default: + panic(SignatureError{Sig: s}) + } +} + +// A FormatError is an error in the wire format. +type FormatError string + +func (e FormatError) Error() string { + return "dbus: wire format error: " + string(e) +} diff --git a/vendor/src/github.com/godbus/dbus/doc.go b/vendor/src/github.com/godbus/dbus/doc.go new file mode 100644 index 0000000000..deff554a38 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/doc.go @@ -0,0 +1,63 @@ +/* +Package dbus implements bindings to the D-Bus message bus system. + +To use the message bus API, you first need to connect to a bus (usually the +session or system bus). The acquired connection then can be used to call methods +on remote objects and emit or receive signals. Using the Export method, you can +arrange D-Bus methods calls to be directly translated to method calls on a Go +value. + +Conversion Rules + +For outgoing messages, Go types are automatically converted to the +corresponding D-Bus types. The following types are directly encoded as their +respective D-Bus equivalents: + + Go type | D-Bus type + ------------+----------- + byte | BYTE + bool | BOOLEAN + int16 | INT16 + uint16 | UINT16 + int32 | INT32 + uint32 | UINT32 + int64 | INT64 + uint64 | UINT64 + float64 | DOUBLE + string | STRING + ObjectPath | OBJECT_PATH + Signature | SIGNATURE + Variant | VARIANT + UnixFDIndex | UNIX_FD + +Slices and arrays encode as ARRAYs of their element type. + +Maps encode as DICTs, provided that their key type can be used as a key for +a DICT. + +Structs other than Variant and Signature encode as a STRUCT containing their +exported fields. Fields whose tags contain `dbus:"-"` and unexported fields will +be skipped. + +Pointers encode as the value they're pointed to. + +Trying to encode any other type or a slice, map or struct containing an +unsupported type will result in an InvalidTypeError. + +For incoming messages, the inverse of these rules are used, with the exception +of STRUCTs. Incoming STRUCTS are represented as a slice of empty interfaces +containing the struct fields in the correct order. The Store function can be +used to convert such values to Go structs. + +Unix FD passing + +Handling Unix file descriptors deserves special mention. To use them, you should +first check that they are supported on a connection by calling SupportsUnixFDs. +If it returns true, all method of Connection will translate messages containing +UnixFD's to messages that are accompanied by the given file descriptors with the +UnixFD values being substituted by the correct indices. Similarily, the indices +of incoming messages are automatically resolved. It shouldn't be necessary to use +UnixFDIndex. + +*/ +package dbus diff --git a/vendor/src/github.com/godbus/dbus/encoder.go b/vendor/src/github.com/godbus/dbus/encoder.go new file mode 100644 index 0000000000..f9d2f05716 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/encoder.go @@ -0,0 +1,179 @@ +package dbus + +import ( + "bytes" + "encoding/binary" + "io" + "reflect" +) + +// An encoder encodes values to the D-Bus wire format. +type encoder struct { + out io.Writer + order binary.ByteOrder + pos int +} + +// NewEncoder returns a new encoder that writes to out in the given byte order. +func newEncoder(out io.Writer, order binary.ByteOrder) *encoder { + enc := new(encoder) + enc.out = out + enc.order = order + return enc +} + +// Aligns the next output to be on a multiple of n. Panics on write errors. +func (enc *encoder) align(n int) { + if enc.pos%n != 0 { + newpos := (enc.pos + n - 1) & ^(n - 1) + empty := make([]byte, newpos-enc.pos) + if _, err := enc.out.Write(empty); err != nil { + panic(err) + } + enc.pos = newpos + } +} + +// Calls binary.Write(enc.out, enc.order, v) and panics on write errors. +func (enc *encoder) binwrite(v interface{}) { + if err := binary.Write(enc.out, enc.order, v); err != nil { + panic(err) + } +} + +// Encode encodes the given values to the underyling reader. All written values +// are aligned properly as required by the D-Bus spec. +func (enc *encoder) Encode(vs ...interface{}) (err error) { + defer func() { + err, _ = recover().(error) + }() + for _, v := range vs { + enc.encode(reflect.ValueOf(v), 0) + } + return nil +} + +// encode encodes the given value to the writer and panics on error. depth holds +// the depth of the container nesting. +func (enc *encoder) encode(v reflect.Value, depth int) { + enc.align(alignment(v.Type())) + switch v.Kind() { + case reflect.Uint8: + var b [1]byte + b[0] = byte(v.Uint()) + if _, err := enc.out.Write(b[:]); err != nil { + panic(err) + } + enc.pos++ + case reflect.Bool: + if v.Bool() { + enc.encode(reflect.ValueOf(uint32(1)), depth) + } else { + enc.encode(reflect.ValueOf(uint32(0)), depth) + } + case reflect.Int16: + enc.binwrite(int16(v.Int())) + enc.pos += 2 + case reflect.Uint16: + enc.binwrite(uint16(v.Uint())) + enc.pos += 2 + case reflect.Int32: + enc.binwrite(int32(v.Int())) + enc.pos += 4 + case reflect.Uint32: + enc.binwrite(uint32(v.Uint())) + enc.pos += 4 + case reflect.Int64: + enc.binwrite(v.Int()) + enc.pos += 8 + case reflect.Uint64: + enc.binwrite(v.Uint()) + enc.pos += 8 + case reflect.Float64: + enc.binwrite(v.Float()) + enc.pos += 8 + case reflect.String: + enc.encode(reflect.ValueOf(uint32(len(v.String()))), depth) + b := make([]byte, v.Len()+1) + copy(b, v.String()) + b[len(b)-1] = 0 + n, err := enc.out.Write(b) + if err != nil { + panic(err) + } + enc.pos += n + case reflect.Ptr: + enc.encode(v.Elem(), depth) + case reflect.Slice, reflect.Array: + if depth >= 64 { + panic(FormatError("input exceeds container depth limit")) + } + var buf bytes.Buffer + bufenc := newEncoder(&buf, enc.order) + + for i := 0; i < v.Len(); i++ { + bufenc.encode(v.Index(i), depth+1) + } + enc.encode(reflect.ValueOf(uint32(buf.Len())), depth) + length := buf.Len() + enc.align(alignment(v.Type().Elem())) + if _, err := buf.WriteTo(enc.out); err != nil { + panic(err) + } + enc.pos += length + case reflect.Struct: + if depth >= 64 && v.Type() != signatureType { + panic(FormatError("input exceeds container depth limit")) + } + switch t := v.Type(); t { + case signatureType: + str := v.Field(0) + enc.encode(reflect.ValueOf(byte(str.Len())), depth+1) + b := make([]byte, str.Len()+1) + copy(b, str.String()) + b[len(b)-1] = 0 + n, err := enc.out.Write(b) + if err != nil { + panic(err) + } + enc.pos += n + case variantType: + variant := v.Interface().(Variant) + enc.encode(reflect.ValueOf(variant.sig), depth+1) + enc.encode(reflect.ValueOf(variant.value), depth+1) + default: + for i := 0; i < v.Type().NumField(); i++ { + field := t.Field(i) + if field.PkgPath == "" && field.Tag.Get("dbus") != "-" { + enc.encode(v.Field(i), depth+1) + } + } + } + case reflect.Map: + // Maps are arrays of structures, so they actually increase the depth by + // 2. + if depth >= 63 { + panic(FormatError("input exceeds container depth limit")) + } + if !isKeyType(v.Type().Key()) { + panic(InvalidTypeError{v.Type()}) + } + keys := v.MapKeys() + var buf bytes.Buffer + bufenc := newEncoder(&buf, enc.order) + for _, k := range keys { + bufenc.align(8) + bufenc.encode(k, depth+2) + bufenc.encode(v.MapIndex(k), depth+2) + } + enc.encode(reflect.ValueOf(uint32(buf.Len())), depth) + length := buf.Len() + enc.align(8) + if _, err := buf.WriteTo(enc.out); err != nil { + panic(err) + } + enc.pos += length + default: + panic(InvalidTypeError{v.Type()}) + } +} diff --git a/vendor/src/github.com/godbus/dbus/examples_test.go b/vendor/src/github.com/godbus/dbus/examples_test.go new file mode 100644 index 0000000000..0218ac5598 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/examples_test.go @@ -0,0 +1,50 @@ +package dbus + +import "fmt" + +func ExampleConn_Emit() { + conn, err := SessionBus() + if err != nil { + panic(err) + } + + conn.Emit("/foo/bar", "foo.bar.Baz", uint32(0xDAEDBEEF)) +} + +func ExampleObject_Call() { + var list []string + + conn, err := SessionBus() + if err != nil { + panic(err) + } + + err = conn.BusObject().Call("org.freedesktop.DBus.ListNames", 0).Store(&list) + if err != nil { + panic(err) + } + for _, v := range list { + fmt.Println(v) + } +} + +func ExampleObject_Go() { + conn, err := SessionBus() + if err != nil { + panic(err) + } + + ch := make(chan *Call, 10) + conn.BusObject().Go("org.freedesktop.DBus.ListActivatableNames", 0, ch) + select { + case call := <-ch: + if call.Err != nil { + panic(err) + } + list := call.Body[0].([]string) + for _, v := range list { + fmt.Println(v) + } + // put some other cases here + } +} diff --git a/vendor/src/github.com/godbus/dbus/export.go b/vendor/src/github.com/godbus/dbus/export.go new file mode 100644 index 0000000000..1dd1591528 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/export.go @@ -0,0 +1,302 @@ +package dbus + +import ( + "errors" + "reflect" + "strings" + "unicode" +) + +var ( + errmsgInvalidArg = Error{ + "org.freedesktop.DBus.Error.InvalidArgs", + []interface{}{"Invalid type / number of args"}, + } + errmsgNoObject = Error{ + "org.freedesktop.DBus.Error.NoSuchObject", + []interface{}{"No such object"}, + } + errmsgUnknownMethod = Error{ + "org.freedesktop.DBus.Error.UnknownMethod", + []interface{}{"Unknown / invalid method"}, + } +) + +// Sender is a type which can be used in exported methods to receive the message +// sender. +type Sender string + +func exportedMethod(v interface{}, name string) reflect.Value { + if v == nil { + return reflect.Value{} + } + m := reflect.ValueOf(v).MethodByName(name) + if !m.IsValid() { + return reflect.Value{} + } + t := m.Type() + if t.NumOut() == 0 || + t.Out(t.NumOut()-1) != reflect.TypeOf(&errmsgInvalidArg) { + + return reflect.Value{} + } + return m +} + +// handleCall handles the given method call (i.e. looks if it's one of the +// pre-implemented ones and searches for a corresponding handler if not). +func (conn *Conn) handleCall(msg *Message) { + name := msg.Headers[FieldMember].value.(string) + path := msg.Headers[FieldPath].value.(ObjectPath) + ifaceName, hasIface := msg.Headers[FieldInterface].value.(string) + sender, hasSender := msg.Headers[FieldSender].value.(string) + serial := msg.serial + if ifaceName == "org.freedesktop.DBus.Peer" { + switch name { + case "Ping": + conn.sendReply(sender, serial) + case "GetMachineId": + conn.sendReply(sender, serial, conn.uuid) + default: + conn.sendError(errmsgUnknownMethod, sender, serial) + } + return + } + if len(name) == 0 || unicode.IsLower([]rune(name)[0]) { + conn.sendError(errmsgUnknownMethod, sender, serial) + } + var m reflect.Value + if hasIface { + conn.handlersLck.RLock() + obj, ok := conn.handlers[path] + if !ok { + conn.sendError(errmsgNoObject, sender, serial) + conn.handlersLck.RUnlock() + return + } + iface := obj[ifaceName] + conn.handlersLck.RUnlock() + m = exportedMethod(iface, name) + } else { + conn.handlersLck.RLock() + if _, ok := conn.handlers[path]; !ok { + conn.sendError(errmsgNoObject, sender, serial) + conn.handlersLck.RUnlock() + return + } + for _, v := range conn.handlers[path] { + m = exportedMethod(v, name) + if m.IsValid() { + break + } + } + conn.handlersLck.RUnlock() + } + if !m.IsValid() { + conn.sendError(errmsgUnknownMethod, sender, serial) + return + } + t := m.Type() + vs := msg.Body + pointers := make([]interface{}, t.NumIn()) + decode := make([]interface{}, 0, len(vs)) + for i := 0; i < t.NumIn(); i++ { + tp := t.In(i) + val := reflect.New(tp) + pointers[i] = val.Interface() + if tp == reflect.TypeOf((*Sender)(nil)).Elem() { + val.Elem().SetString(sender) + } else { + decode = append(decode, pointers[i]) + } + } + if len(decode) != len(vs) { + conn.sendError(errmsgInvalidArg, sender, serial) + return + } + if err := Store(vs, decode...); err != nil { + conn.sendError(errmsgInvalidArg, sender, serial) + return + } + params := make([]reflect.Value, len(pointers)) + for i := 0; i < len(pointers); i++ { + params[i] = reflect.ValueOf(pointers[i]).Elem() + } + ret := m.Call(params) + if em := ret[t.NumOut()-1].Interface().(*Error); em != nil { + conn.sendError(*em, sender, serial) + return + } + if msg.Flags&FlagNoReplyExpected == 0 { + reply := new(Message) + reply.Type = TypeMethodReply + reply.serial = conn.getSerial() + reply.Headers = make(map[HeaderField]Variant) + if hasSender { + reply.Headers[FieldDestination] = msg.Headers[FieldSender] + } + reply.Headers[FieldReplySerial] = MakeVariant(msg.serial) + reply.Body = make([]interface{}, len(ret)-1) + for i := 0; i < len(ret)-1; i++ { + reply.Body[i] = ret[i].Interface() + } + if len(ret) != 1 { + reply.Headers[FieldSignature] = MakeVariant(SignatureOf(reply.Body...)) + } + conn.outLck.RLock() + if !conn.closed { + conn.out <- reply + } + conn.outLck.RUnlock() + } +} + +// Emit emits the given signal on the message bus. The name parameter must be +// formatted as "interface.member", e.g., "org.freedesktop.DBus.NameLost". +func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) error { + if !path.IsValid() { + return errors.New("dbus: invalid object path") + } + i := strings.LastIndex(name, ".") + if i == -1 { + return errors.New("dbus: invalid method name") + } + iface := name[:i] + member := name[i+1:] + if !isValidMember(member) { + return errors.New("dbus: invalid method name") + } + if !isValidInterface(iface) { + return errors.New("dbus: invalid interface name") + } + msg := new(Message) + msg.Type = TypeSignal + msg.serial = conn.getSerial() + msg.Headers = make(map[HeaderField]Variant) + msg.Headers[FieldInterface] = MakeVariant(iface) + msg.Headers[FieldMember] = MakeVariant(member) + msg.Headers[FieldPath] = MakeVariant(path) + msg.Body = values + if len(values) > 0 { + msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...)) + } + conn.outLck.RLock() + defer conn.outLck.RUnlock() + if conn.closed { + return ErrClosed + } + conn.out <- msg + return nil +} + +// Export registers the given value to be exported as an object on the +// message bus. +// +// If a method call on the given path and interface is received, an exported +// method with the same name is called with v as the receiver if the +// parameters match and the last return value is of type *Error. If this +// *Error is not nil, it is sent back to the caller as an error. +// Otherwise, a method reply is sent with the other return values as its body. +// +// Any parameters with the special type Sender are set to the sender of the +// dbus message when the method is called. Parameters of this type do not +// contribute to the dbus signature of the method (i.e. the method is exposed +// as if the parameters of type Sender were not there). +// +// Every method call is executed in a new goroutine, so the method may be called +// in multiple goroutines at once. +// +// Method calls on the interface org.freedesktop.DBus.Peer will be automatically +// handled for every object. +// +// Passing nil as the first parameter will cause conn to cease handling calls on +// the given combination of path and interface. +// +// Export returns an error if path is not a valid path name. +func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error { + if !path.IsValid() { + return errors.New("dbus: invalid path name") + } + conn.handlersLck.Lock() + if v == nil { + if _, ok := conn.handlers[path]; ok { + delete(conn.handlers[path], iface) + if len(conn.handlers[path]) == 0 { + delete(conn.handlers, path) + } + } + return nil + } + if _, ok := conn.handlers[path]; !ok { + conn.handlers[path] = make(map[string]interface{}) + } + conn.handlers[path][iface] = v + conn.handlersLck.Unlock() + return nil +} + +// ReleaseName calls org.freedesktop.DBus.ReleaseName. You should use only this +// method to release a name (see below). +func (conn *Conn) ReleaseName(name string) (ReleaseNameReply, error) { + var r uint32 + err := conn.busObj.Call("org.freedesktop.DBus.ReleaseName", 0, name).Store(&r) + if err != nil { + return 0, err + } + if r == uint32(ReleaseNameReplyReleased) { + conn.namesLck.Lock() + for i, v := range conn.names { + if v == name { + copy(conn.names[i:], conn.names[i+1:]) + conn.names = conn.names[:len(conn.names)-1] + } + } + conn.namesLck.Unlock() + } + return ReleaseNameReply(r), nil +} + +// RequestName calls org.freedesktop.DBus.RequestName. You should use only this +// method to request a name because package dbus needs to keep track of all +// names that the connection has. +func (conn *Conn) RequestName(name string, flags RequestNameFlags) (RequestNameReply, error) { + var r uint32 + err := conn.busObj.Call("org.freedesktop.DBus.RequestName", 0, name, flags).Store(&r) + if err != nil { + return 0, err + } + if r == uint32(RequestNameReplyPrimaryOwner) { + conn.namesLck.Lock() + conn.names = append(conn.names, name) + conn.namesLck.Unlock() + } + return RequestNameReply(r), nil +} + +// ReleaseNameReply is the reply to a ReleaseName call. +type ReleaseNameReply uint32 + +const ( + ReleaseNameReplyReleased ReleaseNameReply = 1 + iota + ReleaseNameReplyNonExistent + ReleaseNameReplyNotOwner +) + +// RequestNameFlags represents the possible flags for a RequestName call. +type RequestNameFlags uint32 + +const ( + NameFlagAllowReplacement RequestNameFlags = 1 << iota + NameFlagReplaceExisting + NameFlagDoNotQueue +) + +// RequestNameReply is the reply to a RequestName call. +type RequestNameReply uint32 + +const ( + RequestNameReplyPrimaryOwner RequestNameReply = 1 + iota + RequestNameReplyInQueue + RequestNameReplyExists + RequestNameReplyAlreadyOwner +) diff --git a/vendor/src/github.com/godbus/dbus/homedir.go b/vendor/src/github.com/godbus/dbus/homedir.go new file mode 100644 index 0000000000..0b745f9313 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/homedir.go @@ -0,0 +1,28 @@ +package dbus + +import ( + "os" + "sync" +) + +var ( + homeDir string + homeDirLock sync.Mutex +) + +func getHomeDir() string { + homeDirLock.Lock() + defer homeDirLock.Unlock() + + if homeDir != "" { + return homeDir + } + + homeDir = os.Getenv("HOME") + if homeDir != "" { + return homeDir + } + + homeDir = lookupHomeDir() + return homeDir +} diff --git a/vendor/src/github.com/godbus/dbus/homedir_dynamic.go b/vendor/src/github.com/godbus/dbus/homedir_dynamic.go new file mode 100644 index 0000000000..2732081e73 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/homedir_dynamic.go @@ -0,0 +1,15 @@ +// +build !static_build + +package dbus + +import ( + "os/user" +) + +func lookupHomeDir() string { + u, err := user.Current() + if err != nil { + return "/" + } + return u.HomeDir +} diff --git a/vendor/src/github.com/godbus/dbus/homedir_static.go b/vendor/src/github.com/godbus/dbus/homedir_static.go new file mode 100644 index 0000000000..b9d9cb5525 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/homedir_static.go @@ -0,0 +1,45 @@ +// +build static_build + +package dbus + +import ( + "bufio" + "os" + "strconv" + "strings" +) + +func lookupHomeDir() string { + myUid := os.Getuid() + + f, err := os.Open("/etc/passwd") + if err != nil { + return "/" + } + defer f.Close() + + s := bufio.NewScanner(f) + + for s.Scan() { + if err := s.Err(); err != nil { + break + } + + line := strings.TrimSpace(s.Text()) + if line == "" { + continue + } + + parts := strings.Split(line, ":") + + if len(parts) >= 6 { + uid, err := strconv.Atoi(parts[2]) + if err == nil && uid == myUid { + return parts[5] + } + } + } + + // Default to / if we can't get a better value + return "/" +} diff --git a/vendor/src/github.com/godbus/dbus/introspect/call.go b/vendor/src/github.com/godbus/dbus/introspect/call.go new file mode 100644 index 0000000000..4aca2ea63e --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/introspect/call.go @@ -0,0 +1,27 @@ +package introspect + +import ( + "encoding/xml" + "github.com/godbus/dbus" + "strings" +) + +// Call calls org.freedesktop.Introspectable.Introspect on a remote object +// and returns the introspection data. +func Call(o *dbus.Object) (*Node, error) { + var xmldata string + var node Node + + err := o.Call("org.freedesktop.DBus.Introspectable.Introspect", 0).Store(&xmldata) + if err != nil { + return nil, err + } + err = xml.NewDecoder(strings.NewReader(xmldata)).Decode(&node) + if err != nil { + return nil, err + } + if node.Name == "" { + node.Name = string(o.Path()) + } + return &node, nil +} diff --git a/vendor/src/github.com/godbus/dbus/introspect/introspect.go b/vendor/src/github.com/godbus/dbus/introspect/introspect.go new file mode 100644 index 0000000000..dafcdb8b7a --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/introspect/introspect.go @@ -0,0 +1,80 @@ +// Package introspect provides some utilities for dealing with the DBus +// introspection format. +package introspect + +import "encoding/xml" + +// The introspection data for the org.freedesktop.DBus.Introspectable interface. +var IntrospectData = Interface{ + Name: "org.freedesktop.DBus.Introspectable", + Methods: []Method{ + { + Name: "Introspect", + Args: []Arg{ + {"out", "s", "out"}, + }, + }, + }, +} + +// The introspection data for the org.freedesktop.DBus.Introspectable interface, +// as a string. +const IntrospectDataString = ` + + + + + +` + +// Node is the root element of an introspection. +type Node struct { + XMLName xml.Name `xml:"node"` + Name string `xml:"name,attr,omitempty"` + Interfaces []Interface `xml:"interface"` + Children []Node `xml:"node,omitempty"` +} + +// Interface describes a DBus interface that is available on the message bus. +type Interface struct { + Name string `xml:"name,attr"` + Methods []Method `xml:"method"` + Signals []Signal `xml:"signal"` + Properties []Property `xml:"property"` + Annotations []Annotation `xml:"annotation"` +} + +// Method describes a Method on an Interface as retured by an introspection. +type Method struct { + Name string `xml:"name,attr"` + Args []Arg `xml:"arg"` + Annotations []Annotation `xml:"annotation"` +} + +// Signal describes a Signal emitted on an Interface. +type Signal struct { + Name string `xml:"name,attr"` + Args []Arg `xml:"arg"` + Annotations []Annotation `xml:"annotation"` +} + +// Property describes a property of an Interface. +type Property struct { + Name string `xml:"name,attr"` + Type string `xml:"type,attr"` + Access string `xml:"access,attr"` + Annotations []Annotation `xml:"annotation"` +} + +// Arg represents an argument of a method or a signal. +type Arg struct { + Name string `xml:"name,attr,omitempty"` + Type string `xml:"type,attr"` + Direction string `xml:"direction,attr,omitempty"` +} + +// Annotation is an annotation in the introspection format. +type Annotation struct { + Name string `xml:"name,attr"` + Value string `xml:"value,attr"` +} diff --git a/vendor/src/github.com/godbus/dbus/introspect/introspectable.go b/vendor/src/github.com/godbus/dbus/introspect/introspectable.go new file mode 100644 index 0000000000..a2a965a343 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/introspect/introspectable.go @@ -0,0 +1,74 @@ +package introspect + +import ( + "encoding/xml" + "github.com/godbus/dbus" + "reflect" +) + +// Introspectable implements org.freedesktop.Introspectable. +// +// You can create it by converting the XML-formatted introspection data from a +// string to an Introspectable or call NewIntrospectable with a Node. Then, +// export it as org.freedesktop.Introspectable on you object. +type Introspectable string + +// NewIntrospectable returns an Introspectable that returns the introspection +// data that corresponds to the given Node. If n.Interfaces doesn't contain the +// data for org.freedesktop.DBus.Introspectable, it is added automatically. +func NewIntrospectable(n *Node) Introspectable { + found := false + for _, v := range n.Interfaces { + if v.Name == "org.freedesktop.DBus.Introspectable" { + found = true + break + } + } + if !found { + n.Interfaces = append(n.Interfaces, IntrospectData) + } + b, err := xml.Marshal(n) + if err != nil { + panic(err) + } + return Introspectable(b) +} + +// Introspect implements org.freedesktop.Introspectable.Introspect. +func (i Introspectable) Introspect() (string, *dbus.Error) { + return string(i), nil +} + +// Methods returns the description of the methods of v. This can be used to +// create a Node which can be passed to NewIntrospectable. +func Methods(v interface{}) []Method { + t := reflect.TypeOf(v) + ms := make([]Method, 0, t.NumMethod()) + for i := 0; i < t.NumMethod(); i++ { + if t.Method(i).PkgPath != "" { + continue + } + mt := t.Method(i).Type + if mt.NumOut() == 0 || + mt.Out(mt.NumOut()-1) != reflect.TypeOf(&dbus.Error{"", nil}) { + + continue + } + var m Method + m.Name = t.Method(i).Name + m.Args = make([]Arg, 0, mt.NumIn()+mt.NumOut()-2) + for j := 1; j < mt.NumIn(); j++ { + if mt.In(j) != reflect.TypeOf((*dbus.Sender)(nil)).Elem() { + arg := Arg{"", dbus.SignatureOfType(mt.In(j)).String(), "in"} + m.Args = append(m.Args, arg) + } + } + for j := 0; j < mt.NumOut()-1; j++ { + arg := Arg{"", dbus.SignatureOfType(mt.Out(j)).String(), "out"} + m.Args = append(m.Args, arg) + } + m.Annotations = make([]Annotation, 0) + ms = append(ms, m) + } + return ms +} diff --git a/vendor/src/github.com/godbus/dbus/message.go b/vendor/src/github.com/godbus/dbus/message.go new file mode 100644 index 0000000000..075d6e38ba --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/message.go @@ -0,0 +1,346 @@ +package dbus + +import ( + "bytes" + "encoding/binary" + "errors" + "io" + "reflect" + "strconv" +) + +const protoVersion byte = 1 + +// Flags represents the possible flags of a D-Bus message. +type Flags byte + +const ( + // FlagNoReplyExpected signals that the message is not expected to generate + // a reply. If this flag is set on outgoing messages, any possible reply + // will be discarded. + FlagNoReplyExpected Flags = 1 << iota + // FlagNoAutoStart signals that the message bus should not automatically + // start an application when handling this message. + FlagNoAutoStart +) + +// Type represents the possible types of a D-Bus message. +type Type byte + +const ( + TypeMethodCall Type = 1 + iota + TypeMethodReply + TypeError + TypeSignal + typeMax +) + +func (t Type) String() string { + switch t { + case TypeMethodCall: + return "method call" + case TypeMethodReply: + return "reply" + case TypeError: + return "error" + case TypeSignal: + return "signal" + } + return "invalid" +} + +// HeaderField represents the possible byte codes for the headers +// of a D-Bus message. +type HeaderField byte + +const ( + FieldPath HeaderField = 1 + iota + FieldInterface + FieldMember + FieldErrorName + FieldReplySerial + FieldDestination + FieldSender + FieldSignature + FieldUnixFDs + fieldMax +) + +// An InvalidMessageError describes the reason why a D-Bus message is regarded as +// invalid. +type InvalidMessageError string + +func (e InvalidMessageError) Error() string { + return "dbus: invalid message: " + string(e) +} + +// fieldType are the types of the various header fields. +var fieldTypes = [fieldMax]reflect.Type{ + FieldPath: objectPathType, + FieldInterface: stringType, + FieldMember: stringType, + FieldErrorName: stringType, + FieldReplySerial: uint32Type, + FieldDestination: stringType, + FieldSender: stringType, + FieldSignature: signatureType, + FieldUnixFDs: uint32Type, +} + +// requiredFields lists the header fields that are required by the different +// message types. +var requiredFields = [typeMax][]HeaderField{ + TypeMethodCall: {FieldPath, FieldMember}, + TypeMethodReply: {FieldReplySerial}, + TypeError: {FieldErrorName, FieldReplySerial}, + TypeSignal: {FieldPath, FieldInterface, FieldMember}, +} + +// Message represents a single D-Bus message. +type Message struct { + Type + Flags + Headers map[HeaderField]Variant + Body []interface{} + + serial uint32 +} + +type header struct { + Field byte + Variant +} + +// DecodeMessage tries to decode a single message in the D-Bus wire format +// from the given reader. The byte order is figured out from the first byte. +// The possibly returned error can be an error of the underlying reader, an +// InvalidMessageError or a FormatError. +func DecodeMessage(rd io.Reader) (msg *Message, err error) { + var order binary.ByteOrder + var hlength, length uint32 + var typ, flags, proto byte + var headers []header + + b := make([]byte, 1) + _, err = rd.Read(b) + if err != nil { + return + } + switch b[0] { + case 'l': + order = binary.LittleEndian + case 'B': + order = binary.BigEndian + default: + return nil, InvalidMessageError("invalid byte order") + } + + dec := newDecoder(rd, order) + dec.pos = 1 + + msg = new(Message) + vs, err := dec.Decode(Signature{"yyyuu"}) + if err != nil { + return nil, err + } + if err = Store(vs, &typ, &flags, &proto, &length, &msg.serial); err != nil { + return nil, err + } + msg.Type = Type(typ) + msg.Flags = Flags(flags) + + // get the header length separately because we need it later + b = make([]byte, 4) + _, err = io.ReadFull(rd, b) + if err != nil { + return nil, err + } + binary.Read(bytes.NewBuffer(b), order, &hlength) + if hlength+length+16 > 1<<27 { + return nil, InvalidMessageError("message is too long") + } + dec = newDecoder(io.MultiReader(bytes.NewBuffer(b), rd), order) + dec.pos = 12 + vs, err = dec.Decode(Signature{"a(yv)"}) + if err != nil { + return nil, err + } + if err = Store(vs, &headers); err != nil { + return nil, err + } + + msg.Headers = make(map[HeaderField]Variant) + for _, v := range headers { + msg.Headers[HeaderField(v.Field)] = v.Variant + } + + dec.align(8) + body := make([]byte, int(length)) + if length != 0 { + _, err := io.ReadFull(rd, body) + if err != nil { + return nil, err + } + } + + if err = msg.IsValid(); err != nil { + return nil, err + } + sig, _ := msg.Headers[FieldSignature].value.(Signature) + if sig.str != "" { + buf := bytes.NewBuffer(body) + dec = newDecoder(buf, order) + vs, err := dec.Decode(sig) + if err != nil { + return nil, err + } + msg.Body = vs + } + + return +} + +// EncodeTo encodes and sends a message to the given writer. The byte order must +// be either binary.LittleEndian or binary.BigEndian. If the message is not +// valid or an error occurs when writing, an error is returned. +func (msg *Message) EncodeTo(out io.Writer, order binary.ByteOrder) error { + if err := msg.IsValid(); err != nil { + return err + } + var vs [7]interface{} + switch order { + case binary.LittleEndian: + vs[0] = byte('l') + case binary.BigEndian: + vs[0] = byte('B') + default: + return errors.New("dbus: invalid byte order") + } + body := new(bytes.Buffer) + enc := newEncoder(body, order) + if len(msg.Body) != 0 { + enc.Encode(msg.Body...) + } + vs[1] = msg.Type + vs[2] = msg.Flags + vs[3] = protoVersion + vs[4] = uint32(len(body.Bytes())) + vs[5] = msg.serial + headers := make([]header, 0, len(msg.Headers)) + for k, v := range msg.Headers { + headers = append(headers, header{byte(k), v}) + } + vs[6] = headers + var buf bytes.Buffer + enc = newEncoder(&buf, order) + enc.Encode(vs[:]...) + enc.align(8) + body.WriteTo(&buf) + if buf.Len() > 1<<27 { + return InvalidMessageError("message is too long") + } + if _, err := buf.WriteTo(out); err != nil { + return err + } + return nil +} + +// IsValid checks whether msg is a valid message and returns an +// InvalidMessageError if it is not. +func (msg *Message) IsValid() error { + if msg.Flags & ^(FlagNoAutoStart|FlagNoReplyExpected) != 0 { + return InvalidMessageError("invalid flags") + } + if msg.Type == 0 || msg.Type >= typeMax { + return InvalidMessageError("invalid message type") + } + for k, v := range msg.Headers { + if k == 0 || k >= fieldMax { + return InvalidMessageError("invalid header") + } + if reflect.TypeOf(v.value) != fieldTypes[k] { + return InvalidMessageError("invalid type of header field") + } + } + for _, v := range requiredFields[msg.Type] { + if _, ok := msg.Headers[v]; !ok { + return InvalidMessageError("missing required header") + } + } + if path, ok := msg.Headers[FieldPath]; ok { + if !path.value.(ObjectPath).IsValid() { + return InvalidMessageError("invalid path name") + } + } + if iface, ok := msg.Headers[FieldInterface]; ok { + if !isValidInterface(iface.value.(string)) { + return InvalidMessageError("invalid interface name") + } + } + if member, ok := msg.Headers[FieldMember]; ok { + if !isValidMember(member.value.(string)) { + return InvalidMessageError("invalid member name") + } + } + if errname, ok := msg.Headers[FieldErrorName]; ok { + if !isValidInterface(errname.value.(string)) { + return InvalidMessageError("invalid error name") + } + } + if len(msg.Body) != 0 { + if _, ok := msg.Headers[FieldSignature]; !ok { + return InvalidMessageError("missing signature") + } + } + return nil +} + +// Serial returns the message's serial number. The returned value is only valid +// for messages received by eavesdropping. +func (msg *Message) Serial() uint32 { + return msg.serial +} + +// String returns a string representation of a message similar to the format of +// dbus-monitor. +func (msg *Message) String() string { + if err := msg.IsValid(); err != nil { + return "" + } + s := msg.Type.String() + if v, ok := msg.Headers[FieldSender]; ok { + s += " from " + v.value.(string) + } + if v, ok := msg.Headers[FieldDestination]; ok { + s += " to " + v.value.(string) + } + s += " serial " + strconv.FormatUint(uint64(msg.serial), 10) + if v, ok := msg.Headers[FieldReplySerial]; ok { + s += " reply_serial " + strconv.FormatUint(uint64(v.value.(uint32)), 10) + } + if v, ok := msg.Headers[FieldUnixFDs]; ok { + s += " unixfds " + strconv.FormatUint(uint64(v.value.(uint32)), 10) + } + if v, ok := msg.Headers[FieldPath]; ok { + s += " path " + string(v.value.(ObjectPath)) + } + if v, ok := msg.Headers[FieldInterface]; ok { + s += " interface " + v.value.(string) + } + if v, ok := msg.Headers[FieldErrorName]; ok { + s += " error " + v.value.(string) + } + if v, ok := msg.Headers[FieldMember]; ok { + s += " member " + v.value.(string) + } + if len(msg.Body) != 0 { + s += "\n" + } + for i, v := range msg.Body { + s += " " + MakeVariant(v).String() + if i != len(msg.Body)-1 { + s += "\n" + } + } + return s +} diff --git a/vendor/src/github.com/godbus/dbus/prop/prop.go b/vendor/src/github.com/godbus/dbus/prop/prop.go new file mode 100644 index 0000000000..ed5bdf2243 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/prop/prop.go @@ -0,0 +1,264 @@ +// Package prop provides the Properties struct which can be used to implement +// org.freedesktop.DBus.Properties. +package prop + +import ( + "github.com/godbus/dbus" + "github.com/godbus/dbus/introspect" + "sync" +) + +// EmitType controls how org.freedesktop.DBus.Properties.PropertiesChanged is +// emitted for a property. If it is EmitTrue, the signal is emitted. If it is +// EmitInvalidates, the signal is also emitted, but the new value of the property +// is not disclosed. +type EmitType byte + +const ( + EmitFalse EmitType = iota + EmitTrue + EmitInvalidates +) + +// ErrIfaceNotFound is the error returned to peers who try to access properties +// on interfaces that aren't found. +var ErrIfaceNotFound = &dbus.Error{"org.freedesktop.DBus.Properties.Error.InterfaceNotFound", nil} + +// ErrPropNotFound is the error returned to peers trying to access properties +// that aren't found. +var ErrPropNotFound = &dbus.Error{"org.freedesktop.DBus.Properties.Error.PropertyNotFound", nil} + +// ErrReadOnly is the error returned to peers trying to set a read-only +// property. +var ErrReadOnly = &dbus.Error{"org.freedesktop.DBus.Properties.Error.ReadOnly", nil} + +// ErrInvalidArg is returned to peers if the type of the property that is being +// changed and the argument don't match. +var ErrInvalidArg = &dbus.Error{"org.freedesktop.DBus.Properties.Error.InvalidArg", nil} + +// The introspection data for the org.freedesktop.DBus.Properties interface. +var IntrospectData = introspect.Interface{ + Name: "org.freedesktop.DBus.Properties", + Methods: []introspect.Method{ + { + Name: "Get", + Args: []introspect.Arg{ + {"interface", "s", "in"}, + {"property", "s", "in"}, + {"value", "v", "out"}, + }, + }, + { + Name: "GetAll", + Args: []introspect.Arg{ + {"interface", "s", "in"}, + {"props", "a{sv}", "out"}, + }, + }, + { + Name: "Set", + Args: []introspect.Arg{ + {"interface", "s", "in"}, + {"property", "s", "in"}, + {"value", "v", "in"}, + }, + }, + }, + Signals: []introspect.Signal{ + { + Name: "PropertiesChanged", + Args: []introspect.Arg{ + {"interface", "s", "out"}, + {"changed_properties", "a{sv}", "out"}, + {"invalidates_properties", "as", "out"}, + }, + }, + }, +} + +// The introspection data for the org.freedesktop.DBus.Properties interface, as +// a string. +const IntrospectDataString = ` + + + + + + + + + + + + + + + + + + + + + +` + +// Prop represents a single property. It is used for creating a Properties +// value. +type Prop struct { + // Initial value. Must be a DBus-representable type. + Value interface{} + + // If true, the value can be modified by calls to Set. + Writable bool + + // Controls how org.freedesktop.DBus.Properties.PropertiesChanged is + // emitted if this property changes. + Emit EmitType + + // If not nil, anytime this property is changed by Set, this function is + // called with an appropiate Change as its argument. If the returned error + // is not nil, it is sent back to the caller of Set and the property is not + // changed. + Callback func(*Change) *dbus.Error +} + +// Change represents a change of a property by a call to Set. +type Change struct { + Props *Properties + Iface string + Name string + Value interface{} +} + +// Properties is a set of values that can be made available to the message bus +// using the org.freedesktop.DBus.Properties interface. It is safe for +// concurrent use by multiple goroutines. +type Properties struct { + m map[string]map[string]*Prop + mut sync.RWMutex + conn *dbus.Conn + path dbus.ObjectPath +} + +// New returns a new Properties structure that manages the given properties. +// The key for the first-level map of props is the name of the interface; the +// second-level key is the name of the property. The returned structure will be +// exported as org.freedesktop.DBus.Properties on path. +func New(conn *dbus.Conn, path dbus.ObjectPath, props map[string]map[string]*Prop) *Properties { + p := &Properties{m: props, conn: conn, path: path} + conn.Export(p, path, "org.freedesktop.DBus.Properties") + return p +} + +// Get implements org.freedesktop.DBus.Properties.Get. +func (p *Properties) Get(iface, property string) (dbus.Variant, *dbus.Error) { + p.mut.RLock() + defer p.mut.RUnlock() + m, ok := p.m[iface] + if !ok { + return dbus.Variant{}, ErrIfaceNotFound + } + prop, ok := m[property] + if !ok { + return dbus.Variant{}, ErrPropNotFound + } + return dbus.MakeVariant(prop.Value), nil +} + +// GetAll implements org.freedesktop.DBus.Properties.GetAll. +func (p *Properties) GetAll(iface string) (map[string]dbus.Variant, *dbus.Error) { + p.mut.RLock() + defer p.mut.RUnlock() + m, ok := p.m[iface] + if !ok { + return nil, ErrIfaceNotFound + } + rm := make(map[string]dbus.Variant, len(m)) + for k, v := range m { + rm[k] = dbus.MakeVariant(v.Value) + } + return rm, nil +} + +// GetMust returns the value of the given property and panics if either the +// interface or the property name are invalid. +func (p *Properties) GetMust(iface, property string) interface{} { + p.mut.RLock() + defer p.mut.RUnlock() + return p.m[iface][property].Value +} + +// Introspection returns the introspection data that represents the properties +// of iface. +func (p *Properties) Introspection(iface string) []introspect.Property { + p.mut.RLock() + defer p.mut.RUnlock() + m := p.m[iface] + s := make([]introspect.Property, 0, len(m)) + for k, v := range m { + p := introspect.Property{Name: k, Type: dbus.SignatureOf(v.Value).String()} + if v.Writable { + p.Access = "readwrite" + } else { + p.Access = "read" + } + s = append(s, p) + } + return s +} + +// set sets the given property and emits PropertyChanged if appropiate. p.mut +// must already be locked. +func (p *Properties) set(iface, property string, v interface{}) { + prop := p.m[iface][property] + prop.Value = v + switch prop.Emit { + case EmitFalse: + // do nothing + case EmitInvalidates: + p.conn.Emit(p.path, "org.freedesktop.DBus.Properties.PropertiesChanged", + iface, map[string]dbus.Variant{}, []string{property}) + case EmitTrue: + p.conn.Emit(p.path, "org.freedesktop.DBus.Properties.PropertiesChanged", + iface, map[string]dbus.Variant{property: dbus.MakeVariant(v)}, + []string{}) + default: + panic("invalid value for EmitType") + } +} + +// Set implements org.freedesktop.Properties.Set. +func (p *Properties) Set(iface, property string, newv dbus.Variant) *dbus.Error { + p.mut.Lock() + defer p.mut.Unlock() + m, ok := p.m[iface] + if !ok { + return ErrIfaceNotFound + } + prop, ok := m[property] + if !ok { + return ErrPropNotFound + } + if !prop.Writable { + return ErrReadOnly + } + if newv.Signature() != dbus.SignatureOf(prop.Value) { + return ErrInvalidArg + } + if prop.Callback != nil { + err := prop.Callback(&Change{p, iface, property, newv.Value()}) + if err != nil { + return err + } + } + p.set(iface, property, newv.Value()) + return nil +} + +// SetMust sets the value of the given property and panics if the interface or +// the property name are invalid. +func (p *Properties) SetMust(iface, property string, v interface{}) { + p.mut.Lock() + p.set(iface, property, v) + p.mut.Unlock() +} diff --git a/vendor/src/github.com/godbus/dbus/proto_test.go b/vendor/src/github.com/godbus/dbus/proto_test.go new file mode 100644 index 0000000000..608a770d41 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/proto_test.go @@ -0,0 +1,369 @@ +package dbus + +import ( + "bytes" + "encoding/binary" + "io/ioutil" + "math" + "reflect" + "testing" +) + +var protoTests = []struct { + vs []interface{} + bigEndian []byte + littleEndian []byte +}{ + { + []interface{}{int32(0)}, + []byte{0, 0, 0, 0}, + []byte{0, 0, 0, 0}, + }, + { + []interface{}{true, false}, + []byte{0, 0, 0, 1, 0, 0, 0, 0}, + []byte{1, 0, 0, 0, 0, 0, 0, 0}, + }, + { + []interface{}{byte(0), uint16(12), int16(32), uint32(43)}, + []byte{0, 0, 0, 12, 0, 32, 0, 0, 0, 0, 0, 43}, + []byte{0, 0, 12, 0, 32, 0, 0, 0, 43, 0, 0, 0}, + }, + { + []interface{}{int64(-1), uint64(1<<64 - 1)}, + bytes.Repeat([]byte{255}, 16), + bytes.Repeat([]byte{255}, 16), + }, + { + []interface{}{math.Inf(+1)}, + []byte{0x7f, 0xf0, 0, 0, 0, 0, 0, 0}, + []byte{0, 0, 0, 0, 0, 0, 0xf0, 0x7f}, + }, + { + []interface{}{"foo"}, + []byte{0, 0, 0, 3, 'f', 'o', 'o', 0}, + []byte{3, 0, 0, 0, 'f', 'o', 'o', 0}, + }, + { + []interface{}{Signature{"ai"}}, + []byte{2, 'a', 'i', 0}, + []byte{2, 'a', 'i', 0}, + }, + { + []interface{}{[]int16{42, 256}}, + []byte{0, 0, 0, 4, 0, 42, 1, 0}, + []byte{4, 0, 0, 0, 42, 0, 0, 1}, + }, + { + []interface{}{MakeVariant("foo")}, + []byte{1, 's', 0, 0, 0, 0, 0, 3, 'f', 'o', 'o', 0}, + []byte{1, 's', 0, 0, 3, 0, 0, 0, 'f', 'o', 'o', 0}, + }, + { + []interface{}{MakeVariant(MakeVariant(Signature{"v"}))}, + []byte{1, 'v', 0, 1, 'g', 0, 1, 'v', 0}, + []byte{1, 'v', 0, 1, 'g', 0, 1, 'v', 0}, + }, + { + []interface{}{map[int32]bool{42: true}}, + []byte{0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 1}, + []byte{8, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 1, 0, 0, 0}, + }, + { + []interface{}{map[string]Variant{}, byte(42)}, + []byte{0, 0, 0, 0, 0, 0, 0, 0, 42}, + []byte{0, 0, 0, 0, 0, 0, 0, 0, 42}, + }, + { + []interface{}{[]uint64{}, byte(42)}, + []byte{0, 0, 0, 0, 0, 0, 0, 0, 42}, + []byte{0, 0, 0, 0, 0, 0, 0, 0, 42}, + }, +} + +func TestProto(t *testing.T) { + for i, v := range protoTests { + buf := new(bytes.Buffer) + bigEnc := newEncoder(buf, binary.BigEndian) + bigEnc.Encode(v.vs...) + marshalled := buf.Bytes() + if bytes.Compare(marshalled, v.bigEndian) != 0 { + t.Errorf("test %d (marshal be): got '%v', but expected '%v'\n", i+1, marshalled, + v.bigEndian) + } + buf.Reset() + litEnc := newEncoder(buf, binary.LittleEndian) + litEnc.Encode(v.vs...) + marshalled = buf.Bytes() + if bytes.Compare(marshalled, v.littleEndian) != 0 { + t.Errorf("test %d (marshal le): got '%v', but expected '%v'\n", i+1, marshalled, + v.littleEndian) + } + unmarshalled := reflect.MakeSlice(reflect.TypeOf(v.vs), + 0, 0) + for i := range v.vs { + unmarshalled = reflect.Append(unmarshalled, + reflect.New(reflect.TypeOf(v.vs[i]))) + } + bigDec := newDecoder(bytes.NewReader(v.bigEndian), binary.BigEndian) + vs, err := bigDec.Decode(SignatureOf(v.vs...)) + if err != nil { + t.Errorf("test %d (unmarshal be): %s\n", i+1, err) + continue + } + if !reflect.DeepEqual(vs, v.vs) { + t.Errorf("test %d (unmarshal be): got %#v, but expected %#v\n", i+1, vs, v.vs) + } + litDec := newDecoder(bytes.NewReader(v.littleEndian), binary.LittleEndian) + vs, err = litDec.Decode(SignatureOf(v.vs...)) + if err != nil { + t.Errorf("test %d (unmarshal le): %s\n", i+1, err) + continue + } + if !reflect.DeepEqual(vs, v.vs) { + t.Errorf("test %d (unmarshal le): got %#v, but expected %#v\n", i+1, vs, v.vs) + } + + } +} + +func TestProtoMap(t *testing.T) { + m := map[string]uint8{ + "foo": 23, + "bar": 2, + } + var n map[string]uint8 + buf := new(bytes.Buffer) + enc := newEncoder(buf, binary.LittleEndian) + enc.Encode(m) + dec := newDecoder(buf, binary.LittleEndian) + vs, err := dec.Decode(Signature{"a{sy}"}) + if err != nil { + t.Fatal(err) + } + if err = Store(vs, &n); err != nil { + t.Fatal(err) + } + if len(n) != 2 || n["foo"] != 23 || n["bar"] != 2 { + t.Error("got", n) + } +} + +func TestProtoVariantStruct(t *testing.T) { + var variant Variant + v := MakeVariant(struct { + A int32 + B int16 + }{1, 2}) + buf := new(bytes.Buffer) + enc := newEncoder(buf, binary.LittleEndian) + enc.Encode(v) + dec := newDecoder(buf, binary.LittleEndian) + vs, err := dec.Decode(Signature{"v"}) + if err != nil { + t.Fatal(err) + } + if err = Store(vs, &variant); err != nil { + t.Fatal(err) + } + sl := variant.Value().([]interface{}) + v1, v2 := sl[0].(int32), sl[1].(int16) + if v1 != int32(1) { + t.Error("got", v1, "as first int") + } + if v2 != int16(2) { + t.Error("got", v2, "as second int") + } +} + +func TestProtoStructTag(t *testing.T) { + type Bar struct { + A int32 + B chan interface{} `dbus:"-"` + C int32 + } + var bar1, bar2 Bar + bar1.A = 234 + bar2.C = 345 + buf := new(bytes.Buffer) + enc := newEncoder(buf, binary.LittleEndian) + enc.Encode(bar1) + dec := newDecoder(buf, binary.LittleEndian) + vs, err := dec.Decode(Signature{"(ii)"}) + if err != nil { + t.Fatal(err) + } + if err = Store(vs, &bar2); err != nil { + t.Fatal(err) + } + if bar1 != bar2 { + t.Error("struct tag test: got", bar2) + } +} + +func TestProtoStoreStruct(t *testing.T) { + var foo struct { + A int32 + B string + c chan interface{} + D interface{} `dbus:"-"` + } + src := []interface{}{[]interface{}{int32(42), "foo"}} + err := Store(src, &foo) + if err != nil { + t.Fatal(err) + } +} + +func TestProtoStoreNestedStruct(t *testing.T) { + var foo struct { + A int32 + B struct { + C string + D float64 + } + } + src := []interface{}{ + []interface{}{ + int32(42), + []interface{}{ + "foo", + 3.14, + }, + }, + } + err := Store(src, &foo) + if err != nil { + t.Fatal(err) + } +} + +func TestMessage(t *testing.T) { + buf := new(bytes.Buffer) + message := new(Message) + message.Type = TypeMethodCall + message.serial = 32 + message.Headers = map[HeaderField]Variant{ + FieldPath: MakeVariant(ObjectPath("/org/foo/bar")), + FieldMember: MakeVariant("baz"), + } + message.Body = make([]interface{}, 0) + err := message.EncodeTo(buf, binary.LittleEndian) + if err != nil { + t.Error(err) + } + _, err = DecodeMessage(buf) + if err != nil { + t.Error(err) + } +} + +func TestProtoStructInterfaces(t *testing.T) { + b := []byte{42} + vs, err := newDecoder(bytes.NewReader(b), binary.LittleEndian).Decode(Signature{"(y)"}) + if err != nil { + t.Fatal(err) + } + if vs[0].([]interface{})[0].(byte) != 42 { + t.Errorf("wrongs results (got %v)", vs) + } +} + +// ordinary org.freedesktop.DBus.Hello call +var smallMessage = &Message{ + Type: TypeMethodCall, + serial: 1, + Headers: map[HeaderField]Variant{ + FieldDestination: MakeVariant("org.freedesktop.DBus"), + FieldPath: MakeVariant(ObjectPath("/org/freedesktop/DBus")), + FieldInterface: MakeVariant("org.freedesktop.DBus"), + FieldMember: MakeVariant("Hello"), + }, +} + +// org.freedesktop.Notifications.Notify +var bigMessage = &Message{ + Type: TypeMethodCall, + serial: 2, + Headers: map[HeaderField]Variant{ + FieldDestination: MakeVariant("org.freedesktop.Notifications"), + FieldPath: MakeVariant(ObjectPath("/org/freedesktop/Notifications")), + FieldInterface: MakeVariant("org.freedesktop.Notifications"), + FieldMember: MakeVariant("Notify"), + FieldSignature: MakeVariant(Signature{"susssasa{sv}i"}), + }, + Body: []interface{}{ + "app_name", + uint32(0), + "dialog-information", + "Notification", + "This is the body of a notification", + []string{"ok", "Ok"}, + map[string]Variant{ + "sound-name": MakeVariant("dialog-information"), + }, + int32(-1), + }, +} + +func BenchmarkDecodeMessageSmall(b *testing.B) { + var err error + var rd *bytes.Reader + + b.StopTimer() + buf := new(bytes.Buffer) + err = smallMessage.EncodeTo(buf, binary.LittleEndian) + if err != nil { + b.Fatal(err) + } + decoded := buf.Bytes() + b.StartTimer() + for i := 0; i < b.N; i++ { + rd = bytes.NewReader(decoded) + _, err = DecodeMessage(rd) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkDecodeMessageBig(b *testing.B) { + var err error + var rd *bytes.Reader + + b.StopTimer() + buf := new(bytes.Buffer) + err = bigMessage.EncodeTo(buf, binary.LittleEndian) + if err != nil { + b.Fatal(err) + } + decoded := buf.Bytes() + b.StartTimer() + for i := 0; i < b.N; i++ { + rd = bytes.NewReader(decoded) + _, err = DecodeMessage(rd) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkEncodeMessageSmall(b *testing.B) { + var err error + for i := 0; i < b.N; i++ { + err = smallMessage.EncodeTo(ioutil.Discard, binary.LittleEndian) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkEncodeMessageBig(b *testing.B) { + var err error + for i := 0; i < b.N; i++ { + err = bigMessage.EncodeTo(ioutil.Discard, binary.LittleEndian) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/vendor/src/github.com/godbus/dbus/sig.go b/vendor/src/github.com/godbus/dbus/sig.go new file mode 100644 index 0000000000..f45b53ce1b --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/sig.go @@ -0,0 +1,257 @@ +package dbus + +import ( + "fmt" + "reflect" + "strings" +) + +var sigToType = map[byte]reflect.Type{ + 'y': byteType, + 'b': boolType, + 'n': int16Type, + 'q': uint16Type, + 'i': int32Type, + 'u': uint32Type, + 'x': int64Type, + 't': uint64Type, + 'd': float64Type, + 's': stringType, + 'g': signatureType, + 'o': objectPathType, + 'v': variantType, + 'h': unixFDIndexType, +} + +// Signature represents a correct type signature as specified by the D-Bus +// specification. The zero value represents the empty signature, "". +type Signature struct { + str string +} + +// SignatureOf returns the concatenation of all the signatures of the given +// values. It panics if one of them is not representable in D-Bus. +func SignatureOf(vs ...interface{}) Signature { + var s string + for _, v := range vs { + s += getSignature(reflect.TypeOf(v)) + } + return Signature{s} +} + +// SignatureOfType returns the signature of the given type. It panics if the +// type is not representable in D-Bus. +func SignatureOfType(t reflect.Type) Signature { + return Signature{getSignature(t)} +} + +// getSignature returns the signature of the given type and panics on unknown types. +func getSignature(t reflect.Type) string { + // handle simple types first + switch t.Kind() { + case reflect.Uint8: + return "y" + case reflect.Bool: + return "b" + case reflect.Int16: + return "n" + case reflect.Uint16: + return "q" + case reflect.Int32: + if t == unixFDType { + return "h" + } + return "i" + case reflect.Uint32: + if t == unixFDIndexType { + return "h" + } + return "u" + case reflect.Int64: + return "x" + case reflect.Uint64: + return "t" + case reflect.Float64: + return "d" + case reflect.Ptr: + return getSignature(t.Elem()) + case reflect.String: + if t == objectPathType { + return "o" + } + return "s" + case reflect.Struct: + if t == variantType { + return "v" + } else if t == signatureType { + return "g" + } + var s string + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + if field.PkgPath == "" && field.Tag.Get("dbus") != "-" { + s += getSignature(t.Field(i).Type) + } + } + return "(" + s + ")" + case reflect.Array, reflect.Slice: + return "a" + getSignature(t.Elem()) + case reflect.Map: + if !isKeyType(t.Key()) { + panic(InvalidTypeError{t}) + } + return "a{" + getSignature(t.Key()) + getSignature(t.Elem()) + "}" + } + panic(InvalidTypeError{t}) +} + +// ParseSignature returns the signature represented by this string, or a +// SignatureError if the string is not a valid signature. +func ParseSignature(s string) (sig Signature, err error) { + if len(s) == 0 { + return + } + if len(s) > 255 { + return Signature{""}, SignatureError{s, "too long"} + } + sig.str = s + for err == nil && len(s) != 0 { + err, s = validSingle(s, 0) + } + if err != nil { + sig = Signature{""} + } + + return +} + +// ParseSignatureMust behaves like ParseSignature, except that it panics if s +// is not valid. +func ParseSignatureMust(s string) Signature { + sig, err := ParseSignature(s) + if err != nil { + panic(err) + } + return sig +} + +// Empty retruns whether the signature is the empty signature. +func (s Signature) Empty() bool { + return s.str == "" +} + +// Single returns whether the signature represents a single, complete type. +func (s Signature) Single() bool { + err, r := validSingle(s.str, 0) + return err != nil && r == "" +} + +// String returns the signature's string representation. +func (s Signature) String() string { + return s.str +} + +// A SignatureError indicates that a signature passed to a function or received +// on a connection is not a valid signature. +type SignatureError struct { + Sig string + Reason string +} + +func (e SignatureError) Error() string { + return fmt.Sprintf("dbus: invalid signature: %q (%s)", e.Sig, e.Reason) +} + +// Try to read a single type from this string. If it was successfull, err is nil +// and rem is the remaining unparsed part. Otherwise, err is a non-nil +// SignatureError and rem is "". depth is the current recursion depth which may +// not be greater than 64 and should be given as 0 on the first call. +func validSingle(s string, depth int) (err error, rem string) { + if s == "" { + return SignatureError{Sig: s, Reason: "empty signature"}, "" + } + if depth > 64 { + return SignatureError{Sig: s, Reason: "container nesting too deep"}, "" + } + switch s[0] { + case 'y', 'b', 'n', 'q', 'i', 'u', 'x', 't', 'd', 's', 'g', 'o', 'v', 'h': + return nil, s[1:] + case 'a': + if len(s) > 1 && s[1] == '{' { + i := findMatching(s[1:], '{', '}') + if i == -1 { + return SignatureError{Sig: s, Reason: "unmatched '{'"}, "" + } + i++ + rem = s[i+1:] + s = s[2:i] + if err, _ = validSingle(s[:1], depth+1); err != nil { + return err, "" + } + err, nr := validSingle(s[1:], depth+1) + if err != nil { + return err, "" + } + if nr != "" { + return SignatureError{Sig: s, Reason: "too many types in dict"}, "" + } + return nil, rem + } + return validSingle(s[1:], depth+1) + case '(': + i := findMatching(s, '(', ')') + if i == -1 { + return SignatureError{Sig: s, Reason: "unmatched ')'"}, "" + } + rem = s[i+1:] + s = s[1:i] + for err == nil && s != "" { + err, s = validSingle(s, depth+1) + } + if err != nil { + rem = "" + } + return + } + return SignatureError{Sig: s, Reason: "invalid type character"}, "" +} + +func findMatching(s string, left, right rune) int { + n := 0 + for i, v := range s { + if v == left { + n++ + } else if v == right { + n-- + } + if n == 0 { + return i + } + } + return -1 +} + +// typeFor returns the type of the given signature. It ignores any left over +// characters and panics if s doesn't start with a valid type signature. +func typeFor(s string) (t reflect.Type) { + err, _ := validSingle(s, 0) + if err != nil { + panic(err) + } + + if t, ok := sigToType[s[0]]; ok { + return t + } + switch s[0] { + case 'a': + if s[1] == '{' { + i := strings.LastIndex(s, "}") + t = reflect.MapOf(sigToType[s[2]], typeFor(s[3:i])) + } else { + t = reflect.SliceOf(typeFor(s[1:])) + } + case '(': + t = interfacesType + } + return +} diff --git a/vendor/src/github.com/godbus/dbus/sig_test.go b/vendor/src/github.com/godbus/dbus/sig_test.go new file mode 100644 index 0000000000..da37bc968e --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/sig_test.go @@ -0,0 +1,70 @@ +package dbus + +import ( + "testing" +) + +var sigTests = []struct { + vs []interface{} + sig Signature +}{ + { + []interface{}{new(int32)}, + Signature{"i"}, + }, + { + []interface{}{new(string)}, + Signature{"s"}, + }, + { + []interface{}{new(Signature)}, + Signature{"g"}, + }, + { + []interface{}{new([]int16)}, + Signature{"an"}, + }, + { + []interface{}{new(int16), new(uint32)}, + Signature{"nu"}, + }, + { + []interface{}{new(map[byte]Variant)}, + Signature{"a{yv}"}, + }, + { + []interface{}{new(Variant), new([]map[int32]string)}, + Signature{"vaa{is}"}, + }, +} + +func TestSig(t *testing.T) { + for i, v := range sigTests { + sig := SignatureOf(v.vs...) + if sig != v.sig { + t.Errorf("test %d: got %q, expected %q", i+1, sig.str, v.sig.str) + } + } +} + +var getSigTest = []interface{}{ + []struct { + b byte + i int32 + t uint64 + s string + }{}, + map[string]Variant{}, +} + +func BenchmarkGetSignatureSimple(b *testing.B) { + for i := 0; i < b.N; i++ { + SignatureOf("", int32(0)) + } +} + +func BenchmarkGetSignatureLong(b *testing.B) { + for i := 0; i < b.N; i++ { + SignatureOf(getSigTest...) + } +} diff --git a/vendor/src/github.com/godbus/dbus/transport_darwin.go b/vendor/src/github.com/godbus/dbus/transport_darwin.go new file mode 100644 index 0000000000..1bba0d6bf7 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/transport_darwin.go @@ -0,0 +1,6 @@ +package dbus + +func (t *unixTransport) SendNullByte() error { + _, err := t.Write([]byte{0}) + return err +} diff --git a/vendor/src/github.com/godbus/dbus/transport_generic.go b/vendor/src/github.com/godbus/dbus/transport_generic.go new file mode 100644 index 0000000000..46f8f49d69 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/transport_generic.go @@ -0,0 +1,35 @@ +package dbus + +import ( + "encoding/binary" + "errors" + "io" +) + +type genericTransport struct { + io.ReadWriteCloser +} + +func (t genericTransport) SendNullByte() error { + _, err := t.Write([]byte{0}) + return err +} + +func (t genericTransport) SupportsUnixFDs() bool { + return false +} + +func (t genericTransport) EnableUnixFDs() {} + +func (t genericTransport) ReadMessage() (*Message, error) { + return DecodeMessage(t) +} + +func (t genericTransport) SendMessage(msg *Message) error { + for _, v := range msg.Body { + if _, ok := v.(UnixFD); ok { + return errors.New("dbus: unix fd passing not enabled") + } + } + return msg.EncodeTo(t, binary.LittleEndian) +} diff --git a/vendor/src/github.com/godbus/dbus/transport_unix.go b/vendor/src/github.com/godbus/dbus/transport_unix.go new file mode 100644 index 0000000000..d16229be40 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/transport_unix.go @@ -0,0 +1,190 @@ +package dbus + +import ( + "bytes" + "encoding/binary" + "errors" + "io" + "net" + "syscall" +) + +type oobReader struct { + conn *net.UnixConn + oob []byte + buf [4096]byte +} + +func (o *oobReader) Read(b []byte) (n int, err error) { + n, oobn, flags, _, err := o.conn.ReadMsgUnix(b, o.buf[:]) + if err != nil { + return n, err + } + if flags&syscall.MSG_CTRUNC != 0 { + return n, errors.New("dbus: control data truncated (too many fds received)") + } + o.oob = append(o.oob, o.buf[:oobn]...) + return n, nil +} + +type unixTransport struct { + *net.UnixConn + hasUnixFDs bool +} + +func newUnixTransport(keys string) (transport, error) { + var err error + + t := new(unixTransport) + abstract := getKey(keys, "abstract") + path := getKey(keys, "path") + switch { + case abstract == "" && path == "": + return nil, errors.New("dbus: invalid address (neither path nor abstract set)") + case abstract != "" && path == "": + t.UnixConn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: "@" + abstract, Net: "unix"}) + if err != nil { + return nil, err + } + return t, nil + case abstract == "" && path != "": + t.UnixConn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: path, Net: "unix"}) + if err != nil { + return nil, err + } + return t, nil + default: + return nil, errors.New("dbus: invalid address (both path and abstract set)") + } +} + +func (t *unixTransport) EnableUnixFDs() { + t.hasUnixFDs = true +} + +func (t *unixTransport) ReadMessage() (*Message, error) { + var ( + blen, hlen uint32 + csheader [16]byte + headers []header + order binary.ByteOrder + unixfds uint32 + ) + // To be sure that all bytes of out-of-band data are read, we use a special + // reader that uses ReadUnix on the underlying connection instead of Read + // and gathers the out-of-band data in a buffer. + rd := &oobReader{conn: t.UnixConn} + // read the first 16 bytes (the part of the header that has a constant size), + // from which we can figure out the length of the rest of the message + if _, err := io.ReadFull(rd, csheader[:]); err != nil { + return nil, err + } + switch csheader[0] { + case 'l': + order = binary.LittleEndian + case 'B': + order = binary.BigEndian + default: + return nil, InvalidMessageError("invalid byte order") + } + // csheader[4:8] -> length of message body, csheader[12:16] -> length of + // header fields (without alignment) + binary.Read(bytes.NewBuffer(csheader[4:8]), order, &blen) + binary.Read(bytes.NewBuffer(csheader[12:]), order, &hlen) + if hlen%8 != 0 { + hlen += 8 - (hlen % 8) + } + + // decode headers and look for unix fds + headerdata := make([]byte, hlen+4) + copy(headerdata, csheader[12:]) + if _, err := io.ReadFull(t, headerdata[4:]); err != nil { + return nil, err + } + dec := newDecoder(bytes.NewBuffer(headerdata), order) + dec.pos = 12 + vs, err := dec.Decode(Signature{"a(yv)"}) + if err != nil { + return nil, err + } + Store(vs, &headers) + for _, v := range headers { + if v.Field == byte(FieldUnixFDs) { + unixfds, _ = v.Variant.value.(uint32) + } + } + all := make([]byte, 16+hlen+blen) + copy(all, csheader[:]) + copy(all[16:], headerdata[4:]) + if _, err := io.ReadFull(rd, all[16+hlen:]); err != nil { + return nil, err + } + if unixfds != 0 { + if !t.hasUnixFDs { + return nil, errors.New("dbus: got unix fds on unsupported transport") + } + // read the fds from the OOB data + scms, err := syscall.ParseSocketControlMessage(rd.oob) + if err != nil { + return nil, err + } + if len(scms) != 1 { + return nil, errors.New("dbus: received more than one socket control message") + } + fds, err := syscall.ParseUnixRights(&scms[0]) + if err != nil { + return nil, err + } + msg, err := DecodeMessage(bytes.NewBuffer(all)) + if err != nil { + return nil, err + } + // substitute the values in the message body (which are indices for the + // array receiver via OOB) with the actual values + for i, v := range msg.Body { + if j, ok := v.(UnixFDIndex); ok { + if uint32(j) >= unixfds { + return nil, InvalidMessageError("invalid index for unix fd") + } + msg.Body[i] = UnixFD(fds[j]) + } + } + return msg, nil + } + return DecodeMessage(bytes.NewBuffer(all)) +} + +func (t *unixTransport) SendMessage(msg *Message) error { + fds := make([]int, 0) + for i, v := range msg.Body { + if fd, ok := v.(UnixFD); ok { + msg.Body[i] = UnixFDIndex(len(fds)) + fds = append(fds, int(fd)) + } + } + if len(fds) != 0 { + if !t.hasUnixFDs { + return errors.New("dbus: unix fd passing not enabled") + } + msg.Headers[FieldUnixFDs] = MakeVariant(uint32(len(fds))) + oob := syscall.UnixRights(fds...) + buf := new(bytes.Buffer) + msg.EncodeTo(buf, binary.LittleEndian) + n, oobn, err := t.UnixConn.WriteMsgUnix(buf.Bytes(), oob, nil) + if err != nil { + return err + } + if n != buf.Len() || oobn != len(oob) { + return io.ErrShortWrite + } + } else { + if err := msg.EncodeTo(t, binary.LittleEndian); err != nil { + return nil + } + } + return nil +} + +func (t *unixTransport) SupportsUnixFDs() bool { + return true +} diff --git a/vendor/src/github.com/godbus/dbus/transport_unix_test.go b/vendor/src/github.com/godbus/dbus/transport_unix_test.go new file mode 100644 index 0000000000..302233fc65 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/transport_unix_test.go @@ -0,0 +1,49 @@ +package dbus + +import ( + "os" + "testing" +) + +const testString = `This is a test! +This text should be read from the file that is created by this test.` + +type unixFDTest struct{} + +func (t unixFDTest) Test(fd UnixFD) (string, *Error) { + var b [4096]byte + file := os.NewFile(uintptr(fd), "testfile") + defer file.Close() + n, err := file.Read(b[:]) + if err != nil { + return "", &Error{"com.github.guelfey.test.Error", nil} + } + return string(b[:n]), nil +} + +func TestUnixFDs(t *testing.T) { + conn, err := SessionBus() + if err != nil { + t.Fatal(err) + } + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + defer w.Close() + if _, err := w.Write([]byte(testString)); err != nil { + t.Fatal(err) + } + name := conn.Names()[0] + test := unixFDTest{} + conn.Export(test, "/com/github/guelfey/test", "com.github.guelfey.test") + var s string + obj := conn.Object(name, "/com/github/guelfey/test") + err = obj.Call("com.github.guelfey.test.Test", 0, UnixFD(r.Fd())).Store(&s) + if err != nil { + t.Fatal(err) + } + if s != testString { + t.Fatal("got", s, "wanted", testString) + } +} diff --git a/vendor/src/github.com/godbus/dbus/transport_unixcred.go b/vendor/src/github.com/godbus/dbus/transport_unixcred.go new file mode 100644 index 0000000000..42a0e769ef --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/transport_unixcred.go @@ -0,0 +1,22 @@ +// +build !darwin + +package dbus + +import ( + "io" + "os" + "syscall" +) + +func (t *unixTransport) SendNullByte() error { + ucred := &syscall.Ucred{Pid: int32(os.Getpid()), Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid())} + b := syscall.UnixCredentials(ucred) + _, oobn, err := t.UnixConn.WriteMsgUnix([]byte{0}, b, nil) + if err != nil { + return err + } + if oobn != len(b) { + return io.ErrShortWrite + } + return nil +} diff --git a/vendor/src/github.com/godbus/dbus/variant.go b/vendor/src/github.com/godbus/dbus/variant.go new file mode 100644 index 0000000000..b1b53ceb47 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/variant.go @@ -0,0 +1,129 @@ +package dbus + +import ( + "bytes" + "fmt" + "reflect" + "strconv" +) + +// Variant represents the D-Bus variant type. +type Variant struct { + sig Signature + value interface{} +} + +// MakeVariant converts the given value to a Variant. It panics if v cannot be +// represented as a D-Bus type. +func MakeVariant(v interface{}) Variant { + return Variant{SignatureOf(v), v} +} + +// ParseVariant parses the given string as a variant as described at +// https://developer.gnome.org/glib/unstable/gvariant-text.html. If sig is not +// empty, it is taken to be the expected signature for the variant. +func ParseVariant(s string, sig Signature) (Variant, error) { + tokens := varLex(s) + p := &varParser{tokens: tokens} + n, err := varMakeNode(p) + if err != nil { + return Variant{}, err + } + if sig.str == "" { + sig, err = varInfer(n) + if err != nil { + return Variant{}, err + } + } + v, err := n.Value(sig) + if err != nil { + return Variant{}, err + } + return MakeVariant(v), nil +} + +// format returns a formatted version of v and whether this string can be parsed +// unambigously. +func (v Variant) format() (string, bool) { + switch v.sig.str[0] { + case 'b', 'i': + return fmt.Sprint(v.value), true + case 'n', 'q', 'u', 'x', 't', 'd', 'h': + return fmt.Sprint(v.value), false + case 's': + return strconv.Quote(v.value.(string)), true + case 'o': + return strconv.Quote(string(v.value.(ObjectPath))), false + case 'g': + return strconv.Quote(v.value.(Signature).str), false + case 'v': + s, unamb := v.value.(Variant).format() + if !unamb { + return "<@" + v.value.(Variant).sig.str + " " + s + ">", true + } + return "<" + s + ">", true + case 'y': + return fmt.Sprintf("%#x", v.value.(byte)), false + } + rv := reflect.ValueOf(v.value) + switch rv.Kind() { + case reflect.Slice: + if rv.Len() == 0 { + return "[]", false + } + unamb := true + buf := bytes.NewBuffer([]byte("[")) + for i := 0; i < rv.Len(); i++ { + // TODO: slooow + s, b := MakeVariant(rv.Index(i).Interface()).format() + unamb = unamb && b + buf.WriteString(s) + if i != rv.Len()-1 { + buf.WriteString(", ") + } + } + buf.WriteByte(']') + return buf.String(), unamb + case reflect.Map: + if rv.Len() == 0 { + return "{}", false + } + unamb := true + buf := bytes.NewBuffer([]byte("{")) + for i, k := range rv.MapKeys() { + s, b := MakeVariant(k.Interface()).format() + unamb = unamb && b + buf.WriteString(s) + buf.WriteString(": ") + s, b = MakeVariant(rv.MapIndex(k).Interface()).format() + unamb = unamb && b + buf.WriteString(s) + if i != rv.Len()-1 { + buf.WriteString(", ") + } + } + buf.WriteByte('}') + return buf.String(), unamb + } + return `"INVALID"`, true +} + +// Signature returns the D-Bus signature of the underlying value of v. +func (v Variant) Signature() Signature { + return v.sig +} + +// String returns the string representation of the underlying value of v as +// described at https://developer.gnome.org/glib/unstable/gvariant-text.html. +func (v Variant) String() string { + s, unamb := v.format() + if !unamb { + return "@" + v.sig.str + " " + s + } + return s +} + +// Value returns the underlying value of v. +func (v Variant) Value() interface{} { + return v.value +} diff --git a/vendor/src/github.com/godbus/dbus/variant_lexer.go b/vendor/src/github.com/godbus/dbus/variant_lexer.go new file mode 100644 index 0000000000..332007d6f1 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/variant_lexer.go @@ -0,0 +1,284 @@ +package dbus + +import ( + "fmt" + "strings" + "unicode" + "unicode/utf8" +) + +// Heavily inspired by the lexer from text/template. + +type varToken struct { + typ varTokenType + val string +} + +type varTokenType byte + +const ( + tokEOF varTokenType = iota + tokError + tokNumber + tokString + tokBool + tokArrayStart + tokArrayEnd + tokDictStart + tokDictEnd + tokVariantStart + tokVariantEnd + tokComma + tokColon + tokType + tokByteString +) + +type varLexer struct { + input string + start int + pos int + width int + tokens []varToken +} + +type lexState func(*varLexer) lexState + +func varLex(s string) []varToken { + l := &varLexer{input: s} + l.run() + return l.tokens +} + +func (l *varLexer) accept(valid string) bool { + if strings.IndexRune(valid, l.next()) >= 0 { + return true + } + l.backup() + return false +} + +func (l *varLexer) backup() { + l.pos -= l.width +} + +func (l *varLexer) emit(t varTokenType) { + l.tokens = append(l.tokens, varToken{t, l.input[l.start:l.pos]}) + l.start = l.pos +} + +func (l *varLexer) errorf(format string, v ...interface{}) lexState { + l.tokens = append(l.tokens, varToken{ + tokError, + fmt.Sprintf(format, v...), + }) + return nil +} + +func (l *varLexer) ignore() { + l.start = l.pos +} + +func (l *varLexer) next() rune { + var r rune + + if l.pos >= len(l.input) { + l.width = 0 + return -1 + } + r, l.width = utf8.DecodeRuneInString(l.input[l.pos:]) + l.pos += l.width + return r +} + +func (l *varLexer) run() { + for state := varLexNormal; state != nil; { + state = state(l) + } +} + +func (l *varLexer) peek() rune { + r := l.next() + l.backup() + return r +} + +func varLexNormal(l *varLexer) lexState { + for { + r := l.next() + switch { + case r == -1: + l.emit(tokEOF) + return nil + case r == '[': + l.emit(tokArrayStart) + case r == ']': + l.emit(tokArrayEnd) + case r == '{': + l.emit(tokDictStart) + case r == '}': + l.emit(tokDictEnd) + case r == '<': + l.emit(tokVariantStart) + case r == '>': + l.emit(tokVariantEnd) + case r == ':': + l.emit(tokColon) + case r == ',': + l.emit(tokComma) + case r == '\'' || r == '"': + l.backup() + return varLexString + case r == '@': + l.backup() + return varLexType + case unicode.IsSpace(r): + l.ignore() + case unicode.IsNumber(r) || r == '+' || r == '-': + l.backup() + return varLexNumber + case r == 'b': + pos := l.start + if n := l.peek(); n == '"' || n == '\'' { + return varLexByteString + } + // not a byte string; try to parse it as a type or bool below + l.pos = pos + 1 + l.width = 1 + fallthrough + default: + // either a bool or a type. Try bools first. + l.backup() + if l.pos+4 <= len(l.input) { + if l.input[l.pos:l.pos+4] == "true" { + l.pos += 4 + l.emit(tokBool) + continue + } + } + if l.pos+5 <= len(l.input) { + if l.input[l.pos:l.pos+5] == "false" { + l.pos += 5 + l.emit(tokBool) + continue + } + } + // must be a type. + return varLexType + } + } +} + +var varTypeMap = map[string]string{ + "boolean": "b", + "byte": "y", + "int16": "n", + "uint16": "q", + "int32": "i", + "uint32": "u", + "int64": "x", + "uint64": "t", + "double": "f", + "string": "s", + "objectpath": "o", + "signature": "g", +} + +func varLexByteString(l *varLexer) lexState { + q := l.next() +Loop: + for { + switch l.next() { + case '\\': + if r := l.next(); r != -1 { + break + } + fallthrough + case -1: + return l.errorf("unterminated bytestring") + case q: + break Loop + } + } + l.emit(tokByteString) + return varLexNormal +} + +func varLexNumber(l *varLexer) lexState { + l.accept("+-") + digits := "0123456789" + if l.accept("0") { + if l.accept("x") { + digits = "0123456789abcdefABCDEF" + } else { + digits = "01234567" + } + } + for strings.IndexRune(digits, l.next()) >= 0 { + } + l.backup() + if l.accept(".") { + for strings.IndexRune(digits, l.next()) >= 0 { + } + l.backup() + } + if l.accept("eE") { + l.accept("+-") + for strings.IndexRune("0123456789", l.next()) >= 0 { + } + l.backup() + } + if r := l.peek(); unicode.IsLetter(r) { + l.next() + return l.errorf("bad number syntax: %q", l.input[l.start:l.pos]) + } + l.emit(tokNumber) + return varLexNormal +} + +func varLexString(l *varLexer) lexState { + q := l.next() +Loop: + for { + switch l.next() { + case '\\': + if r := l.next(); r != -1 { + break + } + fallthrough + case -1: + return l.errorf("unterminated string") + case q: + break Loop + } + } + l.emit(tokString) + return varLexNormal +} + +func varLexType(l *varLexer) lexState { + at := l.accept("@") + for { + r := l.next() + if r == -1 { + break + } + if unicode.IsSpace(r) { + l.backup() + break + } + } + if at { + if _, err := ParseSignature(l.input[l.start+1 : l.pos]); err != nil { + return l.errorf("%s", err) + } + } else { + if _, ok := varTypeMap[l.input[l.start:l.pos]]; ok { + l.emit(tokType) + return varLexNormal + } + return l.errorf("unrecognized type %q", l.input[l.start:l.pos]) + } + l.emit(tokType) + return varLexNormal +} diff --git a/vendor/src/github.com/godbus/dbus/variant_parser.go b/vendor/src/github.com/godbus/dbus/variant_parser.go new file mode 100644 index 0000000000..d20f5da6dd --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/variant_parser.go @@ -0,0 +1,817 @@ +package dbus + +import ( + "bytes" + "errors" + "fmt" + "io" + "reflect" + "strconv" + "strings" + "unicode/utf8" +) + +type varParser struct { + tokens []varToken + i int +} + +func (p *varParser) backup() { + p.i-- +} + +func (p *varParser) next() varToken { + if p.i < len(p.tokens) { + t := p.tokens[p.i] + p.i++ + return t + } + return varToken{typ: tokEOF} +} + +type varNode interface { + Infer() (Signature, error) + String() string + Sigs() sigSet + Value(Signature) (interface{}, error) +} + +func varMakeNode(p *varParser) (varNode, error) { + var sig Signature + + for { + t := p.next() + switch t.typ { + case tokEOF: + return nil, io.ErrUnexpectedEOF + case tokError: + return nil, errors.New(t.val) + case tokNumber: + return varMakeNumNode(t, sig) + case tokString: + return varMakeStringNode(t, sig) + case tokBool: + if sig.str != "" && sig.str != "b" { + return nil, varTypeError{t.val, sig} + } + b, err := strconv.ParseBool(t.val) + if err != nil { + return nil, err + } + return boolNode(b), nil + case tokArrayStart: + return varMakeArrayNode(p, sig) + case tokVariantStart: + return varMakeVariantNode(p, sig) + case tokDictStart: + return varMakeDictNode(p, sig) + case tokType: + if sig.str != "" { + return nil, errors.New("unexpected type annotation") + } + if t.val[0] == '@' { + sig.str = t.val[1:] + } else { + sig.str = varTypeMap[t.val] + } + case tokByteString: + if sig.str != "" && sig.str != "ay" { + return nil, varTypeError{t.val, sig} + } + b, err := varParseByteString(t.val) + if err != nil { + return nil, err + } + return byteStringNode(b), nil + default: + return nil, fmt.Errorf("unexpected %q", t.val) + } + } +} + +type varTypeError struct { + val string + sig Signature +} + +func (e varTypeError) Error() string { + return fmt.Sprintf("dbus: can't parse %q as type %q", e.val, e.sig.str) +} + +type sigSet map[Signature]bool + +func (s sigSet) Empty() bool { + return len(s) == 0 +} + +func (s sigSet) Intersect(s2 sigSet) sigSet { + r := make(sigSet) + for k := range s { + if s2[k] { + r[k] = true + } + } + return r +} + +func (s sigSet) Single() (Signature, bool) { + if len(s) == 1 { + for k := range s { + return k, true + } + } + return Signature{}, false +} + +func (s sigSet) ToArray() sigSet { + r := make(sigSet, len(s)) + for k := range s { + r[Signature{"a" + k.str}] = true + } + return r +} + +type numNode struct { + sig Signature + str string + val interface{} +} + +var numSigSet = sigSet{ + Signature{"y"}: true, + Signature{"n"}: true, + Signature{"q"}: true, + Signature{"i"}: true, + Signature{"u"}: true, + Signature{"x"}: true, + Signature{"t"}: true, + Signature{"d"}: true, +} + +func (n numNode) Infer() (Signature, error) { + if strings.ContainsAny(n.str, ".e") { + return Signature{"d"}, nil + } + return Signature{"i"}, nil +} + +func (n numNode) String() string { + return n.str +} + +func (n numNode) Sigs() sigSet { + if n.sig.str != "" { + return sigSet{n.sig: true} + } + if strings.ContainsAny(n.str, ".e") { + return sigSet{Signature{"d"}: true} + } + return numSigSet +} + +func (n numNode) Value(sig Signature) (interface{}, error) { + if n.sig.str != "" && n.sig != sig { + return nil, varTypeError{n.str, sig} + } + if n.val != nil { + return n.val, nil + } + return varNumAs(n.str, sig) +} + +func varMakeNumNode(tok varToken, sig Signature) (varNode, error) { + if sig.str == "" { + return numNode{str: tok.val}, nil + } + num, err := varNumAs(tok.val, sig) + if err != nil { + return nil, err + } + return numNode{sig: sig, val: num}, nil +} + +func varNumAs(s string, sig Signature) (interface{}, error) { + isUnsigned := false + size := 32 + switch sig.str { + case "n": + size = 16 + case "i": + case "x": + size = 64 + case "y": + size = 8 + isUnsigned = true + case "q": + size = 16 + isUnsigned = true + case "u": + isUnsigned = true + case "t": + size = 64 + isUnsigned = true + case "d": + d, err := strconv.ParseFloat(s, 64) + if err != nil { + return nil, err + } + return d, nil + default: + return nil, varTypeError{s, sig} + } + base := 10 + if strings.HasPrefix(s, "0x") { + base = 16 + s = s[2:] + } + if strings.HasPrefix(s, "0") && len(s) != 1 { + base = 8 + s = s[1:] + } + if isUnsigned { + i, err := strconv.ParseUint(s, base, size) + if err != nil { + return nil, err + } + var v interface{} = i + switch sig.str { + case "y": + v = byte(i) + case "q": + v = uint16(i) + case "u": + v = uint32(i) + } + return v, nil + } + i, err := strconv.ParseInt(s, base, size) + if err != nil { + return nil, err + } + var v interface{} = i + switch sig.str { + case "n": + v = int16(i) + case "i": + v = int32(i) + } + return v, nil +} + +type stringNode struct { + sig Signature + str string // parsed + val interface{} // has correct type +} + +var stringSigSet = sigSet{ + Signature{"s"}: true, + Signature{"g"}: true, + Signature{"o"}: true, +} + +func (n stringNode) Infer() (Signature, error) { + return Signature{"s"}, nil +} + +func (n stringNode) String() string { + return n.str +} + +func (n stringNode) Sigs() sigSet { + if n.sig.str != "" { + return sigSet{n.sig: true} + } + return stringSigSet +} + +func (n stringNode) Value(sig Signature) (interface{}, error) { + if n.sig.str != "" && n.sig != sig { + return nil, varTypeError{n.str, sig} + } + if n.val != nil { + return n.val, nil + } + switch { + case sig.str == "g": + return Signature{n.str}, nil + case sig.str == "o": + return ObjectPath(n.str), nil + case sig.str == "s": + return n.str, nil + default: + return nil, varTypeError{n.str, sig} + } +} + +func varMakeStringNode(tok varToken, sig Signature) (varNode, error) { + if sig.str != "" && sig.str != "s" && sig.str != "g" && sig.str != "o" { + return nil, fmt.Errorf("invalid type %q for string", sig.str) + } + s, err := varParseString(tok.val) + if err != nil { + return nil, err + } + n := stringNode{str: s} + if sig.str == "" { + return stringNode{str: s}, nil + } + n.sig = sig + switch sig.str { + case "o": + n.val = ObjectPath(s) + case "g": + n.val = Signature{s} + case "s": + n.val = s + } + return n, nil +} + +func varParseString(s string) (string, error) { + // quotes are guaranteed to be there + s = s[1 : len(s)-1] + buf := new(bytes.Buffer) + for len(s) != 0 { + r, size := utf8.DecodeRuneInString(s) + if r == utf8.RuneError && size == 1 { + return "", errors.New("invalid UTF-8") + } + s = s[size:] + if r != '\\' { + buf.WriteRune(r) + continue + } + r, size = utf8.DecodeRuneInString(s) + if r == utf8.RuneError && size == 1 { + return "", errors.New("invalid UTF-8") + } + s = s[size:] + switch r { + case 'a': + buf.WriteRune(0x7) + case 'b': + buf.WriteRune(0x8) + case 'f': + buf.WriteRune(0xc) + case 'n': + buf.WriteRune('\n') + case 'r': + buf.WriteRune('\r') + case 't': + buf.WriteRune('\t') + case '\n': + case 'u': + if len(s) < 4 { + return "", errors.New("short unicode escape") + } + r, err := strconv.ParseUint(s[:4], 16, 32) + if err != nil { + return "", err + } + buf.WriteRune(rune(r)) + s = s[4:] + case 'U': + if len(s) < 8 { + return "", errors.New("short unicode escape") + } + r, err := strconv.ParseUint(s[:8], 16, 32) + if err != nil { + return "", err + } + buf.WriteRune(rune(r)) + s = s[8:] + default: + buf.WriteRune(r) + } + } + return buf.String(), nil +} + +var boolSigSet = sigSet{Signature{"b"}: true} + +type boolNode bool + +func (boolNode) Infer() (Signature, error) { + return Signature{"b"}, nil +} + +func (b boolNode) String() string { + if b { + return "true" + } + return "false" +} + +func (boolNode) Sigs() sigSet { + return boolSigSet +} + +func (b boolNode) Value(sig Signature) (interface{}, error) { + if sig.str != "b" { + return nil, varTypeError{b.String(), sig} + } + return bool(b), nil +} + +type arrayNode struct { + set sigSet + children []varNode + val interface{} +} + +func (n arrayNode) Infer() (Signature, error) { + for _, v := range n.children { + csig, err := varInfer(v) + if err != nil { + continue + } + return Signature{"a" + csig.str}, nil + } + return Signature{}, fmt.Errorf("can't infer type for %q", n.String()) +} + +func (n arrayNode) String() string { + s := "[" + for i, v := range n.children { + s += v.String() + if i != len(n.children)-1 { + s += ", " + } + } + return s + "]" +} + +func (n arrayNode) Sigs() sigSet { + return n.set +} + +func (n arrayNode) Value(sig Signature) (interface{}, error) { + if n.set.Empty() { + // no type information whatsoever, so this must be an empty slice + return reflect.MakeSlice(typeFor(sig.str), 0, 0).Interface(), nil + } + if !n.set[sig] { + return nil, varTypeError{n.String(), sig} + } + s := reflect.MakeSlice(typeFor(sig.str), len(n.children), len(n.children)) + for i, v := range n.children { + rv, err := v.Value(Signature{sig.str[1:]}) + if err != nil { + return nil, err + } + s.Index(i).Set(reflect.ValueOf(rv)) + } + return s.Interface(), nil +} + +func varMakeArrayNode(p *varParser, sig Signature) (varNode, error) { + var n arrayNode + if sig.str != "" { + n.set = sigSet{sig: true} + } + if t := p.next(); t.typ == tokArrayEnd { + return n, nil + } else { + p.backup() + } +Loop: + for { + t := p.next() + switch t.typ { + case tokEOF: + return nil, io.ErrUnexpectedEOF + case tokError: + return nil, errors.New(t.val) + } + p.backup() + cn, err := varMakeNode(p) + if err != nil { + return nil, err + } + if cset := cn.Sigs(); !cset.Empty() { + if n.set.Empty() { + n.set = cset.ToArray() + } else { + nset := cset.ToArray().Intersect(n.set) + if nset.Empty() { + return nil, fmt.Errorf("can't parse %q with given type information", cn.String()) + } + n.set = nset + } + } + n.children = append(n.children, cn) + switch t := p.next(); t.typ { + case tokEOF: + return nil, io.ErrUnexpectedEOF + case tokError: + return nil, errors.New(t.val) + case tokArrayEnd: + break Loop + case tokComma: + continue + default: + return nil, fmt.Errorf("unexpected %q", t.val) + } + } + return n, nil +} + +type variantNode struct { + n varNode +} + +var variantSet = sigSet{ + Signature{"v"}: true, +} + +func (variantNode) Infer() (Signature, error) { + return Signature{"v"}, nil +} + +func (n variantNode) String() string { + return "<" + n.n.String() + ">" +} + +func (variantNode) Sigs() sigSet { + return variantSet +} + +func (n variantNode) Value(sig Signature) (interface{}, error) { + if sig.str != "v" { + return nil, varTypeError{n.String(), sig} + } + sig, err := varInfer(n.n) + if err != nil { + return nil, err + } + v, err := n.n.Value(sig) + if err != nil { + return nil, err + } + return MakeVariant(v), nil +} + +func varMakeVariantNode(p *varParser, sig Signature) (varNode, error) { + n, err := varMakeNode(p) + if err != nil { + return nil, err + } + if t := p.next(); t.typ != tokVariantEnd { + return nil, fmt.Errorf("unexpected %q", t.val) + } + vn := variantNode{n} + if sig.str != "" && sig.str != "v" { + return nil, varTypeError{vn.String(), sig} + } + return variantNode{n}, nil +} + +type dictEntry struct { + key, val varNode +} + +type dictNode struct { + kset, vset sigSet + children []dictEntry + val interface{} +} + +func (n dictNode) Infer() (Signature, error) { + for _, v := range n.children { + ksig, err := varInfer(v.key) + if err != nil { + continue + } + vsig, err := varInfer(v.val) + if err != nil { + continue + } + return Signature{"a{" + ksig.str + vsig.str + "}"}, nil + } + return Signature{}, fmt.Errorf("can't infer type for %q", n.String()) +} + +func (n dictNode) String() string { + s := "{" + for i, v := range n.children { + s += v.key.String() + ": " + v.val.String() + if i != len(n.children)-1 { + s += ", " + } + } + return s + "}" +} + +func (n dictNode) Sigs() sigSet { + r := sigSet{} + for k := range n.kset { + for v := range n.vset { + sig := "a{" + k.str + v.str + "}" + r[Signature{sig}] = true + } + } + return r +} + +func (n dictNode) Value(sig Signature) (interface{}, error) { + set := n.Sigs() + if set.Empty() { + // no type information -> empty dict + return reflect.MakeMap(typeFor(sig.str)).Interface(), nil + } + if !set[sig] { + return nil, varTypeError{n.String(), sig} + } + m := reflect.MakeMap(typeFor(sig.str)) + ksig := Signature{sig.str[2:3]} + vsig := Signature{sig.str[3 : len(sig.str)-1]} + for _, v := range n.children { + kv, err := v.key.Value(ksig) + if err != nil { + return nil, err + } + vv, err := v.val.Value(vsig) + if err != nil { + return nil, err + } + m.SetMapIndex(reflect.ValueOf(kv), reflect.ValueOf(vv)) + } + return m.Interface(), nil +} + +func varMakeDictNode(p *varParser, sig Signature) (varNode, error) { + var n dictNode + + if sig.str != "" { + if len(sig.str) < 5 { + return nil, fmt.Errorf("invalid signature %q for dict type", sig) + } + ksig := Signature{string(sig.str[2])} + vsig := Signature{sig.str[3 : len(sig.str)-1]} + n.kset = sigSet{ksig: true} + n.vset = sigSet{vsig: true} + } + if t := p.next(); t.typ == tokDictEnd { + return n, nil + } else { + p.backup() + } +Loop: + for { + t := p.next() + switch t.typ { + case tokEOF: + return nil, io.ErrUnexpectedEOF + case tokError: + return nil, errors.New(t.val) + } + p.backup() + kn, err := varMakeNode(p) + if err != nil { + return nil, err + } + if kset := kn.Sigs(); !kset.Empty() { + if n.kset.Empty() { + n.kset = kset + } else { + n.kset = kset.Intersect(n.kset) + if n.kset.Empty() { + return nil, fmt.Errorf("can't parse %q with given type information", kn.String()) + } + } + } + t = p.next() + switch t.typ { + case tokEOF: + return nil, io.ErrUnexpectedEOF + case tokError: + return nil, errors.New(t.val) + case tokColon: + default: + return nil, fmt.Errorf("unexpected %q", t.val) + } + t = p.next() + switch t.typ { + case tokEOF: + return nil, io.ErrUnexpectedEOF + case tokError: + return nil, errors.New(t.val) + } + p.backup() + vn, err := varMakeNode(p) + if err != nil { + return nil, err + } + if vset := vn.Sigs(); !vset.Empty() { + if n.vset.Empty() { + n.vset = vset + } else { + n.vset = n.vset.Intersect(vset) + if n.vset.Empty() { + return nil, fmt.Errorf("can't parse %q with given type information", vn.String()) + } + } + } + n.children = append(n.children, dictEntry{kn, vn}) + t = p.next() + switch t.typ { + case tokEOF: + return nil, io.ErrUnexpectedEOF + case tokError: + return nil, errors.New(t.val) + case tokDictEnd: + break Loop + case tokComma: + continue + default: + return nil, fmt.Errorf("unexpected %q", t.val) + } + } + return n, nil +} + +type byteStringNode []byte + +var byteStringSet = sigSet{ + Signature{"ay"}: true, +} + +func (byteStringNode) Infer() (Signature, error) { + return Signature{"ay"}, nil +} + +func (b byteStringNode) String() string { + return string(b) +} + +func (b byteStringNode) Sigs() sigSet { + return byteStringSet +} + +func (b byteStringNode) Value(sig Signature) (interface{}, error) { + if sig.str != "ay" { + return nil, varTypeError{b.String(), sig} + } + return []byte(b), nil +} + +func varParseByteString(s string) ([]byte, error) { + // quotes and b at start are guaranteed to be there + b := make([]byte, 0, 1) + s = s[2 : len(s)-1] + for len(s) != 0 { + c := s[0] + s = s[1:] + if c != '\\' { + b = append(b, c) + continue + } + c = s[0] + s = s[1:] + switch c { + case 'a': + b = append(b, 0x7) + case 'b': + b = append(b, 0x8) + case 'f': + b = append(b, 0xc) + case 'n': + b = append(b, '\n') + case 'r': + b = append(b, '\r') + case 't': + b = append(b, '\t') + case 'x': + if len(s) < 2 { + return nil, errors.New("short escape") + } + n, err := strconv.ParseUint(s[:2], 16, 8) + if err != nil { + return nil, err + } + b = append(b, byte(n)) + s = s[2:] + case '0': + if len(s) < 3 { + return nil, errors.New("short escape") + } + n, err := strconv.ParseUint(s[:3], 8, 8) + if err != nil { + return nil, err + } + b = append(b, byte(n)) + s = s[3:] + default: + b = append(b, c) + } + } + return append(b, 0), nil +} + +func varInfer(n varNode) (Signature, error) { + if sig, ok := n.Sigs().Single(); ok { + return sig, nil + } + return n.Infer() +} diff --git a/vendor/src/github.com/godbus/dbus/variant_test.go b/vendor/src/github.com/godbus/dbus/variant_test.go new file mode 100644 index 0000000000..da917c8e29 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/variant_test.go @@ -0,0 +1,78 @@ +package dbus + +import "reflect" +import "testing" + +var variantFormatTests = []struct { + v interface{} + s string +}{ + {int32(1), `1`}, + {"foo", `"foo"`}, + {ObjectPath("/org/foo"), `@o "/org/foo"`}, + {Signature{"i"}, `@g "i"`}, + {[]byte{}, `@ay []`}, + {[]int32{1, 2}, `[1, 2]`}, + {[]int64{1, 2}, `@ax [1, 2]`}, + {[][]int32{{3, 4}, {5, 6}}, `[[3, 4], [5, 6]]`}, + {[]Variant{MakeVariant(int32(1)), MakeVariant(1.0)}, `[<1>, <@d 1>]`}, + {map[string]int32{"one": 1, "two": 2}, `{"one": 1, "two": 2}`}, + {map[int32]ObjectPath{1: "/org/foo"}, `@a{io} {1: "/org/foo"}`}, + {map[string]Variant{}, `@a{sv} {}`}, +} + +func TestFormatVariant(t *testing.T) { + for i, v := range variantFormatTests { + if s := MakeVariant(v.v).String(); s != v.s { + t.Errorf("test %d: got %q, wanted %q", i+1, s, v.s) + } + } +} + +var variantParseTests = []struct { + s string + v interface{} +}{ + {"1", int32(1)}, + {"true", true}, + {"false", false}, + {"1.0", float64(1.0)}, + {"0x10", int32(16)}, + {"1e1", float64(10)}, + {`"foo"`, "foo"}, + {`"\a\b\f\n\r\t"`, "\x07\x08\x0c\n\r\t"}, + {`"\u00e4\U0001f603"`, "\u00e4\U0001f603"}, + {"[1]", []int32{1}}, + {"[1, 2, 3]", []int32{1, 2, 3}}, + {"@ai []", []int32{}}, + {"[1, 5.0]", []float64{1, 5.0}}, + {"[[1, 2], [3, 4.0]]", [][]float64{{1, 2}, {3, 4}}}, + {`[@o "/org/foo", "/org/bar"]`, []ObjectPath{"/org/foo", "/org/bar"}}, + {"<1>", MakeVariant(int32(1))}, + {"[<1>, <2.0>]", []Variant{MakeVariant(int32(1)), MakeVariant(2.0)}}, + {`[[], [""]]`, [][]string{{}, {""}}}, + {`@a{ss} {}`, map[string]string{}}, + {`{"foo": 1}`, map[string]int32{"foo": 1}}, + {`[{}, {"foo": "bar"}]`, []map[string]string{{}, {"foo": "bar"}}}, + {`{"a": <1>, "b": <"foo">}`, + map[string]Variant{"a": MakeVariant(int32(1)), "b": MakeVariant("foo")}}, + {`b''`, []byte{0}}, + {`b"abc"`, []byte{'a', 'b', 'c', 0}}, + {`b"\x01\0002\a\b\f\n\r\t"`, []byte{1, 2, 0x7, 0x8, 0xc, '\n', '\r', '\t', 0}}, + {`[[0], b""]`, [][]byte{{0}, {0}}}, + {"int16 0", int16(0)}, + {"byte 0", byte(0)}, +} + +func TestParseVariant(t *testing.T) { + for i, v := range variantParseTests { + nv, err := ParseVariant(v.s, Signature{}) + if err != nil { + t.Errorf("test %d: parsing failed: %s", i+1, err) + continue + } + if !reflect.DeepEqual(nv.value, v.v) { + t.Errorf("test %d: got %q, wanted %q", i+1, nv, v.v) + } + } +} From cb43fd007133fc05b6bb2b0d3d58fef8b1e60537 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 18 Mar 2014 18:40:00 +0100 Subject: [PATCH 233/384] pkg/systemd: Drop our copy-pasted version of go-systemd/activation Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- pkg/systemd/activation/files.go | 55 ----------------------------- pkg/systemd/activation/listeners.go | 37 ------------------- pkg/systemd/listendfd.go | 2 +- 3 files changed, 1 insertion(+), 93 deletions(-) delete mode 100644 pkg/systemd/activation/files.go delete mode 100644 pkg/systemd/activation/listeners.go diff --git a/pkg/systemd/activation/files.go b/pkg/systemd/activation/files.go deleted file mode 100644 index 0281146310..0000000000 --- a/pkg/systemd/activation/files.go +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright 2013 CoreOS Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -// Package activation implements primitives for systemd socket activation. -package activation - -import ( - "os" - "strconv" - "syscall" -) - -// based on: https://gist.github.com/alberts/4640792 -const ( - listenFdsStart = 3 -) - -func Files(unsetEnv bool) []*os.File { - if unsetEnv { - // there is no way to unset env in golang os package for now - // https://code.google.com/p/go/issues/detail?id=6423 - defer os.Setenv("LISTEN_PID", "") - defer os.Setenv("LISTEN_FDS", "") - } - - pid, err := strconv.Atoi(os.Getenv("LISTEN_PID")) - if err != nil || pid != os.Getpid() { - return nil - } - - nfds, err := strconv.Atoi(os.Getenv("LISTEN_FDS")) - if err != nil || nfds == 0 { - return nil - } - - var files []*os.File - for fd := listenFdsStart; fd < listenFdsStart+nfds; fd++ { - syscall.CloseOnExec(fd) - files = append(files, os.NewFile(uintptr(fd), "LISTEN_FD_"+strconv.Itoa(fd))) - } - - return files -} diff --git a/pkg/systemd/activation/listeners.go b/pkg/systemd/activation/listeners.go deleted file mode 100644 index 3296a08361..0000000000 --- a/pkg/systemd/activation/listeners.go +++ /dev/null @@ -1,37 +0,0 @@ -/* -Copyright 2014 CoreOS Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -package activation - -import ( - "fmt" - "net" -) - -// Listeners returns net.Listeners for all socket activated fds passed to this process. -func Listeners(unsetEnv bool) ([]net.Listener, error) { - files := Files(unsetEnv) - listeners := make([]net.Listener, len(files)) - - for i, f := range files { - var err error - listeners[i], err = net.FileListener(f) - if err != nil { - return nil, fmt.Errorf("Error setting up FileListener for fd %d: %s", f.Fd(), err.Error()) - } - } - - return listeners, nil -} diff --git a/pkg/systemd/listendfd.go b/pkg/systemd/listendfd.go index f6044328c2..0fbc0a6ab6 100644 --- a/pkg/systemd/listendfd.go +++ b/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 From 1296d5ce9ad43d8d833f6e5661da45aef6d4c26b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 21 Feb 2014 10:48:02 +0100 Subject: [PATCH 234/384] Add systemd.SdBooted() This is a conversion of sd_booted() from libsystemd to go and checks if the system was booted with systemd. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- pkg/systemd/booted.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 pkg/systemd/booted.go diff --git a/pkg/systemd/booted.go b/pkg/systemd/booted.go new file mode 100644 index 0000000000..2aae931ec1 --- /dev/null +++ b/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() +} From 6c7835050e53b733181ddfca6152c358fd625400 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 21 Feb 2014 14:35:43 +0100 Subject: [PATCH 235/384] cgroups: Add systemd implementation of cgroups This implements cgroup.Apply() using the systemd apis. We create a transient unit called "docker-$id.scope" that contains the container processes. We also have a way to set unit specific properties, currently only defining the Slice to put the scope in. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- pkg/cgroups/apply_nosystemd.go | 15 ++ pkg/cgroups/apply_systemd.go | 158 ++++++++++++++++++ pkg/cgroups/cgroups.go | 13 +- runtime/execdriver/native/default_template.go | 14 ++ 4 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 pkg/cgroups/apply_nosystemd.go create mode 100644 pkg/cgroups/apply_systemd.go diff --git a/pkg/cgroups/apply_nosystemd.go b/pkg/cgroups/apply_nosystemd.go new file mode 100644 index 0000000000..f94d475907 --- /dev/null +++ b/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") +} diff --git a/pkg/cgroups/apply_systemd.go b/pkg/cgroups/apply_systemd.go new file mode 100644 index 0000000000..c689d5753e --- /dev/null +++ b/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 +} diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go index f35556f712..8554ba9376 100644 --- a/pkg/cgroups/cgroups.go +++ b/pkg/cgroups/cgroups.go @@ -19,6 +19,8 @@ type Cgroup struct { Memory int64 `json:"memory,omitempty"` // Memory limit (in bytes) 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) + + UnitProperties [][2]string `json:"unit_properties,omitempty"` // systemd unit properties } type ActiveCgroup interface { @@ -87,5 +89,14 @@ func writeFile(dir, file, data string) error { } func (c *Cgroup) Apply(pid int) (ActiveCgroup, error) { - return rawApply(c, pid) + // 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/ + + if useSystemd() { + return systemdApply(c, pid) + } else { + return rawApply(c, pid) + } } diff --git a/runtime/execdriver/native/default_template.go b/runtime/execdriver/native/default_template.go index e11f2de1cf..32886f2396 100644 --- a/runtime/execdriver/native/default_template.go +++ b/runtime/execdriver/native/default_template.go @@ -5,6 +5,7 @@ import ( "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/runtime/execdriver" + "github.com/dotcloud/docker/utils" "os" ) @@ -59,6 +60,19 @@ func createContainer(c *execdriver.Command) *libcontainer.Container { container.Cgroups.MemorySwap = c.Resources.MemorySwap } + if opts, ok := c.Config["unit"]; ok { + props := [][2]string{} + for _, opt := range opts { + key, value, err := utils.ParseKeyValueOpt(opt) + if err == nil { + props = append(props, [2]string{key, value}) + } else { + props = append(props, [2]string{opt, ""}) + } + } + container.Cgroups.UnitProperties = props + } + // check to see if we are running in ramdisk to disable pivot root container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != "" From 64dd77fa0eb73ec4f395848982ccb60bb3bf8fc5 Mon Sep 17 00:00:00 2001 From: James Harrison Fisher Date: Thu, 27 Mar 2014 23:02:44 +0000 Subject: [PATCH 236/384] Add missing port NAT configuration Missing port translation causes last line to fail Docker-DCO-1.1-Signed-off-by: James Fisher (github: jameshfisher) --- docs/sources/examples/mongodb.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/examples/mongodb.rst b/docs/sources/examples/mongodb.rst index 930ab2ea9d..913dc2699a 100644 --- a/docs/sources/examples/mongodb.rst +++ b/docs/sources/examples/mongodb.rst @@ -86,10 +86,10 @@ the local port! .. code-block:: bash # Regular style - MONGO_ID=$(sudo docker run -d /mongodb) + MONGO_ID=$(sudo docker run -P -d /mongodb) # Lean and mean - MONGO_ID=$(sudo docker run -d /mongodb --noprealloc --smallfiles) + MONGO_ID=$(sudo docker run -P -d /mongodb --noprealloc --smallfiles) # Check the logs out sudo docker logs $MONGO_ID From 792bb41e524615486ef8266b7bf4804b4fe178f1 Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Thu, 27 Mar 2014 16:38:27 -0400 Subject: [PATCH 237/384] Remount /var/lib/docker as --private to fix scaling issue If an admin mounts all file systems as -rshared (Default on RHEL and Fedora) we see a scaling problem as the number of container increase. Basically every new container needs to have it new mounts in /var/lib/docker shared to all other containers, this ends up with us only able to scale to around 100 containers, before the system slows down. By simply bind mounting /var/lib/docker on its and then setting it private, the scaling issue goes away. Docker-DCO-1.1-Signed-off-by: Dan Walsh (github: rhatdan) --- runtime/runtime.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/runtime/runtime.go b/runtime/runtime.go index 35bcad9781..b035f5df9f 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -10,6 +10,7 @@ import ( "github.com/dotcloud/docker/graph" "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/pkg/graphdb" + "github.com/dotcloud/docker/pkg/mount" "github.com/dotcloud/docker/pkg/sysinfo" "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/runtime/execdriver" @@ -59,6 +60,22 @@ type Runtime struct { execDriver execdriver.Driver } +// Mountpoints should be private to the container +func remountPrivate(mountPoint string) error { + + mounted, err := mount.Mounted(mountPoint) + if err != nil { + return err + } + + if !mounted { + if err := mount.Mount(mountPoint, mountPoint, "none", "bind,rw"); err != nil { + return err + } + } + return mount.ForceMount("", mountPoint, "none", "private") +} + // List returns an array of all containers registered in the runtime. func (runtime *Runtime) List() []*Container { containers := new(History) @@ -654,6 +671,10 @@ func NewRuntimeFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (* } utils.Debugf("Using graph driver %s", driver) + if err := remountPrivate(config.Root); err != nil { + return nil, err + } + runtimeRepo := path.Join(config.Root, "containers") if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) { From 66c5e19f9bd057644fab475499ea45bb428ba2b2 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 28 Mar 2014 15:58:14 +0100 Subject: [PATCH 238/384] devmapper: Ensure we shut down thin pool cleanly. The change in commit a9fa1a13c3b0a654a96be01ff7ec19e8009b2094 made us only deactivate devices that were mounted. Unfortunately this made us not deactivate the base device. Which caused us to not be able to deactivate the pool. This fixes that by always just deactivating the base device. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- runtime/graphdriver/devmapper/deviceset.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/runtime/graphdriver/devmapper/deviceset.go b/runtime/graphdriver/devmapper/deviceset.go index 762e982208..731e9dab8b 100644 --- a/runtime/graphdriver/devmapper/deviceset.go +++ b/runtime/graphdriver/devmapper/deviceset.go @@ -821,6 +821,10 @@ func (devices *DeviceSet) Shutdown() error { info.lock.Unlock() } + if err := devices.deactivateDevice(""); err != nil { + utils.Debugf("Shutdown deactivate base , error: %s\n", err) + } + if err := devices.deactivatePool(); err != nil { utils.Debugf("Shutdown deactivate pool , error: %s\n", err) } From 7d750180e49159ead712843a9cc1c0c58fd5c53c Mon Sep 17 00:00:00 2001 From: Justin Simonelis Date: Fri, 28 Mar 2014 15:15:54 -0500 Subject: [PATCH 239/384] Update AUTHORS --- AUTHORS | 1 - 1 file changed, 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 1c58d953f6..df091d5950 100644 --- a/AUTHORS +++ b/AUTHORS @@ -170,7 +170,6 @@ Julien Barbier Julien Dubois Justin Force Justin Plock -Justin Simonelis Karan Lyons Karl Grzeszczak Kawsar Saiyeed From 36dfa0c4ec90404f76a6ec73c89d199e279ee96c Mon Sep 17 00:00:00 2001 From: Jason Plum Date: Fri, 28 Mar 2014 18:02:17 -0400 Subject: [PATCH 240/384] Fix daemon's documentation for -bip flag --- docs/sources/reference/commandline/cli.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index b39cbc2e9f..3d2aac5233 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -74,7 +74,7 @@ Commands -G, --group="docker": Group to assign the unix socket specified by -H when running in daemon mode; use '' (the empty string) to disable setting of a group --api-enable-cors=false: Enable CORS headers in the remote API -b, --bridge="": Attach containers to a pre-existing network bridge; use 'none' to disable container networking - --bip="": Use this CIDR notation address for the network bridge's IP, not compatible with -b + -bip="": Use this CIDR notation address for the network bridge's IP, not compatible with -b -d, --daemon=false: Enable daemon mode --dns=[]: Force docker to use specific DNS servers --dns-search=[]: Force Docker to use specific DNS search domains From 04f5c75239cba156db70523bcd90657e5c7b5ddb Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 29 Mar 2014 00:48:47 +0000 Subject: [PATCH 241/384] Steve Wozniak is not boring. Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- pkg/namesgenerator/names-generator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/namesgenerator/names-generator.go b/pkg/namesgenerator/names-generator.go index dfece5d611..3776e59225 100644 --- a/pkg/namesgenerator/names-generator.go +++ b/pkg/namesgenerator/names-generator.go @@ -56,7 +56,7 @@ func GenerateRandomName(checker NameChecker) (string, error) { retry := 5 rand.Seed(time.Now().UnixNano()) name := fmt.Sprintf("%s_%s", left[rand.Intn(len(left))], right[rand.Intn(len(right))]) - for checker != nil && checker.Exists(name) && retry > 0 { + for checker != nil && checker.Exists(name) && retry > 0 || name == "boring_wozniak" /* Steve Wozniak is not boring */ { name = fmt.Sprintf("%s%d", name, rand.Intn(10)) retry = retry - 1 } From 6db32fdefdae49843ed9535b3af1099e6bd2755d Mon Sep 17 00:00:00 2001 From: unclejack Date: Tue, 25 Feb 2014 18:17:48 +0200 Subject: [PATCH 242/384] initial version of cli integration tests Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- Makefile | 7 +- hack/make.sh | 1 + hack/make/test-integration-cli | 37 +++ .../TestBuildSixtySteps/Dockerfile | 60 +++++ integration-cli/docker_cli_build_test.go | 28 ++ integration-cli/docker_cli_commit_test.go | 34 +++ integration-cli/docker_cli_diff_test.go | 66 +++++ .../docker_cli_export_import_test.go | 50 ++++ integration-cli/docker_cli_images_test.go | 20 ++ integration-cli/docker_cli_info_test.go | 29 ++ integration-cli/docker_cli_kill_test.go | 36 +++ integration-cli/docker_cli_pull_test.go | 30 +++ integration-cli/docker_cli_push_test.go | 48 ++++ integration-cli/docker_cli_run_test.go | 255 ++++++++++++++++++ integration-cli/docker_cli_save_load_test.go | 52 ++++ integration-cli/docker_cli_search_test.go | 25 ++ integration-cli/docker_cli_tag_test.go | 86 ++++++ integration-cli/docker_cli_top_test.go | 32 +++ integration-cli/docker_cli_version_test.go | 29 ++ integration-cli/docker_test_vars.go | 29 ++ integration-cli/docker_utils.go | 56 ++++ integration-cli/utils.go | 109 ++++++++ 22 files changed, 1117 insertions(+), 2 deletions(-) create mode 100644 hack/make/test-integration-cli create mode 100644 integration-cli/build_tests/TestBuildSixtySteps/Dockerfile create mode 100644 integration-cli/docker_cli_build_test.go create mode 100644 integration-cli/docker_cli_commit_test.go create mode 100644 integration-cli/docker_cli_diff_test.go create mode 100644 integration-cli/docker_cli_export_import_test.go create mode 100644 integration-cli/docker_cli_images_test.go create mode 100644 integration-cli/docker_cli_info_test.go create mode 100644 integration-cli/docker_cli_kill_test.go create mode 100644 integration-cli/docker_cli_pull_test.go create mode 100644 integration-cli/docker_cli_push_test.go create mode 100644 integration-cli/docker_cli_run_test.go create mode 100644 integration-cli/docker_cli_save_load_test.go create mode 100644 integration-cli/docker_cli_search_test.go create mode 100644 integration-cli/docker_cli_tag_test.go create mode 100644 integration-cli/docker_cli_top_test.go create mode 100644 integration-cli/docker_cli_version_test.go create mode 100644 integration-cli/docker_test_vars.go create mode 100644 integration-cli/docker_utils.go create mode 100644 integration-cli/utils.go diff --git a/Makefile b/Makefile index b3bea8a31f..5656472213 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all binary build cross default docs docs-build docs-shell shell test test-integration +.PHONY: all binary build cross default docs docs-build docs-shell shell test test-integration test-integration-cli GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD) DOCKER_IMAGE := docker:$(GIT_BRANCH) @@ -23,11 +23,14 @@ docs-shell: docs-build docker run --rm -i -t -p 8000:8000 "$(DOCKER_DOCS_IMAGE)" bash test: build - $(DOCKER_RUN_DOCKER) hack/make.sh test test-integration + $(DOCKER_RUN_DOCKER) hack/make.sh binary test test-integration test-integration-cli test-integration: build $(DOCKER_RUN_DOCKER) hack/make.sh test-integration +test-integration-cli: build + $(DOCKER_RUN_DOCKER) hack/make.sh binary test-integration-cli + shell: build $(DOCKER_RUN_DOCKER) bash diff --git a/hack/make.sh b/hack/make.sh index dbb9dbfdfd..447a00f039 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -139,6 +139,7 @@ find_dirs() { \( \ -wholename './vendor' \ -o -wholename './integration' \ + -o -wholename './integration-cli' \ -o -wholename './contrib' \ -o -wholename './pkg/mflag/example' \ -o -wholename './.git' \ diff --git a/hack/make/test-integration-cli b/hack/make/test-integration-cli new file mode 100644 index 0000000000..5ab37a4021 --- /dev/null +++ b/hack/make/test-integration-cli @@ -0,0 +1,37 @@ +#!/bin/bash + +DEST=$1 +DOCKERBIN=$DEST/../binary/docker-$VERSION +DYNDOCKERBIN=$DEST/../dynbinary/docker-$VERSION +DOCKERINITBIN=$DEST/../dynbinary/dockerinit-$VERSION + +set -e + +bundle_test_integration_cli() { + go_test_dir ./integration-cli +} + +if [ -x "/usr/bin/docker" ]; then + echo "docker found at /usr/bin/docker" +elif [ -x "$DOCKERBIN" ]; then + ln -s $DOCKERBIN /usr/bin/docker +elif [ -x "$DYNDOCKERBIN" ]; then + ln -s $DYNDOCKERBIN /usr/bin/docker + ln -s $DOCKERINITBIN /usr/bin/dockerinit +else + echo >&2 'error: binary or dynbinary must be run before test-integration-cli' + false +fi + + +docker -d -D -p $DEST/docker.pid &> $DEST/docker.log & +sleep 2 +docker info +DOCKERD_PID=`cat $DEST/docker.pid` + +bundle_test_integration_cli 2>&1 \ + | tee $DEST/test.log + +kill $DOCKERD_PID +wait $DOCKERD_PID + diff --git a/integration-cli/build_tests/TestBuildSixtySteps/Dockerfile b/integration-cli/build_tests/TestBuildSixtySteps/Dockerfile new file mode 100644 index 0000000000..89b66f4f1d --- /dev/null +++ b/integration-cli/build_tests/TestBuildSixtySteps/Dockerfile @@ -0,0 +1,60 @@ +FROM busybox +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" +RUN echo "foo" diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go new file mode 100644 index 0000000000..e6f3096892 --- /dev/null +++ b/integration-cli/docker_cli_build_test.go @@ -0,0 +1,28 @@ +package main + +import ( + "fmt" + "os/exec" + "path/filepath" + "testing" +) + +func TestBuildSixtySteps(t *testing.T) { + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestBuildSixtySteps") + buildCmd := exec.Command(dockerBinary, "build", "-t", "foobuildsixtysteps", ".") + buildCmd.Dir = buildDirectory + out, exitCode, err := runCommandWithOutput(buildCmd) + errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) + + if err != nil || exitCode != 0 { + t.Fatal("failed to build the image") + } + + go deleteImages("foobuildsixtysteps") + + logDone("build - build an image with sixty build steps") +} + +// TODO: TestCaching + +// TODO: TestADDCacheInvalidation diff --git a/integration-cli/docker_cli_commit_test.go b/integration-cli/docker_cli_commit_test.go new file mode 100644 index 0000000000..5ed55ef62a --- /dev/null +++ b/integration-cli/docker_cli_commit_test.go @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + "os/exec" + "testing" +) + +func TestCommitAfterContainerIsDone(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "-i", "-a", "stdin", "busybox", "echo", "foo") + out, _, _, err := runCommandWithStdoutStderr(runCmd) + errorOut(err, t, fmt.Sprintf("failed to run container: %v %v", out, err)) + + cleanedContainerID := stripTrailingCharacters(out) + + waitCmd := exec.Command(dockerBinary, "wait", cleanedContainerID) + _, _, err = runCommandWithOutput(waitCmd) + errorOut(err, t, fmt.Sprintf("error thrown while waiting for container: %s", out)) + + commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID) + out, _, err = runCommandWithOutput(commitCmd) + errorOut(err, t, fmt.Sprintf("failed to commit container to image: %v %v", out, err)) + + cleanedImageID := stripTrailingCharacters(out) + + inspectCmd := exec.Command(dockerBinary, "inspect", cleanedImageID) + out, _, err = runCommandWithOutput(inspectCmd) + errorOut(err, t, fmt.Sprintf("failed to inspect image: %v %v", out, err)) + + go deleteContainer(cleanedContainerID) + go deleteImages(cleanedImageID) + + logDone("commit - echo foo and commit the image") +} diff --git a/integration-cli/docker_cli_diff_test.go b/integration-cli/docker_cli_diff_test.go new file mode 100644 index 0000000000..5f8ba74161 --- /dev/null +++ b/integration-cli/docker_cli_diff_test.go @@ -0,0 +1,66 @@ +package main + +import ( + "fmt" + "os/exec" + "strings" + "testing" +) + +// ensure that an added file shows up in docker diff +func TestDiffFilenameShownInOutput(t *testing.T) { + containerCmd := `echo foo > /root/bar` + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", containerCmd) + cid, _, err := runCommandWithOutput(runCmd) + errorOut(err, t, fmt.Sprintf("failed to start the container: %v", err)) + + cleanCID := stripTrailingCharacters(cid) + + diffCmd := exec.Command(dockerBinary, "diff", cleanCID) + out, _, err := runCommandWithOutput(diffCmd) + errorOut(err, t, fmt.Sprintf("failed to run diff: %v %v", out, err)) + + found := false + for _, line := range strings.Split(out, "\n") { + if strings.Contains("A /root/bar", line) { + found = true + break + } + } + if !found { + t.Errorf("couldn't find the new file in docker diff's output: %v", out) + } + go deleteContainer(cleanCID) + + logDone("diff - check if created file shows up") +} + +// test to ensure GH #3840 doesn't occur any more +func TestDiffEnsureDockerinitFilesAreIgnored(t *testing.T) { + // this is a list of files which shouldn't show up in `docker diff` + dockerinitFiles := []string{"/etc/resolv.conf", "/etc/hostname", "/etc/hosts", "/.dockerinit", "/.dockerenv"} + + // we might not run into this problem from the first run, so start a few containers + for i := 0; i < 20; i++ { + containerCmd := `echo foo > /root/bar` + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", containerCmd) + cid, _, err := runCommandWithOutput(runCmd) + errorOut(err, t, fmt.Sprintf("%s", err)) + + cleanCID := stripTrailingCharacters(cid) + + diffCmd := exec.Command(dockerBinary, "diff", cleanCID) + out, _, err := runCommandWithOutput(diffCmd) + errorOut(err, t, fmt.Sprintf("failed to run diff: %v %v", out, err)) + + go deleteContainer(cleanCID) + + for _, filename := range dockerinitFiles { + if strings.Contains(out, filename) { + t.Errorf("found file which should've been ignored %v in diff output", filename) + } + } + } + + logDone("diff - check if ignored files show up in diff") +} diff --git a/integration-cli/docker_cli_export_import_test.go b/integration-cli/docker_cli_export_import_test.go new file mode 100644 index 0000000000..66ff1055ba --- /dev/null +++ b/integration-cli/docker_cli_export_import_test.go @@ -0,0 +1,50 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "testing" +) + +// export an image and try to import it into a new one +func TestExportContainerAndImportImage(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + t.Fatal("failed to create a container", out, err) + } + + cleanedContainerID := stripTrailingCharacters(out) + + inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) + out, _, err = runCommandWithOutput(inspectCmd) + if err != nil { + t.Fatalf("output should've been a container id: %s %s ", cleanedContainerID, err) + } + + exportCmdTemplate := `%v export %v > /tmp/testexp.tar` + exportCmdFinal := fmt.Sprintf(exportCmdTemplate, dockerBinary, cleanedContainerID) + exportCmd := exec.Command("bash", "-c", exportCmdFinal) + out, _, err = runCommandWithOutput(exportCmd) + errorOut(err, t, fmt.Sprintf("failed to export container: %v %v", out, err)) + + importCmdFinal := `cat /tmp/testexp.tar | docker import - testexp` + importCmd := exec.Command("bash", "-c", importCmdFinal) + out, _, err = runCommandWithOutput(importCmd) + errorOut(err, t, fmt.Sprintf("failed to import image: %v %v", out, err)) + + cleanedImageID := stripTrailingCharacters(out) + + inspectCmd = exec.Command(dockerBinary, "inspect", cleanedImageID) + out, _, err = runCommandWithOutput(inspectCmd) + errorOut(err, t, fmt.Sprintf("output should've been an image id: %v %v", out, err)) + + go deleteImages("testexp") + go deleteContainer(cleanedContainerID) + + os.Remove("/tmp/testexp.tar") + + logDone("export - export a container") + logDone("import - import an image") +} diff --git a/integration-cli/docker_cli_images_test.go b/integration-cli/docker_cli_images_test.go new file mode 100644 index 0000000000..17efc6f5c4 --- /dev/null +++ b/integration-cli/docker_cli_images_test.go @@ -0,0 +1,20 @@ +package main + +import ( + "fmt" + "os/exec" + "strings" + "testing" +) + +func TestImagesEnsureImageIsListed(t *testing.T) { + imagesCmd := exec.Command(dockerBinary, "images") + out, _, err := runCommandWithOutput(imagesCmd) + errorOut(err, t, fmt.Sprintf("listing images failed with errors: %v", err)) + + if !strings.Contains(out, "busybox") { + t.Fatal("images should've listed busybox") + } + + logDone("images - busybox should be listed") +} diff --git a/integration-cli/docker_cli_info_test.go b/integration-cli/docker_cli_info_test.go new file mode 100644 index 0000000000..32aa3a2125 --- /dev/null +++ b/integration-cli/docker_cli_info_test.go @@ -0,0 +1,29 @@ +package main + +import ( + "fmt" + "os/exec" + "strings" + "testing" +) + +// ensure docker info succeeds +func TestInfoEnsureSucceeds(t *testing.T) { + versionCmd := exec.Command(dockerBinary, "info") + out, exitCode, err := runCommandWithOutput(versionCmd) + errorOut(err, t, fmt.Sprintf("encountered error while running docker info: %v", err)) + + if err != nil || exitCode != 0 { + t.Fatal("failed to execute docker info") + } + + stringsToCheck := []string{"Containers:", "Execution Driver:", "Kernel Version:"} + + for _, linePrefix := range stringsToCheck { + if !strings.Contains(out, linePrefix) { + t.Errorf("couldn't find string %v in output", linePrefix) + } + } + + logDone("info - verify that it works") +} diff --git a/integration-cli/docker_cli_kill_test.go b/integration-cli/docker_cli_kill_test.go new file mode 100644 index 0000000000..676ccd0ca0 --- /dev/null +++ b/integration-cli/docker_cli_kill_test.go @@ -0,0 +1,36 @@ +package main + +import ( + "fmt" + "os/exec" + "strings" + "testing" +) + +func TestKillContainer(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "sleep 10") + out, _, err := runCommandWithOutput(runCmd) + errorOut(err, t, fmt.Sprintf("run failed with errors: %v", err)) + + cleanedContainerID := stripTrailingCharacters(out) + + inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) + inspectOut, _, err := runCommandWithOutput(inspectCmd) + errorOut(err, t, fmt.Sprintf("out should've been a container id: %v %v", inspectOut, err)) + + killCmd := exec.Command(dockerBinary, "kill", cleanedContainerID) + out, _, err = runCommandWithOutput(killCmd) + errorOut(err, t, fmt.Sprintf("failed to kill container: %v %v", out, err)) + + listRunningContainersCmd := exec.Command(dockerBinary, "ps", "-q") + out, _, err = runCommandWithOutput(listRunningContainersCmd) + errorOut(err, t, fmt.Sprintf("failed to list running containers: %v", err)) + + if strings.Contains(out, cleanedContainerID) { + t.Fatal("killed container is still running") + } + + go deleteContainer(cleanedContainerID) + + logDone("kill - kill container running sleep 10") +} diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go new file mode 100644 index 0000000000..13b443f3d6 --- /dev/null +++ b/integration-cli/docker_cli_pull_test.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "os/exec" + "testing" +) + +// pulling an image from the central registry should work +func TestPullImageFromCentralRegistry(t *testing.T) { + pullCmd := exec.Command(dockerBinary, "pull", "busybox") + out, exitCode, err := runCommandWithOutput(pullCmd) + errorOut(err, t, fmt.Sprintf("%s %s", out, err)) + + if err != nil || exitCode != 0 { + t.Fatal("pulling the busybox image from the registry has failed") + } + logDone("pull - pull busybox") +} + +// pulling a non-existing image from the central registry should return a non-zero exit code +func TestPullNonExistingImage(t *testing.T) { + pullCmd := exec.Command(dockerBinary, "pull", "fooblahblah1234") + _, exitCode, err := runCommandWithOutput(pullCmd) + + if err == nil || exitCode == 0 { + t.Fatal("expected non-zero exit status when pulling non-existing image") + } + logDone("pull - pull fooblahblah1234 (non-existing image)") +} diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go new file mode 100644 index 0000000000..8117c077bc --- /dev/null +++ b/integration-cli/docker_cli_push_test.go @@ -0,0 +1,48 @@ +package main + +import ( + "fmt" + "os/exec" + "testing" +) + +// these tests need a freshly started empty private docker registry + +// pulling an image from the central registry should work +func TestPushBusyboxImage(t *testing.T) { + // skip this test until we're able to use a registry + t.Skip() + // tag the image to upload it tot he private registry + repoName := fmt.Sprintf("%v/busybox", privateRegistryURL) + tagCmd := exec.Command(dockerBinary, "tag", "busybox", repoName) + out, exitCode, err := runCommandWithOutput(tagCmd) + errorOut(err, t, fmt.Sprintf("%v %v", out, err)) + + if err != nil || exitCode != 0 { + t.Fatal("image tagging failed") + } + + pushCmd := exec.Command(dockerBinary, "push", repoName) + out, exitCode, err = runCommandWithOutput(pushCmd) + errorOut(err, t, fmt.Sprintf("%v %v", out, err)) + + go deleteImages(repoName) + + if err != nil || exitCode != 0 { + t.Fatal("pushing the image to the private registry has failed") + } + logDone("push - push busybox to private registry") +} + +// pushing an image without a prefix should throw an error +func TestPushUnprefixedRepo(t *testing.T) { + // skip this test until we're able to use a registry + t.Skip() + pushCmd := exec.Command(dockerBinary, "push", "busybox") + _, exitCode, err := runCommandWithOutput(pushCmd) + + if err == nil || exitCode == 0 { + t.Fatal("pushing an unprefixed repo didn't result in a non-zero exit status") + } + logDone("push - push unprefixed busybox repo --> must fail") +} diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go new file mode 100644 index 0000000000..12915d72ff --- /dev/null +++ b/integration-cli/docker_cli_run_test.go @@ -0,0 +1,255 @@ +package main + +import ( + "fmt" + "os/exec" + "strings" + "testing" +) + +// "test123" should be printed by docker run +func TestDockerRunEchoStdout(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "busybox", "echo", "test123") + out, _, _, err := runCommandWithStdoutStderr(runCmd) + errorOut(err, t, out) + + if out != "test123\n" { + t.Errorf("container should've printed 'test123'") + } + + deleteAllContainers() + + logDone("run - echo test123") +} + +// "test" should be printed +func TestDockerRunEchoStdoutWithMemoryLimit(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "-m", "2786432", "busybox", "echo", "test") + out, _, _, err := runCommandWithStdoutStderr(runCmd) + errorOut(err, t, out) + + if out != "test\n" { + t.Errorf("container should've printed 'test'") + + } + + deleteAllContainers() + + logDone("run - echo with memory limit") +} + +// "test" should be printed +func TestDockerRunEchoStdoutWitCPULimit(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "-c", "1000", "busybox", "echo", "test") + out, _, _, err := runCommandWithStdoutStderr(runCmd) + errorOut(err, t, out) + + if out != "test\n" { + t.Errorf("container should've printed 'test'") + } + + deleteAllContainers() + + logDone("run - echo with CPU limit") +} + +// "test" should be printed +func TestDockerRunEchoStdoutWithCPUAndMemoryLimit(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "-c", "1000", "-m", "2786432", "busybox", "echo", "test") + out, _, _, err := runCommandWithStdoutStderr(runCmd) + errorOut(err, t, out) + + if out != "test\n" { + t.Errorf("container should've printed 'test'") + } + + deleteAllContainers() + + logDone("run - echo with CPU and memory limit") +} + +// "test" should be printed +func TestDockerRunEchoNamedContainer(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "--name", "testfoonamedcontainer", "busybox", "echo", "test") + out, _, _, err := runCommandWithStdoutStderr(runCmd) + errorOut(err, t, out) + + if out != "test\n" { + t.Errorf("container should've printed 'test'") + } + + if err := deleteContainer("testfoonamedcontainer"); err != nil { + t.Errorf("failed to remove the named container: %v", err) + } + + deleteAllContainers() + + logDone("run - echo with named container") +} + +// it should be possible to ping Google DNS resolver +// this will fail when Internet access is unavailable +func TestDockerRunPingGoogle(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "busybox", "ping", "-c", "1", "8.8.8.8") + out, _, _, err := runCommandWithStdoutStderr(runCmd) + errorOut(err, t, out) + + errorOut(err, t, "container should've been able to ping 8.8.8.8") + + deleteAllContainers() + + logDone("run - ping 8.8.8.8") +} + +// the exit code should be 0 +// some versions of lxc might make this test fail +func TestDockerRunExitCodeZero(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "busybox", "true") + exitCode, err := runCommand(runCmd) + errorOut(err, t, fmt.Sprintf("%s", err)) + + if exitCode != 0 { + t.Errorf("container should've exited with exit code 0") + } + + deleteAllContainers() + + logDone("run - exit with 0") +} + +// the exit code should be 1 +// some versions of lxc might make this test fail +func TestDockerRunExitCodeOne(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "busybox", "false") + exitCode, err := runCommand(runCmd) + if err != nil && !strings.Contains("exit status 1", fmt.Sprintf("%s", err)) { + t.Fatal(err) + } + if exitCode != 1 { + t.Errorf("container should've exited with exit code 1") + } + + deleteAllContainers() + + logDone("run - exit with 1") +} + +// it should be possible to pipe in data via stdin to a process running in a container +// some versions of lxc might make this test fail +func TestRunStdinPipe(t *testing.T) { + runCmd := exec.Command("bash", "-c", `echo "blahblah" | docker run -i -a stdin busybox cat`) + out, _, _, err := runCommandWithStdoutStderr(runCmd) + errorOut(err, t, out) + + out = stripTrailingCharacters(out) + + inspectCmd := exec.Command(dockerBinary, "inspect", out) + inspectOut, _, err := runCommandWithOutput(inspectCmd) + errorOut(err, t, fmt.Sprintf("out should've been a container id: %s %s", out, inspectOut)) + + waitCmd := exec.Command(dockerBinary, "wait", out) + _, _, err = runCommandWithOutput(waitCmd) + errorOut(err, t, fmt.Sprintf("error thrown while waiting for container: %s", out)) + + logsCmd := exec.Command(dockerBinary, "logs", out) + containerLogs, _, err := runCommandWithOutput(logsCmd) + errorOut(err, t, fmt.Sprintf("error thrown while trying to get container logs: %s", err)) + + containerLogs = stripTrailingCharacters(containerLogs) + + if containerLogs != "blahblah" { + t.Errorf("logs didn't print the container's logs %s", containerLogs) + } + + rmCmd := exec.Command(dockerBinary, "rm", out) + _, _, err = runCommandWithOutput(rmCmd) + errorOut(err, t, fmt.Sprintf("rm failed to remove container %s", err)) + + deleteAllContainers() + + logDone("run - pipe in with -i -a stdin") +} + +// the container's ID should be printed when starting a container in detached mode +func TestDockerRunDetachedContainerIDPrinting(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") + out, _, _, err := runCommandWithStdoutStderr(runCmd) + errorOut(err, t, out) + + out = stripTrailingCharacters(out) + + inspectCmd := exec.Command(dockerBinary, "inspect", out) + inspectOut, _, err := runCommandWithOutput(inspectCmd) + errorOut(err, t, fmt.Sprintf("out should've been a container id: %s %s", out, inspectOut)) + + waitCmd := exec.Command(dockerBinary, "wait", out) + _, _, err = runCommandWithOutput(waitCmd) + errorOut(err, t, fmt.Sprintf("error thrown while waiting for container: %s", out)) + + rmCmd := exec.Command(dockerBinary, "rm", out) + rmOut, _, err := runCommandWithOutput(rmCmd) + errorOut(err, t, "rm failed to remove container") + + rmOut = stripTrailingCharacters(rmOut) + if rmOut != out { + t.Errorf("rm didn't print the container ID %s %s", out, rmOut) + } + + deleteAllContainers() + + logDone("run - print container ID in detached mode") +} + +// the working directory should be set correctly +func TestDockerRunWorkingDirectory(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "-w", "/root", "busybox", "pwd") + out, _, _, err := runCommandWithStdoutStderr(runCmd) + errorOut(err, t, out) + + out = stripTrailingCharacters(out) + + if out != "/root" { + t.Errorf("-w failed to set working directory") + } + + runCmd = exec.Command(dockerBinary, "run", "--workdir", "/root", "busybox", "pwd") + out, _, _, err = runCommandWithStdoutStderr(runCmd) + errorOut(err, t, out) + + out = stripTrailingCharacters(out) + + if out != "/root" { + t.Errorf("--workdir failed to set working directory") + } + + deleteAllContainers() + + logDone("run - run with working directory set by -w") + logDone("run - run with working directory set by --workdir") +} + +// pinging Google's DNS resolver should fail when we disable the networking +func TestDockerRunWithoutNetworking(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "--networking=false", "busybox", "ping", "-c", "1", "8.8.8.8") + out, _, exitCode, err := runCommandWithStdoutStderr(runCmd) + if err != nil && exitCode != 1 { + t.Fatal(out, err) + } + if exitCode != 1 { + t.Errorf("--networking=false should've disabled the network; the container shouldn't have been able to ping 8.8.8.8") + } + + runCmd = exec.Command(dockerBinary, "run", "-n=false", "busybox", "ping", "-c", "1", "8.8.8.8") + out, _, exitCode, err = runCommandWithStdoutStderr(runCmd) + if err != nil && exitCode != 1 { + t.Fatal(out, err) + } + if exitCode != 1 { + t.Errorf("-n=false should've disabled the network; the container shouldn't have been able to ping 8.8.8.8") + } + + deleteAllContainers() + + logDone("run - disable networking with --networking=false") + logDone("run - disable networking with -n=false") +} diff --git a/integration-cli/docker_cli_save_load_test.go b/integration-cli/docker_cli_save_load_test.go new file mode 100644 index 0000000000..7f04f7ca53 --- /dev/null +++ b/integration-cli/docker_cli_save_load_test.go @@ -0,0 +1,52 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "testing" +) + +// save a repo and try to load it +func TestSaveAndLoadRepo(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") + out, _, err := runCommandWithOutput(runCmd) + errorOut(err, t, fmt.Sprintf("failed to create a container: %v %v", out, err)) + + cleanedContainerID := stripTrailingCharacters(out) + + repoName := "foobar-save-load-test" + + inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) + out, _, err = runCommandWithOutput(inspectCmd) + errorOut(err, t, fmt.Sprintf("output should've been a container id: %v %v", cleanedContainerID, err)) + + commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, repoName) + out, _, err = runCommandWithOutput(commitCmd) + errorOut(err, t, fmt.Sprintf("failed to commit container: %v %v", out, err)) + + saveCmdTemplate := `%v save %v > /tmp/foobar-save-load-test.tar` + saveCmdFinal := fmt.Sprintf(saveCmdTemplate, dockerBinary, repoName) + saveCmd := exec.Command("bash", "-c", saveCmdFinal) + out, _, err = runCommandWithOutput(saveCmd) + errorOut(err, t, fmt.Sprintf("failed to save repo: %v %v", out, err)) + + deleteImages(repoName) + + loadCmdFinal := `cat /tmp/foobar-save-load-test.tar | docker load` + loadCmd := exec.Command("bash", "-c", loadCmdFinal) + out, _, err = runCommandWithOutput(loadCmd) + errorOut(err, t, fmt.Sprintf("failed to load repo: %v %v", out, err)) + + inspectCmd = exec.Command(dockerBinary, "inspect", repoName) + out, _, err = runCommandWithOutput(inspectCmd) + errorOut(err, t, fmt.Sprintf("the repo should exist after loading it: %v %v", out, err)) + + go deleteImages(repoName) + go deleteContainer(cleanedContainerID) + + os.Remove("/tmp/foobar-save-load-test.tar") + + logDone("save - save a repo") + logDone("load - load a repo") +} diff --git a/integration-cli/docker_cli_search_test.go b/integration-cli/docker_cli_search_test.go new file mode 100644 index 0000000000..050aec51a6 --- /dev/null +++ b/integration-cli/docker_cli_search_test.go @@ -0,0 +1,25 @@ +package main + +import ( + "fmt" + "os/exec" + "strings" + "testing" +) + +// search for repos named "registry" on the central registry +func TestSearchOnCentralRegistry(t *testing.T) { + searchCmd := exec.Command(dockerBinary) + out, exitCode, err := runCommandWithOutput(searchCmd) + errorOut(err, t, fmt.Sprintf("encountered error while searching: %v", err)) + + if err != nil || exitCode != 0 { + t.Fatal("failed to search on the central registry") + } + + if !strings.Contains(out, "registry") { + t.Fatal("couldn't find any repository named (or containing) 'registry'") + } + + logDone("search - search for repositories named (or containing) 'registry'") +} diff --git a/integration-cli/docker_cli_tag_test.go b/integration-cli/docker_cli_tag_test.go new file mode 100644 index 0000000000..67c28c570a --- /dev/null +++ b/integration-cli/docker_cli_tag_test.go @@ -0,0 +1,86 @@ +package main + +import ( + "fmt" + "os/exec" + "testing" +) + +// tagging a named image in a new unprefixed repo should work +func TestTagUnprefixedRepoByName(t *testing.T) { + pullCmd := exec.Command(dockerBinary, "pull", "busybox") + out, exitCode, err := runCommandWithOutput(pullCmd) + errorOut(err, t, fmt.Sprintf("%s %s", out, err)) + + if err != nil || exitCode != 0 { + t.Fatal("pulling the busybox image from the registry has failed") + } + + tagCmd := exec.Command(dockerBinary, "tag", "busybox", "testfoobarbaz") + out, _, err = runCommandWithOutput(tagCmd) + errorOut(err, t, fmt.Sprintf("%v %v", out, err)) + + deleteImages("testfoobarbaz") + + logDone("tag - busybox -> testfoobarbaz") +} + +// tagging an image by ID in a new unprefixed repo should work +func TestTagUnprefixedRepoByID(t *testing.T) { + getIDCmd := exec.Command(dockerBinary, "inspect", "-f", "{{.id}}", "busybox") + out, _, err := runCommandWithOutput(getIDCmd) + errorOut(err, t, fmt.Sprintf("failed to get the image ID of busybox: %v", err)) + + cleanedImageID := stripTrailingCharacters(out) + tagCmd := exec.Command(dockerBinary, "tag", cleanedImageID, "testfoobarbaz") + out, _, err = runCommandWithOutput(tagCmd) + errorOut(err, t, fmt.Sprintf("%s %s", out, err)) + + deleteImages("testfoobarbaz") + + logDone("tag - busybox's image ID -> testfoobarbaz") +} + +// ensure we don't allow the use of invalid tags; these tag operations should fail +func TestTagInvalidUnprefixedRepo(t *testing.T) { + // skip this until we start blocking bad tags + t.Skip() + + invalidRepos := []string{"-foo", "fo$z$", "Foo@3cc", "Foo$3", "Foo*3", "Fo^3", "Foo!3", "F)xcz(", "fo", "f"} + + for _, repo := range invalidRepos { + tagCmd := exec.Command(dockerBinary, "tag", "busybox", repo) + _, _, err := runCommandWithOutput(tagCmd) + if err == nil { + t.Errorf("tag busybox %v should have failed", repo) + continue + } + logMessage := fmt.Sprintf("tag - busybox %v --> must fail", repo) + logDone(logMessage) + } +} + +// ensure we allow the use of valid tags +func TestTagValidPrefixedRepo(t *testing.T) { + pullCmd := exec.Command(dockerBinary, "pull", "busybox") + out, exitCode, err := runCommandWithOutput(pullCmd) + errorOut(err, t, fmt.Sprintf("%s %s", out, err)) + + if err != nil || exitCode != 0 { + t.Fatal("pulling the busybox image from the registry has failed") + } + + validRepos := []string{"fooo/bar", "fooaa/test"} + + for _, repo := range validRepos { + tagCmd := exec.Command(dockerBinary, "tag", "busybox", repo) + _, _, err := runCommandWithOutput(tagCmd) + if err != nil { + t.Errorf("tag busybox %v should have worked: %s", repo, err) + continue + } + go deleteImages(repo) + logMessage := fmt.Sprintf("tag - busybox %v", repo) + logDone(logMessage) + } +} diff --git a/integration-cli/docker_cli_top_test.go b/integration-cli/docker_cli_top_test.go new file mode 100644 index 0000000000..1895054ccc --- /dev/null +++ b/integration-cli/docker_cli_top_test.go @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" + "os/exec" + "strings" + "testing" +) + +func TestTop(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "-i", "-d", "busybox", "sleep", "20") + out, _, err := runCommandWithOutput(runCmd) + errorOut(err, t, fmt.Sprintf("failed to start the container: %v", err)) + + cleanedContainerID := stripTrailingCharacters(out) + + topCmd := exec.Command(dockerBinary, "top", cleanedContainerID) + out, _, err = runCommandWithOutput(topCmd) + errorOut(err, t, fmt.Sprintf("failed to run top: %v %v", out, err)) + + killCmd := exec.Command(dockerBinary, "kill", cleanedContainerID) + _, err = runCommand(killCmd) + errorOut(err, t, fmt.Sprintf("failed to kill container: %v", err)) + + go deleteContainer(cleanedContainerID) + + if !strings.Contains(out, "sleep 20") { + t.Fatal("top should've listed sleep 20 in the process list") + } + + logDone("top - sleep process should be listed") +} diff --git a/integration-cli/docker_cli_version_test.go b/integration-cli/docker_cli_version_test.go new file mode 100644 index 0000000000..8adedd540b --- /dev/null +++ b/integration-cli/docker_cli_version_test.go @@ -0,0 +1,29 @@ +package main + +import ( + "fmt" + "os/exec" + "strings" + "testing" +) + +// ensure docker version works +func TestVersionEnsureSucceeds(t *testing.T) { + versionCmd := exec.Command(dockerBinary, "version") + out, exitCode, err := runCommandWithOutput(versionCmd) + errorOut(err, t, fmt.Sprintf("encountered error while running docker version: %v", err)) + + if err != nil || exitCode != 0 { + t.Fatal("failed to execute docker version") + } + + stringsToCheck := []string{"Client version:", "Go version (client):", "Git commit (client):", "Server version:", "Git commit (server):", "Go version (server):", "Last stable version:"} + + for _, linePrefix := range stringsToCheck { + if !strings.Contains(out, linePrefix) { + t.Errorf("couldn't find string %v in output", linePrefix) + } + } + + logDone("version - verify that it works and that the output is properly formatted") +} diff --git a/integration-cli/docker_test_vars.go b/integration-cli/docker_test_vars.go new file mode 100644 index 0000000000..f8bd5c116b --- /dev/null +++ b/integration-cli/docker_test_vars.go @@ -0,0 +1,29 @@ +package main + +import ( + "os" +) + +// the docker binary to use +var dockerBinary = "docker" + +// the private registry image to use for tests involving the registry +var registryImageName = "registry" + +// the private registry to use for tests +var privateRegistryURL = "127.0.0.1:5000" + +var workingDirectory string + +func init() { + if dockerBin := os.Getenv("DOCKER_BINARY"); dockerBin != "" { + dockerBinary = dockerBin + } + if registryImage := os.Getenv("REGISTRY_IMAGE"); registryImage != "" { + registryImageName = registryImage + } + if registry := os.Getenv("REGISTRY_URL"); registry != "" { + privateRegistryURL = registry + } + workingDirectory, _ = os.Getwd() +} diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go new file mode 100644 index 0000000000..8e9d0a23ff --- /dev/null +++ b/integration-cli/docker_utils.go @@ -0,0 +1,56 @@ +package main + +import ( + "fmt" + "os/exec" + "strings" +) + +func deleteContainer(container string) error { + container = strings.Replace(container, "\n", " ", -1) + container = strings.Trim(container, " ") + rmArgs := fmt.Sprintf("rm %v", container) + rmSplitArgs := strings.Split(rmArgs, " ") + rmCmd := exec.Command(dockerBinary, rmSplitArgs...) + exitCode, err := runCommand(rmCmd) + // set error manually if not set + if exitCode != 0 && err == nil { + err = fmt.Errorf("failed to remove container: `docker rm` exit is non-zero") + } + + return err +} + +func getAllContainers() (string, error) { + getContainersCmd := exec.Command(dockerBinary, "ps", "-q", "-a") + out, exitCode, err := runCommandWithOutput(getContainersCmd) + if exitCode != 0 && err == nil { + err = fmt.Errorf("failed to get a list of containers: %v\n", out) + } + + return out, err +} + +func deleteAllContainers() error { + containers, err := getAllContainers() + if err != nil { + fmt.Println(containers) + return err + } + + if err = deleteContainer(containers); err != nil { + return err + } + return nil +} + +func deleteImages(images string) error { + rmiCmd := exec.Command(dockerBinary, "rmi", images) + exitCode, err := runCommand(rmiCmd) + // set error manually if not set + if exitCode != 0 && err == nil { + err = fmt.Errorf("failed to remove image: `docker rmi` exit is non-zero") + } + + return err +} diff --git a/integration-cli/utils.go b/integration-cli/utils.go new file mode 100644 index 0000000000..680cc6cfcf --- /dev/null +++ b/integration-cli/utils.go @@ -0,0 +1,109 @@ +package main + +import ( + "bytes" + "fmt" + "io" + "os/exec" + "strings" + "syscall" + "testing" +) + +func getExitCode(err error) (int, error) { + exitCode := 0 + if exiterr, ok := err.(*exec.ExitError); ok { + if procExit := exiterr.Sys().(syscall.WaitStatus); ok { + return procExit.ExitStatus(), nil + } + } + return exitCode, fmt.Errorf("failed to get exit code") +} + +func runCommandWithOutput(cmd *exec.Cmd) (output string, exitCode int, err error) { + exitCode = 0 + out, err := cmd.CombinedOutput() + if err != nil { + var exiterr error + if exitCode, exiterr = getExitCode(err); exiterr != nil { + // TODO: Fix this so we check the error's text. + // we've failed to retrieve exit code, so we set it to 127 + exitCode = 127 + } + } + output = string(out) + return +} + +func runCommandWithStdoutStderr(cmd *exec.Cmd) (stdout string, stderr string, exitCode int, err error) { + exitCode = 0 + var stderrBuffer bytes.Buffer + stderrPipe, err := cmd.StderrPipe() + if err != nil { + return "", "", -1, err + } + go io.Copy(&stderrBuffer, stderrPipe) + out, err := cmd.Output() + + if err != nil { + var exiterr error + if exitCode, exiterr = getExitCode(err); exiterr != nil { + // TODO: Fix this so we check the error's text. + // we've failed to retrieve exit code, so we set it to 127 + exitCode = 127 + } + } + stdout = string(out) + stderr = string(stderrBuffer.Bytes()) + return +} + +func runCommand(cmd *exec.Cmd) (exitCode int, err error) { + exitCode = 0 + err = cmd.Run() + if err != nil { + var exiterr error + if exitCode, exiterr = getExitCode(err); exiterr != nil { + // TODO: Fix this so we check the error's text. + // we've failed to retrieve exit code, so we set it to 127 + exitCode = 127 + } + } + return +} + +func startCommand(cmd *exec.Cmd) (exitCode int, err error) { + exitCode = 0 + err = cmd.Start() + if err != nil { + var exiterr error + if exitCode, exiterr = getExitCode(err); exiterr != nil { + // TODO: Fix this so we check the error's text. + // we've failed to retrieve exit code, so we set it to 127 + exitCode = 127 + } + } + return +} + +func logDone(message string) { + fmt.Printf("[PASSED]: %s\n", message) +} + +func stripTrailingCharacters(target string) string { + target = strings.Trim(target, "\n") + target = strings.Trim(target, " ") + return target +} + +func errorOut(err error, t *testing.T, message string) { + if err != nil { + t.Fatal(message) + } +} + +func errorOutOnNonNilError(err error, t *testing.T, message string) { + if err == nil { + t.Fatalf(message) + } +} From 3fb1fc0b7b225295b3059cb9a2f5fd9af7a73f36 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 20 Mar 2014 16:46:55 -0600 Subject: [PATCH 243/384] Small tweaks to the hack scripts to make them simpler Please do with this as you please (including rebasing and/or squashing it), especially under clause (c) of the DCO. Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/make/binary | 1 + hack/make/test-integration-cli | 23 +++++++---------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/hack/make/binary b/hack/make/binary index 7b4d7b5b5b..041e4d1ee8 100755 --- a/hack/make/binary +++ b/hack/make/binary @@ -11,5 +11,6 @@ go build \ " \ ./docker echo "Created binary: $DEST/docker-$VERSION" +ln -sf "docker-$VERSION" "$DEST/docker" hash_files "$DEST/docker-$VERSION" diff --git a/hack/make/test-integration-cli b/hack/make/test-integration-cli index 5ab37a4021..d007fbaf6a 100644 --- a/hack/make/test-integration-cli +++ b/hack/make/test-integration-cli @@ -1,37 +1,28 @@ #!/bin/bash DEST=$1 -DOCKERBIN=$DEST/../binary/docker-$VERSION -DYNDOCKERBIN=$DEST/../dynbinary/docker-$VERSION -DOCKERINITBIN=$DEST/../dynbinary/dockerinit-$VERSION set -e +# subshell so that we can export PATH without breaking other things +( +export PATH="$DEST/../binary:$DEST/../dynbinary:$PATH" + bundle_test_integration_cli() { go_test_dir ./integration-cli } -if [ -x "/usr/bin/docker" ]; then - echo "docker found at /usr/bin/docker" -elif [ -x "$DOCKERBIN" ]; then - ln -s $DOCKERBIN /usr/bin/docker -elif [ -x "$DYNDOCKERBIN" ]; then - ln -s $DYNDOCKERBIN /usr/bin/docker - ln -s $DOCKERINITBIN /usr/bin/dockerinit -else +if ! command -v docker &> /dev/null; then echo >&2 'error: binary or dynbinary must be run before test-integration-cli' false fi - docker -d -D -p $DEST/docker.pid &> $DEST/docker.log & -sleep 2 -docker info -DOCKERD_PID=`cat $DEST/docker.pid` bundle_test_integration_cli 2>&1 \ | tee $DEST/test.log +DOCKERD_PID=$(cat $DEST/docker.pid) kill $DOCKERD_PID wait $DOCKERD_PID - +) From 04d1e686398fff0784a47cb85c37db629d40f5b5 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Mon, 31 Mar 2014 11:05:21 +1000 Subject: [PATCH 244/384] OSX mktemp is different - hopfully this will now work on HP/UX >:} Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/installation/mac.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/mac.rst b/docs/sources/installation/mac.rst index f4c771cf9f..9ce3961f7e 100644 --- a/docs/sources/installation/mac.rst +++ b/docs/sources/installation/mac.rst @@ -66,7 +66,7 @@ Run the following commands to get it downloaded and set up: .. code-block:: bash # Get the docker client file - DIR=$(mktemp -d) && \ + DIR=$(mktemp -d ${TMPDIR:-/tmp}/dockerdl.XXXXXXX) && \ curl -f -o $DIR/ld.tgz https://get.docker.io/builds/Darwin/x86_64/docker-latest.tgz && \ gunzip $DIR/ld.tgz && \ tar xvf $DIR/ld.tar -C $DIR/ && \ From a2487aa683dc84938eb94c1ae29f8160d09441ea Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 31 Mar 2014 09:07:56 -0700 Subject: [PATCH 245/384] Reduce error level form harmless errors Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- api/client.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/api/client.go b/api/client.go index df3265a15a..86858d0b30 100644 --- a/api/client.go +++ b/api/client.go @@ -2374,11 +2374,11 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea } if tcpc, ok := rwc.(*net.TCPConn); ok { if err := tcpc.CloseWrite(); err != nil { - utils.Errorf("Couldn't send EOF: %s\n", err) + utils.Debugf("Couldn't send EOF: %s\n", err) } } else if unixc, ok := rwc.(*net.UnixConn); ok { if err := unixc.CloseWrite(); err != nil { - utils.Errorf("Couldn't send EOF: %s\n", err) + utils.Debugf("Couldn't send EOF: %s\n", err) } } // Discard errors due to pipe interruption @@ -2387,14 +2387,14 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea if stdout != nil || stderr != nil { if err := <-receiveStdout; err != nil { - utils.Errorf("Error receiveStdout: %s", err) + utils.Debugf("Error receiveStdout: %s", err) return err } } if !cli.isTerminal { if err := <-sendStdin; err != nil { - utils.Errorf("Error sendStdin: %s", err) + utils.Debugf("Error sendStdin: %s", err) return err } } @@ -2408,7 +2408,7 @@ func (cli *DockerCli) getTtySize() (int, int) { } ws, err := term.GetWinsize(cli.terminalFd) if err != nil { - utils.Errorf("Error getting size: %s", err) + utils.Debugf("Error getting size: %s", err) if ws == nil { return 0, 0 } @@ -2425,7 +2425,7 @@ func (cli *DockerCli) resizeTty(id string) { v.Set("h", strconv.Itoa(height)) v.Set("w", strconv.Itoa(width)) if _, _, err := readBody(cli.call("POST", "/containers/"+id+"/resize?"+v.Encode(), nil, false)); err != nil { - utils.Errorf("Error resize: %s", err) + utils.Debugf("Error resize: %s", err) } } From a57900e35f2c30026a070fdfdbdb0ce99b35e1ff Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 18 Mar 2014 15:28:40 -0700 Subject: [PATCH 246/384] Allow volumes from to be individual files Fixes #4741 Right now volumes from expected a dir and not a file so when the drivers tried to do the bind mount, the destination was a dir, not a file so it fails to run. Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runtime/volumes.go | 48 ++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/runtime/volumes.go b/runtime/volumes.go index 1bbb14a369..5ac82ef089 100644 --- a/runtime/volumes.go +++ b/runtime/volumes.go @@ -88,7 +88,11 @@ func applyVolumesFrom(container *Container) error { if _, exists := container.Volumes[volPath]; exists { continue } - if err := os.MkdirAll(filepath.Join(container.basefs, volPath), 0755); err != nil { + stat, err := os.Stat(filepath.Join(c.basefs, volPath)) + if err != nil { + return err + } + if err := createIfNotExists(filepath.Join(container.basefs, volPath), stat.IsDir()); err != nil { return err } container.Volumes[volPath] = id @@ -208,24 +212,8 @@ func createVolumes(container *Container) error { if err != nil { return err } - - if _, err := os.Stat(rootVolPath); err != nil { - if os.IsNotExist(err) { - if volIsDir { - if err := os.MkdirAll(rootVolPath, 0755); err != nil { - return err - } - } else { - if err := os.MkdirAll(filepath.Dir(rootVolPath), 0755); err != nil { - return err - } - if f, err := os.OpenFile(rootVolPath, os.O_CREATE, 0755); err != nil { - return err - } else { - f.Close() - } - } - } + if err := createIfNotExists(rootVolPath, volIsDir); err != nil { + return err } // Do not copy or change permissions if we are mounting from the host @@ -266,3 +254,25 @@ func createVolumes(container *Container) error { } return nil } + +func createIfNotExists(path string, isDir bool) error { + if _, err := os.Stat(path); err != nil { + if os.IsNotExist(err) { + if isDir { + if err := os.MkdirAll(path, 0755); err != nil { + return err + } + } else { + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + return err + } + f, err := os.OpenFile(path, os.O_CREATE, 0755) + if err != nil { + return err + } + defer f.Close() + } + } + } + return nil +} From 28015f8e579e7bbe396f65b3343188ca03b06cbd Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 31 Mar 2014 17:41:40 +0000 Subject: [PATCH 247/384] Add integration test for volumes-from as file Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- integration-cli/docker_cli_run_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 12915d72ff..13959adea7 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -253,3 +253,21 @@ func TestDockerRunWithoutNetworking(t *testing.T) { logDone("run - disable networking with --networking=false") logDone("run - disable networking with -n=false") } + +// Regression test for #4741 +func TestDockerRunWithVolumesAsFiles(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "--name", "test-data", "--volume", "/etc/hosts:/target-file", "busybox", "true") + out, stderr, exitCode, err := runCommandWithStdoutStderr(runCmd) + if err != nil && exitCode != 0 { + t.Fatal("1", out, stderr, err) + } + + runCmd = exec.Command(dockerBinary, "run", "--volumes-from", "test-data", "busybox", "cat", "/target-file") + out, stderr, exitCode, err = runCommandWithStdoutStderr(runCmd) + if err != nil && exitCode != 0 { + t.Fatal("2", out, stderr, err) + } + deleteAllContainers() + + logDone("run - regression test for #4741 - volumes from as files") +} From 7808886744595af509b7b144890900674ea5ccfd Mon Sep 17 00:00:00 2001 From: Johannes 'fish' Ziemke Date: Mon, 31 Mar 2014 13:14:56 +0200 Subject: [PATCH 248/384] Add more women MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added Adele Goldstine, Erna Schneider Hoover, Grace Hopper, Jean Bartik, Jean E. Sammet, Karen Spärck Jones, Radia Perlman and Sophie Wilson. Thanks to @jamtur01 for Sophie Kowalevski, Hypatia, Jane Goodall, Maria Mayer, Rosalind Franklin, Gertrude Elion, Elizabeth Blackwell, Marie-Jeanne de Lalande, Maria Kirch, Maria Ardinghelli, Jane Colden, June Almeida, Mary Leakey, Lise Meitner, Johanna Mestorf. Thanks to @xamebax for Françoise Barré-Sinoussi, Rachel Carson, Barbara McClintock, Ada Yonath. Docker-DCO-1.1-Signed-off-by: Johannes 'fish' Ziemke (github: discordianfish) --- pkg/namesgenerator/names-generator.go | 29 ++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/pkg/namesgenerator/names-generator.go b/pkg/namesgenerator/names-generator.go index dfece5d611..78f4d07358 100644 --- a/pkg/namesgenerator/names-generator.go +++ b/pkg/namesgenerator/names-generator.go @@ -15,33 +15,60 @@ var ( // Docker 0.7.x generates names from notable scientists and hackers. // // Ada Lovelace invented the first algorithm. http://en.wikipedia.org/wiki/Ada_Lovelace (thanks James Turnbull) + // Ada Yonath - an Israeli crystallographer, the first woman from the Middle East to win a Nobel prize in the sciences. http://en.wikipedia.org/wiki/Ada_Yonath + // Adele Goldstine, born Adele Katz, wrote the complete technical description for the first electronic digital computer, ENIAC. http://en.wikipedia.org/wiki/Adele_Goldstine // Alan Turing was a founding father of computer science. http://en.wikipedia.org/wiki/Alan_Turing. // Albert Einstein invented the general theory of relativity. http://en.wikipedia.org/wiki/Albert_Einstein // Ambroise Pare invented modern surgery. http://en.wikipedia.org/wiki/Ambroise_Par%C3%A9 // Archimedes was a physicist, engineer and mathematician who invented too many things to list them here. http://en.wikipedia.org/wiki/Archimedes + // Barbara McClintock - a distinguished American cytogeneticist, 1983 Nobel Laureate in Physiology or Medicine for discovering transposons. http://en.wikipedia.org/wiki/Barbara_McClintock // Benjamin Franklin is famous for his experiments in electricity and the invention of the lightning rod. // Charles Babbage invented the concept of a programmable computer. http://en.wikipedia.org/wiki/Charles_Babbage. // Charles Darwin established the principles of natural evolution. http://en.wikipedia.org/wiki/Charles_Darwin. // Dennis Ritchie and Ken Thompson created UNIX and the C programming language. http://en.wikipedia.org/wiki/Dennis_Ritchie http://en.wikipedia.org/wiki/Ken_Thompson // Douglas Engelbart gave the mother of all demos: http://en.wikipedia.org/wiki/Douglas_Engelbart + // Elizabeth Blackwell - American doctor and first American woman to receive a medical degree - http://en.wikipedia.org/wiki/Elizabeth_Blackwell // Emmett Brown invented time travel. http://en.wikipedia.org/wiki/Emmett_Brown (thanks Brian Goff) // Enrico Fermi invented the first nuclear reactor. http://en.wikipedia.org/wiki/Enrico_Fermi. + // Erna Schneider Hoover revolutionized modern communication by inventing a computerized telephon switching method. http://en.wikipedia.org/wiki/Erna_Schneider_Hoover // Euclid invented geometry. http://en.wikipedia.org/wiki/Euclid + // Françoise Barré-Sinoussi - French virologist and Nobel Prize Laureate in Physiology or Medicine; her work was fundamental in identifying HIV as the cause of AIDS. http://en.wikipedia.org/wiki/Fran%C3%A7oise_Barr%C3%A9-Sinoussi // Galileo was a founding father of modern astronomy, and faced politics and obscurantism to establish scientific truth. http://en.wikipedia.org/wiki/Galileo_Galilei + // Gertrude Elion - American biochemist, pharmacologist and the 1988 recipient of the Nobel Prize in Medicine - http://en.wikipedia.org/wiki/Gertrude_Elion + // Grace Hopper developed the first compiler for a computer programming language and is credited with popularizing the term "debugging" for fixing computer glitches. http://en.wikipedia.org/wiki/Grace_Hopper // Henry Poincare made fundamental contributions in several fields of mathematics. http://en.wikipedia.org/wiki/Henri_Poincar%C3%A9 + // Hypatia - Greek Alexandrine Neoplatonist philosopher in Egypt who was one of the earliest mothers of mathematics - http://en.wikipedia.org/wiki/Hypatia // Isaac Newton invented classic mechanics and modern optics. http://en.wikipedia.org/wiki/Isaac_Newton + // Jane Colden - American botanist widely considered the first female American botanist - http://en.wikipedia.org/wiki/Jane_Colden + // Jane Goodall - British primatologist, ethologist, and anthropologist who is considered to be the world's foremost expert on chimpanzees - http://en.wikipedia.org/wiki/Jane_Goodall + // Jean Bartik, born Betty Jean Jennings, was one of the original programmers for the ENIAC computer. http://en.wikipedia.org/wiki/Jean_Bartik + // Jean E. Sammet developed FORMAC, the first widely used computer language for symbolic manipulation of mathematical formulas. http://en.wikipedia.org/wiki/Jean_E._Sammet + // Johanna Mestorf - German prehistoric archaeologist and first female museum director in Germany - http://en.wikipedia.org/wiki/Johanna_Mestorf // John McCarthy invented LISP: http://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist) + // June Almeida - Scottish virologist who took the first pictures of the rubella virus - http://en.wikipedia.org/wiki/June_Almeida + // Karen Spärck Jones came up with the concept of inverse document frequency, which is used in most search engines today. http://en.wikipedia.org/wiki/Karen_Sp%C3%A4rck_Jones // Leonardo Da Vinci invented too many things to list here. http://en.wikipedia.org/wiki/Leonardo_da_Vinci. // Linus Torvalds invented Linux and Git. http://en.wikipedia.org/wiki/Linus_Torvalds + // Lise Meitner - Austrian/Swedish physicist who was involved in the discovery of nuclear fission. The element meitnerium is named after her - http://en.wikipedia.org/wiki/Lise_Meitner // Louis Pasteur discovered vaccination, fermentation and pasteurization. http://en.wikipedia.org/wiki/Louis_Pasteur. // Malcolm McLean invented the modern shipping container: http://en.wikipedia.org/wiki/Malcom_McLean + // Maria Ardinghelli - Italian translator, mathematician and physicist - http://en.wikipedia.org/wiki/Maria_Ardinghelli + // Maria Kirch - German astronomer and first woman to discover a comet - http://en.wikipedia.org/wiki/Maria_Margarethe_Kirch + // Maria Mayer - American theoretical physicist and Nobel laureate in Physics for proposing the nuclear shell model of the atomic nucleus - http://en.wikipedia.org/wiki/Maria_Mayer // Marie Curie discovered radioactivity. http://en.wikipedia.org/wiki/Marie_Curie. + // Marie-Jeanne de Lalande - French astronomer, mathematician and cataloguer of stars - http://en.wikipedia.org/wiki/Marie-Jeanne_de_Lalande + // Mary Leakey - British paleoanthropologist who discovered the first fossilized Proconsul skull - http://en.wikipedia.org/wiki/Mary_Leakey // Muhammad ibn Jābir al-Ḥarrānī al-Battānī was a founding father of astronomy. http://en.wikipedia.org/wiki/Mu%E1%B8%A5ammad_ibn_J%C4%81bir_al-%E1%B8%A4arr%C4%81n%C4%AB_al-Batt%C4%81n%C4%AB // Niels Bohr is the father of quantum theory. http://en.wikipedia.org/wiki/Niels_Bohr. // Nikola Tesla invented the AC electric system and every gaget ever used by a James Bond villain. http://en.wikipedia.org/wiki/Nikola_Tesla // Pierre de Fermat pioneered several aspects of modern mathematics. http://en.wikipedia.org/wiki/Pierre_de_Fermat + // Rachel Carson - American marine biologist and conservationist, her book Silent Spring and other writings are credited with advancing the global environmental movement. http://en.wikipedia.org/wiki/Rachel_Carson + // Radia Perlman is a software designer and network engineer and most famous for her invention of the spanning-tree protocol (STP). http://en.wikipedia.org/wiki/Radia_Perlman // Richard Feynman was a key contributor to quantum mechanics and particle physics. http://en.wikipedia.org/wiki/Richard_Feynman // Rob Pike was a key contributor to Unix, Plan 9, the X graphic system, utf-8, and the Go programming language. http://en.wikipedia.org/wiki/Rob_Pike + // Rosalind Franklin - British biophysicist and X-ray crystallographer whose research was critical to the understanding of DNA - http://en.wikipedia.org/wiki/Rosalind_Franklin + // Sophie Kowalevski - Russian mathematician responsible for important original contributions to analysis, differential equations and mechanics - http://en.wikipedia.org/wiki/Sofia_Kovalevskaya + // Sophie Wilson designed the first Acorn Micro-Computer and the instruction set for ARM processors. http://en.wikipedia.org/wiki/Sophie_Wilson // Stephen Hawking pioneered the field of cosmology by combining general relativity and quantum mechanics. http://en.wikipedia.org/wiki/Stephen_Hawking // Steve Wozniak invented the Apple I and Apple II. http://en.wikipedia.org/wiki/Steve_Wozniak // Werner Heisenberg was a founding father of quantum mechanics. http://en.wikipedia.org/wiki/Werner_Heisenberg @@ -49,7 +76,7 @@ var ( // http://en.wikipedia.org/wiki/John_Bardeen // http://en.wikipedia.org/wiki/Walter_Houser_Brattain // http://en.wikipedia.org/wiki/William_Shockley - right = [...]string{"lovelace", "franklin", "tesla", "einstein", "bohr", "davinci", "pasteur", "nobel", "curie", "darwin", "turing", "ritchie", "torvalds", "pike", "thompson", "wozniak", "galileo", "euclid", "newton", "fermat", "archimedes", "poincare", "heisenberg", "feynman", "hawking", "fermi", "pare", "mccarthy", "engelbart", "babbage", "albattani", "ptolemy", "bell", "wright", "lumiere", "morse", "mclean", "brown", "bardeen", "brattain", "shockley"} + right = [...]string{"lovelace", "franklin", "tesla", "einstein", "bohr", "davinci", "pasteur", "nobel", "curie", "darwin", "turing", "ritchie", "torvalds", "pike", "thompson", "wozniak", "galileo", "euclid", "newton", "fermat", "archimedes", "poincare", "heisenberg", "feynman", "hawking", "fermi", "pare", "mccarthy", "engelbart", "babbage", "albattani", "ptolemy", "bell", "wright", "lumiere", "morse", "mclean", "brown", "bardeen", "brattain", "shockley", "goldstine", "hoover", "hopper", "bartik", "sammet", "jones", "perlman", "wilson", "kowalevski", "hypatia", "goodall", "mayer", "elion", "blackwell", "lalande", "kirch", "ardinghelli", "colden", "almeida", "leakey", "meitner", "mestorf", "rosalind", "sinoussi", "carson", "mcmclintock", "yonath"} ) func GenerateRandomName(checker NameChecker) (string, error) { From e4aaacc2351d2e1dd4b69afeeee2aeab9c625efe Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 31 Mar 2014 10:49:48 -0700 Subject: [PATCH 249/384] Fix expending buffer in StdCopy Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- utils/stdcopy.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/utils/stdcopy.go b/utils/stdcopy.go index 3cb8ab02b3..8b43386140 100644 --- a/utils/stdcopy.go +++ b/utils/stdcopy.go @@ -108,12 +108,13 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) // Retrieve the size of the frame frameSize = int(binary.BigEndian.Uint32(buf[StdWriterSizeIndex : StdWriterSizeIndex+4])) + Debugf("framesize: %d", frameSize) // Check if the buffer is big enough to read the frame. // Extend it if necessary. if frameSize+StdWriterPrefixLen > bufLen { - Debugf("Extending buffer cap.") - buf = append(buf, make([]byte, frameSize-len(buf)+1)...) + Debugf("Extending buffer cap by %d (was %d)", frameSize+StdWriterPrefixLen-bufLen+1, len(buf)) + buf = append(buf, make([]byte, frameSize+StdWriterPrefixLen-bufLen+1)...) bufLen = len(buf) } From 2543912e7b5593722a6a22b9ceb6a23f6268e397 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 31 Mar 2014 11:55:55 -0600 Subject: [PATCH 250/384] Add "test-integration-cli" to our DEFAULT_BUNDLES list (make all) Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/make.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/hack/make.sh b/hack/make.sh index 447a00f039..e81271370d 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -43,6 +43,7 @@ DEFAULT_BUNDLES=( binary test test-integration + test-integration-cli dynbinary dyntest dyntest-integration From a7365a6237c45ed05d96ab11f36fde35d675b462 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 28 Mar 2014 22:59:29 +0000 Subject: [PATCH 251/384] split API into 2 go packages Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api/{ => client}/client.go | 39 +++++++++++++++++++++----------------- api/common.go | 2 +- api/{ => server}/server.go | 32 ++++++++++++++++--------------- builtins/builtins.go | 2 +- docker/docker.go | 7 ++++--- 5 files changed, 45 insertions(+), 37 deletions(-) rename api/{ => client}/client.go (98%) rename api/{ => server}/server.go (98%) diff --git a/api/client.go b/api/client/client.go similarity index 98% rename from api/client.go rename to api/client/client.go index 86858d0b30..29b49464c4 100644 --- a/api/client.go +++ b/api/client/client.go @@ -1,4 +1,4 @@ -package api +package client import ( "bufio" @@ -8,6 +8,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/dotcloud/docker/api" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" @@ -81,7 +82,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { return nil } } - help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=[unix://%s]: tcp://host:port to bind/connect to or unix://path/to/socket to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", DEFAULTUNIXSOCKET) + help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=[unix://%s]: tcp://host:port to bind/connect to or unix://path/to/socket to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", api.DEFAULTUNIXSOCKET) for _, command := range [][]string{ {"attach", "Attach to a running container"}, {"build", "Build a container from a Dockerfile"}, @@ -610,7 +611,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { return err } - container := &Container{} + container := &api.Container{} err = json.Unmarshal(body, container) if err != nil { return err @@ -797,9 +798,13 @@ func (cli *DockerCli) CmdPort(args ...string) error { return nil } - port := cmd.Arg(1) - proto := "tcp" - parts := strings.SplitN(port, "/", 2) + var ( + port = cmd.Arg(1) + proto = "tcp" + parts = strings.SplitN(port, "/", 2) + container api.Container + ) + if len(parts) == 2 && len(parts[1]) != 0 { port = parts[0] proto = parts[1] @@ -808,13 +813,13 @@ func (cli *DockerCli) CmdPort(args ...string) error { if err != nil { return err } - var out Container - err = json.Unmarshal(body, &out) + + err = json.Unmarshal(body, &container) if err != nil { return err } - if frontends, exists := out.NetworkSettings.Ports[nat.Port(port+"/"+proto)]; exists && frontends != nil { + if frontends, exists := container.NetworkSettings.Ports[nat.Port(port+"/"+proto)]; exists && frontends != nil { for _, frontend := range frontends { fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort) } @@ -1425,7 +1430,7 @@ func (cli *DockerCli) CmdPs(args ...string) error { outCommand = utils.Trunc(outCommand, 20) } ports.ReadListFrom([]byte(out.Get("Ports"))) - fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, out.Get("Image"), outCommand, utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), out.Get("Status"), displayablePorts(ports), strings.Join(outNames, ",")) + fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, out.Get("Image"), outCommand, utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), out.Get("Status"), api.DisplayablePorts(ports), strings.Join(outNames, ",")) if *size { if out.GetInt("SizeRootFs") > 0 { fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.GetInt64("SizeRw")), utils.HumanSize(out.GetInt64("SizeRootFs"))) @@ -1606,7 +1611,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error { return err } - container := &Container{} + container := &api.Container{} err = json.Unmarshal(body, container) if err != nil { return err @@ -1643,7 +1648,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error { return err } - container := &Container{} + container := &api.Container{} err = json.Unmarshal(body, container) if err != nil { return err @@ -2159,7 +2164,7 @@ func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo b re := regexp.MustCompile("/+") path = re.ReplaceAllString(path, "/") - req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", APIVERSION, path), params) + req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params) if err != nil { return nil, -1, err } @@ -2236,7 +2241,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h re := regexp.MustCompile("/+") path = re.ReplaceAllString(path, "/") - req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", APIVERSION, path), in) + req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), in) if err != nil { return err } @@ -2281,7 +2286,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h return fmt.Errorf("Error: %s", bytes.TrimSpace(body)) } - if MatchesContentType(resp.Header.Get("Content-Type"), "application/json") { + if api.MatchesContentType(resp.Header.Get("Content-Type"), "application/json") { return utils.DisplayJSONMessagesStream(resp.Body, out, cli.terminalFd, cli.isTerminal) } if _, err := io.Copy(out, resp.Body); err != nil { @@ -2300,7 +2305,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea re := regexp.MustCompile("/+") path = re.ReplaceAllString(path, "/") - req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", APIVERSION, path), nil) + req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), nil) if err != nil { return err } @@ -2484,7 +2489,7 @@ func getExitCode(cli *DockerCli, containerId string) (bool, int, error) { } return false, -1, nil } - c := &Container{} + c := &api.Container{} if err := json.Unmarshal(body, c); err != nil { return false, -1, err } diff --git a/api/common.go b/api/common.go index 5e5d2c5767..7273e5c56d 100644 --- a/api/common.go +++ b/api/common.go @@ -23,7 +23,7 @@ func ValidateHost(val string) (string, error) { } //TODO remove, used on < 1.5 in getContainersJSON -func displayablePorts(ports *engine.Table) string { +func DisplayablePorts(ports *engine.Table) string { result := []string{} ports.SetKey("PublicPort") ports.Sort() diff --git a/api/server.go b/api/server/server.go similarity index 98% rename from api/server.go rename to api/server/server.go index 29ea180030..18aefe42cd 100644 --- a/api/server.go +++ b/api/server/server.go @@ -1,4 +1,4 @@ -package api +package server import ( "bufio" @@ -10,14 +10,6 @@ import ( "encoding/json" "expvar" "fmt" - "github.com/dotcloud/docker/engine" - "github.com/dotcloud/docker/pkg/listenbuffer" - "github.com/dotcloud/docker/pkg/systemd" - "github.com/dotcloud/docker/pkg/user" - "github.com/dotcloud/docker/pkg/version" - "github.com/dotcloud/docker/registry" - "github.com/dotcloud/docker/utils" - "github.com/gorilla/mux" "io" "io/ioutil" "log" @@ -28,6 +20,16 @@ import ( "strconv" "strings" "syscall" + + "github.com/dotcloud/docker/api" + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/pkg/listenbuffer" + "github.com/dotcloud/docker/pkg/systemd" + "github.com/dotcloud/docker/pkg/user" + "github.com/dotcloud/docker/pkg/version" + "github.com/dotcloud/docker/registry" + "github.com/dotcloud/docker/utils" + "github.com/gorilla/mux" ) var ( @@ -315,7 +317,7 @@ func getContainersJSON(eng *engine.Engine, version version.Version, w http.Respo for _, out := range outs.Data { ports := engine.NewTable("", 0) ports.ReadListFrom([]byte(out.Get("Ports"))) - out.Set("Ports", displayablePorts(ports)) + out.Set("Ports", api.DisplayablePorts(ports)) } w.Header().Set("Content-Type", "application/json") if _, err = outs.WriteListTo(w); err != nil { @@ -638,7 +640,7 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res job := eng.Job("start", name) // allow a nil body for backwards compatibility if r.Body != nil { - if MatchesContentType(r.Header.Get("Content-Type"), "application/json") { + if api.MatchesContentType(r.Header.Get("Content-Type"), "application/json") { if err := job.DecodeEnv(r.Body); err != nil { return err } @@ -885,7 +887,7 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp var copyData engine.Env - if contentType := r.Header.Get("Content-Type"); MatchesContentType(contentType, "application/json") { + if contentType := r.Header.Get("Content-Type"); api.MatchesContentType(contentType, "application/json") { if err := copyData.Decode(r.Body); err != nil { return err } @@ -943,14 +945,14 @@ func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, local } version := version.Version(mux.Vars(r)["version"]) if version == "" { - version = APIVERSION + version = api.APIVERSION } if enableCors { writeCorsHeaders(w, r) } - if version.GreaterThan(APIVERSION) { - http.Error(w, fmt.Errorf("client and server don't have same version (client : %s, server: %s)", version, APIVERSION).Error(), http.StatusNotFound) + if version.GreaterThan(api.APIVERSION) { + http.Error(w, fmt.Errorf("client and server don't have same version (client : %s, server: %s)", version, api.APIVERSION).Error(), http.StatusNotFound) return } diff --git a/builtins/builtins.go b/builtins/builtins.go index 10ee9b19e6..109bc5b913 100644 --- a/builtins/builtins.go +++ b/builtins/builtins.go @@ -1,7 +1,7 @@ package builtins import ( - "github.com/dotcloud/docker/api" + api "github.com/dotcloud/docker/api/server" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/runtime/networkdriver/bridge" "github.com/dotcloud/docker/server" diff --git a/docker/docker.go b/docker/docker.go index e4ce8a0b74..e96c173d30 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/dotcloud/docker/api" + "github.com/dotcloud/docker/api/client" "github.com/dotcloud/docker/builtins" "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" @@ -178,7 +179,7 @@ func main() { protoAddrParts := strings.SplitN(flHosts.GetAll()[0], "://", 2) var ( - cli *api.DockerCli + cli *client.DockerCli tlsConfig tls.Config ) tlsConfig.InsecureSkipVerify = true @@ -211,9 +212,9 @@ func main() { } if *flTls || *flTlsVerify { - cli = api.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, protoAddrParts[0], protoAddrParts[1], &tlsConfig) + cli = client.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, protoAddrParts[0], protoAddrParts[1], &tlsConfig) } else { - cli = api.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, protoAddrParts[0], protoAddrParts[1], nil) + cli = client.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, protoAddrParts[0], protoAddrParts[1], nil) } if err := cli.ParseCommands(flag.Args()...); err != nil { From ae9ed84fdab4db5cec663bbd2d4ba8bcad897dcc Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 28 Mar 2014 23:21:55 +0000 Subject: [PATCH 252/384] split client in 2 files Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api/client/cli.go | 102 ++++++ api/client/{client.go => commands.go} | 475 +------------------------- api/client/utils.go | 390 +++++++++++++++++++++ 3 files changed, 503 insertions(+), 464 deletions(-) create mode 100644 api/client/cli.go rename api/client/{client.go => commands.go} (83%) create mode 100644 api/client/utils.go diff --git a/api/client/cli.go b/api/client/cli.go new file mode 100644 index 0000000000..b58d3c3c75 --- /dev/null +++ b/api/client/cli.go @@ -0,0 +1,102 @@ +package client + +import ( + "crypto/tls" + "encoding/json" + "fmt" + "io" + "os" + "reflect" + "strings" + "text/template" + + flag "github.com/dotcloud/docker/pkg/mflag" + "github.com/dotcloud/docker/pkg/term" + "github.com/dotcloud/docker/registry" +) + +var funcMap = template.FuncMap{ + "json": func(v interface{}) string { + a, _ := json.Marshal(v) + return string(a) + }, +} + +func (cli *DockerCli) getMethod(name string) (func(...string) error, bool) { + methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:]) + method := reflect.ValueOf(cli).MethodByName(methodName) + if !method.IsValid() { + return nil, false + } + return method.Interface().(func(...string) error), true +} + +func (cli *DockerCli) ParseCommands(args ...string) error { + if len(args) > 0 { + method, exists := cli.getMethod(args[0]) + if !exists { + fmt.Println("Error: Command not found:", args[0]) + return cli.CmdHelp(args[1:]...) + } + return method(args[1:]...) + } + return cli.CmdHelp(args...) +} + +func (cli *DockerCli) Subcmd(name, signature, description string) *flag.FlagSet { + flags := flag.NewFlagSet(name, flag.ContinueOnError) + flags.Usage = func() { + fmt.Fprintf(cli.err, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description) + flags.PrintDefaults() + os.Exit(2) + } + return flags +} + +func (cli *DockerCli) LoadConfigFile() (err error) { + cli.configFile, err = registry.LoadConfig(os.Getenv("HOME")) + if err != nil { + fmt.Fprintf(cli.err, "WARNING: %s\n", err) + } + return err +} + +func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string, tlsConfig *tls.Config) *DockerCli { + var ( + isTerminal = false + terminalFd uintptr + ) + + if in != nil { + if file, ok := in.(*os.File); ok { + terminalFd = file.Fd() + isTerminal = term.IsTerminal(terminalFd) + } + } + + if err == nil { + err = out + } + return &DockerCli{ + proto: proto, + addr: addr, + in: in, + out: out, + err: err, + isTerminal: isTerminal, + terminalFd: terminalFd, + tlsConfig: tlsConfig, + } +} + +type DockerCli struct { + proto string + addr string + configFile *registry.ConfigFile + in io.ReadCloser + out io.Writer + err io.Writer + isTerminal bool + terminalFd uintptr + tlsConfig *tls.Config +} diff --git a/api/client/client.go b/api/client/commands.go similarity index 83% rename from api/client/client.go rename to api/client/commands.go index 29b49464c4..49a5c008b3 100644 --- a/api/client/client.go +++ b/api/client/commands.go @@ -3,34 +3,16 @@ package client import ( "bufio" "bytes" - "crypto/tls" "encoding/base64" "encoding/json" - "errors" "fmt" - "github.com/dotcloud/docker/api" - "github.com/dotcloud/docker/archive" - "github.com/dotcloud/docker/dockerversion" - "github.com/dotcloud/docker/engine" - "github.com/dotcloud/docker/nat" - flag "github.com/dotcloud/docker/pkg/mflag" - "github.com/dotcloud/docker/pkg/signal" - "github.com/dotcloud/docker/pkg/term" - "github.com/dotcloud/docker/registry" - "github.com/dotcloud/docker/runconfig" - "github.com/dotcloud/docker/utils" "io" "io/ioutil" - "net" "net/http" - "net/http/httputil" "net/url" "os" "os/exec" - gosignal "os/signal" "path" - "reflect" - "regexp" goruntime "runtime" "strconv" "strings" @@ -38,40 +20,19 @@ import ( "text/tabwriter" "text/template" "time" + + "github.com/dotcloud/docker/api" + "github.com/dotcloud/docker/archive" + "github.com/dotcloud/docker/dockerversion" + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/nat" + "github.com/dotcloud/docker/pkg/signal" + "github.com/dotcloud/docker/pkg/term" + "github.com/dotcloud/docker/registry" + "github.com/dotcloud/docker/runconfig" + "github.com/dotcloud/docker/utils" ) -var funcMap = template.FuncMap{ - "json": func(v interface{}) string { - a, _ := json.Marshal(v) - return string(a) - }, -} - -var ( - ErrConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?") -) - -func (cli *DockerCli) getMethod(name string) (func(...string) error, bool) { - methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:]) - method := reflect.ValueOf(cli).MethodByName(methodName) - if !method.IsValid() { - return nil, false - } - return method.Interface().(func(...string) error), true -} - -func (cli *DockerCli) ParseCommands(args ...string) error { - if len(args) > 0 { - method, exists := cli.getMethod(args[0]) - if !exists { - fmt.Println("Error: Command not found:", args[0]) - return cli.CmdHelp(args[1:]...) - } - return method(args[1:]...) - } - return cli.CmdHelp(args...) -} - func (cli *DockerCli) CmdHelp(args ...string) error { if len(args) > 0 { method, exists := cli.getMethod(args[0]) @@ -2135,417 +2096,3 @@ func (cli *DockerCli) CmdLoad(args ...string) error { } return nil } - -func (cli *DockerCli) dial() (net.Conn, error) { - if cli.tlsConfig != nil && cli.proto != "unix" { - return tls.Dial(cli.proto, cli.addr, cli.tlsConfig) - } - return net.Dial(cli.proto, cli.addr) -} - -func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) { - params := bytes.NewBuffer(nil) - if data != nil { - if env, ok := data.(engine.Env); ok { - if err := env.Encode(params); err != nil { - return nil, -1, err - } - } else { - buf, err := json.Marshal(data) - if err != nil { - return nil, -1, err - } - if _, err := params.Write(buf); err != nil { - return nil, -1, err - } - } - } - // fixme: refactor client to support redirect - re := regexp.MustCompile("/+") - path = re.ReplaceAllString(path, "/") - - req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params) - if err != nil { - return nil, -1, err - } - if passAuthInfo { - cli.LoadConfigFile() - // Resolve the Auth config relevant for this server - authConfig := cli.configFile.ResolveAuthConfig(registry.IndexServerAddress()) - getHeaders := func(authConfig registry.AuthConfig) (map[string][]string, error) { - buf, err := json.Marshal(authConfig) - if err != nil { - return nil, err - } - registryAuthHeader := []string{ - base64.URLEncoding.EncodeToString(buf), - } - return map[string][]string{"X-Registry-Auth": registryAuthHeader}, nil - } - if headers, err := getHeaders(authConfig); err == nil && headers != nil { - for k, v := range headers { - req.Header[k] = v - } - } - } - req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) - req.Host = cli.addr - if data != nil { - req.Header.Set("Content-Type", "application/json") - } else if method == "POST" { - req.Header.Set("Content-Type", "plain/text") - } - dial, err := cli.dial() - if err != nil { - if strings.Contains(err.Error(), "connection refused") { - return nil, -1, ErrConnectionRefused - } - return nil, -1, err - } - clientconn := httputil.NewClientConn(dial, nil) - resp, err := clientconn.Do(req) - if err != nil { - clientconn.Close() - if strings.Contains(err.Error(), "connection refused") { - return nil, -1, ErrConnectionRefused - } - return nil, -1, err - } - - if resp.StatusCode < 200 || resp.StatusCode >= 400 { - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, -1, err - } - if len(body) == 0 { - return nil, resp.StatusCode, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(resp.StatusCode), req.URL) - } - return nil, resp.StatusCode, fmt.Errorf("Error: %s", bytes.TrimSpace(body)) - } - - wrapper := utils.NewReadCloserWrapper(resp.Body, func() error { - if resp != nil && resp.Body != nil { - resp.Body.Close() - } - return clientconn.Close() - }) - return wrapper, resp.StatusCode, nil -} - -func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, headers map[string][]string) error { - if (method == "POST" || method == "PUT") && in == nil { - in = bytes.NewReader([]byte{}) - } - - // fixme: refactor client to support redirect - re := regexp.MustCompile("/+") - path = re.ReplaceAllString(path, "/") - - req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), in) - if err != nil { - return err - } - req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) - req.Host = cli.addr - if method == "POST" { - req.Header.Set("Content-Type", "plain/text") - } - - if headers != nil { - for k, v := range headers { - req.Header[k] = v - } - } - - dial, err := cli.dial() - if err != nil { - if strings.Contains(err.Error(), "connection refused") { - return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?") - } - return err - } - clientconn := httputil.NewClientConn(dial, nil) - resp, err := clientconn.Do(req) - defer clientconn.Close() - if err != nil { - if strings.Contains(err.Error(), "connection refused") { - return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?") - } - return err - } - defer resp.Body.Close() - - if resp.StatusCode < 200 || resp.StatusCode >= 400 { - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return err - } - if len(body) == 0 { - return fmt.Errorf("Error :%s", http.StatusText(resp.StatusCode)) - } - return fmt.Errorf("Error: %s", bytes.TrimSpace(body)) - } - - if api.MatchesContentType(resp.Header.Get("Content-Type"), "application/json") { - return utils.DisplayJSONMessagesStream(resp.Body, out, cli.terminalFd, cli.isTerminal) - } - if _, err := io.Copy(out, resp.Body); err != nil { - return err - } - return nil -} - -func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer) error { - defer func() { - if started != nil { - close(started) - } - }() - // fixme: refactor client to support redirect - re := regexp.MustCompile("/+") - path = re.ReplaceAllString(path, "/") - - req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), nil) - if err != nil { - return err - } - req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) - req.Header.Set("Content-Type", "plain/text") - req.Host = cli.addr - - dial, err := cli.dial() - if err != nil { - if strings.Contains(err.Error(), "connection refused") { - return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?") - } - return err - } - clientconn := httputil.NewClientConn(dial, nil) - defer clientconn.Close() - - // Server hijacks the connection, error 'connection closed' expected - clientconn.Do(req) - - rwc, br := clientconn.Hijack() - defer rwc.Close() - - if started != nil { - started <- rwc - } - - var receiveStdout chan error - - var oldState *term.State - - if in != nil && setRawTerminal && cli.isTerminal && os.Getenv("NORAW") == "" { - oldState, err = term.SetRawTerminal(cli.terminalFd) - if err != nil { - return err - } - defer term.RestoreTerminal(cli.terminalFd, oldState) - } - - if stdout != nil || stderr != nil { - receiveStdout = utils.Go(func() (err error) { - defer func() { - if in != nil { - if setRawTerminal && cli.isTerminal { - term.RestoreTerminal(cli.terminalFd, oldState) - } - // For some reason this Close call blocks on darwin.. - // As the client exists right after, simply discard the close - // until we find a better solution. - if goruntime.GOOS != "darwin" { - in.Close() - } - } - }() - - // When TTY is ON, use regular copy - if setRawTerminal { - _, err = io.Copy(stdout, br) - } else { - _, err = utils.StdCopy(stdout, stderr, br) - } - utils.Debugf("[hijack] End of stdout") - return err - }) - } - - sendStdin := utils.Go(func() error { - if in != nil { - io.Copy(rwc, in) - utils.Debugf("[hijack] End of stdin") - } - if tcpc, ok := rwc.(*net.TCPConn); ok { - if err := tcpc.CloseWrite(); err != nil { - utils.Debugf("Couldn't send EOF: %s\n", err) - } - } else if unixc, ok := rwc.(*net.UnixConn); ok { - if err := unixc.CloseWrite(); err != nil { - utils.Debugf("Couldn't send EOF: %s\n", err) - } - } - // Discard errors due to pipe interruption - return nil - }) - - if stdout != nil || stderr != nil { - if err := <-receiveStdout; err != nil { - utils.Debugf("Error receiveStdout: %s", err) - return err - } - } - - if !cli.isTerminal { - if err := <-sendStdin; err != nil { - utils.Debugf("Error sendStdin: %s", err) - return err - } - } - return nil - -} - -func (cli *DockerCli) getTtySize() (int, int) { - if !cli.isTerminal { - return 0, 0 - } - ws, err := term.GetWinsize(cli.terminalFd) - if err != nil { - utils.Debugf("Error getting size: %s", err) - if ws == nil { - return 0, 0 - } - } - return int(ws.Height), int(ws.Width) -} - -func (cli *DockerCli) resizeTty(id string) { - height, width := cli.getTtySize() - if height == 0 && width == 0 { - return - } - v := url.Values{} - v.Set("h", strconv.Itoa(height)) - v.Set("w", strconv.Itoa(width)) - if _, _, err := readBody(cli.call("POST", "/containers/"+id+"/resize?"+v.Encode(), nil, false)); err != nil { - utils.Debugf("Error resize: %s", err) - } -} - -func (cli *DockerCli) monitorTtySize(id string) error { - cli.resizeTty(id) - - sigchan := make(chan os.Signal, 1) - gosignal.Notify(sigchan, syscall.SIGWINCH) - go func() { - for _ = range sigchan { - cli.resizeTty(id) - } - }() - return nil -} - -func (cli *DockerCli) Subcmd(name, signature, description string) *flag.FlagSet { - flags := flag.NewFlagSet(name, flag.ContinueOnError) - flags.Usage = func() { - fmt.Fprintf(cli.err, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description) - flags.PrintDefaults() - os.Exit(2) - } - return flags -} - -func (cli *DockerCli) LoadConfigFile() (err error) { - cli.configFile, err = registry.LoadConfig(os.Getenv("HOME")) - if err != nil { - fmt.Fprintf(cli.err, "WARNING: %s\n", err) - } - return err -} - -func waitForExit(cli *DockerCli, containerId string) (int, error) { - stream, _, err := cli.call("POST", "/containers/"+containerId+"/wait", nil, false) - if err != nil { - return -1, err - } - - var out engine.Env - if err := out.Decode(stream); err != nil { - return -1, err - } - return out.GetInt("StatusCode"), nil -} - -// getExitCode perform an inspect on the container. It returns -// the running state and the exit code. -func getExitCode(cli *DockerCli, containerId string) (bool, int, error) { - body, _, err := readBody(cli.call("GET", "/containers/"+containerId+"/json", nil, false)) - if err != nil { - // If we can't connect, then the daemon probably died. - if err != ErrConnectionRefused { - return false, -1, err - } - return false, -1, nil - } - c := &api.Container{} - if err := json.Unmarshal(body, c); err != nil { - return false, -1, err - } - return c.State.Running, c.State.ExitCode, nil -} - -func readBody(stream io.ReadCloser, statusCode int, err error) ([]byte, int, error) { - if stream != nil { - defer stream.Close() - } - if err != nil { - return nil, statusCode, err - } - body, err := ioutil.ReadAll(stream) - if err != nil { - return nil, -1, err - } - return body, statusCode, nil -} - -func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string, tlsConfig *tls.Config) *DockerCli { - var ( - isTerminal = false - terminalFd uintptr - ) - - if in != nil { - if file, ok := in.(*os.File); ok { - terminalFd = file.Fd() - isTerminal = term.IsTerminal(terminalFd) - } - } - - if err == nil { - err = out - } - return &DockerCli{ - proto: proto, - addr: addr, - in: in, - out: out, - err: err, - isTerminal: isTerminal, - terminalFd: terminalFd, - tlsConfig: tlsConfig, - } -} - -type DockerCli struct { - proto string - addr string - configFile *registry.ConfigFile - in io.ReadCloser - out io.Writer - err io.Writer - isTerminal bool - terminalFd uintptr - tlsConfig *tls.Config -} diff --git a/api/client/utils.go b/api/client/utils.go new file mode 100644 index 0000000000..c2c7b1780a --- /dev/null +++ b/api/client/utils.go @@ -0,0 +1,390 @@ +package client + +import ( + "bytes" + "crypto/tls" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "net/http/httputil" + "net/url" + "os" + gosignal "os/signal" + "regexp" + goruntime "runtime" + "strconv" + "strings" + "syscall" + + "github.com/dotcloud/docker/api" + "github.com/dotcloud/docker/dockerversion" + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/pkg/term" + "github.com/dotcloud/docker/registry" + "github.com/dotcloud/docker/utils" +) + +var ( + ErrConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?") +) + +func (cli *DockerCli) dial() (net.Conn, error) { + if cli.tlsConfig != nil && cli.proto != "unix" { + return tls.Dial(cli.proto, cli.addr, cli.tlsConfig) + } + return net.Dial(cli.proto, cli.addr) +} + +func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) { + params := bytes.NewBuffer(nil) + if data != nil { + if env, ok := data.(engine.Env); ok { + if err := env.Encode(params); err != nil { + return nil, -1, err + } + } else { + buf, err := json.Marshal(data) + if err != nil { + return nil, -1, err + } + if _, err := params.Write(buf); err != nil { + return nil, -1, err + } + } + } + // fixme: refactor client to support redirect + re := regexp.MustCompile("/+") + path = re.ReplaceAllString(path, "/") + + req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params) + if err != nil { + return nil, -1, err + } + if passAuthInfo { + cli.LoadConfigFile() + // Resolve the Auth config relevant for this server + authConfig := cli.configFile.ResolveAuthConfig(registry.IndexServerAddress()) + getHeaders := func(authConfig registry.AuthConfig) (map[string][]string, error) { + buf, err := json.Marshal(authConfig) + if err != nil { + return nil, err + } + registryAuthHeader := []string{ + base64.URLEncoding.EncodeToString(buf), + } + return map[string][]string{"X-Registry-Auth": registryAuthHeader}, nil + } + if headers, err := getHeaders(authConfig); err == nil && headers != nil { + for k, v := range headers { + req.Header[k] = v + } + } + } + req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) + req.Host = cli.addr + if data != nil { + req.Header.Set("Content-Type", "application/json") + } else if method == "POST" { + req.Header.Set("Content-Type", "plain/text") + } + dial, err := cli.dial() + if err != nil { + if strings.Contains(err.Error(), "connection refused") { + return nil, -1, ErrConnectionRefused + } + return nil, -1, err + } + clientconn := httputil.NewClientConn(dial, nil) + resp, err := clientconn.Do(req) + if err != nil { + clientconn.Close() + if strings.Contains(err.Error(), "connection refused") { + return nil, -1, ErrConnectionRefused + } + return nil, -1, err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 400 { + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, -1, err + } + if len(body) == 0 { + return nil, resp.StatusCode, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(resp.StatusCode), req.URL) + } + return nil, resp.StatusCode, fmt.Errorf("Error: %s", bytes.TrimSpace(body)) + } + + wrapper := utils.NewReadCloserWrapper(resp.Body, func() error { + if resp != nil && resp.Body != nil { + resp.Body.Close() + } + return clientconn.Close() + }) + return wrapper, resp.StatusCode, nil +} + +func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, headers map[string][]string) error { + if (method == "POST" || method == "PUT") && in == nil { + in = bytes.NewReader([]byte{}) + } + + // fixme: refactor client to support redirect + re := regexp.MustCompile("/+") + path = re.ReplaceAllString(path, "/") + + req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), in) + if err != nil { + return err + } + req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) + req.Host = cli.addr + if method == "POST" { + req.Header.Set("Content-Type", "plain/text") + } + + if headers != nil { + for k, v := range headers { + req.Header[k] = v + } + } + + dial, err := cli.dial() + if err != nil { + if strings.Contains(err.Error(), "connection refused") { + return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?") + } + return err + } + clientconn := httputil.NewClientConn(dial, nil) + resp, err := clientconn.Do(req) + defer clientconn.Close() + if err != nil { + if strings.Contains(err.Error(), "connection refused") { + return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?") + } + return err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 400 { + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + if len(body) == 0 { + return fmt.Errorf("Error :%s", http.StatusText(resp.StatusCode)) + } + return fmt.Errorf("Error: %s", bytes.TrimSpace(body)) + } + + if api.MatchesContentType(resp.Header.Get("Content-Type"), "application/json") { + return utils.DisplayJSONMessagesStream(resp.Body, out, cli.terminalFd, cli.isTerminal) + } + if _, err := io.Copy(out, resp.Body); err != nil { + return err + } + return nil +} + +func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer) error { + defer func() { + if started != nil { + close(started) + } + }() + // fixme: refactor client to support redirect + re := regexp.MustCompile("/+") + path = re.ReplaceAllString(path, "/") + + req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), nil) + if err != nil { + return err + } + req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) + req.Header.Set("Content-Type", "plain/text") + req.Host = cli.addr + + dial, err := cli.dial() + if err != nil { + if strings.Contains(err.Error(), "connection refused") { + return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?") + } + return err + } + clientconn := httputil.NewClientConn(dial, nil) + defer clientconn.Close() + + // Server hijacks the connection, error 'connection closed' expected + clientconn.Do(req) + + rwc, br := clientconn.Hijack() + defer rwc.Close() + + if started != nil { + started <- rwc + } + + var receiveStdout chan error + + var oldState *term.State + + if in != nil && setRawTerminal && cli.isTerminal && os.Getenv("NORAW") == "" { + oldState, err = term.SetRawTerminal(cli.terminalFd) + if err != nil { + return err + } + defer term.RestoreTerminal(cli.terminalFd, oldState) + } + + if stdout != nil || stderr != nil { + receiveStdout = utils.Go(func() (err error) { + defer func() { + if in != nil { + if setRawTerminal && cli.isTerminal { + term.RestoreTerminal(cli.terminalFd, oldState) + } + // For some reason this Close call blocks on darwin.. + // As the client exists right after, simply discard the close + // until we find a better solution. + if goruntime.GOOS != "darwin" { + in.Close() + } + } + }() + + // When TTY is ON, use regular copy + if setRawTerminal { + _, err = io.Copy(stdout, br) + } else { + _, err = utils.StdCopy(stdout, stderr, br) + } + utils.Debugf("[hijack] End of stdout") + return err + }) + } + + sendStdin := utils.Go(func() error { + if in != nil { + io.Copy(rwc, in) + utils.Debugf("[hijack] End of stdin") + } + if tcpc, ok := rwc.(*net.TCPConn); ok { + if err := tcpc.CloseWrite(); err != nil { + utils.Errorf("Couldn't send EOF: %s\n", err) + } + } else if unixc, ok := rwc.(*net.UnixConn); ok { + if err := unixc.CloseWrite(); err != nil { + utils.Errorf("Couldn't send EOF: %s\n", err) + } + } + // Discard errors due to pipe interruption + return nil + }) + + if stdout != nil || stderr != nil { + if err := <-receiveStdout; err != nil { + utils.Errorf("Error receiveStdout: %s", err) + return err + } + } + + if !cli.isTerminal { + if err := <-sendStdin; err != nil { + utils.Errorf("Error sendStdin: %s", err) + return err + } + } + return nil + +} + +func (cli *DockerCli) resizeTty(id string) { + height, width := cli.getTtySize() + if height == 0 && width == 0 { + return + } + v := url.Values{} + v.Set("h", strconv.Itoa(height)) + v.Set("w", strconv.Itoa(width)) + if _, _, err := readBody(cli.call("POST", "/containers/"+id+"/resize?"+v.Encode(), nil, false)); err != nil { + utils.Errorf("Error resize: %s", err) + } +} + +func waitForExit(cli *DockerCli, containerId string) (int, error) { + stream, _, err := cli.call("POST", "/containers/"+containerId+"/wait", nil, false) + if err != nil { + return -1, err + } + + var out engine.Env + if err := out.Decode(stream); err != nil { + return -1, err + } + return out.GetInt("StatusCode"), nil +} + +// getExitCode perform an inspect on the container. It returns +// the running state and the exit code. +func getExitCode(cli *DockerCli, containerId string) (bool, int, error) { + body, _, err := readBody(cli.call("GET", "/containers/"+containerId+"/json", nil, false)) + if err != nil { + // If we can't connect, then the daemon probably died. + if err != ErrConnectionRefused { + return false, -1, err + } + return false, -1, nil + } + c := &api.Container{} + if err := json.Unmarshal(body, c); err != nil { + return false, -1, err + } + return c.State.Running, c.State.ExitCode, nil +} + +func (cli *DockerCli) monitorTtySize(id string) error { + cli.resizeTty(id) + + sigchan := make(chan os.Signal, 1) + gosignal.Notify(sigchan, syscall.SIGWINCH) + go func() { + for _ = range sigchan { + cli.resizeTty(id) + } + }() + return nil +} + +func (cli *DockerCli) getTtySize() (int, int) { + if !cli.isTerminal { + return 0, 0 + } + ws, err := term.GetWinsize(cli.terminalFd) + if err != nil { + utils.Errorf("Error getting size: %s", err) + if ws == nil { + return 0, 0 + } + } + return int(ws.Height), int(ws.Width) +} + +func readBody(stream io.ReadCloser, statusCode int, err error) ([]byte, int, error) { + if stream != nil { + defer stream.Close() + } + if err != nil { + return nil, statusCode, err + } + body, err := ioutil.ReadAll(stream) + if err != nil { + return nil, -1, err + } + return body, statusCode, nil +} From 185b040e49aa9ab74f8d9254c7ff86b2891e3708 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 28 Mar 2014 23:36:33 +0000 Subject: [PATCH 253/384] fix tests Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api/api_unit_test.go | 46 ----------------- api/server/server_unit_test.go | 52 ++++++++++++++++++++ integration/api_test.go | 90 +++++++++++++++++----------------- integration/commands_test.go | 50 +++++++++---------- integration/https_test.go | 8 +-- 5 files changed, 127 insertions(+), 119 deletions(-) create mode 100644 api/server/server_unit_test.go diff --git a/api/api_unit_test.go b/api/api_unit_test.go index 2b3e76e75c..678331d369 100644 --- a/api/api_unit_test.go +++ b/api/api_unit_test.go @@ -1,9 +1,6 @@ package api import ( - "fmt" - "net/http" - "net/http/httptest" "testing" ) @@ -20,46 +17,3 @@ func TestJsonContentType(t *testing.T) { t.Fail() } } - -func TestGetBoolParam(t *testing.T) { - if ret, err := getBoolParam("true"); err != nil || !ret { - t.Fatalf("true -> true, nil | got %t %s", ret, err) - } - if ret, err := getBoolParam("True"); err != nil || !ret { - t.Fatalf("True -> true, nil | got %t %s", ret, err) - } - if ret, err := getBoolParam("1"); err != nil || !ret { - t.Fatalf("1 -> true, nil | got %t %s", ret, err) - } - if ret, err := getBoolParam(""); err != nil || ret { - t.Fatalf("\"\" -> false, nil | got %t %s", ret, err) - } - if ret, err := getBoolParam("false"); err != nil || ret { - t.Fatalf("false -> false, nil | got %t %s", ret, err) - } - if ret, err := getBoolParam("0"); err != nil || ret { - t.Fatalf("0 -> false, nil | got %t %s", ret, err) - } - if ret, err := getBoolParam("faux"); err == nil || ret { - t.Fatalf("faux -> false, err | got %t %s", ret, err) - } -} - -func TesthttpError(t *testing.T) { - r := httptest.NewRecorder() - - httpError(r, fmt.Errorf("No such method")) - if r.Code != http.StatusNotFound { - t.Fatalf("Expected %d, got %d", http.StatusNotFound, r.Code) - } - - httpError(r, fmt.Errorf("This accound hasn't been activated")) - if r.Code != http.StatusForbidden { - t.Fatalf("Expected %d, got %d", http.StatusForbidden, r.Code) - } - - httpError(r, fmt.Errorf("Some error")) - if r.Code != http.StatusInternalServerError { - t.Fatalf("Expected %d, got %d", http.StatusInternalServerError, r.Code) - } -} diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go new file mode 100644 index 0000000000..5ea5af411c --- /dev/null +++ b/api/server/server_unit_test.go @@ -0,0 +1,52 @@ +package server + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" +) + +func TestGetBoolParam(t *testing.T) { + if ret, err := getBoolParam("true"); err != nil || !ret { + t.Fatalf("true -> true, nil | got %t %s", ret, err) + } + if ret, err := getBoolParam("True"); err != nil || !ret { + t.Fatalf("True -> true, nil | got %t %s", ret, err) + } + if ret, err := getBoolParam("1"); err != nil || !ret { + t.Fatalf("1 -> true, nil | got %t %s", ret, err) + } + if ret, err := getBoolParam(""); err != nil || ret { + t.Fatalf("\"\" -> false, nil | got %t %s", ret, err) + } + if ret, err := getBoolParam("false"); err != nil || ret { + t.Fatalf("false -> false, nil | got %t %s", ret, err) + } + if ret, err := getBoolParam("0"); err != nil || ret { + t.Fatalf("0 -> false, nil | got %t %s", ret, err) + } + if ret, err := getBoolParam("faux"); err == nil || ret { + t.Fatalf("faux -> false, err | got %t %s", ret, err) + + } +} + +func TesthttpError(t *testing.T) { + r := httptest.NewRecorder() + + httpError(r, fmt.Errorf("No such method")) + if r.Code != http.StatusNotFound { + t.Fatalf("Expected %d, got %d", http.StatusNotFound, r.Code) + } + + httpError(r, fmt.Errorf("This accound hasn't been activated")) + if r.Code != http.StatusForbidden { + t.Fatalf("Expected %d, got %d", http.StatusForbidden, r.Code) + } + + httpError(r, fmt.Errorf("Some error")) + if r.Code != http.StatusInternalServerError { + t.Fatalf("Expected %d, got %d", http.StatusInternalServerError, r.Code) + } +} diff --git a/integration/api_test.go b/integration/api_test.go index bac4efea53..d08617ea69 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -5,14 +5,6 @@ import ( "bytes" "encoding/json" "fmt" - "github.com/dotcloud/docker/api" - "github.com/dotcloud/docker/dockerversion" - "github.com/dotcloud/docker/engine" - "github.com/dotcloud/docker/image" - "github.com/dotcloud/docker/runconfig" - "github.com/dotcloud/docker/runtime" - "github.com/dotcloud/docker/utils" - "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" "io" "io/ioutil" "net" @@ -21,6 +13,16 @@ import ( "strings" "testing" "time" + + "github.com/dotcloud/docker/api" + "github.com/dotcloud/docker/api/server" + "github.com/dotcloud/docker/dockerversion" + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/image" + "github.com/dotcloud/docker/runconfig" + "github.com/dotcloud/docker/runtime" + "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) func TestGetVersion(t *testing.T) { @@ -35,7 +37,7 @@ func TestGetVersion(t *testing.T) { t.Fatal(err) } // FIXME getting the version should require an actual running Server - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -77,7 +79,7 @@ func TestGetInfo(t *testing.T) { } r := httptest.NewRecorder() - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -125,7 +127,7 @@ func TestGetEvents(t *testing.T) { r := httptest.NewRecorder() setTimeout(t, "", 500*time.Millisecond, func() { - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -166,7 +168,7 @@ func TestGetImagesJSON(t *testing.T) { r := httptest.NewRecorder() - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -201,7 +203,7 @@ func TestGetImagesJSON(t *testing.T) { if err != nil { t.Fatal(err) } - if err := api.ServeRequest(eng, api.APIVERSION, r2, req2); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r2, req2); err != nil { t.Fatal(err) } assertHttpNotError(r2, t) @@ -234,7 +236,7 @@ func TestGetImagesJSON(t *testing.T) { t.Fatal(err) } - if err := api.ServeRequest(eng, api.APIVERSION, r3, req3); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r3, req3); err != nil { t.Fatal(err) } assertHttpNotError(r3, t) @@ -259,7 +261,7 @@ func TestGetImagesHistory(t *testing.T) { if err != nil { t.Fatal(err) } - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -283,7 +285,7 @@ func TestGetImagesByName(t *testing.T) { } r := httptest.NewRecorder() - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -327,7 +329,7 @@ func TestGetContainersJSON(t *testing.T) { } r := httptest.NewRecorder() - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -363,7 +365,7 @@ func TestGetContainersExport(t *testing.T) { if err != nil { t.Fatal(err) } - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -401,7 +403,7 @@ func TestSaveImageAndThenLoad(t *testing.T) { if err != nil { t.Fatal(err) } - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } if r.Code != http.StatusOK { @@ -415,7 +417,7 @@ func TestSaveImageAndThenLoad(t *testing.T) { if err != nil { t.Fatal(err) } - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } if r.Code != http.StatusOK { @@ -428,7 +430,7 @@ func TestSaveImageAndThenLoad(t *testing.T) { if err != nil { t.Fatal(err) } - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } if r.Code != http.StatusNotFound { @@ -441,7 +443,7 @@ func TestSaveImageAndThenLoad(t *testing.T) { if err != nil { t.Fatal(err) } - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } if r.Code != http.StatusOK { @@ -454,7 +456,7 @@ func TestSaveImageAndThenLoad(t *testing.T) { if err != nil { t.Fatal(err) } - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } if r.Code != http.StatusOK { @@ -481,7 +483,7 @@ func TestGetContainersChanges(t *testing.T) { if err != nil { t.Fatal(err) } - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -548,7 +550,7 @@ func TestGetContainersTop(t *testing.T) { if err != nil { t.Fatal(err) } - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -596,7 +598,7 @@ func TestGetContainersByName(t *testing.T) { if err != nil { t.Fatal(err) } - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -631,7 +633,7 @@ func TestPostCommit(t *testing.T) { } r := httptest.NewRecorder() - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -667,7 +669,7 @@ func TestPostContainersCreate(t *testing.T) { } r := httptest.NewRecorder() - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -716,7 +718,7 @@ func TestPostContainersKill(t *testing.T) { if err != nil { t.Fatal(err) } - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -755,7 +757,7 @@ func TestPostContainersRestart(t *testing.T) { t.Fatal(err) } r := httptest.NewRecorder() - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -797,7 +799,7 @@ func TestPostContainersStart(t *testing.T) { req.Header.Set("Content-Type", "application/json") r := httptest.NewRecorder() - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -814,7 +816,7 @@ func TestPostContainersStart(t *testing.T) { } r = httptest.NewRecorder() - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } // Starting an already started container should return an error @@ -852,7 +854,7 @@ func TestRunErrorBindMountRootSource(t *testing.T) { req.Header.Set("Content-Type", "application/json") r := httptest.NewRecorder() - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } if r.Code != http.StatusInternalServerError { @@ -889,7 +891,7 @@ func TestPostContainersStop(t *testing.T) { t.Fatal(err) } r := httptest.NewRecorder() - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -921,7 +923,7 @@ func TestPostContainersWait(t *testing.T) { if err != nil { t.Fatal(err) } - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -979,7 +981,7 @@ func TestPostContainersAttach(t *testing.T) { t.Fatal(err) } - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r.ResponseRecorder, t) @@ -1057,7 +1059,7 @@ func TestPostContainersAttachStderr(t *testing.T) { t.Fatal(err) } - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r.ResponseRecorder, t) @@ -1114,7 +1116,7 @@ func TestDeleteContainers(t *testing.T) { t.Fatal(err) } r := httptest.NewRecorder() - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -1133,7 +1135,7 @@ func TestOptionsRoute(t *testing.T) { if err != nil { t.Fatal(err) } - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -1152,7 +1154,7 @@ func TestGetEnabledCors(t *testing.T) { if err != nil { t.Fatal(err) } - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -1199,7 +1201,7 @@ func TestDeleteImages(t *testing.T) { } r := httptest.NewRecorder() - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } if r.Code != http.StatusConflict { @@ -1212,7 +1214,7 @@ func TestDeleteImages(t *testing.T) { } r2 := httptest.NewRecorder() - if err := api.ServeRequest(eng, api.APIVERSION, r2, req2); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r2, req2); err != nil { t.Fatal(err) } assertHttpNotError(r2, t) @@ -1264,7 +1266,7 @@ func TestPostContainersCopy(t *testing.T) { t.Fatal(err) } req.Header.Add("Content-Type", "application/json") - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -1312,7 +1314,7 @@ func TestPostContainersCopyWhenContainerNotFound(t *testing.T) { t.Fatal(err) } req.Header.Add("Content-Type", "application/json") - if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { + if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } if r.Code != http.StatusNotFound { diff --git a/integration/commands_test.go b/integration/commands_test.go index 7de7a227ea..2dc0ff384a 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -3,7 +3,7 @@ package docker import ( "bufio" "fmt" - "github.com/dotcloud/docker/api" + "github.com/dotcloud/docker/api/client" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/pkg/term" @@ -121,7 +121,7 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error func TestRunHostname(t *testing.T) { stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli := client.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) c := make(chan struct{}) @@ -166,7 +166,7 @@ func TestRunHostname(t *testing.T) { func TestRunWorkdir(t *testing.T) { stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli := client.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) c := make(chan struct{}) @@ -211,7 +211,7 @@ func TestRunWorkdir(t *testing.T) { func TestRunWorkdirExists(t *testing.T) { stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli := client.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) c := make(chan struct{}) @@ -255,7 +255,7 @@ 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) + cli := client.NewDockerCli(nil, nil, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) c := make(chan struct{}) @@ -275,7 +275,7 @@ func TestRunExit(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli := client.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) c1 := make(chan struct{}) @@ -328,7 +328,7 @@ func TestRunDisconnect(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli := client.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) c1 := make(chan struct{}) @@ -374,7 +374,7 @@ func TestRunDisconnectTty(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli := client.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) c1 := make(chan struct{}) @@ -426,7 +426,7 @@ func TestRunAttachStdin(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli := client.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) ch := make(chan struct{}) @@ -490,7 +490,7 @@ func TestRunDetach(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli := client.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) ch := make(chan struct{}) @@ -537,7 +537,7 @@ func TestAttachDetach(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli := client.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) ch := make(chan struct{}) @@ -570,7 +570,7 @@ func TestAttachDetach(t *testing.T) { stdin, stdinPipe = io.Pipe() stdout, stdoutPipe = io.Pipe() - cli = api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli = client.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) ch = make(chan struct{}) go func() { @@ -618,7 +618,7 @@ func TestAttachDetachTruncatedID(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli := client.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) // Discard the CmdRun output @@ -636,7 +636,7 @@ func TestAttachDetachTruncatedID(t *testing.T) { stdin, stdinPipe = io.Pipe() stdout, stdoutPipe = io.Pipe() - cli = api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli = client.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) ch := make(chan struct{}) go func() { @@ -683,7 +683,7 @@ func TestAttachDisconnect(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli := client.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) go func() { @@ -752,7 +752,7 @@ func TestAttachDisconnect(t *testing.T) { func TestRunAutoRemove(t *testing.T) { t.Skip("Fixme. Skipping test for now, race condition") stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli := client.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) c := make(chan struct{}) @@ -788,7 +788,7 @@ func TestRunAutoRemove(t *testing.T) { func TestCmdLogs(t *testing.T) { t.Skip("Test not impemented") - cli := api.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli := client.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) if err := cli.CmdRun(unitTestImageID, "sh", "-c", "ls -l"); err != nil { @@ -806,7 +806,7 @@ func TestCmdLogs(t *testing.T) { // Expected behaviour: error out when attempting to bind mount non-existing source paths func TestRunErrorBindNonExistingSource(t *testing.T) { - cli := api.NewDockerCli(nil, nil, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli := client.NewDockerCli(nil, nil, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) c := make(chan struct{}) @@ -826,7 +826,7 @@ func TestRunErrorBindNonExistingSource(t *testing.T) { func TestImagesViz(t *testing.T) { stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli := client.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) image := buildTestImages(t, globalEngine) @@ -876,7 +876,7 @@ func TestImagesViz(t *testing.T) { func TestImagesTree(t *testing.T) { stdout, stdoutPipe := io.Pipe() - cli := api.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli := client.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) image := buildTestImages(t, globalEngine) @@ -959,7 +959,7 @@ func TestRunCidFileCheckIDLength(t *testing.T) { } tmpCidFile := path.Join(tmpDir, "cid") - cli := api.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli := client.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) c := make(chan struct{}) @@ -1008,7 +1008,7 @@ func TestRunCidFileCleanupIfEmpty(t *testing.T) { } tmpCidFile := path.Join(tmpDir, "cid") - cli := api.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli := client.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) c := make(chan struct{}) @@ -1038,7 +1038,7 @@ func TestContainerOrphaning(t *testing.T) { defer os.RemoveAll(tmpDir) // setup a CLI and server - cli := api.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli := client.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) srv := mkServerFromEngine(globalEngine, t) @@ -1098,8 +1098,8 @@ func TestCmdKill(t *testing.T) { var ( stdin, stdinPipe = io.Pipe() stdout, stdoutPipe = io.Pipe() - cli = api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) - cli2 = api.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli = client.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + cli2 = client.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) ) defer cleanup(globalEngine, t) diff --git a/integration/https_test.go b/integration/https_test.go index a1c855e1a9..0b4abea881 100644 --- a/integration/https_test.go +++ b/integration/https_test.go @@ -3,7 +3,7 @@ package docker import ( "crypto/tls" "crypto/x509" - "github.com/dotcloud/docker/api" + "github.com/dotcloud/docker/api/client" "io/ioutil" "testing" "time" @@ -35,7 +35,7 @@ func getTlsConfig(certFile, keyFile string, t *testing.T) *tls.Config { // TestHttpsInfo connects via two-way authenticated HTTPS to the info endpoint func TestHttpsInfo(t *testing.T) { - cli := api.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, + cli := client.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonHttpsAddr, getTlsConfig("client-cert.pem", "client-key.pem", t)) setTimeout(t, "Reading command output time out", 10*time.Second, func() { @@ -48,7 +48,7 @@ func TestHttpsInfo(t *testing.T) { // TestHttpsInfoRogueCert connects via two-way authenticated HTTPS to the info endpoint // by using a rogue client certificate and checks that it fails with the expected error. func TestHttpsInfoRogueCert(t *testing.T) { - cli := api.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, + cli := client.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonHttpsAddr, getTlsConfig("client-rogue-cert.pem", "client-rogue-key.pem", t)) setTimeout(t, "Reading command output time out", 10*time.Second, func() { @@ -65,7 +65,7 @@ func TestHttpsInfoRogueCert(t *testing.T) { // TestHttpsInfoRogueServerCert connects via two-way authenticated HTTPS to the info endpoint // which provides a rogue server certificate and checks that it fails with the expected error func TestHttpsInfoRogueServerCert(t *testing.T) { - cli := api.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, + cli := client.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonRogueHttpsAddr, getTlsConfig("client-cert.pem", "client-key.pem", t)) setTimeout(t, "Reading command output time out", 10*time.Second, func() { From 7697aad7b0537dade1d598cca5b7b1b420fa47c9 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 31 Mar 2014 18:08:46 +0000 Subject: [PATCH 254/384] apply Reduce error level form harmless errors Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api/client/utils.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/api/client/utils.go b/api/client/utils.go index c2c7b1780a..4ef09ba783 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -276,11 +276,11 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea } if tcpc, ok := rwc.(*net.TCPConn); ok { if err := tcpc.CloseWrite(); err != nil { - utils.Errorf("Couldn't send EOF: %s\n", err) + utils.Debugf("Couldn't send EOF: %s\n", err) } } else if unixc, ok := rwc.(*net.UnixConn); ok { if err := unixc.CloseWrite(); err != nil { - utils.Errorf("Couldn't send EOF: %s\n", err) + utils.Debugf("Couldn't send EOF: %s\n", err) } } // Discard errors due to pipe interruption @@ -289,14 +289,14 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea if stdout != nil || stderr != nil { if err := <-receiveStdout; err != nil { - utils.Errorf("Error receiveStdout: %s", err) + utils.Debugf("Error receiveStdout: %s", err) return err } } if !cli.isTerminal { if err := <-sendStdin; err != nil { - utils.Errorf("Error sendStdin: %s", err) + utils.Debugf("Error sendStdin: %s", err) return err } } @@ -313,7 +313,7 @@ func (cli *DockerCli) resizeTty(id string) { v.Set("h", strconv.Itoa(height)) v.Set("w", strconv.Itoa(width)) if _, _, err := readBody(cli.call("POST", "/containers/"+id+"/resize?"+v.Encode(), nil, false)); err != nil { - utils.Errorf("Error resize: %s", err) + utils.Debugf("Error resize: %s", err) } } @@ -367,7 +367,7 @@ func (cli *DockerCli) getTtySize() (int, int) { } ws, err := term.GetWinsize(cli.terminalFd) if err != nil { - utils.Errorf("Error getting size: %s", err) + utils.Debugf("Error getting size: %s", err) if ws == nil { return 0, 0 } From 51d9a04f17d1c8c6c1a069227c1417b20283dda2 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 31 Mar 2014 18:21:07 +0000 Subject: [PATCH 255/384] Make sure to set error reguardless of attach or stdin Fixes #3364 Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- api/client/commands.go | 2 +- integration-cli/docker_cli_start_test.go | 34 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 integration-cli/docker_cli_start_test.go diff --git a/api/client/commands.go b/api/client/commands.go index 49a5c008b3..49cd07700f 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -607,8 +607,8 @@ func (cli *DockerCli) CmdStart(args ...string) error { if err != nil { if !*attach || !*openStdin { fmt.Fprintf(cli.err, "%s\n", err) - encounteredError = fmt.Errorf("Error: failed to start one or more containers") } + encounteredError = fmt.Errorf("Error: failed to start one or more containers") } else { if !*attach || !*openStdin { fmt.Fprintf(cli.out, "%s\n", name) diff --git a/integration-cli/docker_cli_start_test.go b/integration-cli/docker_cli_start_test.go new file mode 100644 index 0000000000..c3059a66c4 --- /dev/null +++ b/integration-cli/docker_cli_start_test.go @@ -0,0 +1,34 @@ +package main + +import ( + "os/exec" + "testing" +) + +// Regression test for #3364 +func TestDockerStartWithPortCollision(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "--name", "fail", "-p", "25:25", "busybox", "true") + out, stderr, exitCode, err := runCommandWithStdoutStderr(runCmd) + if err != nil && exitCode != 0 { + t.Fatal(out, stderr, err) + } + + runCmd = exec.Command(dockerBinary, "run", "--name", "conflict", "-dti", "-p", "25:25", "busybox", "sh") + out, stderr, exitCode, err = runCommandWithStdoutStderr(runCmd) + if err != nil && exitCode != 0 { + t.Fatal(out, stderr, err) + } + + startCmd := exec.Command(dockerBinary, "start", "-a", "fail") + out, stderr, exitCode, err = runCommandWithStdoutStderr(startCmd) + if err != nil && exitCode != 1 { + t.Fatal(out, err) + } + + killCmd := exec.Command(dockerBinary, "kill", "conflict") + runCommand(killCmd) + + deleteAllContainers() + + logDone("start - -a=true error on port use") +} From cd51ac92bdf1ce0a1245f5b4565995631512ba64 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Sun, 16 Feb 2014 19:24:22 -0500 Subject: [PATCH 256/384] support for `docker run` environment variables file Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- docs/sources/reference/commandline/cli.rst | 12 ++++++ pkg/opts/envfile.go | 44 ++++++++++++++++++++++ runconfig/parse.go | 14 ++++++- 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 pkg/opts/envfile.go diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 3d2aac5233..6a473ec461 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -1152,6 +1152,7 @@ image is removed. --cidfile="": Write the container ID to the file -d, --detach=false: Detached mode: Run container in the background, print new container id -e, --env=[]: Set environment variables + --envfile="": Read in a line delimited file of ENV variables -h, --hostname="": Container host name -i, --interactive=false: Keep stdin open even if not attached --privileged=false: Give extended privileges to this container @@ -1284,6 +1285,17 @@ This exposes port ``80`` of the container for use within a link without publishing the port to the host system's interfaces. :ref:`port_redirection` explains in detail how to manipulate ports in Docker. +.. code-block:: bash + + $ sudo docker run -e MYVAR1 --env MYVAR2=foo --envfile ./env.list ubuntu bash + +This sets environmental variables to the container. For illustration all three +flags are shown here. Where -e and --env can be repeated, take an environment +variable and value, or if no "=" is provided, then that variable's current +value is passed through (i.e. $MYVAR1 from the host is set to $MYVAR1 in the +container). The --envfile flag takes a filename as an argument and expects each +line to be a VAR=VAL format. + .. code-block:: bash $ sudo docker run --name console -t -i ubuntu bash diff --git a/pkg/opts/envfile.go b/pkg/opts/envfile.go new file mode 100644 index 0000000000..004c320803 --- /dev/null +++ b/pkg/opts/envfile.go @@ -0,0 +1,44 @@ +package opts + +import ( + "bufio" + "bytes" + "io" + "os" +) + +/* +Read in a line delimited file with environment variables enumerated +*/ +func ParseEnvFile(filename string) ([]string, error) { + fh, err := os.Open(filename) + if err != nil { + return []string{}, err + } + var ( + lines []string = []string{} + line, chunk []byte + ) + reader := bufio.NewReader(fh) + line, isPrefix, err := reader.ReadLine() + + for err == nil { + if isPrefix { + chunk = append(chunk, line...) + } else if !isPrefix && len(chunk) > 0 { + line = chunk + chunk = []byte{} + } else { + chunk = []byte{} + } + + if !isPrefix && len(line) > 0 && bytes.Contains(line, []byte("=")) { + lines = append(lines, string(line)) + } + line, isPrefix, err = reader.ReadLine() + } + if err != nil && err != io.EOF { + return []string{}, err + } + return lines, nil +} diff --git a/runconfig/parse.go b/runconfig/parse.go index 43aecdb753..aa1ed6d174 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -68,6 +68,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf 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") + flEnvFile = cmd.String([]string{"#envfile", "-envfile"}, "", "Read in a line delimited file of ENV variables") // For documentation purpose _ = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxify all received signal to the process (even in non-tty mode)") @@ -199,6 +200,17 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf } } + // collect all the environment variables for the container + envVariables := []string{} + envVariables = append(envVariables, flEnv.GetAll()...) + parsedVars, err := opts.ParseEnvFile(*flEnvFile) + if err != nil { + return nil, nil, cmd, err + } + envVariables = append(envVariables, parsedVars...) + // boo, there's no debug output for docker run + //utils.Debugf("Environment variables for the container: %#v", envVariables) + config := &Config{ Hostname: hostname, Domainname: domainname, @@ -213,7 +225,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf AttachStdin: flAttach.Get("stdin"), AttachStdout: flAttach.Get("stdout"), AttachStderr: flAttach.Get("stderr"), - Env: flEnv.GetAll(), + Env: envVariables, Cmd: runCmd, Dns: flDns.GetAll(), DnsSearch: flDnsSearch.GetAll(), From bfaa917a966fab1e2c92e70617320957a8d8b43b Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Tue, 18 Feb 2014 14:22:46 -0500 Subject: [PATCH 257/384] pkg/opts: Close the file handle Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- pkg/opts/envfile.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/opts/envfile.go b/pkg/opts/envfile.go index 004c320803..65495a1585 100644 --- a/pkg/opts/envfile.go +++ b/pkg/opts/envfile.go @@ -15,6 +15,8 @@ func ParseEnvFile(filename string) ([]string, error) { if err != nil { return []string{}, err } + defer fh.Close() + var ( lines []string = []string{} line, chunk []byte From 586e6c5eb9cd21a95d4bbba051249c4b05b2011e Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Tue, 18 Feb 2014 15:41:28 -0500 Subject: [PATCH 258/384] --env-file instead of --envfile Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- docs/sources/reference/commandline/cli.rst | 2 +- runconfig/parse.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 6a473ec461..d8cf5965da 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -1152,7 +1152,7 @@ image is removed. --cidfile="": Write the container ID to the file -d, --detach=false: Detached mode: Run container in the background, print new container id -e, --env=[]: Set environment variables - --envfile="": Read in a line delimited file of ENV variables + --env-file="": Read in a line delimited file of ENV variables -h, --hostname="": Container host name -i, --interactive=false: Keep stdin open even if not attached --privileged=false: Give extended privileges to this container diff --git a/runconfig/parse.go b/runconfig/parse.go index aa1ed6d174..6ebe9f2bc3 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -68,7 +68,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf 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") - flEnvFile = cmd.String([]string{"#envfile", "-envfile"}, "", "Read in a line delimited file of ENV variables") + flEnvFile = cmd.String([]string{"#env-file", "-env-file"}, "", "Read in a line delimited file of ENV variables") // For documentation purpose _ = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxify all received signal to the process (even in non-tty mode)") From 4e0014f582617960bad513518d292b64da866f73 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Thu, 20 Feb 2014 15:34:45 -0500 Subject: [PATCH 259/384] go fmt Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- pkg/opts/envfile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/opts/envfile.go b/pkg/opts/envfile.go index 65495a1585..7c69f5d799 100644 --- a/pkg/opts/envfile.go +++ b/pkg/opts/envfile.go @@ -15,7 +15,7 @@ func ParseEnvFile(filename string) ([]string, error) { if err != nil { return []string{}, err } - defer fh.Close() + defer fh.Close() var ( lines []string = []string{} From bcba5246f993a74eece36aa4b25df5b5e486e15b Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Wed, 26 Feb 2014 16:05:25 -0500 Subject: [PATCH 260/384] Fixing doc references to --env-file Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- docs/sources/reference/commandline/cli.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index d8cf5965da..4e697766bc 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -1287,13 +1287,13 @@ explains in detail how to manipulate ports in Docker. .. code-block:: bash - $ sudo docker run -e MYVAR1 --env MYVAR2=foo --envfile ./env.list ubuntu bash + $ sudo docker run -e MYVAR1 --env MYVAR2=foo --env-file ./env.list ubuntu bash This sets environmental variables to the container. For illustration all three flags are shown here. Where -e and --env can be repeated, take an environment variable and value, or if no "=" is provided, then that variable's current value is passed through (i.e. $MYVAR1 from the host is set to $MYVAR1 in the -container). The --envfile flag takes a filename as an argument and expects each +container). The --env-file flag takes a filename as an argument and expects each line to be a VAR=VAL format. .. code-block:: bash From acf5289dddfbbd69e19714f53575eb5088c618f7 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Thu, 6 Mar 2014 12:55:47 -0500 Subject: [PATCH 261/384] make the --env-file accept multiple flags Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- runconfig/parse.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/runconfig/parse.go b/runconfig/parse.go index 6ebe9f2bc3..e6e8b120d8 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -52,6 +52,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf flVolumesFrom opts.ListOpts flLxcOpts opts.ListOpts flDriverOpts opts.ListOpts + flEnvFile opts.ListOpts flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)") flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: Run container in the background, print new container id") @@ -68,7 +69,6 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf 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") - flEnvFile = cmd.String([]string{"#env-file", "-env-file"}, "", "Read in a line delimited file of ENV variables") // For documentation purpose _ = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxify all received signal to the process (even in non-tty mode)") @@ -79,6 +79,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)") cmd.Var(&flLinks, []string{"#link", "-link"}, "Add link to another container (name:alias)") cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables") + cmd.Var(&flEnvFile, []string{"#env-file", "-env-file"}, "Read in a line delimited file of ENV variables") cmd.Var(&flPublish, []string{"p", "-publish"}, fmt.Sprintf("Publish a container's port to the host (format: %s) (use 'docker port' to see the actual mapping)", nat.PortSpecTemplateFormat)) cmd.Var(&flExpose, []string{"#expose", "-expose"}, "Expose a port from the container without publishing it to your host") @@ -203,11 +204,13 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf // collect all the environment variables for the container envVariables := []string{} envVariables = append(envVariables, flEnv.GetAll()...) - parsedVars, err := opts.ParseEnvFile(*flEnvFile) - if err != nil { - return nil, nil, cmd, err + for _, ef := range flEnvFile.GetAll() { + parsedVars, err := opts.ParseEnvFile(ef) + if err != nil { + return nil, nil, cmd, err + } + envVariables = append(envVariables, parsedVars...) } - envVariables = append(envVariables, parsedVars...) // boo, there's no debug output for docker run //utils.Debugf("Environment variables for the container: %#v", envVariables) From 33dde1f7288781a3a36951309f963e6040a1a0f5 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Thu, 6 Mar 2014 17:49:47 -0500 Subject: [PATCH 262/384] env-file: update functionality and docs Multiple flags allowed. Order prescribed. Examples provided. Multiline accounted for. Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- docs/sources/reference/commandline/cli.rst | 47 +++++++++++++++++++--- pkg/opts/envfile.go | 41 ++++++++----------- runconfig/parse.go | 3 +- 3 files changed, 59 insertions(+), 32 deletions(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 4e697766bc..989ea38798 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -1289,12 +1289,47 @@ explains in detail how to manipulate ports in Docker. $ sudo docker run -e MYVAR1 --env MYVAR2=foo --env-file ./env.list ubuntu bash -This sets environmental variables to the container. For illustration all three -flags are shown here. Where -e and --env can be repeated, take an environment -variable and value, or if no "=" is provided, then that variable's current -value is passed through (i.e. $MYVAR1 from the host is set to $MYVAR1 in the -container). The --env-file flag takes a filename as an argument and expects each -line to be a VAR=VAL format. +This sets environmental variables in the container. For illustration all three +flags are shown here. Where ``-e``, ``--env`` take an environment variable and +value, or if no "=" is provided, then that variable's current value is passed +through (i.e. $MYVAR1 from the host is set to $MYVAR1 in the container). All +three flags, ``-e``, ``--env`` and ``--env-file`` can be repeated. + +Regardless of the order of these three flags, the ``--env-file`` are processed +first, and then ``-e``/``--env`` flags. So that they can override VAR as needed. + +.. code-block:: bash + + $ cat ./env.list + TEST_FOO=BAR + $ sudo docker run --env TEST_FOO="This is a test" --env-file ./env.list busybox env | grep TEST_FOO + TEST_FOO=This is a test + +The ``--env-file`` flag takes a filename as an argument and expects each line +to be in the VAR=VAL format. The VAL is Unquoted, so if you need a multi-line +value, then use `\n` escape characters inside of a double quoted VAL. Single +quotes are literal. An example of a file passed with ``--env-file`` + +.. code-block:: bash + + $ cat ./env.list + TEST_FOO=BAR + TEST_APP_DEST_HOST=10.10.0.127 + TEST_APP_DEST_PORT=8888 + TEST_SOME_MULTILINE_VAR="this is first line\nthis is second line" + TEST_SOME_LITERAL_VAR='this\nwill\nall\nbe\none\nline' + $ sudo docker run --env-file ./env.list busybox env + HOME=/ + PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + HOSTNAME=215d54a814bc + TEST_FOO=BAR + TEST_APP_DEST_HOST=10.10.0.127 + TEST_APP_DEST_PORT=8888 + TEST_SOME_MULTILINE_VAR=this is first line + this is second line + TEST_SOME_LITERAL_VAR='this\nwill\nall\nbe\none\nline' + container=lxc + .. code-block:: bash diff --git a/pkg/opts/envfile.go b/pkg/opts/envfile.go index 7c69f5d799..d9afdd952c 100644 --- a/pkg/opts/envfile.go +++ b/pkg/opts/envfile.go @@ -2,9 +2,10 @@ package opts import ( "bufio" - "bytes" - "io" + "fmt" "os" + "strconv" + "strings" ) /* @@ -17,30 +18,20 @@ func ParseEnvFile(filename string) ([]string, error) { } defer fh.Close() - var ( - lines []string = []string{} - line, chunk []byte - ) - reader := bufio.NewReader(fh) - line, isPrefix, err := reader.ReadLine() - - for err == nil { - if isPrefix { - chunk = append(chunk, line...) - } else if !isPrefix && len(chunk) > 0 { - line = chunk - chunk = []byte{} - } else { - chunk = []byte{} + lines := []string{} + scanner := bufio.NewScanner(fh) + for scanner.Scan() { + line := scanner.Text() + // line is not empty, and not starting with '#' + if len(line) > 0 && !strings.HasPrefix(line, "#") && strings.Contains(line, "=") { + data := strings.SplitN(line, "=", 2) + key := data[0] + val := data[1] + if str, err := strconv.Unquote(data[1]); err == nil { + val = str + } + lines = append(lines, fmt.Sprintf("%s=%s", key, val)) } - - if !isPrefix && len(line) > 0 && bytes.Contains(line, []byte("=")) { - lines = append(lines, string(line)) - } - line, isPrefix, err = reader.ReadLine() - } - if err != nil && err != io.EOF { - return []string{}, err } return lines, nil } diff --git a/runconfig/parse.go b/runconfig/parse.go index e6e8b120d8..d3e32bc2de 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -203,7 +203,6 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf // collect all the environment variables for the container envVariables := []string{} - envVariables = append(envVariables, flEnv.GetAll()...) for _, ef := range flEnvFile.GetAll() { parsedVars, err := opts.ParseEnvFile(ef) if err != nil { @@ -211,6 +210,8 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf } envVariables = append(envVariables, parsedVars...) } + // parse the '-e' and '--env' after, to allow override + envVariables = append(envVariables, flEnv.GetAll()...) // boo, there's no debug output for docker run //utils.Debugf("Environment variables for the container: %#v", envVariables) From d9c257732e435cecdcc1ec8452f35e7614e4713f Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Fri, 7 Mar 2014 16:18:42 -0500 Subject: [PATCH 263/384] env-file: remove the unneeded deprecation markup Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- runconfig/parse.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runconfig/parse.go b/runconfig/parse.go index d3e32bc2de..db4ac351a9 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -79,7 +79,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)") cmd.Var(&flLinks, []string{"#link", "-link"}, "Add link to another container (name:alias)") cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables") - cmd.Var(&flEnvFile, []string{"#env-file", "-env-file"}, "Read in a line delimited file of ENV variables") + cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a line delimited file of ENV variables") cmd.Var(&flPublish, []string{"p", "-publish"}, fmt.Sprintf("Publish a container's port to the host (format: %s) (use 'docker port' to see the actual mapping)", nat.PortSpecTemplateFormat)) cmd.Var(&flExpose, []string{"#expose", "-expose"}, "Expose a port from the container without publishing it to your host") From ff4ac7441ba582c8c339b25b400c6756d9646ff1 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Tue, 11 Mar 2014 16:22:58 -0400 Subject: [PATCH 264/384] --env-file: simple line-delimited match dock functionality, and not try to achieve shell-sourcing compatibility Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- docs/sources/reference/commandline/cli.rst | 26 ++++++++++++---------- {pkg/opts => opts}/envfile.go | 14 +++++------- 2 files changed, 20 insertions(+), 20 deletions(-) rename {pkg/opts => opts}/envfile.go (60%) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 989ea38798..0c9db138c2 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -1296,7 +1296,8 @@ through (i.e. $MYVAR1 from the host is set to $MYVAR1 in the container). All three flags, ``-e``, ``--env`` and ``--env-file`` can be repeated. Regardless of the order of these three flags, the ``--env-file`` are processed -first, and then ``-e``/``--env`` flags. So that they can override VAR as needed. +first, and then ``-e``/``--env`` flags. This way, the ``-e`` or ``--env`` will +override variables as needed. .. code-block:: bash @@ -1306,29 +1307,30 @@ first, and then ``-e``/``--env`` flags. So that they can override VAR as needed. TEST_FOO=This is a test The ``--env-file`` flag takes a filename as an argument and expects each line -to be in the VAR=VAL format. The VAL is Unquoted, so if you need a multi-line -value, then use `\n` escape characters inside of a double quoted VAL. Single -quotes are literal. An example of a file passed with ``--env-file`` +to be in the VAR=VAL format, mimicking the argument passed to ``--env``. +Comment lines need only be prefixed with ``#`` + +An example of a file passed with ``--env-file`` .. code-block:: bash $ cat ./env.list TEST_FOO=BAR + + # this is a comment TEST_APP_DEST_HOST=10.10.0.127 TEST_APP_DEST_PORT=8888 - TEST_SOME_MULTILINE_VAR="this is first line\nthis is second line" - TEST_SOME_LITERAL_VAR='this\nwill\nall\nbe\none\nline' - $ sudo docker run --env-file ./env.list busybox env + + # pass through this variable from the caller + TEST_PASSTHROUGH + $ sudo TEST_PASSTHROUGH=howdy docker run --env-file ./env.list busybox env HOME=/ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin - HOSTNAME=215d54a814bc + HOSTNAME=5198e0745561 TEST_FOO=BAR TEST_APP_DEST_HOST=10.10.0.127 TEST_APP_DEST_PORT=8888 - TEST_SOME_MULTILINE_VAR=this is first line - this is second line - TEST_SOME_LITERAL_VAR='this\nwill\nall\nbe\none\nline' - container=lxc + TEST_PASSTHROUGH=howdy .. code-block:: bash diff --git a/pkg/opts/envfile.go b/opts/envfile.go similarity index 60% rename from pkg/opts/envfile.go rename to opts/envfile.go index d9afdd952c..99a713e761 100644 --- a/pkg/opts/envfile.go +++ b/opts/envfile.go @@ -4,7 +4,6 @@ import ( "bufio" "fmt" "os" - "strconv" "strings" ) @@ -23,14 +22,13 @@ func ParseEnvFile(filename string) ([]string, error) { for scanner.Scan() { line := scanner.Text() // line is not empty, and not starting with '#' - if len(line) > 0 && !strings.HasPrefix(line, "#") && strings.Contains(line, "=") { - data := strings.SplitN(line, "=", 2) - key := data[0] - val := data[1] - if str, err := strconv.Unquote(data[1]); err == nil { - val = str + if len(line) > 0 && !strings.HasPrefix(line, "#") { + if strings.Contains(line, "=") { + data := strings.SplitN(line, "=", 2) + lines = append(lines, fmt.Sprintf("%s=%s", data[0], data[1])) + } else { + lines = append(lines, fmt.Sprintf("%s=%s", line, os.Getenv(line))) } - lines = append(lines, fmt.Sprintf("%s=%s", key, val)) } } return lines, nil From 500c8ba4b66c35cf2c29aeb81a9392cc406835a4 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 17 Mar 2014 17:11:27 -0400 Subject: [PATCH 265/384] env-file: variable behavior trim the front of variables. Error if there are other spaces present. Leave the value alone. Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- opts/envfile.go | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/opts/envfile.go b/opts/envfile.go index 99a713e761..19ee8955f9 100644 --- a/opts/envfile.go +++ b/opts/envfile.go @@ -25,11 +25,30 @@ func ParseEnvFile(filename string) ([]string, error) { if len(line) > 0 && !strings.HasPrefix(line, "#") { if strings.Contains(line, "=") { data := strings.SplitN(line, "=", 2) - lines = append(lines, fmt.Sprintf("%s=%s", data[0], data[1])) + + // trim the front of a variable, but nothing else + variable := strings.TrimLeft(data[0], whiteSpaces) + if strings.ContainsAny(variable, whiteSpaces) { + return []string{}, ErrBadEnvVariable{fmt.Sprintf("variable '%s' has white spaces", variable)} + } + + // pass the value through, no trimming + lines = append(lines, fmt.Sprintf("%s=%s", variable, data[1])) } else { - lines = append(lines, fmt.Sprintf("%s=%s", line, os.Getenv(line))) + // if only a pass-through variable is given, clean it up. + lines = append(lines, fmt.Sprintf("%s=%s", strings.TrimSpace(line), os.Getenv(line))) } } } return lines, nil } + +var whiteSpaces = " \t" + +type ErrBadEnvVariable struct { + msg string +} + +func (e ErrBadEnvVariable) Error() string { + return fmt.Sprintf("poorly formatted environment: %s", e.msg) +} From f16372022875a6dfe9d53489bde635789dd865a6 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Mon, 31 Mar 2014 10:39:02 -0700 Subject: [PATCH 266/384] Move installmirrors anchor in doc so it's before "Mirrors" instead of "Docker and local DNS server warnings" Docker-DCO-1.1-Signed-off-by: Marc Abramowitz (github: msabramo) --- docs/sources/installation/ubuntulinux.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index 85098e9552..44dba6b97e 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -282,8 +282,6 @@ incoming connections on the Docker port (default 4243): sudo ufw allow 4243/tcp -.. _installmirrors: - Docker and local DNS server warnings ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -342,6 +340,8 @@ NetworkManager and Docker need to be restarted afterwards: .. warning:: This might make DNS resolution slower on some networks. +.. _installmirrors: + Mirrors ^^^^^^^ From f7ae3a1381fdc53042bebec085bb3f108bc05da3 Mon Sep 17 00:00:00 2001 From: unclejack Date: Mon, 31 Mar 2014 21:48:30 +0300 Subject: [PATCH 267/384] integration-cli: pull busybox before running Make sure the busybox image is ready to be used when running the cli integration tests. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- hack/make/test-integration-cli | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hack/make/test-integration-cli b/hack/make/test-integration-cli index d007fbaf6a..5c6fc367fc 100644 --- a/hack/make/test-integration-cli +++ b/hack/make/test-integration-cli @@ -19,6 +19,10 @@ fi docker -d -D -p $DEST/docker.pid &> $DEST/docker.log & +# pull the busybox image before running the tests +sleep 2 +docker pull busybox + bundle_test_integration_cli 2>&1 \ | tee $DEST/test.log From 904bf049c1626567ee28a21bde4b68ab82c5ce77 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 31 Mar 2014 19:10:19 +0000 Subject: [PATCH 268/384] Force abs paths for host volumes Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- integration-cli/docker_cli_run_test.go | 12 ++++++++++++ runtime/volumes.go | 7 +++++++ 2 files changed, 19 insertions(+) diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 13959adea7..8d62108fed 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -271,3 +271,15 @@ func TestDockerRunWithVolumesAsFiles(t *testing.T) { logDone("run - regression test for #4741 - volumes from as files") } + +// Regression test for #4830 +func TestDockerRunWithRelativePath(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "-v", "tmp:/other-tmp", "busybox", "true") + if _, _, _, err := runCommandWithStdoutStderr(runCmd); err == nil { + t.Fatalf("relative path should result in an error") + } + + deleteAllContainers() + + logDone("run - volume with relative path") +} diff --git a/runtime/volumes.go b/runtime/volumes.go index 5ac82ef089..c504644ae8 100644 --- a/runtime/volumes.go +++ b/runtime/volumes.go @@ -172,6 +172,13 @@ func createVolumes(container *Container) error { if bindMap, exists := binds[volPath]; exists { isBindMount = true srcPath = bindMap.SrcPath + srcAbs, err := filepath.Abs(srcPath) + if err != nil { + return err + } + if srcPath != srcAbs { + return fmt.Errorf("%s should be an absolute path", srcPath) + } if strings.ToLower(bindMap.Mode) == "rw" { srcRW = true } From 9709c31d1b500fb7cfdb02aaf62c7d8c187874cf Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 31 Mar 2014 19:21:57 +0000 Subject: [PATCH 269/384] fix import display Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- utils/jsonmessage.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/utils/jsonmessage.go b/utils/jsonmessage.go index f84cc42c78..6be421be94 100644 --- a/utils/jsonmessage.go +++ b/utils/jsonmessage.go @@ -131,7 +131,7 @@ func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, if jm.Progress != nil { jm.Progress.terminalFd = terminalFd } - if jm.Progress != nil || jm.ProgressMessage != "" { + if jm.ID != "" && (jm.Progress != nil || jm.ProgressMessage != "") { line, ok := ids[jm.ID] if !ok { line = len(ids) @@ -141,17 +141,15 @@ func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, } else { diff = len(ids) - line } - if isTerminal { + if jm.ID != "" && isTerminal { // [{diff}A = move cursor up diff rows fmt.Fprintf(out, "%c[%dA", 27, diff) } } err := jm.Display(out, isTerminal) - if jm.ID != "" { - if isTerminal { - // [{diff}B = move cursor down diff rows - fmt.Fprintf(out, "%c[%dB", 27, diff) - } + if jm.ID != "" && isTerminal { + // [{diff}B = move cursor down diff rows + fmt.Fprintf(out, "%c[%dB", 27, diff) } if err != nil { return err From b430f4f45be27b9565027b5c89b2506577027e88 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 31 Mar 2014 19:31:21 +0000 Subject: [PATCH 270/384] add test Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- integration-cli/docker_cli_import_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 integration-cli/docker_cli_import_test.go diff --git a/integration-cli/docker_cli_import_test.go b/integration-cli/docker_cli_import_test.go new file mode 100644 index 0000000000..9b36aa9ce1 --- /dev/null +++ b/integration-cli/docker_cli_import_test.go @@ -0,0 +1,20 @@ +package main + +import ( + "fmt" + "os/exec" + "strings" + "testing" +) + +func TestImportDisplay(t *testing.T) { + importCmd := exec.Command(dockerBinary, "import", "https://github.com/ewindisch/docker-cirros/raw/master/cirros-0.3.0-x86_64-lxc.tar.gz") + out, _, err := runCommandWithOutput(importCmd) + errorOut(err, t, fmt.Sprintf("import failed with errors: %v", err)) + + if n := len(strings.Split(out, "\n")); n != 3 { + t.Fatalf("display is messed up: %d '\\n' instead of 3", n) + } + + logDone("import - cirros was imported and display is fine") +} From 5fb28eab3e670f225019174987424be31a0d0527 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 31 Mar 2014 11:40:39 -0700 Subject: [PATCH 271/384] Add regression test Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- integration-cli/docker_cli_logs_test.go | 76 +++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 integration-cli/docker_cli_logs_test.go diff --git a/integration-cli/docker_cli_logs_test.go b/integration-cli/docker_cli_logs_test.go new file mode 100644 index 0000000000..f8fcbe8832 --- /dev/null +++ b/integration-cli/docker_cli_logs_test.go @@ -0,0 +1,76 @@ +package main + +import ( + "fmt" + "os/exec" + "testing" +) + +// This used to work, it test a log of PageSize-1 (gh#4851) +func TestLogsContainerSmallerThanPage(t *testing.T) { + testLen := 32767 + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", fmt.Sprintf("for i in $(seq 1 %d); do echo -n =; done; echo", testLen)) + out, _, _, err := runCommandWithStdoutStderr(runCmd) + errorOut(err, t, fmt.Sprintf("run failed with errors: %v", err)) + + cleanedContainerID := stripTrailingCharacters(out) + exec.Command(dockerBinary, "wait", cleanedContainerID).Run() + + logsCmd := exec.Command(dockerBinary, "logs", cleanedContainerID) + out, _, _, err = runCommandWithStdoutStderr(logsCmd) + errorOut(err, t, fmt.Sprintf("failed to log container: %v %v", out, err)) + + if len(out) != testLen+1 { + t.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out)) + } + + go deleteContainer(cleanedContainerID) + + logDone("logs - logs container running echo smaller than page size") +} + +// Regression test: When going over the PageSize, it used to panic (gh#4851) +func TestLogsContainerBiggerThanPage(t *testing.T) { + testLen := 32768 + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", fmt.Sprintf("for i in $(seq 1 %d); do echo -n =; done; echo", testLen)) + out, _, _, err := runCommandWithStdoutStderr(runCmd) + errorOut(err, t, fmt.Sprintf("run failed with errors: %v", err)) + + cleanedContainerID := stripTrailingCharacters(out) + exec.Command(dockerBinary, "wait", cleanedContainerID).Run() + + logsCmd := exec.Command(dockerBinary, "logs", cleanedContainerID) + out, _, _, err = runCommandWithStdoutStderr(logsCmd) + errorOut(err, t, fmt.Sprintf("failed to log container: %v %v", out, err)) + + if len(out) != testLen+1 { + t.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out)) + } + + go deleteContainer(cleanedContainerID) + + logDone("logs - logs container running echo bigger than page size") +} + +// Regression test: When going much over the PageSize, it used to block (gh#4851) +func TestLogsContainerMuchBiggerThanPage(t *testing.T) { + testLen := 33000 + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", fmt.Sprintf("for i in $(seq 1 %d); do echo -n =; done; echo", testLen)) + out, _, _, err := runCommandWithStdoutStderr(runCmd) + errorOut(err, t, fmt.Sprintf("run failed with errors: %v", err)) + + cleanedContainerID := stripTrailingCharacters(out) + exec.Command(dockerBinary, "wait", cleanedContainerID).Run() + + logsCmd := exec.Command(dockerBinary, "logs", cleanedContainerID) + out, _, _, err = runCommandWithStdoutStderr(logsCmd) + errorOut(err, t, fmt.Sprintf("failed to log container: %v %v", out, err)) + + if len(out) != testLen+1 { + t.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out)) + } + + go deleteContainer(cleanedContainerID) + + logDone("logs - logs container running echo much bigger than page size") +} From 07b60d626acaddffb6a0b118bfc3f19631411d72 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 19 Mar 2014 18:52:38 +0000 Subject: [PATCH 272/384] symlink /etc/mtab and /proc/mounts Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- graph/graph.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/graph/graph.go b/graph/graph.go index 4349cac129..a177cbd1e1 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -259,6 +259,7 @@ func SetupInitLayer(initLayer string) error { "/etc/hosts": "file", "/etc/hostname": "file", "/dev/console": "file", + "/etc/mtab": "/proc/mounts", // "var/run": "dir", // "var/lock": "dir", } { @@ -285,6 +286,10 @@ func SetupInitLayer(initLayer string) error { return err } f.Close() + default: + if err := os.Symlink(typ, path.Join(initLayer, pth)); err != nil { + return err + } } } else { return err From 289377b409b321a5a624af3517032c396df6c22f Mon Sep 17 00:00:00 2001 From: Josh Hawn Date: Mon, 31 Mar 2014 15:32:57 -0700 Subject: [PATCH 273/384] No longer expose gravatar_email in docker.io api Docker.io API has replaced the gravatar_email field with a gravatar_url field instead. Docker-DCO-1.1-Signed-off-by: Josh Hawn (github: jlhawn) --- docs/sources/reference/api/docker_io_accounts_api.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sources/reference/api/docker_io_accounts_api.rst b/docs/sources/reference/api/docker_io_accounts_api.rst index 7976f1fddf..dc5c44d4a8 100644 --- a/docs/sources/reference/api/docker_io_accounts_api.rst +++ b/docs/sources/reference/api/docker_io_accounts_api.rst @@ -49,14 +49,14 @@ docker.io Accounts API { "id": 2, "username": "janedoe", - "url": "", + "url": "https://www.docker.io/api/v1.1/users/janedoe/", "date_joined": "2014-02-12T17:58:01.431312Z", "type": "User", "full_name": "Jane Doe", "location": "San Francisco, CA", "company": "Success, Inc.", "profile_url": "https://docker.io/", - "gravatar_email": "jane.doe+gravatar@example.com", + "gravatar_url": "https://secure.gravatar.com/avatar/0212b397124be4acd4e7dea9aa357.jpg?s=80&r=g&d=mm" "email": "jane.doe@example.com", "is_active": true } @@ -111,14 +111,14 @@ docker.io Accounts API { "id": 2, "username": "janedoe", - "url": "", + "url": "https://www.docker.io/api/v1.1/users/janedoe/", "date_joined": "2014-02-12T17:58:01.431312Z", "type": "User", "full_name": "Jane Doe", "location": "Private Island", "company": "Retired", "profile_url": "http://janedoe.com/", - "gravatar_email": "jane.doe+gravatar@example.com", + "gravatar_url": "https://secure.gravatar.com/avatar/0212b397124be4acd4e7dea9aa357.jpg?s=80&r=g&d=mm" "email": "jane.doe@example.com", "is_active": true } From 4cdcea20474a9f42291fe6b6c6dee348343a7c05 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 31 Mar 2014 21:02:42 +0000 Subject: [PATCH 274/384] Set bridge mac addr on supported kernels Fixes #3200 Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/netlink/netlink_linux.go | 51 ++++++++++++++++++++++++++ pkg/netlink/netlink_unsupported.go | 4 ++ runtime/networkdriver/bridge/driver.go | 30 +++------------ 3 files changed, 61 insertions(+), 24 deletions(-) diff --git a/pkg/netlink/netlink_linux.go b/pkg/netlink/netlink_linux.go index f8bb6bac3c..f4aa92ed34 100644 --- a/pkg/netlink/netlink_linux.go +++ b/pkg/netlink/netlink_linux.go @@ -5,6 +5,7 @@ package netlink import ( "encoding/binary" "fmt" + "math/rand" "net" "syscall" "unsafe" @@ -17,10 +18,16 @@ const ( IFLA_INFO_DATA = 2 VETH_INFO_PEER = 1 IFLA_NET_NS_FD = 28 + SIOC_BRADDBR = 0x89a0 ) var nextSeqNr int +type ifreqHwaddr struct { + IfrnName [16]byte + IfruHwaddr syscall.RawSockaddr +} + func nativeEndian() binary.ByteOrder { var x uint32 = 0x01020304 if *(*byte)(unsafe.Pointer(&x)) == 0x01 { @@ -808,3 +815,47 @@ func NetworkCreateVethPair(name1, name2 string) error { } return s.HandleAck(wb.Seq) } + +// Create the actual bridge device. This is more backward-compatible than +// netlink.NetworkLinkAdd and works on RHEL 6. +func CreateBridge(name string, setMacAddr bool) error { + s, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_STREAM, syscall.IPPROTO_IP) + if err != nil { + // ipv6 issue, creating with ipv4 + s, err = syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_IP) + if err != nil { + return err + } + } + defer syscall.Close(s) + + nameBytePtr, err := syscall.BytePtrFromString(name) + if err != nil { + return err + } + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), SIOC_BRADDBR, uintptr(unsafe.Pointer(nameBytePtr))); err != 0 { + return err + } + if setMacAddr { + return setBridgeMacAddress(s, name) + } + return nil +} + +func setBridgeMacAddress(s int, name string) error { + ifr := ifreqHwaddr{} + ifr.IfruHwaddr.Family = syscall.ARPHRD_ETHER + copy(ifr.IfrnName[:], name) + + for i := 0; i < 6; i++ { + ifr.IfruHwaddr.Data[i] = int8(rand.Intn(255)) + } + + ifr.IfruHwaddr.Data[0] &^= 0x1 // clear multicast bit + ifr.IfruHwaddr.Data[0] |= 0x2 // set local assignment bit (IEEE802) + + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), syscall.SIOCSIFHWADDR, uintptr(unsafe.Pointer(&ifr))); err != 0 { + return err + } + return nil +} diff --git a/pkg/netlink/netlink_unsupported.go b/pkg/netlink/netlink_unsupported.go index bd9e962d35..00a3b3fae8 100644 --- a/pkg/netlink/netlink_unsupported.go +++ b/pkg/netlink/netlink_unsupported.go @@ -59,3 +59,7 @@ func NetworkSetMaster(iface, master *net.Interface) error { func NetworkLinkDown(iface *net.Interface) error { return ErrNotImplemented } + +func CreateBridge(name string, setMacAddr bool) error { + return ErrNotImplemented +} diff --git a/runtime/networkdriver/bridge/driver.go b/runtime/networkdriver/bridge/driver.go index 61e82dd481..f7c3bc6b01 100644 --- a/runtime/networkdriver/bridge/driver.go +++ b/runtime/networkdriver/bridge/driver.go @@ -14,13 +14,10 @@ import ( "log" "net" "strings" - "syscall" - "unsafe" ) const ( DefaultNetworkBridge = "docker0" - siocBRADDBR = 0x89a0 ) // Network interface represents the networking stack of a container @@ -281,28 +278,13 @@ func createBridge(bridgeIP string) error { return nil } -// Create the actual bridge device. This is more backward-compatible than -// netlink.NetworkLinkAdd and works on RHEL 6. func createBridgeIface(name string) error { - s, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_STREAM, syscall.IPPROTO_IP) - if err != nil { - utils.Debugf("Bridge socket creation failed IPv6 probably not enabled: %v", err) - s, err = syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_IP) - if err != nil { - return fmt.Errorf("Error creating bridge creation socket: %s", err) - } - } - defer syscall.Close(s) - - nameBytePtr, err := syscall.BytePtrFromString(name) - if err != nil { - return fmt.Errorf("Error converting bridge name %s to byte array: %s", name, err) - } - - if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), siocBRADDBR, uintptr(unsafe.Pointer(nameBytePtr))); err != 0 { - return fmt.Errorf("Error creating bridge: %s", err) - } - return nil + kv, err := utils.GetKernelVersion() + // only set the bridge's mac address if the kernel version is > 3.3 + // before that it was not supported + setBridgeMacAddr := err == nil && (kv.Kernel >= 3 && kv.Major >= 3) + utils.Debugf("setting bridge mac address = %v", setBridgeMacAddr) + return netlink.CreateBridge(name, setBridgeMacAddr) } // Allocate a network interface From 7a7f59210d5eb7a38a5fac8889010bd54576ea01 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 31 Mar 2014 23:12:08 +0000 Subject: [PATCH 275/384] Ensure secound part of the key is provided Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runconfig/parse.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/runconfig/parse.go b/runconfig/parse.go index a330c6c869..da4e045cd0 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -243,6 +243,8 @@ func parseDriverOpts(opts opts.ListOpts) (map[string][]string, error) { parts := strings.SplitN(o, ".", 2) if len(parts) < 2 { return nil, fmt.Errorf("invalid opt format %s", o) + } else if strings.TrimSpace(parts[0]) == "" { + return nil, fmt.Errorf("key cannot be empty %s", o) } values, exists := out[parts[0]] if !exists { From d52d24dd801f3ffe1b894226b8dba613de59bd87 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 1 Apr 2014 00:28:44 +0000 Subject: [PATCH 276/384] remove setupDev from libcontainer Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- pkg/libcontainer/nsinit/mount.go | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index 796143c68e..59f4a0e0d9 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/nsinit/mount.go @@ -62,9 +62,6 @@ func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, cons } // In non-privileged mode, this fails. Discard the error. setupLoopbackDevices(rootfs) - if err := setupDev(rootfs); err != nil { - return err - } if err := setupPtmx(rootfs, console, mountLabel); err != nil { return err } @@ -172,30 +169,6 @@ func copyDevNode(rootfs, node string) error { return nil } -// setupDev symlinks the current processes pipes into the -// appropriate destination on the containers rootfs -func setupDev(rootfs string) error { - for _, link := range []struct { - from string - to string - }{ - {"/proc/kcore", "/dev/core"}, - {"/proc/self/fd", "/dev/fd"}, - {"/proc/self/fd/0", "/dev/stdin"}, - {"/proc/self/fd/1", "/dev/stdout"}, - {"/proc/self/fd/2", "/dev/stderr"}, - } { - dest := filepath.Join(rootfs, link.to) - if err := os.Remove(dest); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("remove %s %s", dest, err) - } - if err := os.Symlink(link.from, dest); err != nil { - return fmt.Errorf("symlink %s %s", dest, err) - } - } - return nil -} - // setupConsole ensures that the container has a proper /dev/console setup func setupConsole(rootfs, console string, mountLabel string) error { oldMask := system.Umask(0000) From 3f0886c8c3084341e9ef454bf41445cfc22efca2 Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Mon, 31 Mar 2014 17:56:25 -0700 Subject: [PATCH 277/384] Inverted layer checksum and tarsum. The checksum of the payload has to be computed on the Gzip'ed content. Docker-DCO-1.1-Signed-off-by: Sam Alba (github: samalba) --- registry/registry.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index 182ec78a76..414283b82c 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -438,10 +438,10 @@ func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registr utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgID+"/layer") h := sha256.New() - checksumLayer := &utils.CheckSum{Reader: layer, Hash: h} - tarsumLayer := &utils.TarSum{Reader: checksumLayer} + tarsumLayer := &utils.TarSum{Reader: layer} + checksumLayer := &utils.CheckSum{Reader: tarsumLayer, Hash: h} - req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgID+"/layer", tarsumLayer) + req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgID+"/layer", checksumLayer) if err != nil { return "", "", err } From de9fba71721f71f86d53cf94504b10dcea80a5bd Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Mon, 31 Mar 2014 18:31:15 -0700 Subject: [PATCH 278/384] Payload checksum now match the checksum simple Backported for backward compatibility. Docker-DCO-1.1-Signed-off-by: Sam Alba (github: samalba) --- registry/registry.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/registry/registry.go b/registry/registry.go index 414283b82c..5ac04f9e7e 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -437,8 +437,10 @@ func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registr utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgID+"/layer") - h := sha256.New() tarsumLayer := &utils.TarSum{Reader: layer} + h := sha256.New() + h.Write(jsonRaw) + h.Write([]byte{'\n'}) checksumLayer := &utils.CheckSum{Reader: tarsumLayer, Hash: h} req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgID+"/layer", checksumLayer) From e648a186d68dcb3ee0d6123b041c5aa66438cc89 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 31 Mar 2014 18:50:10 -0700 Subject: [PATCH 279/384] Allow push of a single tag Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- api/client/commands.go | 9 ++++++--- api/server/server.go | 1 + server/server.go | 33 ++++++++++++++++++++++++--------- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/api/client/commands.go b/api/client/commands.go index 49cd07700f..ef9796b747 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -1000,7 +1000,7 @@ func (cli *DockerCli) CmdImport(args ...string) error { } func (cli *DockerCli) CmdPush(args ...string) error { - cmd := cli.Subcmd("push", "NAME", "Push an image or a repository to the registry") + cmd := cli.Subcmd("push", "NAME[:TAG]", "Push an image or a repository to the registry") if err := cmd.Parse(args); err != nil { return nil } @@ -1013,8 +1013,10 @@ func (cli *DockerCli) CmdPush(args ...string) error { cli.LoadConfigFile() + remote, tag := utils.ParseRepositoryTag(name) + // Resolve the Repository name from fqn to hostname + name - hostname, _, err := registry.ResolveRepositoryName(name) + hostname, _, err := registry.ResolveRepositoryName(remote) if err != nil { return err } @@ -1033,6 +1035,7 @@ func (cli *DockerCli) CmdPush(args ...string) error { } v := url.Values{} + v.Set("tag", tag) push := func(authConfig registry.AuthConfig) error { buf, err := json.Marshal(authConfig) if err != nil { @@ -1042,7 +1045,7 @@ func (cli *DockerCli) CmdPush(args ...string) error { base64.URLEncoding.EncodeToString(buf), } - return cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), nil, cli.out, map[string][]string{ + return cli.stream("POST", "/images/"+remote+"/push?"+v.Encode(), nil, cli.out, map[string][]string{ "X-Registry-Auth": registryAuthHeader, }) } diff --git a/api/server/server.go b/api/server/server.go index 18aefe42cd..5597d8b92c 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -517,6 +517,7 @@ func postImagesPush(eng *engine.Engine, version version.Version, w http.Response job := eng.Job("push", vars["name"]) job.SetenvJson("metaHeaders", metaHeaders) job.SetenvJson("authConfig", authConfig) + job.Setenv("tag", r.Form.Get("tag")) if version.GreaterThan("1.0") { job.SetenvBool("json", true) streamJSON(job, w, true) diff --git a/server/server.go b/server/server.go index 2cb3328d55..3e97481e0e 100644 --- a/server/server.go +++ b/server/server.go @@ -1401,7 +1401,7 @@ func (srv *Server) ImagePull(job *engine.Job) engine.Status { } // Retrieve the all the images to be uploaded in the correct order -func (srv *Server) getImageList(localRepo map[string]string) ([]string, map[string][]string, error) { +func (srv *Server) getImageList(localRepo map[string]string, requestedTag string) ([]string, map[string][]string, error) { var ( imageList []string imagesSeen map[string]bool = make(map[string]bool) @@ -1409,6 +1409,9 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]string, map[stri ) for tag, id := range localRepo { + if requestedTag != "" && requestedTag != tag { + continue + } var imageListForThisTag []string tagsByImage[id] = append(tagsByImage[id], tag) @@ -1435,25 +1438,29 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]string, map[stri // append to main image list imageList = append(imageList, imageListForThisTag...) } - + if len(imageList) == 0 { + return nil, nil, fmt.Errorf("No images found for the requested repository / tag") + } utils.Debugf("Image list: %v", imageList) utils.Debugf("Tags by image: %v", tagsByImage) return imageList, tagsByImage, nil } -func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName, remoteName string, localRepo map[string]string, sf *utils.StreamFormatter) error { +func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName, remoteName string, localRepo map[string]string, tag string, sf *utils.StreamFormatter) error { out = utils.NewWriteFlusher(out) utils.Debugf("Local repo: %s", localRepo) - imgList, tagsByImage, err := srv.getImageList(localRepo) + imgList, tagsByImage, err := srv.getImageList(localRepo, tag) if err != nil { return err } out.Write(sf.FormatStatus("", "Sending image list")) - var repoData *registry.RepositoryData - var imageIndex []*registry.ImgData + var ( + repoData *registry.RepositoryData + imageIndex []*registry.ImgData + ) for _, imgId := range imgList { if tags, exists := tagsByImage[imgId]; exists { @@ -1488,8 +1495,12 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName return err } + nTag := 1 + if tag == "" { + nTag = len(localRepo) + } for _, ep := range repoData.Endpoints { - out.Write(sf.FormatStatus("", "Pushing repository %s (%d tags)", localName, len(localRepo))) + out.Write(sf.FormatStatus("", "Pushing repository %s (%d tags)", localName, nTag)) for _, imgId := range imgList { if r.LookupRemoteImage(imgId, ep, repoData.Tokens) { @@ -1575,6 +1586,7 @@ func (srv *Server) ImagePush(job *engine.Job) engine.Status { metaHeaders map[string][]string ) + tag := job.Getenv("tag") job.GetenvJson("authConfig", authConfig) job.GetenvJson("metaHeaders", metaHeaders) if _, err := srv.poolAdd("push", localName); err != nil { @@ -1600,11 +1612,14 @@ func (srv *Server) ImagePush(job *engine.Job) engine.Status { } if err != nil { - reposLen := len(srv.runtime.Repositories().Repositories[localName]) + reposLen := 1 + if tag == "" { + reposLen = len(srv.runtime.Repositories().Repositories[localName]) + } job.Stdout.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", localName, reposLen)) // If it fails, try to get the repository if localRepo, exists := srv.runtime.Repositories().Repositories[localName]; exists { - if err := srv.pushRepository(r, job.Stdout, localName, remoteName, localRepo, sf); err != nil { + if err := srv.pushRepository(r, job.Stdout, localName, remoteName, localRepo, tag, sf); err != nil { return job.Error(err) } return engine.StatusOK From 739d1244807bc3522a0af4dc3490305d6f037601 Mon Sep 17 00:00:00 2001 From: tjmehta Date: Mon, 31 Mar 2014 22:21:52 -0700 Subject: [PATCH 280/384] make findNextPort circular, add all-ports-allocated error Docker-DCO-1.1-Signed-off-by: Tejesh Mehta (github: tjmehta) --- runtime/networkdriver/portallocator/portallocator.go | 12 ++++++++---- .../portallocator/portallocator_test.go | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/runtime/networkdriver/portallocator/portallocator.go b/runtime/networkdriver/portallocator/portallocator.go index 4d698f2de2..9ecd447116 100644 --- a/runtime/networkdriver/portallocator/portallocator.go +++ b/runtime/networkdriver/portallocator/portallocator.go @@ -18,8 +18,8 @@ type ( ) var ( + ErrAllPortsAllocated = errors.New("all ports are allocated") ErrPortAlreadyAllocated = errors.New("port has already been allocated") - ErrPortExceedsRange = errors.New("port exceeds upper range") ErrUnknownProtocol = errors.New("unknown protocol") ) @@ -152,17 +152,21 @@ func equalsDefault(ip net.IP) bool { func findNextPort(proto string, allocated *collections.OrderedIntSet) (int, error) { port := nextPort(proto) + startSearchPort := port for allocated.Exists(port) { port = nextPort(proto) - } - if port > EndPortRange { - return 0, ErrPortExceedsRange + if startSearchPort == port { + return 0, ErrAllPortsAllocated + } } return port, nil } func nextPort(proto string) int { c := currentDynamicPort[proto] + 1 + if c > EndPortRange { + c = BeginPortRange + } currentDynamicPort[proto] = c return c } diff --git a/runtime/networkdriver/portallocator/portallocator_test.go b/runtime/networkdriver/portallocator/portallocator_test.go index f01bcfc99e..8b4062c37c 100644 --- a/runtime/networkdriver/portallocator/portallocator_test.go +++ b/runtime/networkdriver/portallocator/portallocator_test.go @@ -110,8 +110,8 @@ func TestAllocateAllPorts(t *testing.T) { } } - if _, err := RequestPort(defaultIP, "tcp", 0); err != ErrPortExceedsRange { - t.Fatalf("Expected error %s got %s", ErrPortExceedsRange, err) + if _, err := RequestPort(defaultIP, "tcp", 0); err != ErrAllPortsAllocated { + t.Fatalf("Expected error %s got %s", ErrAllPortsAllocated, err) } _, err := RequestPort(defaultIP, "udp", 0) From 40c6d00c97c737d9d3827f159518007803affcc7 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 1 Apr 2014 07:07:42 +0000 Subject: [PATCH 281/384] Update imports to be consistent Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runtime/execdriver/native/create.go | 3 ++- runtime/utils_test.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/runtime/execdriver/native/create.go b/runtime/execdriver/native/create.go index 976416a8ca..71fab3e064 100644 --- a/runtime/execdriver/native/create.go +++ b/runtime/execdriver/native/create.go @@ -2,12 +2,13 @@ package native import ( "fmt" + "os" + "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/runtime/execdriver" "github.com/dotcloud/docker/runtime/execdriver/native/configuration" "github.com/dotcloud/docker/runtime/execdriver/native/template" - "os" ) // createContainer populates and configures the container type with the diff --git a/runtime/utils_test.go b/runtime/utils_test.go index 833634cb47..bdf3543a49 100644 --- a/runtime/utils_test.go +++ b/runtime/utils_test.go @@ -1,9 +1,10 @@ package runtime import ( + "testing" + "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" - "testing" ) func TestMergeLxcConfig(t *testing.T) { From f067e263677fc86f9610ca61fbe42f63efad91f2 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 25 Mar 2014 23:21:07 +0000 Subject: [PATCH 282/384] Ensure that all containers are stopped cleanly at shutdown Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runtime/runtime.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/runtime/runtime.go b/runtime/runtime.go index b035f5df9f..85880ff9ab 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -778,8 +778,31 @@ func NewRuntimeFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (* return runtime, nil } +func (runtime *Runtime) shutdown() error { + group := sync.WaitGroup{} + utils.Debugf("starting clean shutdown of all containers...") + for _, container := range runtime.List() { + if container.State.IsRunning() { + utils.Debugf("stopping %s", container.ID) + group.Add(1) + + go func() { + defer group.Done() + container.Stop(10) + }() + } + } + group.Wait() + + return nil +} + func (runtime *Runtime) Close() error { errorsStrings := []string{} + if err := runtime.shutdown(); err != nil { + utils.Errorf("runtime.shutdown(): %s", err) + errorsStrings = append(errorsStrings, err.Error()) + } if err := portallocator.ReleaseAll(); err != nil { utils.Errorf("portallocator.ReleaseAll(): %s", err) errorsStrings = append(errorsStrings, err.Error()) From 5b9069bd990dca0a35d8e490c6f6b56d27163bb8 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 26 Mar 2014 00:04:55 +0000 Subject: [PATCH 283/384] Add kill for other drivers on restart Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runtime/runtime.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/runtime/runtime.go b/runtime/runtime.go index 85880ff9ab..4ece7d1533 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -174,6 +174,7 @@ func (runtime *Runtime) Register(container *Container) error { if container.State.IsGhost() { utils.Debugf("killing ghost %s", container.ID) + existingPid := container.State.Pid container.State.SetGhost(false) container.State.SetStopped(0) @@ -181,9 +182,20 @@ func (runtime *Runtime) Register(container *Container) error { // no ghost processes are left when docker dies if container.ExecDriver == "" || strings.Contains(container.ExecDriver, "lxc") { lxc.KillLxc(container.ID, 9) - if err := container.Unmount(); err != nil { - utils.Debugf("ghost unmount error %s", err) + } else { + // use the current driver and ensure that the container is dead x.x + cmd := &execdriver.Command{ + ID: container.ID, } + var err error + cmd.Process, err = os.FindProcess(existingPid) + if err != nil { + utils.Debugf("cannot find existing process for %d", existingPid) + } + runtime.execDriver.Kill(cmd, 9) + } + if err := container.Unmount(); err != nil { + utils.Debugf("ghost unmount error %s", err) } } From 5bb82f6313d7f789783ffac854be85a44a56617e Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 26 Mar 2014 06:48:16 +0000 Subject: [PATCH 284/384] Ensure a reliable way to kill ghost containers on reboot Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/exec.go | 7 +++++- pkg/libcontainer/nsinit/state.go | 16 +++++++++---- pkg/system/proc.go | 26 +++++++++++++++++++++ runtime/execdriver/driver.go | 1 + runtime/execdriver/lxc/driver.go | 4 ++++ runtime/execdriver/native/driver.go | 36 ++++++++++++++++++++++++++--- runtime/runtime.go | 2 +- 7 files changed, 83 insertions(+), 9 deletions(-) create mode 100644 pkg/system/proc.go diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 73842f729f..c07c45de3c 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -50,8 +50,13 @@ func (ns *linuxNs) Exec(container *libcontainer.Container, term Terminal, args [ if err := command.Start(); err != nil { return -1, err } + + started, err := system.GetProcessStartTime(command.Process.Pid) + if err != nil { + return -1, err + } ns.logger.Printf("writting pid %d to file\n", command.Process.Pid) - if err := ns.stateWriter.WritePid(command.Process.Pid); err != nil { + if err := ns.stateWriter.WritePid(command.Process.Pid, started); err != nil { command.Process.Kill() return -1, err } diff --git a/pkg/libcontainer/nsinit/state.go b/pkg/libcontainer/nsinit/state.go index af38008c03..26d7fa4230 100644 --- a/pkg/libcontainer/nsinit/state.go +++ b/pkg/libcontainer/nsinit/state.go @@ -10,7 +10,7 @@ import ( // StateWriter handles writing and deleting the pid file // on disk type StateWriter interface { - WritePid(pid int) error + WritePid(pid int, startTime string) error DeletePid() error } @@ -19,10 +19,18 @@ type DefaultStateWriter struct { } // writePidFile writes the namespaced processes pid to pid in the rootfs for the container -func (d *DefaultStateWriter) WritePid(pid int) error { - return ioutil.WriteFile(filepath.Join(d.Root, "pid"), []byte(fmt.Sprint(pid)), 0655) +func (d *DefaultStateWriter) WritePid(pid int, startTime string) error { + err := ioutil.WriteFile(filepath.Join(d.Root, "pid"), []byte(fmt.Sprint(pid)), 0655) + if err != nil { + return err + } + return ioutil.WriteFile(filepath.Join(d.Root, "start"), []byte(startTime), 0655) } func (d *DefaultStateWriter) DeletePid() error { - return os.Remove(filepath.Join(d.Root, "pid")) + err := os.Remove(filepath.Join(d.Root, "pid")) + if serr := os.Remove(filepath.Join(d.Root, "start")); err == nil { + err = serr + } + return err } diff --git a/pkg/system/proc.go b/pkg/system/proc.go new file mode 100644 index 0000000000..a492346c7f --- /dev/null +++ b/pkg/system/proc.go @@ -0,0 +1,26 @@ +package system + +import ( + "io/ioutil" + "path/filepath" + "strconv" + "strings" +) + +// look in /proc to find the process start time so that we can verify +// that this pid has started after ourself +func GetProcessStartTime(pid int) (string, error) { + data, err := ioutil.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "stat")) + if err != nil { + return "", err + } + parts := strings.Split(string(data), " ") + // the starttime is located at pos 22 + // from the man page + // + // starttime %llu (was %lu before Linux 2.6) + // (22) The time the process started after system boot. In kernels before Linux 2.6, this + // value was expressed in jiffies. Since Linux 2.6, the value is expressed in clock ticks + // (divide by sysconf(_SC_CLK_TCK)). + return parts[22-1], nil // starts at 1 +} diff --git a/runtime/execdriver/driver.go b/runtime/execdriver/driver.go index d067973419..27a575cb3a 100644 --- a/runtime/execdriver/driver.go +++ b/runtime/execdriver/driver.go @@ -84,6 +84,7 @@ type Driver interface { Name() string // Driver name Info(id string) Info // "temporary" hack (until we move state from core to plugins) GetPidsForContainer(id string) ([]int, error) // Returns a list of pids for the given container. + Terminate(c *Command) error // kill it with fire } // Network settings of the container diff --git a/runtime/execdriver/lxc/driver.go b/runtime/execdriver/lxc/driver.go index 896f215366..ef16dcc380 100644 --- a/runtime/execdriver/lxc/driver.go +++ b/runtime/execdriver/lxc/driver.go @@ -204,6 +204,10 @@ func (d *driver) Kill(c *execdriver.Command, sig int) error { return KillLxc(c.ID, sig) } +func (d *driver) Terminate(c *execdriver.Command) error { + return KillLxc(c.ID, 9) +} + func (d *driver) version() string { var ( version string diff --git a/runtime/execdriver/native/driver.go b/runtime/execdriver/native/driver.go index 4acc4b388c..c5a3837615 100644 --- a/runtime/execdriver/native/driver.go +++ b/runtime/execdriver/native/driver.go @@ -117,9 +117,39 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba } func (d *driver) Kill(p *execdriver.Command, sig int) error { - err := syscall.Kill(p.Process.Pid, syscall.Signal(sig)) + return syscall.Kill(p.Process.Pid, syscall.Signal(sig)) +} + +func (d *driver) Terminate(p *execdriver.Command) error { + // lets check the start time for the process + started, err := d.readStartTime(p) + if err != nil { + // if we don't have the data on disk then we can assume the process is gone + // because this is only removed after we know the process has stopped + if os.IsNotExist(err) { + return nil + } + return err + } + + currentStartTime, err := system.GetProcessStartTime(p.Process.Pid) + if err != nil { + return err + } + if started == currentStartTime { + err = syscall.Kill(p.Process.Pid, 9) + } d.removeContainerRoot(p.ID) return err + +} + +func (d *driver) readStartTime(p *execdriver.Command) (string, error) { + data, err := ioutil.ReadFile(filepath.Join(d.root, p.ID, "start")) + if err != nil { + return "", err + } + return string(data), nil } func (d *driver) Info(id string) execdriver.Info { @@ -235,9 +265,9 @@ type dockerStateWriter struct { callback execdriver.StartCallback } -func (d *dockerStateWriter) WritePid(pid int) error { +func (d *dockerStateWriter) WritePid(pid int, started string) error { d.c.ContainerPid = pid - err := d.dsw.WritePid(pid) + err := d.dsw.WritePid(pid, started) if d.callback != nil { d.callback(d.c) } diff --git a/runtime/runtime.go b/runtime/runtime.go index 4ece7d1533..1c99a02811 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -192,7 +192,7 @@ func (runtime *Runtime) Register(container *Container) error { if err != nil { utils.Debugf("cannot find existing process for %d", existingPid) } - runtime.execDriver.Kill(cmd, 9) + runtime.execDriver.Terminate(cmd) } if err := container.Unmount(); err != nil { utils.Debugf("ghost unmount error %s", err) From 283daced0c919be760947d44d7e46c80e1054d64 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 26 Mar 2014 06:55:46 +0000 Subject: [PATCH 285/384] Don't send prctl to be consistent with other drivers Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/init.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 85182326ee..c7c2addb18 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -54,11 +54,6 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol return fmt.Errorf("setctty %s", err) } } - // this is our best effort to let the process know that the parent has died and that it - // should it should act on it how it sees fit - if err := system.ParentDeathSignal(uintptr(syscall.SIGTERM)); err != nil { - return fmt.Errorf("parent death signal %s", err) - } if err := setupNetwork(container, context); err != nil { return fmt.Errorf("setup networking %s", err) } From e36d89b0f9c8ba5b071374310ca632f6b2fdb7a1 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 26 Mar 2014 06:59:41 +0000 Subject: [PATCH 286/384] Ensure state is saved to disk after we kill the ghost Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runtime/runtime.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/runtime/runtime.go b/runtime/runtime.go index 1c99a02811..d5c1a96ada 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -197,6 +197,9 @@ func (runtime *Runtime) Register(container *Container) error { if err := container.Unmount(); err != nil { utils.Debugf("ghost unmount error %s", err) } + if err := container.ToDisk(); err != nil { + utils.Debugf("saving ghost state to disk %s", err) + } } info := runtime.execDriver.Info(container.ID) From 93779cc7fee4ee0690d9dd28eed478a418e79577 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 1 Apr 2014 00:11:17 +0000 Subject: [PATCH 287/384] Send sigterm and wait forever Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runtime/container.go | 1 - runtime/runtime.go | 11 ++++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/runtime/container.go b/runtime/container.go index ed68fd0844..bd4a6f2bea 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -915,7 +915,6 @@ func (container *Container) Stop(seconds int) error { // 1. Send a SIGTERM if err := container.KillSig(15); err != nil { - 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 diff --git a/runtime/runtime.go b/runtime/runtime.go index d5c1a96ada..9e8323279e 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -797,13 +797,18 @@ func (runtime *Runtime) shutdown() error { group := sync.WaitGroup{} utils.Debugf("starting clean shutdown of all containers...") for _, container := range runtime.List() { - if container.State.IsRunning() { - utils.Debugf("stopping %s", container.ID) + c := container + if c.State.IsRunning() { + utils.Debugf("stopping %s", c.ID) group.Add(1) go func() { defer group.Done() - container.Stop(10) + if err := c.KillSig(15); err != nil { + utils.Debugf("kill 15 error for %s - %s", c.ID, err) + } + c.Wait() + utils.Debugf("container stopped %s", c.ID) }() } } From 6b7cfc9e95af8510abc801a02cda7879e4f82518 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 1 Apr 2014 07:27:34 +0000 Subject: [PATCH 288/384] Update test to reallocate port Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- .../portallocator/portallocator_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/runtime/networkdriver/portallocator/portallocator_test.go b/runtime/networkdriver/portallocator/portallocator_test.go index 8b4062c37c..5a4765ddd4 100644 --- a/runtime/networkdriver/portallocator/portallocator_test.go +++ b/runtime/networkdriver/portallocator/portallocator_test.go @@ -118,6 +118,19 @@ func TestAllocateAllPorts(t *testing.T) { if err != nil { t.Fatal(err) } + + // release a port in the middle and ensure we get another tcp port + port := BeginPortRange + 5 + if err := ReleasePort(defaultIP, "tcp", port); err != nil { + t.Fatal(err) + } + newPort, err := RequestPort(defaultIP, "tcp", 0) + if err != nil { + t.Fatal(err) + } + if newPort != port { + t.Fatalf("Expected port %d got %d", port, newPort) + } } func BenchmarkAllocatePorts(b *testing.B) { From ac9b06ae95f1da8407934036ab1e4019a96a6b21 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 1 Apr 2014 08:18:52 +0000 Subject: [PATCH 289/384] Update sig message Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.go b/server/server.go index 278cab2b2a..65dbcca47b 100644 --- a/server/server.go +++ b/server/server.go @@ -54,7 +54,7 @@ func InitServer(job *engine.Job) engine.Status { gosignal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) go func() { sig := <-c - log.Printf("Received signal '%v', exiting\n", sig) + log.Printf("Received signal '%v', starting shutdown of docker...\n", sig) utils.RemovePidFile(srv.runtime.Config().Pidfile) srv.Close() os.Exit(0) From bd24eb07b6c3a9448d8b4a8b3ab0d9cd60995aaa Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 28 Mar 2014 18:40:16 -0700 Subject: [PATCH 290/384] Add $BINDIR to allow mounting the whole sources if needed (for development) Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- Makefile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b3bea8a31f..717e73cb1a 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,12 @@ GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD) DOCKER_IMAGE := docker:$(GIT_BRANCH) DOCKER_DOCS_IMAGE := docker-docs:$(GIT_BRANCH) -DOCKER_RUN_DOCKER := docker run --rm -i -t --privileged -e TESTFLAGS -v "$(CURDIR)/bundles:/go/src/github.com/dotcloud/docker/bundles" "$(DOCKER_IMAGE)" + +# to allow `make BINDDIR=. shell` +BINDDIR := bundles + +DOCKER_RUN_DOCKER := docker run --rm -i -t --privileged -e TESTFLAGS -v "$(CURDIR)/$(BINDDIR):/go/src/github.com/dotcloud/docker/$(BINDDIR)" "$(DOCKER_IMAGE)" + default: binary From 4cb602afa0a905ceb0cccf49fe142c1c7b62087b Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 31 Mar 2014 20:22:28 -0600 Subject: [PATCH 291/384] Allow "SIG" prefix on signal names in `docker kill` ("SIGKILL", etc) This way, we can use both `docker kill -s INT some_container` and `docker kill -s SIGINT some_container` and both will do nice things for us. :) Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- integration/commands_test.go | 9 +++++++-- server/server.go | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/integration/commands_test.go b/integration/commands_test.go index 2dc0ff384a..15bb61b49c 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -1129,8 +1129,13 @@ func TestCmdKill(t *testing.T) { }) setTimeout(t, "SIGUSR2 timed out", 2*time.Second, func() { - for i := 0; i < 10; i++ { - if err := cli2.CmdKill("--signal=USR2", container.ID); err != nil { + for i := 0; i < 20; i++ { + sig := "USR2" + if i%2 != 0 { + // Swap to testing "SIGUSR2" for every odd iteration + sig = "SIGUSR2" + } + if err := cli2.CmdKill("--signal="+sig, container.ID); err != nil { t.Fatal(err) } if err := expectPipe("SIGUSR2", stdout); err != nil { diff --git a/server/server.go b/server/server.go index 2cb3328d55..d689c0304c 100644 --- a/server/server.go +++ b/server/server.go @@ -144,6 +144,10 @@ func (srv *Server) ContainerKill(job *engine.Job) engine.Status { if err != nil { // The signal is not a number, treat it as a string sig = uint64(signal.SignalMap[job.Args[1]]) + if sig == 0 && strings.HasPrefix(job.Args[1], "SIG") { + // If signal is prefixed with SIG, try with it stripped (ie, "SIGKILL", etc) + sig = uint64(signal.SignalMap[job.Args[1][3:]]) + } if sig == 0 { return job.Errorf("Invalid signal: %s", job.Args[1]) } From a03f83e3370f3859ebff172d504cc29817863b17 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Sat, 29 Mar 2014 22:12:34 -0700 Subject: [PATCH 292/384] Do not error when trying to start a started container Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- runtime/container.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/container.go b/runtime/container.go index 656e9ae587..ca7b23e62b 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -426,7 +426,7 @@ func (container *Container) Start() (err error) { defer container.Unlock() if container.State.IsRunning() { - return fmt.Errorf("The container %s is already running.", container.ID) + return nil } defer func() { From cff5f0357ea35245ac906a0326863ac1f8c47c61 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Sat, 29 Mar 2014 22:12:43 -0700 Subject: [PATCH 293/384] Minor cleanup Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- api/client/commands.go | 14 +++++++++----- server/server.go | 8 +++++--- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/api/client/commands.go b/api/client/commands.go index 49cd07700f..01fc9a9106 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -549,9 +549,11 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal { } func (cli *DockerCli) CmdStart(args ...string) error { - cmd := cli.Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container") - attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach container's stdout/stderr and forward all signals to the process") - openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's stdin") + var ( + cmd = cli.Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container") + attach = cmd.Bool([]string{"a", "-attach"}, false, "Attach container's stdout/stderr and forward all signals to the process") + openStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's stdin") + ) if err := cmd.Parse(args); err != nil { return nil } @@ -560,8 +562,10 @@ func (cli *DockerCli) CmdStart(args ...string) error { return nil } - var cErr chan error - var tty bool + var ( + cErr chan error + tty bool + ) if *attach || *openStdin { if cmd.NArg() > 1 { return fmt.Errorf("You cannot start and attach multiple containers at once.") diff --git a/server/server.go b/server/server.go index 2cb3328d55..af9976b4e2 100644 --- a/server/server.go +++ b/server/server.go @@ -2064,9 +2064,11 @@ func (srv *Server) ContainerStart(job *engine.Job) engine.Status { if len(job.Args) < 1 { return job.Errorf("Usage: %s container_id", job.Name) } - name := job.Args[0] - runtime := srv.runtime - container := runtime.Get(name) + var ( + name = job.Args[0] + runtime = srv.runtime + container = runtime.Get(name) + ) if container == nil { return job.Errorf("No such container: %s", name) From 7a50f03fa69fb81c27d361333fadbc9cccdd8948 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Sun, 30 Mar 2014 16:00:04 -0700 Subject: [PATCH 294/384] Update test to be consistent Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- integration/container_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration/container_test.go b/integration/container_test.go index 8ed5525c72..d3d35734ed 100644 --- a/integration/container_test.go +++ b/integration/container_test.go @@ -350,7 +350,7 @@ func TestStart(t *testing.T) { if !container.State.IsRunning() { t.Errorf("Container should be running") } - if err := container.Start(); err == nil { + if err := container.Start(); err != nil { t.Fatalf("A running container should be able to be started") } @@ -385,7 +385,7 @@ func TestCpuShares(t *testing.T) { if !container.State.IsRunning() { t.Errorf("Container should be running") } - if err := container.Start(); err == nil { + if err := container.Start(); err != nil { t.Fatalf("A running container should be able to be started") } From f9b8161c60f58d383ca0eaf5a99865b83e4a41b8 Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Tue, 1 Apr 2014 09:24:24 -0400 Subject: [PATCH 295/384] Remove hard coding of SELinux labels on systems without proper selinux policy. If a system is configured for SELinux but does not know about docker or containers, then we want the transitions of the policy to work. Hard coding the labels causes docker to break on older Fedora and RHEL systems Docker-DCO-1.1-Signed-off-by: Dan Walsh (github: rhatdan) --- pkg/selinux/selinux.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/selinux/selinux.go b/pkg/selinux/selinux.go index 5236d3fb87..6453f37ea9 100644 --- a/pkg/selinux/selinux.go +++ b/pkg/selinux/selinux.go @@ -313,12 +313,9 @@ func GetLxcContexts() (processLabel string, fileLabel string) { 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 + return "", "" } defer in.Close() @@ -352,6 +349,11 @@ func GetLxcContexts() (processLabel string, fileLabel string) { } } } + + if processLabel == "" || fileLabel == "" { + return "", "" + } + exit: mcs := IntToMcs(os.Getpid(), 1024) scon := NewContext(processLabel) From 2224e0d65adfbd08e53430a1d7c750491f788257 Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Tue, 1 Apr 2014 10:03:29 -0400 Subject: [PATCH 296/384] In certain cases, setting the process label will not happen. When the code attempts to set the ProcessLabel, it checks if SELinux Is enabled. We have seen a case with some of our patches where the code is fooled by the container to think that SELinux is not enabled. Calling label.Init before setting up the rest of the container, tells the library that SELinux is enabled and everything works fine. Docker-DCO-1.1-Signed-off-by: Dan Walsh (github: rhatdan) --- pkg/label/label.go | 3 +++ pkg/label/label_selinux.go | 4 ++++ pkg/libcontainer/nsinit/init.go | 2 ++ 3 files changed, 9 insertions(+) diff --git a/pkg/label/label.go b/pkg/label/label.go index ba1e9f48ea..be0d0ae079 100644 --- a/pkg/label/label.go +++ b/pkg/label/label.go @@ -21,3 +21,6 @@ func SetFileLabel(path string, fileLabel string) error { func GetPidCon(pid int) (string, error) { return "", nil } + +func Init() { +} diff --git a/pkg/label/label_selinux.go b/pkg/label/label_selinux.go index 300a8b6d14..64a1720996 100644 --- a/pkg/label/label_selinux.go +++ b/pkg/label/label_selinux.go @@ -67,3 +67,7 @@ func SetFileLabel(path string, fileLabel string) error { func GetPidCon(pid int) (string, error) { return selinux.Getpidcon(pid) } + +func Init() { + selinux.SelinuxEnabled() +} diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 5aa5f9f5b5..e5d69f5453 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -58,6 +58,8 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol if err := system.ParentDeathSignal(uintptr(syscall.SIGTERM)); err != nil { return fmt.Errorf("parent death signal %s", err) } + + label.Init() ns.logger.Println("setup mount namespace") 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) From b8d660d946c5f0be3a4f01867b11f26a4f303293 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 1 Apr 2014 11:17:54 -0700 Subject: [PATCH 297/384] Update docs. Make PULL up to date, remove deprecated falg and update PUSH Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- docs/sources/reference/commandline/cli.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 0c9db138c2..324b84b0ae 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -1007,12 +1007,10 @@ The last container is marked as a ``Ghost`` container. It is a container that wa :: - Usage: docker pull NAME + Usage: docker pull NAME[:TAG] Pull an image or a repository from the registry - -t, --tag="": Download tagged image in repository - .. _cli_push: @@ -1021,7 +1019,7 @@ The last container is marked as a ``Ghost`` container. It is a container that wa :: - Usage: docker push NAME + Usage: docker push NAME[:TAG] Push an image or a repository to the registry From 4bf70317ed3c2467f444eac9b7865b170da6366c Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Tue, 1 Apr 2014 13:33:46 -0600 Subject: [PATCH 298/384] Simplify the kill "SIG" prefix stripping code Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- server/server.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/server/server.go b/server/server.go index bcaaad742b..96a37527c0 100644 --- a/server/server.go +++ b/server/server.go @@ -142,12 +142,8 @@ func (srv *Server) ContainerKill(job *engine.Job) engine.Status { // The largest legal signal is 31, so let's parse on 5 bits sig, err = strconv.ParseUint(job.Args[1], 10, 5) if err != nil { - // The signal is not a number, treat it as a string - sig = uint64(signal.SignalMap[job.Args[1]]) - if sig == 0 && strings.HasPrefix(job.Args[1], "SIG") { - // If signal is prefixed with SIG, try with it stripped (ie, "SIGKILL", etc) - sig = uint64(signal.SignalMap[job.Args[1][3:]]) - } + // The signal is not a number, treat it as a string (either like "KILL" or like "SIGKILL") + sig = uint64(signal.SignalMap[strings.TrimPrefix(job.Args[1], "SIG")]) if sig == 0 { return job.Errorf("Invalid signal: %s", job.Args[1]) } From dcf2b72f5b6732a4b9b1897cb2b3f7019e3d547e Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 1 Apr 2014 21:07:40 +0000 Subject: [PATCH 299/384] add test Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- integration-cli/docker_cli_diff_test.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/integration-cli/docker_cli_diff_test.go b/integration-cli/docker_cli_diff_test.go index 5f8ba74161..0ae9cca38d 100644 --- a/integration-cli/docker_cli_diff_test.go +++ b/integration-cli/docker_cli_diff_test.go @@ -64,3 +64,28 @@ func TestDiffEnsureDockerinitFilesAreIgnored(t *testing.T) { logDone("diff - check if ignored files show up in diff") } + +func TestDiffEnsureOnlyKmsgAndPtmx(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sleep 0") + cid, _, err := runCommandWithOutput(runCmd) + errorOut(err, t, fmt.Sprintf("%s", err)) + cleanCID := stripTrailingCharacters(cid) + + diffCmd := exec.Command(dockerBinary, "diff", cleanCID) + out, _, err := runCommandWithOutput(diffCmd) + errorOut(err, t, fmt.Sprintf("failed to run diff: %v %v", out, err)) + go deleteContainer(cleanCID) + + expected := map[string]bool{ + "C /dev": true, + "A /dev/full": true, // busybox + "C /dev/ptmx": true, // libcontainer + "A /dev/kmsg": true, // lxc + } + + for _, line := range strings.Split(out, "\n") { + if line != "" && !expected[line] { + t.Errorf("'%s' is shown in the diff but shouldn't", line) + } + } +} From 026aebdebbaa05eab25134949ed5d5bda655ba67 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 1 Apr 2014 14:17:31 -0700 Subject: [PATCH 300/384] Change ownership to root for ADD file/directory Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- server/buildfile.go | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/server/buildfile.go b/server/buildfile.go index 5b098ce3e9..b4a860ad4d 100644 --- a/server/buildfile.go +++ b/server/buildfile.go @@ -419,10 +419,22 @@ func (b *buildFile) addContext(container *runtime.Container, orig, dest string, return err } + chownR := func(destPath string, uid, gid int) error { + return filepath.Walk(destPath, func(path string, info os.FileInfo, err error) error { + if err := os.Lchown(path, uid, gid); err != nil { + return err + } + return nil + }) + } + if fi.IsDir() { if err := archive.CopyWithTar(origPath, destPath); err != nil { return err } + if err := chownR(destPath, 0, 0); err != nil { + return err + } return nil } @@ -452,6 +464,10 @@ func (b *buildFile) addContext(container *runtime.Container, orig, dest string, if err := archive.CopyWithTar(origPath, destPath); err != nil { return err } + + if err := chownR(destPath, 0, 0); err != nil { + return err + } return nil } @@ -486,28 +502,36 @@ func (b *buildFile) CmdAdd(args string) error { ) if utils.IsURL(orig) { + // Initiate the download isRemote = true resp, err := utils.Download(orig) if err != nil { return err } + + // Create a tmp dir tmpDirName, err := ioutil.TempDir(b.contextPath, "docker-remote") if err != nil { return err } + + // Create a tmp file within our tmp dir tmpFileName := path.Join(tmpDirName, "tmp") tmpFile, err := os.OpenFile(tmpFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) if err != nil { return err } defer os.RemoveAll(tmpDirName) - if _, err = io.Copy(tmpFile, resp.Body); err != nil { + + // Download and dump result to tmp file + if _, err := io.Copy(tmpFile, resp.Body); err != nil { tmpFile.Close() return err } - origPath = path.Join(filepath.Base(tmpDirName), filepath.Base(tmpFileName)) tmpFile.Close() + origPath = path.Join(filepath.Base(tmpDirName), filepath.Base(tmpFileName)) + // Process the checksum r, err := archive.Tar(tmpFileName, archive.Uncompressed) if err != nil { From 3ee37f547f4685ab88bfc39517cc18c1911451e5 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 1 Apr 2014 15:46:52 -0700 Subject: [PATCH 301/384] Update Version to not use string anymore Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- api/common.go | 7 ++++--- api/server/server.go | 2 +- pkg/version/version.go | 14 +++++++------- pkg/version/version_test.go | 2 +- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/api/common.go b/api/common.go index 7273e5c56d..44bd901379 100644 --- a/api/common.go +++ b/api/common.go @@ -3,15 +3,16 @@ package api import ( "fmt" "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/pkg/version" "github.com/dotcloud/docker/utils" "mime" "strings" ) const ( - APIVERSION = "1.10" - DEFAULTHTTPHOST = "127.0.0.1" - DEFAULTUNIXSOCKET = "/var/run/docker.sock" + APIVERSION version.Version = "1.10" + DEFAULTHTTPHOST = "127.0.0.1" + DEFAULTUNIXSOCKET = "/var/run/docker.sock" ) func ValidateHost(val string) (string, error) { diff --git a/api/server/server.go b/api/server/server.go index 5597d8b92c..93dd2094b6 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -940,7 +940,7 @@ func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, local if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") { userAgent := strings.Split(r.Header.Get("User-Agent"), "/") - if len(userAgent) == 2 && !dockerVersion.Equal(userAgent[1]) { + if len(userAgent) == 2 && !dockerVersion.Equal(version.Version(userAgent[1])) { utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion) } } diff --git a/pkg/version/version.go b/pkg/version/version.go index 3721d64aa8..5ff9d2ed2a 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -7,10 +7,10 @@ import ( type Version string -func (me Version) compareTo(other string) int { +func (me Version) compareTo(other Version) int { var ( meTab = strings.Split(string(me), ".") - otherTab = strings.Split(other, ".") + otherTab = strings.Split(string(other), ".") ) for i, s := range meTab { var meInt, otherInt int @@ -31,22 +31,22 @@ func (me Version) compareTo(other string) int { return 0 } -func (me Version) LessThan(other string) bool { +func (me Version) LessThan(other Version) bool { return me.compareTo(other) == -1 } -func (me Version) LessThanOrEqualTo(other string) bool { +func (me Version) LessThanOrEqualTo(other Version) bool { return me.compareTo(other) <= 0 } -func (me Version) GreaterThan(other string) bool { +func (me Version) GreaterThan(other Version) bool { return me.compareTo(other) == 1 } -func (me Version) GreaterThanOrEqualTo(other string) bool { +func (me Version) GreaterThanOrEqualTo(other Version) bool { return me.compareTo(other) >= 0 } -func (me Version) Equal(other string) bool { +func (me Version) Equal(other Version) bool { return me.compareTo(other) == 0 } diff --git a/pkg/version/version_test.go b/pkg/version/version_test.go index 4bebd0c434..27c0536c2f 100644 --- a/pkg/version/version_test.go +++ b/pkg/version/version_test.go @@ -5,7 +5,7 @@ import ( ) func assertVersion(t *testing.T, a, b string, result int) { - if r := Version(a).compareTo(b); r != result { + if r := Version(a).compareTo(Version(b)); r != result { t.Fatalf("Unexpected version comparison result. Found %d, expected %d", r, result) } } From 9c0c4aeda47fcebc4470f7ba786749af2999ec78 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Tue, 1 Apr 2014 17:42:54 -0600 Subject: [PATCH 302/384] Add basic initial "check-config" script to contrib Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- contrib/check-config.sh | 102 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100755 contrib/check-config.sh diff --git a/contrib/check-config.sh b/contrib/check-config.sh new file mode 100755 index 0000000000..62606aca04 --- /dev/null +++ b/contrib/check-config.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +set -e + +# bits of this were adapted from lxc-checkconfig +# see also https://github.com/lxc/lxc/blob/lxc-1.0.2/src/lxc/lxc-checkconfig.in + +: ${CONFIG:=/proc/config.gz} +: ${GREP:=zgrep} + +if [ ! -e "$CONFIG" ]; then + echo >&2 "warning: $CONFIG does not exist, searching other paths for kernel config..." + if [ -e "/boot/config-$(uname -r)" ]; then + CONFIG="/boot/config-$(uname -r)" + elif [ -e '/usr/src/linux/.config' ]; then + CONFIG='/usr/src/linux/.config' + else + echo >&2 "error: cannot find kernel config" + echo >&2 " try running this script again, specifying the kernel config:" + echo >&2 " CONFIG=/path/to/kernel/.config $0" + exit 1 + fi +fi + +is_set() { + $GREP "CONFIG_$1=[y|m]" $CONFIG > /dev/null +} + +color() { + color= + prefix= + if [ "$1" = 'bold' ]; then + prefix='1;' + shift + fi + case "$1" in + green) color='32' ;; + red) color='31' ;; + gray) color='30' ;; + reset) color='' ;; + esac + echo -en '\033['"$prefix$color"m +} + +check_flag() { + if is_set "$1"; then + color green + echo -n enabled + else + color bold red + echo -n missing + fi + color reset +} + +check_flags() { + for flag in "$@"; do + echo "- CONFIG_$flag: $(check_flag "$flag")" + done +} + +echo + +# TODO check that the cgroupfs hierarchy is properly mounted + +echo 'Generally Necessary:' +flags=( + NAMESPACES {NET,PID,IPC,UTS}_NS + DEVPTS_MULTIPLE_INSTANCES + CGROUPS CGROUP_DEVICE + MACVLAN VETH BRIDGE + IP_NF_TARGET_MASQUERADE NETFILTER_XT_MATCH_{ADDRTYPE,CONNTRACK} + NF_NAT NF_NAT_NEEDED +) +check_flags "${flags[@]}" +echo + +echo 'Optional Features:' +flags=( + MEMCG_SWAP + RESOURCE_COUNTERS +) +check_flags "${flags[@]}" + +echo '- Storage Drivers:' +{ + echo '- "aufs":' + check_flags AUFS_FS | sed 's/^/ /' + if ! is_set AUFS_FS && grep -q aufs /proc/filesystems; then + echo " $(color bold gray)(note that some kernels include AUFS patches but not the AUFS_FS flag)$(color reset)" + fi + + echo '- "btrfs":' + check_flags BTRFS_FS | sed 's/^/ /' + + echo '- "devicemapper":' + check_flags BLK_DEV_DM DM_THIN_PROVISIONING EXT4_FS | sed 's/^/ /' +} | sed 's/^/ /' +echo + +#echo 'Potential Future Features:' +#check_flags USER_NS +#echo From b246fc33ae4f05b5084fed8fc9f1034e36d87d78 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 1 Apr 2014 17:30:02 -0700 Subject: [PATCH 303/384] Add API version to `docker version` Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- api/client/commands.go | 4 ++++ integration-cli/docker_cli_version_test.go | 12 +++++++++++- server/server.go | 2 ++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/api/client/commands.go b/api/client/commands.go index eddfad5c30..53b8822d69 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -357,6 +357,7 @@ func (cli *DockerCli) CmdVersion(args ...string) error { if dockerversion.VERSION != "" { fmt.Fprintf(cli.out, "Client version: %s\n", dockerversion.VERSION) } + fmt.Fprintf(cli.out, "Client API version: %s\n", api.APIVERSION) fmt.Fprintf(cli.out, "Go version (client): %s\n", goruntime.Version()) if dockerversion.GITCOMMIT != "" { fmt.Fprintf(cli.out, "Git commit (client): %s\n", dockerversion.GITCOMMIT) @@ -379,6 +380,9 @@ func (cli *DockerCli) CmdVersion(args ...string) error { } out.Close() fmt.Fprintf(cli.out, "Server version: %s\n", remoteVersion.Get("Version")) + if apiVersion := remoteVersion.Get("ApiVersion"); apiVersion != "" { + fmt.Fprintf(cli.out, "Server API version: %s\n", apiVersion) + } fmt.Fprintf(cli.out, "Git commit (server): %s\n", remoteVersion.Get("GitCommit")) fmt.Fprintf(cli.out, "Go version (server): %s\n", remoteVersion.Get("GoVersion")) release := utils.GetReleaseVersion() diff --git a/integration-cli/docker_cli_version_test.go b/integration-cli/docker_cli_version_test.go index 8adedd540b..f18d5bede6 100644 --- a/integration-cli/docker_cli_version_test.go +++ b/integration-cli/docker_cli_version_test.go @@ -17,7 +17,17 @@ func TestVersionEnsureSucceeds(t *testing.T) { t.Fatal("failed to execute docker version") } - stringsToCheck := []string{"Client version:", "Go version (client):", "Git commit (client):", "Server version:", "Git commit (server):", "Go version (server):", "Last stable version:"} + stringsToCheck := []string{ + "Client version:", + "Client API version:", + "Go version (client):", + "Git commit (client):", + "Server version:", + "Server API version:", + "Git commit (server):", + "Go version (server):", + "Last stable version:", + } for _, linePrefix := range stringsToCheck { if !strings.Contains(out, linePrefix) { diff --git a/server/server.go b/server/server.go index 65dbcca47b..a47434f73f 100644 --- a/server/server.go +++ b/server/server.go @@ -3,6 +3,7 @@ package server import ( "encoding/json" "fmt" + "github.com/dotcloud/docker/api" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/daemonconfig" "github.com/dotcloud/docker/dockerversion" @@ -823,6 +824,7 @@ func (srv *Server) DockerInfo(job *engine.Job) engine.Status { func (srv *Server) DockerVersion(job *engine.Job) engine.Status { v := &engine.Env{} v.Set("Version", dockerversion.VERSION) + v.Set("ApiVersion", api.APIVERSION) v.Set("GitCommit", dockerversion.GITCOMMIT) v.Set("GoVersion", goruntime.Version()) v.Set("Os", goruntime.GOOS) From d7ec39a4cfbee7b68e0c7973fb629da6f54d873c Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 2 Apr 2014 01:19:26 +0000 Subject: [PATCH 304/384] <3 Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.go b/server/server.go index a47434f73f..1c6c561375 100644 --- a/server/server.go +++ b/server/server.go @@ -824,7 +824,7 @@ func (srv *Server) DockerInfo(job *engine.Job) engine.Status { func (srv *Server) DockerVersion(job *engine.Job) engine.Status { v := &engine.Env{} v.Set("Version", dockerversion.VERSION) - v.Set("ApiVersion", api.APIVERSION) + v.SetJson("ApiVersion", api.APIVERSION) v.Set("GitCommit", dockerversion.GITCOMMIT) v.Set("GoVersion", goruntime.Version()) v.Set("Os", goruntime.GOOS) From b51fe1783347c1bf679870925a271531a925b7e9 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Tue, 1 Apr 2014 22:43:38 -0600 Subject: [PATCH 305/384] Update Makefile with several improvements Especially but not limited to: - make BINDDIR= ... - for when you don't want a bind mount at all - make DOCSPORT=9000 docs - for when you want a not-8000 docs port - when we can't determine a branch name, we don't try to "docker build -t docker: ." anymore - we just "docker build -t docker ." (thus allowing Docker to assume ":latest") Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- Makefile | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 9f8e70b7fe..776d57951f 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,17 @@ .PHONY: all binary build cross default docs docs-build docs-shell shell test test-integration test-integration-cli -GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD) -DOCKER_IMAGE := docker:$(GIT_BRANCH) -DOCKER_DOCS_IMAGE := docker-docs:$(GIT_BRANCH) - -# to allow `make BINDDIR=. shell` +# to allow `make BINDDIR=. shell` or `make BINDDIR= test` BINDDIR := bundles +# to allow `make DOCSPORT=9000 docs` +DOCSPORT := 8000 -DOCKER_RUN_DOCKER := docker run --rm -i -t --privileged -e TESTFLAGS -v "$(CURDIR)/$(BINDDIR):/go/src/github.com/dotcloud/docker/$(BINDDIR)" "$(DOCKER_IMAGE)" +GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null) +DOCKER_IMAGE := docker$(if $(GIT_BRANCH),:$(GIT_BRANCH)) +DOCKER_DOCS_IMAGE := docker-docs$(if $(GIT_BRANCH),:$(GIT_BRANCH)) +DOCKER_MOUNT := $(if $(BINDDIR),-v "$(CURDIR)/$(BINDDIR):/go/src/github.com/dotcloud/docker/$(BINDDIR)") +DOCKER_RUN_DOCKER := docker run --rm -it --privileged -e TESTFLAGS $(DOCKER_MOUNT) "$(DOCKER_IMAGE)" +DOCKER_RUN_DOCS := docker run --rm -it -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)" default: binary @@ -22,10 +25,10 @@ cross: build $(DOCKER_RUN_DOCKER) hack/make.sh binary cross docs: docs-build - docker run --rm -i -t -p 8000:8000 "$(DOCKER_DOCS_IMAGE)" + $(DOCKER_RUN_DOCS) docs-shell: docs-build - docker run --rm -i -t -p 8000:8000 "$(DOCKER_DOCS_IMAGE)" bash + $(DOCKER_RUN_DOCS) bash test: build $(DOCKER_RUN_DOCKER) hack/make.sh binary test test-integration test-integration-cli From 2b64453adbfdf715542b0b4274ed13e6f2a444da Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Wed, 2 Apr 2014 16:52:07 +1000 Subject: [PATCH 306/384] add RHEL6 kernel version, and a 3.8 hint to the binaries doc Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/installation/binaries.rst | 3 +++ docs/sources/installation/rhel.rst | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/docs/sources/installation/binaries.rst b/docs/sources/installation/binaries.rst index ae548e7657..7ba8c596ef 100644 --- a/docs/sources/installation/binaries.rst +++ b/docs/sources/installation/binaries.rst @@ -43,6 +43,9 @@ Check kernel dependencies Docker in daemon mode has specific kernel requirements. For details, check your distribution in :ref:`installation_list`. +In general, a 3.8 Linux kernel (or higher) is preferred, as some of the +prior versions have known issues that are triggered by Docker. + Note that Docker also has a client mode, which can run on virtually any Linux kernel (it even builds on OSX!). diff --git a/docs/sources/installation/rhel.rst b/docs/sources/installation/rhel.rst index 7930da6309..151fba6f1f 100644 --- a/docs/sources/installation/rhel.rst +++ b/docs/sources/installation/rhel.rst @@ -22,6 +22,9 @@ for the RHEL distribution. Also note that due to the current Docker limitations, Docker is able to run only on the **64 bit** architecture. +You will need `RHEL 6.5`_ or higher, with a RHEL 6 kernel version 2.6.32-431 or higher +as this has specific kernel fixes to allow Docker to work. + Installation ------------ @@ -78,4 +81,5 @@ If you have any issues - please report them directly in the `Red Hat Bugzilla fo .. _EPEL installation instructions: https://fedoraproject.org/wiki/EPEL#How_can_I_use_these_extra_packages.3F .. _Red Hat Bugzilla for docker-io component : https://bugzilla.redhat.com/enter_bug.cgi?product=Fedora%20EPEL&component=docker-io .. _bug report: https://bugzilla.redhat.com/show_bug.cgi?id=1043676 +.. _RHEL 6.5: https://access.redhat.com/site/articles/3078#RHEL6 From e5394e35c7a8f730ac76d24dee74d769049a0428 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 1 Apr 2014 10:21:53 +0200 Subject: [PATCH 307/384] devmapper: Pass info rather than hash to activateDeviceIfNeeded There is no need to look this up again, we have it already in all callers. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- runtime/graphdriver/devmapper/deviceset.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/runtime/graphdriver/devmapper/deviceset.go b/runtime/graphdriver/devmapper/deviceset.go index 731e9dab8b..b323627ac2 100644 --- a/runtime/graphdriver/devmapper/deviceset.go +++ b/runtime/graphdriver/devmapper/deviceset.go @@ -235,12 +235,8 @@ func (devices *DeviceSet) registerDevice(id int, hash string, size uint64) (*Dev return info, nil } -func (devices *DeviceSet) activateDeviceIfNeeded(hash string) error { - utils.Debugf("activateDeviceIfNeeded(%v)", hash) - info := devices.Devices[hash] - if info == nil { - return fmt.Errorf("Unknown device %s", hash) - } +func (devices *DeviceSet) activateDeviceIfNeeded(info *DevInfo) error { + utils.Debugf("activateDeviceIfNeeded(%v)", info.Hash) if devinfo, _ := getInfo(info.Name()); devinfo != nil && devinfo.Exists != 0 { return nil @@ -343,7 +339,7 @@ func (devices *DeviceSet) setupBaseImage() error { utils.Debugf("Creating filesystem on base device-manager snapshot") - if err = devices.activateDeviceIfNeeded(""); err != nil { + if err = devices.activateDeviceIfNeeded(info); err != nil { utils.Debugf("\n--->Err: %s\n", err) return err } @@ -605,7 +601,7 @@ func (devices *DeviceSet) deleteDevice(hash string) error { // This is a workaround for the kernel not discarding block so // on the thin pool when we remove a thinp device, so we do it // manually - if err := devices.activateDeviceIfNeeded(hash); err == nil { + if err := devices.activateDeviceIfNeeded(info); err == nil { if err := BlockDeviceDiscard(info.DevName()); err != nil { utils.Debugf("Error discarding block on device: %s (ignoring)\n", err) } @@ -858,7 +854,7 @@ func (devices *DeviceSet) MountDevice(hash, path string, mountLabel string) erro return nil } - if err := devices.activateDeviceIfNeeded(hash); err != nil { + if err := devices.activateDeviceIfNeeded(info); err != nil { return fmt.Errorf("Error activating devmapper device for '%s': %s", hash, err) } @@ -1028,7 +1024,7 @@ func (devices *DeviceSet) GetDeviceStatus(hash string) (*DevStatus, error) { TransactionId: info.TransactionId, } - if err := devices.activateDeviceIfNeeded(hash); err != nil { + if err := devices.activateDeviceIfNeeded(info); err != nil { return nil, fmt.Errorf("Error activating devmapper device for '%s': %s", hash, err) } From 8e39b35c7cd02bbb644b7faf2a434de0098e6dea Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 1 Apr 2014 10:24:26 +0200 Subject: [PATCH 308/384] devmapper: Pass info rather than hash to deleteDevice All the callers already have the info, no need for an extra lookup. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- runtime/graphdriver/devmapper/deviceset.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/runtime/graphdriver/devmapper/deviceset.go b/runtime/graphdriver/devmapper/deviceset.go index b323627ac2..c74db036d2 100644 --- a/runtime/graphdriver/devmapper/deviceset.go +++ b/runtime/graphdriver/devmapper/deviceset.go @@ -313,7 +313,7 @@ func (devices *DeviceSet) setupBaseImage() error { if oldInfo != nil && !oldInfo.Initialized { utils.Debugf("Removing uninitialized base image") - if err := devices.deleteDevice(""); err != nil { + if err := devices.deleteDevice(oldInfo); err != nil { utils.Debugf("\n--->Err: %s\n", err) return err } @@ -592,12 +592,7 @@ func (devices *DeviceSet) AddDevice(hash, baseHash string) error { return nil } -func (devices *DeviceSet) deleteDevice(hash string) error { - info := devices.Devices[hash] - if info == nil { - return fmt.Errorf("hash %s doesn't exists", hash) - } - +func (devices *DeviceSet) deleteDevice(info *DevInfo) error { // This is a workaround for the kernel not discarding block so // on the thin pool when we remove a thinp device, so we do it // manually @@ -652,7 +647,7 @@ func (devices *DeviceSet) DeleteDevice(hash string) error { info.lock.Lock() defer info.lock.Unlock() - return devices.deleteDevice(hash) + return devices.deleteDevice(info) } func (devices *DeviceSet) deactivatePool() error { From 5955846774c9b43291d6de0584fa8c3f62414c43 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 1 Apr 2014 10:31:34 +0200 Subject: [PATCH 309/384] devmapper: Pass info rather than hash to deactivateDevice() We already have the info in most cases, no need to look this up multiple times. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- runtime/graphdriver/devmapper/deviceset.go | 33 +++++++++------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/runtime/graphdriver/devmapper/deviceset.go b/runtime/graphdriver/devmapper/deviceset.go index c74db036d2..fa6b259b63 100644 --- a/runtime/graphdriver/devmapper/deviceset.go +++ b/runtime/graphdriver/devmapper/deviceset.go @@ -666,20 +666,16 @@ func (devices *DeviceSet) deactivatePool() error { return nil } -func (devices *DeviceSet) deactivateDevice(hash string) error { - utils.Debugf("[devmapper] deactivateDevice(%s)", hash) +func (devices *DeviceSet) deactivateDevice(info *DevInfo) error { + utils.Debugf("[devmapper] deactivateDevice(%s)", info.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) + if err := devices.waitClose(info); err != nil { + utils.Errorf("Warning: error waiting for device %s to close: %s\n", info.Hash, err) } - info := devices.Devices[hash] - if info == nil { - return fmt.Errorf("Unknown device %s", hash) - } devinfo, err := getInfo(info.Name()) if err != nil { utils.Debugf("\n--->Err: %s\n", err) @@ -760,11 +756,7 @@ func (devices *DeviceSet) waitRemove(devname string) error { // waitClose blocks until either: // a) the device registered at - is closed, // or b) the 10 second timeout expires. -func (devices *DeviceSet) waitClose(hash string) error { - info := devices.Devices[hash] - if info == nil { - return fmt.Errorf("Unknown device %s", hash) - } +func (devices *DeviceSet) waitClose(info *DevInfo) error { i := 0 for ; i < 1000; i += 1 { devinfo, err := getInfo(info.Name()) @@ -772,7 +764,7 @@ func (devices *DeviceSet) waitClose(hash string) error { return err } if i%100 == 0 { - utils.Debugf("Waiting for unmount of %s: opencount=%d", hash, devinfo.OpenCount) + utils.Debugf("Waiting for unmount of %s: opencount=%d", info.Hash, devinfo.OpenCount) } if devinfo.OpenCount == 0 { break @@ -782,7 +774,7 @@ func (devices *DeviceSet) waitClose(hash string) error { devices.Lock() } if i == 1000 { - return fmt.Errorf("Timeout while waiting for device %s to close", hash) + return fmt.Errorf("Timeout while waiting for device %s to close", info.Hash) } return nil } @@ -805,15 +797,18 @@ func (devices *DeviceSet) Shutdown() error { utils.Debugf("Shutdown unmounting %s, error: %s\n", info.mountPath, err) } - if err := devices.deactivateDevice(info.Hash); err != nil { + if err := devices.deactivateDevice(info); err != nil { utils.Debugf("Shutdown deactivate %s , error: %s\n", info.Hash, err) } } info.lock.Unlock() } - if err := devices.deactivateDevice(""); err != nil { - utils.Debugf("Shutdown deactivate base , error: %s\n", err) + info := devices.Devices[""] + if info != nil { + if err := devices.deactivateDevice(info); err != nil { + utils.Debugf("Shutdown deactivate base , error: %s\n", err) + } } if err := devices.deactivatePool(); err != nil { @@ -920,7 +915,7 @@ func (devices *DeviceSet) UnmountDevice(hash string, mode UnmountMode) error { } utils.Debugf("[devmapper] Unmount done") - if err := devices.deactivateDevice(hash); err != nil { + if err := devices.deactivateDevice(info); err != nil { return err } From 74edcaf1e84aa8bf35e496b2bead833172a79fca Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 1 Apr 2014 10:34:44 +0200 Subject: [PATCH 310/384] devmapper: Pass info rather than hash to setInitialized We already have this at the caller, no need to look up again. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- runtime/graphdriver/devmapper/deviceset.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/runtime/graphdriver/devmapper/deviceset.go b/runtime/graphdriver/devmapper/deviceset.go index fa6b259b63..12407af1b2 100644 --- a/runtime/graphdriver/devmapper/deviceset.go +++ b/runtime/graphdriver/devmapper/deviceset.go @@ -864,7 +864,7 @@ func (devices *DeviceSet) MountDevice(hash, path string, mountLabel string) erro info.mountPath = path info.floating = false - return devices.setInitialized(hash) + return devices.setInitialized(info) } func (devices *DeviceSet) UnmountDevice(hash string, mode UnmountMode) error { @@ -955,12 +955,7 @@ func (devices *DeviceSet) HasActivatedDevice(hash string) bool { return devinfo != nil && devinfo.Exists != 0 } -func (devices *DeviceSet) setInitialized(hash string) error { - info := devices.Devices[hash] - if info == nil { - return fmt.Errorf("Unknown device %s", hash) - } - +func (devices *DeviceSet) setInitialized(info *DevInfo) error { info.Initialized = true if err := devices.saveMetadata(); err != nil { info.Initialized = false From e01b71cebeb96755641a18762dea5b843f107bee Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 1 Apr 2014 10:45:40 +0200 Subject: [PATCH 311/384] devmapper: Add lookupDevice() helper This centralizes the lookup of devices so it is only done in one place. This will be needed later when we change the locking for it. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- runtime/graphdriver/devmapper/deviceset.go | 55 +++++++++++++--------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/runtime/graphdriver/devmapper/deviceset.go b/runtime/graphdriver/devmapper/deviceset.go index 12407af1b2..4faf997002 100644 --- a/runtime/graphdriver/devmapper/deviceset.go +++ b/runtime/graphdriver/devmapper/deviceset.go @@ -214,6 +214,14 @@ func (devices *DeviceSet) saveMetadata() error { return nil } +func (devices *DeviceSet) lookupDevice(hash string) (*DevInfo, error) { + info := devices.Devices[hash] + if info == nil { + return nil, fmt.Errorf("Unknown device %s", hash) + } + return info, nil +} + func (devices *DeviceSet) registerDevice(id int, hash string, size uint64) (*DevInfo, error) { utils.Debugf("registerDevice(%v, %v)", id, hash) info := &DevInfo{ @@ -306,7 +314,7 @@ func (devices *DeviceSet) loadMetaData() error { } func (devices *DeviceSet) setupBaseImage() error { - oldInfo := devices.Devices[""] + oldInfo, _ := devices.lookupDevice("") if oldInfo != nil && oldInfo.Initialized { return nil } @@ -565,13 +573,13 @@ func (devices *DeviceSet) AddDevice(hash, baseHash string) error { devices.Lock() defer devices.Unlock() - if devices.Devices[hash] != nil { - return fmt.Errorf("hash %s already exists", hash) + if info, _ := devices.lookupDevice(hash); info != nil { + return fmt.Errorf("device %s already exists", hash) } - baseInfo := devices.Devices[baseHash] - if baseInfo == nil { - return fmt.Errorf("Error adding device for '%s': can't find device for parent '%s'", hash, baseHash) + baseInfo, err := devices.lookupDevice(baseHash) + if err != nil { + return err } baseInfo.lock.Lock() @@ -639,9 +647,9 @@ func (devices *DeviceSet) DeleteDevice(hash string) error { devices.Lock() defer devices.Unlock() - info := devices.Devices[hash] - if info == nil { - return fmt.Errorf("Unknown device %s", hash) + info, err := devices.lookupDevice(hash) + if err != nil { + return err } info.lock.Lock() @@ -804,7 +812,7 @@ func (devices *DeviceSet) Shutdown() error { info.lock.Unlock() } - info := devices.Devices[""] + info, _ := devices.lookupDevice("") if info != nil { if err := devices.deactivateDevice(info); err != nil { utils.Debugf("Shutdown deactivate base , error: %s\n", err) @@ -822,9 +830,9 @@ func (devices *DeviceSet) MountDevice(hash, path string, mountLabel string) erro devices.Lock() defer devices.Unlock() - info := devices.Devices[hash] - if info == nil { - return fmt.Errorf("Unknown device %s", hash) + info, err := devices.lookupDevice(hash) + if err != nil { + return err } info.lock.Lock() @@ -851,7 +859,7 @@ func (devices *DeviceSet) MountDevice(hash, path string, mountLabel string) erro var flags uintptr = sysMsMgcVal mountOptions := label.FormatMountLabel("discard", mountLabel) - err := sysMount(info.DevName(), path, "ext4", flags, mountOptions) + err = sysMount(info.DevName(), path, "ext4", flags, mountOptions) if err != nil && err == sysEInval { mountOptions = label.FormatMountLabel(mountLabel, "") err = sysMount(info.DevName(), path, "ext4", flags, mountOptions) @@ -873,9 +881,9 @@ func (devices *DeviceSet) UnmountDevice(hash string, mode UnmountMode) error { devices.Lock() defer devices.Unlock() - info := devices.Devices[hash] - if info == nil { - return fmt.Errorf("UnmountDevice: no such device %s\n", hash) + info, err := devices.lookupDevice(hash) + if err != nil { + return err } info.lock.Lock() @@ -928,14 +936,15 @@ func (devices *DeviceSet) HasDevice(hash string) bool { devices.Lock() defer devices.Unlock() - return devices.Devices[hash] != nil + info, _ := devices.lookupDevice(hash) + return info != nil } func (devices *DeviceSet) HasInitializedDevice(hash string) bool { devices.Lock() defer devices.Unlock() - info := devices.Devices[hash] + info, _ := devices.lookupDevice(hash) return info != nil && info.Initialized } @@ -943,7 +952,7 @@ func (devices *DeviceSet) HasActivatedDevice(hash string) bool { devices.Lock() defer devices.Unlock() - info := devices.Devices[hash] + info, _ := devices.lookupDevice(hash) if info == nil { return false } @@ -995,9 +1004,9 @@ func (devices *DeviceSet) GetDeviceStatus(hash string) (*DevStatus, error) { devices.Lock() defer devices.Unlock() - info := devices.Devices[hash] - if info == nil { - return nil, fmt.Errorf("No device %s", hash) + info, err := devices.lookupDevice(hash) + if err != nil { + return nil, err } info.lock.Lock() From 70826e8b3fee27b971852aad89053507c6866d3e Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 1 Apr 2014 11:05:30 +0200 Subject: [PATCH 312/384] devmapper: Add lock to protext Devices map Currently access to the Devices map is serialized by the main DeviceSet lock, but we need to access it outside that lock, so we add a separate lock for this and grab that everywhere we modify or read the map. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- runtime/graphdriver/devmapper/deviceset.go | 27 +++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/runtime/graphdriver/devmapper/deviceset.go b/runtime/graphdriver/devmapper/deviceset.go index 4faf997002..f972c34bb1 100644 --- a/runtime/graphdriver/devmapper/deviceset.go +++ b/runtime/graphdriver/devmapper/deviceset.go @@ -51,7 +51,8 @@ type DevInfo struct { } type MetaData struct { - Devices map[string]*DevInfo `json:devices` + Devices map[string]*DevInfo `json:devices` + devicesLock sync.Mutex `json:"-"` // Protects all read/writes to Devices map } type DeviceSet struct { @@ -179,7 +180,9 @@ func (devices *DeviceSet) allocateTransactionId() uint64 { } func (devices *DeviceSet) saveMetadata() error { + devices.devicesLock.Lock() jsonData, err := json.Marshal(devices.MetaData) + devices.devicesLock.Unlock() if err != nil { return fmt.Errorf("Error encoding metadata to json: %s", err) } @@ -215,6 +218,8 @@ func (devices *DeviceSet) saveMetadata() error { } func (devices *DeviceSet) lookupDevice(hash string) (*DevInfo, error) { + devices.devicesLock.Lock() + defer devices.devicesLock.Unlock() info := devices.Devices[hash] if info == nil { return nil, fmt.Errorf("Unknown device %s", hash) @@ -233,10 +238,15 @@ func (devices *DeviceSet) registerDevice(id int, hash string, size uint64) (*Dev devices: devices, } + devices.devicesLock.Lock() devices.Devices[hash] = info + devices.devicesLock.Unlock() + if err := devices.saveMetadata(); err != nil { // Try to remove unused device + devices.devicesLock.Lock() delete(devices.Devices, hash) + devices.devicesLock.Unlock() return nil, err } @@ -632,10 +642,14 @@ func (devices *DeviceSet) deleteDevice(info *DevInfo) error { } devices.allocateTransactionId() + devices.devicesLock.Lock() delete(devices.Devices, info.Hash) + devices.devicesLock.Unlock() if err := devices.saveMetadata(); err != nil { + devices.devicesLock.Lock() devices.Devices[info.Hash] = info + devices.devicesLock.Unlock() utils.Debugf("Error saving meta data: %s\n", err) return err } @@ -795,7 +809,15 @@ func (devices *DeviceSet) Shutdown() error { utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root) defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix) + var devs []*DevInfo + + devices.devicesLock.Lock() for _, info := range devices.Devices { + devs = append(devs, info) + } + devices.devicesLock.Unlock() + + for _, info := range devs { info.lock.Lock() if info.mountCount > 0 { // We use MNT_DETACH here in case it is still busy in some running @@ -979,12 +1001,15 @@ func (devices *DeviceSet) List() []string { devices.Lock() defer devices.Unlock() + devices.devicesLock.Lock() ids := make([]string, len(devices.Devices)) i := 0 for k := range devices.Devices { ids[i] = k i++ } + devices.devicesLock.Unlock() + return ids } From 2ffef1b7eb618162673c6ffabccb9ca57c7dfce3 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 1 Apr 2014 11:28:21 +0200 Subject: [PATCH 313/384] devmapper: Avoid AB-BA deadlock We currently drop the global lock while holding a per-device lock when waiting for device removal, and then we re-aquire it when the sleep is done. This is causing a AB-BA deadlock if anyone at the same time tries to do any operation on that device like this: thread A: thread B grabs global lock grabs device lock releases global lock sleeps grabs global lock blocks on device lock wakes up blocks on global lock To trigger this you can for instance do: ID=`docker run -d fedora sleep 5` cd /var/lib/docker/devicemapper/mnt/$ID docker wait $ID docker rm $ID & docker rm $ID The unmount will fail due to the mount being busy thus causing the timeout and the second rm will then trigger the deadlock. We fix this by adding a lock ordering such that the device locks are always grabbed before the global lock. This is safe since the device lookups now have a separate lock. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- runtime/graphdriver/devmapper/deviceset.go | 58 +++++++++++++--------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/runtime/graphdriver/devmapper/deviceset.go b/runtime/graphdriver/devmapper/deviceset.go index f972c34bb1..97d670a3d9 100644 --- a/runtime/graphdriver/devmapper/deviceset.go +++ b/runtime/graphdriver/devmapper/deviceset.go @@ -47,6 +47,11 @@ type DevInfo struct { // sometimes release that lock while sleeping. In that case // this per-device lock is still held, protecting against // other accesses to the device that we're doing the wait on. + // + // WARNING: In order to avoid AB-BA deadlocks when releasing + // the global lock while holding the per-device locks all + // device locks must be aquired *before* the device lock, and + // multiple device locks should be aquired parent before child. lock sync.Mutex `json:"-"` } @@ -580,13 +585,6 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { } func (devices *DeviceSet) AddDevice(hash, baseHash string) error { - devices.Lock() - defer devices.Unlock() - - if info, _ := devices.lookupDevice(hash); info != nil { - return fmt.Errorf("device %s already exists", hash) - } - baseInfo, err := devices.lookupDevice(baseHash) if err != nil { return err @@ -595,6 +593,13 @@ func (devices *DeviceSet) AddDevice(hash, baseHash string) error { baseInfo.lock.Lock() defer baseInfo.lock.Unlock() + devices.Lock() + defer devices.Unlock() + + if info, _ := devices.lookupDevice(hash); info != nil { + return fmt.Errorf("device %s already exists", hash) + } + deviceId := devices.allocateDeviceId() if err := devices.createSnapDevice(devices.getPoolDevName(), deviceId, baseInfo.Name(), baseInfo.DeviceId); err != nil { @@ -658,9 +663,6 @@ func (devices *DeviceSet) deleteDevice(info *DevInfo) error { } func (devices *DeviceSet) DeleteDevice(hash string) error { - devices.Lock() - defer devices.Unlock() - info, err := devices.lookupDevice(hash) if err != nil { return err @@ -669,6 +671,9 @@ func (devices *DeviceSet) DeleteDevice(hash string) error { info.lock.Lock() defer info.lock.Unlock() + devices.Lock() + defer devices.Unlock() + return devices.deleteDevice(info) } @@ -802,8 +807,6 @@ func (devices *DeviceSet) waitClose(info *DevInfo) error { } func (devices *DeviceSet) Shutdown() error { - devices.Lock() - defer devices.Unlock() utils.Debugf("[deviceset %s] shutdown()", devices.devicePrefix) utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root) @@ -827,31 +830,36 @@ func (devices *DeviceSet) Shutdown() error { utils.Debugf("Shutdown unmounting %s, error: %s\n", info.mountPath, err) } + devices.Lock() if err := devices.deactivateDevice(info); err != nil { utils.Debugf("Shutdown deactivate %s , error: %s\n", info.Hash, err) } + devices.Unlock() } info.lock.Unlock() } info, _ := devices.lookupDevice("") if info != nil { + info.lock.Lock() + devices.Lock() if err := devices.deactivateDevice(info); err != nil { utils.Debugf("Shutdown deactivate base , error: %s\n", err) } + devices.Unlock() + info.lock.Unlock() } + devices.Lock() if err := devices.deactivatePool(); err != nil { utils.Debugf("Shutdown deactivate pool , error: %s\n", err) } + devices.Unlock() return nil } func (devices *DeviceSet) MountDevice(hash, path string, mountLabel string) error { - devices.Lock() - defer devices.Unlock() - info, err := devices.lookupDevice(hash) if err != nil { return err @@ -860,6 +868,9 @@ func (devices *DeviceSet) MountDevice(hash, path string, mountLabel string) erro info.lock.Lock() defer info.lock.Unlock() + devices.Lock() + defer devices.Unlock() + if info.mountCount > 0 { if path != info.mountPath { return fmt.Errorf("Trying to mount devmapper device in multple places (%s, %s)", info.mountPath, path) @@ -900,8 +911,6 @@ func (devices *DeviceSet) MountDevice(hash, path string, mountLabel string) erro func (devices *DeviceSet) UnmountDevice(hash string, mode UnmountMode) error { utils.Debugf("[devmapper] UnmountDevice(hash=%s, mode=%d)", hash, mode) defer utils.Debugf("[devmapper] UnmountDevice END") - devices.Lock() - defer devices.Unlock() info, err := devices.lookupDevice(hash) if err != nil { @@ -911,6 +920,9 @@ func (devices *DeviceSet) UnmountDevice(hash string, mode UnmountMode) error { info.lock.Lock() defer info.lock.Unlock() + devices.Lock() + defer devices.Unlock() + if mode == UnmountFloat { if info.floating { return fmt.Errorf("UnmountDevice: can't float floating reference %s\n", hash) @@ -971,9 +983,6 @@ func (devices *DeviceSet) HasInitializedDevice(hash string) bool { } func (devices *DeviceSet) HasActivatedDevice(hash string) bool { - devices.Lock() - defer devices.Unlock() - info, _ := devices.lookupDevice(hash) if info == nil { return false @@ -982,6 +991,9 @@ func (devices *DeviceSet) HasActivatedDevice(hash string) bool { info.lock.Lock() defer info.lock.Unlock() + devices.Lock() + defer devices.Unlock() + devinfo, _ := getInfo(info.Name()) return devinfo != nil && devinfo.Exists != 0 } @@ -1026,9 +1038,6 @@ func (devices *DeviceSet) deviceStatus(devName string) (sizeInSectors, mappedSec } func (devices *DeviceSet) GetDeviceStatus(hash string) (*DevStatus, error) { - devices.Lock() - defer devices.Unlock() - info, err := devices.lookupDevice(hash) if err != nil { return nil, err @@ -1037,6 +1046,9 @@ func (devices *DeviceSet) GetDeviceStatus(hash string) (*DevStatus, error) { info.lock.Lock() defer info.lock.Unlock() + devices.Lock() + defer devices.Unlock() + status := &DevStatus{ DeviceId: info.DeviceId, Size: info.Size, From aec989bd0801657efeeb81bafb2c6c61f60de6d4 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 2 Apr 2014 02:44:12 -0600 Subject: [PATCH 314/384] Add more color and cgroupfs hierarchy verification to check-config.sh Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- contrib/check-config.sh | 111 +++++++++++++++++++++++++++------------- 1 file changed, 75 insertions(+), 36 deletions(-) diff --git a/contrib/check-config.sh b/contrib/check-config.sh index 62606aca04..f225443138 100755 --- a/contrib/check-config.sh +++ b/contrib/check-config.sh @@ -7,62 +7,101 @@ set -e : ${CONFIG:=/proc/config.gz} : ${GREP:=zgrep} -if [ ! -e "$CONFIG" ]; then - echo >&2 "warning: $CONFIG does not exist, searching other paths for kernel config..." - if [ -e "/boot/config-$(uname -r)" ]; then - CONFIG="/boot/config-$(uname -r)" - elif [ -e '/usr/src/linux/.config' ]; then - CONFIG='/usr/src/linux/.config' - else - echo >&2 "error: cannot find kernel config" - echo >&2 " try running this script again, specifying the kernel config:" - echo >&2 " CONFIG=/path/to/kernel/.config $0" - exit 1 - fi -fi - is_set() { $GREP "CONFIG_$1=[y|m]" $CONFIG > /dev/null } +# see http://en.wikipedia.org/wiki/ANSI_escape_code#Colors +declare -A colors=( + [black]=30 + [red]=31 + [green]=32 + [yellow]=33 + [blue]=34 + [magenta]=35 + [cyan]=36 + [white]=37 +) color() { - color= - prefix= + color=() if [ "$1" = 'bold' ]; then - prefix='1;' + color+=( '1' ) shift fi - case "$1" in - green) color='32' ;; - red) color='31' ;; - gray) color='30' ;; - reset) color='' ;; - esac - echo -en '\033['"$prefix$color"m + if [ $# -gt 0 ] && [ "${colors[$1]}" ]; then + color+=( "${colors[$1]}" ) + fi + local IFS=';' + echo -en '\033['"${color[*]}"m +} +wrap_color() { + text="$1" + shift + color "$@" + echo -n "$text" + color reset + echo +} + +wrap_good() { + echo "$(wrap_color "$1" white): $(wrap_color "$2" green)" +} +wrap_bad() { + echo "$(wrap_color "$1" bold): $(wrap_color "$2" bold red)" +} +wrap_warning() { + wrap_color >&2 "$*" red } check_flag() { if is_set "$1"; then - color green - echo -n enabled + wrap_good "CONFIG_$1" 'enabled' else - color bold red - echo -n missing + wrap_bad "CONFIG_$1" 'missing' fi - color reset } check_flags() { for flag in "$@"; do - echo "- CONFIG_$flag: $(check_flag "$flag")" + echo "- $(check_flag "$flag")" done } +if [ ! -e "$CONFIG" ]; then + wrap_warning "warning: $CONFIG does not exist, searching other paths for kernel config..." + for tryConfig in \ + '/proc/config.gz' \ + "/boot/config-$(uname -r)" \ + '/usr/src/linux/.config' \ + ; do + if [ -e "$tryConfig" ]; then + CONFIG="$tryConfig" + break + fi + done + if [ ! -e "$CONFIG" ]; then + wrap_warning "error: cannot find kernel config" + wrap_warning " try running this script again, specifying the kernel config:" + wrap_warning " CONFIG=/path/to/kernel/.config $0" + exit 1 + fi +fi + +wrap_color "info: reading kernel config from $CONFIG ..." white echo -# TODO check that the cgroupfs hierarchy is properly mounted - echo 'Generally Necessary:' + +echo -n '- ' +cgroupCpuDir="$(awk '/[, ]cpu[, ]/ && $8 == "cgroup" { print $5 }' /proc/$$/mountinfo | head -n1)" +cgroupDir="$(dirname "$cgroupCpuDir")" +if [ -d "$cgroupDir/cpu" ]; then + echo "$(wrap_good 'cgroup hierarchy' 'properly mounted') [$cgroupDir]" +else + echo "$(wrap_bad 'cgroup hierarchy' 'single mountpoint!') [$cgroupCpuDir]" + echo " $(wrap_color '(see https://github.com/tianon/cgroupfs-mount)' yellow)" +fi + flags=( NAMESPACES {NET,PID,IPC,UTS}_NS DEVPTS_MULTIPLE_INSTANCES @@ -83,16 +122,16 @@ check_flags "${flags[@]}" echo '- Storage Drivers:' { - echo '- "aufs":' + echo '- "'$(wrap_color 'aufs' blue)'":' check_flags AUFS_FS | sed 's/^/ /' if ! is_set AUFS_FS && grep -q aufs /proc/filesystems; then - echo " $(color bold gray)(note that some kernels include AUFS patches but not the AUFS_FS flag)$(color reset)" + echo " $(wrap_color '(note that some kernels include AUFS patches but not the AUFS_FS flag)' bold black)" fi - echo '- "btrfs":' + echo '- "'$(wrap_color 'btrfs' blue)'":' check_flags BTRFS_FS | sed 's/^/ /' - echo '- "devicemapper":' + echo '- "'$(wrap_color 'devicemapper' blue)'":' check_flags BLK_DEV_DM DM_THIN_PROVISIONING EXT4_FS | sed 's/^/ /' } | sed 's/^/ /' echo From 3b3f4bf052e442543ec5772875ce7fbc77924596 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 2 Apr 2014 05:56:11 -0700 Subject: [PATCH 315/384] Return correct exit code upon signal + SIGQUIT now quits without cleanup Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- server/server.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/server/server.go b/server/server.go index 1c6c561375..d6fa7a0c2a 100644 --- a/server/server.go +++ b/server/server.go @@ -54,11 +54,16 @@ func InitServer(job *engine.Job) engine.Status { c := make(chan os.Signal, 1) gosignal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) go func() { - sig := <-c - log.Printf("Received signal '%v', starting shutdown of docker...\n", sig) - utils.RemovePidFile(srv.runtime.Config().Pidfile) - srv.Close() - os.Exit(0) + for sig := range c { + log.Printf("Received signal '%v', starting shutdown of docker...\n", sig) + switch sig { + case os.Interrupt, syscall.SIGTERM: + utils.RemovePidFile(srv.runtime.Config().Pidfile) + srv.Close() + case syscall.SIGQUIT: + } + os.Exit(128 + int(sig.(syscall.Signal))) + } }() job.Eng.Hack_SetGlobalVar("httpapi.server", srv) job.Eng.Hack_SetGlobalVar("httpapi.runtime", srv.runtime) From f80fd5da09013d7cd25a0f246ffffd7b6c064073 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 2 Apr 2014 13:07:11 +0000 Subject: [PATCH 316/384] Fix configuration test for MKNOD Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runtime/execdriver/native/template/default_template.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/runtime/execdriver/native/template/default_template.go b/runtime/execdriver/native/template/default_template.go index 6828812336..a1ecb04d76 100644 --- a/runtime/execdriver/native/template/default_template.go +++ b/runtime/execdriver/native/template/default_template.go @@ -7,7 +7,7 @@ import ( // New returns the docker default configuration for libcontainer func New() *libcontainer.Container { - return &libcontainer.Container{ + container := &libcontainer.Container{ CapabilitiesMask: libcontainer.Capabilities{ libcontainer.GetCapability("SETPCAP"), libcontainer.GetCapability("SYS_MODULE"), @@ -23,6 +23,7 @@ func New() *libcontainer.Container { libcontainer.GetCapability("MAC_OVERRIDE"), libcontainer.GetCapability("MAC_ADMIN"), libcontainer.GetCapability("NET_ADMIN"), + libcontainer.GetCapability("MKNOD"), }, Namespaces: libcontainer.Namespaces{ libcontainer.GetNamespace("NEWNS"), @@ -39,4 +40,6 @@ func New() *libcontainer.Container { "apparmor_profile": "docker-default", }, } + container.CapabilitiesMask.Get("MKNOD").Enabled = true + return container } From 18ef3cc24a933cbf403c2aaf8b374cfc84a722a4 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 2 Apr 2014 13:12:52 +0000 Subject: [PATCH 317/384] Remove loopback setup for native driver Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/mount.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index e2975d0a9a..4b5a42b1ac 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/nsinit/mount.go @@ -55,8 +55,6 @@ func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, cons if err := copyDevNodes(rootfs); err != nil { return fmt.Errorf("copy dev nodes %s", err) } - // In non-privileged mode, this fails. Discard the error. - setupLoopbackDevices(rootfs) if err := setupPtmx(rootfs, console, mountLabel); err != nil { return err } @@ -142,19 +140,6 @@ func copyDevNodes(rootfs string) error { return nil } -func setupLoopbackDevices(rootfs string) error { - for i := 0; ; i++ { - if err := copyDevNode(rootfs, fmt.Sprintf("loop%d", i)); err != nil { - if !os.IsNotExist(err) { - return err - } - break - } - - } - return nil -} - func copyDevNode(rootfs, node string) error { stat, err := os.Stat(filepath.Join("/dev", node)) if err != nil { From a9d6eef2386a3d08840e2a30bd8d6f2ae3679688 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 2 Apr 2014 13:22:51 +0000 Subject: [PATCH 318/384] Remove racy test causing tests to stall Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- integration-cli/docker_cli_start_test.go | 34 ------------------------ 1 file changed, 34 deletions(-) delete mode 100644 integration-cli/docker_cli_start_test.go diff --git a/integration-cli/docker_cli_start_test.go b/integration-cli/docker_cli_start_test.go deleted file mode 100644 index c3059a66c4..0000000000 --- a/integration-cli/docker_cli_start_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "os/exec" - "testing" -) - -// Regression test for #3364 -func TestDockerStartWithPortCollision(t *testing.T) { - runCmd := exec.Command(dockerBinary, "run", "--name", "fail", "-p", "25:25", "busybox", "true") - out, stderr, exitCode, err := runCommandWithStdoutStderr(runCmd) - if err != nil && exitCode != 0 { - t.Fatal(out, stderr, err) - } - - runCmd = exec.Command(dockerBinary, "run", "--name", "conflict", "-dti", "-p", "25:25", "busybox", "sh") - out, stderr, exitCode, err = runCommandWithStdoutStderr(runCmd) - if err != nil && exitCode != 0 { - t.Fatal(out, stderr, err) - } - - startCmd := exec.Command(dockerBinary, "start", "-a", "fail") - out, stderr, exitCode, err = runCommandWithStdoutStderr(startCmd) - if err != nil && exitCode != 1 { - t.Fatal(out, err) - } - - killCmd := exec.Command(dockerBinary, "kill", "conflict") - runCommand(killCmd) - - deleteAllContainers() - - logDone("start - -a=true error on port use") -} From 63c7941172376e81c5e17206f39d7c78c0e95b69 Mon Sep 17 00:00:00 2001 From: unclejack Date: Wed, 2 Apr 2014 16:00:12 +0300 Subject: [PATCH 319/384] docs: explain what docker run -a does This adds a bit of documentation for the `-a` flag for docker run. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- docs/sources/reference/commandline/cli.rst | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 324b84b0ae..64dff1e1c2 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -1359,6 +1359,35 @@ ID may be optionally suffixed with ``:ro`` or ``:rw`` to mount the volumes in read-only or read-write mode, respectively. By default, the volumes are mounted in the same mode (read write or read only) as the reference container. +The ``-a`` flag tells ``docker run`` to bind to the container's stdin, stdout +or stderr. This makes it possible to manipulate the output and input as needed. + +.. code-block:: bash + + $ sudo echo "test" | docker run -i -a stdin ubuntu cat - + +This pipes data into a container and prints the container's ID by attaching +only to the container's stdin. + +.. code-block:: bash + + $ sudo docker run -a stderr ubuntu echo test + +This isn't going to print anything unless there's an error because we've only +attached to the stderr of the container. The container's logs still store +what's been written to stderr and stdout. + +.. code-block:: bash + + $ sudo cat somefile | docker run -i -a stdin mybuilder dobuild + +This is how piping a file into a container could be done for a build. +The container's ID will be printed after the build is done and the build logs +could be retrieved using ``docker logs``. This is useful if you need to pipe +a file or something else into a container and retrieve the container's ID once +the container has finished running. + + A complete example .................. From 32ad78b0430079dcc53c245826a244afa2d9b6b6 Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Tue, 1 Apr 2014 09:24:24 -0400 Subject: [PATCH 320/384] Remove hard coding of SELinux labels on systems without proper selinux policy. If a system is configured for SELinux but does not know about docker or containers, then we want the transitions of the policy to work. Hard coding the labels causes docker to break on older Fedora and RHEL systems Docker-DCO-1.1-Signed-off-by: Dan Walsh (github: rhatdan) --- pkg/selinux/selinux.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/selinux/selinux.go b/pkg/selinux/selinux.go index 5236d3fb87..5362308617 100644 --- a/pkg/selinux/selinux.go +++ b/pkg/selinux/selinux.go @@ -312,13 +312,10 @@ func GetLxcContexts() (processLabel string, fileLabel string) { 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" - + lxcPath := fmt.Sprintf("%s/contexts/lxc_contexts", GetSELinuxPolicyRoot()) in, err := os.Open(lxcPath) if err != nil { - goto exit + return "", "" } defer in.Close() @@ -352,6 +349,11 @@ func GetLxcContexts() (processLabel string, fileLabel string) { } } } + + if processLabel == "" || fileLabel == "" { + return "", "" + } + exit: mcs := IntToMcs(os.Getpid(), 1024) scon := NewContext(processLabel) From d76ac4d429e474a7c79f7aab396e318f4e176025 Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Tue, 1 Apr 2014 10:03:29 -0400 Subject: [PATCH 321/384] In certain cases, setting the process label will not happen. When the code attempts to set the ProcessLabel, it checks if SELinux Is enabled. We have seen a case with some of our patches where the code is fooled by the container to think that SELinux is not enabled. Calling label.Init before setting up the rest of the container, tells the library that SELinux is enabled and everything works fine. Docker-DCO-1.1-Signed-off-by: Dan Walsh (github: rhatdan) --- pkg/label/label.go | 3 +++ pkg/label/label_selinux.go | 4 ++++ pkg/libcontainer/nsinit/init.go | 2 ++ 3 files changed, 9 insertions(+) diff --git a/pkg/label/label.go b/pkg/label/label.go index ba1e9f48ea..be0d0ae079 100644 --- a/pkg/label/label.go +++ b/pkg/label/label.go @@ -21,3 +21,6 @@ func SetFileLabel(path string, fileLabel string) error { func GetPidCon(pid int) (string, error) { return "", nil } + +func Init() { +} diff --git a/pkg/label/label_selinux.go b/pkg/label/label_selinux.go index 300a8b6d14..64a1720996 100644 --- a/pkg/label/label_selinux.go +++ b/pkg/label/label_selinux.go @@ -67,3 +67,7 @@ func SetFileLabel(path string, fileLabel string) error { func GetPidCon(pid int) (string, error) { return selinux.Getpidcon(pid) } + +func Init() { + selinux.SelinuxEnabled() +} diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 5aa5f9f5b5..e5d69f5453 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -58,6 +58,8 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol if err := system.ParentDeathSignal(uintptr(syscall.SIGTERM)); err != nil { return fmt.Errorf("parent death signal %s", err) } + + label.Init() ns.logger.Println("setup mount namespace") 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) From ca4224762b5fe9a319b6c1724ee16d1552403269 Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Wed, 2 Apr 2014 13:56:30 -0400 Subject: [PATCH 322/384] Fix SELinux issue with missing Contexts in lxc execdriver There is a bug in the SELinux patch for the lxc execdriver, that causes lxc containers to blow up whether or not SELinux is enabled. Docker-DCO-1.1-Signed-off-by: Dan Walsh (github: rhatdan) --- daemonconfig/config.go | 3 +-- runtime/execdriver/lxc/lxc_template.go | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/daemonconfig/config.go b/daemonconfig/config.go index 6cb3659e18..1abb6f8b89 100644 --- a/daemonconfig/config.go +++ b/daemonconfig/config.go @@ -1,10 +1,9 @@ package daemonconfig import ( - "net" - "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/runtime/networkdriver" + "net" ) const ( diff --git a/runtime/execdriver/lxc/lxc_template.go b/runtime/execdriver/lxc/lxc_template.go index f325ffcaef..83723285d0 100644 --- a/runtime/execdriver/lxc/lxc_template.go +++ b/runtime/execdriver/lxc/lxc_template.go @@ -32,8 +32,8 @@ lxc.pts = 1024 lxc.console = none {{if getProcessLabel .Context}} lxc.se_context = {{ getProcessLabel .Context}} -{{$MOUNTLABEL := getMountLabel .Context}} {{end}} +{{$MOUNTLABEL := getMountLabel .Context}} # no controlling tty at all lxc.tty = 1 @@ -90,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 {{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 +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}} From 94233a204f82f857536c16f36f94d3a8ff0069dd Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 2 Apr 2014 16:52:49 +0000 Subject: [PATCH 323/384] Fix lxc label handleing This also improves the logic around formatting the labels for selinux Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/label/label.go | 2 +- pkg/label/label_selinux.go | 30 +++++++++++++------------- runtime/execdriver/lxc/lxc_template.go | 13 +---------- 3 files changed, 17 insertions(+), 28 deletions(-) diff --git a/pkg/label/label.go b/pkg/label/label.go index be0d0ae079..38f026bc5a 100644 --- a/pkg/label/label.go +++ b/pkg/label/label.go @@ -6,7 +6,7 @@ func GenLabels(options string) (string, string, error) { return "", "", nil } -func FormatMountLabel(src string, MountLabel string) string { +func FormatMountLabel(src string, mountLabel string) string { return src } diff --git a/pkg/label/label_selinux.go b/pkg/label/label_selinux.go index 64a1720996..d807b2b408 100644 --- a/pkg/label/label_selinux.go +++ b/pkg/label/label_selinux.go @@ -10,12 +10,15 @@ import ( func GenLabels(options string) (string, string, error) { processLabel, mountLabel := selinux.GetLxcContexts() - var err error if processLabel == "" { // SELinux is disabled - return "", "", err + return "", "", nil } - s := strings.Fields(options) - l := len(s) + + var ( + err error + s = strings.Fields(options) + l = len(s) + ) if l > 0 { pcon := selinux.NewContext(processLabel) for i := 0; i < l; i++ { @@ -28,19 +31,16 @@ func GenLabels(options string) (string, string, error) { 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) +func FormatMountLabel(src string, mountLabel string) string { + if mountLabel != "" { + switch src { + case "": + src = fmt.Sprintf("%s,context=%s", src, mountLabel) + default: + src = fmt.Sprintf("context=%s", mountLabel) } } - return mountLabel + return src } func SetProcessLabel(processLabel string) error { diff --git a/runtime/execdriver/lxc/lxc_template.go b/runtime/execdriver/lxc/lxc_template.go index e3582e2369..c49753c6aa 100644 --- a/runtime/execdriver/lxc/lxc_template.go +++ b/runtime/execdriver/lxc/lxc_template.go @@ -32,9 +32,8 @@ lxc.pts = 1024 lxc.console = none {{if .ProcessLabel}} lxc.se_context = {{ .ProcessLabel}} -{{$MOUNTLABEL := .MountLabel}} {{end}} -{{$MOUNTLABEL := getMountLabel .Context}} +{{$MOUNTLABEL := .MountLabel}} # no controlling tty at all lxc.tty = 1 @@ -152,14 +151,6 @@ func getMemorySwap(v *execdriver.Resources) int64 { return v.Memory * 2 } -func getProcessLabel(c map[string][]string) string { - return getLabel(c, "process") -} - -func getMountLabel(c map[string][]string) string { - return getLabel(c, "mount") -} - func getLabel(c map[string][]string, name string) string { label := c["label"] for _, l := range label { @@ -175,8 +166,6 @@ func init() { var err error funcMap := template.FuncMap{ "getMemorySwap": getMemorySwap, - "getProcessLabel": getProcessLabel, - "getMountLabel": getMountLabel, "escapeFstabSpaces": escapeFstabSpaces, "formatMountLabel": label.FormatMountLabel, } From e2779e11db113c5551094dba8079d44d8a210e41 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 3 Apr 2014 04:40:38 +0000 Subject: [PATCH 324/384] Remove runtime options from config Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runconfig/config.go | 12 ------------ runtime/runtime.go | 4 ++-- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/runconfig/config.go b/runconfig/config.go index c3ade575c5..4b334c6848 100644 --- a/runconfig/config.go +++ b/runconfig/config.go @@ -1,10 +1,8 @@ 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. @@ -36,17 +34,9 @@ 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"), @@ -64,7 +54,6 @@ 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) @@ -86,6 +75,5 @@ func ContainerConfigFromJob(job *engine.Job) *Config { if Entrypoint := job.GetenvList("Entrypoint"); Entrypoint != nil { config.Entrypoint = Entrypoint } - return config } diff --git a/runtime/runtime.go b/runtime/runtime.go index 9e8323279e..842dbf8b0b 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -499,7 +499,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, config.Context["mount_label"]); err != nil { + if err := runtime.driver.Create(initID, img.ID, ""); err != nil { return nil, nil, err } initPath, err := runtime.driver.Get(initID) @@ -512,7 +512,7 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe return nil, nil, err } - if err := runtime.driver.Create(container.ID, initID, config.Context["mount_label"]); err != nil { + if err := runtime.driver.Create(container.ID, initID, ""); err != nil { return nil, nil, err } resolvConf, err := utils.GetResolvConf() From 8b450a93b8bb3b1cd0c32754dd499ec0c9b66537 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 3 Apr 2014 06:34:57 +0000 Subject: [PATCH 325/384] Remove driver wide mount label for dm Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runtime/graphdriver/devmapper/driver.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/runtime/graphdriver/devmapper/driver.go b/runtime/graphdriver/devmapper/driver.go index 1324ddab81..35fe883f26 100644 --- a/runtime/graphdriver/devmapper/driver.go +++ b/runtime/graphdriver/devmapper/driver.go @@ -22,8 +22,7 @@ func init() { type Driver struct { *DeviceSet - home string - MountLabel string + home string } var Init = func(home string) (graphdriver.Driver, error) { @@ -62,12 +61,11 @@ func (d *Driver) Cleanup() 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, d.MountLabel); err != nil { + if err := d.mount(id, mp); err != nil { return err } @@ -117,7 +115,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, d.MountLabel); err != nil { + if err := d.mount(id, mp); err != nil { return "", err } @@ -130,13 +128,13 @@ func (d *Driver) Put(id string) { } } -func (d *Driver) mount(id, mountPoint string, mountLabel string) error { +func (d *Driver) mount(id, mountPoint string) error { // Create the target directories if they don't exist if err := osMkdirAll(mountPoint, 0755); err != nil && !osIsExist(err) { return err } // Mount the device - return d.DeviceSet.MountDevice(id, mountPoint, mountLabel) + return d.DeviceSet.MountDevice(id, mountPoint, "") } func (d *Driver) Exists(id string) bool { From 8c4617e0ae3b9c7e5167883ca171ad8e23fc06b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6thlisberger?= Date: Thu, 3 Apr 2014 09:40:28 +0100 Subject: [PATCH 326/384] docs: Fix typo in hello world example --- docs/sources/examples/hello_world.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/examples/hello_world.rst b/docs/sources/examples/hello_world.rst index 507056da85..39d7abea2c 100644 --- a/docs/sources/examples/hello_world.rst +++ b/docs/sources/examples/hello_world.rst @@ -127,7 +127,7 @@ Attach to the container to see the results in real-time. process to see what is going on. - **"--sig-proxy=false"** Do not forward signals to the container; allows us to exit the attachment using Control-C without stopping the container. -- **$container_id** The Id of the container we want to attach too. +- **$container_id** The Id of the container we want to attach to. Exit from the container attachment by pressing Control-C. From 75633a0451a98bf0c803e742625c4de27dbcc2e8 Mon Sep 17 00:00:00 2001 From: Michael Neale Date: Thu, 3 Apr 2014 17:33:34 +1100 Subject: [PATCH 327/384] explained what authConfig actually is. Docker-DCO-1.1-Signed-off-by: Michael Neale (github: michaelneale) --- docs/sources/reference/api/docker_remote_api.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/reference/api/docker_remote_api.rst b/docs/sources/reference/api/docker_remote_api.rst index ca7463f351..7fa8468f3c 100644 --- a/docs/sources/reference/api/docker_remote_api.rst +++ b/docs/sources/reference/api/docker_remote_api.rst @@ -22,6 +22,8 @@ Docker Remote API - Since API version 1.2, the auth configuration is now handled client side, so the client has to send the authConfig as POST in /images/(name)/push +- authConfig, set as the ``X-Registry-Auth`` header, is currently a Base64 encoded (json) string with credentials: + ``{'username': string, 'password': string, 'email': string, 'serveraddress' : string}`` 2. Versions =========== From cd910cb6858541b432e20b650fad262772c9ef18 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 2 Apr 2014 18:00:13 -0700 Subject: [PATCH 328/384] Allow force sigint and allow sigquit after sigint Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- server/server.go | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/server/server.go b/server/server.go index d6fa7a0c2a..fae50094c2 100644 --- a/server/server.go +++ b/server/server.go @@ -54,15 +54,29 @@ func InitServer(job *engine.Job) engine.Status { c := make(chan os.Signal, 1) gosignal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) go func() { + interruptCount := 0 for sig := range c { - log.Printf("Received signal '%v', starting shutdown of docker...\n", sig) - switch sig { - case os.Interrupt, syscall.SIGTERM: - utils.RemovePidFile(srv.runtime.Config().Pidfile) - srv.Close() - case syscall.SIGQUIT: - } - os.Exit(128 + int(sig.(syscall.Signal))) + go func() { + log.Printf("Received signal '%v', starting shutdown of docker...\n", sig) + switch sig { + case os.Interrupt, syscall.SIGTERM: + // If the user really wants to interrupt, let him do so. + if interruptCount < 3 { + interruptCount++ + // Initiate the cleanup only once + if interruptCount == 1 { + utils.RemovePidFile(srv.runtime.Config().Pidfile) + srv.Close() + } else { + return + } + } else { + log.Printf("Force shutdown of docker, interrupting cleanup\n") + } + case syscall.SIGQUIT: + } + os.Exit(128 + int(sig.(syscall.Signal))) + }() } }() job.Eng.Hack_SetGlobalVar("httpapi.server", srv) From bd94f84ded944ab69c18cf9d23c35deee3b15963 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 3 Apr 2014 16:27:07 +0200 Subject: [PATCH 329/384] Fix --volumes-from mount failure As explained in https://github.com/dotcloud/docker/issues/4979 --volumes-from fails with ENOFILE errors. This is because the code tries to look at the "from" volume without ensuring that it is mounted yet. We fix this by mounting the containers before stating in it. Also includes a regression test. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- integration-cli/docker_cli_run_test.go | 18 ++++++++++++++++++ runtime/volumes.go | 7 ++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 8d62108fed..fbb09737fc 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -272,6 +272,24 @@ func TestDockerRunWithVolumesAsFiles(t *testing.T) { logDone("run - regression test for #4741 - volumes from as files") } +// Regression test for #4979 +func TestDockerRunWithVolumesFromExited(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "--name", "test-data", "--volume", "/some/dir", "busybox", "touch", "/some/dir/file") + out, stderr, exitCode, err := runCommandWithStdoutStderr(runCmd) + if err != nil && exitCode != 0 { + t.Fatal("1", out, stderr, err) + } + + runCmd = exec.Command(dockerBinary, "run", "--volumes-from", "test-data", "busybox", "cat", "/some/dir/file") + out, stderr, exitCode, err = runCommandWithStdoutStderr(runCmd) + if err != nil && exitCode != 0 { + t.Fatal("2", out, stderr, err) + } + deleteAllContainers() + + logDone("run - regression test for #4979 - volumes-from on exited container") +} + // Regression test for #4830 func TestDockerRunWithRelativePath(t *testing.T) { runCmd := exec.Command(dockerBinary, "run", "-v", "tmp:/other-tmp", "busybox", "true") diff --git a/runtime/volumes.go b/runtime/volumes.go index c504644ae8..0b6f3734e0 100644 --- a/runtime/volumes.go +++ b/runtime/volumes.go @@ -81,9 +81,14 @@ func applyVolumesFrom(container *Container) error { c := container.runtime.Get(specParts[0]) if c == nil { - return fmt.Errorf("Container %s not found. Impossible to mount its volumes", container.ID) + return fmt.Errorf("Container %s not found. Impossible to mount its volumes", specParts[0]) } + if err := c.Mount(); err != nil { + return fmt.Errorf("Container %s failed to mount. Impossible to mount its volumes", specParts[0]) + } + defer c.Unmount() + for volPath, id := range c.Volumes { if _, exists := container.Volumes[volPath]; exists { continue From 9712f8127a9ac47a3679e20faea08fb971ee1ecc Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 3 Apr 2014 11:46:24 -0600 Subject: [PATCH 330/384] Update contrib/check-config.sh to use zcat and grep if zgrep isn't available Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- contrib/check-config.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/contrib/check-config.sh b/contrib/check-config.sh index f225443138..3b1ff8ad85 100755 --- a/contrib/check-config.sh +++ b/contrib/check-config.sh @@ -5,10 +5,15 @@ set -e # see also https://github.com/lxc/lxc/blob/lxc-1.0.2/src/lxc/lxc-checkconfig.in : ${CONFIG:=/proc/config.gz} -: ${GREP:=zgrep} + +if ! command -v zgrep &> /dev/null; then + zgrep() { + zcat "$2" | grep "$1" + } +fi is_set() { - $GREP "CONFIG_$1=[y|m]" $CONFIG > /dev/null + zgrep "CONFIG_$1=[y|m]" "$CONFIG" > /dev/null } # see http://en.wikipedia.org/wiki/ANSI_escape_code#Colors From fee16d42163822bb23a51c5ebcb1115efc761947 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 3 Apr 2014 11:52:19 -0600 Subject: [PATCH 331/384] Update contrib/check-config.sh cgroupfs check to allow for something like '... cgroup rw,cpu' (looking at you, boot2docker) Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- contrib/check-config.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/check-config.sh b/contrib/check-config.sh index 3b1ff8ad85..53bf708404 100755 --- a/contrib/check-config.sh +++ b/contrib/check-config.sh @@ -98,7 +98,7 @@ echo echo 'Generally Necessary:' echo -n '- ' -cgroupCpuDir="$(awk '/[, ]cpu[, ]/ && $8 == "cgroup" { print $5 }' /proc/$$/mountinfo | head -n1)" +cgroupCpuDir="$(awk '/[, ]cpu([, ]|$)/ && $8 == "cgroup" { print $5 }' /proc/$$/mountinfo | head -n1)" cgroupDir="$(dirname "$cgroupCpuDir")" if [ -d "$cgroupDir/cpu" ]; then echo "$(wrap_good 'cgroup hierarchy' 'properly mounted') [$cgroupDir]" From c94111b61988ad32d87f99d4421cbcde018c3fb4 Mon Sep 17 00:00:00 2001 From: Kevin Wallace Date: Sun, 1 Dec 2013 15:27:24 -0800 Subject: [PATCH 332/384] Allow non-privileged containers to create device nodes. Such nodes could already be created by importing a tarball to a container; now they can be created from within the container itself. This gives non-privileged containers the mknod kernel capability, and modifies their cgroup settings to allow creation of *any* node, not just whitelisted ones. Use of such nodes is still controlled by the existing cgroup whitelist. Docker-DCO-1.1-Signed-off-by: Kevin Wallace (github: kevinwallace) --- integration/container_test.go | 8 ++++---- pkg/cgroups/apply_raw.go | 4 ++++ runtime/execdriver/lxc/init.go | 1 - runtime/execdriver/lxc/lxc_template.go | 4 ++++ runtime/execdriver/native/template/default_template.go | 1 - 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/integration/container_test.go b/integration/container_test.go index d3d35734ed..c64f9e610b 100644 --- a/integration/container_test.go +++ b/integration/container_test.go @@ -1619,16 +1619,16 @@ func TestPrivilegedCanMount(t *testing.T) { } } -func TestPrivilegedCannotMknod(t *testing.T) { +func TestUnprivilegedCanMknod(t *testing.T) { eng := NewTestEngine(t) runtime := mkRuntimeFromEngine(eng, t) defer runtime.Nuke() - if output, _ := runContainer(eng, runtime, []string{"_", "sh", "-c", "mknod /tmp/sda b 8 0 || echo ok"}, t); output != "ok\n" { - t.Fatal("Could mknod into secure container") + if output, _ := runContainer(eng, runtime, []string{"_", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok"}, t); output != "ok\n" { + t.Fatal("Couldn't mknod into secure container") } } -func TestPrivilegedCannotMount(t *testing.T) { +func TestUnprivilegedCannotMount(t *testing.T) { eng := NewTestEngine(t) runtime := mkRuntimeFromEngine(eng, t) defer runtime.Nuke() diff --git a/pkg/cgroups/apply_raw.go b/pkg/cgroups/apply_raw.go index 5fe317937a..220f08f1dc 100644 --- a/pkg/cgroups/apply_raw.go +++ b/pkg/cgroups/apply_raw.go @@ -95,6 +95,10 @@ func (raw *rawCgroup) setupDevices(c *Cgroup, pid int) (err error) { } allow := []string{ + // allow mknod for any device + "c *:* m", + "b *:* m", + // /dev/null, zero, full "c 1:3 rwm", "c 1:5 rwm", diff --git a/runtime/execdriver/lxc/init.go b/runtime/execdriver/lxc/init.go index a64bca15b2..c1933a5e43 100644 --- a/runtime/execdriver/lxc/init.go +++ b/runtime/execdriver/lxc/init.go @@ -144,7 +144,6 @@ func setupCapabilities(args *execdriver.InitArgs) error { capability.CAP_SYS_RESOURCE, capability.CAP_SYS_TIME, capability.CAP_SYS_TTY_CONFIG, - capability.CAP_MKNOD, capability.CAP_AUDIT_WRITE, capability.CAP_AUDIT_CONTROL, capability.CAP_MAC_OVERRIDE, diff --git a/runtime/execdriver/lxc/lxc_template.go b/runtime/execdriver/lxc/lxc_template.go index e5248375a8..bad3249b31 100644 --- a/runtime/execdriver/lxc/lxc_template.go +++ b/runtime/execdriver/lxc/lxc_template.go @@ -44,6 +44,10 @@ lxc.cgroup.devices.allow = a # no implicit access to devices lxc.cgroup.devices.deny = a +# but allow mknod for any device +lxc.cgroup.devices.allow = c *:* m +lxc.cgroup.devices.allow = b *:* m + # /dev/null and zero lxc.cgroup.devices.allow = c 1:3 rwm lxc.cgroup.devices.allow = c 1:5 rwm diff --git a/runtime/execdriver/native/template/default_template.go b/runtime/execdriver/native/template/default_template.go index b9eb87713e..6828812336 100644 --- a/runtime/execdriver/native/template/default_template.go +++ b/runtime/execdriver/native/template/default_template.go @@ -18,7 +18,6 @@ func New() *libcontainer.Container { libcontainer.GetCapability("SYS_RESOURCE"), libcontainer.GetCapability("SYS_TIME"), libcontainer.GetCapability("SYS_TTY_CONFIG"), - libcontainer.GetCapability("MKNOD"), libcontainer.GetCapability("AUDIT_WRITE"), libcontainer.GetCapability("AUDIT_CONTROL"), libcontainer.GetCapability("MAC_OVERRIDE"), From e21607341cd8dc575098bd09a3c992da166f7884 Mon Sep 17 00:00:00 2001 From: Kevin Wallace Date: Sun, 1 Dec 2013 15:33:44 -0800 Subject: [PATCH 333/384] Add myself to AUTHORS. Docker-DCO-1.1-Signed-off-by: Kevin Wallace (github: kevinwallace) --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index df091d5950..6e34065266 100644 --- a/AUTHORS +++ b/AUTHORS @@ -177,6 +177,7 @@ Keli Hu Ken Cochrane Kevin Clark Kevin J. Lynagh +Kevin Wallace Keyvan Fatehi kim0 Kim BKC Carlbacker From 3c36f82f181c4ce3b65dd15c4b6cb5699ea75075 Mon Sep 17 00:00:00 2001 From: Rajat Pandit Date: Thu, 3 Apr 2014 20:54:57 +0100 Subject: [PATCH 334/384] Update nodejs_web_app.rst --- docs/sources/examples/nodejs_web_app.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/examples/nodejs_web_app.rst b/docs/sources/examples/nodejs_web_app.rst index a9e9b1c5e3..87bc1d5aaa 100644 --- a/docs/sources/examples/nodejs_web_app.rst +++ b/docs/sources/examples/nodejs_web_app.rst @@ -50,7 +50,7 @@ Then, create an ``index.js`` file that defines a web app using the res.send('Hello World\n'); }); - app.listen(PORT) + app.listen(PORT); console.log('Running on http://localhost:' + PORT); From 32d6041cc30a636de2f8da89f778c4b6c7d4df19 Mon Sep 17 00:00:00 2001 From: Rajat Pandit Date: Thu, 3 Apr 2014 21:05:50 +0100 Subject: [PATCH 335/384] Update nodejs_web_app.rst --- docs/sources/examples/nodejs_web_app.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/examples/nodejs_web_app.rst b/docs/sources/examples/nodejs_web_app.rst index a9e9b1c5e3..e880db555f 100644 --- a/docs/sources/examples/nodejs_web_app.rst +++ b/docs/sources/examples/nodejs_web_app.rst @@ -18,7 +18,7 @@ https://github.com/gasi/docker-node-hello. Create Node.js app ++++++++++++++++++ -First, create a ``package.json`` file that describes your app and its +First, create a directory ``src`` where all the files would live. Then create a ``package.json`` file that describes your app and its dependencies: .. code-block:: json From 887eeb2b022a4c6d3de8c9bfc586ee82855d3cb9 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 3 Apr 2014 21:55:33 +0000 Subject: [PATCH 336/384] Skip login tests because of external dependency to a hosted service. Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- integration/auth_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/integration/auth_test.go b/integration/auth_test.go index 1d9d450573..8109bbb130 100644 --- a/integration/auth_test.go +++ b/integration/auth_test.go @@ -16,6 +16,7 @@ import ( // - Integration tests should have side-effects limited to the host environment being tested. func TestLogin(t *testing.T) { + t.Skip("FIXME: please remove dependency on external services") os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com") defer os.Setenv("DOCKER_INDEX_URL", "") authConfig := ®istry.AuthConfig{ @@ -34,6 +35,7 @@ func TestLogin(t *testing.T) { } func TestCreateAccount(t *testing.T) { + t.Skip("FIXME: please remove dependency on external services") tokenBuffer := make([]byte, 16) _, err := rand.Read(tokenBuffer) if err != nil { From 7c3b955b907c33238c1c155ae8860b2cec929c8b Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 3 Apr 2014 22:02:03 +0000 Subject: [PATCH 337/384] Deprecate 'docker images --tree' and 'docker images --viz' * The commands are no longer listed or documented. * The commands still work but print a deprecation warning. * The commands should be removed in a future version. Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- api/client/commands.go | 9 +++-- docs/sources/reference/commandline/cli.rst | 42 ---------------------- 2 files changed, 7 insertions(+), 44 deletions(-) diff --git a/api/client/commands.go b/api/client/commands.go index 53b8822d69..93627613a5 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -1137,8 +1137,9 @@ func (cli *DockerCli) CmdImages(args ...string) error { quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs") all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (by default filter out the intermediate images used to build)") noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") - flViz := cmd.Bool([]string{"v", "#viz", "-viz"}, false, "Output graph in graphviz format") - flTree := cmd.Bool([]string{"t", "#tree", "-tree"}, false, "Output graph in tree format") + // FIXME: --viz and --tree are deprecated. Remove them in a future version. + flViz := cmd.Bool([]string{"#v", "#viz", "#-viz"}, false, "Output graph in graphviz format") + flTree := cmd.Bool([]string{"#t", "#tree", "#-tree"}, false, "Output graph in tree format") if err := cmd.Parse(args); err != nil { return nil @@ -1150,6 +1151,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { filter := cmd.Arg(0) + // FIXME: --viz and --tree are deprecated. Remove them in a future version. if *flViz || *flTree { body, _, err := readBody(cli.call("GET", "/images/json?all=1", nil, false)) if err != nil { @@ -1260,6 +1262,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { return nil } +// FIXME: --viz and --tree are deprecated. Remove them in a future version. func (cli *DockerCli) WalkTree(noTrunc bool, images *engine.Table, byParent map[string]*engine.Table, prefix string, printNode func(cli *DockerCli, noTrunc bool, image *engine.Env, prefix string)) { length := images.Len() if length > 1 { @@ -1286,6 +1289,7 @@ func (cli *DockerCli) WalkTree(noTrunc bool, images *engine.Table, byParent map[ } } +// FIXME: --viz and --tree are deprecated. Remove them in a future version. func (cli *DockerCli) printVizNode(noTrunc bool, image *engine.Env, prefix string) { var ( imageID string @@ -1309,6 +1313,7 @@ func (cli *DockerCli) printVizNode(noTrunc bool, image *engine.Env, prefix strin } } +// FIXME: --viz and --tree are deprecated. Remove them in a future version. func (cli *DockerCli) printTreeNode(noTrunc bool, image *engine.Env, prefix string) { var imageID string if noTrunc { diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 64dff1e1c2..6ff66feeb7 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -600,8 +600,6 @@ To see how the ``docker:latest`` image was built: -a, --all=false: Show all images (by default filter out the intermediate images used to build) --no-trunc=false: Don't truncate output -q, --quiet=false: Only show numeric IDs - -t, --tree=false: Output graph in tree format - -v, --viz=false: Output graph in graphviz format Listing the most recently created images ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -637,46 +635,6 @@ Listing the full length image IDs tryout latest 2629d1fa0b81b222fca63371ca16cbf6a0772d07759ff80e8d1369b926940074 23 hours ago 131.5 MB 5ed6274db6ceb2397844896966ea239290555e74ef307030ebb01ff91b1914df 24 hours ago 1.089 GB -Displaying images visually -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: bash - - $ sudo docker images --viz | dot -Tpng -o docker.png - -.. image:: docker_images.gif - :alt: Example inheritance graph of Docker images. - - -Displaying image hierarchy -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: bash - - $ sudo docker images --tree - - ├─8dbd9e392a96 Size: 131.5 MB (virtual 131.5 MB) Tags: ubuntu:12.04,ubuntu:latest,ubuntu:precise - └─27cf78414709 Size: 180.1 MB (virtual 180.1 MB) - └─b750fe79269d Size: 24.65 kB (virtual 180.1 MB) Tags: ubuntu:12.10,ubuntu:quantal - ├─f98de3b610d5 Size: 12.29 kB (virtual 180.1 MB) - │ └─7da80deb7dbf Size: 16.38 kB (virtual 180.1 MB) - │ └─65ed2fee0a34 Size: 20.66 kB (virtual 180.2 MB) - │ └─a2b9ea53dddc Size: 819.7 MB (virtual 999.8 MB) - │ └─a29b932eaba8 Size: 28.67 kB (virtual 999.9 MB) - │ └─e270a44f124d Size: 12.29 kB (virtual 999.9 MB) Tags: progrium/buildstep:latest - └─17e74ac162d8 Size: 53.93 kB (virtual 180.2 MB) - └─339a3f56b760 Size: 24.65 kB (virtual 180.2 MB) - └─904fcc40e34d Size: 96.7 MB (virtual 276.9 MB) - └─b1b0235328dd Size: 363.3 MB (virtual 640.2 MB) - └─7cb05d1acb3b Size: 20.48 kB (virtual 640.2 MB) - └─47bf6f34832d Size: 20.48 kB (virtual 640.2 MB) - └─f165104e82ed Size: 12.29 kB (virtual 640.2 MB) - └─d9cf85a47b7e Size: 1.911 MB (virtual 642.2 MB) - └─3ee562df86ca Size: 17.07 kB (virtual 642.2 MB) - └─b05fc2d00e4a Size: 24.96 kB (virtual 642.2 MB) - └─c96a99614930 Size: 12.29 kB (virtual 642.2 MB) - └─a6a357a48c49 Size: 12.29 kB (virtual 642.2 MB) Tags: ndj/mongodb:latest - .. _cli_import: ``import`` From 6cf137860102b8df5db75dd68924375a7b74c1c3 Mon Sep 17 00:00:00 2001 From: Goffert van Gool Date: Thu, 3 Apr 2014 20:47:49 +0200 Subject: [PATCH 338/384] Fix typo in names-generator Docker-DCO-1.1-Signed-off-by: Goffert van Gool (github: ruphin) --- pkg/namesgenerator/names-generator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/namesgenerator/names-generator.go b/pkg/namesgenerator/names-generator.go index cb18eb8c06..07fadf8171 100644 --- a/pkg/namesgenerator/names-generator.go +++ b/pkg/namesgenerator/names-generator.go @@ -76,7 +76,7 @@ var ( // http://en.wikipedia.org/wiki/John_Bardeen // http://en.wikipedia.org/wiki/Walter_Houser_Brattain // http://en.wikipedia.org/wiki/William_Shockley - right = [...]string{"lovelace", "franklin", "tesla", "einstein", "bohr", "davinci", "pasteur", "nobel", "curie", "darwin", "turing", "ritchie", "torvalds", "pike", "thompson", "wozniak", "galileo", "euclid", "newton", "fermat", "archimedes", "poincare", "heisenberg", "feynman", "hawking", "fermi", "pare", "mccarthy", "engelbart", "babbage", "albattani", "ptolemy", "bell", "wright", "lumiere", "morse", "mclean", "brown", "bardeen", "brattain", "shockley", "goldstine", "hoover", "hopper", "bartik", "sammet", "jones", "perlman", "wilson", "kowalevski", "hypatia", "goodall", "mayer", "elion", "blackwell", "lalande", "kirch", "ardinghelli", "colden", "almeida", "leakey", "meitner", "mestorf", "rosalind", "sinoussi", "carson", "mcmclintock", "yonath"} + right = [...]string{"lovelace", "franklin", "tesla", "einstein", "bohr", "davinci", "pasteur", "nobel", "curie", "darwin", "turing", "ritchie", "torvalds", "pike", "thompson", "wozniak", "galileo", "euclid", "newton", "fermat", "archimedes", "poincare", "heisenberg", "feynman", "hawking", "fermi", "pare", "mccarthy", "engelbart", "babbage", "albattani", "ptolemy", "bell", "wright", "lumiere", "morse", "mclean", "brown", "bardeen", "brattain", "shockley", "goldstine", "hoover", "hopper", "bartik", "sammet", "jones", "perlman", "wilson", "kowalevski", "hypatia", "goodall", "mayer", "elion", "blackwell", "lalande", "kirch", "ardinghelli", "colden", "almeida", "leakey", "meitner", "mestorf", "rosalind", "sinoussi", "carson", "mcclintock", "yonath"} ) func GenerateRandomName(checker NameChecker) (string, error) { From 615ac8feb27b2b3db0c06b37ecd87b710eabffef Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 3 Apr 2014 23:47:58 +0000 Subject: [PATCH 339/384] Deprecate 'docker insert' 'docker insert' is an old command which predates 'docker build'. We no longer recommend using it, it is not actively maintained, and can be replaced with the combination of 'docker build' and 'ADD'. This removes the command from usage and documentation, and prints a warning when it is called. The command still works but it will be removed in a future version. Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- api/client/commands.go | 3 ++- api/server/server.go | 1 + docs/sources/reference/commandline/cli.rst | 28 ---------------------- integration/server_test.go | 1 + server/server.go | 3 +++ 5 files changed, 7 insertions(+), 29 deletions(-) diff --git a/api/client/commands.go b/api/client/commands.go index 53b8822d69..168252a1b7 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -56,7 +56,6 @@ func (cli *DockerCli) CmdHelp(args ...string) error { {"images", "List images"}, {"import", "Create a new filesystem image from the contents of a tarball"}, {"info", "Display system-wide information"}, - {"insert", "Insert a file in an image"}, {"inspect", "Return low-level information on a container"}, {"kill", "Kill a running container"}, {"load", "Load an image from a tar archive"}, @@ -85,7 +84,9 @@ func (cli *DockerCli) CmdHelp(args ...string) error { return nil } +// FIXME: 'insert' is deprecated. func (cli *DockerCli) CmdInsert(args ...string) error { + fmt.Fprintf(os.Stderr, "Warning: '%s' is deprecated and will be removed in a future version. Please use 'docker build' and 'ADD' instead.\n") cmd := cli.Subcmd("insert", "IMAGE URL PATH", "Insert a file from URL in the IMAGE at PATH") if err := cmd.Parse(args); err != nil { return nil diff --git a/api/server/server.go b/api/server/server.go index 93dd2094b6..c6eafaf265 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -458,6 +458,7 @@ func getImagesSearch(eng *engine.Engine, version version.Version, w http.Respons return job.Run() } +// FIXME: 'insert' is deprecated as of 0.10, and should be removed in a future version. func postImagesInsert(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 64dff1e1c2..09ef6e0679 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -753,34 +753,6 @@ preserved. WARNING: No swap limit support -.. _cli_insert: - -``insert`` ----------- - -:: - - Usage: docker insert IMAGE URL PATH - - Insert a file from URL in the IMAGE at PATH - -Use the specified ``IMAGE`` as the parent for a new image which adds a -:ref:`layer ` containing the new file. The ``insert`` command does -not modify the original image, and the new image has the contents of the parent -image, plus the new file. - - -Examples -~~~~~~~~ - -Insert file from GitHub -....................... - -.. code-block:: bash - - $ sudo docker insert 8283e18b24bc https://raw.github.com/metalivedev/django/master/postinstall /tmp/postinstall.sh - 06fd35556d7b - .. _cli_inspect: ``inspect`` diff --git a/integration/server_test.go b/integration/server_test.go index a401f1306e..4ad5ec0f92 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -640,6 +640,7 @@ func TestImagesFilter(t *testing.T) { } } +// FIXE: 'insert' is deprecated and should be removed in a future version. func TestImageInsert(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() diff --git a/server/server.go b/server/server.go index fae50094c2..9cabf17889 100644 --- a/server/server.go +++ b/server/server.go @@ -82,6 +82,7 @@ func InitServer(job *engine.Job) engine.Status { job.Eng.Hack_SetGlobalVar("httpapi.server", srv) job.Eng.Hack_SetGlobalVar("httpapi.runtime", srv.runtime) + // FIXME: 'insert' is deprecated and should be removed in a future version. for name, handler := range map[string]engine.Handler{ "export": srv.ContainerExport, "create": srv.ContainerCreate, @@ -641,7 +642,9 @@ func (srv *Server) ImagesSearch(job *engine.Job) engine.Status { return engine.StatusOK } +// FIXME: 'insert' is deprecated and should be removed in a future version. func (srv *Server) ImageInsert(job *engine.Job) engine.Status { + fmt.Fprintf(job.Stderr, "Warning: '%s' is deprecated and will be removed in a future version. Please use 'build' and 'ADD' instead.\n", job.Name) if len(job.Args) != 3 { return job.Errorf("Usage: %s IMAGE URL PATH\n", job.Name) } From c8f437aee0d90d4955a6aaa35f8e0b74e7ac99a3 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 3 Apr 2014 23:14:51 +0000 Subject: [PATCH 340/384] api/server: replace an integration test with a unit test using engine mocking. Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- api/server/server_unit_test.go | 61 ++++++++++++++++++++++++++++++++++ integration/api_test.go | 37 --------------------- 2 files changed, 61 insertions(+), 37 deletions(-) diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index 5ea5af411c..c14fd8ba9e 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -2,8 +2,13 @@ package server import ( "fmt" + "github.com/dotcloud/docker/api" + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/utils" + "io" "net/http" "net/http/httptest" + "os" "testing" ) @@ -50,3 +55,59 @@ func TesthttpError(t *testing.T) { t.Fatalf("Expected %d, got %d", http.StatusInternalServerError, r.Code) } } + +func TestGetVersion(t *testing.T) { + tmp, err := utils.TestDirectory("") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmp) + eng, err := engine.New(tmp) + if err != nil { + t.Fatal(err) + } + var called bool + eng.Register("version", func(job *engine.Job) engine.Status { + called = true + v := &engine.Env{} + v.Set("Version", "42.1") + v.Set("ApiVersion", "1.1.1.1.1") + v.Set("GoVersion", "2.42") + v.Set("Os", "Linux") + v.Set("Arch", "x86_64") + if _, err := v.WriteTo(job.Stdout); err != nil { + return job.Error(err) + } + return engine.StatusOK + }) + + r := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/version", nil) + if err != nil { + t.Fatal(err) + } + // FIXME getting the version should require an actual running Server + if err := ServeRequest(eng, api.APIVERSION, r, req); err != nil { + t.Fatal(err) + } + if !called { + t.Fatalf("handler was not called") + } + out := engine.NewOutput() + v, err := out.AddEnv() + if err != nil { + t.Fatal(err) + } + if _, err := io.Copy(out, r.Body); err != nil { + t.Fatal(err) + } + out.Close() + expected := "42.1" + if result := v.Get("Version"); result != expected { + t.Errorf("Expected version %s, %s found", expected, result) + } + expected = "application/json" + if result := r.HeaderMap.Get("Content-Type"); result != expected { + t.Errorf("Expected Content-Type %s, %s found", expected, result) + } +} diff --git a/integration/api_test.go b/integration/api_test.go index d08617ea69..61697af8a1 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -16,7 +16,6 @@ import ( "github.com/dotcloud/docker/api" "github.com/dotcloud/docker/api/server" - "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/runconfig" @@ -25,42 +24,6 @@ import ( "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) -func TestGetVersion(t *testing.T) { - eng := NewTestEngine(t) - defer mkRuntimeFromEngine(eng, t).Nuke() - - var err error - r := httptest.NewRecorder() - - req, err := http.NewRequest("GET", "/version", nil) - if err != nil { - t.Fatal(err) - } - // FIXME getting the version should require an actual running Server - if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { - t.Fatal(err) - } - assertHttpNotError(r, t) - - out := engine.NewOutput() - v, err := out.AddEnv() - if err != nil { - t.Fatal(err) - } - if _, err := io.Copy(out, r.Body); err != nil { - t.Fatal(err) - } - out.Close() - expected := dockerversion.VERSION - if result := v.Get("Version"); result != expected { - t.Errorf("Expected version %s, %s found", expected, result) - } - expected = "application/json" - if result := r.HeaderMap.Get("Content-Type"); result != expected { - t.Errorf("Expected Content-Type %s, %s found", expected, result) - } -} - func TestGetInfo(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() From 76057addb255e6f14dd03c276317abc759a15a80 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 3 Apr 2014 23:15:56 +0000 Subject: [PATCH 341/384] engine: fix engine.Env.Encode() to stop auto-guessing types. Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- engine/env.go | 20 +------------------- engine/env_test.go | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/engine/env.go b/engine/env.go index c43a5ec971..1da3ae52e0 100644 --- a/engine/env.go +++ b/engine/env.go @@ -194,25 +194,7 @@ func (env *Env) SetAuto(k string, v interface{}) { } func (env *Env) Encode(dst io.Writer) error { - m := make(map[string]interface{}) - for k, v := range env.Map() { - var val interface{} - if err := json.Unmarshal([]byte(v), &val); err == nil { - // FIXME: we fix-convert float values to int, because - // encoding/json decodes integers to float64, but cannot encode them back. - // (See http://golang.org/src/pkg/encoding/json/decode.go#L46) - if fval, isFloat := val.(float64); isFloat { - val = int(fval) - } - m[k] = val - } else { - m[k] = v - } - } - if err := json.NewEncoder(dst).Encode(&m); err != nil { - return err - } - return nil + return json.NewEncoder(dst).Encode(env.Map()) } func (env *Env) WriteTo(dst io.Writer) (n int64, err error) { diff --git a/engine/env_test.go b/engine/env_test.go index c7079ff942..da7d919f03 100644 --- a/engine/env_test.go +++ b/engine/env_test.go @@ -95,3 +95,21 @@ func TestEnviron(t *testing.T) { t.Fatalf("bar not found in the environ") } } + +func TestEnvWriteTo(t *testing.T) { + e := &Env{} + inputKey := "Version" + inputVal := "42.1" + e.Set(inputKey, inputVal) + out := NewOutput() + e2, err := out.AddEnv() + if err != nil { + t.Fatal(err) + } + e.WriteTo(out) + result := e2.Get(inputKey) + expected := inputVal + if expected != result { + t.Fatalf("%#v\n", result) + } +} From 2cb560988b111ee736c4ab22588d2091cb04075e Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 4 Apr 2014 00:02:44 +0000 Subject: [PATCH 342/384] api/server: convert TestGetInfo from an integration test to a unit test. Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- api/server/server_unit_test.go | 67 ++++++++++++++++++++++++++++++++++ integration/api_test.go | 41 --------------------- 2 files changed, 67 insertions(+), 41 deletions(-) diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index c14fd8ba9e..3fc1cea064 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -111,3 +111,70 @@ func TestGetVersion(t *testing.T) { t.Errorf("Expected Content-Type %s, %s found", expected, result) } } + +func TestGetInfo(t *testing.T) { + tmp, err := utils.TestDirectory("") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmp) + eng, err := engine.New(tmp) + if err != nil { + t.Fatal(err) + } + + var called bool + eng.Register("info", func(job *engine.Job) engine.Status { + called = true + v := &engine.Env{} + v.SetInt("Containers", 1) + v.SetInt("Images", 42000) + if _, err := v.WriteTo(job.Stdout); err != nil { + return job.Error(err) + } + return engine.StatusOK + }) + + r := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/info", nil) + if err != nil { + t.Fatal(err) + } + // FIXME getting the version should require an actual running Server + if err := ServeRequest(eng, api.APIVERSION, r, req); err != nil { + t.Fatal(err) + } + if !called { + t.Fatalf("handler was not called") + } + + out := engine.NewOutput() + i, err := out.AddEnv() + if err != nil { + t.Fatal(err) + } + if _, err := io.Copy(out, r.Body); err != nil { + t.Fatal(err) + } + out.Close() + { + expected := 42000 + result := i.GetInt("Images") + if expected != result { + t.Fatalf("%#v\n", result) + } + } + { + expected := 1 + result := i.GetInt("Containers") + if expected != result { + t.Fatalf("%#v\n", result) + } + } + { + expected := "application/json" + if result := r.HeaderMap.Get("Content-Type"); result != expected { + t.Fatalf("%#v\n", result) + } + } +} diff --git a/integration/api_test.go b/integration/api_test.go index 61697af8a1..26441a2668 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -24,47 +24,6 @@ import ( "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) -func TestGetInfo(t *testing.T) { - eng := NewTestEngine(t) - defer mkRuntimeFromEngine(eng, t).Nuke() - - job := eng.Job("images") - initialImages, err := job.Stdout.AddListTable() - if err != nil { - t.Fatal(err) - } - if err := job.Run(); err != nil { - t.Fatal(err) - } - req, err := http.NewRequest("GET", "/info", nil) - if err != nil { - t.Fatal(err) - } - r := httptest.NewRecorder() - - if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { - t.Fatal(err) - } - assertHttpNotError(r, t) - - out := engine.NewOutput() - i, err := out.AddEnv() - if err != nil { - t.Fatal(err) - } - if _, err := io.Copy(out, r.Body); err != nil { - t.Fatal(err) - } - out.Close() - if images := i.GetInt("Images"); images != initialImages.Len() { - t.Errorf("Expected images: %d, %d found", initialImages.Len(), images) - } - expected := "application/json" - if result := r.HeaderMap.Get("Content-Type"); result != expected { - t.Errorf("Expected Content-Type %s, %s found", expected, result) - } -} - func TestGetEvents(t *testing.T) { eng := NewTestEngine(t) srv := mkServerFromEngine(eng, t) From b2b9334f27e1a773b77241efa214af2e87439d3b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 4 Apr 2014 00:08:51 +0000 Subject: [PATCH 343/384] remove hack in version Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.go b/server/server.go index fae50094c2..a6a0c14a84 100644 --- a/server/server.go +++ b/server/server.go @@ -843,7 +843,7 @@ func (srv *Server) DockerInfo(job *engine.Job) engine.Status { func (srv *Server) DockerVersion(job *engine.Job) engine.Status { v := &engine.Env{} v.Set("Version", dockerversion.VERSION) - v.SetJson("ApiVersion", api.APIVERSION) + v.Set("ApiVersion", string(api.APIVERSION)) v.Set("GitCommit", dockerversion.GITCOMMIT) v.Set("GoVersion", goruntime.Version()) v.Set("Os", goruntime.GOOS) From e09274476f889c08416a819dfb28f2c425868c6b Mon Sep 17 00:00:00 2001 From: unclejack Date: Fri, 4 Apr 2014 03:22:32 +0300 Subject: [PATCH 344/384] cli integration: sync container & image deletion This makes container and image removal in the tests run synchronously. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- integration-cli/docker_cli_build_test.go | 2 +- integration-cli/docker_cli_commit_test.go | 4 ++-- integration-cli/docker_cli_diff_test.go | 6 +++--- integration-cli/docker_cli_export_import_test.go | 4 ++-- integration-cli/docker_cli_kill_test.go | 2 +- integration-cli/docker_cli_logs_test.go | 6 +++--- integration-cli/docker_cli_push_test.go | 2 +- integration-cli/docker_cli_save_load_test.go | 4 ++-- integration-cli/docker_cli_tag_test.go | 2 +- integration-cli/docker_cli_top_test.go | 2 +- 10 files changed, 17 insertions(+), 17 deletions(-) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index e6f3096892..7cd42dc69c 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -18,7 +18,7 @@ func TestBuildSixtySteps(t *testing.T) { t.Fatal("failed to build the image") } - go deleteImages("foobuildsixtysteps") + deleteImages("foobuildsixtysteps") logDone("build - build an image with sixty build steps") } diff --git a/integration-cli/docker_cli_commit_test.go b/integration-cli/docker_cli_commit_test.go index 5ed55ef62a..51adaac9df 100644 --- a/integration-cli/docker_cli_commit_test.go +++ b/integration-cli/docker_cli_commit_test.go @@ -27,8 +27,8 @@ func TestCommitAfterContainerIsDone(t *testing.T) { out, _, err = runCommandWithOutput(inspectCmd) errorOut(err, t, fmt.Sprintf("failed to inspect image: %v %v", out, err)) - go deleteContainer(cleanedContainerID) - go deleteImages(cleanedImageID) + deleteContainer(cleanedContainerID) + deleteImages(cleanedImageID) logDone("commit - echo foo and commit the image") } diff --git a/integration-cli/docker_cli_diff_test.go b/integration-cli/docker_cli_diff_test.go index 0ae9cca38d..478ebd2df1 100644 --- a/integration-cli/docker_cli_diff_test.go +++ b/integration-cli/docker_cli_diff_test.go @@ -30,7 +30,7 @@ func TestDiffFilenameShownInOutput(t *testing.T) { if !found { t.Errorf("couldn't find the new file in docker diff's output: %v", out) } - go deleteContainer(cleanCID) + deleteContainer(cleanCID) logDone("diff - check if created file shows up") } @@ -53,7 +53,7 @@ func TestDiffEnsureDockerinitFilesAreIgnored(t *testing.T) { out, _, err := runCommandWithOutput(diffCmd) errorOut(err, t, fmt.Sprintf("failed to run diff: %v %v", out, err)) - go deleteContainer(cleanCID) + deleteContainer(cleanCID) for _, filename := range dockerinitFiles { if strings.Contains(out, filename) { @@ -74,7 +74,7 @@ func TestDiffEnsureOnlyKmsgAndPtmx(t *testing.T) { diffCmd := exec.Command(dockerBinary, "diff", cleanCID) out, _, err := runCommandWithOutput(diffCmd) errorOut(err, t, fmt.Sprintf("failed to run diff: %v %v", out, err)) - go deleteContainer(cleanCID) + deleteContainer(cleanCID) expected := map[string]bool{ "C /dev": true, diff --git a/integration-cli/docker_cli_export_import_test.go b/integration-cli/docker_cli_export_import_test.go index 66ff1055ba..2e443cd39e 100644 --- a/integration-cli/docker_cli_export_import_test.go +++ b/integration-cli/docker_cli_export_import_test.go @@ -40,8 +40,8 @@ func TestExportContainerAndImportImage(t *testing.T) { out, _, err = runCommandWithOutput(inspectCmd) errorOut(err, t, fmt.Sprintf("output should've been an image id: %v %v", out, err)) - go deleteImages("testexp") - go deleteContainer(cleanedContainerID) + deleteContainer(cleanedContainerID) + deleteImages("testexp") os.Remove("/tmp/testexp.tar") diff --git a/integration-cli/docker_cli_kill_test.go b/integration-cli/docker_cli_kill_test.go index 676ccd0ca0..b8265d8cfb 100644 --- a/integration-cli/docker_cli_kill_test.go +++ b/integration-cli/docker_cli_kill_test.go @@ -30,7 +30,7 @@ func TestKillContainer(t *testing.T) { t.Fatal("killed container is still running") } - go deleteContainer(cleanedContainerID) + deleteContainer(cleanedContainerID) logDone("kill - kill container running sleep 10") } diff --git a/integration-cli/docker_cli_logs_test.go b/integration-cli/docker_cli_logs_test.go index f8fcbe8832..8fcf4d7333 100644 --- a/integration-cli/docker_cli_logs_test.go +++ b/integration-cli/docker_cli_logs_test.go @@ -24,7 +24,7 @@ func TestLogsContainerSmallerThanPage(t *testing.T) { t.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out)) } - go deleteContainer(cleanedContainerID) + deleteContainer(cleanedContainerID) logDone("logs - logs container running echo smaller than page size") } @@ -47,7 +47,7 @@ func TestLogsContainerBiggerThanPage(t *testing.T) { t.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out)) } - go deleteContainer(cleanedContainerID) + deleteContainer(cleanedContainerID) logDone("logs - logs container running echo bigger than page size") } @@ -70,7 +70,7 @@ func TestLogsContainerMuchBiggerThanPage(t *testing.T) { t.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out)) } - go deleteContainer(cleanedContainerID) + deleteContainer(cleanedContainerID) logDone("logs - logs container running echo much bigger than page size") } diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go index 8117c077bc..160bb9e286 100644 --- a/integration-cli/docker_cli_push_test.go +++ b/integration-cli/docker_cli_push_test.go @@ -26,7 +26,7 @@ func TestPushBusyboxImage(t *testing.T) { out, exitCode, err = runCommandWithOutput(pushCmd) errorOut(err, t, fmt.Sprintf("%v %v", out, err)) - go deleteImages(repoName) + deleteImages(repoName) if err != nil || exitCode != 0 { t.Fatal("pushing the image to the private registry has failed") diff --git a/integration-cli/docker_cli_save_load_test.go b/integration-cli/docker_cli_save_load_test.go index 7f04f7ca53..d728c7de95 100644 --- a/integration-cli/docker_cli_save_load_test.go +++ b/integration-cli/docker_cli_save_load_test.go @@ -42,8 +42,8 @@ func TestSaveAndLoadRepo(t *testing.T) { out, _, err = runCommandWithOutput(inspectCmd) errorOut(err, t, fmt.Sprintf("the repo should exist after loading it: %v %v", out, err)) - go deleteImages(repoName) - go deleteContainer(cleanedContainerID) + deleteContainer(cleanedContainerID) + deleteImages(repoName) os.Remove("/tmp/foobar-save-load-test.tar") diff --git a/integration-cli/docker_cli_tag_test.go b/integration-cli/docker_cli_tag_test.go index 67c28c570a..d75b7db385 100644 --- a/integration-cli/docker_cli_tag_test.go +++ b/integration-cli/docker_cli_tag_test.go @@ -79,7 +79,7 @@ func TestTagValidPrefixedRepo(t *testing.T) { t.Errorf("tag busybox %v should have worked: %s", repo, err) continue } - go deleteImages(repo) + deleteImages(repo) logMessage := fmt.Sprintf("tag - busybox %v", repo) logDone(logMessage) } diff --git a/integration-cli/docker_cli_top_test.go b/integration-cli/docker_cli_top_test.go index 1895054ccc..73d590cf06 100644 --- a/integration-cli/docker_cli_top_test.go +++ b/integration-cli/docker_cli_top_test.go @@ -22,7 +22,7 @@ func TestTop(t *testing.T) { _, err = runCommand(killCmd) errorOut(err, t, fmt.Sprintf("failed to kill container: %v", err)) - go deleteContainer(cleanedContainerID) + deleteContainer(cleanedContainerID) if !strings.Contains(out, "sleep 20") { t.Fatal("top should've listed sleep 20 in the process list") From bea71245c8165e0dfdc6b2485c548c04f4d3edd3 Mon Sep 17 00:00:00 2001 From: Dan Stine Date: Fri, 4 Apr 2014 08:12:17 -0400 Subject: [PATCH 345/384] fixed two readme typos --- pkg/libcontainer/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/libcontainer/README.md b/pkg/libcontainer/README.md index e967f6d76d..3bf79549e3 100644 --- a/pkg/libcontainer/README.md +++ b/pkg/libcontainer/README.md @@ -3,7 +3,7 @@ #### background libcontainer specifies configuration options for what a container is. It provides a native Go implementation -for using linux namespaces with no external dependencies. libcontainer provides many convience functions for working with namespaces, networking, and management. +for using linux namespaces with no external dependencies. libcontainer provides many convenience functions for working with namespaces, networking, and management. #### container @@ -91,7 +91,7 @@ Sample `container.json` file: ``` Using this configuration and the current directory holding the rootfs for a process, one can use libcontainer to exec the container. Running the life of the namespace, a `pid` file -is written to the current directory with the pid of the namespaced process to the external world. A client can use this pid to wait, kill, or perform other operation with the container. If a user tries to run an new process inside an existing container with a live namespace the namespace will be joined by the new process. +is written to the current directory with the pid of the namespaced process to the external world. A client can use this pid to wait, kill, or perform other operation with the container. If a user tries to run a new process inside an existing container with a live namespace the namespace will be joined by the new process. You may also specify an alternate root place where the `container.json` file is read and where the `pid` file will be saved. From 62b08f557db91cc5cd12ea9ceb0a4d8cf3d6e0f1 Mon Sep 17 00:00:00 2001 From: unclejack Date: Fri, 4 Apr 2014 19:03:07 +0300 Subject: [PATCH 346/384] cli integration: allow driver selection via vars This makes it possible to choose the graphdriver and the execdriver which is going to be used for the cli integration tests. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- Makefile | 2 +- hack/make/test-integration-cli | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 776d57951f..d49aa3b667 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ DOCKER_IMAGE := docker$(if $(GIT_BRANCH),:$(GIT_BRANCH)) DOCKER_DOCS_IMAGE := docker-docs$(if $(GIT_BRANCH),:$(GIT_BRANCH)) DOCKER_MOUNT := $(if $(BINDDIR),-v "$(CURDIR)/$(BINDDIR):/go/src/github.com/dotcloud/docker/$(BINDDIR)") -DOCKER_RUN_DOCKER := docker run --rm -it --privileged -e TESTFLAGS $(DOCKER_MOUNT) "$(DOCKER_IMAGE)" +DOCKER_RUN_DOCKER := docker run --rm -it --privileged -e TESTFLAGS -e DOCKER_GRAPHDRIVER -e DOCKER_EXECDRIVER $(DOCKER_MOUNT) "$(DOCKER_IMAGE)" DOCKER_RUN_DOCS := docker run --rm -it -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)" default: binary diff --git a/hack/make/test-integration-cli b/hack/make/test-integration-cli index 5c6fc367fc..1760171dd5 100644 --- a/hack/make/test-integration-cli +++ b/hack/make/test-integration-cli @@ -7,6 +7,8 @@ set -e # subshell so that we can export PATH without breaking other things ( export PATH="$DEST/../binary:$DEST/../dynbinary:$PATH" +DOCKER_GRAPHDRIVER=${DOCKER_GRAPHDRIVER:-vfs} +DOCKER_EXECDRIVER=${DOCKER_EXECDRIVER:-native} bundle_test_integration_cli() { go_test_dir ./integration-cli @@ -17,7 +19,8 @@ if ! command -v docker &> /dev/null; then false fi -docker -d -D -p $DEST/docker.pid &> $DEST/docker.log & +echo "running cli integration tests using graphdriver: '$DOCKER_GRAPHDRIVER' and execdriver: '$DOCKER_EXECDRIVER'" +docker -d -D -s $DOCKER_GRAPHDRIVER -e $DOCKER_EXECDRIVER -p $DEST/docker.pid &> $DEST/docker.log & # pull the busybox image before running the tests sleep 2 From 22152ccc47e641050da85b80cebf2912b42fd122 Mon Sep 17 00:00:00 2001 From: unclejack Date: Fri, 4 Apr 2014 19:06:55 +0300 Subject: [PATCH 347/384] cli integration: fix wait race The wait at the end of cli integration script could end up failing if the process had already exited. This was making it look like the tests have failed. This change fixes the problem. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- hack/make/test-integration-cli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/make/test-integration-cli b/hack/make/test-integration-cli index 1760171dd5..18e4ee6602 100644 --- a/hack/make/test-integration-cli +++ b/hack/make/test-integration-cli @@ -31,5 +31,5 @@ bundle_test_integration_cli 2>&1 \ DOCKERD_PID=$(cat $DEST/docker.pid) kill $DOCKERD_PID -wait $DOCKERD_PID +wait $DOCKERD_PID || true ) From 95e6fd819bbef09032bf680e0f7dadd7fbf44559 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 4 Apr 2014 11:29:56 -0700 Subject: [PATCH 348/384] Revert "engine: fix engine.Env.Encode() to stop auto-guessing types." This reverts commit 76057addb255e6f14dd03c276317abc759a15a80. Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- engine/env.go | 20 +++++++++++++++++++- engine/env_test.go | 18 ------------------ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/engine/env.go b/engine/env.go index 1da3ae52e0..c43a5ec971 100644 --- a/engine/env.go +++ b/engine/env.go @@ -194,7 +194,25 @@ func (env *Env) SetAuto(k string, v interface{}) { } func (env *Env) Encode(dst io.Writer) error { - return json.NewEncoder(dst).Encode(env.Map()) + m := make(map[string]interface{}) + for k, v := range env.Map() { + var val interface{} + if err := json.Unmarshal([]byte(v), &val); err == nil { + // FIXME: we fix-convert float values to int, because + // encoding/json decodes integers to float64, but cannot encode them back. + // (See http://golang.org/src/pkg/encoding/json/decode.go#L46) + if fval, isFloat := val.(float64); isFloat { + val = int(fval) + } + m[k] = val + } else { + m[k] = v + } + } + if err := json.NewEncoder(dst).Encode(&m); err != nil { + return err + } + return nil } func (env *Env) WriteTo(dst io.Writer) (n int64, err error) { diff --git a/engine/env_test.go b/engine/env_test.go index da7d919f03..c7079ff942 100644 --- a/engine/env_test.go +++ b/engine/env_test.go @@ -95,21 +95,3 @@ func TestEnviron(t *testing.T) { t.Fatalf("bar not found in the environ") } } - -func TestEnvWriteTo(t *testing.T) { - e := &Env{} - inputKey := "Version" - inputVal := "42.1" - e.Set(inputKey, inputVal) - out := NewOutput() - e2, err := out.AddEnv() - if err != nil { - t.Fatal(err) - } - e.WriteTo(out) - result := e2.Get(inputKey) - expected := inputVal - if expected != result { - t.Fatalf("%#v\n", result) - } -} From 4c6cf9e27fd0ee6c09e836f03722d1c679b6bd29 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 4 Apr 2014 11:35:07 -0700 Subject: [PATCH 349/384] Use setjson hack again for version Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- api/server/server_unit_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index 3fc1cea064..3dbba640ff 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -70,7 +70,7 @@ func TestGetVersion(t *testing.T) { eng.Register("version", func(job *engine.Job) engine.Status { called = true v := &engine.Env{} - v.Set("Version", "42.1") + v.SetJson("Version", "42.1") v.Set("ApiVersion", "1.1.1.1.1") v.Set("GoVersion", "2.42") v.Set("Os", "Linux") From 07887f65de7f909e56bf965b3875a1dd46bd3619 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 4 Apr 2014 11:38:03 -0700 Subject: [PATCH 350/384] Revert "remove hack in version" This reverts commit b2b9334f27e1a773b77241efa214af2e87439d3b. Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.go b/server/server.go index 55ac10fd0d..9cabf17889 100644 --- a/server/server.go +++ b/server/server.go @@ -846,7 +846,7 @@ func (srv *Server) DockerInfo(job *engine.Job) engine.Status { func (srv *Server) DockerVersion(job *engine.Job) engine.Status { v := &engine.Env{} v.Set("Version", dockerversion.VERSION) - v.Set("ApiVersion", string(api.APIVERSION)) + v.SetJson("ApiVersion", api.APIVERSION) v.Set("GitCommit", dockerversion.GITCOMMIT) v.Set("GoVersion", goruntime.Version()) v.Set("Os", goruntime.GOOS) From da8aa712d28cb7177b0fe5b4cc9d7de33ea1da60 Mon Sep 17 00:00:00 2001 From: Kato Kazuyoshi Date: Sat, 5 Apr 2014 10:09:04 +0900 Subject: [PATCH 351/384] Remove archive/stat_unsupported.go because it is not used LUtimesNano and all other functions were implemented on pkg/system after d6114c0da0e844199e3d23c60a04434566fb5392. Docker-DCO-1.1-Signed-off-by: Kato Kazuyoshi (github: kzys) --- archive/stat_unsupported.go | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 archive/stat_unsupported.go diff --git a/archive/stat_unsupported.go b/archive/stat_unsupported.go deleted file mode 100644 index 004fa0f0a4..0000000000 --- a/archive/stat_unsupported.go +++ /dev/null @@ -1,21 +0,0 @@ -// +build !linux !amd64 - -package archive - -import "syscall" - -func getLastAccess(stat *syscall.Stat_t) syscall.Timespec { - return syscall.Timespec{} -} - -func getLastModification(stat *syscall.Stat_t) syscall.Timespec { - return syscall.Timespec{} -} - -func LUtimesNano(path string, ts []syscall.Timespec) error { - return ErrNotImplemented -} - -func UtimesNano(path string, ts []syscall.Timespec) error { - return ErrNotImplemented -} From 794b5de749fceea906222917e90bbc19e131ecc3 Mon Sep 17 00:00:00 2001 From: Kato Kazuyoshi Date: Sat, 5 Apr 2014 10:29:40 +0900 Subject: [PATCH 352/384] Don't assume the file system has sub-second precision timestamp For example, FreeBSD doesn't have that (see http://lists.freebsd.org/pipermail/freebsd-fs/2012-February/013677.html). Docker-DCO-1.1-Signed-off-by: Kato Kazuyoshi (github: kzys) --- archive/changes_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/archive/changes_test.go b/archive/changes_test.go index 1302b76f47..34c0f0da64 100644 --- a/archive/changes_test.go +++ b/archive/changes_test.go @@ -138,7 +138,7 @@ func mutateSampleDir(t *testing.T, root string) { } // Rewrite a file - if err := ioutil.WriteFile(path.Join(root, "file2"), []byte("fileN\n"), 0777); err != nil { + if err := ioutil.WriteFile(path.Join(root, "file2"), []byte("fileNN\n"), 0777); err != nil { t.Fatal(err) } @@ -146,12 +146,12 @@ func mutateSampleDir(t *testing.T, root string) { if err := os.RemoveAll(path.Join(root, "file3")); err != nil { t.Fatal(err) } - if err := ioutil.WriteFile(path.Join(root, "file3"), []byte("fileM\n"), 0404); err != nil { + if err := ioutil.WriteFile(path.Join(root, "file3"), []byte("fileMM\n"), 0404); err != nil { t.Fatal(err) } // Touch file - if err := os.Chtimes(path.Join(root, "file4"), time.Now(), time.Now()); err != nil { + if err := os.Chtimes(path.Join(root, "file4"), time.Now().Add(time.Second), time.Now().Add(time.Second)); err != nil { t.Fatal(err) } @@ -195,7 +195,7 @@ func mutateSampleDir(t *testing.T, root string) { } // Touch dir - if err := os.Chtimes(path.Join(root, "dir3"), time.Now(), time.Now()); err != nil { + if err := os.Chtimes(path.Join(root, "dir3"), time.Now().Add(time.Second), time.Now().Add(time.Second)); err != nil { t.Fatal(err) } } From e35c23311fce853fab318527789f11cc8c150ea2 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 7 Apr 2014 02:02:11 -0400 Subject: [PATCH 353/384] apparmor: docker-default: Include base abstraction Encountered problems on 14.04 relating to signals between container processes being blocked by apparmor. The base abstraction contains appropriate rules to allow this communication. Docker-DCO-1.1-Signed-off-by: Michael Brown (github: Supermathie) --- pkg/libcontainer/apparmor/setup.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/libcontainer/apparmor/setup.go b/pkg/libcontainer/apparmor/setup.go index 4e1c95143a..cc786de9aa 100644 --- a/pkg/libcontainer/apparmor/setup.go +++ b/pkg/libcontainer/apparmor/setup.go @@ -18,6 +18,7 @@ const DefaultProfile = ` @{PROC}=/proc/ profile docker-default flags=(attach_disconnected,mediate_deleted) { + #include network, capability, file, From 320b3e0d211d389addda02998a0f47839827b2af Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 7 Apr 2014 02:47:43 -0400 Subject: [PATCH 354/384] apparmor: abstractions/base expects pid variable Add 'pid' variable pointing to 'self' to allow parsing of profile to succeed Docker-DCO-1.1-Signed-off-by: Michael Brown (github: Supermathie) --- pkg/libcontainer/apparmor/setup.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/libcontainer/apparmor/setup.go b/pkg/libcontainer/apparmor/setup.go index cc786de9aa..d9deec470e 100644 --- a/pkg/libcontainer/apparmor/setup.go +++ b/pkg/libcontainer/apparmor/setup.go @@ -16,6 +16,7 @@ const DefaultProfile = ` #@{HOMEDIRS}+= @{multiarch}=*-linux-gnu* @{PROC}=/proc/ +@{pid}=self profile docker-default flags=(attach_disconnected,mediate_deleted) { #include From 726206f2aa45b8a537ae6d6c819f21befc2e0aca Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 7 Apr 2014 03:04:27 -0400 Subject: [PATCH 355/384] apparmor: pull in variables from tunables/global The variables that were defined at the top of the apparmor profile are best pulled in via the include. Docker-DCO-1.1-Signed-off-by: Michael Brown (github: Supermathie) --- pkg/libcontainer/apparmor/setup.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pkg/libcontainer/apparmor/setup.go b/pkg/libcontainer/apparmor/setup.go index d9deec470e..4c664598ad 100644 --- a/pkg/libcontainer/apparmor/setup.go +++ b/pkg/libcontainer/apparmor/setup.go @@ -11,13 +11,8 @@ import ( const DefaultProfilePath = "/etc/apparmor.d/docker" const DefaultProfile = ` # AppArmor profile from lxc for containers. -@{HOME}=@{HOMEDIRS}/*/ /root/ -@{HOMEDIRS}=/home/ -#@{HOMEDIRS}+= -@{multiarch}=*-linux-gnu* -@{PROC}=/proc/ -@{pid}=self +#include profile docker-default flags=(attach_disconnected,mediate_deleted) { #include network, From 87ea27e80b131ca11d74c89446d4992af0f6c5b9 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Mon, 7 Apr 2014 17:18:45 +1000 Subject: [PATCH 356/384] intermediate image layers are used for more than the build Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- api/client/commands.go | 2 +- contrib/completion/fish/docker.fish | 2 +- docs/sources/reference/commandline/cli.rst | 9 ++++++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/api/client/commands.go b/api/client/commands.go index 2007ae9c2d..bd23b3c7fd 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -1136,7 +1136,7 @@ func (cli *DockerCli) CmdPull(args ...string) error { func (cli *DockerCli) CmdImages(args ...string) error { cmd := cli.Subcmd("images", "[OPTIONS] [NAME]", "List images") quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs") - all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (by default filter out the intermediate images used to build)") + all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (by default filter out the intermediate image layers)") noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") // FIXME: --viz and --tree are deprecated. Remove them in a future version. flViz := cmd.Bool([]string{"#v", "#viz", "#-viz"}, false, "Output graph in graphviz format") diff --git a/contrib/completion/fish/docker.fish b/contrib/completion/fish/docker.fish index edaa5ca8c6..e3bb72aebe 100644 --- a/contrib/completion/fish/docker.fish +++ b/contrib/completion/fish/docker.fish @@ -107,7 +107,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from history' -a '(__fish_pr # images complete -c docker -f -n '__fish_docker_no_subcommand' -a images -d 'List images' -complete -c docker -A -f -n '__fish_seen_subcommand_from images' -s a -l all -d 'Show all images (by default filter out the intermediate images used to build)' +complete -c docker -A -f -n '__fish_seen_subcommand_from images' -s a -l all -d 'Show all images (by default filter out the intermediate image layers)' complete -c docker -A -f -n '__fish_seen_subcommand_from images' -l no-trunc -d "Don't truncate output" complete -c docker -A -f -n '__fish_seen_subcommand_from images' -s q -l quiet -d 'Only show numeric IDs' complete -c docker -A -f -n '__fish_seen_subcommand_from images' -s t -l tree -d 'Output graph in tree format' diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index da643f249a..c0487302dd 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -597,10 +597,17 @@ To see how the ``docker:latest`` image was built: List images - -a, --all=false: Show all images (by default filter out the intermediate images used to build) + -a, --all=false: Show all images (by default filter out the intermediate image layers) --no-trunc=false: Don't truncate output -q, --quiet=false: Only show numeric IDs +The default ``docker images`` will show all top level images, their repository +and tags, and their virtual size. + +Docker images have intermediate layers that increase reuseability, decrease +disk usage, and speed up ``docker build`` by allowing each step to be cached. +These intermediate layers are not shown by default. + Listing the most recently created images ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From c987aa09d81a6916e3893c41b7ec2880570b5c65 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 7 Apr 2014 11:01:35 -0700 Subject: [PATCH 357/384] Move history to separate file Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runtime/history.go | 30 ++++++++++++++++++++++++++++++ runtime/runtime.go | 27 --------------------------- 2 files changed, 30 insertions(+), 27 deletions(-) create mode 100644 runtime/history.go diff --git a/runtime/history.go b/runtime/history.go new file mode 100644 index 0000000000..835ac9c11e --- /dev/null +++ b/runtime/history.go @@ -0,0 +1,30 @@ +package runtime + +import ( + "sort" +) + +// History is a convenience type for storing a list of containers, +// ordered by creation date. +type History []*Container + +func (history *History) Len() int { + return len(*history) +} + +func (history *History) Less(i, j int) bool { + containers := *history + return containers[j].When().Before(containers[i].When()) +} + +func (history *History) Swap(i, j int) { + containers := *history + tmp := containers[i] + containers[i] = containers[j] + containers[j] = tmp +} + +func (history *History) Add(container *Container) { + *history = append(*history, container) + sort.Sort(history) +} diff --git a/runtime/runtime.go b/runtime/runtime.go index 842dbf8b0b..f4c4b09a39 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -26,7 +26,6 @@ import ( "os" "path" "regexp" - "sort" "strings" "sync" "time" @@ -62,7 +61,6 @@ type Runtime struct { // Mountpoints should be private to the container func remountPrivate(mountPoint string) error { - mounted, err := mount.Mounted(mountPoint) if err != nil { return err @@ -973,28 +971,3 @@ func (runtime *Runtime) ContainerGraph() *graphdb.Database { func (runtime *Runtime) SetServer(server Server) { runtime.srv = server } - -// History is a convenience type for storing a list of containers, -// ordered by creation date. -type History []*Container - -func (history *History) Len() int { - return len(*history) -} - -func (history *History) Less(i, j int) bool { - containers := *history - return containers[j].When().Before(containers[i].When()) -} - -func (history *History) Swap(i, j int) { - containers := *history - tmp := containers[i] - containers[i] = containers[j] - containers[j] = tmp -} - -func (history *History) Add(container *Container) { - *history = append(*history, container) - sort.Sort(history) -} From dc7fefc16bfcc4e6d0ccb30233e50b0ab3d172f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Petazzoni?= Date: Thu, 3 Apr 2014 13:50:19 -0700 Subject: [PATCH 358/384] Use https://get.docker.io/ubuntu consistently MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The install script (on https://get.docker.io/) installs an APT sources.list entry referencing an HTTPS repository, and takes care of installing the apt-transport-https package. However, the Debian/Ubuntu specific installation script (on https://get.docker.io/ubuntu) used an HTTPS repository but without installing that package, causing the installation to fail on some platforms. This will use HTTPS everywhere, and updates the documentation accordingly. Docker-DCO-1.1-Signed-off-by: Jérôme Petazzoni (github: jpetazzo) Docker-DCO-1.1-Signed-off-by: Jérôme Petazzoni (github: jpetazzo) --- docs/sources/installation/ubuntulinux.rst | 15 +++++++++++++-- hack/release.sh | 5 +++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index 44dba6b97e..51f303e88a 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -68,7 +68,18 @@ easy. **See the** :ref:`installmirrors` **section below if you are not in the United States.** Other sources of the Debian packages may be faster for you to install. -First add the Docker repository key to your local keychain. +First, check that your APT system can deal with ``https`` URLs: +the file ``/usr/lib/apt/methods/https`` should exist. If it doesn't, +you need to install the package ``apt-transport-https``. + +.. code-block:: bash + + [ -e /usr/lib/apt/methods/https ] || { + apt-get update + apt-get install apt-transport-https + } + +Then, add the Docker repository key to your local keychain. .. code-block:: bash @@ -82,7 +93,7 @@ continue installation.* .. code-block:: bash - sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\ + sudo sh -c "echo deb https://get.docker.io/ubuntu docker main\ > /etc/apt/sources.list.d/docker.list" sudo apt-get update sudo apt-get install lxc-docker diff --git a/hack/release.sh b/hack/release.sh index 6f9df8c7e6..84e1c42383 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -273,6 +273,11 @@ EOF # Upload repo s3cmd --acl-public sync $APTDIR/ s3://$BUCKET/ubuntu/ cat < /etc/apt/sources.list.d/docker.list # Then import the repository key From 1277885420b069abd7468fe3e69deb4fb0a3f4fc Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 7 Apr 2014 12:20:23 -0700 Subject: [PATCH 359/384] Clean runtime create and make it simple Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runtime/runtime.go | 156 ++++++++++++++++++++++++++++++--------------- 1 file changed, 103 insertions(+), 53 deletions(-) diff --git a/runtime/runtime.go b/runtime/runtime.go index f4c4b09a39..d35e2d653a 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -371,53 +371,86 @@ func (runtime *Runtime) restore() error { // Create creates a new container from the given configuration with a given name. func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Container, []string, error) { - // Lookup image + var ( + container *Container + warnings []string + ) + img, err := runtime.repositories.LookupImage(config.Image) if err != nil { return nil, nil, err } + if err := runtime.checkImageDepth(img); err != nil { + return nil, nil, err + } + if warnings, err = runtime.mergeAndVerifyConfig(config, img); err != nil { + return nil, nil, err + } + if container, err = runtime.newContainer(name, config, img); err != nil { + return nil, nil, err + } + if err := runtime.createRootfs(container, img); err != nil { + return nil, nil, err + } + if err := runtime.setupContainerDns(container, config); err != nil { + return nil, nil, err + } + if err := container.ToDisk(); err != nil { + return nil, nil, err + } + if err := runtime.Register(container); err != nil { + return nil, nil, err + } + return container, warnings, nil +} +func (runtime *Runtime) checkImageDepth(img *image.Image) error { // We add 2 layers to the depth because the container's rw and // init layer add to the restriction depth, err := img.Depth() if err != nil { - return nil, nil, err + return err } - if depth+2 >= MaxImageDepth { - return nil, nil, fmt.Errorf("Cannot create container with more than %d parents", MaxImageDepth) + return fmt.Errorf("Cannot create container with more than %d parents", MaxImageDepth) } + return nil +} - checkDeprecatedExpose := func(config *runconfig.Config) bool { - if config != nil { - if config.PortSpecs != nil { - for _, p := range config.PortSpecs { - if strings.Contains(p, ":") { - return true - } +func (runtime *Runtime) checkDeprecatedExpose(config *runconfig.Config) bool { + if config != nil { + if config.PortSpecs != nil { + for _, p := range config.PortSpecs { + if strings.Contains(p, ":") { + return true } } } - return false } + return false +} +func (runtime *Runtime) mergeAndVerifyConfig(config *runconfig.Config, img *image.Image) ([]string, error) { warnings := []string{} - if checkDeprecatedExpose(img.Config) || checkDeprecatedExpose(config) { + if runtime.checkDeprecatedExpose(img.Config) || runtime.checkDeprecatedExpose(config) { warnings = append(warnings, "The mapping to public ports on your host via Dockerfile EXPOSE (host:port:port) has been deprecated. Use -p to publish the ports.") } - if img.Config != nil { if err := runconfig.Merge(config, img.Config); err != nil { - return nil, nil, err + return nil, err } } - if len(config.Entrypoint) == 0 && len(config.Cmd) == 0 { - return nil, nil, fmt.Errorf("No command specified") + return nil, fmt.Errorf("No command specified") } + return warnings, nil +} - // Generate id - id := utils.GenerateRandomID() +func (runtime *Runtime) generateIdAndName(name string) (string, string, error) { + var ( + err error + id = utils.GenerateRandomID() + ) if name == "" { name, err = generateRandomName(runtime) @@ -426,47 +459,51 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe } } else { if !validContainerNamePattern.MatchString(name) { - return nil, nil, fmt.Errorf("Invalid container name (%s), only %s are allowed", name, validContainerNameChars) + return "", "", fmt.Errorf("Invalid container name (%s), only %s are allowed", name, validContainerNameChars) } } - if name[0] != '/' { name = "/" + name } - // Set the enitity in the graph using the default name specified if _, err := runtime.containerGraph.Set(name, id); err != nil { if !graphdb.IsNonUniqueNameError(err) { - return nil, nil, err + return "", "", err } conflictingContainer, err := runtime.GetByName(name) if err != nil { if strings.Contains(err.Error(), "Could not find entity") { - return nil, nil, err + return "", "", err } // Remove name and continue starting the container if err := runtime.containerGraph.Delete(name); err != nil { - return nil, nil, err + return "", "", err } } else { nameAsKnownByUser := strings.TrimPrefix(name, "/") - return nil, nil, fmt.Errorf( + return "", "", fmt.Errorf( "Conflict, The name %s is already assigned to %s. You have to delete (or rename) that container to be able to assign %s to a container again.", nameAsKnownByUser, utils.TruncateID(conflictingContainer.ID), nameAsKnownByUser) } } + return id, name, nil +} +func (runtime *Runtime) generateHostname(id string, config *runconfig.Config) { // Generate default hostname // FIXME: the lxc template no longer needs to set a default hostname if config.Hostname == "" { config.Hostname = id[:12] } +} - var args []string - var entrypoint string - +func (runtime *Runtime) getEntrypointAndArgs(config *runconfig.Config) (string, []string) { + var ( + entrypoint string + args []string + ) if len(config.Entrypoint) != 0 { entrypoint = config.Entrypoint[0] args = append(config.Entrypoint[1:], config.Cmd...) @@ -474,6 +511,21 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe entrypoint = config.Cmd[0] args = config.Cmd[1:] } + return entrypoint, args +} + +func (runtime *Runtime) newContainer(name string, config *runconfig.Config, img *image.Image) (*Container, error) { + var ( + id string + err error + ) + id, name, err = runtime.generateIdAndName(name) + if err != nil { + return nil, err + } + + runtime.generateHostname(id, config) + entrypoint, args := runtime.getEntrypointAndArgs(config) container := &Container{ // FIXME: we should generate the ID here instead of receiving it as an argument @@ -490,42 +542,50 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe ExecDriver: runtime.execDriver.Name(), } container.root = runtime.containerRoot(container.ID) + return container, nil +} + +func (runtime *Runtime) createRootfs(container *Container, img *image.Image) error { // Step 1: create the container directory. // This doubles as a barrier to avoid race conditions. if err := os.Mkdir(container.root, 0700); err != nil { - return nil, nil, err + return err } - initID := fmt.Sprintf("%s-init", container.ID) if err := runtime.driver.Create(initID, img.ID, ""); err != nil { - return nil, nil, err + return err } initPath, err := runtime.driver.Get(initID) if err != nil { - return nil, nil, err + return err } defer runtime.driver.Put(initID) if err := graph.SetupInitLayer(initPath); err != nil { - return nil, nil, err + return err } if err := runtime.driver.Create(container.ID, initID, ""); err != nil { - return nil, nil, err + return err } + return nil +} + +func (runtime *Runtime) setupContainerDns(container *Container, config *runconfig.Config) error { resolvConf, err := utils.GetResolvConf() if err != nil { - return nil, nil, err + return err } - if len(config.Dns) == 0 && len(runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) { runtime.config.Dns = DefaultDns } // If custom dns exists, then create a resolv.conf for the container if len(config.Dns) > 0 || len(runtime.config.Dns) > 0 || len(config.DnsSearch) > 0 || len(runtime.config.DnsSearch) > 0 { - dns := utils.GetNameservers(resolvConf) - dnsSearch := utils.GetSearchDomains(resolvConf) + var ( + dns = utils.GetNameservers(resolvConf) + dnsSearch = utils.GetSearchDomains(resolvConf) + ) if len(config.Dns) > 0 { dns = config.Dns } else if len(runtime.config.Dns) > 0 { @@ -539,33 +599,23 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe container.ResolvConfPath = path.Join(container.root, "resolv.conf") f, err := os.Create(container.ResolvConfPath) if err != nil { - return nil, nil, err + return err } defer f.Close() for _, dns := range dns { if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil { - return nil, nil, err + return err } } if len(dnsSearch) > 0 { if _, err := f.Write([]byte("search " + strings.Join(dnsSearch, " ") + "\n")); err != nil { - return nil, nil, err + return err } } } else { container.ResolvConfPath = "/etc/resolv.conf" } - - // Step 2: save the container json - if err := container.ToDisk(); err != nil { - return nil, nil, err - } - - // Step 3: register the container - if err := runtime.Register(container); err != nil { - return nil, nil, err - } - return container, warnings, nil + return nil } // Commit creates a new filesystem image from the current state of a container. From 30f22ee9e3ea1012ca663a0383c8c9c2330c52cc Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 4 Apr 2014 00:57:41 +0000 Subject: [PATCH 360/384] Convert a legacy integration test to a clean v2 CLI integration test. Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- integration-cli/docker_cli_images_test.go | 38 +++++++++++++++ integration-cli/docker_utils.go | 7 +++ integration-cli/utils.go | 4 ++ integration/server_test.go | 59 ----------------------- 4 files changed, 49 insertions(+), 59 deletions(-) diff --git a/integration-cli/docker_cli_images_test.go b/integration-cli/docker_cli_images_test.go index 17efc6f5c4..82b70bab40 100644 --- a/integration-cli/docker_cli_images_test.go +++ b/integration-cli/docker_cli_images_test.go @@ -18,3 +18,41 @@ func TestImagesEnsureImageIsListed(t *testing.T) { logDone("images - busybox should be listed") } + +func TestCLIImageTagRemove(t *testing.T) { + imagesBefore, _, _ := cmd(t, "images", "-a") + cmd(t, "tag", "busybox", "utest:tag1") + cmd(t, "tag", "busybox", "utest/docker:tag2") + cmd(t, "tag", "busybox", "utest:5000/docker:tag3") + { + imagesAfter, _, _ := cmd(t, "images", "-a") + if nLines(imagesAfter) != nLines(imagesBefore)+3 { + t.Fatalf("before: %#s\n\nafter: %#s\n", imagesBefore, imagesAfter) + } + } + cmd(t, "rmi", "utest/docker:tag2") + { + imagesAfter, _, _ := cmd(t, "images", "-a") + if nLines(imagesAfter) != nLines(imagesBefore)+2 { + t.Fatalf("before: %#s\n\nafter: %#s\n", imagesBefore, imagesAfter) + } + + } + cmd(t, "rmi", "utest:5000/docker:tag3") + { + imagesAfter, _, _ := cmd(t, "images", "-a") + if nLines(imagesAfter) != nLines(imagesBefore)+1 { + t.Fatalf("before: %#s\n\nafter: %#s\n", imagesBefore, imagesAfter) + } + + } + cmd(t, "rmi", "utest:tag1") + { + imagesAfter, _, _ := cmd(t, "images", "-a") + if nLines(imagesAfter) != nLines(imagesBefore)+0 { + t.Fatalf("before: %#s\n\nafter: %#s\n", imagesBefore, imagesAfter) + } + + } + logDone("tag,rmi- tagging the same images multiple times then removing tags") +} diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 8e9d0a23ff..6da86c9753 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -4,6 +4,7 @@ import ( "fmt" "os/exec" "strings" + "testing" ) func deleteContainer(container string) error { @@ -54,3 +55,9 @@ func deleteImages(images string) error { return err } + +func cmd(t *testing.T, args ...string) (string, int, error) { + out, status, err := runCommandWithOutput(exec.Command(dockerBinary, args...)) + errorOut(err, t, fmt.Sprintf("'%s' failed with errors: %v (%v)", strings.Join(args, " "), err, out)) + return out, status, err +} diff --git a/integration-cli/utils.go b/integration-cli/utils.go index 680cc6cfcf..ae7af52687 100644 --- a/integration-cli/utils.go +++ b/integration-cli/utils.go @@ -107,3 +107,7 @@ func errorOutOnNonNilError(err error, t *testing.T, message string) { t.Fatalf(message) } } + +func nLines(s string) int { + return strings.Count(s, "\n") +} diff --git a/integration/server_test.go b/integration/server_test.go index 4ad5ec0f92..9137e8031b 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -9,65 +9,6 @@ import ( "time" ) -func TestImageTagImageDelete(t *testing.T) { - eng := NewTestEngine(t) - defer mkRuntimeFromEngine(eng, t).Nuke() - - srv := mkServerFromEngine(eng, t) - - initialImages := getAllImages(eng, t) - if err := eng.Job("tag", unitTestImageName, "utest", "tag1").Run(); err != nil { - t.Fatal(err) - } - - if err := eng.Job("tag", unitTestImageName, "utest/docker", "tag2").Run(); err != nil { - t.Fatal(err) - } - - if err := eng.Job("tag", unitTestImageName, "utest:5000/docker", "tag3").Run(); err != nil { - t.Fatal(err) - } - - images := getAllImages(eng, t) - - nExpected := len(initialImages.Data[0].GetList("RepoTags")) + 3 - nActual := len(images.Data[0].GetList("RepoTags")) - if nExpected != nActual { - t.Errorf("Expected %d images, %d found", nExpected, nActual) - } - - if err := srv.DeleteImage("utest/docker:tag2", engine.NewTable("", 0), true, false, false); err != nil { - t.Fatal(err) - } - - images = getAllImages(eng, t) - - nExpected = len(initialImages.Data[0].GetList("RepoTags")) + 2 - nActual = len(images.Data[0].GetList("RepoTags")) - if nExpected != nActual { - t.Errorf("Expected %d images, %d found", nExpected, nActual) - } - - if err := srv.DeleteImage("utest:5000/docker:tag3", engine.NewTable("", 0), true, false, false); err != nil { - t.Fatal(err) - } - - images = getAllImages(eng, t) - - nExpected = len(initialImages.Data[0].GetList("RepoTags")) + 1 - nActual = len(images.Data[0].GetList("RepoTags")) - - if err := srv.DeleteImage("utest:tag1", engine.NewTable("", 0), true, false, false); err != nil { - t.Fatal(err) - } - - images = getAllImages(eng, t) - - if images.Len() != initialImages.Len() { - t.Errorf("Expected %d image, %d found", initialImages.Len(), images.Len()) - } -} - func TestCreateRm(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() From ffebcb660f666e3a2a7be6b838ebd55f524d5b5d Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 7 Apr 2014 12:40:41 -0700 Subject: [PATCH 361/384] Move -o cli flag and DriverConfig from HostConfig Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runconfig/hostconfig.go | 2 -- runconfig/parse.go | 10 +--------- runtime/container.go | 6 +----- 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 9a92258644..55a308a5b8 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -14,7 +14,6 @@ type HostConfig struct { PortBindings nat.PortMap Links []string PublishAllPorts bool - DriverOptions map[string][]string } func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { @@ -25,7 +24,6 @@ func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { } job.GetenvJson("LxcConf", &hostConfig.LxcConf) job.GetenvJson("PortBindings", &hostConfig.PortBindings) - job.GetenvJson("DriverOptions", &hostConfig.DriverOptions) if Binds := job.GetenvList("Binds"); Binds != nil { hostConfig.Binds = Binds } diff --git a/runconfig/parse.go b/runconfig/parse.go index c93ec26ed1..3ca326fca6 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -45,7 +45,6 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf flDnsSearch = opts.NewListOpts(opts.ValidateDomain) flVolumesFrom opts.ListOpts flLxcOpts opts.ListOpts - flDriverOpts opts.ListOpts flEnvFile opts.ListOpts flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)") @@ -79,8 +78,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"}, "(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") + cmd.Var(&flLxcOpts, []string{"#lxc-conf", "-lxc-conf"}, "(lxc exec-driver only) Add custom lxc options --lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"") if err := cmd.Parse(args); err != nil { return nil, nil, cmd, err @@ -224,11 +222,6 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf WorkingDir: *flWorkingDir, } - driverOptions, err := parseDriverOpts(flDriverOpts) - if err != nil { - return nil, nil, cmd, err - } - hostConfig := &HostConfig{ Binds: binds, ContainerIDFile: *flContainerIDFile, @@ -237,7 +230,6 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf PortBindings: portBindings, Links: flLinks.GetAll(), PublishAllPorts: *flPublishAll, - DriverOptions: driverOptions, } if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit { diff --git a/runtime/container.go b/runtime/container.go index bd4a6f2bea..a5a2f25c64 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -361,12 +361,8 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s func populateCommand(c *Container) { var ( en *execdriver.Network - driverConfig = c.hostConfig.DriverOptions - ) - - if driverConfig == nil { driverConfig = make(map[string][]string) - } + ) en = &execdriver.Network{ Mtu: c.runtime.config.Mtu, From b1e98e06dc62b0d25f98ea9a2fd94e41cc1d20e2 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 7 Apr 2014 13:29:24 -0700 Subject: [PATCH 362/384] Remove selinux build tag Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2de5b34171..42438e3946 100644 --- a/Dockerfile +++ b/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 selinux +ENV DOCKER_BUILDTAGS apparmor # Wrap all commands in the "docker-in-docker" script to allow nested containers ENTRYPOINT ["hack/dind"] From aaf018017c88a707b35115a9411e4069d9356748 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 7 Apr 2014 14:09:46 -0700 Subject: [PATCH 363/384] Add more label checks for selinux enabled Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- Dockerfile | 2 +- pkg/label/label_selinux.go | 36 ++++++++++++++++++++---------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/Dockerfile b/Dockerfile index 42438e3946..2de5b34171 100644 --- a/Dockerfile +++ b/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"] diff --git a/pkg/label/label_selinux.go b/pkg/label/label_selinux.go index d807b2b408..9f7463f79b 100644 --- a/pkg/label/label_selinux.go +++ b/pkg/label/label_selinux.go @@ -9,30 +9,31 @@ import ( ) func GenLabels(options string) (string, string, error) { - processLabel, mountLabel := selinux.GetLxcContexts() - if processLabel == "" { // SELinux is disabled + if !selinux.SelinuxEnabled() { return "", "", nil } - - var ( - err error - 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] + var err error + processLabel, mountLabel := selinux.GetLxcContexts() + if processLabel != "" { + var ( + 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) } - processLabel = pcon.Get() - mountLabel, err = selinux.CopyLevel(processLabel, mountLabel) } return processLabel, mountLabel, err } func FormatMountLabel(src string, mountLabel string) string { - if mountLabel != "" { + if selinux.SelinuxEnabled() && mountLabel != "" { switch src { case "": src = fmt.Sprintf("%s,context=%s", src, mountLabel) @@ -65,6 +66,9 @@ func SetFileLabel(path string, fileLabel string) error { } func GetPidCon(pid int) (string, error) { + if !selinux.SelinuxEnabled() { + return "", nil + } return selinux.Getpidcon(pid) } From 82f37b874ea17c5e0040f3e41dc761c88d576e33 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 7 Apr 2014 14:43:50 -0700 Subject: [PATCH 364/384] Ensure that selinux is disabled by default This also includes some portability changes so that the package can be imported with the top level runtime. Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemonconfig/config.go | 2 ++ pkg/selinux/selinux.go | 16 ++++++---------- pkg/selinux/selinux_test.go | 5 +---- pkg/system/calls_linux.go | 4 ++++ pkg/system/unsupported.go | 4 ++++ runtime/runtime.go | 4 ++++ 6 files changed, 21 insertions(+), 14 deletions(-) diff --git a/daemonconfig/config.go b/daemonconfig/config.go index 1abb6f8b89..146916d79a 100644 --- a/daemonconfig/config.go +++ b/daemonconfig/config.go @@ -28,6 +28,7 @@ type Config struct { ExecDriver string Mtu int DisableNetwork bool + EnableSelinuxSupport bool } // ConfigFromJob creates and returns a new DaemonConfig object @@ -45,6 +46,7 @@ func ConfigFromJob(job *engine.Job) *Config { InterContainerCommunication: job.GetenvBool("InterContainerCommunication"), GraphDriver: job.Getenv("GraphDriver"), ExecDriver: job.Getenv("ExecDriver"), + EnableSelinuxSupport: false, // FIXME: hardcoded default to disable selinux for .10 release } if dns := job.GetenvList("Dns"); dns != nil { config.Dns = dns diff --git a/pkg/selinux/selinux.go b/pkg/selinux/selinux.go index 5362308617..d2d90b1b37 100644 --- a/pkg/selinux/selinux.go +++ b/pkg/selinux/selinux.go @@ -39,6 +39,11 @@ var ( type SELinuxContext map[string]string +// SetDisabled disables selinux support for the package +func SetDisabled() { + selinuxEnabled, selinuxEnabledChecked = false, true +} + func GetSelinuxMountPoint() string { if selinuxfs != "unknown" { return selinuxfs @@ -140,15 +145,6 @@ 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) } @@ -188,7 +184,7 @@ func writeCon(name string, val string) error { } func Setexeccon(scon string) error { - return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid()), scon) + return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", system.Gettid()), scon) } func (c SELinuxContext) Get() string { diff --git a/pkg/selinux/selinux_test.go b/pkg/selinux/selinux_test.go index 6b59c1db11..181452ae75 100644 --- a/pkg/selinux/selinux_test.go +++ b/pkg/selinux/selinux_test.go @@ -12,9 +12,7 @@ func testSetfilecon(t *testing.T) { 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 { + if err != nil { t.Log("Setfilecon failed") t.Fatal(err) } @@ -41,7 +39,6 @@ func TestSELinux(t *testing.T) { 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()) diff --git a/pkg/system/calls_linux.go b/pkg/system/calls_linux.go index 43c00ed554..cc4727aaa2 100644 --- a/pkg/system/calls_linux.go +++ b/pkg/system/calls_linux.go @@ -143,3 +143,7 @@ func SetCloneFlags(cmd *exec.Cmd, flag uintptr) { } cmd.SysProcAttr.Cloneflags = flag } + +func Gettid() int { + return syscall.Gettid() +} diff --git a/pkg/system/unsupported.go b/pkg/system/unsupported.go index eb3ec7ee92..c52a1e5d00 100644 --- a/pkg/system/unsupported.go +++ b/pkg/system/unsupported.go @@ -13,3 +13,7 @@ func SetCloneFlags(cmd *exec.Cmd, flag uintptr) { func UsetCloseOnExec(fd uintptr) error { return ErrNotSupportedPlatform } + +func Gettid() int { + return 0 +} diff --git a/runtime/runtime.go b/runtime/runtime.go index d35e2d653a..864874c8e4 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -11,6 +11,7 @@ import ( "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/pkg/graphdb" "github.com/dotcloud/docker/pkg/mount" + "github.com/dotcloud/docker/pkg/selinux" "github.com/dotcloud/docker/pkg/sysinfo" "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/runtime/execdriver" @@ -723,6 +724,9 @@ func NewRuntime(config *daemonconfig.Config, eng *engine.Engine) (*Runtime, erro } func NewRuntimeFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (*Runtime, error) { + if !config.EnableSelinuxSupport { + selinux.SetDisabled() + } // Set the default driver graphdriver.DefaultDriver = config.GraphDriver From 028d44d12683b170704537c3435361ae8a4e74d8 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 7 Apr 2014 14:59:44 -0700 Subject: [PATCH 365/384] Remove and unexport selinux functions Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/selinux/selinux.go | 25 +++++++++---------------- pkg/selinux/selinux_test.go | 2 -- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/pkg/selinux/selinux.go b/pkg/selinux/selinux.go index d2d90b1b37..edabc4f7dd 100644 --- a/pkg/selinux/selinux.go +++ b/pkg/selinux/selinux.go @@ -44,7 +44,7 @@ func SetDisabled() { selinuxEnabled, selinuxEnabledChecked = false, true } -func GetSelinuxMountPoint() string { +func getSelinuxMountPoint() string { if selinuxfs != "unknown" { return selinuxfs } @@ -75,15 +75,15 @@ func SelinuxEnabled() bool { return selinuxEnabled } selinuxEnabledChecked = true - if fs := GetSelinuxMountPoint(); fs != "" { - if con, _ := Getcon(); con != "kernel" { + if fs := getSelinuxMountPoint(); fs != "" { + if con, _ := getcon(); con != "kernel" { selinuxEnabled = true } } return selinuxEnabled } -func ReadConfig(target string) (value string) { +func readConfig(target string) (value string) { var ( val, key string bufin *bufio.Reader @@ -124,8 +124,8 @@ func ReadConfig(target string) (value string) { return "" } -func GetSELinuxPolicyRoot() string { - return selinuxDir + ReadConfig(selinuxTypeTag) +func getSELinuxPolicyRoot() string { + return selinuxDir + readConfig(selinuxTypeTag) } func readCon(name string) (string, error) { @@ -153,7 +153,7 @@ func Getfscreatecon() (string, error) { return readCon("/proc/self/attr/fscreate") } -func Getcon() (string, error) { +func getcon() (string, error) { return readCon("/proc/self/attr/current") } @@ -220,7 +220,7 @@ func SelinuxGetEnforce() int { } func SelinuxGetEnforceMode() int { - switch ReadConfig(selinuxTag) { + switch readConfig(selinuxTag) { case "enforcing": return Enforcing case "permissive": @@ -292,13 +292,6 @@ func uniqMcs(catRange uint32) string { return mcs } -func FreeContext(con string) { - if con != "" { - scon := NewContext(con) - mcsDelete(scon["level"]) - } -} - func GetLxcContexts() (processLabel string, fileLabel string) { var ( val, key string @@ -308,7 +301,7 @@ func GetLxcContexts() (processLabel string, fileLabel string) { if !SelinuxEnabled() { return "", "" } - lxcPath := fmt.Sprintf("%s/contexts/lxc_contexts", GetSELinuxPolicyRoot()) + lxcPath := fmt.Sprintf("%s/contexts/lxc_contexts", getSELinuxPolicyRoot()) in, err := os.Open(lxcPath) if err != nil { return "", "" diff --git a/pkg/selinux/selinux_test.go b/pkg/selinux/selinux_test.go index 181452ae75..fde6ab147d 100644 --- a/pkg/selinux/selinux_test.go +++ b/pkg/selinux/selinux_test.go @@ -38,7 +38,6 @@ func TestSELinux(t *testing.T) { t.Log("getenforcemode ", selinux.SelinuxGetEnforceMode()) pid := os.Getpid() t.Log("PID:%d MCS:%s\n", pid, selinux.IntToMcs(pid, 1023)) - t.Log(selinux.Getcon()) err = selinux.Setfscreatecon("unconfined_u:unconfined_r:unconfined_t:s0") if err == nil { t.Log(selinux.Getfscreatecon()) @@ -54,7 +53,6 @@ func TestSELinux(t *testing.T) { t.Fatal(err) } t.Log(selinux.Getpidcon(1)) - t.Log(selinux.GetSelinuxMountPoint()) } else { t.Log("Disabled") } From da8231a26bc4532ded49f93a82e731694ee6587c Mon Sep 17 00:00:00 2001 From: William Henry Date: Fri, 4 Apr 2014 11:57:58 -0600 Subject: [PATCH 366/384] Added man pages for several docker commands. Docker-DCO-1.1-Signed-off-by: William Henry (github: ipbabble) new file: contrib/man/man1/docker-attach.1 new file: contrib/man/man1/docker-build.1 new file: contrib/man/man1/docker-images.1 new file: contrib/man/man1/docker-info.1 new file: contrib/man/man1/docker-inspect.1 new file: contrib/man/man1/docker-rm.1 new file: contrib/man/man1/docker-rmi.1 new file: contrib/man/man1/docker-run.1 new file: contrib/man/man1/docker-tag.1 new file: contrib/man/man1/docker.1 --- contrib/man/man1/docker-attach.1 | 56 ++++++ contrib/man/man1/docker-build.1 | 65 +++++++ contrib/man/man1/docker-images.1 | 84 +++++++++ contrib/man/man1/docker-info.1 | 39 +++++ contrib/man/man1/docker-inspect.1 | 237 +++++++++++++++++++++++++ contrib/man/man1/docker-rm.1 | 45 +++++ contrib/man/man1/docker-rmi.1 | 29 ++++ contrib/man/man1/docker-run.1 | 277 ++++++++++++++++++++++++++++++ contrib/man/man1/docker-tag.1 | 49 ++++++ contrib/man/man1/docker.1 | 172 +++++++++++++++++++ 10 files changed, 1053 insertions(+) create mode 100644 contrib/man/man1/docker-attach.1 create mode 100644 contrib/man/man1/docker-build.1 create mode 100644 contrib/man/man1/docker-images.1 create mode 100644 contrib/man/man1/docker-info.1 create mode 100644 contrib/man/man1/docker-inspect.1 create mode 100644 contrib/man/man1/docker-rm.1 create mode 100644 contrib/man/man1/docker-rmi.1 create mode 100644 contrib/man/man1/docker-run.1 create mode 100644 contrib/man/man1/docker-tag.1 create mode 100644 contrib/man/man1/docker.1 diff --git a/contrib/man/man1/docker-attach.1 b/contrib/man/man1/docker-attach.1 new file mode 100644 index 0000000000..f0879d7507 --- /dev/null +++ b/contrib/man/man1/docker-attach.1 @@ -0,0 +1,56 @@ +.\" Process this file with +.\" nroff -man -Tascii docker-attach.1 +.\" +.TH "DOCKER" "1" "APRIL 2014" "0.1" "Docker" +.SH NAME +docker-attach \- Attach to a running container +.SH SYNOPSIS +.B docker attach +\fB--no-stdin\fR[=\fIfalse\fR] +\fB--sig-proxy\fR[=\fItrue\fR] +container +.SH DESCRIPTION +If you \fBdocker run\fR a container in detached mode (\fB-d\fR), you can reattach to the detached container with \fBdocker attach\fR using the container's ID or name. +.sp +You can detach from the container again (and leave it running) with CTRL-c (for a quiet exit) or CTRL-\ to get a stacktrace of the Docker client when it quits. When you detach from the container the exit code will be returned to the client. +.SH "OPTIONS" +.TP +.B --no-stdin=\fItrue\fR|\fIfalse\fR: +When set to true, do not attach to stdin. The default is \fIfalse\fR. +.TP +.B --sig-proxy=\fItrue\fR|\fIfalse\fR: +When set to true, proxify all received signal to the process (even in non-tty mode). The default is \fItrue\fR. +.sp +.SH EXAMPLES +.sp +.PP +.B Attaching to a container +.TP +In this example the top command is run inside a container, from an image called fedora, in detached mode. The ID from the container is passed into the \fBdocker attach\fR command: +.sp +.nf +.RS +# ID=$(sudo docker run -d fedora /usr/bin/top -b) +# sudo docker attach $ID +top - 02:05:52 up 3:05, 0 users, load average: 0.01, 0.02, 0.05 +Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie +Cpu(s): 0.1%us, 0.2%sy, 0.0%ni, 99.7%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st +Mem: 373572k total, 355560k used, 18012k free, 27872k buffers +Swap: 786428k total, 0k used, 786428k free, 221740k cached + +PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND +1 root 20 0 17200 1116 912 R 0 0.3 0:00.03 top + +top - 02:05:55 up 3:05, 0 users, load average: 0.01, 0.02, 0.05 +Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie +Cpu(s): 0.0%us, 0.2%sy, 0.0%ni, 99.8%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st +Mem: 373572k total, 355244k used, 18328k free, 27872k buffers +Swap: 786428k total, 0k used, 786428k free, 221776k cached + +PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND +1 root 20 0 17208 1144 932 R 0 0.3 0:00.03 top +.RE +.fi +.sp +.SH HISTORY +April 2014, Originally compiled by William Henry (whenry at redhat dot com) based on dockier.io source material and internal work. diff --git a/contrib/man/man1/docker-build.1 b/contrib/man/man1/docker-build.1 new file mode 100644 index 0000000000..6546b7be2a --- /dev/null +++ b/contrib/man/man1/docker-build.1 @@ -0,0 +1,65 @@ +.\" Process this file with +.\" nroff -man -Tascii docker-build.1 +.\" +.TH "DOCKER" "1" "MARCH 2014" "0.1" "Docker" +.SH NAME +docker-build \- Build a container image from a Dockerfile source at PATH +.SH SYNOPSIS +.B docker build +[\fB--no-cache\fR[=\fIfalse\fR] +[\fB-q\fR|\fB--quiet\fR[=\fIfalse\fR] +[\fB--rm\fR[=\fitrue\fR]] +[\fB-t\fR|\fB--tag\fR=\fItag\fR] +PATH | URL | - +.SH DESCRIPTION +This will read the Dockerfile from the directory specified in \fBPATH\fR. It also sends any other files and directories found in the current directory to the Docker daemon. The contents of this directory would be used by ADD command found within the Dockerfile. +Warning, this will send a lot of data to the Docker daemon if the current directory contains a lot of data. +If the absolute path is provided instead of ‘.’, only the files and directories required by the ADD commands from the Dockerfile will be added to the context and transferred to the Docker daemon. +.sp +When a single Dockerfile is given as URL, then no context is set. When a Git repository is set as URL, the repository is used as context. +.SH "OPTIONS" +.TP +.B -q, --quiet=\fItrue\fR|\fIfalse\fR: +When set to true, suppress verbose build output. Default is \fIfalse\fR. +.TP +.B --rm=\fItrue\fr|\fIfalse\fR: +When true, remove intermediate containers that are created during the build process. The default is true. +.TP +.B -t, --tag=\fItag\fR: +Tag to be applied to the resulting image on successful completion of the build. +.TP +.B --no-cache=\fItrue\fR|\fIfalse\fR +When set to true, do not use a cache when building the image. The default is \fIfalse\fR. +.sp +.SH EXAMPLES +.sp +.sp +.B Building an image from current directory +.TP +USing a Dockerfile, Docker images are built using the build command: +.sp +.RS +docker build . +.RE +.sp +If, for some reasone, you do not what to remove the intermediate containers created during the build you must set--rm=false. +.sp +.RS +docker build --rm=false . +.sp +.RE +.sp +A good practice is to make a subdirectory with a related name and create the Dockerfile in that directory. E.g. a directory called mongo may contain a Dockerfile for a MongoDB image, or a directory called httpd may contain an Dockerfile for an Apache web server. +.sp +It is also good practice to add the files required for the image to the subdirectory. These files will be then specified with the `ADD` instruction in the Dockerfile. Note: if you include a tar file, which is good practice, then Docker will automatically extract the contents of the tar file specified in the `ADD` instruction into the specified target. +.sp +.B Building an image container using a URL +.TP +This will clone the Github repository and use it as context. The Dockerfile at the root of the repository is used as Dockerfile. This only works if the Github repository is a dedicated repository. Note that you can specify an arbitrary Git repository by using the ‘git://’ schema. +.sp +.RS +docker build github.com/scollier/Fedora-Dockerfiles/tree/master/apache +.RE +.sp +.SH HISTORY +March 2014, Originally compiled by William Henry (whenry at redhat dot com) based on dockier.io source material and internal work. diff --git a/contrib/man/man1/docker-images.1 b/contrib/man/man1/docker-images.1 new file mode 100644 index 0000000000..e540ba2b79 --- /dev/null +++ b/contrib/man/man1/docker-images.1 @@ -0,0 +1,84 @@ +.\" Process this file with +.\" nroff -man -Tascii docker-images.1 +.\" +.TH "DOCKER" "1" "April 2014" "0.1" "Docker" +.SH NAME +docker-images \- List the images in the local repository +.SH SYNOPSIS +.B docker images +[\fB-a\fR|\fB--all\fR=\fIfalse\fR] +[\fB--no-trunc\fR[=\fIfalse\fR] +[\fB-q\fR|\fB--quiet\fR[=\fIfalse\fR] +[\fB-t\fR|\fB--tree\fR=\fIfalse\fR] +[\fB-v\fR|\fB--viz\fR=\fIfalse\fR] +[NAME] +.SH DESCRIPTION +This command lists the images stored in the local Docker repository. +.sp +By default, intermediate images, used during builds, are not listed. Some of the output, e.g. image ID, is truncated, for space reasons. However the truncated image ID, and often the first few characters, are enough to be used in other Docker commands that use the image ID. The output includes repository, tag, image ID, date created and the virtual size. +.sp +The title REPOSITORY for the first title may seem confusing. It is essentially the image name. However, because you can tag a specific image, and multiple tags (image instances) can be associated with a single name, the name is really a repository for all tagged images of the same name. +.SH "OPTIONS" +.TP +.B -a, --all=\fItrue\fR|\fIfalse\fR: +When set to true, also include all intermediate images in the list. The default is false. +.TP +.B --no-trunc=\fItrue\fR|\fIfalse\fR: +When set to true, list the full image ID and not the truncated ID. The default is false. +.TP +.B -q, --quiet=\fItrue\fR|\fIfalse\fR: +When set to true, list the complete image ID as part of the output. The default is false. +.TP +.B -t, --tree=\fItrue\fR|\fIfalse\fR: +When set to true, list the images in a tree dependency tree (hierarchy) format. The default is false. +.TP +.B -v, --viz=\fItrue\fR|\fIfalse\fR +When set to true, list the graph in graphviz format. The default is \fIfalse\fR. +.sp +.SH EXAMPLES +.sp +.B Listing the images +.TP +To list the images in a local repository (not the registry) run: +.sp +.RS +docker images +.RE +.sp +The list will contain the image repository name, a tag for the image, and an image ID, when it was created and its virtual size. Columns: REPOSITORY, TAG, IMAGE ID, CREATED, and VIRTUAL SIZE. +.sp +To get a verbose list of images which contains all the intermediate images used in builds use \fB-a\fR: +.sp +.RS +docker images -a +.RE +.sp +.B List images dependency tree hierarchy +.TP +To list the images in the local repository (not the registry) in a dependency tree format then use the \fB-t\fR|\fB--tree=true\fR option. +.sp +.RS +docker images -t +.RE +.sp +This displays a staggered hierarchy tree where the less indented image is the oldest with dependent image layers branching inward (to the right) on subsequent lines. The newest or top level image layer is listed last in any tree branch. +.sp +.B List images in GraphViz format +.TP +To display the list in a format consumable by a GraphViz tools run with \fB-v\fR|\fB--viz=true\fR. For example to produce a .png graph file of the hierarchy use: +.sp +.RS +docker images --viz | dot -Tpng -o docker.png +.sp +.RE +.sp +.B Listing only the shortened image IDs +.TP +Listing just the shortened image IDs. This can be useful for some automated tools. +.sp +.RS +docker images -q +.RE +.sp +.SH HISTORY +April 2014, Originally compiled by William Henry (whenry at redhat dot com) based on dockier.io source material and internal work. diff --git a/contrib/man/man1/docker-info.1 b/contrib/man/man1/docker-info.1 new file mode 100644 index 0000000000..dca2600af0 --- /dev/null +++ b/contrib/man/man1/docker-info.1 @@ -0,0 +1,39 @@ +.\" Process this file with +.\" nroff -man -Tascii docker-info.1 +.\" +.TH "DOCKER" "1" "APRIL 2014" "0.1" "Docker" +.SH NAME +docker-info \- Display system wide information +.SH SYNOPSIS +.B docker info +.SH DESCRIPTION +This command displays system wide information regarding the Docker installation. Information displayed includes the number of containers and images, pool name, data file, metadata file, data space used, total data space, metadata space used, total metadata space, execution driver, and the kernel version. +.sp +The data file is where the images are stored and the metadata file is where the meta data regarding those images are stored. When run for the first time Docker allocates a certain amount of data space and meta data space from the space available on the volume where /var/lib/docker is mounted. +.SH "OPTIONS" +There are no available options. +.sp +.SH EXAMPLES +.sp +.B Display Docker system information +.TP +Here is a sample output: +.sp +.RS + # docker info + Containers: 18 + Images: 95 + Storage Driver: devicemapper + Pool Name: docker-8:1-170408448-pool + Data file: /var/lib/docker/devicemapper/devicemapper/data + Metadata file: /var/lib/docker/devicemapper/devicemapper/metadata + Data Space Used: 9946.3 Mb + Data Space Total: 102400.0 Mb + Metadata Space Used: 9.9 Mb + Metadata Space Total: 2048.0 Mb + Execution Driver: native-0.1 + Kernel Version: 3.10.0-116.el7.x86_64 +.RE +.sp +.SH HISTORY +April 2014, Originally compiled by William Henry (whenry at redhat dot com) based on dockier.io source material and internal work. diff --git a/contrib/man/man1/docker-inspect.1 b/contrib/man/man1/docker-inspect.1 new file mode 100644 index 0000000000..225125e564 --- /dev/null +++ b/contrib/man/man1/docker-inspect.1 @@ -0,0 +1,237 @@ +.\" Process this file with +.\" nroff -man -Tascii docker-inspect.1 +.\" +.TH "DOCKER" "1" "APRIL 2014" "0.1" "Docker" +.SH NAME +docker-inspect \- Return low-level information on a container/image +.SH SYNOPSIS +.B docker inspect +[\fB-f\fR|\fB--format\fR="" +CONTAINER|IMAGE [CONTAINER|IMAGE...] +.SH DESCRIPTION +This displays all the information available in Docker for a given container or image. By default, this will render all results in a JSON array. If a format is specified, the given template will be executed for each result. +.SH "OPTIONS" +.TP +.B -f, --format="": +The text/template package of Go describes all the details of the format. See examples section +.SH EXAMPLES +.sp +.PP +.B Getting information on a container +.TP +To get information on a container use it's ID or instance name +.sp +.fi +.RS +#docker inspect 1eb5fabf5a03 + +[{ + "ID": "1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b", + "Created": "2014-04-04T21:33:52.02361335Z", + "Path": "/usr/sbin/nginx", + "Args": [], + "Config": { + "Hostname": "1eb5fabf5a03", + "Domainname": "", + "User": "", + "Memory": 0, + "MemorySwap": 0, + "CpuShares": 0, + "AttachStdin": false, + "AttachStdout": false, + "AttachStderr": false, + "PortSpecs": null, + "ExposedPorts": { + "80/tcp": {} + }, + "Tty": true, + "OpenStdin": false, + "StdinOnce": false, + "Env": [ + "HOME=/", + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Cmd": [ + "/usr/sbin/nginx" + ], + "Dns": null, + "DnsSearch": null, + "Image": "summit/nginx", + "Volumes": null, + "VolumesFrom": "", + "WorkingDir": "", + "Entrypoint": null, + "NetworkDisabled": false, + "OnBuild": null, + "Context": { + "mount_label": "system_u:object_r:svirt_sandbox_file_t:s0:c0,c650", + "process_label": "system_u:system_r:svirt_lxc_net_t:s0:c0,c650" + } + }, + "State": { + "Running": true, + "Pid": 858, + "ExitCode": 0, + "StartedAt": "2014-04-04T21:33:54.16259207Z", + "FinishedAt": "0001-01-01T00:00:00Z", + "Ghost": false + }, + "Image": "df53773a4390e25936f9fd3739e0c0e60a62d024ea7b669282b27e65ae8458e6", + "NetworkSettings": { + "IPAddress": "172.17.0.2", + "IPPrefixLen": 16, + "Gateway": "172.17.42.1", + "Bridge": "docker0", + "PortMapping": null, + "Ports": { + "80/tcp": [ + { + "HostIp": "0.0.0.0", + "HostPort": "80" + } + ] + } + }, + "ResolvConfPath": "/etc/resolv.conf", + "HostnamePath": "/var/lib/docker/containers/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b/hostname", + "HostsPath": "/var/lib/docker/containers/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b/hosts", + "Name": "/ecstatic_ptolemy", + "Driver": "devicemapper", + "ExecDriver": "native-0.1", + "Volumes": {}, + "VolumesRW": {}, + "HostConfig": { + "Binds": null, + "ContainerIDFile": "", + "LxcConf": [], + "Privileged": false, + "PortBindings": { + "80/tcp": [ + { + "HostIp": "0.0.0.0", + "HostPort": "80" + } + ] + }, + "Links": null, + "PublishAllPorts": false, + "DriverOptions": { + "lxc": null + }, + "CliAddress": "" + } +.RE +.nf +.sp +.B Getting the IP address of a container instance +.TP +To get the IP address of a container use: +.sp +.fi +.RS +# docker inspect --format='{{.NetworkSettings.IPAddress}}' 1eb5fabf5a03 + +172.17.0.2 +.RE +.nf +.sp +.B Listing all port bindings +.TP +One can loop over arrays and maps in the results to produce simple text output: +.sp +.fi +.RS +# docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}} {{$p}} -> {{(index $conf 0).HostPort}} {{end}}' 1eb5fabf5a03 + +80/tcp -> 80 +.RE +.nf +.sp +.B Getting information on an image +.TP +Use an image's ID or name (e.g. repository/name[:tag]) to get information on it. +.sp +.fi +.RS +docker inspect 58394af37342 +[{ + "id": "58394af373423902a1b97f209a31e3777932d9321ef10e64feaaa7b4df609cf9", + "parent": "8abc22fbb04266308ff408ca61cb8f6f4244a59308f7efc64e54b08b496c58db", + "created": "2014-02-03T16:10:40.500814677Z", + "container": "f718f19a28a5147da49313c54620306243734bafa63c76942ef6f8c4b4113bc5", + "container_config": { + "Hostname": "88807319f25e", + "Domainname": "", + "User": "", + "Memory": 0, + "MemorySwap": 0, + "CpuShares": 0, + "AttachStdin": false, + "AttachStdout": false, + "AttachStderr": false, + "PortSpecs": null, + "ExposedPorts": null, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": [ + "HOME=/", + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Cmd": [ + "/bin/sh", + "-c", + "#(nop) ADD fedora-20-medium.tar.xz in /" + ], + "Dns": null, + "DnsSearch": null, + "Image": "8abc22fbb04266308ff408ca61cb8f6f4244a59308f7efc64e54b08b496c58db", + "Volumes": null, + "VolumesFrom": "", + "WorkingDir": "", + "Entrypoint": null, + "NetworkDisabled": false, + "OnBuild": null, + "Context": null + }, + "docker_version": "0.6.3", + "author": "Lokesh Mandvekar \u003clsm5@redhat.com\u003e - ./buildcontainers.sh", + "config": { + "Hostname": "88807319f25e", + "Domainname": "", + "User": "", + "Memory": 0, + "MemorySwap": 0, + "CpuShares": 0, + "AttachStdin": false, + "AttachStdout": false, + "AttachStderr": false, + "PortSpecs": null, + "ExposedPorts": null, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": [ + "HOME=/", + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Cmd": null, + "Dns": null, + "DnsSearch": null, + "Image": "8abc22fbb04266308ff408ca61cb8f6f4244a59308f7efc64e54b08b496c58db", + "Volumes": null, + "VolumesFrom": "", + "WorkingDir": "", + "Entrypoint": null, + "NetworkDisabled": false, + "OnBuild": null, + "Context": null + }, + "architecture": "x86_64", + "Size": 385520098 +}] +.RE +.nf +.sp +.SH HISTORY +April 2014, Originally compiled by William Henry (whenry at redhat dot com) based on dockier.io source material and internal work. diff --git a/contrib/man/man1/docker-rm.1 b/contrib/man/man1/docker-rm.1 new file mode 100644 index 0000000000..b06e014d3b --- /dev/null +++ b/contrib/man/man1/docker-rm.1 @@ -0,0 +1,45 @@ +.\" Process this file with +.\" nroff -man -Tascii docker-rm.1 +.\" +.TH "DOCKER" "1" "MARCH 2014" "0.1" "Docker" +.SH NAME +docker-rm \- Remove one or more containers. +.SH SYNOPSIS +.B docker rm +[\fB-f\fR|\fB--force\fR[=\fIfalse\fR] +[\fB-l\fR|\fB--link\fR[=\fIfalse\fR] +[\fB-v\fR|\fB--volumes\fR[=\fIfalse\fR] +CONTAINER [CONTAINER...] +.SH DESCRIPTION +This will remove one or more containers from the host node. The container name or ID can be used. This does not remove images. You cannot remove a running container unless you use the \fB-f\fR option. To see all containers on a host use the \fBdocker ps -a\fR command. +.SH "OPTIONS" +.TP +.B -f, --force=\fItrue\fR|\fIfalse\fR: +When set to true, force the removal of the container. The default is \fIfalse\fR. +.TP +.B -l, --link=\fItrue\fR|\fIfalse\fR: +When set to true, remove the specified link and not the underlying container. The default is \fIfalse\fR. +.TP +.B -v, --volumes=\fItrue\fR|\fIfalse\fR: +When set to true, remove the volumes associated to the container. The default is \fIfalse\fR. +.SH EXAMPLES +.sp +.PP +.B Removing a container using its ID +.TP +To remove a container using its ID, find either from a \fBdocker ps -a\fR command, or use the ID returned from the \fBdocker run\fR command, or retrieve it from a file used to store it using the \fBdocker run --cidfile\fR: +.sp +.RS +docker rm abebf7571666 +.RE +.sp +.B Removing a container using the container name: +.TP +The name of the container can be found using the \fBdocker ps -a\fR command. The use that name as follows: +.sp +.RS +docker rm hopeful_morse +.RE +.sp +.SH HISTORY +March 2014, Originally compiled by William Henry (whenry at redhat dot com) based on dockier.io source material and internal work. diff --git a/contrib/man/man1/docker-rmi.1 b/contrib/man/man1/docker-rmi.1 new file mode 100644 index 0000000000..6f33446ecd --- /dev/null +++ b/contrib/man/man1/docker-rmi.1 @@ -0,0 +1,29 @@ +.\" Process this file with +.\" nroff -man -Tascii docker-run.1 +.\" +.TH "DOCKER" "1" "MARCH 2014" "0.1" "Docker" +.SH NAME +docker-rmi \- Remove one or more images. +.SH SYNOPSIS +.B docker rmi +[\fB-f\fR|\fB--force\fR[=\fIfalse\fR] +IMAGE [IMAGE...] +.SH DESCRIPTION +This will remove one or more images from the host node. This does not remove images from a registry. You cannot remove an image of a running container unless you use the \fB-f\fR option. To see all images on a host use the \fBdocker images\fR command. +.SH "OPTIONS" +.TP +.B -f, --force=\fItrue\fR|\fIfalse\fR: +When set to true, force the removal of the image. The default is \fIfalse\fR. +.SH EXAMPLES +.sp +.PP +.B Removing an image +.TP +Here is an example of removing and image: +.sp +.RS +docker rmi fedora/httpd +.RE +.sp +.SH HISTORY +March 2014, Originally compiled by William Henry (whenry at redhat dot com) based on dockier.io source material and internal work. diff --git a/contrib/man/man1/docker-run.1 b/contrib/man/man1/docker-run.1 new file mode 100644 index 0000000000..fd449374e3 --- /dev/null +++ b/contrib/man/man1/docker-run.1 @@ -0,0 +1,277 @@ +.\" Process this file with +.\" nroff -man -Tascii docker-run.1 +.\" +.TH "DOCKER" "1" "MARCH 2014" "0.1" "Docker" +.SH NAME +docker-run \- Run a process in an isolated container +.SH SYNOPSIS +.B docker run +[\fB-a\fR|\fB--attach\fR[=]] [\fB-c\fR|\fB--cpu-shares\fR[=0] [\fB-m\fR|\fB--memory\fR=\fImemory-limit\fR] +[\fB--cidfile\fR=\fIfile\fR] [\fB-d\fR|\fB--detach\fR[=\fIfalse\fR]] [\fB--dns\fR=\fIIP-address\fR] +[\fB--name\fR=\fIname\fR] [\fB-u\fR|\fB--user\fR=\fIusername\fR|\fIuid\fR] +[\fB--link\fR=\fIname\fR:\fIalias\fR] +[\fB-e\fR|\fB--env\fR=\fIenvironment\fR] [\fB--entrypoint\fR=\fIcommand\fR] +[\fB--expose\fR=\fIport\fR] [\fB-P\fR|\fB--publish-all\fR[=\fIfalse\fR]] +[\fB-p\fR|\fB--publish\fR=\fIport-mappping\fR] [\fB-h\fR|\fB--hostname\fR=\fIhostname\fR] +[\fB--rm\fR[=\fIfalse\fR]] [\fB--priviledged\fR[=\fIfalse\fR] +[\fB-i\fR|\fB--interactive\fR[=\fIfalse\fR] +[\fB-t\fR|\fB--tty\fR[=\fIfalse\fR]] [\fB--lxc-conf\fR=\fIoptions\fR] +[\fB-n\fR|\fB--networking\fR[=\fItrue\fR]] +[\fB-v\fR|\fB--volume\fR=\fIvolume\fR] [\fB--volumes-from\fR=\fIcontainer-id\fR] +[\fB-w\fR|\fB--workdir\fR=\fIdirectory\fR] [\fB--sig-proxy\fR[=\fItrue\fR]] +IMAGE [COMMAND] [ARG...] +.SH DESCRIPTION +.PP +Run a process in a new container. \fBdocker run\fR starts a process with its own file system, its own networking, and its own isolated process tree. The \fIIMAGE\fR which starts the process may define defaults related to the process that will be run in the container, the networking to expose, and more, but \fBdocker run\fR gives final control to the operator or administrator who starts the container from the image. For that reason \fBdocker run\fR has more options than any other docker command. + +If the \fIIMAGE\fR is not already loaded then \fBdocker run\fR will pull the \fIIMAGE\fR, and all image dependencies, from the repository in the same way running \fBdocker pull\fR \fIIMAGE\fR, before it starts the container from that image. + + +.SH "OPTIONS" + +.TP +.B -a, --attach=\fIstdin\fR|\fIstdout\fR|\fIstderr\fR: +Attach to stdin, stdout or stderr. In foreground mode (the default when -d is not specified), \fBdocker run\fR can start the process in the container and attach the console to the process’s standard input, output, and standard error. It can even pretend to be a TTY (this is what most commandline executables expect) and pass along signals. The \fB-a\fR option can be set for each of stdin, stdout, and stderr. + +.TP +.B -c, --cpu-shares=0: +CPU shares in relative weight. You can increase the priority of a container with the -c option. By default, all containers run at the same priority and get the same proportion of CPU cycles, but you can tell the kernel to give more shares of CPU time to one or more containers when you start them via \fBdocker run\fR. + +.TP +.B -m, --memory=\fImemory-limit\fR: +Allows you to constrain the memory available to a container. If the host supports swap memory, then the -m memory setting can be larger than physical RAM. The memory limit format: , where unit = b, k, m or g. + +.TP +.B --cidfile=\fIfile\fR: +Write the container ID to the file specified. + +.TP +.B -d, --detach=\fItrue\fR|\fIfalse\fR: +Detached mode. This runs the container in the background. It outputs the new container's id and and error messages. At any time you can run \fBdocker ps\fR in the other shell to view a list of the running containers. You can reattach to a detached container with \fBdocker attach\fR. If you choose to run a container in the detached mode, then you cannot use the -rm option. + +.TP +.B --dns=\fIIP-address\fR: +Set custom DNS servers. This option can be used to override the DNS configuration passed to the container. Typically this is necessary when the host DNS configuration is invalid for the container (eg. 127.0.0.1). When this is the case the \fB-dns\fR flags is necessary for every run. + +.TP +.B -e, --env=\fIenvironment\fR: +Set environment variables. This option allows you to specify arbitrary environment variables that are available for the process that will be launched inside of the container. + +.TP +.B --entrypoint=\ficommand\fR: +This option allows you to overwrite the default entrypoint of the image that is set in the Dockerfile. The ENTRYPOINT of an image is similar to a COMMAND because it specifies what executable to run when the container starts, but it is (purposely) more difficult to override. The ENTRYPOINT gives a container its default nature or behavior, so that when you set an ENTRYPOINT you can run the container as if it were that binary, complete with default options, and you can pass in more options via the COMMAND. But, sometimes an operator may want to run something else inside the container, so you can override the default ENTRYPOINT at runtime by using a \fB--entrypoint\fR and a string to specify the new ENTRYPOINT. + +.TP +.B --expose=\fIport\fR: +Expose a port from the container without publishing it to your host. A containers port can be exposed to other containers in three ways: 1) The developer can expose the port using the EXPOSE parameter of the Dockerfile, 2) the operator can use the \fB--expose\fR option with \fBdocker run\fR, or 3) the container can be started with the \fB--link\fR. + +.TP +.B -P, --publish-all=\fItrue\fR|\fIfalse\fR: +When set to true publish all exposed ports to the host interfaces. The default is false. If the operator uses -P (or -p) then Docker will make the exposed port accessible on the host and the ports will be available to any client that can reach the host. To find the map between the host ports and the exposed ports, use \fBdocker port\fR. + +.TP +.B -p, --publish=[]: +Publish a container's port to the host (format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort) (use 'docker port' to see the actual mapping) + +.TP +.B -h , --hostname=\fIhostname\fR: +Sets the container host name that is available inside the container. + +.TP +.B -i , --interactive=\fItrue\fR|\fIfalse\fR: +When set to true, keep stdin open even if not attached. The default is false. + +.TP +.B --link=\fIname\fR:\fIalias\fR: +Add link to another container. The format is name:alias. If the operator uses \fB--link\fR when starting the new client container, then the client container can access the exposed port via a private networking interface. Docker will set some environment variables in the client container to help indicate which interface and port to use. + +.TP +.B -n, --networking=\fItrue\fR|\fIfalse\fR: +By default, all containers have networking enabled (true) and can make outgoing connections. The operator can disable networking with \fB--networking\fR to false. This disables all incoming and outgoing networking. In cases like this, I/O can only be performed through files or by using STDIN/STDOUT. + +Also by default, the container will use the same DNS servers as the host. but you canThe operator may override this with \fB-dns\fR. + +.TP +.B --name=\fIname\fR: +Assign a name to the container. The operator can identify a container in three ways: +.sp +.nf +UUID long identifier (“f78375b1c487e03c9438c729345e54db9d20cfa2ac1fc3494b6eb60872e74778”) +UUID short identifier (“f78375b1c487”) +Name (“jonah”) +.fi +.sp +The UUID identifiers come from the Docker daemon, and if a name is not assigned to the container with \fB--name\fR then the daemon will also generate a random string name. The name is useful when defining links (see \fB--link\fR) (or any other place you need to identify a container). This works for both background and foreground Docker containers. + +.TP +.B --privileged=\fItrue\fR|\fIfalse\fR: +Give extended privileges to this container. By default, Docker containers are “unprivileged” (=false) and cannot, for example, run a Docker daemon inside the Docker container. This is because by default a container is not allowed to access any devices. A “privileged” container is given access to all devices. + +When the operator executes \fBdocker run -privileged\fR, Docker will enable access to all devices on the host as well as set some configuration in AppArmor (\fB???\fR) to allow the container nearly all the same access to the host as processes running outside of a container on the host. + +.TP +.B --rm=\fItrue\fR|\fIfalse\fR: +If set to \fItrue\fR the container is automatically removed when it exits. The default is \fIfalse\fR. This option is incompatible with \fB-d\fR. + +.TP +.B --sig-proxy=\fItrue\fR|\fIfalse\fR: +When set to true, proxify all received signals to the process (even in non-tty mode). The default is true. + +.TP +.B -t, --tty=\fItrue\fR|\fIfalse\fR: +When set to true Docker can allocate a pseudo-tty and attach to the standard input of any container. This can be used, for example, to run a throwaway interactive shell. The default is value is false. + +.TP +.B -u, --user=\fIusername\fR,\fRuid\fR: +Set a username or UID for the container. + +.TP +.B -v, --volume=\fIvolume\fR: +Bind mount a volume to the container. The \fB-v\fR option can be used one or more times to add one or more mounts to a container. These mounts can then be used in other containers using the \fB--volumes-from\fR option. See examples. + +.TP +.B --volumes-from=\fIcontainer-id\fR: +Will mount volumes from the specified container identified by container-id. Once a volume is mounted in a one container it can be shared with other containers using the \fB--volumes-from\fR option when running those other containers. The volumes can be shared even if the original container with the mount is not running. + +.TP +.B -w, --workdir=\fIdirectory\fR: +Working directory inside the container. The default working directory for running binaries within a container is the root directory (/). The developer can set a different default with the Dockerfile WORKDIR instruction. The operator can override the working directory by using the \fB-w\fR option. + +.TP +.B IMAGE: +The image name or ID. + +.TP +.B COMMAND: +The command or program to run inside the image. + +.TP +.B ARG: +The arguments for the command to be run in the container. + +.SH EXAMPLES +.sp +.sp +.B Exposing log messages from the container to the host's log +.TP +If you want messages that are logged in your container to show up in the host's syslog/journal then you should bind mount the /var/log directory as follows. +.sp +.RS +docker run -v /dev/log:/dev/log -i -t fedora /bin/bash +.RE +.sp +From inside the container you can test this by sending a message to the log. +.sp +.RS +logger "Hello from my container" +.sp +.RE +Then exit and check the journal. +.RS +.sp +exit +.sp +journalctl -b | grep hello +.RE +.sp +This should list the message sent to logger. +.sp +.B Attaching to one or more from STDIN, STDOUT, STDERR +.TP +If you do not specify -a then Docker will attach everything (stdin,stdout,stderr). You can specify to which of the three standard streams (stdin, stdout, stderr) you’d like to connect instead, as in: +.sp +.RS +docker run -a stdin -a stdout -i -t fedora /bin/bash +.RE +.sp +.B Linking Containers +.TP +The link feature allows multiple containers to communicate with each other. For example, a container whose Dockerfile has exposed port 80 can be run and named as follows: +.sp +.RS +docker run --name=link-test -d -i -t fedora/httpd +.RE +.sp +.TP +A second container, in this case called linker, can communicate with the httpd container, named link-test, by running with the \fB--link=:\fR +.sp +.RS +docker run -t -i --link=link-test:lt --name=linker fedora /bin/bash +.RE +.sp +.TP +Now the container linker is linked to container link-test with the alias lt. Running the \fBenv\fR command in the linker container shows environment variables with the LT (alias) context (\fBLT_\fR) +.sp +.nf +.RS +# env +HOSTNAME=668231cb0978 +TERM=xterm +LT_PORT_80_TCP=tcp://172.17.0.3:80 +LT_PORT_80_TCP_PORT=80 +LT_PORT_80_TCP_PROTO=tcp +LT_PORT=tcp://172.17.0.3:80 +PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +PWD=/ +LT_NAME=/linker/lt +SHLVL=1 +HOME=/ +LT_PORT_80_TCP_ADDR=172.17.0.3 +_=/usr/bin/env +.RE +.fi +.sp +.TP +When linking two containers Docker will use the exposed ports of the container to create a secure tunnel for the parent to access. +.TP +.sp +.B Mapping Ports for External Usage +.TP +The exposed port of an application can be mapped to a host port using the \fB-p\fR flag. For example a httpd port 80 can be mapped to the host port 8080 using the following: +.sp +.RS +docker run -p 8080:80 -d -i -t fedora/httpd +.RE +.sp +.TP +.B Creating and Mounting a Data Volume Container +.TP +Many applications require the sharing of persistent data across several containers. Docker allows you to create a Data Volume Container that other containers can mount from. For example, create a named container that contains directories /var/volume1 and /tmp/volume2. The image will need to contain these directories so a couple of RUN mkdir instructions might be required for you fedora-data image: +.sp +.RS +docker run --name=data -v /var/volume1 -v /tmp/volume2 -i -t fedora-data true +.sp +docker run --volumes-from=data --name=fedora-container1 -i -t fedora bash +.RE +.sp +.TP +Multiple -volumes-from parameters will bring together multiple data volumes from multiple containers. And it's possible to mount the volumes that came from the DATA container in yet another container via the fedora-container1 intermidiery container, allowing to abstract the actual data source from users of that data: +.sp +.RS +docker run --volumes-from=fedora-container1 --name=fedora-container2 -i -t fedora bash +.RE +.TP +.sp +.B Mounting External Volumes +.TP +To mount a host directory as a container volume, specify the absolute path to the directory and the absolute path for the container directory separated by a colon: +.sp +.RS +docker run -v /var/db:/data1 -i -t fedora bash +.RE +.sp +.TP +When using SELinux, be aware that the host has no knowledge of container SELinux policy. Therefore, in the above example, if SELinux policy is enforced, the /var/db directory is not writable to the container. A "Permission Denied" message will occur and an avc: message in the host's syslog. +.sp +.TP +To work around this, at time of writing this man page, the following command needs to be run in order for the proper SELinux policy type label to be attached to the host directory: +.sp +.RS +chcon -Rt svirt_sandbox_file_t /var/db +.RE +.sp +.TP +Now, writing to the /data1 volume in the container will be allowed and the changes will also be reflected on the host in /var/db. +.sp +.SH HISTORY +March 2014, Originally compiled by William Henry (whenry at redhat dot com) based on dockier.io source material and internal work. diff --git a/contrib/man/man1/docker-tag.1 b/contrib/man/man1/docker-tag.1 new file mode 100644 index 0000000000..df85a1e8c1 --- /dev/null +++ b/contrib/man/man1/docker-tag.1 @@ -0,0 +1,49 @@ +.\" Process this file with +.\" nroff -man -Tascii docker-tag.1 +.\" +.TH "DOCKER" "1" "APRIL 2014" "0.1" "Docker" +.SH NAME +docker-tag \- Tag an image in the repository +.SH SYNOPSIS +.B docker tag +[\fB-f\fR|\fB--force\fR[=\fIfalse\fR] +\fBIMAGE\fR [REGISTRYHOST/][USERNAME/]NAME[:TAG] +.SH DESCRIPTION +This will tag an image in the repository. +.SH "OPTIONS" +.TP +.B -f, --force=\fItrue\fR|\fIfalse\fR: +When set to true, force the tag name. The default is \fIfalse\fR. +.TP +.B REGISTRYHOST: +The hostname of the registry if required. This may also include the port separated by a ':' +.TP +.B USERNAME: +The username or other qualifying identifier for the image. +.TP +.B NAME: +The image name. +.TP +.B TAG: +The tag you are assigning to the image. +.SH EXAMPLES +.sp +.PP +.B Tagging an image +.TP +Here is an example where an image is tagged with the tag 'Version-1.0' : +.sp +.RS +docker tag 0e5574283393 fedora/httpd:Version-1.0 +.RE +.sp +.B Tagging an image for an internal repository +.TP +To push an image to an internal Registry and not the default docker.io based registry you must tag it with the registry hostname and port (if needed). +.sp +.RS +docker tag 0e5574283393 myregistryhost:5000/fedora/httpd:version1.0 +.RE +.sp +.SH HISTORY +April 2014, Originally compiled by William Henry (whenry at redhat dot com) based on dockier.io source material and internal work. diff --git a/contrib/man/man1/docker.1 b/contrib/man/man1/docker.1 new file mode 100644 index 0000000000..4a36e5baf5 --- /dev/null +++ b/contrib/man/man1/docker.1 @@ -0,0 +1,172 @@ +.\" Process this file with +.\" nroff -man -Tascii docker.1 +.\" +.TH "DOCKER" "1" "APRIL 2014" "0.1" "Docker" +.SH NAME +docker \- Docker image and container command line interface +.SH SYNOPSIS +.B docker [OPTIONS] [COMMAND] [arg...] +.SH DESCRIPTION +\fBdocker\fR has two distinct functions. It is used for starting the Docker daemon and to run the CLI (i.e., to command the daemon to manage images, containers etc.) So \fBdocker\fR is both a server as deamon and a client to the daemon through the CLI. +.sp +To run the Docker deamon you do not specify any of the commands listed below but must specify the \fB-d\fR option. The other options listed below are for the daemon only. +.sp +The Docker CLI has over 30 commands. The commands are listed below and each has its own man page which explain usage and arguements. +.sp +To see the man page for a command run \fBman docker \fR. +.SH "OPTIONS" +.B \-D=false: +Enable debug mode +.TP +.B\-H=[unix:///var/run/docker.sock]: tcp://[host[:port]] to bind or unix://[/path/to/socket] to use. +When host=[0.0.0.0], port=[4243] or path +=[/var/run/docker.sock] is omitted, default values are used. +.TP +.B \-\-api-enable-cors=false +Enable CORS headers in the remote API +.TP +.B \-b="" +Attach containers to a pre\-existing network bridge; use 'none' to disable container networking +.TP +.B \-\-bip="" +Use the provided CIDR notation address for the dynamically created bridge (docker0); Mutually exclusive of \-b +.TP +.B \-d=false +Enable daemon mode +.TP +.B \-\-dns="" +Force Docker to use specific DNS servers +.TP +.B \-g="/var/lib/docker" +Path to use as the root of the Docker runtime +.TP +.B \-\-icc=true +Enable inter\-container communication +.TP +.B \-\-ip="0.0.0.0" +Default IP address to use when binding container ports +.TP +.B \-\-iptables=true +Disable Docker's addition of iptables rules +.TP +.B \-\-mtu=1500 +Set the containers network mtu +.TP +.B \-p="/var/run/docker.pid" +Path to use for daemon PID file +.TP +.B \-r=true +Restart previously running containers +.TP +.B \-s="" +Force the Docker runtime to use a specific storage driver +.TP +.B \-v=false +Print version information and quit +.SH "COMMANDS" +.TP +.B attach +Attach to a running container +.TP +.B build +Build a container from a Dockerfile +.TP +.B commit +Create a new image from a container's changes +.TP +.B cp +Copy files/folders from the containers filesystem to the host at path +.TP +.B diff +Inspect changes on a container's filesystem + +.TP +.B events +Get real time events from the server +.TP +.B export +Stream the contents of a container as a tar archive +.TP +.B history +Show the history of an image +.TP +.B images +List images +.TP +.B import +Create a new filesystem image from the contents of a tarball +.TP +.B info +Display system-wide information +.TP +.B insert +Insert a file in an image +.TP +.B inspect +Return low-level information on a container +.TP +.B kill +Kill a running container (which includes the wrapper process and everything inside it) +.TP +.B load +Load an image from a tar archive +.TP +.B login +Register or Login to a Docker registry server +.TP +.B logs +Fetch the logs of a container +.TP +.B port +Lookup the public-facing port which is NAT-ed to PRIVATE_PORT +.TP +.B ps +List containers +.TP +.B pull +Pull an image or a repository from a Docker registry server +.TP +.B push +Push an image or a repository to a Docker registry server +.TP +.B restart +Restart a running container +.TP +.B rm +Remove one or more containers +.TP +.B rmi +Remove one or more images +.TP +.B run +Run a command in a new container +.TP +.B save +Save an image to a tar archive +.TP +.B search +Search for an image in the Docker index +.TP +.B start +Start a stopped container +.TP +.B stop +Stop a running container +.TP +.B tag +Tag an image into a repository +.TP +.B top +Lookup the running processes of a container +.TP +.B version +Show the Docker version information +.TP +.B wait +Block until a container stops, then print its exit code +.SH EXAMPLES +.sp +For specific examples please see the man page for the specific Docker command. +.sp +.SH HISTORY +April 2014, Originally compiled by William Henry (whenry at redhat dot com) based on dockier.io source material and internal work. From b6042f252dd8a0c7a75da481b667f89c2e4ab071 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 7 Apr 2014 18:23:22 -0700 Subject: [PATCH 367/384] Ensure that ro mounts are remounted Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- integration-cli/docker_cli_run_test.go | 11 +++++++++++ pkg/libcontainer/nsinit/mount.go | 11 +++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index fbb09737fc..d085574741 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -301,3 +301,14 @@ func TestDockerRunWithRelativePath(t *testing.T) { logDone("run - volume with relative path") } + +func TestVolumesMountedAsReadonly(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "-v", "/test:/test:ro", "busybox", "touch", "/test/somefile") + if code, err := runCommand(cmd); err == nil || code == 0 { + t.Fatalf("run should fail because volume is ro: exit code %d", code) + } + + deleteAllContainers() + + logDone("run - volumes as readonly mount") +} diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index 4b5a42b1ac..3b0cf13bc9 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/nsinit/mount.go @@ -37,14 +37,21 @@ func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, cons } for _, m := range bindMounts { - flags := syscall.MS_BIND | syscall.MS_REC + var ( + flags = syscall.MS_BIND | syscall.MS_REC + dest = filepath.Join(rootfs, m.Destination) + ) if !m.Writable { flags = flags | syscall.MS_RDONLY } - dest := filepath.Join(rootfs, m.Destination) if err := system.Mount(m.Source, dest, "bind", uintptr(flags), ""); err != nil { return fmt.Errorf("mounting %s into %s %s", m.Source, dest, err) } + if !m.Writable { + if err := system.Mount(m.Source, dest, "bind", uintptr(flags|syscall.MS_REMOUNT), ""); err != nil { + return fmt.Errorf("remounting %s into %s %s", m.Source, dest, err) + } + } if m.Private { if err := system.Mount("", dest, "none", uintptr(syscall.MS_PRIVATE), ""); err != nil { return fmt.Errorf("mounting %s private %s", dest, err) From 9c4d10b9a91b9f11794ceb094331496c733410bb Mon Sep 17 00:00:00 2001 From: Dan Stine Date: Mon, 7 Apr 2014 22:09:15 -0400 Subject: [PATCH 368/384] fixed three more typos --- pkg/libcontainer/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/libcontainer/README.md b/pkg/libcontainer/README.md index 3bf79549e3..d6d0fbae44 100644 --- a/pkg/libcontainer/README.md +++ b/pkg/libcontainer/README.md @@ -3,7 +3,7 @@ #### background libcontainer specifies configuration options for what a container is. It provides a native Go implementation -for using linux namespaces with no external dependencies. libcontainer provides many convenience functions for working with namespaces, networking, and management. +for using Linux namespaces with no external dependencies. libcontainer provides many convenience functions for working with namespaces, networking, and management. #### container @@ -91,7 +91,7 @@ Sample `container.json` file: ``` Using this configuration and the current directory holding the rootfs for a process, one can use libcontainer to exec the container. Running the life of the namespace, a `pid` file -is written to the current directory with the pid of the namespaced process to the external world. A client can use this pid to wait, kill, or perform other operation with the container. If a user tries to run a new process inside an existing container with a live namespace the namespace will be joined by the new process. +is written to the current directory with the pid of the namespaced process to the external world. A client can use this pid to wait, kill, or perform other operation with the container. If a user tries to run a new process inside an existing container with a live namespace, the namespace will be joined by the new process. You may also specify an alternate root place where the `container.json` file is read and where the `pid` file will be saved. @@ -99,7 +99,7 @@ You may also specify an alternate root place where the `container.json` file is #### nsinit `nsinit` is a cli application used as the reference implementation of libcontainer. It is able to -spawn or join new containers giving the current directory. To use `nsinit` cd into a linux +spawn or join new containers giving the current directory. To use `nsinit` cd into a Linux rootfs and copy a `container.json` file into the directory with your specified configuration. To execute `/bin/bash` in the current directory as a container just run: From 919dbbe44df0722ca35538223a9c89e71856ec88 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 7 Apr 2014 19:12:22 -0700 Subject: [PATCH 369/384] Move DNS options to hostconfig The local resolver warning needed to be moved at daemon start because it was only show for the first container started anyways before having a default value set. Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runconfig/compare.go | 12 -------- runconfig/config.go | 8 ----- runconfig/config_test.go | 41 ------------------------- runconfig/hostconfig.go | 9 +++++- runconfig/merge.go | 19 ------------ runconfig/parse.go | 4 +-- runtime/container.go | 53 ++++++++++++++++++++++++++++++++ runtime/runtime.go | 66 ++++++++++------------------------------ server/server.go | 9 ------ 9 files changed, 79 insertions(+), 142 deletions(-) diff --git a/runconfig/compare.go b/runconfig/compare.go index 6ed7405246..122687f669 100644 --- a/runconfig/compare.go +++ b/runconfig/compare.go @@ -19,8 +19,6 @@ func Compare(a, b *Config) bool { return false } if len(a.Cmd) != len(b.Cmd) || - len(a.Dns) != len(b.Dns) || - len(a.DnsSearch) != len(b.DnsSearch) || len(a.Env) != len(b.Env) || len(a.PortSpecs) != len(b.PortSpecs) || len(a.ExposedPorts) != len(b.ExposedPorts) || @@ -34,16 +32,6 @@ func Compare(a, b *Config) bool { return false } } - for i := 0; i < len(a.Dns); i++ { - if a.Dns[i] != b.Dns[i] { - return false - } - } - for i := 0; i < len(a.DnsSearch); i++ { - if a.DnsSearch[i] != b.DnsSearch[i] { - return false - } - } for i := 0; i < len(a.Env); i++ { if a.Env[i] != b.Env[i] { return false diff --git a/runconfig/config.go b/runconfig/config.go index 4b334c6848..9db545976b 100644 --- a/runconfig/config.go +++ b/runconfig/config.go @@ -25,8 +25,6 @@ type Config struct { StdinOnce bool // If true, close stdin after the 1 attached client disconnects. Env []string Cmd []string - Dns []string - DnsSearch []string Image string // Name of the image as it was passed by the operator (eg. could be symbolic) Volumes map[string]struct{} VolumesFrom string @@ -66,12 +64,6 @@ func ContainerConfigFromJob(job *engine.Job) *Config { if Cmd := job.GetenvList("Cmd"); Cmd != nil { config.Cmd = Cmd } - if Dns := job.GetenvList("Dns"); Dns != nil { - config.Dns = Dns - } - if DnsSearch := job.GetenvList("DnsSearch"); DnsSearch != nil { - config.DnsSearch = DnsSearch - } if Entrypoint := job.GetenvList("Entrypoint"); Entrypoint != nil { config.Entrypoint = Entrypoint } diff --git a/runconfig/config_test.go b/runconfig/config_test.go index 784341b08c..d5ab75b3b9 100644 --- a/runconfig/config_test.go +++ b/runconfig/config_test.go @@ -163,32 +163,18 @@ func TestCompare(t *testing.T) { volumes1 := make(map[string]struct{}) volumes1["/test1"] = struct{}{} config1 := Config{ - Dns: []string{"1.1.1.1", "2.2.2.2"}, - DnsSearch: []string{"foo", "bar"}, - PortSpecs: []string{"1111:1111", "2222:2222"}, - Env: []string{"VAR1=1", "VAR2=2"}, - VolumesFrom: "11111111", - Volumes: volumes1, - } - config2 := Config{ - Dns: []string{"0.0.0.0", "2.2.2.2"}, - DnsSearch: []string{"foo", "bar"}, PortSpecs: []string{"1111:1111", "2222:2222"}, Env: []string{"VAR1=1", "VAR2=2"}, VolumesFrom: "11111111", Volumes: volumes1, } config3 := Config{ - Dns: []string{"1.1.1.1", "2.2.2.2"}, - DnsSearch: []string{"foo", "bar"}, PortSpecs: []string{"0000:0000", "2222:2222"}, Env: []string{"VAR1=1", "VAR2=2"}, VolumesFrom: "11111111", Volumes: volumes1, } config4 := Config{ - Dns: []string{"1.1.1.1", "2.2.2.2"}, - DnsSearch: []string{"foo", "bar"}, PortSpecs: []string{"0000:0000", "2222:2222"}, Env: []string{"VAR1=1", "VAR2=2"}, VolumesFrom: "22222222", @@ -197,24 +183,11 @@ func TestCompare(t *testing.T) { volumes2 := make(map[string]struct{}) volumes2["/test2"] = struct{}{} config5 := Config{ - Dns: []string{"1.1.1.1", "2.2.2.2"}, - DnsSearch: []string{"foo", "bar"}, PortSpecs: []string{"0000:0000", "2222:2222"}, Env: []string{"VAR1=1", "VAR2=2"}, VolumesFrom: "11111111", Volumes: volumes2, } - config6 := Config{ - Dns: []string{"1.1.1.1", "2.2.2.2"}, - DnsSearch: []string{"foos", "bars"}, - PortSpecs: []string{"1111:1111", "2222:2222"}, - Env: []string{"VAR1=1", "VAR2=2"}, - VolumesFrom: "11111111", - Volumes: volumes1, - } - if Compare(&config1, &config2) { - t.Fatalf("Compare should return false, Dns are different") - } if Compare(&config1, &config3) { t.Fatalf("Compare should return false, PortSpecs are different") } @@ -224,9 +197,6 @@ func TestCompare(t *testing.T) { if Compare(&config1, &config5) { t.Fatalf("Compare should return false, Volumes are different") } - if Compare(&config1, &config6) { - t.Fatalf("Compare should return false, DnsSearch are different") - } if !Compare(&config1, &config1) { t.Fatalf("Compare should return true") } @@ -237,7 +207,6 @@ func TestMerge(t *testing.T) { volumesImage["/test1"] = struct{}{} volumesImage["/test2"] = struct{}{} configImage := &Config{ - Dns: []string{"1.1.1.1", "2.2.2.2"}, PortSpecs: []string{"1111:1111", "2222:2222"}, Env: []string{"VAR1=1", "VAR2=2"}, VolumesFrom: "1111", @@ -247,7 +216,6 @@ func TestMerge(t *testing.T) { volumesUser := make(map[string]struct{}) volumesUser["/test3"] = struct{}{} configUser := &Config{ - Dns: []string{"2.2.2.2", "3.3.3.3"}, PortSpecs: []string{"3333:2222", "3333:3333"}, Env: []string{"VAR2=3", "VAR3=3"}, Volumes: volumesUser, @@ -257,15 +225,6 @@ func TestMerge(t *testing.T) { t.Error(err) } - if len(configUser.Dns) != 3 { - t.Fatalf("Expected 3 dns, 1.1.1.1, 2.2.2.2 and 3.3.3.3, found %d", len(configUser.Dns)) - } - for _, dns := range configUser.Dns { - if dns != "1.1.1.1" && dns != "2.2.2.2" && dns != "3.3.3.3" { - t.Fatalf("Expected 1.1.1.1 or 2.2.2.2 or 3.3.3.3, found %s", dns) - } - } - if len(configUser.ExposedPorts) != 3 { t.Fatalf("Expected 3 ExposedPorts, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts)) } diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 55a308a5b8..5c5e291bad 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -14,6 +14,8 @@ type HostConfig struct { PortBindings nat.PortMap Links []string PublishAllPorts bool + Dns []string + DnsSearch []string } func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { @@ -30,6 +32,11 @@ func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { if Links := job.GetenvList("Links"); Links != nil { hostConfig.Links = Links } - + if Dns := job.GetenvList("Dns"); Dns != nil { + hostConfig.Dns = Dns + } + if DnsSearch := job.GetenvList("DnsSearch"); DnsSearch != nil { + hostConfig.DnsSearch = DnsSearch + } return hostConfig } diff --git a/runconfig/merge.go b/runconfig/merge.go index a154d4caf5..7a089855b2 100644 --- a/runconfig/merge.go +++ b/runconfig/merge.go @@ -94,25 +94,6 @@ func Merge(userConf, imageConf *Config) error { if userConf.Cmd == nil || len(userConf.Cmd) == 0 { userConf.Cmd = imageConf.Cmd } - if userConf.Dns == nil || len(userConf.Dns) == 0 { - userConf.Dns = imageConf.Dns - } else { - 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 - } else { - //duplicates aren't an issue here - userConf.DnsSearch = append(userConf.DnsSearch, imageConf.DnsSearch...) - } if userConf.Entrypoint == nil || len(userConf.Entrypoint) == 0 { userConf.Entrypoint = imageConf.Entrypoint } diff --git a/runconfig/parse.go b/runconfig/parse.go index 3ca326fca6..58d6c9ebb9 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -213,8 +213,6 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf AttachStderr: flAttach.Get("stderr"), Env: envVariables, Cmd: runCmd, - Dns: flDns.GetAll(), - DnsSearch: flDnsSearch.GetAll(), Image: image, Volumes: flVolumes.GetMap(), VolumesFrom: strings.Join(flVolumesFrom.GetAll(), ","), @@ -230,6 +228,8 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf PortBindings: portBindings, Links: flLinks.GetAll(), PublishAllPorts: *flPublishAll, + Dns: flDns.GetAll(), + DnsSearch: flDnsSearch.GetAll(), } if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit { diff --git a/runtime/container.go b/runtime/container.go index a5a2f25c64..c8053b146c 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -430,6 +430,12 @@ func (container *Container) Start() (err error) { } }() + if container.ResolvConfPath == "" { + if err := container.setupContainerDns(); err != nil { + return err + } + } + if err := container.Mount(); err != nil { return err } @@ -1174,3 +1180,50 @@ func (container *Container) DisableLink(name string) { } } } + +func (container *Container) setupContainerDns() error { + var ( + config = container.hostConfig + runtime = container.runtime + ) + resolvConf, err := utils.GetResolvConf() + if err != nil { + return err + } + // If custom dns exists, then create a resolv.conf for the container + if len(config.Dns) > 0 || len(runtime.config.Dns) > 0 || len(config.DnsSearch) > 0 || len(runtime.config.DnsSearch) > 0 { + var ( + dns = utils.GetNameservers(resolvConf) + dnsSearch = utils.GetSearchDomains(resolvConf) + ) + if len(config.Dns) > 0 { + dns = config.Dns + } else if len(runtime.config.Dns) > 0 { + dns = runtime.config.Dns + } + if len(config.DnsSearch) > 0 { + dnsSearch = config.DnsSearch + } else if len(runtime.config.DnsSearch) > 0 { + dnsSearch = runtime.config.DnsSearch + } + container.ResolvConfPath = path.Join(container.root, "resolv.conf") + f, err := os.Create(container.ResolvConfPath) + if err != nil { + return err + } + defer f.Close() + for _, dns := range dns { + if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil { + return err + } + } + if len(dnsSearch) > 0 { + if _, err := f.Write([]byte("search " + strings.Join(dnsSearch, " ") + "\n")); err != nil { + return err + } + } + } else { + container.ResolvConfPath = "/etc/resolv.conf" + } + return nil +} diff --git a/runtime/runtime.go b/runtime/runtime.go index 864874c8e4..98903cfa08 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -24,6 +24,7 @@ import ( "github.com/dotcloud/docker/utils" "io" "io/ioutil" + "log" "os" "path" "regexp" @@ -393,9 +394,6 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe if err := runtime.createRootfs(container, img); err != nil { return nil, nil, err } - if err := runtime.setupContainerDns(container, config); err != nil { - return nil, nil, err - } if err := container.ToDisk(); err != nil { return nil, nil, err } @@ -572,53 +570,6 @@ func (runtime *Runtime) createRootfs(container *Container, img *image.Image) err return nil } -func (runtime *Runtime) setupContainerDns(container *Container, config *runconfig.Config) error { - resolvConf, err := utils.GetResolvConf() - if err != nil { - return err - } - if len(config.Dns) == 0 && len(runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) { - runtime.config.Dns = DefaultDns - } - - // If custom dns exists, then create a resolv.conf for the container - if len(config.Dns) > 0 || len(runtime.config.Dns) > 0 || len(config.DnsSearch) > 0 || len(runtime.config.DnsSearch) > 0 { - var ( - dns = utils.GetNameservers(resolvConf) - dnsSearch = utils.GetSearchDomains(resolvConf) - ) - if len(config.Dns) > 0 { - dns = config.Dns - } else if len(runtime.config.Dns) > 0 { - dns = runtime.config.Dns - } - if len(config.DnsSearch) > 0 { - dnsSearch = config.DnsSearch - } else if len(runtime.config.DnsSearch) > 0 { - dnsSearch = runtime.config.DnsSearch - } - container.ResolvConfPath = path.Join(container.root, "resolv.conf") - f, err := os.Create(container.ResolvConfPath) - if err != nil { - return err - } - defer f.Close() - for _, dns := range dns { - if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil { - return err - } - } - if len(dnsSearch) > 0 { - if _, err := f.Write([]byte("search " + strings.Join(dnsSearch, " ") + "\n")); err != nil { - return err - } - } - } else { - container.ResolvConfPath = "/etc/resolv.conf" - } - return nil -} - // Commit creates a new filesystem image from the current state of a container. // The image can optionally be tagged into a repository func (runtime *Runtime) Commit(container *Container, repository, tag, comment, author string, config *runconfig.Config) (*image.Image, error) { @@ -839,6 +790,9 @@ func NewRuntimeFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (* eng: eng, } + if err := runtime.checkLocaldns(); err != nil { + return nil, err + } if err := runtime.restore(); err != nil { return nil, err } @@ -1025,3 +979,15 @@ func (runtime *Runtime) ContainerGraph() *graphdb.Database { func (runtime *Runtime) SetServer(server Server) { runtime.srv = server } + +func (runtime *Runtime) checkLocaldns() error { + resolvConf, err := utils.GetResolvConf() + if err != nil { + return err + } + if len(runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) { + log.Printf("Local (127.0.0.1) DNS resolver found in resolv.conf and containers can't use it. Using default external servers : %v\n", DefaultDns) + runtime.config.Dns = DefaultDns + } + return nil +} diff --git a/server/server.go b/server/server.go index 9cabf17889..0feaff4eac 100644 --- a/server/server.go +++ b/server/server.go @@ -1731,15 +1731,6 @@ func (srv *Server) ContainerCreate(job *engine.Job) engine.Status { job.Errorf("Your kernel does not support swap limit capabilities. Limitation discarded.\n") config.MemorySwap = -1 } - resolvConf, err := utils.GetResolvConf() - if err != nil { - return job.Error(err) - } - if !config.NetworkDisabled && len(config.Dns) == 0 && len(srv.runtime.Config().Dns) == 0 && utils.CheckLocalDns(resolvConf) { - job.Errorf("Local (127.0.0.1) DNS resolver found in resolv.conf and containers can't use it. Using default external servers : %v\n", runtime.DefaultDns) - config.Dns = runtime.DefaultDns - } - container, buildWarnings, err := srv.runtime.Create(config, name) if err != nil { if srv.runtime.Graph().IsNotExist(err) { From 603088be928564be2d863a897fbc1729adc74814 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 7 Apr 2014 21:20:03 -0600 Subject: [PATCH 370/384] Fix edge case in bind mount absolute path detection `filepath.Abs` does more than just `filepath.IsAbs` - namely, `filepath.Clean`, which resolves things like `.../.` or `.../../...`, and causes even an absolute path like `/some/path/../absolute` to fail (or, in my case, `/path/to/docker/.`) Just using `filepath.IsAbs` directly is a much cheaper check, too. :) Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- runtime/volumes.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/runtime/volumes.go b/runtime/volumes.go index 0b6f3734e0..40db177174 100644 --- a/runtime/volumes.go +++ b/runtime/volumes.go @@ -177,12 +177,8 @@ func createVolumes(container *Container) error { if bindMap, exists := binds[volPath]; exists { isBindMount = true srcPath = bindMap.SrcPath - srcAbs, err := filepath.Abs(srcPath) - if err != nil { - return err - } - if srcPath != srcAbs { - return fmt.Errorf("%s should be an absolute path", srcPath) + if !filepath.IsAbs(srcPath) { + return fmt.Errorf("%s must be an absolute path", srcPath) } if strings.ToLower(bindMap.Mode) == "rw" { srcRW = true From 886eb85dec7f4e9e193151befa7e6b4213ea67a0 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 7 Apr 2014 22:22:03 -0600 Subject: [PATCH 371/384] Update RELEASE_BUNDLES to include integration tests Previously, running just "hack/release.sh" only ran the unit tests. This updates that to run the unit tests, then the integration tests, then build the binaries, then run the cli integration tests (so we're literally testing the binary we're about to release, which is super freaking cool IMO <3). Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/release.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/hack/release.sh b/hack/release.sh index 84e1c42383..d77d454e27 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -53,9 +53,13 @@ RELEASE_BUNDLES=( ) if [ "$1" != '--release-regardless-of-test-failure' ]; then - RELEASE_BUNDLES=( test "${RELEASE_BUNDLES[@]}" ) + RELEASE_BUNDLES=( + test test-integration + "${RELEASE_BUNDLES[@]}" + test-integration-cli + ) fi - + VERSION=$(cat VERSION) BUCKET=$AWS_S3_BUCKET From e6a64af966698d8b9dfe8721ca4404fc06331d69 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Tue, 8 Apr 2014 14:25:57 +1000 Subject: [PATCH 372/384] use the docs sidebar to be the TOC means that level 2 headings are the most important Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/reference/builder.rst | 62 ++++++++++++++---------------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/docs/sources/reference/builder.rst b/docs/sources/reference/builder.rst index 6462512da0..4858a0b17c 100644 --- a/docs/sources/reference/builder.rst +++ b/docs/sources/reference/builder.rst @@ -13,12 +13,10 @@ Dockerfile Reference to create an image. Executing ``docker build`` will run your steps and commit them along the way, giving you a final image. -.. contents:: Table of Contents - .. _dockerfile_usage: -1. Usage -======== +Usage +===== To :ref:`build ` an image from a source repository, create a description file called ``Dockerfile`` at the root of your @@ -71,8 +69,8 @@ When you're done with your build, you're ready to look into .. _dockerfile_format: -2. Format -========= +Format +====== Here is the format of the Dockerfile: @@ -99,16 +97,14 @@ allows statements like: .. _dockerfile_instructions: -3. Instructions -=============== Here is the set of instructions you can use in a ``Dockerfile`` for building images. .. _dockerfile_from: -3.1 FROM --------- +``FROM`` +======== ``FROM `` @@ -134,8 +130,8 @@ assumed. If the used tag does not exist, an error will be returned. .. _dockerfile_maintainer: -3.2 MAINTAINER --------------- +``MAINTAINER`` +============== ``MAINTAINER `` @@ -144,8 +140,8 @@ the generated images. .. _dockerfile_run: -3.3 RUN -------- +``RUN`` +======= RUN has 2 forms: @@ -174,8 +170,8 @@ Known Issues (RUN) .. _dockerfile_cmd: -3.4 CMD -------- +``CMD`` +======= CMD has three forms: @@ -229,8 +225,8 @@ override the default specified in CMD. .. _dockerfile_expose: -3.5 EXPOSE ----------- +``EXPOSE`` +========== ``EXPOSE [...]`` @@ -241,8 +237,8 @@ functionally equivalent to running ``docker commit --run '{"PortSpecs": .. _dockerfile_env: -3.6 ENV -------- +``ENV`` +======= ``ENV `` @@ -262,8 +258,8 @@ from the resulting image. You can view the values using ``docker inspect``, and .. _dockerfile_add: -3.7 ADD -------- +``ADD`` +======= ``ADD `` @@ -329,8 +325,8 @@ The copy obeys the following rules: .. _dockerfile_entrypoint: -3.8 ENTRYPOINT --------------- +``ENTRYPOINT`` +============== ENTRYPOINT has two forms: @@ -378,8 +374,8 @@ this optional but default, you could use a CMD: .. _dockerfile_volume: -3.9 VOLUME ----------- +``VOLUME`` +========== ``VOLUME ["/data"]`` @@ -389,8 +385,8 @@ and mounting instructions via docker client, refer to :ref:`volume_def` document .. _dockerfile_user: -3.10 USER ---------- +``USER`` +======== ``USER daemon`` @@ -399,8 +395,8 @@ the image. .. _dockerfile_workdir: -3.11 WORKDIR ------------- +``WORKDIR`` +=========== ``WORKDIR /path/to/workdir`` @@ -418,8 +414,8 @@ instruction. For example: The output of the final ``pwd`` command in this Dockerfile would be ``/a/b/c``. -3.11 ONBUILD ------------- +``ONBUILD`` +=========== ``ONBUILD [INSTRUCTION]`` @@ -480,7 +476,7 @@ For example you might add something like this: .. _dockerfile_examples: -4. Dockerfile Examples +Dockerfile Examples ====================== .. code-block:: bash From 4c3eb7db675c7f8f15ef0d55e99a6699908f930c Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 7 Apr 2014 23:10:40 -0600 Subject: [PATCH 373/384] Update test-integration-cli bundlescript for consistency with other bundlescripts and slightly more verbose logging of which commands were executed Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/make/test-integration-cli | 49 +++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/hack/make/test-integration-cli b/hack/make/test-integration-cli index 18e4ee6602..b0506d261a 100644 --- a/hack/make/test-integration-cli +++ b/hack/make/test-integration-cli @@ -4,9 +4,6 @@ DEST=$1 set -e -# subshell so that we can export PATH without breaking other things -( -export PATH="$DEST/../binary:$DEST/../dynbinary:$PATH" DOCKER_GRAPHDRIVER=${DOCKER_GRAPHDRIVER:-vfs} DOCKER_EXECDRIVER=${DOCKER_EXECDRIVER:-native} @@ -14,22 +11,30 @@ bundle_test_integration_cli() { go_test_dir ./integration-cli } -if ! command -v docker &> /dev/null; then - echo >&2 'error: binary or dynbinary must be run before test-integration-cli' - false -fi - -echo "running cli integration tests using graphdriver: '$DOCKER_GRAPHDRIVER' and execdriver: '$DOCKER_EXECDRIVER'" -docker -d -D -s $DOCKER_GRAPHDRIVER -e $DOCKER_EXECDRIVER -p $DEST/docker.pid &> $DEST/docker.log & - -# pull the busybox image before running the tests -sleep 2 -docker pull busybox - -bundle_test_integration_cli 2>&1 \ - | tee $DEST/test.log - -DOCKERD_PID=$(cat $DEST/docker.pid) -kill $DOCKERD_PID -wait $DOCKERD_PID || true -) +# subshell so that we can export PATH without breaking other things +( + export PATH="$DEST/../binary:$DEST/../dynbinary:$PATH" + + if ! command -v docker &> /dev/null; then + echo >&2 'error: binary or dynbinary must be run before test-integration-cli' + false + fi + + ( set -x; exec \ + docker --daemon --debug \ + --storage-driver "$DOCKER_GRAPHDRIVER" \ + --exec-driver "$DOCKER_EXECDRIVER" \ + --pidfile "$DEST/docker.pid" \ + &> "$DEST/docker.log" + ) & + + # pull the busybox image before running the tests + sleep 2 + ( set -x; docker pull busybox ) + + bundle_test_integration_cli + + DOCKERD_PID=$(set -x; cat $DEST/docker.pid) + ( set -x; kill $DOCKERD_PID ) + wait $DOCKERD_PID || true +) 2>&1 | tee $DEST/test.log From 8b2bcd9a4bddf1b99ca201d81ac0a67c2b09fbe0 Mon Sep 17 00:00:00 2001 From: shin- Date: Tue, 8 Apr 2014 16:53:16 +0200 Subject: [PATCH 374/384] Added specific error message when hitting 401 over HTTP on push Docker-DCO-1.1-Signed-off-by: Joffrey F (github: shin-) --- registry/registry.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/registry/registry.go b/registry/registry.go index 5ac04f9e7e..817c08afa9 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -417,6 +417,9 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis return fmt.Errorf("Failed to upload metadata: %s", err) } defer res.Body.Close() + if res.StatusCode == 401 && strings.HasPrefix(registry, "http://") { + return utils.NewHTTPRequestError("HTTP code 401, Docker will not send auth headers over HTTP.", res) + } if res.StatusCode != 200 { errBody, err := ioutil.ReadAll(res.Body) if err != nil { From cf655ca98d9c230d02454a39e815fc6b11ac5aed Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Tue, 8 Apr 2014 09:42:47 -0600 Subject: [PATCH 375/384] Update DOCKER_CROSSPLATFORMS to be more readable Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- Dockerfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2de5b34171..ec95bad293 100644 --- a/Dockerfile +++ b/Dockerfile @@ -68,7 +68,10 @@ ENV GOPATH /go:/go/src/github.com/dotcloud/docker/vendor RUN cd /usr/local/go/src && ./make.bash --no-clean 2>&1 # Compile Go for cross compilation -ENV DOCKER_CROSSPLATFORMS linux/386 linux/arm darwin/amd64 darwin/386 freebsd/amd64 freebsd/386 freebsd/arm +ENV DOCKER_CROSSPLATFORMS \ + linux/386 linux/arm \ + darwin/amd64 darwin/386 \ + freebsd/amd64 freebsd/386 freebsd/arm # (set an explicit GOARM of 5 for maximum compatibility) ENV GOARM 5 RUN cd /usr/local/go/src && bash -xc 'for platform in $DOCKER_CROSSPLATFORMS; do GOOS=${platform%/*} GOARCH=${platform##*/} ./make.bash --no-clean 2>&1; done' From 4f828d67f00449182eaada50dfba37e00f8f01ef Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 8 Apr 2014 10:10:51 -0700 Subject: [PATCH 376/384] Backup current docker apparmor profile and replace it with the new one Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- pkg/libcontainer/apparmor/setup.go | 36 +++++++++++++++++++++++++---- runtime/execdriver/native/driver.go | 8 ++++--- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/pkg/libcontainer/apparmor/setup.go b/pkg/libcontainer/apparmor/setup.go index 4c664598ad..548e72f550 100644 --- a/pkg/libcontainer/apparmor/setup.go +++ b/pkg/libcontainer/apparmor/setup.go @@ -2,13 +2,17 @@ package apparmor import ( "fmt" + "io" "io/ioutil" "os" "os/exec" "path" ) -const DefaultProfilePath = "/etc/apparmor.d/docker" +const ( + DefaultProfilePath = "/etc/apparmor.d/docker" +) + const DefaultProfile = ` # AppArmor profile from lxc for containers. @@ -73,14 +77,38 @@ profile docker-default flags=(attach_disconnected,mediate_deleted) { } ` -func InstallDefaultProfile() error { +func InstallDefaultProfile(backupPath string) error { if !IsEnabled() { return nil } - // If the profile already exists, let it be. + // If the profile already exists, check if we already have a backup + // if not, do the backup and override it. (docker 0.10 upgrade changed the apparmor profile) + // see gh#5049, apparmor blocks signals in ubuntu 14.04 if _, err := os.Stat(DefaultProfilePath); err == nil { - return nil + if _, err := os.Stat(backupPath); err == nil { + // If both the profile and the backup are present, do nothing + return nil + } + // Make sure the directory exists + if err := os.MkdirAll(path.Dir(backupPath), 0755); err != nil { + return err + } + + // Create the backup file + f, err := os.Create(backupPath) + if err != nil { + return err + } + defer f.Close() + src, err := os.Open(DefaultProfilePath) + if err != nil { + return err + } + defer src.Close() + if _, err := io.Copy(f, src); err != nil { + return err + } } // Make sure /etc/apparmor.d exists diff --git a/runtime/execdriver/native/driver.go b/runtime/execdriver/native/driver.go index c5a3837615..d18865e508 100644 --- a/runtime/execdriver/native/driver.go +++ b/runtime/execdriver/native/driver.go @@ -21,8 +21,9 @@ import ( ) const ( - DriverName = "native" - Version = "0.1" + DriverName = "native" + Version = "0.1" + BackupApparmorProfilePath = "apparmor/docker.back" // relative to docker root ) func init() { @@ -66,7 +67,8 @@ func NewDriver(root, initPath string) (*driver, error) { if err := os.MkdirAll(root, 0700); err != nil { return nil, err } - if err := apparmor.InstallDefaultProfile(); err != nil { + // native driver root is at docker_root/execdriver/native. Put apparmor at docker_root + if err := apparmor.InstallDefaultProfile(filepath.Join(root, "../..", BackupApparmorProfilePath)); err != nil { return nil, err } return &driver{ From fa5223dab5b9f5cef2a0a341ee5065fec9c6d663 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 8 Apr 2014 11:59:02 -0700 Subject: [PATCH 377/384] Added Fedora installation method Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- hack/install.sh | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/hack/install.sh b/hack/install.sh index 205b57ecc7..575f328292 100755 --- a/hack/install.sh +++ b/hack/install.sh @@ -72,8 +72,35 @@ fi if [ -z "$lsb_dist" ] && [ -r /etc/debian_version ]; then lsb_dist='Debian' fi +if [ -z "$lsb_dist" ] && [ -r /etc/fedora-release ]; then + lsb_dist='Fedora' +fi case "$lsb_dist" in + Fedora) + ( + set -x + $sh_c 'sleep 3; yum -y -q install docker-io' + ) + if command_exists docker && [ -e /var/run/docker.sock ]; then + ( + set -x + $sh_c 'docker run busybox echo "Docker has been successfully installed!"' + ) || true + fi + your_user=your-user + [ "$user" != 'root' ] && your_user="$user" + echo + echo 'If you would like to use Docker as a non-root user, you should now consider' + echo 'adding your user to the "docker" group with something like:' + echo + echo ' sudo usermod -aG docker' $your_user + echo + echo 'Remember that you will have to log out and back in for this to take effect!' + echo + exit 0 + ;; + Ubuntu|Debian) export DEBIAN_FRONTEND=noninteractive From a2aa902ec194169431fea6784c4a7cdab25aaf24 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 8 Apr 2014 11:59:36 -0700 Subject: [PATCH 378/384] Removed extra whitespace Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- hack/install.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/hack/install.sh b/hack/install.sh index 575f328292..43248cf2c0 100755 --- a/hack/install.sh +++ b/hack/install.sh @@ -103,7 +103,7 @@ case "$lsb_dist" in Ubuntu|Debian) export DEBIAN_FRONTEND=noninteractive - + did_apt_get_update= apt_get_update() { if [ -z "$did_apt_get_update" ]; then @@ -111,21 +111,21 @@ case "$lsb_dist" in did_apt_get_update=1 fi } - + # 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)" - + apt_get_update ( set -x; $sh_c 'sleep 3; apt-get install -y -q '"$kern_extras" ) || true - + if ! grep -q aufs /proc/filesystems && ! $sh_c 'modprobe aufs'; then echo >&2 'Warning: tried to install '"$kern_extras"' (for AUFS)' echo >&2 ' but we still have no AUFS. Docker may not work. Proceeding anyways!' ( set -x; sleep 10 ) fi fi - + if [ ! -e /usr/lib/apt/methods/https ]; then apt_get_update ( set -x; $sh_c 'sleep 3; apt-get install -y -q apt-transport-https' ) @@ -165,7 +165,7 @@ case "$lsb_dist" in echo exit 0 ;; - + Gentoo) if [ "$url" = "https://test.docker.io/" ]; then echo >&2 @@ -180,7 +180,7 @@ case "$lsb_dist" in echo >&2 exit 1 fi - + ( set -x $sh_c 'sleep 3; emerge app-emulation/docker' From 168f8aba74d9c2996acec6fe1b93a2301523e305 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 8 Apr 2014 09:49:48 -0700 Subject: [PATCH 379/384] Early deprecation warning for 'docker commit --run' Warn users of the planned deprecation of 'docker commit --run', and hide it from the docs and usage message. The option continues to work. Note that an alternative to 'commit --run' is being implemented but is not yet available. We are printing the warning anyway because on the basis that it never hurts to give more advance warning. The 'commit --run' flag is a leftover from the very early days of Docker, and has several problems: 1) It is very user unfriendly. You have to pass a literal json dict which is poorly documented and changes regularly (see PortSpecs vs ExposedPorts). The merge behavior is not clear and also changes regularly. it's not possible to unset a value. 2) It overlaps with the Dockerfile syntax. There are 2 ways to set a default command, expose a port or change an env variable. Some things can be done in a Dockerfile but not in --run. Some things can be done in --run but not in a Dockerfile. It would be better to push a single syntax, allow using it both in a file and via the command line, and make improvements in a single place. 3) It exposes data structures which should not be publicly exposed. There are several planned improvements to Docker which require moving around the content and schema of the various Config, Image and Container structures. The less of those we expose in public interfaces, the easier it is to move things around without a reverse compatibility nightmare. Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- api/client/commands.go | 4 +- docs/sources/reference/builder.rst | 12 ++- docs/sources/reference/commandline/cli.rst | 92 ---------------------- 3 files changed, 8 insertions(+), 100 deletions(-) diff --git a/api/client/commands.go b/api/client/commands.go index bd23b3c7fd..ef6bbd055a 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -1433,7 +1433,8 @@ func (cli *DockerCli) CmdCommit(args ...string) error { cmd := cli.Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes") flComment := cmd.String([]string{"m", "-message"}, "", "Commit message") flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (eg. \"John Hannibal Smith \"") - flConfig := cmd.String([]string{"#run", "-run"}, "", "Config automatically applied when the image is run. "+`(ex: --run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`) + // FIXME: --run is deprecated, it will be replaced with inline Dockerfile commands. + flConfig := cmd.String([]string{"#run", "#-run"}, "", "this option is deprecated and will be removed in a future version in favor of inline Dockerfile-compatible commands") if err := cmd.Parse(args); err != nil { return nil } @@ -1471,6 +1472,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error { env engine.Env ) if *flConfig != "" { + fmt.Fprintf(cli.err, "WARNING: 'commit --run' is deprecated and will be removed in a future version, in favor of inline Dockerfile-compatible commands.\n") config = &runconfig.Config{} if err := json.Unmarshal([]byte(*flConfig), config); err != nil { return err diff --git a/docs/sources/reference/builder.rst b/docs/sources/reference/builder.rst index 4858a0b17c..e8897d1b09 100644 --- a/docs/sources/reference/builder.rst +++ b/docs/sources/reference/builder.rst @@ -188,9 +188,7 @@ omit the executable, in which case you must specify an ENTRYPOINT as well. When used in the shell or exec formats, the ``CMD`` instruction sets -the command to be executed when running the image. This is -functionally equivalent to running ``docker commit --run '{"Cmd": -}'`` outside the builder. +the command to be executed when running the image. If you use the *shell* form of the CMD, then the ```` will execute in ``/bin/sh -c``: @@ -230,10 +228,10 @@ override the default specified in CMD. ``EXPOSE [...]`` -The ``EXPOSE`` instruction exposes ports for use within links. This is -functionally equivalent to running ``docker commit --run '{"PortSpecs": -["", ""]}'`` outside the builder. Refer to -:ref:`port_redirection` for detailed information. +The ``EXPOSE`` instructions informs Docker that the container will listen +on the specified network ports at runtime. Docker uses this information +to interconnect containers using links (see :ref:`links `), +and to setup port redirection on the host system (see :ref:`port_redirection`). .. _dockerfile_env: diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index c0487302dd..c0df5f8175 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -316,8 +316,6 @@ by using the ``git://`` schema. -m, --message="": Commit message -a, --author="": Author (eg. "John Hannibal Smith " - --run="": Configuration changes to be applied when the image is launched with `docker run`. - (ex: --run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}') .. _cli_commit_examples: @@ -336,96 +334,6 @@ Commit an existing container REPOSITORY TAG ID CREATED VIRTUAL SIZE SvenDowideit/testimage version3 f5283438590d 16 seconds ago 335.7 MB -Change the command that a container runs -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Sometimes you have an application container running just a service and you need -to make a quick change and then change it back. - -In this example, we run a container with ``ls`` and then change the image to -run ``ls /etc``. - -.. code-block:: bash - - $ docker run -t --name test ubuntu ls - bin boot dev etc home lib lib64 media mnt opt proc root run sbin selinux srv sys tmp usr var - $ docker commit --run='{"Cmd": ["ls","/etc"]}' test test2 - 933d16de9e70005304c1717b5c6f2f39d6fd50752834c6f34a155c70790011eb - $ docker run -t test2 - adduser.conf gshadow login.defs rc0.d - alternatives gshadow- logrotate.d rc1.d - apt host.conf lsb-base rc2.d - ... - -Merged configs example -...................... - -Say you have a Dockerfile like so: - -.. code-block:: bash - - ENV MYVAR foobar - RUN apt-get install openssh - EXPOSE 22 - CMD ["/usr/sbin/sshd -D"] - ... - -If you run that, make some changes, and then commit, Docker will merge the environment variable and exposed port configuration settings with any that you specify in the --run= option. This is a change from Docker 0.8.0 and prior where no attempt was made to preserve any existing configuration on commit. - -.. code-block:: bash - - $ docker build -t me/foo . - $ docker run -t -i me/foo /bin/bash - foo-container$ [make changes in the container] - foo-container$ exit - $ docker commit --run='{"Cmd": ["ls"]}' [container-id] me/bar - ... - -The me/bar image will now have port 22 exposed, MYVAR env var set to 'foobar', and its default command will be ["ls"]. - -Note that this is currently a shallow merge. So, for example, if you had specified a new port spec in the --run= config above, that would have clobbered the 'EXPOSE 22' setting from the parent container. - -Full --run example -.................. - -The ``--run`` JSON hash changes the ``Config`` section when running ``docker inspect CONTAINERID`` -or ``config`` when running ``docker inspect IMAGEID``. Existing configuration key-values that are -not overridden in the JSON hash will be merged in. - -(Multiline is okay within a single quote ``'``) - -.. code-block:: bash - - $ sudo docker commit --run=' - { - "Entrypoint" : null, - "Privileged" : false, - "User" : "", - "VolumesFrom" : "", - "Cmd" : ["cat", "-e", "/etc/resolv.conf"], - "Dns" : ["8.8.8.8", "8.8.4.4"], - "DnsSearch" : ["example.com"], - "MemorySwap" : 0, - "AttachStdin" : false, - "AttachStderr" : false, - "CpuShares" : 0, - "OpenStdin" : false, - "Volumes" : null, - "Hostname" : "122612f45831", - "PortSpecs" : ["22", "80", "443"], - "Image" : "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", - "Tty" : false, - "Env" : [ - "HOME=/", - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" - ], - "StdinOnce" : false, - "Domainname" : "", - "WorkingDir" : "/", - "NetworkDisabled" : false, - "Memory" : 0, - "AttachStdout" : false - }' $CONTAINER_ID .. _cli_cp: From 77a04357a1b66b9e5b2bae2efc0192b927f926fe Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 8 Apr 2014 20:56:30 +0000 Subject: [PATCH 380/384] Remove restart ghost test We do not allow ghosts anymore and this test does not add any value Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- integration/container_test.go | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/integration/container_test.go b/integration/container_test.go index c64f9e610b..d089e7dc45 100644 --- a/integration/container_test.go +++ b/integration/container_test.go @@ -1714,31 +1714,3 @@ func TestMultipleVolumesFrom(t *testing.T) { t.Fail() } } - -func TestRestartGhost(t *testing.T) { - runtime := mkRuntime(t) - defer nuke(runtime) - - container, _, err := runtime.Create( - &runconfig.Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"}, - Volumes: map[string]struct{}{"/test": {}}, - }, - "", - ) - if err != nil { - t.Fatal(err) - } - - if err := container.Kill(); err != nil { - t.Fatal(err) - } - - container.State.SetGhost(true) - - _, err = container.Output() - if err != nil { - t.Fatal(err) - } -} From 9260c06f7a7cb172205dc45af96870ec0d02ebcd Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 8 Apr 2014 20:58:19 +0000 Subject: [PATCH 381/384] remove double deprecation warning Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api/client/commands.go | 1 - 1 file changed, 1 deletion(-) diff --git a/api/client/commands.go b/api/client/commands.go index ef6bbd055a..443917d3fb 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -1472,7 +1472,6 @@ func (cli *DockerCli) CmdCommit(args ...string) error { env engine.Env ) if *flConfig != "" { - fmt.Fprintf(cli.err, "WARNING: 'commit --run' is deprecated and will be removed in a future version, in favor of inline Dockerfile-compatible commands.\n") config = &runconfig.Config{} if err := json.Unmarshal([]byte(*flConfig), config); err != nil { return err From af9746412b6070063f105ae97eba1f8fbd56bd22 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 8 Apr 2014 09:26:09 +0000 Subject: [PATCH 382/384] Move volumesfrom to hostconfig This also migrates the volumes from integration tests into the new cli integration test framework. Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- integration-cli/docker_cli_run_test.go | 72 +++++++ integration/container_test.go | 262 ------------------------- runconfig/compare.go | 3 +- runconfig/config.go | 2 - runconfig/config_test.go | 41 ++-- runconfig/hostconfig.go | 2 + runconfig/merge.go | 3 - runconfig/parse.go | 2 +- runtime/volumes.go | 7 +- 9 files changed, 92 insertions(+), 302 deletions(-) diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index d085574741..b0805dd35c 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -312,3 +312,75 @@ func TestVolumesMountedAsReadonly(t *testing.T) { logDone("run - volumes as readonly mount") } + +func TestVolumesFromInReadonlyMode(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--name", "parent", "-v", "/test", "busybox", "true") + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + + cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent:ro", "busybox", "touch", "/test/file") + if code, err := runCommand(cmd); err == nil || code == 0 { + t.Fatalf("run should fail because volume is ro: exit code %d", code) + } + + deleteAllContainers() + + logDone("run - volumes from as readonly mount") +} + +// Regression test for #1201 +func TestVolumesFromInReadWriteMode(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--name", "parent", "-v", "/test", "busybox", "true") + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + + cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent", "busybox", "touch", "/test/file") + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + + deleteAllContainers() + + logDone("run - volumes from as read write mount") +} + +// Test for #1351 +func TestApplyVolumesFromBeforeVolumes(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--name", "parent", "-v", "/test", "busybox", "touch", "/test/foo") + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + + cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent", "-v", "/test", "busybox", "cat", "/test/foo") + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + + deleteAllContainers() + + logDone("run - volumes from mounted first") +} + +func TestMultipleVolumesFrom(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--name", "parent1", "-v", "/test", "busybox", "touch", "/test/foo") + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + + cmd = exec.Command(dockerBinary, "run", "--name", "parent2", "-v", "/other", "busybox", "touch", "/other/bar") + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + + cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent1", "--volumes-from", "parent2", + "busybox", "sh", "-c", "cat /test/foo && cat /other/bar") + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + + deleteAllContainers() + + logDone("run - multiple volumes from") +} diff --git a/integration/container_test.go b/integration/container_test.go index d089e7dc45..43f51c1e5f 100644 --- a/integration/container_test.go +++ b/integration/container_test.go @@ -1273,123 +1273,6 @@ func TestBindMounts(t *testing.T) { } } -// Test that -volumes-from supports both read-only mounts -func TestFromVolumesInReadonlyMode(t *testing.T) { - runtime := mkRuntime(t) - defer nuke(runtime) - container, _, err := runtime.Create( - &runconfig.Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"/bin/echo", "-n", "foobar"}, - Volumes: map[string]struct{}{"/test": {}}, - }, - "", - ) - if err != nil { - t.Fatal(err) - } - defer runtime.Destroy(container) - _, err = container.Output() - if err != nil { - t.Fatal(err) - } - if !container.VolumesRW["/test"] { - t.Fail() - } - - container2, _, err := runtime.Create( - &runconfig.Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"/bin/echo", "-n", "foobar"}, - VolumesFrom: container.ID + ":ro", - }, - "", - ) - if err != nil { - t.Fatal(err) - } - defer runtime.Destroy(container2) - - _, err = container2.Output() - if err != nil { - t.Fatal(err) - } - - if container.Volumes["/test"] != container2.Volumes["/test"] { - t.Logf("container volumes do not match: %s | %s ", - container.Volumes["/test"], - container2.Volumes["/test"]) - t.Fail() - } - - _, exists := container2.VolumesRW["/test"] - if !exists { - t.Logf("container2 is missing '/test' volume: %s", container2.VolumesRW) - t.Fail() - } - - if container2.VolumesRW["/test"] != false { - t.Log("'/test' volume mounted in read-write mode, expected read-only") - t.Fail() - } -} - -// Test that VolumesRW values are copied to the new container. Regression test for #1201 -func TestVolumesFromReadonlyMount(t *testing.T) { - runtime := mkRuntime(t) - defer nuke(runtime) - container, _, err := runtime.Create( - &runconfig.Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"/bin/echo", "-n", "foobar"}, - Volumes: map[string]struct{}{"/test": {}}, - }, - "", - ) - if err != nil { - t.Fatal(err) - } - defer runtime.Destroy(container) - _, err = container.Output() - if err != nil { - t.Fatal(err) - } - if !container.VolumesRW["/test"] { - t.Fail() - } - - container2, _, err := runtime.Create( - &runconfig.Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"/bin/echo", "-n", "foobar"}, - VolumesFrom: container.ID, - }, - "", - ) - if err != nil { - t.Fatal(err) - } - defer runtime.Destroy(container2) - - _, err = container2.Output() - if err != nil { - t.Fatal(err) - } - - if container.Volumes["/test"] != container2.Volumes["/test"] { - t.Fail() - } - - actual, exists := container2.VolumesRW["/test"] - if !exists { - t.Fail() - } - - if container.VolumesRW["/test"] != actual { - t.Fail() - } -} - // Test that restarting a container with a volume does not create a new volume on restart. Regression test for #819. func TestRestartWithVolumes(t *testing.T) { runtime := mkRuntime(t) @@ -1434,73 +1317,6 @@ func TestRestartWithVolumes(t *testing.T) { } } -// Test for #1351 -func TestVolumesFromWithVolumes(t *testing.T) { - runtime := mkRuntime(t) - defer nuke(runtime) - - container, _, err := runtime.Create(&runconfig.Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"}, - Volumes: map[string]struct{}{"/test": {}}, - }, - "", - ) - if err != nil { - t.Fatal(err) - } - defer runtime.Destroy(container) - - for key := range container.Config.Volumes { - if key != "/test" { - t.Fail() - } - } - - _, err = container.Output() - if err != nil { - t.Fatal(err) - } - - expected := container.Volumes["/test"] - if expected == "" { - t.Fail() - } - - container2, _, err := runtime.Create( - &runconfig.Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"cat", "/test/foo"}, - VolumesFrom: container.ID, - Volumes: map[string]struct{}{"/test": {}}, - }, - "", - ) - if err != nil { - t.Fatal(err) - } - defer runtime.Destroy(container2) - - output, err := container2.Output() - if err != nil { - t.Fatal(err) - } - - if string(output) != "bar" { - t.Fail() - } - - if container.Volumes["/test"] != container2.Volumes["/test"] { - t.Fail() - } - - // Ensure it restarts successfully - _, err = container2.Output() - if err != nil { - t.Fatal(err) - } -} - func TestContainerNetwork(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) @@ -1636,81 +1452,3 @@ func TestUnprivilegedCannotMount(t *testing.T) { t.Fatal("Could mount into secure container") } } - -func TestMultipleVolumesFrom(t *testing.T) { - runtime := mkRuntime(t) - defer nuke(runtime) - - container, _, err := runtime.Create(&runconfig.Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"}, - Volumes: map[string]struct{}{"/test": {}}, - }, - "", - ) - if err != nil { - t.Fatal(err) - } - defer runtime.Destroy(container) - - for key := range container.Config.Volumes { - if key != "/test" { - t.Fail() - } - } - - _, err = container.Output() - if err != nil { - t.Fatal(err) - } - - expected := container.Volumes["/test"] - if expected == "" { - t.Fail() - } - - container2, _, err := runtime.Create( - &runconfig.Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"sh", "-c", "echo -n bar > /other/foo"}, - Volumes: map[string]struct{}{"/other": {}}, - }, - "", - ) - if err != nil { - t.Fatal(err) - } - defer runtime.Destroy(container2) - - for key := range container2.Config.Volumes { - if key != "/other" { - t.FailNow() - } - } - if _, err := container2.Output(); err != nil { - t.Fatal(err) - } - - container3, _, err := runtime.Create( - &runconfig.Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"/bin/echo", "-n", "foobar"}, - VolumesFrom: strings.Join([]string{container.ID, container2.ID}, ","), - }, "") - - if err != nil { - t.Fatal(err) - } - defer runtime.Destroy(container3) - - if _, err := container3.Output(); err != nil { - t.Fatal(err) - } - - if container3.Volumes["/test"] != container.Volumes["/test"] { - t.Fail() - } - if container3.Volumes["/other"] != container2.Volumes["/other"] { - t.Fail() - } -} diff --git a/runconfig/compare.go b/runconfig/compare.go index 122687f669..5c1bf46575 100644 --- a/runconfig/compare.go +++ b/runconfig/compare.go @@ -14,8 +14,7 @@ func Compare(a, b *Config) bool { a.MemorySwap != b.MemorySwap || a.CpuShares != b.CpuShares || a.OpenStdin != b.OpenStdin || - a.Tty != b.Tty || - a.VolumesFrom != b.VolumesFrom { + a.Tty != b.Tty { return false } if len(a.Cmd) != len(b.Cmd) || diff --git a/runconfig/config.go b/runconfig/config.go index 9db545976b..33a7882b6f 100644 --- a/runconfig/config.go +++ b/runconfig/config.go @@ -27,7 +27,6 @@ type Config struct { Cmd []string Image string // Name of the image as it was passed by the operator (eg. could be symbolic) Volumes map[string]struct{} - VolumesFrom string WorkingDir string Entrypoint []string NetworkDisabled bool @@ -49,7 +48,6 @@ func ContainerConfigFromJob(job *engine.Job) *Config { OpenStdin: job.GetenvBool("OpenStdin"), StdinOnce: job.GetenvBool("StdinOnce"), Image: job.Getenv("Image"), - VolumesFrom: job.Getenv("VolumesFrom"), WorkingDir: job.Getenv("WorkingDir"), NetworkDisabled: job.GetenvBool("NetworkDisabled"), } diff --git a/runconfig/config_test.go b/runconfig/config_test.go index d5ab75b3b9..f71528ff8e 100644 --- a/runconfig/config_test.go +++ b/runconfig/config_test.go @@ -163,37 +163,25 @@ func TestCompare(t *testing.T) { volumes1 := make(map[string]struct{}) volumes1["/test1"] = struct{}{} config1 := Config{ - PortSpecs: []string{"1111:1111", "2222:2222"}, - Env: []string{"VAR1=1", "VAR2=2"}, - VolumesFrom: "11111111", - Volumes: volumes1, + PortSpecs: []string{"1111:1111", "2222:2222"}, + Env: []string{"VAR1=1", "VAR2=2"}, + Volumes: volumes1, } config3 := Config{ - PortSpecs: []string{"0000:0000", "2222:2222"}, - Env: []string{"VAR1=1", "VAR2=2"}, - VolumesFrom: "11111111", - Volumes: volumes1, - } - config4 := Config{ - PortSpecs: []string{"0000:0000", "2222:2222"}, - Env: []string{"VAR1=1", "VAR2=2"}, - VolumesFrom: "22222222", - Volumes: volumes1, + PortSpecs: []string{"0000:0000", "2222:2222"}, + Env: []string{"VAR1=1", "VAR2=2"}, + Volumes: volumes1, } volumes2 := make(map[string]struct{}) volumes2["/test2"] = struct{}{} config5 := Config{ - PortSpecs: []string{"0000:0000", "2222:2222"}, - Env: []string{"VAR1=1", "VAR2=2"}, - VolumesFrom: "11111111", - Volumes: volumes2, + PortSpecs: []string{"0000:0000", "2222:2222"}, + Env: []string{"VAR1=1", "VAR2=2"}, + Volumes: volumes2, } if Compare(&config1, &config3) { t.Fatalf("Compare should return false, PortSpecs are different") } - if Compare(&config1, &config4) { - t.Fatalf("Compare should return false, VolumesFrom are different") - } if Compare(&config1, &config5) { t.Fatalf("Compare should return false, Volumes are different") } @@ -207,10 +195,9 @@ func TestMerge(t *testing.T) { volumesImage["/test1"] = struct{}{} volumesImage["/test2"] = struct{}{} configImage := &Config{ - PortSpecs: []string{"1111:1111", "2222:2222"}, - Env: []string{"VAR1=1", "VAR2=2"}, - VolumesFrom: "1111", - Volumes: volumesImage, + PortSpecs: []string{"1111:1111", "2222:2222"}, + Env: []string{"VAR1=1", "VAR2=2"}, + Volumes: volumesImage, } volumesUser := make(map[string]struct{}) @@ -251,10 +238,6 @@ func TestMerge(t *testing.T) { } } - if configUser.VolumesFrom != "1111" { - t.Fatalf("Expected VolumesFrom to be 1111, found %s", configUser.VolumesFrom) - } - ports, _, err := nat.ParsePortSpecs([]string{"0000"}) if err != nil { t.Error(err) diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 5c5e291bad..127c06d9cb 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -16,6 +16,7 @@ type HostConfig struct { PublishAllPorts bool Dns []string DnsSearch []string + VolumesFrom string } func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { @@ -23,6 +24,7 @@ func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { ContainerIDFile: job.Getenv("ContainerIDFile"), Privileged: job.GetenvBool("Privileged"), PublishAllPorts: job.GetenvBool("PublishAllPorts"), + VolumesFrom: job.Getenv("VolumesFrom"), } job.GetenvJson("LxcConf", &hostConfig.LxcConf) job.GetenvJson("PortBindings", &hostConfig.PortBindings) diff --git a/runconfig/merge.go b/runconfig/merge.go index 7a089855b2..1240dbcacd 100644 --- a/runconfig/merge.go +++ b/runconfig/merge.go @@ -100,9 +100,6 @@ func Merge(userConf, imageConf *Config) error { if userConf.WorkingDir == "" { userConf.WorkingDir = imageConf.WorkingDir } - if userConf.VolumesFrom == "" { - userConf.VolumesFrom = imageConf.VolumesFrom - } if userConf.Volumes == nil || len(userConf.Volumes) == 0 { userConf.Volumes = imageConf.Volumes } else { diff --git a/runconfig/parse.go b/runconfig/parse.go index 58d6c9ebb9..b76f59a360 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -215,7 +215,6 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf Cmd: runCmd, Image: image, Volumes: flVolumes.GetMap(), - VolumesFrom: strings.Join(flVolumesFrom.GetAll(), ","), Entrypoint: entrypoint, WorkingDir: *flWorkingDir, } @@ -230,6 +229,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf PublishAllPorts: *flPublishAll, Dns: flDns.GetAll(), DnsSearch: flDnsSearch.GetAll(), + VolumesFrom: strings.Join(flVolumesFrom.GetAll(), ","), } if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit { diff --git a/runtime/volumes.go b/runtime/volumes.go index 40db177174..e74442e1b5 100644 --- a/runtime/volumes.go +++ b/runtime/volumes.go @@ -59,8 +59,9 @@ func setupMountsForContainer(container *Container, envPath string) error { } func applyVolumesFrom(container *Container) error { - if container.Config.VolumesFrom != "" { - for _, containerSpec := range strings.Split(container.Config.VolumesFrom, ",") { + volumesFrom := container.hostConfig.VolumesFrom + if volumesFrom != "" { + for _, containerSpec := range strings.Split(volumesFrom, ",") { var ( mountRW = true specParts = strings.SplitN(containerSpec, ":", 2) @@ -68,7 +69,7 @@ func applyVolumesFrom(container *Container) error { switch len(specParts) { case 0: - return fmt.Errorf("Malformed volumes-from specification: %s", container.Config.VolumesFrom) + return fmt.Errorf("Malformed volumes-from specification: %s", volumesFrom) case 2: switch specParts[1] { case "ro": From b4f2821e6d4ba6f6073365a244681df21f5d4472 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 8 Apr 2014 10:02:17 +0000 Subject: [PATCH 383/384] Make volumes-from a slice instead of string split Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runconfig/hostconfig.go | 6 ++++-- runconfig/parse.go | 2 +- runtime/volumes.go | 6 +++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 127c06d9cb..3235bf1f4e 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -16,7 +16,7 @@ type HostConfig struct { PublishAllPorts bool Dns []string DnsSearch []string - VolumesFrom string + VolumesFrom []string } func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { @@ -24,7 +24,6 @@ func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { ContainerIDFile: job.Getenv("ContainerIDFile"), Privileged: job.GetenvBool("Privileged"), PublishAllPorts: job.GetenvBool("PublishAllPorts"), - VolumesFrom: job.Getenv("VolumesFrom"), } job.GetenvJson("LxcConf", &hostConfig.LxcConf) job.GetenvJson("PortBindings", &hostConfig.PortBindings) @@ -40,5 +39,8 @@ func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { if DnsSearch := job.GetenvList("DnsSearch"); DnsSearch != nil { hostConfig.DnsSearch = DnsSearch } + if VolumesFrom := job.GetenvList("VolumesFrom"); VolumesFrom != nil { + hostConfig.VolumesFrom = VolumesFrom + } return hostConfig } diff --git a/runconfig/parse.go b/runconfig/parse.go index b76f59a360..d395b49e80 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -229,7 +229,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf PublishAllPorts: *flPublishAll, Dns: flDns.GetAll(), DnsSearch: flDnsSearch.GetAll(), - VolumesFrom: strings.Join(flVolumesFrom.GetAll(), ","), + VolumesFrom: flVolumesFrom.GetAll(), } if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit { diff --git a/runtime/volumes.go b/runtime/volumes.go index e74442e1b5..004f1bb024 100644 --- a/runtime/volumes.go +++ b/runtime/volumes.go @@ -60,8 +60,8 @@ func setupMountsForContainer(container *Container, envPath string) error { func applyVolumesFrom(container *Container) error { volumesFrom := container.hostConfig.VolumesFrom - if volumesFrom != "" { - for _, containerSpec := range strings.Split(volumesFrom, ",") { + if len(volumesFrom) > 0 { + for _, containerSpec := range volumesFrom { var ( mountRW = true specParts = strings.SplitN(containerSpec, ":", 2) @@ -69,7 +69,7 @@ func applyVolumesFrom(container *Container) error { switch len(specParts) { case 0: - return fmt.Errorf("Malformed volumes-from specification: %s", volumesFrom) + return fmt.Errorf("Malformed volumes-from specification: %s", containerSpec) case 2: switch specParts[1] { case "ro": From dc9c28f51d669d6b09e81c2381f800f1a33bb659 Mon Sep 17 00:00:00 2001 From: unclejack Date: Wed, 9 Apr 2014 00:49:33 +0300 Subject: [PATCH 384/384] Bump version to v0.10.0 Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- CHANGELOG.md | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++ VERSION | 2 +- 2 files changed, 138 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8ea94361b..8743d3a7db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,142 @@ # Changelog +## 0.10.0 (2014-04-08) + +#### Builder +- Fix printing multiple messages on a single line. Fixes broken output during builds. +- Follow symlinks inside container's root for ADD build instructions. +- Fix EXPOSE caching. + +#### Documentation +- Add the new options of `docker ps` to the documentation. +- Add the options of `docker restart` to the documentation. +- Update daemon docs and help messages for --iptables and --ip-forward. +- Updated apt-cacher-ng docs example. +- Remove duplicate description of --mtu from docs. +- Add missing -t and -v for `docker images` to the docs. +- Add fixes to the cli docs. +- Update libcontainer docs. +- Update images in docs to remove references to AUFS and LXC. +- Update the nodejs_web_app in the docs to use the new epel RPM address. +- Fix external link on security of containers. +- Update remote API docs. +- Add image size to history docs. +- Be explicit about binding to all interfaces in redis example. +- Document DisableNetwork flag in the 1.10 remote api. +- Document that `--lxc-conf` is lxc only. +- Add chef usage documentation. +- Add example for an image with multiple for `docker load`. +- Explain what `docker run -a` does in the docs. + +#### Contrib +- Add variable for DOCKER_LOGFILE to sysvinit and use append instead of overwrite in opening the logfile. +- Fix init script cgroup mounting workarounds to be more similar to cgroupfs-mount and thus work properly. +- Remove inotifywait hack from the upstart host-integration example because it's not necessary any more. +- Add check-config script to contrib. +- Fix fish shell completion. + +#### Hack +* Clean up "go test" output from "make test" to be much more readable/scannable. +* Excluse more "definitely not unit tested Go source code" directories from hack/make/test. ++ Generate md5 and sha256 hashes when building, and upload them via hack/release.sh. +- Include contributed completions in Ubuntu PPA. ++ Add cli integration tests. +* Add tweaks to the hack scripts to make them simpler. + +#### Remote API ++ Add TLS auth support for API. +* Move git clone from daemon to client. +- Fix content-type detection in docker cp. +* Split API into 2 go packages. + +#### Runtime +* Support hairpin NAT without going through Docker server. +- devicemapper: succeed immediately when removing non-existing devices. +- devicemapper: improve handling of devicemapper devices (add per device lock, increase sleep time and unlock while sleeping). +- devicemapper: increase timeout in waitClose to 10 seconds. +- devicemapper: ensure we shut down thin pool cleanly. +- devicemapper: pass info, rather than hash to activateDeviceIfNeeded, deactivateDevice, setInitialized, deleteDevice. +- devicemapper: avoid AB-BA deadlock. +- devicemapper: make shutdown better/faster. +- improve alpha sorting in mflag. +- Remove manual http cookie management because the cookiejar is being used. +- Use BSD raw mode on Darwin. Fixes nano, tmux and others. +- Add FreeBSD support for the client. +- Merge auth package into registry. +- Add deprecation warning for -t on `docker pull`. +- Remove goroutine leak on error. +- Update parseLxcInfo to comply with new lxc1.0 format. +- Fix attach exit on darwin. +- Improve deprecation message. +- Retry to retrieve the layer metadata up to 5 times for `docker pull`. +- Only unshare the mount namespace for execin. +- Merge existing config when committing. +- 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. +- Mount cgroups automatically if they're not mounted already. +- Use mock for search tests. +- Update to double-dash everywhere. +- Move .dockerenv parsing to lxc driver. +- Move all bind-mounts in the container inside the namespace. +- Don't use separate bind mount for container. +- Always symlink /dev/ptmx for libcontainer. +- Don't kill by pid for other drivers. +- Add initial logging to libcontainer. +* Sort by port in `docker ps`. +- Move networking drivers into runtime top level package. ++ Add --no-prune to `docker rmi`. ++ Add time since exit in `docker ps`. +- graphdriver: add build tags. +- Prevent allocation of previously allocated ports & prevent improve port allocation. +* Add support for --since/--before in `docker ps`. +- Clean up container stop. ++ Add support for configurable dns search domains. +- Add support for relative WORKDIR instructions. +- Add --output flag for docker save. +- Remove duplication of DNS entries in config merging. +- Add cpuset.cpus to cgroups and native driver options. +- Remove docker-ci. +- Promote btrfs. btrfs is no longer considered experimental. +- Add --input flag to `docker load`. +- Return error when existing bridge doesn't match IP address. +- Strip comments before parsing line continuations to avoid interpreting instructions as comments. +- Fix TestOnlyLoopbackExistsWhenUsingDisableNetworkOption to ignore "DOWN" interfaces. +- Add systemd implementation of cgroups and make containers show up as systemd units. +- Fix commit and import when no repository is specified. +- Remount /var/lib/docker as --private to fix scaling issue. +- Use the environment's proxy when pinging the remote registry. +- Reduce error level from harmless errors. +* Allow --volumes-from to be individual files. +- Fix expanding buffer in StdCopy. +- Set error regardless of attach or stdin. This fixes #3364. +- Add support for --env-file to load environment variables from files. +- Symlink /etc/mtab and /proc/mounts. +- Allow pushing a single tag. +- Shut down containers cleanly at shutdown and wait forever for the containers to shut down. This makes container shutdown on daemon shutdown work properly via SIGTERM. +- Don't throw error when starting an already running container. +- Fix dynamic port allocation limit. +- remove setupDev from libcontainer. +- Add API version to `docker version`. +- Return correct exit code when receiving signal and make SIGQUIT quit without cleanup. +- Fix --volumes-from mount failure. +- Allow non-privileged containers to create device nodes. +- Skip login tests because of external dependency on a hosted service. +- Deprecate `docker images --tree` and `docker images --viz`. +- Deprecate `docker insert`. +- Include base abstraction for apparmor. This fixes some apparmor related problems on Ubuntu 14.04. +- Add specific error message when hitting 401 over HTTP on push. +- Fix absolute volume check. +- Remove volumes-from from the config. +- Move DNS options to hostconfig. +- Update the apparmor profile for libcontainer. +- Add deprecation notice for `docker commit -run`. + ## 0.9.1 (2014-03-24) #### Builder diff --git a/VERSION b/VERSION index dc9bff91aa..78bc1abd14 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.1-dev +0.10.0
7n%yi5Ol+c>l@ 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

Qaq@mJsoofN1vaFDF{aH_-}l< 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 diff --git a/docs/sources/terms/images/docker-filesystems.svg b/docs/sources/terms/images/docker-filesystems.svg index d41aff2522..dc4dc8e687 100644 --- a/docs/sources/terms/images/docker-filesystems.svg +++ b/docs/sources/terms/images/docker-filesystems.svg @@ -11,7 +11,7 @@ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:export-ydpi="90" inkscape:export-xdpi="90" - inkscape:export-filename="/Users/arothfusz/src/metalivedev/docker/docs/sources/terms/images/docker-filesystems-multiroot.png" + inkscape:export-filename="/Users/arothfusz/src/metalivedev/dockerclone/docs/sources/terms/images/docker-filesystems-busyboxrw.png" sodipodi:docname="docker-filesystems.svg" width="800" height="600" @@ -26,7 +26,7 @@ inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="0.82666667" - inkscape:cx="236.08871" + inkscape:cx="346.87978" inkscape:cy="300" inkscape:document-units="px" inkscape:current-layer="layer2" @@ -149,7 +149,7 @@ image/svg+xml - + @@ -294,67 +294,6 @@ d="m 514.91047,422.62215 c 0,0 -1.06434,42.27288 -1.06434,42.27288 0,0 4.45362,-2.8241 4.45362,-2.8241 0.2761,-0.17507 0.46813,-0.15759 0.57629,0.0523 0.10868,0.18619 0.15712,0.50328 0.14534,0.95133 -0.0112,0.42443 -0.0782,0.81493 -0.20113,1.17164 -0.12299,0.35687 -0.32235,0.62363 -0.59831,0.80035 0,0 -10.15763,6.50487 -10.15763,6.50487 -0.27917,0.17878 -0.476,0.16246 -0.5903,-0.0494 -0.11437,-0.21191 -0.16642,-0.53506 -0.15609,-0.96944 0.0109,-0.45857 0.0801,-0.85922 0.20776,-1.20182 0.12814,-0.36656 0.33197,-0.63844 0.61129,-0.81556 0,0 4.56188,-2.89274 4.56188,-2.89274 0,0 0.97884,-39.26779 0.97884,-39.26779 0,0 -3.35907,1.85407 -3.35907,1.85407 -0.27977,0.15447 -0.48159,0.1208 -0.60529,-0.10124 -0.11445,-0.22726 -0.16609,-0.57399 -0.15489,-1.04015 0.0106,-0.44163 0.0802,-0.843 0.20889,-1.204 0.12859,-0.36073 0.33761,-0.62003 0.62686,-0.77784 0,0 4.51628,-2.46343 4.51628,-2.46343" inkscape:connector-curvature="0" /> - - - - - - - - - - - - - - - - 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 Pf$NsyU39MeBj5i5q?->U literal 104199 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�eXo`PO_cS?#DZPm zwAjaO%RI#t%GPBF&ppe`{d`0x%>S%kPhA6qs!hX(c?YFxHs0H6Eccm+_=<--)+i9x zk;&!ikPyhR&FWc$`frpFiydt6v7xdel3*f0iNnWk&<*?K-bb7q;C>kjN;)@T z)veyc9}g5hgPJq(zZ%I<+;(aSJ#JiT;KR}>EZ2%Wv(`3mh@VukGC5D_W!=$Ij|p@n z(Ee%cp1hLc7e(!ZEq7{yk zNu4u@l*3Ddx!dvnch&k^NREwgK*!*diF@EbUsYMZeJeT!io}Pjw3hFwbnY2Bg|B#j znK@)e>xEBx$y^$`-S3KyE4OUgSnfyU9hP<1x~<20yn{B{yNjYI40Jyn*F2r93l?5g zJG6(|9QHQ5$CPt4P1uh3g%dbDG-mld`jV0i#qvmoj+O7E4&SVsF)>KB+eErwUUaU( zy1b@*{{)!+sXRG?D*8!#D?WCHfpP!pffVZqJ7Z*GFVA1FoJIHN(Wb#+)=}xK6iT}a z`ND74idwma`VSt|Y%z;V?LW;3c`@-=@Do0{p`@xL+;5JXW*GXtpVh<9jJfIepW7W= zH`*JS5C475(unr!yw9_-s#>x=ZAgCl&ynW)rzEWOr*H8@0H9-;XS6!vz3&c7oz^68 zE8BBrfMpubAVCPp7z^Ya75MJU`c|X)afwh$54hfn1ba)D%U#We=MEhZ@D;(yM;Q zRLIJEu+{AZqv_(obw}?{UHfeOV96K-I+eSr>tcZ)-mg~_E%&+3P=F`nsaPcz=)(pVj!#r zQF1uilG4I83=0VIQ2*pnM!sB5;~BPGV@E3Nq6vFv5XgU4(Y(XAe!EAUue_ZQ#!UX0 zht~E;N68LXxS!o^U1Ut*Av!|JApezWqBHH0M9gRWhttL1B7MLAGVSB&w2F;bm{TRdR|Wp zJ-KBLByt9fWkp|*GY!7*kd2xV+&XkQ#v&X%;6<)}Ra*VWvb#Rv-N})+_V8pmr61>! z&{aMP&@=dgM|)`MZum7N`GgGCir&F=T^!%yf#19(=>XBRmWoH`jqG&aiO^Rp@nq8T zdBgoCG^(Rw*l#hefy}<;z}lB!l_dlY;fURSabE@?cDTJgqv#qzkI1vth7|M$;W@N1 zYxv*H`2Vz(0e}qIuFRoFFy`lG>Dq)&V;KbbdN|YkcqDRK4xh#y6^KdW3{EKz!fCX8 zp%5tP;C6H>>d<$5;nKj%axiTeHs>HWFqv^Q2lD94qCAzxzKAU;?lK`?|6LNJBDxLh zN~K@*EP7o!0E(Blm#k(1S1B`oSXdFcvs~bpEZ+ajlW)5Bdnz4SC4Y1semEcbxgcwx z`YN2q(xl)q0y*fw<940$eqs6{MW{XSewPkb(C)pbHZB>+{yW>HNwaemgl0v@K-kD# zMf2?pJ=Z=&>S=p~JmFh+_+E2BRf#ABFsk~%`8n^g)bB~LI~pE9(9L6CBA*}S+c#so zZ42zfKmyepxT13M*$hcjdDkprGEhn(dDwkb`c}!jYYt~;1#?E{3m-o6_*v=ucige& zGjEsuf%{#w%nd6?iXCO^PE-+~0szyzKuJ&7jRP@tY7w;i-ngpc?5*yB!FSwRFWK)4 zPHxCD!OUCMeQ|#jOycE55=&NF5t!yX5DxM)){3bWE^r5ESk|PmmwJ6 zzhNy2ok!d(7>v6oZV4o|te6wFq>(cbG=I z)<8*63kqTvQiaN|4N0 zQwRNDFMxyZ=F7(`+v>ifti}~EaaoVdyMO=}vbwmkitV?=8*X%28YsQ&q>>L{lv<_lWA@3 z;85fTlfSOXrUQU_{aIFUU? z6J(^c-wl}_@w{ak?g6S6pk3Yr?>u6z_Q8b^0FY}(2OK*qNQ)7aaDr@v-Sn0F68jtQ zzmZAXYg#pp@X!rNY>aokpO|FgTYqewBi~?v_rG1L(_yft_@=v_)H@i__}jDFECWpW zU&iZh18T%AoZ&+2e(YF*lzsxFZv1kF^<4z-dcRcb@B~HfITK(Cnu08=Z`_)BQCpYr z=)?Jr4&a>YX;CdCo_gLG!#>*(5j;0NUR`9kp&3_c_*4v7uu-YKBh?A1RwSOS&;CFO zn+)|`VzR9*=?yZg?V#7_xFFF)q=?G%51>BtJfUa4Hu>h6tTk zfQ!o~e%Cy#;n|-pjvA%sWw1zP4G9(TJJbs#?#qX)kP*YoAOg7I#x^8dkuk$ z7`Dlg4mp}4kM@(`{VdamU`4vVRd*n4r+^;L+m0o*3)JuBN(01aabV;i3;$o8- zRag&*7sBth72$t)K#_qDwfq7oPFQK~oLf6>+hn9P3D2XFTtMJ-4 ze`;0fQGzNJe(qxIvjZ|5R1e{FL*1PWDXi^%ceca zT7^t87LK|2&z6q1X?~2C%*SI*TIWiQ2;@OZneSRsl|l7c1Co4?^sfB`9m9{0(6IA? z<ESaSN7l(o9rGOz2O>)zq4kOK4lVeMcnPejD6&{OZ0YLT2_Q)ZTAF5V2|hgnzq zcMYt!&4;CS;+-vEaQn1YkS1N>z)(736-tqdO zQ;)p5s_QUuB+AvfQ_lis!g6Lp>vvFfHX)}Nd%JghyIv!M7Kb}I^+wnXE7$E!u~*RK zuNirP)T_s?M`nSZ%CFA$J^yC$wpCh^yGLub-GoaV9j}I13*XI!@cqFXwxmTb%o1Kj zsEebZDuwTlgX2KQK8OQuS4?~2gvfWJuK=L2(bGg3c!iY$P7_>1Np+0XTYVDR40VFqvrdWI*$Pz&p#P!$K_s_2uP!rh*J|B4w zg1T3-KbBlSulxJ+qGQZ3GMSioeU-(7ruRL!2Jr)+nl;wBK@~w)E$^ZGz0hL^c<)~8 zq`W3u)oEY%{m1&PZKYq}5FwB6TM?IeIeR-^lr`gHlj3MH%;&56npYON&`rpnL+5Hj zSmC=Hg&#m{kv$qOY@i&AQr*Pov%LRF(OwsuA&)`FJ>ORllt=+8_or^lX~%NX>2kaw z7h7Zq12vhC#wIA|!Ti0ZC##)#G!}mS_+zxhP;uC$ersIrU~gwcFz~kbcag$*7B90<_R7u4LF8hFZDLgWIc#nsSo?K4_+XN$>BO(BDnNAqova_`Ny96Q( z^$o5~5Qhu10r-GjcU>QLjC@b`M^DqYRZfDWIu(@e-JasSK_1slkL};PZ&eafJbX?2 zIE3dYN!2xmy|G>Wun1o_A6d^MAb<9<`JRgp`V^pWAEQEOSW_1u^ZU4C`mM;TQ}U!7 z5nB*y@A0W6;<#+pF>j$IxkON_e@w7D;o;tM3%Ax!m)dt0%S1;r&&pHp>W?A*PE9TV z)M?Eu{E}?4PFjDo8q^!~j$gIKcWCV`cjR|wBp@IL3{?(i}$0Lw~%rn)HaBn9SUDTWp5y?cfh zC#D`OIUsnu?Qn8-?_swo%3!=-2;1LCdR?`d2dg}qqVIIXpbIZi+hA#1PG6`|F&175 zi+phJ{=8FdmaSBZY%0vHAMAi#nAMa0j@sWgDBFVqJ3*M4@WC+<_7|!ku||1 zsb&zV0TJ_87qxxNUwR;oVnjUOi>0D$gEz&_3oaj%fXY#sxdNk3TMA7n#{H)(3QKe?n~v}W^Q1G4ww!Ua4AM(}`JHLxqh-4;kl$NsZgG34K- zqVP(f>E`>UZ_Sd7YTcB-F56NwY661PB{dkCs1uF@l$LW50N|+VBACJ%HNlK7L7XIV z4?+(&@$bIj0V|W@BE52hz9shjrkRj>L@J=~oJ`DebI4>SZS><&(M^a{7 z!nWU$^`VOH3WMdoBRv0G9ZWz8gQ^6{!M-KGa*fSpntP$?Cjc1jQA~E!O4_3km>Q7> z{K-Bs(2RdwS}nEJ+pFcTweak=A?Q-@R=^d5qBT9gz8D=TZ@TiNBJIRB8?X54(sgGh zEd_L=(3cA_i!U&E2mqxETlh-Met{NhSm%}{9uc+N>O7y|M}cDl=h2erOJR&0JW!jBS% zzv+VXnou830?K}C(Y#BD>$1vF7Aq-~PpQ5w)a5OtQEitJ^dbCb_|G!vJiBuIS}}B{ zE3fz~Wj>B@5OL3#pXp}34BSv@1u7O394#sb)jQy~@X9-Chiq(14^#KVyuyvNpfS0i z%nSfWHsm^S9)3@!riYDT%U1^R+Xp5%tY1IE@FlI{_(?TPq-zEfspB3bkdH>5RZd}b zBqWmAteknP{KkpfqIrq*73ZcLA_aqE$7;pcV{^Q(vgy_9Q)&0Znz_yp+-V0`SGkJP z5xLboozl8wVXZ(YDQMFkZ7SZt75>zFa*dxQNc$!pq4jiSI|vpD0l-O-zn1%YulbT& z$kG<^&GhNqrrz(@cg3Mmj(+*yVE4_j*dx}rnaPHXxQ~uZ}el5Yr$Faq1QbfSF6y|D}|i&L%;Pjt_$)L zN1q}36_+0?S^LBU3F;BSGB=P>IlycH4vGsHXX)vs!<`=*Mg@!^3X>B?P9}7tWcv}E zW(8pUrVN-mSMnHc&3`-+YSzCK*Q7)|?ThE_C_RU#I(x5)eeC?_cwOu?_GWC+(j}it z|FIFa(`*R0tj92gx$YWR*784ir5(_xicb1gTmNqLFtwmOq`QP)lCaQx7THL&sN z2oJ0hAKAPi^V6tgwRX;hovWN(SL!LD_8#eSpZspOA%y+H4c)(|(kc||Av0r8tfVeo zRoIEeSdIN&;5ANpv@~D|Ox!verq_gH0l-f3x90?eALIGdW$dEtN^iBsl@C{-1OwZp z{p^%R46cg2_8$Vrc_>-Wf3@e2>%;vtI9vS((p_7!WkwpW_%&0EZdi7gdEVodmwR7> zf6A!2)dlM;=%k|=bd~+O-cItm;zy0rgvnAf|GlIVDk9AD9^EeK7?4GjPmYmGmX*6T zuMo{EI|y71$irSma2X&CBKLt{)4k|-30mTyYQm_{3*A7<16r@5QX_sMIlj2wjt9~` zRD~qAV5-&f3uZrFus>}s0~*UB+LIjQBf_y5GY*xb3uFGS9`LvQo!EX?t1Lq6NU!cX z(ja=C&Tf57!LT2JFE&3Bid08@l*T4?LDSinhD?G^w69@#_!M*?Rn6o20cq5t`w zMR1kr;?n>{uTqhJY0_jUak{t+RnazfhA3pS@KyaYFYmC*OKGbU^RpF+}ByhsV#Gzv$M zNKiiUuRq>e-!Bl}ZN)b0P7A?A8Oi)go7PG$(}Wg}AX2V8<~1~0Tit8c3Wg82?{7XU z1uH%a%`yD-PV?db=t37Q8KqtOmn_%)R+d15E3eiv5NBAJ|GF8B+8T4zM!D2OH6_d~ zkJ#T#{vgiLhe!~EUBqtikq&V3p77`&;QiceM{L0f8))xBcDwpgO&ww*%*V|x(5g;5W~IbDxorIXAZ*q9Vjmp^sRsw})k% zX=8By2CCW^c7Bb*#?Y*5V?^Pj*Td%{;ZG@bAxQa49SyV;L3qmM#)>Ireb46Qy{P4> z_U$0KjRw_11)k0DyiVx>c?J zddMJ87ZKdV=Ur23m8R>ehI;_gAEoN_2Mvo27m{q4syr^E&9Dxy&B*>Grv{BFBnYo@ zL#6`?sRjC(jEF>#XTjpcWivd-cIdT6t$(kq z&e~CG*qyYJE1Q_j0?&#x_rq)25edkvwk|sR3V@T>x85<&Z9989_0ZH%i*?1ve1=k( zfm;)AzYhlx{&Vs~qlrPOz64blc*LR{R)GEGnzV405jh)MU;a7Y@vXqM`TZ!jrab@* z->g|_lHL-kv}{GEKUc^3$Md=i>%P*zOtgZiX<%Kv?^G$D2-Pf{Vp6{P>t8+3^I61r zr1Tv-2J&>F((gAxZVSSU1tGiggxY9vUvekM@S90#Gf#d$3~OTUwDwUjA5uqz1Me*X#t#BZ#Ew9~4_*=Z*j|%_wOONwog0$l z>9GLUOELo#P-jtnU<$sjLQsKCcH&4$p0Fy?W)YSEZkoL^kBN16bvQ*E{e^X}5Qd_R z6HMXrX~To%9FWq9v&B0rKQptAa5Yu(VZuj0ZWV5|pX3Hn7n&$L|r zAO<3J)#QF?O(EPs2oW~D`u_g5Dm4ZcZp1N8ucNF9N9|yW>+|Tf;(@V$W$MzH`}Cf) zsqy$pC#BH7$X6AJ>ijore^2;_tvf&c7My|T2m~PC?c_?q6|kn*lijT52MfKeh0Zyc zCD{CrJcylvAJ~Na(zZVM#H+&;9FN-!2Rv*6GAtk5vgz3fyrOKa+yZM$ z8`NbF=m}%yllIduE-o}b!OAa4pR$*Yzjl#b-uS~uCRw!@-nV-Od#v#nu17tVwEEW6 z72*xm!zFwg2U9EN-ywpa$?AOC2$5fM5HA`)9=?KDm;vz<4!ayK`XF4T?*}af`)&Ag zzlHUu$#_HWwUKdaIeV~bhzZ)V)0bPeqyv8z=)|+oZARv~^>)9ReYI*9$!6ZR@a4~B zB*hq-`{nKT0*6*yS&v`DE>=*}V1V*8}Nph;G&HlKmAlj&c~eb@!YN~p>R%98APpg0{bBgtsWC; za0dH5f|rgD!w9HZ?o~qVQ|l7q%z?`I%zHJ@*Um5umK*EZ0MCYul{hNA1?R8uj&1Df z8603Jcy8g{ym#5%3tuj_q^Tq9^cmZ{br*tnK5>r;2uR6-I8T>QNj3##58D zh*76%0C#xHhUBvwUJ!+2ZV#szz30fku+u50hd+Kp?Up2*4Dt12DpoD`zL5fJe`L>mq_kGc5RGIe zYGhxKV)lb*MU|=I#Zo8kLJRS_pKP7Qr{NC#gwpYHcBU1+9OCdP1L@e}^!%)h(hw_c{pRjvt-ulUyrQ@W(+9%o+h z^G2s4IpX&|V&gYDQVvavPEk@WoMOX8hA;HOW+@;d2IvWmx4-iz7B5*;*4+Q0U%_)4)Y|k$!#p(bGU*uW0 zRCiv+9&Q(DmrG$Bl~#x*Our z(!(b}{+_zJQpnCz^54Vgy6`r_2NU@qS!_SzK$Vb|sp`8_qAVNqNoK%VT9K{Wufzh&)4dP)2|ljJTo8cq@_^R#2V%3oxmiA8nie1+;U`C-`V}4y zlu(8(*Sbh!f6S`jAdfVLRNBtbDxmj{5|vRCjwX#8cy@zmqPc z^ZNGX4hP`jMkx9z4=KO%H+-Yw*+F2d>Th_eeUN!l4+l{=t1NG+yYsK`kQnEk@k@EC+cJ(tWrzRLLDlg1IT=O6>?4zPd{LP$`ZzOs0b~oo+R2S|5An} zc1mw#^@4q%$0)^Ih1|MMvj*W;y6u z)oJtkLyuk-`CN_|)OD@3jCpqU)cXsu<&43I1QELszx_YlWn14O`sr8j^RQCGTXUS) z6bvBO*Y5gzCZ_rNx1kBwNd)>;z-BPG*f*$3kgONU5yAuIx$_xB>fzg3x%FkpMhG&7 zTvGC_r#WqadEaj{h<)lCjxg_zSIxZ;pjblo^byUrs4*gvaN>VUcQC2Ak}fn%Nh;VI4R8=&p=&2(aXk19Y=mWx$w~}>mF<6> zByH!CBh<5K`HcSfua_-^%Cqtz@ha0V5~bkl^^m=hXW2q)1R)w(Cez?GJuxYJrjWDWpA+0nyUzF5|e@PwL zy@vTaUI?Ya+7a*UlsrGj6>cJ>dNX886)VVZKbTyIuG3k8NZXgYZ2N=y5;dOG4pgSm zMBU8*99b`^b&`p-f8WW4zWPfyPWoD!OeB*PSN)JFcyxN@^-lJbyIOknZ&8NWiynLU+hDxkaRYQa3 z!zfWD7;F1R4`m19?zy{gYAt-?vGr~qRVAk5gTYp5&?uqsb@+Dd6;N3IQMRDwCjWX# zY`o#Ulk?vuI?@v(pG^vEUt;|(%)gs~<*NkEPcK4x;M<<8fJo6$ufME8E(J%MmA^l` zu^{=3TDvO7FTouy5c00f8F;`UWKn>;C?Rew{H$TJW$9_xWB&Yy{a7=l)$;BKK_bMz zE?v;pJQO)oBZxR$AN0FkE6(V9l1_Z{p4B(W+xy*4IQgk{^WZ}@9ta@Gk!|SW(bKK3 zDJfSv(fu_780B3~NgyU5GE#j@NlYalK7f}xm@2=+z`O`-6(qQ<;_MSO*eUTd&x__v zDS>@2z_x&8jD{(&+8zfwNv6&_U?(vQoGu&1 z?bnxYvk^wu-`*^e+v{l*xTs>_`u?4x6MialAW3k59O;sVec3wDVSS_S!Kg#mjYYke zn|eQUNmQm%V;;Ka5O^dm$^EL?Vcs@nT-=wyt#+YcClNkNSkObU&)=<&!?&a2O++3d zUz5FZV2zo#s$)zd)o~k1zrH5hgJ>zn;qJ5zZ*1T#vlrz%G@C5Zu}>5}uK*ehe4IaT zgnfUti}2SvpWt2hY6d^{YW|9I6a*UVFDXbPNjhV+fgr2f;?iIRshM5-Wg}MI7an%- z6K_+H%ZZ-{HV5xiYjdh>`-LOe`n(Qmw{~&E`c&Hyjb^<9D}P%t5?Psp$RwSC6OCC2 za=`^}K^khTIwnojMz-{rb$U*fyApu~=UH!_W29_1r^+1Ihi|D^?RDd>z|lz-G&MZ7 zG)e2l4lSX^+a-;AanztW3khQ+{C@uC@(Ph?)GMjPc#c^a+dx0Kz431eMNHhD{?onkAtAfn6`>(~?w*Jq({fy7ynP3qHIy z`kKvu;yTgo{e7(S-Sqc=6A~Esuc7Bk6YF>D*iVOCu5tB<@<@*lSi#qsH$g2YK0)7H zzTwB#Y}tF1)qWESi{Jiu@`VMYeQ1C6*SD(XQD^zVo($!;Qf8bteO}*_Xi8$s6A4Bu&+#BgqjBA;a#DNNeFgdjsQy z&~LT)w&+v|t3QA*IR^jIY2I1_#C|V~7$8GXbr!f#*gjg1!h1rgiblTaxQBxyDvFNs ztpJay_pX8;w@91$l>$&gYCCYHplA!Nj&Ms2eq*q_`S};94Y@&p^D;gfQUb;x9RHCz zxTG!CHuu2}{0Z2RZBJaF^vF)b#TwjY!X`CnR`B>?{e<%LDEK2t+v*_sJYGryN%(Laiqwmlh6T= zz3w|HwHrHFruB$=?yb z@e8*Db;27lBG9;#Kec(y&w#j3TT~DAK#$Bqc$#0fZMVz%+J(ORW!y3Q?hVapB`f4K z8UU~jp5)oPJ^ZWLL{{9a4?ep8QjgmHQjE3w_T>DfGc2|dMi(_UK*J}1aC)Fo!qYj| zf$=w{PDow2AD$P^ckj0S;o(oNEp^9VSz#4@qlf!u-BgYGq;bUC(;SSOa#4a_N|-qAcYp;DttvcJP!{fbV_G; z!I7Kye|Y-JsJNP@TQs;0!QF$qyA#|kH~~U}yF+j%KyZg(g9mqa3l`kn-R&Ooyx(1G z=I8Y3uCCs-YghFd4_jYUqt8f12b8oGgq$AV_*kUqqgJSsqoFlt-?3yAe3i)WqBm-q0d5d9gDSHB$n_Nadh31LkuKt<(4jLf%%Uu+u!<@*ilPM+se0`psX zgITqeRz%XouL73uI->7ik1|}73AGaHSHWY;@p1k*RS5Fj0Bn<~1wLbOwL_=9r{Z7|Uorc-(YRDExR4NiDPMo#_K^ zK5&^iP9ffZIY?jS#Q|TTckonYtajI_Bx?!~yLN(aQ=7C3|QI2tws<=O;SNI7F!TP;&VQ!$C}C=}@~AOd#&$xI41# zU@tx>MGB_<(Q3Oe%UYak4mv{!dJ066h`np zT;>saX~z@3U1v=@A$!X6Ra;%>Ahdnd+qoOP#b|wg?aQd2cSCoq#9@5#=DZQ)KJmPI zaNdlD)G(iW-lPe8IzZ~l^rhTvFz{un8(xV#?wZYtgOk48{`_J+LarLQGAgZ}sxFOb zZb$+Uk50{qp(~Hg%?V%MNP0Q`|gJ;AXWTKc)%gM?S-vWfx!eaIGfkT+>CrS@U}Kb0+h? zCl@s;j;<;qP%^y64Zxz%x8^%}HZljr~Jh)WRy_Y#g#JZML9>yu)hpp=zv+caytrGqsyxcA>u;}BqQTvW} z9&t6JzVt|F@m0c=6ZN+ddc&BPKP0R-R;hokYwT4kMe?741%+65*LFWf;&5ZO9_|*; zw%wsv?#g1a?-QekV2&oSdnr_xXxz&T;TX{yLRc19RM!iV$>O8sXcaynnj--%o$eBl zCh2oZS1X35?JuTW%{illEC){=cWS5yHW%PG3mRWV^9puDQEUt=v3o>x zenN*aF2H~!|HV>K^UHN2oD*3B<*9W!Fy@%t>kFI|r3CT}jYx@hpg}FnS1qGY4TxUz z;O4r;VW(A2C$A$<9+7*x^gP!mXP>)87-8r8&|;1oZtw8DRbP%LK8n%Lf$;DmchaxH z&wo&@4hszNHJ+NCGICZGn|&Yd-w$Z&KNl;M*FjR+{L!> z9D9i(f-17-DKc3PfKM{eJra#a=Aw*bQdGLbUN>d(@oVr!c}rg}V+H@(K9$ zFSv~KB-RF;x-+1wgZ#aS+1%YE|9-fY^vIgxsV;bwT~#5iUh;I3SE1On_VD?vqW9Zw zi@Sde?cK%v_Ua;GtsJJs(hT9gi(;jO28OOYBjvijn)G~IVDj?bu~5P!rI56)>2erJ z%<}`cNiaP8R{d#CaHG|pFfeL3EMSx>@$%qpl6Knv@_?2%Qzkk*CO*uh+zVx*qLHz; z=3)}_%GhffAAQ_)C?&Y#HtP#od`XsFbWh<~xtBcBYPGmJG_=F2g7XnhZ^}2@E^d#C zqr;LcH3)CpiHlU{hgcH2;^^cnWKoE$+Z<9}b%V&Og=XycC+fuP9{>{xn5 zf+;=z(aRczC%PS`Ig{MMOL(0zBvjxMZNv^f-KJ*4q2@kfqU5lOQ7=fS9d(KL>~lBr zFOfN>Rt)duZ|WKhQ$2Mif)57~tkFkO^dEYUYzXG1<9_bs;f(jY%y`1T-Zs-XwK}PK z`29#Zfi>daWs4L9L)Xr)N?OgT&5gOw4B(`QZ}j+FKZXjr^Y>M?QV?@19??8aDHWbL zd6HoV$VlbyB42?$tZ?)=A0X<{X0>c7o`g@;TGbU{(}?=py|5oqUio%d80+C@(MWoc zk&E6XR+rH~Q?fQGnGnzCJs8(;6$N{0QWbZQ?r}MVVCvWRMy)975!;4Oe8OvSRO54X z{UpfZ)hN-*>sTTk3Yf`mXe25jf@Szm=vETOAalUO_1VHLbT(XwT|M{OSN`z2P1@WL z1Jd8Oc;wAoAjFVPj){wi2|DatEk@A_{qNT6vS&NsFJUxkRS&`PgCniHcry`KK_zyt zE^y|)vvyl#DeqUMT!`x~%W>;x{hv`t#0_hxFFRmVE3FPI$Sy)8HoNy$UzPG5@{yvl zv;O!l`)y(h5AD&i(pT}@c^%LMzB~{89NRJlT`5_mFNRz?wtu(xp#NgV+0f?5yq@Ld z(Lj0Pwihwpc|LIpwze!FDXpk;~uPOrUR&D*A80+!?&CThQV? z(Z`)UZOmjWe5u($zt97F^54fF@&tE=z= z;Clt8n!_%YsL(5IA@7Pxt}>iE%0gv zXZ`7FTuQ%^y3iChq>H# zMaTxmh|8$`q&r#gSmN}4=GS;W*3XTn4U#`N<$T+>3x5p?hf z!)u0q#Io&w*-^^bPLQ_|TCuv)z)?IQoRZ`TopAcX<8NG(<#!`BbC}l-*9BQg<$U#* zBi)93jDb0G(u8n@!DJ{Zjga??0ZJuR^DA* zd)T^-aD=ECEyGAWL?hzWtcgID?R2UH9^o^ET9GV>x?!7if(;6U#h}@Bu&&ruc^0PU zji{LkF)zo{l8T$PH$4Mj6=Slbs9@9HQN%ZS|e6 zZrtp&X4i%k6{BRGPKhfcS2B;-X)@%|0YY6J3V~rRIFT-v%Nta=hh;Yd2lg&wMRpGj!7-&5Tyip#abek`6i@Yq9~iI{!FUll zX1jUJhB4OL@2;qJ$Cg)sI*GBcxe|d?w@>5_g{)o;HyyKz;cStVZPH>uNiLy<0hyGh zX!tkK6%b9`TR{s>>EN7@A$1#}S z+`;`v(NEANeXt$uO4y@HI8ba}EDRiU;!q<*u;n@-5t2RHZw^~J z#~djT?+&Rs@*+Has9;SyMNpGr$Q9uIE*e?TeK6so=Uj67rR&I&WN|MeE5cMo@co$F zxuHb$1@6_(#Nja3e~zq43MIj_$*N}nl`ssi&J-}bQtp!aZtO%;Zxn9*UYhkrSjlNN zUZPG=f`brhIarei&Dw{^1AwrF?KyA~SSD<-q=jnAnR~&!-(^2)j;RsSbav`%?4h0U zC(r#Q1?ki%2{BotKxkZ=7*h_SE-U`;6Dz+WR|Jofp($`%oly#hquBY8JV(W@;_hjw zzUH+BcCc8;4j=+X>tYat3AJ;{-48^fhsV+Ug}Y+?;?r`BGUp;3Rdq;fg5nu=km8Dq`9K9my+mcsLu`)qGsK~8oh<$TkQk9^IF~}r?h5Il5p$8&RKuMs- zg}OVQ&6fJ7YMed9KxgBz>SZX*#hgej-jG&xdxqMyh3xh5hXw|$UF0UH=V~!U7uuDH zFqdfx@-7+7RDVXj+D)EH?z6k{x5KS&N6Ncrk@^+@!XX+>*8eDFs~}#@ugdT7_`8Zx zCrqd!Ra;Rdu4BMrAW5$MpO?cJ%c4V)!*3cLaEVdP1j>Tf{bx~5>(QJx`P|3t%xw z{mfSjAYOtb{B1EXG8 zq8hxSR@a%03*1fBF^nmeq;b=*zi?%IhrL5Epds z8^~Ox)iRGnp5?t>tS_2~v;J6nW}HT@RH?L0-t0Fi1%Z^Wu(9wGVz~N@xQzW9-YlsJ zI27}H)~?x$40-j@g8Vq2+^Ia~dzCf$(}}Upw>NIX|6BbI;@i{lL$?yqbuQ_&FTv~H z9z1@JTdY^R#63%w$W_qe7|V^Z$~^GYUtp=eY$Hj_tcPoDT9(@%I;!LIcI6?!V-N<<>7=At1Odmckf_qzzN{3$2I6`Az{y5 za%jtjQ!V`~bAYkP90?9CSppMQgbC`6ak^E<7ycail|1~`V;(C$E?ew6f7L6q3Xu{$ zG0MR`BEVUW(*b-1!bI&`n23g6G@cS(3-%3aiA%{GJrbFM$N7obwijhLK@j4)2nF2$ zWD;qo-2#O6^*mh%FGl7wVoIzz=+Y|f$s zLtx+KKvc!|$HPc9O)TQx7|&UASrL=eJ%MT@>9fBgR6Vif+UL0{KP0>{E`M}sm{1l+ zCyuV?_MqVvwk8Nxl#i{DCYe@G*Sq50x!uj?J$AKO9XC(n$^6_P{@($6nS~k@lHDT1 z^iuSO*;JOwyXQ0Euho-h30F=CAV@WxFnejl~z-nq%$#mcK_f&d?c zCHyV2S!ojxv&geIcys^2q#z*J?7)Xi|K*j6i7Z3ws1QaoY=J%at2o}W17Bc6!BK%- z{v7JjQ-yY8w0g_%@b@u{XkMXoor*kWMV1GoidDw}|J43XLpEWV_;l~MW5*$H7b|Br z3tHnE_ww-P#A2G96_=X=@GKZn9tz;hj}XQ-K;V^pG^B)oUZ!J|Nk*S2^~&AVN7&Wp zZBdGlvg0Npy-mvMg-1r$d;FRkF|HmLSblarwXn0*!B z1_ChI5>SO$9MffCK9{oY6E2)z>{9;Q71DG~2|sxD5Uk}&{(cfiIx{^qE8ly^e}jsm zE7Ak;4;U3H1e3%1iq)82;qW~Zt{YXZ`q;nQ6?6@hjwihWZB!fhne97(dYWxGEJYO|AytVR0g$ zDtLIP^%WYTknrI=@L70J|F;bG=(kNw4uwb=NdBxFQ+#X{?jH#KX9h9#sqsyI@FyJz zmr3UB$~kW(ibaV0xbrz+;bxQ?5_DVDpqLYv^q(#8!vBp@gz&5`dsfF)bZqsWwD5xW zzX^>gzfHPdqNlgGz^In9$oiGx{8REfi7q2o8N|eY13Es^#em!Ke>cgIZY9yATxt)_wkAr_TM0M9ilBB@&{ zd|@I&E#iku)Q4zV@_gGYb5wx9Pgg#(YJA0B%QWn!;_d<*+*b(RCToF&(0}x{3t*nD zf(1>;6&jF8=Eg7*fImbb-*ojY8ee*cost&K&d9AF8~Q!PrKDv6tFY6gsP7CcHr^x( z+l4dqE62VT&=K(39zClb?_z!xZ$*Wp`Qh)3s8Oj)00P!OB~05%FV8J#eb$QB6<=R+ znpKcbEZl}>^PmKeJT~$KOfj(fEy6U2x$G0r!_(vX3X;9+MqF(rwST0>ifc!k({-vvvudc7A- zDwzd>^-M#B@qeI4h5|ff3vW*aO(9?gr^K64x?^2F(*3C-Y_|ZCKhy6EkA@yh$l4~s z+Tv;Oqpj*=SB`uv8-5we+Mxfvb9Fy7IdJAa1hf9`n7cx|8+Ki5J`G`RQOxI$?pqcP z*y&cTf7f>A*>ELg=c$3WcjVP@v3&O3w)k{&jK;X=)X5wF?fm0ny-mWB7hG4-pto$+ zx`%S$?C_xB;aqA#0y8B~RDEwi^%=tEv5l=xx(hSHPoirmKvg&YHXPL{De=GW z5~p9pP`sVKO)_8^0*unrH(iG_H~u9vf5ZKfrNn+Q=)>tWeiel+-q@IEr_4mV?#{HB z_M|Zpv*j=EOH>zC{Jy|VFj?893M+>j)TX!{)^uNj@EV5^u&mTQ{z`<8M&0+|V6;uY z{t(l3uNCFhR(J}N1h)d*Bf7?oh_LnRVoRX2OjSEGemhOZ8^A%JR`SeS7&9!b;~bv( zE1U84n>t3=6X@>Y_-rtX`eZ|QgPa8(uC6fCb@;j1w6CklR4Y1@nWgrE9CIN5V$MivR~g(<$%1a>2kYgRP*V5#~ZR8_=j#Q&FkL zv~)y*CahL@3n8KA`^R9(eW2!W-y@Zc&833)9-WLIZURY8%f z2>@NSzxap$5U{)K8LK}gyRJ9O7gh{hxZ`YA?CP4q^nE)_r1GFQhd|JUqV(CFi7%V1 zD1BcVO(V*k`m+!*%r7#EiVK)Gv3?m4UZ&Ov&TDe##fCB5Hf&`H=ZIK*5n66C z3U7$`TGX)2259}ze2}~vi~Hs1);ERyvb?Fuy86knHTtd+a58mmW8&x%z%#3Y=eKX} z)Ajx>^a$km4XG#@1a7~lRxO;QpoF;Z{rWA+G92m0@JjB9m*Krl!)2j8+JFSifPHO- z7D_A8fx8Fuj~l7GN3a@6Z<}c=b{UQQz$B-IYIjz%Ixk z53VLt{qP2}8vneD=La2=NGmZmsXfKZmX+rJ^#ZsU*iW9{InqN-E@anTA5Pa95vwz( zN0fy9GRhDr2$Zuo%l&ED1lI$C_RculD?7NqjOj{zlv30W3p9RpjE{&z<|a>Fln=hBr!hPhx4b7(X+(y zXy3MS%><9@p~LB2f(XY7Ke$&GAI|y}m)bI2wg=oPb*Pqe5xpm(z@0qsdl$`>yGx)- zh8{@!cJkf3I^`iZh9N%*fQ|CMY*XcjXcv_bt4R2TbSa#u{xr!y#!ew?}?`kbEC(=UzVOJxP5i!Fx>C zM2kV0jr8>{Bp7Q~b><+6$V8>GlPj6|HWgTo{Us79sxN@fd87&BNAxeFw<9+d353`S zp5-;AW;y;jq~6P!OFNO}K}AKCDwqf=CV%lEf;fRb5W}XV2w&>Y>`H7S9rpu|{bH&_ zG6C<-TE{%uMcL>-g!5%Ra2=O19pd#xKeHcg{keUh#@sayc*)7I-0kxdgs~uVeMlN}-y}4=6V-2CWeL&dlr+(jKWPvg=z8e4C1J77Zp%a=X%j zuz%709Y7G`X$sL53FVh(7jq(4vaw(0?~5(vGZ|_X6sLx0l`Yk=_f_I1z9_I|2Y}ex zmgFH#Bt<4!P3Qza@FK<;cz?NU1-mc4SlgNSf+2Y8aN1s&*RDE}UP`g&u*pT62P z#@hW)A0Wn`D6U&WT#5`(e$a-#+&oLDZ-2A>o@J}x<$^(4h*P5bw=QIDEQ?kNx@dKG z*B8!(=IPQ^*6Y1S$INLEkrG{J<2kC^rG=F6ZNh2F93h8-BuA5OAMvcWs`cSt#iN!V zXR=X2SjUz8*`-`$96QXKxq3-1N?jv)z2p#1*H`kDx&eSj!v@f?!FV;GiK?`BO(aG( z>^E48cR6JOzC?A#4l&V?F*B|TX3#C-_V2q|g|WkNT0e@Y;!UxS`oIgAbY^0oXKb~o zO-r;8KIGYW)bwzy?>DpUPWeF93C000_AlsbJzzX={*CZFVB>bAnebvN84{>waXTd| z!a)>lwp%i`mUuV7=$|7*CB(g4@(^m}tA5?+HlEaZ9I$=3PH(HZiznxoj@V{(-AaOV z$S)jQ6fEiTayJpaM4s?)J(!ib5;y73q9PBiWy^muCMA#&)xb|+j|@)ZX!U-+XBXfH zxEGdmqL(*`{v6V-lT+4@Qd&fjP)4BrN(%QN#2Ss>9&$|QVTx^ywtPP>3d>$^ey7T0@dCLR&sTGdg&EgWxAgUaQLWAGas2xmuMd z&+h(&J3k4MROs=5cHwnt-sLN2@O1Zi+xVb_n5h;jG#1r-^+#_e(PT&~GU1bQ>LvTC z^^>ns^|`T@JQ>j{4#u#eJZV6(Bu_ldv*N6Zfc`DVhOs72Qu<@QhY%rT81v5Ziu`7& zhJlkFnoPjL$}@XHO#4Ox0_)Ul20CdX?uV2tx6>!d1Jp|(Z>X)BU+)oewld&+I*j?* z@X)~<_Ce9XFmCIj6ubYnBviIHS7Lg5WtCDw%eQ`Lw*6u`<}j_&T%w`w@UKG;wVjS?40laULQ6sTX@ zG8^e654LXCi)+5Z$e&(Qa7__@I4uyc-&%WWXH1zKK7%FfJO)>*Gn_>0F1kj(YI4fW z)Y(LZQ!Bt)_s^WV%&H-b#|IUdc_dGsZ&Sk@N_>1x!$pu&CRr#Z7cK0HT?RKdV+n%B z9OTozGlrTc@?k=%D==kHbd#H+inx=ECcNJ5&a!K(FmuZSU`q8bBJe9MpWE^x8hUw~ zv{Ml>bYHJ9+O2&1iEhc8%Gb9&*s{3D@2^BjR08$)3uR%Ywh3uFhqJs-7+dBiTB8@p zT7~GJlo8l+dfI|iza2C%`4mFXeBm;^Sy;_$c5IKM5T@FCg?(aqPtM!JA@GnC)MiE| zt8yLW#z3RLRFN7EYrBKDabgvJdTLHN+%}%i@+-ACzGTj?7=smzlq(t5b35Wi* z`dl-Wb2}B&5Oxvk_)-}d)w)Iep6)2R2TGt&gz;~HI6`$L7NmW2Y~ADn%gvHhq~9+D z^pYjpfvhG4g8?kc)_#!0-{m8!U24^D4v0pb;Un;&qo}L*g>2cY=cb%VZP7ioX=oM& zmfyp}k*|ur%7{@9Gx4+{?frdsH-qyt3FhbR=j&c&?E-RNDf#O)w-e*ZQ;@}vPrrKl zMbNesJCK0%_^To2`UZDb*=kV8E6nS~>1K?r8-mZR*|lKHSyPz4c=9E1BGp<5%G+)t z$>9phF|W5kbPN`DFRJh8lZ<7Z!~p^(1+15|)YDqbZ<;|3DScgtR52lK#Rp2ro7btf z@0KQ|vFyXgZvkxZAy0qUzrQc7dsoX?tiD^TTEP%UL8{4-YXo$76sdMp6@fGJ7gce> z26KBsI03CG{SbfkQj@ExMEpRJuHxaO3xl8Hng;oo0MQk*RXfDv+ke0(@{ zptBT)Y=3D1k(hA~Vb&aM^xX2LY(DlecYpI9$43)62|llAf83u?yPMSUt~zkN=rXO` z0Xd5saiE?C3VZ243%2ZJfQ0mPP)u zFn}Z!0R+qZDaLBE@ z!tR~q6a=IE0zlKNcT*R>`FiLrc>{OU2UOV)z*&BgG<^|sU>OQ|1>a&~G zg8to>;;iO^ma&eBp-N+uX{Hs65Pn6b^NYrTBL9c#OGmVVn-3TTAW&RtrkM;gdMx!a8Z^Hn_p%@C=Z?VkYiPombHe5b25JcC&Ekv)@%E4ETa;9=^u$qn zmL!sXZkQMmv8tXe^Tm$8C-EjJNdM~ZZc#f7LQ&G6>{iNNr<&(_oyIw0ZZb8BBK!av z>>M!^nU$Ut!d6|McBi=_TlPGzo7-Ng-BxF3T)8t$J=fPisXJNC!(s~8FGHT*BQ8B@ zI7f|PU(56*L07zn{?Z?1B@7U3X#V-<%!zB`h&L<|WBE_0$8Ut~BzEnpnV3fH7E=Fgl6khzQMhCwnsn;hNR&wN8) zrGzroiYB!gf?;KJ62KzDEr`vZ#yHa zSM=bP@MQx^1H)TgpjEqA^?KJ#^MLo2?rxLYxKatP@v#|{P#|S8_sS{@-Hum^WDaosYHT>a?(Od4zn6Y#zy5UII327SuQRiDfMCd zqA18xiT{cwWj|;tBB4%QKort$5lyj7{o+7QJ#dqG6^~cp;HhNo;8ww_tt(xWJ-4Ch zQ#_0`=8cGNAJ5bdX`H@adqZj`7z;)gEX|V!OnK=|O!piZ3t?VGtoOpSd4gGbs;!dy zrzL;!CM~^6X=9=iAwLgl$Kw4ip}}ORn7fcTTE1f114^hYo$MSmF>NsXXpx(4!)9EP ztC=d@10pT2QD>^xlJrX~ms3AT)yjhz_=18GR9sl1@oYo0@jT}g!{(DkSHh$Ub#;A(g_W?0#h+Ttq!cNImx+U@8fn?z*TwD@>*gGKx$|fLp((_UtwEf! zHMS=j<|j{ZfBAE_uoYoQT8B`o`)v1BW${oL@i9|vrljp2_1gPW%{!qdVa(tGz}C`O*-jb%I6`s{peF-eM@3(ydKuVG#2_f!oD!FhHzL%_)Mfxy{3xwv62 z`YPOuML|-JZT+l|0Nu1*+;H7|c1uE?OFpPkMTbqRc3y>}p^Ge{)`BA)4pF6ADP{RS ziFhB^+Lxu;$9Jbu0+?jbxj!Uv}+@Ce)Ca!0A-mUVQ;hheZzK* zi$V`u(oSE+qSr?3B3vfFJecniPeFvOWT&hzf`vVwR6(P8nDP zYh8!Nw`!G-Dbt`H%&<84!m(AKZ23)YAGVn*-!8#bR;R|ym){~ss%k&P^Zt& z9npDj%LpX&?yK&z%FPzHcqhRbJl9R%GUz6ph5w3Ypa$fW#rS>=wZ^7eSud*=@eFqT zFWSG7)@xy81}Y}K%iWqm!Xd8Ro3Tf^E44`~K)BDG(mR&(ht~NXn<3bplWiytMya&5 zJJ+7_k;f{OT4~AN)NxvXgjPe-!w&Cj#=JhnH$8fvV5K0*Drog>Jo?1XX{jO{M->zQ z$aUC;MnI)iyYPKNJ$V8IQ2$s8(uq(IPHJL9fQ|b{zErhc<$5VWo2RWU;n(@0`1822 z5+;Aw7mEYdBx#LC-5Qsx@^$BLGp0@Zr~Zt$v}7$iar^Brgpz1*07LZTPW?lX6KNkj zrNC2&>ubpf%~^u5`+l^;3aF-!831&`0xl7^5+-o5(BV#cbAy`_CbXdVXIff&DqdKT zUTH3gCs*gAX31g8>0$A#6ICmXJF%v20)26MMM4`@q=KEyJ!|x_Mif0gPPdsYvA36o z_rc-Akbr)c;O59E;to+Zh84%U zMpkF+|Cv<`#Gy#QA!XKklWt}q_C(ut*GBtb7lu2VkWy{nNuq*E5=0ue3FtDwlr}s{ zIHZRLB{1-xr39qH{j|58;%~R9m%Wd_;5Xk6&MWf3x4+U6uH?66DTGYtwX$mX&1KMm zRT{N9Q{+!~SqRO(OY`&COVps_j z60?8Ty-uF4CXxLs5rD_jJyT!B*7(K%x|E=gZZ1u9y z^1>TM7s8(PhWTrlH(3D)!Zg`6e9j|44b!l#WD?9!8Y=w-riyh^08v(hz3Dq~?GHt4 z(*E20n-9EGVUju5xEy_t<)!V^*KSi}JT>g+x>MPegymkY2MtGFfWD}Bsz);nB;(^1 z*^i%hs_rA+Xz~m1D0w=xenb4fgir#D4M~CKV31l{c}%$%^sYqHXoTAStyMGv0<(I-QK>+!OjbL*fXdL(E!it^>JwEEcOMQJLLs{+hr*YUYstyQ%-eUM9rn`0Wy<22J^Q~ zHh*=rLai&$#Q3Y_Dm!k!5%!}DGeQKb^Bf_ba1!mnJWq<>oP^RmPWCI-VAOR+v&cdh; zm}8gITZIfAdYIY7=w1@y^YNnmO5LMDG}4a(71W+QucWUXy58?EJKV zbpz&uG>Z#|lP4T+&LrKPDu_Av!Ywoot;*7($XX!S1T0{lc&@}e|L1+wqG$-_5I6ZsmEK%hLqvYR4F zFJWBH(%=LgAg3|AR|@@U(DOuSX*A?dK)}|KkAM2Il2JnoGNVw;_k4mgX6*pWG-K1f zVb&a_#1@l=r?TS5-xoh)mlNvd=t?wV<6A$mjXO>dD6SF~29yez6>%w0as8^k#S6fW z*f_E2T%IW!T6f_xK|(c;WIsmj(t!n7G7vUJvwwN~6BytvkbWbgQ}?dLA>rydKti+y zEDm^{7zm8Q{So*1eQQPOIW9#dbueaD=_4qPG~SsWM#rg%w~{hOX4rt})5cqU$<^^~ zC52WboK7{5!Z87O{h%+Le?=2Rmol`Z2VX-(_>m@ev=sOK}N@K$SL|h(6WOVSLWOXNJb_izbt$Jc3TekTQOf9 zA|NV-Htl}F`}|g6?*o7}-Q}saz*OEH za+~ZP&&CQ$b33zXI?yA1 zzKy{EHmP&~GB%{|yh}gSfyz-b)|+mSATlZo#Yqo&mOdx5L;`Dr1My3 zw2)!X`a3hH$+572Pth&WO@vxZ)5aXmPBa;&ZjCf^tiUZ0u;GbjjW(sJ-UwEV&B&(# zk0X<> zMrJZ_{c7@guoM;)Y~~}A>VcM^c&Kn&^Q%4UMlSaPCCd%-NDxU7WZ zODN;Q{_S~TrV}4Do;yM`C=%xxO%b`p9IKmbow<00 z)T`w#!cUj0TIxoO)9hfdlBeSV{BnISxcL(1q}=@K3y*2~x*g^aBeinVkM9c_fz?QF z;0IAx0KFHp9B%K#%=!wUqR_p6{zHu|raX~4@~Vv$D9k4SF0zB1<6c|ScD@Q}SC^G- zH??|Byc|YM*yZ^dZiRxM<=PNbq*L5f%WNPmFJIH8*U4djZnuHe*3vYKJV<2n*y$#909iBtJj||x2jyz}PyK(&OpnB#AJC+WknDFUZ zLP4HxOl%nA_?X(h|LXp&-&o|FG(IcH;RjT~qE*7KSwY6DV(dG0jL6aQACuO{V@z ze)G?L{zf_Xk5$4qI3qaq=)eX2uefs=OkVSiTeD0(>kgQR?{GOdqOq7dw11<54@K*X zF$KJ5M|>cF0wmFYa}=?r**658m?U-e>R;&E9H87W?zfX&_RAoFvqBptPLT?}Yxu2D!q1}tdGX>nO;peP3R`nN93gcf zXW4wS2{HG`_hHV7Uk5-FuIZ4%b1H!-W|gP2Q}{7MwsatKr}b#elHv&f^m;x0(eq3K zy8I#R3yVnhBSrH0ptpEhWbm=l?&Zx`QvGe$J~b93q<>wYC_vQa)tiGu?xmXY0LJ9 zyg9h04{kdyzfpxEtSfO`r+k(a9%+5H%>A*Pk(rh|M4mlz8+!}Ah}K6sSm%grFJ=P) zd?;UWVTqa+O_iF?ko?e&?CVbSJX6Awev*<>3HalR3*lm(ZzoSn{G75dYsdP{4=rs8 z8^C%BG`MH%Mz@o?Rl1VAvL*jb@%w*UN>)omQzjc#`$Ue3%a1y7B(_lTz;dkRjr(YX zcAs`adf1_P1ijplhPGiNBr}oMPNQwW4VZ6`KI6(cXg&-(V(sK>*dv!ZUjic!pnBKv z!&_jXGnAWWd%%9*ICzUKkpad=z6UOIFSu1>=8<3CkDheIXDFQHQn^(-ga(Qu2UBo7 z423cAlVF!ag?uZpV1n<$duus$v7@7Q@)gkC$XEyt0J?bj3?3ohzDMHTTefWhrQT^( zXjf}Cyn)Tz=#nwZVntTQ0t(**0l!q^gERK9N4_rb;Pz0=jY3}zZS;b<++AlbG0ir| zVoh>2r)GfP%qCe4dATb~Mq$RdL<3E6=n*r!Q<-fg{xnIY7H!Wm&|(_p+y$%u6{Gb5 znCI0StszPWgqZhIB2vKRru@zD+X?&L$IAQ1fmJV>c_0P@cbaBAU;wrsPW(y%An`dz z8Hxnrmhky}$X`a8{huACKs=&Mg<^xNy-MaFAiACp-qKczqw3-dtf0i@Sd3; z%LX(w&`w`KTkpq~mI6Nl_l;4Wi}^!2i96umh3@m&RKz@m%L1SRTy_2~;>YAF^wQ)O zrg!g+xbZ+XlW?rNZnRVY=%!!4ZDTcRV zDbtzy|9JYUsJgab+2HQ(Zoz{)!Civ{5AN=^ad!_I9753G?i$?P-QD3W&b{}I!T#V2 zR`0pGtE#Je!U7|o`zGhVu4~+Wg}&_AHDv%CYWu)yVAhvV*xM}O}l8czm0)rZ^&otjz3>U|E7ebely-aAU-e;i1p^0<|mBoW?T=F zf)#wyP3z#kZJb7$_f>+6FMkiJ-$sY}No6izvq18Q(eHoS3+)*OrjVL(@&bMpvH#=m zG8{Anq9yXu{;UzhcNi0d0yLnn;q>A7oUtE>o!uJPYJ%BJ;Of2?gst?U@jd|c`@WYJ z7>p*s4w~aCpy=XsBv(YPM*sl2;g;A@&l?>W@Xpj7#pJ(sgU^2C+YE42TXF-Qt+0+w z;PiVSBRBWSvOR#4&TPL%iht?v14dm|(;Ae-xoWNec%~|+NMPNJ^VC9GiK_?9%9F0# zbxbVgW%KD6{55HRUxZPMnDy#NrxQ$Q+JmTi_W+zc?#KH@FGjE!oh(&0Z6r3!fxQaZ zolz1j)~+kxzwa=W^rU7evJ)E8ViqlG$Thx!&%#dZ{yh8%3@OOrf0#O0zdb7y!lzKFn<_&Zvm=H^o<**S-HY3a#D)y{-03;q^M~iR zK$*EA0HFI9fK0`%MgX#$?f-NeG>koVQ(KJmA!^sttxLRY;+_oQe(17mIsw60h@<+$ zoiCmflA3W#^EqOPxL3lMi=+jRrsJzY3 znojiVi&cDw9=ik|A3##L%5F=|6j0ncvFx9mmU-Lb8v;^weCyN?2(K#dNjXI?nd;yd zi?wL%|AF1k7-Mw(LUv#fvt_XVX^_R?nwWrdgS%ZZJPI(j*QUSOCfmBqE2A3{~xQ!Qk%2{-|7=Jdye#1-LMdA*C(CSV}HJ{E%J0;vfoVw1YP z>x82ySx~RPv%DCNlqLg96Vc)(S!|F!Iah?oLkK#<5;{(O9bE-t{{DrNv?F-bKBoru z(ug@l;#KuZ^%0gW7=Os^fM^xtU-9ohl#dZ>JHw{ggs3VM`DXF7f1dQ9S^c$80xOv& z#>NZx?1gUS5#!bkbA)}VI4TS1jJ=A{mMh1{RbZo$s)qTVh~LoI_Lwh(qTx~&0g#*c zYA-oaTL->KnslRhJWE#f)T3%gN39f?o84sCVtCTme`ad6H3qvH%u!um zo`mG%qLw>2gL6(JR+vxEB92p)Xqa|-bGgX`5#h34WrO@yvSPPRth?9dzrOvwk9$86 z5OazMnXV1EPGwo{4Pzwi%-UKtmxvAe(vOFXvzbZ$Qh0eFvG%?7!PDXIZ>hQmLihR9 zFGzj+Z4$4qD_2Oc$kB{|`9u1ZK#m4GxF6yuq-#=lc!n76`hAh_{Gz_>FVZ@G3gdFk zj)0LF--;q)&;0vDD9Hnb@|9aFRkQi}H=NerW1er|_=!=p z0{z_+@nrSf@}jG`PdJZO^^ku$Ioy&^C>xqUmMGCO{g}}g7GJ&;rvz^~)ST*dSzOz| zAss*o-}od=7tSUsUb@q+ZDqnyZ=nZV1Rza+sC_W*(a|Xpgo9Zg^ILgdh2u!;!oA9x zmi)&yc%CA$gubf3$tWR%)!xPzDrv5AU!!W*;{hlf3O(kK^u(`q1R?1%odn*Q5|Jb3 z-6@&kKCVJXjxZaOx|B>S_0XDctk(KeiC*Q%)T{8K)b(%pIq#AxXvIQk?HQB598{Ei zo!yMVR8--Pg>?o?C!|ih%fn)%)MaU`K)LT5Az4uk zX8H8o33u$89|akdl@`j;6||x3FuX$@Q;ecJCg=cXUC{JZ%QB}(Xz|Jg*)}w>(0*Q( zyq|(~m+9f$X%i}Jo<3eG7ABE0@?uR|87g_IPtEZ~F&+ zAB0Hc5rltdH@+|Av_s9V#~yFiRch9-BuBQz*+0_@+>9ZqKW#qkYi_zY?0D_Y*A_%1 zW=29AMu}|b@25hC!ego5wdzYZ*l0{SUv`S)4>6ZS6xK}+i+z7(boUZ*%-_Hz51^pS z-}R#VG?Bs}+;XJK3hvB%ao~)*y~XNs9!$py*Dx&|xT@e`{blZjH9^9SbFU0OSi}J? z|05V;916S{5|k=VJCmsayKJ!|@aAk8@i2>RR9pfVIrp1cRbHYOx5($1Mxs{p%p!IK zT9zU5MWzeJowD6~;%Tyb=ssJYnwvlWqXK@3upjtIKtW+-F-U-`9%Y{8YWP|9b+zWF+LQeo30zBnVS^|p9HoHc*u_Y>rWR#kydsdaYd6>!Z+*3xC)J%qTxj0*U7 zMkHVzERuVWKns><&ety0J2bVyi@M%BNh5spdzZB={7_Fq&k3x?hA4LN_$p21hseRU z31OBlZc>njTR2wo`H~WoMWkH1Pn5qvjy5+nj>K-c@LV3{v-A%nPW)wdFhyVr4TKI& ztZ8&Ufij|uD2V!m1s|L@`oEzNdyL;WEqvaYT+oD2S*I0FpNn|VgGooV)%qNp!tQPY z=Z^m>sy-Hv<>-?nbd|>~I{Hg`1~DDd?{LIKa}K0%Q|)trZ^>ep3EFGiDUS>syFSSK zH~@uv${Bp0thDa$h!$`vzh1tl<9vM%i@=^gZv{UPByGg9@%n4S;e7!=!|7_LeGSF` zk}*hB;P@m%)W6$M%MW&%f^xjyQ@S0OiT9M2-tR>dyhsv}rrGge8?i=uOBCu#3x*CR zzo_SoV7k57fu-n!L>vtr$cpWx1)Y+_K4Dp{O;8dYQOMv%yAvCbbP_{cFOsXIWctfWj&3gEZr{SM_s-JGg?+Ggjq&j4?BUBv9mG^D z2VK{E%~ySDJcHbtx#XV;VX)K=e*HAgLc9p)$H$B9rGkC?)2HNA5ZBF_eu*T99*Luh zw$ou5l_K%x>~;6}bbV<2I_11>T|o9eBC=5eLSt<+_FwX8;7_4c&v08Gs?Tmukls&< z4`(Brm8o%%iRYH9Z9X~k_J#<*?vkQLjV)F=zKTV1iYPl-#)u`4h5Sxb@{pUVXObY~ zyy+u}Kr`57ZMEeR6Gt=qVl;H9D3=LW+@sJoJ&NXp923Bb?lP@UnYDxoeDR6@rNl%I;ys7Ymc;hHjL1viQh*Jur6r(?FI>Y&1BK2O{!m5`O;OMbAYWiDH^3QrvhI z-Q~%e*J=5^qo{vae~=a0bDc8WJjiefQA#mRQLR*o0J z=nzw)dab|#igHsF>%@UdA*eE;KrE6Gj%!J&-uxpjrG5iDk;oQF@3*2w9klg&mj z%aVQv_~PP?0SK7uSLNWTs_P$$?I4b%3Gsr8b65$C}AOg4kpfm_p$kdHK05E$edoj=TQyBs=T zTCLXF22%mJ`g_CU-Mzjtc)?!> z>F5@w2i{lsA7_Xq+@5zv_DfW&#}8ypsmr@KGx6l4Dc>G5m+=EYlHJv1Vil(m24ZQ` z8gd94KJXMYBgaZJ*&*MV2OI6){X6>Xa6}qhpwhA4dz_q6h+B|i)g|S02#ai2rKJ@5 zPF;WeT`XatR>={P$RlL*D5nL^@K!)BWY`uW==FL1`E2cPOEIe;n0!IF0&Tw@J-G3v z_2U&<8oZ%G`~XI1;JAP)Jvu5F*wC`wlEq-^$a=c(b4F^2p2=DZTs>3h*X`Hkdd!|8 zDwa#m??h@k!ed>3Mt@w-!&$g|Sp?n3ev!TpM~NHP$=STRcIDkzs+9h$Ns1TEAvMNA zR3zf?1ocob?J)(AkkXcR%41*rdb2?p6WPfy^SP?P! z=;u))XUz0FW0C3UhTC>AEL3B=jnb_MCz0X*T@0XNgwRa_3tg2E;6*ODNuiZ=wfhg^ zVrF3i2lx|%W(~u&kX^e|KC50HCY(0*j}m?vmrZs{g#JyNQd*~BUG(qfTJ8k2!k#Gc z1KTixv6pbD`nsX#nMxju7SU+&bC|hy<|j8vNyr_Ka*k`DOBZRr?@wQ=KJhqcTEeLN zK#zC8yg$=Zn$_9;s3iwC!vkmS?L>KJqWc!Po}8mCPjy2w?JeH1E`Q)3qvojhQN{wMONwXM8&N_&c!|k+^Z` zNgb~&j*yges=SPhJ|q(I(K@jJJ`@wf)Pu6xmmsF-#B!60*w$u_=!ik_Nt)TxaWN}) zKAFqp53qgHcE7g;7Hk85sUP>_MafL@S19hW{i{+Oy(hLu$`3^m=o1ROD6}+xNzpsqUZ#`B1I)y9BNtl&WIo!TW>AbSuwuJ!kiGErO9-StzefNKG%>=;I z85$URBl*uX>at#%*lw%wuAAbGx}3-90`GzT{*gNR5#v5C`>BJ-l-h4WXRgwhtWtWd zDEh-glLB3<0Y?N-Qip3B#|)kXXTDlXb?xsF1#2sBnh!6YeLTN-S^`c?i@6luB;~Io z6NJ5UNOaSr+s~Z{Ef9&h{yp%V=+N9ZJ|FJ+a+;mZN02-FBo#1XgD4NxD2hV_ZMV@6 ztzG6-?VDS7VZ&nAL^ne#BXF-8ciVRNA~mGUx^_icjxgAd_qKUFAS*|xB>bo`_SPPI z)BiPQBL6|g8Q7Ytz9pewZT6OD{GSjhgNvb|$By*DDPq#ZBqwhCp`l~TC^^90y0am* zU5D1$`}R9jG1v3Jc*XxvNfo~hy_ML)$I}{g5j?OCY+QxfYbVq9e^58!(4zZNSs73G z*r_&FBjMDOLQcw2{4#$eG}j@p1}=@45K;#!D>(66r>v%{D>gyDf1@|lgsJicd-3@0 zp2*V&RirE&bX@B~-HrCuz`QP=0{eI`@M~r3 z<+J;HiXp4q36GSP67zG_EjVPsf#-%vxzHQMGu;(bmGcHG%7@kjS^z+1#5lJrSAsMvs#|HjIalPTBq3^A|10{Hg{s^PG2 zW@)7LUZ|x<)^aP>HGfO$M03#ma@Woe9;vYL9j#o0$!W#+B*#j_p7Qa-oX+N65%a74 z0qDxV)sat6Qv2(2N(-HZHl6SmBg>|Q<^yNz zohq(Zw3u40=R%bdWQ`!at+jP&*>q0Ur$a<;>w}b5gp8D}QvR6v?GRcBhayOZCXe3U z{!Qlv3wsal1XKe^z%r)r0TOVKjw)NpfsG3f2#6i%W{+sYd!qrtf2E_MsaZK7hzyq0 zqb#&p?`rvfBUBc-ZtS`vvgTH%CXqo^<-E!2WvEhiv{{4fzfpjKVB?}Ae_TK=s%z}x zU^|p!lIq3Q8tA?@tvp98zlzXQdBrn>8%ksS$er!l)SbekSMq*<{E6}VX74C*hLP8L)TD$C-t8;W&*kJ|eO)TC0po8pMU~&%hd8E9b7YP36>aGh8 z8tKw1m3gYK-~NRHe?4@o7UN{KLt0JC4;clh)`dTuzl|6O4_D10)8bYpd(?p{+6N^D zhbn$7eN5eS;3hbBnC!$Y;6}~*+z_9T70J3L=KM%D)$X30B+gcX=L|I)tm;tUY~`Ev z>`UEuXce(>W>%ny{bJ+ZrC%jzmjatoYkSer@p#{W3Fg3?ZpY_BDEN^0?h^OOBFb+E z>j5Fa3mF{@+%U|2wTI

SUG(v? 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 C5;Ro+ delta 77568 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